Issue with FileExists and Modified Date
Asked Answered
J

3

12

On my server there are a few files with Modified Date 31/DEC/1979 (Don't ask me why). So FileExists returns false.

Sysutils.FileExists looks like this:

function FileAge(const FileName: string): Integer;
var
  Handle: THandle;
  FindData: TWin32FindData;
  LocalFileTime: TFileTime;
begin
  Handle := FindFirstFile(PChar(FileName), FindData);
  if Handle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    begin
      FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
      if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
        LongRec(Result).Lo) then Exit;
    end;
  end;
  Result := -1;
end;

function FileExists(const FileName: string): Boolean;
begin
  Result := FileAge(FileName) <> -1;
end;

My question is, Why does the function depends on FileAge in the first place? Isn't the following line sufficient?:

if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
  // Yes the file exists!

Or even based on file attributes:

function MyFileExists(const Name: string): Boolean;
var
  R: DWORD;
begin
  R := GetFileAttributes(PChar(Name));
  Result := (R <> DWORD(-1)) and ((R and FILE_ATTRIBUTE_DIRECTORY) = 0);
end;
Jury answered 19/12, 2012 at 18:56 Comment(1)
It was to handle storage systems that did implicit create on read. I know they used to exist, but I'm d'd if I can find any documentation now.Lea
O
11

The modern versions of Delphi implement FileExists in broadly the same way as your code does. The implementation has extra handling for symlinks but is otherwise essentially identical to your version.

There's one interesting nuance in the modern Delphi implementation. If the call to GetFileAttributes returns INVALID_FILE_ATTRIBUTES, then the code doesn't immediately bail out. Instead it does this:

LastError := GetLastError;
Result := (LastError <> ERROR_FILE_NOT_FOUND) and
  (LastError <> ERROR_PATH_NOT_FOUND) and
  (LastError <> ERROR_INVALID_NAME) and ExistsLockedOrShared(Filename);

And the implementation of ExistsLockedOrShared uses FindFirstFile and a check of the FILE_ATTRIBUTE_DIRECTORY on dwFileAttributes. This indicates that GetFileAttributes can fail when the file exists, but is locked. But that FindFirstFile can succeed in such a scenario. That's reasonable because FindFirstFile uses the file metadata rather than the data stored in the file itself.

It's hard to say why the code is is the way it is in the older versions. I think it's weak. Personally I would replace FileExists with a better version, using a code hook. For example: Patch routine call in delphi

As always, there's a Raymond Chen article on the subject: Superstition: Why is GetFileAttributes the way old-timers test file existence?

Orland answered 19/12, 2012 at 19:14 Comment(5)
+1 like all old-timers I was also using GetFileAttributes with my D5 to check if file exists. but didn't know about the lock/share problem. Can you please post the full code implementation for FileExists? :) This is what I found for D2009.Basso
@Basso I can't post Delphi source. But I'd just use the FindFirstFile approach and test (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0. It might not be the fastest, but it gets around problems with locked files. If you want to be cute, the LastError checking in the answer is enough to fill in the rest.Orland
Thanks @David. Interesting I've found another version that sets SetErrorMode(SEM_FAILCRITICALERRORS) (and restores it) before calling the code, to avoid getting a critical error dialog if for example you eject a CDROM drive and try to test if file exists.Basso
@Basso I add SEM_FAILCRITICALERRORS at process start.Orland
Yeah I figured that this should be a common practice. MSDN - Best practice is that all applications call the process-wide SetErrorMode function with a parameter of SEM_FAILCRITICALERRORS at startup..Basso
L
4

Judging by the 'modern' implementation of FileExists (which does not use FileAge and also optimized and can follow symlinks to check if linked files exist):

  • the first variant ((FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0) is OK;
  • the second variant (GetFileAttributes) could fail if file is locked or shared.
Limber answered 19/12, 2012 at 19:25 Comment(0)
W
1

For old Delphi versiosn you can download Jedi Code Library. It has the following implementation (aside of many other useful classes and functions):

function FileExists(const FileName: string): Boolean;
{$IFDEF MSWINDOWS}
var
  Attr: Cardinal;
{$ENDIF MSWINDOWS}
begin
  if FileName <> '' then
  begin
    {$IFDEF MSWINDOWS}
    // FileGetSize is very slow, GetFileAttributes is much faster
    Attr := GetFileAttributes(Pointer(Filename));
    Result := (Attr <> $FFFFFFFF) and (Attr and FILE_ATTRIBUTE_DIRECTORY = 0);
    {$ELSE ~MSWINDOWS}
    // Attempt to access the file, doesn't matter how, using FileGetSize is as good as anything else.
    Result := FileGetSize(FileName) <> -1;
    {$ENDIF ~MSWINDOWS}
  end
  else
    Result := False;
end;
Winfordwinfred answered 20/12, 2012 at 7:42 Comment(1)
I think it is also a good answer since it confirms that JCL also uses GetFileAttributes like I meant to do.Jury

© 2022 - 2024 — McMap. All rights reserved.