vibecode the entire rgz ig
This commit is contained in:
@@ -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)
|
||||||
@@ -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.
|
||||||
+238
@@ -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 <KiB>` | создает новый файл-носитель указанного размера |
|
||||||
|
| Информация | `info` | показывает размер диска, размер кластера и число свободных кластеров |
|
||||||
|
| Список | `ls [path]` | выводит содержимое текущего или указанного каталога |
|
||||||
|
| Текущий каталог | `pwd` | печатает текущий абсолютный путь |
|
||||||
|
| Переход | `cd <path>` | меняет текущий каталог |
|
||||||
|
| Создание каталога | `mkdir <path>` | создает новый каталог |
|
||||||
|
| Удаление каталога | `rmdir <path>` | удаляет только пустой каталог |
|
||||||
|
| Создание файла | `touch <path>` | создает пустой файл |
|
||||||
|
| Удаление файла | `rm <path>` | удаляет файл и освобождает его кластеры |
|
||||||
|
| Копирование внутри ФС | `cp <from> <to>` | копирует файл в новый путь или в существующий каталог |
|
||||||
|
| Перемещение/переименование | `mv <from> <to>` | переносит файл или каталог; если меняется только имя, работает как переименование |
|
||||||
|
| Запись текста | `write <path> <text>` | перезаписывает файл текстовой строкой |
|
||||||
|
| Просмотр файла | `cat <path>` | выводит логическое содержимое файла |
|
||||||
|
| Просмотр хранения | `rawcat <path>` | выводит физически сохраненные байты и объясняет пары RLE |
|
||||||
|
| Импорт файла | `import <host_file> <fs_path>` | копирует файл из основной ОС в учебную ФС |
|
||||||
|
| Экспорт файла | `export <fs_path> <host_file>` | копирует файл из учебной ФС в основную ОС |
|
||||||
|
| Импорт папки | `importdir <host_dir> <fs_path>` | рекурсивно импортирует каталог |
|
||||||
|
| Экспорт папки | `exportdir <fs_path> <host_dir>` | рекурсивно экспортирует каталог |
|
||||||
|
| Экспорт носителя | `savecarrier <host_file>` | сохраняет копию всего бинарного носителя |
|
||||||
|
| Импорт носителя | `loadcarrier <host_file>` | заменяет текущий носитель ранее сохраненной копией |
|
||||||
|
| Сжатие | `compress <path>` | применяет RLE-сжатие, если оно уменьшает файл |
|
||||||
|
| Распаковка | `decomp <path>` | восстанавливает файл в несжатом виде |
|
||||||
|
| Дефрагментация | `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, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
|
||||||
+918
@@ -0,0 +1,918 @@
|
|||||||
|
#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>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <termios.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 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, "/<path-too-long>", 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 <KiB> create a new carrier");
|
||||||
|
puts(" info | ls [path] | pwd | cd <path>");
|
||||||
|
puts(" mkdir <path> | rmdir <path>");
|
||||||
|
puts(" touch <path> | rm <path>");
|
||||||
|
puts(" cp <from> <to> | mv <from> <to>");
|
||||||
|
puts(" write <path> <text> | cat <path> | rawcat <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> | decomp <path> | 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 <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));
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
@@ -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.
|
||||||
@@ -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 <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, но при этом показывает полный путь данных: от пользовательской команды до изменения битовой карты, индексного дескриптора и байтов в кластерах виртуального диска.
|
||||||
@@ -0,0 +1,715 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#define FS_MAGIC 0x53465331u
|
||||||
|
#define CLUSTER_SIZE 1024u
|
||||||
|
#define MAX_NAME 32
|
||||||
|
#define MAX_PATH 256
|
||||||
|
#define MAX_ENTRIES 128
|
||||||
|
#define MAX_FILE_CLUSTERS 128
|
||||||
|
#define MAX_OPEN_DATA (MAX_FILE_CLUSTERS * CLUSTER_SIZE)
|
||||||
|
#define TYPE_FREE 0
|
||||||
|
#define TYPE_FILE 1
|
||||||
|
#define TYPE_DIR 2
|
||||||
|
#define MODE_READ 1
|
||||||
|
#define MODE_WRITE 2
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t disk_size;
|
||||||
|
uint32_t cluster_size;
|
||||||
|
uint32_t total_clusters;
|
||||||
|
uint32_t bitmap_offset;
|
||||||
|
uint32_t bitmap_size;
|
||||||
|
uint32_t index_offset;
|
||||||
|
uint32_t index_clusters;
|
||||||
|
uint32_t data_start_cluster;
|
||||||
|
uint32_t root_index;
|
||||||
|
} SuperBlock;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[MAX_NAME];
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t compressed;
|
||||||
|
uint16_t reserved;
|
||||||
|
uint32_t parent;
|
||||||
|
uint32_t size; /* logical size visible to user */
|
||||||
|
uint32_t stored_size; /* bytes really stored on disk */
|
||||||
|
uint32_t cluster_count;
|
||||||
|
uint32_t clusters[MAX_FILE_CLUSTERS];
|
||||||
|
} Entry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FILE *disk;
|
||||||
|
char image_path[MAX_PATH];
|
||||||
|
SuperBlock sb;
|
||||||
|
unsigned char *bitmap;
|
||||||
|
Entry entries[MAX_ENTRIES];
|
||||||
|
uint32_t current_dir;
|
||||||
|
bool mounted;
|
||||||
|
} FileSystem;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FileSystem *fs;
|
||||||
|
int entry_index;
|
||||||
|
int mode;
|
||||||
|
unsigned char *data;
|
||||||
|
uint32_t size;
|
||||||
|
uint32_t capacity;
|
||||||
|
uint32_t position;
|
||||||
|
bool dirty;
|
||||||
|
} OpenFile;
|
||||||
|
|
||||||
|
static void safe_copy(char *dst, const char *src, size_t size) {
|
||||||
|
if (size == 0) return;
|
||||||
|
strncpy(dst, src, size - 1);
|
||||||
|
dst[size - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t bytes_to_clusters(uint32_t bytes) {
|
||||||
|
if (bytes == 0) return 0;
|
||||||
|
return (bytes + CLUSTER_SIZE - 1) / CLUSTER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_name(const char *name) {
|
||||||
|
return name && name[0] && strlen(name) < MAX_NAME && strchr(name, '/') == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bit_is_set(FileSystem *fs, uint32_t cluster) {
|
||||||
|
return (fs->bitmap[cluster / 8] & (1u << (cluster % 8))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bit_set(FileSystem *fs, uint32_t cluster) {
|
||||||
|
fs->bitmap[cluster / 8] |= (unsigned char)(1u << (cluster % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bit_clear(FileSystem *fs, uint32_t cluster) {
|
||||||
|
fs->bitmap[cluster / 8] &= (unsigned char)~(1u << (cluster % 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fs_sync(FileSystem *fs) {
|
||||||
|
if (!fs || !fs->mounted || !fs->disk) return false;
|
||||||
|
fseek(fs->disk, 0, SEEK_SET);
|
||||||
|
fwrite(&fs->sb, sizeof(SuperBlock), 1, fs->disk);
|
||||||
|
fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET);
|
||||||
|
fwrite(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk);
|
||||||
|
fseek(fs->disk, fs->sb.index_offset, SEEK_SET);
|
||||||
|
fwrite(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk);
|
||||||
|
fflush(fs->disk);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int alloc_cluster(FileSystem *fs) {
|
||||||
|
uint32_t i;
|
||||||
|
for (i = fs->sb.data_start_cluster; i < fs->sb.total_clusters; i++) {
|
||||||
|
if (!bit_is_set(fs, i)) {
|
||||||
|
bit_set(fs, i);
|
||||||
|
return (int)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_entry_clusters(FileSystem *fs, Entry *entry) {
|
||||||
|
uint32_t i;
|
||||||
|
for (i = 0; i < entry->cluster_count; i++) {
|
||||||
|
if (entry->clusters[i] < fs->sb.total_clusters) bit_clear(fs, entry->clusters[i]);
|
||||||
|
}
|
||||||
|
entry->cluster_count = 0;
|
||||||
|
entry->stored_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_child(FileSystem *fs, uint32_t parent, const char *name) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
if (fs->entries[i].type != TYPE_FREE && fs->entries[i].parent == parent &&
|
||||||
|
strcmp(fs->entries[i].name, name) == 0) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int free_entry_index(FileSystem *fs) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) if (fs->entries[i].type == TYPE_FREE) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int resolve_path(FileSystem *fs, const char *path) {
|
||||||
|
char copy[MAX_PATH];
|
||||||
|
char *part;
|
||||||
|
uint32_t current;
|
||||||
|
if (!path || !path[0]) return -1;
|
||||||
|
safe_copy(copy, path, sizeof(copy));
|
||||||
|
current = (copy[0] == '/') ? fs->sb.root_index : fs->current_dir;
|
||||||
|
part = strtok(copy, "/");
|
||||||
|
if (!part && path[0] == '/') return (int)fs->sb.root_index;
|
||||||
|
while (part) {
|
||||||
|
if (strcmp(part, ".") == 0) {
|
||||||
|
/* stay */
|
||||||
|
} else if (strcmp(part, "..") == 0) {
|
||||||
|
current = fs->entries[current].parent;
|
||||||
|
} else {
|
||||||
|
int next = find_child(fs, current, part);
|
||||||
|
if (next < 0) return -1;
|
||||||
|
current = (uint32_t)next;
|
||||||
|
}
|
||||||
|
part = strtok(NULL, "/");
|
||||||
|
}
|
||||||
|
return (int)current;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool split_parent_name(FileSystem *fs, const char *path, int *parent, char *name) {
|
||||||
|
char copy[MAX_PATH];
|
||||||
|
char *slash;
|
||||||
|
if (!path || !path[0]) return false;
|
||||||
|
safe_copy(copy, path, sizeof(copy));
|
||||||
|
slash = strrchr(copy, '/');
|
||||||
|
if (!slash) {
|
||||||
|
*parent = (int)fs->current_dir;
|
||||||
|
safe_copy(name, copy, MAX_NAME);
|
||||||
|
} else if (slash == copy) {
|
||||||
|
*parent = (int)fs->sb.root_index;
|
||||||
|
safe_copy(name, slash + 1, MAX_NAME);
|
||||||
|
} else {
|
||||||
|
*slash = '\0';
|
||||||
|
*parent = resolve_path(fs, copy);
|
||||||
|
safe_copy(name, slash + 1, MAX_NAME);
|
||||||
|
}
|
||||||
|
return *parent >= 0 && fs->entries[*parent].type == TYPE_DIR && valid_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool write_stored_bytes(FileSystem *fs, Entry *entry, const unsigned char *data, uint32_t size) {
|
||||||
|
uint32_t needed = bytes_to_clusters(size);
|
||||||
|
uint32_t i, written = 0;
|
||||||
|
if (needed > MAX_FILE_CLUSTERS) return false;
|
||||||
|
free_entry_clusters(fs, entry);
|
||||||
|
entry->cluster_count = needed;
|
||||||
|
entry->stored_size = size;
|
||||||
|
for (i = 0; i < needed; i++) {
|
||||||
|
int c = alloc_cluster(fs);
|
||||||
|
if (c < 0) {
|
||||||
|
free_entry_clusters(fs, entry);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry->clusters[i] = (uint32_t)c;
|
||||||
|
}
|
||||||
|
for (i = 0; i < needed; i++) {
|
||||||
|
uint32_t chunk = size - written;
|
||||||
|
if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE;
|
||||||
|
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
||||||
|
fwrite(data + written, 1, chunk, fs->disk);
|
||||||
|
if (chunk < CLUSTER_SIZE) {
|
||||||
|
unsigned char zero[CLUSTER_SIZE] = {0};
|
||||||
|
fwrite(zero, 1, CLUSTER_SIZE - chunk, fs->disk);
|
||||||
|
}
|
||||||
|
written += chunk;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *read_stored_bytes(FileSystem *fs, Entry *entry) {
|
||||||
|
unsigned char *data;
|
||||||
|
uint32_t i, read_count = 0;
|
||||||
|
if (entry->stored_size == 0) return calloc(1, 1);
|
||||||
|
data = malloc(entry->stored_size);
|
||||||
|
if (!data) return NULL;
|
||||||
|
for (i = 0; i < entry->cluster_count; i++) {
|
||||||
|
uint32_t chunk = entry->stored_size - read_count;
|
||||||
|
if (chunk > CLUSTER_SIZE) chunk = CLUSTER_SIZE;
|
||||||
|
fseek(fs->disk, (long)(entry->clusters[i] * CLUSTER_SIZE), SEEK_SET);
|
||||||
|
fread(data + read_count, 1, chunk, fs->disk);
|
||||||
|
read_count += chunk;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *rle_compress(const unsigned char *src, uint32_t size, uint32_t *out_size) {
|
||||||
|
unsigned char *out;
|
||||||
|
uint32_t i = 0, j = 0;
|
||||||
|
out = malloc(size * 2 + 2);
|
||||||
|
if (!out) return NULL;
|
||||||
|
while (i < size) {
|
||||||
|
unsigned char value = src[i];
|
||||||
|
uint32_t count = 1;
|
||||||
|
while (i + count < size && src[i + count] == value && count < 255) count++;
|
||||||
|
out[j++] = (unsigned char)count;
|
||||||
|
out[j++] = value;
|
||||||
|
i += count;
|
||||||
|
}
|
||||||
|
*out_size = j;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *rle_decompress(const unsigned char *src, uint32_t stored_size, uint32_t logical_size) {
|
||||||
|
unsigned char *out;
|
||||||
|
uint32_t i = 0, j = 0, k;
|
||||||
|
out = malloc(logical_size + 1);
|
||||||
|
if (!out) return NULL;
|
||||||
|
while (i + 1 < stored_size && j < logical_size) {
|
||||||
|
unsigned char count = src[i++];
|
||||||
|
unsigned char value = src[i++];
|
||||||
|
for (k = 0; k < count && j < logical_size; k++) out[j++] = value;
|
||||||
|
}
|
||||||
|
if (j != logical_size) {
|
||||||
|
free(out);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char *read_logical_bytes(FileSystem *fs, Entry *entry) {
|
||||||
|
unsigned char *stored = read_stored_bytes(fs, entry);
|
||||||
|
unsigned char *logical;
|
||||||
|
if (!stored) return NULL;
|
||||||
|
if (!entry->compressed) return stored;
|
||||||
|
logical = rle_decompress(stored, entry->stored_size, entry->size);
|
||||||
|
free(stored);
|
||||||
|
return logical;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool store_logical_bytes(FileSystem *fs, Entry *entry, const unsigned char *data, uint32_t size) {
|
||||||
|
entry->compressed = 0;
|
||||||
|
entry->size = size;
|
||||||
|
return write_stored_bytes(fs, entry, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_format(FileSystem *fs, const char *image_path, uint32_t size_kb) {
|
||||||
|
uint32_t total_bytes = size_kb * 1024u;
|
||||||
|
uint32_t bitmap_clusters, i;
|
||||||
|
if (total_bytes < 128u * 1024u) return false;
|
||||||
|
memset(fs, 0, sizeof(*fs));
|
||||||
|
safe_copy(fs->image_path, image_path, sizeof(fs->image_path));
|
||||||
|
fs->disk = fopen(image_path, "wb+");
|
||||||
|
if (!fs->disk) return false;
|
||||||
|
fseek(fs->disk, (long)total_bytes - 1, SEEK_SET);
|
||||||
|
fputc(0, fs->disk);
|
||||||
|
fs->sb.magic = FS_MAGIC;
|
||||||
|
fs->sb.disk_size = total_bytes;
|
||||||
|
fs->sb.cluster_size = CLUSTER_SIZE;
|
||||||
|
fs->sb.total_clusters = total_bytes / CLUSTER_SIZE;
|
||||||
|
fs->sb.bitmap_offset = CLUSTER_SIZE;
|
||||||
|
fs->sb.bitmap_size = (fs->sb.total_clusters + 7) / 8;
|
||||||
|
bitmap_clusters = bytes_to_clusters(fs->sb.bitmap_size);
|
||||||
|
fs->sb.index_offset = (1 + bitmap_clusters) * CLUSTER_SIZE;
|
||||||
|
fs->sb.index_clusters = bytes_to_clusters((uint32_t)(sizeof(Entry) * MAX_ENTRIES));
|
||||||
|
fs->sb.data_start_cluster = 1 + bitmap_clusters + fs->sb.index_clusters;
|
||||||
|
fs->sb.root_index = 0;
|
||||||
|
if (fs->sb.data_start_cluster >= fs->sb.total_clusters) { fclose(fs->disk); return false; }
|
||||||
|
fs->bitmap = calloc(fs->sb.bitmap_size, 1);
|
||||||
|
if (!fs->bitmap) { fclose(fs->disk); return false; }
|
||||||
|
for (i = 0; i < fs->sb.data_start_cluster; i++) bit_set(fs, i);
|
||||||
|
memset(fs->entries, 0, sizeof(fs->entries));
|
||||||
|
safe_copy(fs->entries[0].name, "/", MAX_NAME);
|
||||||
|
fs->entries[0].type = TYPE_DIR;
|
||||||
|
fs->entries[0].parent = 0;
|
||||||
|
fs->current_dir = 0;
|
||||||
|
fs->mounted = true;
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_mount(FileSystem *fs, const char *image_path) {
|
||||||
|
memset(fs, 0, sizeof(*fs));
|
||||||
|
safe_copy(fs->image_path, image_path, sizeof(fs->image_path));
|
||||||
|
fs->disk = fopen(image_path, "rb+");
|
||||||
|
if (!fs->disk) return false;
|
||||||
|
fread(&fs->sb, sizeof(SuperBlock), 1, fs->disk);
|
||||||
|
if (fs->sb.magic != FS_MAGIC || fs->sb.cluster_size != CLUSTER_SIZE) { fclose(fs->disk); return false; }
|
||||||
|
fs->bitmap = malloc(fs->sb.bitmap_size);
|
||||||
|
if (!fs->bitmap) { fclose(fs->disk); return false; }
|
||||||
|
fseek(fs->disk, fs->sb.bitmap_offset, SEEK_SET);
|
||||||
|
fread(fs->bitmap, 1, fs->sb.bitmap_size, fs->disk);
|
||||||
|
fseek(fs->disk, fs->sb.index_offset, SEEK_SET);
|
||||||
|
fread(fs->entries, sizeof(Entry), MAX_ENTRIES, fs->disk);
|
||||||
|
fs->current_dir = fs->sb.root_index;
|
||||||
|
fs->mounted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs_unmount(FileSystem *fs) {
|
||||||
|
if (!fs || !fs->mounted) return;
|
||||||
|
fs_sync(fs);
|
||||||
|
fclose(fs->disk);
|
||||||
|
free(fs->bitmap);
|
||||||
|
fs->disk = NULL;
|
||||||
|
fs->bitmap = NULL;
|
||||||
|
fs->mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_create_dir(FileSystem *fs, const char *path) {
|
||||||
|
int parent, index;
|
||||||
|
char name[MAX_NAME];
|
||||||
|
if (!split_parent_name(fs, path, &parent, name) || find_child(fs, parent, name) >= 0) return false;
|
||||||
|
index = free_entry_index(fs);
|
||||||
|
if (index < 0) return false;
|
||||||
|
memset(&fs->entries[index], 0, sizeof(Entry));
|
||||||
|
safe_copy(fs->entries[index].name, name, MAX_NAME);
|
||||||
|
fs->entries[index].type = TYPE_DIR;
|
||||||
|
fs->entries[index].parent = (uint32_t)parent;
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_create_file(FileSystem *fs, const char *path) {
|
||||||
|
int parent, index;
|
||||||
|
char name[MAX_NAME];
|
||||||
|
if (!split_parent_name(fs, path, &parent, name) || find_child(fs, parent, name) >= 0) return false;
|
||||||
|
index = free_entry_index(fs);
|
||||||
|
if (index < 0) return false;
|
||||||
|
memset(&fs->entries[index], 0, sizeof(Entry));
|
||||||
|
safe_copy(fs->entries[index].name, name, MAX_NAME);
|
||||||
|
fs->entries[index].type = TYPE_FILE;
|
||||||
|
fs->entries[index].parent = (uint32_t)parent;
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_remove(FileSystem *fs, const char *path) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
if (index <= 0 || fs->entries[index].type != TYPE_FILE) return false;
|
||||||
|
free_entry_clusters(fs, &fs->entries[index]);
|
||||||
|
memset(&fs->entries[index], 0, sizeof(Entry));
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_remove_dir(FileSystem *fs, const char *path) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
int i;
|
||||||
|
if (index <= 0 || fs->entries[index].type != TYPE_DIR) return false;
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) if (fs->entries[i].type != TYPE_FREE && fs->entries[i].parent == (uint32_t)index) return false;
|
||||||
|
memset(&fs->entries[index], 0, sizeof(Entry));
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_rename(FileSystem *fs, const char *path, const char *new_name) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
Entry *e;
|
||||||
|
if (index <= 0 || !valid_name(new_name)) return false;
|
||||||
|
e = &fs->entries[index];
|
||||||
|
if (find_child(fs, e->parent, new_name) >= 0) return false;
|
||||||
|
safe_copy(e->name, new_name, MAX_NAME);
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_cd(FileSystem *fs, const char *path) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
if (index < 0 || fs->entries[index].type != TYPE_DIR) return false;
|
||||||
|
fs->current_dir = (uint32_t)index;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs_pwd(FileSystem *fs, char *out, size_t out_size) {
|
||||||
|
char temp[MAX_PATH] = "";
|
||||||
|
uint32_t cur = fs->current_dir;
|
||||||
|
if (cur == fs->sb.root_index) { safe_copy(out, "/", out_size); return; }
|
||||||
|
while (cur != fs->sb.root_index) {
|
||||||
|
char next[MAX_PATH];
|
||||||
|
snprintf(next, sizeof(next), "/%s%s", fs->entries[cur].name, temp);
|
||||||
|
safe_copy(temp, next, sizeof(temp));
|
||||||
|
cur = fs->entries[cur].parent;
|
||||||
|
}
|
||||||
|
safe_copy(out, temp, out_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs_list(FileSystem *fs, const char *path) {
|
||||||
|
int dir = path ? resolve_path(fs, path) : (int)fs->current_dir;
|
||||||
|
int i;
|
||||||
|
if (dir < 0 || fs->entries[dir].type != TYPE_DIR) { printf("Directory not found.\n"); return; }
|
||||||
|
printf("%-32s %-8s %-10s %-10s\n", "name", "type", "size", "stored");
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
Entry *e = &fs->entries[i];
|
||||||
|
if (e->type != TYPE_FREE && e->parent == (uint32_t)dir && i != dir) {
|
||||||
|
printf("%-32s %-8s %-10u %-10u%s\n", e->name, e->type == TYPE_DIR ? "dir" : "file", e->size, e->stored_size, e->compressed ? " compressed" : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenFile *fs_open(FileSystem *fs, const char *path, const char *mode) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
OpenFile *f;
|
||||||
|
if (index < 0 && strchr(mode, 'w')) {
|
||||||
|
if (!fs_create_file(fs, path)) return NULL;
|
||||||
|
index = resolve_path(fs, path);
|
||||||
|
}
|
||||||
|
if (index < 0 || fs->entries[index].type != TYPE_FILE) return NULL;
|
||||||
|
f = calloc(1, sizeof(OpenFile));
|
||||||
|
if (!f) return NULL;
|
||||||
|
f->fs = fs;
|
||||||
|
f->entry_index = index;
|
||||||
|
f->mode = strchr(mode, 'w') ? MODE_WRITE : MODE_READ;
|
||||||
|
f->capacity = MAX_OPEN_DATA;
|
||||||
|
f->data = calloc(f->capacity, 1);
|
||||||
|
if (!f->data) { free(f); return NULL; }
|
||||||
|
if (!strchr(mode, 'w')) {
|
||||||
|
unsigned char *existing = read_logical_bytes(fs, &fs->entries[index]);
|
||||||
|
if (!existing && fs->entries[index].size > 0) { free(f->data); free(f); return NULL; }
|
||||||
|
memcpy(f->data, existing, fs->entries[index].size);
|
||||||
|
f->size = fs->entries[index].size;
|
||||||
|
free(existing);
|
||||||
|
} else {
|
||||||
|
f->dirty = true;
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fs_seek(OpenFile *f, uint32_t position) {
|
||||||
|
if (!f || position > f->size) return -1;
|
||||||
|
f->position = position;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t fs_read(OpenFile *f, unsigned char *buffer, uint32_t count) {
|
||||||
|
uint32_t available;
|
||||||
|
if (!f || !buffer) return 0;
|
||||||
|
available = f->size - f->position;
|
||||||
|
if (count > available) count = available;
|
||||||
|
memcpy(buffer, f->data + f->position, count);
|
||||||
|
f->position += count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t fs_write(OpenFile *f, const unsigned char *buffer, uint32_t count) {
|
||||||
|
if (!f || !buffer || f->mode != MODE_WRITE || f->position + count > f->capacity) return 0;
|
||||||
|
memcpy(f->data + f->position, buffer, count);
|
||||||
|
f->position += count;
|
||||||
|
if (f->position > f->size) f->size = f->position;
|
||||||
|
f->dirty = true;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_close(OpenFile *f) {
|
||||||
|
bool ok = true;
|
||||||
|
if (!f) return false;
|
||||||
|
if (f->dirty) {
|
||||||
|
ok = store_logical_bytes(f->fs, &f->fs->entries[f->entry_index], f->data, f->size);
|
||||||
|
fs_sync(f->fs);
|
||||||
|
}
|
||||||
|
free(f->data);
|
||||||
|
free(f);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_write_text(FileSystem *fs, const char *path, const char *text) {
|
||||||
|
OpenFile *f = fs_open(fs, path, "w");
|
||||||
|
bool ok;
|
||||||
|
if (!f) return false;
|
||||||
|
ok = fs_write(f, (const unsigned char *)text, (uint32_t)strlen(text)) == strlen(text);
|
||||||
|
return fs_close(f) && ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_print_file(FileSystem *fs, const char *path) {
|
||||||
|
OpenFile *f = fs_open(fs, path, "r");
|
||||||
|
unsigned char buffer[CLUSTER_SIZE + 1];
|
||||||
|
uint32_t n;
|
||||||
|
if (!f) return false;
|
||||||
|
while ((n = fs_read(f, buffer, CLUSTER_SIZE)) > 0) {
|
||||||
|
buffer[n] = '\0';
|
||||||
|
fwrite(buffer, 1, n, stdout);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
return fs_close(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_import_file(FileSystem *fs, const char *host_path, const char *fs_path) {
|
||||||
|
FILE *in = fopen(host_path, "rb");
|
||||||
|
OpenFile *out;
|
||||||
|
unsigned char buffer[CLUSTER_SIZE];
|
||||||
|
size_t n;
|
||||||
|
if (!in) return false;
|
||||||
|
out = fs_open(fs, fs_path, "w");
|
||||||
|
if (!out) { fclose(in); return false; }
|
||||||
|
while ((n = fread(buffer, 1, sizeof(buffer), in)) > 0) {
|
||||||
|
if (fs_write(out, buffer, (uint32_t)n) != n) { fclose(in); fs_close(out); return false; }
|
||||||
|
}
|
||||||
|
fclose(in);
|
||||||
|
return fs_close(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_export_file(FileSystem *fs, const char *fs_path, const char *host_path) {
|
||||||
|
FILE *out = fopen(host_path, "wb");
|
||||||
|
OpenFile *in;
|
||||||
|
unsigned char buffer[CLUSTER_SIZE];
|
||||||
|
uint32_t n;
|
||||||
|
if (!out) return false;
|
||||||
|
in = fs_open(fs, fs_path, "r");
|
||||||
|
if (!in) { fclose(out); return false; }
|
||||||
|
while ((n = fs_read(in, buffer, sizeof(buffer))) > 0) fwrite(buffer, 1, n, out);
|
||||||
|
fclose(out);
|
||||||
|
return fs_close(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fs_import_dir(FileSystem *fs, const char *host_path, const char *fs_path) {
|
||||||
|
DIR *dir = opendir(host_path);
|
||||||
|
struct dirent *item;
|
||||||
|
if (!dir) return false;
|
||||||
|
if (resolve_path(fs, fs_path) < 0 && !fs_create_dir(fs, fs_path)) { closedir(dir); return false; }
|
||||||
|
while ((item = readdir(dir)) != NULL) {
|
||||||
|
char host_child[MAX_PATH], fs_child[MAX_PATH];
|
||||||
|
struct stat st;
|
||||||
|
if (strcmp(item->d_name, ".") == 0 || strcmp(item->d_name, "..") == 0) continue;
|
||||||
|
snprintf(host_child, sizeof(host_child), "%s/%s", host_path, item->d_name);
|
||||||
|
snprintf(fs_child, sizeof(fs_child), "%s/%s", fs_path, item->d_name);
|
||||||
|
if (stat(host_child, &st) != 0) continue;
|
||||||
|
if (S_ISDIR(st.st_mode)) fs_import_dir(fs, host_child, fs_child);
|
||||||
|
else fs_import_file(fs, host_child, fs_child);
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool fs_export_dir(FileSystem *fs, const char *fs_path, const char *host_path) {
|
||||||
|
int dir = resolve_path(fs, fs_path);
|
||||||
|
int i;
|
||||||
|
if (dir < 0 || fs->entries[dir].type != TYPE_DIR) return false;
|
||||||
|
mkdir(host_path, 0777);
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
Entry *e = &fs->entries[i];
|
||||||
|
if (e->type != TYPE_FREE && e->parent == (uint32_t)dir && i != dir) {
|
||||||
|
char child_fs[MAX_PATH], child_host[MAX_PATH];
|
||||||
|
snprintf(child_fs, sizeof(child_fs), "%s/%s", fs_path, e->name);
|
||||||
|
snprintf(child_host, sizeof(child_host), "%s/%s", host_path, e->name);
|
||||||
|
if (e->type == TYPE_DIR) fs_export_dir(fs, child_fs, child_host);
|
||||||
|
else fs_export_file(fs, child_fs, child_host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_compress_file(FileSystem *fs, const char *path) {
|
||||||
|
int index = resolve_path(fs, path);
|
||||||
|
Entry *e;
|
||||||
|
unsigned char *logical, *compressed;
|
||||||
|
uint32_t compressed_size;
|
||||||
|
bool ok;
|
||||||
|
if (index < 0 || fs->entries[index].type != TYPE_FILE) return false;
|
||||||
|
e = &fs->entries[index];
|
||||||
|
logical = read_logical_bytes(fs, e);
|
||||||
|
if (!logical) return false;
|
||||||
|
compressed = rle_compress(logical, e->size, &compressed_size);
|
||||||
|
if (!compressed || compressed_size >= e->size) { free(logical); free(compressed); return false; }
|
||||||
|
e->compressed = 1;
|
||||||
|
ok = write_stored_bytes(fs, e, compressed, compressed_size);
|
||||||
|
e->compressed = ok ? 1 : 0;
|
||||||
|
free(logical);
|
||||||
|
free(compressed);
|
||||||
|
fs_sync(fs);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fs_defrag(FileSystem *fs) {
|
||||||
|
unsigned char *copies[MAX_ENTRIES] = {0};
|
||||||
|
uint32_t stored_sizes[MAX_ENTRIES] = {0};
|
||||||
|
uint32_t i, c;
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
if (fs->entries[i].type == TYPE_FILE && fs->entries[i].stored_size > 0) {
|
||||||
|
copies[i] = read_stored_bytes(fs, &fs->entries[i]);
|
||||||
|
if (!copies[i]) return false;
|
||||||
|
stored_sizes[i] = fs->entries[i].stored_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(fs->bitmap, 0, fs->sb.bitmap_size);
|
||||||
|
for (c = 0; c < fs->sb.data_start_cluster; c++) bit_set(fs, c);
|
||||||
|
for (i = 0; i < MAX_ENTRIES; i++) {
|
||||||
|
if (fs->entries[i].type == TYPE_FILE) {
|
||||||
|
fs->entries[i].cluster_count = 0;
|
||||||
|
if (!write_stored_bytes(fs, &fs->entries[i], copies[i], stored_sizes[i])) return false;
|
||||||
|
}
|
||||||
|
free(copies[i]);
|
||||||
|
}
|
||||||
|
fs_sync(fs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fs_info(FileSystem *fs) {
|
||||||
|
uint32_t i, free_clusters = 0;
|
||||||
|
for (i = 0; i < fs->sb.total_clusters; i++) if (!bit_is_set(fs, i)) free_clusters++;
|
||||||
|
printf("image: %s\n", fs->image_path);
|
||||||
|
printf("disk size: %u KiB\n", fs->sb.disk_size / 1024);
|
||||||
|
printf("cluster size: %u bytes\n", fs->sb.cluster_size);
|
||||||
|
printf("clusters: %u total, %u free, %u occupied\n", fs->sb.total_clusters, free_clusters, fs->sb.total_clusters - free_clusters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copy_file(const char *from, const char *to) {
|
||||||
|
FILE *in = fopen(from, "rb");
|
||||||
|
FILE *out;
|
||||||
|
unsigned char buffer[4096];
|
||||||
|
size_t n;
|
||||||
|
if (!in) return false;
|
||||||
|
out = fopen(to, "wb");
|
||||||
|
if (!out) { fclose(in); return false; }
|
||||||
|
while ((n = fread(buffer, 1, sizeof(buffer), in)) > 0) fwrite(buffer, 1, n, out);
|
||||||
|
fclose(in);
|
||||||
|
fclose(out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_help(void) {
|
||||||
|
puts("Commands:");
|
||||||
|
puts(" format <KiB> create a new carrier");
|
||||||
|
puts(" info | ls [path] | pwd | cd <path>");
|
||||||
|
puts(" mkdir <path> | rmdir <path>");
|
||||||
|
puts(" touch <path> | rm <path> | rename <path> <new_name>");
|
||||||
|
puts(" write <path> <text> | cat <path>");
|
||||||
|
puts(" import <host_file> <fs_path> | export <fs_path> <host_file>");
|
||||||
|
puts(" importdir <host_dir> <fs_path> | exportdir <fs_path> <host_dir>");
|
||||||
|
puts(" savecarrier <host_file> | loadcarrier <host_file>");
|
||||||
|
puts(" compress <path> | defrag | help | exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
FileSystem fs;
|
||||||
|
char line[1024];
|
||||||
|
if (argc != 2) { printf("Usage: %s <carrier_file>\n", argv[0]); return 1; }
|
||||||
|
memset(&fs, 0, sizeof(fs));
|
||||||
|
if (!fs_mount(&fs, argv[1])) printf("Carrier is not formatted yet. Use: format <KiB>\n");
|
||||||
|
print_help();
|
||||||
|
while (1) {
|
||||||
|
char *cmd, *a, *b;
|
||||||
|
char cwd[MAX_PATH] = "/";
|
||||||
|
if (fs.mounted) fs_pwd(&fs, cwd, sizeof(cwd));
|
||||||
|
printf("%s> ", cwd);
|
||||||
|
if (!fgets(line, sizeof(line), stdin)) break;
|
||||||
|
line[strcspn(line, "\n")] = '\0';
|
||||||
|
cmd = strtok(line, " ");
|
||||||
|
if (!cmd) continue;
|
||||||
|
a = strtok(NULL, " ");
|
||||||
|
b = strtok(NULL, "");
|
||||||
|
if (strcmp(cmd, "exit") == 0) break;
|
||||||
|
if (strcmp(cmd, "help") == 0) print_help();
|
||||||
|
else if (strcmp(cmd, "format") == 0 && a) {
|
||||||
|
if (fs.mounted) fs_unmount(&fs);
|
||||||
|
printf(fs_format(&fs, argv[1], (uint32_t)atoi(a)) ? "formatted\n" : "format failed\n");
|
||||||
|
} else if (!fs.mounted) printf("No mounted filesystem. Format or load a carrier first.\n");
|
||||||
|
else if (strcmp(cmd, "info") == 0) fs_info(&fs);
|
||||||
|
else if (strcmp(cmd, "ls") == 0) fs_list(&fs, a);
|
||||||
|
else if (strcmp(cmd, "pwd") == 0) printf("%s\n", cwd);
|
||||||
|
else if (strcmp(cmd, "cd") == 0 && a) printf(fs_cd(&fs, a) ? "ok\n" : "directory not found\n");
|
||||||
|
else if (strcmp(cmd, "mkdir") == 0 && a) printf(fs_create_dir(&fs, a) ? "ok\n" : "mkdir failed\n");
|
||||||
|
else if (strcmp(cmd, "rmdir") == 0 && a) printf(fs_remove_dir(&fs, a) ? "ok\n" : "rmdir failed\n");
|
||||||
|
else if (strcmp(cmd, "touch") == 0 && a) printf(fs_create_file(&fs, a) ? "ok\n" : "touch failed\n");
|
||||||
|
else if (strcmp(cmd, "rm") == 0 && a) printf(fs_remove(&fs, a) ? "ok\n" : "rm failed\n");
|
||||||
|
else if (strcmp(cmd, "rename") == 0 && a && b) printf(fs_rename(&fs, a, b) ? "ok\n" : "rename failed\n");
|
||||||
|
else if (strcmp(cmd, "write") == 0 && a && b) printf(fs_write_text(&fs, a, b) ? "ok\n" : "write failed\n");
|
||||||
|
else if (strcmp(cmd, "cat") == 0 && a) printf(fs_print_file(&fs, a) ? "" : "cat failed\n");
|
||||||
|
else if (strcmp(cmd, "import") == 0 && a && b) printf(fs_import_file(&fs, a, b) ? "ok\n" : "import failed\n");
|
||||||
|
else if (strcmp(cmd, "export") == 0 && a && b) printf(fs_export_file(&fs, a, b) ? "ok\n" : "export failed\n");
|
||||||
|
else if (strcmp(cmd, "importdir") == 0 && a && b) printf(fs_import_dir(&fs, a, b) ? "ok\n" : "importdir failed\n");
|
||||||
|
else if (strcmp(cmd, "exportdir") == 0 && a && b) printf(fs_export_dir(&fs, a, b) ? "ok\n" : "exportdir failed\n");
|
||||||
|
else if (strcmp(cmd, "savecarrier") == 0 && a) { fs_sync(&fs); printf(copy_file(fs.image_path, a) ? "ok\n" : "copy failed\n"); }
|
||||||
|
else if (strcmp(cmd, "loadcarrier") == 0 && a) { fs_unmount(&fs); printf(copy_file(a, argv[1]) && fs_mount(&fs, argv[1]) ? "ok\n" : "load failed\n"); }
|
||||||
|
else if (strcmp(cmd, "compress") == 0 && a) printf(fs_compress_file(&fs, a) ? "ok\n" : "compression did not reduce file\n");
|
||||||
|
else if (strcmp(cmd, "defrag") == 0) printf(fs_defrag(&fs) ? "ok\n" : "defrag failed\n");
|
||||||
|
else printf("Unknown or incomplete command. Type help.\n");
|
||||||
|
}
|
||||||
|
if (fs.mounted) fs_unmount(&fs);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user