Skip to content

Ottenere il descrittore di file più alto allocato

Dopo esserci consultati con esperti del settore, programmatori di varie aree e professori, abbiamo trovato la soluzione al quesito e la condividiamo in questo post.

Soluzione:

Durante la portabilità, chiudere tutti i descrittori di file fino a sysconf(_SC_OPEN_MAX) non è affidabile, perché nella maggior parte dei sistemi questa chiamata restituisce il limite morbido del descrittore di file corrente, che potrebbe essere stato abbassato al di sotto del descrittore di file più utilizzato. Un altro problema è che su molti sistemi sysconf(_SC_OPEN_MAX) può restituire INT_MAXe questo può rendere questo approccio inaccettabilmente lento. Purtroppo non esiste un'alternativa affidabile e portabile che non implichi l'iterazione su ogni possibile descrittore di file int non negativo.

Anche se non è portabile, la maggior parte dei sistemi operativi oggi in uso comune fornisce una o più delle seguenti soluzioni a questo problema:

  1. Una funzione di libreria per chiudere tutti i descrittori di file = fd. Questa è la soluzione più semplice per il caso comune di chiusura di tutti i descrittori di file, anche se non può essere utilizzata per molto altro. Per chiudere tutti i descrittori di file, tranne un certo insieme, dup2 può essere usato per spostarli prima al limite inferiore e, se necessario, per riportarli indietro.

    • closefrom(fd) (Solaris 9 o successivo, FreeBSD 7.3 o 8.0 e successivi, NetBSD 3.0 o successivi, OpenBSD 3.5 o successivi).

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

  2. Una funzione di libreria per fornire la funzione descrittore di file massimo attualmente in uso dal processo. Per chiudere tutti i descrittori di file al di sopra di un certo numero, è possibile chiuderli tutti fino a questo massimo, oppure ottenere e chiudere continuamente il descrittore di file più alto in un ciclo finché non viene raggiunto il limite inferiore. La scelta più efficiente dipende dalla densità dei descrittori di file.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Restituisce informazioni sul processo, compreso il descrittore di file più alto attualmente aperto in ps.pst_highestfd. (HP-UX)

  3. Una funzione di libreria per elencare tutti i descrittori di file attualmente in uso dal processo. Questa funzione è più flessibile in quanto consente di chiudere tutti i descrittori di file, di trovare il descrittore di file più alto o di fare praticamente qualsiasi altra cosa su ogni descrittore di file aperto, eventualmente anche su quelli di un altro processo. Esempio (OpenSSH)

    • proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, fdinfo_buf, sz) (macOS)
  4. A che contiene una voce per ogni descrittore di file aperto. È simile al precedente, tranne per il fatto che non si tratta di una funzione di libreria. Questo può essere più complicato degli altri approcci per gli usi comuni e può fallire per una serie di motivi, come proc/fdescfs non montato, un ambiente chroot o nessun descrittore di file disponibile per aprire la directory (limite del processo o del sistema). Pertanto, l'uso di questo approccio è spesso combinato con un meccanismo di fallback. Esempio (OpenSSH), altro esempio (glib).

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

    • /dev/fd/ (FreeBSD, macOS)

    Con questo approccio può essere difficile gestire in modo affidabile tutti i casi particolari. Per esempio, si consideri la situazione in cui tutti i descrittori di file >= fd devono essere chiusi, ma tutti i descrittori di file fd sono utilizzati, il limite di risorse del processo corrente è fd e sono presenti descrittori di file >= fd in uso. Poiché il limite delle risorse del processo è stato raggiunto, la directory non può essere aperta. Se si chiudono tutti i descrittori di file da fd attraverso il limite di risorse o sysconf(_SC_OPEN_MAX) viene utilizzato come ripiego, non verrà chiuso nulla.

Il modo POSIX è:

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

(si noti che si chiude da 3 in su, per mantenere aperti stdin/stdout/stderr)

close() restituisce innocuamente EBADF se il descrittore di file non è aperto. Non c'è bisogno di sprecare un'altra chiamata di sistema per controllare.

Alcuni Unix supportano una closefrom(). Questo evita il numero eccessivo di chiamate a close() a seconda del numero massimo possibile di descrittori di file. Pur essendo la soluzione migliore che conosco, è completamente non portabile.

Ho scritto del codice per gestire tutte le caratteristiche specifiche della piattaforma. Tutte le funzioni sono sicure per i segnali asincroni. Ho pensato che le persone potessero trovarlo utile. Al momento è stato testato solo su OS X, sentitevi liberi di migliorare/correggere.

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



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.