Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5a26d66b9 | |||
| 9a16b6d369 |
+225
@@ -25,6 +25,18 @@
|
|||||||
|
|
||||||
В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие.
|
В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие.
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Файловая система — это способ организации долговременного хранения данных, при котором содержимое носителя представляется не как сплошной массив байтов, а как совокупность именованных объектов, метаданных и правил размещения. Даже в упрощенной реализации должны присутствовать три ключевых слоя: физическое хранение, служебные структуры и пользовательский интерфейс доступа к файлам.
|
||||||
|
|
||||||
|
Уже в начале программы задаются основные параметры будущей файловой системы: сигнатура формата, размер кластера, число дескрипторов и ограничения на открытый файл.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define FS_MAGIC 0x53465331u
|
||||||
|
#define CLUSTER_SIZE 1024u
|
||||||
|
#define MAX_ENTRIES 128
|
||||||
|
#define MAX_FILE_CLUSTERS 128
|
||||||
|
#define MAX_OPEN_DATA (MAX_FILE_CLUSTERS * CLUSTER_SIZE)
|
||||||
|
```
|
||||||
|
|
||||||
## Постановка задачи
|
## Постановка задачи
|
||||||
|
|
||||||
Согласно заданию необходимо разработать систему управления файлами, где разделом является бинарный файл произвольного доступа. Реализация должна включать:
|
Согласно заданию необходимо разработать систему управления файлами, где разделом является бинарный файл произвольного доступа. Реализация должна включать:
|
||||||
@@ -37,10 +49,28 @@
|
|||||||
- символьные операции над файлами и каталогами;
|
- символьные операции над файлами и каталогами;
|
||||||
- прикладную утилиту для форматирования, просмотра, импорта, экспорта, дефрагментации и сжатия.
|
- прикладную утилиту для форматирования, просмотра, импорта, экспорта, дефрагментации и сжатия.
|
||||||
|
|
||||||
|
> **Теоретическая основа.** При проектировании файловой системы важно разделять данные пользователя и служебные данные самой системы. Пользователь работает с именами файлов и каталогов, однако внутри реализации требуется хранить состояние носителя, сведения о свободном пространстве, метаданные объектов и текущий контекст работы.
|
||||||
|
|
||||||
|
В программе эта постановка отражается в центральной структуре состояния файловой системы. Она объединяет физический носитель, карту свободных блоков, таблицу дескрипторов и текущий каталог пользователя.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
FILE *disk;
|
||||||
|
char image_path[MAX_PATH];
|
||||||
|
SuperBlock sb;
|
||||||
|
unsigned char *bitmap;
|
||||||
|
Entry entries[MAX_ENTRIES];
|
||||||
|
uint32_t current_dir;
|
||||||
|
bool mounted;
|
||||||
|
} FileSystem;
|
||||||
|
```
|
||||||
|
|
||||||
## Выбранная архитектура файловой системы
|
## Выбранная архитектура файловой системы
|
||||||
|
|
||||||
Файловая система разделена на уровни. Такое деление полезно не только для описания в отчете: каждый уровень решает свою задачу и скрывает детали от следующего. Пользователь работает с командами и путями, а не с номерами кластеров; функции чтения работают с файлами, а не с отдельными битами карты свободного пространства.
|
Файловая система разделена на уровни. Такое деление полезно не только для описания в отчете: каждый уровень решает свою задачу и скрывает детали от следующего. Пользователь работает с командами и путями, а не с номерами кластеров; функции чтения работают с файлами, а не с отдельными битами карты свободного пространства.
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Многоуровневая архитектура уменьшает связность системы. Физический уровень отвечает за доступ к байтам носителя, базовый — за метаданные, логический — за каталоги и пути, а прикладной — за команды пользователя. Такое разделение облегчает развитие системы: например, способ учета свободных блоков можно изменить, почти не затрагивая синтаксис команд.
|
||||||
|
|
||||||
| Уровень | Реализация | Назначение |
|
| Уровень | Реализация | Назначение |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Физический уровень | Кластеры фиксированного размера по 1024 байта | Делит носитель на одинаковые адресуемые блоки; смещение кластера вычисляется как `номер * 1024` |
|
| Физический уровень | Кластеры фиксированного размера по 1024 байта | Делит носитель на одинаковые адресуемые блоки; смещение кластера вычисляется как `номер * 1024` |
|
||||||
@@ -55,22 +85,90 @@
|
|||||||
|
|
||||||
Носителем является обычный бинарный файл. Он рассматривается как последовательность кластеров одинакового размера. Фиксированный размер выбран ради простоты: адрес любого кластера находится прямым умножением, а алгоритм выделения не обязан искать переменные по длине участки памяти. Недостатком является внутреннее фрагментирование: даже файл в несколько байт занимает целый кластер.
|
Носителем является обычный бинарный файл. Он рассматривается как последовательность кластеров одинакового размера. Фиксированный размер выбран ради простоты: адрес любого кластера находится прямым умножением, а алгоритм выделения не обязан искать переменные по длине участки памяти. Недостатком является внутреннее фрагментирование: даже файл в несколько байт занимает целый кластер.
|
||||||
|
|
||||||
|
> **Теория.** Кластер — минимальная единица выделения дискового пространства. Чем больше размер кластера, тем меньше служебных записей требуется для описания больших файлов, но тем выше потери на внутреннее фрагментирование при хранении маленьких файлов.
|
||||||
|
|
||||||
|
Адрес кластера вычисляется напрямую по его номеру. Это видно при записи данных:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
||||||
|
fwrite(data + written, 1, chunk, fs->disk);
|
||||||
|
```
|
||||||
|
|
||||||
### Уровень свободного пространства
|
### Уровень свободного пространства
|
||||||
|
|
||||||
Для каждого кластера хранится один бит. При форматировании служебные кластеры суперблока, битовой карты и индексного файла сразу помечаются занятыми. При записи файла функция выделения просматривает карту от начала области данных и выбирает первые свободные кластеры; при удалении файла соответствующие биты сбрасываются обратно в ноль.
|
Для каждого кластера хранится один бит. При форматировании служебные кластеры суперблока, битовой карты и индексного файла сразу помечаются занятыми. При записи файла функция выделения просматривает карту от начала области данных и выбирает первые свободные кластеры; при удалении файла соответствующие биты сбрасываются обратно в ноль.
|
||||||
|
|
||||||
|
> **Теория.** Битовая карта — одна из классических структур учета свободного пространства. Она компактна и проста в реализации: состояние каждого блока кодируется одним битом. Недостатком является необходимость линейного просмотра при поиске свободных блоков, если не используются дополнительные индексы или кеши.
|
||||||
|
|
||||||
|
```c
|
||||||
|
static bool bit_is_set(FileSystem *fs, uint32_t cluster) {
|
||||||
|
return (fs->bitmap[cluster / 8] & (1u << (cluster % 8))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int alloc_cluster(FileSystem *fs) {
|
||||||
|
for (uint32_t i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) {
|
||||||
|
if (!bit_is_set(fs, i)) {
|
||||||
|
bit_set(fs, i);
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Базовый уровень
|
### Базовый уровень
|
||||||
|
|
||||||
Индексный файл — это таблица дескрипторов `Entry`. Один дескриптор описывает один файл или каталог: имя, тип, родителя, размеры и список кластеров. Корневой каталог хранится в нулевой записи. Благодаря индексному файлу метаданные можно прочитать сразу после монтирования носителя, не просматривая всю область данных.
|
Индексный файл — это таблица дескрипторов `Entry`. Один дескриптор описывает один файл или каталог: имя, тип, родителя, размеры и список кластеров. Корневой каталог хранится в нулевой записи. Благодаря индексному файлу метаданные можно прочитать сразу после монтирования носителя, не просматривая всю область данных.
|
||||||
|
|
||||||
|
> **Теория.** Метаданные отделяются от содержимого файлов, чтобы система могла быстро находить объект, определять его тип, размер и расположение без чтения пользовательских данных. В реальных файловых системах аналогичную роль играют, например, inode-структуры.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
char name[MAX_NAME];
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t compressed;
|
||||||
|
uint16_t reserved;
|
||||||
|
uint32_t parent;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t stored_size;
|
||||||
|
uint32_t cluster_count;
|
||||||
|
uint32_t clusters[MAX_FILE_CLUSTERS];
|
||||||
|
} Entry;
|
||||||
|
```
|
||||||
|
|
||||||
### Уровень размещения файлов
|
### Уровень размещения файлов
|
||||||
|
|
||||||
Каждый файл хранит массив номеров своих кластеров. Поэтому файл может лежать не непрерывно, а частями в разных местах виртуального диска. При чтении система идет по этому массиву и собирает содержимое в правильной последовательности. Дефрагментация перестраивает эти списки так, чтобы занятые кластеры снова располагались компактно.
|
Каждый файл хранит массив номеров своих кластеров. Поэтому файл может лежать не непрерывно, а частями в разных местах виртуального диска. При чтении система идет по этому массиву и собирает содержимое в правильной последовательности. Дефрагментация перестраивает эти списки так, чтобы занятые кластеры снова располагались компактно.
|
||||||
|
|
||||||
|
> **Теория.** Несмежное размещение повышает гибкость выделения места: файл можно расширять даже при отсутствии одного большого непрерывного участка. Однако чтение таких файлов требует хранения списка блоков и может приводить к фрагментации.
|
||||||
|
|
||||||
|
```c
|
||||||
|
for (i = 0; i < entry->cluster_count; i++) {
|
||||||
|
uint32_t chunk = entry->stored_size - read_count;
|
||||||
|
if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE;
|
||||||
|
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
||||||
|
fread(data + read_count, 1, chunk, fs->disk);
|
||||||
|
read_count += chunk;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Логический и символьный уровни
|
### Логический и символьный уровни
|
||||||
|
|
||||||
Каталог реализован без отдельного файла содержимого: у каждого объекта есть поле `parent`, содержащее индекс родительского каталога. Путь разбивается по символу `/`, после чего система шаг за шагом ищет дочерний объект в текущем каталоге. Символьный уровень дает привычные операции над открытым файлом: `open`, `close`, `seek`, `read`, `write`; внутри программы они работают через буфер и скрывают детали физического размещения.
|
Каталог реализован без отдельного файла содержимого: у каждого объекта есть поле `parent`, содержащее индекс родительского каталога. Путь разбивается по символу `/`, после чего система шаг за шагом ищет дочерний объект в текущем каталоге. Символьный уровень дает привычные операции над открытым файлом: `open`, `close`, `seek`, `read`, `write`; внутри программы они работают через буфер и скрывают детали физического размещения.
|
||||||
|
|
||||||
|
> **Теория.** Логический уровень преобразует человекоориентированные имена в внутренние идентификаторы объектов. Символьный уровень дополняет это абстракцией «открытого файла», где пользователь работает с текущей позицией чтения и записи, а не с физическими блоками.
|
||||||
|
|
||||||
|
```c
|
||||||
|
static int find_child(FileSystem *fs, uint32_t parent, const char *name) {
|
||||||
|
for (int i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
if (fs->entries[i].type != TYPE_FREE &&
|
||||||
|
fs->entries[i].parent == parent &&
|
||||||
|
strcmp(fs->entries[i].name, name) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
A[Команда пользователя] --> B[Путь /docs/a.txt]
|
A[Команда пользователя] --> B[Путь /docs/a.txt]
|
||||||
@@ -84,6 +182,8 @@ flowchart LR
|
|||||||
|
|
||||||
В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога.
|
В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога.
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Суперблок — это точка входа в структуру файловой системы. После подключения носителя именно по нему программа понимает, корректный ли перед ней формат и где искать остальные области. Потеря суперблока делает интерпретацию содержимого носителя затруднительной даже при сохранности пользовательских данных.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t magic;
|
uint32_t magic;
|
||||||
@@ -116,6 +216,17 @@ typedef struct {
|
|||||||
|
|
||||||
Поле `size` хранит логический размер файла, видимый пользователю, а `stored_size` — фактическое количество байт на носителе. Это различие необходимо для поддержки сжатых файлов.
|
Поле `size` хранит логический размер файла, видимый пользователю, а `stored_size` — фактическое количество байт на носителе. Это различие необходимо для поддержки сжатых файлов.
|
||||||
|
|
||||||
|
При форматировании эти области рассчитываются последовательно: после суперблока располагается битовая карта, затем индексный файл, после чего начинается пользовательская область данных.
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs->sb.bitmap_offset = CLUSTER_SIZE;
|
||||||
|
fs->sb.bitmap_size = (fs->sb.total_clusters + 7) / 8;
|
||||||
|
bitmap_clusters = bytes_to_clusters(fs->sb.bitmap_size);
|
||||||
|
fs->sb.index_offset = (1 + bitmap_clusters) * CLUSTER_SIZE;
|
||||||
|
fs->sb.index_clusters = bytes_to_clusters((uint32_t)(sizeof(Entry) * MAX_ENTRIES));
|
||||||
|
fs->sb.data_start_cluster = 1 + bitmap_clusters + fs->sb.index_clusters;
|
||||||
|
```
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
S[Суперблок] --> B[Битовая карта]
|
S[Суперблок] --> B[Битовая карта]
|
||||||
@@ -137,6 +248,8 @@ gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs
|
|||||||
|
|
||||||
Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **Up** и **Down** перемещаются по ранее введенным строкам. Основные команды названы в стиле Unix, чтобы работа с учебной файловой системой напоминала привычную командную строку.
|
Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **Up** и **Down** перемещаются по ранее введенным строкам. Основные команды названы в стиле Unix, чтобы работа с учебной файловой системой напоминала привычную командную строку.
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Набор операций файловой системы обычно разделяется на операции над объектами (`create`, `remove`, `rename`), операции навигации (`cd`, `ls`, `pwd`), операции передачи данных (`read`, `write`, `import`, `export`) и сервисные операции обслуживания носителя (`format`, `defrag`, `compress`).
|
||||||
|
|
||||||
### Справочник команд
|
### Справочник команд
|
||||||
|
|
||||||
| Команда | Синтаксис | Назначение |
|
| Команда | Синтаксис | Назначение |
|
||||||
@@ -168,32 +281,107 @@ gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs
|
|||||||
|
|
||||||
На уровне программного интерфейса реализованы функции `fs_open`, `fs_close`, `fs_seek`, `fs_read` и `fs_write`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами.
|
На уровне программного интерфейса реализованы функции `fs_open`, `fs_close`, `fs_seek`, `fs_read` и `fs_write`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами.
|
||||||
|
|
||||||
|
```c
|
||||||
|
uint32_t fs_write(OpenFile *f, const unsigned char *buffer, uint32_t count) {
|
||||||
|
if (!f || !buffer || f->mode != MODE_WRITE ||
|
||||||
|
f->position + count > f->capacity) return 0;
|
||||||
|
memcpy(f->data + f->position, buffer, count);
|
||||||
|
f->position += count;
|
||||||
|
if (f->position > f->size) f->size = f->position;
|
||||||
|
f->dirty = true;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Алгоритмы работы
|
## Алгоритмы работы
|
||||||
|
|
||||||
|
В этом разделе приведены основные алгоритмы, которые обеспечивают корректное изменение состояния носителя. Они важны не только как детали реализации, но и как отражение базовых принципов файловых систем: инициализация структуры, выделение ресурсов, разрешение имен, преобразование данных и восстановление компактного размещения.
|
||||||
|
|
||||||
### Форматирование
|
### Форматирование
|
||||||
|
|
||||||
При выполнении `format` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`.
|
При выполнении `format` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
for (i = 0; i < fs->sb.data_start_cluster; i++) bit_set(fs, i);
|
||||||
|
memset(fs->entries, 0, sizeof(fs->entries));
|
||||||
|
safe_copy(fs->entries[0].name, "/", MAX_NAME);
|
||||||
|
fs->entries[0].type = TYPE_DIR;
|
||||||
|
fs->entries[0].parent = 0;
|
||||||
|
```
|
||||||
|
|
||||||
### Выделение свободного кластера
|
### Выделение свободного кластера
|
||||||
|
|
||||||
Алгоритм линейно просматривает битовую карту, начиная с области данных. Первый свободный бит помечается как занятый, а номер соответствующего кластера возвращается вызывающей функции.
|
Алгоритм линейно просматривает битовую карту, начиная с области данных. Первый свободный бит помечается как занятый, а номер соответствующего кластера возвращается вызывающей функции.
|
||||||
|
|
||||||
|
```c
|
||||||
|
static int alloc_cluster(FileSystem *fs) {
|
||||||
|
for (uint32_t i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) {
|
||||||
|
if (!bit_is_set(fs, i)) {
|
||||||
|
bit_set(fs, i);
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Работа с каталогами
|
### Работа с каталогами
|
||||||
|
|
||||||
Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `parent`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя.
|
Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `parent`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя.
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool fs_create_dir(FileSystem *fs, const char *path) {
|
||||||
|
int parent, index;
|
||||||
|
char name[MAX_NAME];
|
||||||
|
if (!split_parent_name(fs, path, &parent, name) ||
|
||||||
|
find_child(fs, parent, name) >= 0) return false;
|
||||||
|
index = free_entry_index(fs);
|
||||||
|
if (index < 0) return false;
|
||||||
|
memset(&fs->entries[index], 0, sizeof(Entry));
|
||||||
|
safe_copy(fs->entries[index].name, name, MAX_NAME);
|
||||||
|
fs->entries[index].type = TYPE_DIR;
|
||||||
|
fs->entries[index].parent = (uint32_t)parent;
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Сжатие
|
### Сжатие
|
||||||
|
|
||||||
Команда `compress` использует простой алгоритм RLE: одинаковые подряд идущие байты заменяются парой «количество + значение». Если сжатый вариант не меньше исходного, файл не изменяется. При чтении сжатый файл автоматически восстанавливается в обычный вид. Для наблюдения за внутренним представлением предусмотрена команда `rawcat`: она показывает сохраненные байты в шестнадцатеричном виде и расшифровывает каждую RLE-пару. Команда `decomp` выполняет обратное преобразование и снова сохраняет файл без сжатия.
|
Команда `compress` использует простой алгоритм RLE: одинаковые подряд идущие байты заменяются парой «количество + значение». Если сжатый вариант не меньше исходного, файл не изменяется. При чтении сжатый файл автоматически восстанавливается в обычный вид. Для наблюдения за внутренним представлением предусмотрена команда `rawcat`: она показывает сохраненные байты в шестнадцатеричном виде и расшифровывает каждую RLE-пару. Команда `decomp` выполняет обратное преобразование и снова сохраняет файл без сжатия.
|
||||||
|
|
||||||
|
```c
|
||||||
|
while (i < size) {
|
||||||
|
unsigned char value = src[i];
|
||||||
|
uint32_t count = 1;
|
||||||
|
while (i + count < size && src[i + count] == value && count < 255) count++;
|
||||||
|
out[j++] = (unsigned char)count;
|
||||||
|
out[j++] = value;
|
||||||
|
i += count;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Дефрагментация
|
### Дефрагментация
|
||||||
|
|
||||||
Команда `defrag` временно считывает содержимое файлов, очищает карту размещения области данных и записывает файлы заново подряд. После операции используемые кластеры становятся компактно расположенными.
|
Команда `defrag` временно считывает содержимое файлов, очищает карту размещения области данных и записывает файлы заново подряд. После операции используемые кластеры становятся компактно расположенными.
|
||||||
|
|
||||||
|
```c
|
||||||
|
memset(fs->bitmap, 0, fs->sb.bitmap_size);
|
||||||
|
for (c = 0; c < fs->sb.data_start_cluster; c++) bit_set(fs, c);
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
if (fs->entries[i].type == TYPE_FILE) {
|
||||||
|
fs->entries[i].cluster_count = 0;
|
||||||
|
write_stored_bytes(fs, &fs->entries[i], copies[i], stored_sizes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Тестирование
|
## Тестирование
|
||||||
|
|
||||||
Пример базового сценария:
|
Пример базового сценария:
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Тестирование файловой системы должно проверять не только отдельные команды, но и сохранение инвариантов: после записи файл должен читаться тем же содержимым, после удаления пространство должно освобождаться, после сжатия логические данные не должны меняться, а после дефрагментации содержимое должно сохраняться при изменении физического размещения.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
format 512
|
format 512
|
||||||
mkdir /docs
|
mkdir /docs
|
||||||
@@ -220,6 +408,15 @@ info
|
|||||||
| Экспорт | создается внешний файл с тем же содержимым |
|
| Экспорт | создается внешний файл с тем же содержимым |
|
||||||
| Дефрагментация | содержимое файлов сохраняется, кластеры уплотняются |
|
| Дефрагментация | содержимое файлов сохраняется, кластеры уплотняются |
|
||||||
|
|
||||||
|
Для проверки сценария полезно опираться на сами команды интерактивной оболочки. Разбор пользовательского ввода связывает каждую строку с соответствующей функцией файловой системы:
|
||||||
|
|
||||||
|
```c
|
||||||
|
else if (strcmp(cmd, "mkdir") == 0 && a) printf(fs_create_dir(&fs, a) ? "ok\n" : "mkdir failed\n");
|
||||||
|
else if (strcmp(cmd, "write") == 0 && a && b) printf(fs_write_text(&fs, a, b) ? "ok\n" : "write failed\n");
|
||||||
|
else if (strcmp(cmd, "compress") == 0 && a) printf(fs_compress_file(&fs, a) ? "ok\n" : "compression did not reduce file\n");
|
||||||
|
else if (strcmp(cmd, "defrag") == 0) printf(fs_defrag(&fs) ? "ok\n" : "defrag failed\n");
|
||||||
|
```
|
||||||
|
|
||||||
## Ограничения реализации
|
## Ограничения реализации
|
||||||
|
|
||||||
| Ограничение | Причина |
|
| Ограничение | Причина |
|
||||||
@@ -231,8 +428,36 @@ info
|
|||||||
| RLE эффективно не для всех данных | алгоритм выбран ради понятности реализации |
|
| RLE эффективно не для всех данных | алгоритм выбран ради понятности реализации |
|
||||||
| Нет одновременного доступа нескольких процессов | учебная однопользовательская модель |
|
| Нет одновременного доступа нескольких процессов | учебная однопользовательская модель |
|
||||||
|
|
||||||
|
> **Теоретическая основа.** Ограничения учебной реализации обычно связаны с заранее фиксированными структурами данных. Это упрощает код и делает поведение системы предсказуемым, но уменьшает масштабируемость по сравнению с промышленными файловыми системами, где размеры таблиц, число блоков и механизмы синхронизации более гибкие.
|
||||||
|
|
||||||
|
Большинство ограничений заданы непосредственно константами программы. Это делает реализацию прозрачной для учебных целей, но одновременно фиксирует максимальные размеры структур:
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define MAX_NAME 32
|
||||||
|
#define MAX_PATH 256
|
||||||
|
#define MAX_ENTRIES 128
|
||||||
|
#define MAX_FILE_CLUSTERS 128
|
||||||
|
```
|
||||||
|
|
||||||
## Вывод
|
## Вывод
|
||||||
|
|
||||||
В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем.
|
В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем.
|
||||||
|
|
||||||
Полученная программа остается достаточно простой для изучения начинающим разработчиком на C, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
|
Полученная программа остается достаточно простой для изучения начинающим разработчиком на C, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
|
||||||
|
|
||||||
|
> **Итоговое обобщение.** Разработанная система демонстрирует фундаментальный принцип файловых систем: удобные для человека операции над именованными объектами опираются на строго организованный набор служебных структур и алгоритмов управления блоками носителя.
|
||||||
|
|
||||||
|
Ключевая идея реализации хорошо видна в операции синхронизации: состояние файловой системы собирается из нескольких уровней и затем последовательно сохраняется на виртуальный носитель.
|
||||||
|
|
||||||
|
```c
|
||||||
|
static bool fs_sync(FileSystem *fs) {
|
||||||
|
fseek(fs->disk, 0, SEEK_SET);
|
||||||
|
fwrite(&fs->sb, sizeof(SuperBlock), 1, fs->disk);
|
||||||
|
fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET);
|
||||||
|
fwrite(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk);
|
||||||
|
fseek(fs->disk, fs->sb.index_offset, SEEK_SET);
|
||||||
|
fwrite(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk);
|
||||||
|
fflush(fs->disk);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
CC = gcc
|
|
||||||
CFLAGS = -std=c11 -Wall -Wextra -pedantic
|
|
||||||
TARGET = simplefs
|
|
||||||
SOURCE = simplefs.c
|
|
||||||
DISK = disk.img
|
|
||||||
|
|
||||||
.PHONY: all run clean
|
|
||||||
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
$(TARGET): $(SOURCE)
|
|
||||||
$(CC) $(CFLAGS) $(SOURCE) -o $(TARGET)
|
|
||||||
|
|
||||||
run: $(TARGET)
|
|
||||||
./$(TARGET) $(DISK)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET) $(DISK)
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
# Brief testing guide
|
|
||||||
|
|
||||||
## 1. Build and start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make
|
|
||||||
make run
|
|
||||||
```
|
|
||||||
|
|
||||||
`make run` starts the program with `disk.img` as the virtual carrier file.
|
|
||||||
|
|
||||||
## 2. Basic filesystem scenario
|
|
||||||
|
|
||||||
Enter these commands inside the program:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format 512
|
|
||||||
mkdir /docs
|
|
||||||
touch /docs/test.txt
|
|
||||||
write /docs/test.txt hello filesystem
|
|
||||||
ls /docs
|
|
||||||
cat /docs/test.txt
|
|
||||||
rename /docs/test.txt renamed.txt
|
|
||||||
cat /docs/renamed.txt
|
|
||||||
info
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
- formatting succeeds;
|
|
||||||
- `/docs` contains one file;
|
|
||||||
- `cat` prints `hello filesystem`;
|
|
||||||
- after renaming, the file is available as `renamed.txt`;
|
|
||||||
- `info` shows a 512 KiB carrier and occupied clusters.
|
|
||||||
|
|
||||||
## 3. Compression and defragmentation
|
|
||||||
|
|
||||||
```text
|
|
||||||
touch /docs/repeat.txt
|
|
||||||
write /docs/repeat.txt aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
compress /docs/repeat.txt
|
|
||||||
ls /docs
|
|
||||||
defrag
|
|
||||||
cat /docs/repeat.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
- `compress` succeeds for repeated text;
|
|
||||||
- in `ls`, `stored` is smaller than `size` for `repeat.txt`;
|
|
||||||
- after `defrag`, the file still reads correctly.
|
|
||||||
|
|
||||||
## 4. Import and export
|
|
||||||
|
|
||||||
Before starting the program, create a host file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
echo "outside file" > host.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Then inside the program:
|
|
||||||
|
|
||||||
```text
|
|
||||||
import host.txt /docs/imported.txt
|
|
||||||
cat /docs/imported.txt
|
|
||||||
export /docs/imported.txt exported.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
After leaving the program, verify:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat exported.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result: `exported.txt` contains `outside file`.
|
|
||||||
|
|
||||||
## 5. Cleanup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
make clean
|
|
||||||
rm -f host.txt exported.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
`make clean` removes the executable and the default virtual disk image.
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
# Отчет по дисциплине «Операционные системы»
|
|
||||||
|
|
||||||
## РГР «Разработка простой файловой системы»
|
|
||||||
|
|
||||||
**Выполнил:** ____________________
|
|
||||||
**Группа:** ____________________
|
|
||||||
**Проверил:** ____________________
|
|
||||||
**Новосибирск, 2026**
|
|
||||||
|
|
||||||
## Оглавление
|
|
||||||
|
|
||||||
1. [Введение](#введение)
|
|
||||||
2. [Постановка задачи](#постановка-задачи)
|
|
||||||
3. [Выбранная архитектура файловой системы](#выбранная-архитектура-файловой-системы)
|
|
||||||
4. [Структура носителя и основные данные](#структура-носителя-и-основные-данные)
|
|
||||||
5. [Реализованные операции](#реализованные-операции)
|
|
||||||
6. [Алгоритмы работы](#алгоритмы-работы)
|
|
||||||
7. [Тестирование](#тестирование)
|
|
||||||
8. [Ограничения реализации](#ограничения-реализации)
|
|
||||||
9. [Вывод](#вывод)
|
|
||||||
|
|
||||||
## Введение
|
|
||||||
|
|
||||||
Цель работы — разработать учебную файловую систему на языке C, использующую обычный бинарный файл как виртуальный носитель произвольного доступа. Программа должна не только хранить файлы и каталоги, но и показывать основные принципы настоящих файловых систем: разметку носителя, учет свободного места, хранение метаданных, работу с путями и прикладные операции обслуживания.
|
|
||||||
|
|
||||||
В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие.
|
|
||||||
|
|
||||||
## Постановка задачи
|
|
||||||
|
|
||||||
Согласно заданию необходимо разработать систему управления файлами, где разделом является бинарный файл произвольного доступа. Реализация должна включать:
|
|
||||||
|
|
||||||
- физический уровень хранения данных;
|
|
||||||
- структуру учета свободных блоков;
|
|
||||||
- базовый уровень с индексными метаданными;
|
|
||||||
- размещение содержимого файлов;
|
|
||||||
- систему каталогов и путей;
|
|
||||||
- символьные операции над файлами и каталогами;
|
|
||||||
- прикладную утилиту для форматирования, просмотра, импорта, экспорта, дефрагментации и сжатия.
|
|
||||||
|
|
||||||
## Выбранная архитектура файловой системы
|
|
||||||
|
|
||||||
| Уровень | Реализация |
|
|
||||||
| --- | --- |
|
|
||||||
| Физический уровень | Кластеры фиксированного размера по 1024 байта |
|
|
||||||
| Учет свободного места | Битовая карта |
|
|
||||||
| Базовый уровень | Индексный файл с дескрипторами объектов |
|
|
||||||
| Размещение файлов | Список номеров кластеров в дескрипторе |
|
|
||||||
| Каталоги | Неупорядоченный список имен с линейным поиском |
|
|
||||||
| Прикладной уровень | Интерактивная консольная утилита |
|
|
||||||
|
|
||||||
Выбранная схема хорошо подходит для учебной реализации: она достаточно близка к реальным файловым системам, но не требует сложных деревьев, журналирования и динамических таблиц.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A[Файл-носитель] --> B[Суперблок]
|
|
||||||
A --> C[Битовая карта]
|
|
||||||
A --> D[Индексный файл]
|
|
||||||
A --> E[Область данных]
|
|
||||||
D --> F[Файлы]
|
|
||||||
D --> G[Каталоги]
|
|
||||||
F --> E
|
|
||||||
```
|
|
||||||
|
|
||||||
## Структура носителя и основные данные
|
|
||||||
|
|
||||||
В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога.
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct {
|
|
||||||
uint32_t magic;
|
|
||||||
uint32_t disk_size;
|
|
||||||
uint32_t cluster_size;
|
|
||||||
uint32_t total_clusters;
|
|
||||||
uint32_t bitmap_offset;
|
|
||||||
uint32_t bitmap_size;
|
|
||||||
uint32_t index_offset;
|
|
||||||
uint32_t index_clusters;
|
|
||||||
uint32_t data_start_cluster;
|
|
||||||
uint32_t root_index;
|
|
||||||
} SuperBlock;
|
|
||||||
```
|
|
||||||
|
|
||||||
После суперблока располагается битовая карта. Каждый ее бит соответствует одному кластеру: `0` означает свободный кластер, `1` — занятый. Далее находится индексный файл, представленный массивом дескрипторов `Entry`.
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct {
|
|
||||||
char name[32];
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t compressed;
|
|
||||||
uint32_t parent;
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t stored_size;
|
|
||||||
uint32_t cluster_count;
|
|
||||||
uint32_t clusters[128];
|
|
||||||
} Entry;
|
|
||||||
```
|
|
||||||
|
|
||||||
Поле `size` хранит логический размер файла, видимый пользователю, а `stored_size` — фактическое количество байт на носителе. Это различие необходимо для поддержки сжатых файлов.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TB
|
|
||||||
S[Суперблок] --> B[Битовая карта]
|
|
||||||
B --> I[Индексный файл]
|
|
||||||
I --> D[Область данных]
|
|
||||||
I --> R[Корневой каталог /]
|
|
||||||
R --> F[Дескрипторы файлов и каталогов]
|
|
||||||
F --> D
|
|
||||||
```
|
|
||||||
|
|
||||||
## Реализованные операции
|
|
||||||
|
|
||||||
Программа собирается из файла `simplefs.c` и запускается с именем файла-носителя:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
gcc -std=c11 -Wall -Wextra -pedantic simplefs.c -o simplefs
|
|
||||||
./simplefs disk.img
|
|
||||||
```
|
|
||||||
|
|
||||||
Поддерживаются следующие команды:
|
|
||||||
|
|
||||||
| Команда | Назначение |
|
|
||||||
| --- | --- |
|
|
||||||
| `format <KiB>` | создание новой файловой структуры |
|
|
||||||
| `info` | сведения о носителе и свободном месте |
|
|
||||||
| `ls [path]`, `pwd`, `cd <path>` | просмотр и навигация по каталогам |
|
|
||||||
| `mkdir`, `rmdir` | создание и удаление каталогов |
|
|
||||||
| `touch`, `rm`, `rename` | операции над файлами |
|
|
||||||
| `write`, `cat` | запись и чтение текстовых данных |
|
|
||||||
| `import`, `export` | импорт и экспорт отдельных файлов |
|
|
||||||
| `importdir`, `exportdir` | импорт и экспорт папок |
|
|
||||||
| `savecarrier`, `loadcarrier` | экспорт и импорт всего носителя |
|
|
||||||
| `compress` | сжатие файла методом RLE |
|
|
||||||
| `defrag` | упорядочивание занятых кластеров |
|
|
||||||
|
|
||||||
На уровне программного интерфейса реализованы функции `fs_open`, `fs_close`, `fs_seek`, `fs_read` и `fs_write`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами.
|
|
||||||
|
|
||||||
## Алгоритмы работы
|
|
||||||
|
|
||||||
### Форматирование
|
|
||||||
|
|
||||||
При выполнении `format` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`.
|
|
||||||
|
|
||||||
### Выделение свободного кластера
|
|
||||||
|
|
||||||
Алгоритм линейно просматривает битовую карту, начиная с области данных. Первый свободный бит помечается как занятый, а номер соответствующего кластера возвращается вызывающей функции.
|
|
||||||
|
|
||||||
### Работа с каталогами
|
|
||||||
|
|
||||||
Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `parent`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя.
|
|
||||||
|
|
||||||
### Сжатие
|
|
||||||
|
|
||||||
Команда `compress` использует простой алгоритм RLE: одинаковые подряд идущие байты заменяются парой «количество + значение». Если сжатый вариант не меньше исходного, файл не изменяется. При чтении сжатый файл автоматически восстанавливается в обычный вид.
|
|
||||||
|
|
||||||
### Дефрагментация
|
|
||||||
|
|
||||||
Команда `defrag` временно считывает содержимое файлов, очищает карту размещения области данных и записывает файлы заново подряд. После операции используемые кластеры становятся компактно расположенными.
|
|
||||||
|
|
||||||
## Тестирование
|
|
||||||
|
|
||||||
Пример базового сценария:
|
|
||||||
|
|
||||||
```text
|
|
||||||
format 512
|
|
||||||
mkdir /docs
|
|
||||||
touch /docs/a.txt
|
|
||||||
write /docs/a.txt aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
||||||
cat /docs/a.txt
|
|
||||||
compress /docs/a.txt
|
|
||||||
ls /docs
|
|
||||||
rename /docs/a.txt b.txt
|
|
||||||
export /docs/b.txt exported.txt
|
|
||||||
defrag
|
|
||||||
info
|
|
||||||
```
|
|
||||||
|
|
||||||
Ожидаемые результаты:
|
|
||||||
|
|
||||||
| Проверка | Результат |
|
|
||||||
| --- | --- |
|
|
||||||
| Форматирование носителя | создается корректный виртуальный диск |
|
|
||||||
| Создание каталога и файла | объекты появляются в `ls` |
|
|
||||||
| Запись и чтение | `cat` выводит ранее записанный текст |
|
|
||||||
| Сжатие повторяющихся данных | `stored_size` становится меньше `size` |
|
|
||||||
| Переименование | файл доступен под новым именем |
|
|
||||||
| Экспорт | создается внешний файл с тем же содержимым |
|
|
||||||
| Дефрагментация | содержимое файлов сохраняется, кластеры уплотняются |
|
|
||||||
|
|
||||||
## Ограничения реализации
|
|
||||||
|
|
||||||
| Ограничение | Причина |
|
|
||||||
| --- | --- |
|
|
||||||
| Не более 128 объектов | фиксированный размер индексного файла |
|
|
||||||
| Не более 128 кластеров на файл | фиксированный массив номеров кластеров |
|
|
||||||
| Длина имени до 31 символа | размер поля `name[32]` |
|
|
||||||
| Линейный поиск по каталогу | выбрана простая неупорядоченная структура |
|
|
||||||
| RLE эффективно не для всех данных | алгоритм выбран ради понятности реализации |
|
|
||||||
| Нет одновременного доступа нескольких процессов | учебная однопользовательская модель |
|
|
||||||
|
|
||||||
## Вывод
|
|
||||||
|
|
||||||
В ходе работы была реализована простая файловая система, работающая поверх бинарного файла как виртуального носителя. Реализация охватывает все основные уровни, указанные в задании: физическое размещение, учет свободных блоков, индексные метаданные, каталоги, операции над файлами и сервисные команды управления носителем.
|
|
||||||
|
|
||||||
Полученная программа остается достаточно простой для изучения начинающим разработчиком на C, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
|
|
||||||
@@ -1,715 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
#define FS_MAGIC 0x53465331u
|
|
||||||
#define CLUSTER_SIZE 1024u
|
|
||||||
#define MAX_NAME 32
|
|
||||||
#define MAX_PATH 256
|
|
||||||
#define MAX_ENTRIES 128
|
|
||||||
#define MAX_FILE_CLUSTERS 128
|
|
||||||
#define MAX_OPEN_DATA (MAX_FILE_CLUSTERS * CLUSTER_SIZE)
|
|
||||||
#define TYPE_FREE 0
|
|
||||||
#define TYPE_FILE 1
|
|
||||||
#define TYPE_DIR 2
|
|
||||||
#define MODE_READ 1
|
|
||||||
#define MODE_WRITE 2
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint32_t magic;
|
|
||||||
uint32_t disk_size;
|
|
||||||
uint32_t cluster_size;
|
|
||||||
uint32_t total_clusters;
|
|
||||||
uint32_t bitmap_offset;
|
|
||||||
uint32_t bitmap_size;
|
|
||||||
uint32_t index_offset;
|
|
||||||
uint32_t index_clusters;
|
|
||||||
uint32_t data_start_cluster;
|
|
||||||
uint32_t root_index;
|
|
||||||
} SuperBlock;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char name[MAX_NAME];
|
|
||||||
uint8_t type;
|
|
||||||
uint8_t compressed;
|
|
||||||
uint16_t reserved;
|
|
||||||
uint32_t parent;
|
|
||||||
uint32_t size; /* logical size visible to user */
|
|
||||||
uint32_t stored_size; /* bytes really stored on disk */
|
|
||||||
uint32_t cluster_count;
|
|
||||||
uint32_t clusters[MAX_FILE_CLUSTERS];
|
|
||||||
} Entry;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
FILE *disk;
|
|
||||||
char image_path[MAX_PATH];
|
|
||||||
SuperBlock sb;
|
|
||||||
unsigned char *bitmap;
|
|
||||||
Entry entries[MAX_ENTRIES];
|
|
||||||
uint32_t current_dir;
|
|
||||||
bool mounted;
|
|
||||||
} FileSystem;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
FileSystem *fs;
|
|
||||||
int entry_index;
|
|
||||||
int mode;
|
|
||||||
unsigned char *data;
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t capacity;
|
|
||||||
uint32_t position;
|
|
||||||
bool dirty;
|
|
||||||
} OpenFile;
|
|
||||||
|
|
||||||
static void safe_copy(char *dst, const char *src, size_t size) {
|
|
||||||
if (size == 0) return;
|
|
||||||
strncpy(dst, src, size - 1);
|
|
||||||
dst[size - 1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t bytes_to_clusters(uint32_t bytes) {
|
|
||||||
if (bytes == 0) return 0;
|
|
||||||
return (bytes + CLUSTER_SIZE - 1) / CLUSTER_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool valid_name(const char *name) {
|
|
||||||
return name && name[0] && strlen(name) < MAX_NAME && strchr(name, '/') == NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool bit_is_set(FileSystem *fs, uint32_t cluster) {
|
|
||||||
return (fs->bitmap[cluster / 8] & (1u << (cluster % 8))) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bit_set(FileSystem *fs, uint32_t cluster) {
|
|
||||||
fs->bitmap[cluster / 8] |= (unsigned char)(1u << (cluster % 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bit_clear(FileSystem *fs, uint32_t cluster) {
|
|
||||||
fs->bitmap[cluster / 8] &= (unsigned char)~(1u << (cluster % 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool fs_sync(FileSystem *fs) {
|
|
||||||
if (!fs || !fs->mounted || !fs->disk) return false;
|
|
||||||
fseek(fs->disk, 0, SEEK_SET);
|
|
||||||
fwrite(&fs->sb, sizeof(SuperBlock), 1, fs->disk);
|
|
||||||
fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET);
|
|
||||||
fwrite(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk);
|
|
||||||
fseek(fs->disk, fs->sb.index_offset, SEEK_SET);
|
|
||||||
fwrite(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk);
|
|
||||||
fflush(fs->disk);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int alloc_cluster(FileSystem *fs) {
|
|
||||||
uint32_t i;
|
|
||||||
for (i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) {
|
|
||||||
if (!bit_is_set(fs, i)) {
|
|
||||||
bit_set(fs, i);
|
|
||||||
return (int)i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_entry_clusters(FileSystem *fs, Entry *entry) {
|
|
||||||
uint32_t i;
|
|
||||||
for (i = 0; i < entry->cluster_count; i++) {
|
|
||||||
if (entry->clusters[i] < fs->sb.total_clusters) bit_clear(fs, entry->clusters[i]);
|
|
||||||
}
|
|
||||||
entry->cluster_count = 0;
|
|
||||||
entry->stored_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int find_child(FileSystem *fs, uint32_t parent, const char *name) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) {
|
|
||||||
if (fs->entries[i].type != TYPE_FREE && fs->entries[i].parent == parent &&
|
|
||||||
strcmp(fs->entries[i].name, name) == 0) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int free_entry_index(FileSystem *fs) {
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) if (fs->entries[i].type == TYPE_FREE) return i;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int resolve_path(FileSystem *fs, const char *path) {
|
|
||||||
char copy[MAX_PATH];
|
|
||||||
char *part;
|
|
||||||
uint32_t current;
|
|
||||||
if (!path || !path[0]) return -1;
|
|
||||||
safe_copy(copy, path, sizeof(copy));
|
|
||||||
current = (copy[0] == '/') ? fs->sb.root_index : fs->current_dir;
|
|
||||||
part = strtok(copy, "/");
|
|
||||||
if (!part && path[0] == '/') return (int)fs->sb.root_index;
|
|
||||||
while (part) {
|
|
||||||
if (strcmp(part, ".") == 0) {
|
|
||||||
/* stay */
|
|
||||||
} else if (strcmp(part, "..") == 0) {
|
|
||||||
current = fs->entries[current].parent;
|
|
||||||
} else {
|
|
||||||
int next = find_child(fs, current, part);
|
|
||||||
if (next < 0) return -1;
|
|
||||||
current = (uint32_t)next;
|
|
||||||
}
|
|
||||||
part = strtok(NULL, "/");
|
|
||||||
}
|
|
||||||
return (int)current;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool split_parent_name(FileSystem *fs, const char *path, int *parent, char *name) {
|
|
||||||
char copy[MAX_PATH];
|
|
||||||
char *slash;
|
|
||||||
if (!path || !path[0]) return false;
|
|
||||||
safe_copy(copy, path, sizeof(copy));
|
|
||||||
slash = strrchr(copy, '/');
|
|
||||||
if (!slash) {
|
|
||||||
*parent = (int)fs->current_dir;
|
|
||||||
safe_copy(name, copy, MAX_NAME);
|
|
||||||
} else if (slash == copy) {
|
|
||||||
*parent = (int)fs->sb.root_index;
|
|
||||||
safe_copy(name, slash + 1, MAX_NAME);
|
|
||||||
} else {
|
|
||||||
*slash = '\0';
|
|
||||||
*parent = resolve_path(fs, copy);
|
|
||||||
safe_copy(name, slash + 1, MAX_NAME);
|
|
||||||
}
|
|
||||||
return *parent >= 0 && fs->entries[*parent].type == TYPE_DIR && valid_name(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool write_stored_bytes(FileSystem *fs, Entry *entry, const unsigned char *data, uint32_t size) {
|
|
||||||
uint32_t needed = bytes_to_clusters(size);
|
|
||||||
uint32_t i, written = 0;
|
|
||||||
if (needed > MAX_FILE_CLUSTERS) return false;
|
|
||||||
free_entry_clusters(fs, entry);
|
|
||||||
entry->cluster_count = needed;
|
|
||||||
entry->stored_size = size;
|
|
||||||
for (i = 0; i < needed; i++) {
|
|
||||||
int c = alloc_cluster(fs);
|
|
||||||
if (c < 0) {
|
|
||||||
free_entry_clusters(fs, entry);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
entry->clusters[i] = (uint32_t)c;
|
|
||||||
}
|
|
||||||
for (i = 0; i < needed; i++) {
|
|
||||||
uint32_t chunk = size - written;
|
|
||||||
if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE;
|
|
||||||
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
|
||||||
fwrite(data + written, 1, chunk, fs->disk);
|
|
||||||
if (chunk < CLUSTER_SIZE) {
|
|
||||||
unsigned char zero[CLUSTER_SIZE] = {0};
|
|
||||||
fwrite(zero, 1, CLUSTER_SIZE - chunk, fs->disk);
|
|
||||||
}
|
|
||||||
written += chunk;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char *read_stored_bytes(FileSystem *fs, Entry *entry) {
|
|
||||||
unsigned char *data;
|
|
||||||
uint32_t i, read_count = 0;
|
|
||||||
if (entry->stored_size == 0) return calloc(1, 1);
|
|
||||||
data = malloc(entry->stored_size);
|
|
||||||
if (!data) return NULL;
|
|
||||||
for (i = 0; i < entry->cluster_count; i++) {
|
|
||||||
uint32_t chunk = entry->stored_size - read_count;
|
|
||||||
if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE;
|
|
||||||
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
|
||||||
fread(data + read_count, 1, chunk, fs->disk);
|
|
||||||
read_count += chunk;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char *rle_compress(const unsigned char *src, uint32_t size, uint32_t *out_size) {
|
|
||||||
unsigned char *out;
|
|
||||||
uint32_t i = 0, j = 0;
|
|
||||||
out = malloc(size * 2 + 2);
|
|
||||||
if (!out) return NULL;
|
|
||||||
while (i < size) {
|
|
||||||
unsigned char value = src[i];
|
|
||||||
uint32_t count = 1;
|
|
||||||
while (i + count < size && src[i + count] == value && count < 255) count++;
|
|
||||||
out[j++] = (unsigned char)count;
|
|
||||||
out[j++] = value;
|
|
||||||
i += count;
|
|
||||||
}
|
|
||||||
*out_size = j;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char *rle_decompress(const unsigned char *src, uint32_t stored_size, uint32_t logical_size) {
|
|
||||||
unsigned char *out;
|
|
||||||
uint32_t i = 0, j = 0, k;
|
|
||||||
out = malloc(logical_size + 1);
|
|
||||||
if (!out) return NULL;
|
|
||||||
while (i + 1 < stored_size && j < logical_size) {
|
|
||||||
unsigned char count = src[i++];
|
|
||||||
unsigned char value = src[i++];
|
|
||||||
for (k = 0; k < count && j < logical_size; k++) out[j++] = value;
|
|
||||||
}
|
|
||||||
if (j != logical_size) {
|
|
||||||
free(out);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char *read_logical_bytes(FileSystem *fs, Entry *entry) {
|
|
||||||
unsigned char *stored = read_stored_bytes(fs, entry);
|
|
||||||
unsigned char *logical;
|
|
||||||
if (!stored) return NULL;
|
|
||||||
if (!entry->compressed) return stored;
|
|
||||||
logical = rle_decompress(stored, entry->stored_size, entry->size);
|
|
||||||
free(stored);
|
|
||||||
return logical;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool store_logical_bytes(FileSystem *fs, Entry *entry, const unsigned char *data, uint32_t size) {
|
|
||||||
entry->compressed = 0;
|
|
||||||
entry->size = size;
|
|
||||||
return write_stored_bytes(fs, entry, data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_format(FileSystem *fs, const char *image_path, uint32_t size_kb) {
|
|
||||||
uint32_t total_bytes = size_kb * 1024u;
|
|
||||||
uint32_t bitmap_clusters, i;
|
|
||||||
if (total_bytes < 128u * 1024u) return false;
|
|
||||||
memset(fs, 0, sizeof(*fs));
|
|
||||||
safe_copy(fs->image_path, image_path, sizeof(fs->image_path));
|
|
||||||
fs->disk = fopen(image_path, "wb+");
|
|
||||||
if (!fs->disk) return false;
|
|
||||||
fseek(fs->disk, (long)total_bytes - 1, SEEK_SET);
|
|
||||||
fputc(0, fs->disk);
|
|
||||||
fs->sb.magic = FS_MAGIC;
|
|
||||||
fs->sb.disk_size = total_bytes;
|
|
||||||
fs->sb.cluster_size = CLUSTER_SIZE;
|
|
||||||
fs->sb.total_clusters = total_bytes / CLUSTER_SIZE;
|
|
||||||
fs->sb.bitmap_offset = CLUSTER_SIZE;
|
|
||||||
fs->sb.bitmap_size = (fs->sb.total_clusters + 7) / 8;
|
|
||||||
bitmap_clusters = bytes_to_clusters(fs->sb.bitmap_size);
|
|
||||||
fs->sb.index_offset = (1 + bitmap_clusters) * CLUSTER_SIZE;
|
|
||||||
fs->sb.index_clusters = bytes_to_clusters((uint32_t)(sizeof(Entry) * MAX_ENTRIES));
|
|
||||||
fs->sb.data_start_cluster = 1 + bitmap_clusters + fs->sb.index_clusters;
|
|
||||||
fs->sb.root_index = 0;
|
|
||||||
if (fs->sb.data_start_cluster >= fs->sb.total_clusters) { fclose(fs->disk); return false; }
|
|
||||||
fs->bitmap = calloc(fs->sb.bitmap_size, 1);
|
|
||||||
if (!fs->bitmap) { fclose(fs->disk); return false; }
|
|
||||||
for (i = 0; i < fs->sb.data_start_cluster; i++) bit_set(fs, i);
|
|
||||||
memset(fs->entries, 0, sizeof(fs->entries));
|
|
||||||
safe_copy(fs->entries[0].name, "/", MAX_NAME);
|
|
||||||
fs->entries[0].type = TYPE_DIR;
|
|
||||||
fs->entries[0].parent = 0;
|
|
||||||
fs->current_dir = 0;
|
|
||||||
fs->mounted = true;
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_mount(FileSystem *fs, const char *image_path) {
|
|
||||||
memset(fs, 0, sizeof(*fs));
|
|
||||||
safe_copy(fs->image_path, image_path, sizeof(fs->image_path));
|
|
||||||
fs->disk = fopen(image_path, "rb+");
|
|
||||||
if (!fs->disk) return false;
|
|
||||||
fread(&fs->sb, sizeof(SuperBlock), 1, fs->disk);
|
|
||||||
if (fs->sb.magic != FS_MAGIC || fs->sb.cluster_size != CLUSTER_SIZE) { fclose(fs->disk); return false; }
|
|
||||||
fs->bitmap = malloc(fs->sb.bitmap_size);
|
|
||||||
if (!fs->bitmap) { fclose(fs->disk); return false; }
|
|
||||||
fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET);
|
|
||||||
fread(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk);
|
|
||||||
fseek(fs->disk, fs->sb.index_offset, SEEK_SET);
|
|
||||||
fread(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk);
|
|
||||||
fs->current_dir = fs->sb.root_index;
|
|
||||||
fs->mounted = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fs_unmount(FileSystem *fs) {
|
|
||||||
if (!fs || !fs->mounted) return;
|
|
||||||
fs_sync(fs);
|
|
||||||
fclose(fs->disk);
|
|
||||||
free(fs->bitmap);
|
|
||||||
fs->disk = NULL;
|
|
||||||
fs->bitmap = NULL;
|
|
||||||
fs->mounted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_create_dir(FileSystem *fs, const char *path) {
|
|
||||||
int parent, index;
|
|
||||||
char name[MAX_NAME];
|
|
||||||
if (!split_parent_name(fs, path, &parent, name) || find_child(fs, parent, name) >= 0) return false;
|
|
||||||
index = free_entry_index(fs);
|
|
||||||
if (index < 0) return false;
|
|
||||||
memset(&fs->entries[index], 0, sizeof(Entry));
|
|
||||||
safe_copy(fs->entries[index].name, name, MAX_NAME);
|
|
||||||
fs->entries[index].type = TYPE_DIR;
|
|
||||||
fs->entries[index].parent = (uint32_t)parent;
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_create_file(FileSystem *fs, const char *path) {
|
|
||||||
int parent, index;
|
|
||||||
char name[MAX_NAME];
|
|
||||||
if (!split_parent_name(fs, path, &parent, name) || find_child(fs, parent, name) >= 0) return false;
|
|
||||||
index = free_entry_index(fs);
|
|
||||||
if (index < 0) return false;
|
|
||||||
memset(&fs->entries[index], 0, sizeof(Entry));
|
|
||||||
safe_copy(fs->entries[index].name, name, MAX_NAME);
|
|
||||||
fs->entries[index].type = TYPE_FILE;
|
|
||||||
fs->entries[index].parent = (uint32_t)parent;
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_remove(FileSystem *fs, const char *path) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
if (index <= 0 || fs->entries[index].type != TYPE_FILE) return false;
|
|
||||||
free_entry_clusters(fs, &fs->entries[index]);
|
|
||||||
memset(&fs->entries[index], 0, sizeof(Entry));
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_remove_dir(FileSystem *fs, const char *path) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
int i;
|
|
||||||
if (index <= 0 || fs->entries[index].type != TYPE_DIR) return false;
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) if (fs->entries[i].type != TYPE_FREE && fs->entries[i].parent == (uint32_t)index) return false;
|
|
||||||
memset(&fs->entries[index], 0, sizeof(Entry));
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_rename(FileSystem *fs, const char *path, const char *new_name) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
Entry *e;
|
|
||||||
if (index <= 0 || !valid_name(new_name)) return false;
|
|
||||||
e = &fs->entries[index];
|
|
||||||
if (find_child(fs, e->parent, new_name) >= 0) return false;
|
|
||||||
safe_copy(e->name, new_name, MAX_NAME);
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_cd(FileSystem *fs, const char *path) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
if (index < 0 || fs->entries[index].type != TYPE_DIR) return false;
|
|
||||||
fs->current_dir = (uint32_t)index;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fs_pwd(FileSystem *fs, char *out, size_t out_size) {
|
|
||||||
char temp[MAX_PATH] = "";
|
|
||||||
uint32_t cur = fs->current_dir;
|
|
||||||
if (cur == fs->sb.root_index) { safe_copy(out, "/", out_size); return; }
|
|
||||||
while (cur != fs->sb.root_index) {
|
|
||||||
char next[MAX_PATH];
|
|
||||||
snprintf(next, sizeof(next), "/%s%s", fs->entries[cur].name, temp);
|
|
||||||
safe_copy(temp, next, sizeof(temp));
|
|
||||||
cur = fs->entries[cur].parent;
|
|
||||||
}
|
|
||||||
safe_copy(out, temp, out_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
void fs_list(FileSystem *fs, const char *path) {
|
|
||||||
int dir = path ? resolve_path(fs, path) : (int)fs->current_dir;
|
|
||||||
int i;
|
|
||||||
if (dir < 0 || fs->entries[dir].type != TYPE_DIR) { printf("Directory not found.\n"); return; }
|
|
||||||
printf("%-32s %-8s %-10s %-10s\n", "name", "type", "size", "stored");
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) {
|
|
||||||
Entry *e = &fs->entries[i];
|
|
||||||
if (e->type != TYPE_FREE && e->parent == (uint32_t)dir && i != dir) {
|
|
||||||
printf("%-32s %-8s %-10u %-10u%s\n", e->name, e->type == TYPE_DIR ? "dir" : "file", e->size, e->stored_size, e->compressed ? " compressed" : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenFile *fs_open(FileSystem *fs, const char *path, const char *mode) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
OpenFile *f;
|
|
||||||
if (index < 0 && strchr(mode, 'w')) {
|
|
||||||
if (!fs_create_file(fs, path)) return NULL;
|
|
||||||
index = resolve_path(fs, path);
|
|
||||||
}
|
|
||||||
if (index < 0 || fs->entries[index].type != TYPE_FILE) return NULL;
|
|
||||||
f = calloc(1, sizeof(OpenFile));
|
|
||||||
if (!f) return NULL;
|
|
||||||
f->fs = fs;
|
|
||||||
f->entry_index = index;
|
|
||||||
f->mode = strchr(mode, 'w') ? MODE_WRITE : MODE_READ;
|
|
||||||
f->capacity = MAX_OPEN_DATA;
|
|
||||||
f->data = calloc(f->capacity, 1);
|
|
||||||
if (!f->data) { free(f); return NULL; }
|
|
||||||
if (!strchr(mode, 'w')) {
|
|
||||||
unsigned char *existing = read_logical_bytes(fs, &fs->entries[index]);
|
|
||||||
if (!existing && fs->entries[index].size > 0) { free(f->data); free(f); return NULL; }
|
|
||||||
memcpy(f->data, existing, fs->entries[index].size);
|
|
||||||
f->size = fs->entries[index].size;
|
|
||||||
free(existing);
|
|
||||||
} else {
|
|
||||||
f->dirty = true;
|
|
||||||
}
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fs_seek(OpenFile *f, uint32_t position) {
|
|
||||||
if (!f || position > f->size) return -1;
|
|
||||||
f->position = position;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t fs_read(OpenFile *f, unsigned char *buffer, uint32_t count) {
|
|
||||||
uint32_t available;
|
|
||||||
if (!f || !buffer) return 0;
|
|
||||||
available = f->size - f->position;
|
|
||||||
if (count > available) count = available;
|
|
||||||
memcpy(buffer, f->data + f->position, count);
|
|
||||||
f->position += count;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t fs_write(OpenFile *f, const unsigned char *buffer, uint32_t count) {
|
|
||||||
if (!f || !buffer || f->mode != MODE_WRITE || f->position + count > f->capacity) return 0;
|
|
||||||
memcpy(f->data + f->position, buffer, count);
|
|
||||||
f->position += count;
|
|
||||||
if (f->position > f->size) f->size = f->position;
|
|
||||||
f->dirty = true;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_close(OpenFile *f) {
|
|
||||||
bool ok = true;
|
|
||||||
if (!f) return false;
|
|
||||||
if (f->dirty) {
|
|
||||||
ok = store_logical_bytes(f->fs, &f->fs->entries[f->entry_index], f->data, f->size);
|
|
||||||
fs_sync(f->fs);
|
|
||||||
}
|
|
||||||
free(f->data);
|
|
||||||
free(f);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_write_text(FileSystem *fs, const char *path, const char *text) {
|
|
||||||
OpenFile *f = fs_open(fs, path, "w");
|
|
||||||
bool ok;
|
|
||||||
if (!f) return false;
|
|
||||||
ok = fs_write(f, (const unsigned char *)text, (uint32_t)strlen(text)) == strlen(text);
|
|
||||||
return fs_close(f) && ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_print_file(FileSystem *fs, const char *path) {
|
|
||||||
OpenFile *f = fs_open(fs, path, "r");
|
|
||||||
unsigned char buffer[CLUSTER_SIZE + 1];
|
|
||||||
uint32_t n;
|
|
||||||
if (!f) return false;
|
|
||||||
while ((n = fs_read(f, buffer, CLUSTER_SIZE)) > 0) {
|
|
||||||
buffer[n] = '\0';
|
|
||||||
fwrite(buffer, 1, n, stdout);
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
return fs_close(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_import_file(FileSystem *fs, const char *host_path, const char *fs_path) {
|
|
||||||
FILE *in = fopen(host_path, "rb");
|
|
||||||
OpenFile *out;
|
|
||||||
unsigned char buffer[CLUSTER_SIZE];
|
|
||||||
size_t n;
|
|
||||||
if (!in) return false;
|
|
||||||
out = fs_open(fs, fs_path, "w");
|
|
||||||
if (!out) { fclose(in); return false; }
|
|
||||||
while ((n = fread(buffer, 1, sizeof(buffer), in)) > 0) {
|
|
||||||
if (fs_write(out, buffer, (uint32_t)n) != n) { fclose(in); fs_close(out); return false; }
|
|
||||||
}
|
|
||||||
fclose(in);
|
|
||||||
return fs_close(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_export_file(FileSystem *fs, const char *fs_path, const char *host_path) {
|
|
||||||
FILE *out = fopen(host_path, "wb");
|
|
||||||
OpenFile *in;
|
|
||||||
unsigned char buffer[CLUSTER_SIZE];
|
|
||||||
uint32_t n;
|
|
||||||
if (!out) return false;
|
|
||||||
in = fs_open(fs, fs_path, "r");
|
|
||||||
if (!in) { fclose(out); return false; }
|
|
||||||
while ((n = fs_read(in, buffer, sizeof(buffer))) > 0) fwrite(buffer, 1, n, out);
|
|
||||||
fclose(out);
|
|
||||||
return fs_close(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool fs_import_dir(FileSystem *fs, const char *host_path, const char *fs_path) {
|
|
||||||
DIR *dir = opendir(host_path);
|
|
||||||
struct dirent *item;
|
|
||||||
if (!dir) return false;
|
|
||||||
if (resolve_path(fs, fs_path) < 0 && !fs_create_dir(fs, fs_path)) { closedir(dir); return false; }
|
|
||||||
while ((item = readdir(dir)) != NULL) {
|
|
||||||
char host_child[MAX_PATH], fs_child[MAX_PATH];
|
|
||||||
struct stat st;
|
|
||||||
if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0) continue;
|
|
||||||
snprintf(host_child, sizeof(host_child), "%s/%s", host_path, item->d_name);
|
|
||||||
snprintf(fs_child, sizeof(fs_child), "%s/%s", fs_path, item->d_name);
|
|
||||||
if (stat(host_child, &st) != 0) continue;
|
|
||||||
if (S_ISDIR(st.st_mode)) fs_import_dir(fs, host_child, fs_child);
|
|
||||||
else fs_import_file(fs, host_child, fs_child);
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool fs_export_dir(FileSystem *fs, const char *fs_path, const char *host_path) {
|
|
||||||
int dir = resolve_path(fs, fs_path);
|
|
||||||
int i;
|
|
||||||
if (dir < 0 || fs->entries[dir].type != TYPE_DIR) return false;
|
|
||||||
mkdir(host_path, 0777);
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) {
|
|
||||||
Entry *e = &fs->entries[i];
|
|
||||||
if (e->type != TYPE_FREE && e->parent == (uint32_t)dir && i != dir) {
|
|
||||||
char child_fs[MAX_PATH], child_host[MAX_PATH];
|
|
||||||
snprintf(child_fs, sizeof(child_fs), "%s/%s", fs_path, e->name);
|
|
||||||
snprintf(child_host, sizeof(child_host), "%s/%s", host_path, e->name);
|
|
||||||
if (e->type == TYPE_DIR) fs_export_dir(fs, child_fs, child_host);
|
|
||||||
else fs_export_file(fs, child_fs, child_host);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_compress_file(FileSystem *fs, const char *path) {
|
|
||||||
int index = resolve_path(fs, path);
|
|
||||||
Entry *e;
|
|
||||||
unsigned char *logical, *compressed;
|
|
||||||
uint32_t compressed_size;
|
|
||||||
bool ok;
|
|
||||||
if (index < 0 || fs->entries[index].type != TYPE_FILE) return false;
|
|
||||||
e = &fs->entries[index];
|
|
||||||
logical = read_logical_bytes(fs, e);
|
|
||||||
if (!logical) return false;
|
|
||||||
compressed = rle_compress(logical, e->size, &compressed_size);
|
|
||||||
if (!compressed || compressed_size >= e->size) { free(logical); free(compressed); return false; }
|
|
||||||
e->compressed = 1;
|
|
||||||
ok = write_stored_bytes(fs, e, compressed, compressed_size);
|
|
||||||
e->compressed = ok ? 1 : 0;
|
|
||||||
free(logical);
|
|
||||||
free(compressed);
|
|
||||||
fs_sync(fs);
|
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool fs_defrag(FileSystem *fs) {
|
|
||||||
unsigned char *copies[MAX_ENTRIES] = {0};
|
|
||||||
uint32_t stored_sizes[MAX_ENTRIES] = {0};
|
|
||||||
uint32_t i, c;
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) {
|
|
||||||
if (fs->entries[i].type == TYPE_FILE && fs->entries[i].stored_size > 0) {
|
|
||||||
copies[i] = read_stored_bytes(fs, &fs->entries[i]);
|
|
||||||
if (!copies[i]) return false;
|
|
||||||
stored_sizes[i] = fs->entries[i].stored_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
memset(fs->bitmap, 0, fs->sb.bitmap_size);
|
|
||||||
for (c = 0; c < fs->sb.data_start_cluster; c++) bit_set(fs, c);
|
|
||||||
for (i = 0; i < MAX_ENTRIES; i++) {
|
|
||||||
if (fs->entries[i].type == TYPE_FILE) {
|
|
||||||
fs->entries[i].cluster_count = 0;
|
|
||||||
if (!write_stored_bytes(fs, &fs->entries[i], copies[i], stored_sizes[i])) return false;
|
|
||||||
}
|
|
||||||
free(copies[i]);
|
|
||||||
}
|
|
||||||
fs_sync(fs);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fs_info(FileSystem *fs) {
|
|
||||||
uint32_t i, free_clusters = 0;
|
|
||||||
for (i = 0; i < fs->sb.total_clusters; i++) if (!bit_is_set(fs, i)) free_clusters++;
|
|
||||||
printf("image: %s\n", fs->image_path);
|
|
||||||
printf("disk size: %u KiB\n", fs->sb.disk_size / 1024);
|
|
||||||
printf("cluster size: %u bytes\n", fs->sb.cluster_size);
|
|
||||||
printf("clusters: %u total, %u free, %u occupied\n", fs->sb.total_clusters, free_clusters, fs->sb.total_clusters - free_clusters);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool copy_file(const char *from, const char *to) {
|
|
||||||
FILE *in = fopen(from, "rb");
|
|
||||||
FILE *out;
|
|
||||||
unsigned char buffer[4096];
|
|
||||||
size_t n;
|
|
||||||
if (!in) return false;
|
|
||||||
out = fopen(to, "wb");
|
|
||||||
if (!out) { fclose(in); return false; }
|
|
||||||
while ((n = fread(buffer, 1, sizeof(buffer), in)) > 0) fwrite(buffer, 1, n, out);
|
|
||||||
fclose(in);
|
|
||||||
fclose(out);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_help(void) {
|
|
||||||
puts("Commands:");
|
|
||||||
puts(" format <KiB> create a new carrier");
|
|
||||||
puts(" info | ls [path] | pwd | cd <path>");
|
|
||||||
puts(" mkdir <path> | rmdir <path>");
|
|
||||||
puts(" touch <path> | rm <path> | rename <path> <new_name>");
|
|
||||||
puts(" write <path> <text> | cat <path>");
|
|
||||||
puts(" import <host_file> <fs_path> | export <fs_path> <host_file>");
|
|
||||||
puts(" importdir <host_dir> <fs_path> | exportdir <fs_path> <host_dir>");
|
|
||||||
puts(" savecarrier <host_file> | loadcarrier <host_file>");
|
|
||||||
puts(" compress <path> | defrag | help | exit");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
FileSystem fs;
|
|
||||||
char line[1024];
|
|
||||||
if (argc != 2) { printf("Usage: %s <carrier_file>\n", argv[0]); return 1; }
|
|
||||||
memset(&fs, 0, sizeof(fs));
|
|
||||||
if (!fs_mount(&fs, argv[1])) printf("Carrier is not formatted yet. Use: format <KiB>\n");
|
|
||||||
print_help();
|
|
||||||
while (1) {
|
|
||||||
char *cmd, *a, *b;
|
|
||||||
char cwd[MAX_PATH] = "/";
|
|
||||||
if (fs.mounted) fs_pwd(&fs, cwd, sizeof(cwd));
|
|
||||||
printf("%s> ", cwd);
|
|
||||||
if (!fgets(line, sizeof(line), stdin)) break;
|
|
||||||
line[strcspn(line, "\n")] = '\0';
|
|
||||||
cmd = strtok(line, " ");
|
|
||||||
if (!cmd) continue;
|
|
||||||
a = strtok(NULL, " ");
|
|
||||||
b = strtok(NULL, "");
|
|
||||||
if (strcmp(cmd, "exit") == 0) break;
|
|
||||||
if (strcmp(cmd, "help") == 0) print_help();
|
|
||||||
else if (strcmp(cmd, "format") == 0 && a) {
|
|
||||||
if (fs.mounted) fs_unmount(&fs);
|
|
||||||
printf(fs_format(&fs, argv[1], (uint32_t)atoi(a)) ? "formatted\n" : "format failed\n");
|
|
||||||
} else if (!fs.mounted) printf("No mounted filesystem. Format or load a carrier first.\n");
|
|
||||||
else if (strcmp(cmd, "info") == 0) fs_info(&fs);
|
|
||||||
else if (strcmp(cmd, "ls") == 0) fs_list(&fs, a);
|
|
||||||
else if (strcmp(cmd, "pwd") == 0) printf("%s\n", cwd);
|
|
||||||
else if (strcmp(cmd, "cd") == 0 && a) printf(fs_cd(&fs, a) ? "ok\n" : "directory not found\n");
|
|
||||||
else if (strcmp(cmd, "mkdir") == 0 && a) printf(fs_create_dir(&fs, a) ? "ok\n" : "mkdir failed\n");
|
|
||||||
else if (strcmp(cmd, "rmdir") == 0 && a) printf(fs_remove_dir(&fs, a) ? "ok\n" : "rmdir failed\n");
|
|
||||||
else if (strcmp(cmd, "touch") == 0 && a) printf(fs_create_file(&fs, a) ? "ok\n" : "touch failed\n");
|
|
||||||
else if (strcmp(cmd, "rm") == 0 && a) printf(fs_remove(&fs, a) ? "ok\n" : "rm failed\n");
|
|
||||||
else if (strcmp(cmd, "rename") == 0 && a && b) printf(fs_rename(&fs, a, b) ? "ok\n" : "rename failed\n");
|
|
||||||
else if (strcmp(cmd, "write") == 0 && a && b) printf(fs_write_text(&fs, a, b) ? "ok\n" : "write failed\n");
|
|
||||||
else if (strcmp(cmd, "cat") == 0 && a) printf(fs_print_file(&fs, a) ? "" : "cat failed\n");
|
|
||||||
else if (strcmp(cmd, "import") == 0 && a && b) printf(fs_import_file(&fs, a, b) ? "ok\n" : "import failed\n");
|
|
||||||
else if (strcmp(cmd, "export") == 0 && a && b) printf(fs_export_file(&fs, a, b) ? "ok\n" : "export failed\n");
|
|
||||||
else if (strcmp(cmd, "importdir") == 0 && a && b) printf(fs_import_dir(&fs, a, b) ? "ok\n" : "importdir failed\n");
|
|
||||||
else if (strcmp(cmd, "exportdir") == 0 && a && b) printf(fs_export_dir(&fs, a, b) ? "ok\n" : "exportdir failed\n");
|
|
||||||
else if (strcmp(cmd, "savecarrier") == 0 && a) { fs_sync(&fs); printf(copy_file(fs.image_path, a) ? "ok\n" : "copy failed\n"); }
|
|
||||||
else if (strcmp(cmd, "loadcarrier") == 0 && a) { fs_unmount(&fs); printf(copy_file(a, argv[1]) && fs_mount(&fs, argv[1]) ? "ok\n" : "load failed\n"); }
|
|
||||||
else if (strcmp(cmd, "compress") == 0 && a) printf(fs_compress_file(&fs, a) ? "ok\n" : "compression did not reduce file\n");
|
|
||||||
else if (strcmp(cmd, "defrag") == 0) printf(fs_defrag(&fs) ? "ok\n" : "defrag failed\n");
|
|
||||||
else printf("Unknown or incomplete command. Type help.\n");
|
|
||||||
}
|
|
||||||
if (fs.mounted) fs_unmount(&fs);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user