content: /*
   * 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)
  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 StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
          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;
          } finally {
              StrictMode.setThreadPolicy(savedPolicy);
          }
      }
  }
  