#2 FAT - Directory Entry
Table of Contents
Abstract
본 글에서는 FAT 파일 시스템에서의 파일 테이블 표현을 위한 디렉토리 엔트리의 구조, Long File Name을 위한 LFN 엔트리와 LFN 엔트리의 해석법, 마지막으로 트리 구조의 폴더 구조를 FAT에서는 어떻게 표현하는지를 설명한다.
FAT에 대한 개요와 BPB, FAT 해석 및 FAT Chain 분석법은 앞선 글을 참고하길 바란다.
- #1 FAT - File Allocate Table
https://geun-yeong.tistory.com/75
Directory Table
Root Directory
트리 구조의 파일 구조를 지원하는 모든 파일 시스템은 디렉토리와 파일 위치의 최초 시작 지점인 루트 디렉토리가 존재한다. 리눅스 시스템에선 '/'가 루트 디렉토리고, Windows 시스템에선 C:\, D:\ 등 드라이브 문자 바로 뒤가 루트 디렉토리다. 모든 파일과 디렉토리는 이 루트 디렉토리를 시작으로 트리 노드로써 뻗어나간다. FAT 볼륨의 루트 디렉토리 위치는 BPB에 정의되어 있으며 별 일이 없다면 루트 디렉토리는 클러스터 2에 위치한다. 클러스터 2는 FAT 영역 바로 뒤에 위치한다.
FAT 볼륨의 루트 디렉토리는 볼륨 이름을 첫 디렉토리 엔트리로 한다. 아래 그림은 FAT32로 포맷하고 볼륨 이름으로 "FAT32"를 지정한 볼륨의 루트 디렉토리 엔트리이다. 루트 디렉토리 엔트리를 찾으면 볼륨 이름을 알 수 있다는 의미다.
루트 디렉토리가 저장된 클러스터를 시작으로 FAT 체인을 따라가면 그것이 곧 파일 테이블(=디렉토리 테이블)이다. 앞서 FAT 체인 절에서 봤듯이 클러스터 2(FAT[2])에 EoC 마커 값이 들어있었으므로 아직 파일 테이블은 1개 클러스터만 사용중이라는 걸 알 수 있다. 파일과 디렉토리 수가 늘어나 디렉토리 엔트리가 많아질 수록 파일 테이블에 할당되는 클러스터는 많아진다.
Directory Entry
Structure
디렉토리 엔트리는 디렉토리나 파일에 대한 메타데이터를 가지는 32 바이트 크기의 구조체다. 디렉토리 엔트리의 구조는 아래 표와 같다.
이름 | 오프셋(h) | 크기 | 설명 |
Name | 0 | 8 | 디렉토리 혹은 파일의 이름이다. 이름 길이가 8 바이트보다 작은 경우 나머지 영역은 ' '(0x20)으로 패딩되며, 파일 사용 시 패딩은 무시된다. 8 바이트 중 첫 1 바이트는 파일 이름을 나타내면서 동시에 파일의 상태를 나타내는 플래그 바이트로도 사용된다. 예를 들어 첫 1 바이트가 0xE5 값을 가지고 있다면 해당 디렉토리, 파일은 삭제되었다는 걸 의미한다. |
Extension | 8 | 3 | 파일의 확장자가 저장되는 영역이다. 만약 디렉토리라면 패딩 문자인 ' '(0x20)으로 3 바이트 모두 채워진다. |
File Attributes | B | 1 | 해당 엔트리의 속성을 나타낸다. FAT에는 LFN 엔트리도 존재하기 때문에 LFN 엔트리인지를 나타낼 때 사용하기도 하고, 해당 엔트리가 가리키는 대상이 디렉토리인지 파일인지 등 다양한 속성 정보를 나타낸다. - 0x01: 읽기 전용 파일 (Read only) - 0x02: 숨김 파일 (Hidden file) - 0x04: 운영체제 시스템 파일 (System file) - 0x08: 볼륨의 이름 (Volume label) - 0x0F: LFN 엔트리 - 0x10: 디렉토리 (Directory) - 0x20: 일반 파일 (Archive) |
Reserved | C | 1 | - |
Create Time Tenths | D | 1 | 10 ms 단위의 파일 생성 시간이다. 0부터 199 사이의 값을 가질 수 있다. FAT의 디렉토리 엔트리는 초(Second) 값을 2초 단위로만 가질 수 있기 때문에 이를 보완하기 위함이다. 100 미만이면 0.## 그대로 더하면 되고, 100을 넘으면 1.##을 생성 시간 중 초에 더해주면 된다. - 일부 DOS 버전에서는 삭제된 파일의 첫번째 문자를 나타낸다. |
Create Time | E | 2 | 파일 생성 시간(시, 분, 초) |
Create Date | 10 | 2 | 파일 생성 날짜 |
Last Accessed Date | 12 | 2 | 마지막 접근 날짜 |
Starting Cluster High | 14 | 2 | 파일 데이터가 저장된 클러스터의 시작 번호 상위 2 바이트 |
Last Written Time | 16 | 2 | 마지막 수정 시간(시, 분, 초) |
Last Written Date | 18 | 2 | 마지막 수정 날짜 |
Starting Cluster Low | 1A | 2 | 파일 데이터가 저장된 클러스터의 시작 번호 하위 2 바이트 |
File Size | 1C | 4 | 파일 크기 |
Name & Extension Field
LFN 엔트리가 아닌 일반 Directory Entry의 Name 필드는 Short file name을 저장한다. 파일 이름이 영어 8자, 유니코드 4자 이하면 Name 필드에 모두 저장되지만 그보다 길면 LFN이라는 기능을 사용해 긴 파일 이름을 저장한다. 확장자 필드도 3 바이트밖에 안되기 때문에 확장자가 이보다 길면 이 때도 LFN을 사용한다.
Name 필드의 첫 바이트는 State Byte라고도 한다. 첫 바이트의 값에 따라 의미가 달라진다. State Byte 값과 의미는 아래와 같다.[1]
- 0x00 : 엔트리를 사용할 수 있으며, 사용 중인 후속 항목이 없다. 또한 DOS에서 디렉토리 테이블을 스캔할 때 종료 마커 역할을 한다. 단 일부 DOS 버전은 종료 마커로 사용하지 않는다.
- 0x05 : 0xE5 값을 문자로써 사용해야 하는 경우 이를 대신하기 위해 0x05를 사용한다. 일부 DOS에서는 이 값을 삭제된 파일임을 나타내는 데 사용한다.
- 0xE5 : 엔트리가 삭제되어 사용 가능함을 나타낸다.
Name과 Extension 필드 모두 이름이 짧을 경우 공백 ' '(0x20)으로 패딩한다. 예를 들어 파일 이름이 test라면 디렉토리 엔트리의 Name 필드에는 "test "으로 저장된다. 하지만 파일 목록을 읽거나 파일을 열 때 파일 이름 뒤의 공백은 모두 무시된다. 때문에 "test "이 아닌 "test"로 접근이 가능하다. 또한 Short file name에서 소문자는 모두 대문자로 변환된다. LFN을 이용하면 소문자를 사용할 수 있다.
Date & Time Format
파일의 생성 시간과 수정 시간을 나타내는 Create Time과 Last Written Time은 2 바이트 크기 안에 시, 분, 초를 표현한다. 시(Hour)를 표현하는 데 하위 5 비트, 분(Minute)을 표현하는 데 중간 6 비트, 초(Second)를 표현하는 데 상위 5비트를 쓴다. Time 값이 리틀 엔디언으로 0x8B8C이라면 시, 분, 초는 17시 36분 22초가 된다.
2^5은 32이므로 0-24 시를 표현할 수 있고, 2^6은 64이므로 0-59 분을 모두 표현할 수 있는데 초는 2^5이기 때문에 분처럼 0-59를 표현할 수 없다. 그래서 FAT의 초는 2초 단위로 나타낸다. 예를 들어 초에 해당하는 값이 13이라면 이는 26초를 의미한다.
FAT의 디렉토리 엔트리는 날짜도 2 바이트로 표현한다. 상위 7 비트를 년(Year), 중간 4 비트를 월(Month), 하위 5 비트를 일(Day)로 사용하며, 년의 경우 1980년부터 흐른 년도를 의미하기 때문에 1980을 더해야 한다. Date 값이 리틀 엔디언으로 0x3454라면 년, 월, 일은 2022년 1월 20일을 의미하게 된다.
Parsing the Root Directory Entry
디렉토리 엔트리 구조대로 Root Directory Entry를 분석하면 아래 표와 같다. 여러 시간 값 중 Written 시간만은 가지고 있기 때문에 볼륨 이름을 설정한 마지막 시간을 알 수 있다.
이름 | 값 | 의미 |
Name | "FAT32 " | 볼륨의 이름은 "FAT32"다. |
Extension | " " | 확장자는 없다. |
Attribute | 0x08 | 볼륨의 이름을 나타내는 엔트리다. |
Create Time Tenth | 0x00 | 루트 디렉토리 엔트리는 해당 시간 값을 가지지 않는다. |
Create Time | 0x0000 | |
Create Date | 0x0000 | |
Last Accessed Date | 0x0000 | |
Starting Clutster High | 0x0000 | 루트 디렉토리 엔트리는 시작 클러스터 값을 가지지 않는다. |
Last Written Time | 0x8C88 | 17시 36분 16초다. |
Last Written Date | 0x5434 | 2022년 1월 20일이다. |
Starting Cluster Low | 0x0000 | 루트 디렉토리 엔트리는 시작 클러스터 값을 가지지 않는다. |
File Size | 0x0000 | 루트 디렉토리 엔트리는 파일 크기 값을 가지지 않는다. |
Parsing the Regular File Entry
이번엔 루트 디렉토리 엔트리가 아닌 일반 파일의 디렉토리 엔트리를 분석해보자.
이름 | 값 | 의미 |
Name | "IMAGE" | 파일의 이름은 "image"다. |
Extension | "JPG" | 확장자는 "jpg"다. |
Attribute | 0x20 | 일반 파일임을 나타낸다. |
Create Time Tenth | 0x3F | 파일의 생성 시간 중 10 ms 단위의 값은 63이다. 1/100 * 63을 한 값을 Create Time에 더해주면 된다. |
Create Time | 0x8C8B | 17시 36분 22초다. Time Tenth 값과 합쳐 17시 36분 22.63초가 된다. |
Create Date | 0x5434 | 2022년 1월 20일이다. |
Last Accessed Date | 0x5434 | 2022년 1월 20일이다. |
Starting Clutster High | 0x0000 | image.jpg의 파일 데이터 위치를 나타내는 클러스터 주소 4 바이트 중 상위 2 바이트는 0이다. |
Last Written Time | 0x8C61 | 17시 33분 4초다. |
Last Written Date | 0x52DB | 2021년 6월 27일이다. |
Starting Cluster Low | 0x0005 | Image.jpg의 파일 데이터 위치를 나타내는 클러스터 주소 4 바이트 중 하위 2 바이트는 5다. 상위 2바이트 값과 합쳐 image.jpg의 파일 데이터는 클러스터 5부터 시작한다. |
File Size | 0x0001BAAA | image.jpg의 파일 크기는 113,322 바이트다. |
Long File Name (LFN)
앞서 Name 필드에 대한 설명에서 파일 이름이 엔트리 구조체가 포함할 수 있는 크기를 넘어셔면 LFN이라는 기능을 사용해 긴 파일 이름을 지원한다고 했다. LFN을 사용하면 최대 255자의 파일 이름을 유니코드로 저장할 수 있다. LFN은 간단해 보이는 디렉토리 테이블 구조를 복잡해보이게 만드는 주범이다.
8.3 File Name (Short FileName; SFN)
8.3 파일 이름은 Windows 95, Windows NT 3.51 이전의 Windows와 오래된 DOS 버전에서 사용하는 파일 이름 규칙이다.[2] 이름으론 8자를, 확장자론 3자를 사용하도록 한 규칙인데, 이 이름 규칙에 포함되지 않는 이름들은 특별한 변환 규칙을 통해 변환되고 LFN 엔트리에 전체 이름이 저장된다. 이 변환 규칙에 의해 만들어진 파일 이름은 '~'를 포함하는 이름이 된다. 예를 들어 파일 이름이 "test_file_name.txt"라면 변환 규칙에 의해 "TEST_F~1.TXT"로 바뀌고 전체 이름은 LFN 엔트리에 저장된다.
Structure
LFN 엔트리 구조는 아래 표와 같다. 디렉토리 엔트리와의 호환성을 위해 똑같이 32바이트 크기를 갖는다.
이름 | 오프셋(h) | 크기 | 설명 |
Sequence Number | 0 | 1 | 하나의 LFN 엔트리는 최대 13 자를 포함할 수 있다. 파일 이름이 이보다 길면 여러 개의 LFN 엔트리를 사용해야 하는데, 이 LFN 엔트리 간의 순서를 나타내는 필드다. 만약 여러 개의 LFN 엔트리 중 마지막을 의미하게 되면 순서 값과 0x40을 OR 연산한 값을 저장한다. 만약 3번째 LFN 엔트리가 마지막이라면 0x40 | 0x03인 0x43이 마지막 LFN 엔트리의 Seq Num 값으로 저장된다. Seq Num 값은 1부터 시작한다. |
LFN Characters 1-5 | 1 | 10 | 유니코드 형식의 파일 이름 문자열 5자가 저장된다. - 문자가 할당되지 않을 경우 0xFF로 패딩된다. |
Attribute | B | 1 | 디렉토리 엔트리의 Attribute 필드와 동일한데, LFN 엔트리이므로 항상 0x0F 값을 갖는다. |
Reserved | C | 1 | - |
Checksum | D | 1 | 파일 이름의 체크섬 값이다. ^ 보충 필요 |
LFN Characters 6-11 | E | 12 | 유니코드 형식의 파일 이름 문자열 6자가 저장된다. - 문자가 할당되지 않을 경우 0xFF로 패딩된다. |
Reserved | 1A | 2 | - |
LFN Characters 12-13 | 1C | 4 | 유니코드 형식의 파일 이름 문자열 2자가 저장된다. - 문자가 할당되지 않을 경우 0xFF로 패딩된다. |
How to read it
LFN 엔트리를 해석하는 법을 알아보자. 아래 그림은 "long_file_name_test.long_ext_test"라는 텍스트 파일의 디렉토리 엔트리(④)와 LFN 엔트리(①, ②, ③)다. 파일 이름이 19자, 확장자가 13자, Dot('.')까지 합치면 총 33자이므로 LFN을 사용해야만 한다.
Short File Name은 "LONG_F~1.LON"이다. 이는 ④에 기록되어 있다.
우리가 흔히 생각하는 것처럼 위에서부터 32바이트 씩 읽어보자. 그럼 LFN 엔트리가 SFN이 담긴 디렉토리 엔트리보다 먼저 나온다. 그리고 순서도 거꾸로다. "long_file_name_test.long_ext_test"를 만들기 위해서는 ③->②->① 순으로 읽어야 한다. 그러므로 만약 Parser를 만든다면 0xB 오프셋에 위치한 Attribute 값이 0xF일 때 해당 엔트리들을 별도로 기억해두고, Sequence 번호대로 정렬 후 파일 이름을 저장한 영역을 Concatenate한다면 원래 파일 이름을 알아낼 수 있을 것이다.
위 그림속 LFN을 정리하면 아래와 같다.
- ①: xt_test
- ②: e_test.long_e
- ③: long_file_nam
- ③ + ② + ① = "long_file_nam" + "e_test.long_e" + "xt_test" = "long_file_name_test.long_ext_test"
Representation of Folder Structure
앞서 Root Directory의 Directroy Entry들을 알아봤다. 하지만 알다시피 폴더는 하나만 있지 않다. 하나의 폴더는 여러 개의 하위 폴더를 가질 수 있다. 이런 하위 폴더들은 어떻게 표현될까? 정답은 다중 리스트처럼 디렉토리 엔트리의 배열을 가리키는 방식으로 표현된다. 디렉토리 엔트리 내에 파일의 데이터가 위치한 클러스터를 가리키는 필드(Starting Cluster)가 있다는 걸 봤을 것이다. 폴더를 나타내는 디렉토리 엔트리 Starting Cluster를 따라가보면 또다른 디렉토리 엔트리의 배열이 나타난다.
그림으로 나타내면 아래와 같은 구조다. 그림으로 나타낸 폴더 구조를 그대로 빼다 박은 듯한, 매우 직관적인 방식으로 폴더 구조가 표현된다는 걸 알 수 있다.
Close
이상으로 FAT에서의 폴더 구조를 알아봤다. FAT는 파일이나 폴더마다 자신의 부모 엔트리를 가리키는 방식이 아니라 폴더가 자신이 포함하고 있는 하위 엔트리들의 목록을 저장하는 방식이기 때문에 매우 직관적이다. 하지만 특정 파일을 찾는 작업에 있어서는 매우 극심한 효율을 보인다는 걸 알 수 있다.
다음 글에서는 FAT 파일 시스템에서의 파일 삭제, 복구, 삭제된 엔트리의 재사용 등에 대해서 다뤄보려 한다.
Refs.
- [1] Design of the FAT file system, Wikipedia, https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system
- [2] 8.3 파일 이름, Wikipedia, https://ko.wikipedia.org/wiki/8.3_%ED%8C%8C%EC%9D%BC_%EC%9D%B4%EB%A6%84