리눅스 커널 내부구조 10장 #4 FAT File System (FAT)

문연수·2022년 3월 6일
0

iamroot (Linux Internal)

목록 보기
20/24

사실 지금까지의 모든 내용은 FAT File System 의 편린이였다. 솔직히 설명할 내용도 많지 않았고 그 내용도 매우 쉬웠다. 좀 나쁜 말 섞어셔 얘기하면 fat.c 부터가 존나게 지랄맞다. 3,000 라인 중 1,500 라인이 fat.c 이므로 말 다 했다.

1. FAT 구조

 우선 아래의 내용을 이를 이해하기 위해선 몇 가지 알아야 되는 사항이 있다. 우선 FAT 의 구조에 대해 이해할 필요가 있다. 책에 나온 내용은 FAT12 이므로 FAT32 와 약간의 차이가 있지만 하위 호환성을 고려하여 설계 되었기 때문에 엄청 크게 다르거나 하진 않다.

 FAT 자체만 하더라도 매우 많은 내용을 설명해야 하지만 필자는 딱 코드를 이해하는데 어려움을 겪지 않을 정도로만 설명하려 한다.

- BPB (Bios Parameter Block)

 가장 먼저 알아야 하는 것은 BPB 이다. BPBBios Parameter Block 의 약자로, 저장 장치의 정보 및 부팅에 필요한 필수적인 데이터를 포함하고 있는 자료구조이다. 여기에 대해서 디테일하게 설명하려면 서너 개의 글을 더 작성해야 할만큼 그 분량이 많기 때문에 이 모든걸 하나의 글에 담을 순 없고 일단 BPB 는 부팅 및 장치에 대한 정보를 담고 있는 자료구조 정도로 이해하면 좋을 듯 하다.

 이렇게 위 세 장의 삽화를 합친 자료구조가 필자가 작성한 FAT12 의 자료구조이다. struct fat_bpb 로 표현되고 있다. 확장성을 위해 union 을 통해 bpb32 라는 자료구조도 생성 했으나 실제로 사용되진 않는다.

 위 내용과 코드를 비교하면서 보면 쉽게 이해할 수 있을 것이다. 이 중에서 일부 정보만 뽑아다 쓰기 때문에 일단 이렇게 생겼구나 정도만 기억하라.

- SectorCluster

 영속 저장 장치가 읽고 쓰는 최소 단위를 우리는 일반적으로 sector 라 부르고 이러한 연속된 sector 들의 모음을 cluster 라고 하게 된다. 자세한 내용은 필자가 이전의 작성했던 다음의 글을 참조하길 바란다.

 뭐가 어찌 되었든 일반적으로 섹터는 512byte 의 크기를 가지고 이러한 섹터를 합쳐서 클러스터라는 자료구조로 관리하는 편이다. 아래의 fat.c 코드도 이러한 관습을 그대로 따르고 있다.

- directory entryroot directory

 디렉터리 엔트리는 파일의 정보를 표현하는 메타 데이터 중 하나이다. 위 데이터를 통해 파일의 이름, 확장자, 속성, 생성, 수정 시간, 클러스터 위치, 파일 크기와 같은 데이터 정보를 얻어낼 수 있다. 필자가 작성한 코드에선 struct fat_dirent 구조체로 표현된다.

FAT32 부터는 root directory 의 위치가 디스크의 데이터 영역 어디에든 위치할 수 있으나 필자가 작성하고 있는 FAT12 시스템에서의 root directoryReserved sectorFAT region 바로 뒤에 위치하게 된다.

dirent 의 크기는 (number of root entries * 32) / (bytes per sector) 로 표현되는데 여기에서 number of root entriesBPBroot directory entries 를 의미하고, 32 는 dirent 의 고정 크기인 32 바이트를 의미한다. 마지막으로 섹터의 크기로 나누면 root directory entry 를 위한 sector 의 크기를 계산할 수 있다.

- cluster chain

FAT12 의 경우 각 FAT entry12 bit 를 가지게 된다. 이 12bit FAT entry 의 인덱스 그 자체가 하나의 cluster 를 가리키고 있고, 이 인덱스에 해당하는 FAT entry 는 그 다음 FAT entry 의 인덱스를 가리키게 된다. 참으로 심오하다...

뭐 아무튼 FAT12 entry 의 값에 따라 cluster 의 속성이 조금씩 바뀌게 된다.

AddressMeaning
0x000Free cluster
0x001Reserved cluster
0x002Allocated cluster
0xFF0 ~ 0xFF6Reserved cluster
0xFF7Bad cluster
0xFF8EOC Marker

 여기에서 FAT entry0x002 부터 0xFEF 사이의 범위의 값을 가지고 있다면 다음 FAT entry 를 가리키고 있는 것이다. 이렇게 연결되어 마지막의 0xFF8 를 만나게 되면 파일의 끝을 나타내게 된다. 이렇게 cluster 들이 연결 되어진 형태를 cluster chain 이라고 부르게 된다.


 당장은 요종도 개념만 가지고 바로 코드로 넘어가서 살펴보고, 추가적인 개념은 아래에 덧붙여 설명하려 한다.

2. fat.h

#ifndef FAT_H__
#define FAT_H__

#include <stdint.h>

#include "cluster_list.h"
#include "disksim.h"

typedef unsigned char byte;

enum  fat_type {
	FAT_TYPE_FAT12	= 0x01,
	FAT_TYPE_FAT16	= 0x02,
	FAT_TYPE_FAT32	= 0x04,
};

enum fat_limit {
	FAT_LIMIT_MAX_SECTOR_SIZE	= 512,
	FAT_LIMIT_MAX_NAME_LENGTH	= 256,
	FAT_LIMIT_ENTRY_NAME_LENGTH	= 11,
	FAT_LIMIT_ENTRY_SIZE		= 32
};

enum fat_eoc {
	FAT_EOC12	= 0x0FF8,
	FAT_EOC16	= 0xFFF8,
	FAT_MS_EOC12	= 0x0FFF,
	FAT_MS_EOC16	= 0xFFFF,

	FAT_EOC32	= 0x0FFFFFF8,
	FAT_MS_EOC32	= 0x0FFFFFFF,
};

enum  __attribute__ ((__packed__)) fat_attr {
	FAT_ATTR_READ_ONLY	= 0x01,
	FAT_ATTR_HIDDEN		= 0x02,
	FAT_ATTR_SYSTEM		= 0x04,
	FAT_ATTR_VOLUME_ID	= 0x08,
	FAT_ATTR_DIRECTORY	= 0x10,
	FAT_ATTR_ARCHIVE	= 0x20,
	FAT_ATTR_LONG_NAME	= FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN
		                | FAT_ATTR_SYSTEM    | FAT_ATTR_VOLUME_ID,
};

enum fat_dirent_attr {
	FAT_DIRENT_ATTR_FREE		= 0xE5,
	FAT_DIRENT_ATTR_NO_MORE		= 0x00,
	FAT_DIRENT_ATTR_OVERWRITE	= 0x01
};

enum fat_bit_mask16 {
	FAT_BIT_MASK16_SHUT	= 0x8000,
	FAT_BIT_MASK16_ERR	= 0x4000,
};

enum fat_bit_mask32 {
	FAT_BIT_MASK32_SHUT	= 0x08000000,
	FAT_BIT_MASK32_ERR	= 0x04000000
};

#if defined(_WIN32)
	#pragma pack(push, fatstructures)
#elif defined(__linux__)
	#pragma pack(1)
#else
	#error Unknown system!
#endif

struct fat_boot_sector {
	byte		drive_number;
	byte		reserved1;
	byte		boot_signature;
	uint32_t	volume_id;
	byte		volume_label[11];
	byte		filesystem_type[8];
};

struct fat_bpb {
	// 0x00 ~ 0x0F
	byte		jmp_boot[3];
	byte		oem_name[8];
	uint16_t	bytes_per_sector;
	uint8_t		sectors_per_cluster;
	uint16_t	reserved_sector_count;

	// 0x10 ~ 0x1F
	uint8_t		number_of_fats;
	uint16_t	root_entry_count;
	uint16_t	total_sectors;
	byte		media;
	uint16_t	fat_size16;
	uint16_t	sectors_per_track;
	uint16_t	number_of_heads;
	uint32_t	hidden_sector;

	// 0x20 ~ 0x59
	uint32_t	total_sectors32;

	union {
		struct fat_boot_sector bs;

		struct {
			uint32_t	fat_size32;
			uint16_t	exflags;
			uint16_t	filesystem_version;
			uint32_t	root_cluster;
			uint16_t	filesystem_info;
			uint16_t	backup_boot_sectors;
			byte		reserved[12];
			struct fat_boot_sector bs;
		} bpb32;

		byte padding[FAT_LIMIT_MAX_SECTOR_SIZE - 36];
	};
};

struct fat_fsinfo {
	uint32_t lead_signature;
	byte reserved1[480];
	uint32_t struct_signature;
	uint32_t free_count;
	uint32_t next_free;
	byte reserved2[12];
	uint32_t trail_signature;
};

struct fat_dirent {
	byte name[FAT_LIMIT_ENTRY_NAME_LENGTH];
	enum fat_attr attribute;	// enum fat_attr is packed, 1-byte
	byte nt_reserved;
	byte created_time_then;
	uint16_t created_time;
	uint16_t created_date;

	uint16_t last_access_date;
	uint16_t first_cluster_hi;

	uint16_t write_time;
	uint16_t write_date;

	uint16_t first_cluster_lo;

	uint32_t filesize;
};

#if defined(_WIN32)
	#pragma pack(pop, fatstructures)
#elif defined(__linux__)
	#pragma pack()
#else
	#error Unknown system!
#endif

struct fat_filesystem {
	enum fat_type	type;
	uint32_t	fat_size;
	uint32_t	eoc_mark;
	struct fat_bpb	bpb;

	struct cluster_list cluster_list;
	struct disk_operations *disk;

	union {
		struct fat_fsinfo info32;

		struct {
			uint32_t free_count;
			uint32_t next_free;
		} info;
	};
};

struct fat_filetime {
	uint16_t year;
};

struct fat_dirent_location {
	// cluster index
	uint32_t cluster;
	// cluster 내에서의 sector index
	uint32_t sector;
	// sector 내에서의 dirent index
	int32_t number;
};

struct fat_node {
	struct fat_filesystem		*fs;
	struct fat_dirent		entry;
	struct fat_dirent_location	location;
};

typedef int (*fat_node_add_func)(void *, struct fat_node *);

void fat_umount(struct fat_filesystem *);

int fat_read_superblock(struct fat_filesystem *, struct fat_node *);
int fat_read_dir(struct fat_node *, fat_node_add_func , void *);

int fat_mkdir(const struct fat_node *, const char *, struct fat_node *);
int fat_rmdir(struct fat_node *);

int fat_lookup(struct fat_node *, const char *, struct fat_node *);
int fat_create(struct fat_node *, const char *, struct fat_node *);

int fat_read(struct fat_node *, unsigned long, unsigned long, char *);
int fat_write(struct fat_node *, unsigned long, unsigned long, const char *);

int fat_remove(struct fat_node *);
int fat_df(struct fat_filesystem *, uint32_t *, uint32_t *);

int fat_format(struct disk_operations *, enum fat_type );
const char *fat_type_to_string(enum fat_type type);

#endif /* end of #ifndef FAT_H__ */

 위 코드가 FAT 파일 시스템에서 사용되는 함수와 자료구조가 모여있는 fat.h 헤더파일이다. 위에서 소개했던 bpb 의 각 원소를 맞춰보면 쉽게 이해가 될 것이다.

3. fat.c

fat.c 코드가 1,500 줄이 넘는 관계로 모든 함수를 다 설명하진 못하겠지만, 호출되는 함수 순서대로 차근 차근 풀어서 설명토록 하겠다.

- fat_format()

fat_format() 함수는 format 명령어를 입력하게 되면 최종적으로 호출되는 함수이다. 말 그대로 fat filesystem 에 필요한 기본적인 데이터를 초기화하는 단계로 이 단계에서 bpb (reserved area) 와 fat (fat entry 들), 그리고 마지막으로 root dirent 가 초기화된다.

int fat_format(struct disk_operations *disk, enum fat_type type)
{
	struct fat_bpb bpb;
	
	// bpb 에 기본적인 내용을 채워 넣는다.
	if (fill_bpb(&bpb, type, disk->number_of_sectors, 
		     disk->bytes_per_sector) != 0)
		return -1;
	
	// 위 내용을 0번째 sector 에 기록한다. bpb 의 크기 역시 512 bytes 이다.
	// reserved area 의 초기화라고 생각하면 된다.
	disk->write_sector(disk, 0, &bpb);
	
	printf("bytes per sector: %u\n", bpb.bytes_per_sector);
	printf("sectors per cluster: %u\n", bpb.sectors_per_cluster);
	printf("number of FATs: %u\n", bpb.number_of_fats);
	printf("root entry count: %u\n", bpb.root_entry_count);
	printf("total sectors: %u\n", bpb.total_sectors ? bpb.total_sectors 
				                        : bpb.total_sectors32);
	putchar('\n');
	
	// reserved area 다음 영역인 FAT 영역의 초기화를 진행한다.
	// 초기화된 FAT 영역을 디스크에 기록한다.
	clear_fat(disk, &bpb);
	
	// FAT 의 다음 영역인, root directory entry 를 생성하고
	// 이를 디스크에 기록한다.
	create_root(disk, &bpb);
	
	return 0;
}

- fat_read_superblock()

int fat_read_superblock(struct fat_filesystem *fs, struct fat_node *root)
{
	byte sector[FAT_LIMIT_MAX_SECTOR_SIZE];
	
	if (fs == NULL || fs->disk == NULL)
		return -1;
	
	// reserved area 를 디스크로부터 읽어 들인다. 이는 앞서
	// fill_bpb 를 통해 구성한 내용이다.
	if (fs->disk->read_sector(fs->disk, 0, &fs->bpb))
		return -1;
	
	// bpb 의 유효성을 검사한다.
	if (validate_bpb(&fs->bpb) != 0)
		return -1;
	
	// bpb 를 통해 사용하고 있는 FAT 파일 시스템의 타입 정보를 추출한다.
	fs->type = get_fat_type(&fs->bpb);
	// FAT32 는 아직 지원하지 않으므로 -1 반환
	if (fs->type == FAT_TYPE_FAT32)
		return -1;
	
	// 루트 디렉터리 엔트리 섹터를 읽어 들인다.
	if (read_root_sector(fs, 0, sector))
		return -1;
	
	// root 노드를 0x00 으로 초기화하고...
	memset(root, 0x00, sizeof(struct fat_node));
	// root 노드의 dirent 를 root dirent 로 초기화한다.
	memcpy(&root->entry, sector, sizeof(struct fat_dirent));
	// root 의 filesystem 을 인자로 받아온 filesystem 으로 초기화
	root->fs = fs;
	
	// 두 번째 cluster 의 값을 읽어 들인다. (partition status)
	// 원래는 file system 이 정상적인 상태인지를 확인하는 코드인데 FAT12 에
	// 해당하는 case 가 없으므로 아래의 if statement 가 실행될 일은 없다.
	fs->eoc_mark = get_fat_entry(fs, 1);
	if (fs->type == FAT_TYPE_FAT32) {
		if (fs->eoc_mark & (FAT_BIT_MASK16_SHUT | FAT_BIT_MASK32_ERR))
			return -1;
	} else if (fs->type == FAT_TYPE_FAT16) {
		if (fs->eoc_mark & (FAT_BIT_MASK16_SHUT | FAT_BIT_MASK16_ERR))
			return -1;
	}
	
	// fat_size16 의 값이 0 이 아니라면 (FAT32 가 아니라면)
	if (fs->bpb.fat_size16 != 0)
		fs->fat_size = fs->bpb.fat_size16;
	// fat_size16 의 값이 0 이라면 (FAT32 라면)
	else
		fs->fat_size = fs->bpb.bpb32.fat_size32;
	
	// cluster_list 를 초기화한다.
	cluster_list_init(&fs->cluster_list);
	
	// free cluster 를 찾아 다니면서 cluster chain 을 구성한다.
	// 이는 cluster_list 로 표현된다.
	search_free_clusters(fs);
	
	// root_entry 의 이름을 0x20 으로 초기화한다. 0x20 은 공백문자다.
	memset(root->entry.name, 0x20, FAT_LIMIT_ENTRY_NAME_LENGTH);
	
	return 0;
}

 각 라인마다 주석을 달아놔서 코드를 이해하는데 큰 어려움을 없겠지만 전체적인 맥락에서 설명하면 다음과 같다.

  1. fat_read_superblock() 함수는 mount 명령어 호출 시 최종적으로 호출되는 함수이다.
    (shell_cmd_mount() => fs_mount() => fat_read_superblock())
  2. fat_format() 에서 초기화했던 reserved area 섹터를 읽어 들여 유효성을 검사한다.
  3. root directory 섹터를 읽어온 후 struct fat_direntstruct fat_node 로 변환한다.
  4. cluster 를 읽어 들이면서 cluster chain list 를 구성한다.

 정도가 되겠다. read_root_sector() 함수는 root sectoroffset 을 기준으로 탐색하므로 0 을 인자로 전달하면 root dirent area 의 첫 sector 를 읽어 들인다. 이후에 등장하는 read_root_sector, read_data_sector 류 함수들 역시 자동으로 offset 을 물리 sector 영역 주소로 변환해준다.

- fat_read_dir()

int fat_read_dir(struct fat_node *dir, fat_node_add_func adder, void *list)
{
	byte sector[FAT_LIMIT_MAX_SECTOR_SIZE];
	sector_t root_sector;
	struct fat_dirent_location location;
	
	// 요청한 dirent 가 root directory entry 인지 확인
	if ((IS_POINT_ROOT_ENTRY(dir->entry))
	&&  (dir->fs->type & (FAT_TYPE_FAT12 | FAT_TYPE_FAT16)))
	{
		// FAT32 는 지원 안함
		if (dir->fs->type == FAT_TYPE_FAT32)
			return -1;

		// root dirent 에 존재 가능한 엔트리의 개수를 받아서...
		// root_sector 의 크기를 구한다. 
		struct fat_bpb *bpb = &dir->fs->bpb;
		root_sector = (
			(bpb->root_entry_count * sizeof(struct fat_dirent))
		      + (bpb->bytes_per_sector - 1)
		) / bpb->bytes_per_sector;

		// root directory 가 가질 수 있는 sector size 만큼 반복
		for (int i = 0; i < root_sector; i++) {
			read_root_sector(dir->fs, i, sector);
			location.cluster = 0;
			location.sector = i;
			location.number = 0;

			if (read_dir_from_sector(
				dir->fs, &location, sector, adder, list
			    ) != 0)
				break;
		}
	} else { // root directory 가 아니라면?
		int i = GET_FIRST_CLUSTER(dir->entry);
		do {
			// FAT12 의 경우 사실 cluster 당 sector 가 1 개라서
			// location.sector 는 언제나 0 이고 반복문은 한번만
			// 실행된다.
			for (int j = 0;
			     j < dir->fs->bpb.sectors_per_cluster;
			     j++)
			{
				read_data_sector(dir->fs, i, j, sector);
				location.cluster = i;
				location.sector = j;
				location.number = 0;

				if (read_dir_from_sector(
					dir->fs, &location, sector, adder, list
				))
					break;
			}

			// 다음 cluster 정보를 가져온다.
			i = get_fat_entry(dir->fs, i);
		} while ( (!is_eoc(dir->fs->type, i)) && (i != 0) );
	}

	return 0;
}

 위 코드는 현재 directory 를 순회하면서 dirent list 를 만들어서 유저 영역으로 넘겨주는 함수이다. shell 에서 ls 명령을 입력하면 호출된다. 여기에서는 root dirent areadirentdata sector areadirent 의 동작 방식이 다르다.

- root dirent area 동작

root dirent area 에 있는 모든~ direntcluster 가 0 으로 지정되어 있고 cluster 가 0 번이면 이를 root dirent area 에 속해있는 dirent 로 이해하고 이하의 함수들이 동작하게 구성되어 있다.

root directory 에서 ls 명령어를 입력하게 되면 IS_POINT_ROOT_ENTRY 에서 true 가 되며 if 문 안의 구문들이 실행된다.

root entry 의 전체 개수를 bpb 로 부터 뽑아온 후 root dirent areasector 크기를 구해 그 크기만큼 반복한다. 실제 fat_dirent_node 구조체를 유저 영역으로 넘기는 것은 read_dir_from_sector() 가 수행한다. 이 함수는 sector 내에서 다시 dirent 를 찾는다.

- data sector area 동작

root dirent 가 아닌 다른 directory 에서 ls 명령어를 입력하게 되면 아래의 else 문으로 빠진다. 동작 방식에 큰 차이는 없으나 주목할만한 점은 location 정보를 정확하게 초기화한다는 것이다.

  두 번째는 do ~ while() 문 최하단의 get_fat_entry() 이다. root dirent area 와 달리 data sector areadirent 는 선형적으로 구성될 수가 없기 때문에 read_dir_from_sector() 에서 NO MORE dirent 를 만난게 아니라면 현재 cluster 값을 통해 다음 cluster 값을 얻어온다. 만일 EOC 라면 탈출한다.

- fat_mkdir()

int fat_mkdir(
		const struct fat_node *parent, const char *entry_name,
		struct fat_node *ret
){
	struct fat_node dot_node, dotdot_node;
	uint32_t first_cluster;
	char name[FAT_LIMIT_MAX_NAME_LENGTH];
	int result;

	strncpy(name, entry_name, FAT_LIMIT_MAX_NAME_LENGTH);

	// 적합한 이름으로 변경
	if (format_name(parent->fs, name))
		return -1;

	// fat_node 초기화
	memset(ret, 0x00, sizeof(struct fat_node));
	memcpy(ret->entry.name, name, FAT_LIMIT_ENTRY_NAME_LENGTH);
	ret->entry.attribute = FAT_ATTR_DIRECTORY;

	// 빈 cluster 를 할당 받고 이를 종단 cluster 로 설정한다.
	first_cluster = alloc_free_cluster(parent->fs);
	if (first_cluster == 0)
		return -1;

	set_fat_entry(parent->fs, first_cluster, get_ms_eoc(parent->fs->type));

	// 할당받은 cluster 를 fat_node entry 의 first cluster 로 등록
	SET_FIRST_CLUSTER(ret->entry, first_cluster);
	// 새로 생성한 dirent 를 root dirent 의 자식으로 등록
	result = register_child_dirent(parent, ret, FAT_DIRENT_ATTR_NO_MORE);
	if (result)
		return -1;

	// 파일 시스템을 부모로부터 계승 받는다.
	ret->fs = parent->fs;

	// dot entry 를 생성 및 부모의 자식으로 등록
	memset(&dot_node, 0x00, sizeof(struct fat_node));
	memset(dot_node.entry.name, 0x20, FAT_LIMIT_ENTRY_NAME_LENGTH);
	dot_node.entry.name[0] = '.';
	dot_node.entry.attribute = FAT_ATTR_DIRECTORY;
	// FAT_DIRENT_ATTR_OVERWRITE 를 사용하기 때문에 새로 생성된 dirent 의
	// 첫 번째 entry 는 dot entry 가 먹게 된다.
	register_child_dirent(ret, &dot_node, FAT_DIRENT_ATTR_OVERWRITE);

	// dotdot entry 를 생성 및 부모의 자식으로 등록
	memset(&dotdot_node, 0x00, sizeof(struct fat_node));
	memset(dotdot_node.entry.name, 0x20, 11);
	dotdot_node.entry.name[0] = '.';
	dotdot_node.entry.name[1] = '.';
	dotdot_node.entry.attribute = FAT_ATTR_DIRECTORY;

	// dotdot entry 의 cluster 값을 parent 의 첫 번째 cluster 로 등록
	// 부모의 위치를 정보를 알림
	SET_FIRST_CLUSTER(dotdot_node.entry, GET_FIRST_CLUSTER(parent->entry));
	register_child_dirent(ret, &dotdot_node, FAT_DIRENT_ATTR_NO_MORE); 

	return 0;
}

 코드 전체에 주석을 달아서 이해의 어려움은 없으리라 생각한다. 주목할 점은 register_child_dirent 함수의 인자로 전달한 attribute 이다 이는 register_child_dirent() 함수에게 전달되어 이 정보를 바탕으로 첫 번째 dirent 를 덮어쓸지 여부를 결정하게 된다. 새로 생성된 ret dirent 의 자식 direntdot dirent 의 경우 FAT_DIRENT_ATTR_OVERWRITE 를 옵션으로 주어 첫 번째 direntdot dirent 로 설정하고 그 다음 direntNO MORE entry 로 설정하게 된다.

- fat_rmdir()

int fat_rmdir(struct fat_node *dir)
{
	
	// sub directory 가 있는지 확인하고, 찾았다면 -1 반환
	if (has_sub_dirents(dir->fs, &dir->entry))
		return -1;

	// 못 찾았으나 정작 삭제를 요청한 dirent 가 directory 가 아니라면
	if ( !(dir->entry.attribute & FAT_ATTR_DIRECTORY) )
		return -1;

	// dirent 의 attribute 를 free 로 변경
	dir->entry.name[0] = FAT_DIRENT_ATTR_FREE;
	// 변경된 정보를 disk 에 기록
	write_dirent(dir->fs, &dir->location, &dir->entry);
	
	// free cluster list 에 등록
	free_cluster_chain(dir->fs, GET_FIRST_CLUSTER(dir->entry));

	return 0;
}

설명 생략

- fat_lookup()

int fat_lookup(
		struct fat_node *parent, const char *entry_name,
		struct fat_node *ret_entry
){
	struct fat_dirent_location begin;
	char formatted_name[FAT_LIMIT_ENTRY_NAME_LENGTH] = { 0, };

	begin.cluster = GET_FIRST_CLUSTER(parent->entry);
	begin.sector = 0;
	begin.number = 0;

	strncpy(formatted_name, entry_name, FAT_LIMIT_ENTRY_NAME_LENGTH);

	if (format_name(parent->fs, formatted_name))
		return -1;

	if (IS_POINT_ROOT_ENTRY(parent->entry))
		begin.cluster = 0;

	return lookup_dirent(parent->fs, &begin, formatted_name, ret_entry);
}

인자로 전달된 이름의 dirent 를 찾는 함수이다.

- fat_create()

int fat_create(
		struct fat_node *parent, const char *entry_name,
		struct fat_node *ret_entry
){
	struct fat_dirent_location first;
	char name[FAT_LIMIT_ENTRY_NAME_LENGTH] = { 0, };

	// 이름을 형식에 맞춘다.
	strncpy(name, entry_name, FAT_LIMIT_ENTRY_NAME_LENGTH);
	if (format_name(parent->fs, name))
		return -1;

	// ret_entry 의 정보를 초기화
	memset(ret_entry, 0x00, sizeof(struct fat_node));

	// 이름만 등록
	memcpy(ret_entry->entry.name, name, FAT_LIMIT_ENTRY_NAME_LENGTH);

	// parent 의 cluster 를 가져온다.
	// sector 와 number 를 0 으로 초기화해서 
	first.cluster = GET_FIRST_CLUSTER(parent->entry);
	first.sector = 0;
	first.number = 0;

	// 같은 이름은 가진 entry 가 있는지 탐색해본다.
	if (lookup_dirent(parent->fs, &first, name, ret_entry) == 0)
		return -1;

	// 없었다면 file system 을 상속 받아 parent 의 자식으로 등록한다.
	ret_entry->fs = parent->fs;
	if (register_child_dirent(parent, ret_entry, 0))
		return -1;

	return 0;
}

최초의 direntroot dirent 이고 이곳에서 mkdir 명령을 수행하게 되면 새롭게 cluster 를 할당 받고 이것이 새롭게 생성된 dirent 의 첫 번째 cluster 가 된다. 해당 directory 내에 생성되는 파일은 마찬가지로 부모의 cluster 정보를 가지고 새롭게 파일이 생성된다.

lookup_dirent() 함수는 struct fat_dirent_location 정보를 적절하게 초기화하여 빈 자리인 first.sectorfirst.number 정보를 채운다.

- fat_read()

int fat_read(
		struct fat_node *file, unsigned long offset,
		unsigned long length, char *buffer
){
	byte sector[FAT_LIMIT_MAX_SECTOR_SIZE];
	uint32_t current_offset, current_cluster, cluster_seq = 0;
	uint32_t cluster_number, sector_number, sector_offset;
	uint32_t read_end;
	uint32_t cluster_size, cluster_offset = 0;

	if (offset > file->entry.filesize)
		return -1;

	current_cluster = GET_FIRST_CLUSTER(file->entry);
	read_end = MIN(offset + length, file->entry.filesize);

	current_offset = offset;

	cluster_size = file->fs->bpb.bytes_per_sector
		     * file->fs->bpb.sectors_per_cluster;
	cluster_offset = cluster_size;

	while (offset > cluster_offset)
	{
		current_cluster = get_fat_entry(file->fs, current_cluster);
		cluster_offset += cluster_size;

		cluster_seq++;
	}

	while (current_offset < read_end)
	{
		uint32_t copy_length;

		cluster_number = current_offset / cluster_size;
		while (cluster_seq < cluster_number) {
			cluster_seq++;
			current_cluster = get_fat_entry(
				file->fs, current_cluster
			);
		}

		sector_number = (
			current_offset / (file->fs->bpb.bytes_per_sector)
		) % file->fs->bpb.sectors_per_cluster;

		sector_offset = current_offset % file->fs->bpb.bytes_per_sector;

		if (read_data_sector(
			file->fs, current_cluster, sector_number, sector
		    ))
			break;

		copy_length = MIN(
			file->fs->bpb.bytes_per_sector - sector_offset,
			read_end - current_offset
		);

		memcpy(buffer, &sector[sector_offset], copy_length);

		buffer += copy_length;
		current_offset += copy_length;
	}

	return current_offset - offset;
}

cat 명령어 입력 시 파일 정보를 읽어 들이는 함수이다. offset 을 인자로 받는데 이 정보를 통해 몇 번째 cluster 로 이동해야 할지에 대한 정보를 받는다.

 가령 1324offset 으로 들어왔고 sector 의 크기가 512, cluster per sector1 이라면 get_fat_entry() 함수가 두 번 호출되게 된다. 그럼 1024 번째 데이터부터 읽을 수 있게 된다.

- fat_write()

int fat_write(
		struct fat_node *file, unsigned long offset,
		unsigned long length, const char *buffer
) {
	byte sector[FAT_LIMIT_MAX_SECTOR_SIZE];
	uint32_t current_offset, current_cluster, cluster_seq = 0;
	uint32_t cluster_number, sector_number, sector_offset;
	uint32_t read_end, cluster_size, cluster_offset;

	current_cluster = GET_FIRST_CLUSTER(file->entry);
	read_end = offset + length;

	current_offset = offset;

	cluster_size = file->fs->bpb.bytes_per_sector 
		     * file->fs->bpb.sectors_per_cluster;
	cluster_offset = cluster_size;

	while (offset > cluster_offset) {
		current_cluster = get_fat_entry(file->fs, current_cluster);
		cluster_offset += cluster_size;
		cluster_seq++;
	}

	while (current_offset < read_end) {
		uint32_t copy_length;

		cluster_number = current_offset / cluster_size;
		if (current_cluster == 0) {
			current_cluster = alloc_free_cluster(file->fs);
			if (current_cluster == 0)
				return -1;

			SET_FIRST_CLUSTER(file->entry, current_cluster);
			set_fat_entry(file->fs,
				      current_cluster,
			              get_ms_eoc(file->fs->type));
		}

		while (cluster_seq < cluster_number) {
			uint32_t next_cluster;
			cluster_seq++;

			next_cluster = get_fat_entry(file->fs, current_cluster);
			if (is_eoc(file->fs->type, next_cluster)) {
				next_cluster = span_cluster_chain(
					file->fs, current_cluster
				);

				if (next_cluster == 0)
					break;
			}

			current_cluster = next_cluster;
		}

		sector_number = (
			current_offset / (file->fs->bpb.bytes_per_sector)
		) % file->fs->bpb.sectors_per_cluster;
		sector_offset = current_offset % file->fs->bpb.bytes_per_sector;

		copy_length = MIN(
			file->fs->bpb.bytes_per_sector - sector_offset,
			read_end - current_offset
		);

		if (copy_length != file->fs->bpb.bytes_per_sector)
			if (read_data_sector(
				file->fs, current_cluster,
				sector_number, sector
			    ))
				break;

		memcpy(&sector[sector_offset], buffer, copy_length);

		if (write_data_sector(
				file->fs, current_cluster,
				sector_number, sector
		    ))
			break;

		buffer += copy_length;
		current_offset += copy_length;
	}

	file->entry.filesize = MAX(current_offset, file->entry.filesize);
	write_dirent(file->fs, &file->location, &file->entry);

	return current_offset - offset;
}

fat_write() 함수는 fat_read() 함수와는 정 반대로 행동한다. 탐색 방식에는 별 차이가 없지만 fat_read() 함수와 비교해서 대조적인 차이점은 fat_write() 함수는 새로운 cluster 를 할당받아 cluster chain 을 구성해 파일의 크기를 늘일 수 있다는 점이다.

- fat_remove()

int fat_remove(struct fat_node *file)
{
	// directory 면 바로 빠져 나간다.
	if (file->entry.attribute & FAT_ATTR_DIRECTORY)
		return -1;

	// file 의 ATTR 만 free 로 바꿔서 등록한다.
	file->entry.name[0] = FAT_DIRENT_ATTR_FREE;
	write_dirent(file->fs, &file->location, &file->entry);

	// 파일에 할당된 cluster 를 따라가면서 모두 할당해제한다.
	free_cluster_chain(file->fs, GET_FIRST_CLUSTER(file->entry));

	return 0;
}

fat_remove() 함수는 fat_create() 로 할당된(directory 가 아닌) dirent 를 삭제한다. cluster chain 이 있다면 순차적으로 따라가면서 cluster 를 해제하고 이를 free cluster listpush 한다.

- fat_df()

int fat_df(
		struct fat_filesystem *fs,
		uint32_t *total_sectors, uint32_t *used_sectors
){
	if (fs->bpb.total_sectors != 0)
		*total_sectors = fs->bpb.total_sectors;
	else
		*total_sectors = fs->bpb.total_sectors32;

	*used_sectors = *total_sectors 
		      - (fs->cluster_list.count * fs->bpb.sectors_per_cluster);

	return 0;
}

fat_df() 함수는 현재 디스크의 상태 정보를 반환한다.

4. 실행 결과

출처

[사이트] https://en.wikipedia.org/wiki/BIOS_parameter_block#FAT12_/_FAT16
[사이트] https://hyd3.tistory.com/125
[사이트] https://ko.wikipedia.org/wiki/데이터_클러스터
[사이트] https://blog.naver.com/sjhmc9695/221332151696
[사이트] http://forensic-proof.com/archives/385
[사이트] https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system
[사이트] http://forensic-proof.com/archives/378
[사이트] https://kkikyul.tistory.com/39
[책] 리눅스 커널 내부구조

profile
2000.11.30

0개의 댓글