Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Пишем свою операционную систему. Файловая система


Доброго времени суток!

Наш загрузчик готов к тому, чтобы научить его загружать что-нибудь полезное. Осталось определиться с файловой системой.

Можно было бы воспользоваться готовой ФС, однако либо они достаточно сложные (NTFS, FAT при использовании имён длиннее 12 символов), либо обладают целым набором ограничений (FAT). Поскольку немаловажным для нашей ОС критерием является наглядность разработки, мы реализуем загрузку с нашей собственной файловой системы. С одной стороны она имеет очень простую структуру, с другой с ней можно достаточно быстро работать и она не имеет ограничений вроде FAT. Её возможности можно будет легко расширить, ну а уже потом, в рабочую систему, можно добавить драйверы для работы с другими ФС, чтобы был возможен обмен данными с другими операционными системами.

Моя файловая система называется ListFS, потому что построена на концепции двунаправленных связанных списков. Рассмотрим её структуру.

Начинается файловая система с заголовка, который располагается в загрузочном секторе, начиная с 4-ого байта (байты до этого хранят команду перехода к настоящей точке входа в загрузчик).

НазваниеРазмерОписание 
fs_magic4 байтаСигнатура файловой системы. Должна быть равна 0x84837376.
fs_version4 байтаВерсия файловой системы - для возможности будущих изменений. Сейчас, равна единице.
fs_flags4 байта

Атрибуты файловой системы.

fs_base8 байтНомер начального сектора файловой системы. Служит для упрощения работы загрузчика, когда на диске несколько разделов (Сервисы BIOS ничего про разделы не знают). Все остальные адреса рассчитываются относительно этого значения.
fs_size8 байтРазмер файловой системы в секторах
fs_map_base8 байтБазовый адрес битовой карты свободных секторов.
fs_map_size8 байтРазмер битовой карты свободных секторов. Равно fs_size / 8 / fs_block_size (при делении всегда округлять в большую сторону).
fs_first_file8 байтНомер сектора, содержащего заголовок первого файла в корневом каталоге. Если диск пуст -1.
fs_uid8 байтУникальный идентификатор файловой системы
fs_block_size4 байтаРазмер сектора. Для поддержки любых блочных устройств. У нас равно 512.

Таким образом заголовок файловой системы занимает целых 64 байта, зато предусматривает самые разнообразные требования к ФС и оставляет возможность расширения. Битовая карта представляет собой непрерывный массив секторов, доступ осуществляется на уровне отдельных битов (ОС, которая планирует производить операции записи на ListFS, скорее всего просто загрузит всю карту в оперативную память для удобства доступа). Каждый бит соответствует одному сектору ФС. Если бит установлен, значит сектор занят, если сброшен, то свободен. Карта необходима только для изменения данных - для поиска свободного сектора для новых и для освобождения секторов от удалённых файлов.

Все каталоги файлов имеют структуру списков. Каждый заголовок файла имеет указатель на следующий и предыдущие, таким образом зная первый элемент списка можно перебрать все файлы каталога. Каждый заголовок файла занимает ровно один сектор:

НазваниеРазмерОписание 
f_name256 байтИмя файла в кодировке UTF-8. Пока мы не делаем локализованный интерфейс пользователя нас мало волнуют особенности Юникода, но без него будет плохо в будущем.
f_next8 байтНомер сектора со следующим заголовком файла. Если файл последний в каталоге, то -1.
f_prev8 байтНомер сектора с предыдущим заголовком файла. Если файл первый в каталоге, то -2
f_parent8 байтУказатель на родительский каталог. -1, если файл находится в корневом каталоге
f_flags8 байтАтрибуты файла. Пока определён только один - 0-ой бит - признак того, что это не файл, а каталог.
f_data8 байтУказатель на данные файлы. Если это каталог, то это указатель на первый файл каталога или -1 для пустого каталога. В обратном случае это указатель на сектор со списком секторов файла (у пустого файла так же будет -1). Такой сектор хранит sector_size / 8 номеров секторов. Список оканчивается номером -1. Последний элемент списка является указателем на следующий список (если он не равен -1 и до него не встретилось -1). Таким образом можно хранить файлы любой длины.
f_size8 байтРазмер файла в байтах
f_create_time8 байтДата создания
f_modify_time8 байтДата последнего изменения
f_access_time8 байтДата последнего обращения

Остальные байты сектора с заголовком зарезервированы для будущего использования. Например, можно добавить поля для хранения прав доступа POSIX.

Вот и вся структура ListFS. Возможно, она понравится и другим начинающим авторам операционных систем за свою простоту и гибкость. Только давайте договоримся об одном соглашении - любое расширение ListFS должно лишь добавлять новые поля в структуру заголовка ФС или файла, но не модифицировать старые. То есть должна сохраняться обратная совместимость и любая ОС, поддерживающая ListFS, сможет работать с вашей модификацией, просто без отсутствия некоторых плюшек.

Для удобного создания образов я написал кросс-платформенную утилиту на Си. Её исходники будут представлены в конце выпуска. Вы можете собрать её как в Windows (с использованием компилятора MinGW), так и в Linux (с использованием GCC). При запуске без параметров эта утилита выводит полную справку о своих аргументах.

А теперь, научим наш начальный загрузчик загружать файлы с нашей файловой системы.

Для начала введём новые переменные:

org 0x7C00
	jmp boot
; Заголовок ListFS
align 4
fs_magic dd ?
fs_version dd ?
fs_flags dd ?
fs_base dq ?
fs_size dq ?
fs_map_base dq ?
fs_map_size dq ?
fs_first_file dq ?
fs_uid dq ?
fs_block_size dd ?
; Заголовок файла
virtual at 0x800
f_info:
	f_name rb 256
	f_next dq ?
	f_prev dq ?
	f_parent dq ?
	f_flags dq ?
	f_data dq ?
	f_size dq ?
	f_ctime dq ?
	f_mtime dq ?
	f_atime dq ?
end virtual
; Данные начального загрузчика label sector_per_track word at $$ label head_count byte at $$ + 2 label disk_id byte at $$ + 3 boot_msg db "MyOS boot loader. Version 0.04",13,10,0 reboot_msg db "Press any key...",13,10,0 ...

Все данные мы делаем неинициализированными (с помощью вопросительных знаков вместо значений) - Ассемблер оставит в файле пустое место для них, а заполнять уже будет утилита генерации образа ListFS. Специальная директива virtual позволяет разместить данные "как бы по указанному адресу". На выходной файл это никак не влияет, лишь создаёт метки по желаемым адресам. Заголовок файла мы будем загружать по адресу 0x800 и потом уже анализировать. Это будет специальный 512-байтный буфер для чтения служебных данных файловой системы.

Чтение файла мы разделим на несколько этапов - поиск файла, загрузка данных. Поскольку в 512 байт уместить код чтения с обработкой подкаталогов уже не получится, мы уместим туда лишь код поиска и загрузки, которого хватит, чтобы найти в корневом каталоге файл boot.bin и загрузить его, а уже он будет содержать продолжение загрузчика.

Начнём с подпрограммы для поиска файла в каталоге. Она будет принимать два параметра - имя файла в DS:SI и указатель на первый файл каталога в DX:AX. В результате в f_info должен оказаться заголовок файла, либо, если файл не найден, вывестись сообщение "NOT FOUND" и система переходит в ожидание перезагрузки (подпрограмма error), потому что на этом этапе любая ошибка является критической.

; Поиск файла с именем DS:SI в каталоге DX:AX
find_file:
	push cx dx di ; Сохраним регистры
 .find:
	cmp ax, -1 ; Проверим, не достигли ли мы конца списка
	jne @f
	cmp dx, -1
	jne @f
 .not_found: ; Если достигли, то не остаётся ничего как вывести сообщение об ошибке
	call error
	db "NOT FOUND",13,10,0
 @@:
	mov di, f_info
	call load_sector ; Загружаем сектор с информацией о файле
	push di ; Ниже расположен код определения длины имени файла (DI = f_info = f_name)
	mov cx, 0xFFFF
	xor al, al
	repne scasb
	neg cx
	dec cx
	pop di
	push si ; Сравнение имени файлов
	repe cmpsb
	pop si
	je .found ; Если совпало, то выходим
	mov ax, word[f_next] ; Загружаем номер следующего файла
	mov dx, word[f_next + 2]
	jmp .find ; Повторяем цикл
 .found:
	pop di dx cx ; Восстанавливаем регистры
	ret  ; Выходим

 Функция выше позволит нам найти файл в каталоге по имени. В качестве значения DX:AX можно указать либо f_data каталога, либо fs_first_file (для поиска в корневом каталоге). После выполнения этой подпрограммы структура f_info будет содержать всю информацию о файле. После того как файл найден, его можно загрузить в оперативную память. Этим занимается следующая подпрограмма:

; Загрузка текущего файла в память по адресу BX:0. Количество загруженных секторов возвращается в AX load_file_data: push bx cx dx si di ; Сохраняем регистры mov ax, word[f_data] ; Загружаем в DX:AX номер сектора с первым списком mov dx, word[f_data + 2] .load_list: cmp ax, -1 ; Проверяем, не достигли ли мы конца списка jne @f cmp dx, -1 jne @f .file_end: ; Мы полностью загрузили файл pop di si dx cx ; Восстанавливаем все регистры кроме BX mov ax, bx ; Запоминаем BX pop bx ; Восстанавливаем его старое значение sub ax, bx ; Находим разницу - это и будет размер файла в блоках по 16 байт shr ax, 9 - 4 ; Переводим в сектора ret ; Выходим @@: mov di, f_info ; Будем загружать список во временный буфер call load_sector mov si, di ; SI := DI mov cx, 512 / 8 - 1 ; Количество секторов в списке .load_sector: lodsw ; Загружаем очередной номер сектора mov dx, [si] add si, 2 cmp ax, -1 ; Проверяем, что мы не в конце списка jne @f cmp dx, -1 je .file_end ; Если в конце, то выходим @@: push es mov es, bx ; Загружаем очередной сектор куда надо call load_sector add bx, 0x200 / 16 ; Следующий сектор будем загружать на 512 байт дальше pop es loop .load_sector ; Данная команда уменьшает CX на 1 и, если он ещё больше нуля, переходит на метку .load_sector lodsw ; Загружаем в DX:AX номер следующего списка mov dx, [si] jmp .load_list ; Переходим в начало цикла по спискам секторов

 Готово, теперь у нас есть всё, чтобы загрузить продолжение загрузчика любой длины (надо только помнить, что лишь первые 640 КБ ОЗУ можно свободно изменять, дальше идут служебные области и BIOS, которые трогать не стоит). Что мы собственно и сделаем:

        ; Загрузим продолжение начального загрузчика
	mov si, boot_file_name
	mov ax, word[fs_first_file]
	mov dx, word[fs_first_file + 2]
	call find_file
	mov di, 0x7E00 / 16
	call load_file_data
	; Переходим на продолжение
	jmp 0x7E00 

 А в области данных опишем:

boot_file_name db "boot.bin",0  

Ну вот наш загрузочный сектор и завершён :-)

Он настраивает содержимое регистров, как нам нужно, определяет параметры загрузочного устройства, находит и загружает в память файл boot.bin и, наконец, передаёт управление на него. При этом корректно обрабатывает все ошибки, кроме нехватки памяти (если boot.bin окажется больше, чем 500 с чем-то килобайт). От вывода названия загрузчика в этом месте пришлось отказаться, потому что иначе уже не получалось уложиться в один сектор.

Теперь продолжим писать код. В этом выпуске мы реализуем полноценную функцию загрузки файла, которая поддерживает подкаталоги. Располагаться она будет, как и весь остальной код во второй половине загрузчика. Мы не будем особо думать, а просто продолжим писать код после db 0x55,0xAA. При создании образа файл boot.bin следует разделить на две части - первые 512 байт подлежат записи в загрузочный сектор, всё остальное - в файл boot.bin уже на диске. Теперь мы не скованы размерами сектора и можно "разгуляться".

Приведу полнофункциональную подпрограмму загрузки файла:

; Дополнительные данные загрузчика
load_msg_preffix db "Loading '",0
load_msg_suffix db "'...",0
ok_msg db "OK",13,10,0
; Разбиение строки DS:SI по символу слеша
split_file_name:
	push si
 @@:
	lodsb
	cmp al, "/"
	je @f
	jmp @b
 @@:
	mov byte[si - 1], 0
	mov ax, si
	pop si
	ret
; Загрузка файла с именем DS:SI в буфер DI:0. Размер файла в секторах возвращается в AX
load_file:
	push si
	mov si, load_msg_preffix
	call write_str
	pop si
	call write_str
	push si
	mov si, load_msg_suffix
	call write_str
	pop si
	push si bp
	mov dx, word[fs_first_file + 2] ; Начинаем поиск с корневого каталога
	mov ax, word[fs_first_file]
 @@:
	push ax
	call split_file_name
	mov bp, ax
	pop ax
	call find_file
	test byte[f_flags], 1 ; Каталог?
	jz @f
	mov si, bp
	mov dx, word[f_data + 2]
	mov ax, word[f_data]
	jmp @b	
 @@:
	call load_file_data
	mov si, ok_msg
	call write_str
	pop bp si
	ret 

 Ну вот теперь работа с ListFS реализована в нашем загрузчике полностью. Можно читать любые файлы с диска, например, как-нибудь так:

mov si, file_name
mov di, 0x8000 / 16
call load_file
 ...
file_name db "system/kernel.bin",0 

На этом сегодняшний выпуск можно закончить. Мы проделали большую работу, реализовав все необходимые для загрузчика функции и убрав ограничение на размер. В следующем выпуске мы закончим работу над загрузчиком, сделав загрузку файла ядра и перехода в защищённый режим. После этого начнётся более интересная часть разработки, уже на языке высокого уровня - Си.

В заключение приведу полный исходный код загрузчика:

 ; Начальный загрузчик
ядра для архитектуры x86
format Binary as "bin"
org 0x7C00
	jmp boot
; Заголовок ListFS
align 4
fs_magic dd ?
fs_version dd ?
fs_flags dd ?
fs_base dq ?
fs_size dq ?
fs_map_base dq ?
fs_map_size dq ?
fs_first_file dq ?
fs_uid dq ?
fs_block_size dd ?
; Заголовок файла
virtual at 0x800
f_info:
	f_name rb 256
	f_next dq ?
	f_prev dq ?
	f_parent dq ?
	f_flags dq ?
	f_data dq ?
	f_size dq ?
	f_ctime dq ?
	f_mtime dq ?
	f_atime dq ?
end virtual
; Данные начального загрузчика
label sector_per_track word at $$
label head_count byte at $$ + 2
label disk_id byte at $$ + 3
reboot_msg db "Press any key...",13,10,0
boot_file_name db "boot.bin",0
; Вывод строки DS:SI на экран
write_str:
	push si
	mov ah, 0x0E
 @@:
	lodsb
	test al, al
	jz @f
	int 0x10
	jmp @b
 @@:
	pop si
	ret
; Критическая ошибка
error:
	pop si
	call write_str
; Перезагрузка
reboot:
	mov si, reboot_msg
	call write_str
	xor ah, ah
	int 0x16
	jmp 0xFFFF:0
; Загрузка сектора DX:AX в буфер ES:DI
load_sector:
	cmp byte[sector_per_track], 0xFF
	je .use_EDD
	push bx cx dx si
	div [sector_per_track]
	mov cl, dl
	inc cl
	div [head_count]
	mov dh, ah
	mov ch, al
	mov dl, [disk_id]
	mov bx, di
	mov al, 1
	mov si, 3
 @@:
	mov ah, 2
	int 0x13
	jnc @f
	xor ah, ah
	int 0x13
	dec si
	jnz @b
 .error:
	call error
	db "DISK ERROR",13,10,0
 @@:
	pop si dx cx bx
	ret
 .use_EDD:
	push dx si
	mov byte[0x600], 0x10
	mov byte[0x601], 0
	mov word[0x602], 1
	mov [0x604], di
	push es
	pop word[0x606]
	mov [0x608], ax
	mov [0x60A], dx
	mov word[0x60C], 0
	mov word[0x60E], 0
	mov ah, 0x42
	mov dl, [disk_id]
	mov si, 0x600
	int 0x13
	jc .error
	pop si dx
	ret
; Поиск файла с именем DS:SI в каталоге DX:AX
find_file:
	push cx dx di
 .find:
	cmp ax, -1
	jne @f
	cmp dx, -1
	jne @f
 .not_found:
	call error
	db "NOT FOUND",13,10,0
 @@:
	mov di, f_info
	call load_sector
	push di
	mov cx, 0xFFFF
	xor al, al
	repne scasb
	neg cx
	dec cx
	pop di
	push si
	repe cmpsb
	pop si
	je .found
	mov ax, word[f_next]
	mov dx, word[f_next + 2]
	jmp .find
 .found:
	pop di dx cx
	ret
; Загрузка текущего файла в память по адресу BX:0. Количество загруженных секторов возвращается в AX
load_file_data:
	push bx cx dx si di
	mov ax, word[f_data]
	mov dx, word[f_data + 2]
 .load_list:
	cmp ax, -1
	jne @f
	cmp dx, -1
	jne @f
 .file_end:
	pop di si dx cx
	mov ax, bx
	pop bx
	sub ax, bx
	shr ax, 9 - 4
	ret
 @@:
	mov di, 0x8000 / 16
	call load_sector
	mov si, di
	mov cx, 512 / 8 - 1
 .load_sector:
	lodsw
	mov dx, [si]
	add si, 6
	cmp ax, -1
	jne @f
	cmp dx, -1
	je .file_end	
 @@:
	push es
	mov es, bx
	xor di, di
	call load_sector
	add bx, 0x200 / 16
	pop es
	loop .load_sector
	lodsw
	mov dx, [si]
	jmp .load_list
; Точка входа в начальный загрузчик
boot:
	; Настроим сегментные регистры
	jmp 0:@f
 @@:
	mov ax, cs
	mov ds, ax
	mov es, ax
	; Настроим стек
	mov ss, ax
	mov sp, $$
	; Разрешим прерывания
	sti
	; Запомним номер загрузочного диска
	mov [disk_id], dl
	; Определим параметры загрузочного диска
	mov ah, 0x41
	mov bx, 0x55AA
	int 0x13
	jc @f
	mov byte[sector_per_track], 0xFF
	jmp .disk_detected
 @@:
	mov ah, 0x08
	xor di, di
	push es
	int 0x13
	pop es
	jc load_sector.error
	inc dh
	mov [head_count], dh
	and cx, 111111b
	mov [sector_per_track], cx
 .disk_detected:
	; Загрузим продолжение начального загрузчика
	mov si, boot_file_name
	mov ax, word[fs_first_file]
	mov dx, word[fs_first_file + 2]
	call find_file
	mov bx, 0x7E00 / 16
	call load_file_data
	; Переходим на продолжение
	jmp boot2
; Пустое пространство и сигнатура
rb 510 - ($ - $$)
db 0x55,0xAA
; Дополнительные данные загрузчика
load_msg_preffix db "Loading '",0
load_msg_suffix db "'...",0
ok_msg db "OK",13,10,0
; Разбиение строки DS:SI по символу слеша
split_file_name:
	push si
 @@:
	lodsb
	cmp al, "/"
	je @f
	jmp @b
 @@:
	mov byte[si - 1], 0
	mov ax, si
	pop si
	ret
; Загрузка файла с именем DS:SI в буфер BX:0. Размер файла в секторах возвращается в AX
load_file:
	push si
	mov si, load_msg_preffix
	call write_str
	pop si
	call write_str
	push si
	mov si, load_msg_suffix
	call write_str
	pop si
	push si bp
	mov dx, word[fs_first_file + 2]
	mov ax, word[fs_first_file]
 @@:
	push ax
	call split_file_name
	mov bp, ax
	pop ax
	call find_file
	test byte[f_flags], 1
	jz @f
	mov si, bp
	mov dx, word[f_data + 2]
	mov ax, word[f_data]
	jmp @b	
 @@:
	call load_file_data
	mov si, ok_msg
	call write_str
	pop bp si
	ret
; Продолжение начального загрузчика
boot2:
	; Завершение
	jmp reboot

 Утилита создания образов дисков make_listfs.c:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>

#define LISTFS_MAGIC 0x84837376

#define LISTFS_VERSION 0x0001

typedef unsigned char uint8;
typedef signed char int8;
typedef unsigned short uint16;
typedef signed short int16;
typedef unsigned int uint32;
typedef signed int int32;
typedef unsigned long long uint64;
typedef signed long long int64;

typedef struct {
	char jump[4];
	uint32 magic;
	uint32 version;
	uint32 flags;
	uint64 base;
	uint64 size;
	uint64 map_base;
	uint64 map_size;
	uint64 first_file;
	uint64 uid;
	uint32 block_size;
} __attribute((packed)) listfs_header;

typedef struct {
	char name[256];
	uint64 next;
	uint64 prev;
	uint64 parent;
	uint64 flags;
	uint64 data;
	uint64 size;
	uint64 create_time;
	uint64 modify_time;
	uint64 access_time;
} __attribute((packed)) listfs_file_header;

char *output_file_name = NULL;
char *boot_loader_file_name = NULL;
char *source_dir_name = NULL;
long block_size = 512;
long block_count = 0;
FILE *output_file;
listfs_header *fs_header;
char *disk_map;
long boot_loader_extra_blocks = 0;

bool get_commandline_options(int argc, char **argv) {
	int i;
	for (i = 1; i < argc; i++) {
		if (!strncmp(argv[i], "of=", 3)) {
			output_file_name = argv[i] + 3;
		} else if (!strncmp(argv[i], "bs=", 3)) {
			block_size = atoi(argv[i] + 3);
		} else if (!strncmp(argv[i], "size=", 5)) {
			block_count = atoi(argv[i] + 5);
		} else if (!strncmp(argv[i], "boot=", 5)) {
			boot_loader_file_name = argv[i] + 5;
		} else if (!strncmp(argv[i], "src=", 4)) {
			source_dir_name = argv[i] + 4;
		}
	}
	return (output_file_name && block_count);
}

void usage() {
	printf("Syntax:\n");
	printf("  make_listfs [options]\n");
	printf("Options:\n");
	printf("  of=XXX - output file name (required)\n");
	printf("  bs=XXX - size of disk image block in bytes (optional, default - 512)\n");
	printf("  size=XXX - size of disk image in blocks (required)\n");
	printf("  boot=XXX - name of boot loader image file (optional, need only for bootable image)\n");
	printf("  src=XXX - Dir, that contents will be copied on created disk image (optional)\n");
	printf("Author:\n");
	printf("  kiv (kiv.apple@gmail.com)\n");
	printf("\n");
}

bool check_options() {
	if ((block_size < 512) && !(block_size & 7)) {
		fprintf(stderr, "Error: Block size must be larger than 512 and be multiple of 8!\n");
		return false;
	}
	if (block_count < 2) {
		fprintf(stderr, "Error: Block count must be larger than 1!\n");
		return false;
	}
	return true;
}

bool open_output_file() {
	char *buf;
	output_file = fopen(output_file_name, "wb+");
	if (!output_file) {
		fprintf(stderr, "Error: Could not create output file: %s\n", strerror(errno));
		return false;
	}
	fseek(output_file, block_size * (block_count - 1), SEEK_SET);
	buf = calloc(1, block_size);
	if (fwrite(buf, block_size, 1, output_file) == 0) {
		fprintf(stderr, "Error: Could not create output file: %s\n", strerror(errno));
		fclose(output_file);
		remove(output_file_name);
		free(buf);
		return false;
	}
	free(buf);
	return true;
}

void close_output_file() {
	fclose(output_file);
}

void write_block(uint64 index, void *data, unsigned int count) {
	fseek(output_file, block_size * index, SEEK_SET);
	fwrite(data, block_size, count, output_file);
}

void init_listfs_header() {
	fs_header = calloc(block_size, 1);
	if (boot_loader_file_name) {
		FILE *f;
		f = fopen(boot_loader_file_name, "rb");
		if (f) {
			fread(fs_header, block_size, 1, f);
			if (!feof(f)){
				char *buffer = calloc(block_size, 1);
				int i = 1;
				while (!feof(f)) {
					fread(buffer, block_size, 1, f);
					write_block(i, buffer, 1);
					i++;
					boot_loader_extra_blocks++;
				}
			}
			fclose(f);
		} else
			fprintf(stderr, "Warning: Could not open boot loader file for reading. No boot loader will be installed.\n");
	}
	fs_header->magic = LISTFS_MAGIC;
	fs_header->version = LISTFS_VERSION;
	fs_header->base = 0;
	fs_header->size = block_count;
	fs_header->first_file = -1;
	fs_header->flags = 0;
	fs_header->block_size = block_size;
	fs_header->uid = (time(NULL) << 16) | (rand() & 0xFFFF);
}

void store_listfs_header() {
	write_block(0, fs_header, 1);
	free(fs_header);
}

uint64 alloc_disk_block() {
	uint64 index = 0;
	while (disk_map[index >> 3] & (1 << (index & 7))) {
		index++;
		if (index >= fs_header->size) {
			fprintf(stderr, "Error: Disk image is full. Could not write more.\n");
			exit(-3);
		}
	}
	disk_map[index >> 3] |= 1 << (index & 7);
	return index;
}

void get_disk_block(uint64 index) {
	disk_map[index >> 3] |= 1 << (index & 7);
}

void init_disk_map() {
	int i;
	fs_header->map_base = /* 1 + */ boot_loader_extra_blocks;
	fs_header->map_size = block_count / 8;
	if (fs_header->map_size % block_size) fs_header->map_size += block_size;
	fs_header->map_size /= block_size;
	disk_map = calloc(block_size, fs_header->map_size);
	for (i = 0; i < fs_header->map_base + fs_header->map_size; i++) {
		get_disk_block(i);
	}
}

void store_disk_map() {
	write_block(fs_header->map_base, disk_map, fs_header->map_size);
	free(disk_map);
}

uint64 store_file_data(FILE *file) {
	if (feof(file)) return -1;
	uint64 *block_list = malloc(block_size);
	uint64 block_list_index = alloc_disk_block();
	char *block_data = malloc(block_size);
	int i;
	for (i = 0; i < (block_size / 8 - 1); i++) {
		if (!feof(file)) {
			memset(block_data, 0, block_size);
			if (fread(block_data, 1, block_size, file) == 0) {
                		block_list[i] = -1;
                		continue;
			}
			uint64 block_index = alloc_disk_block();
			block_list[i] = block_index;
			write_block(block_index, block_data, 1);
		} else {
			block_list[i] = -1;
		}
	}
	block_list[block_size / 8 - 1] = store_file_data(file);
	write_block(block_list_index, block_list, 1);
	free(block_list);
	free(block_data);
	return block_list_index;
}

uint64 process_dir(uint64 parent, char *dir_name) {
	DIR *dir = opendir(dir_name);
	struct dirent *entry;
	uint64 prev_file = -1;
	uint64 cur_file;
	uint64 first_file = -1;
	char file_name[260];
	struct stat file_stat;
	listfs_file_header *file_header;
	if (!dir) {
		fprintf(stderr, "Warning: Could not read contents of directory '%s': %s\n", dir_name, strerror(errno));
		return -1;
	}
	file_header = malloc(block_size);
	while (entry = readdir(dir)) {
		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
		cur_file = alloc_disk_block();
		if (prev_file != -1) {
			file_header->next = cur_file;
			write_block(prev_file, file_header, 1);
		} else
			first_file = cur_file;
		memset(file_header, 0, block_size);
		file_header->prev = prev_file;
		file_header->next = -1;
		strncpy(file_header->name, entry->d_name, sizeof(file_header->name) - 1);
		file_header->name[sizeof(file_header->name) - 1] = 0;
		file_header->size = 0;
		sprintf((char*)&file_name, "%s/%s", dir_name, entry->d_name);
		stat(file_name, &file_stat);
		file_header->parent = parent;
		file_header->flags = 0;
		file_header->create_time = file_stat.st_ctime;
		file_header->modify_time = file_stat.st_mtime;
		file_header->access_time = file_stat.st_atime;
		if (S_ISDIR(file_stat.st_mode)) {
			file_header->flags = 1;
			file_header->data = process_dir(cur_file, file_name);
		} else if (S_ISREG(file_stat.st_mode)) {
			FILE *f = fopen(file_name, "rb");
			if (f) {
				file_header->size = file_stat.st_size;
				file_header->data = store_file_data(f);
				fclose(f);
			} else {
				fprintf(stderr, "Warning: Could not open file '%s' for reading!\n", file_name);
				file_header->data = -1;
			}
		} else {
			fprintf(stderr, "Warning: File '%s' is not directory or regular file.\n", file_name);
		}
		prev_file = cur_file;
	}
	write_block(prev_file, file_header, 1);
	free(file_header);
	closedir(dir);
	return first_file;
}

int main(int argc, char *argv[]) {
	if (!get_commandline_options(argc, argv)) {
		usage();
		return 0;
	}
	if (!check_options()) return -1;
	if (!open_output_file()) return -2;
	init_listfs_header();
	init_disk_map();
	if (source_dir_name) fs_header->first_file = process_dir(-1, source_dir_name);
	store_disk_map();
	store_listfs_header();
	close_output_file();
	return 0;
} 

 Пример скрипта создания образа:

dd if=bin/boot.bios.bin of=bin/boot_sector.bin bs=512 count=1
dd if=bin/boot.bios.bin of=disk/boot.bin bs=1 skip=512
bin/make_listfs of=disk.img bs=512 size=2880 boot=bin/boot_sector.bin src=./disk

В избранное