简体   繁体   English

execl(“ / bin / bash”,“ bash”,“-l”,“-c”,“ env”,NULL),怎么了?

[英]execl(“/bin/bash”,“bash”,“-l”,“-c”,“env”,NULL), what's wrong?

I want to use execl("/bin/bash","bash","-l","-c","env",NULL) to get the environment variables,the reason why I use parameter "-l" is that I needn't source "/etc/profile" , "~/.bash_login" and so on. 我想使用execl("/bin/bash","bash","-l","-c","env",NULL)来获取环境变量,使用参数"-l"的原因是我不需要来源"/etc/profile" "~/.bash_login"等等。 But when I run it, the program is suspended and I have to use ctrl+c or ctrl+d to stop it? 但是,当我运行它时,该程序被挂起,我必须使用ctrl + c或ctrl + d来停止它吗? Can you tell me how to modify it? 你能告诉我如何修改吗?

The code is shown as follows,getPtrArray is used to change one-dimensional array to two-dimensional array. 代码如下所示,getPtrArray用于将一维数组更改为二维数组。

int pid;
int fd[2];
char buffer[10000];
char** envi;
int res=pipe(fd);
//create a child process to get environment variable
if((pid=fork())==0){
    close(fd[0]);
    dup2(fd[1],STDOUT_FILENO);
    struct passwd *pw=getpwnam("hgchen");
    char *shell_type=pw->pw_shell;
    if(execl("/bin/bash","bash","-l","-c","env",(char*)0)<0){
        printf("Error\n");
    }
    exit(0);     
}
// main process
else{
    wait(NULL);
    close(fd[1]);
    int nbytes=read(fd[0],buffer,sizeof(buffer));
    envi=getPtrArray(buffer);
}

Edit note: This is a full rewrite of the original example code, since the OP posted the code and I realized it causes bash to block on standard output instead of input as I originally thought. 编辑说明:这是对原始示例代码的完整重写,因为OP发布了该代码,并且我意识到这会导致bash阻塞标准输出,而不是我最初认为的输入。 The reason is bash output is redirected to a pipe, with nothing reading from the pipe until the child exits. 原因是bash输出重定向到管道,直到子项退出之前,管道不会读取任何内容。

Before you execl() , reopen STDIN_FILENO from /dev/null , and STDERR_FILENO to /dev/null . 在执行execl()之前,从/dev/null重新打开STDIN_FILENO ,然后将STDERR_FILENO重新打开到/dev/null When STDOUT_FILENO (standard output) is redirected to a pipe, you cannot just wait() for the child to exit: you must actively read from the pipe while the child process runs. STDOUT_FILENO (标准输出)重定向到管道时,您不能仅wait()子进程退出:您必须在子进程运行时主动从管道读取数据。

Consider this example program. 考虑这个示例程序。 It takes one command-line parameter, the user name. 它使用一个命令行参数,即用户名。 (Without any parameters or just -h or --help it outputs short usage information.) (不带任何参数,或者仅带-h--help它会输出简短的使用信息。)

It obtains the struct passwd corresponding to that user name, creating a duplicate of the path to the user shell stored in that structure. 它获取与该用户名相对应的struct passwd ,从而创建该结构中存储的用户外壳程序路径的副本。 It forks a child process, executing path-to-shell shell-name -c env in the child process, capturing the output to a dynamically allocated array (using the execute() function). 它派生一个子进程,在该子进程中执行path-to-shell shell-name -c env程序path-to-shell shell-name -c env ,将输出捕获到动态分配的数组(使用execute()函数)。 The main then just writes the output to original standard output, for simplicity. 为了简单起见,主电源然后仅将输出写入原始标准输出。 You can omit the final while () { ... } loop to see that the output is really captured to the dynamically allocated array. 您可以省略最后的while () { ... }循环,以查看输出是否确实捕获到了动态分配的数组。

Note that I haven't actually verified that all shells support the -c syntax. 请注意,我实际上尚未验证所有 shell都支持-c语法。 I do know that bash , sh (original Bourne shells), dash (a POSIX shell), tcsh , and zsh all do -- covering all shells that are in my /etc/shells , ie allowed shells file --, so it should work in practice; 我确实知道bashsh (原始的Bourne外壳), dash (POSIX外壳), tcshzsh都可以-覆盖了我的/etc/shells所有/etc/shells ,即允许的shell文件,因此应该在实践中工作; I just cannot guarantee it. 我不能保证。

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <pwd.h>
#include <string.h>
#include <errno.h>

/* Open file or device to the specified descriptor.
 * Will never create files.
 * Returns 0 if success, errno otherwise.
*/
static int reopen(const int descriptor, const char *const path, const int flags)
{
    int result, fd;

    if (descriptor == -1)
        return errno = EBADF;
    if (!path || !*path || flags & O_CREAT)
        return errno = EINVAL;

    do {
        fd = open(path, flags);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1)
        return errno;

    if (fd == descriptor)
        return errno = 0;

    do {
        result = dup2(fd, descriptor);
    } while (result == -1 && errno == EINTR);
    if (result == -1)
        return errno;

    do {
        result = close(fd);
    } while (result == -1 && errno == EINTR);
    if (result == -1)
        return errno;

    return errno = 0;
}

/* Helper function: Close descriptor keeping errno unchanged.
 * Returns 0 if success, errno.h error code otherwise.
*/
static int closefd(const int descriptor)
{
    if (descriptor != -1) {
        const int saved_errno = errno;
        int       result;
        do {
            result = close(descriptor);
        } while (result == -1 && errno == EINTR);
        if (result == -1)
            result = errno;
        else
            result = 0;
        errno = saved_errno;
        return result;
    } else
        return EBADF;
}

/* Execute a command in a child process, capturing the output.
 * Standard input and error are redirected to /dev/null.
 * Returns zero if success, errno error code otherwise.
*/
int execute(const char *const cmdpath,
            const char *const args[],
            char      **const dataptr,
            size_t     *const sizeptr,
            size_t     *const lenptr,
            int        *const statusptr)
{
    pid_t   child, p;
    int     out[2], result, *childstatus;
    char   *data;
    size_t  size, used = 0;
    ssize_t bytes;

    if (!cmdpath || !*cmdpath || !args || !args[0] || !dataptr || !sizeptr || !lenptr)
        return errno = EINVAL;

    /* Create the standard output pipe. */
    if (pipe(out))
        return errno;

    /* Fork the child process. */
    child = fork();
    if (child == (pid_t)-1) {
        closefd(out[0]);
        closefd(out[1]);
        return errno;
    }

    if (!child) {
        /*
         * Child process.
        */
        closefd(STDIN_FILENO);
        closefd(STDOUT_FILENO);
        closefd(STDERR_FILENO);
        closefd(out[0]);

        /* Redirect standard output to the pipe. */
        if (out[1] != STDOUT_FILENO) {
            do {
                result = dup2(out[1], STDOUT_FILENO);
            } while (result == -1 && errno == EINTR);
            if (result == -1)
                _exit(127);
            closefd(out[1]);
        }

        /* Open standard input from /dev/null. */
        if (reopen(STDIN_FILENO, "/dev/null", O_RDONLY))
            _exit(127);

        /* Open standard error to /dev/null. */
        if (reopen(STDERR_FILENO, "/dev/null", O_WRONLY))
            _exit(127);

        /* Execute the specified command. */
        execv(cmdpath, (char **)args);

        /* Failed. */
        _exit(127);
    }

    /*
     * Parent process.
    */

    closefd(out[1]);

    if (*sizeptr > 0) {
        data = *dataptr;
        size = *sizeptr;
    } else {
        data = *dataptr = NULL;
        size = *sizeptr = 0;
    }

    while (1) {

        /* Grow data array if needed. */
        if (used >= size) {
            size = (used | 32767) + 32769;
            data = realloc(data, size);
            if (!data) {
                kill(child, SIGTERM);
                do {
                    p = waitpid(child, NULL, 0);
                } while (p == (pid_t)-1 && errno == EINTR);
                return errno = ENOMEM;
            }
            *dataptr = data;
            *sizeptr = size;
        }

        /* Read more data. */
        do {
            bytes = read(out[0], data + used, size - used);
        } while (bytes == (ssize_t)-1 && errno == EINTR);
        if (bytes > (ssize_t)0)
            used += (size_t)bytes;
        else
        if (bytes == (ssize_t)0)
            break; /* All read (end of input) */
        else {
            const int retval = (bytes == (ssize_t)-1) ? errno : EIO;
            kill(child, SIGTERM);
            do {
                p = waitpid(child, NULL, 0);
            } while (p == (pid_t)-1 && errno == EINTR);
            return errno = retval;
        }
    }

    /* We need to add the final '\0', which might not fit. */
    if (used + 1 >= size) {
        size = used + 1;
        data = realloc(data, size);
        if (!data) {
            kill(child, SIGTERM);
            do {
                p = waitpid(child, NULL, 0);
            } while (p == (pid_t)-1 && errno == EINTR);
            return errno = ENOMEM;
        }
        *dataptr = data;
        *sizeptr = size;
    }

    data[used] = '\0';
    if (lenptr)
        *lenptr = used;

    /* Reap the child process. */
    if (statusptr)
        childstatus = statusptr;
    else
        childstatus = &result;
    do {
        p = waitpid(child, childstatus, 0);
    } while (p == (pid_t)-1 && errno == EINTR);
    if (p == (pid_t)-1)
        return errno;

    /* Success. */
    return errno = 0;
}

/* A helper to write to standard error. Errno is kept unchanged.
 * Returns zero if success, errno error code otherwise.
 * Async-signal safe, in case you wish to use this safely in a signal handler.
*/
static int wrerr(const char *const message)
{
    if (message && *message) {
        const int   saved_errno = errno;
        const char *p = message;
        const char *q = message;
        ssize_t     n;

        /* q = message + strlen(message), except that strlen()
         * is not an async-signal safe function. */
        while (*q)
            q++;

        while (p < q) {
            n = write(STDERR_FILENO, p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1) {
                errno = saved_errno;
                return EIO;
            } else
            if (errno != EINTR) {
                const int retval = errno;
                errno = saved_errno;
                return retval;
            }
        }

        errno = saved_errno;
        return 0;
    } else
        return 0;
}

const char *basename_of(const char *const string)
{
    const char *r;

    if (!string)
        return NULL;

    r = strrchr(string, '/');
    if (r && r[1])
        return r + 1;

    return NULL;
}

int main(int argc, char *argv[])
{
    struct passwd *pw;
    char          *shell;
    const char    *args[4];
    char          *data = NULL;
    size_t         size = 0;
    size_t         used = 0;
    int            status;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        wrerr("\n");
        wrerr("Usage: "); wrerr(argv[0]); wrerr(" [ -h | --help ]\n");
        wrerr("       "); wrerr(argv[0]); wrerr(" USERNAME\n");
        wrerr("\n");
        return 1;
    }

    pw = getpwnam(argv[1]);
    if (!pw) {
        wrerr(argv[1]);
        wrerr(": ");
        wrerr(strerror(errno));
        wrerr(".\n");
        return 1;
    }

    if (pw->pw_shell && pw->pw_shell[0] == '/')
        shell = strdup(pw->pw_shell);
    else
        shell = strdup("/bin/sh");
    args[0] = basename_of(shell);
    if (!args[0]) {
        wrerr(argv[1]);
        wrerr(": User has invalid shell, '");
        wrerr(shell);
        wrerr("'.\n");
        return 1;
    }

    args[1] = "-c";
    args[2] = "env";
    args[3] = NULL;

    if (execute(shell, args, &data, &size, &used, &status)) {
        wrerr("Failed to execute ");
        wrerr(shell);
        wrerr(": ");
        wrerr(strerror(errno));
        wrerr(".\n");
        return 1;
    }

    free(shell);

    /* Dump environment to standard output. */
    {
        const char       *p = data;
        const char *const q = data + used;
        ssize_t           n;

        while (p < q) {
            n = write(STDOUT_FILENO, p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1) {
                wrerr("Error writing to standard output.\n");
                return 1;
            } else
            if (errno != EINTR) {
                wrerr("standard output: ");
                wrerr(strerror(errno));
                wrerr(".\n");
                return 1;
            }
        }
    }

    free(data);
    data = NULL;
    size = 0;
    used = 0;

    /* All done. */
    return 0;
}

This is much lower-level code than is really necessary (or preferred); 这是比实际需要(或首选)低得多的代码; you can do the same using popen() and other stdio.h I/O functions. 您可以使用popen()和其他stdio.h I / O函数执行相同的操作。

(I avoided those only to make this more interesting to myself.) (我避免那些只是为了让自己对此更加有趣。)

The wrerr() is just a helper function I like to use, as unlike fprintf() / printf() / perror() , it is async-signal safe and ignores signal deliveries ( errno==EINTR ). wrerr()只是我喜欢使用的辅助函数,与fprintf() / printf() / perror() ,它是异步信号安全的,并且忽略信号传递( errno==EINTR )。 Here, it is not needed, and you could use eg fprintf() just as well. 这里, 没有必要,你可以使用如fprintf()一样好。 (Unlike just about every example you can see on the net, printf() et al. are not supposed to work in signal handlers. They usually do work, but there are absolutely no guarantees. wrerr() will work, as it is POSIX compliant.) (与您在网上看到的几乎每个示例不同, printf()等人不应该在信号处理程序中工作。它们通常可以工作,但是绝对不能保证wrerr()可以工作,因为它是POSIX兼容。)

I also included full error checking. 我还包括完整的错误检查。 Some of the error cases are impossible to hit without a kernel bug, but I prefer to have them anyway. 如果没有内核错误,某些错误情况是不可能发生的,但是无论如何,我还是更喜欢它们。 You really do want them in the cases where you do hit a bug, be it in your own code or elsewhere. 您确实希望在遇到错误的情况下使用它们,无论是在您自己的代码中还是在其他地方。

In the error cases I don't bother to free dynamically allocated memory (although I could have), since the kernel will always take care of that automatically. 在错误情况下,我不会费心释放动态分配的内存(尽管我可以这样做),因为内核将始终自动处理该问题。 The program does, however, release all dynamically allocated memory before returning from main() if no errors occur. 但是,如果没有发生错误,则程序会在从main()返回之前释放所有动态分配的内存。

Questions? 有问题吗?

This doesn't directly answer your question, but it's much easier to use popen(3) . 这不会直接回答您的问题,但是使用popen(3)会容易得多。

This is tested and working (under OSX, not Linux): 这已经过测试并且可以正常工作(在OSX而非Linux下):

#include <stdio.h>

int main(int argc, const char **argv) {
    char line[1024];
    FILE *pipefp = popen("/bin/bash -l -c env", "r");
    if (pipefp) {
        while (fgets(line, sizeof(line), pipefp)) {
            // Note: line contains newline
            printf("%s", line);
        }
        pclose(pipefp);
    }
    return 0;
}

A way to find what's causing your command to suspend add "-x" option. 查找导致命令挂起的原因的方法是添加“ -x”选项。 And this code work for me: 这段代码对我有用:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    int r = execl("/bin/bash", "bash", "-x", "-l", "-c", "env", NULL);
    printf("r: %d\n", r); /* This should not be printed or else execl has errors. */
    return 0;
}

The wait in the parent is wrong. 父母的wait是错误的。 You must first read the output from the shall and then wait for it. 您必须首先读取来自must的输出, 然后等待它。 As already explained in the comment, the shell is waiting for you to read the output and you are waiting for the shell to exit, therefore you deadlocked. 正如注释中已经解释的那样,shell正在等待您读取输出,并且正在等待shell退出,因此您陷入了僵局。

You need to move the wait after the read . read ,您需要移动wait

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM