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, ¤tStat) != 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 }