Lazarus

Free Pascal => Windows => Topic started by: dietmar on August 01, 2021, 09:38:36 pm

Title: Test if I could write to a certain file
Post by: dietmar on August 01, 2021, 09:38:36 pm
Hi,

does anyone have a test routine ready to determine whether I am allowed to write to a specific file?
The caveat is, that this file may be already existent, but should not be changed (yet).
Using Unix, I would just use fpaccess - is there an equivalent for windows?

Thx,
--Dietmar
Title: Re: Test if I could write to a certain file
Post by: Bart on August 02, 2021, 10:55:59 am
On Windows: try to open it in write mode (fmOpenWrite or fmShareDenyWrite).
As long as you don't do any writing, the file won't change.

Bart
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 02, 2021, 11:08:00 am
Alternatively you could use SysUtils.FileIsReadOnly(), e.g.
Code: Pascal  [Select][+][-]
  1. { uses sysutils }
  2. function FileIsWriteable(const AFilename; String): Boolean;
  3. begin
  4.   Result := FileExists(AFilename) and not FileIsReadOnly(AFilename);
  5. end;


Never mind; all it does elsewhere than on *nix is to check the file attributes, which doesn't guarantee that it can indeed can written to :-[
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 02, 2021, 03:17:05 pm
I now tried:

function FileIsWriteable(fn: String) : Boolean;
begin
  //exit(FileOpen(fn,fmOpenWrite or fmShareDenyWrite)<>-1);
  exit(FileOpen(fn,fmOpenWrite or fmShareDenyWrite)<>THandle(-1));
end;

Both variants don't work and always return TRUE. The first variant additionally gives a strange compiler warning.

--Dietmar
Title: Re: Test if I could write to a certain file
Post by: PascalDragon on August 03, 2021, 09:03:35 am
I now tried:

function FileIsWriteable(fn: String) : Boolean;
begin
  //exit(FileOpen(fn,fmOpenWrite or fmShareDenyWrite)<>-1);
  exit(FileOpen(fn,fmOpenWrite or fmShareDenyWrite)<>THandle(-1));
end;

Both variants don't work and always return TRUE. The first variant additionally gives a strange compiler warning.

Independent of if it works for your purpose or not: if a valid handle is returned you should close it with FileClose before leaving FileIsWriteable otherwise you'll leak handles.
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 03, 2021, 02:43:53 pm
Thanks for the tip. I now changed it:

Code: Pascal  [Select][+][-]
  1. function FileIsWriteable(fn: String) : Boolean;
  2. var h: THandle;
  3. begin
  4.   h := FileOpen(fn,fmOpenWrite or fmShareDenyWrite);
  5.   if (h=-1) then
  6.     exit(False)
  7.   else begin
  8.     FileClose(h);
  9.     exit(True);
  10.   end;
  11. end;
  12.  

But the main problem remains. Even for Filenames like "C:\Programs\bla.txt", the function results a file handle... :(
Title: Re: Test if I could write to a certain file
Post by: marcio2003 on August 03, 2021, 03:03:34 pm
Hi,

Try it.

http://blog.vitorrubio.com.br/2010/10/obtendo-atributos-de-arquivos-com-o.html (http://blog.vitorrubio.com.br/2010/10/obtendo-atributos-de-arquivos-com-o.html)

Good job.
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 03, 2021, 04:06:08 pm
Try it.
http://blog.vitorrubio.com.br/2010/10/obtendo-atributos-de-arquivos-com-o.html (http://blog.vitorrubio.com.br/2010/10/obtendo-atributos-de-arquivos-com-o.html)

That's basically what FileIsReadOnly() does, i.e. read the file attributes. Unfortunately that's not enough because the file might not have the read-only attr. set but be nevertheless unwriteable due to file/folder permissions, etc. :(
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 04, 2021, 02:33:49 pm
I tried another solution:

Code: Pascal  [Select][+][-]
  1. function FileIsWriteable(fn: String) : Boolean;
  2. var
  3.   s: String;
  4.   h: THandle;
  5. begin
  6.   // try to write random file in same directory
  7.   repeat
  8.     s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';
  9.   until not FileExists(s);
  10.   h := FileCreate(s,fmShareExclusive);
  11.   if (h=-1) then
  12.     exit(False)
  13.   else begin
  14.     FileClose(h);
  15.     exit(True);
  16.   end;
  17. end;
  18.  

Didn't help either :(
What am I doing wrong? I am very desperate now...

--Dietmar
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 04, 2021, 02:39:55 pm
Have you checked whether the file is indeed created or (for previous attempts) if you can in fact write to that file?
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 04, 2021, 02:51:03 pm
I tried it with C:\Program Files\Test.dat.
A random file name C:\Program Files\32Afk4jf...$$$ was generated, but I could not write to this file. When choosing another folder like C:\ProgramData, it works...

--Dietmar
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 04, 2021, 03:05:21 pm
A random file name C:\Program Files\32Afk4jf...$$$ was generated, but I could not write to this file.

So FileCreate() didn't fail, after all ... but if you try a FileWrite() it fails, is that it? Now, that's certainly strange but it also means that to check if you can write to a file you'll have to ... try to write to it.

Apparently nothing else works ... unless, now I think about it, maybe trying to change its timestamp or its attributes would also work. Just remeber to store the previous vales, to restore them in case it does work after all.

In the meantime I'll see if I can mount and fire on a Windows machine to make some tests of my own.
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 04, 2021, 03:08:37 pm
In my function, I added

Code: Pascal  [Select][+][-]
  1.     For I:=0 to 100 do
  2.       FileWrite(h,I,SizeOf(i));
  3.  

just before the FileClose. It worked (in ProgramData) and didn't work in C:\Program Files - as it should. But nevertheless, both tests result in TRUE...

--Dietmar
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 04, 2021, 03:22:18 pm
You forgot to check the result of FileWrite. This should work:
Code: Pascal  [Select][+][-]
  1. function FileIsWriteable(fn: String) : Boolean;
  2. var
  3.   s: String;
  4.   h: THandle;
  5. begin
  6.   // try to write random file in same directory
  7.   repeat
  8.     s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';
  9.   until not FileExists(s);
  10.   { Note that this only tests if you can create a file and write
  11.     to it in that directory, not the writeability of the file "fn"}
  12.   h := FileCreate(s,fmShareExclusive)
  13.   Result := h <> -1;
  14.   if Result then begin
  15.     Result := FileWrite(h, s[1], Lentgh(s));
  16.     FileClose(h);
  17.   end;
  18. end;
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 04, 2021, 08:38:07 pm
Hi,

your code was a bit erroneous. I have corrected that (see below).
Nevertheless: Thanks very much, because now it WORKS ;-))

Just for the records or for anyone who can need this:

Code: Pascal  [Select][+][-]
  1. function MakeRandomString(l: Byte): String;
  2. var i: Integer;
  3.     s: String;
  4. begin
  5.   s := '';
  6.   for i:=1 to l do
  7.     case random(3) of
  8.       0: s := s + Chr(65+Random(26));
  9.       1: s := s + Chr(97+Random(26));
  10.       2: s := s + Chr(48+Random(10));
  11.     end;
  12.   exit(s);
  13. end;
  14.  
  15. function FileIsWriteable(fn: String) : Boolean;
  16. var
  17.   s: String;
  18.   h: THandle;
  19. begin
  20.   repeat
  21.     s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';
  22.   until not FileExists(s);
  23.   h := FileCreate(s,fmShareExclusive);
  24.   Result := h<>-1;
  25.   if Result then begin
  26.     Result := FileWrite(h,s[1],Length(s))<>-1;
  27.     FileClose(h);
  28.     DeleteFile(PChar(s));
  29.   end;
  30. end;
  31.  

Greetz
--Dietmar
Title: Re: Test if I could write to a certain file
Post by: lucamar on August 04, 2021, 09:06:28 pm
Duh! I went to the help to look for a DeleteFile() (and see its sintax) and on coming back ... forgot to include it! Sorry, man :-[
Title: Re: Test if I could write to a certain file
Post by: Kays on August 04, 2021, 09:39:14 pm
does anyone have a test routine ready to determine whether I am allowed to write to a specific file?
No. There are many ways how a user gets the privilege to write a file. While you can easily check for the classic Unix file mode yourself, it is also possible that you are allowed to write to a file by virtue of group membership. Then you had to determine whether a user is member of that group. There are also ACLs (https://en.wikipedia.org/wiki/Access-control_list), which can also grant permissions. Last but not least, it is also possible your program is run with superuser/root privileges. You see, you cannot positively check all of this.

You will necessarily have to involve the operating system, because only the OS will have the final say when you eventually attempt writing to that file. So a method is to simply try writing to that file.
The caveat is, that this file may be already existent, but should not be changed (yet).
Yes, instead of rewrite, which will clear the file, you can use append (https://www.freepascal.org/docs-html/rtl/system/append.html) (Delphi/FPC)/extend (Extended Pascal):
Code: Pascal  [Select][+][-]
  1. {**
  2.         \brief tests whether a file is writable
  3.        
  4.         \param f a text file that is already
  5.                 bound to file name (`assign`)
  6.         \return true if existent file is writable
  7. }
  8. function isWritable(var f: text): Boolean;
  9. begin
  10.         { remove any unchecked errors from previous code }
  11.         InOutRes := 0;
  12.        
  13.         {$push}
  14.                 {$IOChecks off}
  15.                 { this may potentially cause RTE 5 "file access denied" }
  16.                 append(f);
  17.         {$pop}
  18.        
  19.         isWritable := IOResult = 0;
  20.        
  21.         { close fails on an _un_opened file }
  22.         if isWritable then
  23.         begin
  24.                 close(f);
  25.         end;
  26. end;
It’s up to you to implement the part checking for not yet existent files. The RTE 2 “file not found” tells you that.

This method may potentially alter meta-data, like the access time. My tests on a Linux system here showed that the data/payload modification time was not altered, even if there was write(f, '');.

PS: Sorry, I just see in an strace that write(f, '') won’t even bother the OS, so it’s no surprise it does not change the mtime.
Title: Re: Test if I could write to a certain file
Post by: BobDog on August 05, 2021, 01:44:16 am

Check the file attributes-Windows.
Code: Pascal  [Select][+][-]
  1.    program arrtibutes;
  2.   uses
  3.   process;
  4.  
  5.     procedure getattributes(filename:ansistring);
  6.   var
  7.   s,chr,g:ansistring;
  8.   i:integer;
  9.   begin
  10.    (RunCommand('attrib '+ filename,s));
  11.    write(s);
  12.    if s[1]='F' then exit;
  13.    for i:=1 to 20 do
  14.    begin
  15.    chr:=s[i];
  16.    case chr of
  17.    'A':writeln('archive');
  18.    'C':writeln('compressed');
  19.    'H':writeln('hidden');
  20.    'I':writeln('not content indexed');
  21.    'O':writeln('offline');
  22.    'P':writeln('pinned (Windows 10 and OneDrive only)');
  23.    'R':writeln('readonly');
  24.    'S':writeln('system');
  25.    'T':writeln('temperory');
  26.    'U':writeln('unpinned (Windows 10 and OneDrive only)');
  27.    'V':writeln('integrity (Windows Server 2012R2+ ReFS only0');
  28.    'X':writeln('no_scrub_data (Windows Server 2012R2+ ReFS only)');
  29.    end
  30.    end;
  31.    writeln('----------------');
  32.   end;
  33.  
  34.  
  35.   begin
  36. getattributes('C:\Windows\notepad.exe');
  37. getattributes('C:\Windows\system32\attrib.exe');
  38. getattributes('C:\pagefile.sys');
  39. getattributes('C:\hiberfil.sys');
  40. getattributes('C:\swapfile.sys');
  41. getattributes('Nonesuch.txt');
  42.  writeln('Press return to finish . . .');
  43. readln;
  44. end.
Title: Re: Test if I could write to a certain file
Post by: Remy Lebeau on August 05, 2021, 04:03:09 am
your code was a bit erroneous. I have corrected that (see below).
Nevertheless: Thanks very much, because now it WORKS ;-))

Except, it doesn't actually work, at least not the way you originally asked for it:

does anyone have a test routine ready to determine whether I am allowed to write to a specific file?

If the caller actually passed in a full file path, the code is not creating/opening the requested file anymore, it merely determines the parent folder of the file and then creates its own random file in that folder, completely ignoring the requested file.  Essentially, you have now created a FolderIsWriteable() type of function (and in that scenario, using MakeRandomString() is overkill, when you can just use GetTempFileName() (https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamea) instead, or a GUID).

For Windows, I would opt for something more like the following instead.  If you need something that is cross-platform, {$IFDEF} the code accordingly:

Code: Pascal  [Select][+][-]
  1. uses
  2.   .., Windows;
  3.  
  4. function FileIsWriteable(fn: String; KeepFileIfCreatedNew: Boolean = False) : Boolean;
  5. var
  6.   h: THandle;
  7.   Err: DWord;
  8. begin
  9.   h := CreateFile(PChar(fn), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
  10.   Result := h <> INVALID_HANDLE_VALUE;
  11.   if Result then begin
  12.     Err := GetLastError;
  13.     CloseHandle(h);
  14.     if (Err <> ERROR_ALREADY_EXISTS) and (not KeepFileIfCreatedNew) then
  15.       DeleteFile(PChar(fn));
  16.   end;
  17. end;
  18.  

If CreateFile() succeeds, you are guaranteed to have write access to the file via the returned THandle only.  Once you close that THandle, all bets are off, you lose any guarantee that you will still have write access to the same file at a later time (ie, someone else could open the file and decide not to share write access to it, etc).

The mere presence of a FileIsWriteable()-like function introduces a TOCTOU race condition (https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) that you really should not be introducing to begin with.  If you want write access to a file, then just open the file for writing AT THE TIME you want to write to it, then actually write to it (and keep it open for later writes, if needed), and then close it when you are done using it.  Handle any failure that may occur during that time as needed.

You know the old saying "It is better to ask for forgiveness than permission"?  Well, that applies here, too.  If you want to do something (write to a file, etc), then just go ahead and attempt to do it, and handle the consequences as needed.  Don't try asking for permission first.
Title: Re: Test if I could write to a certain file
Post by: dietmar on August 05, 2021, 10:31:20 pm
Thank you for your answer! I will have to think about that...

--Dietmar
TinyPortal © 2005-2018