Simple Locking Wrapper for VI

This post presents a quick way to add file locking to the vi editor.

The vi editor is found on pretty much every unix system, which means it is often used by system administrators to update configuration files. Unfortunately vi does not lock the file being edited. This can lead to two or more people inadvertently editing the same file at the same time. Edits can become duplicated, confused or lost entirely, perhaps leaving the file in a non-working state.

Editing Critical Files

It is bad enough in the case of small files such as yum.conf or ntp.conf. More serious are files like /etc/fstab, where a simple syntax error could lead to an unbootable system. Worst of all are network-wide resources like DNS zone files. A mistake here could mess up more than one machine.

Possible Solutions

Commands like visudo and vipw effectively lock the file being edited (by allowing only one session), but they only work on certain files: sudoers and passwd. Vim, the enhanced vi on Linux, will print a warning message if a file is already being edited, but will still allow multiple edits to take place, with all the hazards described above.

Wrapper for VI

It is easy to put a “wrapper” around the vi command that will implement file locking and avoid multi-edit pile ups. Below is a Perl script designed to wrap the “vi” binary on a Linux system. It should also work on Solaris and other versions of Unix. Once installed, when you type “vi“, the script is called, locks the file of interest and then calls vi to do the edit. When you quit vi, the script removes the lock and terminates. Anybody else typing “vi” during your edit will receive a polite message and be rebuffed from editing the same file.

#!/usr/bin/perl -w
#
# Wrapper script for vi.  Uses exclusive locking to prevent 2 people
# from editing the file at the same time.
#
# For more info see
# http://unixetc.co.uk/2011/11/05/simple-locking-wrapper-for-vi/
#

$VI = "/bin/vi.original";

my $target = shift || "";

use Fcntl qw(:flock);

unless (-e "$target") {
   system("$VI $target");
   print STDERR "File "$target" was new.  Did not lock the file.n";
   exit;
}

unless (-w "$target") {
   print STDERR "You do not have permissions to edit "$target"n";
   exit;
}

# Open the file
unless (open (TARGET, "+<", "$target"))  {
   die "Could not open file $target for writing.";
}

# Attempt to lock file.
unless (flock(TARGET, LOCK_EX | LOCK_NB)) {
   print STDERR "Could not get write lock on "$target"n";
   print STDERR "Probably the file is being edited by another usern";
   exit;
}

# File successfully locked. Run vi.
system("$VI $target");

### Script sleeps here while user is in the spawned vi session ##

print "Finished editing file "$target".  Releasing lockn";
close TARGET;

Installing the Script

It could be installed as follows. Save the script to your system as vilock.pl, then, as root, locate the vi binary:

# which vi
/bin/vi

Install the script by renaming the original vi program and putting the script in its place:
# mv /bin/vi /bin/vi.org
# mv vilock.pl /bin/vilock.pl
# ln -s /bin/vilock.pl /bin/vi
# chmod 755 /bin/vilock.pl

NB: If vi is located somewhere else on your system (eg on Linux it is often at /usr/bin/vi), edit vilock.pl and change the "$VI = "/bin/vi.original"; line accordingly. The $VI variable tells the script how to call the original vi.

More Explanation of the Script

There is not much to explain here. The script tries to obtain an exclusive lock on the file. If somebody else is already editing the file, the lock is not obtainable: the script warns that the file is busy, and quits. If, on the other hand, the file is not already being edited, the script successfully obtains its lock, calls vi, and the user edits the file. During this time, the file is locked. Other users attempting to edit the file will receive the warning above and be rebuffed. When the user quits the vi session, the script resumes, releases the lock and exits, leaving the file once again free for others to edit.

That "NB" flag makes the lock non blocking. That just means that the flock call will not sit and wait for the file to become free. It will return immediately, which it needs to so that the script can print the warning and exit. If the "NB" flag were not used, a second user trying to edit the file would see his vi command apparently hang.

Note that the Perl flock command will use the operating system's native locking mechanism, which on unix is "advisory" only. While the file is locked, the OS will not actually prevent it from being changed. Even with the lock active, another user could come along and use a different editor to modify the file, or clobber it from the command line, or just delete it. But the script should at least keep important files safe from basic multi-edits.

6 thoughts on “Simple Locking Wrapper for VI

  1. Hello

    Thank you for this, very useful. I made one minor change to the script so that it could cope with vi being in different locations on different systems:

    my $VI = `which vi`;
    chomp $VI;

    Replaces:

    my $VI = “/bin/vi.org”;

  2. Thank you Hobett. The which command itself might also be in different locations on different systems. It is probably best to check when installing the script.

  3. Hi There,
    I’m trying to run this on solaris 10, and getting a lot of errors!! Any idea how to fix these? Sorry, my perl knowledge is extremely limited and I tried to debug this script but to no avail.

    Much appreciated.

    Scalar found where operator expected at vilock.pl line 25, near “”File “$target”
    (Missing operator before $target?)
    String found where operator expected at vilock.pl line 25, near “$target” was new. Did not lock the file.n””
    (Missing operator before ” was new. Did not lock the file.n”?)
    Scalar found where operator expected at vilock.pl line 30, near “”You do not have permissions to edit “$target”
    (Missing operator before $target?)
    String found where operator expected at vilock.pl line 30, near “$target”n””
    (Missing operator before “n”?)
    Scalar found where operator expected at vilock.pl line 41, near “”Could not get write lock on “$target”
    (Missing operator before $target?)
    String found where operator expected at vilock.pl line 41, near “$target”n””
    (Missing operator before “n”?)
    Scalar found where operator expected at vilock.pl line 51, near “”Finished editing file “$target”
    (Missing operator before $target?)
    String found where operator expected at vilock.pl line 51, near “$target”. Releasing lock.n””
    (Missing operator before “. Releasing lock.n”?)
    syntax error at vilock.pl line 25, near “”File “$target”
    Global symbol “%class” requires explicit package name at vilock.pl line 25.
    syntax error at vilock.pl line 30, near “”You do not have permissions to edit “$target”
    syntax error at vilock.pl line 41, near “”Could not get write lock on “$target”
    syntax error at vilock.pl line 51, near “”Finished editing file “$target”
    Execution of vilock.pl aborted due to compilation errors.

    • Hi Ramiz, I fixed the script – please try again. For some reason all of the escape characters had been removed. Maybe due to WordPress (the blogging platform) or the way I pasted it. Anyway it seems to work now. Just tested it on Linux, will try Solaris tomorrow.

  4. Note that this is not as safe as it seems — vim or any other editor that uses temporary files and renames them over the original is dangerous because it loses the lock when you save. So be careful to not write the file until finished!

    What happens is that when the file is saved, the temporary file is written to disk and then `rename(2)`’d into place (or similar). The _in situ_ file is now a different file than the locked file (which exists now as an anonymous file in memory), so the file lock is effectively lost.

    Running `vim` with the `-n` option (“No swap file, use memory only”) does not
    circumvent the edit-temporary-and-rename behavior.

    You can see this by watching the inode of the file being edited: `ls -i
    `. This does not always happen, however, depending on an uncertain set of
    factors, such as file systems, directory permissions, kernel version, vi/vim
    version, features, etc.

    My cursory testing with the stock `/usr/bin/vi` in Solaris 10 did not show this behavior (but it has its own problems, like wide terminals and truncating too-long files). But on a Linux system `vim` is the default and it most definitely does exhibit this behavior (although strangely not with a tmpfs “/tmp”).

  5. Hi Wil. Interesting comments. I could not reproduce your observation of vim losing a lock. In my own testing with Linux, vim does not lock the target file at all. The only lock it obtains is on its own swap (“.swp”) file, and it does not lose this lock when saving, only when vim exits. Note I don’t use a .vimrc file, do you ? If so, its contents might explain why vim behaves differently for both of us.

    Vim does not appear to use any “temporary file”. It does create a swap file (.swp), but this is a binary journal, not a copy of the target file, and it is not copied over the target file at any time, as far as I can see.

    Vim behaviour does change depending on which of its many soft links is used to call the binary (vi, view, ex, rview… and so on), as well as flags being passed and the effect of any .vimrc. Invoking vim as the actual binary (/usr/bin/vim.tiny on my system), it exhibits copy-on-write behaviour, as most editors tend to. Every time the target file is saved, its inode number changes, as you observe. In other words you end up with a new file and inode every time, but under the same file name.

    In the wrapper script above, the calling name is “vi.original”, with no flags, and behaviour is becomes edit-in-place, not copy-on-write. That is, I can modify and save the file many times in the same session, and the on-disk inode number does not change. Finally, when I exit the session, the inode number again remains unchanged. Typing “vi.original” on the command line, or the name of another interevening soft link (eg. /etc/alternatives/vi) results in the same behaviour.

    You describe a bug with the way vim handles temporary files and/or virtual memory. However as I can’t reproduce the behaviour, I can’t really comment on that. The wrapper script above is intended to reduce the risk of two people accidentally editing a file at the same time. It does that by simply blocking the second person from invoking the editor at all. It won’t help with other vi bugs or shortcomings though.

Leave a Reply

Your email address will not be published. Required fields are marked *