【问题标题】:C stdin corrupted from file redirection?C标准输入因文件重定向而损坏?
【发布时间】:2019-10-01 01:38:15
【问题描述】:

我正在做一个任务,其中包括编写一个使用 forks 和 execvp 执行其他程序的迷你 shell。当一切似乎正常时,我尝试在 bash 上使用 ./myshell < file.txt 直接将命令列表从文件传递到我的 shell,并获得了一个永远不会完成执行的无限*流。

我不确定是什么原因造成的,所以我使用gdb 对其进行了调试。令我惊讶的是,当到达文件的最后一行时,从 stdin 读取的行中添加了一个附加字符。此外,之前执行的更多行将返回到提要并重新执行。这是输入到 shell 的文件:

-trimmed for compactness-
chdir file
chdir ../
touch hello.txt index.html app.py
ls
rm hello.txt
ls -1
wc app.py
cat index.html
pwd
history
touch .hidden.txt
ls -a
history
echo preparing to exit
cd ../../
ip route show
ls -R
rm -rf path
invalid command
history

注意invalid command 之后是 history/ 然后下一行是 touch hello.txt index.html app.py 等等。我尝试了多种方法来调试这个问题,将我的 readLine 函数放入一个单独的文件并单独测试它,但程序在读取最后一行后正确终止。我还在 MacOS 上编译了我的 shell,令我惊讶的是,这个问题也没有发生。作为参考,该错误发生在运行 Ubuntu 18.04.3 LTS 的系统上。

我完全不知道为什么会这样。我假设这可能与我的标准输入是由其自身的分叉副本编写的有关,但我真的不确定。一些见解将不胜感激。

*不确定它是否真的是无限的

编辑 1:这是我的代码的一部分(抱歉,我无法在不消除问题的情况下进一步减小它的大小,因为我不知道是什么原因造成的)

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <signal.h>
#include <fcntl.h>


char* get_a_line();
char * extract_arguments(char** line, char delimiter);
int my_system(char* line);

// buffer size to read stdin
size_t BUFFER_SIZE = 256;

// string node for tailq
struct string_node {
    char* value;
    TAILQ_ENTRY(string_node) entries; 
};

// macro to init node struct for string
TAILQ_HEAD(str_list, string_node);
struct str_list history;


int main(int argc, char** argv) {

    int user_input = isatty(0);

    while (1) {

        if (user_input) {
            printf("$ ");
        }
        char* line = get_a_line();
        if (feof(stdin)) {
            exit(0);
        }
        if (strlen(line) > 0) {
            my_system(line);
        } 
        // won't free since line is used in the queue to display `history`
        // free(line);
    }

    return 0;

}

char* get_a_line() {

    char * buffer = (char*)malloc(BUFFER_SIZE * sizeof(char));
    size_t len = getline(&buffer, &BUFFER_SIZE, stdin);
    // transform `\n' to '\0' to terminate string
    if (len != -1 && buffer[len-1] == '\n') {
        buffer[len-1] = '\0';
    }

    return buffer;
}

int parse(char** line, char*** parsed) {

    // init string list to contain arguments
    struct str_list strings_list;
    TAILQ_INIT(&strings_list);

    struct string_node *tmp_node;

    // number of argument parts
    int count = 0 ;
    char * s;
    while((s = extract_arguments(line, ' ')) != NULL) {

        tmp_node = malloc(sizeof(struct string_node));
        tmp_node->value = s;
        TAILQ_INSERT_TAIL(&strings_list, tmp_node, entries);

        count++;
    }

    // save arguments into array of strings( array of char array)
    char ** arguments = malloc (sizeof(char**) * (count+1));

    int i=0;
    while ((tmp_node = TAILQ_FIRST(&strings_list))) {
            arguments[i++] = tmp_node->value;
            TAILQ_REMOVE(&strings_list, tmp_node, entries);
            free(tmp_node);
    }

    // terminate array
    arguments[count] = NULL;

    // check the type of termination
    *parsed =  arguments;
    if (**line == '|') {
        (*line) += 1;
        return 1;
    }

    return 0;

}


// extract string from the start of the *line until length by allocating through malloc
char * extract_string(char ** line, int length) {
    char * str = NULL;
    if (length > 0) {
        str = malloc((length+1) * sizeof(char));
        strncpy(str, *line, length);
        str[length] = '\0';
        *line += (length);
    }
    return str;
}

/*
    Merges two string str1 and str2 by calloc on str1 and freeing str2
    (prob should not free str2 in this but w/e)
*/
char * strcat_alloc(char * str1, char * str2) {

    if (str1 == NULL) {
        return str2;
    } 

    if (str2 == NULL) {
        return str1;
    }
    size_t length1 = strlen(str1) ;
    size_t length2 =  strlen(str2);
    str1 = realloc(str1, length1 + length2+1);
    strcpy(str1+length1, str2);
    str1[length1+length2] = '\0';
    free(str2);
    return str1;
}


/*
    Extract a single argument of the line, terminated by the delimiter
    Basic quotes and escaping implemented in order to support multiword arguments
*/
char * extract_arguments(char** line, char delimiter) {
    // remove trailing spaces
    while (**line == ' ') {
        (*line)+=1;
    }
    int right = 0;
    char * str_p = NULL;
    while ((*line)[right] != delimiter && 
            (*line)[right] != EOF && 
            (*line)[right] != '\0' &&
            (*line)[right] != '|')
    {
        if ((*line)[right] == '\\'){
            str_p = extract_string(line, right);
            // the escaped character is one after '\'
            *line+=1;
            char *c = malloc(sizeof(char));
            *c = **line;
            *line +=1;
            return strcat_alloc(strcat_alloc(str_p, c), extract_arguments(line, delimiter));
        }


        if ((*line)[right] == '\''){
            str_p = extract_string(line, right);
            *line+=1;
            char * str_p2 =  extract_arguments(line, '\'');
            return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));

        } else if ((*line)[right] == '\"') {
            str_p = extract_string(line, right);
            *line+=1;
            char * str_p2 = extract_arguments(line, '\"');
            return strcat_alloc(strcat_alloc(str_p, str_p2), extract_arguments(line, ' '));
        }
        right++;
    }

    str_p = extract_string(line, right);

    if (**line == delimiter) {
        *line+=1;
    }

    return str_p;

}


/*
    Execute command defined by **args dending on the flag (pipe or normal execution)
*/
int execute(char **args, int flag, int * wait_count) {

        pid_t pid = fork();
        if (pid == 0) {
            // exit to prevent multiple instance of shell
            exit(0);

        } else {
            // PARENT
            wait(NULL);

        }
        return 0;
}


int my_system(char* line) {

    char** args;

    int flag = 0;

    // wait count keeps tracks of the amount of fork to wait for
    // max 2 in this case 
    int wait_count= 0;
    while(*line != '\0') {
        flag = parse(&line, &args);

        if (*args == NULL) {
            return 0;
        }
        // exit can't be in fork nor chdir
        if (strcasecmp(args[0], "exit") == 0) {
            exit(0);
        } else if (strcasecmp(args[0], "chdir") == 0 || strcasecmp(args[0], "cd") == 0) {
            if(chdir(args[1]) < 0) {
                printf("chdir: change directory failed\n");
            }
            return 0;
        } 
        execute(args, flag, &wait_count);
    }

    return 0;
}

【问题讨论】:

  • get_a_line() 到达EOF 时应该做什么?
  • 使用getline()时不需要自己分配缓冲区。将buffer 初始化为NULLgetline() 将分配空间。
  • getline() 在收到错误或 EOF 时返回 -1。您需要检查并以某种方式将其报告给您的调用者(可能通过返回一个空指针)。
  • 当它返回-1 时,您正在访问buffer[-2],这会导致未定义的行为。
  • 您需要创建一个minimal reproducible example。我们不需要查看您的整个外壳,但我们确实需要更多。从完整的程序开始,去掉所有与这个特定错误不直接相关的代码。您应该能够将其简化为最多几十行代码。除了演示无限循环问题之外,它不需要做任何有用的事情。

标签: c fork stdin stdio corruption


【解决方案1】:

一位朋友帮我解决了这个问题。问题源于在子叉中使用exit。显然,stdin 在后来被父级读取时以某种方式刷新了两次并损坏了。要修复它,只需将其更改为 _exit

【讨论】:

    猜你喜欢
    • 2021-04-08
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-11
    • 1970-01-01
    • 2019-09-11
    • 1970-01-01
    相关资源
    最近更新 更多