Что не так с моим использованием execvp?

Я пишу небольшую оболочку для изучения 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;
}

person Niklas R.    schedule 07.05.2015    source источник


Ответы (3)


После

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';

....
person dekkard    schedule 07.05.2015

Ошибка не в том, что вы используете execvp, а в том, что вы используете fgets. fgets оставляет новую строку в конце строки в буфере, поэтому в конечном итоге вы передаете "ls\n" в execvp, и он справедливо жалуется, что не может найти эту команду.

Поскольку я предполагаю, что вы все равно в конечном итоге замените этот код, на данный момент,

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

избавится от проблемы, пока вы не научитесь правильно анализировать входные данные. Однако я не могу рекомендовать ничего, что использует strtok в качестве долгосрочного решения. В долгосрочной перспективе вас может заинтересовать специфичная для GNU функция getline или даже libreadline (если размещение вашего кода под лицензией GPL не является для вас проблемой).

person Wintermute    schedule 07.05.2015

Как обычно, дело можно было решить с помощью strace.

К сожалению, код слишком неправильный и слишком длинный, чтобы я мог написать исчерпывающий комментарий.

meh.c:99:13: предупреждение: неявное объявление функции 'cd' недопустимо в C99 [-Wimplicit-function-declaration] cd(tok); ^ meh.c:80:11: предупреждение: неиспользуемая переменная 'args' [-Wunused-variable] char *args[80]; ^ meh.c:132:11: предупреждение: неиспользуемая переменная 'token' [-Wunused-variable] char *token; ^ Создано 3 предупреждения.

Что случилось с этим?

#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);
        }

Если вам нужно проверять и извлекать fds, как это, скорее всего, вы уже делаете что-то не так. Подумайте, что произойдет, если «out» равен 0. На этом уровне просто убедитесь, что в вашей оболочке всегда открыты 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 переменных */]) = -1 ENOENT (Нет такого файла или каталога)

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

Неправильно. Вы предполагаете, что q не длиннее p.

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

Есть лучшие способы, чем посимвольное сравнение.

    return 0;
}

Это вообще к чему?

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

путь и пт? Мужчина. Рассмотрим «orig_path» или что-то в этом роде. Варианты одного /слова/ выглядят как опечатка, и на самом деле вы легко можете случайно ввести его неправильно. 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;
}
person employee of the month    schedule 07.05.2015