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