2009-05-22 12 views
41

¿Existe una forma portátil (POSIX) para obtener el número máximo de descriptor de archivo asignado para el proceso actual?Obteniendo el descriptor de archivo más alto asignado

Sé que hay una buena manera de obtener el número en AIX, por ejemplo, pero estoy buscando un método portátil.

La razón por la que estoy preguntando es que quiero cerrar todos los descriptores de archivos abiertos. Mi programa es un servidor que se ejecuta como root y bifurca y ejecuta programas secundarios para usuarios no root. Dejar abiertos los descriptores de archivos privilegiados en el proceso secundario es un problema de seguridad. Algunos descriptores de archivos pueden ser abiertos por código que no puedo controlar (la biblioteca C, bibliotecas de terceros, etc.), así que tampoco puedo confiar en FD_CLOEXEC.

+3

Tenga en cuenta que sería mejor simplemente abrir todos sus archivos con el indicador de cierre cercano ejecutado para que se cierren automáticamente por cualquiera de las funciones de la familia 'exec'. –

+0

Glibc moderno admite el caracter "e" stdio.h FILE * open flag para indicar el tratamiento FD_CLOEXEC. – fche

Respuesta

61

Aunque es portátil, cerrar todos los descriptores de archivo hasta sysconf(_SC_OPEN_MAX) no es confiable, porque en la mayoría de los sistemas esta llamada devuelve el límite del descriptor de archivo actual, que podría haberse bajado por debajo del descriptor de archivo utilizado más alto. Otro problema es que en muchos sistemas sysconf(_SC_OPEN_MAX) puede devolver INT_MAX, lo que puede causar que este enfoque sea inaceptablemente lento. Desafortunadamente, no existe una alternativa fiable y portátil que no implique iterar sobre todos los posibles descriptores de archivos no negativos.

Aunque no portátiles, la mayoría de los sistemas operativos de uso común hoy en día proporcionan una o más de las siguientes soluciones a este problema:

  1. una función de biblioteca a cerca descriptores de todos los archivos de> = fd. Esta es la solución más simple para el caso común de cerrar todos los descriptores de archivos, aunque no se puede usar para mucho más. Para cerrar todos los descriptores de archivos, excepto un determinado conjunto, se puede usar dup2 para moverlos al extremo inferior de antemano, y luego moverlos hacia atrás si es necesario.

    • closefrom(fd) (Solaris 9 o posterior, FreeBSD 7.3 o 8.0 y más tarde, NetBSD 3.0 o posterior, OpenBSD 3.5 o posterior.)

    • fcntl(fd, F_CLOSEM, 0) (AIX, IRIX, NetBSD)

  2. Una función de biblioteca para proporcionar el máximo descriptor de archivo actualmente en uso por el proceso.Para cerrar todos los descriptores de archivo por encima de un cierto número, ciérrelos todos hasta este máximo, o obtenga y cierre continuamente el descriptor de archivo más alto en un bucle hasta alcanzar el límite bajo. Lo que es más eficiente depende de la densidad del descriptor de archivo.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      devuelve información sobre el proceso, incluyendo el descriptor de archivo más alto actualmente abierta en ps.pst_highestfd. (HP-UX)

  3. Un directorio que contiene una entrada para cada descriptor de fichero abierto. Este es el enfoque más flexible, ya que permite cerrar todos los descriptores de archivos, encontrar el descriptor de archivo más alto o hacer casi cualquier otra cosa en cada descriptor de archivo abierto, incluso los de otro proceso (en la mayoría de los sistemas). Sin embargo, esto puede ser más complicado que los otros enfoques para los usos comunes. Además, puede fallar por una variedad de razones, tales como proc/fdescfs no montado, un entorno chroot, o no hay descriptores de archivos disponibles para abrir el directorio (proceso o límite del sistema). Por lo tanto, el uso de este enfoque a menudo se combina con un mecanismo de respaldo. Example (OpenSSH), another example (glib).

    • /proc/pid/fd/ o /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX no soporta "self")

    • /dev/fd/ (FreeBSD, Darwin, OS X)

    Puede ser difícil manejar todos los casos de esquina de manera confiable con este enfoque. Por ejemplo, consideremos la situación en la que todos los descriptores de fichero> = fd se van a cerrar, pero todos los descriptores de fichero < fd se utilizan, el límite actual de los recursos proceso es fd, y hay descriptores de fichero> = fd en uso. Debido a que se ha alcanzado el límite de recursos del proceso, no se puede abrir el directorio. Si se cierra cada descriptor de archivo desde fd hasta el límite de recursos o sysconf(_SC_OPEN_MAX) se usa como reserva, nada se cerrará.

+1

Acerca del enfoque 3: existen problemas graves al usar esto entre fork/exec en un programa multiproceso porque opendir() puede llamar a malloc() que puede estancarse en esta situación. Me temo que simplemente no hay forma de hacer lo que la pregunta en Linux, y los desarrolladores no harán nada al respecto: https://sourceware.org/bugzilla/show_bug.cgi?id=10353 – medoc

+0

@ medoc: el desarrollo de glibc sufrió una gran reorganización en 2012, y varias cosas previamente rechazadas lo han incluido en el nuevo modelo de desarrollo. Puede valer la pena iniciar una nueva discusión sobre el tema. – mark4o

-2

¿Por qué no cerrar todos los descriptores de 0 a, digamos, 10000.

Sería bastante rápido, y lo peor que podría pasar es EBADF.

+0

Funcionará, pero tendrá que hacer que sea configurable, ya que simplemente no sabe cuántos necesitan estar cerrados (depende de la carga). –

12

La forma POSIX es:

int maxfd=sysconf(_SC_OPEN_MAX); 
for(int fd=3; fd<maxfd; fd++) 
    close(fd); 

(tenga en cuenta que está acercando desde 3 hasta, para mantener la entrada estándar/stdout/stderr abierto)

close() sin causar daños vuelve EBADF si el descriptor de archivo no está abierto . No hay necesidad de perder otra comprobación de llamada del sistema.

Algunos Unix soportan un closefrom(). Esto evita el número excesivo de llamadas para cerrar() según el número de descriptor de archivo máximo posible. Si bien la mejor solución que conozco, es completamente no portátil.

5

He escrito un código para tratar todas las funciones específicas de la plataforma. Todas las funciones son señales asíncronas seguras. Pensamiento que las personas pueden encontrar esto útil. Solo probado en OS X en este momento, no dude en mejorar/corregir.

// Async-signal safe way to get the current process's hard file descriptor limit. 
static int 
getFileDescriptorLimit() { 
    long long sysconfResult = sysconf(_SC_OPEN_MAX); 

    struct rlimit rl; 
    long long rlimitResult; 
    if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { 
     rlimitResult = 0; 
    } else { 
     rlimitResult = (long long) rl.rlim_max; 
    } 

    long result; 
    if (sysconfResult > rlimitResult) { 
     result = sysconfResult; 
    } else { 
     result = rlimitResult; 
    } 
    if (result < 0) { 
     // Both calls returned errors. 
     result = 9999; 
    } else if (result < 2) { 
     // The calls reported broken values. 
     result = 2; 
    } 
    return result; 
} 

// Async-signal safe function to get the highest file 
// descriptor that the process is currently using. 
// See also http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor 
static int 
getHighestFileDescriptor() { 
#if defined(F_MAXFD) 
    int ret; 

    do { 
     ret = fcntl(0, F_MAXFD); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     ret = getFileDescriptorLimit(); 
    } 
    return ret; 

#else 
    int p[2], ret, flags; 
    pid_t pid = -1; 
    int result = -1; 

    /* Since opendir() may not be async signal safe and thus may lock up 
    * or crash, we use it in a child process which we kill if we notice 
    * that things are going wrong. 
    */ 

    // Make a pipe. 
    p[0] = p[1] = -1; 
    do { 
     ret = pipe(p); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     goto done; 
    } 

    // Make the read side non-blocking. 
    do { 
     flags = fcntl(p[0], F_GETFL); 
    } while (flags == -1 && errno == EINTR); 
    if (flags == -1) { 
     goto done; 
    } 
    do { 
     fcntl(p[0], F_SETFL, flags | O_NONBLOCK); 
    } while (ret == -1 && errno == EINTR); 
    if (ret == -1) { 
     goto done; 
    } 

    do { 
     pid = fork(); 
    } while (pid == -1 && errno == EINTR); 

    if (pid == 0) { 
     // Don't close p[0] here or it might affect the result. 

     resetSignalHandlersAndMask(); 

     struct sigaction action; 
     action.sa_handler = _exit; 
     action.sa_flags = SA_RESTART; 
     sigemptyset(&action.sa_mask); 
     sigaction(SIGSEGV, &action, NULL); 
     sigaction(SIGPIPE, &action, NULL); 
     sigaction(SIGBUS, &action, NULL); 
     sigaction(SIGILL, &action, NULL); 
     sigaction(SIGFPE, &action, NULL); 
     sigaction(SIGABRT, &action, NULL); 

     DIR *dir = NULL; 
     #ifdef __APPLE__ 
      /* /dev/fd can always be trusted on OS X. */ 
      dir = opendir("/dev/fd"); 
     #else 
      /* On FreeBSD and possibly other operating systems, /dev/fd only 
      * works if fdescfs is mounted. If it isn't mounted then /dev/fd 
      * still exists but always returns [0, 1, 2] and thus can't be 
      * trusted. If /dev and /dev/fd are on different filesystems 
      * then that probably means fdescfs is mounted. 
      */ 
      struct stat dirbuf1, dirbuf2; 
      if (stat("/dev", &dirbuf1) == -1 
      || stat("/dev/fd", &dirbuf2) == -1) { 
       _exit(1); 
      } 
      if (dirbuf1.st_dev != dirbuf2.st_dev) { 
       dir = opendir("/dev/fd"); 
      } 
     #endif 
     if (dir == NULL) { 
      dir = opendir("/proc/self/fd"); 
      if (dir == NULL) { 
       _exit(1); 
      } 
     } 

     struct dirent *ent; 
     union { 
      int highest; 
      char data[sizeof(int)]; 
     } u; 
     u.highest = -1; 

     while ((ent = readdir(dir)) != NULL) { 
      if (ent->d_name[0] != '.') { 
       int number = atoi(ent->d_name); 
       if (number > u.highest) { 
        u.highest = number; 
       } 
      } 
     } 
     if (u.highest != -1) { 
      ssize_t ret, written = 0; 
      do { 
       ret = write(p[1], u.data + written, sizeof(int) - written); 
       if (ret == -1) { 
        _exit(1); 
       } 
       written += ret; 
      } while (written < (ssize_t) sizeof(int)); 
     } 
     closedir(dir); 
     _exit(0); 

    } else if (pid == -1) { 
     goto done; 

    } else { 
     do { 
      ret = close(p[1]); 
     } while (ret == -1 && errno == EINTR); 
     p[1] = -1; 

     union { 
      int highest; 
      char data[sizeof(int)]; 
     } u; 
     ssize_t ret, bytesRead = 0; 
     struct pollfd pfd; 
     pfd.fd = p[0]; 
     pfd.events = POLLIN; 

     do { 
      do { 
       // The child process must finish within 30 ms, otherwise 
       // we might as well query sysconf. 
       ret = poll(&pfd, 1, 30); 
      } while (ret == -1 && errno == EINTR); 
      if (ret <= 0) { 
       goto done; 
      } 

      do { 
       ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead); 
      } while (ret == -1 && ret == EINTR); 
      if (ret == -1) { 
       if (errno != EAGAIN) { 
        goto done; 
       } 
      } else if (ret == 0) { 
       goto done; 
      } else { 
       bytesRead += ret; 
      } 
     } while (bytesRead < (ssize_t) sizeof(int)); 

     result = u.highest; 
     goto done; 
    } 

done: 
    if (p[0] != -1) { 
     do { 
      ret = close(p[0]); 
     } while (ret == -1 && errno == EINTR); 
    } 
    if (p[1] != -1) { 
     do { 
      close(p[1]); 
     } while (ret == -1 && errno == EINTR); 
    } 
    if (pid != -1) { 
     do { 
      ret = kill(pid, SIGKILL); 
     } while (ret == -1 && errno == EINTR); 
     do { 
      ret = waitpid(pid, NULL, 0); 
     } while (ret == -1 && errno == EINTR); 
    } 

    if (result == -1) { 
     result = getFileDescriptorLimit(); 
    } 
    return result; 
#endif 
} 

void 
closeAllFileDescriptors(int lastToKeepOpen) { 
    #if defined(F_CLOSEM) 
     int ret; 
     do { 
      ret = fcntl(lastToKeepOpen + 1, F_CLOSEM); 
     } while (ret == -1 && errno == EINTR); 
     if (ret != -1) { 
      return; 
     } 
    #elif defined(HAS_CLOSEFROM) 
     closefrom(lastToKeepOpen + 1); 
     return; 
    #endif 

    for (int i = getHighestFileDescriptor(); i > lastToKeepOpen; i--) { 
     int ret; 
     do { 
      ret = close(i); 
     } while (ret == -1 && errno == EINTR); 
    } 
} 
0

Justo cuando su programa comenzó y no ha abierto nada. P.ej. como el inicio de main(). la tubería y la horquilla inician inmediatamente un servidor de ejecución. De esta forma, la memoria y otros detalles están limpios y puedes darle las cosas al tenedor & exec.

#include <unistd.h> 
#include <stdio.h> 
#include <memory.h> 
#include <stdlib.h> 

struct PipeStreamHandles { 
    /** Write to this */ 
    int output; 
    /** Read from this */ 
    int input; 

    /** true if this process is the child after a fork */ 
    bool isChild; 
    pid_t childProcessId; 
}; 

PipeStreamHandles forkFullDuplex(){ 
    int childInput[2]; 
    int childOutput[2]; 

    pipe(childInput); 
    pipe(childOutput); 

    pid_t pid = fork(); 
    PipeStreamHandles streams; 
    if(pid == 0){ 
     // child 
     close(childInput[1]); 
     close(childOutput[0]); 

     streams.output = childOutput[1]; 
     streams.input = childInput[0]; 
     streams.isChild = true; 
     streams.childProcessId = getpid(); 
    } else { 
     close(childInput[0]); 
     close(childOutput[1]); 

     streams.output = childInput[1]; 
     streams.input = childOutput[0]; 
     streams.isChild = false; 
     streams.childProcessId = pid; 
    } 

    return streams; 
} 


struct ExecuteData { 
    char command[2048]; 
    bool shouldExit; 
}; 

ExecuteData getCommand() { 
    // maybe use json or semething to read what to execute 
    // environment if any and etc..   
    // you can read via stdin because of the dup setup we did 
    // in setupExecutor 
    ExecuteData data; 
    memset(&data, 0, sizeof(data)); 
    data.shouldExit = fgets(data.command, 2047, stdin) == NULL; 
    return data; 
} 

void executorServer(){ 

    while(true){ 
     printf("executor server waiting for command\n"); 
     // maybe use json or semething to read what to execute 
     // environment if any and etc..   
     ExecuteData command = getCommand(); 
     // one way is for getCommand() to check if stdin is gone 
     // that way you can set shouldExit to true 
     if(command.shouldExit){ 
      break; 
     } 
     printf("executor server doing command %s", command.command); 
     system(command.command); 
     // free command resources. 
    } 
} 

static PipeStreamHandles executorStreams; 
void setupExecutor(){ 
    PipeStreamHandles handles = forkFullDuplex(); 

    if(handles.isChild){ 
     // This simplifies so we can just use standard IO 
     dup2(handles.input, 0); 
     // we comment this out so we see output. 
     // dup2(handles.output, 1); 
     close(handles.input); 
     // we uncomment this one so we can see hello world 
     // if you want to capture the output you will want this. 
     //close(handles.output); 
     handles.input = 0; 
     handles.output = 1; 
     printf("started child\n"); 
     executorServer(); 
     printf("exiting executor\n"); 
     exit(0); 
    } 

    executorStreams = handles; 
} 

/** Only has 0, 1, 2 file descriptiors open */ 
pid_t cleanForkAndExecute(const char *command) { 
    // You can do json and use a json parser might be better 
    // so you can pass other data like environment perhaps. 
    // and also be able to return details like new proccess id so you can 
    // wait if it's done and ask other relevant questions. 
    write(executorStreams.output, command, strlen(command)); 
    write(executorStreams.output, "\n", 1); 
} 

int main() { 
    // needs to be done early so future fds do not get open 
    setupExecutor(); 

    // run your program as usual. 
    cleanForkAndExecute("echo hello world"); 
    sleep(3); 
} 

Si quieren hacer IO en el programa ejecutado el servidor ejecutor tendrá que hacer redirecciones de socket y se puede utilizar sockets UNIX.

Cuestiones relacionadas