Получение дескриптора файла с наивысшим распределением

Есть ли переносимый способ (POSIX) получить наивысший номер дескриптора файла, выделенный для текущего процесса?

Я знаю, что есть хороший способ получить номер, например, в AIX, но я ищу переносимый метод.

Я спрашиваю, потому что хочу закрыть все дескрипторы открытых файлов. Моя программа - это сервер, который работает как root, а также разветвляет и запускает дочерние программы для некорневых пользователей. Оставление привилегированных файловых дескрипторов открытыми в дочернем процессе является проблемой безопасности. Некоторые файловые дескрипторы могут быть открыты кодом, которым я не могу управлять (библиотека C, сторонние библиотеки и т. Д.), Поэтому я тоже не могу полагаться на FD_CLOEXEC.


person Ville Laurikari    schedule 22.05.2009    source источник
comment
Обратите внимание, что было бы лучше просто открывать все ваши файлы с установленным флагом close-on-exec, чтобы они автоматически закрывались любой из функций семейства exec.   -  person R.. GitHub STOP HELPING ICE    schedule 25.07.2011
comment
Современный glibc поддерживает символ открытия e stdio.h FILE *, указывающий на обработку FD_CLOEXEC.   -  person fche    schedule 16.01.2018
comment
Также стоит отметить, что close-on-exec - это не close-on-fork. Если вы разветвляете непривилегированный дочерний процесс для запуска подпрограммы из той же программы, дочерний процесс унаследует файловые дескрипторы привилегированного родителя. Не тривиально закрыть все ненужные. Close-on-exec в этой ситуации не помогает.   -  person Lassi    schedule 30.03.2018


Ответы (5)


Несмотря на переносимость, закрытие всех файловых дескрипторов до sysconf(_SC_OPEN_MAX) ненадежно, потому что в большинстве систем этот вызов возвращает мягкое ограничение текущего файлового дескриптора, которое могло быть ниже самого высокого используемого файлового дескриптора. Другая проблема заключается в том, что во многих системах sysconf(_SC_OPEN_MAX) может возвращать INT_MAX, что может сделать этот подход неприемлемо медленным. К сожалению, не существует надежной переносимой альтернативы, которая не включала бы перебор всех возможных неотрицательных файловых дескрипторов int.

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

  1. Библиотечная функция для закрытия всех дескрипторов файлов> = fd. Это простейшее решение для распространенного случая закрытия всех файловых дескрипторов, хотя его нельзя использовать ни для чего другого. Чтобы закрыть все файловые дескрипторы, за исключением определенного набора, dup2 можно использовать для их предварительного перемещения в нижний предел и, при необходимости, для последующего перемещения обратно.

    • closefrom(fd) (Solaris 9 или новее, FreeBSD 7.3 или 8.0 и новее, NetBSD 3.0 или новее, OpenBSD 3.5 или новее.)

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

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

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Возвращает информацию о процессе, включая дескриптор самого высокого файла, открытого в данный момент в ps.pst_highestfd. (HP-UX)

  3. Библиотечная функция для вывода списка всех файловых дескрипторов, используемых в данный момент процессом. Это более гибко, поскольку позволяет закрывать все файловые дескрипторы, находить файловый дескриптор наивысшего уровня или делать что-нибудь еще с каждым открытым файловым дескриптором, возможно, даже с дескрипторами другого процесса. Пример (OpenSSH)

    • proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, fdinfo_buf, sz) (macOS)
  4. Каталог, содержащий записи для каждого дескриптора открытого файла. Это похоже на предыдущее, за исключением того, что это не библиотечная функция. Это может быть более сложным, чем другие подходы для общего использования, и может завершиться ошибкой по ряду причин, таких как не смонтированный proc / fdescfs, среда chroot или отсутствие файловых дескрипторов, доступных для открытия каталога (ограничение процесса или системы). Поэтому использование этого подхода часто сочетается с резервным механизмом. Пример (OpenSSH) , другой пример ( glib).

    • /proc/ pid /fd/ или /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX не поддерживает "self")

    • /dev/fd/ (FreeBSD, macOS)

    При таком подходе может быть сложно надежно обрабатывать все угловые случаи. Например, рассмотрим ситуацию, когда все файловые дескрипторы> = fd должны быть закрыты, но используются все файловые дескрипторы ‹fd, текущий лимит ресурсов процесса равен fd , и используются файловые дескрипторы> = fd. Поскольку достигнут предел ресурсов процесса, каталог не может быть открыт. Если закрыть каждый файловый дескриптор от fd до ограничения ресурса или sysconf(_SC_OPEN_MAX) используется в качестве запасного варианта, ничего не будет закрыто.

person mark4o    schedule 27.05.2009
comment
О подходе 3: существуют серьезные проблемы с использованием этого метода между fork / exec в многопоточной программе, потому что opendir () может вызвать malloc (), что может привести к тупиковой ситуации в этой ситуации. Боюсь, что под Linux просто нет способа сделать то, что задается вопросом, и разработчики ничего не сделают с этим: sourceware.org/bugzilla/show_bug.cgi?id=10353 - person medoc; 17.07.2015
comment
@medoc: разработка glibc претерпела серьезную реорганизацию в 2012 году, и несколько ранее отвергнутых вещей теперь вошли в новую модель разработки. Возможно, стоит начать новое обсуждение этого вопроса. - person mark4o; 17.07.2015

Способ POSIX:

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

(обратите внимание, что закрытие с 3 вверх, чтобы stdin / stdout / stderr оставался открытым)

close () безвредно возвращает EBADF, если дескриптор файла не открыт. Нет необходимости тратить время на очередную проверку системных вызовов.

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

person chuck    schedule 22.05.2009

Я написал код для работы со всеми особенностями платформы. Все функции безопасны для асинхронных сигналов. Думал, что люди могут найти это полезным. Проверено только на OS X прямо сейчас, не стесняйтесь улучшать / исправлять.

// 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);
    }
}
person Community    schedule 09.10.2010

Прямо тогда, когда ваша программа запустилась и ничего не открыла. Например. как начало main (). pipe и fork немедленно запускают сервер-исполнитель. Таким образом, память и другие детали будут чистыми, и вы можете просто передать их в fork и 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);
}

Если вы хотите выполнить ввод-вывод в исполняемой программе, серверу-исполнителю придется выполнять перенаправление сокетов, и вы можете использовать сокеты unix.

person over_optimistic    schedule 11.06.2016

Почему бы вам не закрыть все дескрипторы от 0 до, скажем, 10000.

Это будет довольно быстро, и худшее, что может случиться, - это EBADF.

person alamar    schedule 22.05.2009
comment
Будет работать, но вам придется сделать это настраиваемым, так как вы просто не знаете, сколько нужно закрыть (зависит от нагрузки). - person ; 30.12.2010