-1

Some files this works with and others it does not.

var
  Src : integer;
  FileDate : LongInt;
begin
  Src:=FileOpen(SrcPath,fmOpenRead);
  FileDate:=FileGetDate(Src); // Crash here with FileDate = -1
  ...
  FileSetDate(Dest,FileDate);

I have checked Attributes for files that work and some that do not and they are identical.

Same for "Security," identical.

"Src" is a valid Integer for the ones that work and the ones that do not work.

The only thing I can see is that the full path to the ones that do not can be 130 characters and longer. But I renamed some Folders and shortened that to 118 and still no good.

Got me baffled. In a 2000+ file copy process, just 149 all in the same sub-Folder crash at this FileGetDate.

Any suggestions?

Thanks

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • May or may not be related, but are you closing the files which you've opened? Also, have you done load testing on a single file to see if one file might succeed sometimes and fail other times? – Jerry Dodge Oct 12 '13 at 20:58
  • Try this and see if you get the same results: http://edn.embarcadero.com/article/15541 – Jerry Dodge Oct 12 '13 at 21:00
  • Can you give the name of that subfolder? – Uwe Raabe Oct 12 '13 at 21:56
  • For handles you should use THandle, not integer. For timestamp use FileAge function. Or check at least that Src is valid handle after FileOpen (maybe file opened in exclusive mode by another app, ...). Check error code. And "-1" as result is not crash. – Andrei Galatyn Oct 12 '13 at 22:06
  • "149 all in the same sub-Folder" - Do other files in the same subfolder copy? What is the subfolder name, and where is it located? Why aren't you checking to see if `FileOpen` worked before using the results? You've omitted some information that would be useful. (And as @Andrei said, a return value of -1 is not a crash. Please be specific if you want help from us - we can't see your screen from where we are, and can only see the code you've posted. If you don't give us the proper info, we can't help you solve your problem.) – Ken White Oct 12 '13 at 22:37
  • 1
    What's with the legacy Pascal IO? How about using modern APIs? That provide informative error codes. – David Heffernan Oct 12 '13 at 22:39
  • 1
    @Andrei For FileOpen, you use whatever type the function returns. In older Delphi it will be Integer IIRC. – David Heffernan Oct 12 '13 at 22:49
  • This code has been working for years. It has suddenly stopped working on 9 sub-folders and their files, totaling 149 files in all. The folders and files before and after those 9 folders are all copied and time stamp-adjusted as this code has been doing for at best guess 12 years. – user2874842 Oct 12 '13 at 23:51
  • Legacy code? Speed. And as far as I know there are no "modern APIs" for D3. I have to work with what I am given and not with what I would like. – user2874842 Oct 12 '13 at 23:52
  • 1
    -1 is a pretty valid integer. It is not evident from your question that FileOpen succeeds or fails. – Sertac Akyuz Oct 13 '13 at 00:17
  • @user2874842 Er, you can call Win32 APIs from D3. And perhaps you might like to tell us which Delphi version you are using. – David Heffernan Oct 13 '13 at 06:03
  • @SertacAkyuz Read the docs for [FileGetData](http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.FileGetDate) where it is stated that -1 means invalid handle – David Heffernan Oct 13 '13 at 07:06
  • @David - That's why I commented. It's not evident in the question if FileOpen is returning -1 or any other *valid* integer > 0. – Sertac Akyuz Oct 13 '13 at 10:08
  • 1
    @David - BTW, FileGetDate's documentation is wrong. It returns -1 whenever it fails, which can happen either one of the three api calls it makes fail. – Sertac Akyuz Oct 13 '13 at 10:20
  • @DavidHeffernan Ooops, sorry, D5. – user2874842 Nov 08 '13 at 16:09

3 Answers3

2

The call to FileGetDate returns -1. The documentation says this:

The return value is -1 if the handle is invalid.

In other words, the handle returned by your call to FileOpen is not valid. You don't check for any errors in the code. Your code makes the assumption that all the calls succeed. The failure mode for FileOpen is that it returns -1. You are not checking the return value of FileOpen. You must add code to do so.

Note that the documentation for FileOpen says:

Note: We do not encourage the use of the non-native Delphi language file handlers such as FileOpen. These routines map to system routines and return OS file handles, not normal Delphi file variables. These are low-level file access routines. For normal file operations use AssignFile, Rewrite, and Reset instead.

So even ancient legacy Pascal I/O is to be preferred to FileOpen.

Frankly, if you want to work with files and get meaningful error diagnostics, you should use the Win32 API. Call CreateFile and if it fails, check GetLastError to find out why. There are lots of ways in which a file open request can fail and realistically only you can work out what the reason is for your files. We don't have the files at hand, only you do.

Finally, you say that you are writing a file copy routine. The system already provides such a thing, and you would be far better off using it. You are spending a lot of effort re-inventing the wheel. What's more, writing a good file copy function is hard. The one that the system provides is known to work. Your version is liable to be inferior.

To copy a single file you can use CopyFile or CopyGFileEx. But you are copying multiple files and SHFileOperation is the API for that.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I need to access the three file dates, Created, Modified, Accessed and as far as I can find, I need to use the "FileGetDate(Src);" approach. If this is not the case then I would appreciate a code snippet to guide me. – user2874842 Nov 08 '13 at 16:12
  • That's a different question. The answer to that one is `GetFileAttributesEx`. I think I answered the question that you asked. – David Heffernan Nov 08 '13 at 16:17
  • [`GetFileAttributesEx()`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364946.aspx) takes a filename as input. If you have already opened the file, use [`GetFileTime()`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724320.aspx) instead. – Remy Lebeau Jun 28 '15 at 05:22
0

3 thoughts,

The first is that something else has exclusive access to the file and you simply can not open it regardless. Check then your opened file handle is valid.

The second though is that some files can have VERY damaged time stamps on them. I am not sure how it happens, I just know that it does.

Finally, according to the documents on Linux, -1 is a valid date value, you do not mention what file system your source files are stored on.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

Here is the implementation of FileGetDate() in Delphi 5:

function FileGetDate(Handle: Integer): Integer;
var
  FileTime, LocalFileTime: TFileTime;
begin
  if GetFileTime(THandle(Handle), nil, nil, @FileTime) and
    FileTimeToLocalFileTime(FileTime, LocalFileTime) and
    FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
      LongRec(Result).Lo) then Exit;
  Result := -1;
end;

That is 3 different points of failure that could happen on any given input file handle:

  1. does GetFileTime() fail?

  2. does FileTimeToLocalFileTime() fail?

  3. does FileTimeToDosDateTime() fail?

Unless FileOpen() fails (which you are not checking for - it returns -1 if it is not able to open the file), then it is unlikely (but not impossible) that #1 or #2 are failing. But #3 does have a documented caveat:

The MS-DOS date format can represent only dates between 1/1/1980 and 12/31/2107; this conversion fails if the input file time is outside this range.

It is not likely that you encounter files with timestamps in the year 2108 and later, but you can certainly encounter files with timestamps in the year 1979 and earlier.

All 4 functions (counting the CreateFile() function called inside of FileOpen()) report an error code via GetLastError(), so you can do this:

var
  Src : integer;
  FileDate : LongInt;
begin
  Src := FileOpen(SrcPath, fmOpenRead);
  Win32Check(Src <> -1);
  FileDate := FileGetDate(Src);
  Win32Check(FileDate <> -1);
  ...
  Win32Check(FileSetDate(Dest, FileDate) = 0);

Win32Check() calls RaiseLastWin32Error() if the input parameter is false. RaiseLastWin32Error() raises an EOSError exception containing the actual error code in its ErrorCode property.

If FileGetDate() fails, obviously you won't know which Win32 function actually failed. That is where the debugger comes into play. Enable Debug DCUs in your Project Options to allow you to step into the VCL/RTL source code. Find a file that fails, call FileGetDate() on it, and step through its source code to see which if the three API functions is actually failing.

Similarly for FileSetDate(), which also calls 3 API functions internally:

function FileSetDate(Handle: Integer; Age: Integer): Integer;
var
  LocalFileTime, FileTime: TFileTime;
begin
  Result := 0;
  if DosDateTimeToFileTime(LongRec(Age).Hi, LongRec(Age).Lo, LocalFileTime) and
    LocalFileTimeToFileTime(LocalFileTime, FileTime) and
    SetFileTime(Handle, nil, nil, @FileTime) then Exit;
  Result := GetLastError;
end;

If FileSetDate() fails, is it because:

  1. DosDateTimeToFileTime() failed?

  2. LocalFileTimeToFileTime() failed?

  3. does SetFileTime() failed?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770