View Javadoc
1   /*
2    * Copyright 2021-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.unix.aix;
6   
7   import java.io.IOException;
8   import java.nio.file.Files;
9   import java.nio.file.Path;
10  import java.nio.file.Paths;
11  import java.util.ArrayList;
12  import java.util.LinkedHashMap;
13  import java.util.List;
14  import java.util.Locale;
15  import java.util.Map;
16  
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import com.sun.jna.Memory;
21  import com.sun.jna.NativeLong;
22  import com.sun.jna.platform.unix.LibCAPI.size_t;
23  import com.sun.jna.platform.unix.LibCAPI.ssize_t;
24  
25  import oshi.annotation.concurrent.ThreadSafe;
26  import oshi.jna.platform.unix.AixLibc;
27  import oshi.jna.platform.unix.AixLibc.AixLwpsInfo;
28  import oshi.jna.platform.unix.AixLibc.AixPsInfo;
29  import oshi.util.FileUtil;
30  import oshi.util.tuples.Pair;
31  import oshi.util.tuples.Triplet;
32  
33  /**
34   * Utility to query /proc/psinfo
35   */
36  @ThreadSafe
37  public final class PsInfo {
38      private static final Logger LOG = LoggerFactory.getLogger(PsInfo.class);
39  
40      private static final AixLibc LIBC = AixLibc.INSTANCE;
41  
42      // AIX has multiple page size units, but for purposes of "pages" in perfstat,
43      // the docs specify 4KB pages so we hardcode this
44      private static final long PAGE_SIZE = 4096L;
45  
46      private PsInfo() {
47      }
48  
49      /**
50       * Reads /proc/pid/psinfo and returns data in a structure
51       *
52       * @param pid The process ID
53       * @return A structure containing information for the requested process
54       */
55      public static AixPsInfo queryPsInfo(int pid) {
56          return new AixPsInfo(FileUtil.readAllBytesAsBuffer(String.format(Locale.ROOT, "/proc/%d/psinfo", pid)));
57      }
58  
59      /**
60       * Reads /proc/pid/lwp/tid/lwpsinfo and returns data in a structure
61       *
62       * @param pid The process ID
63       * @param tid The thread ID (lwpid)
64       * @return A structure containing information for the requested thread
65       */
66      public static AixLwpsInfo queryLwpsInfo(int pid, int tid) {
67          return new AixLwpsInfo(
68                  FileUtil.readAllBytesAsBuffer(String.format(Locale.ROOT, "/proc/%d/lwp/%d/lwpsinfo", pid, tid)));
69      }
70  
71      /**
72       * Reads the pr_argc, pr_argv, and pr_envp fields from /proc/pid/psinfo
73       *
74       * @param pid    The process ID
75       * @param psinfo A populated {@link AixPsInfo} structure containing the offset pointers for these fields
76       * @return A triplet containing the argc, argv, and envp values, or null if unable to read
77       */
78      public static Triplet<Integer, Long, Long> queryArgsEnvAddrs(int pid, AixPsInfo psinfo) {
79          if (psinfo != null) {
80              int argc = psinfo.pr_argc;
81              // Must have at least one argc (the command itself) so failure here means exit
82              if (argc > 0) {
83                  long argv = psinfo.pr_argv;
84                  long envp = psinfo.pr_envp;
85                  return new Triplet<>(argc, argv, envp);
86              }
87              LOG.trace("Failed argc sanity check: argc={}", argc);
88              return null;
89          }
90          LOG.trace("Failed to read psinfo file for pid: {} ", pid);
91          return null;
92      }
93  
94      /**
95       * Read the argument and environment strings from process address space
96       *
97       * @param pid    the process id
98       * @param psinfo A populated {@link AixPsInfo} structure containing the offset pointers for these fields
99       * @return A pair containing a list of the arguments and a map of environment variables
100      */
101     public static Pair<List<String>, Map<String, String>> queryArgsEnv(int pid, AixPsInfo psinfo) {
102         List<String> args = new ArrayList<>();
103         Map<String, String> env = new LinkedHashMap<>();
104 
105         // Get the arg count and list of env vars
106         Triplet<Integer, Long, Long> addrs = queryArgsEnvAddrs(pid, psinfo);
107         if (addrs != null) {
108             // Open a file descriptor to the address space
109             String procas = "/proc/" + pid + "/as";
110             int fd = LIBC.open(procas, 0);
111             if (fd < 0) {
112                 LOG.trace("No permission to read file: {} ", procas);
113                 return new Pair<>(args, env);
114             }
115             try {
116                 // Non-null addrs means argc > 0
117                 int argc = addrs.getA();
118                 long argv = addrs.getB();
119                 long envp = addrs.getC();
120 
121                 // We need to determine if the process is 32-bit or 64-bit data model.
122                 long increment;
123                 Path p = Paths.get("/proc/" + pid + "/status");
124                 try {
125                     byte[] status = Files.readAllBytes(p);
126                     if (status[17] == 1) {
127                         increment = 8;
128                     } else {
129                         increment = 4;
130                     }
131                 } catch (IOException e) {
132                     return new Pair<>(args, env);
133                 }
134 
135                 // Reusable buffer
136                 try (Memory buffer = new Memory(PAGE_SIZE * 2)) {
137                     size_t bufSize = new size_t(buffer.size());
138 
139                     // Read the pointers to the arg strings
140                     long bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, 0, argv);
141                     long[] argPtr = new long[argc];
142                     long argp = bufStart == 0 ? 0 : getOffsetFromBuffer(buffer, argv - bufStart, increment);
143                     if (argp > 0) {
144                         for (int i = 0; i < argc; i++) {
145                             long offset = argp + i * increment;
146                             bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, bufStart, offset);
147                             argPtr[i] = bufStart == 0 ? 0 : getOffsetFromBuffer(buffer, offset - bufStart, increment);
148                         }
149                     }
150 
151                     // Also read the pointers to the env strings
152                     // We don't know how many, so stop when we get to null pointer
153                     bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, bufStart, envp);
154                     List<Long> envPtrList = new ArrayList<>();
155                     long addr = bufStart == 0 ? 0 : getOffsetFromBuffer(buffer, envp - bufStart, increment);
156                     int limit = 500; // sane max env strings to stop at
157                     long offset = addr;
158                     while (addr != 0 && --limit > 0) {
159                         bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, bufStart, offset);
160                         long envPtr = bufStart == 0 ? 0 : getOffsetFromBuffer(buffer, offset - bufStart, increment);
161                         if (envPtr != 0) {
162                             envPtrList.add(envPtr);
163                         }
164                         offset += increment;
165                     }
166 
167                     // Now read the arg strings from the buffer
168                     for (int i = 0; i < argPtr.length && argPtr[i] != 0; i++) {
169                         bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, bufStart, argPtr[i]);
170                         if (bufStart != 0) {
171                             String argStr = buffer.getString(argPtr[i] - bufStart);
172                             if (!argStr.isEmpty()) {
173                                 args.add(argStr);
174                             }
175                         }
176                     }
177 
178                     // And now read the env strings from the buffer
179                     for (Long envPtr : envPtrList) {
180                         bufStart = conditionallyReadBufferFromStartOfPage(fd, buffer, bufSize, bufStart, envPtr);
181                         if (bufStart != 0) {
182                             String envStr = buffer.getString(envPtr - bufStart);
183                             int idx = envStr.indexOf('=');
184                             if (idx > 0) {
185                                 env.put(envStr.substring(0, idx), envStr.substring(idx + 1));
186                             }
187                         }
188                     }
189                 }
190             } finally {
191                 LIBC.close(fd);
192             }
193         }
194         return new Pair<>(args, env);
195     }
196 
197     /**
198      * Reads the page containing addr into buffer, unless the buffer already contains that page (as indicated by the
199      * bufStart address), in which case nothing is changed.
200      *
201      * @param fd       The file descriptor for the address space
202      * @param buffer   An allocated buffer, possibly with data reread from bufStart
203      * @param bufSize  The size of the buffer
204      * @param bufStart The start of data currently in bufStart, or 0 if uninitialized
205      * @param addr     THe address whose page to read into the buffer
206      * @return The new starting pointer for the buffer
207      */
208     private static long conditionallyReadBufferFromStartOfPage(int fd, Memory buffer, size_t bufSize, long bufStart,
209             long addr) {
210         // If we don't have the right buffer, update it
211         if (addr < bufStart || addr - bufStart > PAGE_SIZE) {
212             long newStart = Math.floorDiv(addr, PAGE_SIZE) * PAGE_SIZE;
213             ssize_t result = LIBC.pread(fd, buffer, bufSize, new NativeLong(newStart));
214             // May return less than asked but should be at least a full page
215             if (result.longValue() < PAGE_SIZE) {
216                 LOG.debug("Failed to read page from address space: {} bytes read", result.longValue());
217                 return 0;
218             }
219             return newStart;
220         }
221         return bufStart;
222     }
223 
224     private static long getOffsetFromBuffer(Memory buffer, long offset, long increment) {
225         return increment == 8 ? buffer.getLong(offset) : buffer.getInt(offset);
226     }
227 }