거북이의 쉼터

(2022.04.02) Soft Link 구현 본문

코딩 삽질/KAIST PINTOS (CS330)

(2022.04.02) Soft Link 구현

onlim 2022. 4. 2. 21:29

1. 서론 및 필요 내용 설명

지난 포스팅에 이어 계속 생각해봤는데, 아무래도 해당 링크를 만들 때 상대 주소로 들어올 경우에 절대 주소로 변환하는 루틴을 만들어 저장할 때 절대 주소로 일괄적으로 저장하는 것이 filesys_open을 할 때 더 수월할 것 같았다. 따라서 dir를 주면 절대 경로를 구할 수 있는 함수를 만들고, 해당 함수를 사용해 일괄적인 관리를 해보자.

 


2. 구현해야 하는 것

  • 절대경로 변환 함수
  • symlink syscall 구현
  • filesys_open과 dir_traverse 수정

 


3. 구현 과정

우선 가장 먼저 할 일은 inode_disk 구조체에 is_link field를 넣는 것이었다. 해당 field는 특별한 일이 없다면 0으로 초기화된채로 있을 것이며, inode_set_link을 활용해 변환해줄 수 있다.

struct inode_disk {
	unsigned is_dir;                    /* 4 byte bool variable 0 : file 1 : dir */
	unsigned is_link;                   /* 4 byte bool variable 0 : nonlink 1 : link */  
	off_t length;                       /* File size in bytes. */
	unsigned magic;                     /* Magic number. */
	cluster_t start_clst;               /* First data sector in cluster */
	disk_sector_t parent_sector;        /* Parent directory sector */
	uint32_t unused[122];               /* Not used. */
};

bool
inode_set_link (disk_sector_t cur_sec, bool is_link) {
	struct inode *inode = inode_open (cur_sec);
	inode->data.is_link = (is_link? 1 : 0);
	disk_write (filesys_disk, cur_sec, &inode->data);
	inode_close (inode);

	return true;
}

bool
inode_is_link (struct inode *inode) {
	return (inode->data.is_link != 0);
}

다음으로 해줄 것은 주어진 경로를 절대 경로로 변환하는 기능을 만드는 것이었다. 이를 위해서 기존 함수를 최대한 활용하기로 했다. 우선 target을 파싱해서 dir로 만드는 것은 기존 함수로도 가능하다. 그러면 dir에서 역으로 parent_sector를 활용해 루트까지 타고 올라간다면 될 것이라고 생각했다. 각 디렉토리의 이름은 일단 모아놨다가 루트에 도달한 이후에 역순으로 합치면서 최종적인 절대 경로를 만들어줄 수 있다.

void
dir_rev_traverse (struct dir *org_dir, char *buffer)
{
	struct inode *par_inode, *cur_inode;
	struct dir *dir;
	struct dir_entry e;
	int cnt = 0, pos = 0;

	char *dir_names[100] = {0, };

	cur_inode = inode_reopen (dir_get_inode (org_dir));
	par_inode = inode_open_parent (dir_get_inode (org_dir));

	while (inode_get_inumber (cur_inode) != cluster_to_sector (ROOT_DIR_CLUSTER)) {
		dir = dir_open (par_inode);
		
		while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) {
			dir->pos += sizeof e;

			if (e.inode_sector == inode_get_inumber (cur_inode)) {
				dir_names[cnt] = calloc (NAME_MAX + 1, 1);
				strlcpy (dir_names[cnt], e.name, NAME_MAX + 1);
				cnt++;
				break;
			}
		}

		inode_close (cur_inode);
		cur_inode = inode_reopen (par_inode);
		par_inode = inode_open_parent (dir_get_inode (dir));
		dir_close (dir);
	}

	inode_close(cur_inode);
	inode_close (par_inode);

	buffer[pos] = '/';
	pos++;

	int i;
	for (i = cnt - 1; i >= 0; i--) {
		strlcpy(buffer + pos, dir_names[i], strlen(dir_names[i]) + 1);
		pos += strlen(dir_names[i]);
		free (dir_names[i]);
		buffer[pos] = '/';
		pos++;
	}

	return;
}

해당 함수를 활용하여 symlink를 구현한다.

int _symlink (const char *target, const char *linkpath)
{
	int res = -1;

	filesys_enter ();
	if (make_link (target, linkpath))
		res = 0;
	filesys_exit ();

	return res;
}

bool
make_link (const char *target, const char *linkpath) {
	char filename[NAME_MAX + 1] = {0, };
	char dir_path[FULLPATH_MAX + 1] = {0, };
	char target_path[FULLPATH_MAX + 1] = {0, };

	struct dir *parent_dir;

	// first find absolute path of target
	if (target[0] == '/') {
		strlcpy(target_path, target, strlen(target) + 1);
	}

	else {
		parse_dir_name (target, dir_path, filename);
		parent_dir = dir_traverse (dir_path);
		dir_rev_traverse (parent_dir, target_path);
		dir_close(parent_dir);
		strlcpy (target_path + strlen(target_path), filename, strlen(filename) + 1);
	}

	parse_dir_name (linkpath, dir_path, filename);
	parent_dir = dir_traverse (dir_path);

	cluster_t inode_clst = fat_create_chain (0); // newly create cluster(sector) to hold file metadata
	
	bool success = (parent_dir != NULL && filename[0] != '\0'
					&& inode_clst != 0
					&& inode_create (cluster_to_sector (inode_clst), 0)
					&& dir_add (parent_dir, filename, cluster_to_sector (inode_clst))
					&& inode_set_parent (cluster_to_sector (inode_clst), inode_get_inumber (dir_get_inode(parent_dir)))
					&& inode_set_link (cluster_to_sector (inode_clst), true));
	if (!success && inode_clst != 0)
		fat_remove_chain (inode_clst, 0);
	dir_close (parent_dir);

	// now write the target path
	struct inode *inode = inode_open (cluster_to_sector (inode_clst));
	inode_write_at(inode, target_path, strlen(target_path), 0);
	inode_close (inode);

	return success;
}

이제 거의 다 됐다. filesys_open과 dir_traverse에서 링크 파일을 만났을 때는 링크 파일의 절대 경로를 구해서 해당 파일의 inode를 열고 그대로 진행해주면 된다. 문제는 해당 link가 여러 단계에 걸쳐 있을 수가 있기 때문에 모든 링크를 다 따라간 후의 파일이 제대로 선택되도록 해 주어야 한다. 그래서 조금 코드가 길어졌다... 아래는 dir_traverse이고,

struct dir *
dir_traverse (char *dir_path)
{
	char filename[NAME_MAX + 1] = {0, };
	char directory_path[FULLPATH_MAX + 1] = {0, };

	struct thread *curr = thread_current ();
	char *path_copy = (char *) malloc (strlen (dir_path) + 1);
	char *token, *ptr = NULL;

	strlcpy(path_copy, dir_path, strlen(dir_path) + 1);
	struct dir *cur_dir, *dir;
	struct inode *inode;

	if (path_copy[0] == '/') {
		cur_dir = dir_open_root ();
	}

	else {
		if (curr->cwd == NULL) {
			cur_dir = dir_open_root ();
		}
		else {
			cur_dir = dir_reopen (curr->cwd);
		}
	}

	for (token = strtok_r(path_copy, "/", &ptr); token != NULL; token = strtok_r(NULL, "/", &ptr))
	{
		if (strcmp (token, ".") == 0) {
			continue;
		}

		else if (strcmp (token, "..") == 0) {
			// open parent inode
			inode = inode_open_parent (dir_get_inode (cur_dir));
			dir_close (cur_dir);
			cur_dir = dir_open (inode);
		}

		else {
			if (dir_lookup (cur_dir, token, &inode))
			{
				if (inode_is_dir (inode)) { // confirm that it is dir
					dir_close (cur_dir);
					cur_dir = dir_open (inode);
				}

				else if (inode_is_link (inode)) {
					dir_close (cur_dir);
					char *file_path = (char *) calloc (FULLPATH_MAX + 1, 1);

					while (inode_is_link (inode)) {
						memset (file_path, 0, FULLPATH_MAX);

						inode_read_at (inode, file_path, FULLPATH_MAX, 0);
						inode_close (inode);

						parse_dir_name (file_path, directory_path, filename);
						dir = dir_traverse (directory_path);

						if (dir != NULL) {
							if (file_path[0] != '\0' && filename[0] == '\0') { // implying root
								inode = inode_open (cluster_to_sector (ROOT_DIR_CLUSTER));
							}

							else if (strcmp (filename, ".") == 0) {
								inode = inode_reopen (dir_get_inode (dir));
							}

							else if (strcmp (filename, "..") == 0) {
								inode = inode_open_parent (dir_get_inode (dir));
							}

							else {
								dir_lookup (dir, filename, &inode);
							}
						}

						dir_close (dir);

						if (inode == NULL) {
							free (file_path);
							free (path_copy);
							return NULL;
						}
					}

					free (file_path);

					if (!inode_is_dir (inode)) { // confirm that it is dir
						free (path_copy);
						return NULL;
					}

					if ((cur_dir = dir_open (inode)) == NULL) {
						free (path_copy);
						return NULL;
					}
				}

				else {
					dir_close (cur_dir);
					free (path_copy);
					return NULL;
				}
			}
			
			else {
				dir_close (cur_dir);
				free (path_copy);
				return NULL;
			}
		}
	}

	free (path_copy);
	return cur_dir;
}

아래는 filesys_open이다. 이건 재귀라서 그나마 짧다.

struct file *
filesys_open (const char *name) {
	char filename[NAME_MAX + 1] = {0, };
	char dir_path[FULLPATH_MAX + 1] = {0, };

	parse_dir_name (name, dir_path, filename);
	struct dir *dir = dir_traverse (dir_path); //dir_open_root ();
	struct inode *inode = NULL;

	if (dir != NULL) {
		if (name[0] != '\0' && filename[0] == '\0') { // implying root
			inode = inode_open (cluster_to_sector (ROOT_DIR_CLUSTER));
		}

		else if (strcmp (filename, ".") == 0) {
			inode = inode_reopen (dir_get_inode (dir));
		}

		else if (strcmp (filename, "..") == 0) {
			inode = inode_open_parent (dir_get_inode (dir));
		}

		else {
			dir_lookup (dir, filename, &inode);
		}
	}
	dir_close (dir);

	if (inode != NULL && inode_is_link (inode)) {
		char *file_path = (char *) calloc (FULLPATH_MAX + 1, 1);
		inode_read_at (inode, file_path, FULLPATH_MAX, 0);
		inode_close (inode);
		struct file *fp = filesys_open (file_path);
		free (file_path);
		return fp;
	}

	return file_open (inode);
}

이제 다 된 것 같으니 테스트를 해 보자.

 


4. 디버깅

간단한 기능을 구현한 것이라 문제 없이 돌아갈 것이라 생각해서 make check와 make grade를 했다.

TOTAL TESTING SCORE: 100.0%

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

SUMMARY BY TEST SET

Test Set                                      Pts Max  % Ttl  % Max
--------------------------------------------- --- --- ------ ------
tests/threads/Rubric.alarm                      7/  7   2.0%/  2.0%
tests/threads/Rubric.priority                  25/ 25   3.0%/  3.0%
tests/userprog/Rubric.functionality            40/ 40  10.0%/ 10.0%
tests/userprog/Rubric.robustness               40/ 40   5.0%/  5.0%
tests/vm/Rubric.functionality                  82/ 82   8.0%/  8.0%
tests/vm/Rubric.robustness                     29/ 29   2.0%/  2.0%
tests/filesys/base/Rubric                      17/ 17  10.0%/ 10.0%
tests/filesys/extended/Rubric.functionality    49/ 49  25.0%/ 25.0%
tests/filesys/extended/Rubric.robustness       10/ 10  15.0%/ 15.0%
tests/filesys/extended/Rubric.persistence      26/ 26  20.0%/ 20.0%
tests/filesys/mount/Rubric                      0/  1   0.0%/ 20.0%
tests/filesys/buffer-cache/Rubric               0/  1   0.0%/ 20.0%
--------------------------------------------- --- --- ------ ------
Total                                                 100.0%/140.0%

다행히 엑스트라 테케를 제외하고 잘 돌아간다. 이렇게 프로젝트 4의 일반 엔딩을 보았다.

 


5. 후기

히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히히힣히ㅣㅣ히히히히히히히히히히히히히히히ㅣ히히히히히히히히히히히히히히히히히히히히ㅣㅣ히히히히히히히히히히히히히히히히히히히히ㅣ히히ㅣ히히히히힣히히히히히히히히ㅣ히히히ㅣ히ㅣ히히히히히히히히히히히히히히히히히히히히ㅣㅣ.....

 

FREEEEEEEEEDOM~~!!!!

끝났다... 자세한 후기는 다음 포스팅에서 좀 더 길게 적도록 하겠다.

Comments