1
2
3
4
5 package oshi.software.os.mac;
6
7 import java.io.File;
8 import java.nio.charset.StandardCharsets;
9 import java.nio.file.PathMatcher;
10 import java.util.ArrayList;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Locale;
14 import java.util.Map;
15 import java.util.regex.Pattern;
16 import java.util.stream.Collectors;
17
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 import com.sun.jna.Native;
22 import com.sun.jna.Pointer;
23 import com.sun.jna.platform.mac.CoreFoundation;
24 import com.sun.jna.platform.mac.CoreFoundation.CFDictionaryRef;
25 import com.sun.jna.platform.mac.CoreFoundation.CFMutableDictionaryRef;
26 import com.sun.jna.platform.mac.CoreFoundation.CFStringRef;
27 import com.sun.jna.platform.mac.DiskArbitration;
28 import com.sun.jna.platform.mac.DiskArbitration.DADiskRef;
29 import com.sun.jna.platform.mac.DiskArbitration.DASessionRef;
30 import com.sun.jna.platform.mac.IOKit.IOIterator;
31 import com.sun.jna.platform.mac.IOKit.IORegistryEntry;
32 import com.sun.jna.platform.mac.IOKitUtil;
33 import com.sun.jna.platform.mac.SystemB;
34 import com.sun.jna.platform.mac.SystemB.Statfs;
35
36 import oshi.annotation.concurrent.ThreadSafe;
37 import oshi.software.common.AbstractFileSystem;
38 import oshi.software.os.OSFileStore;
39 import oshi.util.FileSystemUtil;
40 import oshi.util.platform.mac.CFUtil;
41 import oshi.util.platform.mac.SysctlUtil;
42
43
44
45
46
47
48 @ThreadSafe
49 public class MacFileSystem extends AbstractFileSystem {
50
51 private static final Logger LOG = LoggerFactory.getLogger(MacFileSystem.class);
52
53 public static final String OSHI_MAC_FS_PATH_EXCLUDES = "oshi.os.mac.filesystem.path.excludes";
54 public static final String OSHI_MAC_FS_PATH_INCLUDES = "oshi.os.mac.filesystem.path.includes";
55 public static final String OSHI_MAC_FS_VOLUME_EXCLUDES = "oshi.os.mac.filesystem.volume.excludes";
56 public static final String OSHI_MAC_FS_VOLUME_INCLUDES = "oshi.os.mac.filesystem.volume.includes";
57
58 private static final List<PathMatcher> FS_PATH_EXCLUDES = FileSystemUtil
59 .loadAndParseFileSystemConfig(OSHI_MAC_FS_PATH_EXCLUDES);
60 private static final List<PathMatcher> FS_PATH_INCLUDES = FileSystemUtil
61 .loadAndParseFileSystemConfig(OSHI_MAC_FS_PATH_INCLUDES);
62 private static final List<PathMatcher> FS_VOLUME_EXCLUDES = FileSystemUtil
63 .loadAndParseFileSystemConfig(OSHI_MAC_FS_VOLUME_EXCLUDES);
64 private static final List<PathMatcher> FS_VOLUME_INCLUDES = FileSystemUtil
65 .loadAndParseFileSystemConfig(OSHI_MAC_FS_VOLUME_INCLUDES);
66
67
68 private static final Pattern LOCAL_DISK = Pattern.compile("/dev/disk\\d");
69
70
71 private static final int MNT_RDONLY = 0x00000001;
72 private static final int MNT_SYNCHRONOUS = 0x00000002;
73 private static final int MNT_NOEXEC = 0x00000004;
74 private static final int MNT_NOSUID = 0x00000008;
75 private static final int MNT_NODEV = 0x00000010;
76 private static final int MNT_UNION = 0x00000020;
77 private static final int MNT_ASYNC = 0x00000040;
78 private static final int MNT_CPROTECT = 0x00000080;
79 private static final int MNT_EXPORTED = 0x00000100;
80 private static final int MNT_QUARANTINE = 0x00000400;
81 private static final int MNT_LOCAL = 0x00001000;
82 private static final int MNT_QUOTA = 0x00002000;
83 private static final int MNT_ROOTFS = 0x00004000;
84 private static final int MNT_DOVOLFS = 0x00008000;
85 private static final int MNT_DONTBROWSE = 0x00100000;
86 private static final int MNT_IGNORE_OWNERSHIP = 0x00200000;
87 private static final int MNT_AUTOMOUNTED = 0x00400000;
88 private static final int MNT_JOURNALED = 0x00800000;
89 private static final int MNT_NOUSERXATTR = 0x01000000;
90 private static final int MNT_DEFWRITE = 0x02000000;
91 private static final int MNT_MULTILABEL = 0x04000000;
92 private static final int MNT_NOATIME = 0x10000000;
93
94 private static final Map<Integer, String> OPTIONS_MAP = new HashMap<>();
95 static {
96 OPTIONS_MAP.put(MNT_SYNCHRONOUS, "synchronous");
97 OPTIONS_MAP.put(MNT_NOEXEC, "noexec");
98 OPTIONS_MAP.put(MNT_NOSUID, "nosuid");
99 OPTIONS_MAP.put(MNT_NODEV, "nodev");
100 OPTIONS_MAP.put(MNT_UNION, "union");
101 OPTIONS_MAP.put(MNT_ASYNC, "asynchronous");
102 OPTIONS_MAP.put(MNT_CPROTECT, "content-protection");
103 OPTIONS_MAP.put(MNT_EXPORTED, "exported");
104 OPTIONS_MAP.put(MNT_QUARANTINE, "quarantined");
105 OPTIONS_MAP.put(MNT_LOCAL, "local");
106 OPTIONS_MAP.put(MNT_QUOTA, "quotas");
107 OPTIONS_MAP.put(MNT_ROOTFS, "rootfs");
108 OPTIONS_MAP.put(MNT_DOVOLFS, "volfs");
109 OPTIONS_MAP.put(MNT_DONTBROWSE, "nobrowse");
110 OPTIONS_MAP.put(MNT_IGNORE_OWNERSHIP, "noowners");
111 OPTIONS_MAP.put(MNT_AUTOMOUNTED, "automounted");
112 OPTIONS_MAP.put(MNT_JOURNALED, "journaled");
113 OPTIONS_MAP.put(MNT_NOUSERXATTR, "nouserxattr");
114 OPTIONS_MAP.put(MNT_DEFWRITE, "defwrite");
115 OPTIONS_MAP.put(MNT_MULTILABEL, "multilabel");
116 OPTIONS_MAP.put(MNT_NOATIME, "noatime");
117 }
118
119 @Override
120 public List<OSFileStore> getFileStores(boolean localOnly) {
121
122 return getFileStoreMatching(null, localOnly);
123 }
124
125
126 static List<OSFileStore> getFileStoreMatching(String nameToMatch) {
127 return getFileStoreMatching(nameToMatch, false);
128 }
129
130 private static List<OSFileStore> getFileStoreMatching(String nameToMatch, boolean localOnly) {
131 List<OSFileStore> fsList = new ArrayList<>();
132
133
134
135 int numfs = SystemB.INSTANCE.getfsstat64(null, 0, 0);
136 if (numfs > 0) {
137
138
139 DASessionRef session = DiskArbitration.INSTANCE
140 .DASessionCreate(CoreFoundation.INSTANCE.CFAllocatorGetDefault());
141 if (session == null) {
142 LOG.error("Unable to open session to DiskArbitration framework.");
143 } else {
144 CFStringRef daVolumeNameKey = CFStringRef.createCFString("DAVolumeName");
145
146
147 Statfs s = new Statfs();
148 Statfs[] fs = (Statfs[]) s.toArray(numfs);
149
150 numfs = SystemB.INSTANCE.getfsstat64(fs, fs[0].size() * fs.length, SystemB.MNT_NOWAIT);
151 for (int f = 0; f < numfs; f++) {
152
153
154
155
156
157 String volume = Native.toString(fs[f].f_mntfromname, StandardCharsets.UTF_8);
158 String path = Native.toString(fs[f].f_mntonname, StandardCharsets.UTF_8);
159 String type = Native.toString(fs[f].f_fstypename, StandardCharsets.UTF_8);
160
161 final int flags = fs[f].f_flags;
162
163
164 if ((localOnly && (flags & MNT_LOCAL) == 0) || !path.equals("/")
165 && (PSEUDO_FS_TYPES.contains(type) || FileSystemUtil.isFileStoreExcluded(path, volume,
166 FS_PATH_INCLUDES, FS_PATH_EXCLUDES, FS_VOLUME_INCLUDES, FS_VOLUME_EXCLUDES))) {
167 continue;
168 }
169
170 String description = "Volume";
171 if (LOCAL_DISK.matcher(volume).matches()) {
172 description = "Local Disk";
173 } else if (volume.startsWith("localhost:") || volume.startsWith("//") || volume.startsWith("smb://")
174 || NETWORK_FS_TYPES.contains(type)) {
175 description = "Network Drive";
176 }
177 File file = new File(path);
178 String name = file.getName();
179
180 if (name.isEmpty()) {
181 name = file.getPath();
182 }
183 if (nameToMatch != null && !nameToMatch.equals(name)) {
184 continue;
185 }
186
187 StringBuilder options = new StringBuilder((MNT_RDONLY & flags) == 0 ? "rw" : "ro");
188 String moreOptions = OPTIONS_MAP.entrySet().stream().filter(e -> (e.getKey() & flags) > 0)
189 .map(Map.Entry::getValue).collect(Collectors.joining(","));
190 if (!moreOptions.isEmpty()) {
191 options.append(',').append(moreOptions);
192 }
193
194 String uuid = "";
195
196
197 String bsdName = volume.replace("/dev/disk", "disk");
198 if (bsdName.startsWith("disk")) {
199
200
201 DADiskRef disk = DiskArbitration.INSTANCE.DADiskCreateFromBSDName(
202 CoreFoundation.INSTANCE.CFAllocatorGetDefault(), session, volume);
203 if (disk != null) {
204 CFDictionaryRef diskInfo = DiskArbitration.INSTANCE.DADiskCopyDescription(disk);
205 if (diskInfo != null) {
206
207 Pointer result = diskInfo.getValue(daVolumeNameKey);
208 name = CFUtil.cfPointerToString(result);
209 diskInfo.release();
210 }
211 disk.release();
212 }
213
214 CFMutableDictionaryRef matchingDict = IOKitUtil.getBSDNameMatchingDict(bsdName);
215 if (matchingDict != null) {
216
217 IOIterator fsIter = IOKitUtil.getMatchingServices(matchingDict);
218 if (fsIter != null) {
219
220
221 IORegistryEntry fsEntry = fsIter.next();
222 if (fsEntry != null && fsEntry.conformsTo("IOMedia")) {
223
224 uuid = fsEntry.getStringProperty("UUID");
225 if (uuid != null) {
226 uuid = uuid.toLowerCase(Locale.ROOT);
227 }
228 fsEntry.release();
229 }
230 fsIter.release();
231 }
232 }
233 }
234
235 fsList.add(new MacOSFileStore(name, volume, name, path, options.toString(),
236 uuid == null ? "" : uuid, "", description, type, file.getFreeSpace(), file.getUsableSpace(),
237 file.getTotalSpace(), fs[f].f_ffree, fs[f].f_files));
238 }
239 daVolumeNameKey.release();
240
241 session.release();
242 }
243 }
244 return fsList;
245 }
246
247 @Override
248 public long getOpenFileDescriptors() {
249 return SysctlUtil.sysctl("kern.num_files", 0);
250 }
251
252 @Override
253 public long getMaxFileDescriptors() {
254 return SysctlUtil.sysctl("kern.maxfiles", 0);
255 }
256
257 @Override
258 public long getMaxFileDescriptorsPerProcess() {
259 return SysctlUtil.sysctl("kern.maxfilesperproc", 0);
260 }
261 }