문제

현재 프로세스에 가장 높은 할당 된 파일 디스크립터 번호를 얻는 휴대용 방법 (POSIX)이 있습니까?

예를 들어 AIX에서 번호를 얻는 좋은 방법이 있다는 것을 알고 있지만 휴대용 방법을 찾고 있습니다.

내가 묻는 이유는 모든 열린 파일 설명자를 닫고 싶기 때문입니다. 내 프로그램은 루트가 아닌 사용자를위한 Root 및 Forks 및 Execs Child 프로그램으로 실행되는 서버입니다. 어린이 프로세스에서 권한있는 파일 설명자를 열어 두는 것은 보안 문제입니다. 일부 파일 설명자는 제어 할 수없는 코드 (C 라이브러리, 타사 라이브러리 등)에 의해 열 수 있으므로 의존 할 수 없습니다. FD_CLOEXEC 어느 하나.

도움이 되었습니까?

해결책

휴대용이지만 모든 파일 설명자를 닫습니다 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 닫아야하지만 모든 파일 설명자 FD 현재 프로세스 리소스 제한은 사용됩니다 FD, 그리고 파일 설명자> =입니다 FD 사용. 프로세스 리소스 제한에 도달 했으므로 디렉토리를 열 수 없습니다. 모든 파일 디스크립터를 닫는 경우 FD 자원 제한을 통해 또는 sysconf(_SC_OPEN_MAX) 폴백으로 사용되며 아무것도 닫히지 않습니다.

다른 팁

Posix 방식은 다음과 같습니다.

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

(Stdin/stdout/stderr를 열어두기 위해 3 위로 닫힙니다)

Close () 파일 디스크립터가 열리지 않으면 ebadf를 무해하게 반환합니다. 다른 시스템 통화 점검을 낭비 할 필요가 없습니다.

일부 유닉스는 ()를 지원합니다. 이렇게하면 가능한 최대 파일 디스크립터 번호에 따라 닫는 ()에 대한 과도한 호출을 피할 수 있습니다 (). 내가 알고있는 최상의 솔루션이지만 완전히 포송 할 수 없습니다.

모든 플랫폼 별 기능을 처리하기 위해 코드를 작성했습니다. 모든 기능은 비동기 신호입니다. 사람들이 이것이 유용하다고 생각했다고 생각했습니다. 지금 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 ()의 시작처럼. 파이프와 포크는 즉시 Executer 서버를 시작합니다. 이렇게하면 메모리와 기타 세부 사항이 깨끗하며 포크 & 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를 수행하려면 Executor Server는 소켓 리디렉션을 수행해야하며 Unix 소켓을 사용할 수 있습니다.

모든 디스크립터를 0에서 10000까지 닫지 않겠습니까?

그것은 꽤 빠르며, 일어날 최악의 일은 EBADF입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top