fstool 0.4.31

Build disk images and filesystems (ext2/3/4, MBR, GPT) from a directory tree and TOML spec, in the spirit of genext2fs.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//! File handles: read, write, seek, truncate.
//!
//! A [`File`] is a plain `Copy`-sized value that borrows nothing — it
//! records where the data is, where the cursor is, and where the directory
//! entry to update lives. The volume is passed back in on every call, so a
//! program can hold as many handles as it likes without an allocator and
//! without the borrow checker getting in the way.
//!
//! The cursor caches which cluster it is in, so reading or writing
//! forwards costs one FAT lookup per cluster boundary rather than a walk
//! from the start. Seeking backwards re-walks the chain.

use super::{EntryLoc, Error, SectorDriver, Volume};

/// FAT records a file's size in 32 bits.
pub const MAX_FILE_LEN: u32 = u32::MAX;

impl<D: SectorDriver, const S: usize> Volume<D, S> {
    /// Read whole sectors straight into the caller's buffer, going around
    /// the one-sector cache (after writing it back if it covers any of
    /// them).
    fn read_sectors_direct(&mut self, first: u32, buf: &mut [u8]) -> Result<(), Error<D::Error>> {
        let count = (buf.len() / self.bps()) as u32;
        // These two are the only paths that do not go through `load`, so
        // they carry its bounds check themselves: the `SectorDriver`
        // contract promises a driver never sees a request past the end of
        // the medium.
        self.check_range(first, count)?;
        self.invalidate(first, count)?;
        let abs = self.abs(first);
        self.dev.read_sectors(abs, buf).map_err(Error::Io)
    }

    /// Write whole sectors straight from the caller's buffer.
    fn write_sectors_direct(&mut self, first: u32, buf: &[u8]) -> Result<(), Error<D::Error>> {
        let count = (buf.len() / self.bps()) as u32;
        self.check_range(first, count)?;
        self.invalidate(first, count)?;
        let abs = self.abs(first);
        self.dev.write_sectors(abs, buf).map_err(Error::Io)
    }
}

/// An open file.
///
/// Every method takes the [`Volume`] the file came from; handing it a
/// different one is a logic error the driver cannot detect.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct File {
    first_cluster: u32,
    len: u32,
    pos: u32,
    /// The cluster holding `cur_index`'s worth of data, when known.
    cur_cluster: u32,
    cur_index: u32,
    loc: EntryLoc,
    /// Size or first cluster changed and the entry needs rewriting.
    dirty: bool,
}

impl File {
    pub(crate) fn new(first_cluster: u32, len: u32, loc: EntryLoc) -> Self {
        Self {
            first_cluster,
            len,
            pos: 0,
            cur_cluster: first_cluster,
            cur_index: 0,
            loc,
            dirty: false,
        }
    }

    /// Size in bytes.
    pub fn len(&self) -> u32 {
        self.len
    }

    /// Whether the file is empty.
    pub fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// The read/write cursor.
    pub fn pos(&self) -> u32 {
        self.pos
    }

    /// Move the cursor. Seeking past the end is allowed; writing there
    /// first fills the gap with zeros.
    pub fn seek<D: SectorDriver, const S: usize>(
        &mut self,
        _vol: &mut Volume<D, S>,
        pos: u32,
    ) -> Result<(), Error<D::Error>> {
        self.pos = pos;
        Ok(())
    }

    /// Move the cursor to the end of the file.
    pub fn seek_to_end<D: SectorDriver, const S: usize>(
        &mut self,
        _vol: &mut Volume<D, S>,
    ) -> Result<(), Error<D::Error>> {
        self.pos = self.len;
        Ok(())
    }

    /// The cluster holding byte offset `index * cluster_bytes`, walking
    /// the chain from wherever the cursor already is.
    fn cluster_at<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        index: u32,
    ) -> Result<Option<u32>, Error<D::Error>> {
        if self.first_cluster == 0 {
            return Ok(None);
        }
        let (mut cluster, mut at) = if index >= self.cur_index && self.cur_cluster != 0 {
            (self.cur_cluster, self.cur_index)
        } else {
            (self.first_cluster, 0)
        };
        // A chain cannot be longer than the volume has clusters; anything
        // more means it loops back on itself.
        if index > vol.geom.cluster_count {
            return Err(Error::CorruptChain);
        }
        while at < index {
            match vol.next_cluster(cluster)? {
                Some(next) => {
                    cluster = next;
                    at += 1;
                }
                None => return Ok(None),
            }
        }
        // Every hop above came through `next_cluster`, which validates;
        // the starting cluster came off the directory entry and has not.
        if !vol.geom.is_data_cluster(cluster) {
            return Err(Error::CorruptChain);
        }
        self.cur_cluster = cluster;
        self.cur_index = at;
        Ok(Some(cluster))
    }

    /// Like [`Self::cluster_at`], but allocates (and links) clusters up to
    /// `index` when the chain is short.
    ///
    /// `zero_new` fills freshly allocated clusters, which is what keeps a
    /// gap — or the tail of the last cluster — from exposing whatever the
    /// previous owner left there.
    fn cluster_at_or_alloc<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        index: u32,
        zero_new: bool,
    ) -> Result<u32, Error<D::Error>> {
        if self.first_cluster == 0 {
            let cluster = if zero_new {
                vol.alloc_zeroed_cluster(None)?
            } else {
                vol.alloc_cluster(None)?
            };
            self.first_cluster = cluster;
            self.cur_cluster = cluster;
            self.cur_index = 0;
            self.dirty = true;
        }
        if index > vol.geom.cluster_count {
            return Err(Error::CorruptChain);
        }
        let (mut cluster, mut at) = if index >= self.cur_index && self.cur_cluster != 0 {
            (self.cur_cluster, self.cur_index)
        } else {
            (self.first_cluster, 0)
        };
        // As in `cluster_at`: the first cluster is whatever the entry
        // said, so check it before it reaches `cluster_first_sector`.
        if !vol.geom.is_data_cluster(cluster) {
            return Err(Error::CorruptChain);
        }
        while at < index {
            cluster = match vol.next_cluster(cluster)? {
                Some(next) => next,
                None => {
                    if zero_new {
                        vol.alloc_zeroed_cluster(Some(cluster))?
                    } else {
                        vol.alloc_cluster(Some(cluster))?
                    }
                }
            };
            at += 1;
        }
        self.cur_cluster = cluster;
        self.cur_index = at;
        Ok(cluster)
    }

    /// Read into `buf`, returning how many bytes were read: fewer than
    /// asked for only at the end of the file.
    pub fn read<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        buf: &mut [u8],
    ) -> Result<usize, Error<D::Error>> {
        let want = buf.len().min(self.len.saturating_sub(self.pos) as usize);
        if want == 0 {
            return Ok(0);
        }
        let bps = vol.bps();
        let spc = vol.geom.sectors_per_cluster;
        let cb = vol.geom.cluster_bytes();
        let mut done = 0;

        while done < want {
            let index = self.pos / cb;
            let in_cluster = self.pos % cb;
            let cluster = self
                .cluster_at(vol, index)?
                // The chain ended before the size said it would.
                .ok_or(Error::CorruptChain)?;
            let sector_in_cluster = in_cluster / bps as u32;
            let in_sector = (in_cluster % bps as u32) as usize;
            let first_sector = vol.geom.cluster_first_sector(cluster) + sector_in_cluster;
            let left = want - done;

            if in_sector == 0 && left >= bps {
                // Whole sectors: straight into the caller's buffer, as
                // many as remain in this cluster.
                let max = (spc - sector_in_cluster) as usize;
                let n = (left / bps).min(max);
                let bytes = n * bps;
                vol.read_sectors_direct(first_sector, &mut buf[done..done + bytes])?;
                done += bytes;
                self.pos += bytes as u32;
            } else {
                let n = left.min(bps - in_sector);
                let sector = vol.sector(first_sector)?;
                buf[done..done + n].copy_from_slice(&sector[in_sector..in_sector + n]);
                done += n;
                self.pos += n as u32;
            }
        }
        Ok(done)
    }

    /// Read exactly `buf.len()` bytes, or fail with
    /// [`Error::InvalidOffset`] at the end of the file.
    pub fn read_exact<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        buf: &mut [u8],
    ) -> Result<(), Error<D::Error>> {
        let n = self.read(vol, buf)?;
        if n == buf.len() {
            Ok(())
        } else {
            Err(Error::InvalidOffset)
        }
    }

    /// Write `buf` at the cursor, extending the file as needed.
    pub fn write<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        buf: &[u8],
    ) -> Result<usize, Error<D::Error>> {
        if buf.is_empty() {
            return Ok(0);
        }
        // Writing past the end leaves a gap that must read as zeros.
        if self.pos > self.len {
            let gap_to = self.pos;
            self.pos = self.len;
            self.zero_extend(vol, gap_to)?;
            self.pos = gap_to;
        }
        let len = u32::try_from(buf.len()).map_err(|_| Error::FileTooLarge)?;
        let end = self.pos.checked_add(len).ok_or(Error::FileTooLarge)?;

        let bps = vol.bps();
        let spc = vol.geom.sectors_per_cluster;
        let cb = vol.geom.cluster_bytes();
        let mut done = 0usize;

        while done < buf.len() {
            let index = self.pos / cb;
            let in_cluster = self.pos % cb;
            let left = buf.len() - done;
            // A cluster the write covers end to end needs no zeroing.
            let covers_cluster = in_cluster == 0 && left >= cb as usize;
            let cluster = self.cluster_at_or_alloc(vol, index, !covers_cluster)?;
            let sector_in_cluster = in_cluster / bps as u32;
            let in_sector = (in_cluster % bps as u32) as usize;
            let first_sector = vol.geom.cluster_first_sector(cluster) + sector_in_cluster;

            if in_sector == 0 && left >= bps {
                let max = (spc - sector_in_cluster) as usize;
                let n = (left / bps).min(max);
                let bytes = n * bps;
                vol.write_sectors_direct(first_sector, &buf[done..done + bytes])?;
                done += bytes;
                self.pos += bytes as u32;
            } else {
                let n = left.min(bps - in_sector);
                let sector = vol.sector_mut(first_sector)?;
                sector[in_sector..in_sector + n].copy_from_slice(&buf[done..done + n]);
                done += n;
                self.pos += n as u32;
            }
        }

        if end > self.len {
            self.len = end;
        }
        self.dirty = true;
        Ok(done)
    }

    /// Write all of `buf`.
    pub fn write_all<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        buf: &[u8],
    ) -> Result<(), Error<D::Error>> {
        let n = self.write(vol, buf)?;
        if n == buf.len() {
            Ok(())
        } else {
            Err(Error::NoSpace)
        }
    }

    /// Grow the file to `new_len` with zeros, from the current end.
    fn zero_extend<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        new_len: u32,
    ) -> Result<(), Error<D::Error>> {
        let cb = vol.geom.cluster_bytes();
        let bps = vol.bps();
        let mut at = self.len;
        while at < new_len {
            let index = at / cb;
            let in_cluster = at % cb;
            let left = new_len - at;
            let covers = in_cluster == 0 && left >= cb;
            // A cluster allocated here is zeroed at allocation; only the
            // tail of an already-allocated one needs clearing.
            let existed = self.cluster_at(vol, index)?.is_some();
            let cluster = self.cluster_at_or_alloc(vol, index, !covers)?;
            let step = left.min(cb - in_cluster);
            if existed {
                let mut off = in_cluster;
                let mut remaining = step;
                while remaining > 0 {
                    let sector_in_cluster = off / bps as u32;
                    let in_sector = (off % bps as u32) as usize;
                    let n = (remaining as usize).min(bps - in_sector);
                    let first = vol.geom.cluster_first_sector(cluster) + sector_in_cluster;
                    let sector = vol.sector_mut(first)?;
                    sector[in_sector..in_sector + n].fill(0);
                    off += n as u32;
                    remaining -= n as u32;
                }
            }
            at += step;
        }
        self.len = self.len.max(new_len);
        self.dirty = true;
        Ok(())
    }

    /// Resize the file. Growing fills with zeros; shrinking frees the
    /// clusters that fall off the end.
    pub fn set_len<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
        new_len: u32,
    ) -> Result<(), Error<D::Error>> {
        if new_len > self.len {
            let keep = self.pos;
            self.zero_extend(vol, new_len)?;
            self.pos = keep;
            return Ok(());
        }
        if new_len == self.len {
            return Ok(());
        }
        let cb = vol.geom.cluster_bytes();
        if new_len == 0 {
            if self.first_cluster != 0 {
                vol.free_chain(self.first_cluster)?;
            }
            self.first_cluster = 0;
            self.cur_cluster = 0;
            self.cur_index = 0;
        } else {
            // The last cluster the file still needs, counted from zero.
            let last_index = (new_len - 1) / cb;
            if let Some(cluster) = self.cluster_at(vol, last_index)? {
                vol.truncate_chain(cluster)?;
            }
        }
        self.len = new_len;
        self.pos = self.pos.min(new_len);
        self.cur_cluster = self.first_cluster;
        self.cur_index = 0;
        self.dirty = true;
        Ok(())
    }

    /// Write the file's size and first cluster back into its directory
    /// entry, then flush the volume's cached sector and the device.
    pub fn flush<D: SectorDriver, const S: usize>(
        &mut self,
        vol: &mut Volume<D, S>,
    ) -> Result<(), Error<D::Error>> {
        if self.dirty {
            vol.update_entry(self.loc, self.first_cluster, self.len)?;
            self.dirty = false;
        }
        vol.flush()
    }

    /// The file's first cluster, or 0 when it has never held data.
    pub fn first_cluster(&self) -> u32 {
        self.first_cluster
    }
}