Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

execl("/bin/bash","bash","-l","-c","env",NULL), what's wrong?

Tags:

c

linux

bash

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. But when I run it, the program is suspended and I have to use ctrl+c or ctrl+d to stop it? 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.

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);
}
like image 332
BreakingDawn Avatar asked Feb 14 '26 15:02

BreakingDawn


2 Answers

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. The reason is bash output is redirected to a pipe, with nothing reading from the pipe until the child exits.

Before you execl(), reopen STDIN_FILENO from /dev/null, and STDERR_FILENO to /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.

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.)

It obtains the struct passwd corresponding to that user name, creating a duplicate of the path to the user shell stored in that structure. 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). 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.

Note that I haven't actually verified that all shells support the -c syntax. 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, i.e. allowed shells file --, so it should work in practice; 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.

(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). Here, it is not needed, and you could use e.g. fprintf() just as well. (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.)

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.

Questions?

like image 149
Nominal Animal Avatar answered Feb 17 '26 04:02

Nominal Animal


This doesn't directly answer your question, but it's much easier to use popen(3).

This is tested and working (under OSX, not 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;
}
like image 29
trojanfoe Avatar answered Feb 17 '26 05:02

trojanfoe