개발/프로젝트

Pintos Project 4-3: 디렉터리도 fd로 열고 readdir/isdir/inumber 연결하기

cedis 2026. 5. 21. 00:09
Project 4 File System 시리즈 3편

하위 디렉터리까지 만들었다면 다음 문제는 user program이 그 디렉터리를 어떻게 다루느냐이다. Project 4는 directory도 open()으로 열고, fd를 통해 readdir, isdir, inumber를 호출하게 한다.

핵심부터 말하면

이번 구현에서는 fd table을 크게 갈아엎지 않고, 디렉터리 inode도 struct file로 열었다. 대신 file_is_dir(), file_readdir(), file_inumber() 같은 wrapper를 추가해서 syscall 계층이 같은 fd 경로로 파일과 디렉터리를 구분하게 했다.

fd에서 디렉터리 entry까지
user fd정수 fd
fd tablefd → file handle
struct fileinode + pos
directory inodedir_entry 순회

1. 디렉터리에는 일반 read/write를 허용하지 않는다

디렉터리를 fd로 열 수 있다고 해서 read(fd)write(fd)가 일반 파일처럼 동작하면 안 된다. 디렉터리 목록 읽기는 readdir라는 별도 syscall이 담당한다.

off_t
file_read (struct file *file, void *buffer, off_t size) {
	if (file_is_dir (file))
		return -1;
	off_t bytes_read = inode_read_at (file->inode, buffer, size, file->pos);
	file->pos += bytes_read;
	return bytes_read;
}

off_t
file_write (struct file *file, const void *buffer, off_t size) {
	if (file_is_dir (file))
		return -1;
	off_t bytes_written = inode_write_at (file->inode, buffer, size, file->pos);
	file->pos += bytes_written;
	return bytes_written;
}
왜 이렇게 막는가

디렉터리 파일의 내부 형식은 struct dir_entry 배열이다. user program이 byte 단위 read/write로 직접 만지게 두면 directory entry 불변식이 깨진다. 그래서 디렉터리는 전용 syscall로만 읽게 한다.

2. file 레벨에서 디렉터리용 helper를 제공한다

syscall이 inode 내부 구조를 직접 알 필요는 없다. fd에서 file을 얻고, file helper가 directory inode인지 확인한 뒤 필요한 동작을 수행한다.

bool
file_is_dir (struct file *file) {
	return file != NULL && inode_is_dir (file->inode);
}

bool
file_readdir (struct file *file, char name[NAME_MAX + 1]) {
	struct dir_entry {
		disk_sector_t inode_sector;
		char name[NAME_MAX + 1];
		bool in_use;
	};
	struct dir_entry e;

	if (!file_is_dir (file))
		return false;

	while (inode_read_at (file->inode, &e, sizeof e, file->pos) == sizeof e) {
		file->pos += sizeof e;
		if (e.in_use && strcmp (e.name, ".") && strcmp (e.name, "..")) {
			strlcpy (name, e.name, NAME_MAX + 1);
			return true;
		}
	}
	return false;
}

int
file_inumber (struct file *file) {
	return inode_get_inumber (file->inode);
}
이 블록이 바꾼 것

이전 fd는 일반 파일만 열 수 있다고 생각해도 됐다. 이제 fd는 파일일 수도 있고 디렉터리일 수도 있다. 하지만 fd table 구조를 크게 바꾸지 않고, file helper에서 inode flag를 보고 분기하게 했다.

3. syscall은 user pointer를 검증하고 file helper로 위임한다

Project 4 syscall의 모양은 단순하다. user pointer를 확인하고, fd table에서 file을 찾고, 파일 시스템 lock을 잡은 뒤 helper를 호출한다.

case SYS_READDIR:
{
	int fd = (int) f->R.rdi;
	char *name = (char *) f->R.rsi;
	struct file *file;

	user_check_write (name, NAME_MAX + 1);
	file = process_get_file (fd);
	lock_acquire (&filesys_lock);
	f->R.rax = file != NULL && file_readdir (file, name);
	lock_release (&filesys_lock);
	break;
}
case SYS_ISDIR:
{
	struct file *file = process_get_file ((int) f->R.rdi);

	lock_acquire (&filesys_lock);
	f->R.rax = file != NULL && file_is_dir (file);
	lock_release (&filesys_lock);
	break;
}
case SYS_INUMBER:
{
	struct file *file = process_get_file ((int) f->R.rdi);

	lock_acquire (&filesys_lock);
	f->R.rax = file != NULL ? file_inumber (file) : -1;
	lock_release (&filesys_lock);
	break;
}

4. mkdir와 chdir syscall은 filesys 계층으로 보낸다

디렉터리 fd syscall과 별개로, 디렉터리 생성과 cwd 변경도 syscall에 연결해야 한다. 이때 syscall handler는 문자열 검증만 하고, 실제 경로 해석은 filesys_mkdir(), filesys_chdir()에 맡긴다.

case SYS_CHDIR:
{
	const char *dir = (const char *) f->R.rdi;

	user_check_string (dir);
	lock_acquire (&filesys_lock);
	f->R.rax = filesys_chdir (dir);
	lock_release (&filesys_lock);
	break;
}
case SYS_MKDIR:
{
	const char *dir = (const char *) f->R.rdi;

	user_check_string (dir);
	lock_acquire (&filesys_lock);
	f->R.rax = filesys_mkdir (dir);
	lock_release (&filesys_lock);
	break;
}

5. 이 단계에서 의미 있는 테스트

테스트 의미 실패 시 먼저 볼 곳
dir-open 디렉터리를 fd로 열 수 있는가 filesys_open(), file_open()
dir-readdir 계열 fd 기준으로 디렉터리 entry를 하나씩 읽는가 file_readdir(), SYS_READDIR
dir-under-file 파일 아래를 디렉터리처럼 접근하지 못하게 막는가 resolve_path_from(), inode_is_dir()
dir-empty-name 빈 이름과 잘못된 path를 거절하는가 split_path(), syscall string 검증

마무리

이번 단계의 핵심은 fd table을 복잡하게 새로 설계하지 않고도 디렉터리를 다룰 수 있게 만든 점이다. 디렉터리도 inode를 가진 객체이므로 struct file로 감싸고, file helper에서 directory 여부를 분기했다. 이 덕분에 syscall handler는 기존 fd 조회 흐름을 유지하면서 Project 4의 디렉터리 syscall을 연결할 수 있었다.