progress: make gmsh great again

This commit is contained in:
2026-01-07 20:02:39 +09:00
parent db1e2c2364
commit 7f6de0567a
7 changed files with 442 additions and 231 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# userprog/gmsh/Makefile
TARGET = gmsh
SRCS = gmsh.c builtin.c
SRCS = gmsh.c job.c parser.c exec.c builtin.c
CC = gcc
CFLAGS = -static -Wall
-55
View File
@@ -115,58 +115,3 @@ void cmd_resize(char* row, char* col) {
}
void cmd_help() { printf("Built-ins: ls, cd, pwd, cat, sz, poweroff, reboot\n"); }
void handle_redirection(const char *argv[]) {
int i = 0;
while (argv[i] != NULL) {
int fd = -1;
int is_redirect = 0;
// 1. Output Redirection (Overwrite): >
if (strcmp(argv[i], ">") == 0) {
if (argv[i+1] == NULL) {
fprintf(stderr, "gmsh: syntax error near unexpected token 'newline'\n");
exit(1);
}
fd = open(argv[i+1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); exit(1); }
dup2(fd, STDOUT_FILENO); // stdout(1) -> fd
close(fd);
is_redirect = 1;
}
// 2. Output Redirection (Append): >>
else if (strcmp(argv[i], ">>") == 0) {
if (argv[i+1] == NULL) {
fprintf(stderr, "gmsh: syntax error\n");
exit(1);
}
// O_APPEND: 뒤에 추가
fd = open(argv[i+1], O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd < 0) { perror("open"); exit(1); }
dup2(fd, STDOUT_FILENO);
close(fd);
is_redirect = 1;
}
// 3. Input Redirection: <
else if (strcmp(argv[i], "<") == 0) {
if (argv[i+1] == NULL) {
fprintf(stderr, "gmsh: syntax error\n");
exit(1);
}
fd = open(argv[i+1], O_RDONLY);
if (fd < 0) { perror("open"); exit(1); }
dup2(fd, STDIN_FILENO); // stdin(0) -> fd
close(fd);
is_redirect = 1;
}
if (is_redirect) {
argv[i] = NULL;
break;
}
i++;
}
}
+127
View File
@@ -0,0 +1,127 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include "job.h"
// 리다이렉션 처리 헬퍼
static void setup_redirection(command_t *cmd) {
if (cmd->input_path) {
int fd = open(cmd->input_path, O_RDONLY);
if (fd < 0) {
perror("gmsh: input error");
exit(1);
}
dup2(fd, STDIN_FILENO);
close(fd);
}
if (cmd->output_path) {
int flags = O_WRONLY | O_CREAT;
if (cmd->append_mode) flags |= O_APPEND;
else flags |= O_TRUNC;
int fd = open(cmd->output_path, flags, 0644);
if (fd < 0) {
perror("gmsh: output error");
exit(1);
}
dup2(fd, STDOUT_FILENO);
close(fd);
}
}
// Job 실행 (핵심 함수)
void launch_job(job_t *job) {
command_t *cmd;
int next_in_fd = STDIN_FILENO; // 파이프라인의 이전 단계 출력 (현재 단계의 입력)
int pipe_fds[2];
pid_t pid;
// 1. 명령어 리스트 순회
for (cmd = job->head; cmd != NULL; cmd = cmd->next) {
// 다음 명령어가 있으면 파이프 생성
if (cmd->next) {
if (pipe(pipe_fds) < 0) {
perror("pipe");
exit(1);
}
}
// 2. 프로세스 생성
pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
else if (pid == 0) {
// [자식 프로세스]
// A. 프로세스 그룹 설정 (Job Control)
// 첫 번째 프로세스의 PID를 그룹 ID로 삼음
if (job->pgid == 0) job->pgid = getpid();
setpgid(0, job->pgid);
// B. 시그널 핸들러 복구 (쉘의 보호막 해제)
signal(SIGINT, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
// C. 입력 연결 (이전 파이프 -> 내 입력)
if (next_in_fd != STDIN_FILENO) {
dup2(next_in_fd, STDIN_FILENO);
close(next_in_fd);
}
// D. 출력 연결 (내 출력 -> 다음 파이프)
if (cmd->next) {
dup2(pipe_fds[1], STDOUT_FILENO);
close(pipe_fds[1]);
close(pipe_fds[0]); // 읽기 쪽은 안 씀
}
// E. 리다이렉션 (파이프보다 우선순위 높음)
setup_redirection(cmd);
// F. 실행
execvp(cmd->argv[0], cmd->argv);
fprintf(stderr, "gmsh: command not found: %s\n", cmd->argv[0]);
exit(127);
}
else {
// [부모 프로세스]
// A. Job PGID 설정 (첫 번째 자식의 PID로)
if (job->pgid == 0) job->pgid = pid;
setpgid(pid, job->pgid); // 자식과 부모 양쪽에서 해줘야 경쟁상태 방지
// B. 파이프 정리
// 이전에 썼던 입력 파이프 닫기
if (next_in_fd != STDIN_FILENO) close(next_in_fd);
// 현재 만든 파이프의 읽기 쪽을 다음 루프로 넘김
if (cmd->next) {
next_in_fd = pipe_fds[0];
close(pipe_fds[1]); // 쓰기 쪽은 자식이 가져갔으니 닫음
}
}
}
// 3. 대기 (Foreground인 경우)
if (!job->is_bg) {
// 이 Job에 속한 모든 프로세스가 끝날 때까지 대기
int status;
while (waitpid(-job->pgid, &status, WNOHANG) == 0) {
// 아직 안 끝난 자식이 있으면 잠시 대기 (CPU 절약)
// 실제로는 sigsuspend 등을 쓰는 게 정석이지만 간단히 sleep/pause
// 여기선 blocking wait로 단순화
waitpid(-job->pgid, &status, 0);
}
} else {
// Background
printf("[%d] %d\n", job->id, job->pgid);
add_job(job); // Job 리스트에 등록 (나중에 확인용)
}
}
+50 -167
View File
@@ -9,122 +9,62 @@
#include <fcntl.h>
#include <stdbool.h>
#include "para.h"
#include "builtin.h"
#include "para.h"
#include "job.h"
job_t *parse_line(char *line);
void launch_job(job_t *job);
static bool verbose = false;
volatile pid_t fg_pgid = 0;
int parse_cmd(char *cmd, char *argv[]) {
int argc = 0;
char *ptr = cmd;
char *token_start;
char quote_char = 0;
while (*ptr) {
while (*ptr == ' ' && quote_char == 0) ptr++;
if (*ptr == '\0') break;
token_start = ptr;
char *write_ptr = ptr;
while (*ptr) {
if (quote_char == 0 && *ptr == ' ') {
ptr++;
break;
void handle_sigint(int sig) {
printf("\n");
}
if (*ptr == '"' || *ptr == '\'') {
if (quote_char == *ptr) quote_char = 0;
else if (quote_char == 0) quote_char = *ptr;
else *write_ptr++ = *ptr;
}
else {
*write_ptr++ = *ptr;
void handle_sigchld(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
ptr++;
int builtin_cmd(job_t *job) {
command_t *cmd = job->head;
if (!cmd || !cmd->argv[0]) return 0;
// if (strcmp(cmd->argv[0], "exit") == 0) exit(0);
// if (strcmp(cmd->argv[0], "cd") == 0) {
// if (cmd->argc < 2) fprintf(stderr, "cd: missing argument\n");
// else if (chdir(cmd->argv[1]) != 0) perror("cd");
// return 1;
// }
bool ret = 0;
if (strcmp(cmd->argv[0], "exit") == 0) exit(0), ret = 1;
else if (strcmp(cmd->argv[0], "ls") == 0) cmd_ls(cmd->argv[1]), ret = 1;
else if (strcmp(cmd->argv[0], "cd") == 0) cmd_cd(cmd->argv[1]), ret = 1;
else if (strcmp(cmd->argv[0], "pwd") == 0) cmd_pwd(), ret = 1;
// else if (strcmp(cmd->argv[0], "cat") == 0) cmd_cat(argc, (const char**) argv);
// else if (strcmp(cmd->argv[0], "proc") == 0) cmd_proc();
// else if (strcmp(cmd->argv[0], "echo") == 0) cmd_echo(argc, (const char**) argv);
else if (strcmp(cmd->argv[0], "size") == 0) cmd_size(), ret = 1;
else if (strcmp(cmd->argv[0], "resize") == 0){
if(cmd->argc != 3){
printf("Usage: resize <rows> <cols>\n");
}
else cmd_resize(cmd->argv[1], cmd->argv[2]);
ret = 1;
}
*write_ptr = '\0';
argv[argc++] = token_start;
else if (strcmp(cmd->argv[0], "poweroff") == 0) cmd_poweroff(), ret = 1;
else if (strcmp(cmd->argv[0], "reboot") == 0) cmd_reboot(), ret = 1;
else if (strcmp(cmd->argv[0], "help") == 0) cmd_help(), ret = 1;
if (argc >= MAX_ARGS - 1) break;
return ret;
}
argv[argc] = NULL;
return argc;
}
// 반환값: 1(처리함), 0(파이프 없음)
int handle_pipe(char *argv[]) {
int pipe_idx = -1;
for (int i = 0; argv[i] != NULL; i++) {
if (strcmp(argv[i], "|") == 0) {
pipe_idx = i;
break;
}
}
if (pipe_idx == -1) return 0;
argv[pipe_idx] = NULL;
char **left_cmd = &argv[0];
char **right_cmd = &argv[pipe_idx + 1];
if (right_cmd[0] == NULL) {
printf("gmsh: syntax error near unexpected token `|'\n");
return 1;
}
int fds[2];
if (pipe(fds) == -1) {
perror("pipe");
return 1;
}
pid_t pid1 = fork();
if (pid1 == 0) {
close(fds[0]);
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
handle_redirection((const char**) left_cmd);
execvp(left_cmd[0], left_cmd);
perror("execvp left");
exit(1);
}
pid_t pid2 = fork();
if (pid2 == 0) {
close(fds[1]);
dup2(fds[0], STDIN_FILENO);
close(fds[0]);
if (handle_pipe(right_cmd) == 1) {
exit(0);
}
else {
handle_redirection((const char**) right_cmd);
execvp(right_cmd[0], right_cmd);
perror("execvp right");
exit(1);
}
}
close(fds[0]);
close(fds[1]);
waitpid(pid1, NULL, 0);
waitpid(pid2, NULL, 0);
return 1; // 처리 완료
}
void run_shell() {
char cmd_buf[MAX_CMD_LEN];
char *args[MAX_ARGS];
char cwd[1024];
while (1) {
@@ -136,82 +76,23 @@ void run_shell() {
cmd_buf[strcspn(cmd_buf, "\n")] = 0;
if (strlen(cmd_buf) == 0) continue;
// int argc = 0;
// args[argc] = strtok(cmd_buf, " ");
// while (args[argc] != NULL && argc < MAX_ARGS - 1) args[++argc] = strtok(NULL, " ");
job_t *job = parse_line(cmd_buf);
if (job == NULL) continue;
int argc = parse_cmd(cmd_buf, args);
if (argc == 0) continue;
if (handle_pipe(args)) {
if (builtin_cmd(job)) {
free_job(job);
continue;
}
if (verbose) {
printf("[debug] argc: %d\n", argc);
for(int i=0; i<argc; i++) printf("[debug] argv[%d]: '%s'\n", i, args[i]);
}
if (strcmp(args[0], "exit") == 0) exit(0);
else if (strcmp(args[0], "ls") == 0) cmd_ls(args[1]);
else if (strcmp(args[0], "cd") == 0) cmd_cd(args[1]);
else if (strcmp(args[0], "pwd") == 0) cmd_pwd();
// else if (strcmp(args[0], "cat") == 0) cmd_cat(argc, (const char**) args);
// else if (strcmp(args[0], "proc") == 0) cmd_proc();
// else if (strcmp(args[0], "echo") == 0) cmd_echo(argc, (const char**) args);
else if (strcmp(args[0], "size") == 0) cmd_size();
else if (strcmp(args[0], "resize") == 0){
if(argc != 3){
printf("Usage: resize <rows> <cols>\n");
}
else cmd_resize(args[1], args[2]);
}
else if (strcmp(args[0], "poweroff") == 0) cmd_poweroff();
else if (strcmp(args[0], "reboot") == 0) cmd_reboot();
else if (strcmp(args[0], "help") == 0) cmd_help();
else {
pid_t pid = fork();
// if (pid == 0) {
// execvp(args[0], args);
// printf("%s: command not found\n", args[0]);
// exit(1);
// } else wait(NULL);
if (pid == 0) {
handle_redirection((const char**) args);
execvp(args[0], args);
fprintf(stderr, "%s: command not found\n", args[0]);
exit(127);
} else if (pid > 0) {
int status;
if (verbose) {
printf("[debug] Spawned child PID: %d\n", pid);
}
waitpid(pid, &status, 0);
if (verbose) {
if (WIFEXITED(status)) {
printf("[debug] Process %d exited with code %d\n", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("[debug] Process %d killed by signal %d\n", pid, WTERMSIG(status));
}
}
} else {
perror("fork");
}
}
launch_job(job);
if (!job->is_bg) free_job(job);
}
}
int main(int argc, char** argv) {
printf("GMSH (GMS Minimal SHell) v1.0 initialized.\n");
signal(SIGINT, handle_sigint);
signal(SIGCHLD, handle_sigchld);
signal(SIGTTOU, SIG_IGN);
int opt;
while ((opt = getopt(argc, argv, "v")) != -1) {
@@ -226,6 +107,8 @@ int main(int argc, char** argv) {
}
}
if(verbose) printf("GMSH (GMS Minimal SHell) v1.0 initialized.\n");
run_shell();
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "job.h"
job_t *job_list_head = NULL;
static int next_job_id = 1;
// 초기화
void init_job_list() {
job_list_head = NULL;
next_job_id = 1;
}
// 새로운 Job 생성 (껍데기)
job_t *create_job(char *line) {
job_t *job = (job_t *)malloc(sizeof(job_t));
memset(job, 0, sizeof(job_t));
job->id = next_job_id++; // ID 자동 할당
job->command_line = strdup(line); // 원본 보존
job->state = JOB_RUNNING;
job->next = NULL;
job->head = NULL;
return job;
}
// 명령어 추가 (파이프라인 연결)
void append_command(job_t *job, command_t *cmd) {
if (job->head == NULL) {
job->head = cmd;
} else {
// 리스트 끝을 찾아서 연결
command_t *curr = job->head;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = cmd;
}
}
// Job 리스트에 등록
void add_job(job_t *job) {
if (job_list_head == NULL) {
job_list_head = job;
} else {
job_t *curr = job_list_head;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = job;
}
}
// Job 삭제 (PGID로 찾아서)
int delete_job(pid_t pgid) {
job_t *curr = job_list_head;
job_t *prev = NULL;
while (curr != NULL) {
if (curr->pgid == pgid) {
// 연결 끊기
if (prev == NULL) job_list_head = curr->next;
else prev->next = curr->next;
free_job(curr);
return 1; // 성공
}
prev = curr;
curr = curr->next;
}
return 0; // 못 찾음
}
// PGID로 Job 찾기
job_t *find_job(pid_t pgid) {
job_t *curr = job_list_head;
while (curr != NULL) {
if (curr->pgid == pgid) return curr;
curr = curr->next;
}
return NULL;
}
// ID로 Job 찾기 (fg %1, bg %2 처리용)
job_t *find_job_by_id(int id) {
job_t *curr = job_list_head;
while (curr != NULL) {
if (curr->id == id) return curr;
curr = curr->next;
}
return NULL;
}
// 메모리 해제 (재귀적으로 Command도 해제)
void free_command(command_t *cmd) {
if (cmd == NULL) return;
free_command(cmd->next); // 재귀 호출로 다음꺼 먼저 삭제
// 인자 해제
// (주의: argv 문자열들이 한 덩어리 buffer를 가리키는지, 각각 malloc인지에 따라 다름)
// 여기서는 파서 구현에 따라 달라지므로 일단 구조체만 해제
if (cmd->input_path) free(cmd->input_path);
if (cmd->output_path) free(cmd->output_path);
free(cmd);
}
void free_job(job_t *job) {
if (job == NULL) return;
free_command(job->head); // 연결된 명령어들 해제
if (job->command_line) free(job->command_line);
free(job);
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef _JOB_H_
#define _JOB_H_
#include <sys/types.h>
#define MAX_ARGS 64
// [1단계] 단일 명령어 (Simple Command)
// 예: "grep error > log.txt"
typedef struct command_t {
char *argv[MAX_ARGS]; // 인자 리스트 (예: {"grep", "error", NULL})
int argc; // 인자 개수
char *input_path; // 리다이렉션 < (없으면 NULL)
char *output_path; // 리다이렉션 > (없으면 NULL)
int append_mode; // >> 모드 여부 (0 or 1)
struct command_t *next; // 파이프라인의 다음 명령어 (Linked List)
} command_t;
// [2단계] 작업 상태 (Job State)
typedef enum {
JOB_RUNNING, // 실행 중
JOB_STOPPED, // 정지됨 (Ctrl+Z)
JOB_DONE // 완료됨
} job_state_t;
// [3단계] 작업 (Job) - 파이프라인 전체를 하나의 작업으로 봄
// 예: "ls | grep c &"
typedef struct job_t {
int id; // Job ID (쉘이 관리하는 번호: 1, 2, 3...)
pid_t pgid; // Process Group ID (이 작업의 대표 PID)
job_state_t state; // 현재 상태
int is_bg; // 백그라운드 실행 여부 (1=BG, 0=FG)
char *command_line; // 원본 명령어 문자열 (jobs 출력용)
command_t *head; // 명령어 리스트의 첫 번째 (ls)
struct job_t *next; // 다음 작업 (Job List용)
} job_t;
// --- 함수 원형 ---
// Job 관리
void init_job_list();
job_t *create_job(char *line);
void add_job(job_t *job);
int delete_job(pid_t pgid);
job_t *find_job(pid_t pgid);
job_t *find_job_by_id(int id);
void free_job(job_t *job);
// Command 관리
void append_command(job_t *job, command_t *cmd);
void free_command(command_t *cmd);
// 전역 변수 (외부에서 접근 가능)
extern job_t *job_list_head;
#endif
+81
View File
@@ -0,0 +1,81 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "job.h"
// 문자열을 공백 기준으로 쪼개서 argv에 넣기
static void parse_args(char *cmd_str, command_t *cmd) {
char *token = strtok(cmd_str, " \t\n");
while (token != NULL && cmd->argc < MAX_ARGS - 1) {
// 리다이렉션 처리 (간이 버전)
if (strcmp(token, "<") == 0) {
token = strtok(NULL, " \t\n");
if (token) cmd->input_path = strdup(token);
}
else if (strcmp(token, ">") == 0) {
token = strtok(NULL, " \t\n");
if (token) cmd->output_path = strdup(token);
cmd->append_mode = 0;
}
else if (strcmp(token, ">>") == 0) {
token = strtok(NULL, " \t\n");
if (token) cmd->output_path = strdup(token);
cmd->append_mode = 1;
}
else {
// 일반 인자
cmd->argv[cmd->argc++] = strdup(token);
}
token = strtok(NULL, " \t\n");
}
cmd->argv[cmd->argc] = NULL;
}
// 메인 파서 함수
job_t *parse_line(char *line) {
if (line == NULL || strlen(line) == 0) return NULL;
// 1. Job 껍데기 생성
job_t *job = create_job(line);
// 2. 백그라운드(&) 확인
// 문자열 끝에서부터 역탐색하거나, 토큰화 과정에서 체크해야 함.
// 편의상 strrchr 사용 (단, 따옴표 안에 있는 &는 무시 못함. 일단 간단히 구현)
char *bg_ptr = strrchr(line, '&');
if (bg_ptr != NULL) {
job->is_bg = 1;
*bg_ptr = ' '; // &를 공백으로 치환해서 명령어 파싱 방해 안 되게 함
}
// 3. 파이프(|) 단위로 자르기
// strtok는 원본을 훼손하므로 복사본 사용
char *line_copy = strdup(line);
char *cmd_str = strtok(line_copy, "|");
while (cmd_str != NULL) {
command_t *cmd = (command_t *)malloc(sizeof(command_t));
memset(cmd, 0, sizeof(command_t));
// 내부 인자 및 리다이렉션 파싱
parse_args(cmd_str, cmd);
if (cmd->argc > 0) {
append_command(job, cmd);
} else {
free(cmd); // 빈 명령어는 버림
}
cmd_str = strtok(NULL, "|");
}
free(line_copy);
if (job->head == NULL) { // 명령어가 하나도 없으면
free_job(job);
return NULL;
}
return job;
}