| #include <stdio.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <string.h> |
. | #include <unistd.h> // fork, execvp |
| #include <unistd.h> // fork, execvp, dup2 |
| #include <sys/wait.h> // waitpid |
| #include <sys/wait.h> // waitpid |
| #include <glob.h> // glob |
| #include <glob.h> // glob |
| #include <errno.h> // errno |
| #include <errno.h> // errno |
| #include <limits.h> // PATH_MAX |
| #include <limits.h> // PATH_MAX |
. | |
| #include <fcntl.h> // open |
| |
| |
| #define MAX_CMD_LEN 1024 |
| #define MAX_CMD_LEN 1024 |
| #define MAX_ARGS 100 |
| #define MAX_ARGS 100 |
| |
| |
| // 組み込みコマンド関連の宣言 |
| // 組み込みコマンド関連の宣言 |
| int cd (char *argv[]); |
| int cd (char *argv[]); |
| int pwd(char *argv[]); |
| int pwd(char *argv[]); |
| struct builtin { |
| struct builtin { |
| char cmd[256]; // コマンド名 |
| char cmd[256]; // コマンド名 |
| int (*func)(char *argv[]); // 関数へのポインタ |
| int (*func)(char *argv[]); // 関数へのポインタ |
| }; |
| }; |
| struct builtin blt_in[] = {{"cd",cd}, {"pwd",pwd}, {"",0}}; |
| struct builtin blt_in[] = {{"cd",cd}, {"pwd",pwd}, {"",0}}; |
| |
| |
. | |
| // リダイレクション関連の宣言 |
| |
| void handle_redirection(char *argv[]); |
| |
| enum Action { SAVE, RESTORE }; |
| |
| typedef enum { // リダイレクションのタイプを表す列挙型 |
| |
| REDIR_INPUT, |
| |
| REDIR_OUTPUT, |
| |
| REDIR_APPEND, |
| |
| REDIR_MERGE_STDERR |
| |
| } RedirectionType; |
| |
| typedef struct { // リダイレクションの設定を格納する構造体 |
| |
| const char *symbol; // リダイレクション記号 |
| |
| RedirectionType type; // リダイレクションのタイプ |
| |
| int target_fd; // 対象のファイル記述子 |
| |
| int flags; // ファイルオープンフラグ |
| |
| } RedirectionRule; |
| |
| static const RedirectionRule redirection_rules[] = { // リダイレクションルールの定義 |
| |
| {"<", REDIR_INPUT, STDIN_FILENO, O_RDONLY}, |
| |
| {">", REDIR_OUTPUT, STDOUT_FILENO, O_WRONLY | O_CREAT | O_TRUNC}, |
| |
| {">>", REDIR_APPEND, STDOUT_FILENO, O_WRONLY | O_CREAT | O_APPEND}, |
| |
| {"2>&1", REDIR_MERGE_STDERR, STDERR_FILENO, 0} // ファイルフラグ不要 |
| |
| }; |
| |
| |
| // コマンドをスペースで分割する関数 |
| // コマンドをスペースで分割する関数 |
| void parse_command(char *cmd, char *argv[]) { |
| void parse_command(char *cmd, char *argv[]) { |
| char *token = strtok(cmd, " \n"); |
| char *token = strtok(cmd, " \n"); |
| int i = 0; |
| int i = 0; |
| while (token != NULL) { |
| while (token != NULL) { |
| argv[i++] = token; |
| argv[i++] = token; |
| token = strtok(NULL, " \n"); |
| token = strtok(NULL, " \n"); |
| } |
| } |
| argv[i] = NULL; // 最後の引数をNULLで終わらせる |
| argv[i] = NULL; // 最後の引数をNULLで終わらせる |
| } |
| } |
| |
| |
| // 引用符を削除する関数 |
| // 引用符を削除する関数 |
| void remove_quotes(char *str) { |
| void remove_quotes(char *str) { |
| char *src = str, *dst = str; |
| char *src = str, *dst = str; |
| while (*src) { |
| while (*src) { |
| if (*src == '"' || *src == '\'') { |
| if (*src == '"' || *src == '\'') { |
| src++; // 引用符をスキップ |
| src++; // 引用符をスキップ |
| } else { |
| } else { |
| *dst++ = *src++; |
| *dst++ = *src++; |
| } |
| } |
| } |
| } |
| *dst = '\0'; // 文字列の終端を追加 |
| *dst = '\0'; // 文字列の終端を追加 |
| } |
| } |
| |
| |
| // 引数リストにパス名展開を適用する関数 |
| // 引数リストにパス名展開を適用する関数 |
| int expand_arguments(char *argv[], char **expanded_argv) { |
| int expand_arguments(char *argv[], char **expanded_argv) { |
| glob_t glob_result; |
| glob_t glob_result; |
| int argc = 0; // 展開後の引数数 |
| int argc = 0; // 展開後の引数数 |
| |
| |
| for (int i = 0; argv[i] != NULL; i++) { |
| for (int i = 0; argv[i] != NULL; i++) { |
| // 引用符が含まれている場合は展開しない |
| // 引用符が含まれている場合は展開しない |
| if ((strchr(argv[i], '*') || strchr(argv[i], '?') || strchr(argv[i], '[')) && |
| if ((strchr(argv[i], '*') || strchr(argv[i], '?') || strchr(argv[i], '[')) && |
| (argv[i][0] != '"' && argv[i][0] != '\'')) { // 引用符外でパス名展開 |
| (argv[i][0] != '"' && argv[i][0] != '\'')) { // 引用符外でパス名展開 |
| if (glob(argv[i], GLOB_NOCHECK | GLOB_TILDE, NULL, &glob_result) != 0) { |
| if (glob(argv[i], GLOB_NOCHECK | GLOB_TILDE, NULL, &glob_result) != 0) { |
| perror("glob failed"); |
| perror("glob failed"); |
| return -1; |
| return -1; |
| } |
| } |
| // 展開された結果を追加 |
| // 展開された結果を追加 |
| for (size_t j = 0; j < glob_result.gl_pathc; j++) { |
| for (size_t j = 0; j < glob_result.gl_pathc; j++) { |
| if (argc >= MAX_ARGS - 1) { |
| if (argc >= MAX_ARGS - 1) { |
| fprintf(stderr, "Too many arguments after glob expansion\n"); |
| fprintf(stderr, "Too many arguments after glob expansion\n"); |
| globfree(&glob_result); |
| globfree(&glob_result); |
| return -1; |
| return -1; |
| } |
| } |
| expanded_argv[argc++] = strdup(glob_result.gl_pathv[j]); |
| expanded_argv[argc++] = strdup(glob_result.gl_pathv[j]); |
| } |
| } |
| globfree(&glob_result); // メモリを解放 |
| globfree(&glob_result); // メモリを解放 |
| } else { |
| } else { |
| // 引用符を取り除く |
| // 引用符を取り除く |
| remove_quotes(argv[i]); |
| remove_quotes(argv[i]); |
| |
| |
| // パス名展開が不要な場合 |
| // パス名展開が不要な場合 |
| if (argc >= MAX_ARGS - 1) { |
| if (argc >= MAX_ARGS - 1) { |
| fprintf(stderr, "Too many arguments\n"); |
| fprintf(stderr, "Too many arguments\n"); |
| return -1; |
| return -1; |
| } |
| } |
| expanded_argv[argc++] = strdup(argv[i]); |
| expanded_argv[argc++] = strdup(argv[i]); |
| } |
| } |
| } |
| } |
| expanded_argv[argc] = NULL; // 最後にNULLを追加 |
| expanded_argv[argc] = NULL; // 最後にNULLを追加 |
| return argc; |
| return argc; |
| } |
| } |
| |
| |
. | |
| // ファイル記述子の保存または復元を行う関数 |
| |
| void manage_file_descriptors(enum Action action, int *saved_fds) { |
| |
| if (action == SAVE) { |
| |
| // 現在の標準入力、標準出力、標準エラー出力を保存 |
| |
| saved_fds[0] = dup(STDIN_FILENO); |
| |
| saved_fds[1] = dup(STDOUT_FILENO); |
| |
| saved_fds[2] = dup(STDERR_FILENO); |
| |
| if (saved_fds[0] == -1 || saved_fds[1] == -1 || saved_fds[2] == -1) { |
| |
| perror("Failed to save file descriptors"); |
| |
| exit(1); |
| |
| } |
| |
| } else if (action == RESTORE) { |
| |
| // 保存されたファイル記述子を復元 |
| |
| if (dup2(saved_fds[0], STDIN_FILENO) == -1 || |
| |
| dup2(saved_fds[1], STDOUT_FILENO) == -1 || |
| |
| dup2(saved_fds[2], STDERR_FILENO) == -1) { |
| |
| perror("Failed to restore file descriptors"); |
| |
| exit(1); |
| |
| } |
| |
| // 保存したファイル記述子を閉じる |
| |
| close(saved_fds[0]); |
| |
| close(saved_fds[1]); |
| |
| close(saved_fds[2]); |
| |
| } |
| |
| } |
| |
| |
| // 組み込みコマンドの判定・実行 |
| // 組み込みコマンドの判定・実行 |
| int built_in(char *argv[], int bg){ |
| int built_in(char *argv[], int bg){ |
| struct builtin *p; |
| struct builtin *p; |
. | |
| int saved_fds[3]; |
| // 組み込みコマンド配列(blt_in)の走査 |
| // 組み込みコマンド配列(blt_in)の走査 |
| for (p = blt_in; *p->cmd != '\0'; p++) { |
| for (p = blt_in; *p->cmd != '\0'; p++) { |
| // 組み込みコマンドでない場合は次をチェック |
| // 組み込みコマンドでない場合は次をチェック |
| if (strcmp(p->cmd, argv[0]) != 0) continue; |
| if (strcmp(p->cmd, argv[0]) != 0) continue; |
. | |
| // 標準入力/出力/エラーのファイル記述子をバックアップ |
| |
| manage_file_descriptors(SAVE, saved_fds); |
| |
| // リダイレクションの処理 |
| |
| handle_redirection(argv); |
| // バックグラウンド実行の場合 |
| // バックグラウンド実行の場合 |
| if (bg) { |
| if (bg) { |
| pid_t pid = fork(); |
| pid_t pid = fork(); |
| if (pid == -1) { |
| if (pid == -1) { |
| perror("fork failed"); |
| perror("fork failed"); |
| exit(1); |
| exit(1); |
| } |
| } |
| // 子プロセス |
| // 子プロセス |
| if (pid == 0) exit ((p->func)(argv)); // 組み込みコマンドの関数を呼び出し |
| if (pid == 0) exit ((p->func)(argv)); // 組み込みコマンドの関数を呼び出し |
| // 親プロセス |
| // 親プロセス |
| fprintf(stderr, "Background process started with PID: %d\n", pid); |
| fprintf(stderr, "Background process started with PID: %d\n", pid); |
. | |
| // ファイル記述子をバックアップから復元 |
| |
| manage_file_descriptors(RESTORE, saved_fds); |
| return 0; |
| return 0; |
| } |
| } |
| // バックグラウンド実行でない場合 |
| // バックグラウンド実行でない場合 |
| // 組み込みコマンドの関数を呼び出し |
| // 組み込みコマンドの関数を呼び出し |
| (p->func)(argv); |
| (p->func)(argv); |
. | |
| // ファイル記述子をバックアップから復元 |
| |
| manage_file_descriptors(RESTORE, saved_fds); |
| return 0; |
| return 0; |
| } |
| } |
| return -1; |
| return -1; |
| } |
| } |
| |
| |
| // cd コマンド |
| // cd コマンド |
| int cd(char *argv[]) { |
| int cd(char *argv[]) { |
| if (argv[1] == NULL) { |
| if (argv[1] == NULL) { |
| // 引数なしの場合はホームディレクトリに移動 |
| // 引数なしの場合はホームディレクトリに移動 |
| const char *home_dir = getenv("HOME"); |
| const char *home_dir = getenv("HOME"); |
| if (home_dir == NULL) { |
| if (home_dir == NULL) { |
| perror("cd: HOME not set"); |
| perror("cd: HOME not set"); |
| return -1; |
| return -1; |
| } else { |
| } else { |
| if (chdir(home_dir) != 0) { |
| if (chdir(home_dir) != 0) { |
| perror("cd failed"); |
| perror("cd failed"); |
| return -1; |
| return -1; |
| } |
| } |
| } |
| } |
| } else { |
| } else { |
| // 引数が指定された場合、そのディレクトリに移動 |
| // 引数が指定された場合、そのディレクトリに移動 |
| if (chdir(argv[1]) != 0) { |
| if (chdir(argv[1]) != 0) { |
| perror("cd failed"); |
| perror("cd failed"); |
| return -1; |
| return -1; |
| } |
| } |
| } |
| } |
| return 0; |
| return 0; |
| } |
| } |
| |
| |
| // pwd コマンド |
| // pwd コマンド |
| int pwd(char *argv[]){ |
| int pwd(char *argv[]){ |
| char buf[PATH_MAX+1]; |
| char buf[PATH_MAX+1]; |
| if (getcwd(buf, sizeof(buf)) == NULL) { |
| if (getcwd(buf, sizeof(buf)) == NULL) { |
| perror("getcwd failed"); |
| perror("getcwd failed"); |
| return -1; |
| return -1; |
| } |
| } |
| printf("%s\n", buf); |
| printf("%s\n", buf); |
| return 0; |
| return 0; |
| } |
| } |
| |
| |
| // バックグラウンド実行判定 |
| // バックグラウンド実行判定 |
| // コマンドの末尾が '&' かどうかを確認し、'&' を取り除いて戻り値として返す |
| // コマンドの末尾が '&' かどうかを確認し、'&' を取り除いて戻り値として返す |
| int is_background_command(char *argv[]) { |
| int is_background_command(char *argv[]) { |
| int i = 0; |
| int i = 0; |
| while (argv[i] != NULL) i++; |
| while (argv[i] != NULL) i++; |
| if (i > 0 && strcmp(argv[i - 1], "&") == 0) { |
| if (i > 0 && strcmp(argv[i - 1], "&") == 0) { |
| argv[i - 1] = NULL; // '&' をコマンド引数から削除 |
| argv[i - 1] = NULL; // '&' をコマンド引数から削除 |
| return 1; |
| return 1; |
| } |
| } |
| return 0; |
| return 0; |
| } |
| } |
| |
| |
. | |
| // リダイレクションを処理する関数 |
| |
| void handle_redirection(char *argv[]) { |
| |
| int i = 0; |
| |
| |
| |
| while (argv[i] != NULL) { |
| |
| const RedirectionRule *rule = NULL; // 該当するリダイレクションルール |
| |
| char *redirect_file = NULL; |
| |
| |
| |
| // リダイレクション記号を検索 |
| |
| for (size_t j = 0; j < sizeof(redirection_rules) / sizeof(redirection_rules[0]); j++) { |
| |
| if (strcmp(argv[i], redirection_rules[j].symbol) == 0) { |
| |
| rule = &redirection_rules[j]; |
| |
| break; |
| |
| } |
| |
| } |
| |
| |
| |
| if (rule) { |
| |
| // リダイレクション記号が見つかった場合の処理 |
| |
| switch (rule->type) { |
| |
| case REDIR_INPUT: |
| |
| case REDIR_OUTPUT: |
| |
| case REDIR_APPEND: |
| |
| // リダイレクト先のファイル名を取得 |
| |
| redirect_file = argv[i + 1]; |
| |
| if (!redirect_file) { |
| |
| fprintf(stderr, "Syntax error: No file specified for redirection\n"); |
| |
| exit(1); |
| |
| } |
| |
| |
| |
| // ファイルを開き、リダイレクションを設定 |
| |
| int fd = open(redirect_file, rule->flags, 0644); |
| |
| if (fd == -1) { |
| |
| perror("Redirection failed"); |
| |
| exit(1); |
| |
| } |
| |
| if (dup2(fd, rule->target_fd) == -1) { |
| |
| perror("dup2 failed"); |
| |
| close(fd); |
| |
| exit(1); |
| |
| } |
| |
| close(fd); |
| |
| break; |
| |
| |
| |
| case REDIR_MERGE_STDERR: |
| |
| // 標準エラー出力を標準出力にリダイレクト |
| |
| if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) { |
| |
| perror("dup2 failed"); |
| |
| exit(1); |
| |
| } |
| |
| break; |
| |
| } |
| |
| |
| |
| // リダイレクション記号と関連引数を削除 |
| |
| argv[i] = NULL; |
| |
| if (redirect_file) { |
| |
| argv[i + 1] = NULL; |
| |
| i++; // ファイル名をスキップ |
| |
| } |
| |
| } |
| |
| i++; |
| |
| } |
| |
| } |
| |
| |
| int main() { |
| int main() { |
| char cmd[MAX_CMD_LEN]; // コマンド入力用のバッファ |
| char cmd[MAX_CMD_LEN]; // コマンド入力用のバッファ |
| char *argv[MAX_ARGS]; // コマンド引数 |
| char *argv[MAX_ARGS]; // コマンド引数 |
| char *expanded_argv[MAX_ARGS]; // 展開後の引数 |
| char *expanded_argv[MAX_ARGS]; // 展開後の引数 |
| pid_t pid, wpid; |
| pid_t pid, wpid; |
| int status, bg; |
| int status, bg; |
| |
| |
| // シェルのメインループ |
| // シェルのメインループ |
| while (1) { |
| while (1) { |
| // ゾンビを消滅させる |
| // ゾンビを消滅させる |
| while ((wpid = waitpid(-1, &status, WNOHANG)) > 0) |
| while ((wpid = waitpid(-1, &status, WNOHANG)) > 0) |
| fprintf(stderr, "process %d done\n", wpid); |
| fprintf(stderr, "process %d done\n", wpid); |
| |
| |
| // プロンプトを表示 |
| // プロンプトを表示 |
| fprintf(stderr, "mysh> "); |
| fprintf(stderr, "mysh> "); |
| |
| |
| // コマンドを読み込む |
| // コマンドを読み込む |
| if (fgets(cmd, sizeof(cmd), stdin) == NULL) { |
| if (fgets(cmd, sizeof(cmd), stdin) == NULL) { |
| if (feof(stdin)) { |
| if (feof(stdin)) { |
| break; // EOFが入力された場合、終了 |
| break; // EOFが入力された場合、終了 |
| } |
| } |
| perror("fgets failed"); |
| perror("fgets failed"); |
| continue; |
| continue; |
| } |
| } |
| |
| |
| // コマンドを解析 |
| // コマンドを解析 |
| parse_command(cmd, argv); |
| parse_command(cmd, argv); |
| |
| |
| // 空のコマンドの場合は次の入力へスキップ |
| // 空のコマンドの場合は次の入力へスキップ |
| if (argv[0] == NULL) { |
| if (argv[0] == NULL) { |
| continue; |
| continue; |
| } |
| } |
| |
| |
| // 終了コマンドの場合 |
| // 終了コマンドの場合 |
| if (strcmp(argv[0], "exit") == 0) { |
| if (strcmp(argv[0], "exit") == 0) { |
| break; |
| break; |
| } |
| } |
| |
| |
| // バックグラウンド実行判定 |
| // バックグラウンド実行判定 |
| bg = is_background_command(argv); |
| bg = is_background_command(argv); |
| |
| |
| // 引数を展開 |
| // 引数を展開 |
| if (expand_arguments(argv, expanded_argv) == -1) { |
| if (expand_arguments(argv, expanded_argv) == -1) { |
| fprintf(stderr, "Argument expansion failed\n"); |
| fprintf(stderr, "Argument expansion failed\n"); |
| continue; |
| continue; |
| } |
| } |
| |
| |
| // 組み込みコマンド判定・実行 |
| // 組み込みコマンド判定・実行 |
| if (built_in(expanded_argv, bg) == 0) { |
| if (built_in(expanded_argv, bg) == 0) { |
| continue; |
| continue; |
| } |
| } |
| |
| |
| // 子プロセスを作成してコマンドを実行 |
| // 子プロセスを作成してコマンドを実行 |
| pid = fork(); |
| pid = fork(); |
| if (pid == -1) { |
| if (pid == -1) { |
| perror("fork failed"); |
| perror("fork failed"); |
| exit(1); |
| exit(1); |
| } |
| } |
| |
| |
| // 子プロセス |
| // 子プロセス |
| if (pid == 0) { |
| if (pid == 0) { |
. | |
| // リダイレクションの処理 |
| |
| handle_redirection(expanded_argv); |
| |
| |
| // execvpを使用してコマンドを実行 |
| // execvpを使用してコマンドを実行 |
| if (execvp(expanded_argv[0], expanded_argv) == -1) { |
| if (execvp(expanded_argv[0], expanded_argv) == -1) { |
| perror("exec failed"); |
| perror("exec failed"); |
| exit(1); |
| exit(1); |
| } |
| } |
| } |
| } |
| |
| |
| // 親プロセス |
| // 親プロセス |
| while (1) { |
| while (1) { |
| // バックグラウンド実行の場合 |
| // バックグラウンド実行の場合 |
| if (bg) { |
| if (bg) { |
| fprintf(stderr, "Background process started with PID: %d\n", pid); |
| fprintf(stderr, "Background process started with PID: %d\n", pid); |
| break; // バックグラウンド実行の場合は待機しない |
| break; // バックグラウンド実行の場合は待機しない |
| } |
| } |
| // バックグラウンド実行でない場合 |
| // バックグラウンド実行でない場合 |
| if (waitpid(pid, &status, 0) == (pid_t)-1) { // 子プロセスの終了を待つ |
| if (waitpid(pid, &status, 0) == (pid_t)-1) { // 子プロセスの終了を待つ |
| if (errno == EINTR) { |
| if (errno == EINTR) { |
| continue; // 割り込み発生時はリトライする |
| continue; // 割り込み発生時はリトライする |
| } else { |
| } else { |
| perror("waitpid failed"); |
| perror("waitpid failed"); |
| exit(1); |
| exit(1); |
| } |
| } |
| } |
| } |
| break; |
| break; |
| } |
| } |
| |
| |
| // メモリを解放 |
| // メモリを解放 |
| for (int i = 0; expanded_argv[i] != NULL; i++) { |
| for (int i = 0; expanded_argv[i] != NULL; i++) { |
| free(expanded_argv[i]); |
| free(expanded_argv[i]); |
| } |
| } |
| } |
| } |
| |
| |
| printf("Exiting shell...\n"); |
| printf("Exiting shell...\n"); |
| return 0; |
| return 0; |
| } |
| } |
| |
| |