From 79f46aa9717f0a3fbe5010f6b8fe80357fb22787 Mon Sep 17 00:00:00 2001 From: Minseong Gwak Date: Thu, 1 Jan 2026 04:22:30 +0000 Subject: [PATCH] update: userprog (tee, tsh, sleep, hex2bin) --- .gitignore | 2 + Makefile | 58 +-- README.md | 84 ++++ free/asm/build.sh | 4 + free/asm/build2.sh | 4 + free/asm/free.s | 5 + free/asm/free32.s | 5 + free/asm/vuln/build.sh | 7 + free/asm/vuln/vuln.s | 62 +++ free/build.sh | 3 +- free/build2.sh | 4 + qemu/third/mybios.asm | 2 +- qemu/third/mybios2.asm | 2 +- userprog/gmsh/gmsh.c | 17 +- userprog/hex2bin/Makefile | 15 + userprog/hex2bin/hex2bin.c | 13 + userprog/sleep/Makefile | 15 + userprog/sleep/sleep.c | 41 ++ userprog/tee/Makefile | 15 + userprog/tee/tee.c | 103 +++++ userprog/tsh/Makefile | 15 + userprog/tsh/tsh.c | 767 +++++++++++++++++++++++++++++++++++++ 22 files changed, 1196 insertions(+), 47 deletions(-) create mode 100755 free/asm/build.sh create mode 100755 free/asm/build2.sh create mode 100644 free/asm/free.s create mode 100644 free/asm/free32.s create mode 100755 free/asm/vuln/build.sh create mode 100644 free/asm/vuln/vuln.s create mode 100755 free/build2.sh create mode 100644 userprog/hex2bin/Makefile create mode 100644 userprog/hex2bin/hex2bin.c create mode 100644 userprog/sleep/Makefile create mode 100644 userprog/sleep/sleep.c create mode 100644 userprog/tee/Makefile create mode 100644 userprog/tee/tee.c create mode 100644 userprog/tsh/Makefile create mode 100644 userprog/tsh/tsh.c diff --git a/.gitignore b/.gitignore index d973b34..93f9aa4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ build/ .temp +ext/ +.gdb_history *.swp *~ diff --git a/Makefile b/Makefile index 8cb091c..41208ec 100644 --- a/Makefile +++ b/Makefile @@ -10,12 +10,10 @@ 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 @@ -30,10 +28,11 @@ 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 @@ -45,7 +44,7 @@ CORE_IMG = $(BUILD_DIR)/core.img # ============================================================================== # 2. Main Targets # ============================================================================== -.PHONY: all clean run dirs help force_look +.PHONY: all clean run dirs help all: dirs $(DISK_IMG) @echo " [DONE] Image ready: $(DISK_IMG)" @@ -53,17 +52,21 @@ all: dirs $(DISK_IMG) run: $(DISK_IMG) qemu-system-x86_64 -drive file=$(DISK_IMG),format=raw -nographic \ -net nic,model=e1000 -net user + @echo " [SYNC] Saving session data to homefs.img..." + @$(DD) if=$(DISK_IMG) of=$(HOME_IMG) bs=512 skip=$(HOME_OFFSET) count=$(HOME_SECTORS) status=none + @echo " -> Data saved successfully." 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) +# @rm -rf $(BUILD_DIR) + @if [ -d $(BUILD_DIR) ]; then \ + find $(BUILD_DIR) -type f ! -name "homefs.img" -delete; \ + fi + @echo " [CLEAN] Cleaning user programs..." - @# 각 유저 프로그램 폴더 들어가서 make clean 실행 @for dir in $(USER_DIRS); do \ if [ -f $$dir/Makefile ]; then $(MAKE) -C $$dir clean; fi; \ done @@ -108,6 +111,7 @@ $(ROOT_IMG): $(INIT_BIN) $(USER_BINS) $(KERNEL_SRC_BIN) $(GRUB_CFG_SRC) | dirs @$(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 + @$(DEBUGFS) -w -R "mkdir /ext" $@ > /dev/null 2>&1 @echo " -> Injecting System Binaries..." @$(DEBUGFS) -w -R "write $(INIT_BIN) /init" $@ > /dev/null 2>&1 @@ -118,20 +122,24 @@ $(ROOT_IMG): $(INIT_BIN) $(USER_BINS) $(KERNEL_SRC_BIN) $(GRUB_CFG_SRC) | dirs $(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"; \ + @if [ -d ext ]; then \ + echo " -> Injecting external files from ./ext..."; \ + for file in ext/*; do \ + if [ -f "$$file" ]; then \ + fname=$$(basename "$$file"); \ + $(DEBUGFS) -w -R "write $$file /ext/$$fname" $@ > /dev/null 2>&1; \ + fi; \ + done; \ fi +$(HOME_IMG): | dirs + @if [ ! -f $@ ]; then \ + echo " [HOME] Creating NEW Home Filesystem..."; \ + $(DD) if=/dev/zero of=$@ bs=1M count=$(HOME_SIZE_MB) status=none; \ + mkfs.ext4 -q -O ^64bit,^metadata_csum $@; \ + else \ + echo " [HOME] Using existing homefs.img."; \ + fi $(DISK_IMG): $(ROOT_IMG) $(HOME_IMG) $(CORE_IMG) @echo " [ASM] Assembling disk with partitions..." @@ -150,12 +158,4 @@ $(DISK_IMG): $(ROOT_IMG) $(HOME_IMG) $(CORE_IMG) @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 + @$(DD) if=$(CORE_IMG) of=$@ bs=512 seek=1 conv=notrunc \ No newline at end of file diff --git a/README.md b/README.md index c00aca4..c81b3ee 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,90 @@ Or, alternatively make ``` +## disk.img structure +### 1. 디스크 전체 구조도 (Overview) + +```text +[ MBR ] [ GRUB Core ] [ Partition 1 (RootFS) ] [ Partition 2 (HomeFS) ] [ Free Space ] + | | | | | +Sec 0 Sec 1 Sec 2048 Sec 526336 Sec 788480 +(0B) (512B) (1MB) (257MB) (385MB) + +``` + +--- + +### 2. 구역별 상세 분석 (Byte-level Breakdown) + +모든 섹터 크기는 **512 Bytes**입니다. + +#### ① 섹터 0: MBR (Master Boot Record) + +* **위치:** `0` ~ `511` bytes (총 512 bytes) +* **구성:** +* `0 ~ 445` (446 bytes): **GRUB Stage 1 (`boot.img`)**. 부팅 시 가장 먼저 실행되는 기계어 코드. +* `446 ~ 509` (64 bytes): **파티션 테이블**. (`sfdisk`가 작성함). 파티션 1, 2의 정보가 16바이트씩 기록됨. +* `510 ~ 511` (2 bytes): **부트 시그니처 (`0x55 0xAA`)**. BIOS가 "이건 부팅 가능한 디스크다"라고 인식하는 도장. + + + +#### ② 섹터 1 ~ 2047: GRUB Core & Gap (예비 공간) + +* **위치:** `512` ~ `1,048,575` bytes (약 1MB 구간) +* **설명:** +* MBR과 첫 번째 파티션 사이의 **"빈 공간(Gap)"**입니다. +* **섹터 1 (`seek=1`)**부터 **GRUB Stage 1.5/2 (`core.img`)**가 들어갑니다. +* 파일 시스템이 없는 영역(`Raw Data`)이므로, GRUB이 자신을 여기에 숨겨놓고 MBR에서 점프해 옵니다. +* 현대적인 파티셔닝 툴은 성능 최적화(Alignment)를 위해 첫 파티션을 1MB(`2048 sector`)부터 시작하게 잡는데, 그 남는 공간을 알뜰하게 쓰는 겁니다. + + + +#### ③ 섹터 2048 ~ 526,335: Partition 1 (/ - RootFS) + +* **위치:** `1,048,576` bytes (1MB 지점) ~ `269,484,031` bytes +* **크기:** **256MB** (`256 * 1024 * 1024` bytes) +* **내용:** `rootfs.img`의 내용이 비트 단위로 그대로 복사됨. +* **파일 시스템:** Ext4 +* **내용물:** `/bin`, `/boot(bzImage)`, `/init`, `/dev` 등 OS 시스템 파일. +* **특징:** 매번 빌드할 때마다 새로 포맷되고 덮어씌워짐. + + + +#### ④ 섹터 526,336 ~ 788,479: Partition 2 (/home - HomeFS) + +* **위치:** `269,484,032` bytes (약 257MB 지점) ~ `403,701,759` bytes +* **시작점 계산:** `ROOT_OFFSET(2048)` + `ROOT_SECTORS(256MB어치)` = `526336` 섹터. +* **크기:** **128MB** (`128 * 1024 * 1024` bytes) +* **내용:** `homefs.img`의 내용. +* **파일 시스템:** Ext4 +* **내용물:** 사용자 텍스트 파일, 소스 코드 등. +* **특징:** `make run` 종료 시 `disk.img`에서 다시 백업받아 보존됨. + + + +#### ⑤ 섹터 788,480 ~ 1,048,575: Unallocated Space (빈 공간) + +* **위치:** `403,701,760` bytes ~ `536,870,911` bytes (끝) +* **크기:** **127MB** (총 512MB - 1MB(Gap) - 256MB(Root) - 128MB(Home)) +* **설명:** +* 현재 파티션이 잡혀있지 않은 **잉여 공간**입니다. +* 나중에 스왑 파티션을 만들거나, 제3의 파티션을 만들 때 쓸 수 있습니다. +* 지금은 그냥 `0`으로 채워져 있습니다. + + + +--- + +### 3. 요약표 (한눈에 보기) + +| 구역 | 시작 섹터 | 시작 바이트 (Offset) | 크기 | 용도 | +| --- | --- | --- | --- | --- | +| **MBR** | 0 | 0 (`0x0`) | 512 B | 부트로더(Stage1) + 파티션 테이블 | +| **Gap** | 1 | 512 (`0x200`) | ~1 MB | **GRUB Core (`core.img`)** | +| **P1** | 2048 | 1,048,576 (`0x100000`) | 256 MB | **Root Filesystem (System)** | +| **P2** | 526336 | 269,484,032 (`0x10100000`) | 128 MB | **Home Filesystem (User)** | +| **Free** | 788480 | 403,701,760 | 127 MB | (사용 안 함) | + # `make run` diff --git a/free/asm/build.sh b/free/asm/build.sh new file mode 100755 index 0000000..99a8e4a --- /dev/null +++ b/free/asm/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +as -o free.o free.s +ld -s -o free free.o \ No newline at end of file diff --git a/free/asm/build2.sh b/free/asm/build2.sh new file mode 100755 index 0000000..0431af2 --- /dev/null +++ b/free/asm/build2.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +as --32 -o free32.o free32.s +ld -m elf_i386 -s -N -o free32 free32.o \ No newline at end of file diff --git a/free/asm/free.s b/free/asm/free.s new file mode 100644 index 0000000..add435e --- /dev/null +++ b/free/asm/free.s @@ -0,0 +1,5 @@ +.global _start +_start: + mov $60, %rax # sys_exit (64-bit) + mov $0, %rdi # status 0 + syscall diff --git a/free/asm/free32.s b/free/asm/free32.s new file mode 100644 index 0000000..3134330 --- /dev/null +++ b/free/asm/free32.s @@ -0,0 +1,5 @@ +.global _start +_start: + xor %ebx, %ebx + mov $1, %eax + int $0x80 diff --git a/free/asm/vuln/build.sh b/free/asm/vuln/build.sh new file mode 100755 index 0000000..d3c842c --- /dev/null +++ b/free/asm/vuln/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh +as --32 -o vuln.o vuln.s + +# -N 옵션을 줘서 코드 섹션도 읽기/쓰기 가능하게 하면 쉘코드 실습도 가능하지만, +# 지금은 RET Overwrite만 할 거라 기본 옵션도 상관없음. +ld -m elf_i386 -s -N -o vuln vuln.o +rm vuln.o diff --git a/free/asm/vuln/vuln.s b/free/asm/vuln/vuln.s new file mode 100644 index 0000000..5d703cf --- /dev/null +++ b/free/asm/vuln/vuln.s @@ -0,0 +1,62 @@ +.section .data + msg_normal: .ascii "Normal exit.\n" + msg_secret: .ascii "SUCCESS! Secret function executed!\n" + +.section .text +.global _start + +# -------------------------------------------------------- +# [타겟 함수] 정상적인 흐름으로는 절대 호출되지 않음 +# -------------------------------------------------------- +secret_function: + # "SUCCESS! ..." 출력 + mov $4, %eax # sys_write + mov $1, %ebx # stdout + mov $msg_secret, %ecx + mov $35, %edx # length + int $0x80 + + # 즉시 종료 + xor %ebx, %ebx + mov $1, %eax # sys_exit + int $0x80 + +# -------------------------------------------------------- +# [취약한 함수] 4바이트 버퍼에 100바이트를 들이부음 +# -------------------------------------------------------- +vuln_func: + # 스택 프레임 확보 (겨우 4바이트) + sub $4, %esp + + # sys_read(stdin, buffer=esp, len=100) + mov $3, %eax # sys_read + xor %ebx, %ebx # stdin (0) + mov %esp, %ecx # buffer (현재 스택 포인터 위치) + mov $100, %edx # [취약점] 4바이트 방에 100명을 초대함! + int $0x80 + + # 스택 정리 + add $4, %esp + + # [여기가 핵심] + # 스택에서 리턴 주소(RET)를 꺼내서 점프하는데, + # 위에서 오버플로우로 RET가 조작되었다면? -> 펑! + ret + +# -------------------------------------------------------- +# [메인 진입점] +# -------------------------------------------------------- +_start: + call vuln_func + + # 정상 종료 메시지 출력 (익스플로잇 실패 시 여기로 옴) + mov $4, %eax + mov $1, %ebx + mov $msg_normal, %ecx + mov $13, %edx + int $0x80 + + # 종료 + xor %ebx, %ebx + mov $1, %eax + int $0x80 \ No newline at end of file diff --git a/free/build.sh b/free/build.sh index 1710f48..2639744 100755 --- a/free/build.sh +++ b/free/build.sh @@ -1,3 +1,4 @@ #!/bin/sh -g++ -m32 -c -o free.o free.cpp -ffreestanding && ld -m elf_i386 -o free free.o +g++ -m32 -c -o free.o free.cpp -ffreestanding -nostdlib +ld -m elf_i386 -o free free.o diff --git a/free/build2.sh b/free/build2.sh new file mode 100755 index 0000000..2ad1d83 --- /dev/null +++ b/free/build2.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +gcc -m32 -c -o free.o free.cpp -ffreestanding -nostdlib +ld -m elf_i386 -o free free.o diff --git a/qemu/third/mybios.asm b/qemu/third/mybios.asm index 8f1c322..88c45cb 100644 --- a/qemu/third/mybios.asm +++ b/qemu/third/mybios.asm @@ -1,4 +1,4 @@ -; mybios_ssh.asm +; mybios.asm [BITS 16] [ORG 0] diff --git a/qemu/third/mybios2.asm b/qemu/third/mybios2.asm index 51efffe..db65bed 100644 --- a/qemu/third/mybios2.asm +++ b/qemu/third/mybios2.asm @@ -1,4 +1,4 @@ -; mybios_real.asm +; mybios2.asm [BITS 16] [ORG 0] diff --git a/userprog/gmsh/gmsh.c b/userprog/gmsh/gmsh.c index 4af2ae4..74c428e 100644 --- a/userprog/gmsh/gmsh.c +++ b/userprog/gmsh/gmsh.c @@ -60,7 +60,6 @@ int parse_cmd(char *cmd, char *argv[]) { 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; @@ -68,28 +67,23 @@ int handle_pipe(char *argv[]) { } } - // 파이프가 없으면 0 반환 (호출자가 일반 execvp 실행) if (pipe_idx == -1) return 0; - // 2. 명령어 쪼개기 - argv[pipe_idx] = NULL; // 파이프 기호를 NULL로 바꿔서 왼쪽 끊음 + argv[pipe_idx] = 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]); @@ -102,22 +96,16 @@ int handle_pipe(char *argv[]) { exit(1); } - // 5. 오른쪽 자식 (Reader + Recursion Manager) pid_t pid2 = fork(); if (pid2 == 0) { close(fds[1]); - dup2(fds[0], STDIN_FILENO); // 입력 연결 + 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"); @@ -125,7 +113,6 @@ int handle_pipe(char *argv[]) { } } - // 6. 부모 프로세스 close(fds[0]); close(fds[1]); diff --git a/userprog/hex2bin/Makefile b/userprog/hex2bin/Makefile new file mode 100644 index 0000000..5e3416b --- /dev/null +++ b/userprog/hex2bin/Makefile @@ -0,0 +1,15 @@ +TARGET = hex2bin +SRCS = hex2bin.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/hex2bin/hex2bin.c b/userprog/hex2bin/hex2bin.c new file mode 100644 index 0000000..db1c9f7 --- /dev/null +++ b/userprog/hex2bin/hex2bin.c @@ -0,0 +1,13 @@ +#include + +// 사용법: echo "41 42 43" | hex2bin +int main() { + int byte; + // 16진수(%x)로 정수를 읽어서 + while (scanf("%x", &byte) == 1) { + // char(바이트) 형태로 stdout에 씀 + unsigned char c = (unsigned char)byte; + fwrite(&c, 1, 1, stdout); + } + return 0; +} \ No newline at end of file diff --git a/userprog/sleep/Makefile b/userprog/sleep/Makefile new file mode 100644 index 0000000..fb19630 --- /dev/null +++ b/userprog/sleep/Makefile @@ -0,0 +1,15 @@ +TARGET = sleep +SRCS = sleep.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/sleep/sleep.c b/userprog/sleep/sleep.c new file mode 100644 index 0000000..de45ea9 --- /dev/null +++ b/userprog/sleep/sleep.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +// 숫자인지 확인하는 헬퍼 함수 +int is_number(const char *str) { + while (*str) { + if (!isdigit(*str)) return 0; + str++; + } + return 1; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: sleep \n"); + return 1; + } + + if (!is_number(argv[1])) { + fprintf(stderr, "sleep: invalid time interval '%s'\n", argv[1]); + return 1; + } + + unsigned int seconds = (unsigned int)atoi(argv[1]); + + // sleep() 시스템 콜 호출 + // 리눅스 커널에서 프로세스 상태를 TASK_INTERRUPTIBLE로 변경하고 스케줄링에서 뺍니다. + // 시그널(Ctrl+C)이 오면 sleep은 즉시 깨어나고 남은 시간을 반환합니다. + unsigned int left = sleep(seconds); + + // 만약 시그널에 의해 깨어났다면? (예: SIGINT) + // 쉘이 이미 처리했겠지만, 프로그램 입장에서는 남은 시간을 확인할 수 있습니다. + if (left > 0) { + printf("Sleep interrupted! %u seconds remaining.\n", left); + } + + return 0; +} \ No newline at end of file diff --git a/userprog/tee/Makefile b/userprog/tee/Makefile new file mode 100644 index 0000000..672e4f2 --- /dev/null +++ b/userprog/tee/Makefile @@ -0,0 +1,15 @@ +TARGET = tee +SRCS = tee.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/tee/tee.c b/userprog/tee/tee.c new file mode 100644 index 0000000..98fe4fc --- /dev/null +++ b/userprog/tee/tee.c @@ -0,0 +1,103 @@ +#include +#include +#include +#include +#include +#include // isprint + +#define BUF_SIZE 4096 + +// Hexdump 헬퍼 함수 +// offset_base: 전체 스트림에서의 현재 위치 (0x0000, 0x0010 ...) +void print_hexdump(const unsigned char *buf, ssize_t len, size_t offset_base) { + for (size_t i = 0; i < len; i += 16) { + // 1. 오프셋 출력 + fprintf(stderr, "%08zx: ", offset_base + i); + + // 2. 16진수 출력 + for (size_t j = 0; j < 16; j++) { + if (i + j < len) + fprintf(stderr, "%02x ", buf[i + j]); + else + fprintf(stderr, " "); // 패딩 + } + + fprintf(stderr, " |"); + + // 3. ASCII 출력 + for (size_t j = 0; j < 16; j++) { + if (i + j < len) { + unsigned char c = buf[i + j]; + // 출력 가능한 문자면 출력, 아니면 점(.) + fprintf(stderr, "%c", isprint(c) ? c : '.'); + } + } + fprintf(stderr, "|\n"); + } +} + +int main(int argc, char *argv[]) { + int *fds = (int *)malloc(sizeof(int) * argc); + int fd_count = 0; + + // 모드 플래그 + int mode_stderr = 0; // --stderr (Raw 출력) + int mode_hexdump = 0; // --hexdump (Hex 출력) + + // 1. 인자 파싱 + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--stderr") == 0) { + mode_stderr = 1; + continue; + } + if (strcmp(argv[i], "--hexdump") == 0) { + mode_hexdump = 1; + continue; + } + + // 일반 파일 열기 + int fd = open(argv[i], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror(argv[i]); + continue; + } + fds[fd_count++] = fd; + } + + // 2. 데이터 처리 루프 + unsigned char buf[BUF_SIZE]; // unsigned로 변경 + ssize_t n; + size_t total_bytes = 0; // 전체 스트림 오프셋 추적용 + + while ((n = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) { + // (A) STDOUT 출력 (필수: 파이프 연결 유지) + if (write(STDOUT_FILENO, buf, n) != n) { + perror("write stdout"); + break; + } + + // (B) STDERR 처리 (디버깅용) + if (mode_hexdump) { + // Hexdump 모드면 예쁘게 포맷팅해서 stderr로 + print_hexdump(buf, n, total_bytes); + } else if (mode_stderr) { + // 그냥 stderr 모드면 Raw 데이터를 stderr로 + if (write(STDERR_FILENO, buf, n) != n) { /* ignore */ } + } + + // (C) 파일 출력 + for (int i = 0; i < fd_count; i++) { + if (write(fds[i], buf, n) != n) { + perror("write file"); + } + } + + total_bytes += n; + } + + for (int i = 0; i < fd_count; i++) { + close(fds[i]); + } + free(fds); + return 0; +} \ No newline at end of file diff --git a/userprog/tsh/Makefile b/userprog/tsh/Makefile new file mode 100644 index 0000000..1c55164 --- /dev/null +++ b/userprog/tsh/Makefile @@ -0,0 +1,15 @@ +TARGET = tsh +SRCS = tsh.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/tsh/tsh.c b/userprog/tsh/tsh.c new file mode 100644 index 0000000..c01ba9f --- /dev/null +++ b/userprog/tsh/tsh.c @@ -0,0 +1,767 @@ +/* + * tsh - A tiny shell program with job control + * + * Minseong Gwak(곽민성), gmspweber, 20230840 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Misc manifest constants */ +#define MAXLINE 1024 /* max line size */ +#define MAXARGS 128 /* max args on a command line */ +#define MAXJOBS 16 /* max jobs at any point in time */ +#define MAXJID 1<<16 /* max job ID */ + +/* Job states */ +#define UNDEF 0 /* undefined */ +#define FG 1 /* running in foreground */ +#define BG 2 /* running in background */ +#define ST 3 /* stopped */ + +/* + * Jobs states: FG (foreground), BG (background), ST (stopped) + * Job state transitions and enabling actions: + * FG -> ST : ctrl-z + * ST -> FG : fg command + * ST -> BG : bg command + * BG -> FG : fg command + * At most 1 job can be in the FG state. + */ + +/* Global variables */ +extern char **environ; /* defined in libc */ +char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */ +int verbose = 0; /* if true, print additional output */ +int nextjid = 1; /* next job ID to allocate */ +char sbuf[MAXLINE]; /* for composing sprintf messages */ + +struct job_t { /* The job struct */ + pid_t pid; /* job PID */ + int jid; /* job ID [1, 2, ...] */ + int state; /* UNDEF, BG, FG, or ST */ + char cmdline[MAXLINE]; /* command line */ +}; +struct job_t jobs[MAXJOBS]; /* The job list */ +/* End global variables */ + + +/* Function prototypes */ + +/* Here are the functions that you will implement */ +void eval(char *cmdline); +int builtin_cmd(char **argv); +void do_bgfg(char **argv); +void waitfg(pid_t pid); + +void sigchld_handler(int sig); +void sigtstp_handler(int sig); +void sigint_handler(int sig); + +/* Here are helper routines that we've provided for you */ +int parseline(const char *cmdline, char **argv); +void sigquit_handler(int sig); + +void clearjob(struct job_t *job); +void initjobs(struct job_t *jobs); +int maxjid(struct job_t *jobs); +int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline); +int deletejob(struct job_t *jobs, pid_t pid); +pid_t fgpid(struct job_t *jobs); +struct job_t *getjobpid(struct job_t *jobs, pid_t pid); +struct job_t *getjobjid(struct job_t *jobs, int jid); +int pid2jid(pid_t pid); +void listjobs(struct job_t *jobs); + +void usage(void); +void unix_error(char *msg); +void app_error(char *msg); +typedef void handler_t(int); +handler_t *Signal(int signum, handler_t *handler); + +/* + * main - The shell's main routine + */ +int main(int argc, char **argv) +{ + char c; + char cmdline[MAXLINE]; + int emit_prompt = 1; /* emit prompt (default) */ + + /* Redirect stderr to stdout (so that driver will get all output + * on the pipe connected to stdout) */ + dup2(1, 2); + + /* Parse the command line */ + while ((c = getopt(argc, argv, "hvp")) != EOF) { + switch (c) { + case 'h': /* print help message */ + usage(); + break; + case 'v': /* emit additional diagnostic info */ + verbose = 1; + break; + case 'p': /* don't print a prompt */ + emit_prompt = 0; /* handy for automatic testing */ + break; + default: + usage(); + } + } + + /* Install the signal handlers */ + + /* These are the ones you will need to implement */ + Signal(SIGINT, sigint_handler); /* ctrl-c */ + Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */ + Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */ + + /* This one provides a clean way to kill the shell */ + Signal(SIGQUIT, sigquit_handler); + + /* Initialize the job list */ + initjobs(jobs); + + /* Execute the shell's read/eval loop */ + while (1) { + + /* Read command line */ + if (emit_prompt) { + printf("%s", prompt); + fflush(stdout); + } + if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin)) + app_error("fgets error"); + if (feof(stdin)) { /* End of file (ctrl-d) */ + fflush(stdout); + exit(0); + } + + /* Evaluate the command line */ + eval(cmdline); + fflush(stdout); + fflush(stdout); + } + + exit(0); /* control never reaches here */ +} + +/* + * eval - Evaluate the command line that the user has just typed in + * + * If the user has requested a built-in command (quit, jobs, bg or fg) + * then execute it immediately. Otherwise, fork a child process and + * run the job in the context of the child. If the job is running in + * the foreground, wait for it to terminate and then return. Note: + * each child process must have a unique process group ID so that our + * background children don't receive SIGINT (SIGTSTP) from the kernel + * when we type ctrl-c (ctrl-z) at the keyboard. +*/ +void eval(char *cmdline) +{ + char* argv[MAXARGS]; + char buf[MAXLINE]; + int bg; + pid_t pid; + sigset_t mask_all, mask_SIGCHLD, prev; + + strcpy(buf, cmdline); + bg = parseline(buf, argv); + if(argv[0] == NULL) return; + + sigfillset(&mask_all); + sigemptyset(&mask_SIGCHLD); sigaddset(&mask_SIGCHLD, SIGCHLD); + + if(!builtin_cmd(argv)) + { + sigprocmask(SIG_BLOCK, &mask_SIGCHLD, &prev); + //Non-builtin command processing + if((pid = fork()) == 0) + { + sigprocmask(SIG_SETMASK, &prev, NULL); + setpgrp(); + + if(execve(argv[0], argv, environ) < 0) + { + printf("%s: Command not found\n", argv[0]); + exit(0); + } + } + + sigprocmask(SIG_BLOCK, &mask_all, NULL); + if(!bg) addjob(jobs, pid, FG, cmdline); + else + { + addjob(jobs, pid, BG, cmdline); + printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline); + } + sigprocmask(SIG_SETMASK, &prev, NULL); + + if(!bg) waitfg(pid); + } + return; +} + +/* + * parseline - Parse the command line and build the argv array. + * + * Characters enclosed in single quotes are treated as a single + * argument. Return true if the user has requested a BG job, false if + * the user has requested a FG job. + */ +int parseline(const char *cmdline, char **argv) +{ + static char array[MAXLINE]; /* holds local copy of command line */ + char *buf = array; /* ptr that traverses command line */ + char *delim; /* points to first space delimiter */ + int argc; /* number of args */ + int bg; /* background job? */ + + strcpy(buf, cmdline); + buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */ + while (*buf && (*buf == ' ')) /* ignore leading spaces */ + buf++; + + /* Build the argv list */ + argc = 0; + if (*buf == '\'') { + buf++; + delim = strchr(buf, '\''); + } + else { + delim = strchr(buf, ' '); + } + + while (delim) { + argv[argc++] = buf; + *delim = '\0'; + buf = delim + 1; + while (*buf && (*buf == ' ')) /* ignore spaces */ + buf++; + + if (*buf == '\'') { + buf++; + delim = strchr(buf, '\''); + } + else { + delim = strchr(buf, ' '); + } + } + argv[argc] = NULL; + + if (argc == 0) /* ignore blank line */ + return 1; + + /* should the job run in the background? */ + if ((bg = (*argv[argc-1] == '&')) != 0) { + argv[--argc] = NULL; + } + return bg; +} + +/* + * builtin_cmd - If the user has typed a built-in command then execute + * it immediately. + */ +int builtin_cmd(char **argv) +{ + if(strcmp(argv[0], "quit") == 0) exit(0); + if(strcmp(argv[0], "jobs") == 0) + { + sigset_t mask_all, prev; + sigfillset(&mask_all); + sigprocmask(SIG_BLOCK, &mask_all, &prev); + + listjobs(jobs); + + sigprocmask(SIG_SETMASK, &prev, NULL); + return 1; + } + if(strcmp(argv[0], "fg") == 0 || strcmp(argv[0], "bg") == 0) + { + do_bgfg(argv); + return 1; + } + return 0; /* not a builtin command */ +} + +/* + * do_bgfg - Execute the builtin bg and fg commands + */ +void do_bgfg(char **argv) +{ + int argc = 0; + while(argv[argc] != NULL) argc++; + if(argc == 1) + { + printf("%s command requires PID or %%jobid argument\n", argv[0]); + return; + } + if(argc != 2) return; + sigset_t mask_all, prev; + sigfillset(&mask_all); + sigprocmask(SIG_BLOCK, &mask_all, &prev); + + //find the job of given argument + struct job_t* job = NULL; int pid = 0; + if(argv[1][0] == '%') + { + char* end; + int jid = strtol(argv[1]+1, &end, 10); + + if(end == argv[1]+1 || *end != '\0' || errno == ERANGE) + printf("%s: argument must be a PID or %%jobid\n", argv[0]); + else if((job = getjobjid(jobs, jid)) == NULL) + printf("%s: No such job\n", argv[1]); + } + else + { + char* end; + pid_t pid = strtol(argv[1], &end, 10); + + if(end == argv[1] || *end != '\0' || errno == ERANGE) + printf("%s: argument must be a PID or %%jobid\n", argv[0]); + else if((job = getjobpid(jobs, pid)) == NULL) + printf("(%s): No such process\n", argv[1]); + } + + if(job) + { + pid = job->pid; + if(strcmp(argv[0], "fg") == 0) + { + if(job->state == ST) + { + kill(-job->pid, SIGCONT); + job->state = FG; + } + else if(job->state == BG) job->state = FG; + } + if(strcmp(argv[0], "bg") == 0) + { + if(job->state == ST) + { + kill(-job->pid, SIGCONT); + job->state = BG; + } + else if(job->state == BG); + printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline); + } + } + + sigprocmask(SIG_SETMASK, &prev, NULL); + + if(pid && strcmp(argv[0], "fg") == 0) waitfg(pid); + return; +} + +/* + * waitfg - Block until process pid is no longer the foreground process + */ +void waitfg(pid_t pid) +{ + sigset_t mask_SIGCHLD, prev; + sigemptyset(&mask_SIGCHLD); + sigaddset(&mask_SIGCHLD, SIGCHLD); + + sigprocmask(SIG_BLOCK, &mask_SIGCHLD, &prev); + while(fgpid(jobs) == pid) sigsuspend(&prev); + sigprocmask(SIG_SETMASK, &prev, NULL); + return; +} + +/***************** + * Signal handlers + *****************/ + +/* + * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever + * a child job terminates (becomes a zombie), or stops because it + * received a SIGSTOP or SIGTSTP signal. The handler reaps all + * available zombie children, but doesn't wait for any other + * currently running children to terminate. + */ +void sigchld_handler(int sig) +{ + int olderrno = errno; + sigset_t mask_all, prev; + pid_t pid; int status; + char msg[100]; + const char* TER = "terminated "; + const char* STP = "stopped "; + const char* STR = "by signal "; + + sigfillset(&mask_all); + while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) + { + sigprocmask(SIG_BLOCK, &mask_all, &prev); + int jid = pid2jid(pid); + + if(WIFEXITED(status)) deletejob(jobs, pid); + else + { + int by = 0, isExited = 0; + if(WIFSIGNALED(status)) + { + deletejob(jobs, pid); + by = WTERMSIG(status); + isExited = 1; + } + else if(WIFSTOPPED(status)) + { + struct job_t *bg = getjobpid(jobs, pid); + bg->state = ST; + by = WSTOPSIG(status); + isExited = 0; + } + + + int k = 0, j; + msg[k++] = 'J'; msg[k++] = 'o'; msg[k++] = 'b'; msg[k++] = ' '; + msg[k++] = '['; + { + int num = jid; + int c = 0, cc; + if(num == 0) msg[k++] = '0'; + else + { + if(num < 0) msg[k++] = '-', num = -num; + while(num != 0) msg[k++] = '0'+num%10, num /= 10, c++; + cc = c/2; + + while(cc) + { + char tmp = msg[k-cc]; + msg[k-cc] = msg[k-c+cc-1]; + msg[k-c+cc-1] = tmp; + cc--; + } + } + } + msg[k++] = ']'; msg[k++] = ' '; msg[k++] = '('; + { + int num = pid; + int c = 0, cc; + if(num == 0) msg[k++] = '0'; + else + { + if(num < 0) msg[k++] = '-', num = -num; + while(num != 0) msg[k++] = '0'+num%10, num /= 10, c++; + cc = c/2; + + while(cc) + { + char tmp = msg[k-cc]; + msg[k-cc] = msg[k-c+cc-1]; + msg[k-c+cc-1] = tmp; + cc--; + } + } + } + msg[k++] = ')'; msg[k++] = ' '; + + if(isExited) + { + j = 0; + while(TER[j]) msg[k++] = TER[j++]; + } + else + { + j = 0; + while(STP[j]) msg[k++] = STP[j++]; + } + + j = 0; + while(STR[j]) msg[k++] = STR[j++]; + + { + int num = by; + int c = 0, cc; + if(num == 0) msg[k++] = '0'; + else + { + if(num < 0) msg[k++] = '-', num = -num; + while(num != 0) msg[k++] = '0'+num%10, num /= 10, c++; + cc = c/2; + + while(cc) + { + char tmp = msg[k-cc]; + msg[k-cc] = msg[k-c+cc-1]; + msg[k-c+cc-1] = tmp; + cc--; + } + } + } + msg[k++] = '\n'; msg[k] = 0; + + j = write(1, msg, k); + } + sigprocmask(SIG_SETMASK, &prev, NULL); + } + errno = olderrno; +} + +/* + * sigint_handler - The kernel sends a SIGINT to the shell whenver the + * user types ctrl-c at the keyboard. Catch it and send it along + * to the foreground job. + */ +void sigint_handler(int sig) +{ + int olderrno = errno; + sigset_t mask_all, prev; + sigfillset(&mask_all); + sigprocmask(SIG_BLOCK, &mask_all, &prev); + + pid_t pid = fgpid(jobs); + if(pid > 0) kill(-pid, SIGINT); + + sigprocmask(SIG_SETMASK, &prev, NULL); + + errno = olderrno; +} + +/* + * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever + * the user types ctrl-z at the keyboard. Catch it and suspend the + * foreground job by sending it a SIGTSTP. + */ +void sigtstp_handler(int sig) +{ + int olderrno = errno; + sigset_t mask_all, prev; + sigfillset(&mask_all); + sigprocmask(SIG_BLOCK, &mask_all, &prev); + + pid_t pid = fgpid(jobs); + if(pid > 0) kill(-pid, SIGTSTP); + + sigprocmask(SIG_SETMASK, &prev, NULL); + errno = olderrno; +} + +/********************* + * End signal handlers + *********************/ + +/*********************************************** + * Helper routines that manipulate the job list + **********************************************/ + +/* clearjob - Clear the entries in a job struct */ +void clearjob(struct job_t *job) { + job->pid = 0; + job->jid = 0; + job->state = UNDEF; + job->cmdline[0] = '\0'; +} + +/* initjobs - Initialize the job list */ +void initjobs(struct job_t *jobs) { + int i; + + for (i = 0; i < MAXJOBS; i++) + clearjob(&jobs[i]); +} + +/* maxjid - Returns largest allocated job ID */ +int maxjid(struct job_t *jobs) +{ + int i, max=0; + + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].jid > max) + max = jobs[i].jid; + return max; +} + +/* addjob - Add a job to the job list */ +int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) +{ + int i; + + if (pid < 1) + return 0; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid == 0) { + jobs[i].pid = pid; + jobs[i].state = state; + jobs[i].jid = nextjid++; + if (nextjid > MAXJOBS) + nextjid = 1; + strcpy(jobs[i].cmdline, cmdline); + if(verbose){ + printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline); + } + return 1; + } + } + printf("Tried to create too many jobs\n"); + return 0; +} + +/* deletejob - Delete a job whose PID=pid from the job list */ +int deletejob(struct job_t *jobs, pid_t pid) +{ + int i; + + if (pid < 1) + return 0; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid == pid) { + clearjob(&jobs[i]); + nextjid = maxjid(jobs)+1; + return 1; + } + } + return 0; +} + +/* fgpid - Return PID of current foreground job, 0 if no such job */ +pid_t fgpid(struct job_t *jobs) { + int i; + + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].state == FG) + return jobs[i].pid; + return 0; +} + +/* getjobpid - Find a job (by PID) on the job list */ +struct job_t *getjobpid(struct job_t *jobs, pid_t pid) { + int i; + + if (pid < 1) + return NULL; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].pid == pid) + return &jobs[i]; + return NULL; +} + +/* getjobjid - Find a job (by JID) on the job list */ +struct job_t *getjobjid(struct job_t *jobs, int jid) +{ + int i; + + if (jid < 1) + return NULL; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].jid == jid) + return &jobs[i]; + return NULL; +} + +/* pid2jid - Map process ID to job ID */ +int pid2jid(pid_t pid) +{ + int i; + + if (pid < 1) + return 0; + for (i = 0; i < MAXJOBS; i++) + if (jobs[i].pid == pid) { + return jobs[i].jid; + } + return 0; +} + +/* listjobs - Print the job list */ +void listjobs(struct job_t *jobs) +{ + int i; + + for (i = 0; i < MAXJOBS; i++) { + if (jobs[i].pid != 0) { + printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid); + switch (jobs[i].state) { + case BG: + printf("Running "); + break; + case FG: + printf("Foreground "); + break; + case ST: + printf("Stopped "); + break; + default: + printf("listjobs: Internal error: job[%d].state=%d ", + i, jobs[i].state); + } + printf("%s", jobs[i].cmdline); + } + } +} +/****************************** + * end job list helper routines + ******************************/ + + +/*********************** + * Other helper routines + ***********************/ + +/* + * usage - print a help message + */ +void usage(void) +{ + printf("Usage: shell [-hvp]\n"); + printf(" -h print this message\n"); + printf(" -v print additional diagnostic information\n"); + printf(" -p do not emit a command prompt\n"); + exit(1); +} + +/* + * unix_error - unix-style error routine + */ +void unix_error(char *msg) +{ + fprintf(stdout, "%s: %s\n", msg, strerror(errno)); + exit(1); +} + +/* + * app_error - application-style error routine + */ +void app_error(char *msg) +{ + fprintf(stdout, "%s\n", msg); + exit(1); +} + +/* + * Signal - wrapper for the sigaction function + */ +handler_t *Signal(int signum, handler_t *handler) +{ + struct sigaction action, old_action; + + action.sa_handler = handler; + sigemptyset(&action.sa_mask); /* block sigs of type being handled */ + action.sa_flags = SA_RESTART; /* restart syscalls if possible */ + + if (sigaction(signum, &action, &old_action) < 0) + unix_error("Signal error"); + return (old_action.sa_handler); +} + +/* + * sigquit_handler - The driver program can gracefully terminate the + * child shell by sending it a SIGQUIT signal. + */ +void sigquit_handler(int sig) +{ + printf("Terminating after receipt of SIGQUIT signal\n"); + exit(1); +}