vibecode the entire rgz ig

This commit is contained in:
2026-05-18 16:59:56 +07:00
parent 0b31a50c20
commit ba6614d7a9
8 changed files with 2286 additions and 0 deletions
+18
View File
@@ -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)
+91
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}