Formato on-disk do ChrisFS¶
Sobre este capítulo Especificações e referências primárias
Neste capítulo
- Formato on-disk do ChrisFS
- Status
- Unidade fundamental
- Magic e versões
- Endianness
- Layout geral do volume
- Geometria legada de 512 MiB
- Geometria dinâmica da v5
- Compatibilidade v4/v3
- Localização do superblock
- Layout do superblock
- Checksum do superblock
- Validação de geometria
- Inode table
- Tipos de inode
- Layout do inode
- Checksum do inode
- Root inode
- Allocation bitmap
- Data blocks
- Direct pointers
- Single indirect
- Double indirect
- Triple indirect
- Limite da API whole-file
- Limite de write_at
- Formato de dirent
- Densidade de directory
- Slots vazios
- Names e paths
- Sintaxe de path
- Limite de directory blocks
- Permission bits
- Compatibilidade de permissions
- UID/GID
- Modification time
- Journal
- Header do journal
- Records do journal
- Semântica do journal
- Política para file data
- Clean flag e generation
- Mount
- Format
- Fsck
- Modelo de resultado do fsck
- Error codes
- Cache não é estado on-disk
- Histórico de migração
- Evidência da v5
- Limitações atuais
- Regra de versionamento
- Resumo de conformidade
- Nota de revisão
Status¶
ChrisFS é o filesystem nativo do ChrisOS.
Este documento especifica o ChrisFS versão 5 implementado na revisão e05a17fd76333114a3fb5c2452f38ca747d4ac56.
O mounter atual também aceita superblocks versão 4 e versão de compatibilidade 3.
Versões 1 e 2 não são montadas diretamente pelo decoder atual; ferramentas separadas contêm readers para esses layouts históricos.
Unidade fundamental¶
O filesystem usa logical sector fixo de:
512 bytes
A geometria default continua sendo:
1.048.576 sectors
536.870.912 bytes
512 MiB
A versão 5 não fica restrita exatamente a esse tamanho.
O formatter deriva a geometria de bitmap/data a partir do sector_count real do block device.
Magic e versões¶
Constantes:
CFS_MAGIC = 0x31534643
CFS_VERSION = 5
CFS_VERSION_V4 = 4
CFS_VERSION_COMPAT = 3
Em little-endian, os bytes do magic são:
C F S 1
O identificador permanece CFS1 embora o formato tenha evoluído para versões posteriores.
Endianness¶
Todos os integer fields on-disk são codificados por helpers little-endian explícitos.
Logo ChrisFS possui formato de disco explicitamente little-endian e não depende de layout nativo de struct.
Isso vale para superblock, inode, dirent, pointer blocks e journal metadata.
Layout geral do volume¶
As regiões lógicas são:
LBA 0 superblock
bitmap_lba... allocation bitmap
inode_lba... inode table
journal_lba... metadata journal
data_lba... data/pointer blocks
A versão 5 armazena posições e comprimentos no superblock.
O formatter atual normalmente posiciona as regiões de forma contígua nessa ordem.
Geometria legada de 512 MiB¶
Para a image tradicional de 512 MiB:
superblock LBA 0
bitmap LBA 1..256
inode table LBA 257..768
journal LBA 769..832
data LBA 833..
Constantes:
CFS_BITMAP_LBA = 1
CFS_BITMAP_SECTORS = 256
CFS_INODE_LBA = 257
CFS_INODE_SECTORS = 512
CFS_JOURNAL_LBA = 769
CFS_JOURNAL_SECTORS = 64
CFS_DATA_LBA = 833
Com 1.048.576 sectors restam:
1.047.743 data sectors
ou 536.444.416 bytes de address space de data region.
Geometria dinâmica da v5¶
A v5 calcula o tamanho do bitmap a partir do sector count real.
A inode table continua fixa em 512 sectors e o journal em 64.
O bitmap cresce até possuir ao menos um bit para cada data sector.
Se (B) é bitmap sectors e (D) é data sectors:
[ B cdot 512 cdot 8 ge D ]
e:
[ 1 + B + 512 + 64 + D = total_sectors ]
O formatter itera até convergir.
Isso permite usar sectors além do antigo limite de 512 MiB.
Compatibilidade v4/v3¶
Para versões 4 e 3, o decoder exige a geometria histórica fixa.
Ele valida:
- total sectors;
- bitmap location/size;
- inode location/count/size;
- data location/size;
- root inode;
- journal location/size.
Assim uma image antiga não é reinterpretada silenciosamente sob nova geometria.
Os tests alteram um superblock v5 de 512 MiB para v4 e confirmam mount, fsck, read e write.
Localização do superblock¶
O superblock ocupa:
LBA 0
Os fields significativos ocupam os primeiros 64 bytes.
O restante do sector de 512 bytes é zerado pelo encoder atual.
Layout do superblock¶
| Offset | Tamanho | Campo |
|---|---|---|
| 0 | 4 | magic |
| 4 | 2 | version |
| 6 | 2 | sector size |
| 8 | 4 | total sectors |
| 12 | 4 | bitmap LBA |
| 16 | 4 | bitmap sectors |
| 20 | 4 | inode-table LBA |
| 24 | 4 | inode count |
| 28 | 4 | inode size |
| 32 | 4 | data LBA |
| 36 | 4 | data sectors |
| 40 | 4 | root inode |
| 44 | 4 | clean flag |
| 48 | 4 | generation |
| 52 | 4 | journal LBA |
| 56 | 4 | journal sectors |
| 60 | 4 | checksum |
Checksum cobre bytes 0 a 59.
Checksum do superblock¶
ChrisFS usa checksum estilo FNV-1a de 32 bits:
initial = 2166136261
hash ^= byte
hash *= 16777619
Formalmente:
[ checksum = FNV1a32(bytes[0..59]) ]
Mismatch causa falha de decode.
Validação de geometria¶
Para v5 o decoder verifica:
- bitmap presente;
- data region presente;
- inode count igual a 2048;
- journal com ao menos dois sectors;
- metadata regions ordenadas e não sobrepostas;
- data end dentro de total sectors;
- bitmap com bits suficientes para data region.
Também exige:
inode_size = 128
root_inode = 0
Inode table¶
A tabela contém:
CFS_INODE_COUNT = 2048
records fixos.
Cada record tem:
CFS_INODE_SIZE = 128 bytes
Quatro inodes cabem em cada sector de 512 bytes.
Portanto são usados exatamente 512 sectors.
Tipos de inode¶
Valores persistidos:
0 = free
1 = file
2 = directory
Outros valores são inválidos segundo fsck atual.
Layout do inode¶
| Offset | Tamanho | Campo |
|---|---|---|
| 0 | 2 | type |
| 2 | 2 | flags |
| 4 | 4 | file size |
| 8 | 4 | generation |
| 12 | 48 | 12 direct block LBAs |
| 60 | 4 | single-indirect LBA |
| 64 | 4 | double-indirect LBA |
| 68 | 4 | uid |
| 72 | 4 | gid |
| 76 | 4 | mode |
| 80 | 4 | triple-indirect LBA |
| 84 | 4 | mtime low |
| 88 | 4 | mtime high |
| 92 | 32 | reserved/zero no encoder atual |
| 124 | 4 | checksum |
O checksum cobre bytes 0 a 123.
Checksum do inode¶
O mesmo helper FNV-1a-style é aplicado aos primeiros 124 bytes.
Se falhar, cfs_inode_decode rejeita o record.
Fsck classifica esse caso como:
inode checksum
O host test corrompe um byte de checksum propositalmente e confirma detecção sem escrita no disco.
Root inode¶
O root inode é:
0
Na formatação, inode 0 vira directory.
Seu primeiro direct pointer referencia o primeiro data sector.
O formatter também marca o primeiro bit do bitmap porque esse bloco já pertence ao root.
Allocation bitmap¶
O bitmap descreve sectors da data region, não LBAs absolutos.
O índice é:
[ i = lba - data_lba ]
Bit 1 significa sector alocado.
Cada bitmap sector contém:
[ 512 cdot 8 = 4096 ]
bits.
Data blocks¶
A unidade de allocation é um sector de 512 bytes.
Sectors de data podem armazenar:
- file data;
- directory entries;
- single-indirect pointer tables;
- double-indirect tables;
- triple-indirect tables.
Pointer blocks contêm LBAs little-endian de 32 bits.
Direct pointers¶
Cada inode possui 12 direct LBAs.
Capacidade direta:
[ 12 cdot 512 = 6144 ]
bytes.
Single indirect¶
Um pointer block contém:
512 / 4 = 128
pointers.
Logo single-indirect acrescenta 128 data blocks.
Double indirect¶
O root double-indirect pode apontar para 128 single-indirect tables.
Capacidade:
[ 128^2 = 16.384 ]
data blocks.
Triple indirect¶
Triple-indirect acrescenta:
[ 128^3 = 2.097.152 ]
possible data-block pointers.
Somando direct/single/double/triple, a árvore possui capacidade estrutural teórica de:
2.113.676 blocks
antes de outros limites.
Na prática disk size e o field de file size de 32 bits impõem limites menores.
Limite da API whole-file¶
cfs_write() e cfs_truncate() continuam usando o teto direct+single+double:
CFS_MAX_BLOCKS = 16.524
CFS_MAX_FILE_SIZE = 8.460.288 bytes
aproximadamente 8,06 MiB.
tools/test_cfs_maxwrite.c comprova que exatamente esse tamanho funciona e um byte a mais retorna CFS_EFBIG.
É limite da API, não do pointer tree inteiro.
Limite de write_at¶
cfs_write_at() usa o caminho mais profundo e verifica:
CFS_MAX_FILE_BYTES = data_sectors * 512
também sujeito ao arithmetic de 32 bits e ao file-size field de 32 bits do inode.
As APIs de escrita não possuem portanto o mesmo maximum-size contract.
Formato de dirent¶
Cada directory entry ocupa:
CFS_DIRENT_SIZE = 80 bytes
Layout:
| Offset | Tamanho | Campo |
|---|---|---|
| 0 | 4 | inode number |
| 4 | 1 | type |
| 5 | 1 | name length |
| 6 | 2 | flags |
| 8 | 64 | name bytes |
| 72 | 8 | reserved/zero |
O nome fica inline.
Não existe string table.
Densidade de directory¶
Com records de 80 bytes em sector de 512:
CFS_DIRENTS_PER_SECTOR = 6
por integer division.
Os 32 bytes restantes não formam uma sétima entry.
Slots vazios¶
Uma entry é tratada como vazia quando inode ou name length é zero.
Como root inode é 0, dirents comuns não usam inode 0 como child target.
O root é alcançado pelas semantics do filesystem.
Names e paths¶
Maximum component/name:
64 bytes
Maximum path:
512 bytes
Maximum depth:
32 components
Comparação de names é byte-for-byte, logo case-sensitive.
Os tests demonstram que:
foo
Foo
são distintos.
Sintaxe de path¶
O parser atual:
- permite leading
/; - rejeita empty interior components;
- rejeita trailing slash em path não-root;
- rejeita backslash;
- rejeita
.; - rejeita
..; - rejeita component >64 bytes;
- rejeita depth >32.
Root pode ser representado por path vazio ou / nas APIs que aceitam root.
Limite de directory blocks¶
Directory traversal usa:
CFS_DIR_MAX_BLOCKS =
CFS_DIRECT_COUNT + CFS_PTRS_PER_BLOCK
isto é:
12 + 128 = 140 blocks
É um limite de runtime para directories, embora files possam usar árvores mais profundas.
Com seis entries por sector, o teto simples de slots é 840 entries.
Permission bits¶
Bits atuais:
READ = 1
WRITE = 2
EXEC = 4
WALK = 8
e:
CFS_PERM_ALL = 15
O inode contém mode e também os permission bits compatíveis em flags.
Compatibilidade de permissions¶
O effective mode é escolhido nesta ordem:
modese não zero;- low permission bits de
flags; - para inode non-free legado, default all permissions.
Isso mantém compatibilidade com records anteriores.
UID/GID¶
Novos inodes começam com:
uid = 0
gid = 0
O permission checker atual rejeita acesso quando uid != 0.
Portanto os fields uid/gid existem no formato, mas o runtime não implementa um modelo multi-user UNIX geral.
Na prática opera em torno de owner 0 e dos quatro permission bits ChrisFS.
Modification time¶
O mtime é armazenado em:
mtime_lo
mtime_hi
formando:
[ mtime = lo | (hi << 32) ]
O clock atual é um valor interno monotonicamente incrementado, salvo quando caller define um valor não zero por cfs_set_now().
O formato não define um epoch externo nesta revisão.
Journal¶
ChrisFS reserva um metadata journal.
Constantes:
JNL_MAGIC = 0x4C4E4A43
JNL_EMPTY = 0
JNL_BEGIN = 1
JNL_COMMIT = 2
JNL_MAX_REC = 30
O journal default ocupa 64 sectors.
Header do journal¶
Primeiro sector:
| Offset | Tamanho | Campo |
|---|---|---|
| 0 | 4 | journal magic |
| 4 | 4 | sequence |
| 8 | 4 | state |
| 12 | 4 | record count |
| 16 | 4 | checksum |
Checksum cobre bytes 0 a 15.
O restante é zerado nas writes normais.
Records do journal¶
Cada record usa dois sectors.
Em:
[ journal_lba + 1 + 2i ]
o metadata sector contém:
| Offset | Tamanho | Campo |
|---|---|---|
| 0 | 4 | target LBA |
| 4 | 4 | checksum do payload de 512 bytes |
O sector seguinte contém os 512 bytes completos de replacement data.
Com 30 records:
1 + 30*2 = 61 sectors
são usados dentro da região default de 64.
Semântica do journal¶
jnl_begin marca o superblock dirty e grava BEGIN.
Em metadata write, jnl_log salva replacement sector no journal antes de escrever o target metadata sector.
jnl_commit grava COMMIT, depois muda para EMPTY e finalmente marca o superblock clean.
No mount:
- EMPTY não exige replay;
- BEGIN é descartado como transaction incompleta;
- COMMIT reaplica os replacement sectors e depois limpa o journal.
É um pequeno redo-oriented metadata journal.
Não é undo log nem full data journal.
Política para file data¶
Durante writes de file data, jnl_data faz com que sectors de conteúdo não sejam individualmente journaled.
Metadata é a parte protegida pelo journal.
Por isso journal saudável não deve ser interpretado como prova de atomicidade total diante de power loss.
Crash-consistency continua sendo área explícita de validação futura.
Clean flag e generation¶
O superblock possui:
clean
generation
Transactions marcam dirty no início e clean após commit.
Operações também avançam generation conforme metadata muda.
Os fields ajudam a identificar state, mas não substituem checksums e journal validation.
Mount¶
Mount bem-sucedido exige:
- block device com sectors de 512 bytes;
- superblock legível/válido;
- versão suportada;
- checksum e geometria válidos;
- backing device grande o suficiente;
- journal handling bem-sucedido;
- root inode válido;
- root sendo directory.
cfs_mount não reformata silenciosamente mídia inválida.
Format¶
cfs_format exige block device writable de 512-byte sectors.
Ele:
- deriva geometry v5;
- zera metadata até o início de data;
- marca bitmap bit 0;
- inicializa todos os inode records com checksum válido;
- inicializa root inode 0;
- aponta root direct[0] ao primeiro data sector;
- inicializa permissions do root;
- zera o root data block;
- grava superblock;
- cria EMPTY journal;
- flush.
Fsck¶
cfs_fsck funciona como verificador read-only de invariants.
Verifica, entre outros:
- superblock checksum;
- journal state;
- inode checksums/types;
- block ranges;
- duplicate block ownership;
- missing bitmap bits;
- bitmap leaks;
- direct/indirect/double/triple trees;
- root type;
- directory cycles;
- duplicate names;
- invalid dirents;
- file size versus allocated blocks.
O host test confirma que corrupção é detectada e que fsck não escreve no disk.
Modelo de resultado do fsck¶
Filesystem limpo retorna:
CFS_OK
com reason:
clean
Problemas estruturais retornam positive error count quando aplicável.
I/O/setup failures podem retornar negative ChrisFS error codes.
cfs_fsck_reason() fornece a primeira classificação.
Error codes¶
A API expõe erros para:
- invalid argument;
- I/O;
- format;
- not found;
- already exists;
- no space;
- file too large;
- name too long;
- corruption;
- not mounted;
- not directory;
- not empty;
- is directory;
- cross-device;
- permission denied.
Esses numeric runtime codes não fazem parte automaticamente do on-disk format.
Cache não é estado on-disk¶
O cache de 64 lines, hit/miss counters e age values pertencem ao runtime.
Não são serializados na image.
Uma disk image bruta não contém cache metadata.
Histórico de migração¶
Existem readers explícitos para layouts v1 e v2 em:
tools/cfs_migrate_v1v2.c
tools/cfs_migrate_v2v3.c
Apesar dos filenames históricos, o destination path chama o cfs_format() atual.
Nesta revisão, portanto, media recém-criada pelo destination segue a implementação corrente, não necessariamente a versão histórica sugerida pelo nome da ferramenta.
Ferramentas novas devem declarar source/destination version de forma explícita.
Evidência da v5¶
A suite v5 confirma que:
- image v5 de 512 MiB preserva addresses históricos;
- o mesmo superblock marcado como v4 monta;
- v4 read/write/fsck funciona;
- volume v5 maior registra sector count maior;
- data region ultrapassa antigo fim do disco;
- allocation pode ocorrer além do limite histórico de 512 MiB.
É evidência direta da principal mudança v5: dynamic volume geometry.
Limitações atuais¶
ChrisFS v5 ainda não possui:
- extent trees;
- sparse-file contract formal;
- symbolic links;
- hard links;
- rich timestamps;
- general multi-user permissions;
- ACL lists;
- xattrs;
- checksums de ordinary data sectors;
- copy-on-write;
- snapshots;
- inode allocation acima de 2048 inodes;
- indexed large directories;
- full data journaling.
Esses recursos estão fora do formato atual.
Regra de versionamento¶
Mudança incompatível precisa de nova filesystem version quando altera interpretação persistida de:
- superblock;
- inode;
- dirent;
- pointer tree;
- permission fields;
- journal records;
- checksum rules;
- bitmap semantics;
- geometry rules.
Compatibilidade antiga deve ser explícita, não inferida.
Resumo de conformidade¶
Implementação v5 conforme precisa preservar:
- logical sector de 512 bytes;
- little-endian;
- magic
CFS1; - offsets/checksum do superblock v5;
- inode de 128 bytes com checksum;
- dirent de 80 bytes;
- names de até 64 bytes;
- bitmap por data sector;
- direct/single/double/triple pointers;
- root inode 0;
- journal header/record layout;
- dynamic geometry invariants.
Limites de API, como o whole-file ceiling de 8,06 MiB, devem ser separados da capacidade do formato on-disk.
Nota de revisão¶
Esta especificação foi reconciliada contra ChrisOS e05a17fd76333114a3fb5c2452f38ca747d4ac56.
A mudança central da v5 é a geometria dinâmica de volume mantendo compatibilidade com images v4/v3 de geometria fixa. O formato já possui fields e pointer depth mais ricos que algumas APIs de alto nível, por isso capacidade do formato e capacidade da API precisam continuar claramente separadas.