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

Commit 61981ca7 authored by Jerry Chang's avatar Jerry Chang Committed by Gerrit Code Review
Browse files

Merge "Dump prereboot information before device reboot"

parents 5ff37df1 1f823470
Loading
Loading
Loading
Loading
+133 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.power;

import android.annotation.NonNull;
import android.content.Context;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

/**
 * Provides utils to dump/wipe pre-reboot information.
 */
final class PreRebootLogger {
    private static final String TAG = "PreRebootLogger";
    private static final String PREREBOOT_DIR = "prereboot";

    private static final String[] BUFFERS_TO_DUMP = {"system"};
    private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"};

    private static final Object sLock = new Object();

    /**
     * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if
     * enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise.
     */
    static void log(Context context) {
        log(context, getDumpDir());
    }

    @VisibleForTesting
    static void log(Context context, @NonNull File dumpDir) {
        if (Settings.Global.getInt(
                context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) {
            Slog.d(TAG, "Dumping pre-reboot information...");
            dump(dumpDir);
        } else {
            Slog.d(TAG, "Wiping pre-reboot information...");
            wipe(dumpDir);
        }
    }

    private static void dump(@NonNull File dumpDir) {
        synchronized (sLock) {
            for (String buffer : BUFFERS_TO_DUMP) {
                dumpLogsLocked(dumpDir, buffer);
            }
            for (String service : SERVICES_TO_DUMP) {
                dumpServiceLocked(dumpDir, service);
            }
        }
    }

    private static void wipe(@NonNull File dumpDir) {
        synchronized (sLock) {
            for (File file : dumpDir.listFiles()) {
                file.delete();
            }
        }
    }

    private static File getDumpDir() {
        final File dumpDir = new File(Environment.getDataMiscDirectory(), PREREBOOT_DIR);
        if (!dumpDir.exists() || !dumpDir.isDirectory()) {
            throw new UnsupportedOperationException("Pre-reboot dump directory not found");
        }
        return dumpDir;
    }

    @GuardedBy("sLock")
    private static void dumpLogsLocked(@NonNull File dumpDir, @NonNull String buffer) {
        try {
            final File dumpFile = new File(dumpDir, buffer);
            if (dumpFile.createNewFile()) {
                dumpFile.setWritable(true /* writable */, true /* ownerOnly */);
            } else {
                // Wipes dumped information in existing file before recording new information.
                new FileWriter(dumpFile, false).flush();
            }

            final String[] cmdline =
                    {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()};
            Runtime.getRuntime().exec(cmdline).waitFor();
        } catch (IOException | InterruptedException e) {
            Slog.d(TAG, "Dump system log buffer before reboot fail", e);
        }
    }

    @GuardedBy("sLock")
    private static void dumpServiceLocked(@NonNull File dumpDir, @NonNull String serviceName) {
        final IBinder binder = ServiceManager.checkService(serviceName);
        if (binder == null) {
            return;
        }

        try {
            final File dumpFile = new File(dumpDir, serviceName);
            final ParcelFileDescriptor fd = ParcelFileDescriptor.open(dumpFile,
                    ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_TRUNCATE
                            | ParcelFileDescriptor.MODE_WRITE_ONLY);
            binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class));
        } catch (FileNotFoundException | RemoteException e) {
            Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e);
        }
    }
}
+11 −1
Original line number Diff line number Diff line
@@ -44,11 +44,12 @@ import android.os.Vibrator;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.TimingsTraceLog;
import android.view.WindowManager;

import com.android.server.RescueParty;
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.PackageManagerService;
import com.android.server.statusbar.StatusBarManagerInternal;

@@ -444,6 +445,15 @@ public final class ShutdownThread extends Thread {
            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
        }

        shutdownTimingLog.traceBegin("DumpPreRebootInfo");
        try {
            Slog.i(TAG, "Logging pre-reboot information...");
            PreRebootLogger.log(mContext);
        } catch (Exception e) {
            Slog.e(TAG, "Failed to log pre-reboot information", e);
        }
        shutdownTimingLog.traceEnd(); // DumpPreRebootInfo

        metricStarted(METRIC_SEND_BROADCAST);
        shutdownTimingLog.traceBegin("SendShutdownBroadcast");
        Log.i(TAG, "Sending shutdown broadcast...");
+97 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.power;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.when;

import android.content.Context;
import android.provider.Settings;
import android.test.mock.MockContentResolver;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.util.test.FakeSettingsProvider;

import com.google.common.io.Files;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;

/**
 * Tests for {@link PreRebootLogger}
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PreRebootLoggerTest {
    @Mock Context mContext;
    private MockContentResolver mContentResolver;
    private File mDumpDir;

    @BeforeClass
    public static void setupOnce() {
        FakeSettingsProvider.clearSettingsProvider();
    }

    @AfterClass
    public static void tearDownOnce() {
        FakeSettingsProvider.clearSettingsProvider();
    }

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
        when(mContext.getContentResolver()).thenReturn(mContentResolver);
        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());

        mDumpDir = Files.createTempDir();
        mDumpDir.mkdir();
        mDumpDir.deleteOnExit();
    }

    @Test
    public void log_adbEnabled_dumpsInformationProperly() {
        Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);

        PreRebootLogger.log(mContext, mDumpDir);

        assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback");
    }

    @Test
    public void log_adbDisabled_wipesDumpedInformation() {
        Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
        PreRebootLogger.log(mContext, mDumpDir);
        Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0);

        PreRebootLogger.log(mContext, mDumpDir);

        assertThat(mDumpDir.listFiles()).isEmpty();
    }
}