web-dev-qa-db-ja.com

割り当てられたファイル記述子の最大値を取得する

現在のプロセスに割り当てられた最大のファイル記述子番号を取得するための移植可能な方法(POSIX)はありますか?

たとえば、AIXで番号を取得するための優れた方法があることは知っていますが、移植可能な方法を探しています。

私が尋ねている理由は、開いているすべてのファイル記述子を閉じたいからです。私のプログラムは、rootとして実行され、root以外のユーザーの子プログラムをフォークして実行するサーバーです。子プロセスで特権ファイル記述子を開いたままにしておくことは、セキュリティの問題です。一部のファイル記述子は、制御できないコード(Cライブラリ、サードパーティライブラリなど)によって開かれる可能性があるため、FD_CLOEXECにも依存できません。

45
Ville Laurikari

移植可能ではありますが、sysconf(_SC_OPEN_MAX)までのすべてのファイル記述子を閉じることは信頼できません。これは、ほとんどのシステムで、この呼び出しが現在のファイル記述子のソフト制限を返すためです。もう1つの問題は、多くのシステムでsysconf(_SC_OPEN_MAX)が_INT_MAX_を返す可能性があることです。これにより、このアプローチが許容できないほど遅くなる可能性があります。残念ながら、すべての可能な非負のintファイル記述子を反復処理する必要のない信頼性の高いポータブルな代替手段はありません。

ポータブルではありませんが、今日一般的に使用されているほとんどのオペレーティングシステムは、この問題に対して次の1つ以上のソリューションを提供します。

  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)がフォールバックとして使用される場合、何も閉じられません。

66
mark4o

POSIXの方法は次のとおりです。

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

(stdin/stdout/stderrを開いたままにするために、3から閉じていることに注意してください)

close()は、ファイル記述子が開いていない場合、無害にEBADFを返します。別のシステムコールチェックを無駄にする必要はありません。

一部のUnixはclosefrom()をサポートしています。これにより、可能な最大ファイル記述子数に応じて、close()への過剰な呼び出しが回避されます。私が知っている最善の解決策ですが、それは完全に移植性がありません。

14
chuck

プラットフォーム固有のすべての機能を処理するコードを作成しました。すべての機能は非同期信号に対して安全です。人々はこれが役に立つと思うかもしれません。現在OSXでのみテストされており、自由に改善/修正してください。

// 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);
    }
}
6
Hongli

プログラムが開始され、何も開かれていないとき。例えば。 main()の開始のように。パイプとフォークはすぐに実行サーバーを起動します。このように、それはメモリと他の詳細がきれいであり、あなたはそれをフォークと実行に与えることができます。

#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
over_optimistic