<aside> 💡 확장 가능한 파일을 구현한다.

</aside>

지금까지의 파일 시스템에서 파일 크기는 파일을 만들 때 지정된다. 그러나 대부분의 현대 파일 시스템에서 파일은 처음에 크기가 0인 상태로 생성되었다가 파일 끝에 쓰기를 할 때마다 확장된다.

이 때 파일이 파일 시스템 크기(메타데이터 제외)를 초과할 수 없다는 점을 제외하고는 파일 크기에 미리 결정된 제한이 없어야 한다. 루트 디렉터리 파일에도 똑같이 적용되는데, 디렉터리 파일에 들어갈 수 있는 파일의 개수가 기존에는 최대 16개 파일이었다면, 이제는 그 이상으로 확장될 수 있다.

사용자 프로그램은 EOF(End-of-File)를 넘어 탐색할 수 있다. 이 때 SEEK(검색) 자체는 파일을 확장하지 않는다. 하지만 EOF를 지난 위치에서 WRITE하면 파일이 기록 중인 위치로 파일의 크기가 확장되어야 한다. 이 때 이전 EOF와 WRITE 시작 위치 사이의 간격은 0으로 채워져야 한다. EOF 이후의 위치에서 READ하면 바이트를 반환하지 않는다.

EOF를 훨씬 넘어서 쓰면 많은 블록이 완전히 0이 될 수 있다. 일부 파일 시스템은 실제로 데이터 블록을 할당해 0으로 해당 데이터 블록을 채운다. 하지만 굳이 실제로 데이터 블록을 할당하지 않고, 실제로 해당 블록이 명시적으로 작성될 때까지 sparse 파일을 이용해 간접적으로 0 블록들을 표현한다. 파일 시스템에서 할당 전략으로 이 둘 중 하나를 채택할 수 있다.

inode_write_at() 수정 → file growth

<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;
}