14
$\begingroup$

I'm using Mathematica 9.0.1 on Windows XP and my drive letters are assigned as follows: C: and D: are hard local disks, while E: is assigned to some removable media. Now, the situation is as follows

DirectoryQ[# <> ":\\"] & /@ {"c", "d", "e"}

returns {True,True,True}, even when an optical disk isn't in the drive.

To outflank the problem I use a directoryQ function, defined in the code below.

However, it obviously fails against a full hard disk or when I don't have write access. Therefore, I' m wondering about the kind of check DirectoryQ basically performs. Is there a better solution to fix the problem?

   directoryQ[unit_]:=Module[
       {root,file,ret},
       ret=False;
       root=unit<>":\\";
       file = FileNameJoin[{root, "testFile.txt"}];
       If[
         DirectoryQ[root],
         Quiet[Export[file,"test","Text"]];
         If[
           FileExistsQ[file],
           DeleteFile[file];
           ret=True
         ];
       ];
       Return[ret];
];

directoryQ["c"]
directoryQ["e"]
$\endgroup$
2
  • 1
    $\begingroup$ I don't have windows, but I like this question. I did some slight edits on your text to make it more conform with the site. What I can tell you from Linux, Unix and Mac is that disks or USB drives are always mounted to an existing directory (usually, some subfolder is created). Even my second (built-in) hard drive has a fixed mountpoint which is /home, a directory. It doesn't matter whether the hard disk is there or not. $\endgroup$
    – halirutan
    Apr 10, 2014 at 21:23
  • 1
    $\begingroup$ Is the problem just that DirectoryQ returns the right answer except in the one case that its argument is a root directory of a drive that isn't mounted? In that case, you could try verifying the result by (1) checking if the path is a root directory (maybe using FileNameSplit?), then (2) calling SetDirectory or FileExistsQ to see if you can actually go there. $\endgroup$
    – librik
    Apr 11, 2014 at 5:46

3 Answers 3

18
$\begingroup$

Since Mathematica is written in the C programming language, it is likely that any file-system status operation is doing nothing more than calling C's built-in functions. In this case, that would be stat.

So let's test what stat does on Windows. According to the C language documentation, stat is given the name of something in the file system, and returns a C structure whose fields describe the properties of that thing. In particular, the st_mode field seems relevant:

st_mode: Bit mask for file-mode information. The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit is set if path specifies an ordinary file or a device. User read/write bits are set according to the file's permission mode; user execute bits are set according to the filename extension.

I wrote a simple test program in Microsoft Visual C that tests the stat function. Drive C: is my Windows system drive; drive E: is my DVD drive, which currently doesn't have anything in it. Here's what I found:

stat("C:\\WINDOWS") returns success and sets _S_IFDIR
stat("C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE") returns success and does not set _S_IFDIR
stat("C:\\") returns success and sets _S_IFDIR
stat("E:\\") returns success and sets _S_IFDIR
stat("W:\\") returns failure

So we can conclude from this that Windows, or at least the stat function on Windows, considers empty DVD drives to be "there." I also have a flash memory stick that I can plug into a USB slot, which I have mapped to drive W, but that "isn't there" when it's unplugged.

This is consistent with what Windows Explorer shows me when I click on "My Computer". I see drive C: with a name, and I see drive E:, even though I get an error message when I double-click on it. I don't see drive W: (my flash memory stick) after I pulled it out of the socket. So Windows does consider an empty DVD drive to be present, which is probably why stat says it's a valid directory.

I have no idea how to find out whether a DVD drive is full or empty in C, much less in Mathematica. But at least we can see the core of the problem.

$\endgroup$
4
  • 3
    $\begingroup$ I really like your detailed answers and extensive knowledge of low-level I/O. +1 $\endgroup$
    – halirutan
    Apr 11, 2014 at 0:30
  • $\begingroup$ I fully agree with halirutan. I only wish you posted more often. :-) $\endgroup$
    – Mr.Wizard
    Apr 11, 2014 at 1:03
  • 1
    $\begingroup$ Using strace, I can confirm that on Linux at least, DirectoryQ does simply stat the input. $\endgroup$
    – 0xFE
    Apr 11, 2014 at 4:04
  • $\begingroup$ Admirable answer ! Please, can you share your test program's source code ? $\endgroup$ Apr 11, 2014 at 9:25
7
$\begingroup$

This response shows how to obtain more detailed information about directories (and files) using JLink, NETLink and the Win32 API. Such information can be used to determine if a device is offline, non-writable, etc. The various methods differ in how much information they return.

JLink

The Java 6 File class can be used to retrieve many file system attributes on any operating system supported by Mathematica:

Needs["JLink`"]
InstallJava[];

javaFileAttributes[path_String] :=
  JavaBlock @ Module[{p, file = JavaNew["java.io.File", path]}
  , Cases[{canExecute, canRead, canWrite, exists, isDirectory, isFile, isHidden}
    , p_ /; file@p[]
    ]
  ]

Example use (d:\ is an offline drive):

Table[{p, javaFileAttributes@p}
, {p, {"c:\\", "d:\\", "c:\\temp", "c:\\pagefile.sys", "c:\\bootsect.bak"}}
] // Grid

(*
c:\             {canExecute,canRead,canWrite,exists,isDirectory,isHidden}
d:\             {}
c:\temp         {canExecute,canRead,canWrite,exists,isDirectory}
c:\pagefile.sys {exists,isFile}
c:\bootsect.bak {canExecute,canRead,exists,isFile,isHidden}
*)

NETLink

On Windows, or a system running Mono, we can perform a similar operation using the .NET method File.GetFileAttributes:

Needs["NETLink`"]
InstallNET[];

LoadNETType["System.IO.File", AllowShortContext -> False];
LoadNETType["System.IO.FileAttributes", AllowShortContext -> False];

dotNetException[] := dotNetException[GetNETException[]]
dotNetException[e_] := Module[{i = e@InnerException[]}, dotNetException[i] /; i =!= Null]
dotNetException[e_] := $Failed@#&[e@Message // StringTrim]

dotNetFileAttributes[path_String] :=
  NETBlock @ Module[{p, file = Quiet@System`IO`File`GetAttributes[path]}
  , file /.
    { f_?NETObjectQ :> StringSplit[f@ToString[], (Whitespace|",")..]
    , $Failed :> dotNetException[]
    }
  ]

Example use (d:\ is an offline drive):

Table[{p, dotNetFileAttributes@p}
, {p, {"c:\\", "d:\\", "c:\\temp", "c:\\pagefile.sys", "c:\\bootsect.bak"}}
] // Grid

(*
c:\             {Hidden,System,Directory}
d:\             $Failed[The device is not ready.]
c:\temp         {Directory}
c:\pagefile.sys {Hidden,System,Archive}
c:\bootsect.bak {ReadOnly,Hidden,System,Archive}
*)

Win32 API through NETLink

On Windows, we can access the Windows API function GetFileAttributes through NETLink:

Needs["NETLink`"]
InstallNET[]

kernel32GetLastError =
  DefineDLLFunction["GetLastError", "kernel32.dll", "DWORD", {}];

kernel32GetFileAttributes =
  DefineDLLFunction["GetFileAttributes", "kernel32.dll", "DWORD", {"string"}];

windowsError[] :=
  kernel32GetLastError[] /.
    { 2 -> "FILE_NOT_FOUND", 3 -> "PATH_NOT_FOUND", 5 -> "ACCESS_DENIED"
    , 15 -> "INVALID_DRIVE", 21 -> "NOT_READY", 32 -> "SHARING_VIOLATION"
    , n_ :> "Error code: "~~ToString[n]
    }

$fileAttributes =
 { {"ARCHIVE", 32}, {"COMPRESSED", 2048}, {"DEVICE", 64}, {"DIRECTORY", 16}
 , {"ENCRYPTED", 16384}, {"HIDDEN", 2}, {"INTEGRITY_STREAM", 32768}
 , {"NORMAL", 128}, {"NOT_CONTENT_INDEXED", 8192}, {"NO_SCRUB_DATA", 131072}
 , {"OFFLINE", 4096}, {"READONLY", 1}, {"REPARSE_POINT", 1024}, {"SPARSE_FILE", 512}
 , {"SYSTEM", 4}, {"TEMPORARY", 256}, {"VIRTUAL", 65536}
 };

windowsFileAttributes[path_String] :=
  kernel32GetFileAttributes[path] /.
    { -1 :> { $Failed@#&@windowsError[] }
        , a_ :> Cases[$fileAttributes, {n_, v_} /; BitAnd[v, a] != 0 :> n]
    }

Example use (d:\ is an offline drive):

Table[{p, windowsFileAttributes@p}
, {p, {"c:\\", "d:\\", "c:\\temp", "c:\\pagefile.sys", "c:\\bootsect.bak"}}
] // Grid

(*
    c:\             {DIRECTORY,HIDDEN,SYSTEM}
    d:\             {$Failed[NOT_READY]}
    c:\temp         {DIRECTORY}
    c:\pagefile.sys {$Failed[SHARING_VIOLATION]}
    c:\bootsect.bak {ARCHIVE,HIDDEN,READONLY,SYSTEM}
*)
$\endgroup$
1
$\begingroup$

I'm extremely grateful to halirutan for repairing my message ! I will try to bother as less I can.

Following librik's guidance I wrote two functions:

directoryQversionA[unit_]:=Module[
{root,file,ret},
   ret=True;
   root=unit<>":\\";
   Quiet[Check[SetDirectory[root],ret=False]];
Return[ret];
];

directoryQversionB[unit_]:=Module[
{root,file,ret},ret=False;
   root=unit<>":\\";
   ret=FileExistsQ[root];
   Return[ret];
];

This works as expected

directoryQversionA/@{"c","z"}
directoryQversionB/@{"c","z"}

Regarding the removable media, using

directoryQversionA["e"]
directoryQversionB["e"]

I get True when a support is inside and False when it isn't.

As far as I can see,the problem who drove me to ask the question is solved on my operating system: I will check on more recent versions.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.