FileGetDate works some times, thoughts? - delphi

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

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.

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.

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:
does GetFileTime() fail?
does FileTimeToLocalFileTime() fail?
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:
DosDateTimeToFileTime() failed?
LocalFileTimeToFileTime() failed?
does SetFileTime() failed?

Related

FindFirstFile matches wildcards that shouldn't match

On some of my Windows 10 machines, FindFirstFile matches files that definitely should not match. Assume the following program in Delphi:
{$apptype console}
uses Windows;
var
FindHandle: THandle;
FindData: WIN32_FIND_DATA;
begin
FindHandle := FindFirstFile('*.qqq', FindData);
if FindHandle <> INVALID_HANDLE_VALUE then
begin
try
repeat
Writeln(PChar(#FindData.cFileName[0]));
until not FindNextFile(FindHandle, FindData);
finally
FindClose(FindHandle);
end;
end;
end.
and four files:
a.qqq
b.qqqt
c.qqqx
c.qqq123
The output I expect to get is just a.qqq. But what actually happens is, all four files get printed out. I get the same result with CMD's dir *.qqq, too, so it's not just Delphi doing weird stuff, but PowerShell's dir *.qqq works as expected. What could possibly be causing this behavior? And particularly, if it is some specific settings in the OS (which seems to be indicated by the fact that I don't get this result on all machines, just some), is there something I can do from within my program to enforce the expected behavior regardless of the OS settings?
The reason is that the underlying Windows functions checks the long and the short file names:
The search includes the long and short file names.
You can see that when you add the /X parameter to the dir call in CMD:
dir /X *.qqq
A possible solution is to add another filter check for each found name that only takes the long name.
Actually, that is what Delphi does in TDirectory.GetFiles, making that sometimes a better alternative to the hand written routine.

Delphi 2010 - Is File in Use?

I have a function which works ok to check if a local file is in use.
However if I map a network drive and try to check if a file from the mapped drive is in use then the result of the function is always false.
I need to wait before a large file is being copied to the mapped drive and after completion I rename the file.
If the file in not in use then i start performing various actions else i wait another minute and check again.
How can I modify the function below in order to work with mapped drive files that are constantly copied?
Thank you
function IsFileInUse(FileName: TFileName): Boolean;
var
HFileRes: HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage('Fisierul "'+Filename+'" nu exista!');
Exit;
end
else
begin
HFileRes := CreateFile(PChar(FileName),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
Result := (HFileRes = INVALID_HANDLE_VALUE);
if not Result then
CloseHandle(HFileRes);
end;
end;
What you are claiming is that CreateFile succeeds in opening a file in exclusive mode whilst another party is writing to the file. Possible explanations:
You have made a mistake with the file name and the file you are opening is not the one in use.
The other party is writing to the file without having locked it. In other words it opened the file with a share mode that allowed other parties to read and write. This would be quite unusual. If this is the case then you need to fix the other process.
The remote file server is broken and fails to respect locks. I'd regard this as quite unlikely.
I think the final option can be rejected immediately. Option 2 seems rather unlikely. Which leaves option 1. You are able to lock the file because it is not locked.
I'd also comment that the function is spurious. You can remove it. Simply attempt whatever operation you need to perform. If that operation fails due to a sharing violation you know that the file was locked. Consider also the race condition in any code using that function. The fact that a file is unlocked now does not prevent another party locking the file before you can do anything with it.

Adding large resources with UpdateResource

Nowhere in the Windows documentation do I see a reference to a size limit to the resources one can add using UpdateResource, but it seems I have stumbled upon one - and it's tiny!
I was developing a Windows Ribbon app and wanted to programmatically build and attach the resource. Linking the resource using a $R directive worked just dandy, but I kept getting memory junk when attaching the very same thing from code.
I have managed to reduce it to a simple example using a string resource:
Handle := BeginUpdateResource(PChar(DestFileName), True);
try
AddResource(Handle, 'STRING', 'ManyXs', StrUtils.DupeString('X', 1000));
finally
EndUpdateResource(Handle, False);
end;
And AddResource is defined as:
procedure TForm2.AddResource(Handle: NativeUInt; ResType, ResName, Value: string);
begin
if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
PChar(Value), Value.Length * SizeOf(Char)) then
RaiseLastOSError;
end;
Please ignore my hard-coded language for the moment.
When I inspect the resource subsequent to calling this, I see a thousand Xs. Fabulous.
I can change the resource to 1990 Xs and it's fine. The moment it goes to 1991, I get nonsense written to the DLL. The size of the resource is correctly indicated as 3982 (1991 * 2 because it's Unicode), but the contents is just a dump of stuff from memory.
I can view larger resources with my resource editor, and the IDE routinely inserts larger resources (Delphi forms, for example), so I'm definitely missing something.
I've tried the following, despite not thinking any of them would make a difference (they didn't):
Using just large memory buffers instead of strings
Using the Ansi version of the UpdateResource function
Many different resource types - what I really need to get working, is UIFILE
Looking for other functions in the API (I found none)
Combinations of 1, 2 and 3
Any ideas?
Update:
Inspired by the comments and Jolyon's answer, tried a few more things.
First, I tried in Delphi XE7 and XE5 as well (original was in XE6). I don't have XE2 installed anymore, so i cannot confirm what Sertak has said. I'll find out if someone else in my office still has it installed.
Second, here is the memory buffer version:
procedure TForm2.AddResource(Handle: NativeUInt; const ResType, ResName, Value: string);
var
Buffer: Pointer;
BuffLen: Integer;
begin
BuffLen := Value.Length * SizeOf(Char);
GetMem(Buffer, BuffLen);
try
StrPCopy(PChar(Buffer), Value);
if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
Buffer, BuffLen) then
RaiseLastOSError;
finally
FreeMem(Buffer);
end;
end;
I actually had a previous version of this code where I dumped the contents of that pointer into a file before the call to UpdateResource and the file saved correctly but the resource still saved junk. Then I did this version, which doesn't involve strings at all:
procedure TForm2.AddResource(Handle: NativeUInt; const ResType, ResName: string;
C: AnsiChar; Len: Integer );
var
Buffer: Pointer;
BuffLen: Integer;
begin
BuffLen := Len;
GetMem(Buffer, BuffLen);
try
FillMemory(Buffer, Len, Byte(C));
if not UpdateResource(Handle, PChar(ResType), PChar(ResName), 1033,
Buffer, BuffLen) then
RaiseLastOSError;
finally
FreeMem(Buffer);
end;
end;
With this version I still have the same problem when I use 3882 Xs. Of course, I'm now using single-byte characters, that's why it's double. But I have the exact same issue.
I did notice a difference between the versions in the output of TDUMP though. For versions 1 (strings) and 2 (string copied to buffer), my resource size is suddenly indicated as FFFFFF90 when I use 1991 characters. With version 3 (no strings), the size is the actual hex value of whatever size I used.
The fact that you are getting "junk" data but data of the right size leads me to suspect the PChar() casting of the string value yielding an incorrect address. This normally should not be a problem, but I wonder if the issue is some strange behaviour as the result of passing the result of a function directly into a parameter of a method ? A behaviour which for some strange reason is only triggered when the string involved reaches a certain size, perhaps indicating some edge-case optimization behaviour.
This might also explain difficulties in reproducing the problem if it is some combination of optimization (and/or other compiler settings) in some specific version of Delphi.
I would suggest to try eliminating this possibility by creating your new resource string in an explicit variable and passing that to the AddResource() method. I would also suggest that you be explicit in your parameter semantics and since the string involved is not modified, nor intended to be modified, in the AddResource() method, declare it as a formally const parameter.
You do mention having tried an alternative approach using "memory buffers". If the above suggestions do not resolve the problem, perhaps it would be helpful to post a minimal example that reproduces the problem using those, to eliminate any possible influence on things by the rather more exotic "string" type.

Getting WPARAM in TWndMethod to return 4 bytes

I'm using AllocateHWnd in a class I'm writing to receive system messages with a TWndMethod and the messages I'm receiving need to handle a 4-byte WPARAM, which specifically references a pointer. But I'm only getting 2 bytes in return. How do I set up things so I can correctly receive these messages within the class?
Edit: Specific code. I'm setting a message event up using SHChangeNotifyRegister, based on a Microsoft sample I downloaded. The proc works enough to pull back events (in lEvent) that I can buy off on, but the code Microsoft used defines WParam to be Thandle and LParam to be DWord. The specific problem I have is that when the function IsItemNotificationEvent is true, SHGetPathFromIDList is AVing or pulling back garbage. I kept looking this over and am not really seeing a problem other than what the docs I have indicate in that WParam is a Word (probably old) and that GetLastError at the point I put in the code returns "The handle is invalid".
function IsItemNotificationevent(lEvent: Longint): boolean;
var
flagval: Longint;
begin
flagval := (lEvent and (SCHNE_UPDATEIMAGE or SHCNE_ASSOCCHANGED
or SHCNE_EXTENDED_EVENT or SHCNE_FREESPACE
or SHCNE_DRIVEADDGUI or SHCNE_SERVERDISCONNECT));
Result := (flagval > 0);
end;
procedure TShellNotifyHandler.WindowProc(Var msg: TMessage);
var
hNotifyLock: THandle;
lEvent: Longint;
pgpidl: PitemIDList;
psi1: array[1..MAX_PATH] of Char;
begin
if Msg.Msg = FShellMsg then
begin
hNotifyLock := SHChangeNotification_Lock(THandle(Msg.WParam),DWord(Msg.LParam),
pgpidl, lEvent);
writeln(SysErrorMessage(GetLastError));
if (hNotifyLock > 0) then
begin
if IsItemNotificationEvent(lEvent) then
// this limits events for this to what Microsoft defined in their example
begin
if (pgpidl <> nil) then
SHGetPathFromIDList(pgpidl, #psi1);
Writeln('Path #1: ', String(psi1));
end;
SHChangeNotification_Unlock(hNotifyLock);
end;
if Assigned(FOnShellNotify) then
FOnShellNotify(Self, LEvent);
end
else
FWndProc(Msg);
end;
The main thing that I see wrong with this code, and I've only really studied the call to SHChangeNotification_Lock, is that you are unconditionally calling GetLastError.
The documentation for that API function is inadequate because it does not specify how errors are signalled. However, I would strongly expect that errors to be signalled by the function returning NULL. Since the documentation does not say anything about calling GetLastError it is entirely possible that the API function does not set the last error value. No matter, even if you can be sure that GetLastError can be called, you should only do so after a failure, ie. if the call to SHChangeNotification_Lock returns NULL. If you call GetLastError after a successful API call you will get the error code for the most recent failed API call, which is unrelated to the current call.
The bottom line is that I'm sure WParam is carrying all 4 bytes and that your problem is not with that part of the process.
The upshot of all this is the I strongly believe that SHChangeNotification_Lock is succeeding, but the call to SHGetPathFromIDList is failing. You don't check the return value for that. I bet it returns FALSE.
Take a look at the C++ declarations for the two functions.
SHChangeNotification_Lock returns the ID list in a parameter typed liked this:
PIDLIST_ABSOLUTE **pppidl
SHGetPathFromIDList receives the ID list in a parameter typed liked this:
PCIDLIST_ABSOLUTE pidl
I don't know what your declaration of SHChangeNotification_Lock looks like, but the one supplied in my version of Delphi (XE2) looks plain wrong. It has this parameter declared like this:
out pppidl: array of PItemIDList
I honestly can't see how a Windows API function can return a Delphi open array as an out parameter. I think it should be declared so:
out pppidl: PPItemIDList
and you may need to declare PPItemIDList to be ^PItemIDList.
Now, pppidl is an array. It points to the first element of an array of PItemIDList. So you would obtain the path of the first element by calling:
SHGetPathFromIDList(pppidl^, #psi1);
This, I believe, is the real problem you have.
Finally I can't understand why you would test for success with hNotifyLock > 0. The correct test is hNotifyLock <> 0. Now, I know that some of the Delphi types have changed in recent versions, but if THandle was a signed value in your version of Delphi then you code would be wrong. No matter what, the correct logical test is <>0.
Okay, I got this answered. A number of problems all over the board, actually:
1) I had things wrong when it comes to IsItemNotificationEvent. To have valid PIDLs, I needed to make sure that the event WASN'T one of those, because no PIDL is valid to process against those.
if IsItemNotificationEvent(lEvent) then
2) "out" was necessary in the definition to SHChangeNotification_Lock and not "var" or a simple pointer reference. I don't have anything that indicates what "out" does specifically, so if anyone can help, please do. The fixed definition is below.
function SHChangeNotification_Lock(hChangeNotification: THandle; dwProcessID: DWord;
out pppidl: PSHNotifyStruct; out plEvent: Longint): THandle; stdcall;
3) In my documentation (including the source samples), it indicates that multiple pidls are possible for some event types. Which makes the suggested correction invalid in the QC report. The problem with using the original definition is probably as suggested. It's not quite right. Reference the definition above, and you'll see a different type. That definition is below. No events have more than two parms, so it would suffice.
TSHNotifyStruct = packed record
dw1: PItemIDList;
dw2: PItemIDList;
end;
PSHNotifyStruct = ^TSHNotifyStruct;
Got it working as I expect it to now. I just need to find a valid list of two parm events and code in to make it a little cleaner (i.e. not reference the second pitemid if known to be invalid). Some samples of output from my test program are below to illustrate:
Event received: $00001000 Parm 1: (C:) Local Disk // update directory
Event received: $00000008 Parm 1: ChangeNotifyWatcher // make directory
Event received: $00000002 Parm 1: ChangeNotifyWatcher // create file
Event received: $00000010 Parm 1: ChangeNotifyWatcher Parm 2: RECYCLER // remove directory
Thanks all for your help!

Synapse and string problems with HTTPSend in Delphi 2010

I have been trying to get to the bottom of this problem off and on for the past 2 days and I'm really stuck. Hopefully some smart folks can help me out.
The issue is that I have a function that I call in a thread that downloads a file (using Synapse libraries) from a website that is passed to it. However, I've found that every once in a while there are sites where it will not pull down a file, but wget or Firefox/IE will download it without issue.
Digging into it, I've found some curious things. Here is the relevant code:
uses
//[..]
HTTPSend,
blcksock;
//[..]
type
TMyThread = class(TThread)
protected
procedure Execute; override;
private
{ Private declarations }
fTheUrl: string;
procedure GetFile(const TheUrl: string);
public
property thrd_TheUrl: string read fTheUrl write fTheUrl;
end;
implementation
[..]
procedure TMyThread.GetFile(const TheUrl: string);
var
HTTP: THTTPSend;
success: boolean;
sLocalUrl: string;
IsSame : boolean;
begin
HTTP := THTTPSend.Create;
try
HTTP.UserAgent :=
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)';
HTTP.ProxyHost := 'MYPROXY.COM';
HTTP.ProxyPort := '80';
sLocalUrl :=
'http://web.archive.org/web/20071212205017/energizer.com/usbcharger/download/UsbCharger_setup_V1_1_1.exe';
IsSame := SameText(sLocalUrl, sTheUrl); //this equals True when I debug
///
///
/// THIS IS WHERE THE ISSUE BEGINS
/// I will comment out 1 of the following when debugging
///
HTTP.HTTPMethod('GET', sLocalUrl); // ----this works and WILL download the file
HTTP.HTTPMethod('GET', sTheUrl); // --- this always fails, and HTTP.ResultString contains "Not Found"
success := SysUtils.UpperCase(HTTP.ResultString) = 'OK';
if HTTP.ResultCode > 0 then
success := True; //this is here just to keep the value around while debugging
finally
HTTP.Free;
end;
end;
procedure TMyThread.Execute
begin
//fTheURL contains this value: http://web.archive.org/web/20071212205017/energizer.com/usbcharger/download/UsbCharger_setup_V1_1_1.exe
GetFile(fTheUrl);
end;
The problem is that when I assign a local variable to the function and give it the URL directly, everything works. However, when passing the variable into the function, it fails. Anyone have any ideas?
HTTP.HTTPMethod('GET', sLocalUrl); // ----this works and WILL download the file
HTTP.HTTPMethod('GET', sTheUrl); // --- this always fails, and HTTP.ResultString contains "Not Found"
I'm using the latest version of Synapse from their SVN repository (version from 2 days ago).
NOTE: The file I am attempting to download is known to have a virus, the program I am writing is meant to download malicious files for analysis. So, don't execute the file once you download it.
However, I'm using this URL b/c this is the one I can reproduce the issue with.
Your code is missing the crucial detail how you use TMyThread class. However, you write
every once in a while there are sites where it will not pull down a file, but wget or Firefox/IE will download it without issue.
which sounds like a timing issue.
Using the local variable works every time. Using the function parameter works only some of the time. That may be caused by the function parameter not containing the correct URL some of the time.
You need to be aware that creating a non-suspended thread may result in it starting to execute immediately (and possibly even to complete), before the next line after the construction call has even started to execute. Setting any property of the thread object after the thread has been created may therefore not work, as the thread execution may be past the point where the property is read. The fTheUrl field of the thread object will be an empty string initially, so whether the thread downloads the file will depend on it being set before.
Your fTheUrl field isn't even protected by a synchronization primitive. Both the thread proc and the code in the main thread can access it concurrently. Sharing data in this way between threads is an unsafe thing to do and may result in any thing from wrong behaviour to actual crashes.
If your thread is really used to download a single file you should remove the write access to the property, and write a custom constructor with a parameter for the URL. That will properly initialize the field before the thread starts.
If you are downloading several files in your program you should really not create a thread for each. Use a pool of threads (may be only one even) that will be assigned the files to download. For that a thread property is the right solution, but then it will need to be implemented with synchronization, and the thread should block when no file is to be downloaded, and unblock when the property is set. The download thread (or threads) would be consumer(s) in a producer-consumer implementation. Stack Overflow has questions and answers regarding this in the Delphi tag, in particular in the questions where alternatives to Suspend() and Resume() are discussed.
One last thing: Don't let unhandled exceptions escape the Execute() method. I'm not sure about whether Delphi 2010 handles these exceptions in the VCL, but unhandled exceptions in a thread may lead to problems like app crashes or freezes.
Well, I'm almost embarrassed to report it, but I owe it to those that took the time to respond.
The issue had nothing to do with Synapse, or TThread, but instead had everything to do with the fact that the URL is case-sensitive!
In my full application, I had a helper function that lowercased the URL (for some reason). I removed that and everything started working again...
Please update last Synapse Revision 127.

Resources