49

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

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

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

4

6 に答える 6

70

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

移植性はありませんが、現在一般的に使用されているほとんどのオペレーティング システムは、この問題に対して次の解決策を 1 つ以上提供しています。

  1. 開いているすべてのファイル記述子>= fdまたは範囲内を閉じるライブラリ関数。これは、すべてのファイル記述子を閉じるという一般的なケースに対する最も簡単な解決策ですが、他の多くの場合には使用できません。特定のセットを除くすべてのファイル記述子を閉じるには、事前にそれらをローエンドに移動し、必要に応じて後で元に戻すために使用できます。dup2

    • closefrom(fd) (glibc 2.34 以降の Linux、Solaris 9 以降、FreeBSD 7.3 以降、NetBSD 3.0 以降、OpenBSD 3.5 以降)

    • fcntl(fd, F_CLOSEM, 0) (AIX、IRIX、NetBSD)

    • close_range(lowfd, highfd, 0) (Linux カーネル 5.9 以降、glibc 2.34 以降、FreeBSD 12.2 以降)

  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) (マックOS)
  4. プロセスに現在割り当てられているファイル記述子スロットの数は、現在使用中のファイル記述子数の上限を提供します。 例 (ルビー)

    • /proc/pid/statusまたは/proc/self/status (Linux)の「FDSize:」行
  5. 開いている各ファイル記述子のエントリを含むディレクトリ。これは #3 と似ていますが、ライブラリ関数ではない点が異なります。これは、一般的な用途の他のアプローチよりも複雑になる可能性があり、proc/fdescfs がマウントされていない、chroot 環境、ディレクトリを開くためのファイル記述子がない (プロセスまたはシステムの制限) など、さまざまな理由で失敗する可能性があります。したがって、このアプローチの使用は、多くの場合、フォールバック メカニズムと組み合わされます。 例 (OpenSSH)別の例 (glib)

    • /proc/pid/fd/または/proc/self/fd/ (Linux、Solaris、AIX、Cygwin、NetBSD)
      (AIX は " " をサポートしていませんself)

    • /dev/fd/ (FreeBSD、macOS)

このアプローチでは、すべてのコーナー ケースを確実に処理することは困難な場合があります。たとえば、すべてのファイル記述子 >= fdがクローズされるが、すべてのファイル記述子 < fdが使用され、現在のプロセス リソース制限がfdであり、使用中のファイル記述子 >= fdがある状況を考えます。プロセス リソースの制限に達したため、ディレクトリを開くことができません。fdからのすべてのファイル記述子をリソース制限まで閉じるかsysconf(_SC_OPEN_MAX)、フォールバックとして使用すると、何も閉じられません。

于 2009-05-27T23:25:07.737 に答える
13

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() の過剰な呼び出しが回避されます。私が知っている最善の解決策ですが、それは完全に移植性がありません。

于 2009-05-22T19:15:58.133 に答える
6

すべてのプラットフォーム固有の機能を処理するコードを作成しました。すべての関数は非同期シグナルセーフです。人々はこれが便利だと思うかもしれません。現在 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);
    }
}
于 2010-10-09T16:09:20.707 に答える
-2

0 から 10000 までのすべての記述子を閉じてみませんか。

それは非常に高速であり、最悪の事態は EBADF です。

于 2009-05-22T17:54:45.110 に答える