View Javadoc
1   /*
2    * Copyright 2016-2022 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.unix.solaris;
6   
7   import static oshi.software.os.unix.solaris.SolarisOperatingSystem.HAS_KSTAT2;
8   import static oshi.util.Memoizer.defaultExpiration;
9   
10  import java.io.File;
11  import java.nio.file.PathMatcher;
12  import java.util.ArrayList;
13  import java.util.HashMap;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.function.Supplier;
17  
18  import com.sun.jna.platform.unix.solaris.LibKstat.Kstat;
19  
20  import oshi.annotation.concurrent.ThreadSafe;
21  import oshi.software.common.AbstractFileSystem;
22  import oshi.software.os.OSFileStore;
23  import oshi.util.ExecutingCommand;
24  import oshi.util.FileSystemUtil;
25  import oshi.util.FileUtil;
26  import oshi.util.Memoizer;
27  import oshi.util.ParseUtil;
28  import oshi.util.platform.unix.solaris.KstatUtil;
29  import oshi.util.platform.unix.solaris.KstatUtil.KstatChain;
30  import oshi.util.tuples.Pair;
31  
32  /**
33   * The Solaris File System contains {@link oshi.software.os.OSFileStore}s which are a storage pool, device, partition,
34   * volume, concrete file system or other implementation specific means of file storage. In Solaris, these are found in
35   * the /proc/mount filesystem, excluding temporary and kernel mounts.
36   */
37  @ThreadSafe
38  public class SolarisFileSystem extends AbstractFileSystem {
39  
40      private static final Supplier<Pair<Long, Long>> FILE_DESC = Memoizer
41              .memoize(SolarisFileSystem::queryFileDescriptors, defaultExpiration());
42  
43      public static final String OSHI_SOLARIS_FS_PATH_EXCLUDES = "oshi.os.solaris.filesystem.path.excludes";
44      public static final String OSHI_SOLARIS_FS_PATH_INCLUDES = "oshi.os.solaris.filesystem.path.includes";
45      public static final String OSHI_SOLARIS_FS_VOLUME_EXCLUDES = "oshi.os.solaris.filesystem.volume.excludes";
46      public static final String OSHI_SOLARIS_FS_VOLUME_INCLUDES = "oshi.os.solaris.filesystem.volume.includes";
47  
48      private static final List<PathMatcher> FS_PATH_EXCLUDES = FileSystemUtil
49              .loadAndParseFileSystemConfig(OSHI_SOLARIS_FS_PATH_EXCLUDES);
50      private static final List<PathMatcher> FS_PATH_INCLUDES = FileSystemUtil
51              .loadAndParseFileSystemConfig(OSHI_SOLARIS_FS_PATH_INCLUDES);
52      private static final List<PathMatcher> FS_VOLUME_EXCLUDES = FileSystemUtil
53              .loadAndParseFileSystemConfig(OSHI_SOLARIS_FS_VOLUME_EXCLUDES);
54      private static final List<PathMatcher> FS_VOLUME_INCLUDES = FileSystemUtil
55              .loadAndParseFileSystemConfig(OSHI_SOLARIS_FS_VOLUME_INCLUDES);
56  
57      @Override
58      public List<OSFileStore> getFileStores(boolean localOnly) {
59          return getFileStoreMatching(null, localOnly);
60      }
61  
62      // Called by SolarisOSFileStore
63      static List<OSFileStore> getFileStoreMatching(String nameToMatch) {
64          return getFileStoreMatching(nameToMatch, false);
65      }
66  
67      private static List<OSFileStore> getFileStoreMatching(String nameToMatch, boolean localOnly) {
68          List<OSFileStore> fsList = new ArrayList<>();
69  
70          // Get inode usage data
71          Map<String, Long> inodeFreeMap = new HashMap<>();
72          Map<String, Long> inodeTotalMap = new HashMap<>();
73          String key = null;
74          String total = null;
75          String free = null;
76          String command = "df -g" + (localOnly ? " -l" : "");
77          for (String line : ExecutingCommand.runNative(command)) {
78              /*- Sample Output:
79              /                  (/dev/md/dsk/d0    ):         8192 block size          1024 frag size
80              41310292 total blocks   18193814 free blocks 17780712 available        2486848 total files
81               2293351 free files     22282240 filesys id
82                   ufs fstype       0x00000004 flag             255 filename length
83              */
84              if (line.startsWith("/")) {
85                  key = ParseUtil.whitespaces.split(line)[0];
86                  total = null;
87              } else if (line.contains("available") && line.contains("total files")) {
88                  total = ParseUtil.getTextBetweenStrings(line, "available", "total files").trim();
89              } else if (line.contains("free files")) {
90                  free = ParseUtil.getTextBetweenStrings(line, "", "free files").trim();
91                  if (key != null && total != null) {
92                      inodeFreeMap.put(key, ParseUtil.parseLongOrDefault(free, 0L));
93                      inodeTotalMap.put(key, ParseUtil.parseLongOrDefault(total, 0L));
94                      key = null;
95                  }
96              }
97          }
98  
99          // Get mount table
100         for (String fs : ExecutingCommand.runNative("cat /etc/mnttab")) { // NOSONAR squid:S135
101             String[] split = ParseUtil.whitespaces.split(fs);
102             if (split.length < 5) {
103                 continue;
104             }
105             // 1st field is volume name
106             // 2nd field is mount point
107             // 3rd field is fs type
108             // 4th field is options
109             // other fields ignored
110             String volume = split[0];
111             String path = split[1];
112             String type = split[2];
113             String options = split[3];
114 
115             // Skip non-local drives if requested, and exclude pseudo file systems
116             if ((localOnly && NETWORK_FS_TYPES.contains(type))
117                     || !path.equals("/") && (PSEUDO_FS_TYPES.contains(type) || FileSystemUtil.isFileStoreExcluded(path,
118                             volume, FS_PATH_INCLUDES, FS_PATH_EXCLUDES, FS_VOLUME_INCLUDES, FS_VOLUME_EXCLUDES))) {
119                 continue;
120             }
121 
122             String name = path.substring(path.lastIndexOf('/') + 1);
123             // Special case for /, pull last element of volume instead
124             if (name.isEmpty()) {
125                 name = volume.substring(volume.lastIndexOf('/') + 1);
126             }
127 
128             if (nameToMatch != null && !nameToMatch.equals(name)) {
129                 continue;
130             }
131             File f = new File(path);
132             long totalSpace = f.getTotalSpace();
133             long usableSpace = f.getUsableSpace();
134             long freeSpace = f.getFreeSpace();
135 
136             String description;
137             if (volume.startsWith("/dev") || path.equals("/")) {
138                 description = "Local Disk";
139             } else if (volume.equals("tmpfs")) {
140                 description = "Ram Disk";
141             } else if (NETWORK_FS_TYPES.contains(type)) {
142                 description = "Network Disk";
143             } else {
144                 description = "Mount Point";
145             }
146 
147             fsList.add(new SolarisOSFileStore(name, volume, name, path, options, "", "", description, type, freeSpace,
148                     usableSpace, totalSpace, inodeFreeMap.containsKey(path) ? inodeFreeMap.get(path) : 0L,
149                     inodeTotalMap.containsKey(path) ? inodeTotalMap.get(path) : 0L));
150         }
151         return fsList;
152     }
153 
154     @Override
155     public long getOpenFileDescriptors() {
156         if (HAS_KSTAT2) {
157             // Use Kstat2 implementation
158             return FILE_DESC.get().getA();
159         }
160         try (KstatChain kc = KstatUtil.openChain()) {
161             Kstat ksp = kc.lookup(null, -1, "file_cache");
162             // Set values
163             if (ksp != null && kc.read(ksp)) {
164                 return KstatUtil.dataLookupLong(ksp, "buf_inuse");
165             }
166         }
167         return 0L;
168     }
169 
170     @Override
171     public long getMaxFileDescriptors() {
172         if (HAS_KSTAT2) {
173             // Use Kstat2 implementation
174             return FILE_DESC.get().getB();
175         }
176         try (KstatChain kc = KstatUtil.openChain()) {
177             Kstat ksp = kc.lookup(null, -1, "file_cache");
178             // Set values
179             if (ksp != null && kc.read(ksp)) {
180                 return KstatUtil.dataLookupLong(ksp, "buf_max");
181             }
182         }
183         return 0L;
184     }
185 
186     @Override
187     public long getMaxFileDescriptorsPerProcess() {
188         final List<String> lines = FileUtil.readFile("/etc/system");
189         for (final String line : lines) {
190             if (line.startsWith("set rlim_fd_max")) {
191                 return ParseUtil.parseLastLong(line, 65536L);
192             }
193         }
194         return 65536L; // 65536 is the default value for the process open file limit in Solaris
195     }
196 
197     private static Pair<Long, Long> queryFileDescriptors() {
198         Object[] results = KstatUtil.queryKstat2("kstat:/kmem_cache/kmem_default/file_cache", "buf_inuse", "buf_max");
199         long inuse = results[0] == null ? 0L : (long) results[0];
200         long max = results[1] == null ? 0L : (long) results[1];
201         return new Pair<>(inuse, max);
202     }
203 }