In my C++ application, my application does an execv()
in a fork()
ed child process to use the same executable to process some work in a new child process with different arguments that communicates with pipes to the parent process. To get the pathname to self, I execute the following code on the Linux port (I have different code on Macintosh):
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
However, if while the executable is running, I replace the executable with an updated version of the binary on disk, the readlink()
string result is: "/usr/local/bin/myExecutable (deleted)"
I understand that my executable has been replaced by a newer updated version and the original for /proc/self/exe
is now replaced, however, when I go to execv()
it now fails with the errno
2 - No such file or directory.
due to the extra trailing " (deleted)"
in the result.
I would like the execv()
to either use the old executable for self, or the updated one. I could just detect the string ending with " (deleted)"
and modify it to omit that and resolve to the updated executable, but that seems clumsy to me.
How can I execv()
the current executable (or its replacement if that is easier) with a new set of arguments when the original executable has been replaced by an updated one during execution?
One solution is at executable startup (e.g. near the beginning of main()
) to read the value of the link /proc/self/exe
once and store it statically for future use:
static string savedBinary;
static bool initialized = false;
// To deal with issue of long running executable having its binary replaced
// with a newer one on disk, we compute the resolved binary once at startup.
if (!initialized) {
const size_t bufSize = PATH_MAX + 1;
char dirNameBuffer[bufSize];
// Read the symbolic link '/proc/self/exe'.
const char *linkName = "/proc/self/exe";
const int ret = int(readlink(linkName, dirNameBuffer, bufSize - 1));
savedBinary = dirNameBuffer;
// On at least Linux, if the executable is replaced, readlink() of
// "/proc/self/exe" gives "/usr/local/bin/flume (deleted)".
// Therefore, we just compute the binary location statically once at
// startup, before it can possibly be replaced, but we leave this code
// here as an extra precaution.
const string deleted(" (deleted)");
const size_t deletedSize = deleted.size();
const size_t pathSize = savedBinary.size();
if (pathSize > deletedSize) {
const size_t matchPos = pathSize - deletedSize;
if (0 == savedBinary.compare(matchPos, deletedSize, deleted)) {
// Deleted original binary, Issue warning, throw an exception, or exit.
// Or cludge the original path with: savedBinary.erase(matchPos);
}
}
initialized = true;
}
// Use savedBinary value.
In this way, it is very unlikely that the original executable would be replaced within the microseconds of main() caching the path to its binary. Thus, a long running application (e.g. hours or days) could get replaced on disk, but per the original question, it could fork()
and execv()
to the updated binary that perhaps has a bug fix. This has the added benefit of working across platforms, and thus the differing Macintosh code to read the binary path could be likewise protected from binary replacement after startup.
WARNING editors note: readlink
does not null terminate the string, so the above program may or may not work accidentally if the buffer was not filled with zeros before calling readlink
Instead of using readlink
to discover the path to your own executable, you can directly call open
on /proc/self/exe
. Since the kernel already has an open fd to processes that are currently executing, this will give you an fd regardless of whether the path has been replaced with a new executable or not.
Next, you can use fexecve
instead of execv
which accepts an fd
parameter instead of a filename
parameter for the executable.
int fd = open("/proc/self/exe", O_RDONLY);
fexecve(fd, argv, envp);
Above code omits error handling for brevity.
The reason you get the (deleted)
part into the symbolic link is that you have substituted the file with the right program binary text with a different file, and the symbolic link to the executable is never valid again. Suppose you use this symbolic link to get the symbol table of this program or to load some data embedded on it, and you change the program... the table would be incorrect and you can even crash your program. The executable file for the program you were executing is no longer available (you have deleted it) and the program you have put in its place doesn't correspond to the binary you are executing.
When you unlink(2) a program that is being executed, the kernel marks that symlink in /proc
, so the program can
/proc
tree)You cannot write to a file that is being executed by the kernel, but nobody prevents you to erase that file. The file will continue to be present in the filesystem as long as you execute it, but no name points to it (it's space will be deallocated once the process exit(2)) The kernel doesn't erase its contents until the inode count in kernel memory gets to zero, which happens when all uses (references) to that file are due.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With