获取最高分配的文件描述符

发布于 2024-07-21 00:04:46 字数 272 浏览 11 评论 0原文

是否有一种可移植的方法(POSIX)来获取当前进程的最高分配文件描述符编号?

例如,我知道有一种很好的方法可以获取 AIX 上的号码,但我正在寻找一种可移植的方法。

我问的原因是我想关闭所有打开的文件描述符。 我的程序是一个以 root 身份运行的服务器,并为非 root 用户分叉和执行子程序。 在子进程中保持特权文件描述符打开是一个安全问题。 某些文件描述符可能由我无法控制的代码(C 库、第三方库等)打开,因此我也不能依赖 FD_CLOEXEC

Is there a portable way (POSIX) to get the highest allocated file descriptor number for the current process?

I know that there's a nice way to get the number on AIX, for example, but I'm looking for a portable method.

The reason I'm asking is that I want to close all open file descriptors. My program is a server which runs as root and forks and execs child programs for non-root users. Leaving the privileged file descriptors open in the child process is a security problem. Some file descriptors may be opened by code I cannot control (the C library, third party libraries, etc.), so I cannot rely on FD_CLOEXEC either.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

鼻尖触碰 2024-07-28 00:04:46

虽然可移植,但关闭 sysconf(_SC_OPEN_MAX) 之前的所有文件描述符并不可靠,因为在大多数系统上,此调用会返回当前文件描述符软限制,该限制可能已降低到最高使用的文件描述符以下。 另一个问题是,在许多系统上 sysconf(_SC_OPEN_MAX) 可能会返回 INT_MAX,这可能导致此方法速度慢得令人无法接受。 不幸的是,没有可靠、可移植的替代方案不涉及迭代每个可能的非负 int 文件描述符。

虽然不可移植,但当今常用的大多数操作系统都针对此问题提供了以下一个或多个解决方案:

  1. 用于关闭所有打开的文件描述符的库函数 >= fd< /em> 或在一个范围内。 对于关闭所有文件描述符的常见情况,这是最简单的解决方案,尽管它不能用于其他用途。 要关闭除特定集合之外的所有文件描述符,可以使用 dup2 将它们预先移至低端,并在必要时将它们移回。

    • closefrom(fd)(Linux with glibc 2.34+、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())
      返回有关进程的信息,包括当前在 pst.pst_highestfd 中打开的最高文件描述符。 (HP-UX)

  3. 用于列出进程当前使用的所有文件描述符的库函数。 这更加灵活,因为它允许关闭所有文件描述符,查找最高的文件描述符,或者对每个打开的文件描述符(甚至可能是另一个进程的文件描述符)执行任何其他操作。 示例 (OpenSSH)

    • proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, fdinfo_buf, sz) (macOS)

  4. 当前为进程分配的文件描述符槽数提供了上限绑定到当前使用的文件描述符编号。 示例 (Ruby)

    • /proc/pid/status/proc/self/status 中的“FDSize:”行(Linux)
  5. A 包含每个打开的文件描述符的条目的目录。 这与 #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正在使用。 由于已达到进程资源限制,因此无法打开目录。 如果通过资源限制关闭 fd 中的每个文件描述符或将 sysconf(_SC_OPEN_MAX) 用作后备,则不会关闭任何内容。

While portable, closing all file descriptors up to sysconf(_SC_OPEN_MAX) is not reliable, because on most systems this call returns the current file descriptor soft limit, which could have been lowered below the highest used file descriptor. Another issue is that on many systems sysconf(_SC_OPEN_MAX) may return INT_MAX, which can cause this approach to be unacceptably slow. Unfortunately, there is no reliable, portable alternative that does not involve iterating over every possible non-negative int file descriptor.

Although not portable, most operating systems in common use today provide one or more of the following solutions to this problem:

  1. A library function to close all open file descriptors >= fd or within a range. This is the simplest solution for the common case of closing all file descriptors, although it cannot be used for much else. To close all file descriptors except for a certain set, dup2 can be used to move them to the low end beforehand, and to move them back afterward if necessary.

    • closefrom(fd) (Linux with glibc 2.34+, 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 kernel 5.9+ with glibc 2.34+, FreeBSD 12.2+)

  2. A library function to provide the maximum file descriptor currently in use by the process. To close all file descriptors above a certain number, either close all of them up to this maximum, or continually get and close the highest file descriptor in a loop until the low bound is reached. Which is more efficient depends on the file descriptor density.

    • fcntl(0, F_MAXFD) (NetBSD)

    • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
      Returns information about the process, including the highest file descriptor currently open in ps.pst_highestfd. (HP-UX)

  3. A library function to list all file descriptors currently in use by the process. This is more flexible in that it allows for closing all file descriptors, finding the highest file descriptor, or doing just about anything else on every open file descriptor, possibly even those of another process. Example (OpenSSH)

    • proc_pidinfo(getpid(), PROC_PIDLISTFDS, 0, fdinfo_buf, sz) (macOS)
  4. The number of file descriptor slots currently allocated for a process provides an upper bound on the file descriptor numbers currently in use. Example (Ruby)

    • "FDSize:" line in /proc/pid/status or /proc/self/status (Linux)
  5. A directory containing an entry for each open file descriptor. This is similar to #3 except that it isn't a library function. This can be more complicated than the other approaches for the common uses, and can fail for a variety of reasons such as proc/fdescfs not mounted, a chroot environment, or no file descriptors available to open the directory (process or system limit). Therefore use of this approach is often combined with a fallback mechanism. Example (OpenSSH), another example (glib).

    • /proc/pid/fd/ or /proc/self/fd/ (Linux, Solaris, AIX, Cygwin, NetBSD)
      (AIX does not support "self")

    • /dev/fd/ (FreeBSD, macOS)

It can be difficult to handle all corner cases reliably with this approach. For example consider the situation where all file descriptors >= fd are to be closed, but all file descriptors < fd are used, the current process resource limit is fd, and there are file descriptors >= fd in use. Because the process resource limit has been reached the directory cannot be opened. If closing every file descriptor from fd through the resource limit or sysconf(_SC_OPEN_MAX) is used as a fallback, nothing will be closed.

彡翼 2024-07-28 00:04:46

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() 进行过多的调用。 虽然这是我所知道的最好的解决方案,但它完全不可移植。

The POSIX way is:

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

(note that's closing from 3 up, to keep stdin/stdout/stderr open)

close() harmlessly returns EBADF if the file descriptor is not open. There's no need to waste another system call checking.

Some Unixes support a closefrom(). This avoids the excessive number of calls to close() depending on the maximum possible file descriptor number. While the best solution I'm aware of, it's completely nonportable.

∞梦里开花 2024-07-28 00:04:46

我已经编写了代码来处理所有特定于平台的功能。 所有函数都是异步信号安全的。 我认为人们可能会发现这很有用。 目前仅在 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);
    }
}

I've written code to deal with all platform-specific features. All functions are async-signal safe. Thought people might find this useful. Only tested on OS X right now, feel free to improve/fix.

// 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);
    }
}
吹梦到西洲 2024-07-28 00:04:46

在 MacOS 上,您可以将 posix_spawn 与通过 posix_spawnattr_setflags 设置的 Apple 扩展 POSIX_SPAWN_CLOEXEC_DEFAULT 结合使用。

这将只留下在 posix_spawn 调用 open 中显式设置的文件描述符,关闭调用其他文件描述符。

On MacOS, you can use posix_spawn with the Apple extension POSIX_SPAWN_CLOEXEC_DEFAULT set with posix_spawnattr_setflags.

This will leave only the file descriptors set up explicitly in the posix_spawn call open, closing call the others.

过潦 2024-07-28 00:04:46

当您的程序启动但尚未打开任何内容时。 例如,像 main() 的开头一样。 pipeline 和 fork 立即启动执行服务器。 这样它的内存和其他细节就干净了,你可以给它一些东西来 fork & 执行。

#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 套接字。

Right when your program started and hasn't opened anything. E.g. like the start of main(). pipe and fork immediately starting an executer server. This way it's memory and other details is clean and you can just give it things to 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);
}

If you want to do IO on the executed program the executor server will have to do socket redirects and you can use unix sockets.

雪化雨蝶 2024-07-28 00:04:46

为什么不关闭从 0 到 10000 的所有描述符。

这会非常快,而且最糟糕的情况是 EBADF。

Why don't you close all descriptors from 0 to, say, 10000.

It would be pretty fast, and the worst thing that would happen is EBADF.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文