Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9cf3f636 authored by Satoshi Niwa's avatar Satoshi Niwa
Browse files

Improve error reporting and command handling of `bu` command.

Redirects error messages to stderr in addition to Log.e.
This makes errors visible when running bu from adb shell,
which could previously fail silently. For example, an
unknown backup flag error is now printed to the console.

Additionally, moves the command validation to an earlier phase.
This ensures that running bu without a valid command
correctly prints the usage instructions, which was previously
prevented by userId check.

Test: `adb shell bu` shows usage.
Test: atest BackupEligibilityHostSideTest
Bug: 420527847
Flag: TEST_ONLY
Change-Id: Idb948ca2699a8feb0296d346923db8ea99e92727
parent d0dba9b0
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,4 +22,5 @@ java_binary {
    name: "bu",
    wrapper: "bu.sh",
    srcs: ["**/*.java"],
    libs: ["error_prone_annotations"],
}
+69 −51
Original line number Diff line number Diff line
@@ -27,11 +27,47 @@ import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Locale;

public final class Backup {
    static final String TAG = "bu";
    static final String USAGE = """
        WARNING: bu is deprecated and may be removed in a future release

        Usage: bu <command> [options]

        Commands:
          backup [-user USER_ID] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared]
                 [-all] [-system|-nosystem] [-widgets|-nowidgets]
                 [-compress|-nocompress] [-keyvalue|-nokeyvalue] [PACKAGE...]
            Write an archive of the device's data to stdout.
            The package list is optional if -all or -shared is supplied.
            Example: bu backup -all -apk > backup.ab

          restore [-user USER_ID]
            Restore device contents from stdin.
            Example: bu restore < backup.ab

        Global options:
          -user USER_ID: user ID for which to perform the operation (default - system user)

        Options for 'backup':
          -apk/-noapk: do/don't back up .apk files (default -noapk)
          -obb/-noobb: do/don't back up .obb files (default -noobb)
          -shared|-noshared: do/don't back up shared storage (default -noshared)
          -all: back up all installed applications
          -system|-nosystem: include system apps in -all (default -system)
          -widgets|-nowidgets: do/don't back up widget data (default: -nowidgets)
          -compress|-nocompress: enable/disable compression of the backup data (default: -compress)
          -keyvalue|-nokeyvalue: do/don't back up apps that are key/value based
                                 (default: -nokeyvalue)

        'restore' command does not take any package names or options other than -user.""";

    static String[] mArgs;
    int mNextArg;
@@ -46,38 +82,53 @@ public final class Backup {
        mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
    }

    @FormatMethod
    private static void logAndPrintError(@FormatString String logFormat, Object... args) {
        String message = String.format(Locale.ENGLISH, logFormat, args);
        Log.e(TAG, message);
        System.err.println("Error: " + message);
    }

    public static void main(String[] args) {
        try {
            new Backup().run(args);
        } catch (Exception e) {
            Log.e(TAG, "Error running backup/restore", e);
            logAndPrintError("Error running backup/restore: %s", e);
        }
        Log.d(TAG, "Finished.");
    }

    public void run(String[] args) {
        if (mBackupManager == null) {
            Log.e(TAG, "Can't obtain Backup Manager binder");
            logAndPrintError("Can't obtain Backup Manager binder");
            return;
        }

        Log.d(TAG, "Beginning: " + args[0]);
        mArgs = args;
        String firstArg = nextArg();

        boolean isBackupCommand;
        if ("backup".equals(firstArg)) {
            isBackupCommand = true;
        } else if ("restore".equals(firstArg)) {
            isBackupCommand = false;
        } else {
            System.err.println(USAGE);
            return;
        }

        int userId = parseUserId();
        if (!isBackupActiveForUser(userId)) {
            Log.e(TAG, "BackupManager is not available for user " + userId);
            logAndPrintError("BackupManager is not active for user %d", userId);
            return;
        }

        String arg = nextArg();
        if (arg.equals("backup")) {
        Log.i(TAG, "Beginning " + firstArg + " for user " + userId);
        if (isBackupCommand) {
            doBackup(OsConstants.STDOUT_FILENO, userId);
        } else if (arg.equals("restore")) {
            doRestore(OsConstants.STDIN_FILENO, userId);
        } else {
            showUsage();
            doRestore(OsConstants.STDIN_FILENO, userId);
        }
        Log.i(TAG, "Finished " + firstArg + " for user " + userId);
    }

    private void doBackup(int socketFd, @UserIdInt int userId) {
@@ -129,7 +180,8 @@ public final class Backup {
                    nextArg();
                    continue;
                } else {
                    Log.w(TAG, "Unknown backup flag " + arg);
                    // Log error and continue.
                    logAndPrintError("Unknown backup flag %s", arg);
                    continue;
                }
            } else {
@@ -139,11 +191,11 @@ public final class Backup {
        }

        if (doEverything && packages.size() > 0) {
            Log.w(TAG, "-all passed for backup along with specific package names");
            logAndPrintError("-all passed for backup along with specific package names");
        }

        if (!doEverything && !saveShared && packages.size() == 0) {
            Log.e(TAG, "no backup packages supplied and neither -shared nor -all given");
            logAndPrintError("no backup packages supplied and neither -shared nor -all given");
            return;
        }

@@ -154,13 +206,13 @@ public final class Backup {
            mBackupManager.adbBackup(userId, fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
                    allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to invoke backup manager for backup");
            logAndPrintError("Unable to invoke backup manager for backup (user %d)", userId);
        } finally {
            if (fd != null) {
                try {
                    fd.close();
                } catch (IOException e) {
                    Log.e(TAG, "IO error closing output for backup: " + e.getMessage());
                    logAndPrintError("IO error closing output for backup: %s", e);
                }
            }
        }
@@ -173,7 +225,7 @@ public final class Backup {
            fd = ParcelFileDescriptor.adoptFd(socketFd);
            mBackupManager.adbRestore(userId, fd);
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to invoke backup manager for restore");
            logAndPrintError("Unable to invoke backup manager for restore (user %d)", userId);
        } finally {
            if (fd != null) {
                try {
@@ -197,45 +249,11 @@ public final class Backup {
        try {
            return mBackupManager.isBackupServiceActive(userId);
        } catch (RemoteException e) {
            Log.e(TAG, "Could not access BackupManager: " + e.toString());
            logAndPrintError("Could not access BackupManager: %s", e);
            return false;
        }
    }

    private static void showUsage() {
        System.err.println("""
        Usage: bu <command> [options]

        Commands:
          backup [-user USER_ID] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared]
                 [-all] [-system|-nosystem] [-widgets|-nowidgets]
                 [-compress|-nocompress] [-keyvalue|-nokeyvalue] [PACKAGE...]
            Write an archive of the device's data to stdout.
            The package list is optional if -all or -shared is supplied.
            Example: bu backup -all -apk > backup.ab

          restore [-user USER_ID]
            Restore device contents from stdin.
            Example: bu restore < backup.ab

        Global options:
          -user USER_ID: user ID for which to perform the operation (default - system user)

        Options for 'backup':
          -apk/-noapk: do/don't back up .apk files (default -noapk)
          -obb/-noobb: do/don't back up .obb files (default -noobb)
          -shared|-noshared: do/don't back up shared storage (default -noshared)
          -all: back up all installed applications
          -system|-nosystem: include system apps in -all (default -system)
          -widgets|-nowidgets: do/don't back up widget data (default: -nowidgets)
          -compress|-nocompress: enable/disable compression of the backup data (default: -compress)
          -keyvalue|-nokeyvalue: do/don't back up apps that are key/value based
                                 (default: -nokeyvalue)

        'restore' command does not take any package names or options other than -user.
                """);
    }

    private String nextArg() {
        if (mNextArg >= mArgs.length) {
            return null;