1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.commands.bmgr;
18
19import android.app.backup.BackupManager;
20import android.app.backup.BackupManagerMonitor;
21import android.app.backup.BackupProgress;
22import android.app.backup.IBackupManager;
23import android.app.backup.IBackupObserver;
24import android.app.backup.IRestoreObserver;
25import android.app.backup.IRestoreSession;
26import android.app.backup.RestoreSet;
27import android.app.backup.ISelectBackupTransportCallback;
28import android.content.ComponentName;
29import android.content.pm.IPackageManager;
30import android.content.pm.PackageInfo;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.UserHandle;
34import android.util.Log;
35
36import java.util.ArrayList;
37import java.util.HashSet;
38import java.util.List;
39import java.util.concurrent.CountDownLatch;
40
41public final class Bmgr {
42    IBackupManager mBmgr;
43    IRestoreSession mRestore;
44
45    static final String BMGR_NOT_RUNNING_ERR =
46            "Error: Could not access the Backup Manager.  Is the system running?";
47    static final String TRANSPORT_NOT_RUNNING_ERR =
48            "Error: Could not access the backup transport.  Is the system running?";
49    static final String PM_NOT_RUNNING_ERR =
50            "Error: Could not access the Package Manager.  Is the system running?";
51
52    private String[] mArgs;
53    private int mNextArg;
54
55    public static void main(String[] args) {
56        try {
57            new Bmgr().run(args);
58        } catch (Exception e) {
59            System.err.println("Exception caught:");
60            e.printStackTrace();
61        }
62    }
63
64    public void run(String[] args) {
65        if (args.length < 1) {
66            showUsage();
67            return;
68        }
69
70        mBmgr = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
71        if (mBmgr == null) {
72            System.err.println(BMGR_NOT_RUNNING_ERR);
73            return;
74        }
75
76        mArgs = args;
77        String op = args[0];
78        mNextArg = 1;
79
80        if ("enabled".equals(op)) {
81            doEnabled();
82            return;
83        }
84
85        if ("enable".equals(op)) {
86            doEnable();
87            return;
88        }
89
90        if ("run".equals(op)) {
91            doRun();
92            return;
93        }
94
95        if ("backup".equals(op)) {
96            doBackup();
97            return;
98        }
99
100        if ("list".equals(op)) {
101            doList();
102            return;
103        }
104
105        if ("restore".equals(op)) {
106            doRestore();
107            return;
108        }
109
110        if ("transport".equals(op)) {
111            doTransport();
112            return;
113        }
114
115        if ("wipe".equals(op)) {
116            doWipe();
117            return;
118        }
119
120        if ("fullbackup".equals(op)) {
121            doFullTransportBackup();
122            return;
123        }
124
125        if ("backupnow".equals(op)) {
126            doBackupNow();
127            return;
128        }
129
130        if ("cancel".equals(op)) {
131            doCancel();
132            return;
133        }
134
135        if ("whitelist".equals(op)) {
136            doPrintWhitelist();
137            return;
138        }
139
140        System.err.println("Unknown command");
141        showUsage();
142    }
143
144    private String enableToString(boolean enabled) {
145        return enabled ? "enabled" : "disabled";
146    }
147
148    private void doEnabled() {
149        try {
150            boolean isEnabled = mBmgr.isBackupEnabled();
151            System.out.println("Backup Manager currently "
152                    + enableToString(isEnabled));
153        } catch (RemoteException e) {
154            System.err.println(e.toString());
155            System.err.println(BMGR_NOT_RUNNING_ERR);
156        }
157    }
158
159    private void doEnable() {
160        String arg = nextArg();
161        if (arg == null) {
162            showUsage();
163            return;
164        }
165
166        try {
167            boolean enable = Boolean.parseBoolean(arg);
168            mBmgr.setBackupEnabled(enable);
169            System.out.println("Backup Manager now " + enableToString(enable));
170        } catch (NumberFormatException e) {
171            showUsage();
172            return;
173        } catch (RemoteException e) {
174            System.err.println(e.toString());
175            System.err.println(BMGR_NOT_RUNNING_ERR);
176        }
177    }
178
179    private void doRun() {
180        try {
181            mBmgr.backupNow();
182        } catch (RemoteException e) {
183            System.err.println(e.toString());
184            System.err.println(BMGR_NOT_RUNNING_ERR);
185        }
186    }
187
188    private void doBackup() {
189        String pkg = nextArg();
190        if (pkg == null) {
191            showUsage();
192            return;
193        }
194
195        try {
196            mBmgr.dataChanged(pkg);
197        } catch (RemoteException e) {
198            System.err.println(e.toString());
199            System.err.println(BMGR_NOT_RUNNING_ERR);
200        }
201    }
202
203    private void doFullTransportBackup() {
204        System.out.println("Performing full transport backup");
205
206        String pkg;
207        ArrayList<String> allPkgs = new ArrayList<String>();
208        while ((pkg = nextArg()) != null) {
209            allPkgs.add(pkg);
210        }
211        if (allPkgs.size() > 0) {
212            try {
213                mBmgr.fullTransportBackup(allPkgs.toArray(new String[allPkgs.size()]));
214            } catch (RemoteException e) {
215                System.err.println(e.toString());
216                System.err.println(BMGR_NOT_RUNNING_ERR);
217            }
218        }
219    }
220
221    class BackupObserver extends IBackupObserver.Stub {
222        boolean done = false;
223
224        @Override
225        public void onUpdate(String currentPackage, BackupProgress backupProgress) {
226            System.out.println(
227                "Package " + currentPackage + " with progress: " + backupProgress.bytesTransferred
228                    + "/" + backupProgress.bytesExpected);
229        }
230
231        @Override
232        public void onResult(String currentPackage, int status) {
233            System.out.println("Package " + currentPackage + " with result: "
234                    + convertBackupStatusToString(status));
235        }
236
237        @Override
238        public void backupFinished(int status) {
239            System.out.println("Backup finished with result: "
240                    + convertBackupStatusToString(status));
241            synchronized (this) {
242                done = true;
243                this.notify();
244            }
245        }
246
247        public void waitForCompletion() {
248            // The backupFinished() callback will throw the 'done' flag; we
249            // just sit and wait on that notification.
250            synchronized (this) {
251                while (!this.done) {
252                    try {
253                        this.wait();
254                    } catch (InterruptedException ex) {
255                    }
256                }
257            }
258        }
259
260    }
261
262    private static String convertBackupStatusToString(int errorCode) {
263        switch (errorCode) {
264            case BackupManager.SUCCESS:
265                return "Success";
266            case BackupManager.ERROR_BACKUP_NOT_ALLOWED:
267                return "Backup is not allowed";
268            case BackupManager.ERROR_PACKAGE_NOT_FOUND:
269                return "Package not found";
270            case BackupManager.ERROR_TRANSPORT_ABORTED:
271                return "Transport error";
272            case BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED:
273                return "Transport rejected package";
274            case BackupManager.ERROR_AGENT_FAILURE:
275                return "Agent error";
276            case BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED:
277                return "Size quota exceeded";
278            case BackupManager.ERROR_BACKUP_CANCELLED:
279                return "Backup Cancelled";
280            default:
281                return "Unknown error";
282        }
283    }
284
285    private void backupNowAllPackages(boolean nonIncrementalBackup) {
286        int userId = UserHandle.USER_SYSTEM;
287        IPackageManager mPm =
288                IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
289        if (mPm == null) {
290            System.err.println(PM_NOT_RUNNING_ERR);
291            return;
292        }
293        List<PackageInfo> installedPackages = null;
294        try {
295            installedPackages =  mPm.getInstalledPackages(0, userId).getList();
296        } catch (RemoteException e) {
297            System.err.println(e.toString());
298            System.err.println(PM_NOT_RUNNING_ERR);
299        }
300        if (installedPackages != null) {
301            List<String> packages = new ArrayList<>();
302            for (PackageInfo pi : installedPackages) {
303                try {
304                    if (mBmgr.isAppEligibleForBackup(pi.packageName)) {
305                        packages.add(pi.packageName);
306                    }
307                } catch (RemoteException e) {
308                    System.err.println(e.toString());
309                    System.err.println(BMGR_NOT_RUNNING_ERR);
310                }
311            }
312            backupNowPackages(packages, nonIncrementalBackup);
313        }
314    }
315
316    private void backupNowPackages(List<String> packages, boolean nonIncrementalBackup) {
317        int flags = 0;
318        if (nonIncrementalBackup) {
319            flags |= BackupManager.FLAG_NON_INCREMENTAL_BACKUP;
320        }
321        try {
322            BackupObserver observer = new BackupObserver();
323            // TODO: implement monitor here?
324            int err = mBmgr.requestBackup(packages.toArray(new String[packages.size()]), observer,
325                    null, flags);
326            if (err == 0) {
327                // Off and running -- wait for the backup to complete
328                observer.waitForCompletion();
329            } else {
330                System.err.println("Unable to run backup");
331            }
332        } catch (RemoteException e) {
333            System.err.println(e.toString());
334            System.err.println(BMGR_NOT_RUNNING_ERR);
335        }
336    }
337
338    private void doBackupNow() {
339        String pkg;
340        boolean backupAll = false;
341        boolean nonIncrementalBackup = false;
342        ArrayList<String> allPkgs = new ArrayList<String>();
343        while ((pkg = nextArg()) != null) {
344            if (pkg.equals("--all")) {
345                backupAll = true;
346            } else if (pkg.equals("--non-incremental")) {
347                nonIncrementalBackup = true;
348            } else if (pkg.equals("--incremental")) {
349                nonIncrementalBackup = false;
350            } else {
351                allPkgs.add(pkg);
352            }
353        }
354        if (backupAll) {
355            if (allPkgs.size() == 0) {
356                System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
357                        "incremental backup for all packages.");
358                backupNowAllPackages(nonIncrementalBackup);
359            } else {
360                System.err.println("Provide only '--all' flag or list of packages.");
361            }
362        } else if (allPkgs.size() > 0) {
363            System.out.println("Running " + (nonIncrementalBackup ? "non-" : "") +
364                    "incremental backup for " + allPkgs.size() +" requested packages.");
365            backupNowPackages(allPkgs, nonIncrementalBackup);
366        } else {
367            System.err.println("Provide '--all' flag or list of packages.");
368        }
369    }
370
371    private void doCancel() {
372        String arg = nextArg();
373        if ("backups".equals(arg)) {
374            try {
375                mBmgr.cancelBackups();
376            } catch (RemoteException e) {
377                System.err.println(e.toString());
378                System.err.println(BMGR_NOT_RUNNING_ERR);
379            }
380            return;
381        }
382
383        System.err.println("Unknown command.");
384    }
385
386    private void doTransport() {
387        try {
388            String which = nextArg();
389            if (which == null) {
390                showUsage();
391                return;
392            }
393
394            if ("-c".equals(which)) {
395                doTransportByComponent();
396                return;
397            }
398
399            String old = mBmgr.selectBackupTransport(which);
400            if (old == null) {
401                System.out.println("Unknown transport '" + which
402                        + "' specified; no changes made.");
403            } else {
404                System.out.println("Selected transport " + which + " (formerly " + old + ")");
405            }
406
407        } catch (RemoteException e) {
408            System.err.println(e.toString());
409            System.err.println(BMGR_NOT_RUNNING_ERR);
410        }
411    }
412
413    private void doTransportByComponent() {
414        String which = nextArg();
415        if (which == null) {
416            showUsage();
417            return;
418        }
419
420        final CountDownLatch latch = new CountDownLatch(1);
421
422        try {
423            mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which),
424                    new ISelectBackupTransportCallback.Stub() {
425                        @Override
426                        public void onSuccess(String transportName) {
427                            System.out.println("Success. Selected transport: " + transportName);
428                            latch.countDown();
429                        }
430
431                        @Override
432                        public void onFailure(int reason) {
433                            System.err.println("Failure. error=" + reason);
434                            latch.countDown();
435                        }
436                    });
437        } catch (RemoteException e) {
438            System.err.println(e.toString());
439            System.err.println(BMGR_NOT_RUNNING_ERR);
440            return;
441        }
442
443        try {
444            latch.await();
445        } catch (InterruptedException e) {
446            System.err.println("Operation interrupted.");
447        }
448    }
449
450    private void doWipe() {
451        String transport = nextArg();
452        if (transport == null) {
453            showUsage();
454            return;
455        }
456
457        String pkg = nextArg();
458        if (pkg == null) {
459            showUsage();
460            return;
461        }
462
463        try {
464            mBmgr.clearBackupData(transport, pkg);
465            System.out.println("Wiped backup data for " + pkg + " on " + transport);
466        } catch (RemoteException e) {
467            System.err.println(e.toString());
468            System.err.println(BMGR_NOT_RUNNING_ERR);
469        }
470    }
471
472    private void doList() {
473        String arg = nextArg();     // sets, transports, packages set#
474        if ("transports".equals(arg)) {
475            doListTransports();
476            return;
477        }
478
479        // The rest of the 'list' options work with a restore session on the current transport
480        try {
481            mRestore = mBmgr.beginRestoreSession(null, null);
482            if (mRestore == null) {
483                System.err.println(BMGR_NOT_RUNNING_ERR);
484                return;
485            }
486
487            if ("sets".equals(arg)) {
488                doListRestoreSets();
489            } else if ("transports".equals(arg)) {
490                doListTransports();
491            }
492
493            mRestore.endRestoreSession();
494        } catch (RemoteException e) {
495            System.err.println(e.toString());
496            System.err.println(BMGR_NOT_RUNNING_ERR);
497        }
498    }
499
500    private void doListTransports() {
501        String arg = nextArg();
502
503        try {
504            if ("-c".equals(arg)) {
505                for (ComponentName transport : mBmgr.listAllTransportComponents()) {
506                    System.out.println(transport.flattenToShortString());
507                }
508                return;
509            }
510
511            String current = mBmgr.getCurrentTransport();
512            String[] transports = mBmgr.listAllTransports();
513            if (transports == null || transports.length == 0) {
514                System.out.println("No transports available.");
515                return;
516            }
517
518            for (String t : transports) {
519                String pad = (t.equals(current)) ? "  * " : "    ";
520                System.out.println(pad + t);
521            }
522        } catch (RemoteException e) {
523            System.err.println(e.toString());
524            System.err.println(BMGR_NOT_RUNNING_ERR);
525        }
526    }
527
528    private void doListRestoreSets() {
529        try {
530            RestoreObserver observer = new RestoreObserver();
531            // TODO implement monitor here
532            int err = mRestore.getAvailableRestoreSets(observer, null);
533            if (err != 0) {
534                System.out.println("Unable to request restore sets");
535            } else {
536                observer.waitForCompletion();
537                printRestoreSets(observer.sets);
538            }
539        } catch (RemoteException e) {
540            System.err.println(e.toString());
541            System.err.println(TRANSPORT_NOT_RUNNING_ERR);
542        }
543    }
544
545    private void printRestoreSets(RestoreSet[] sets) {
546        if (sets == null || sets.length == 0) {
547            System.out.println("No restore sets");
548            return;
549        }
550        for (RestoreSet s : sets) {
551            System.out.println("  " + Long.toHexString(s.token) + " : " + s.name);
552        }
553    }
554
555    class RestoreObserver extends IRestoreObserver.Stub {
556        boolean done;
557        RestoreSet[] sets = null;
558
559        public void restoreSetsAvailable(RestoreSet[] result) {
560            synchronized (this) {
561                sets = result;
562                done = true;
563                this.notify();
564            }
565        }
566
567        public void restoreStarting(int numPackages) {
568            System.out.println("restoreStarting: " + numPackages + " packages");
569        }
570
571        public void onUpdate(int nowBeingRestored, String currentPackage) {
572            System.out.println("onUpdate: " + nowBeingRestored + " = " + currentPackage);
573        }
574
575        public void restoreFinished(int error) {
576            System.out.println("restoreFinished: " + error);
577            synchronized (this) {
578                done = true;
579                this.notify();
580            }
581        }
582
583        /**
584         * Wait until either {@link #restoreFinished} or {@link #restoreStarting} is called.
585         * Once one is called, it clears the internal flag again, so that the same observer intance
586         * can be reused for a next operation.
587         */
588        public void waitForCompletion() {
589            // The restoreFinished() callback will throw the 'done' flag; we
590            // just sit and wait on that notification.
591            synchronized (this) {
592                while (!this.done) {
593                    try {
594                        this.wait();
595                    } catch (InterruptedException ex) {
596                    }
597                }
598                done = false;
599            }
600        }
601    }
602
603    private void doRestore() {
604        String arg = nextArg();
605        if (arg == null) {
606            showUsage();
607            return;
608        }
609
610        if (arg.indexOf('.') >= 0 || arg.equals("android")) {
611            // it's a package name
612            doRestorePackage(arg);
613        } else {
614            try {
615                long token = Long.parseLong(arg, 16);
616                HashSet<String> filter = null;
617                while ((arg = nextArg()) != null) {
618                    if (filter == null) filter = new HashSet<String>();
619                    filter.add(arg);
620                }
621
622                doRestoreAll(token, filter);
623            } catch (NumberFormatException e) {
624                showUsage();
625                return;
626            }
627        }
628
629        System.out.println("done");
630    }
631
632    private void doRestorePackage(String pkg) {
633        try {
634            mRestore = mBmgr.beginRestoreSession(pkg, null);
635            if (mRestore == null) {
636                System.err.println(BMGR_NOT_RUNNING_ERR);
637                return;
638            }
639
640            RestoreObserver observer = new RestoreObserver();
641            // TODO implement monitor here
642            int err = mRestore.restorePackage(pkg, observer, null );
643            if (err == 0) {
644                // Off and running -- wait for the restore to complete
645                observer.waitForCompletion();
646            } else {
647                System.err.println("Unable to restore package " + pkg);
648            }
649
650            // And finally shut down the session
651            mRestore.endRestoreSession();
652        } catch (RemoteException e) {
653            System.err.println(e.toString());
654            System.err.println(BMGR_NOT_RUNNING_ERR);
655        }
656    }
657
658    private void doRestoreAll(long token, HashSet<String> filter) {
659        RestoreObserver observer = new RestoreObserver();
660
661        try {
662            boolean didRestore = false;
663            mRestore = mBmgr.beginRestoreSession(null, null);
664            if (mRestore == null) {
665                System.err.println(BMGR_NOT_RUNNING_ERR);
666                return;
667            }
668            RestoreSet[] sets = null;
669            // TODO implement monitor here
670            int err = mRestore.getAvailableRestoreSets(observer, null);
671            if (err == 0) {
672                observer.waitForCompletion();
673                sets = observer.sets;
674                if (sets != null) {
675                    for (RestoreSet s : sets) {
676                        if (s.token == token) {
677                            System.out.println("Scheduling restore: " + s.name);
678                            if (filter == null) {
679                                didRestore = (mRestore.restoreAll(token, observer, null) == 0);
680                            } else {
681                                String[] names = new String[filter.size()];
682                                filter.toArray(names);
683                                didRestore = (mRestore.restoreSome(token, observer,
684                                        null, names) == 0);
685                            }
686                            break;
687                        }
688                    }
689                }
690            }
691            if (!didRestore) {
692                if (sets == null || sets.length == 0) {
693                    System.out.println("No available restore sets; no restore performed");
694                } else {
695                    System.out.println("No matching restore set token.  Available sets:");
696                    printRestoreSets(sets);
697                }
698            }
699
700            // if we kicked off a restore successfully, we have to wait for it
701            // to complete before we can shut down the restore session safely
702            if (didRestore) {
703                observer.waitForCompletion();
704            }
705
706            // once the restore has finished, close down the session and we're done
707            mRestore.endRestoreSession();
708        } catch (RemoteException e) {
709            System.err.println(e.toString());
710            System.err.println(BMGR_NOT_RUNNING_ERR);
711        }
712    }
713
714    private void doPrintWhitelist() {
715        try {
716            final String[] whitelist = mBmgr.getTransportWhitelist();
717            if (whitelist != null) {
718                for (String transport : whitelist) {
719                    System.out.println(transport);
720                }
721            }
722        } catch (RemoteException e) {
723            System.err.println(e.toString());
724            System.err.println(BMGR_NOT_RUNNING_ERR);
725        }
726    }
727
728    private String nextArg() {
729        if (mNextArg >= mArgs.length) {
730            return null;
731        }
732        String arg = mArgs[mNextArg];
733        mNextArg++;
734        return arg;
735    }
736
737    private static void showUsage() {
738        System.err.println("usage: bmgr [backup|restore|list|transport|run]");
739        System.err.println("       bmgr backup PACKAGE");
740        System.err.println("       bmgr enable BOOL");
741        System.err.println("       bmgr enabled");
742        System.err.println("       bmgr list transports [-c]");
743        System.err.println("       bmgr list sets");
744        System.err.println("       bmgr transport WHICH|-c WHICH_COMPONENT");
745        System.err.println("       bmgr restore TOKEN");
746        System.err.println("       bmgr restore TOKEN PACKAGE...");
747        System.err.println("       bmgr restore PACKAGE");
748        System.err.println("       bmgr run");
749        System.err.println("       bmgr wipe TRANSPORT PACKAGE");
750        System.err.println("       bmgr fullbackup PACKAGE...");
751        System.err.println("       bmgr backupnow --all|PACKAGE...");
752        System.err.println("       bmgr cancel backups");
753        System.err.println("");
754        System.err.println("The 'backup' command schedules a backup pass for the named package.");
755        System.err.println("Note that the backup pass will effectively be a no-op if the package");
756        System.err.println("does not actually have changed data to store.");
757        System.err.println("");
758        System.err.println("The 'enable' command enables or disables the entire backup mechanism.");
759        System.err.println("If the argument is 'true' it will be enabled, otherwise it will be");
760        System.err.println("disabled.  When disabled, neither backup or restore operations will");
761        System.err.println("be performed.");
762        System.err.println("");
763        System.err.println("The 'enabled' command reports the current enabled/disabled state of");
764        System.err.println("the backup mechanism.");
765        System.err.println("");
766        System.err.println("The 'list transports' command reports the names of the backup transports");
767        System.err.println("BackupManager is currently bound to. These names can be passed as arguments");
768        System.err.println("to the 'transport' and 'wipe' commands.  The currently active transport");
769        System.err.println("is indicated with a '*' character. If -c flag is used, all available");
770        System.err.println("transport components on the device are listed. These can be used with");
771        System.err.println("the component variant of 'transport' command.");
772        System.err.println("");
773        System.err.println("The 'list sets' command reports the token and name of each restore set");
774        System.err.println("available to the device via the currently active transport.");
775        System.err.println("");
776        System.err.println("The 'transport' command designates the named transport as the currently");
777        System.err.println("active one.  This setting is persistent across reboots. If -c flag is");
778        System.err.println("specified, the following string is treated as a component name.");
779        System.err.println("");
780        System.err.println("The 'restore' command when given just a restore token initiates a full-system");
781        System.err.println("restore operation from the currently active transport.  It will deliver");
782        System.err.println("the restore set designated by the TOKEN argument to each application");
783        System.err.println("that had contributed data to that restore set.");
784        System.err.println("");
785        System.err.println("The 'restore' command when given a token and one or more package names");
786        System.err.println("initiates a restore operation of just those given packages from the restore");
787        System.err.println("set designated by the TOKEN argument.  It is effectively the same as the");
788        System.err.println("'restore' operation supplying only a token, but applies a filter to the");
789        System.err.println("set of applications to be restored.");
790        System.err.println("");
791        System.err.println("The 'restore' command when given just a package name intiates a restore of");
792        System.err.println("just that one package according to the restore set selection algorithm");
793        System.err.println("used by the RestoreSession.restorePackage() method.");
794        System.err.println("");
795        System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
796        System.err.println("immediately, without the usual waiting period for batching together");
797        System.err.println("data changes.");
798        System.err.println("");
799        System.err.println("The 'wipe' command causes all backed-up data for the given package to be");
800        System.err.println("erased from the given transport's storage.  The next backup operation");
801        System.err.println("that the given application performs will rewrite its entire data set.");
802        System.err.println("Transport names to use here are those reported by 'list transports'.");
803        System.err.println("");
804        System.err.println("The 'fullbackup' command induces a full-data stream backup for one or more");
805        System.err.println("packages.  The data is sent via the currently active transport.");
806        System.err.println("");
807        System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
808        System.err.println("    --all flag runs backup for all eligible packages.");
809        System.err.println("For each package it will run key/value or full data backup ");
810        System.err.println("depending on the package's manifest declarations.");
811        System.err.println("The data is sent via the currently active transport.");
812        System.err.println("The 'cancel backups' command cancels all running backups.");
813    }
814}
815