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