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
//! Installing a validated bundle onto a FAT volume.
//!
//! This is the half that knows what a boot partition is. It takes the
//! entries [`Bundle::parse`] handed back and writes them where they say,
//! in an order chosen so that a failure part-way through leaves a board
//! that still boots.
//!
//! # What the caller still supplies
//!
//! The transport and the reboot: how a bundle arrived is application
//! shaped, and a crate that took it would be choosing the web framework.
//! And the clock — [`Progress`] reports what happened and the caller
//! decides what to time, which is what keeps an async runtime's clock and
//! whatever counts device commands out of this crate. The timing itself is
//! [`crate::measure::Measure`], a `Progress` that takes the clock as a
//! function, for a caller that wants what every board measures.
//!
//! # Why it writes the way it does
//!
//! Two rules below look like implementation detail and are not: they are
//! the difference between an update that takes seconds and one that takes
//! minutes. The figures come from a Pi 2 writing a 1.6 MB kernel to an SD
//! card, and they are here rather than in an application because the rules
//! they justify are here.
//!
//! **The card charges per command, not per block.** A single-block write
//! cost 17.5 ms on that card; a 128-block write about 26 ms — one and a
//! half times the cost for a hundred and twenty-eight times the data, so
//! 62× cheaper per block. Nothing about the card changed between those two
//! numbers. It was being asked the wrong way.
//!
//! So: **one `write_file` per entry, with the length known up front.** The
//! chain is then allocated in one go and the file comes out contiguous,
//! which is what lets the layer underneath issue one long transfer instead
//! of thousands of short ones. A file grown a write at a time gets whatever
//! clusters happen to be spare at each step, and no amount of care further
//! down recovers from that. Through a filesystem that wrote single blocks,
//! the same kernel took 99,683 ms and 5,703 card commands; written this
//! way, 928 ms and 36. That is the whole of the 107×.
//!
//! And: **the read-back buffer is 64 KiB**, which is 128 blocks a command —
//! the same argument from the reading side.
//!
//! **Skipping costs a read, and a read is much cheaper than a write.**
//! Reading an entry back to decide whether to write it sounds like added
//! work and is how a bundle carrying 3 MB of Raspberry Pi firmware — which
//! changes about once a year — stays affordable to ship every time. A
//! 6-entry, 4.8 MB bundle whose contents the card already held cost 1055 ms
//! and **no write commands at all**, against 3724 ms to write the same
//! thing in full.
//!
//! **What is left is a floor of roughly 250 ms per entry, whatever its
//! size**, nearly all of it filesystem bookkeeping rather than data: a
//! rewrite frees the old chain and allocates a new one, each of which
//! flushes the allocation table, and FAT32 keeps two copies of it. So the
//! cost of an update scales with the *number* of entries far more than with
//! their bytes. Six files is nothing; several hundred small assets would be
//! the thing to think about.
use ;
use crate;
/// Bytes read at a time when checking what is already on the card.
///
/// Not a stack buffer: this is allocated once per [`apply`] call and reused,
/// because the size is the whole point. A card charges per command far more
/// than per block, so reading a 3 MB file 512 bytes at a time would cost
/// thousands of commands and defeat the comparison it exists to make cheap.
/// 64 KiB is 128 blocks a call — enough that the per-command cost stops
/// mattering, small enough to be nothing beside the bundle already in
/// memory.
const SCRATCH: usize = 64 * 1024;
/// What installing a bundle touched.
/// Told what the installer is doing, so that timing stays with the caller.
///
/// Every method does nothing by default, so an implementation names only
/// what it cares about, and `()` implements the whole thing for a caller
/// that cares about none of it.
///
/// The boundaries are where they are so that a caller can time the write
/// and the read-back separately. Those are not the same operation and do
/// not have the same fix — a write is erase-block program cycles and a read
/// is not — so a single figure covering both would hide which of them an
/// improvement had touched.
/// Why an install was refused or failed.
///
/// Generic over the device error, unlike [`bundle::Error`], which is why the
/// two are separate types: a host that only ever builds bundles would
/// otherwise have to name a block-device error it does not have.
/// Installs `bundle` onto `volume`.
///
/// Does **not** reboot. The caller answers whoever sent the bundle first,
/// because a failure that nobody is told about is worse than a slow one.
///
/// # Order
///
/// Entries are written in role order rather than the order they were
/// packed, and the crate imposes it rather than trusting the bundle — the
/// same reasoning that made the kernel a role instead of a position:
///
/// 1. [`Role::File`] — assets, settings, blobs. Nothing here is loaded by
/// the firmware, so a failure leaves a board that still boots what it
/// booted last time.
/// 2. [`Role::Firmware`] — `start*.elf`, `fixup*.dat`, `bootcode.bin`.
/// 3. [`Role::Config`] — `config.txt`, which the firmware does read, but
/// which cannot on its own stop a board booting: lose it and the
/// firmware falls back to loading `kernel7.img`/`kernel8.img`.
/// 4. [`Role::Kernel`] — last, because with one boot image this write **is**
/// the commit: the board boots whatever it leaves behind.
///
/// Steps 3 and 4 swap once there are two kernel slots to write to. The
/// kernel then goes to whichever is not running — which is no longer the
/// live image, and no longer the commit — and rewriting `config.txt`'s
/// `kernel=` line takes both jobs over.
///
/// # `bootcode.bin` has no safety net
///
/// The ROM loads it by name, so there is no slot to write it to and no
/// `config.txt` line to point elsewhere: a failed write there means the
/// card comes out. Three things keep that acceptable rather than merely
/// unavoidable — it is about 50 KB, it changes almost never, and on the
/// Pi 4 it lives in SPI EEPROM and is not on the card at all. Every other
/// boot file has a way out.
/// The install itself, so [`apply`] can sync after it either way.
/// Counts one entry as written or skipped.
/// Puts one entry where its path says. Returns whether it had to be written.
/// Whether the card already holds exactly `data` at `path`.
///
/// Used for both halves of the same question: before a write it decides
/// whether to write at all, and after one it is the verification. Sharing
/// the code is not only tidiness — it means a skipped entry has been
/// checked exactly as strictly as a written one.
///
/// The comparison is a checksum rather than the byte-for-byte compare an
/// in-memory copy would allow. That is a real if small weakening, and it
/// buys the ability to check a file of any size against a fixed scratch
/// buffer: the alternative is holding a 3 MB read-back beside the 3 MB
/// already in the bundle.
/// Creates every directory above `path` that is not already there.
///
/// A path in a bundle can be nested, and `write_file` resolves a parent
/// rather than making one, so a first update onto a card that has never held
/// the directory would otherwise fail at the first asset. One level at a
/// time, because that is what `create_dir` does.