Normally an executable file cannot be overwritten while a process is started from this file. Is there a possibility to circumvent/break this lock?
Yes. Do not overwrite the executable; replace it.
That is, you save the new executable under a temporary name in the same directory (or anywhere in the same file system -- must be on the same mount!), then either rename()
or link()
the temporary file over the executable.
In a shell script, you can use mv -f newbinary oldbinary
, if both newbinary
and oldbinary
are in the same file system and mount. In a Bash script, you might use something like
#!/bin/bash
BINDIR=/usr/bin
# Autoremoved work directory
Work="$(mktemp -d)" || exit 1
trap "cd / ; rm -rf '$Work'" EXIT
# ... Check if new binaries available ...
# Otherwise: exit 0
# ... Download new binaries under "$Work/" ...
# Copy 'executable' to $BINDIR, under a temporary name
tempbin="executable.$PID-$RANDOM$RANDOM$RANDOM"
if ! mv -f "$Work/executable" "$BINDIR/$tempbin" ; then
# Failed
exit 1
elif ! mv -f "$BINDIR/$tempbin" "$BINDIR/executable" ; then
# Failed
exit 1
fi
# Successfully replced.
exit 0
This works on all POSIXy systems, because file name is completely separate of the inode that specifies its contents, access mode, ownership, timestamps, and so on.
In practice, the kernel will retain the old inode for as long as there are executables running it, or any process has it open. However, the file name will immediately point to the new inode, with the new executable contents. So, essentially, the rename/link simply changes which inode the file name refers to. That is also why the temporary file must reside on the same filesystem (same mount).
The goal is the process (a long running task) should update itself with as little downtime as possible.
It is a common security hole to allow a process to change itself. Typically, it is not even allowed at all in POSIXy systems, unless the process is run with superuser privileges (i.e., as root
or in Linux, with CAP_FOWNER
capability). You do NOT want to do this.
(Just because it is common to do so, for example with PHP web stuff, does not make it sane or safe. If it did, then we'd have to agree that excrement tastes good, because there are billions of flies and dung beetles that think so. If you take a look, you'll find that such web services ALL have had severe security problems, some directly related to this update mechanism. Some maintainers of said package claim that problems during updates, like man-in-the-middle attacks, are the users' fault, not theirs, though. They're wrong, of course.)
Instead, you should have a separate, privileged service that periodically checks for updates, and when found, retrieves the new version using the above replacement method. In the simplest case, this can simply be run from cron or similar.
If your users really want you to, you can create a minimal C daemon that periodically checks if a new version is available. You can have it receive on a specific Unix domain datagram address, so that your executable can send a single character to it (no matter which user it is run as) for the update daemon to do a check then and there (unless it has checked recently enough). Essentially, it'll just wait (say, using select()
) for enough time to elapse, or a specific request to check. When it is time, it'll run a shell script to check if a new executable is available (say, using popen()
etc.; the typical location to save such scripts is in /usr/lib/yourservice/
). If the script responds that a new version is available, run another script to download and replace the binary. If the process receives a SIGHUP
signal, do the check immediately; if it receives a SIGTERM
signal, exit. That way it can be run as a service, and won't consume much resources when running.
In your long-running executable, if it is at a point where it can replace itself with a newer version, use stat()
on /proc/self/exe
and argv[0]
, to verify if they have the same st_dev
and st_ino
. If they do not, then the update service has provided a newer version of the executable, and your service can run
if (argv[0][0] == '/')
execv(argv[0], argv);
else
execvp(argv[0], argv);
or, if you define the absolute path to your executable at compile time in say exepath
, then
execvp(exepath, argv);
to replace itself with the newer version.
Do note that such a process should close all open file descriptors (except for standard streams; 0, 1, and 2, or STDIN_FILENO
, STDOUT_FILENO
, and STDERR_FILENO
), when it starts up. (That is, close all open file descriptors between 3 and sysconf(_SC_OPEN_MAX)
, inclusive.) This is because exec*()
functions do not close file descriptors (other than those marked O_CLOEXEC
/FD_CLOEXEC
), so any descriptors that might be open at time of exec will be left open. Doing it this way also means that if exec fails, your service can continue running normally.