/* * Copyright (C) 2018 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.internal.os; import android.annotation.Nullable; import android.os.StrictMode; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; /** * Utility functions for reading {@code proc} files */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) @android.ravenwood.annotation.RavenwoodKeepWholeClass public final class ProcStatsUtil { private static final boolean DEBUG = false; private static final String TAG = "ProcStatsUtil"; /** * How much to read into a buffer when reading a proc file */ private static final int READ_SIZE = 1024; /** * Class only contains static utility functions, and should not be instantiated */ private ProcStatsUtil() { } /** * Read a {@code proc} file where the contents are separated by null bytes. Replaces the null * bytes with spaces, and removes any trailing null bytes * * @param path path of the file to read */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) @Nullable public static String readNullSeparatedFile(String path) { String contents = readSingleLineProcFile(path); if (contents == null) { return null; } // Content is either double-null terminated, or terminates at end of line. Remove anything // after the double-null final int endIndex = contents.indexOf("\0\0"); if (endIndex != -1) { contents = contents.substring(0, endIndex); } // Change the null-separated contents into space-seperated return contents.replace("\0", " "); } /** * Read a {@code proc} file that contains a single line (e.g. {@code /proc/$PID/cmdline}, {@code * /proc/$PID/comm}) * * @param path path of the file to read */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) @Nullable public static String readSingleLineProcFile(String path) { return readTerminatedProcFile(path, (byte) '\n'); } /** * Read a {@code proc} file that terminates with a specific byte * * @param path path of the file to read * @param terminator byte that terminates the file. We stop reading once this character is * seen, or at the end of the file */ @Nullable public static String readTerminatedProcFile(String path, byte terminator) { // Permit disk reads here, as /proc isn't really "on disk" and should be fast. // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps? final int savedPolicy = StrictMode.allowThreadDiskReadsMask(); try { return readTerminatedProcFileInternal(path, terminator); } finally { StrictMode.setThreadPolicyMask(savedPolicy); } } private static String readTerminatedProcFileInternal(String path, byte terminator) { try (FileInputStream is = new FileInputStream(path)) { ByteArrayOutputStream byteStream = null; final byte[] buffer = new byte[READ_SIZE]; while (true) { // Read file into buffer final int len = is.read(buffer); if (len <= 0) { // If we've read nothing, we're done break; } // Find the terminating character int terminatingIndex = -1; for (int i = 0; i < len; i++) { if (buffer[i] == terminator) { terminatingIndex = i; break; } } final boolean foundTerminator = terminatingIndex != -1; // If we have found it and the byte stream isn't initialized, we don't need to // initialize it and can return the string here if (foundTerminator && byteStream == null) { return new String(buffer, 0, terminatingIndex); } // Initialize the byte stream if (byteStream == null) { byteStream = new ByteArrayOutputStream(READ_SIZE); } // Write the whole buffer if terminator not found, or up to the terminator if found byteStream.write(buffer, 0, foundTerminator ? terminatingIndex : len); // If we've found the terminator, we can finish if (foundTerminator) { break; } } // If the byte stream is null at the end, this means that we have read an empty file if (byteStream == null) { return ""; } return byteStream.toString(); } catch (IOException e) { if (DEBUG) { Slog.d(TAG, "Failed to open proc file", e); } return null; } } }