1 /**
2  * Getting currently mounted volumes and information about them in crossplatform way.
3  * Authors:
4  *  $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov)
5  * Copyright:
6  *  Roman Chistokhodov, 2018
7  * License:
8  *  $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
9  */
10 module volumeinfo;
11 
12 import std.typecons : RefCounted, BitFlags;
13 
14 version(Windows)
15 {
16     import core.sys.windows.windows;
17     import std.utf : toUTF16z, toUTF8;
18     import core.stdc.wchar_ : wcslen;
19 }
20 
21 version(OSX) {} else version(Posix)
22 {
23     private @safe bool isSpecialFileSystem(const(char)[] dir, const(char)[] type)
24     {
25         import std.string : startsWith;
26         if (dir.startsWith("/dev") || dir.startsWith("/proc") || dir.startsWith("/sys") ||
27             dir.startsWith("/var/run") || dir.startsWith("/var/lock"))
28         {
29             return true;
30         }
31 
32         if (type == "tmpfs" || type == "rootfs" || type == "rpc_pipefs") {
33             return true;
34         }
35         return false;
36     }
37 }
38 
39 version(FreeBSD)
40 {
41 private:
42     import core.sys.posix.sys.types;
43 
44     enum MFSNAMELEN = 16;          /* length of type name including null */
45     enum MNAMELEN  = 88;          /* size of on/from name bufs */
46     enum STATFS_VERSION = 0x20030518;      /* current version number */
47     enum MNT_RDONLY = 1;
48 
49     struct fsid_t
50     {
51         int[2] val;
52     }
53 
54     struct statfs_t {
55         uint f_version;         /* structure version number */
56         uint f_type;            /* type of filesystem */
57         ulong f_flags;           /* copy of mount exported flags */
58         ulong f_bsize;           /* filesystem fragment size */
59         ulong f_iosize;          /* optimal transfer block size */
60         ulong f_blocks;          /* total data blocks in filesystem */
61         ulong f_bfree;           /* free blocks in filesystem */
62         long  f_bavail;          /* free blocks avail to non-superuser */
63         ulong f_files;           /* total file nodes in filesystem */
64         long  f_ffree;           /* free nodes avail to non-superuser */
65         ulong f_syncwrites;      /* count of sync writes since mount */
66         ulong f_asyncwrites;         /* count of async writes since mount */
67         ulong f_syncreads;       /* count of sync reads since mount */
68         ulong f_asyncreads;      /* count of async reads since mount */
69         ulong[10] f_spare;       /* unused spare */
70         uint f_namemax;         /* maximum filename length */
71         uid_t     f_owner;          /* user that mounted the filesystem */
72         fsid_t    f_fsid;           /* filesystem id */
73         char[80]      f_charspare;      /* spare string space */
74         char[MFSNAMELEN] f_fstypename; /* filesystem type name */
75         char[MNAMELEN] f_mntfromname;  /* mounted filesystem */
76         char[MNAMELEN] f_mntonname;    /* directory on which mounted */
77     };
78 
79     extern(C) @nogc nothrow
80     {
81         int getmntinfo(statfs_t **mntbufp, int flags);
82         int statfs(const char *path, statfs_t *buf);
83     }
84 
85     @trusted bool parseStatfs(ref const(statfs_t) buf, out const(char)[] device, out const(char)[] mountDir, out const(char)[] type) nothrow {
86         import std.string : fromStringz;
87         type = fromStringz(buf.f_fstypename.ptr);
88         device = fromStringz(buf.f_mntfromname.ptr);
89         mountDir = fromStringz(buf.f_mntonname.ptr);
90         return true;
91     }
92 }
93 
94 version(CRuntime_Glibc)
95 {
96 private:
97     import core.stdc.stdio : FILE;
98     struct mntent
99     {
100         char *mnt_fsname;   /* Device or server for filesystem.  */
101         char *mnt_dir;      /* Directory mounted on.  */
102         char *mnt_type;     /* Type of filesystem: ufs, nfs, etc.  */
103         char *mnt_opts;     /* Comma-separated options for fs.  */
104         int mnt_freq;       /* Dump frequency (in days).  */
105         int mnt_passno;     /* Pass number for `fsck'.  */
106     };
107 
108     extern(C) @nogc nothrow
109     {
110         FILE *setmntent(const char *file, const char *mode);
111         mntent *getmntent(FILE *stream);
112         mntent *getmntent_r(FILE * stream, mntent *result, char * buffer, int bufsize);
113         int addmntent(FILE* stream, const mntent *mnt);
114         int endmntent(FILE * stream);
115         char *hasmntopt(const mntent *mnt, const char *opt);
116     }
117 
118     @safe string decodeLabel(string label) nothrow pure
119     {
120         import std.string : replace;
121         import std.conv : to;
122         string res;
123         res.reserve(label.length);
124         for(size_t i = 0; i<label.length; ++i) {
125             if (label[i] == '\\' && label.length > i+4 && label[i+1] == 'x') {
126                 try {
127                     const code = to!ubyte(label[i+2..i+4], 16);
128                     if (code >= 0x20 && code < 0x80) {
129                         res ~= cast(char)code;
130                         i+=3;
131                         continue;
132                     }
133                 } catch(Exception e) {
134 
135                 }
136             }
137             res ~= label[i];
138         }
139         return res;
140     }
141 
142     unittest
143     {
144         assert(decodeLabel("Label\\x20space") == "Label space");
145         assert(decodeLabel("Label\\x5Cslash") == "Label\\slash");
146         assert(decodeLabel("Label") == "Label");
147         assert(decodeLabel("\\xNO") == "\\xNO");
148     }
149 
150     @trusted string retrieveLabel(string fsName) nothrow {
151         import std.file : dirEntries, SpanMode, readLink;
152         import std.path : buildNormalizedPath, isAbsolute, baseName;
153         import std.exception : collectException;
154         enum byLabel = "/dev/disk/by-label";
155         if (fsName.isAbsolute) { // /dev/sd*
156             try {
157                 foreach(entry; dirEntries(byLabel, SpanMode.shallow))
158                 {
159                     string resolvedLink;
160                     if (entry.isSymlink && collectException(entry.readLink, resolvedLink) is null) {
161                         auto normalized = buildNormalizedPath(byLabel, resolvedLink);
162                         if (normalized == fsName)
163                             return entry.name.baseName.decodeLabel();
164                     }
165                 }
166             } catch(Exception e) {
167 
168             }
169         }
170         return string.init;
171     }
172 
173     unittest
174     {
175         assert(retrieveLabel("cgroup") == string.init);
176     }
177 
178     @trusted bool parseMntent(ref const mntent ent, out const(char)[] device, out const(char)[] mountDir, out const(char)[] type) nothrow {
179         import std.string : fromStringz;
180         device = fromStringz(ent.mnt_fsname);
181         mountDir = fromStringz(ent.mnt_dir);
182         type = fromStringz(ent.mnt_type);
183         return true;
184     }
185     @trusted bool parseMountsLine(const(char)[] line, out const(char)[] device, out const(char)[] mountDir, out const(char)[] type) nothrow {
186         import std.algorithm.iteration : splitter;
187         import std.string : representation;
188         auto splitted = splitter(line.representation, ' ');
189         if (!splitted.empty) {
190             device = cast(const(char)[])splitted.front;
191             splitted.popFront();
192             if (!splitted.empty) {
193                 mountDir = cast(const(char)[])splitted.front;
194                 splitted.popFront();
195                 if (!splitted.empty) {
196                     type = cast(const(char)[])splitted.front;
197                     return true;
198                 }
199             }
200         }
201         return false;
202     }
203 
204     unittest
205     {
206         const(char)[] device, mountDir, type;
207         parseMountsLine("/dev/sda2 /media/storage ext4 rw,noexec,relatime,errors=remount-ro,data=ordered 0 0", device, mountDir, type);
208         assert(device == "/dev/sda2");
209         assert(mountDir == "/media/storage");
210         assert(type == "ext4");
211     }
212 }
213 
214 /**
215  * Get mountpoint where the provided path resides on.
216  */
217 @trusted string volumePath(string path)
218 out(result) {
219     import std.path : isAbsolute;
220     if (result.length) {
221         assert(result.isAbsolute);
222     }
223 }
224 body {
225     if (path.length == 0)
226         return string.init;
227     import std.path : absolutePath;
228     path = path.absolutePath;
229     version(Posix) {
230         import core.sys.posix.sys.types;
231         import core.sys.posix.sys.stat;
232         import core.sys.posix.unistd;
233         import core.sys.posix.fcntl;
234         import std.path : dirName;
235         import std.string : toStringz;
236 
237         auto current = path;
238         stat_t currentStat;
239         if (stat(current.toStringz, &currentStat) != 0) {
240             return null;
241         }
242         stat_t parentStat;
243         while(current != "/") {
244             string parent = current.dirName;
245             if (lstat(parent.toStringz, &parentStat) != 0) {
246                 return null;
247             }
248             if (currentStat.st_dev != parentStat.st_dev) {
249                 return current;
250             }
251             current = parent;
252         }
253         return current;
254     } else version(Windows) {
255         const(wchar)* wpath = path.toUTF16z;
256         wchar[MAX_PATH+1] buf;
257         if (GetVolumePathName(wpath, buf.ptr, buf.length)) {
258             return buf[0..wcslen(buf.ptr)].toUTF8;
259         }
260         return string.init;
261     } else {
262         return string.init;
263     }
264 }
265 
266 private struct VolumeInfoImpl
267 {
268     enum Info : ushort {
269         Type = 1 << 0,
270         Device = 1 << 1,
271         Label = 1 << 2,
272         BytesAvailable = 1 << 3,
273         BytesTotal = 1 << 4,
274         BytesFree = 1 << 5,
275         ReadOnly = 1 << 6,
276         Ready = 1 << 7,
277         Valid = 1 << 8,
278     }
279 
280     @safe this(string path) nothrow {
281         import std.path : isAbsolute;
282         assert(path.isAbsolute);
283         this.path = path;
284     }
285     version(Posix) @safe this(string mountPoint, string device, string type) nothrow {
286         path = mountPoint;
287         if (device.length)
288             this.device = device;
289         if (type.length)
290             this.type = type;
291     }
292     version(FreeBSD) @safe this(string mountPoint, string device, string type, ref const(statfs_t) buf) nothrow {
293         this(mountPoint, device, type);
294         applyStatfs(buf);
295         ready = valid = true;
296     }
297 
298     BitFlags!Info retrieved;
299     bool _readOnly;
300     bool _ready;
301     bool _valid;
302 
303     string path;
304     string _device;
305     string _type;
306     string _label;
307 
308     long _bytesTotal = -1;
309     long _bytesFree = -1;
310     long _bytesAvailable = -1;
311 
312     @safe @property string device() nothrow {
313         retrieve(Info.Device);
314         return _device;
315     }
316     @safe @property void device(string dev) nothrow {
317         retrieved |= Info.Device;
318         _device = dev;
319     }
320     @safe @property string type() nothrow {
321         retrieve(Info.Type);
322         return _type;
323     }
324     @safe @property void type(string t) nothrow {
325         retrieved |= Info.Type;
326         _type = t;
327     }
328     @safe @property string label() nothrow {
329         retrieve(Info.Label);
330         return _label;
331     }
332     @safe @property void label(string name) nothrow {
333         retrieved |= Info.Label;
334         _label = name;
335     }
336 
337     @safe @property long bytesTotal() nothrow {
338         retrieve(Info.BytesTotal);
339         return _bytesTotal;
340     }
341     @safe @property void bytesTotal(long bytes) nothrow {
342         retrieved |= Info.BytesTotal;
343         _bytesTotal = bytes;
344     }
345     @safe @property long bytesFree() nothrow {
346         retrieve(Info.BytesFree);
347         return _bytesFree;
348     }
349     @safe @property void bytesFree(long bytes) nothrow {
350         retrieved |= Info.BytesFree;
351         _bytesFree = bytes;
352     }
353     @safe @property long bytesAvailable() nothrow {
354         retrieve(Info.BytesAvailable);
355         return _bytesAvailable;
356     }
357     @safe @property void bytesAvailable(long bytes) nothrow {
358         retrieved |= Info.BytesAvailable;
359         _bytesAvailable = bytes;
360     }
361     @safe @property bool readOnly() nothrow {
362         retrieve(Info.ReadOnly);
363         return _readOnly;
364     }
365     @safe @property void readOnly(bool rdOnly) nothrow {
366         retrieved |= Info.ReadOnly;
367         _readOnly = rdOnly;
368     }
369     @safe @property bool valid() nothrow {
370         import std.file : exists;
371         retrieve(Info.Valid);
372         return path.length && path.exists && _valid;
373     }
374     @safe @property bool valid(bool ok) nothrow {
375         retrieved |= Info.Valid;
376         _valid = ok;
377         return ok;
378     }
379     @safe @property bool ready() nothrow {
380         retrieve(Info.Ready);
381         return path.length && _ready;
382     }
383     @safe @property void ready(bool r) nothrow {
384         retrieved |= Info.Ready;
385         _ready = r;
386     }
387     @safe void refresh() nothrow {
388         retrieved = BitFlags!Info();
389     }
390 
391     version(Posix)
392     {
393         import core.sys.posix.sys.statvfs;
394         version(FreeBSD) {
395             alias statfs_t STATFS_T;
396             alias statfs STATFS;
397             alias MNT_RDONLY READONLY_FLAG;
398         } else {
399             alias statvfs_t STATFS_T;
400             alias statvfs STATFS;
401             alias FFlag.ST_RDONLY READONLY_FLAG;
402         }
403 
404         @trusted void retrieveVolumeInfo() nothrow {
405             import std.string : toStringz;
406             import std.exception : assumeWontThrow;
407 
408             STATFS_T buf;
409             const result = assumeWontThrow(STATFS(toStringz(path), &buf)) == 0;
410             ready = valid = result;
411             if (result)
412                 applyStatfs(buf);
413         }
414 
415         @safe void applyStatfs(ref const(STATFS_T) buf) nothrow {
416             version(FreeBSD) {
417                 bytesTotal = buf.f_bsize * buf.f_blocks;
418                 bytesFree = buf.f_bsize * buf.f_bfree;
419                 bytesAvailable = buf.f_bsize * buf.f_bavail;
420                 readOnly = (buf.f_flags & READONLY_FLAG) != 0;
421             } else {
422                 bytesTotal = buf.f_frsize * buf.f_blocks;
423                 bytesFree = buf.f_frsize * buf.f_bfree;
424                 bytesAvailable = buf.f_frsize * buf.f_bavail;
425                 readOnly = (buf.f_flag & READONLY_FLAG) != 0;
426             }
427         }
428     }
429 
430     version(Posix) @trusted void retrieveDeviceAndType() nothrow {
431         version(CRuntime_Glibc)
432         {
433             // we need to loop through all mountpoints again to find a type by path. Is there a faster way to get file system type?
434             try {
435                 import std.stdio : File;
436                 foreach(line; File("/proc/self/mounts", "r").byLine) {
437                     const(char)[] device, mountDir, type;
438                     if (parseMountsLine(line, device, mountDir, type)) {
439                         if (mountDir == path) {
440                             this.device = device.idup;
441                             this.type = type.idup;
442                             break;
443                         }
444                     }
445                 }
446             } catch(Exception e) {
447                 mntent ent;
448                 char[1024] buf;
449                 FILE* f = setmntent("/etc/mtab", "r");
450                 if (f is null)
451                     return;
452                 scope(exit) endmntent(f);
453                 while(getmntent_r(f, &ent, buf.ptr, cast(int)buf.length) !is null) {
454                     const(char)[] device, mountDir, type;
455                     parseMntent(ent, device, mountDir, type);
456                     if (mountDir == path) {
457                         this.device = device.idup;
458                         this.type = type.idup;
459                         break;
460                     }
461                 }
462             }
463         }
464         else version(FreeBSD)
465         {
466             import std.string : toStringz;
467             statfs_t buf;
468             const result = statfs(toStringz(path), &buf) == 0;
469             ready = valid = result;
470             if (result) {
471                 const(char)[] device, mountDir, type;
472                 parseStatfs(buf, device, mountDir, type);
473                 this.device = device.idup;
474                 this.type = type.idup;
475                 applyStatfs(buf);
476             }
477         }
478     }
479 
480     version(Windows) @trusted void retrieveVolumeInfo() nothrow {
481         const oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
482         scope(exit) SetErrorMode(oldmode);
483 
484         import std.exception : collectException;
485         const(wchar)* wpath;
486         if (collectException(path.toUTF16z, wpath) !is null) {
487             ready = valid = false;
488             return;
489         }
490         wchar[MAX_PATH+1] name;
491         wchar[MAX_PATH+1] fsType;
492         DWORD flags = 0;
493         const bool result = GetVolumeInformation(wpath,
494                                                    name.ptr, name.length,
495                                                    null, null,
496                                                    &flags,
497                                                    fsType.ptr, fsType.length) != 0;
498         if (!result) {
499             ready = false;
500             valid = GetLastError() == ERROR_NOT_READY;
501         } else {
502             try {
503                 this.type = fsType[0..wcslen(fsType.ptr)].toUTF8;
504                 this.label = name[0..wcslen(name.ptr)].toUTF8;
505             } catch(Exception e) {
506             }
507 
508             ready = true;
509             valid = true;
510             readOnly = (flags & FILE_READ_ONLY_VOLUME) != 0;
511         }
512     }
513 
514     version(Windows) @trusted void retrieveSizes() nothrow
515     {
516         const oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
517         scope(exit) SetErrorMode(oldmode);
518 
519         import std.exception : collectException;
520         const(wchar)* wpath;
521         if (collectException(path.toUTF16z, wpath) !is null)
522             return;
523         ULARGE_INTEGER bytesA, bytesF, bytesT;
524         ready = GetDiskFreeSpaceEx(wpath, &bytesA, &bytesT, &bytesF) != 0;
525         bytesAvailable = cast(long)bytesA.QuadPart;
526         bytesFree = cast(long)bytesF.QuadPart;
527         bytesTotal = cast(long)bytesT.QuadPart;
528 
529     }
530 
531     version(Windows) @trusted string retrieveGUID() nothrow
532     {
533         import std.exception : collectException;
534         import std.string : toStringz, fromStringz;
535         char[51] guidBuffer;
536         if (GetVolumeNameForVolumeMountPointA(path.toStringz, guidBuffer.ptr, guidBuffer.length))
537             return fromStringz(guidBuffer.ptr).idup;
538         return string.init;
539     }
540 
541     @trusted void retrieve(Info requested) nothrow {
542         if ((requested & retrieved) == BitFlags!Info(requested) || !path.length)
543             return;
544         with(Info)
545         {
546             version(Windows) {
547                 if (requested & (BitFlags!Info() | Ready | Valid | ReadOnly | Label | Type))
548                     retrieveVolumeInfo();
549                 if (requested & (BitFlags!Info() | BytesAvailable | BytesFree | BytesTotal))
550                     retrieveSizes();
551                 if (requested & (BitFlags!Info() | Device))
552                     device = retrieveGUID();
553             }
554             version(Posix) {
555                 if (requested & (BitFlags!Info() | Ready | Valid | ReadOnly | BytesAvailable | BytesFree | BytesTotal))
556                     retrieveVolumeInfo();
557                 if (requested & (BitFlags!Info() | Type | Device))
558                     retrieveDeviceAndType();
559             }
560             version(CRuntime_Glibc) {
561                 if (requested & (BitFlags!Info() | Label))
562                     label = retrieveLabel(device);
563             }
564         }
565     }
566 }
567 
568 /**
569  * Represents a filesystem volume. Provides information about mountpoint, filesystem type and storage size.
570  * All values except for $(D VolumeInfo.path) are retrieved on the first demand and then getting cached. Use $(D VolumeInfo.refresh) to refresh info.
571  */
572 struct VolumeInfo
573 {
574     /**
575      * Construct an object that gives information about volume on which the provided path is located.
576      * Params:
577      *  path = either root path of volume or any file or directory that resides on the volume.
578      */
579     @trusted this(string path) {
580         impl = RefCounted!VolumeInfoImpl(volumePath(path));
581     }
582     /// Root path of file system (mountpoint of partition).
583     @trusted @property string path() nothrow {
584         return impl.path;
585     }
586     /// Device string, e.g. /dev/sda on Linux and volume guid on Windows.
587     @trusted @property string device() nothrow {
588         return impl.device;
589     }
590     /**
591      * File system type, e.g. ext4 on Linux or NTFS on Windows.
592      */
593     @trusted @property string type() nothrow {
594         return impl.type;
595     }
596     /**
597      * Name of volume. Empty string if volume label could not be retrieved.
598      * In case the label is empty you may consider using the base name of volume path as a display name, possible in combination with type.
599      */
600     @trusted @property string label() nothrow {
601         return impl.label;
602     }
603     /**
604      * Total volume size.
605      * Returns: total volume size in bytes or -1 if could not determine the size.
606      */
607     @trusted @property long bytesTotal() nothrow {
608         return impl.bytesTotal;
609     }
610     /**
611      * Free space in a volume
612      * Note: This is size of free space in a volume, but actual free space available for the current user may be smaller.
613      * Returns: number of free bytes in a volume or -1 if could not determine the number.
614      * See_Also: $(D bytesAvailable)
615      */
616     @trusted @property long bytesFree() nothrow {
617         return impl.bytesFree;
618     }
619     /**
620      * Free space available for the current user.
621      * This is what most tools and GUI applications show as free space.
622      * Returns: number of free bytes available for the current user or -1 if could not determine the number.
623      */
624     @trusted @property long bytesAvailable() nothrow {
625         return impl.bytesAvailable;
626     }
627     /// Whether the referenced filesystem is marked as readonly.
628     @trusted @property bool readOnly() nothrow {
629         return impl.readOnly;
630     }
631     @safe string toString() {
632         import std.format;
633         return format("VolumeInfo(%s, %s)", path, type);
634     }
635     /// Whether the filesystem is ready for work.
636     @trusted @property bool ready() nothrow {
637         return impl.ready;
638     }
639     /// Whether the object is valid (specified path exists).
640     @trusted @property bool isValid() nothrow {
641         return impl.valid;
642     }
643     /// Refresh cached info.
644     @trusted void refresh() nothrow {
645         return impl.refresh();
646     }
647 private:
648     this(VolumeInfoImpl impl) {
649         this.impl = RefCounted!VolumeInfoImpl(impl);
650     }
651     RefCounted!VolumeInfoImpl impl;
652 }
653 
654 unittest
655 {
656     VolumeInfo info;
657     assert(info.path == "");
658     assert(info.type == "");
659     assert(info.device == "");
660     assert(info.label == "");
661     assert(info.bytesTotal < 0);
662     assert(info.bytesAvailable < 0);
663     assert(info.bytesFree < 0);
664     assert(!info.readOnly);
665     assert(!info.ready);
666     assert(!info.isValid);
667 }
668 
669 /**
670  * The list of currently mounted volumes.
671  */
672 VolumeInfo[] mountedVolumes() {
673     VolumeInfo[] res;
674     version(CRuntime_Glibc) {
675         try {
676             import std.stdio : File;
677 
678             foreach(line; File("/proc/self/mounts", "r").byLine) {
679                 const(char)[] device, mountDir, type;
680                 if (parseMountsLine(line, device, mountDir, type)) {
681                     if (!isSpecialFileSystem(mountDir, type)) {
682                         res ~= VolumeInfo(VolumeInfoImpl(mountDir.idup, device.idup, type.idup));
683                     }
684                 }
685             }
686         } catch(Exception e) {
687             res.length = 0;
688             res ~= VolumeInfo(VolumeInfoImpl("/"));
689 
690             mntent ent;
691             char[1024] buf;
692             FILE* f = setmntent("/etc/mtab", "r");
693             if (f is null)
694                 return res;
695 
696             scope(exit) endmntent(f);
697             while(getmntent_r(f, &ent, buf.ptr, cast(int)buf.length) !is null) {
698                 const(char)[] device, mountDir, type;
699                 parseMntent(ent, device, mountDir, type);
700 
701                 if (mountDir == "/" || isSpecialFileSystem(mountDir, type))
702                     continue;
703 
704                 res ~= VolumeInfo(VolumeInfoImpl(mountDir.idup, device.idup, type.idup));
705             }
706         }
707     }
708     else version(FreeBSD) {
709         import std.string : fromStringz;
710         res ~= VolumeInfo(VolumeInfoImpl("/"));
711 
712         statfs_t* mntbufsPtr;
713         int mntbufsLen = getmntinfo(&mntbufsPtr, 0);
714         if (mntbufsLen) {
715             auto mntbufs = mntbufsPtr[0..mntbufsLen];
716 
717             foreach(buf; mntbufs) {
718                 const(char)[] device, mountDir, type;
719                 parseStatfs(buf, device, mountDir, type);
720 
721                 if (mountDir == "/" || isSpecialFileSystem(mountDir, type))
722                     continue;
723 
724                 res ~= VolumeInfo(VolumeInfoImpl(mountDir.idup, device.idup, type.idup, buf));
725             }
726         }
727     }
728     else version(Posix) {
729         res ~= VolumeInfo(VolumeInfoImpl("/"));
730     }
731 
732     version (Windows) {
733         const oldmode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
734         scope(exit) SetErrorMode(oldmode);
735         const uint mask = GetLogicalDrives();
736         foreach(int i; 0 .. 26) {
737             if (mask & (1 << i)) {
738                 const char letter = cast(char)('A' + i);
739                 string path = letter ~ ":\\";
740                 res ~= VolumeInfo(VolumeInfoImpl(path));
741             }
742         }
743     }
744     return res;
745 }