1 /******************************************************************************* 2 3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved 4 5 license: BSD: 6 AFL 3.0: 7 8 version: Mar 2004: Initial release 9 version: Feb 2007: Now using mutating paths 10 11 authors: Kris, Chris Sauls (Win95 file support) 12 13 *******************************************************************************/ 14 15 module tango.io.FileSystem; 16 17 private import tango.sys.Common; 18 19 private import tango.io.FilePath; 20 21 private import tango.core.Exception; 22 23 private import tango.io.Path : standard, native; 24 25 /******************************************************************************* 26 27 *******************************************************************************/ 28 29 version (Win32) 30 { 31 private import Text = tango.text.Util; 32 private extern (Windows) DWORD GetLogicalDriveStringsA (DWORD, LPSTR); 33 private import tango.stdc.stringz : fromString16z, fromStringz; 34 35 enum { 36 FILE_DEVICE_DISK = 7, 37 IOCTL_DISK_BASE = FILE_DEVICE_DISK, 38 METHOD_BUFFERED = 0, 39 FILE_READ_ACCESS = 1 40 } 41 uint CTL_CODE(uint t, uint f, uint m, uint a) { 42 return (t << 16) | (a << 14) | (f << 2) | m; 43 } 44 45 const IOCTL_DISK_GET_LENGTH_INFO = CTL_CODE(IOCTL_DISK_BASE,0x17,METHOD_BUFFERED,FILE_READ_ACCESS); 46 } 47 48 version (Posix) 49 { 50 private import tango.stdc.string; 51 private import tango.stdc.posix.unistd, 52 tango.stdc.posix.sys.statvfs; 53 54 private import tango.io.device.File; 55 private import Integer = tango.text.convert.Integer; 56 } 57 58 /******************************************************************************* 59 60 Models an OS-specific file-system. Included here are methods to 61 manipulate the current working directory, and to convert a path 62 to its absolute form. 63 64 *******************************************************************************/ 65 66 struct FileSystem 67 { 68 /*********************************************************************** 69 70 ***********************************************************************/ 71 72 private static void exception (string msg) 73 { 74 throw new IOException (msg); 75 } 76 77 /*********************************************************************** 78 79 Windows specifics 80 81 ***********************************************************************/ 82 83 version (Windows) 84 { 85 /*************************************************************** 86 87 private helpers 88 89 ***************************************************************/ 90 91 version (Win32SansUnicode) 92 { 93 private static void windowsPath(const(char)[] path, ref char[] result) 94 { 95 result[0..path.length] = path; 96 result[path.length] = 0; 97 } 98 } 99 else 100 { 101 private static void windowsPath(const(char)[] path, ref wchar[] result) 102 { 103 assert (path.length < result.length); 104 auto i = MultiByteToWideChar (CP_UTF8, 0, 105 cast(PCHAR)path.ptr, 106 path.length, 107 result.ptr, result.length); 108 result[i] = 0; 109 } 110 } 111 112 /*************************************************************** 113 114 Set the current working directory 115 116 deprecated: see Environment.cwd() 117 118 ***************************************************************/ 119 120 deprecated static void setDirectory (const(char)[] path) 121 { 122 version (Win32SansUnicode) 123 { 124 char[MAX_PATH+1] tmp = void; 125 tmp[0..path.length] = path; 126 tmp[path.length] = 0; 127 128 if (! SetCurrentDirectoryA (tmp.ptr)) 129 exception ("Failed to set current directory"); 130 } 131 else 132 { 133 // convert into output buffer 134 wchar[MAX_PATH+1] tmp = void; 135 assert (path.length < tmp.length); 136 auto i = MultiByteToWideChar (CP_UTF8, 0, 137 cast(PCHAR)path.ptr, path.length, 138 tmp.ptr, tmp.length); 139 tmp[i] = 0; 140 141 if (! SetCurrentDirectoryW (tmp.ptr)) 142 exception ("Failed to set current directory"); 143 } 144 } 145 146 /*************************************************************** 147 148 Return the current working directory 149 150 deprecated: see Environment.cwd() 151 152 ***************************************************************/ 153 154 deprecated static char[] getDirectory () 155 { 156 char[] path; 157 158 version (Win32SansUnicode) 159 { 160 int len = GetCurrentDirectoryA (0, null); 161 auto dir = new char [len]; 162 GetCurrentDirectoryA (len, dir.ptr); 163 if (len) 164 { 165 dir[len-1] = '/'; 166 path = standard (dir); 167 } 168 else 169 exception ("Failed to get current directory"); 170 } 171 else 172 { 173 wchar[MAX_PATH+2] tmp = void; 174 175 auto len = GetCurrentDirectoryW (0, null); 176 assert (len < tmp.length); 177 auto dir = new char [len * 3]; 178 GetCurrentDirectoryW (len, tmp.ptr); 179 auto i = WideCharToMultiByte (CP_UTF8, 0, tmp.ptr, len, 180 cast(PCHAR)dir.ptr, dir.length, null, null); 181 if (len && i) 182 { 183 path = standard (dir[0..i]); 184 path[$-1] = '/'; 185 } 186 else 187 exception ("Failed to get current directory"); 188 } 189 190 return path; 191 } 192 193 /*************************************************************** 194 195 List the set of root devices (C:, D: etc) 196 197 ***************************************************************/ 198 199 @property static char[][] roots () 200 { 201 int len; 202 char[] str; 203 char[][] roots; 204 205 // acquire drive strings 206 len = GetLogicalDriveStringsA (0, null); 207 if (len) 208 { 209 str = new char [len]; 210 GetLogicalDriveStringsA (len, cast(PCHAR)str.ptr); 211 212 // split roots into seperate strings 213 roots = Text.delimit (str [0 .. $-1], "\0"); 214 } 215 return roots; 216 } 217 218 private enum { 219 volumePathBufferLen = MAX_PATH + 6 220 } 221 222 private static TCHAR[] getVolumePath(const(char)[] folder, TCHAR[] volPath_, 223 bool trailingBackslash) 224 in { 225 assert (volPath_.length > 5); 226 } body { 227 version (Win32SansUnicode) { 228 alias GetVolumePathNameA GetVolumePathName; 229 alias fromStringz fromStringzT; 230 } 231 else { 232 alias GetVolumePathNameW GetVolumePathName; 233 alias fromString16z fromStringzT; 234 } 235 236 // convert to (w)stringz 237 TCHAR[MAX_PATH+2] tmp_ = void; 238 TCHAR[] tmp = tmp_; 239 windowsPath(folder, tmp); 240 241 // we'd like to open a volume 242 volPath_[0..4] = `\\.\`w; 243 244 if (!GetVolumePathName(tmp.ptr, volPath_.ptr+4, volPath_.length-4)) 245 exception ("GetVolumePathName failed"); 246 247 TCHAR[] volPath; 248 249 // the path could have the volume/network prefix already 250 if (volPath_[4..6] != `\\`) { 251 volPath = fromStringzT(volPath_.ptr); 252 } else { 253 volPath = fromStringzT(volPath_[4..$].ptr); 254 } 255 256 // GetVolumePathName returns a path with a trailing backslash 257 // some winapi functions want that backslash, some don't 258 if ('\\' == volPath[$-1] && !trailingBackslash) { 259 volPath[$-1] = '\0'; 260 } 261 262 return volPath; 263 } 264 265 /*************************************************************** 266 267 Request how much free space in bytes is available on the 268 disk/mountpoint where folder resides. 269 270 If a quota limit exists for this area, that will be taken 271 into account unless superuser is set to true. 272 273 If a user has exceeded the quota, a negative number can 274 be returned. 275 276 Note that the difference between total available space 277 and free space will not equal the combined size of the 278 contents on the file system, since the numbers for the 279 functions here are calculated from the used blocks, 280 including those spent on metadata and file nodes. 281 282 If actual used space is wanted one should use the 283 statistics functionality of tango.io.vfs. 284 285 See also: totalSpace() 286 287 Since: 0.99.9 288 289 ***************************************************************/ 290 291 static long freeSpace(const(char)[] folder, bool superuser = false) 292 { 293 scope fp = new FilePath(folder.dup); 294 295 const bool wantTrailingBackslash = true; 296 TCHAR[volumePathBufferLen] volPathBuf; 297 auto volPath = getVolumePath(fp.native.toString(), volPathBuf, wantTrailingBackslash); 298 299 version (Win32SansUnicode) { 300 alias GetDiskFreeSpaceExA GetDiskFreeSpaceEx; 301 } else { 302 alias GetDiskFreeSpaceExW GetDiskFreeSpaceEx; 303 } 304 305 ULARGE_INTEGER free, totalFree; 306 GetDiskFreeSpaceEx(volPath.ptr, &free, null, &totalFree); 307 return cast(long) (superuser ? totalFree : free).QuadPart; 308 } 309 310 /*************************************************************** 311 312 Request how large in bytes the 313 disk/mountpoint where folder resides is. 314 315 If a quota limit exists for this area, then 316 that quota can be what will be returned unless superuser 317 is set to true. On Posix systems this distinction is not 318 made though. 319 320 NOTE Access to this information when _superuser is 321 set to true may only be available if the program is 322 run in superuser mode. 323 324 See also: freeSpace() 325 326 Since: 0.99.9 327 328 ***************************************************************/ 329 330 static ulong totalSpace(const(char)[] folder, bool superuser = false) 331 { 332 version (Win32SansUnicode) { 333 alias GetDiskFreeSpaceExA GetDiskFreeSpaceEx; 334 alias CreateFileA CreateFile; 335 } else { 336 alias GetDiskFreeSpaceExW GetDiskFreeSpaceEx; 337 alias CreateFileW CreateFile; 338 } 339 340 scope fp = new FilePath(folder.dup); 341 342 bool wantTrailingBackslash = (false == superuser); 343 TCHAR[volumePathBufferLen] volPathBuf; 344 auto volPath = getVolumePath(fp.native.toString(), volPathBuf, wantTrailingBackslash); 345 346 if (superuser) { 347 struct GET_LENGTH_INFORMATION { 348 LARGE_INTEGER Length; 349 } 350 GET_LENGTH_INFORMATION lenInfo; 351 DWORD numBytes; 352 OVERLAPPED overlap; 353 354 HANDLE h = CreateFile( 355 volPath.ptr, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 356 null, OPEN_EXISTING, 0, null 357 ); 358 359 if (h == INVALID_HANDLE_VALUE) { 360 exception ("Failed to open volume for reading"); 361 } 362 363 if (0 == DeviceIoControl( 364 h, IOCTL_DISK_GET_LENGTH_INFO, null , 0, 365 cast(void*)&lenInfo, lenInfo.sizeof, &numBytes, &overlap 366 )) { 367 exception ("IOCTL_DISK_GET_LENGTH_INFO failed:" ~ SysError.lastMsg.idup); 368 } 369 370 return cast(ulong)lenInfo.Length.QuadPart; 371 } 372 else { 373 ULARGE_INTEGER total; 374 GetDiskFreeSpaceEx(volPath.ptr, null, &total, null); 375 return cast(ulong)total.QuadPart; 376 } 377 } 378 } 379 380 /*********************************************************************** 381 382 ***********************************************************************/ 383 384 version (Posix) 385 { 386 /*************************************************************** 387 388 Set the current working directory 389 390 deprecated: see Environment.cwd() 391 392 ***************************************************************/ 393 394 deprecated static void setDirectory (const(char)[] path) 395 { 396 char[512] tmp = void; 397 tmp [path.length] = 0; 398 tmp[0..path.length] = path[]; 399 400 if (tango.stdc.posix.unistd.chdir (tmp.ptr)) 401 exception ("Failed to set current directory"); 402 } 403 404 /*************************************************************** 405 406 Return the current working directory 407 408 deprecated: see Environment.cwd() 409 410 ***************************************************************/ 411 412 deprecated static char[] getDirectory () 413 { 414 char[512] tmp = void; 415 416 char *s = tango.stdc.posix.unistd.getcwd (tmp.ptr, tmp.length); 417 if (s is null) 418 exception ("Failed to get current directory"); 419 420 auto path = s[0 .. strlen(s)+1]; 421 path[$-1] = '/'; 422 return path; 423 } 424 425 /*************************************************************** 426 427 List the set of root devices. 428 429 ***************************************************************/ 430 431 @property static char[][] roots () 432 { 433 version(OSX) 434 { 435 assert(0); 436 } 437 else 438 { 439 char[] path; 440 char[][] list; 441 int spaces; 442 443 auto fc = new File("/etc/mtab"); 444 scope (exit) 445 fc.close(); 446 447 auto content = new char[cast(size_t)fc.length]; 448 fc.input.read (content); 449 450 for(int i = 0; i < content.length; i++) 451 { 452 if(content[i] == ' ') spaces++; 453 else if(content[i] == '\n') 454 { 455 spaces = 0; 456 list ~= path; 457 path.length = 0; 458 } 459 else if(spaces == 1) 460 { 461 if(content[i] == '\\') 462 { 463 path ~= cast(char)Integer.parse(content[++i..i+3], 8u); 464 i += 2; 465 } 466 else path ~= content[i]; 467 } 468 } 469 470 return list; 471 } 472 } 473 474 /*************************************************************** 475 476 Request how much free space in bytes is available on the 477 disk/mountpoint where folder resides. 478 479 If a quota limit exists for this area, that will be taken 480 into account unless superuser is set to true. 481 482 If a user has exceeded the quota, a negative number can 483 be returned. 484 485 Note that the difference between total available space 486 and free space will not equal the combined size of the 487 contents on the file system, since the numbers for the 488 functions here are calculated from the used blocks, 489 including those spent on metadata and file nodes. 490 491 If actual used space is wanted one should use the 492 statistics functionality of tango.io.vfs. 493 494 See also: totalSpace() 495 496 Since: 0.99.9 497 498 ***************************************************************/ 499 500 static long freeSpace(const(char)[] folder, bool superuser = false) 501 { 502 scope fp = new FilePath(folder.dup); 503 statvfs_t info; 504 int res = statvfs(fp.native.cString().ptr, &info); 505 if (res == -1) 506 exception ("freeSpace->statvfs failed:" 507 ~ SysError.lastMsg.idup); 508 509 if (superuser) 510 return cast(long)info.f_bfree * cast(long)info.f_bsize; 511 else 512 return cast(long)info.f_bavail * cast(long)info.f_bsize; 513 } 514 515 /*************************************************************** 516 517 Request how large in bytes the 518 disk/mountpoint where folder resides is. 519 520 If a quota limit exists for this area, then 521 that quota can be what will be returned unless superuser 522 is set to true. On Posix systems this distinction is not 523 made though. 524 525 NOTE Access to this information when _superuser is 526 set to true may only be available if the program is 527 run in superuser mode. 528 529 See also: freeSpace() 530 531 Since: 0.99.9 532 533 ***************************************************************/ 534 535 static long totalSpace(const(char)[] folder, bool superuser = false) 536 { 537 scope fp = new FilePath(folder.dup); 538 statvfs_t info; 539 int res = statvfs(fp.native.cString().ptr, &info); 540 if (res == -1) 541 exception ("totalSpace->statvfs failed:" 542 ~ SysError.lastMsg.idup); 543 544 return cast(long)info.f_blocks * cast(long)info.f_frsize; 545 } 546 } 547 } 548 549 550 /****************************************************************************** 551 552 ******************************************************************************/ 553 554 debug (FileSystem) 555 { 556 import tango.io.Stdout; 557 558 static void foo (FilePath path) 559 { 560 Stdout("all: ") (path).newline; 561 Stdout("path: ") (path.path).newline; 562 Stdout("file: ") (path.file).newline; 563 Stdout("folder: ") (path.folder).newline; 564 Stdout("name: ") (path.name).newline; 565 Stdout("ext: ") (path.ext).newline; 566 Stdout("suffix: ") (path.suffix).newline.newline; 567 } 568 569 void main() 570 { 571 Stdout.formatln ("dir: {}", FileSystem.getDirectory); 572 573 auto path = new FilePath ("."); 574 foo (path); 575 576 path.set (".."); 577 foo (path); 578 579 path.set ("..."); 580 foo (path); 581 582 path.set (r"/x/y/.file"); 583 foo (path); 584 585 path.suffix = ".foo"; 586 foo (path); 587 588 path.set ("file.bar"); 589 path.absolute("c:/prefix"); 590 foo(path); 591 592 path.set (r"arf/test"); 593 foo(path); 594 path.absolute("c:/prefix"); 595 foo(path); 596 597 path.name = "foo"; 598 foo(path); 599 600 path.suffix = ".d"; 601 path.name = path.suffix; 602 foo(path); 603 604 } 605 }