summaryrefslogtreecommitdiffstats
path: root/02_exercise/shell.c
blob: 7032a992b7d83c5827e8591c65ec897feed31428 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/wait.h>

#include "array.h"
#include "prompt_utils.h"

int parse_line(char const *line, char ***parts, size_t *part_count);

// returns the return code of the executed program
int exec_command(const char *path, char *const argv[], bool background);

int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0);

    char const *const original_wd = get_current_dir_name();
    char const *prompt = relative_path(original_wd, original_wd);
    bool done = false;
    while (!done) {
        bool bg = false;
        char *line = NULL;
        size_t cap = 0;
        __ssize_t length;

        printf("%s > ", prompt);
        if ((length = getline(&line, &cap, stdin)) < 0) {
            fprintf(stderr, "Failed to read from STDIN");
            exit(-1);
        }

        if (strspn(line, " \n\t") == strlen(line)) {
            continue;
        }

        line[length - 1] = '\0'; // cut the line feed

        char **arguments = NULL;
        size_t argument_count;
        parse_line(line, &arguments, &argument_count);

        if (argument_count <= 0)
            goto clean;

        char *command = arguments[0];
        char *last_arg = arguments[argument_count - 2];

        if (strcmp(last_arg, "&") == 0) {
            bg = true;
            arrayRelease(arguments[argument_count - 2]);
            arguments[argument_count - 2] = NULL;
        }

        if (strcmp(command, "cd") == 0) {
            if (arrayLen(arguments) != 3) {
                fprintf(stderr, "usage: cd <path>");
                goto clean;
            }

            int ret = chdir(arguments[1]);
            if (ret)
                printf("[%i] ", ret);

            free((void *)prompt);
            char const *current_wd = get_current_dir_name();
            prompt = relative_path(original_wd, current_wd);
            free((void *)current_wd);
        } else if (strcmp(command, "exit") == 0) {
            done = true;
        } else {
            int ret = exec_command(command, arguments, bg);
            if (ret)
                printf("[%i] ", ret);
        }

        clean:
        free((void *)line);
        while (arrayLen(arguments) > 0) {
            char *tmp = arrayPop(arguments);
            if (tmp)
                arrayRelease(tmp);
        }
        arrayRelease(arguments);
    }

    free((void *)original_wd);
    free((void *)prompt);
}

int parse_line(char const *line, char ***parts, size_t *part_count) {
    char *part;
    char **local_parts;

    if (arrayInit(part) != 0 || arrayInit(local_parts) != 0) {
        fprintf(stderr, "Failed to prepare new part / parts array whilst parsing line");
        return -1;
    }

    for (size_t i = 0; i < strlen(line) + 1; ++i) {
        char c = line[i];
        if (c == ' ' || c == '\0') {
            if (arrayLen(part) == 0)
                continue;
            arrayPush(part) = '\0';
            arrayPush(local_parts) = part;
            if (c == '\0') {
                arrayPush(local_parts) = NULL;
                break;
            } else {
                arrayInit(part);
            }
        } else {
            arrayPush(part) = c;
        }
    }

    *part_count = arrayLen(local_parts);
    *parts = local_parts;

    return 0;
}

int exec_command(const char *path, char *const argv[], bool background) {
    int  pid;
    int  pipefd[2];
    int  status;
    char buf[512];

    pipe(pipefd);
    if ((pid = fork()) == 0) {
        close(pipefd[0]);
        dup2(pipefd[1], 1);
        close(pipefd[1]);
        execvp(path, argv);
        fprintf(stderr, "could not execute \"%s\"\n", path);
        fflush(stderr);
        exit(-1);
    }

    if (pid < 0) {
        fprintf(stderr, "no fork\n");
        exit(-2);
    }

    close(pipefd[1]);

    int new_pid;
    if (background) {
        new_pid = fork();
    }

    if (!background || new_pid == 0) {
        if (background)
            printf("[%i]\n", pid);

        __ssize_t length;
        while ((length = read(pipefd[0], buf, 10)) > 0) {
            buf[length] = '\0';
            printf("%s", buf);
        }

        waitpid(pid, &status, 0);

        if (background) {
            printf("[%i] TERMINATED\n", pid);
            printf("[%i] EXIT STATUS: %i\n", pid, status);
            exit(0);
        }
    }

    close(pipefd[0]);

    return WEXITSTATUS(status);
}