1 /* 2 * Copyright (C) 2018 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 17 package com.android.internal.os; 18 19 import android.annotation.Nullable; 20 import android.os.StrictMode; 21 import android.util.Slog; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.io.ByteArrayOutputStream; 26 import java.io.FileInputStream; 27 import java.io.IOException; 28 29 /** 30 * Utility functions for reading {@code proc} files 31 */ 32 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) 33 public final class ProcStatsUtil { 34 35 private static final boolean DEBUG = false; 36 37 private static final String TAG = "ProcStatsUtil"; 38 39 /** 40 * How much to read into a buffer when reading a proc file 41 */ 42 private static final int READ_SIZE = 1024; 43 44 /** 45 * Class only contains static utility functions, and should not be instantiated 46 */ ProcStatsUtil()47 private ProcStatsUtil() { 48 } 49 50 /** 51 * Read a {@code proc} file where the contents are separated by null bytes. Replaces the null 52 * bytes with spaces, and removes any trailing null bytes 53 * 54 * @param path path of the file to read 55 */ 56 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) 57 @Nullable readNullSeparatedFile(String path)58 public static String readNullSeparatedFile(String path) { 59 String contents = readSingleLineProcFile(path); 60 if (contents == null) { 61 return null; 62 } 63 64 // Content is either double-null terminated, or terminates at end of line. Remove anything 65 // after the double-null 66 final int endIndex = contents.indexOf("\0\0"); 67 if (endIndex != -1) { 68 contents = contents.substring(0, endIndex); 69 } 70 71 // Change the null-separated contents into space-seperated 72 return contents.replace("\0", " "); 73 } 74 75 /** 76 * Read a {@code proc} file that contains a single line (e.g. {@code /proc/$PID/cmdline}, {@code 77 * /proc/$PID/comm}) 78 * 79 * @param path path of the file to read 80 */ 81 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) 82 @Nullable readSingleLineProcFile(String path)83 public static String readSingleLineProcFile(String path) { 84 return readTerminatedProcFile(path, (byte) '\n'); 85 } 86 87 /** 88 * Read a {@code proc} file that terminates with a specific byte 89 * 90 * @param path path of the file to read 91 * @param terminator byte that terminates the file. We stop reading once this character is 92 * seen, or at the end of the file 93 */ 94 @Nullable readTerminatedProcFile(String path, byte terminator)95 public static String readTerminatedProcFile(String path, byte terminator) { 96 // Permit disk reads here, as /proc isn't really "on disk" and should be fast. 97 // TODO: make BlockGuard ignore /proc/ and /sys/ files perhaps? 98 final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads(); 99 try (FileInputStream is = new FileInputStream(path)) { 100 ByteArrayOutputStream byteStream = null; 101 final byte[] buffer = new byte[READ_SIZE]; 102 while (true) { 103 // Read file into buffer 104 final int len = is.read(buffer); 105 if (len <= 0) { 106 // If we've read nothing, we're done 107 break; 108 } 109 110 // Find the terminating character 111 int terminatingIndex = -1; 112 for (int i = 0; i < len; i++) { 113 if (buffer[i] == terminator) { 114 terminatingIndex = i; 115 break; 116 } 117 } 118 final boolean foundTerminator = terminatingIndex != -1; 119 120 // If we have found it and the byte stream isn't initialized, we don't need to 121 // initialize it and can return the string here 122 if (foundTerminator && byteStream == null) { 123 return new String(buffer, 0, terminatingIndex); 124 } 125 126 // Initialize the byte stream 127 if (byteStream == null) { 128 byteStream = new ByteArrayOutputStream(READ_SIZE); 129 } 130 131 // Write the whole buffer if terminator not found, or up to the terminator if found 132 byteStream.write(buffer, 0, foundTerminator ? terminatingIndex : len); 133 134 // If we've found the terminator, we can finish 135 if (foundTerminator) { 136 break; 137 } 138 } 139 140 // If the byte stream is null at the end, this means that we have read an empty file 141 if (byteStream == null) { 142 return ""; 143 } 144 return byteStream.toString(); 145 } catch (IOException e) { 146 if (DEBUG) { 147 Slog.d(TAG, "Failed to open proc file", e); 148 } 149 return null; 150 } finally { 151 StrictMode.setThreadPolicy(savedPolicy); 152 } 153 } 154 } 155