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}