0

I'm running emacs under WSL 2, in which Windows drives are mounted below /mnt/<Drive Letter> with 9p as the filesystem. That filesystem does not support (setting) permissions on files.

I want to move a file from my native Linux filesystem to that Windows filesystem (mounted via 9p), like this:

(rename-file "/tmp/moveme.txt" "/mnt/d/temp/moved.txt")

However, this throws an error:

Debugger entered--Lisp error: (file-error "Copying permissions to" "Operation not permitted" "/mnt/d/temp/moved.txt")
  rename-file("/tmp/moveme.txt" "/mnt/d/temp/moved.txt")
  (progn (rename-file "/tmp/moveme.txt" "/mnt/d/temp/moved.txt"))
  elisp--eval-last-sexp(nil)
  eval-last-sexp(nil)
  funcall-interactively(eval-last-sexp nil)
  command-execute(eval-last-sexp)

Which is of course correct, the filesystem of the destination does not support permissions. Is there a way of moving a file and be "permissive" if setting permissions fails?

Workaround

It looks like when the error is thrown, the target file has already been created, but the source file has not been deleted yet. So I could probably work around this by catching the file-error, checking whether the target has been created, and if so, manually removing the source (and otherwise rethrowing). However, this workaround is dependent on the inner workings of rename-file and may break if the rename-file implementation changes. Thus I would prefer a "clean" solution.

2 Answers 2

0

In case anybody finds this and has the same problem, here's my workaround as described in the question. It seems to be working reasonably well.

(defun permissive-rename-file (source dest)
    """Move a file like rename-file, but handle the error that
    permissions cannot be set at the target. In this case, the file
    is moved without permissions being transferred."

    (defun handle-file-error (errvar)
        (let ((errsym (car errvar))
                    (errdata (cdr errvar)))
            ;; Make sure the error we're handling is really a 'copying permissions' error.
            ;; If not, rethrow
            (when (not (and (string-equal (nth 0 errdata) "Copying permissions to")
                            (string-equal (nth 1 errdata) "Operation not permitted")))
                (signal errsym errdata))
            ;; Make sure that the file actually arrived at its destination, otherwise
            ;; deleting the source would cause data loss.
            ;;
            ;; Note that we don't check here that source and dest are identical files
            ;; now. Since we called rename-file without OK-IF-ALREADY-EXISTS, it would
            ;; have thrown a (different) error if the destination had already existed.
            ;; Thus, if it exists now, we may assume that it's a copy of source.
            ;;
            ;; Yes, in the worst case this could race with something else creating
            ;; dest, but that's a user error.
            (when (not (file-exists-p dest))
                (signal errsym errdata))
            ;; If the file exists at the destination, complete the 'move' by deleting
            ;; the source.
            (when (file-exists-p source)
                (delete-file source))
            ))

    (condition-case errvar
            (rename-file source dest)
        (file-error (handle-file-error errvar))))

Warning: This may break when the implementation of rename-file changes, e.g. if the wording of the error message changes or if the (partially created) destination file is deleted again on an error.

0

I know nothing about Windows or WSL.

My guess is instead of using rename-file, you can advice it so that the renaming is done by a WSL shell-command. This way you use the rename-file interface, but the implementation is done by the agent who knows a lot about the 9p filesystem (and also linux file system) It would be good to check if you can use TRAMP to accomplish what you want. IOW, given a mount point---which appears as remote, but which is in fact a loopback/localhost connection--- if I move a file from a to b within that mount point, will TRAMP use the command line utilities behind the scense to copy the file. (This sentence could be confusing: For example, I can do sudo-edit on a local file, with the TRAMP handling the user permission behind the scenes; in your case, you want the TRAMP to handle file system difference behind the scenes)

If you are willing to take extra trouble, do a M-x report-emacs-bug, and a shoot a mail to the developers. Look inside the buffer, for the To: email address.


I googled a bit, and here are some resources that I found ...

From Editing files in a Windows Subsystem for Linux development environment

Microsoft has recently added comprehensive support for this, and it should be generally available in the April 2019 (19H1) update. Once it's ready, a 9P server will run in the background whenever a Linux distro is running. The 9P server will be able to handle Linux filesystem metadata, and Windows will be able to treat it as a network drive so it can access it safely. You can read about it at https://blogs.msdn.microsoft.com/commandline/2019/02/15/whats-new-for-wsl-in-windows-10-version-1903/.

With the new feature, you'll be able to safely access both Windows and Linux filesystem files from Windows, as long as you go through the 9P server. This will be handled natively from within WSL. For example, from the WSL command line you'll be able to type code /mnt/c/Users/username/src/windows-file.txt to open a Windows file in VS Code, or type code /home/username/src/linux-file.txt to open a Linux file in VS Code.

If you're not part of the Windows Insider Program, you won't have access to this yet so you'll still have to use an older method, such as wslpath.

wslpath will convert between Windows- and Linux-style paths so you can easily open Windows files from the WSL command line. As per https://github.com/Microsoft/WSL/issues/3146#issuecomment-388118689, it will refuse to convert Linux filesystem paths (i.e. %AppData%\lxss), because without 9P it's unsafe to modify these files from Windows. This means you can't open /home/username/src/linux-file.txt, but you can use code "$(wslpath -aw /mnt/c/Users/username/src/windows-file.txt)".

In the past, there were a number of third-party tools to perform the same conversion but wslpath does it natively-- in fact, ls -l /bin/wslpath shows that it's just a link to /init.


From What’s new for WSL in Windows 10 version 1903? - Windows Command Line

To put it briefly: a 9P protocol file server facilitates file related requests, with Windows acting as the client.

We've modified the WSL init daemon to initiate a 9P server. This server contains protocols that support Linux metadata, including permissions. A Windows service and driver that act as the client and talks to the 9P server (which is running inside of a WSL instance). Client and server communicate over AF_UNIX sockets, since WSL allows interop between a Windows application and a Linux application using AF_UNIX as described in this post.

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.