Skip to content

Commit 18f9501

Browse files
committed
Add disk header loader.
This commit introduces the first piece of code in the core component of TFS. It does something relatively basicaly: it reads the disk header as specified in the spec document.
1 parent 27c32e7 commit 18f9501

File tree

6 files changed

+252
-8
lines changed

6 files changed

+252
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Cargo.lock
55
*.log
66
*.out
77
*.pdf_tex
8+
*.toc

specification.tex

+11-8
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
\newcommand{\concat}{\Vert}
2222

2323
\begin{document}
24-
\maketitle
2524
\begin{abstract}
2625
We give a complete specification of the on-disk representation of the
2726
TFS file system. Certain implementation details are covered.
2827
\end{abstract}
2928

29+
\maketitle
30+
\tableofcontents
31+
3032
\chapter{Introduction}
3133
This specification should detail the TFS file system such that it can be
3234
implemented without ambiguity. As such, it should bridge various
@@ -116,7 +118,7 @@
116118
\subsection{Encryption parameters (byte 68-84)}
117119
\label{header:encryptionparams}
118120
These are parameters used differently based on the choice
119-
in~\ref{header:encyption}.
121+
in~\ref{header:encryption}.
120122

121123
\subsection{Bitwise negation of encryption parameters (byte 84-100)}
122124
Repetition of~\ref{header:encyptionparams}, bitwise negated.
@@ -128,9 +130,10 @@
128130

129131
\begin{description}
130132
\item [$n = 0$] state block uninitialized.
131-
\item [$n \neq 0$] the $n$'th cluster (starting at byte
132-
$\clustersize n$) is used as the state block and abides the
133-
format set out in~\ref{stateblock}.
133+
\item [$n \neq 0$] the $n$'th cluster
134+
(starting at byte $\clustersize n$) is used
135+
as the state block and abides the format set out
136+
in~\ref{stateblock}.
134137
\end{description}
135138

136139
\subsection{Bitwise negation of state block address (byte 136-144)}
@@ -218,8 +221,8 @@
218221

219222
\begin{description}
220223
\item [$n = 0$] super-page uninitialized.
221-
\item [$n \neq 0$] the $n$'th page is the super-page as
222-
defined in~\ref{fs:superpage}.
224+
\item [$n \neq 0$] the $n$'th page is the super-page as defined
225+
in~\ref{fs:superpage}.
223226
\end{description}
224227

225228
\section{Integrity checking}
@@ -310,7 +313,7 @@
310313

311314
\section{Checksums}
312315
\subsection{Constant checksum}
313-
\label{checksum:seahash}
316+
\label{checksum:constant}
314317
Constant checksum is independent of the input\footnote{This is used as
315318
an alternative to storing no checksum, which would make implementation
316319
harder. Instead, this allows some -- but weak -- protection against failure

src/cluster.rs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use std::NonZero;
2+
3+
pub struct ClusterPointer(NonZero<u64>);
4+
5+
impl ClusterPointer {
6+
pub fn new(x: u64) -> Option<ClusterPointer> {
7+
if x == 0 {
8+
None
9+
} else {
10+
Some(ClusterPointer(unsafe {
11+
NonZero::new(x)
12+
}))
13+
}
14+
}
15+
}
16+
17+

src/disk.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
type Sector = u64;
2+
type SectorOffset = u16;
3+
4+
enum Error {
5+
OutOfBounds,
6+
SectorCorrupted,
7+
}
8+
9+
trait Disk {
10+
fn sector_size(&self) -> SectorOffset;
11+
fn number_of_sectors(&self) -> Sector;
12+
13+
fn write(sector: Sector, offset: SectorOffset, buffer: &[u8]) -> Result<(), Error>;
14+
fn read(sector: Sector, offset: SectorOffset, buffer: &mut [u8]) -> Result<(), Error>;
15+
16+
fn write_all(sector: Sector, offset: SectorOffset, buffer: &[u8]) -> Result<(), Error> {
17+
18+
}
19+
fn read_all(sector: Sector, offset: SectorOffset, buffer: &mut [u8]) -> Result<(), Error> {
20+
let sector_size = self.sector_size();
21+
22+
self.read(buffer.len() / sector_size, offset, buffer[..offset])?;
23+
for i in 1..buffer.len() as Sector / sector_size + 1 {
24+
self.read(sector + i, 0, buffer[i * sector_size..])
25+
}
26+
}
27+
}
28+
29+
/// For testing, we allow byte slices to act as disks.
30+
#[cfg(tests)]
31+
impl Disk for &mut [u8] {
32+
fn sector_size(&self) -> SectorOffset {
33+
512
34+
}
35+
36+
fn number_of_sectors(&self) -> Sector {
37+
self.len() as Sector / self.sector_size()
38+
}
39+
40+
fn write(sector: Sector, offset: SectorOffset, buffer: &[u8]) -> Result<(), Error> {
41+
if offset + buffer.len() > self.sector_size() {
42+
Err(Error::OutOfBounds)
43+
} else {
44+
Ok(self[(sector / self.sector_size() + offset as Sector) as usize..][..buffer.len()]
45+
.copy_from_slice(buffer))
46+
}
47+
}
48+
49+
fn read(sector: Sector, offset: SectorOffset, buffer: &mut [u8]) -> Result<(), Error> {
50+
if offset + buffer.len() > self.sector_size() {
51+
Err(Error::OutOfBounds)
52+
} else {
53+
Ok(buffer.copy_from_slice(self[(sector / self.sector_size() + offset as Sector) as usize..][..buffer.len()]))
54+
}
55+
}
56+
}

src/header.rs

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
const DISK_HEADER_SIZE: usize = 4096;
2+
const DEFAULT_DISK_HEADER: [u8; 146] = [
3+
// The magic number (`TFS fmt `).
4+
b'T', b'F', b'S', b' ', b'f', b'm', b't', b' ',
5+
// The version number.
6+
0x00, 0x00, 0x00, 0x00,
7+
0xFF, 0xFF, 0xFF, 0xFF,
8+
// The implementation ID (`official`).
9+
b'o', b'f', b'f', b'i', b'c', b'i', b'a', b'l',
10+
!b'o', !b'f', !b'f', !b'i', !b'c', !b'i', !b'a', !b'l',
11+
// Encryption algorithm.
12+
0x00, 0x00,
13+
0xFF, 0xFF,
14+
// Encryption parameters.
15+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
16+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
17+
// State block address (uninitialized).
18+
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
19+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
20+
// Consistency flag.
21+
0x03, 0xFC,
22+
];
23+
24+
enum Error {
25+
CorruptConsistencyFlag,
26+
CorruptEncryptionAlgorithm,
27+
CorruptEncryptionParameters,
28+
CorruptImplementationId,
29+
CorruptStateBlockAddress,
30+
CorruptVersionNumber,
31+
IncompatibleVersion,
32+
UnknownCipher,
33+
UnknownConsistencyFlag,
34+
UnknownFormat,
35+
Disk(disk::Error),
36+
}
37+
38+
impl fmt::Display for Error {
39+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), Error> {
40+
write!(f, )
41+
}
42+
}
43+
44+
enum MagicNumber {
45+
PartialCompatibility,
46+
TotalCompatibility,
47+
}
48+
49+
enum EncryptionAlgorithm {
50+
Identity = 0,
51+
Speck128 = 1,
52+
}
53+
54+
enum ConsistencyFlag {
55+
Closed,
56+
StillActive,
57+
Inconsistent,
58+
Uninitialized,
59+
}
60+
61+
#[derive(Default)]
62+
struct DiskHeader {
63+
magic_number: MagicNumber,
64+
version_number: u32,
65+
implementation_id: u32,
66+
encryption_algorithm: EncryptionAlgorithm,
67+
encryption_parameters: [u8; 16],
68+
state_block_address: ClusterPointer,
69+
consistency_flag: ConsistencyFlag,
70+
}
71+
72+
impl DiskHeader {
73+
fn flush_encryption_algorithm<D: Disk>(disk: &mut D) -> Result<DiskHeader, Error> {
74+
75+
}
76+
77+
/// Load the disk header from some disk.
78+
///
79+
/// This will construct it into memory while performing error checks on the header to ensure
80+
/// correctness.
81+
fn load<D: Disk>(disk: &mut D) -> Result<DiskHeader, Error> {
82+
// Load the disk header into a buffer in memory.
83+
let mut buf = [0; DISK_HEADER_SIZE];
84+
disk.read_all(0, 0, &mut buf)?;
85+
// Start with some default value, which will be filled out later.
86+
let mut ret = DiskHeader::default();
87+
88+
// # Introducer Section
89+
//
90+
// This section has the purpose of defining the implementation, version, and type of the
91+
// disk image. It is rarely changed unless updates or reformatting happens.
92+
93+
// Load the magic number.
94+
ret.magic_number = match buf[..8] {
95+
// Total compatibility.
96+
b"TFS fmt " => MagicNumber::TotalCompatibility,
97+
// Partial compatibility.
98+
b"~TFS fmt" => MagicNumber::PartialCompatibility,
99+
// Unknown format; abort.
100+
_ => return Err(Error::UnknownFormat),
101+
};
102+
103+
// Load the version number.
104+
ret.version_number = LittleEndian::read(buf[8..12]);
105+
// Right after the version number, the same number follows, but bitwise negated. Make sure
106+
// that these numbers match (if it is bitwise negated). The reason for using this form of
107+
// code rather than just repeating it as-is is that if one overwrites all bytes with a
108+
// constant value, like zero, it won't be detected.
109+
if ret.version_number == !LittleEndian::read(buf[12..16]) {
110+
// Check if the version is compatible.
111+
if ret.version_number >> 16 > 0 {
112+
// The version is not compatible; abort.
113+
return Err(Error::IncompatibleVersion);
114+
}
115+
} else {
116+
// The version number is corrupt; abort.
117+
return Err(Error::CorruptVersionNumber);
118+
}
119+
120+
// Load the implementation ID.
121+
ret.implementation_id = LittleEndian::read(buf[16..24]);
122+
// Similarly to the version number, a bitwise negated repetition follows. Make sure it
123+
// matches.
124+
if ret.implementation_id != !LittleEndian::read(buf[24..32]) {
125+
// The implementation ID is corrupt; abort.
126+
return Err(Error::CorruptImplementationId);
127+
}
128+
129+
// == Encryption Section ==
130+
131+
// Load the encryption algorithm choice.
132+
ret.encryption_algorithm = EncryptionAlgorithm::from(LittleEndian::read(buf[64..66]))?;
133+
// Repeat the bitwise negation.
134+
if ret.encryption_algorithm as u16 != !LittleEndian::read(buf[66..68]) {
135+
// The implementation ID is corrupt; abort.
136+
return Err(Error::CorruptEncryptionAlgorithm);
137+
}
138+
139+
// Load the encryption parameters (e.g. salt).
140+
self.encryption_parameters.copy_from_slice(&buf[68..84]);
141+
// Repeat the bitwise negation.
142+
if self.encryption_parameters.iter().eq(buf[84..100].iter().map(|x| !x)) {
143+
// The encryption parameters are corrupt; abort.
144+
return Err(Error::CorruptEncryptionParameters);
145+
}
146+
147+
// == State ==
148+
149+
// Load the state block pointer.
150+
ret.state_block_address = ClusterPointer::new(LittleEndian::read(buf[128..136]));
151+
// Repeat the bitwise negation.
152+
if ret.state_block_address as u64 != !LittleEndian::read(buf[136..144]) {
153+
// The state block address is corrupt; abort.
154+
return Err(Error::CorruptStateBlockAddress);
155+
}
156+
157+
// Load the consistency flag.
158+
self.consistency_flag = ConsistencyFlag::from(buf[144])?;
159+
// Repeat the bitwise negation.
160+
if self.consistency_flag as u8 != !buf[145] {
161+
// The consistency flag is corrupt; abort.
162+
return Err(Error::CorruptConsistencyFlag);
163+
}
164+
}
165+
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mod config;
2+
mod disk;

0 commit comments

Comments
 (0)