ZigiZ
ZigiZ

Reputation: 2520

Issue with FileExists and Modified Date

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;

Upvotes: 12

Views: 3476

Answers (3)

Arioch &#39;The
Arioch &#39;The

Reputation: 16045

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;

Upvotes: 1

kludg
kludg

Reputation: 27493

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.

Upvotes: 4

David Heffernan
David Heffernan

Reputation: 612954

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?

Upvotes: 11

Related Questions