【问题标题】:What is wrong with my execvp usage?我的 execvp 使用有什么问题?
【发布时间】:2015-05-07 18:44:11
【问题描述】:

我正在编写一个小 shell 来学习 C。现在我想执行自定义命令,但它不起作用。

$ ./a.out 
OS>ls
10357: executing ls

failed to execute ls
: (2: No such file or directory)

我不能使用系统调用来执行自定义命令,我应该使用 execvp 和 fork。但为什么它现在起作用了?整个代码是

#include<sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <signal.h>
int mystrcmp(char const *, char const *);

struct command
{
    char * const *argv;
};
static _Noreturn void err_syserr(char *fmt, ...)
{
    int errnum = errno;
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(EXIT_FAILURE);
}
/* Helper function that spawns processes */
static int spawn_proc(int in, int out, struct command *cmd)
{
    pid_t pid;
    if ((pid = fork()) == 0)
    {
        if (in != 0)
        {
            if (dup2(in, 0) < 0)
                err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
            ;
            close(in);
        }
        if (out != 1)
        {
            if (dup2(out, 1) < 0)
                err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
            close(out);
        }
        fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
        execvp(cmd->argv[0], cmd->argv);
        err_syserr("failed to execute %s: ", cmd->argv[0]);
    }
    else if (pid < 0)   {
        err_syserr("fork failed: ");
    }
    return pid;
}

/* Helper function that forks pipes */
static void fork_pipes(int n, struct command *cmd)
{
    int i;
    int in = 0;
    int fd[2];
    for (i = 0; i < n - 1; ++i)
    {
        pipe(fd);
        spawn_proc(in, fd[1], cmd + i);
        close(fd[1]);
        in = fd[0];
    }
    if (dup2(in, 0) < 0)    {
        err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
    }
    fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
    execvp(cmd[i].argv[0], cmd[i].argv);
    err_syserr("failed to execute %s: ", cmd[i].argv[0]);
}

#define BUFFERSIZE 200
int main() {

    char *args[80];
    char buffer[BUFFERSIZE];
    char *prompt = "OS";
    char *a = ">";

    char *tok;
    tok = strtok (buffer," ");


    while(buffer != NULL) {
        bzero(buffer, BUFFERSIZE);
        printf("%s%s",prompt,a);
        fgets(buffer, BUFFERSIZE, stdin);



        if(mystrcmp(buffer,"cd") == 0) {
            tok = strchr(buffer,' ')+1; //use something more powerful
            *strchr(tok, '\n')='\0';
            cd(tok);
        }
        else if(mystrcmp(buffer,"exit") == 0) {
            return 0;
        }
        else {
            //system("ls"); //for testing the CWD/PWD

            char *commandbuffer[] = { buffer, 0 };
            //char *less[] = { "less", 0 };
            struct command cmd[] = { {commandbuffer} };
            fork_pipes(1, cmd);
            printf("Spawned foreground process: %d\n", getpid());
        }
    }
    return 0;
}

int mystrcmp(char const *p, char const *q)
{
    int i = 0;
    for(i = 0; q[i]; i++)
    {
        if(p[i] != q[i])
            return -1;
    }
    return 0;
}

int cd(char *pth) {
    char path[BUFFERSIZE];
    strcpy(path,pth);

    char *token;

    char cwd[BUFFERSIZE];
    if(pth[0] != '/')
    {   // true for the dir in cwd
        getcwd(cwd,sizeof(cwd));
        strcat(cwd,"/");
        strcat(cwd,path);
        chdir(cwd);
    } else { //true for dir w.r.t. /
        chdir(pth);
    }
    printf("Spawned foreground process: %d\n", getpid());
    return 0;
}

【问题讨论】:

    标签: c linux exec fgets execvp


    【解决方案1】:

    之后

    fgets(buffer, BUFFERSIZE, stdin);
    

    buffer 始终以“\n”结尾,因为您的输入以回车结束。

    因此,如果您只是将ls 作为命令传递,那么您的程序将得到ls\n,显然PATH 中没有这样的命令或二进制文件。

    要解决此问题,您只需执行以下操作:

    fgets(buffer, BUFFERSIZE, stdin);
    
    if (buffer[strlen(buffer)-1] == '\n')
        buffer[strlen(buffer)-1] = '\0';
    
    ....
    

    【讨论】:

      【解决方案2】:

      错误不是您使用execvp,而是您使用fgetsfgets 将换行符留在缓冲区中的行尾,因此最终您将"ls\n" 提供给execvp,它正确地抱怨它找不到该命令。

      因为我猜你最终还是会替换这段代码,所以暂时,

      fgets(buffer, BUFFERSIZE, stdin);
      strtok(buffer, "\n"); /* quick & dirty: remove newline if there. */
      

      在您开始正确地进行输入解析之前解决这个问题。 不过,我不能推荐任何使用 strtok 作为长期解决方案的东西。 从长远来看,您可能对 GNU 特定的 getline 函数感兴趣,或者实际上对 libreadline 感兴趣(如果将您的代码置于 GPL 下对您来说不是问题)。

      【讨论】:

        【解决方案3】:

        像往常一样,这个案子可以用 strace 解决。

        很遗憾,代码太错误太长,我无法写出详尽的评论。

        meh.c:99:13: warning: implicit declaration of function 'cd' is invalid
        in C99 [-Wimplicit-function-declaration]
                cd(tok);
                ^
        meh.c:80:11: warning: unused variable 'args' [-Wunused-variable]
        char *args[80];
              ^
        meh.c:132:11: warning: unused variable 'token' [-Wunused-variable]
        char *token;
              ^
        3 warnings generated.
        

        这是怎么回事?

        #include <sys/stat.h>
        #include <stdio.h>
        #include <unistd.h>
        #include <string.h>
        #include <dirent.h>
        #include <errno.h>
        #include <stdarg.h>
        #include <stdlib.h>
        #include <signal.h>
        int mystrcmp(char const *, char const *);
        
        struct command
        {
            char * const *argv;
        };
        static _Noreturn void err_syserr(char *fmt, ...)
        {
            int errnum = errno;
            va_list args;
            va_start(args, fmt);
            vfprintf(stderr, fmt, args);
            va_end(args);
            if (errnum != 0)
                fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
            exit(EXIT_FAILURE);
        }
        

        请考虑使用非标准的 err func。

        /* Helper function that spawns processes */
        static int spawn_proc(int in, int out, struct command *cmd)
        {
            pid_t pid;
            if ((pid = fork()) == 0)
            {
                if (in != 0)
                {
                    if (dup2(in, 0) < 0)
                        err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]);
                    ;
                    close(in);
                }
                if (out != 1)
                {
                    if (dup2(out, 1) < 0)
                        err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]);
                    close(out);
                }
        

        如果您必须像这样签入和签出 fd,很可能您已经做错了什么。考虑一下如果 'out' 为 0 会发生什么。在这个级别上,只需确保您的 shell 始终打开 0、1、2,这样就可以解决问题。

                fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]);
                execvp(cmd->argv[0], cmd->argv);
                err_syserr("failed to execute %s: ", cmd->argv[0]);
            }
            else if (pid < 0)   {
                err_syserr("fork failed: ");
            }
        

        改组允许尽早放置父代码并避免长子大小写的缩进。

            return pid;
        }
        
        /* Helper function that forks pipes */
        static void fork_pipes(int n, struct command *cmd)
        {
            int i;
            int in = 0;
            int fd[2];
            for (i = 0; i < n - 1; ++i)
            {
                pipe(fd);
                spawn_proc(in, fd[1], cmd + i);
                close(fd[1]);
                in = fd[0];
            }
            if (dup2(in, 0) < 0)    {
                err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]);
            }
            fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]);
        

        如果带有换行符的 printfs 不够用,strace 会显示问题:

        execve("/usr/bin/ls\n", ["ls\n"], [/* 58 vars */]) = -1 ENOENT (没有那个文件或目录)

            execvp(cmd[i].argv[0], cmd[i].argv);
        

        你确实意识到这会覆盖你的 shell?

            err_syserr("failed to execute %s: ", cmd[i].argv[0]);
        }
        
        #define BUFFERSIZE 200
        int main() {
        
            char *args[80];
            char buffer[BUFFERSIZE];
            char *prompt = "OS";
            char *a = ">";
        
            char *tok;
            tok = strtok (buffer," ");
        
        
            while(buffer != NULL) {
                bzero(buffer, BUFFERSIZE);
                printf("%s%s",prompt,a);
                fgets(buffer, BUFFERSIZE, stdin);
        
        
        
                if(mystrcmp(buffer,"cd") == 0) {
                    tok = strchr(buffer,' ')+1; //use something more powerful
                    *strchr(tok, '\n')='\0';
                    cd(tok);
                }
                else if(mystrcmp(buffer,"exit") == 0) {
                    return 0;
                }
                else {
                    //system("ls"); //for testing the CWD/PWD
        
                    char *commandbuffer[] = { buffer, 0 };
                    //char *less[] = { "less", 0 };
                    struct command cmd[] = { {commandbuffer} };
                    fork_pipes(1, cmd);
                    printf("Spawned foreground process: %d\n", getpid());
                }
            }
            return 0;
        }
        
        int mystrcmp(char const *p, char const *q)
        {
            int i = 0;
        

        这个初始化是怎么回事?

            for(i = 0; q[i]; i++)
        

        不正确。你假设 q 不长于 p。

            {
                if(p[i] != q[i])
                    return -1;
            }
        

        有比逐个字符比较更好的方法。

            return 0;
        }
        

        这到底是干什么用的?

        int cd(char *pth) {
            char path[BUFFERSIZE];
            strcpy(path,pth);
        

        路径和pth?男人。考虑'orig_path'或其他东西。一个 /word/ 的变体看起来像是一个错字,实际上你很容易意外地输入错误。 fscking 避免。

            char *token;
        
            char cwd[BUFFERSIZE];
            if(pth[0] != '/')
            {   // true for the dir in cwd
                getcwd(cwd,sizeof(cwd));
                strcat(cwd,"/");
                strcat(cwd,path);
                chdir(cwd);
        

        即使忽略通常的缓冲区溢出问题和缺少错误检查,这也是不正确的。如果在 getcwd 后修改了与此过程相关的目录树,则您输入了错误的目录(假设 chdir 成功)。此外,包括“..”在内的路径对符号链接很敏感。1

            } else { //true for dir w.r.t. /
                chdir(pth);
            }
            printf("Spawned foreground process: %d\n", getpid());
        

        看起来像复制粘贴?

            return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-10-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-26
          相关资源
          最近更新 更多