<aside> 💡 확장 가능한 파일을 구현한다.
</aside>
지금까지의 파일 시스템에서 파일 크기는 파일을 만들 때 지정된다. 그러나 대부분의 현대 파일 시스템에서 파일은 처음에 크기가 0인 상태로 생성되었다가 파일 끝에 쓰기를 할 때마다 확장된다.
이 때 파일이 파일 시스템 크기(메타데이터 제외)를 초과할 수 없다는 점을 제외하고는 파일 크기에 미리 결정된 제한이 없어야 한다. 루트 디렉터리 파일에도 똑같이 적용되는데, 디렉터리 파일에 들어갈 수 있는 파일의 개수가 기존에는 최대 16개 파일이었다면, 이제는 그 이상으로 확장될 수 있다.
사용자 프로그램은 EOF(End-of-File)를 넘어 탐색할 수 있다. 이 때 SEEK(검색) 자체는 파일을 확장하지 않는다. 하지만 EOF를 지난 위치에서 WRITE하면 파일이 기록 중인 위치로 파일의 크기가 확장되어야 한다. 이 때 이전 EOF와 WRITE 시작 위치 사이의 간격은 0으로 채워져야 한다. EOF 이후의 위치에서 READ하면 바이트를 반환하지 않는다.
EOF를 훨씬 넘어서 쓰면 많은 블록이 완전히 0이 될 수 있다. 일부 파일 시스템은 실제로 데이터 블록을 할당해 0으로 해당 데이터 블록을 채운다. 하지만 굳이 실제로 데이터 블록을 할당하지 않고, 실제로 해당 블록이 명시적으로 작성될 때까지 sparse 파일을 이용해 간접적으로 0 블록들을 표현한다. 파일 시스템에서 할당 전략으로 이 둘 중 하나를 채택할 수 있다.
<aside> 💡 메모리의 BUFFER에서 SIZE만큼의 데이터를 INODE에 해당하는 디스크 파일의 OFFSET부터 쓴다.
</aside>
먼저 디스크의 파일에 충분한 공간이 있는지를 파악한다. 즉, 파일의 시작 클러스터부터 OFFSET + SIZE까지의 공간이 모두 파일의 클러스터 체인에 있어야 한다.
만약 없다면 디스크에서 빈 클러스터를 할당받아 파일의 클러스터 체인에 추가해주면서 파일을 확장시킨다. 이 때, 파일의 끝, EOF에서부터 WRITE의 시작점까지의 위치가 떨어져 있을 수 있다. 그렇다면 이 공간을 모두 0으로 초기화해주어야 한다. 추가적으로 새로 할당받은 디스크 클러스터도 모두 0으로 초기화한다.
그 후에 쓰기 작업을 수행한다. SECTOR SIZE 단위로 메모리의 BUFFER에서부터 디스크의 파일 클러스터에 데이터를 복사한다.
off_t
inode_write_at (struct inode *inode, const void *buffer_, off_t size,
off_t offset) {
const uint8_t *buffer = buffer_;
off_t bytes_written = 0;
uint8_t *bounce = NULL;
bool grow = false; // 이 파일이 EXTENDED 된 파일임을 나타낸다.
uint8_t zero[DISK_SECTOR_SIZE]; // zero padding을 위한 버퍼
/* 해당 파일이 WRITE 작업을 허용하지 않으면 0을 리턴한다. */
if (inode->deny_write_cnt)
return 0;
/* 아이노드의 데이터 영역에 충분한 공간이 있는지를 확인한다.
WRITE가 끝나는 지점인 offset+size 까지의 공간이 있어야 한다.
그 정도의 공간이 없으면 -1을 리턴한다. */
disk_sector_t sector_idx = byte_to_sector(inode, offset + size);
// #ifdef EFILESYS
/* 디스크에 충분한 공간이 없다면 파일을 EXTEND한다.
EXTEND 시, EOF부터 WRITE를 끝내는 지점까지의 모든 데이터를 0으로 초기화한다. */
while (sector_idx == -1){
grow = true; // 파일 확장이 일어난다는 것을 표시
off_t inode_len = inode_length(inode); // 아이노드에 해당하는 파일의 데이터 영역 길이
// 파일 데이터 영역의 가장 끝 데이터 클러스터의 섹터 번호를 불러온다.
cluster_t endclst = sector_to_cluster(byte_to_sector(inode, inode_len - 1));
// endclst의 뒤에 클러스터 하나를 새로 만든다!
cluster_t newclst = inode_len == 0 ? endclst : fat_create_chain(endclst);
if (newclst == 0){
break;
}
/* EOF부터 OFFSET+SIZE까지의 디스크 공간들을 ZERO PADDING 해준다. */
memset (zero, 0, DISK_SECTOR_SIZE);
// 이전 EOF에서부터 EOF가 있는 클러스터의 끝까지를 디스크에 추가한다.
off_t inode_ofs = inode_len % DISK_SECTOR_SIZE;
if (inode_ofs != 0)
inode->data.length += DISK_SECTOR_SIZE - inode_ofs;
// 우선 write해야하는 디스크 섹터를 0으로 다 만들어준다.
disk_write (filesys_disk, cluster_to_sector(newclst), zero);
if (inode_ofs != 0){
disk_read (filesys_disk, cluster_to_sector(newclst), zero);
memset(zero + inode_ofs + 1, 0, DISK_SECTOR_SIZE - inode_ofs);
// 이전 EOF와 WRITE 시작 위치 사이의 간격은 0으로 채워져야 한다.
disk_write(filesys_disk, cluster_to_sector(endclst), zero);
/*
endclst newclst (extended)
--------------- -----------
| data 0 0 0 0 | - | 0 0 0 0 0 |
--------------- -----------
↑ zero padding here!
*/
}
inode->data.length += DISK_SECTOR_SIZE; // 파일 길이 추가한다.
sector_idx = byte_to_sector(inode, offset + size);
// 다시 한번 WRITE 끝점까지 파일이 확장됐는지 검사한다.
}
/* WRITE를 시작한다. */
sector_idx = byte_to_sector (inode, offset); // OFFSET에 해당되는 SECTOR부터 시작한다.
/* SECTOR SIZE만큼 나누어서 클러스터에 기록한다. */
while (size > 0) {
/* Sector to write, starting byte offset within sector. */
int sector_ofs = offset % DISK_SECTOR_SIZE;
/* Bytes left in inode, bytes left in sector, lesser of the two. */
off_t inode_left = inode_length (inode) - offset;
int sector_left = DISK_SECTOR_SIZE - sector_ofs;
int min_left = inode_left < sector_left ? inode_left : sector_left;
/* Number of bytes to actually write into this sector. */
int chunk_size = size < min_left ? size : min_left;
if (chunk_size <= 0)
break;
if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) {
/* Write full sector directly to disk. */
disk_write (filesys_disk, sector_idx, buffer + bytes_written);
} else {
/* We need a bounce buffer. */
if (bounce == NULL) {
bounce = malloc (DISK_SECTOR_SIZE);
if (bounce == NULL)
break;
}
/* If the sector contains data before or after the chunk
we're writing, then we need to read in the sector
first. Otherwise we start with a sector of all zeros. */
if (sector_ofs > 0 || chunk_size < sector_left)
disk_read (filesys_disk, sector_idx, bounce);
else
memset (bounce, 0, DISK_SECTOR_SIZE);
memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size);
disk_write (filesys_disk, sector_idx, bounce);
}
/* Advance. */
size -= chunk_size;
offset += chunk_size;
bytes_written += chunk_size;
disk_sector_t sector_idx = byte_to_sector (inode, offset);
}
free (bounce);
/* 아이노드 자체의 데이터를 디스크에 저장해준다. */
disk_write(filesys_disk, inode->sector, &inode->data);
return bytes_written;
}