FS plugin: TC doesn't show empty directory

Discuss and announce Total Commander plugins, addons and other useful tools here, both their usage and their development.

Moderators: white, Hacker, petermad, Stefan2

speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

FS plugin: TC doesn't show empty directory

Post by *speller2 »

I'm following the developer's guide and trying to show an empty directory in my FS plugin:
Return INVALID_HANDLE_VALUE (==-1, not zero!) if an error occurs...

When an error occurs, call SetLastError() to set the reason of the error. Total Commander checks for the following two errors:
1. ERROR_NO_MORE_FILES: The directory exists, but it's empty (Totalcmd can open it, e.g. to copy files to it)
2. Any other error: The directory does not exist, and Total Commander will not try to open it.
I'm following the case #1 to let TC know that the directory exists but empty:

Code: Select all

function FsFindFirstW(Path: PWideChar; var FindData: tWIN32FINDDATAW): PDirectoryContents; stdcall;
...
begin
  ...
  SetLastError(ERROR_NO_MORE_FILES);
  Result := Pointer(INVALID_HANDLE_VALUE);
end;
But TC plays the ding system sound and doesn't enter the directory at all. What I'm doing wrong?
User avatar
Dalai
Power Member
Power Member
Posts: 9364
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Re: FS plugin: TC doesn't show empty directory

Post by *Dalai »

The function's return value type seems wrong. The plugin guide defines HANDLE (or THandle in Delphi terms) as return value type for FsFindFirst(W), and the sample plugin uses that very return type. Then you can use

Code: Select all

Result:= INVALID_HANDLE_VALUE;
Also make sure that SetLastError is the last call in your function or the last error code might get reset by calls to other functions. It's OK as in your example since it's a simple variable assignment.

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

The pointer type has the same size as the integer used in the documentation. Typecasts between pointers and integers are direct with no modifications. It's for the convenience to use the pointer type for less typecasting the code. By the way, directory listing works perfectly with this code. Non-empty directories work well. I just don't understand why TC doesn't enter into empty dirs. The SetLastError function call is 100% the last in the function.
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

I Just made an experiment and switched to the THandle type and without success.
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Re: FS plugin: TC doesn't show empty directory

Post by *ghisler(Author) »

I have checked that - a return value of INVALID_HANDLE_VALUE (minus 1) is supported with ERROR_NO_MORE_FILES set as last error. For example, my WebDAV plugin does this with empty folders.

Do you handle directory reading in a background thread? I ask because the error set via SetLastError is thread-specific, so if you call SetLastError in the background thread, it has no influence on the foreground thread from which TC calls your plugin.
Author of Total Commander
https://www.ghisler.com
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

I'm calling the SetLastError function in the same thread as FsFindFirstW. No background threads.
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Re: FS plugin: TC doesn't show empty directory

Post by *ghisler(Author) »

Maybe one of the variables is wrong? I remember that INVALID_HANDLE_VALUE was 0 in some Lazarus files.

Try setting the numeric values:
SetLastError(18);
Result := Pointer(-1);
Author of Total Commander
https://www.ghisler.com
User avatar
Dalai
Power Member
Power Member
Posts: 9364
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Re: FS plugin: TC doesn't show empty directory

Post by *Dalai »

speller2 wrote: 2021-07-09, 02:11 UTCThe pointer type has the same size as the integer used in the documentation. Typecasts between pointers and integers are direct with no modifications.
Until you compile the code for the 64-bit platform where pointers are 64-bit but THandle remains 32-bit in size. Also keep in mind that THandle is unsigned while integer is signed - in other words, integers aren't involved here (although they're identical in size).

I'm using something similar in several of my plugins and it works. I know this doesn't help you directly, but I just wanted to confirm that it's supposed to work as you think.

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

ghisler(Author) wrote: 2021-07-09, 10:16 UTC Maybe one of the variables is wrong? I remember that INVALID_HANDLE_VALUE was 0 in some Lazarus files.

Try setting the numeric values:
SetLastError(18);
Result := Pointer(-1);
Tried this, but no luck... :(
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

Dalai wrote: 2021-07-09, 12:24 UTC Until you compile the code for the 64-bit platform where pointers are 64-bit but THandle remains 32-bit in size. Also keep in mind that THandle is unsigned while integer is signed - in other words, integers aren't involved here (although they're identical in size).
We're both wrong :) Just ran this code in Lazarus project compiled for 64 bit:

Code: Select all

  MessageBox(0, PChar('THandle: ' + IntToStr(SizeOf(THandle)) + ' Integer: ' + IntToStr(SizeOf(Integer)) + ' Pointer: ' + IntToStr(SizeOf(Pointer))), 'Sizes', 0);

Code: Select all

---------------------------
Sizes
---------------------------
THandle: 8 Integer: 4 Pointer: 8
---------------------------
ОК   
---------------------------
I remembered how it was in Delphi, but FPC has differences. I was wrong in assuming that Integer is architecture-dependent. Anyway, both Pointer(-1) and Pointer(INVALID_HANDLE_VALUE) expressions produce the same value:

Code: Select all

anyfs.lpr:310                             SetLastError(ERROR_BAD_DEVICE);
0000000110002E42 b9b0040000               mov    $0x4b0,%ecx
0000000110002E47 e8f4e1ffff               callq  0x110001040 <__$dll$kernel32$SetLastError>
anyfs.lpr:311                             Result := Pointer(INVALID_HANDLE_VALUE);
0000000110002E4C 48c745e8ffffffff         movq   $0xffffffffffffffff,-0x18(%rbp)
anyfs.lpr:250                             try
0000000110002E54 e837a90000               callq  0x11000d790 <fpc_doneexception>
0000000110002E59 eb14                     jmp    0x110002e6f <FSFINDFIRSTW+1871>
0000000110002E5B 90                       nop
anyfs.lpr:315                             SetLastError(18);
0000000110002E5C b912000000               mov    $0x12,%ecx
0000000110002E61 e8dae1ffff               callq  0x110001040 <__$dll$kernel32$SetLastError>
anyfs.lpr:316                             Result := Pointer(-1);
0000000110002E66 48c745e8ffffffff         movq   $0xffffffffffffffff,-0x18(%rbp)
anyfs.lpr:245                             begin
0000000110002E6E 90                       nop
0000000110002E6F 4889e9                   mov    %rbp,%rcx
0000000110002E72 e849f8ffff               callq  0x1100026c0 <fin$00000038>
anyfs.lpr:317                             end;
You may see that lines 311 and 316 produce the same raw operand value in asm instructions.
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

I'm not sure whether it's related or not, but I also can't display a link file. My code filling the `WIN32FINDDATAW` structure for files is the following:

Code: Select all

  FillChar(aFindDataW, SizeOf(aFindDataW), 0);
  ...
      Move(name[1], aFindDataW.cFileName, Length(name) * SizeOf(name[1]));
      aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
      if fil.fIsLink then
        aFindDataW.dwFileAttributes := aFindDataW.dwFileAttributes or FILE_ATTRIBUTE_REPARSE_POINT;
      aFindDataW.ftLastWriteTime := DateTimeToFileTime64(fil.fTime);
      aFindDataW.nFileSizeHigh := Int64Rec(fil.fSize).Hi;
      aFindDataW.nFileSizeLow := Int64Rec(fil.fSize).Lo;
TC 9.51 x64. It shows files correctly, but files that I want to be displayed as symlinks are still displayed as regular files. I tried to set it in this way:

Code: Select all

      if fil.fIsLink then
        aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_REPARSE_POINT;
But it also didn't help. What I'm missing here?
User avatar
Dalai
Power Member
Power Member
Posts: 9364
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Re: FS plugin: TC doesn't show empty directory

Post by *Dalai »

speller2 wrote: 2021-07-09, 15:19 UTCI remembered how it was in Delphi, but FPC has differences.
Sorry, I was confused by the documentation stating that THandle is just a Cardinal (unsigned 32-bit) which contradicts the source code of Delphi's System.pas which states that it's NativeUInt on the Windows platform which is architecture dependent. Makes sense when you think about it because what good would a 32-bit handle on a 64-bit system do? It would work only half of the time ;).
I was wrong in assuming that Integer is architecture-dependent.
Delphi at least froze the size of the type integer with the introduction of 64-bit compilers (in XE2). Since that could cause some side effects they introduced NativeInt and NativeUInt types which can, as the name suggests, vary in size. It would be logical and more consistent if FreePascal did the same, but I don't know because I only rarely use it.

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Re: FS plugin: TC doesn't show empty directory

Post by *ghisler(Author) »

2speller2
Maybe the compiler translates pointer(-1) to zero because pointers are positive numbers. Try
pointer($FFFFFFFF) on 32-bit
pointer($FFFFFFFFFFFFFFFF) on 64-bit.
If that doesn't work either, there is one more way to report an empty directory: Just return 1 entry, the "go up" directory "..". TC adds this automatically if the plugin doesn't report it, but you can return it yourself.

Regarding symlinks, FILE_ATTRIBUTE_REPARSE_POINT isn't supported - it didn't exist when I created the plugin interface!
As the help states, you need to OR dwFileAttributes with 0x80000000 ($80000000 in Delphi) and then set dwReserved0 to the Unix permissions.
There you need to set S_IFLNK which is defined as 0xA000.

Code: Select all

			if (oddata->type & FILE_ATTRIBUTE_DIRECTORY)
				FindData->dwReserved0=S_IFLNK | 0755;
			else
				FindData->dwReserved0=S_IFLNK | 0644;
			FindData->dwFileAttributes|=0x80000000;
Author of Total Commander
https://www.ghisler.com
speller2
Junior Member
Junior Member
Posts: 91
Joined: 2009-01-26, 13:49 UTC
Location: Russia

Re: FS plugin: TC doesn't show empty directory

Post by *speller2 »

2ghisler(Author)
Regarding pointers, the compiler translates typecasts correcly to $0xffffffffffffffff:

Code: Select all

anyfs.lpr:311                             Result := Pointer(INVALID_HANDLE_VALUE);
0000000110002E4C 48c745e8ffffffff         movq   $0xffffffffffffffff,-0x18(%rbp)
anyfs.lpr:316                             Result := Pointer(-1);
0000000110002E66 48c745e8ffffffff         movq   $0xffffffffffffffff,-0x18(%rbp)
But when I added the '..' directory it now works well!


Regarding links, I tried this:

Code: Select all

      S_IFLNK = $A000;
      S_UNIX_PERMISSIONS_FLAG = $80000000;

...
      aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
      aFindDataW.dwReserved0 := S_UNIX_PERMISSIONS_FLAG or fil.fPermissions;
      if fil.fIsLink then
      begin
        //aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_REPARSE_POINT;
        aFindDataW.dwReserved0 := aFindDataW.dwReserved0 or S_IFLNK;
      end;
But still, it doesn't work...
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 48021
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Re: FS plugin: TC doesn't show empty directory

Post by *ghisler(Author) »

You need to add the S_UNIX_PERMISSIONS_FLAG to the dwFileAttributes field to tell Total Commander that the dwReserved0 field contains valid data.
Case 1: If the file system supports Unix permissions:

Code: Select all

      aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_NORMAL or S_UNIX_PERMISSIONS_FLAG;
      aFindDataW.dwReserved0 :=  fil.fPermissions;
      if fil.fIsLink then
        aFindDataW.dwReserved0 := aFindDataW.dwReserved0 or S_IFLNK;
Case 2: If the file system does not support Unix permissions:

Code: Select all

      aFindDataW.dwFileAttributes := FILE_ATTRIBUTE_NORMAL;
      aFindDataW.dwReserved0 :=  0;
      if fil.fIsLink then begin
        aFindDataW.dwFileAttributes := aFindDataW.dwFileAttributes or S_UNIX_PERMISSIONS_FLAG;
        aFindDataW.dwReserved0 := aFindDataW.dwReserved0 or S_IFLNK;
      end;
Author of Total Commander
https://www.ghisler.com
Post Reply