1

The file-writable-p docstring says:

Return t if file FILENAME can be written or created by you.

In Windows you cannot usually write on files when they are used by a different app. In these instances you get a permission denied error since the process blocks the access.

For example, in most of the viewers, if you open ~/foo.pdf, you cannot write access to it. However:

(file-writable-p "~/foo.pdf")

still returns t. Therefore, with ~/foo.pdf open,

(if (file-writable-p "~/foo.pdf")
    (with-temp-file "~/foo.pdf" (insert "Hello World")))

returns the error: (file-error "Opening output file" "Permission denied" ..... The same happens with:

(if (file-writable-p "~/foo.pdf")
    (delete-file "~/foo.pdf"))

Is this the way file-writable-p is intended to work?
If so, which is the proper path to safely write some output to a file?

7
  • This doesn't sound like an Emacs question. Check whether you have execute (x) permissions on the directory where the file is. You typically need that.
    – Drew
    Commented Jan 29, 2018 at 18:29
  • "Blocked by another application" sounds like a Windows issue. The best way to handle these is to just error out and not attempt doing anything with that file.
    – wasamasa
    Commented Jan 29, 2018 at 18:46
  • 1
    @wasamasa: "Sounds like a Windows issue": of course it is (see the title). "error out and not attempt doing anything with that file": this is what I would like to do with file-writable-p or an equivalent Emacs file system function.
    – antonio
    Commented Feb 1, 2018 at 8:35
  • 1
    It's not a matter of file permissions, it's your operating system locking files when accessed by an application (so that access by another application fails). There's nothing you can do about it, best leave the error unhandled or if you must, offer the user to retry later.
    – wasamasa
    Commented Feb 1, 2018 at 8:48
  • 2
    Furthermore, this pattern is considered bad enough to be a vulnerability class of its own: en.wikipedia.org/wiki/Time_of_check_to_time_of_use
    – wasamasa
    Commented Feb 1, 2018 at 14:27

1 Answer 1

0

A version of file-writable-p which returns nil if a Windows process is blocking write access.

(defun file-unblocked-p (filename)
  "This function works similarly to `file-writable-p' but is  with Windows permission denied errors. Therefore it returns nil if another process is blocking write access."  

  (if (not (file-exists-p filename))
      (file-writable-p filename)
    (not (condition-case err
             (rename-file filename filename)        
           (error err)))))

Note: I have tested it on Windows only.

Use this function wisely. In the generality of cases:

(if (file-unblocked-p "~/foo.pdf")
    (delete-file "~/foo.pdf"))

should work, but it is always possible that the writable condition holds when you test it and does not when you actually delete (write) the file. It could be pretty rare, but that depends on your specific use case. If one wants to be absolutely sure to avoid race conditions, one could just write and then check for errors:

(not (condition-case err
     (delete-file filename)        
       (error err)))
3
  • 1
    The docstring for rename-file says it signals a file-already-exists error if the new filename already exists, so file-writable-cross-p returns nil for unlocked and writable files. Would the optional argument OK-IF-ALREADY-EXISTS help? By the way, if you think the behaviour of file-writable-p is incorrect you should report it with M-x report-emacs-bug RET.
    – Basil
    Commented Feb 1, 2018 at 13:23
  • @Basil: Have you tried this in Linux? In Windows, the problem happens only, when the directory is different: (rename-file "~/foo.pdf" "~/sub/foo.pdf"), which is never the case here. More than a bug, it is a misnomer. The function addresses Linux style permissions. Still a user might think that, if they can write to a file, no process is blocking it preventing them to do so. To avoid confusion I have changed my name.
    – antonio
    Commented Feb 1, 2018 at 16:16
  • Yes, I'm on GNU/Linux, and I understand your description of the underlying issue, but what your function says it does is different from what it actually does, since it returns nil even for writable files which are not being blocked by any other process. Furthermore, the docstring of rename-file should be correct for all platforms. If its behaviour does not match its docstring please consider reporting it.
    – Basil
    Commented Feb 1, 2018 at 16:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.