How vim overwrites readonly mode?

When you do w! in Vim, what actually happens depends on who owns the file.

  • If you (the current user) are the owner of the file, Vim will change the permissions to be writable before rewriting the file. It then removes the write permissions to restore the permission bits to what it was from the start.

  • If you are not the owner of the file, but if you have write permissions in the current directory, Vim will delete the original file and write the document to a new file with the same name. The new file will then be assigned the same permissions as the original file, but will be owned by you.

At no time does Vim gain elevated privileges to be able to write to the file.

The mechanics described above are the available options that any program that needs to write to a read-only file has to choose from (i.e. either temporarily change the permission while writing to the file, or delete the file and create a new one), and what Vim ends up choosing to do may in the end may depend on a number of configurable settings.

As seen in comments below, there is some confusion about the above. If you want to see for yourself what actually happens with your setup of Vim on your particular brand of Unix, I'd recommend tracing the system calls that Vim does while writing to a read-only file. How this is done depends on what Unix you are using. On Linux, this is likely done through e.g. strace vim file (then editing the file, saving it with w! and exiting).


This is the first case (output from ktrace+kdump on OpenBSD):

13228 vim      CALL  chmod(0x19b1d94b4b10,0100644<S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH|S_IFREG>)
13228 vim      NAMI  "file"
13228 vim      RET   chmod 0
13228 vim      CALL  lseek(3,0x1000,SEEK_SET)
13228 vim      RET   lseek 4096/0x1000
13228 vim      CALL  write(3,0x19b1e0aa9000,0x1000)

This changes permissions on the file so that it's writable (the S_IWUSR flag used with chmod()) and writes the buffer to it.

It then sets the original permissions:

13228 vim      CALL  fchmod(4,0100444<S_IRUSR|S_IRGRP|S_IROTH|S_IFREG>)
13228 vim      RET   fchmod 0
13228 vim      CALL  close(4)
13228 vim      RET   close 0

For the other case:

It first unlinks (deletes) the file and then recreates it (before writing to the file and changing permissions later):

44487 vim      CALL  unlink(0x79fdbc1f000)
44487 vim      NAMI  "file"
44487 vim      RET   unlink 0
44487 vim      CALL  open(0x79fdbc1f000,0x201<O_WRONLY|O_CREAT>,0644<S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH>)
44487 vim      NAMI  "file"
44487 vim      RET   open 4

Vim cannot gain additional permissions. The :w! just overrides the internal 'readonly' option, which may have been set because:

  • you've opened the file via the -R command-line option or with :view instead of :edit, or :setlocal readonly
  • Vim recognizes that the file currently doesn't have write permissions

For the latter case, a file write may still be possible because Vim (by default) creates a new file and then replaces the original file with it. That still depends on the permissions being set in a way to allow this.


To really gain write permissions where the user that opened Vim doesn't have any, the :w !sudo tee >/dev/null file trick has to be used, either directly, or through a plugin like SudoEdit.


You can't ever write to a file that you don't have write permissions on. However you can delete that file if you have write permissions on the directory.

The trick that VIM is using is to delete the file and write a new one.

It's possible to show this is the method VIM uses without reading source code by checking the inode number before and after:

$ touch foo
$ chmod u-w foo
$ ls -li foo
60818465 -r--r----- 1 philip philip 0 Feb 25 10:24 foo
$ vi foo
$ # edit the file and save with :w!
$ ls -li foo
60818467 -r--r----- 1 philip philip 8 Feb 25 10:25 foo

Note that the inode number changed showing that the new file is NOT the same file as the one you edited.


FYI My current config is really short:

runtime! debian.vim
if has("syntax")
  syntax on
endif
set tabstop=4
set autoindent

And debian packages installed are:

vim                               2:8.1.0875-1
vim-common                        2:8.1.0875-1
vim-runtime                       2:8.1.0875-1
vim-tiny                          2:8.1.0875-1

Tags:

Vim

Shell