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

StackOverflow https://stackoverflow.com/questions/899038

  •  23-08-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

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

  1. Библиотечная функция, позволяющая закрыть все файловые дескрипторы >= ФД.Это самое простое решение для обычного случая закрытия всех файловых дескрипторов, хотя его нельзя использовать ни для чего другого.Чтобы закрыть все файловые дескрипторы, кроме определенного набора, 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) (НетБСД)

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

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

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

    • /dev/fd/ (FreeBSD, Дарвин, OS X)

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

Другие советы

Путь POSIX:

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

(обратите внимание, что он закрывается с 3-го уровня, чтобы стандартный ввод/вывод/stderr оставался открытым)

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

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

Я написал код для работы со всеми функциями, специфичными для платформы.Все функции безопасны для асинхронных сигналов.Подумал, что людям это может пригодиться.На данный момент протестировано только на 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);
    }
}

Именно тогда, когда ваша программа запустилась и ничего не открыла.Например.как начало main().Pipe and 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.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top