View Javadoc
1   /*
2    * Copyright 2020-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.driver.unix;
6   
7   import java.time.LocalDateTime;
8   import java.time.Year;
9   import java.time.ZoneId;
10  import java.time.format.DateTimeFormatter;
11  import java.time.format.DateTimeFormatterBuilder;
12  import java.time.format.DateTimeParseException;
13  import java.time.temporal.ChronoField;
14  import java.time.temporal.ChronoUnit;
15  import java.util.ArrayList;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.regex.Matcher;
19  import java.util.regex.Pattern;
20  
21  import com.sun.jna.Platform;
22  
23  import oshi.annotation.concurrent.ThreadSafe;
24  import oshi.software.os.OSSession;
25  import oshi.util.Constants;
26  import oshi.util.ExecutingCommand;
27  
28  /**
29   * Utility to query logged in users.
30   */
31  @ThreadSafe
32  public final class Who {
33  
34      // sample format:
35      // oshi pts/0 2020-05-14 21:23 (192.168.1.23)
36      private static final Pattern WHO_FORMAT_LINUX = Pattern
37              .compile("(\\S+)\\s+(\\S+)\\s+(\\d{4}-\\d{2}-\\d{2})\\s+(\\d{2}:\\d{2})\\s*(?:\\((.+)\\))?");
38      private static final DateTimeFormatter WHO_DATE_FORMAT_LINUX = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm",
39              Locale.ROOT);
40      // oshi ttys000 May 4 23:50 (192.168.1.23)
41      // middle 12 characters from Thu Nov 24 18:22:48 1986
42      private static final Pattern WHO_FORMAT_UNIX = Pattern
43              .compile("(\\S+)\\s+(\\S+)\\s+(\\S+)\\s+(\\d+)\\s+(\\d{2}:\\d{2})\\s*(?:\\((.+)\\))?");
44      private static final DateTimeFormatter WHO_DATE_FORMAT_UNIX = new DateTimeFormatterBuilder()
45              .appendPattern("MMM d HH:mm").parseDefaulting(ChronoField.YEAR, Year.now(ZoneId.systemDefault()).getValue())
46              .toFormatter(Locale.US);
47  
48      private Who() {
49      }
50  
51      /**
52       * Query {@code who} to get logged in users
53       *
54       * @return A list of logged in user sessions
55       */
56      public static synchronized List<OSSession> queryWho() {
57          List<OSSession> whoList = new ArrayList<>();
58          List<String> who = ExecutingCommand.runNative("who");
59          for (String s : who) {
60              boolean matched = false;
61              if (Platform.isLinux()) {
62                  matched = matchLinux(whoList, s);
63              }
64              if (!matched) {
65                  matchUnix(whoList, s);
66              }
67          }
68          return whoList;
69      }
70  
71      /**
72       * Attempt to match Linux WHO format and add to the list
73       *
74       * @param whoList the list to add to
75       * @param s       the string to match
76       * @return true if successful, false otherwise
77       */
78      private static boolean matchLinux(List<OSSession> whoList, String s) {
79          Matcher m = WHO_FORMAT_LINUX.matcher(s);
80          if (m.matches()) {
81              try {
82                  whoList.add(new OSSession(m.group(1), m.group(2),
83                          LocalDateTime.parse(m.group(3) + " " + m.group(4), WHO_DATE_FORMAT_LINUX)
84                                  .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
85                          m.group(5) == null ? Constants.UNKNOWN : m.group(5)));
86                  return true;
87              } catch (DateTimeParseException | NullPointerException e) {
88                  // shouldn't happen if regex matches and OS is producing sensible dates
89              }
90          }
91          return false;
92      }
93  
94      /**
95       * Attempt to match Unix WHO format and add to the list
96       *
97       * @param whoList the list to add to
98       * @param s       the string to match
99       * @return true if successful, false otherwise
100      */
101     private static boolean matchUnix(List<OSSession> whoList, String s) {
102         Matcher m = WHO_FORMAT_UNIX.matcher(s);
103         if (m.matches()) {
104             try {
105                 // Missing year, parse date time with current year
106                 LocalDateTime login = LocalDateTime.parse(m.group(3) + " " + m.group(4) + " " + m.group(5),
107                         WHO_DATE_FORMAT_UNIX);
108                 // If this date is in the future, subtract a year
109                 if (login.isAfter(LocalDateTime.now(ZoneId.systemDefault()))) {
110                     login = login.minus(1, ChronoUnit.YEARS);
111                 }
112                 long millis = login.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
113                 whoList.add(new OSSession(m.group(1), m.group(2), millis, m.group(6) == null ? "" : m.group(6)));
114                 return true;
115             } catch (DateTimeParseException | NullPointerException e) {
116                 // shouldn't happen if regex matches and OS is producing sensible dates
117             }
118         }
119         return false;
120     }
121 }