clinuxfileipc

In Linux, can I make a file unchangeable while the process creating it is running while removable if the process has been terminated?


My initiative here is to make a detection mechanism on process status by a "file lock".

Say I am developing a program in user mode. It will be inappropriate for some sources to be shared within multiple processes of the program(that is, multiple instances of the same executable? I am not very sure about the terminology here), so I am thinking of creating a tmp file to signify the program is currently running.

Let's take output directory as an example, say if I don't want the processes to compete for log file names, etc, I would like to try to create a tmp file, if the creation success, make the file unremovable during the process lifetime so as to signify this process's "ownership" for this output directory. If the creation failed due to the file existing, then try to delete the file, and:

I wonder:

  1. Is the "Make file unchangeable while the process creating it is running while removable if the process has been terminated" somehow achievable? By "terminated" I am any kind of way, SIGKILL, SIGTERM, normal exit, etc.

  2. Should I worry about inter-process-race? what bad could happen if say one process is wrapping up but hasn'y yet fully released while others are trying to create the tmp file? ( possible "misdetection" is tolerable, by "bad" I mean dead-lock, resource leakage, etc. )


Solution

  • Here is a simple solution to ensure only a single process accesses some resources:

    1. convert the current process PID to a string s
    2. using symlink, attempt to create a symbolic link with a fixed name in the "/tmp" directory (eg: my_resource_locked_by) using s as the target file.
    3. if the call succeeds, you have exclusive access to the resource.
    4. otherwise, check errno:
      • if different from EEXIST, report the error and abort
      • otherwise use readlink to get the pid xxx of the process that has locked the resource:
        • if readlink fails:
          • if errno == ENOENT (symlink file was removed) try again step 2
          • otherwise report the error and abort
        • check if the process is still running using stat on /proc/xxx where xxx is the pid (attn: readlink does not null terminate the symlink contents):
        • if the process is running, either report the conflict or wait some time and retry step 2
        • If the process is not running, remove the lock file with unlink, check for failure and report the reason, otherwise retry step 2.
    5. you can remove the lock file with unlink as soon as exclusive access is no longer required.

    Here is a simple implementation (error reporting delegated to the caller):

    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    
    // try and acquire a named resource (eg: "/tmp/mylog.lock")
    // return 0 if resource locked
    // return 1 if resource locked by another process (pid in *pid)
    // return <0 and set *err_code to error code in case of other errors
    int acquire_resource(const char *name, int *err_code, pid_t *pid) {
        char pid_buf[32];
        char proc_name[6 + 32] = "/proc/";
        struct stat sb;
        
        *pid = 0;
        snprintf(pid_buf, sizeof pid_buf, "%ld", (long)getpid());
        for (int n = 0; n < 10; n++) {
            if (!symlink(name, pid_buf))
                return 0;
            if (errno != EEXIST) {
                *err_code = errno;
                return -1;
            }
            ssize_t linklen = readlink(name, proc_name + 6, sizeof(proc_name) - 6 - 1);
            if (linklen < 0) {
                if (errno == ENOENT)
                    continue;
                *err_code = errno;
                return -2;
            }
            proc_name[6 + linklen] = '\0';
            *pid = strtol(proc_name + 6, NULL, 10);
            if (!stat(proc_name, &sb)
                return 1;
            if (unlink(name) && errno != ENOENT) {
                *err_code = errno;
                return -3;
            }
        }
        return -4;
    }
    
    int release_resource(const char *name) {
        return unlink(name);
    }