2012-03-26 17 views
6

Escribí un demonio de UNIX (apuntando a Debian, pero no debería importar) y quería proporcionar una forma de crear un "archivo pid" (un archivo que contiene el identificador de proceso del daemon).¿Cómo crear un archivo solo si no existe?

buscaba una forma de abrir un archivo solamente si no existe, pero no pude encontrar uno.

Básicamente, yo podría hacer algo como:

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

Pero tal y como está, este código no realizar la tarea de forma atómica, y hacerlo sería peligroso, porque otro proceso podría crear el archivo durante mi prueba, y la creación del archivo.

¿Tienen alguna idea de cómo hacerlo?

Gracias.

P.S: Punto de bonificación para una solución que solo implica std::streams.

+1

posible duplicado de [Cómo probar si un archivo existe antes de crearlo] (http://stackoverflow.com/questions/7863145/how-to-test-if-a-file-exists-before-creating-it) –

+0

¿quizás fopen y flock juntos pueden lograr lo que quieres? – Kevin

Respuesta

7

hombre 2 abierta:

O_EXCL asegurar que esta llamada crea el archivo : si este indicador se especifica junto con O_CREAT, y el nombre de ruta ya existe, entonces abrirá() fallará. El comportamiento de O_EXCL no está definido si O_CREAT no está especificado.

por lo tanto, podría llamar al fd = open(name, O_CREAT | O_EXCL, 0644);/* Open() es atómico. (por una razón) */

ACTUALIZACIÓN: y usted debe, por supuesto, O uno de los indicadores O_RDONLY, O_WRONLY u O_RDWR en el argumento flags.

+0

¡Gracias! Exactamente lo que necesitaba. – ereOn

4

he aprendido acerca de daemonizing adecuada aquí (en su día):

Es una buena lectura. Desde entonces, he mejorado el código de bloqueo para eliminar las condiciones de carrera en plataformas que permiten bloqueo de archivo de aviso con regiones específicas especificadas.

Aquí hay un fragmento relevante de un proyecto que yo estaba involucrado en:

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

Véase también http://gitweb.zfs-fuse.net/?p=sehe;a=blob;f=src/zfs-fuse/util.c;h=7c9816cc895db4f65b94592eebf96d05cd2c369a;hb=refs/heads/maint

+0

Muy buen artículo de lectura e interesante. Ojalá pudiera aceptar esta respuesta también. Voto a favor de la equidad. – ereOn

1

Una forma de abordar este problema es abrir el archivo para anexar. Si la función tiene éxito y la posición está en 0, entonces puede estar bastante seguro de que este es un archivo nuevo. Aún podría ser un archivo vacío, pero ese escenario puede no ser importante.

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

La suposición de que el archivo estará vacío es bastante falaz. Puede contener la mayoría de las veces, pero sin conocer las especificaciones ... –

+0

Pero puede darse el caso cuando algunos hilos abren este archivo simultáneamente. Cada uno de ellos verificará el puesto. Existe la posibilidad de que la posición sea cero para cada uno de los hilos. Y luego tendremos carrera de datos. – Andigor

0

Parecería que no hay forma de hacerlo estrictamente utilizando transmisiones.

En su lugar, puede utilizar abrir (como se menciona anteriormente por wildplasser) y si eso tiene éxito, proceda a abrir el mismo archivo como una secuencia. Por supuesto, si todo lo que está escribiendo en el archivo es un PID, no está claro por qué no lo haría simplemente escribiendo C-style write().

O_EXCL solo excluye otros procesos que intentan abrir el mismo archivo con O_EXCL. Esto, por supuesto, significa que nunca tienes una garantía perfecta, pero si el nombre/ubicación del archivo se encuentra en algún lugar que probablemente nadie esté abriendo (aparte de las personas que conoces están usando O_EXCL) deberías estar bien.

+0

En realidad 'O_EXCL' * does * excluye todos los demás procesos, no solo aquellos que intentan abrir el mismo archivo con' O_EXCL'. ¿Tal vez estás pensando en 'rebaño' y amigos? – zwol

+0

No. Confiar en la página man. – DRVic

+0

Supongo que hay un sentido en el que tienes razón. 'O_EXCL' fallará si el archivo ya existe, independientemente de lo que hagan o hayan hecho otros procesos. Pero * después de 'abrir' crea el archivo *, podría aparecer otro proceso y abrirlo sin' O_EXCL' (o incluso 'O_CREAT') y tener éxito. Podría solucionar esto haciendo 'open (fname, O_WRONLY | O_CREAT | O_EXCL, 0000)' - sí, modo cero - en qué punto * su proceso * puede escribir el archivo, pero no se puede abrir ningún otro proceso que no sea raíz hasta que tú (u otra persona) 'chmod's. – zwol

Cuestiones relacionadas