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 }