delphi directoryexists function odd behaviour for network mapped units
Asked Answered
C

3

6

In delphi XE, when I call SysUtils DirectoryExists function with the following input

'Y:\blabla\'

where Y is a network mapped unit, it correctly returns false because blabla doesnt exist.

But when I call with the following input

'Y:\blabla\Y:\bla'

it returns true.

Documentation is poor, and I've not found anywhere on the internets people with the same problem

maybe someone here already had this problem, or know what is happening?

Callery answered 1/3, 2013 at 20:34 Comment(0)
Y
6

It seems a bug in the implementation of the DirectoryExists function.

This is the relevant code of this function

function DirectoryExists(const Directory: string; FollowLink: Boolean = True): Boolean;
{$IFDEF MSWINDOWS}
var
  Code: Cardinal;
  Handle: THandle;
  LastError: Cardinal;
begin
  Result := False;
  Code := GetFileAttributes(PChar(Directory));

  if Code <> INVALID_FILE_ATTRIBUTES then
  begin
    ...
    //more code
    ...
  end
  else
  begin
    LastError := GetLastError;
    Result := (LastError <> ERROR_FILE_NOT_FOUND) and
      (LastError <> ERROR_PATH_NOT_FOUND) and
      (LastError <> ERROR_INVALID_NAME) and
      (LastError <> ERROR_BAD_NETPATH);
  end;
end;
{$ENDIF MSWINDOWS}

As you see if the GetFileAttributes function call fails, the result of the GetLastError method is compared against a set of possible values. but in your case passing a invalid path will return a ERROR_BAD_PATHNAME (161) code, so the function returns True.

York answered 1/3, 2013 at 21:10 Comment(10)
+1. This has been fixed in XE3, according to a quick test using the same path as in the original question. I don't know what version the first fix was made in, though; I'd suspect XE2, because of the XPlatform stuff that went in there.Prompter
@KenWhite, maybe the bug was fixed in XE3 upd1, because in XE3 is still present.York
The code appears the same, but actually running a test returns False as it should. I didn't step through the code to see what was different in the behavior; I just added a quick line to a console app I was using for something else and ran it.Prompter
In XE3 up1 the code also checks against ERROR_NOT_READY. But when I run the code, I get LastError of ERROR_INVALID_NAME, or ERROR_PATH_NOT_FOUND if Y: is not mapped. I've not seen ERROR_BAD_PATHNAME. Which OS are you using @RRUZ? It seems that there should be a QC report submitted once we can corner this bug.Foeman
@DavidHeffernan, I'm using Win7 x64, this DirectoryExists('Y:\blabla\Y:\bla') returns true, but this DirectoryExists('Y:\blabla') returns false.York
Win7 x64 either. But I cannot get ERROR_BAD_PATHNAME. Odd.Foeman
@DavidHeffernan, do you create a Y: network drive and then test the code?York
No, I used C: for my test. Does it have to be a network volume?Foeman
@DavidHeffernan, Yes the issue is replicable in a network drive.York
Right. I've just reproduced on my work computer using one of the mapped network volumes. Would you like to submit the QC report?Foeman
G
1

I have the same problem with mapped path to drive (Y:).

The same problem with full path \\IP\dir\dir

So at now I using not good, but working function:

function FolderExists(const BasePath: WideString): boolean;
const
  FIND_FIRST_EX_LARGE_FETCH          = $00000002;
  FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY = $00000004;
var
  FindExHandle    : THandle;
  Win32FindData   : TWin32FindDataW;
  FindExInfoLevels: TFindexInfoLevels;
  FindExSearchOps : TFindexSearchOps;
  AdditionalFlags : DWORD;
  i, ii           : Integer;
  folderTime: TDateTime;
begin
  result := false;
  FindExInfoLevels := _FINDEX_INFO_LEVELS.FindExInfoBasic;
  FindExSearchOps  := _FINDEX_SEARCH_OPS.FindExSearchLimitToDirectories;
  AdditionalFlags  := FIND_FIRST_EX_LARGE_FETCH or FIND_FIRST_EX_ON_DISK_ENTRIES_ONLY;
  FindExHandle     := Winapi.Windows.FindFirstFileExW(PWideChar(IncludeTrailingBackslash(TDirectory.GetParent(ExcludeTrailingPathDelimiter(BasePath))) + '*.*' ), FindExInfoLevels, @Win32FindData, FindExSearchOps, nil ,AdditionalFlags);
  if (FindExHandle <> INVALID_HANDLE_VALUE) then
    repeat
      if ((Win32FindData.cFileName = ExtractFileName(ExcludeTrailingPathDelimiter(BasePath))) and (0 <> (Win32FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY))) then begin
        result := true;
        break;
      end;
    until not Winapi.Windows.FindNextFileW(FindExHandle, Win32FindData);
  Winapi.Windows.FindClose(FindExHandle);
end;

Function works fine with full paths like \\IP\dir\dir

Yes, function using not optimal algorithm :) (optimistic: ON) but it works

Griselgriselda answered 6/11, 2023 at 11:50 Comment(0)
J
0

The bug is still there in XE8 (and probably in other versions too). As pointed out above by RRUZ it lies in the SysUtils implementations of BOTH DirectoryExists() and TDirectory.Exists().

The problem is with that long list of "checks" that assumes that they are the only valid reasons that INVALID_FILE_ATTRIBUTES may have been returned in the context of "existence" of the folder. But the reason we call these routines is nearly always so we can check whether we can actually use the folder we are asking about. Having INVALID_FILE_ATTRIBUTES almost always means that we cannot. Either way, the tests presently being done do not produce sensible results. This fact alone makes it a totally redundant exercise, as it allows certain other fault codes to slip through the net and set the end result to TRUE when it should not be. Whilst I can only imagine that there was originally some method to this madness, along the lines of "oh, it exists but might be invalid", it falls foul of the inherent truth that the future may bring many more codes generated by many as yet unknown file systems and/or hardware: so the bottom line is that for 99.9% of usage, getting INVALID_FILE_ATTRIBUTES should mean folder not usable, and it is therefore illogical to allow TRUE to be returned once that has already been established.

Unfortunately we cannot know whether there is code that relies upon the present behavior to identify "existing paths with issues" - in which case the call to the routine as it presently exists would have to be preceded with a path-syntax validation check first: otherwise it would say that a path described with bad syntax "exists", which is a nonsense! You can't redo the GetLastError afterwards, because the error will have already been cleared.

So the only cure is to wrap any Sysutils.DirectoryExists and (unfortunately also) TDirectory.Exists calls with code that eliminates the problem up-front. For example:

DirectoryUsable(const Directory: string; FollowLink: Boolean = True): Boolean;
begin
   Result := GetFileAttributes(PChar(Directory)) <> INVALID_FILE_ATTRIBUTES;
   if Result then Result := DirectoryExists( Directory, FollowLink );
end;

Either that, or you do a separate up-front validation of all the "bad" cases you know about and Embarcadero missed - i.e. play the same losing game as they did. Horrible, but there you go.

You could also modify the SysUtils library yourself, to add the missing cases, which you would have to redo with every release of Delphi and for every new case you encounter. It would probably be better if Embarcadero finally "bites the bullet" and finds a better solution for this problem. Perhaps by using another defaulted flag parameter that says "reject all invalid directories". I would further suggest that this defaults to TRUE.

Jaunitajaunt answered 12/6, 2017 at 12:17 Comment(4)
I wouldn't stress here. Only real way (not speaking about badly concatenated path in code) when this function can get a malformed path string is the user's input which as such should be validated, or restricted (if you let the user free hand for input, they can enter anything). For validation you can use e.g. PathIsDirectory function.Synopsize
Thanks for the hint about PathIsDirectory - I wasn't even aware of that routine. And you are right about validating user input - it's a royal pain to do this though, given all the permutations that a user could use - such as \\?\ lead-ins and embedded %token% substitutions.Jaunitajaunt
I am still not entirely convinced though that the user would be the only cause of getting the scenario whereby you have INVALID_FILE_ATTRIBUTES that result in DirectoryExists returning TRUE when it really should not. It begs the question whether after establishing that the directory exists you then always have to call GetFileAttributes to make sure it is actually usable (which means you are explicitly coding what is already in DirectoryExists merely to detect further cases. This still seems wrong to me.Jaunitajaunt
@Synopsize - The function DirectoryExists pretends that it returns true if the folder exists, and false otherwise. So, the function should do all possible validations to ensure its result is accurate. Otherwise, it should be called MaybeDirectoryExists.Elmira

© 2022 - 2024 — McMap. All rights reserved.