Home > database >  Restart process using execve: Cannot get exclusive lock after restart
Restart process using execve: Cannot get exclusive lock after restart

Time:02-03

I need to restart process using the common method for all processes. I use execve() system call for restart. The example below suggests that the process may have other locks that are not known in scope of restart(...) function.

#include <sys/file.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define LOCKFILE "./test_exec_restart.lock"

extern char ** environ;

void restart(char ** argv)
{
    char * new_argv[] = { argv[0], NULL };
    int res = execve(new_argv[0], new_argv, environ);

    fprintf(stderr, "execve() error: %d(%s)\n", errno, strerror(errno));
    exit(-3);
}

int main(int argc, char** argv)
{
    int do_exec = 0;
    if (argc > 1 && strncmp(argv[1], "-e", 2) == 0)
        do_exec = 1;

    printf("do_exec: %d\n", do_exec);

    FILE* file = fopen(LOCKFILE, "w ");
    if (file == NULL)
    {   
        fprintf(stderr, "Cannot open lockfile, error: %d(%s)\n", errno, strerror(errno));
        return -1; 
    }   
    if (flock(fileno(file), LOCK_EX | LOCK_NB) < 0)
    {   
        fprintf(stderr, "Cannot exclusive lock, error: %d(%s)\n", errno, strerror(errno));
        return -2; 
    }   

    if (do_exec)
        restart(argv);

    if (flock(fileno(file), LOCK_UN) < 0)
    {   
        fprintf(stderr, "Cannot unlock, error: %d(%s)\n", errno, strerror(errno));
        return -4; 
    }   

    return 0;
}

I ran this example, and gets lock error after restart:

$ gcc main.c -o main
$ ./main -e
do_exec: 1
do_exec: 0
Cannot exclusive lock, error: 11(Resource temporarily unavailable)

What I need to do for get exclusive lock after exec() so that the client code does not do any extra work for this?

CodePudding user response:

man execve:

      By default, file descriptors remain open across an execve().
      File descriptors that are marked close-on-exec are closed; see
      the description of FD_CLOEXEC in fcntl(2).  (If a file
      descriptor is closed, this will cause the release of all
      record locks obtained on the underlying file by this process.
      See fcntl(2) for details.)  POSIX.1 says that if file
      descriptors 0, 1, and 2 would otherwise be closed after a
      successful execve(), and the process would gain privilege
      because the set-user-ID or set-group-ID mode bit was set on
      the executed file, then the system may open an unspecified
      file for each of these file descriptors.  As a general
      principle, no portable program, whether privileged or not, can
      assume that these three file descriptors will remain closed
      across an execve().

Restarted process can't exclusuve lock because it inherited opened files from first process, and trying lock inherited locked file.

My solution is close all files, except stdin, stdout and stderr:

...
int close_all_files(int lowfd)
{
    DIR* dir = opendir("/proc/self/fd");
    if (dir == NULL)
    {   
        fprintf(stderr, "Cannot open directory: '/proc/self/fd', error: %d(%s)\n", errno, strerror(errno));
        return -1; 
    }   

    struct dirent* ent = NULL;
    while ((ent = readdir(dir)) != NULL)
    {   
        int fd = atoi(ent->d_name);
        if (fd > lowfd)
            close(fd);
    }   
    closedir(dir);

    return 0;
}

void restart(char ** argv)
{
    close_all_files(fileno(stderr));

    char * new_argv[] = { argv[0], NULL };
    int res = execve(new_argv[0], new_argv, environ);

    fprintf(stderr, "execve() error: %d(%s)\n", errno, strerror(errno));
    exit(-3);
}
...

It is also possible using fdwalk(...) function, but it is possibly not available on all Linux/UNIX systems.

REM: This is bad solution, because active thread that handled signal is unspecified, and all files may be closed during writes in them. This will result in a loss of data consistency.

CodePudding user response:

You can mark the Lock filedescriptor O_CLOEXEC, then it will be closed by the execve, and can be reopened by the new process.

According to https://www.gnu.org/software/libc/manual/html_node/Opening-Streams.html you can add "e" to the end of the mode for the fopen to achieve this.

  • Related