Как создать файл, только если он не существует? - PullRequest
7 голосов
/ 26 марта 2012

Я написал демон UNIX (нацеленный на Debian, но это не должно иметь значения), и я хотел предоставить какой-то способ создания «pid-файла» (файла, который содержит идентификатор процесса демона).

Я искал способ открыть файл только , если не существует, но не смог его найти.

В принципе, я мог бы сделать что-то вроде:

if (fileexists())
{
  //fail...
}
else
{
  //create it with fopen() or similar
}

Но в его нынешнем виде этот код не выполняет задачу атомарным способом, и это будет опасно, поскольку другой процесс может создать файл во время моего теста и создания файла.

Ребята, у вас есть идеи, как это сделать?

Спасибо.

P.S: Бонусный балл за решение, которое включает только std::streams.

Ответы [ 5 ]

8 голосов
/ 26 марта 2012

man 2 open:

O_EXCL Убедитесь, что этот вызов создает файл: если этот флаг указан вместе с O_CREAT, а путь уже существует, тогда open () завершится неудачей.Поведение O_EXCL не определено, если O_CREAT не указан.

, поэтому вы можете вызвать fd = open(name, O_CREAT | O_EXCL, 0644); / * Open () атомарно.(по причине) * /

ОБНОВЛЕНИЕ: и вы должны, конечно, ИЛИ один из флагов O_RDONLY, O_WRONLY или O_RDWR в аргумент flags.

4 голосов
/ 26 марта 2012

Я узнал о правильном демонизации здесь (в тот день):

Это хорошее чтение. С тех пор я улучшил код блокировки, чтобы устранить условия гонки на платформах, которые позволяют рекомендованную блокировку файлов с указанием определенных регионов.

Вот соответствующий фрагмент проекта, в котором я принимал участие:

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);
        }
    }
}

См. Также http://gitweb.zfs -fuse.net /? P = sehe; a = blob; f = src / zfs-fuse / util.c; h = 7c9816cc895db4f65b94592eebf96d05cd2c369a; hb = refs /heads / maint

1 голос
/ 26 марта 2012

Один из способов решения этой проблемы - открыть файл для добавления. Если функция завершается успешно и ее позиция равна 0, тогда вы можете быть уверены, что это новый файл. Может все еще быть пустым файлом, но этот сценарий не может быть важным.

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);
}
1 голос
/ 26 марта 2012

Единственный способ, которым я могу думать, - это использовать блокировки системного уровня. Смотрите это: C ++, как проверить, используется ли файл - многопоточная многопроцессорная система

0 голосов
/ 26 марта 2012

Казалось бы, нет способа сделать это строго с использованием потоков.

Вместо этого вы можете использовать open (как упомянуто выше в wildplasser) и, если это удастся, перейти к открытию того же файла, что и поток. Конечно, если все, что вы записываете в файл, это PID, неясно, почему вы не просто записали бы его с помощью write в стиле C ().

O_EXCL исключает только другие процессы, которые пытаются открыть тот же файл, используя O_EXCL. Это, конечно, означает, что у вас никогда не будет полной гарантии, но если имя / местоположение файла где-то, вероятно, никто другой не откроет (кроме тех, кого вы знаете, что используете O_EXCL), вы должны быть в порядке.

...