commit ac241a91481b47653768f1ce2e426f360ee76bae Author: Minseong Gwak Date: Tue Dec 30 01:20:20 2025 +0000 GMS Linux: start? diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d973b34 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +.temp + +*.swp +*~ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ff1e9cc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "linux"] + path = linux + url = https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8cb091c --- /dev/null +++ b/Makefile @@ -0,0 +1,161 @@ +# ============================================================================== +# 1. Variables +# ============================================================================== +KERNEL_DIR = linux +INIT_DIR = init +USERPROG_DIR = userprog +GRUB_DIR = grub +BUILD_DIR = build + +USER_OUT_DIR = $(BUILD_DIR)/userprog +INIT_OUT_DIR = $(BUILD_DIR)/init + +# [AUTOMATION] +USER_DIRS = $(wildcard $(USERPROG_DIR)/*) +USER_NAMES = $(notdir $(USER_DIRS)) +USER_BINS = $(addprefix $(USER_OUT_DIR)/, $(USER_NAMES)) + +# Config & Tools +DISK_SIZE_MB = 512 + +ROOT_SIZE_MB = 256 +ROOT_SECTORS = $$(($(ROOT_SIZE_MB) * 2048)) +ROOT_OFFSET = 2048 + +HOME_SIZE_MB = 128 +HOME_SECTORS = $$(($(HOME_SIZE_MB) * 2048)) +HOME_OFFSET = $$(($(ROOT_OFFSET) + $(ROOT_SECTORS))) + +GRUB_LIB = /usr/lib/grub/i386-pc +DD = dd status=none +DEBUGFS = debugfs + +# Sources & Artifacts +KERNEL_SRC_BIN = $(KERNEL_DIR)/arch/x86/boot/bzImage +INIT_SRC = $(INIT_DIR)/init.c +GRUB_CFG_SRC = $(GRUB_DIR)/grub.cfg + +INIT_BIN = $(INIT_OUT_DIR)/init + +DISK_IMG = $(BUILD_DIR)/disk.img +ROOT_IMG = $(BUILD_DIR)/rootfs.img +HOME_IMG = $(BUILD_DIR)/homefs.img +CORE_IMG = $(BUILD_DIR)/core.img + +# ============================================================================== +# 2. Main Targets +# ============================================================================== +.PHONY: all clean run dirs help force_look + +all: dirs $(DISK_IMG) + @echo " [DONE] Image ready: $(DISK_IMG)" + +run: $(DISK_IMG) + qemu-system-x86_64 -drive file=$(DISK_IMG),format=raw -nographic \ + -net nic,model=e1000 -net user + +dirs: + @mkdir -p $(BUILD_DIR) $(USER_OUT_DIR) $(INIT_OUT_DIR) +# @mkdir -p $(USER_OUT_DIR) +# @mkdir -p $(INIT_OUT_DIR) + +clean: + @echo " [CLEAN] Cleaning root build dir..." + @rm -rf $(BUILD_DIR) + @echo " [CLEAN] Cleaning user programs..." + @# 각 유저 프로그램 폴더 들어가서 make clean 실행 + @for dir in $(USER_DIRS); do \ + if [ -f $$dir/Makefile ]; then $(MAKE) -C $$dir clean; fi; \ + done + +clean-kernel: + @$(MAKE) -C $(KERNEL_DIR) clean + +# ============================================================================== +# 3. Build Rules +# ============================================================================== + +$(INIT_BIN): $(INIT_SRC) | dirs + @echo " [CC] Compiling init..." + @gcc -static -o $@ $< + +$(USER_BINS): $(USER_OUT_DIR)/%: force_look | dirs + @echo " [MAKE] Entering directory: $(USERPROG_DIR)/$*" + @$(MAKE) -s -C $(USERPROG_DIR)/$* OUT_BIN=$(abspath $@) --no-print-directory + +force_look: + @true + +$(KERNEL_SRC_BIN): + @echo " [MAKE] Checking Kernel..." + @$(MAKE) -C $(KERNEL_DIR) -j$$(nproc) bzImage + +$(CORE_IMG): | dirs + @grub-mkimage -O i386-pc -o $@ -p "(hd0,msdos1)/boot/grub" biosdisk part_msdos ext2 linux configfile normal boot + +$(ROOT_IMG): $(INIT_BIN) $(USER_BINS) $(KERNEL_SRC_BIN) $(GRUB_CFG_SRC) | dirs + @echo " [ROOT] Generating Root Filesystem..." + + @$(DD) if=/dev/zero of=$@ bs=1M count=$(ROOT_SIZE_MB) + @mkfs.ext4 -q -O ^64bit,^metadata_csum $@ + + @echo " -> Creating Directory Structure..." + @$(DEBUGFS) -w -R "mkdir /boot" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /boot/grub" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /bin" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /dev" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /proc" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /sys" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /tmp" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "mkdir /home" $@ > /dev/null 2>&1 + + @echo " -> Injecting System Binaries..." + @$(DEBUGFS) -w -R "write $(INIT_BIN) /init" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "write $(KERNEL_SRC_BIN) /boot/bzImage" $@ > /dev/null 2>&1 + @$(DEBUGFS) -w -R "write $(GRUB_CFG_SRC) /boot/grub/grub.cfg" $@ > /dev/null 2>&1 + + @for prog in $(USER_NAMES); do \ + $(DEBUGFS) -w -R "write $(USER_OUT_DIR)/$$prog /bin/$$prog" $@ > /dev/null 2>&1; \ + done + +$(HOME_IMG): | dirs + @echo " [HOME] Checking Home Filesystem..." + + @if [ -f $(DISK_IMG) ]; then \ + echo " -> Extracting HomeFS from disk.img (Preserving Data)..."; \ + $(DD) if=$(DISK_IMG) of=$@ bs=512 skip=$(HOME_OFFSET) count=$(HOME_SECTORS); \ + elif [ ! -f $@ ]; then \ + echo " -> Creating new HomeFS (Format)..."; \ + $(DD) if=/dev/zero of=$@ bs=1M count=$(HOME_SIZE_MB); \ + mkfs.ext4 -q -O ^64bit,^metadata_csum $@; \ + else \ + echo " -> Using existing homefs.img"; \ + fi + + +$(DISK_IMG): $(ROOT_IMG) $(HOME_IMG) $(CORE_IMG) + @echo " [ASM] Assembling disk with partitions..." + @$(DD) if=/dev/zero of=$@ bs=1M count=$(DISK_SIZE_MB) + + @echo "start=$(ROOT_OFFSET), size=$(ROOT_SECTORS), type=83, bootable" > partition.script + @echo "start=$(HOME_OFFSET), size=$(HOME_SECTORS), type=83" >> partition.script + @sfdisk $@ < partition.script > /dev/null 2>&1 + @rm partition.script + + @echo " -> Writing RootFS (p1)..." + @$(DD) if=$(ROOT_IMG) of=$@ bs=512 seek=$(ROOT_OFFSET) conv=notrunc + + @echo " -> Writing HomeFS (p2)..." + @$(DD) if=$(HOME_IMG) of=$@ bs=512 seek=$(HOME_OFFSET) conv=notrunc + + @echo " -> Installing GRUB..." + @$(DD) if=$(GRUB_LIB)/boot.img of=$@ bs=446 count=1 conv=notrunc + @$(DD) if=$(CORE_IMG) of=$@ bs=512 seek=1 conv=notrunc + +# $(DISK_IMG): $(PART_IMG) $(CORE_IMG) +# @echo " [ASM] Assembling disk..." +# @$(DD) if=/dev/zero of=$@ bs=1M count=$(DISK_SIZE_MB) +# @echo "2048,,83,*" | sfdisk $@ > /dev/null 2>&1 +# @$(DD) if=$(PART_IMG) of=$@ bs=512 seek=$(SECTOR_OFF) conv=notrunc +# @$(DD) if=$(GRUB_LIB)/boot.img of=$@ bs=446 count=1 conv=notrunc +# @$(DD) if=$(CORE_IMG) of=$@ bs=512 seek=1 conv=notrunc diff --git a/README.md b/README.md new file mode 100644 index 0000000..c00aca4 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Build Linux from Scratch + +## Prerequisites + +```bash +sudo apt install qemu-system-x86 grub-pc-bin e2fsprogs +``` + +## Run + +```bash +gcc -static -o init/init init/init.c + +dd if=/dev/zero of=disk.img bs=1M count=512 status=none +echo -e "n\np\n1\n2048\n\nw" | fdisk disk.img > /dev/null + +dd if=disk.img of=part.img bs=512 skip=2048 status=none + +mkfs.ext4 part.img +debugfs -w -R "mkdir /boot" part.img +debugfs -w -R "mkdir /boot/grub" part.img + +debugfs -w -R "write init/init /init" part.img +debugfs -w -R "write linux/arch/x86/boot/bzImage /boot/bzImage" part.img +debugfs -w -R "write grub/grub.cfg /boot/grub/grub.cfg" part.img + +dd if=part.img of=disk.img bs=512 seek=2048 conv=notrunc status=none + +``` + +Or, alternatively + +```bash +make +``` + + +# `make run` + +```bash +ifconfig eth0 10.0.2.15 +route add default gw 10.0.2.2 + +medit req.txt +GET / HTTP/1.0 +Host: 1.1.1.1 + +:w +:q + +nc 1.1.1.1 80 < req.txt +``` \ No newline at end of file diff --git a/grub/grub.cfg b/grub/grub.cfg new file mode 100644 index 0000000..edd6a89 --- /dev/null +++ b/grub/grub.cfg @@ -0,0 +1,8 @@ +set default=0 +set timeout=0 # 5 +set timeout_style=hidden + +menuentry "MyLinux" { + set root=(hd0,msdos1) + linux /boot/bzImage root=/dev/sda1 rw init=/init console=ttyS0 # quiet +} diff --git a/init/init.c b/init/init.c new file mode 100644 index 0000000..17b5f06 --- /dev/null +++ b/init/init.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include + +void spawn_shell() { + pid_t pid = fork(); + + if (pid == 0) { + char *argv[] = { "gmsh", NULL }; + char *envp[] = { "PATH=/bin", "HOME=/", "TERM=linux", NULL }; + execve("/bin/gmsh", argv, envp); + + perror("[init] Failed to exec gmsh"); + exit(1); + } else if (pid > 0) { + int status; + waitpid(pid, &status, 0); + fprintf(stdout, "[init] gmsh exited (status %d).\n", status); + } else { + perror("[init] fork failed"); + } +} + +int main() { + printf("[init] System Initializing...\n"); + + if (mount("proc", "/proc", "proc", 0, NULL) != 0) + perror("mount /proc"); + + if (mount("sysfs", "/sys", "sysfs", 0, NULL) != 0) + perror("mount /sys"); + + // if (mount("devtmpfs", "/dev", "devtmpfs", 0, NULL) != 0) + // perror("mount /dev"); + + mkdir("/tmp", 0777); + if (mount("tmpfs", "/tmp", "tmpfs", 0, "mode=1777") != 0) { + perror("[init] Failed to mount /tmp"); + } + + mkdir("/home", 0755); + if (mount("/dev/sda2", "/home", "ext4", 0, NULL) != 0) { + perror("[init] Failed to mount /home"); + } + + while (1) spawn_shell(); + + return 0; +} diff --git a/linux b/linux new file mode 160000 index 0000000..9448598 --- /dev/null +++ b/linux @@ -0,0 +1 @@ +Subproject commit 9448598b22c50c8a5bb77a9103e2d49f134c9578 diff --git a/userprog/argdump/Makefile b/userprog/argdump/Makefile new file mode 100644 index 0000000..d7d926f --- /dev/null +++ b/userprog/argdump/Makefile @@ -0,0 +1,15 @@ +TARGET = argdump +SRCS = argdump.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/argdump/argdump.c b/userprog/argdump/argdump.c new file mode 100644 index 0000000..6494e2d --- /dev/null +++ b/userprog/argdump/argdump.c @@ -0,0 +1,16 @@ +#include +#include + +int main(int argc, char *argv[]) { + printf("----------------------------------------\n"); + printf("Executable: '%s'\n", argv[0]); + printf("Total Args: %d\n", argc); + printf("----------------------------------------\n"); + + for (int i = 0; i < argc; i++) { + printf("argv[%d] (len=%2d): [%s]\n", i, (int)strlen(argv[i]), argv[i]); + } + + printf("----------------------------------------\n"); + return 0; +} \ No newline at end of file diff --git a/userprog/bracket/Makefile b/userprog/bracket/Makefile new file mode 100644 index 0000000..197ee29 --- /dev/null +++ b/userprog/bracket/Makefile @@ -0,0 +1,15 @@ +TARGET = bracket +SRCS = bracket.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/bracket/bracket.c b/userprog/bracket/bracket.c new file mode 100644 index 0000000..6917850 --- /dev/null +++ b/userprog/bracket/bracket.c @@ -0,0 +1,108 @@ +#include +#include +#include +#include + +#define MAX_ITEMS 1024 +#define ITEMS_PER_WORD 16 + +typedef struct { + uint32_t data[MAX_ITEMS / ITEMS_PER_WORD]; + int top; +} BitStack; + +void init_stack(BitStack *s) { + s->top = 0; + memset(s->data, 0, sizeof(s->data)); +} + +int encode(char c) { + switch(c) { + case '(': return 0; + case '{': return 1; + case '[': return 2; + default: return 3; + } +} + +char decode(int code) { + switch(code) { + case 0: return '('; + case 1: return '{'; + case 2: return '['; + default: return '?'; + } +} + +bool push(BitStack *s, char c) { + if (s->top >= MAX_ITEMS) return false; + + int code = encode(c); + int word_idx = s->top / ITEMS_PER_WORD; + int bit_offset = (s->top % ITEMS_PER_WORD) * 2; + + s->data[word_idx] &= ~((uint32_t)0x3 << bit_offset); + s->data[word_idx] |= ((uint32_t)code << bit_offset); + + s->top++; + return true; +} + +char pop(BitStack *s) { + if (s->top <= 0) return '\0'; + + s->top--; + + int word_idx = s->top / ITEMS_PER_WORD; + int bit_offset = (s->top % ITEMS_PER_WORD) * 2; + int code = (s->data[word_idx] >> bit_offset) & 0x3; + + return decode(code); +} + +bool is_empty(BitStack *s) { + return s->top == 0; +} + +bool check_bracket(char *str) { + BitStack s; + init_stack(&s); + + int len = strlen(str); + for (int i = 0; i < len; i++) { + char ch = str[i]; + + if (ch == '(' || ch == '{' || ch == '[') { + if (!push(&s, ch)) { + printf("Error: Stack Overflow\n"); + return false; + } + } + else if (ch == ')' || ch == '}' || ch == ']') { + if (is_empty(&s)) return false; + + char open_ch = pop(&s); + + if (ch == ')' && open_ch != '(') return false; + if (ch == '}' && open_ch != '{') return false; + if (ch == ']' && open_ch != '[') return false; + } + } + + return is_empty(&s); +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: bracket \n"); + return 1; + } + + if (check_bracket(argv[1])) { + printf("Valid\n"); + return 0; + } else { + printf("Invalid\n"); + return 1; + } +} \ No newline at end of file diff --git a/userprog/cat/Makefile b/userprog/cat/Makefile new file mode 100644 index 0000000..ee9131c --- /dev/null +++ b/userprog/cat/Makefile @@ -0,0 +1,15 @@ +TARGET = cat +SRCS = cat.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/cat/cat.c b/userprog/cat/cat.c new file mode 100644 index 0000000..46985de --- /dev/null +++ b/userprog/cat/cat.c @@ -0,0 +1,30 @@ +#include +#include +#include + +int main(int argc, char *argv[]) { + char buf[1024]; + int n; + int fd; + + if (argc == 1) { + while ((n = read(STDIN_FILENO, buf, sizeof(buf))) > 0) { + write(STDOUT_FILENO, buf, n); + } + return 0; + } + + for (int i = 1; i < argc; i++) { + fd = open(argv[i], O_RDONLY); + if (fd < 0) { + perror(argv[i]); + continue; + } + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + write(STDOUT_FILENO, buf, n); + } + close(fd); + } + return 0; +} \ No newline at end of file diff --git a/userprog/cp/Makefile b/userprog/cp/Makefile new file mode 100644 index 0000000..ea6f6ea --- /dev/null +++ b/userprog/cp/Makefile @@ -0,0 +1,15 @@ +TARGET = cp +SRCS = cp.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/cp/cp.c b/userprog/cp/cp.c new file mode 100644 index 0000000..e1de0e7 --- /dev/null +++ b/userprog/cp/cp.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +#define BUF_SIZE 4096 + +int main(int argc, char *argv[]) { + if (argc != 3) { + printf("Usage: cp \n"); + return 1; + } + + int src_fd = open(argv[1], O_RDONLY); + if (src_fd < 0) { + perror("cp: source open error"); + return 1; + } + + // dest 파일 생성 (이미 있으면 덮어쓰기) + int dest_fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0644); + if (dest_fd < 0) { + perror("cp: dest open error"); + close(src_fd); + return 1; + } + + char buf[BUF_SIZE]; + int n; + + // EOF(0)가 될 때까지 읽어서 씀 + while ((n = read(src_fd, buf, BUF_SIZE)) > 0) { + if (write(dest_fd, buf, n) != n) { + perror("cp: write error"); + break; + } + } + + close(src_fd); + close(dest_fd); + return 0; +} \ No newline at end of file diff --git a/userprog/debug/Makefile b/userprog/debug/Makefile new file mode 100644 index 0000000..ec588bd --- /dev/null +++ b/userprog/debug/Makefile @@ -0,0 +1,15 @@ +TARGET = debug +SRCS = debug.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/debug/debug.c b/userprog/debug/debug.c new file mode 100644 index 0000000..07f6122 --- /dev/null +++ b/userprog/debug/debug.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("Usage: debug [args...]\n"); + return 1; + } + + struct timeval start, end; + + gettimeofday(&start, NULL); + + pid_t pid = fork(); + + if (pid < 0) { + perror("debug: fork failed"); + return 1; + } + + if (pid == 0) { + execvp(argv[1], &argv[1]); + + perror("debug: exec failed"); + exit(127); + } + else { + int status; + + waitpid(pid, &status, 0); + + gettimeofday(&end, NULL); + + long seconds = end.tv_sec - start.tv_sec; + long micros = end.tv_usec - start.tv_usec; + if (micros < 0) { + seconds -= 1; + micros += 1000000; + } + double elapsed = seconds + micros / 1000000.0; + + printf("\n\033[1;33m[Debug Report]\033[0m\n"); + printf("Target : %s\n", argv[1]); + printf("PID : %d\n", pid); + printf("Time Elapsed : %.6f sec\n", elapsed); + + if (WIFEXITED(status)) { + printf("Exit Code : %d\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("Terminated by Signal : %d\n", WTERMSIG(status)); + } + printf("------------------------------\n"); + } + + return 0; +} \ No newline at end of file diff --git a/userprog/echo/Makefile b/userprog/echo/Makefile new file mode 100644 index 0000000..26eaf5a --- /dev/null +++ b/userprog/echo/Makefile @@ -0,0 +1,15 @@ +TARGET = echo +SRCS = echo.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/echo/echo.c b/userprog/echo/echo.c new file mode 100644 index 0000000..16c9fb7 --- /dev/null +++ b/userprog/echo/echo.c @@ -0,0 +1,25 @@ +#include +#include + +int main(int argc, char *argv[]) { + int start_index = 1; + int no_newline = 0; + + if (argc > 1 && strcmp(argv[1], "-n") == 0) { + no_newline = 1; + start_index = 2; + } + + for (int i = start_index; i < argc; i++) { + printf("%s", argv[i]); + if (i < argc - 1) { + printf(" "); + } + } + + if (!no_newline) { + printf("\n"); + } + + return 0; +} \ No newline at end of file diff --git a/userprog/gmsh/Makefile b/userprog/gmsh/Makefile new file mode 100644 index 0000000..99b28df --- /dev/null +++ b/userprog/gmsh/Makefile @@ -0,0 +1,17 @@ +# userprog/gmsh/Makefile + +TARGET = gmsh +SRCS = gmsh.c builtin.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/gmsh/builtin.c b/userprog/gmsh/builtin.c new file mode 100644 index 0000000..1b9780c --- /dev/null +++ b/userprog/gmsh/builtin.c @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include "para.h" +#include "builtin.h" + + +void cmd_cd(char *path) { + if (!path) path = "/"; + if (chdir(path) != 0) perror("cd"); +} + +void cmd_pwd() { + char buf[1024]; + if(getcwd(buf, sizeof(buf))) printf("%s\n", buf); +} + +void cmd_ls(char *path) { + DIR *d = opendir(path ? path : "."); + struct dirent *dir; + if (d) { + while ((dir = readdir(d)) != NULL) if(dir->d_name[0] != '.') printf("%s ", dir->d_name); + printf("\n"); closedir(d); + } else perror("ls"); +} + +// void cmd_cat(int argc, const char *argv[]) { +// if (argc < 2) { +// printf("Usage: cat [file2] ...\n"); +// return; +// } + +// char buf[1024]; +// int n, fd; + +// for (int i = 1; i < argc; i++) { +// fd = open(argv[i], O_RDONLY); + +// if (fd < 0) { +// perror(argv[i]); +// continue; +// } + +// while ((n = read(fd, buf, sizeof(buf))) > 0) { +// write(STDOUT_FILENO, buf, n); +// } + +// close(fd); +// } +// } + +// void cmd_proc() { +// const int argc = 2; +// const char* args[] = {"cat", "/proc/cpuinfo", NULL}; +// cmd_cat(argc, args); +// } + +// void cmd_echo(int argc, const char *argv[]) { +// int start_index = 1; +// int no_newline = 0; + +// if (argc > 1 && strcmp(argv[1], "-n") == 0) { +// no_newline = 1; +// start_index = 2; +// } + +// for (int i = start_index; i < argc; i++) { +// printf("%s", argv[i]); + +// if (i < argc - 1) { +// printf(" "); +// } +// } + +// if (!no_newline) { +// printf("\n"); +// } + +// fflush(stdout); +// } + +void cmd_poweroff() { sync(); reboot(RB_POWER_OFF); } +void cmd_reboot() { sync(); reboot(RB_AUTOBOOT); } + +void cmd_size() { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { + printf("Terminal Size: %d rows x %d cols\n", ws.ws_row, ws.ws_col); + } else { + perror("size: failed to get window size"); + } +} + +void cmd_resize(char* row, char* col) { + struct winsize ws; + ws.ws_row = atoi(row); + ws.ws_col = atoi(col); + ws.ws_xpixel = 0; + ws.ws_ypixel = 0; + + if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) == 0) { + printf("Terminal resized to %d x %d\n", ws.ws_row, ws.ws_col); + } else { + perror("resize failed"); + } +} + +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++; + } +} diff --git a/userprog/gmsh/builtin.h b/userprog/gmsh/builtin.h new file mode 100644 index 0000000..2ca0e26 --- /dev/null +++ b/userprog/gmsh/builtin.h @@ -0,0 +1,23 @@ +#ifndef BUILTIN_H +#define BUILTIN_H + +void cmd_cd(char *path); +void cmd_pwd(); + +void cmd_ls(char *path); + +// void cmd_cat(int argc, const char* args[]); +// void cmd_proc(); +// void cmd_echo(int argc, const char* args[]); + +void cmd_poweroff(); +void cmd_reboot(); + +void cmd_size(); +void cmd_resize(char* row, char* col); + +void cmd_help(); + +void handle_redirection(const char *argv[]); + +#endif // BUILTIN_H diff --git a/userprog/gmsh/gmsh.c b/userprog/gmsh/gmsh.c new file mode 100644 index 0000000..4af2ae4 --- /dev/null +++ b/userprog/gmsh/gmsh.c @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "builtin.h" + + +static bool verbose = false; + +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; + } + + 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; + } + + ptr++; + } + + *write_ptr = '\0'; + argv[argc++] = token_start; + + if (argc >= MAX_ARGS - 1) break; + } + + argv[argc] = NULL; + return argc; +} + +// 반환값: 1(처리함), 0(파이프 없음) +int handle_pipe(char *argv[]) { + int pipe_idx = -1; + + // 1. 첫 번째 파이프 기호(|) 찾기 + for (int i = 0; argv[i] != NULL; i++) { + if (strcmp(argv[i], "|") == 0) { + pipe_idx = i; + break; + } + } + + // 파이프가 없으면 0 반환 (호출자가 일반 execvp 실행) + if (pipe_idx == -1) return 0; + + // 2. 명령어 쪼개기 + argv[pipe_idx] = NULL; // 파이프 기호를 NULL로 바꿔서 왼쪽 끊음 + char **left_cmd = &argv[0]; + char **right_cmd = &argv[pipe_idx + 1]; + + // 오른쪽 명령어가 없으면 에러 (예: "ls |") + if (right_cmd[0] == NULL) { + printf("gmsh: syntax error near unexpected token `|'\n"); + return 1; + } + + // 3. 파이프 생성 + int fds[2]; + if (pipe(fds) == -1) { + perror("pipe"); + return 1; + } + + // 4. 왼쪽 자식 (Writer) + 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); + } + + // 5. 오른쪽 자식 (Reader + Recursion Manager) + pid_t pid2 = fork(); + if (pid2 == 0) { + close(fds[1]); + dup2(fds[0], STDIN_FILENO); // 입력 연결 + close(fds[0]); + + // [핵심] 오른쪽 명령어에 또 파이프가 있는지 확인! + // 재귀 호출: 만약 파이프가 또 있다면 handle_pipe가 다시 fork를 뜨고 처리함 + 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); + } + } + + // 6. 부모 프로세스 + 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) { + if (getcwd(cwd, sizeof(cwd))) printf("\033[1;32mgmsh\033[0m:%s$ ", cwd); + else printf("gmsh:$ "); + fflush(stdout); + + if (!fgets(cmd_buf, sizeof(cmd_buf), stdin)) break; + 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, " "); + + int argc = parse_cmd(cmd_buf, args); + + if (argc == 0) continue; + + if (handle_pipe(args)) { + continue; + } + + if (verbose) { + printf("[debug] argc: %d\n", argc); + for(int i=0; i \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"); + } + } + } +} + +int main(int argc, char** argv) { + printf("GMSH (GMS Minimal SHell) v1.0 initialized.\n"); + + int opt; + while ((opt = getopt(argc, argv, "v")) != -1) { + switch (opt) { + case 'v': + verbose = 1; + printf("[gmsh] Verbose mode enabled.\n"); + break; + default: + fprintf(stderr, "Usage: %s [-v]\n", argv[0]); + exit(1); + } + } + + run_shell(); + return 0; +} diff --git a/userprog/gmsh/para.h b/userprog/gmsh/para.h new file mode 100644 index 0000000..f212565 --- /dev/null +++ b/userprog/gmsh/para.h @@ -0,0 +1,7 @@ +#ifndef PARA_H +#define PARA_H + +#define MAX_CMD_LEN 1024 +#define MAX_ARGS 64 + +#endif // PARA_H diff --git a/userprog/grep/Makefile b/userprog/grep/Makefile new file mode 100644 index 0000000..bc2db8f --- /dev/null +++ b/userprog/grep/Makefile @@ -0,0 +1,15 @@ +TARGET = grep +SRCS = grep.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/grep/grep.c b/userprog/grep/grep.c new file mode 100644 index 0000000..33e236d --- /dev/null +++ b/userprog/grep/grep.c @@ -0,0 +1,53 @@ +#include +#include +#include + +#define MAX_LINE 1024 + +// 사용법: +// 1. 파일 읽기: grep +// 2. 파이프(Stdin): cat file | grep +int main(int argc, char *argv[]) { + // 최소한 패턴은 있어야 함 + if (argc < 2) { + printf("Usage: grep [filename]\n"); + return 1; + } + + char *pattern = argv[1]; + FILE *fp; + + // 인자가 2개면 (grep pattern) -> 표준 입력(stdin) 사용 + if (argc == 2) { + fp = stdin; + } + // 인자가 3개 이상이면 (grep pattern file) -> 파일 열기 + else { + fp = fopen(argv[2], "r"); + if (!fp) { + perror("grep"); + return 1; + } + } + + char line[MAX_LINE]; + + // fp가 파일이든 stdin이든 똑같이 읽음 + while (fgets(line, sizeof(line), fp)) { + // strstr: 부분 문자열 찾기 + if (strstr(line, pattern) != NULL) { + printf("%s", line); + + // 입력에 개행이 없는 경우(드물지만)를 대비해 안전장치 + // (보통 fgets가 개행까지 읽어오므로 중복 개행 방지 로직 필요시 추가) + // 여기서는 단순하게 그대로 출력 + } + } + + // 파일일 때만 닫아줌 (stdin은 닫으면 안 됨) + if (argc > 2) { + fclose(fp); + } + + return 0; +} \ No newline at end of file diff --git a/userprog/hello/Makefile b/userprog/hello/Makefile new file mode 100644 index 0000000..5c68550 --- /dev/null +++ b/userprog/hello/Makefile @@ -0,0 +1,15 @@ +TARGET = hello +SRCS = hello.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/hello/hello.c b/userprog/hello/hello.c new file mode 100644 index 0000000..5108637 --- /dev/null +++ b/userprog/hello/hello.c @@ -0,0 +1,5 @@ +#include +int main() { + printf("Hello! I am a completely separate program.\n"); + return 0; +} diff --git a/userprog/ifconfig/Makefile b/userprog/ifconfig/Makefile new file mode 100644 index 0000000..a17c318 --- /dev/null +++ b/userprog/ifconfig/Makefile @@ -0,0 +1,15 @@ +TARGET = ifconfig +SRCS = ifconfig.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/ifconfig/ifconfig.c b/userprog/ifconfig/ifconfig.c new file mode 100644 index 0000000..3b0d5e5 --- /dev/null +++ b/userprog/ifconfig/ifconfig.c @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#include // ifreq +#include // inet_pton, sockaddr_in +#include + +// 사용법: ifconfig eth0 10.0.2.15 +int main(int argc, char *argv[]) { + if (argc != 3) { + printf("Usage: ifconfig \n"); + return 1; + } + + char *ifname = argv[1]; + char *ip_str = argv[2]; + + // 1. 커널과 대화하기 위한 소켓 생성 (DGRAM: UDP) + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + // 인터페이스 이름 설정 (예: eth0) + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); + + // 2. IP 주소 설정 (SIOCSIFADDR) + struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr; + addr->sin_family = AF_INET; + + // 문자열 IP -> 바이너리 변환 + if (inet_pton(AF_INET, ip_str, &addr->sin_addr) != 1) { + fprintf(stderr, "Invalid IP format\n"); + close(fd); + return 1; + } + + if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) { + perror("ioctl(SIOCSIFADDR) - Failed to set IP"); + close(fd); + return 1; + } + printf("IP %s assigned to %s\n", ip_str, ifname); + + // 3. 인터페이스 활성화 (UP & RUNNING) (SIOCGIFFLAGS -> SIOCSIFFLAGS) + // 현재 플래그 가져오기 + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { + perror("ioctl(SIOCGIFFLAGS)"); + close(fd); + return 1; + } + + // UP 플래그와 RUNNING 플래그 추가 + ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); + + // 플래그 다시 설정 + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) { + perror("ioctl(SIOCSIFFLAGS) - Failed to set UP"); + close(fd); + return 1; + } + printf("Interface %s is now UP\n", ifname); + + close(fd); + return 0; +} \ No newline at end of file diff --git a/userprog/medit/Makefile b/userprog/medit/Makefile new file mode 100644 index 0000000..e12a65b --- /dev/null +++ b/userprog/medit/Makefile @@ -0,0 +1,15 @@ +TARGET = medit +SRCS = medit.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/medit/medit.c b/userprog/medit/medit.c new file mode 100644 index 0000000..2c3b1b5 --- /dev/null +++ b/userprog/medit/medit.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +#define MAX_LINES 1000 +#define MAX_LEN 128 + +char buffer[MAX_LINES][MAX_LEN]; +int line_count = 0; +char filename[64]; + +// 화면 지우기 (ANSI Code) +void clear_screen() { + printf("\033[2J\033[H"); +} + +// 현재 버퍼 내용 출력 +void print_buffer() { + clear_screen(); + printf("\033[1;33m=== %s ===\033[0m\n", filename); + for (int i = 0; i < line_count; i++) { + printf("\033[1;34m%3d |\033[0m %s\n", i + 1, buffer[i]); + } + printf("---------------------------------------------------\n"); + printf("[Type text to append] [Cmds: :w (save), :q (quit), :d (del)]\n"); +} + +// 파일 불러오기 +void load_file() { + int fd = open(filename, O_RDONLY); + if (fd < 0) return; // 파일 없으면 새로 생성 + + char ch; + int buf_idx = 0; + while (read(fd, &ch, 1) > 0) { + if (ch == '\n') { + buffer[line_count][buf_idx] = '\0'; + line_count++; + buf_idx = 0; + if (line_count >= MAX_LINES) break; + } else { + if (buf_idx < MAX_LEN - 1) { + buffer[line_count][buf_idx++] = ch; + } + } + } + close(fd); +} + +// 파일 저장하기 +void save_file() { + int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror("save failed"); + return; + } + + for (int i = 0; i < line_count; i++) { + write(fd, buffer[i], strlen(buffer[i])); + write(fd, "\n", 1); + } + close(fd); + printf("Saved to %s\n", filename); + sleep(1); // 저장 메시지 확인용 +} + +// 라인 삭제 +void delete_line(int line_num) { + if (line_num < 1 || line_num > line_count) { + printf("Invalid line number\n"); + sleep(1); + return; + } + + // 뒤의 라인들을 앞으로 당김 + for (int i = line_num - 1; i < line_count - 1; i++) { + strcpy(buffer[i], buffer[i+1]); + } + line_count--; +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + printf("Usage: medit \n"); + return 1; + } + + strncpy(filename, argv[1], 63); + load_file(); + + char input[MAX_LEN + 10]; // 명령어 포함 여유분 + + while (1) { + print_buffer(); + printf("> "); + + if (fgets(input, sizeof(input), stdin) == NULL) break; + input[strcspn(input, "\n")] = 0; // 개행 제거 + + // 명령어 처리 (:) + if (input[0] == ':') { + if (strcmp(input, ":q") == 0) { + break; + } else if (strcmp(input, ":w") == 0) { + save_file(); + } else if (strncmp(input, ":d", 2) == 0) { + int ln = atoi(input + 3); + delete_line(ln); + } else { + printf("Unknown command. :w, :q, :d \n"); + sleep(1); + } + } + // 일반 텍스트 입력 (추가) + else { + if (line_count < MAX_LINES) { + strncpy(buffer[line_count], input, MAX_LEN - 1); + buffer[line_count][MAX_LEN - 1] = '\0'; + line_count++; + } else { + printf("Buffer full!\n"); + sleep(1); + } + } + } + + return 0; +} \ No newline at end of file diff --git a/userprog/mkdir/Makefile b/userprog/mkdir/Makefile new file mode 100644 index 0000000..ec02d4a --- /dev/null +++ b/userprog/mkdir/Makefile @@ -0,0 +1,15 @@ +TARGET = mkdir +SRCS = mkdir.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/mkdir/mkdir.c b/userprog/mkdir/mkdir.c new file mode 100644 index 0000000..8e3b415 --- /dev/null +++ b/userprog/mkdir/mkdir.c @@ -0,0 +1,18 @@ +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: mkdir \n"); + return 1; + } + + // 0755: 권한 (rwxr-xr-x) + if (mkdir(argv[1], 0755) < 0) { + perror("mkdir"); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/userprog/nc/Makefile b/userprog/nc/Makefile new file mode 100644 index 0000000..3a08364 --- /dev/null +++ b/userprog/nc/Makefile @@ -0,0 +1,15 @@ +TARGET = nc +SRCS = nc.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/nc/nc.c b/userprog/nc/nc.c new file mode 100644 index 0000000..ef3ef53 --- /dev/null +++ b/userprog/nc/nc.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 1024 + +// 에러 처리 헬퍼 +void error_exit(char *msg) { + perror(msg); + exit(1); +} + +void loop(int sockfd) { + fd_set read_fds; + char buf[BUF_SIZE]; + int max_fd = sockfd > STDIN_FILENO ? sockfd : STDIN_FILENO; + int stdin_eof = 0; // [NEW] 입력 종료 여부 플래그 + + while (1) { + FD_ZERO(&read_fds); + + // [NEW] 입력이 아직 안 끝났을 때만 STDIN 감시 + if (!stdin_eof) { + FD_SET(STDIN_FILENO, &read_fds); + } + FD_SET(sockfd, &read_fds); // 소켓은 항상 감시 + + if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) continue; + error_exit("select"); + } + + // 1. 키보드/파일 입력 -> 소켓 전송 + if (!stdin_eof && FD_ISSET(STDIN_FILENO, &read_fds)) { + int n = read(STDIN_FILENO, buf, BUF_SIZE); + + if (n < 0) error_exit("read stdin"); + + if (n == 0) { + // [NEW] EOF(파일 끝) 도달 시: + // 즉시 종료하지 않고, "나는 보낼 거 다 보냈다"고 표시만 함 + stdin_eof = 1; + + // (선택사항) TCP Half-Close: 서버에게 "보낼 거 끝났다"고 알림 + shutdown(sockfd, SHUT_WR); + } else { + if (write(sockfd, buf, n) < 0) error_exit("write to socket"); + } + } + + // 2. 소켓 수신 -> 화면 출력 + if (FD_ISSET(sockfd, &read_fds)) { + int n = read(sockfd, buf, BUF_SIZE); + + if (n < 0) error_exit("read socket"); + + if (n == 0) { + // [NEW] 서버가 연결을 끊었을 때 비로소 루프 종료! + break; + } + if (write(STDOUT_FILENO, buf, n) < 0) error_exit("write to stdout"); + } + } +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + printf("Usage:\n"); + printf(" Connect: nc \n"); + printf(" Listen : nc -l \n"); + return 1; + } + + int sockfd; + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + + // --- Server Mode (-l) --- + if (strcmp(argv[1], "-l") == 0) { + int port = atoi(argv[2]); + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) error_exit("socket"); + + // 주소 재사용 허용 (Time-wait 방지) + int opt = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) error_exit("bind"); + if (listen(listen_fd, 1) < 0) error_exit("listen"); + + printf("Listening on port %d...\n", port); + + struct sockaddr_in client_addr; + socklen_t client_len = sizeof(client_addr); + sockfd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len); + if (sockfd < 0) error_exit("accept"); + + printf("Connection from %s\n", inet_ntoa(client_addr.sin_addr)); + close(listen_fd); // 더 이상 리스닝 안 함 (1:1 채팅) + } + + // --- Client Mode --- + else { + char *ip = argv[1]; + int port = atoi(argv[2]); + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) error_exit("socket"); + + addr.sin_port = htons(port); + if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) error_exit("invalid address"); + + printf("Connecting to %s:%d...\n", ip, port); + if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) error_exit("connect"); + printf("Connected!\n"); + } + + // 데이터 교환 루프 진입 + loop(sockfd); + + close(sockfd); + return 0; +} \ No newline at end of file diff --git a/userprog/rm/Makefile b/userprog/rm/Makefile new file mode 100644 index 0000000..aa6ca27 --- /dev/null +++ b/userprog/rm/Makefile @@ -0,0 +1,15 @@ +TARGET = rm +SRCS = rm.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/rm/rm.c b/userprog/rm/rm.c new file mode 100644 index 0000000..eb46745 --- /dev/null +++ b/userprog/rm/rm.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: rm \n"); + return 1; + } + + if (unlink(argv[1]) < 0) { + perror("rm"); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/userprog/route/Makefile b/userprog/route/Makefile new file mode 100644 index 0000000..776e488 --- /dev/null +++ b/userprog/route/Makefile @@ -0,0 +1,15 @@ +TARGET = route +SRCS = route.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/route/route.c b/userprog/route/route.c new file mode 100644 index 0000000..3ca4757 --- /dev/null +++ b/userprog/route/route.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include // struct rtentry +#include + +// 사용법: route add default gw +int main(int argc, char *argv[]) { + // 파싱을 간단하게 하기 위해 고정된 포맷만 지원 + if (argc != 5 || strcmp(argv[1], "add") != 0 || + strcmp(argv[2], "default") != 0 || strcmp(argv[3], "gw") != 0) { + printf("Usage: route add default gw \n"); + return 1; + } + + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) { + perror("socket"); + return 1; + } + + struct rtentry rt; + memset(&rt, 0, sizeof(rt)); + + // 1. 목적지 (Destination): 0.0.0.0 (모든 주소) + struct sockaddr_in *dst = (struct sockaddr_in *)&rt.rt_dst; + dst->sin_family = AF_INET; + dst->sin_addr.s_addr = INADDR_ANY; + + // 2. 마스크 (Genmask): 0.0.0.0 (모든 비트 허용) + struct sockaddr_in *mask = (struct sockaddr_in *)&rt.rt_genmask; + mask->sin_family = AF_INET; + mask->sin_addr.s_addr = INADDR_ANY; + + // 3. 게이트웨이 (Gateway): 입력받은 IP (예: 10.0.2.2) + struct sockaddr_in *gw = (struct sockaddr_in *)&rt.rt_gateway; + gw->sin_family = AF_INET; + if (inet_pton(AF_INET, argv[4], &gw->sin_addr) <= 0) { + printf("Invalid Gateway IP\n"); + close(sockfd); + return 1; + } + + // 4. 플래그 설정 + // RTF_UP: 경로 활성화 + // RTF_GATEWAY: 목적지가 게이트웨이임 + rt.rt_flags = RTF_UP | RTF_GATEWAY; + + // 5. 커널에 라우팅 테이블 추가 요청 (SIOCADDRT) + if (ioctl(sockfd, SIOCADDRT, &rt) < 0) { + perror("route add"); + close(sockfd); + return 1; + } + + printf("Default gateway set to %s\n", argv[4]); + close(sockfd); + return 0; +} \ No newline at end of file diff --git a/userprog/sui/Makefile b/userprog/sui/Makefile new file mode 100644 index 0000000..345dc2a --- /dev/null +++ b/userprog/sui/Makefile @@ -0,0 +1,15 @@ +TARGET = sui +SRCS = sui.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/sui/sui.c b/userprog/sui/sui.c new file mode 100644 index 0000000..49cc0f4 --- /dev/null +++ b/userprog/sui/sui.c @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, char** argv) { + if(argc <= 1) return 0; + for(int i=0;i +#include +#include +#include + +int main(int argc, char *argv[]) { + if (argc != 2) { + printf("Usage: touch \n"); + return 1; + } + + // O_CREAT: 없으면 생성 + // O_TRUNC: 있으면 내용을 비움 (0바이트로 만듦) + // 0644: 파일 권한 (rw-r--r--) + int fd = open(argv[1], O_CREAT | O_TRUNC | O_WRONLY, 0644); + + if (fd < 0) { + perror("touch"); + return 1; + } + + close(fd); + return 0; +} \ No newline at end of file diff --git a/userprog/wg/Makefile b/userprog/wg/Makefile new file mode 100644 index 0000000..a20e29f --- /dev/null +++ b/userprog/wg/Makefile @@ -0,0 +1,15 @@ +TARGET = wg +SRCS = wg.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + @echo " [CC] Compiling to $(OUT_BIN)..." + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) diff --git a/userprog/wg/wg.c b/userprog/wg/wg.c new file mode 100644 index 0000000..1a20669 --- /dev/null +++ b/userprog/wg/wg.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUF_SIZE 4096 + +// 요청 메시지 구조체 (패딩 문제 방지를 위해 바이트 배열로 관리) +struct req_t { + struct nlmsghdr n; + char buf[BUF_SIZE]; +}; + +// 속성 추가 (RTA_APPEND) +// 커널의 표준 방식대로 테일 포인터를 이동시키며 추가 +int addattr_l(struct req_t *req, int type, const void *data, int alen) { + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + // 현재 메시지 길이 (헤더 포함) + int msg_len = req->n.nlmsg_len; + + if (NLMSG_ALIGN(msg_len) + RTA_ALIGN(len) > sizeof(struct req_t)) { + fprintf(stderr, "addattr_l: Message too long\n"); + return -1; + } + + // 데이터가 들어갈 위치 계산 + rta = (struct rtattr *)(((char *)&req->n) + NLMSG_ALIGN(msg_len)); + rta->rta_type = type; + rta->rta_len = len; + + if (alen) { + memcpy(RTA_DATA(rta), data, alen); + } + + // 메시지 전체 길이 업데이트 + req->n.nlmsg_len = NLMSG_ALIGN(msg_len) + RTA_ALIGN(len); + return 0; +} + +// 중첩 속성 시작 (Nested Start) +struct rtattr *addattr_nest(struct req_t *req, int type) { + struct rtattr *nest = (struct rtattr *)(((char *)&req->n) + NLMSG_ALIGN(req->n.nlmsg_len)); + + // 빈 속성 추가 (일단 데이터 없이) + if (addattr_l(req, type, NULL, 0) < 0) return NULL; + + return nest; +} + +// 중첩 속성 끝 (Nested End) +void addattr_nest_end(struct req_t *req, struct rtattr *nest) { + // 중첩 속성의 길이는 (현재 전체 길이) - (중첩 속성 시작 위치) + nest->rta_len = (char *)&req->n + req->n.nlmsg_len - (char *)nest; +} + +int main(int argc, char *argv[]) { + if (argc != 3 || strcmp(argv[1], "create") != 0) { + printf("Usage: wg create \n"); + return 1; + } + + char *ifname = argv[2]; + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { perror("socket"); return 1; } + + struct req_t req; + memset(&req, 0, sizeof(req)); + + // 1. 헤더 설정 + // 초기 길이는 헤더 + ifinfomsg 구조체 크기 + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.n.nlmsg_type = RTM_NEWLINK; + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; + + struct ifinfomsg *ifi = (struct ifinfomsg *)NLMSG_DATA(&req.n); + ifi->ifi_family = AF_UNSPEC; + + // 2. IFLA_IFNAME: 인터페이스 이름 ("wg0") + // 문자열은 반드시 NULL 문자 포함 길이 (+1) + addattr_l(&req, IFLA_IFNAME, ifname, strlen(ifname) + 1); + + // 3. IFLA_LINKINFO: 링크 정보 시작 + struct rtattr *linkinfo = addattr_nest(&req, IFLA_LINKINFO); + + // 4. IFLA_INFO_KIND: 타입 ("wireguard") + addattr_l(&req, IFLA_INFO_KIND, "wireguard", strlen("wireguard") + 1); + + // 링크 정보 종료 + addattr_nest_end(&req, linkinfo); + + // 디버깅: 전송할 패킷 크기 출력 + // printf("Sending Netlink Msg: %d bytes\n", req.n.nlmsg_len); + + // 5. 전송 + if (send(fd, &req.n, req.n.nlmsg_len, 0) < 0) { + perror("send"); + close(fd); + return 1; + } + + // 6. 응답 수신 + char buf[BUF_SIZE]; + int len = recv(fd, buf, sizeof(buf), 0); + if (len < 0) { perror("recv"); return 1; } + + struct nlmsghdr *nh = (struct nlmsghdr *)buf; + if (nh->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nh); + if (err->error == 0) { + printf("WireGuard interface '%s' created successfully!\n", ifname); + } else { + fprintf(stderr, "RTNETLINK error: %s (%d)\n", strerror(-err->error), -err->error); + return 1; + } + } + + close(fd); + return 0; +} \ No newline at end of file diff --git a/userprog/xxd/Makefile b/userprog/xxd/Makefile new file mode 100644 index 0000000..e41d4f1 --- /dev/null +++ b/userprog/xxd/Makefile @@ -0,0 +1,14 @@ +TARGET = xxd +SRCS = xxd.c +CC = gcc +CFLAGS = -static -Wall + +OUT_BIN ?= $(TARGET) + +all: $(OUT_BIN) + +$(OUT_BIN): $(SRCS) + $(CC) $(CFLAGS) -o $@ $^ + +clean: + rm -f $(TARGET) *.o \ No newline at end of file diff --git a/userprog/xxd/xxd.c b/userprog/xxd/xxd.c new file mode 100644 index 0000000..98391f2 --- /dev/null +++ b/userprog/xxd/xxd.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include + +#define MAX_COLS 256 + +int main(int argc, char *argv[]) { + int fd = 0; + unsigned char buf[MAX_COLS]; + + int n; + unsigned int offset = 0; + + int cols = 16; + char *filename = NULL; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-l") == 0) { + if (i + 1 < argc) { + cols = atoi(argv[++i]); + if (cols <= 0 || cols > MAX_COLS) { + printf("xxd: invalid column length (1-%d)\n", MAX_COLS); + return 1; + } + } else { + printf("xxd: option -l requires an argument\n"); + return 1; + } + } else { + filename = argv[i]; + } + } + + // 2. 파일 열기 + if (filename != NULL) { + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("xxd"); + return 1; + } + } + + // 3. 읽기 및 출력 루프 + // sizeof(buf)가 아니라 설정된 cols 만큼 읽어야 함 + while ((n = read(fd, buf, cols)) > 0) { + + // (1) 오프셋 출력 + printf("%08x: ", offset); + + // (2) Hex 데이터 출력 + for (int i = 0; i < cols; i++) { + // 2바이트마다 그룹핑 (가독성) + if (i % 2 == 0) printf(" "); + + if (i < n) { + // 데이터가 있으면 출력 + printf("%02x", buf[i]); + } else { + // 데이터가 없으면 공백 채움 (Padding) + printf(" "); + } + } + + printf(" "); + + // (3) ASCII 문자 출력 + for (int i = 0; i < n; i++) { + if (isprint(buf[i])) { + printf("%c", buf[i]); + } else { + printf("."); + } + } + + printf("\n"); + offset += n; + } + + if (filename != NULL) close(fd); + return 0; +} \ No newline at end of file