-
Notifications
You must be signed in to change notification settings - Fork 144
/
f3brew.c
478 lines (411 loc) · 11.8 KB
/
f3brew.c
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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <argp.h>
#include <inttypes.h>
#include <err.h>
#include "version.h"
#include "libutils.h"
#include "libdevs.h"
/* Argp's global variables. */
const char *argp_program_version = "F3 BREW " F3_STR_VERSION;
/* Arguments. */
static char adoc[] = "<DISK_DEV>";
/* The capital "E" in "REad" in the string below is not a typo.
* It shows from where the name B-RE-W comes.
*/
static char doc[] = "F3 Block REad and Write -- assess the media of "
"a block device writing blocks, resetting the drive, and "
"reading the blocks back";
static struct argp_option options[] = {
{"debug", 'd', NULL, OPTION_HIDDEN,
"Enable debugging; only needed if none --debug-* option used",
1},
{"debug-real-size", 'r', "SIZE_BYTE", OPTION_HIDDEN,
"Real size of the emulated drive", 0},
{"debug-fake-size", 'f', "SIZE_BYTE", OPTION_HIDDEN,
"Fake size of the emulated drive", 0},
{"debug-wrap", 'w', "N", OPTION_HIDDEN,
"Wrap parameter of the emulated drive", 0},
{"debug-block-order", 'b', "ORDER", OPTION_HIDDEN,
"Block size of the emulated drive is 2^ORDER Bytes", 0},
{"debug-cache-order", 'c', "ORDER", OPTION_HIDDEN,
"Cache size of the emulated drive is 2^ORDER blocks", 0},
{"debug-strict-cache", 'o', NULL, OPTION_HIDDEN,
"Force the cache to be strict", 0},
{"debug-keep-file", 'k', NULL, OPTION_HIDDEN,
"Don't remove file used for emulating the drive", 0},
{"reset-type", 's', "TYPE", 0,
"Reset method to use during the probe", 2},
{"start-at", 'h', "BLOCK", 0,
"Where test begins; the default is block zero", 0},
{"end-at", 'e', "BLOCK", 0,
"Where test ends; the default is the very last block", 0},
{"do-not-write", 'W', NULL, 0,
"Do not write blocks", 0},
{"do-not-read", 'R', NULL, 0,
"Do not read blocks", 0},
{ 0 }
};
struct args {
char *filename;
/* Debugging options. */
bool debug;
bool keep_file;
/* Behavior options. */
enum reset_type reset_type;
bool test_write;
bool test_read;
/* 3 free bytes. */
/* Geometry. */
uint64_t real_size_byte;
uint64_t fake_size_byte;
int wrap;
int block_order;
int cache_order;
int strict_cache;
/* What to do. */
uint64_t first_block;
uint64_t last_block;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state)
{
struct args *args = state->input;
long long ll;
switch (key) {
case 'd':
args->debug = true;
break;
case 'r':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0)
argp_error(state,
"Real size must be greater or equal to zero");
args->real_size_byte = ll;
args->debug = true;
break;
case 'f':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0)
argp_error(state,
"Fake size must be greater or equal to zero");
args->fake_size_byte = ll;
args->debug = true;
break;
case 'w':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0 || ll >= 64)
argp_error(state,
"Wrap must be in the interval [0, 63]");
args->wrap = ll;
args->debug = true;
break;
case 'b':
ll = arg_to_ll_bytes(state, arg);
if (ll != 0 && (ll < 9 || ll > 20))
argp_error(state,
"Block order must be in the interval [9, 20] or be zero");
args->block_order = ll;
args->debug = true;
break;
case 'c':
ll = arg_to_ll_bytes(state, arg);
if (ll < -1 || ll > 64)
argp_error(state,
"Cache order must be in the interval [-1, 64]");
args->cache_order = ll;
args->debug = true;
break;
case 'o':
args->strict_cache = true;
args->debug = true;
break;
case 'k':
args->keep_file = true;
args->debug = true;
break;
case 's':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0 || ll >= RT_MAX)
argp_error(state,
"Reset type must be in the interval [0, %i]",
RT_MAX - 1);
args->reset_type = ll;
break;
case 'h':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0)
argp_error(state,
"The first block must be greater or equal to zero");
args->first_block = ll;
break;
case 'e':
ll = arg_to_ll_bytes(state, arg);
if (ll < 0)
argp_error(state,
"The last block must be greater or equal to zero");
args->last_block = ll;
break;
case 'W':
args->test_write = false;
break;
case 'R':
args->test_read = false;
break;
case ARGP_KEY_INIT:
args->filename = NULL;
break;
case ARGP_KEY_ARG:
if (args->filename)
argp_error(state,
"Wrong number of arguments; only one is allowed");
args->filename = arg;
break;
case ARGP_KEY_END:
if (!args->filename)
argp_error(state,
"The disk device was not specified");
if (args->debug &&
!dev_param_valid(args->real_size_byte,
args->fake_size_byte, args->wrap,
args->block_order))
argp_error(state,
"The debugging parameters are not valid");
if (args->first_block > args->last_block)
argp_error(state,
"The first block parameter must be less or equal to the last block parameter. They are now: first_block=%"
PRIu64 " > last_block=%" PRIu64,
args->first_block, args->last_block);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL};
static void write_blocks(struct device *dev,
uint64_t first_block, uint64_t last_block)
{
const int block_order = dev_get_block_order(dev);
const int block_size = dev_get_block_size(dev);
char stack[align_head(block_order) + BIG_BLOCK_SIZE_BYTE];
char *buffer = align_mem(stack, block_order);
char *stamp_blk = buffer;
char *flush_blk = buffer + BIG_BLOCK_SIZE_BYTE;
uint64_t offset = first_block << block_order;
uint64_t pos, first_pos = first_block;
assert(BIG_BLOCK_SIZE_BYTE >= block_size);
for (pos = first_block; pos <= last_block; pos++) {
fill_buffer_with_block(stamp_blk, block_order, offset, 0);
stamp_blk += block_size;
offset += block_size;
if (stamp_blk == flush_blk || pos == last_block) {
if (dev_write_blocks(dev, buffer, first_pos, pos))
warn("Failed to write blocks from 0x%" PRIx64
" to 0x%" PRIx64, first_pos, pos);
stamp_blk = buffer;
first_pos = pos + 1;
}
}
}
/* XXX Properly handle return errors. */
static void test_write_blocks(struct device *dev,
uint64_t first_block, uint64_t last_block)
{
printf("Writing blocks from 0x%" PRIx64 " to 0x%" PRIx64 "...",
first_block, last_block);
fflush(stdout);
write_blocks(dev, first_block, last_block);
printf(" Done\n\n");
}
enum block_state {
bs_unknown,
bs_good,
bs_bad,
bs_overwritten,
};
struct block_range {
enum block_state state;
int block_order;
uint64_t start_sector_offset;
uint64_t end_sector_offset;
/* Only used by state bs_overwritten. */
uint64_t found_sector_offset;
};
static const char *block_state_to_str(enum block_state state)
{
const char *conv_array[] = {
[bs_unknown] = "Unknown",
[bs_good] = "Good",
[bs_bad] = "Bad",
[bs_overwritten] = "Overwritten",
};
return conv_array[state];
}
static int is_block(uint64_t offset, int block_order)
{
return !(((1ULL << block_order) - 1) & offset);
}
static void print_offset(uint64_t offset, int block_order)
{
assert(is_block(offset, block_order));
printf("block 0x%" PRIx64, offset >> block_order);
}
static void print_block_range(const struct block_range *range)
{
printf("[%s] from ", block_state_to_str(range->state));
print_offset(range->start_sector_offset, range->block_order);
printf(" to ");
print_offset(range->end_sector_offset, range->block_order);
switch (range->state) {
case bs_good:
case bs_bad:
break;
case bs_overwritten:
printf(", found ");
print_offset(range->found_sector_offset, range->block_order);
break;
default:
assert(0);
break;
}
printf("\n");
}
static void validate_block(uint64_t expected_sector_offset,
const char *probe_blk, int block_order, struct block_range *range)
{
uint64_t found_sector_offset;
enum block_state state;
bool push_range;
if (validate_buffer_with_block(probe_blk, block_order,
&found_sector_offset, 0))
state = bs_bad; /* Bad block. */
else if (expected_sector_offset == found_sector_offset)
state = bs_good; /* Good block. */
else
state = bs_overwritten; /* Overwritten block. */
push_range = (range->state != state) || (
state == bs_overwritten
&& (
(expected_sector_offset
- range->start_sector_offset)
!=
(found_sector_offset
- range->found_sector_offset)
)
);
if (push_range) {
if (range->state != bs_unknown)
print_block_range(range);
range->state = state;
range->start_sector_offset = expected_sector_offset;
range->end_sector_offset = expected_sector_offset;
range->found_sector_offset = found_sector_offset;
} else {
range->end_sector_offset = expected_sector_offset;
}
}
static void read_blocks(struct device *dev,
uint64_t first_block, uint64_t last_block)
{
const int block_size = dev_get_block_size(dev);
const int block_order = dev_get_block_order(dev);
char stack[align_head(block_order) + BIG_BLOCK_SIZE_BYTE];
char *buffer = align_mem(stack, block_order);
uint64_t expected_sector_offset = first_block << block_order;
uint64_t first_pos = first_block;
uint64_t step = (BIG_BLOCK_SIZE_BYTE >> block_order) - 1;
struct block_range range = {
.state = bs_unknown,
.block_order = block_order,
.start_sector_offset = 0,
.end_sector_offset = 0,
.found_sector_offset = 0,
};
assert(BIG_BLOCK_SIZE_BYTE >= block_size);
while (first_pos <= last_block) {
char *probe_blk = buffer;
uint64_t pos, next_pos = first_pos + step;
if (next_pos > last_block)
next_pos = last_block;
if (dev_read_blocks(dev, buffer, first_pos, next_pos))
warn("Failed to read blocks from 0x%" PRIx64
" to 0x%" PRIx64, first_pos, next_pos);
for (pos = first_pos; pos <= next_pos; pos++) {
validate_block(expected_sector_offset, probe_blk,
block_order, &range);
expected_sector_offset += block_size;
probe_blk += block_size;
}
first_pos = next_pos + 1;
}
if (range.state != bs_unknown)
print_block_range(&range);
else
assert(first_block > last_block);
}
/* XXX Properly handle return errors. */
static void test_read_blocks(struct device *dev,
uint64_t first_block, uint64_t last_block)
{
printf("Reading blocks from 0x%" PRIx64 " to 0x%" PRIx64 ":\n",
first_block, last_block);
read_blocks(dev, first_block, last_block);
printf("\n");
}
int main(int argc, char **argv)
{
struct args args = {
/* Defaults. */
.debug = false,
.keep_file = false,
.reset_type = RT_MANUAL_USB,
.test_write = true,
.test_read = true,
.real_size_byte = 1ULL << 31,
.fake_size_byte = 1ULL << 34,
.wrap = 31,
.block_order = 0,
.cache_order = -1,
.strict_cache = false,
.first_block = 0,
.last_block = -1ULL,
};
struct device *dev;
uint64_t very_last_block;
/* Read parameters. */
argp_parse(&argp, argc, argv, 0, NULL, &args);
print_header(stdout, "brew");
dev = args.debug
? create_file_device(args.filename, args.real_size_byte,
args.fake_size_byte, args.wrap, args.block_order,
args.cache_order, args.strict_cache, args.keep_file)
: create_block_device(args.filename, args.reset_type);
if (!dev) {
fprintf(stderr, "\nApplication cannot continue, finishing...\n");
exit(1);
}
printf("Physical block size: 2^%i Bytes\n\n", dev_get_block_order(dev));
very_last_block =
(dev_get_size_byte(dev) >> dev_get_block_order(dev)) - 1;
if (args.first_block > very_last_block)
args.first_block = very_last_block;
if (args.last_block > very_last_block)
args.last_block = very_last_block;
if (args.test_write)
test_write_blocks(dev, args.first_block, args.last_block);
if (args.test_write && args.test_read) {
const char *final_dev_filename;
assert(!dev_reset(dev));
final_dev_filename = dev_get_filename(dev);
if (strcmp(args.filename, final_dev_filename))
printf("\nWARNING: device `%s' moved to `%s' due to the reset\n\n",
args.filename, final_dev_filename);
}
if (args.test_read)
test_read_blocks(dev, args.first_block, args.last_block);
free_device(dev);
return 0;
}