View Javadoc
1   /*
2    * Copyright 2016-2023 The OSHI Project Contributors
3    * SPDX-License-Identifier: MIT
4    */
5   package oshi.software.os.windows;
6   
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Locale;
11  import java.util.Map;
12  import java.util.stream.Collectors;
13  
14  import com.sun.jna.Native;
15  import com.sun.jna.platform.win32.Kernel32;
16  import com.sun.jna.platform.win32.WinBase;
17  import com.sun.jna.platform.win32.WinNT;
18  import com.sun.jna.platform.win32.COM.WbemcliUtil.WmiResult;
19  
20  import oshi.annotation.concurrent.ThreadSafe;
21  import oshi.driver.windows.perfmon.ProcessInformation;
22  import oshi.driver.windows.perfmon.ProcessInformation.HandleCountProperty;
23  import oshi.driver.windows.wmi.Win32LogicalDisk;
24  import oshi.driver.windows.wmi.Win32LogicalDisk.LogicalDiskProperty;
25  import oshi.jna.ByRef.CloseableIntByReference;
26  import oshi.software.common.AbstractFileSystem;
27  import oshi.software.os.OSFileStore;
28  import oshi.util.ParseUtil;
29  import oshi.util.platform.windows.WmiUtil;
30  
31  /**
32   * The Windows File System contains {@link oshi.software.os.OSFileStore}s which are a storage pool, device, partition,
33   * volume, concrete file system or other implementation specific means of file storage. In Windows, these are
34   * represented by a drive letter, e.g., "A:\" and "C:\"
35   */
36  @ThreadSafe
37  public class WindowsFileSystem extends AbstractFileSystem {
38  
39      private static final int BUFSIZE = 255;
40  
41      private static final int SEM_FAILCRITICALERRORS = 0x0001;
42  
43      private static final int FILE_CASE_SENSITIVE_SEARCH = 0x00000001;
44      private static final int FILE_CASE_PRESERVED_NAMES = 0x00000002;
45      private static final int FILE_FILE_COMPRESSION = 0x00000010;
46      private static final int FILE_DAX_VOLUME = 0x20000000;
47      private static final int FILE_NAMED_STREAMS = 0x00040000;
48      private static final int FILE_PERSISTENT_ACLS = 0x00000008;
49      private static final int FILE_READ_ONLY_VOLUME = 0x00080000;
50      private static final int FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000;
51      private static final int FILE_SUPPORTS_ENCRYPTION = 0x00020000;
52      private static final int FILE_SUPPORTS_OBJECT_IDS = 0x00010000;
53      private static final int FILE_SUPPORTS_REPARSE_POINTS = 0x00000080;
54      private static final int FILE_SUPPORTS_SPARSE_FILES = 0x00000040;
55      private static final int FILE_SUPPORTS_TRANSACTIONS = 0x00200000;
56      private static final int FILE_SUPPORTS_USN_JOURNAL = 0x02000000;
57      private static final int FILE_UNICODE_ON_DISK = 0x00000004;
58      private static final int FILE_VOLUME_IS_COMPRESSED = 0x00008000;
59      private static final int FILE_VOLUME_QUOTAS = 0x00000020;
60  
61      private static final Map<Integer, String> OPTIONS_MAP = new HashMap<>();
62      static {
63          OPTIONS_MAP.put(FILE_CASE_PRESERVED_NAMES, "casepn");
64          OPTIONS_MAP.put(FILE_CASE_SENSITIVE_SEARCH, "casess");
65          OPTIONS_MAP.put(FILE_FILE_COMPRESSION, "fcomp");
66          OPTIONS_MAP.put(FILE_DAX_VOLUME, "dax");
67          OPTIONS_MAP.put(FILE_NAMED_STREAMS, "streams");
68          OPTIONS_MAP.put(FILE_PERSISTENT_ACLS, "acls");
69          OPTIONS_MAP.put(FILE_SEQUENTIAL_WRITE_ONCE, "wronce");
70          OPTIONS_MAP.put(FILE_SUPPORTS_ENCRYPTION, "efs");
71          OPTIONS_MAP.put(FILE_SUPPORTS_OBJECT_IDS, "oids");
72          OPTIONS_MAP.put(FILE_SUPPORTS_REPARSE_POINTS, "reparse");
73          OPTIONS_MAP.put(FILE_SUPPORTS_SPARSE_FILES, "sparse");
74          OPTIONS_MAP.put(FILE_SUPPORTS_TRANSACTIONS, "trans");
75          OPTIONS_MAP.put(FILE_SUPPORTS_USN_JOURNAL, "journaled");
76          OPTIONS_MAP.put(FILE_UNICODE_ON_DISK, "unicode");
77          OPTIONS_MAP.put(FILE_VOLUME_IS_COMPRESSED, "vcomp");
78          OPTIONS_MAP.put(FILE_VOLUME_QUOTAS, "quota");
79      }
80  
81      static final long MAX_WINDOWS_HANDLES;
82      static {
83          // Determine whether 32-bit or 64-bit handle limit, although both are
84          // essentially infinite for practical purposes. See
85          // https://blogs.technet.microsoft.com/markrussinovich/2009/09/29/pushing-the-limits-of-windows-handles/
86          if (System.getenv("ProgramFiles(x86)") == null) {
87              MAX_WINDOWS_HANDLES = 16_777_216L - 32_768L;
88          } else {
89              MAX_WINDOWS_HANDLES = 16_777_216L - 65_536L;
90          }
91      }
92  
93      /**
94       * <p>
95       * Constructor for WindowsFileSystem.
96       * </p>
97       */
98      public WindowsFileSystem() {
99          // Set error mode to fail rather than prompt for FLoppy/CD-Rom
100         Kernel32.INSTANCE.SetErrorMode(SEM_FAILCRITICALERRORS);
101     }
102 
103     @Override
104     public List<OSFileStore> getFileStores(boolean localOnly) {
105         // Create list to hold results
106         ArrayList<OSFileStore> result;
107 
108         // Begin with all the local volumes
109         result = getLocalVolumes(null);
110 
111         // Build a map of existing mount point to OSFileStore
112         Map<String, OSFileStore> volumeMap = new HashMap<>();
113         for (OSFileStore volume : result) {
114             volumeMap.put(volume.getMount(), volume);
115         }
116 
117         // Iterate through volumes in WMI and update description (if it exists)
118         // or add new if it doesn't (expected for network drives)
119         for (OSFileStore wmiVolume : getWmiVolumes(null, localOnly)) {
120             if (volumeMap.containsKey(wmiVolume.getMount())) {
121                 // If the volume is already in our list, update the name field
122                 // using WMI's more verbose name and update label if needed
123                 OSFileStore volume = volumeMap.get(wmiVolume.getMount());
124                 result.remove(volume);
125                 result.add(new WindowsOSFileStore(wmiVolume.getName(), volume.getVolume(),
126                         volume.getLabel().isEmpty() ? wmiVolume.getLabel() : volume.getLabel(), volume.getMount(),
127                         volume.getOptions(), volume.getUUID(), "", volume.getDescription(), volume.getType(),
128                         volume.getFreeSpace(), volume.getUsableSpace(), volume.getTotalSpace(), 0, 0));
129             } else if (!localOnly) {
130                 // Otherwise add the new volume in its entirety
131                 result.add(wmiVolume);
132             }
133         }
134         return result;
135     }
136 
137     /**
138      * Package private method for getting all mounted local drives.
139      *
140      * @param volumeToMatch an optional string to filter match, null otherwise
141      * @return A list of {@link OSFileStore} objects representing all local mounted volumes
142      */
143     static ArrayList<OSFileStore> getLocalVolumes(String volumeToMatch) {
144         ArrayList<OSFileStore> fs;
145         String volume;
146         String strFsType;
147         String strName;
148         String strMount;
149         WinNT.HANDLE hVol;
150         WinNT.LARGE_INTEGER userFreeBytes;
151         WinNT.LARGE_INTEGER totalBytes;
152         WinNT.LARGE_INTEGER systemFreeBytes;
153         char[] aVolume;
154         char[] fstype;
155         char[] name;
156         char[] mount;
157 
158         fs = new ArrayList<>();
159         aVolume = new char[BUFSIZE];
160 
161         hVol = Kernel32.INSTANCE.FindFirstVolume(aVolume, BUFSIZE);
162         if (WinBase.INVALID_HANDLE_VALUE.equals(hVol)) {
163             return fs;
164         }
165         try (CloseableIntByReference pFlags = new CloseableIntByReference()) {
166             do {
167                 fstype = new char[16];
168                 name = new char[BUFSIZE];
169                 mount = new char[BUFSIZE];
170 
171                 userFreeBytes = new WinNT.LARGE_INTEGER(0L);
172                 totalBytes = new WinNT.LARGE_INTEGER(0L);
173                 systemFreeBytes = new WinNT.LARGE_INTEGER(0L);
174 
175                 volume = Native.toString(aVolume);
176                 Kernel32.INSTANCE.GetVolumeInformation(volume, name, BUFSIZE, null, null, pFlags, fstype, 16);
177                 final int flags = pFlags.getValue();
178                 Kernel32.INSTANCE.GetVolumePathNamesForVolumeName(volume, mount, BUFSIZE, null);
179 
180                 strMount = Native.toString(mount);
181                 if (!strMount.isEmpty() && (volumeToMatch == null || volumeToMatch.equals(volume))) {
182                     strName = Native.toString(name);
183                     strFsType = Native.toString(fstype);
184 
185                     StringBuilder options = new StringBuilder((FILE_READ_ONLY_VOLUME & flags) == 0 ? "rw" : "ro");
186                     String moreOptions = OPTIONS_MAP.entrySet().stream().filter(e -> (e.getKey() & flags) > 0)
187                             .map(Map.Entry::getValue).collect(Collectors.joining(","));
188                     if (!moreOptions.isEmpty()) {
189                         options.append(',').append(moreOptions);
190                     }
191                     Kernel32.INSTANCE.GetDiskFreeSpaceEx(volume, userFreeBytes, totalBytes, systemFreeBytes);
192                     // Parse uuid from volume name
193                     String uuid = ParseUtil.parseUuidOrDefault(volume, "");
194 
195                     fs.add(new WindowsOSFileStore(String.format(Locale.ROOT, "%s (%s)", strName, strMount), volume,
196                             strName, strMount, options.toString(), uuid, "", getDriveType(strMount), strFsType,
197                             systemFreeBytes.getValue(), userFreeBytes.getValue(), totalBytes.getValue(), 0, 0));
198                 }
199             } while (Kernel32.INSTANCE.FindNextVolume(hVol, aVolume, BUFSIZE));
200             return fs;
201         } finally {
202             Kernel32.INSTANCE.FindVolumeClose(hVol);
203         }
204     }
205 
206     /**
207      * Package private method for getting logical drives listed in WMI.
208      *
209      * @param nameToMatch an optional string to filter match, null otherwise
210      * @param localOnly   Whether to only search local drives
211      * @return A list of {@link OSFileStore} objects representing all network mounted volumes
212      */
213     static List<OSFileStore> getWmiVolumes(String nameToMatch, boolean localOnly) {
214         long free;
215         long total;
216         List<OSFileStore> fs = new ArrayList<>();
217         WmiResult<LogicalDiskProperty> drives = Win32LogicalDisk.queryLogicalDisk(nameToMatch, localOnly);
218         for (int i = 0; i < drives.getResultCount(); i++) {
219             free = WmiUtil.getUint64(drives, LogicalDiskProperty.FREESPACE, i);
220             total = WmiUtil.getUint64(drives, LogicalDiskProperty.SIZE, i);
221             String description = WmiUtil.getString(drives, LogicalDiskProperty.DESCRIPTION, i);
222             String name = WmiUtil.getString(drives, LogicalDiskProperty.NAME, i);
223             String label = WmiUtil.getString(drives, LogicalDiskProperty.VOLUMENAME, i);
224             String options = WmiUtil.getUint16(drives, LogicalDiskProperty.ACCESS, i) == 1 ? "ro" : "rw";
225             int type = WmiUtil.getUint32(drives, LogicalDiskProperty.DRIVETYPE, i);
226             String volume;
227             if (type != 4) {
228                 char[] chrVolume = new char[BUFSIZE];
229                 Kernel32.INSTANCE.GetVolumeNameForVolumeMountPoint(name + "\\", chrVolume, BUFSIZE);
230                 volume = Native.toString(chrVolume);
231             } else {
232                 volume = WmiUtil.getString(drives, LogicalDiskProperty.PROVIDERNAME, i);
233                 String[] split = volume.split("\\\\");
234                 if (split.length > 1 && split[split.length - 1].length() > 0) {
235                     description = split[split.length - 1];
236                 }
237             }
238             fs.add(new WindowsOSFileStore(String.format(Locale.ROOT, "%s (%s)", description, name), volume, label,
239                     name + "\\", options, "", "", getDriveType(name),
240                     WmiUtil.getString(drives, LogicalDiskProperty.FILESYSTEM, i), free, free, total, 0, 0));
241         }
242         return fs;
243     }
244 
245     /**
246      * Private method for getting mounted drive type.
247      *
248      * @param drive Mounted drive
249      * @return A drive type description
250      */
251     private static String getDriveType(String drive) {
252         switch (Kernel32.INSTANCE.GetDriveType(drive)) {
253         case 2:
254             return "Removable drive";
255         case 3:
256             return "Fixed drive";
257         case 4:
258             return "Network drive";
259         case 5:
260             return "CD-ROM";
261         case 6:
262             return "RAM drive";
263         default:
264             return "Unknown drive type";
265         }
266     }
267 
268     @Override
269     public long getOpenFileDescriptors() {
270         Map<HandleCountProperty, List<Long>> valueListMap = ProcessInformation.queryHandles().getB();
271         List<Long> valueList = valueListMap.get(HandleCountProperty.HANDLECOUNT);
272         long descriptors = 0L;
273         if (valueList != null) {
274             for (Long value : valueList) {
275                 descriptors += value;
276             }
277         }
278         return descriptors;
279     }
280 
281     @Override
282     public long getMaxFileDescriptors() {
283         return MAX_WINDOWS_HANDLES;
284     }
285 
286     @Override
287     public long getMaxFileDescriptorsPerProcess() {
288         return MAX_WINDOWS_HANDLES;
289     }
290 }