From ba6614d7a9cc3676b436bf36a49546d16e9b8f3a Mon Sep 17 00:00:00 2001 From: pajjilykk Date: Mon, 18 May 2026 16:59:56 +0700 Subject: [PATCH] vibecode the entire rgz ig --- rgz/Makefile | 18 + rgz/TESTING.md | 91 +++++ rgz/report.md | 238 +++++++++++ rgz/simplefs.c | 918 +++++++++++++++++++++++++++++++++++++++++++ rgz_vlasd/Makefile | 18 + rgz_vlasd/TESTING.md | 84 ++++ rgz_vlasd/report.md | 204 ++++++++++ rgz_vlasd/simplefs.c | 715 +++++++++++++++++++++++++++++++++ 8 files changed, 2286 insertions(+) create mode 100644 rgz/Makefile create mode 100644 rgz/TESTING.md create mode 100644 rgz/report.md create mode 100644 rgz/simplefs.c create mode 100644 rgz_vlasd/Makefile create mode 100644 rgz_vlasd/TESTING.md create mode 100644 rgz_vlasd/report.md create mode 100644 rgz_vlasd/simplefs.c diff --git a/rgz/Makefile b/rgz/Makefile new file mode 100644 index 0000000..205e502 --- /dev/null +++ b/rgz/Makefile @@ -0,0 +1,18 @@ +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) diff --git a/rgz/TESTING.md b/rgz/TESTING.md new file mode 100644 index 0000000..65e275b --- /dev/null +++ b/rgz/TESTING.md @@ -0,0 +1,91 @@ +# 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 + +When testing manually in a terminal, try the **Up** and **Down** arrows after a few commands: they browse command history. + +Enter these commands inside the program: + +```text +format 512 +mkdir /docs +touch /docs/test.txt +write /docs/test.txt hello filesystem +cp /docs/test.txt /docs/copy.txt +ls /docs +cat /docs/test.txt +mv /docs/test.txt /docs/renamed.txt +cat /docs/renamed.txt +info +``` + +Expected result: + +- formatting succeeds; +- `/docs` contains the original file and its copy; +- `cat` prints `hello filesystem`; +- after `mv`, 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 +rawcat /docs/repeat.txt +decomp /docs/repeat.txt +rawcat /docs/repeat.txt +defrag +cat /docs/repeat.txt +``` + +Expected result: + +- `compress` succeeds for repeated text; +- in `ls`, `stored` is smaller than `size` for `repeat.txt`; +- `rawcat` shows compact RLE pairs before decompression and direct bytes after `decomp`; +- 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. diff --git a/rgz/report.md b/rgz/report.md new file mode 100644 index 0000000..632d61d --- /dev/null +++ b/rgz/report.md @@ -0,0 +1,238 @@ +# Отчет по дисциплине «Операционные системы» + +## РГР «Разработка простой файловой системы» + +**Выполнил:** ____________________ +**Группа:** ____________________ +**Проверил:** ____________________ +**Новосибирск, 2026** + +## Оглавление + +1. [Введение](#введение) +2. [Постановка задачи](#постановка-задачи) +3. [Выбранная архитектура файловой системы](#выбранная-архитектура-файловой-системы) +4. [Структура носителя и основные данные](#структура-носителя-и-основные-данные) +5. [Реализованные операции](#реализованные-операции) +6. [Алгоритмы работы](#алгоритмы-работы) +7. [Тестирование](#тестирование) +8. [Ограничения реализации](#ограничения-реализации) +9. [Вывод](#вывод) + +## Введение + +Цель работы — разработать учебную файловую систему на языке C, использующую обычный бинарный файл как виртуальный носитель произвольного доступа. Программа должна не только хранить файлы и каталоги, но и показывать основные принципы настоящих файловых систем: разметку носителя, учет свободного места, хранение метаданных, работу с путями и прикладные операции обслуживания. + +В качестве ориентира использовался предоставленный пример реализации, однако приоритет был отдан требованиям задания. Поэтому итоговая программа сохраняет простую учебную архитектуру примера, но дополнительно реализует операции `open`, `close`, `seek`, `read`, `write`, `rename`, импорт и экспорт, дефрагментацию и сжатие. + +## Постановка задачи + +Согласно заданию необходимо разработать систему управления файлами, где разделом является бинарный файл произвольного доступа. Реализация должна включать: + +- физический уровень хранения данных; +- структуру учета свободных блоков; +- базовый уровень с индексными метаданными; +- размещение содержимого файлов; +- систему каталогов и путей; +- символьные операции над файлами и каталогами; +- прикладную утилиту для форматирования, просмотра, импорта, экспорта, дефрагментации и сжатия. + +## Выбранная архитектура файловой системы + +Файловая система разделена на уровни. Такое деление полезно не только для описания в отчете: каждый уровень решает свою задачу и скрывает детали от следующего. Пользователь работает с командами и путями, а не с номерами кластеров; функции чтения работают с файлами, а не с отдельными битами карты свободного пространства. + +| Уровень | Реализация | Назначение | +| --- | --- | --- | +| Физический уровень | Кластеры фиксированного размера по 1024 байта | Делит носитель на одинаковые адресуемые блоки; смещение кластера вычисляется как `номер * 1024` | +| Учет свободного места | Битовая карта | Показывает, какие кластеры свободны, а какие уже принадлежат служебным областям или файлам | +| Базовый уровень | Индексный файл с дескрипторами объектов | Хранит метаданные файлов и каталогов отдельно от пользовательских данных | +| Размещение файлов | Список номеров кластеров в дескрипторе | Позволяет файлу занимать несколько несмежных кластеров и читать их в правильном порядке | +| Логический уровень | Неупорядоченный список имен | Представляет каталоги через связь «объект — родительский каталог» и выполняет линейный поиск по именам | +| Символьный уровень | Пути вида `/docs/file.txt`, буферизированные операции `open/close/seek/read/write` | Преобразует удобные человеку имена и позиции в обращения к дескрипторам и кластерам | +| Прикладной уровень | Интерактивная консольная утилита | Дает команды для форматирования, просмотра, копирования, импорта, экспорта, сжатия и дефрагментации | + +### Физический уровень + +Носителем является обычный бинарный файл. Он рассматривается как последовательность кластеров одинакового размера. Фиксированный размер выбран ради простоты: адрес любого кластера находится прямым умножением, а алгоритм выделения не обязан искать переменные по длине участки памяти. Недостатком является внутреннее фрагментирование: даже файл в несколько байт занимает целый кластер. + +### Уровень свободного пространства + +Для каждого кластера хранится один бит. При форматировании служебные кластеры суперблока, битовой карты и индексного файла сразу помечаются занятыми. При записи файла функция выделения просматривает карту от начала области данных и выбирает первые свободные кластеры; при удалении файла соответствующие биты сбрасываются обратно в ноль. + +### Базовый уровень + +Индексный файл — это таблица дескрипторов `Entry`. Один дескриптор описывает один файл или каталог: имя, тип, родителя, размеры и список кластеров. Корневой каталог хранится в нулевой записи. Благодаря индексному файлу метаданные можно прочитать сразу после монтирования носителя, не просматривая всю область данных. + +### Уровень размещения файлов + +Каждый файл хранит массив номеров своих кластеров. Поэтому файл может лежать не непрерывно, а частями в разных местах виртуального диска. При чтении система идет по этому массиву и собирает содержимое в правильной последовательности. Дефрагментация перестраивает эти списки так, чтобы занятые кластеры снова располагались компактно. + +### Логический и символьный уровни + +Каталог реализован без отдельного файла содержимого: у каждого объекта есть поле `parent`, содержащее индекс родительского каталога. Путь разбивается по символу `/`, после чего система шаг за шагом ищет дочерний объект в текущем каталоге. Символьный уровень дает привычные операции над открытым файлом: `open`, `close`, `seek`, `read`, `write`; внутри программы они работают через буфер и скрывают детали физического размещения. + +```mermaid +flowchart LR + A[Команда пользователя] --> B[Путь /docs/a.txt] + B --> C[Поиск дескриптора] + C --> D[Список кластеров] + D --> E[Битовая карта] + D --> F[Байты в области данных] +``` + +## Структура носителя и основные данные + +В начале виртуального диска находится суперблок. Он хранит сигнатуру файловой системы, размер диска, размер кластера, смещения служебных областей и индекс корневого каталога. + +```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 +``` + +Интерактивная оболочка поддерживает историю команд: в обычном терминале стрелки **Up** и **Down** перемещаются по ранее введенным строкам. Основные команды названы в стиле Unix, чтобы работа с учебной файловой системой напоминала привычную командную строку. + +### Справочник команд + +| Команда | Синтаксис | Назначение | +| --- | --- | --- | +| Форматирование | `format ` | создает новый файл-носитель указанного размера | +| Информация | `info` | показывает размер диска, размер кластера и число свободных кластеров | +| Список | `ls [path]` | выводит содержимое текущего или указанного каталога | +| Текущий каталог | `pwd` | печатает текущий абсолютный путь | +| Переход | `cd ` | меняет текущий каталог | +| Создание каталога | `mkdir ` | создает новый каталог | +| Удаление каталога | `rmdir ` | удаляет только пустой каталог | +| Создание файла | `touch ` | создает пустой файл | +| Удаление файла | `rm ` | удаляет файл и освобождает его кластеры | +| Копирование внутри ФС | `cp ` | копирует файл в новый путь или в существующий каталог | +| Перемещение/переименование | `mv ` | переносит файл или каталог; если меняется только имя, работает как переименование | +| Запись текста | `write ` | перезаписывает файл текстовой строкой | +| Просмотр файла | `cat ` | выводит логическое содержимое файла | +| Просмотр хранения | `rawcat ` | выводит физически сохраненные байты и объясняет пары RLE | +| Импорт файла | `import ` | копирует файл из основной ОС в учебную ФС | +| Экспорт файла | `export ` | копирует файл из учебной ФС в основную ОС | +| Импорт папки | `importdir ` | рекурсивно импортирует каталог | +| Экспорт папки | `exportdir ` | рекурсивно экспортирует каталог | +| Экспорт носителя | `savecarrier ` | сохраняет копию всего бинарного носителя | +| Импорт носителя | `loadcarrier ` | заменяет текущий носитель ранее сохраненной копией | +| Сжатие | `compress ` | применяет RLE-сжатие, если оно уменьшает файл | +| Распаковка | `decomp ` | восстанавливает файл в несжатом виде | +| Дефрагментация | `defrag` | заново записывает файлы подряд в область данных | +| Справка и выход | `help`, `exit`, `quit` | показывает команды или завершает программу | + +На уровне программного интерфейса реализованы функции `fs_open`, `fs_close`, `fs_seek`, `fs_read` и `fs_write`. Открытый файл использует буфер в памяти, поэтому чтение и запись записей произвольной длины выполняются без прямой работы пользователя с кластерами. + +## Алгоритмы работы + +### Форматирование + +При выполнении `format` создается бинарный файл нужного размера, вычисляются размеры служебных областей, резервируются занятые кластеры и создается корневой каталог `/`. + +### Выделение свободного кластера + +Алгоритм линейно просматривает битовую карту, начиная с области данных. Первый свободный бит помечается как занятый, а номер соответствующего кластера возвращается вызывающей функции. + +### Работа с каталогами + +Каталог не хранит отдельный массив дочерних элементов. Вместо этого у каждого дескриптора есть поле `parent`, содержащее индекс родительского каталога. Для поиска элемента программа просматривает индексный файл и сравнивает имя и родителя. + +### Сжатие + +Команда `compress` использует простой алгоритм RLE: одинаковые подряд идущие байты заменяются парой «количество + значение». Если сжатый вариант не меньше исходного, файл не изменяется. При чтении сжатый файл автоматически восстанавливается в обычный вид. Для наблюдения за внутренним представлением предусмотрена команда `rawcat`: она показывает сохраненные байты в шестнадцатеричном виде и расшифровывает каждую RLE-пару. Команда `decomp` выполняет обратное преобразование и снова сохраняет файл без сжатия. + +### Дефрагментация + +Команда `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, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска. diff --git a/rgz/simplefs.c b/rgz/simplefs.c new file mode 100644 index 0000000..552c5ed --- /dev/null +++ b/rgz/simplefs.c @@ -0,0 +1,918 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 MAX_HISTORY 50 +#define MAX_LINE 1024 +#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 join_path(char *out, size_t out_size, const char *left, const char *right) { + size_t left_len = strlen(left); + size_t right_len = strlen(right); + if (left_len + 1 + right_len + 1 > out_size) return false; + memcpy(out, left, left_len); + out[left_len] = '/'; + memcpy(out + left_len + 1, right, right_len + 1); + return true; +} + +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_move(FileSystem *fs, const char *from, const char *to) { + int source = resolve_path(fs, from); + int target = resolve_path(fs, to); + int new_parent; + char new_name[MAX_NAME]; + if (source <= 0) return false; + if (target >= 0 && fs->entries[target].type == TYPE_DIR) { + new_parent = target; + safe_copy(new_name, fs->entries[source].name, sizeof(new_name)); + } else { + if (!split_parent_name(fs, to, &new_parent, new_name)) return false; + } + if (find_child(fs, (uint32_t)new_parent, new_name) >= 0) return false; + fs->entries[source].parent = (uint32_t)new_parent; + safe_copy(fs->entries[source].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]; + size_t name_len = strlen(fs->entries[cur].name); + size_t temp_len = strlen(temp); + if (1 + name_len + temp_len + 1 > sizeof(next)) { + safe_copy(out, "/", out_size); + return; + } + next[0] = '/'; + memcpy(next + 1, fs->entries[cur].name, name_len); + memcpy(next + 1 + name_len, temp, temp_len + 1); + 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_copy_file(FileSystem *fs, const char *from, const char *to) { + OpenFile *in; + OpenFile *out; + unsigned char buffer[CLUSTER_SIZE]; + uint32_t n; + int source = resolve_path(fs, from); + int target = resolve_path(fs, to); + char destination[MAX_PATH]; + if (source < 0 || fs->entries[source].type != TYPE_FILE) return false; + if (target >= 0 && fs->entries[target].type == TYPE_DIR) { + snprintf(destination, sizeof(destination), "%s/%s", to, fs->entries[source].name); + } else { + safe_copy(destination, to, sizeof(destination)); + } + in = fs_open(fs, from, "r"); + out = fs_open(fs, destination, "w"); + if (!in || !out) { + if (in) fs_close(in); + if (out) fs_close(out); + return false; + } + while ((n = fs_read(in, buffer, sizeof(buffer))) > 0) { + if (fs_write(out, buffer, n) != n) { + fs_close(in); + fs_close(out); + return false; + } + } + fs_close(in); + return fs_close(out); +} + +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; + if (!join_path(host_child, sizeof(host_child), host_path, item->d_name)) continue; + if (!join_path(fs_child, sizeof(fs_child), fs_path, item->d_name)) continue; + 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_rawcat(FileSystem *fs, const char *path) { + int index = resolve_path(fs, path); + Entry *e; + unsigned char *stored; + uint32_t i; + if (index < 0 || fs->entries[index].type != TYPE_FILE) return false; + e = &fs->entries[index]; + stored = read_stored_bytes(fs, e); + if (!stored) return false; + printf("logical size: %u bytes\n", e->size); + printf("stored size: %u bytes\n", e->stored_size); + printf("compression: %s\n", e->compressed ? "RLE" : "none"); + printf("raw bytes:"); + for (i = 0; i < e->stored_size; i++) printf(" %02X", stored[i]); + printf("\n"); + if (e->compressed) { + printf("RLE meaning:\n"); + for (i = 0; i + 1 < e->stored_size; i += 2) { + unsigned char count = stored[i]; + unsigned char value = stored[i + 1]; + if (value >= 32 && value <= 126) + printf(" %02X %02X -> repeat '%c' %u time(s)\n", count, value, value, count); + else + printf(" %02X %02X -> repeat byte 0x%02X %u time(s)\n", count, value, value, count); + } + } else { + printf("meaning: bytes are stored directly, without compression\n"); + } + free(stored); + return true; +} + +bool fs_decompress_file(FileSystem *fs, const char *path) { + int index = resolve_path(fs, path); + Entry *e; + unsigned char *logical; + bool ok; + if (index < 0 || fs->entries[index].type != TYPE_FILE) return false; + e = &fs->entries[index]; + if (!e->compressed) return false; + logical = read_logical_bytes(fs, e); + if (!logical) return false; + ok = store_logical_bytes(fs, e, logical, e->size); + free(logical); + 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 create a new carrier"); + puts(" info | ls [path] | pwd | cd "); + puts(" mkdir | rmdir "); + puts(" touch | rm "); + puts(" cp | mv "); + puts(" write | cat | rawcat "); + puts(" import | export "); + puts(" importdir | exportdir "); + puts(" savecarrier | loadcarrier "); + puts(" compress | decomp | defrag | help | exit"); + puts("Use Up/Down arrows to browse command history in an interactive terminal."); +} + +static void redraw_line(const char *prompt, const char *line) { + printf("\r%s%s\033[K", prompt, line); + fflush(stdout); +} + +static bool read_command(const char *prompt, char *line, size_t size) { + static char history[MAX_HISTORY][MAX_LINE]; + static int history_count = 0; + int history_pos; + size_t len = 0; + struct termios old_mode, raw_mode; + if (!isatty(STDIN_FILENO)) { + printf("%s", prompt); + fflush(stdout); + if (!fgets(line, size, stdin)) return false; + line[strcspn(line, "\n")] = '\0'; + return true; + } + printf("%s", prompt); + fflush(stdout); + if (tcgetattr(STDIN_FILENO, &old_mode) != 0) return false; + raw_mode = old_mode; + raw_mode.c_lflag &= (tcflag_t)~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &raw_mode); + line[0] = '\0'; + history_pos = history_count; + while (1) { + int ch = getchar(); + if (ch == EOF) { tcsetattr(STDIN_FILENO, TCSANOW, &old_mode); return false; } + if (ch == '\n' || ch == '\r') { + putchar('\n'); + break; + } else if ((ch == 127 || ch == 8) && len > 0) { + line[--len] = '\0'; + redraw_line(prompt, line); + } else if (ch == 27) { + int a = getchar(); + int b = getchar(); + if (a == '[' && b == 'A' && history_count > 0 && history_pos > 0) { + history_pos--; + safe_copy(line, history[history_pos], size); + len = strlen(line); + redraw_line(prompt, line); + } else if (a == '[' && b == 'B') { + if (history_pos < history_count - 1) { + history_pos++; + safe_copy(line, history[history_pos], size); + } else { + history_pos = history_count; + line[0] = '\0'; + } + len = strlen(line); + redraw_line(prompt, line); + } + } else if (ch >= 32 && ch <= 126 && len + 1 < size) { + line[len++] = (char)ch; + line[len] = '\0'; + putchar(ch); + fflush(stdout); + } + } + tcsetattr(STDIN_FILENO, TCSANOW, &old_mode); + if (line[0]) { + if (history_count < MAX_HISTORY) { + safe_copy(history[history_count++], line, MAX_LINE); + } else { + int i; + for (i = 1; i < MAX_HISTORY; i++) safe_copy(history[i - 1], history[i], MAX_LINE); + safe_copy(history[MAX_HISTORY - 1], line, MAX_LINE); + } + } + return true; +} + +int main(int argc, char **argv) { + FileSystem fs; + char line[1024]; + if (argc != 2) { printf("Usage: %s \n", argv[0]); return 1; } + memset(&fs, 0, sizeof(fs)); + if (!fs_mount(&fs, argv[1])) printf("Carrier is not formatted yet. Use: format \n"); + print_help(); + while (1) { + char *cmd, *a, *b; + char cwd[MAX_PATH] = "/"; + if (fs.mounted) fs_pwd(&fs, cwd, sizeof(cwd)); + { + char prompt[MAX_PATH + 3]; + snprintf(prompt, sizeof(prompt), "%s> ", cwd); + if (!read_command(prompt, line, sizeof(line))) break; + } + cmd = strtok(line, " "); + if (!cmd) continue; + a = strtok(NULL, " "); + b = strtok(NULL, ""); + if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "quit") == 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, "cp") == 0 && a && b) printf(fs_copy_file(&fs, a, b) ? "ok\n" : "cp failed\n"); + else if (strcmp(cmd, "mv") == 0 && a && b) printf(fs_move(&fs, a, b) ? "ok\n" : "mv 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, "rawcat") == 0 && a) printf(fs_rawcat(&fs, a) ? "" : "rawcat 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, "decomp") == 0 && a) printf(fs_decompress_file(&fs, a) ? "ok\n" : "decomp failed\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; +} diff --git a/rgz_vlasd/Makefile b/rgz_vlasd/Makefile new file mode 100644 index 0000000..205e502 --- /dev/null +++ b/rgz_vlasd/Makefile @@ -0,0 +1,18 @@ +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) diff --git a/rgz_vlasd/TESTING.md b/rgz_vlasd/TESTING.md new file mode 100644 index 0000000..877e327 --- /dev/null +++ b/rgz_vlasd/TESTING.md @@ -0,0 +1,84 @@ +# 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. diff --git a/rgz_vlasd/report.md b/rgz_vlasd/report.md new file mode 100644 index 0000000..5c5c83a --- /dev/null +++ b/rgz_vlasd/report.md @@ -0,0 +1,204 @@ +# Отчет по дисциплине «Операционные системы» + +## РГР «Разработка простой файловой системы» + +**Выполнил:** ____________________ +**Группа:** ____________________ +**Проверил:** ____________________ +**Новосибирск, 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 ` | создание новой файловой структуры | +| `info` | сведения о носителе и свободном месте | +| `ls [path]`, `pwd`, `cd ` | просмотр и навигация по каталогам | +| `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, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска. diff --git a/rgz_vlasd/simplefs.c b/rgz_vlasd/simplefs.c new file mode 100644 index 0000000..dca7d12 --- /dev/null +++ b/rgz_vlasd/simplefs.c @@ -0,0 +1,715 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#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 create a new carrier"); + puts(" info | ls [path] | pwd | cd "); + puts(" mkdir | rmdir "); + puts(" touch | rm | rename "); + puts(" write | cat "); + puts(" import | export "); + puts(" importdir | exportdir "); + puts(" savecarrier | loadcarrier "); + puts(" compress | defrag | help | exit"); +} + +int main(int argc, char **argv) { + FileSystem fs; + char line[1024]; + if (argc != 2) { printf("Usage: %s \n", argv[0]); return 1; } + memset(&fs, 0, sizeof(fs)); + if (!fs_mount(&fs, argv[1])) printf("Carrier is not formatted yet. Use: format \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; +}