View Javadoc
1   /*
2    * Copyright 2022-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.util;
6   
7   import static oshi.util.Memoizer.memoize;
8   
9   import com.sun.jna.Platform;
10  
11  import java.util.List;
12  import java.util.Map;
13  import java.util.concurrent.ConcurrentHashMap;
14  import java.util.concurrent.TimeUnit;
15  import java.util.function.Supplier;
16  
17  import oshi.annotation.concurrent.ThreadSafe;
18  
19  /**
20   * Utility class to temporarily cache the userID and group maps in *nix, for
21   * parsing process ownership. Cache expires after one minute.
22   */
23  @ThreadSafe
24  public final class UserGroupInfo {
25  
26      // Temporarily cache users and groups in concurrent maps, completely refresh
27      // every 5 minutes
28      private static final Supplier<Map<String, String>> USERS_ID_MAP = memoize(UserGroupInfo::getUserMap,
29              TimeUnit.MINUTES.toNanos(5));
30      private static final Supplier<Map<String, String>> GROUPS_ID_MAP = memoize(UserGroupInfo::getGroupMap,
31              TimeUnit.MINUTES.toNanos(5));
32  
33      private static final boolean ELEVATED = 0 == ParseUtil.parseIntOrDefault(ExecutingCommand.getFirstAnswer("id -u"),
34              -1);
35  
36      private UserGroupInfo() {
37      }
38  
39      /**
40       * Determine whether the current process has elevated permissions such as sudo /
41       * Administrator
42       *
43       * @return True if this process has elevated permissions
44       */
45      public static boolean isElevated() {
46          return ELEVATED;
47      }
48  
49      /**
50       * Gets a user from their ID
51       *
52       * @param userId
53       *            a user ID
54       * @return a pair containing that user id as the first element and the user name
55       *         as the second
56       */
57      public static String getUser(String userId) {
58          // If value is in cached /etc/passwd return, else do getent passwd uid
59          return USERS_ID_MAP.get().getOrDefault(userId, getentPasswd(userId));
60      }
61  
62      /**
63       * Gets the group name for a given ID
64       *
65       * @param groupId
66       *            a {@link java.lang.String} object.
67       * @return a {@link java.lang.String} object.
68       */
69      public static String getGroupName(String groupId) {
70          // If value is in cached /etc/passwd return, else do getent group gid
71          return GROUPS_ID_MAP.get().getOrDefault(groupId, getentGroup(groupId));
72      }
73  
74      private static Map<String, String> getUserMap() {
75          return parsePasswd(FileUtil.readFile("/etc/passwd"));
76      }
77  
78      private static String getentPasswd(String userId) {
79          if (Platform.isAIX()) {
80              return Constants.UNKNOWN;
81          }
82          Map<String, String> newUsers = parsePasswd(ExecutingCommand.runNative("getent passwd " + userId));
83          // add to user map for future queries
84          USERS_ID_MAP.get().putAll(newUsers);
85          return newUsers.getOrDefault(userId, Constants.UNKNOWN);
86      }
87  
88      private static Map<String, String> parsePasswd(List<String> passwd) {
89          Map<String, String> userMap = new ConcurrentHashMap<>();
90          // see man 5 passwd for the fields
91          for (String entry : passwd) {
92              String[] split = entry.split(":");
93              if (split.length > 2) {
94                  String userName = split[0];
95                  String uid = split[2];
96                  // it is allowed to have multiple entries for the same userId,
97                  // we use the first one
98                  userMap.putIfAbsent(uid, userName);
99              }
100         }
101         return userMap;
102     }
103 
104     private static Map<String, String> getGroupMap() {
105         return parseGroup(FileUtil.readFile("/etc/group"));
106     }
107 
108     private static String getentGroup(String groupId) {
109         if (Platform.isAIX()) {
110             return Constants.UNKNOWN;
111         }
112         Map<String, String> newGroups = parseGroup(ExecutingCommand.runNative("getent group " + groupId));
113         // add to group map for future queries
114         GROUPS_ID_MAP.get().putAll(newGroups);
115         return newGroups.getOrDefault(groupId, Constants.UNKNOWN);
116     }
117 
118     private static Map<String, String> parseGroup(List<String> group) {
119         Map<String, String> groupMap = new ConcurrentHashMap<>();
120         // see man 5 group for the fields
121         for (String entry : group) {
122             String[] split = entry.split(":");
123             if (split.length > 2) {
124                 String groupName = split[0];
125                 String gid = split[2];
126                 groupMap.putIfAbsent(gid, groupName);
127             }
128         }
129         return groupMap;
130     }
131 }