Obtener el descriptor de archivo más alto asignado

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

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 progtwig es un servidor que se ejecuta como root y bifurca y ejecuta progtwigs 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 .

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 más usado. 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 es portátil, la mayoría de los sistemas operativos de uso común en la actualidad proporcionan una o más de las siguientes soluciones a este problema:

  1. Una función de biblioteca para cerrar todos los descriptores de archivos > = 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, dup2 se puede usar para moverlos al extremo inferior de antemano, y para moverlos hacia atrás luego si es necesario.

    • closefrom(fd) (Solaris 9 o posterior, FreeBSD 7.3 o 8.0 y posterior, 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, incluido el descriptor de archivo más alto actualmente abierto en ps.pst_highestfd . (HP-UX)

  3. Un directorio que contiene una entrada para cada descriptor de archivo 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. Ejemplo (OpenSSH) , otro ejemplo (glib) .

    • /proc/ pid /fd/ o /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX no es compatible con ” 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, considere la situación en la que se deben cerrar todos los descriptores de archivos> = fd , pero se usan todos los descriptores de archivos < fd , el límite de recursos del proceso actual es fd y hay descriptores de archivos> = 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 sysconf(_SC_OPEN_MAX) como reserva, nada se cerrará.

La forma de POSIX es:

 int maxfd=sysconf(_SC_OPEN_MAX); for(int fd=3; fd 

(tenga en cuenta que está cerrando desde 3 hacia arriba, para mantener abierto stdin / stdout / stderr)

close () devuelve inofensivamente 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.

He escrito un código para tratar con 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); } } 

Justo cuando su progtwig comenzó y no ha abierto nada. Por ejemplo, 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 y al ejecutivo.

 #include  #include  #include  #include  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 desea hacer IO en el progtwig ejecutado, el servidor ejecutor tendrá que hacer redireccionamientos de socket y puede usar sockets Unix.

¿Por qué no cierras todos los descriptores de 0 a, digamos, 10000?

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

    Intereting Posts