Skip to main content

ternaria_fs/
lib.rs

1//! TFS, the Ternaria filesystem.
2//!
3//! # What this is for
4//!
5//! A filesystem the guest can implement in assembly, so that a shell exists
6//! before a compiler does. Every structure is sized to a whole number of
7//! trytes, every field is a whole word, and nothing needs multiplication by a
8//! number that is not a power of three to reach.
9//!
10//! It is deliberately plain: no journal, no extents, no indirection. Those are
11//! not the interesting part of a ternary machine, and a format that can be
12//! driven by a few hundred instructions is worth more right now than one that
13//! cannot be driven at all.
14//!
15//! # Layout
16//!
17//! The unit is the block, which is one device sector: 243 trytes.
18//!
19//! ```text
20//!   block 0        superblock
21//!   block 1..4     allocation map, one tryte per block
22//!   block 4..13    inode table, 9 inodes per block
23//!   block 13..     data
24//! ```
25//!
26//! Every one of those boundaries is recorded in the superblock, so a driver
27//! reads them rather than assuming them.
28//!
29//! # Sizes
30//!
31//! An inode is 27 trytes, so nine fit a block with nothing left over. A
32//! directory entry is also 27 trytes, so a directory block holds nine entries.
33//! Both numbers are 3^2, and a block is 3^5 trytes, so dividing an offset by an
34//! entry size is a trit shift rather than a division.
35
36#![forbid(unsafe_code)]
37#![warn(missing_docs)]
38
39pub mod image;
40
41pub use image::Builder;
42
43/// Trytes per block. One device sector.
44pub const BLOCK_TRYTES: usize = 243;
45
46/// Trytes per word.
47pub const WORD_TRYTES: usize = 3;
48
49/// Words per block.
50pub const BLOCK_WORDS: usize = BLOCK_TRYTES / WORD_TRYTES;
51
52/// Trytes in one inode: 3^3.
53pub const INODE_TRYTES: usize = 27;
54
55/// Inodes per block: 3^2.
56pub const INODES_PER_BLOCK: usize = BLOCK_TRYTES / INODE_TRYTES;
57
58/// Trytes in one directory entry. The same as an inode, so a driver walking
59/// either steps by the same amount.
60pub const DIRENT_TRYTES: usize = 27;
61
62/// Directory entries per block.
63pub const DIRENTS_PER_BLOCK: usize = BLOCK_TRYTES / DIRENT_TRYTES;
64
65/// Trytes of name in a directory entry.
66///
67/// An entry is 27 trytes, of which the first word is the inode number, leaving
68/// 24 for the name. Names are zero-padded and are not terminated when they use
69/// the whole field.
70pub const NAME_TRYTES: usize = DIRENT_TRYTES - WORD_TRYTES;
71
72/// Direct block pointers in an inode.
73///
74/// An inode is nine words: kind, size, then seven pointers. There is no
75/// indirection, so a file is at most this many blocks.
76pub const DIRECT_BLOCKS: usize = 7;
77
78/// Largest file the format can express, in trytes.
79pub const MAX_FILE_TRYTES: usize = DIRECT_BLOCKS * BLOCK_TRYTES;
80
81/// Value identifying a TFS image, in word 0 of the superblock.
82///
83/// Reads as `3^9 + 3^3 + 1` and is chosen only to be unlikely and easy to
84/// recognise in a dump.
85pub const MAGIC: i64 = 19_683 + 27 + 1;
86
87/// Inode number of the root directory. Inode 0 means "none".
88pub const ROOT_INODE: i64 = 1;
89
90/// What an inode describes.
91pub mod kind {
92    /// The inode is not in use.
93    pub const FREE: i64 = 0;
94    /// A regular file.
95    pub const FILE: i64 = 1;
96    /// A directory.
97    pub const DIRECTORY: i64 = 2;
98}
99
100/// Word offsets within the superblock, which is block 0.
101pub mod sb {
102    /// Identifies the format.
103    pub const MAGIC: usize = 0;
104    /// Total blocks in the image.
105    pub const TOTAL_BLOCKS: usize = 1;
106    /// First block of the allocation map.
107    pub const MAP_START: usize = 2;
108    /// How many blocks the allocation map occupies.
109    pub const MAP_BLOCKS: usize = 3;
110    /// First block of the inode table.
111    pub const INODE_START: usize = 4;
112    /// How many blocks the inode table occupies.
113    pub const INODE_BLOCKS: usize = 5;
114    /// First block available for data.
115    pub const DATA_START: usize = 6;
116    /// Inode number of the root directory.
117    pub const ROOT: usize = 7;
118}
119
120/// Word offsets within an inode.
121pub mod ino {
122    /// What the inode describes. See [`kind`](super::kind).
123    pub const KIND: usize = 0;
124    /// Size in trytes. For a directory, the number of entry slots in use.
125    pub const SIZE: usize = 1;
126    /// First of the direct block pointers.
127    pub const DIRECT: usize = 2;
128}
129
130/// Assembler `.equ` lines naming every constant a driver needs.
131///
132/// Generated from the definitions above for the same reason the device
133/// prelude is: the guest driver and the host builder must agree about the
134/// layout, and a number written into both is a number that will eventually
135/// differ between them.
136pub fn prelude() -> String {
137    let entries: [(&str, i64); 23] = [
138        ("FS_MAGIC", MAGIC),
139        ("FS_BLOCK_TRYTES", BLOCK_TRYTES as i64),
140        ("FS_BLOCK_WORDS", BLOCK_WORDS as i64),
141        ("FS_INODE_TRYTES", INODE_TRYTES as i64),
142        ("FS_INODES_PER_BLOCK", INODES_PER_BLOCK as i64),
143        ("FS_DIRENT_TRYTES", DIRENT_TRYTES as i64),
144        ("FS_DIRENTS_PER_BLOCK", DIRENTS_PER_BLOCK as i64),
145        ("FS_NAME_TRYTES", NAME_TRYTES as i64),
146        ("FS_DIRECT_BLOCKS", DIRECT_BLOCKS as i64),
147        ("FS_ROOT_INODE", ROOT_INODE),
148        ("KIND_FREE", kind::FREE),
149        ("KIND_FILE", kind::FILE),
150        ("KIND_DIRECTORY", kind::DIRECTORY),
151        ("SB_MAGIC", sb::MAGIC as i64),
152        ("SB_TOTAL_BLOCKS", sb::TOTAL_BLOCKS as i64),
153        ("SB_MAP_START", sb::MAP_START as i64),
154        ("SB_INODE_START", sb::INODE_START as i64),
155        ("SB_DATA_START", sb::DATA_START as i64),
156        ("SB_ROOT", sb::ROOT as i64),
157        ("INO_KIND", ino::KIND as i64),
158        ("INO_SIZE", ino::SIZE as i64),
159        ("FS_RADIX", image::RADIX),
160        ("FS_RADIX_TRITS", image::RADIX_TRITS as i64),
161    ];
162    let mut out = String::new();
163    for (name, value) in entries {
164        out.push_str(&format!(".equ {name}, {value}\n"));
165    }
166    out.push_str(&format!(".equ INO_DIRECT, {}\n", ino::DIRECT));
167    out
168}