How to create a file only if it doesn't exist?

前端 未结 5 1294
庸人自扰
庸人自扰 2021-01-12 04:10

I wrote a UNIX daemon (targeting Debian, but it shouldn\'t matter) and I wanted to provide some way of creating a \".pid\" file, (a file which contains the process identifie

5条回答
  •  旧巷少年郎
    2021-01-12 04:28

    I learned about proper daemonizing here (back in the day):

    • http://www.enderunix.org/docs/eng/daemon.php

    It is a good read. I have since improved the locking code to eliminate race conditions on platforms that allow advisory file locking with specific regions specified.

    Here is a relevant snippet from a project that I was involved in:

    static int zfsfuse_do_locking(int in_child)
    {
        /* Ignores errors since the directory might already exist */
        mkdir(LOCKDIR, 0700);
    
        if (!in_child)
        {
            ASSERT(lock_fd == -1);
            /*
             * before the fork, we create the file, truncating it, and locking the
             * first byte
             */
            lock_fd = creat(LOCKFILE, S_IRUSR | S_IWUSR);
            if(lock_fd == -1)
                return -1;
    
            /*
             * only if we /could/ lock all of the file,
             * we shall lock just the first byte; this way
             * we can let the daemon child process lock the
             * remainder of the file after forking
             */
            if (0==lockf(lock_fd, F_TEST, 0))
                return lockf(lock_fd, F_TLOCK, 1);
            else
                return -1;
        } else
        {
            ASSERT(lock_fd != -1);
            /*
             * after the fork, we instead try to lock only the region /after/ the
             * first byte; the file /must/ already exist. Only in this way can we
             * prevent races with locking before or after the daemonization
             */
            lock_fd = open(LOCKFILE, O_WRONLY);
            if(lock_fd == -1)
                return -1;
    
            ASSERT(-1 == lockf(lock_fd, F_TEST, 0)); /* assert that parent still has the lock on the first byte */
            if (-1 == lseek(lock_fd, 1, SEEK_SET))
            {
                perror("lseek");
                return -1;
            }
    
            return lockf(lock_fd, F_TLOCK, 0);
        }
    }
    
    void do_daemon(const char *pidfile)
    {
        chdir("/");
        if (pidfile) {
            struct stat dummy;
            if (0 == stat(pidfile, &dummy)) {
                cmn_err(CE_WARN, "%s already exists; aborting.", pidfile);
                exit(1);
            }
        }
    
        /*
         * info gleaned from the web, notably
         * http://www.enderunix.org/docs/eng/daemon.php
         *
         * and
         *
         * http://sourceware.org/git/?p=glibc.git;a=blob;f=misc/daemon.c;h=7597ce9996d5fde1c4ba622e7881cf6e821a12b4;hb=HEAD
         */
        {
            int forkres, devnull;
    
            if(getppid()==1)
                return; /* already a daemon */
    
            forkres=fork();
            if (forkres<0)
            { /* fork error */
                cmn_err(CE_WARN, "Cannot fork (%s)", strerror(errno));
                exit(1);
            }
            if (forkres>0)
            {
                int i;
                /* parent */
                for (i=getdtablesize();i>=0;--i)
                    if ((lock_fd!=i) && (ioctl_fd!=i))       /* except for the lockfile and the comm socket */
                        close(i);                            /* close all descriptors */
    
                /* allow for airtight lockfile semantics... */
                struct timeval tv;
                tv.tv_sec = 0;
                tv.tv_usec = 200000;  /* 0.2 seconds */
                select(0, NULL, NULL, NULL, &tv);
    
                VERIFY(0 == close(lock_fd));
                lock_fd == -1;
                exit(0);
            }
    
            /* child (daemon) continues */
            setsid();                         /* obtain a new process group */
            VERIFY(0 == chdir("/"));          /* change working directory */
            umask(027);                       /* set newly created file permissions */
            devnull=open("/dev/null",O_RDWR); /* handle standard I/O */
            ASSERT(-1 != devnull);
            dup2(devnull, 0); /* stdin  */
            dup2(devnull, 1); /* stdout */
            dup2(devnull, 2); /* stderr */
            if (devnull>2)
                close(devnull);
    
            /*
             * contrary to recommendation, do _not_ ignore SIGCHLD:
             * it will break exec-ing subprocesses, e.g. for kstat mount and
             * (presumably) nfs sharing!
             *
             * this will lead to really bad performance too
             */
            signal(SIGTSTP,SIG_IGN);     /* ignore tty signals */
            signal(SIGTTOU,SIG_IGN);
            signal(SIGTTIN,SIG_IGN);
        }
    
        if (0 != zfsfuse_do_locking(1))
        {
            cmn_err(CE_WARN, "Unexpected locking conflict (%s: %s)", strerror(errno), LOCKFILE);
            exit(1);
        }
    
        if (pidfile) {
            FILE *f = fopen(pidfile, "w");
            if (!f) {
                cmn_err(CE_WARN, "Error opening %s.", pidfile);
                exit(1);
            }
            if (fprintf(f, "%d\n", getpid()) < 0) {
                unlink(pidfile);
                exit(1);
            }
            if (fclose(f) != 0) {
                unlink(pidfile);
                exit(1);
            }
        }
    }
    

    See also http://gitweb.zfs-fuse.net/?p=sehe;a=blob;f=src/zfs-fuse/util.c;h=7c9816cc895db4f65b94592eebf96d05cd2c369a;hb=refs/heads/maint

提交回复
热议问题