Test if I could write to a certain file lazarus / 67-Windows ---------------------------------------------------------------------- dietmar · 2021-08-01 19:38 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 ---------------------------------------------------------------------- Bart · 2021-08-02 08:55 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 ---------------------------------------------------------------------- lucamar · 2021-08-02 09:08 Alternatively you could use SysUtils.FileIsReadOnly(), e.g. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { uses sysutils }function FileIsWriteable(const AFilename; String): Boolean;begin  Result := FileExists(AFilename) and not FileIsReadOnly(AFilename);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 ---------------------------------------------------------------------- dietmar · 2021-08-02 13:17 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 ---------------------------------------------------------------------- PascalDragon · 2021-08-03 07:03 Quote from: dietmar on August 02, 2021, 03:17:05 pmI 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. ---------------------------------------------------------------------- dietmar · 2021-08-03 12:43 Thanks for the tip. I now changed it: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function FileIsWriteable(fn: String) : Boolean;var h: THandle;begin  h := FileOpen(fn,fmOpenWrite or fmShareDenyWrite);  if (h=-1) then    exit(False)  else begin    FileClose(h);    exit(True);  end;end; But the main problem remains. Even for Filenames like "C:\Programs\bla.txt", the function results a file handle... ---------------------------------------------------------------------- marcio2003 · 2021-08-03 13:03 Hi, Try it. http://blog.vitorrubio.com.br/2010/10/obtendo-atributos-de-arquivos- com-o.html Good job. ---------------------------------------------------------------------- lucamar · 2021-08-03 14:06 Quote from: marcio2003 on August 03, 2021, 03:03:34 pmTry it. 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. ---------------------------------------------------------------------- dietmar · 2021-08-04 12:33 I tried another solution: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function FileIsWriteable(fn: String) : Boolean;var  s: String;  h: THandle;begin  // try to write random file in same directory  repeat  s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';  until not FileExists(s);  h := FileCreate(s,fmShareExclusive);  if (h=-1) then  exit(False)  else begin    FileClose(h);    exit(True);  end;end;  Didn't help either What am I doing wrong? I am very desperate now... --Dietmar ---------------------------------------------------------------------- lucamar · 2021-08-04 12:39 Have you checked whether the file is indeed created or (for previous attempts) if you can in fact write to that file? ---------------------------------------------------------------------- dietmar · 2021-08-04 12:51 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 ---------------------------------------------------------------------- lucamar · 2021-08-04 13:05 Quote from: dietmar on August 04, 2021, 02:51:03 pmA 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. ---------------------------------------------------------------------- dietmar · 2021-08-04 13:08 In my function, I added Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     For I:=0 to 100 do      FileWrite(h,I,SizeOf(i));  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 ---------------------------------------------------------------------- lucamar · 2021-08-04 13:22 You forgot to check the result of FileWrite. This should work: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function FileIsWriteable(fn: String) : Boolean;var  s: String;  h: THandle;begin  // try to write random file in same directory  repeat  s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';  until not FileExists(s);  { Note that this only tests if you can create a file and write    to it in that directory, not the writeability of the file "fn"}  h := FileCreate(s,fmShareExclusive)  Result := h <> -1;  if Result then begin    Result := FileWrite(h, s[1], Lentgh(s));    FileClose(h);  end;end; ---------------------------------------------------------------------- dietmar · 2021-08-04 18:38 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function MakeRandomString(l: Byte): String;var i: Integer;    s: String;begin  s := '';  for i:=1 to l do    case random(3) of      0: s := s + Chr(65+Random(26));      1: s := s + Chr(97+Random(26));      2: s := s + Chr(48+Random(10));    end;  exit(s);end; function FileIsWriteable(fn: String) : Boolean;var  s: String;  h: THandle;begin  repeat    s := ExtractFilePath(fn) + MakeRandomString(16) + '.$$$';  until not FileExists(s);  h := FileCreate(s,fmShareExclusive);  Result := h<>-1;  if Result then begin    Result := FileWrite(h,s[1],Length(s))<>-1;    FileClose(h);  DeleteFile(PChar(s));  end;end;  Greetz --Dietmar ---------------------------------------------------------------------- lucamar · 2021-08-04 19:06 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 ---------------------------------------------------------------------- Kays · 2021-08-04 19:39 Quote from: dietmar on August 01, 2021, 09:38:36 pmdoes 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, 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.Quote from: dietmar on August 01, 2021, 09:38:36 pmThe 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 (Delphi/FPC)/extend (Extended Pascal):Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {**        \brief tests whether a file is writable                \param f a text file that is already                bound to file name (`assign`)      \return true if existent file is writable}function isWritable(var f: text): Boolean;begin        { remove any unchecked errors from previous code }        InOutRes := 0;                {$push}              {$IOChecks off}                { this may potentially cause RTE 5 "file access denied" }                append(f);        {$pop}              isWritable := IOResult = 0;                { close fails on an _un_opened file }        if isWritable then        begin              close(f);        end;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. ---------------------------------------------------------------------- BobDog · 2021-08-04 23:44 Check the file attributes-Windows. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i    program arrtibutes;  uses  process;     procedure getattributes(filename:ansistring);  var  s,chr,g:ansistring;  i:integer;  begin   (RunCommand('attrib '+ filename,s));   write(s);   if s[1]='F' then exit;   for i:=1 to 20 do   begin   chr:=s[i];   case chr of   'A':writeln('archive');   'C':writeln('compressed');   'H':writeln('hidden');   'I':writeln('not content indexed');   'O':writeln('offline');   'P':writeln('pinned (Windows 10 and OneDrive only)');   'R':writeln('readonly');   'S':writeln('system');   'T':writeln('temperory');   'U':writeln('unpinned (Windows 10 and OneDrive only)');   'V':writeln('integrity (Windows Server 2012R2+ ReFS only0');   'X':writeln('no_scrub_data (Windows Server 2012R2+ ReFS only)');   end   end;   writeln('----------------');  end;    beg ingetattributes('C:\Windows\notepad.exe');getattributes('C:\Windows\sy stem32\attrib.exe');getattributes('C:\pagefile.sys');getattributes('C: \hiberfil.sys');getattributes('C:\swapfile.sys');getattributes('Nonesu ch.txt'); writeln('Press return to finish . . .');readln;end. ---------------------------------------------------------------------- Remy Lebeau · 2021-08-05 02:03 Quote from: dietmar on August 04, 2021, 08:38:07 pmyour 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: Quote from: dietmar on August 01, 2021, 09:38:36 pmdoes 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() 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  .., Windows; function FileIsWriteable(fn: String; KeepFileIfCreatedNew: Boolean = False) : Boolean;var  h: THandle;  Err: DWord;begin  h := CreateFile(PChar(fn), GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, nil, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);  Result := h <> INVALID_HANDLE_VALUE;  if Result then begin    Err := GetLastError;    CloseHandle(h);    if (Err <> ERROR_ALREADY_EXISTS) and (not KeepFileIfCreatedNew) then    DeleteFile(PChar(fn));  end;end;  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 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. ---------------------------------------------------------------------- dietmar · 2021-08-05 20:31 Thank you for your answer! I will have to think about that... --Dietmar