Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

Несмотря на переносимость, закрытие всех файловых дескрипторов до 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)

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

Ответ 2

Путь POSIX:

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

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

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

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

Ответ 3

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

// Async-signal safe way to get the current process 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);
    }
}

Ответ 4

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

Ответ 5

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

Это будет довольно быстро, и самое худшее, что произойдет, это EBADF.