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