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