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

前端 未结 5 1265
庸人自扰
庸人自扰 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

    0 讨论(0)
  • 2021-01-12 04:28

    The only way I can think of is to use system level locks. See this: C++ how to check if file is in use - multi-threaded multi-process system

    0 讨论(0)
  • 2021-01-12 04:28

    One way to approach this problem is to open the file for appending. If the function succeeds and the position is at 0 then you can be fairly certain this is a new file. Could still be an empty file but that scenario may not be important.

    FILE* pFile = fopen(theFilePath, "a+");
    if (pFile && gfetpos(pFile) == 0) { 
      // Either file didn't previously exist or it did and was empty
    
    } else if (pFile) { 
      fclose(pFile);
    }
    
    0 讨论(0)
  • 2021-01-12 04:29

    It would appear that there's no way to do it strictly using streams.

    You can, instead, use open (as mentioned above by wildplasser) and if that succeeds, proceed to open the same file as a stream. Of course, if all you're writing to the file is a PID, it is unclear why you wouldn't just write it using C-style write().

    O_EXCL only excludes other processes that are attempting to open the same file using O_EXCL. This, of course, means that you never have a perfect guarantee, but if the file name/location is somewhere nobody else is likely to be opening (other than folks you know are using O_EXCL) you should be OK.

    0 讨论(0)
  • 2021-01-12 04:48

    man 2 open:

    O_EXCL Ensure that this call creates the file: if this flag is specified in conjunction with O_CREAT, and pathname already exists, then open() will fail. The behavior of O_EXCL is undefined if O_CREAT is not specified.

    so, you could call fd = open(name, O_CREAT | O_EXCL, 0644); /* Open() is atomic. (for a reason) */

    UPDATE: and you should of course OR one of the O_RDONLY, O_WRONLY, or O_RDWR flags into the flags argument.

    0 讨论(0)
提交回复
热议问题