Получение максимально выделенного дескриптора файла - PullRequest
45 голосов
/ 22 мая 2009

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

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

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

Ответы [ 5 ]

66 голосов
/ 28 мая 2009

Несмотря на переносимость, закрытие всех файловых дескрипторов до 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. Каталог , содержащий запись для каждого дескриптора открытого файла . Это наиболее гибкий подход, поскольку он позволяет закрывать все файловые дескрипторы, находить самый высокий файловый дескриптор или делать что-либо еще с каждым открытым файловым дескриптором, даже с дескриптором другого процесса (в большинстве систем). Однако это может быть более сложным, чем другие подходы для общего использования. Кроме того, это может привести к сбою по разным причинам, таким как proc / fdescfs не смонтирован, среда chroot или файловые дескрипторы недоступны для открытия каталога (ограничение процесса или системы). Поэтому использование этого подхода часто сочетается с резервным механизмом. Пример (OpenSSH) , Другой пример (glib) .

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

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

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

13 голосов
/ 22 мая 2009

Путь 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 () в зависимости от максимально возможного числа дескрипторов файлов. Хотя это лучшее решение, которое я знаю, оно абсолютно не переносимо.

6 голосов
/ 09 октября 2010

Я написал код для работы со всеми функциями, специфичными для платформы. Все функции защищены от асинхронных сигналов. Мысли люди могут найти это полезным. Только сейчас протестировано на 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 /829528/poluchenie-maksimalno-vydelennogo-deskriptora-faila
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 голосов
/ 12 июня 2016

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

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

0 голосов
/ 22 мая 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...