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