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 androidx.test.internal.util;
  
  import static androidx.test.internal.util.Checks.checkNotNull;
  
  import androidx.annotation.VisibleForTesting;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.IOException;
  import java.io.InputStreamReader;
  
  /**
   * Represents summarized process information.
   *
   * @hide
   */
  public final class ProcSummary {
    public final String name;
    public final String pid;
    public final String parent;
    public final String realUid;
    public final String cmdline;
    public final long startTime;
  
    private ProcSummary(Builder b) {
      this.name = checkNotNull(b.name);
      this.pid = checkNotNull(b.pid);
      this.realUid = checkNotNull(b.realUid);
      this.parent = checkNotNull(b.parent);
      this.cmdline = checkNotNull(b.cmdline);
      this.startTime = b.startTime;
    }
  
    /**
     * Uses data in /proc to summarize a process.
     *
     * @param pid the process directory name under /proc
     * @return a ProcSummary
     * @throws SummaryException if a summary can't be generated.
     */
    public static ProcSummary summarize(String pid) {
      // /proc/$PID/stat has pid, command name, parent, start time
      File statFile = new File(new File("/proc", pid), "stat");
      String statContent = readToString(statFile);
  
      // /proc/$PID/status has Uid
      File statusFile = new File(new File("/proc", pid), "status");
      String statusContent = readToString(statusFile);
  
      // /proc/$PID/cmdline has the packagename.
      File cmdlineFile = new File(new File("/proc", pid), "cmdline");
      String cmdline = readToString(cmdlineFile);
  
      return parse(statContent, statusContent, cmdline);
    }
  
    private static final String readToString(File path) {
      StringBuilder sb = new StringBuilder();
      char[] buff = new char[1024];
      InputStreamReader isr = null;
      try {
        isr = new InputStreamReader(new FileInputStream(path));
        int read = 0;
  
        while ((read = isr.read(buff, 0, buff.length)) != -1) {
          sb.append(buff, 0, read);
        }
        return sb.toString();
      } catch (RuntimeException re) {
        throw new SummaryException("Error reading: " + path, re);
      } catch (IOException ioe) {
        throw new SummaryException("Could not read: " + path, ioe);
      } finally {
        if (null != isr) {
          try {
            isr.close();
          } catch (IOException ignored) {
            // ignore.
          }
        }
      }
    }
  
    /** Thrown when we just cant figure out a process :) */
    public static class SummaryException extends RuntimeException {
      public SummaryException(String msg, Throwable cause) {
        super(msg, cause);
      }
  
      public SummaryException(String msg) {
        super(msg);
      }
    }
  
    @VisibleForTesting
    static ProcSummary parse(String statLine, String statusContent, String cmdline) {
      // See man proc(5)
      String[] stats = statLine.substring(statLine.lastIndexOf(')') + 2).split(" ", -1);
  
      statusContent = statusContent.substring(statusContent.indexOf("\nUid:") + 1);
      statusContent = statusContent.substring(0, statusContent.indexOf('\n'));
      String[] uids = statusContent.split("\\s", -1);
  
      ProcSummary.Builder b =
          new ProcSummary.Builder()
              .withPid(statLine.substring(0, statLine.indexOf(' ')))
              .withName(statLine.substring(statLine.indexOf('(') + 1, statLine.lastIndexOf(')')))
              .withParent(stats[1])
              .withRealUid(uids[1])
              .withCmdline(cmdline.trim().replace('\0', ' '))
              .withStartTime(Long.parseLong(stats[19]));
      return b.build();
    }
  
    @VisibleForTesting
    static class Builder {
      private String name;
      private String pid;
      private String realUid;
      private String cmdline;
      private long startTime;
      private String parent;
  
      Builder withParent(String ppid) {
        try {
          Integer.parseInt(ppid);
        } catch (NumberFormatException nfe) {
          throw new IllegalArgumentException("not a pid: " + ppid);
        }
        this.parent = ppid;
        return this;
      }
  
      Builder withCmdline(String cmdline) {
        this.cmdline = cmdline;
        return this;
      }
  
      Builder withName(String name) {
        this.name = name;
        return this;
      }
  
      Builder withPid(String pid) {
        try {
          Integer.parseInt(pid);
        } catch (NumberFormatException nfe) {
          throw new IllegalArgumentException("not a pid: " + pid);
        }
        this.pid = pid;
        return this;
      }
  
      Builder withRealUid(String uid) {
        try {
          Integer.parseInt(uid);
        } catch (NumberFormatException nfe) {
          throw new IllegalArgumentException("not a uid: " + uid);
        }
        this.realUid = uid;
        return this;
      }
  
      Builder withStartTime(long startTime) {
        this.startTime = startTime;
        return this;
      }
  
      ProcSummary build() {
        return new ProcSummary(this);
      }
    }
  
    @Override
    public String toString() {
      return String.format(
          "ProcSummary("
              + "name: '%s', "
              + "cmdline: '%s', "
              + "pid: '%s', "
              + "parent: '%s', "
              + "realUid: '%s', "
              + "startTime: %d)",
          name, cmdline, pid, parent, realUid, startTime);
    }
  
    @Override
    public int hashCode() {
      return pid.hashCode();
    }
  
    @Override
    public boolean equals(Object o) {
      if (o == null) {
        return false;
      }
      if (!(o instanceof ProcSummary)) {
        return false;
      }
      ProcSummary ops = (ProcSummary) o;
      return ops.name.equals(name)
          && ops.pid.equals(pid)
          && ops.parent.equals(parent)
          && ops.realUid.equals(realUid)
          && ops.cmdline.equals(cmdline)
          && ops.startTime == startTime;
    }
  }
  