Compare commits

..

2 Commits

Author SHA1 Message Date
pajjilykk e5a26d66b9 cleanup 2026-05-18 17:22:21 +07:00
pajjilykk 9a16b6d369 update report 2026-05-18 17:22:11 +07:00
5 changed files with 225 additions and 1021 deletions
+225
View File
@@ -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;
}
```
-18
View File
@@ -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)
-84
View File
@@ -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.
-204
View File
@@ -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, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
-715
View File
@@ -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;
}