├── .clang-tidy
├── .gitignore
├── Makefile
├── README.md
├── UNLICENSE
├── compile_flags.txt
└── src
├── ast.c
├── builtins.c
├── eval.c
├── exec.c
├── include
├── ast.h
├── builtins.h
├── eval.h
├── exec.h
├── lexer.h
├── parser.h
├── psh.h
└── utils.h
├── lexer.c
├── parser.c
├── psh.c
└── utils.c
/.clang-tidy:
--------------------------------------------------------------------------------
1 | Checks: "
2 | bugprone-*,
3 | -bugprone-easily-swappable-parameters,
4 | misc-*,
5 | -misc-no-recursion,
6 | performance-*,
7 | portability-*,
8 | readability-*,
9 | -readability-braces-around-statements,
10 | -readability-identifier-length,
11 | clang-analyzer-*,
12 | modernize-*,
13 | llvm-*,"
14 |
15 | CheckOptions:
16 | - key: readability-identifier-naming.EnumCase
17 | value: lower_case
18 | - key: readability-identifier-naming.UnionCase
19 | value: lower_case
20 |
21 | - key: readability-identifier-naming.FunctionCase
22 | value: lower_case
23 | - key: readability-identifier-naming.ParameterCase
24 | value: lower_case
25 | - key: readability-identifier-naming.VariableCase
26 | value: lower_case
27 |
28 | - key: readability-identifier-naming.MacroDefinitionCase
29 | value: UPPER_CASE
30 | - key: readability-identifier-naming.GlobalVariableCase
31 | value: lower_case
32 |
33 | - key: readability-function-cognitive-complexity.IgnoreMacros
34 | value: true
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Object files
5 | *.o
6 | *.ko
7 | *.obj
8 | *.elf
9 |
10 | # Linker output
11 | *.ilk
12 | *.map
13 | *.exp
14 |
15 | # Precompiled Headers
16 | *.gch
17 | *.pch
18 |
19 | # Libraries
20 | *.lib
21 | *.a
22 | *.la
23 | *.lo
24 |
25 | # Shared objects (inc. Windows DLLs)
26 | *.dll
27 | *.so
28 | *.so.*
29 | *.dylib
30 |
31 | # Executables
32 | *.exe
33 | *.out
34 | *.app
35 | *.i*86
36 | *.x86_64
37 | *.hex
38 |
39 | # Debug files
40 | *.dSYM/
41 | *.su
42 | *.idb
43 | *.pdb
44 |
45 | # Kernel Module Compile Results
46 | *.mod*
47 | *.cmd
48 | .tmp_versions/
49 | modules.order
50 | Module.symvers
51 | Mkfile.old
52 | dkms.conf
53 | build/
54 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | CC=clang
2 | TARGET_EXEC := psh
3 | BUILD_DIR := ./build
4 | SRC_DIRS := ./src
5 | SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
6 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
7 | DEPS := $(OBJS:.o=.d)
8 | INC_DIRS := $(shell find $(SRC_DIRS) -type d)
9 | INC_FLAGS := $(addprefix -I,$(INC_DIRS))
10 | CFLAGS := $(INC_FLAGS) -MMD -MP -Wall -Werror -g -Wextra -pedantic -std=c99
11 | LDFLAGS := $(shell pkg-config --cflags --libs readline)
12 |
13 | $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
14 | $(CC) $(OBJS) -o $@ $(LDFLAGS)
15 |
16 | $(BUILD_DIR)/%.c.o: %.c
17 | mkdir -p $(dir $@)
18 | $(CC) $(CFLAGS) $(CFLAGS) -c $< -o $@
19 |
20 | .PHONY: clean
21 | clean:
22 | rm -r $(BUILD_DIR)
23 |
24 | -include $(DEPS)
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | psh
6 |
7 |
8 | A small and minimal shell
9 |
10 | ### Preview🙈:
11 | [](https://asciinema.org/a/ojkpeKFyhRJgohB4LNHK3sJ4w)
12 |
13 | ### Features:
14 | 1. Basic commands
15 | 2. Wildcards
16 | 3. Redirections
17 | 4. Pipes
18 | 5. Background jobs
19 | 6. Seperate commands with ';'
20 | 7. Signal handling
21 | 8. Cursor movement, tab, history with libreadline
22 |
23 | ### Motivation:
24 | I did this mostly for the sake of learning and fun! I always wanted to have my own small shell that I can run everywhere and I can add custom features to.
25 | I am aiming to implement some other features in this later like aliasing, environmental variables and some basic if/else logics :). This project can also be seen as learning resource for people who want to learn how to make their own shell(I might write an article on this topic).
26 |
27 |
28 |
29 | ### How to install⬇️:
30 |
31 | #### Arch Linux
32 | [psh](https://aur.archlinux.org/packages/psh) is available on AUR. You can install it using an AUR helper (e.g. paru):
33 | ```shell
34 | paru -S psh
35 | ```
36 |
37 | #### Manually
38 | 1. Clone this repo.
39 | ```shell
40 | git clone https://github.com/proh14/psh.git
41 | cd psh
42 | ```
43 | 2. Run `make` inside this repo's main directory.
44 | ```shell
45 | make
46 | ```
47 |
48 | 3. go to build directory
49 | ```shell
50 | cd build
51 | ```
52 | 4. run psh!
53 | ```shell
54 | ./psh
55 | ```
56 |
57 | OR to install you may run the command
58 | ```shell
59 | make install
60 | ```
61 | then restart your shell and run
62 | ```shell
63 | psh
64 | ```
65 |
66 | ### Contributions💖:
67 | If your willing to contribute, I must say thank you :)
68 |
69 | ### Thank you Stargazers⭐:
70 | [](https://github.com/proh14/psh/stargazers)
71 |
72 | ### Thank you Forkers🍴:
73 | [](https://github.com/proh14/psh/network/members)
74 |
75 | ### Inspired by:
76 | 1. [mysh](https://github.com/Swoorup/mysh)
77 | 2. [ash(netbsd)](https://github.com/NetBSD/src/tree/trunk/bin/sh)
78 |
79 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/compile_flags.txt:
--------------------------------------------------------------------------------
1 | -Wall
2 | -g
3 | -Wextra
4 | -Werror
5 | -pedantic
6 | -std=c99
7 | -I./src/include
8 | -MMD
9 | -MP
10 |
--------------------------------------------------------------------------------
/src/ast.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | ast_t *ast_create_node(void) {
6 | ast_t *node = malloc(sizeof(ast_t));
7 | node->left = NULL;
8 | node->right = NULL;
9 | node->token = NULL;
10 | return node;
11 | }
12 |
13 | void ast_destroy_node(ast_t *node) {
14 | if (node == NULL) {
15 | return;
16 | }
17 | ast_destroy_node(node->left);
18 | ast_destroy_node(node->right);
19 | node->left = NULL;
20 | node->right = NULL;
21 | node->token = NULL;
22 |
23 | free(node);
24 | node = NULL;
25 | }
26 |
27 | static const char *ast_type_to_string(enum astype type) {
28 | switch (type) {
29 | case AST_COMMAND_TAIL:
30 | return "AST_COMMAND_TAIL";
31 | break;
32 | case AST_BACKGROUND:
33 | return "AST_BACKGROUND";
34 | break;
35 | case AST_REDIRECT_IN:
36 | return "AST_REDIRECT_IN";
37 | break;
38 | case AST_REDIRECT_OUT:
39 | return "AST_REDIRECT_OUT";
40 | break;
41 | case AST_PIPE:
42 | return "AST_PIPE";
43 | break;
44 | default:
45 | return "UNKNOWN";
46 | break;
47 | }
48 | }
49 |
50 | void ast_print(ast_t *node, int level) {
51 | if (node == NULL) {
52 | return;
53 | }
54 |
55 | for (int i = 0; i < level; i++) {
56 | printf(" ");
57 | }
58 |
59 | if (node->token == NULL) {
60 | printf("%s\n", ast_type_to_string(node->type));
61 | } else {
62 | printf("%s(%s)\n", node->token->lexeme, ast_type_to_string(node->type));
63 | }
64 |
65 | ast_print(node->right, level + 1);
66 | ast_print(node->left, level + 1);
67 | }
68 |
--------------------------------------------------------------------------------
/src/builtins.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | static void builtin_exit(int argc, char **argv) {
9 | (void)argc;
10 | (void)argv;
11 | exit(0);
12 | }
13 |
14 | static void builtin_cd(int argc, char **argv) {
15 | if (argc == 1) {
16 | if (chdir(getenv("HOME")) == -1) {
17 | perror("cd");
18 | }
19 | } else if (argc == 2) {
20 | if (chdir(argv[1]) == -1) {
21 | perror("cd");
22 | }
23 | } else {
24 | eprintf("cd: too many arguments\n");
25 | }
26 | }
27 |
28 | static void builtin_pwd(int argc, char **argv) {
29 | (void)argv;
30 | if (argc > 1) {
31 | eprintf("pwd: too many arguments\n");
32 | return;
33 | }
34 | char *cwd = getcwd(NULL, 0);
35 | printf("%s\n", cwd);
36 | free(cwd);
37 | }
38 |
39 | static builtin_t builtins[] = {
40 | {"exit", &builtin_exit},
41 | {"cd", &builtin_cd},
42 | {"pwd", &builtin_pwd},
43 | };
44 |
45 | builtin_t *builtin_find_by_name(char *name) {
46 | for (size_t i = 0; i < sizeof(builtins) / sizeof(builtin_t); i++) {
47 | if (strcmp(builtins[i].name, name) == 0) {
48 | return &builtins[i];
49 | }
50 | }
51 | return NULL;
52 | }
53 |
54 | void builtin_execute(builtin_t *builtin, int argc, char **argv) {
55 | if (builtin != NULL) {
56 | builtin->func(argc, argv);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/eval.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | static void eval_full(ast_t *ast);
12 |
13 | static int input_fd = -1;
14 | static int output_fd = -1;
15 | static int bg = 0;
16 |
17 | static void eval_commandtail(ast_t *ast) {
18 | command_t *cmd = command_create();
19 | command_argv_from_ast(cmd, ast);
20 | cmd->input_fd = input_fd;
21 | cmd->output_fd = output_fd;
22 | cmd->bg = bg;
23 | command_execute(cmd);
24 | command_destroy(cmd);
25 | }
26 | static void eval_sequence(ast_t *ast) {
27 | if (ast->type == AST_BACKGROUND) {
28 | bg = 1;
29 | }
30 |
31 | eval_full(ast->left);
32 | eval_full(ast->right);
33 |
34 | if (ast->type == AST_BACKGROUND) {
35 | bg = 0;
36 | }
37 | }
38 | static void eval_redirection(ast_t *ast) {
39 | int fd;
40 | if (ast->type == AST_REDIRECT_IN) {
41 | fd = open(ast->token->lexeme, O_RDONLY);
42 | if (fd == -1) {
43 | psherror("open");
44 | return;
45 | }
46 | input_fd = fd;
47 | } else {
48 | fd = open(ast->token->lexeme, O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE);
49 | if (fd == -1) {
50 | psherror("open");
51 | return;
52 | }
53 | output_fd = fd;
54 | }
55 |
56 | eval_full(ast->right);
57 | close(fd);
58 | input_fd = -1;
59 | output_fd = -1;
60 | }
61 | static void eval_pipeline(ast_t *ast) {
62 | int pipefd[2];
63 | if (pipe(pipefd) == -1) {
64 | psherror("pipe");
65 | return;
66 | }
67 |
68 | input_fd = -1;
69 | output_fd = pipefd[1];
70 | eval_full(ast->left);
71 | close(pipefd[1]);
72 |
73 | ast = ast->right;
74 |
75 | input_fd = pipefd[0];
76 |
77 | while (ast->type == AST_PIPE) {
78 | if (pipe(pipefd) == -1) {
79 | psherror("pipe");
80 | return;
81 | }
82 | output_fd = pipefd[1];
83 | eval_full(ast->left);
84 | close(pipefd[1]);
85 | close(input_fd);
86 | input_fd = pipefd[0];
87 | ast = ast->right;
88 | }
89 | output_fd = -1;
90 | input_fd = pipefd[0];
91 | eval_full(ast);
92 | close(pipefd[0]);
93 | }
94 |
95 | static void eval_full(ast_t *ast) {
96 | if (ast == NULL) {
97 | return;
98 | }
99 | switch (ast->type) {
100 | case AST_PIPE:
101 | eval_pipeline(ast);
102 | break;
103 | case AST_REDIRECT_IN:
104 | case AST_REDIRECT_OUT:
105 | eval_redirection(ast);
106 | break;
107 | case AST_SEQUENCE:
108 | case AST_BACKGROUND:
109 | eval_sequence(ast);
110 | break;
111 | case AST_COMMAND_TAIL:
112 | eval_commandtail(ast);
113 | break;
114 | default:
115 | break;
116 | }
117 | }
118 |
119 | void evaluate(parser_t *parser) { eval_full(parser->ast); }
120 |
--------------------------------------------------------------------------------
/src/exec.c:
--------------------------------------------------------------------------------
1 | #define _DEFAULT_SOURCE
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 |
18 | static void command_reset(command_t *cmd) {
19 | cmd->argc = 0;
20 | cmd->argv = NULL;
21 | cmd->input_fd = -1;
22 | cmd->output_fd = -1;
23 | cmd->bg = 0;
24 | }
25 |
26 | command_t *command_create(void) {
27 | command_t *cmd = xmalloc(sizeof(command_t));
28 | command_reset(cmd);
29 | return cmd;
30 | }
31 |
32 | void command_argv_from_ast(command_t *cmd, ast_t *ast) {
33 |
34 | // Prepare argv
35 | for (ast_t *n = ast; n != NULL; n = n->right) {
36 | cmd->argc++;
37 | }
38 | cmd->argv = xmalloc(sizeof(char *) * (cmd->argc + 1));
39 |
40 | for (int i = 0; i < cmd->argc; i++) {
41 | cmd->argv[i] = xstrdup(ast->token->lexeme);
42 | ast = ast->right;
43 | }
44 | cmd->argv[cmd->argc] = NULL;
45 | }
46 |
47 | static int xopen(const char *path, int flags, mode_t mode) {
48 | int fd = open(path, flags, mode);
49 | if (fd == -1) {
50 | die("open");
51 | }
52 | return fd;
53 | }
54 |
55 | static void untrap_signals(void) {
56 | signal(SIGINT, SIG_DFL);
57 | signal(SIGQUIT, SIG_DFL);
58 | signal(SIGTSTP, SIG_DFL);
59 | signal(SIGTTIN, SIG_DFL);
60 | signal(SIGTTOU, SIG_DFL);
61 | }
62 |
63 | static void background_signal_handler(int signo) {
64 | if (signo == SIGCHLD) {
65 | int status;
66 | pid_t pid;
67 | while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
68 | if (WIFEXITED(status) || WIFSIGNALED(status)) {
69 | printf("[BACKGROUND] Process %d exited\n", pid);
70 | rl_on_new_line();
71 | rl_refresh_line(0, 0);
72 | }
73 | }
74 | }
75 | }
76 |
77 | void command_execute(command_t *cmd) {
78 | if (cmd->argc == 0) {
79 | return;
80 | }
81 |
82 | // Builtins
83 | builtin_t *builtin = builtin_find_by_name(cmd->argv[0]);
84 | if (builtin) {
85 | builtin_execute(builtin, cmd->argc, cmd->argv);
86 | return;
87 | }
88 |
89 | // Fork
90 | pid_t pid = fork();
91 | if (pid == -1) {
92 | psherror("fork");
93 | return;
94 | }
95 |
96 | // Child
97 | if (pid == 0) {
98 | untrap_signals();
99 |
100 | if (cmd->bg) {
101 | printf("\n[BACKGROUND] started backgroundjob: %s\n", cmd->argv[0]);
102 | setpgid(0, 0);
103 |
104 | int fd = xopen("/dev/null", O_RDONLY, 0);
105 | dup2(fd, STDIN_FILENO);
106 | close(fd);
107 | }
108 |
109 | // Pipe
110 | if (cmd->input_fd != -1) {
111 | dup2(cmd->input_fd, STDIN_FILENO);
112 | close(cmd->input_fd);
113 | }
114 | if (cmd->output_fd != -1) {
115 | dup2(cmd->output_fd, STDOUT_FILENO);
116 | close(cmd->output_fd);
117 | }
118 |
119 | // Execute
120 | if (execvp(cmd->argv[0], cmd->argv) == -1) {
121 | die("execvp");
122 | }
123 | }
124 | // Wait for child
125 | if (!cmd->bg) {
126 | int status;
127 | do {
128 | waitpid(pid, &status, WUNTRACED);
129 | } while (!WIFEXITED(status) && !WIFSIGNALED(status));
130 | return;
131 | }
132 |
133 | // Background process
134 | struct sigaction sa;
135 | sa.sa_handler = background_signal_handler;
136 | sigemptyset(&sa.sa_mask);
137 | sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; // Restart interrupted syscalls,
138 | // don't handle stopped children
139 | sigaction(SIGCHLD, &sa, NULL);
140 | }
141 |
142 | void command_destroy(command_t *cmd) {
143 | if (cmd->argv) {
144 | for (int i = 0; i < cmd->argc; i++) {
145 | free(cmd->argv[i]);
146 | }
147 | free(cmd->argv);
148 | }
149 | command_reset(cmd);
150 | free(cmd);
151 | cmd = NULL;
152 | }
153 |
--------------------------------------------------------------------------------
/src/include/ast.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _AST_H_
3 | #define _AST_H_
4 |
5 | #include
6 |
7 | enum astype {
8 | AST_COMMAND_TAIL,
9 | AST_BACKGROUND,
10 | AST_SEQUENCE,
11 | AST_REDIRECT_IN,
12 | AST_PIPE,
13 | AST_REDIRECT_OUT,
14 | };
15 |
16 | typedef struct astree {
17 | enum astype type;
18 | token_t *token;
19 | struct astree *left;
20 | struct astree *right;
21 | } ast_t;
22 |
23 | ast_t *ast_create_node(void);
24 | void ast_destroy_node(ast_t *node);
25 | void ast_print(ast_t *node, int level);
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/src/include/builtins.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _BUILTINS_H_
3 | #define _BIULTINS_H_
4 |
5 | typedef struct builtin {
6 | char *name;
7 | void (*func)(int argc, char **argv);
8 | } builtin_t;
9 |
10 | builtin_t *builtin_find_by_name(char *name);
11 | void builtin_execute(builtin_t *builtin, int argc, char **argv);
12 |
13 | #endif
14 |
--------------------------------------------------------------------------------
/src/include/eval.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _EVAL_H_
3 | #define _EVAL_H_
4 |
5 | #include
6 |
7 | void evaluate(parser_t *parser);
8 |
9 | #endif // ! _EVAL_H_
10 |
--------------------------------------------------------------------------------
/src/include/exec.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _EXEC_H_
3 | #define _EXEC_H_
4 |
5 | #include
6 |
7 | typedef struct command {
8 | int argc;
9 | char **argv;
10 | int input_fd;
11 | int output_fd;
12 | int bg;
13 | } command_t;
14 |
15 | command_t *command_create(void);
16 | void command_execute(command_t *cmd);
17 | void command_argv_from_ast(command_t *cmd, ast_t *ast);
18 | void command_destroy(command_t *cmd);
19 |
20 | #endif // ! _EXEC_H_
21 |
--------------------------------------------------------------------------------
/src/include/lexer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _LEXER_H_
3 | #define _LEXER_H_
4 |
5 | enum token_type {
6 | TOKEN_ARGUMENT,
7 | TOKEN_PIPE,
8 | TOKEN_AMPERSAND,
9 | TOKEN_GREATER,
10 | TOKEN_LESSER,
11 | TOKEN_SEMICOLON,
12 | };
13 |
14 | enum state {
15 | IN_GENERAL,
16 | IN_QUOTE,
17 | IN_DQUOTE,
18 | IN_ESCAPESEQUENCE,
19 | };
20 |
21 | typedef struct token {
22 | char *lexeme;
23 | enum token_type type;
24 | struct token *next;
25 | } token_t;
26 |
27 | typedef struct lexical_error {
28 | char *msg;
29 | int character;
30 | } lexical_error_t;
31 |
32 | typedef struct lexer {
33 | token_t *root;
34 | lexical_error_t *error;
35 | } lexer_t;
36 |
37 | lexer_t *lexer_create(void);
38 | token_t *lex(lexer_t *l, char *input);
39 | void lexer_destroy(lexer_t *l);
40 | char *token_to_string(token_t *t);
41 |
42 | #endif
43 |
--------------------------------------------------------------------------------
/src/include/parser.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _PARSER_H_
3 | #define _PARSER_H_
4 |
5 | #include
6 | #include
7 |
8 | typedef struct parsing_error {
9 | char *msg;
10 | char *bad_tok;
11 | } parsing_error_t;
12 |
13 | typedef struct parser {
14 | ast_t *ast;
15 | parsing_error_t *err;
16 | } parser_t;
17 |
18 | parser_t *parser_create(void);
19 | ast_t *parse(parser_t *parser, lexer_t *l);
20 | void parser_destroy(parser_t *p);
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/src/include/psh.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _PSH_H_
3 | #define _PSH_H_
4 |
5 | #define PROMPT "psh> "
6 |
7 | #define PROGRAM_NAME "psh"
8 | #define PROGRAM_VERSION "1.0"
9 | #define AUTHOR "proh14"
10 |
11 | #endif // ! _PSH_H_
12 |
--------------------------------------------------------------------------------
/src/include/utils.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _UTILS_H_
3 | #define _UTILS_H_
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
10 |
11 | void *xmalloc(size_t size);
12 | void *xrealloc(void *ptr, size_t size);
13 | char *xstrdup(const char *s);
14 |
15 | void eprintf(const char *fmt, ...);
16 | void psherror(const char *msg);
17 | void die(const char *msg);
18 |
19 | #endif // !_UTILS_H_
20 |
--------------------------------------------------------------------------------
/src/lexer.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | lexer_t *lexer_create(void) {
8 | lexer_t *l = xmalloc(sizeof(lexer_t));
9 | l->root = NULL;
10 | l->error = NULL;
11 | return l;
12 | }
13 |
14 | static token_t *add_token(token_t *ptok, char *lexeme, enum token_type type) {
15 | token_t *t = xmalloc(sizeof(token_t));
16 | if (lexeme) {
17 | t->lexeme = xstrdup(lexeme);
18 | } else {
19 | t->lexeme = NULL;
20 | }
21 | t->type = type;
22 | t->next = NULL;
23 | if (ptok) {
24 | ptok->next = t;
25 | }
26 | return t;
27 | }
28 |
29 | static enum token_type get_type(char ch) {
30 | switch (ch) {
31 | case '|':
32 | return TOKEN_PIPE;
33 | break;
34 | case '&':
35 | return TOKEN_AMPERSAND;
36 | break;
37 | case '<':
38 | return TOKEN_LESSER;
39 | break;
40 | case '>':
41 | return TOKEN_GREATER;
42 | break;
43 | case ';':
44 | return TOKEN_SEMICOLON;
45 | break;
46 | default:
47 | return TOKEN_ARGUMENT;
48 | break;
49 | }
50 | }
51 |
52 | char *token_to_string(token_t *t) {
53 | switch (t->type) {
54 | case TOKEN_PIPE:
55 | return "|";
56 | break;
57 | case TOKEN_AMPERSAND:
58 | return "&";
59 | break;
60 | case TOKEN_LESSER:
61 | return "<";
62 | break;
63 | case TOKEN_GREATER:
64 | return ">";
65 | break;
66 | case TOKEN_SEMICOLON:
67 | return ";";
68 | break;
69 | case TOKEN_ARGUMENT:
70 | return t->lexeme;
71 | break;
72 | default:
73 | return NULL;
74 | break;
75 | }
76 | }
77 |
78 | static int advace_string(const char *from, char *to) {
79 | char type = *from;
80 | int i = 1;
81 | while (from[i] != '\0') {
82 | if (from[i] == type) {
83 | return i + 1;
84 | }
85 | to[i - 1] = from[i];
86 | i++;
87 | }
88 |
89 | return 0;
90 | }
91 |
92 | static void add_error(lexer_t *l, char *message, int character) {
93 | if (l->error) {
94 | free(l->error);
95 | }
96 | l->error = xmalloc(sizeof(lexical_error_t));
97 | l->error->msg = message;
98 | l->error->character = character;
99 | }
100 |
101 | static token_t *add_lexeme(lexer_t *l, token_t *prev, char *lexeme,
102 | int *lexeme_i) {
103 | if (*lexeme_i <= 0) {
104 | return prev;
105 | }
106 |
107 | lexeme[*lexeme_i] = '\0';
108 | *lexeme_i = 0;
109 |
110 | glob_t globbuf;
111 | glob(lexeme, GLOB_TILDE | GLOB_NOCHECK | GLOB_NOSORT, NULL, &globbuf);
112 |
113 | for (size_t i = 0; i < globbuf.gl_pathc; i++) {
114 | prev = add_token(prev, globbuf.gl_pathv[i], TOKEN_ARGUMENT);
115 | }
116 |
117 | if (!l->root) {
118 | l->root = prev;
119 | }
120 |
121 | globfree(&globbuf);
122 |
123 | return prev;
124 | }
125 |
126 | token_t *lex(lexer_t *l, char *input) {
127 | int len = (int)strlen(input) + 1;
128 | char *lexeme = xmalloc(len);
129 | token_t *t = NULL;
130 | int add = 0;
131 |
132 | int i = 0;
133 | int lexeme_i = 0;
134 | while (i < len - 1) {
135 | switch (input[i]) {
136 | case ' ':
137 | case '\t':
138 | case '\n':
139 | t = add_lexeme(l, t, lexeme, &lexeme_i);
140 | break;
141 | case '&':
142 | case '>':
143 | case '<':
144 | case ';':
145 | case '|':
146 | t = add_lexeme(l, t, lexeme, &lexeme_i);
147 | t = add_token(t, NULL, get_type(input[i]));
148 | break;
149 | case '\'':
150 | case '\"':
151 | add = advace_string(input + i, lexeme + lexeme_i);
152 | if (add == 0) {
153 | add_error(l, "lexical error: unmatched quote", i);
154 | return NULL;
155 | }
156 | lexeme_i += add - 2;
157 | i += add;
158 | break;
159 | case '\\':
160 | if (input[i + 1] == '\0') {
161 | add_error(l, "lexical error: escape sequence at end of input", i);
162 | return NULL;
163 | }
164 | lexeme[lexeme_i++] = input[i + 1];
165 | i++;
166 | break;
167 | default:
168 | lexeme[lexeme_i++] = input[i];
169 | break;
170 | }
171 | i++;
172 | }
173 |
174 | t = add_lexeme(l, t, lexeme, &lexeme_i);
175 | free(lexeme);
176 | return l->root;
177 | }
178 |
179 | void lexer_destroy(lexer_t *l) {
180 | token_t *t = l->root;
181 | while (t) {
182 | token_t *next = t->next;
183 | free(t->lexeme);
184 | free(t);
185 | t = next;
186 | }
187 | if (l->error) {
188 | free(l->error);
189 | }
190 | l->error = NULL;
191 |
192 | free(l);
193 | l = NULL;
194 | }
195 |
--------------------------------------------------------------------------------
/src/parser.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | /* Our tiny shell grammar :)
8 | *
9 | * ::= |
10 | * '&' |
11 | * '&' |
12 | * ';' |
13 | * ';'
14 | *
15 | * ::= | '|'
16 | *
17 | * ::= '>' |
18 | * '<' |
19 | *
20 | *
21 | * ::= |
22 | */
23 |
24 | static token_t *curtok = NULL;
25 |
26 | static token_t *next_token(void) {
27 | if (curtok->next != NULL) {
28 | curtok = curtok->next;
29 | return curtok;
30 | }
31 | return NULL;
32 | }
33 |
34 | static token_t *peek(void) { return curtok->next; }
35 |
36 | static int match_token(enum token_type type) { return curtok->type == type; }
37 |
38 | static int expect_token(enum token_type type) {
39 | token_t *t = peek();
40 | return t != NULL && t->type == type;
41 | }
42 |
43 | static ast_t *simple_command(void) {
44 | token_t *t = curtok;
45 | if (t->type != TOKEN_ARGUMENT) {
46 | return NULL;
47 | }
48 | ast_t *ast = ast_create_node();
49 | ast->type = AST_COMMAND_TAIL;
50 | ast->token = t;
51 |
52 | if (next_token() != NULL) {
53 | ast->right = simple_command();
54 | }
55 |
56 | return ast;
57 | }
58 |
59 | static inline ast_t *handle_redirection(enum astype type, ast_t *simple) {
60 | if (!expect_token(TOKEN_ARGUMENT)) {
61 | return NULL;
62 | }
63 | ast_t *ast = ast_create_node();
64 | ast->type = type;
65 | ast->right = simple;
66 | ast->token = next_token();
67 | next_token();
68 | return ast;
69 | }
70 |
71 | static ast_t *command(void) {
72 | ast_t *simple = simple_command();
73 |
74 | if (simple == NULL) {
75 | return NULL;
76 | }
77 |
78 | if (match_token(TOKEN_GREATER)) {
79 | // '>'
80 | ast_t *ast = handle_redirection(AST_REDIRECT_OUT, simple);
81 | if (ast == NULL) {
82 | ast_destroy_node(simple);
83 | }
84 | return ast;
85 | }
86 | if (match_token(TOKEN_LESSER)) {
87 | // '<'
88 | ast_t *ast = handle_redirection(AST_REDIRECT_IN, simple);
89 | if (ast == NULL) {
90 | ast_destroy_node(simple);
91 | }
92 | return ast;
93 | }
94 |
95 | //
96 | return simple;
97 | }
98 |
99 | static ast_t *pipe(void) {
100 | ast_t *left = command();
101 |
102 | if (left == NULL) {
103 | return NULL;
104 | }
105 |
106 | // '|'
107 | if (match_token(TOKEN_PIPE)) {
108 | if (!expect_token(TOKEN_ARGUMENT)) {
109 | ast_destroy_node(left);
110 | return NULL;
111 | }
112 | ast_t *ast = ast_create_node();
113 | ast->type = AST_PIPE;
114 | ast->left = left;
115 | next_token();
116 | ast->right = pipe();
117 | return ast;
118 | }
119 |
120 | //
121 | return left;
122 | }
123 |
124 | static ast_t *full_command(void) {
125 | ast_t *left = pipe();
126 |
127 | if (left == NULL) {
128 | return NULL;
129 | }
130 |
131 | // '&' | '&'
132 | if (match_token(TOKEN_AMPERSAND)) {
133 | if (!expect_token(TOKEN_ARGUMENT) && peek() != NULL) {
134 | ast_destroy_node(left);
135 | return NULL;
136 | }
137 | ast_t *ast = ast_create_node();
138 | ast->type = AST_BACKGROUND;
139 | ast->left = left;
140 | next_token();
141 | ast->right = full_command();
142 | return ast;
143 | }
144 |
145 | // ';' | ';'
146 | if (match_token(TOKEN_SEMICOLON)) {
147 | if (!expect_token(TOKEN_ARGUMENT) && peek() != NULL) {
148 | ast_destroy_node(left);
149 | return NULL;
150 | }
151 | ast_t *ast = ast_create_node();
152 | ast->type = AST_SEQUENCE;
153 | ast->left = left;
154 | next_token();
155 | ast->right = full_command();
156 | return ast;
157 | }
158 |
159 | return left;
160 | }
161 |
162 | static void add_error(parser_t *p, token_t *t) {
163 | if (p->err != NULL) {
164 | free(p->err);
165 | }
166 | parsing_error_t *err = xmalloc(sizeof(parsing_error_t));
167 | err->msg = "parsing error: bad token";
168 | err->bad_tok = token_to_string(t);
169 | p->err = err;
170 | }
171 |
172 | parser_t *parser_create(void) {
173 | parser_t *p = xmalloc(sizeof(parser_t));
174 | p->ast = NULL;
175 | p->err = NULL;
176 | return p;
177 | }
178 |
179 | ast_t *parse(parser_t *parser, lexer_t *l) {
180 | curtok = l->root;
181 | parser->ast = full_command();
182 | if (peek() != NULL) {
183 | add_error(parser, peek());
184 | curtok = NULL;
185 | return NULL;
186 | }
187 | curtok = NULL;
188 | return parser->ast;
189 | }
190 |
191 | void parser_destroy(parser_t *p) {
192 | ast_destroy_node(p->ast);
193 | if (p->err) {
194 | free(p->err);
195 | }
196 | p->err = NULL;
197 | free(p);
198 | p = NULL;
199 | }
200 |
--------------------------------------------------------------------------------
/src/psh.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 |
13 | static void lexical_error(lexical_error_t *e) {
14 | if (e == NULL) {
15 | return;
16 | }
17 | eprintf("%s: %s: at character %d\n", PROGRAM_NAME, e->msg, e->character);
18 | }
19 |
20 | static void parsing_error(parsing_error_t *e) {
21 | if (e == NULL) {
22 | return;
23 | }
24 | eprintf("%s: %s: '%s'\n", PROGRAM_NAME, e->msg, e->bad_tok);
25 | }
26 |
27 | static void trap_signals(void) {
28 | signal(SIGINT, SIG_IGN);
29 | signal(SIGQUIT, SIG_IGN);
30 | signal(SIGTSTP, SIG_IGN);
31 | }
32 |
33 | static void do_input(char *input) {
34 | lexer_t *lexer = lexer_create();
35 | if (lex(lexer, input) == NULL) {
36 | lexical_error(lexer->error);
37 | lexer_destroy(lexer);
38 | return;
39 | }
40 |
41 | parser_t *parser = parser_create();
42 |
43 | if (parse(parser, lexer) == NULL) {
44 | parsing_error(parser->err);
45 |
46 | parser_destroy(parser);
47 | lexer_destroy(lexer);
48 | return;
49 | }
50 |
51 | evaluate(parser);
52 |
53 | parser_destroy(parser);
54 | lexer_destroy(lexer);
55 | }
56 |
57 | int main(void) {
58 | trap_signals();
59 |
60 | while (1) {
61 | char *input = readline(PROMPT);
62 |
63 | if (feof(stdin) || input == NULL) {
64 | exit(0);
65 | }
66 |
67 | add_history(input);
68 |
69 | do_input(input);
70 |
71 | free(input);
72 | }
73 | return 0;
74 | }
75 |
--------------------------------------------------------------------------------
/src/utils.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | void eprintf(const char *fmt, ...) {
10 | va_list ap;
11 | va_start(ap, fmt);
12 | vfprintf(stderr, fmt, ap);
13 | va_end(ap);
14 | }
15 |
16 | void psherror(const char *msg) {
17 | eprintf("%s: %s: %s\n", PROGRAM_NAME, msg, strerror(errno));
18 | }
19 |
20 | void die(const char *msg) {
21 | if (errno) {
22 | psherror(msg);
23 | }
24 | exit(EXIT_FAILURE);
25 | }
26 |
27 | void *xmalloc(size_t size) {
28 | void *p = malloc(size);
29 | if (!p) {
30 | die("malloc");
31 | }
32 | return p;
33 | }
34 |
35 | void *xrealloc(void *ptr, size_t size) {
36 | void *p = realloc(ptr, size);
37 | if (!p) {
38 | die("realloc");
39 | }
40 | return p;
41 | }
42 |
43 | char *xstrdup(const char *s) {
44 | char *p = malloc(strlen(s) + 1);
45 | if (!p) {
46 | die("strdup");
47 | }
48 | strcpy(p, s);
49 | return p;
50 | }
51 |
--------------------------------------------------------------------------------