Imported Debian patch 5.0-1
Joao Eriberto Mota Filho
9 years ago
0 | 0 | CC ?= gcc |
1 | CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD | |
1 | CFLAGS += -std=c99 -Wall -Wextra -pedantic -MMD -ggdb | |
2 | 2 | |
3 | 3 | TARGETS = f3write f3read |
4 | EXPERIMENTAL_TARGETS = f3probe f3brew f3fix | |
4 | 5 | |
5 | 6 | all: $(TARGETS) |
7 | experimental: $(EXPERIMENTAL_TARGETS) | |
6 | 8 | |
7 | 9 | f3write: utils.o f3write.o |
8 | 10 | $(CC) -o $@ $^ -lm |
10 | 12 | f3read: utils.o f3read.o |
11 | 13 | $(CC) -o $@ $^ |
12 | 14 | |
15 | f3probe: libutils.o libdevs.o libprobe.o utils.o f3probe.o | |
16 | $(CC) -o $@ $^ -lm -ludev | |
17 | ||
18 | f3brew: libutils.o libdevs.o f3brew.o | |
19 | $(CC) -o $@ $^ -lm -ludev | |
20 | ||
21 | f3fix: utils.o f3fix.o | |
22 | $(CC) -o $@ $^ -lparted | |
23 | ||
13 | 24 | -include *.d |
14 | 25 | |
15 | PHONY: clean | |
26 | PHONY: cscope clean | |
27 | ||
28 | cscope: | |
29 | cscope -b *.c *.h | |
16 | 30 | |
17 | 31 | clean: |
18 | rm -f *.o *.d $(TARGETS) | |
32 | rm -f *.o *.d cscope.out $(TARGETS) $(EXPERIMENTAL_TARGETS) |
0 | ### Compile on Linux, Apple Mac, Windows/Cygwin, and FreeBSD | |
0 | ### Compile stable software on Linux, Apple Mac, Windows/Cygwin, and FreeBSD | |
1 | 1 | |
2 | 2 | make |
3 | 3 | |
4 | ### Use example | |
5 | 4 | |
6 | ./f3write /media/5EBD-5C80/ | |
7 | ./f3read /media/5EBD-5C80/ | |
5 | ### Compile experimental software on Linux | |
8 | 6 | |
9 | Please replace "/media/5EBD-5C80/" with the appropriate path. | |
7 | make experimental | |
8 | ||
9 | NOTES: | |
10 | - Experimental software might compile on non-Linux platforms, but | |
11 | there is no guarantee given that they are only tested on Linux. | |
12 | - Please do not e-mail me saying that you want an experimental software | |
13 | to run on your platform; I already know that. | |
14 | - If you want experimental software to run on your platform, | |
15 | help to port them, or find someone that can port them for you. | |
16 | If you do port the software, please send me a patch to help others. | |
17 | - Currently, f3probe, f3brew, and f3fix are experimental. | |
18 | - f3probe and f3brew require version 1 of the library libudev to compile. | |
19 | On Ubuntu, you can install this library with the following command: | |
20 | sudo apt-get install libudev1 libudev-dev | |
21 | - f3fix requires version 0 of the library libparted to compile. | |
22 | On Ubuntu, you can install this library with the following command: | |
23 | sudo apt-get install libparted0-dev | |
24 | ||
25 | ||
26 | ### Use example of f3write/f3read | |
27 | ||
28 | ./f3write /media/michel/5EBD-5C80/ | |
29 | ./f3read /media/michel/5EBD-5C80/ | |
30 | ||
31 | Please replace "/media/michel/5EBD-5C80/" with the appropriate path. | |
10 | 32 | USB devices are mounted in "/Volumes" on Macs. |
11 | 33 | |
12 | ### For more information see http://oss.digirati.com.br/f3/ | |
34 | For more information see http://oss.digirati.com.br/f3/ | |
35 | ||
13 | 36 | |
14 | 37 | ### Files |
15 | 38 | |
31 | 54 | So you can personalize F3 to your specific needs. |
32 | 55 | |
33 | 56 | f3write.h2w - Script to create files exactly like H2testw. |
34 | Use example: 'f3write.h2w /media/5EBD-5C80/' | |
57 | Use example: 'f3write.h2w /media/michel/5EBD-5C80/' | |
35 | 58 | |
36 | 59 | log-f3wr - Script that runs f3write and f3read, and records |
37 | 60 | their output into a log file. |
38 | Use example: 'log-f3wr log-filename /media/5EBD-5C80/' | |
61 | Use example: 'log-f3wr log-filename /media/michel/5EBD-5C80/' | |
39 | 62 | |
40 | 63 | Please notice that all scripts and use examples above assume that |
41 | 64 | f3write, f3read, and the scripts are reachable from |
44 | 67 | prefix the use examples above with 'PATH=$PATH:./' as shown below |
45 | 68 | for the script log-f3wr: |
46 | 69 | |
47 | PATH=$PATH:./ log-f3wr log-filename /media/5EBD-5C80/ | |
70 | PATH=$PATH:./ log-f3wr log-filename /media/michel/5EBD-5C80/ | |
48 | 71 | |
49 | 72 | The current path is represented by './' in the previous example. |
0 | Version 5.0 - Dec 24, 2014 | |
1 | ||
2 | * add f3probe (experimental). | |
3 | * add f3fix (experimental). | |
4 | * fix building issue on Macs. | |
5 | ||
0 | 6 | Version 4.0 - Sep 9, 2014 |
1 | 7 | |
2 | 8 | * add support for FreeBSD. |
0 | f3 for Debian | |
1 | ------------- | |
2 | ||
3 | There are several additional information about F3 in upstream homepage. | |
4 | ||
5 | Please, see this URL: http://oss.digirati.com.br/f3 | |
6 | ||
7 | -- Joao Eriberto Mota Filho <eriberto@debian.org> Mon, 23 Feb 2015 11:55:18 -0300 |
0 | f3 (5.0-1) experimental; urgency=medium | |
1 | ||
2 | * New upstream release. | |
3 | * debian/copyright: | |
4 | - Fixed the GPL-3 license text. | |
5 | - Updated the packaging copyright years. | |
6 | * debian/patches/: reviewed the Makefile and README patches. | |
7 | * debian/README.Debian: added to point to upstream homepage. | |
8 | ||
9 | -- Joao Eriberto Mota Filho <eriberto@debian.org> Mon, 23 Feb 2015 11:19:19 -0300 | |
10 | ||
0 | 11 | f3 (4.0-2) unstable; urgency=medium |
1 | 12 | |
2 | 13 | * Bumped Standards-Version to 3.9.6. |
6 | 6 | License: GPL-3 |
7 | 7 | |
8 | 8 | Files: debian/* |
9 | Copyright: 2013-2014 Joao Eriberto Mota Filho <eriberto@debian.org> | |
9 | Copyright: 2013-2015 Joao Eriberto Mota Filho <eriberto@debian.org> | |
10 | 10 | License: GPL-3 |
11 | 11 | |
12 | 12 | License: GPL-3 |
13 | 13 | This program is free software: you can redistribute it and/or modify |
14 | 14 | it under the terms of the GNU General Public License as published by |
15 | the Free Software Foundation, either version 3 of the License. | |
15 | the Free Software Foundation, version 3 of the License. | |
16 | 16 | . |
17 | 17 | This package is distributed in the hope that it will be useful, |
18 | 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
0 | 0 | Description: add GCC hardening. |
1 | 1 | Author: Joao Eriberto Mota Filho <eriberto@debian.org> |
2 | Last-Update: 2014-09-19 | |
3 | Index: f3-4.0/Makefile | |
2 | Last-Update: 2015-02-23 | |
3 | Index: f3-5.0/Makefile | |
4 | 4 | =================================================================== |
5 | --- f3-4.0.orig/Makefile | |
6 | +++ f3-4.0/Makefile | |
7 | @@ -6,10 +6,10 @@ TARGETS = f3write f3read | |
8 | all: $(TARGETS) | |
5 | --- f3-5.0.orig/Makefile | |
6 | +++ f3-5.0/Makefile | |
7 | @@ -8,10 +8,10 @@ all: $(TARGETS) | |
8 | experimental: $(EXPERIMENTAL_TARGETS) | |
9 | 9 | |
10 | 10 | f3write: utils.o f3write.o |
11 | 11 | - $(CC) -o $@ $^ -lm |
15 | 15 | - $(CC) -o $@ $^ |
16 | 16 | + $(CC) $(LDFLAGS) -o $@ $^ |
17 | 17 | |
18 | -include *.d | |
19 | ||
18 | f3probe: libutils.o libdevs.o libprobe.o utils.o f3probe.o | |
19 | $(CC) -o $@ $^ -lm -ludev |
0 | Description: add a notice about some upstream files place and | |
1 | remove instructions for compile. | |
0 | Description: add a notice about some upstream files place. | |
2 | 1 | Author: Joao Eriberto Mota Filho <eriberto@debian.org> |
3 | Last-Update: 2014-09-19 | |
4 | Index: f3-4.0/README | |
2 | Last-Update: 2015-02-23 | |
3 | Index: f3-5.0/README | |
5 | 4 | =================================================================== |
6 | --- f3-4.0.orig/README | |
7 | +++ f3-4.0/README | |
8 | @@ -1,7 +1,3 @@ | |
9 | -### Compile on Linux, Apple Mac, Windows/Cygwin, and FreeBSD | |
10 | - | |
11 | -make | |
12 | - | |
13 | ### Use example | |
14 | ||
15 | ./f3write /media/5EBD-5C80/ | |
16 | @@ -38,6 +34,8 @@ log-f3wr - Script that runs f3write and | |
5 | --- f3-5.0.orig/README | |
6 | +++ f3-5.0/README | |
7 | @@ -61,6 +61,8 @@ log-f3wr - Script that runs f3write and | |
17 | 8 | their output into a log file. |
18 | Use example: 'log-f3wr log-filename /media/5EBD-5C80/' | |
9 | Use example: 'log-f3wr log-filename /media/michel/5EBD-5C80/' | |
19 | 10 | |
20 | 11 | +(On Debian systems these files are in /usr/share/f3/ directory.) |
21 | 12 | + |
0 | #include <stdio.h> | |
1 | #include <stdbool.h> | |
2 | #include <assert.h> | |
3 | #include <string.h> | |
4 | #include <inttypes.h> | |
5 | #include <err.h> | |
6 | ||
7 | #include "libutils.h" | |
8 | #include "libdevs.h" | |
9 | #include "utils.h" | |
10 | ||
11 | /* XXX Add parameters. */ | |
12 | ||
13 | /* XXX Avoid code duplication. This function is copied from f3write.c. */ | |
14 | static uint64_t fill_buffer(void *buf, size_t size, uint64_t offset) | |
15 | { | |
16 | uint8_t *p, *ptr_next_sector, *ptr_end; | |
17 | uint64_t rn; | |
18 | ||
19 | assert(size > 0); | |
20 | assert(size % SECTOR_SIZE == 0); | |
21 | assert(SECTOR_SIZE >= sizeof(offset) + sizeof(rn)); | |
22 | assert((SECTOR_SIZE - sizeof(offset)) % sizeof(rn) == 0); | |
23 | ||
24 | p = buf; | |
25 | ptr_end = p + size; | |
26 | while (p < ptr_end) { | |
27 | rn = offset; | |
28 | memmove(p, &offset, sizeof(offset)); | |
29 | ptr_next_sector = p + SECTOR_SIZE; | |
30 | p += sizeof(offset); | |
31 | for (; p < ptr_next_sector; p += sizeof(rn)) { | |
32 | rn = random_number(rn); | |
33 | memmove(p, &rn, sizeof(rn)); | |
34 | } | |
35 | assert(p == ptr_next_sector); | |
36 | offset += SECTOR_SIZE; | |
37 | } | |
38 | ||
39 | return offset; | |
40 | } | |
41 | ||
42 | static void write_blocks(char *stamp_blk, struct device *dev, | |
43 | uint64_t first_block, uint64_t last_block) | |
44 | { | |
45 | const int block_size = dev_get_block_size(dev); | |
46 | uint64_t sector_offset = first_block << dev_get_block_order(dev); | |
47 | uint64_t i; | |
48 | ||
49 | for (i = first_block; i <= last_block; i++) { | |
50 | sector_offset = | |
51 | fill_buffer(stamp_blk, block_size, sector_offset); | |
52 | if (dev_write_block(dev, stamp_blk, i)) | |
53 | warn("Failed writing block 0x%" PRIx64, i); | |
54 | } | |
55 | } | |
56 | ||
57 | /* XXX Avoid code duplication. Some code of this function is copied from | |
58 | * f3read.c. | |
59 | */ | |
60 | /* XXX Group the results so it is not too verbose. | |
61 | * For now, the less important reports are commented. | |
62 | */ | |
63 | #define TOLERANCE 2 | |
64 | static void validate_sector(uint64_t expected_sector_offset, | |
65 | const char *sector) | |
66 | { | |
67 | uint64_t sector_offset, rn; | |
68 | const char *p, *ptr_end; | |
69 | int error_count; | |
70 | ||
71 | sector_offset = *((__typeof__(sector_offset) *) sector); | |
72 | rn = sector_offset; | |
73 | p = sector + sizeof(sector_offset); | |
74 | ptr_end = sector + SECTOR_SIZE; | |
75 | error_count = 0; | |
76 | for (; error_count <= TOLERANCE && p < ptr_end; p += sizeof(rn)) { | |
77 | rn = random_number(rn); | |
78 | if (rn != *((__typeof__(rn) *) p)) | |
79 | error_count++; | |
80 | } | |
81 | ||
82 | if (sector_offset == expected_sector_offset) { | |
83 | if (error_count == 0) | |
84 | /*printf("GOOD sector 0x%" PRIx64 "\n", | |
85 | expected_sector_offset)*/; | |
86 | else if (error_count <= TOLERANCE) | |
87 | printf("Changed sector 0x%" PRIx64 "\n", | |
88 | expected_sector_offset); | |
89 | else | |
90 | printf("BAD matching sector 0x%" PRIx64 "\n", | |
91 | expected_sector_offset); | |
92 | } else if (error_count == 0) { | |
93 | printf("Overwritten sector 0x%" PRIx64 | |
94 | ", found 0x%" PRIx64 "\n", | |
95 | expected_sector_offset, sector_offset); | |
96 | } else if (error_count <= TOLERANCE) { | |
97 | printf("Overwritten and changed sector 0x%" PRIx64 | |
98 | ", found 0x%" PRIx64 "\n", | |
99 | expected_sector_offset, sector_offset); | |
100 | } else { | |
101 | /*printf("BAD sector 0x%" PRIx64 "\n", expected_sector_offset)*/; | |
102 | } | |
103 | } | |
104 | ||
105 | static void validate_block(uint64_t expected_sector_offset, | |
106 | const char *probe_blk, int block_size) | |
107 | { | |
108 | const char *sector = probe_blk; | |
109 | const char *stop_sector = sector + block_size; | |
110 | ||
111 | assert(block_size % SECTOR_SIZE == 0); | |
112 | ||
113 | while (sector < stop_sector) { | |
114 | validate_sector(expected_sector_offset, sector); | |
115 | expected_sector_offset += SECTOR_SIZE; | |
116 | sector += SECTOR_SIZE; | |
117 | } | |
118 | } | |
119 | ||
120 | static void read_blocks(char *probe_blk, struct device *dev, | |
121 | uint64_t first_block, uint64_t last_block) | |
122 | { | |
123 | const int block_size = dev_get_block_size(dev); | |
124 | uint64_t expected_sector_offset = | |
125 | first_block << dev_get_block_order(dev); | |
126 | uint64_t i; | |
127 | ||
128 | for (i = first_block; i <= last_block; i++) { | |
129 | if (!dev_read_block(dev, probe_blk, i)) | |
130 | validate_block(expected_sector_offset, probe_blk, | |
131 | block_size); | |
132 | else | |
133 | warn("Failed reading block 0x%" PRIx64, i); | |
134 | expected_sector_offset += block_size; | |
135 | } | |
136 | } | |
137 | ||
138 | /* XXX Properly handle return errors. */ | |
139 | static void write_and_read_blocks(struct device *dev, | |
140 | uint64_t first_block, uint64_t last_block) | |
141 | { | |
142 | const int block_size = dev_get_block_size(dev); | |
143 | char stack[511 + block_size]; | |
144 | char *blk = align_512(stack); | |
145 | ||
146 | printf("Writing blocks from 0x%" PRIx64 " to 0x%" PRIx64 "...", | |
147 | first_block, last_block); | |
148 | fflush(stdout); | |
149 | write_blocks(blk, dev, first_block, last_block); | |
150 | printf(" Done\n\n"); | |
151 | ||
152 | assert(!dev_reset(dev)); | |
153 | ||
154 | printf("Reading those blocks..."); | |
155 | fflush(stdout); | |
156 | read_blocks(blk, dev, first_block, last_block); | |
157 | printf(" Done\n\n"); | |
158 | } | |
159 | ||
160 | int main(void) | |
161 | { | |
162 | struct device *dev; | |
163 | uint64_t first_block = 20; | |
164 | uint64_t last_block = 1ULL << 18; | |
165 | ||
166 | dev = false | |
167 | ? create_file_device("xuxu", | |
168 | 1ULL << 21, /* Real size. */ | |
169 | 1ULL << 30, /* Fake size. */ | |
170 | 22, /* Wrap. */ | |
171 | 9, /* Block order. */ | |
172 | false) /* Keep file? */ | |
173 | : create_block_device("/dev/sdc", 0, RT_MANUAL_USB); | |
174 | assert(dev); | |
175 | ||
176 | write_and_read_blocks(dev, first_block, last_block); | |
177 | ||
178 | free_device(dev); | |
179 | return 0; | |
180 | } |
0 | #include <stdbool.h> | |
1 | #include <argp.h> | |
2 | #include <parted/parted.h> | |
3 | ||
4 | #include "version.h" | |
5 | ||
6 | /* XXX Refactor utils library since f3probe barely uses it. */ | |
7 | #include "utils.h" | |
8 | ||
9 | /* Argp's global variables. */ | |
10 | const char *argp_program_version = "F3 Fix " F3_STR_VERSION; | |
11 | ||
12 | /* Arguments. */ | |
13 | static char adoc[] = "<DISK_DEV>"; | |
14 | ||
15 | static char doc[] = "F3 Fix -- edit the partition table of " | |
16 | "a fake flash drive to have a single partition that fully covers " | |
17 | "the real capacity of the drive"; | |
18 | ||
19 | static struct argp_option options[] = { | |
20 | {"disk-type", 'd', "TYPE", 0, | |
21 | "Disk type of the partition table", 2}, | |
22 | {"fs-type", 'f', "TYPE", 0, | |
23 | "Type of the file system of the partition", 0}, | |
24 | {"boot", 'b', NULL, 0, | |
25 | "Mark the partition for boot", 0}, | |
26 | {"no-boot", 'n', NULL, 0, | |
27 | "Do not mark the partition for boot", 0}, | |
28 | {"first-sec", 'a', "SEC-NUM", 0, | |
29 | "Sector where the partition starts", 0}, | |
30 | {"last-sec", 'l', "SEC-NUM", 0, | |
31 | "Sector where the partition ends", 0}, | |
32 | {"list-disk-types", 'k', NULL, 0, | |
33 | "List all supported disk types", 3}, | |
34 | {"list-fs-types", 's', NULL, 0, | |
35 | "List all supported types of file systems", 0}, | |
36 | { 0 } | |
37 | }; | |
38 | ||
39 | struct args { | |
40 | bool list_disk_types; | |
41 | bool list_fs_types; | |
42 | ||
43 | bool boot; | |
44 | ||
45 | /* 29 free bytes. */ | |
46 | ||
47 | const char *dev_filename; | |
48 | PedDiskType *disk_type; | |
49 | PedFileSystemType *fs_type; | |
50 | PedSector first_sec; | |
51 | PedSector last_sec; | |
52 | }; | |
53 | ||
54 | static long long arg_to_long_long(const struct argp_state *state, | |
55 | const char *arg) | |
56 | { | |
57 | char *end; | |
58 | long long ll = strtoll(arg, &end, 0); | |
59 | if (!arg) | |
60 | argp_error(state, "An integer must be provided"); | |
61 | if (!*arg || *end) | |
62 | argp_error(state, "`%s' is not an integer", arg); | |
63 | return ll; | |
64 | } | |
65 | ||
66 | static error_t parse_opt(int key, char *arg, struct argp_state *state) | |
67 | { | |
68 | struct args *args = state->input; | |
69 | long long ll; | |
70 | ||
71 | switch (key) { | |
72 | case 'd': | |
73 | args->disk_type = ped_disk_type_get(arg); | |
74 | if (!args->disk_type) | |
75 | argp_error(state, | |
76 | "Disk type `%s' is not supported; use --list-disk-types to see the supported types"); | |
77 | break; | |
78 | ||
79 | case 'f': | |
80 | args->fs_type = ped_file_system_type_get(arg); | |
81 | if (!args->fs_type) | |
82 | argp_error(state, | |
83 | "File system type `%s' is not supported; use --list-fs-types to see the supported types"); | |
84 | break; | |
85 | ||
86 | case 'b': | |
87 | args->boot = true; | |
88 | break; | |
89 | ||
90 | case 'n': | |
91 | args->boot = false; | |
92 | break; | |
93 | ||
94 | case 'a': | |
95 | ll = arg_to_long_long(state, arg); | |
96 | if (ll < 0) | |
97 | argp_error(state, | |
98 | "First sector must be greater or equal to 0"); | |
99 | args->first_sec = ll; | |
100 | break; | |
101 | ||
102 | case 'l': | |
103 | ll = arg_to_long_long(state, arg); | |
104 | if (ll < 0) | |
105 | argp_error(state, | |
106 | "Last sector must be greater or equal to 0"); | |
107 | args->last_sec = ll; | |
108 | break; | |
109 | ||
110 | case 'k': | |
111 | args->list_disk_types = true; | |
112 | break; | |
113 | ||
114 | case 's': | |
115 | args->list_fs_types = true; | |
116 | break; | |
117 | ||
118 | case ARGP_KEY_INIT: | |
119 | args->dev_filename = NULL; | |
120 | args->last_sec = -1; | |
121 | break; | |
122 | ||
123 | case ARGP_KEY_ARG: | |
124 | if (args->dev_filename) | |
125 | argp_error(state, | |
126 | "Wrong number of arguments; only one is allowed"); | |
127 | args->dev_filename = arg; | |
128 | break; | |
129 | ||
130 | case ARGP_KEY_END: | |
131 | if (args->list_disk_types || args->list_fs_types) | |
132 | break; | |
133 | ||
134 | if (!args->dev_filename) | |
135 | argp_error(state, | |
136 | "The disk device was not specified"); | |
137 | ||
138 | if (args->last_sec < 0) | |
139 | argp_error(state, | |
140 | "Option --last-sec is required"); | |
141 | if (args->first_sec > args->last_sec) | |
142 | argp_error(state, | |
143 | "Option --fist_sec must be less or equal to option --last_sec"); | |
144 | break; | |
145 | ||
146 | default: | |
147 | return ARGP_ERR_UNKNOWN; | |
148 | } | |
149 | return 0; | |
150 | } | |
151 | ||
152 | static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL}; | |
153 | ||
154 | static void list_disk_types(void) | |
155 | { | |
156 | PedDiskType *type; | |
157 | int i = 0; | |
158 | printf("Disk types:\n"); | |
159 | for (type = ped_disk_type_get_next(NULL); type; | |
160 | type = ped_disk_type_get_next(type)) { | |
161 | printf("%s\t", type->name); | |
162 | i++; | |
163 | if (i == 5) { | |
164 | printf("\n"); | |
165 | i = 0; | |
166 | } | |
167 | } | |
168 | if (i > 0) | |
169 | printf("\n"); | |
170 | printf("\n"); | |
171 | } | |
172 | ||
173 | static void list_fs_types(void) | |
174 | { | |
175 | PedFileSystemType *fs_type; | |
176 | int i = 0; | |
177 | printf("File system types:\n"); | |
178 | for (fs_type = ped_file_system_type_get_next(NULL); fs_type; | |
179 | fs_type = ped_file_system_type_get_next(fs_type)) { | |
180 | printf("%s\t", fs_type->name); | |
181 | i++; | |
182 | if (i == 5) { | |
183 | printf("\n"); | |
184 | i = 0; | |
185 | } | |
186 | } | |
187 | if (i > 0) | |
188 | printf("\n"); | |
189 | printf("\n"); | |
190 | } | |
191 | ||
192 | /* 0 on failure, 1 otherwise. */ | |
193 | static int fix_disk(PedDevice *dev, PedDiskType *type, | |
194 | PedFileSystemType *fs_type, int boot, PedSector start, PedSector end) | |
195 | { | |
196 | PedDisk *disk; | |
197 | PedPartition *part; | |
198 | PedGeometry *geom; | |
199 | PedConstraint *constraint; | |
200 | int ret = 0; | |
201 | ||
202 | disk = ped_disk_new_fresh(dev, type); | |
203 | if (!disk) | |
204 | goto out; | |
205 | ||
206 | part = ped_partition_new(disk, PED_PARTITION_NORMAL, | |
207 | fs_type, start, end); | |
208 | if (!part) | |
209 | goto disk; | |
210 | if (boot && !ped_partition_set_flag(part, PED_PARTITION_BOOT, 1)) | |
211 | goto part; | |
212 | ||
213 | geom = ped_geometry_new(dev, start, end - start + 1); | |
214 | if (!geom) | |
215 | goto part; | |
216 | constraint = ped_constraint_exact(geom); | |
217 | ped_geometry_destroy(geom); | |
218 | if (!constraint) | |
219 | goto part; | |
220 | ||
221 | ret = ped_disk_add_partition(disk, part, constraint); | |
222 | ped_constraint_destroy(constraint); | |
223 | if (!ret) | |
224 | goto part; | |
225 | /* ped_disk_print(disk); */ | |
226 | ||
227 | ret = ped_disk_commit(disk); | |
228 | goto disk; | |
229 | ||
230 | part: | |
231 | ped_partition_destroy(part); | |
232 | disk: | |
233 | ped_disk_destroy(disk); | |
234 | out: | |
235 | return ret; | |
236 | } | |
237 | ||
238 | int main (int argc, char *argv[]) | |
239 | { | |
240 | struct args args = { | |
241 | /* Defaults. */ | |
242 | .list_disk_types = false, | |
243 | .list_fs_types = false, | |
244 | ||
245 | .boot = true, | |
246 | ||
247 | .disk_type = ped_disk_type_get("msdos"), | |
248 | .fs_type = ped_file_system_type_get("fat32"), | |
249 | .first_sec = 2048, /* Skip first 1MB. */ | |
250 | }; | |
251 | ||
252 | PedDevice *dev; | |
253 | int ret; | |
254 | ||
255 | /* Read parameters. */ | |
256 | argp_parse(&argp, argc, argv, 0, NULL, &args); | |
257 | print_header(stdout, "fix"); | |
258 | ||
259 | if (args.list_disk_types) | |
260 | list_disk_types(); | |
261 | ||
262 | if (args.list_fs_types) | |
263 | list_fs_types(); | |
264 | ||
265 | if (args.list_disk_types || args.list_fs_types) { | |
266 | /* If the user has asked for the types, | |
267 | * so she doesn't want to fix the drive yet. | |
268 | */ | |
269 | return 0; | |
270 | } | |
271 | ||
272 | /* XXX If @dev is a partition, refer the user to | |
273 | * the disk of this partition. | |
274 | */ | |
275 | dev = ped_device_get(args.dev_filename); | |
276 | if (!dev) | |
277 | return 1; | |
278 | ||
279 | ret = !fix_disk(dev, args.disk_type, args.fs_type, args.boot, | |
280 | args.first_sec, args.last_sec); | |
281 | printf("Drive `%s' was successfully fixed\n", args.dev_filename); | |
282 | ped_device_destroy(dev); | |
283 | return ret; | |
284 | } |
0 | #define _POSIX_C_SOURCE 200809L | |
1 | ||
2 | #include <stdio.h> | |
3 | #include <stdlib.h> | |
4 | #include <string.h> | |
5 | #include <argp.h> | |
6 | #include <stdbool.h> | |
7 | #include <assert.h> | |
8 | #include <inttypes.h> | |
9 | #include <sys/time.h> | |
10 | ||
11 | #include "version.h" | |
12 | #include "libprobe.h" | |
13 | ||
14 | /* XXX Refactor utils library since f3probe barely uses it. */ | |
15 | #include "utils.h" | |
16 | ||
17 | /* Argp's global variables. */ | |
18 | const char *argp_program_version = "F3 Probe " F3_STR_VERSION; | |
19 | ||
20 | /* Arguments. */ | |
21 | static char adoc[] = "<DISK_DEV>"; | |
22 | ||
23 | static char doc[] = "F3 Probe -- probe a block device for " | |
24 | "counterfeit flash memory. If counterfeit, " | |
25 | "f3probe identifies the fake type and real memory size"; | |
26 | ||
27 | static struct argp_option options[] = { | |
28 | {"debug", 'd', NULL, OPTION_HIDDEN, | |
29 | "Enable debugging; only needed if none --debug-* option used", | |
30 | 1}, | |
31 | {"debug-real-size", 'r', "SIZE_BYTE", OPTION_HIDDEN, | |
32 | "Real size of the emulated drive", 0}, | |
33 | {"debug-fake-size", 'f', "SIZE_BYTE", OPTION_HIDDEN, | |
34 | "Fake size of the emulated drive", 0}, | |
35 | {"debug-wrap", 'w', "N", OPTION_HIDDEN, | |
36 | "Wrap parameter of the emulated drive", 0}, | |
37 | {"debug-keep-file", 'k', NULL, OPTION_HIDDEN, | |
38 | "Don't remove file used for emulating the drive", 0}, | |
39 | {"debug-unit-test", 'u', NULL, OPTION_HIDDEN, | |
40 | "Run a unit test; it ignores all other debug options", 0}, | |
41 | {"block-order", 'b', "ORDER", 0, | |
42 | "Force block size of the drive to 2^ORDER Bytes", 2}, | |
43 | {"destructive", 'n', NULL, 0, | |
44 | "Do not restore blocks of the device after probing it", 0}, | |
45 | {"min-memory", 'l', NULL, 0, | |
46 | "Trade speed for less use of memory", 0}, | |
47 | {"reset-type", 's', "TYPE", 0, | |
48 | "Reset method to use during the probe", 0}, | |
49 | {"time-ops", 't', NULL, 0, | |
50 | "Time reads, writes, and resets", 0}, | |
51 | { 0 } | |
52 | }; | |
53 | ||
54 | struct args { | |
55 | char *filename; | |
56 | ||
57 | /* Debugging options. */ | |
58 | bool debug; | |
59 | bool unit_test; | |
60 | bool keep_file; | |
61 | ||
62 | /* Behavior options. */ | |
63 | bool save; | |
64 | bool min_mem; | |
65 | enum reset_type reset_type; | |
66 | bool time_ops; | |
67 | /* 1 free bytes. */ | |
68 | ||
69 | /* Geometry. */ | |
70 | uint64_t real_size_byte; | |
71 | uint64_t fake_size_byte; | |
72 | int wrap; | |
73 | int block_order; | |
74 | }; | |
75 | ||
76 | static long long arg_to_long_long(const struct argp_state *state, | |
77 | const char *arg) | |
78 | { | |
79 | char *end; | |
80 | long long ll = strtoll(arg, &end, 0); | |
81 | if (end == arg) | |
82 | argp_error(state, "An integer must be provided"); | |
83 | ||
84 | /* Deal with units. */ | |
85 | switch (*end) { | |
86 | case 's': | |
87 | case 'S': /* Sectors */ | |
88 | ll <<= 9; | |
89 | end++; | |
90 | break; | |
91 | ||
92 | case 'k': | |
93 | case 'K': /* KB */ | |
94 | ll <<= 10; | |
95 | end++; | |
96 | break; | |
97 | ||
98 | case 'm': | |
99 | case 'M': /* MB */ | |
100 | ll <<= 20; | |
101 | end++; | |
102 | break; | |
103 | ||
104 | case 'g': | |
105 | case 'G': /* GB */ | |
106 | ll <<= 30; | |
107 | end++; | |
108 | break; | |
109 | ||
110 | case 't': | |
111 | case 'T': /* TB */ | |
112 | ll <<= 40; | |
113 | end++; | |
114 | break; | |
115 | } | |
116 | ||
117 | if (*end) | |
118 | argp_error(state, "`%s' is not an integer", arg); | |
119 | return ll; | |
120 | } | |
121 | ||
122 | static error_t parse_opt(int key, char *arg, struct argp_state *state) | |
123 | { | |
124 | struct args *args = state->input; | |
125 | long long ll; | |
126 | ||
127 | switch (key) { | |
128 | case 'd': | |
129 | args->debug = true; | |
130 | break; | |
131 | ||
132 | case 'r': | |
133 | ll = arg_to_long_long(state, arg); | |
134 | if (ll < 0) | |
135 | argp_error(state, | |
136 | "Real size must be greater or equal to zero"); | |
137 | args->real_size_byte = ll; | |
138 | args->debug = true; | |
139 | break; | |
140 | ||
141 | case 'f': | |
142 | ll = arg_to_long_long(state, arg); | |
143 | if (ll < 0) | |
144 | argp_error(state, | |
145 | "Fake size must be greater or equal to zero"); | |
146 | args->fake_size_byte = ll; | |
147 | args->debug = true; | |
148 | break; | |
149 | ||
150 | case 'w': | |
151 | ll = arg_to_long_long(state, arg); | |
152 | if (ll < 0 || ll >= 64) | |
153 | argp_error(state, | |
154 | "Wrap must be in the interval [0, 63]"); | |
155 | args->wrap = ll; | |
156 | args->debug = true; | |
157 | break; | |
158 | ||
159 | case 'b': | |
160 | ll = arg_to_long_long(state, arg); | |
161 | if (ll != 0 && (ll < 9 || ll > 20)) | |
162 | argp_error(state, | |
163 | "Block order must be in the interval [9, 20] or be zero"); | |
164 | args->block_order = ll; | |
165 | break; | |
166 | ||
167 | case 'k': | |
168 | args->keep_file = true; | |
169 | break; | |
170 | ||
171 | case 'u': | |
172 | args->unit_test = true; | |
173 | break; | |
174 | ||
175 | case 'n': | |
176 | args->save = false; | |
177 | break; | |
178 | ||
179 | case 'l': | |
180 | args->min_mem = true; | |
181 | break; | |
182 | ||
183 | case 's': | |
184 | ll = arg_to_long_long(state, arg); | |
185 | if (ll < 0 || ll >= RT_MAX) | |
186 | argp_error(state, | |
187 | "Reset type must be in the interval [0, %i]", | |
188 | RT_MAX - 1); | |
189 | args->reset_type = ll; | |
190 | break; | |
191 | ||
192 | case 't': | |
193 | args->time_ops = true; | |
194 | break; | |
195 | ||
196 | case ARGP_KEY_INIT: | |
197 | args->filename = NULL; | |
198 | break; | |
199 | ||
200 | case ARGP_KEY_ARG: | |
201 | if (args->filename) | |
202 | argp_error(state, | |
203 | "Wrong number of arguments; only one is allowed"); | |
204 | args->filename = arg; | |
205 | break; | |
206 | ||
207 | case ARGP_KEY_END: | |
208 | if (!args->filename) | |
209 | argp_error(state, | |
210 | "The disk device was not specified"); | |
211 | if (args->debug && | |
212 | !dev_param_valid(args->real_size_byte, | |
213 | args->fake_size_byte, args->wrap, | |
214 | args->block_order)) | |
215 | argp_error(state, | |
216 | "The debugging parameters are not valid"); | |
217 | break; | |
218 | ||
219 | default: | |
220 | return ARGP_ERR_UNKNOWN; | |
221 | } | |
222 | return 0; | |
223 | } | |
224 | ||
225 | static struct argp argp = {options, parse_opt, adoc, doc, NULL, NULL, NULL}; | |
226 | ||
227 | struct unit_test_item { | |
228 | uint64_t real_size_byte; | |
229 | uint64_t fake_size_byte; | |
230 | int wrap; | |
231 | int block_order; | |
232 | }; | |
233 | ||
234 | static const struct unit_test_item ftype_to_params[] = { | |
235 | /* Smallest good drive. */ | |
236 | {1ULL << 20, 1ULL << 20, 20, 9}, | |
237 | ||
238 | /* Good, 4KB-block, 1GB drive. */ | |
239 | {1ULL << 30, 1ULL << 30, 30, 12}, | |
240 | ||
241 | /* Bad drive. */ | |
242 | {0, 1ULL << 30, 30, 9}, | |
243 | ||
244 | /* Geometry of a real limbo drive. */ | |
245 | {1777645568ULL, 32505331712ULL, 35, 9}, | |
246 | ||
247 | /* Wraparound drive. */ | |
248 | {1ULL << 31, 1ULL << 34, 31, 9}, | |
249 | ||
250 | /* Chain drive. */ | |
251 | {1ULL << 31, 1ULL << 34, 32, 9}, | |
252 | ||
253 | /* Extreme case for memory usage (limbo drive). */ | |
254 | {1ULL << 20, 1ULL << 40, 40, 9}, | |
255 | }; | |
256 | ||
257 | #define UNIT_TEST_N_CASES \ | |
258 | ((int)(sizeof(ftype_to_params)/sizeof(struct unit_test_item))) | |
259 | ||
260 | static int unit_test(const char *filename) | |
261 | { | |
262 | int i, success = 0; | |
263 | for (i = 0; i < UNIT_TEST_N_CASES; i++) { | |
264 | const struct unit_test_item *item = &ftype_to_params[i]; | |
265 | enum fake_type origin_type = dev_param_to_type( | |
266 | item->real_size_byte, item->fake_size_byte, | |
267 | item->wrap, item->block_order); | |
268 | double f_real = item->real_size_byte; | |
269 | double f_fake = item->fake_size_byte; | |
270 | const char *unit_real = adjust_unit(&f_real); | |
271 | const char *unit_fake = adjust_unit(&f_fake); | |
272 | ||
273 | enum fake_type fake_type; | |
274 | uint64_t real_size_byte, announced_size_byte; | |
275 | int wrap, block_order, max_probe_blocks; | |
276 | struct device *dev; | |
277 | ||
278 | dev = create_file_device(filename, item->real_size_byte, | |
279 | item->fake_size_byte, item->wrap, item->block_order, | |
280 | false); | |
281 | assert(dev); | |
282 | max_probe_blocks = probe_device_max_blocks(dev); | |
283 | assert(!probe_device(dev, &real_size_byte, &announced_size_byte, | |
284 | &wrap, &block_order)); | |
285 | free_device(dev); | |
286 | fake_type = dev_param_to_type(real_size_byte, | |
287 | announced_size_byte, wrap, block_order); | |
288 | ||
289 | /* Report */ | |
290 | printf("Test %i\t\ttype/real size/fake size/module/block size\n", | |
291 | i + 1); | |
292 | printf("\t\t%s/%.2f %s/%.2f %s/2^%i Byte/2^%i Byte\n", | |
293 | fake_type_to_name(origin_type), | |
294 | f_real, unit_real, f_fake, unit_fake, item->wrap, | |
295 | item->block_order); | |
296 | if (real_size_byte == item->real_size_byte && | |
297 | announced_size_byte == item->fake_size_byte && | |
298 | wrap == item->wrap && | |
299 | block_order == item->block_order) { | |
300 | success++; | |
301 | printf("\t\tPerfect!\tMax # of probed blocks: %i\n\n", | |
302 | max_probe_blocks); | |
303 | } else { | |
304 | double ret_f_real = real_size_byte; | |
305 | double ret_f_fake = announced_size_byte; | |
306 | const char *ret_unit_real = adjust_unit(&ret_f_real); | |
307 | const char *ret_unit_fake = adjust_unit(&ret_f_fake); | |
308 | printf("\tError\t%s/%.2f %s/%.2f %s/2^%i Byte/2^%i Byte\n\n", | |
309 | fake_type_to_name(fake_type), | |
310 | ret_f_real, ret_unit_real, | |
311 | ret_f_fake, ret_unit_fake, wrap, block_order); | |
312 | } | |
313 | } | |
314 | ||
315 | printf("SUMMARY: "); | |
316 | if (success == UNIT_TEST_N_CASES) | |
317 | printf("Perfect!\n"); | |
318 | else | |
319 | printf("Missed %i tests out of %i\n", | |
320 | UNIT_TEST_N_CASES - success, UNIT_TEST_N_CASES); | |
321 | return 0; | |
322 | } | |
323 | ||
324 | static void report_size(const char *prefix, uint64_t bytes, int block_order) | |
325 | { | |
326 | double f = bytes; | |
327 | const char *unit = adjust_unit(&f); | |
328 | printf("%s %.2f %s (%" PRIu64 " blocks)\n", prefix, f, unit, | |
329 | bytes >> block_order); | |
330 | } | |
331 | ||
332 | static void report_order(const char *prefix, int order) | |
333 | { | |
334 | double f = (1ULL << order); | |
335 | const char *unit = adjust_unit(&f); | |
336 | printf("%s %.2f %s (2^%i Bytes)\n", prefix, f, unit, order); | |
337 | } | |
338 | ||
339 | static void report_sector(const char *prefix, uint64_t sector) | |
340 | { | |
341 | printf("%s %" PRIu64 "\n", prefix, sector); | |
342 | } | |
343 | ||
344 | static void report_ops(const char *op, uint64_t count, uint64_t time_us) | |
345 | { | |
346 | printf("Probe %s op: count=%" PRIu64 | |
347 | ", total time=%.2fs, avg op time=%.2fms\n", | |
348 | op, count, time_us / 1e6, (time_us / count) / 1e3); | |
349 | } | |
350 | ||
351 | static int test_device(struct args *args) | |
352 | { | |
353 | struct timeval t1, t2; | |
354 | double time_s; | |
355 | struct device *dev, *pdev; | |
356 | enum fake_type fake_type; | |
357 | uint64_t real_size_byte, announced_size_byte; | |
358 | int wrap, block_order; | |
359 | uint64_t read_count, read_time_us; | |
360 | uint64_t write_count, write_time_us; | |
361 | uint64_t reset_count, reset_time_us; | |
362 | const char *final_dev_filename; | |
363 | uint64_t last_good_sector; | |
364 | ||
365 | dev = args->debug | |
366 | ? create_file_device(args->filename, args->real_size_byte, | |
367 | args->fake_size_byte, args->wrap, args->block_order, | |
368 | args->keep_file) | |
369 | : create_block_device(args->filename, args->block_order, | |
370 | args->reset_type); | |
371 | if (!dev) { | |
372 | fprintf(stderr, "\nApplication cannot continue, finishing...\n"); | |
373 | exit(1); | |
374 | } | |
375 | ||
376 | if (args->time_ops) { | |
377 | pdev = create_perf_device(dev); | |
378 | assert(pdev); | |
379 | dev = pdev; | |
380 | } else { | |
381 | pdev = NULL; | |
382 | } | |
383 | ||
384 | if (args->save) { | |
385 | struct device *sdev = create_safe_device(dev, | |
386 | probe_device_max_blocks(dev), args->min_mem); | |
387 | if (!sdev) { | |
388 | if (!args->min_mem) | |
389 | fprintf(stderr, "Out of memory, try `f3probe --min-memory %s'\n", | |
390 | dev_get_filename(dev)); | |
391 | else | |
392 | fprintf(stderr, "Out of memory, try `f3probe --destructive %s'\nPlease back your data up before using option --destructive.\nAlternatively, you could use a machine with more memory to run f3probe.\n", | |
393 | dev_get_filename(dev)); | |
394 | exit(1); | |
395 | } | |
396 | dev = sdev; | |
397 | } | |
398 | ||
399 | assert(!gettimeofday(&t1, NULL)); | |
400 | /* XXX Have a better error handling to recover | |
401 | * the state of the drive. | |
402 | */ | |
403 | assert(!probe_device(dev, &real_size_byte, &announced_size_byte, | |
404 | &wrap, &block_order)); | |
405 | assert(!gettimeofday(&t2, NULL)); | |
406 | ||
407 | if (!args->debug && args->reset_type == RT_MANUAL_USB) { | |
408 | printf("CAUTION\t\tCAUTION\t\tCAUTION\n"); | |
409 | printf("No more resets are needed, so do not unplug the drive\n"); | |
410 | fflush(stdout); | |
411 | } | |
412 | ||
413 | /* Keep free_device() as close of probe_device() as possible to | |
414 | * make sure that the written blocks are recovered when | |
415 | * @args->save is true. | |
416 | */ | |
417 | if (args->time_ops) | |
418 | perf_device_sample(pdev, | |
419 | &read_count, &read_time_us, | |
420 | &write_count, &write_time_us, | |
421 | &reset_count, &reset_time_us); | |
422 | if (args->save) { | |
423 | printf("Probe finished, recovering blocks..."); | |
424 | fflush(stdout); | |
425 | } | |
426 | ||
427 | final_dev_filename = strdup(dev_get_filename(dev)); | |
428 | free_device(dev); | |
429 | assert(final_dev_filename); | |
430 | ||
431 | if (args->save) | |
432 | printf(" Done\n\n"); | |
433 | ||
434 | if (strcmp(args->filename, final_dev_filename)) | |
435 | printf("\nWARNING: device `%s' moved to `%s' due to the resets\n\n", | |
436 | args->filename, final_dev_filename); | |
437 | ||
438 | last_good_sector = (real_size_byte >> 9) - 1; | |
439 | fake_type = dev_param_to_type(real_size_byte, announced_size_byte, | |
440 | wrap, block_order); | |
441 | switch (fake_type) { | |
442 | case FKTY_GOOD: | |
443 | printf("Good news: The device `%s' is the real thing\n", | |
444 | final_dev_filename); | |
445 | break; | |
446 | ||
447 | case FKTY_BAD: | |
448 | printf("Bad news: The device `%s' is damaged\n", | |
449 | final_dev_filename); | |
450 | break; | |
451 | ||
452 | case FKTY_LIMBO: | |
453 | case FKTY_WRAPAROUND: | |
454 | case FKTY_CHAIN: | |
455 | printf("Bad news: The device `%s' is a counterfeit of type %s\n\n" | |
456 | "You can \"fix\" this device using the following command:\n" | |
457 | "f3fix --last-sec=%" PRIu64 " %s\n", | |
458 | final_dev_filename, fake_type_to_name(fake_type), | |
459 | last_good_sector, final_dev_filename); | |
460 | break; | |
461 | ||
462 | default: | |
463 | assert(0); | |
464 | break; | |
465 | } | |
466 | ||
467 | time_s = (t2.tv_sec - t1.tv_sec) + (t2.tv_usec - t1.tv_usec)/1000000.; | |
468 | printf("\nDevice geometry:\n"); | |
469 | report_size("\t *Real* size:", real_size_byte, block_order); | |
470 | report_size("\t Announced size:", announced_size_byte, block_order); | |
471 | report_order("\t Module:", wrap); | |
472 | report_order("\t Block size:", block_order); | |
473 | assert(block_order >= 9); | |
474 | report_sector("\tLast good sector:", last_good_sector); | |
475 | printf("\nProbe time: %.2f seconds\n", time_s); | |
476 | ||
477 | if (args->time_ops) { | |
478 | report_ops("read", read_count, read_time_us); | |
479 | report_ops("write", write_count, write_time_us); | |
480 | report_ops("reset", reset_count, reset_time_us); | |
481 | } | |
482 | return 0; | |
483 | } | |
484 | ||
485 | int main(int argc, char **argv) | |
486 | { | |
487 | struct args args = { | |
488 | /* Defaults. */ | |
489 | .debug = false, | |
490 | .unit_test = false, | |
491 | .keep_file = false, | |
492 | .save = true, | |
493 | .min_mem = false, | |
494 | .reset_type = RT_DEFAULT, | |
495 | .time_ops = false, | |
496 | .real_size_byte = 1ULL << 31, | |
497 | .fake_size_byte = 1ULL << 34, | |
498 | .wrap = 31, | |
499 | .block_order = 0, | |
500 | }; | |
501 | ||
502 | /* Read parameters. */ | |
503 | argp_parse(&argp, argc, argv, 0, NULL, &args); | |
504 | print_header(stdout, "probe"); | |
505 | ||
506 | if (args.unit_test) | |
507 | return unit_test(args.filename); | |
508 | return test_device(&args); | |
509 | } |
0 | 0 | .\"Text automatically generated by txt2man |
1 | .TH F3 "1" "September 2014" "F3 4.0" "test real flash memory capacity" | |
1 | .TH F3 "1" "December 2014" "F3 5.0" "test real flash memory capacity" | |
2 | 2 | .SH NAME |
3 | 3 | \fBf3write, f3read \fP- test real flash memory capacity |
4 | 4 | .SH SYNOPSIS |
0 | #define _GNU_SOURCE | |
1 | #define _POSIX_C_SOURCE 200809L | |
2 | #define _FILE_OFFSET_BITS 64 | |
3 | ||
4 | #include <stdio.h> | |
5 | #include <stdlib.h> | |
6 | #include <stdbool.h> | |
7 | #include <assert.h> | |
8 | #include <string.h> | |
9 | #include <inttypes.h> | |
10 | #include <errno.h> | |
11 | #include <err.h> | |
12 | #include <sys/ioctl.h> | |
13 | #include <sys/time.h> | |
14 | #include <sys/types.h> | |
15 | #include <sys/stat.h> | |
16 | #include <fcntl.h> | |
17 | #include <unistd.h> | |
18 | #include <linux/fs.h> | |
19 | #include <linux/usbdevice_fs.h> | |
20 | #include <libudev.h> | |
21 | ||
22 | #include "libutils.h" | |
23 | #include "libdevs.h" | |
24 | ||
25 | static const char const *ftype_to_name[FKTY_MAX] = { | |
26 | [FKTY_GOOD] = "good", | |
27 | [FKTY_BAD] = "bad", | |
28 | [FKTY_LIMBO] = "limbo", | |
29 | [FKTY_WRAPAROUND] = "wraparound", | |
30 | [FKTY_CHAIN] = "chain", | |
31 | }; | |
32 | ||
33 | const char *fake_type_to_name(enum fake_type fake_type) | |
34 | { | |
35 | assert(fake_type < FKTY_MAX); | |
36 | return ftype_to_name[fake_type]; | |
37 | } | |
38 | ||
39 | int dev_param_valid(uint64_t real_size_byte, | |
40 | uint64_t announced_size_byte, int wrap, int block_order) | |
41 | { | |
42 | int block_size; | |
43 | ||
44 | /* Check general ranges. */ | |
45 | if (real_size_byte > announced_size_byte || wrap < 0 || wrap >= 64 || | |
46 | block_order < 9 || block_order > 20) | |
47 | return false; | |
48 | ||
49 | /* Check alignment of the sizes. */ | |
50 | block_size = 1 << block_order; | |
51 | if (real_size_byte % block_size || announced_size_byte % block_size) | |
52 | return false; | |
53 | ||
54 | /* If good, @wrap must make sense. */ | |
55 | if (real_size_byte == announced_size_byte) { | |
56 | uint64_t two_wrap = ((uint64_t)1) << wrap; | |
57 | return announced_size_byte <= two_wrap; | |
58 | } | |
59 | ||
60 | return true; | |
61 | } | |
62 | ||
63 | enum fake_type dev_param_to_type(uint64_t real_size_byte, | |
64 | uint64_t announced_size_byte, int wrap, int block_order) | |
65 | { | |
66 | uint64_t two_wrap; | |
67 | ||
68 | assert(dev_param_valid(real_size_byte, announced_size_byte, | |
69 | wrap, block_order)); | |
70 | ||
71 | if (real_size_byte == announced_size_byte) | |
72 | return FKTY_GOOD; | |
73 | ||
74 | if (real_size_byte == 0) | |
75 | return FKTY_BAD; | |
76 | ||
77 | /* real_size_byte < announced_size_byte */ | |
78 | ||
79 | two_wrap = ((uint64_t)1) << wrap; | |
80 | if (two_wrap <= real_size_byte) | |
81 | return FKTY_WRAPAROUND; | |
82 | if (two_wrap < announced_size_byte) | |
83 | return FKTY_CHAIN; | |
84 | return FKTY_LIMBO; | |
85 | } | |
86 | ||
87 | struct device { | |
88 | uint64_t size_byte; | |
89 | int block_order; | |
90 | ||
91 | int (*read_block)(struct device *dev, char *buf, int length, | |
92 | uint64_t offset); | |
93 | int (*write_block)(struct device *dev, const char *buf, int length, | |
94 | uint64_t offset); | |
95 | int (*reset)(struct device *dev); | |
96 | void (*free)(struct device *dev); | |
97 | const char *(*get_filename)(struct device *dev); | |
98 | }; | |
99 | ||
100 | uint64_t dev_get_size_byte(struct device *dev) | |
101 | { | |
102 | return dev->size_byte; | |
103 | } | |
104 | ||
105 | int dev_get_block_order(struct device *dev) | |
106 | { | |
107 | return dev->block_order; | |
108 | } | |
109 | ||
110 | int dev_get_block_size(struct device *dev) | |
111 | { | |
112 | return 1 << dev->block_order; | |
113 | } | |
114 | ||
115 | int dev_read_block(struct device *dev, char *buf, uint64_t block) | |
116 | { | |
117 | const int block_size = 1 << dev->block_order; | |
118 | uint64_t offset = block << dev->block_order; | |
119 | assert(offset + block_size <= dev->size_byte); | |
120 | return dev->read_block(dev, buf, block_size, offset); | |
121 | } | |
122 | ||
123 | int dev_write_block(struct device *dev, const char *buf, uint64_t block) | |
124 | { | |
125 | const int block_size = 1 << dev->block_order; | |
126 | uint64_t offset = block << dev->block_order; | |
127 | assert(offset + block_size <= dev->size_byte); | |
128 | return dev->write_block(dev, buf, block_size, offset); | |
129 | } | |
130 | ||
131 | int dev_reset(struct device *dev) | |
132 | { | |
133 | return dev->reset ? dev->reset(dev) : 0; | |
134 | } | |
135 | ||
136 | void free_device(struct device *dev) | |
137 | { | |
138 | if (dev->free) | |
139 | dev->free(dev); | |
140 | free(dev); | |
141 | } | |
142 | ||
143 | const char *dev_get_filename(struct device *dev) | |
144 | { | |
145 | return dev->get_filename(dev); | |
146 | } | |
147 | ||
148 | struct file_device { | |
149 | /* This must be the first field. See dev_fdev() for details. */ | |
150 | struct device dev; | |
151 | ||
152 | const char *filename; | |
153 | int fd; | |
154 | uint64_t real_size_byte; | |
155 | uint64_t address_mask; | |
156 | }; | |
157 | ||
158 | static inline struct file_device *dev_fdev(struct device *dev) | |
159 | { | |
160 | return (struct file_device *)dev; | |
161 | } | |
162 | ||
163 | static int fdev_read_block(struct device *dev, char *buf, int length, | |
164 | uint64_t offset) | |
165 | { | |
166 | struct file_device *fdev = dev_fdev(dev); | |
167 | off_t off_ret; | |
168 | int done; | |
169 | ||
170 | offset &= fdev->address_mask; | |
171 | if (offset >= fdev->real_size_byte) { | |
172 | memset(buf, 0, length); | |
173 | return 0; | |
174 | } | |
175 | ||
176 | off_ret = lseek(fdev->fd, offset, SEEK_SET); | |
177 | if (off_ret < 0) | |
178 | return - errno; | |
179 | assert((uint64_t)off_ret == offset); | |
180 | ||
181 | done = 0; | |
182 | do { | |
183 | ssize_t rc = read(fdev->fd, buf + done, length - done); | |
184 | assert(rc >= 0); | |
185 | if (!rc) { | |
186 | /* Tried to read beyond the end of the file. */ | |
187 | assert(!done); | |
188 | memset(buf, 0, length); | |
189 | done += length; | |
190 | } | |
191 | done += rc; | |
192 | } while (done < length); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | static int write_all(int fd, const char *buf, int count) | |
198 | { | |
199 | int done = 0; | |
200 | do { | |
201 | ssize_t rc = write(fd, buf + done, count - done); | |
202 | if (rc < 0) { | |
203 | /* The write() failed. */ | |
204 | return errno; | |
205 | } | |
206 | done += rc; | |
207 | } while (done < count); | |
208 | return 0; | |
209 | } | |
210 | ||
211 | static int fdev_write_block(struct device *dev, const char *buf, int length, | |
212 | uint64_t offset) | |
213 | { | |
214 | struct file_device *fdev = dev_fdev(dev); | |
215 | off_t off_ret; | |
216 | ||
217 | offset &= fdev->address_mask; | |
218 | if (offset >= fdev->real_size_byte) | |
219 | return 0; | |
220 | ||
221 | off_ret = lseek(fdev->fd, offset, SEEK_SET); | |
222 | if (off_ret < 0) | |
223 | return - errno; | |
224 | assert((uint64_t)off_ret == offset); | |
225 | ||
226 | return write_all(fdev->fd, buf, length); | |
227 | } | |
228 | ||
229 | static void fdev_free(struct device *dev) | |
230 | { | |
231 | struct file_device *fdev = dev_fdev(dev); | |
232 | free((void *)fdev->filename); | |
233 | assert(!close(fdev->fd)); | |
234 | } | |
235 | ||
236 | static const char *fdev_get_filename(struct device *dev) | |
237 | { | |
238 | return dev_fdev(dev)->filename; | |
239 | } | |
240 | ||
241 | struct device *create_file_device(const char *filename, | |
242 | uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, | |
243 | int block_order, int keep_file) | |
244 | { | |
245 | struct file_device *fdev; | |
246 | ||
247 | fdev = malloc(sizeof(*fdev)); | |
248 | if (!fdev) | |
249 | goto error; | |
250 | ||
251 | fdev->filename = strdup(filename); | |
252 | if (!fdev->filename) | |
253 | goto fdev; | |
254 | ||
255 | fdev->fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); | |
256 | if (fdev->fd < 0) { | |
257 | err(errno, "Can't create file `%s'", filename); | |
258 | goto filename; | |
259 | } | |
260 | if (!keep_file) { | |
261 | /* Unlinking the file now guarantees that it won't exist if | |
262 | * there is a crash. | |
263 | */ | |
264 | assert(!unlink(filename)); | |
265 | } | |
266 | ||
267 | if (!block_order) { | |
268 | struct stat fd_stat; | |
269 | blksize_t block_size; | |
270 | assert(!fstat(fdev->fd, &fd_stat)); | |
271 | block_size = fd_stat.st_blksize; | |
272 | block_order = ilog2(block_size); | |
273 | assert(block_size == (1 << block_order)); | |
274 | } | |
275 | ||
276 | if (!dev_param_valid(real_size_byte, fake_size_byte, wrap, block_order)) | |
277 | goto keep_file; | |
278 | ||
279 | fdev->real_size_byte = real_size_byte; | |
280 | fdev->address_mask = (((uint64_t)1) << wrap) - 1; | |
281 | ||
282 | fdev->dev.size_byte = fake_size_byte; | |
283 | fdev->dev.block_order = block_order; | |
284 | fdev->dev.read_block = fdev_read_block; | |
285 | fdev->dev.write_block = fdev_write_block; | |
286 | fdev->dev.reset = NULL; | |
287 | fdev->dev.free = fdev_free; | |
288 | fdev->dev.get_filename = fdev_get_filename; | |
289 | ||
290 | return &fdev->dev; | |
291 | ||
292 | keep_file: | |
293 | if (keep_file) | |
294 | unlink(filename); | |
295 | assert(!close(fdev->fd)); | |
296 | filename: | |
297 | free((void *)fdev->filename); | |
298 | fdev: | |
299 | free(fdev); | |
300 | error: | |
301 | return NULL; | |
302 | } | |
303 | ||
304 | struct block_device { | |
305 | /* This must be the first field. See dev_bdev() for details. */ | |
306 | struct device dev; | |
307 | ||
308 | const char *filename; | |
309 | int fd; | |
310 | }; | |
311 | ||
312 | static inline struct block_device *dev_bdev(struct device *dev) | |
313 | { | |
314 | return (struct block_device *)dev; | |
315 | } | |
316 | ||
317 | static int read_all(int fd, char *buf, int count) | |
318 | { | |
319 | int done = 0; | |
320 | do { | |
321 | ssize_t rc = read(fd, buf + done, count - done); | |
322 | assert(rc >= 0); /* Did the read() went right? */ | |
323 | assert(rc != 0); /* We should never hit the end of the file. */ | |
324 | done += rc; | |
325 | } while (done < count); | |
326 | return 0; | |
327 | } | |
328 | ||
329 | static int bdev_read_block(struct device *dev, char *buf, int length, | |
330 | uint64_t offset) | |
331 | { | |
332 | struct block_device *bdev = dev_bdev(dev); | |
333 | off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); | |
334 | if (off_ret < 0) | |
335 | return - errno; | |
336 | assert((uint64_t)off_ret == offset); | |
337 | return read_all(bdev->fd, buf, length); | |
338 | } | |
339 | ||
340 | static int bdev_write_block(struct device *dev, const char *buf, int length, | |
341 | uint64_t offset) | |
342 | { | |
343 | struct block_device *bdev = dev_bdev(dev); | |
344 | off_t off_ret = lseek(bdev->fd, offset, SEEK_SET); | |
345 | if (off_ret < 0) | |
346 | return - errno; | |
347 | assert((uint64_t)off_ret == offset); | |
348 | return write_all(bdev->fd, buf, length); | |
349 | } | |
350 | ||
351 | static inline int bdev_open(const char *filename) | |
352 | { | |
353 | return open(filename, O_RDWR | O_DIRECT | O_SYNC); | |
354 | } | |
355 | ||
356 | static struct udev_device *map_dev_to_usb_dev(struct udev_device *dev) | |
357 | { | |
358 | struct udev_device *usb_dev; | |
359 | ||
360 | /* The device pointed to by dev contains information about | |
361 | * the USB device. | |
362 | * In order to get information about the USB device, | |
363 | * get the parent device with the subsystem/devtype pair of | |
364 | * "usb"/"usb_device". | |
365 | * This will be several levels up the tree, | |
366 | * but the function will find it. | |
367 | */ | |
368 | usb_dev = udev_device_get_parent_with_subsystem_devtype( | |
369 | dev, "usb", "usb_device"); | |
370 | ||
371 | /* @usb_dev is not referenced, and will be freed when | |
372 | * the child (i.e. @dev) is freed. | |
373 | * See udev_device_get_parent_with_subsystem_devtype() for | |
374 | * details. | |
375 | */ | |
376 | return udev_device_ref(usb_dev); | |
377 | } | |
378 | ||
379 | static struct udev_device *dev_from_block_fd(struct udev *udev, int block_fd) | |
380 | { | |
381 | struct stat fd_stat; | |
382 | ||
383 | assert(!fstat(block_fd, &fd_stat)); | |
384 | if (!S_ISBLK(fd_stat.st_mode)) | |
385 | err(EINVAL, "FD %i is not a block device", block_fd); | |
386 | return udev_device_new_from_devnum(udev, 'b', fd_stat.st_rdev); | |
387 | } | |
388 | ||
389 | static struct udev_device *map_block_to_usb_dev(struct udev *udev, int block_fd) | |
390 | { | |
391 | struct udev_device *dev, *usb_dev; | |
392 | ||
393 | dev = dev_from_block_fd(udev, block_fd); | |
394 | assert(dev); | |
395 | usb_dev = map_dev_to_usb_dev(dev); | |
396 | udev_device_unref(dev); | |
397 | return usb_dev; | |
398 | } | |
399 | ||
400 | static struct udev_monitor *create_monitor(struct udev *udev, | |
401 | const char *subsystem, const char *devtype) | |
402 | { | |
403 | struct udev_monitor *mon; | |
404 | int mon_fd, flags; | |
405 | ||
406 | mon = udev_monitor_new_from_netlink(udev, "udev"); | |
407 | assert(mon); | |
408 | assert(!udev_monitor_filter_add_match_subsystem_devtype(mon, | |
409 | subsystem, devtype)); | |
410 | assert(!udev_monitor_enable_receiving(mon)); | |
411 | mon_fd = udev_monitor_get_fd(mon); | |
412 | assert(mon_fd >= 0); | |
413 | flags = fcntl(mon_fd, F_GETFL); | |
414 | assert(flags >= 0); | |
415 | assert(!fcntl(mon_fd, F_SETFL, flags & ~O_NONBLOCK)); | |
416 | ||
417 | return mon; | |
418 | } | |
419 | ||
420 | static char *wait_for_add_action(struct udev *udev, | |
421 | const char *id_vendor, const char *id_product, const char *serial) | |
422 | { | |
423 | char *devnode = NULL; | |
424 | bool done = false; | |
425 | struct udev_monitor *mon; | |
426 | ||
427 | mon = create_monitor(udev, "block", "disk"); | |
428 | assert(mon); | |
429 | do { | |
430 | struct udev_device *dev, *usb_dev; | |
431 | ||
432 | dev = udev_monitor_receive_device(mon); | |
433 | assert(dev); | |
434 | if (strcmp(udev_device_get_action(dev), "add")) { | |
435 | udev_device_unref(dev); | |
436 | continue; | |
437 | } | |
438 | ||
439 | usb_dev = map_dev_to_usb_dev(dev); | |
440 | if (usb_dev && | |
441 | !strcmp(udev_device_get_sysattr_value(usb_dev, | |
442 | "idVendor"), id_vendor) && | |
443 | !strcmp(udev_device_get_sysattr_value(usb_dev, | |
444 | "idProduct"), id_product) && | |
445 | !strcmp(udev_device_get_sysattr_value(usb_dev, | |
446 | "serial"), serial)) { | |
447 | devnode = strdup(udev_device_get_devnode(dev)); | |
448 | assert(devnode); | |
449 | done = true; | |
450 | } | |
451 | ||
452 | udev_device_unref(usb_dev); | |
453 | udev_device_unref(dev); | |
454 | } while (!done); | |
455 | assert(!udev_monitor_unref(mon)); | |
456 | ||
457 | return devnode; | |
458 | } | |
459 | ||
460 | static int bdev_manual_usb_reset(struct device *dev) | |
461 | { | |
462 | struct block_device *bdev = dev_bdev(dev); | |
463 | struct udev *udev; | |
464 | struct udev_device *usb_dev; | |
465 | const char *id_vendor, *id_product, *serial, *devnode; | |
466 | ||
467 | udev = udev_new(); | |
468 | if (!udev) | |
469 | err(errno, "Can't create udev"); | |
470 | ||
471 | /* Obtain @bus_num and @dev_num of the USB device to monitor. */ | |
472 | usb_dev = map_block_to_usb_dev(udev, bdev->fd); | |
473 | if (!usb_dev) | |
474 | errx(1, "Unable to find USB parent device of block device `%s'", | |
475 | bdev->filename); | |
476 | id_vendor = udev_device_get_sysattr_value(usb_dev, "idVendor"); | |
477 | id_product = udev_device_get_sysattr_value(usb_dev, "idProduct"); | |
478 | serial = udev_device_get_sysattr_value(usb_dev, "serial"); | |
479 | ||
480 | /* Close @bdev->fd before the drive is removed to increase | |
481 | * the chance that the device will receive the same filename. | |
482 | * The code is robust enough to deal with the case the drive doesn't | |
483 | * receive the same file name, though. | |
484 | */ | |
485 | assert(!close(bdev->fd)); | |
486 | ||
487 | printf("Please unplug and plug back the USB drive. Waiting..."); | |
488 | fflush(stdout); | |
489 | devnode = wait_for_add_action(udev, id_vendor, id_product, serial); | |
490 | assert(devnode); | |
491 | printf(" Thanks\n\n"); | |
492 | ||
493 | udev_device_unref(usb_dev); | |
494 | assert(!udev_unref(udev)); | |
495 | ||
496 | bdev->fd = bdev_open(devnode); | |
497 | if (bdev->fd < 0) | |
498 | err(errno, "Can't REopen device `%s'", devnode); | |
499 | free((void *)bdev->filename); | |
500 | bdev->filename = devnode; | |
501 | ||
502 | return 0; | |
503 | } | |
504 | ||
505 | /* Return an open fd to the underlying hardware of the block device. */ | |
506 | static int usb_fd_from_block_dev(int block_fd, int open_flags) | |
507 | { | |
508 | struct udev *udev; | |
509 | struct udev_device *dev; | |
510 | const char *usb_filename; | |
511 | int usb_fd; | |
512 | ||
513 | udev = udev_new(); | |
514 | if (!udev) | |
515 | err(errno, "Can't create udev"); | |
516 | ||
517 | dev = map_block_to_usb_dev(udev, block_fd); | |
518 | assert(dev); | |
519 | ||
520 | usb_filename = udev_device_get_devnode(dev); | |
521 | if (!usb_filename) | |
522 | err(EINVAL, "Block device is not backed by a USB device"); | |
523 | ||
524 | usb_fd = open(usb_filename, open_flags | O_NONBLOCK); | |
525 | if (usb_fd < 0) | |
526 | err(errno, "Can't open device `%s'", usb_filename); | |
527 | ||
528 | udev_device_unref(dev); | |
529 | assert(!udev_unref(udev)); | |
530 | return usb_fd; | |
531 | } | |
532 | ||
533 | static int bdev_usb_reset(struct device *dev) | |
534 | { | |
535 | struct block_device *bdev = dev_bdev(dev); | |
536 | int usb_fd; | |
537 | ||
538 | usb_fd = usb_fd_from_block_dev(bdev->fd, O_WRONLY); | |
539 | assert(usb_fd >= 0); | |
540 | ||
541 | assert(!close(bdev->fd)); | |
542 | assert(!ioctl(usb_fd, USBDEVFS_RESET)); | |
543 | assert(!close(usb_fd)); | |
544 | bdev->fd = bdev_open(bdev->filename); | |
545 | if (bdev->fd < 0) | |
546 | err(errno, "Can't REopen device `%s'", bdev->filename); | |
547 | return 0; | |
548 | } | |
549 | ||
550 | static void bdev_free(struct device *dev) | |
551 | { | |
552 | struct block_device *bdev = dev_bdev(dev); | |
553 | if (bdev->fd >= 0) | |
554 | assert(!close(bdev->fd)); | |
555 | free((void *)bdev->filename); | |
556 | } | |
557 | ||
558 | static const char *bdev_get_filename(struct device *dev) | |
559 | { | |
560 | return dev_bdev(dev)->filename; | |
561 | } | |
562 | ||
563 | static struct udev_device *map_partition_to_disk(struct udev_device *dev) | |
564 | { | |
565 | struct udev_device *disk_dev; | |
566 | ||
567 | disk_dev = udev_device_get_parent_with_subsystem_devtype( | |
568 | dev, "block", "disk"); | |
569 | ||
570 | /* @disk_dev is not referenced, and will be freed when | |
571 | * the child (i.e. @dev) is freed. | |
572 | * See udev_device_get_parent_with_subsystem_devtype() for | |
573 | * details. | |
574 | */ | |
575 | return udev_device_ref(disk_dev); | |
576 | } | |
577 | ||
578 | struct device *create_block_device(const char *filename, int block_order, | |
579 | enum reset_type rt) | |
580 | { | |
581 | struct block_device *bdev; | |
582 | struct udev *udev; | |
583 | struct udev_device *fd_dev, *usb_dev; | |
584 | const char *s; | |
585 | ||
586 | bdev = malloc(sizeof(*bdev)); | |
587 | if (!bdev) | |
588 | goto error; | |
589 | ||
590 | bdev->filename = strdup(filename); | |
591 | if (!bdev->filename) | |
592 | goto bdev; | |
593 | ||
594 | bdev->fd = bdev_open(filename); | |
595 | if (bdev->fd < 0) { | |
596 | if (errno == EACCES && getuid()) { | |
597 | fprintf(stderr, "Your username doesn't have access to device `%s'.\n" | |
598 | "Try to run this program as root:\n" | |
599 | "sudo f3probe %s\n" | |
600 | "In case you don't have access to root, use f3write/f3read.\n", | |
601 | filename, filename); | |
602 | } else { | |
603 | err(errno, "Can't open device `%s'", filename); | |
604 | } | |
605 | goto filename; | |
606 | } | |
607 | ||
608 | /* Make sure that @bdev->fd is a disk, not a partition, and that | |
609 | * it is in fact backed by a USB device. | |
610 | */ | |
611 | udev = udev_new(); | |
612 | if (!udev) { | |
613 | warn("Can't create udev"); | |
614 | goto fd; | |
615 | } | |
616 | fd_dev = dev_from_block_fd(udev, bdev->fd); | |
617 | if (!fd_dev) { | |
618 | fprintf(stderr, "Can't create udev device from `%s'\n", | |
619 | filename); | |
620 | goto udev; | |
621 | } | |
622 | assert(!strcmp(udev_device_get_subsystem(fd_dev), "block")); | |
623 | s = udev_device_get_devtype(fd_dev); | |
624 | if (!strcmp(s, "partition")) { | |
625 | struct udev_device *disk_dev = map_partition_to_disk(fd_dev); | |
626 | assert(disk_dev); | |
627 | s = udev_device_get_devnode(disk_dev); | |
628 | fprintf(stderr, "Device `%s' is a partition of disk device `%s'.\n" | |
629 | "You must run f3probe on the disk device as follows:\n" | |
630 | "f3probe %s\n", | |
631 | filename, s, s); | |
632 | udev_device_unref(disk_dev); | |
633 | goto fd_dev; | |
634 | } else if (strcmp(s, "disk")) { | |
635 | fprintf(stderr, "Device `%s' is not a disk, but `%s'", | |
636 | filename, s); | |
637 | goto fd_dev; | |
638 | } | |
639 | usb_dev = map_dev_to_usb_dev(fd_dev); | |
640 | if (!usb_dev) { | |
641 | fprintf(stderr, "Device `%s' is not backed by a USB device", | |
642 | filename); | |
643 | goto fd_dev; | |
644 | } | |
645 | udev_device_unref(usb_dev); | |
646 | udev_device_unref(fd_dev); | |
647 | assert(!udev_unref(udev)); | |
648 | ||
649 | switch (rt) { | |
650 | case RT_MANUAL_USB: | |
651 | bdev->dev.reset = bdev_manual_usb_reset; | |
652 | break; | |
653 | case RT_USB: | |
654 | bdev->dev.reset = bdev_usb_reset; | |
655 | break; | |
656 | default: | |
657 | assert(0); | |
658 | } | |
659 | ||
660 | assert(!ioctl(bdev->fd, BLKGETSIZE64, &bdev->dev.size_byte)); | |
661 | ||
662 | if (!block_order) { | |
663 | int block_size; | |
664 | assert(!ioctl(bdev->fd, BLKBSZGET, &block_size)); | |
665 | block_order = ilog2(block_size); | |
666 | assert(block_size == (1 << block_order)); | |
667 | } | |
668 | bdev->dev.block_order = block_order; | |
669 | ||
670 | bdev->dev.read_block = bdev_read_block; | |
671 | bdev->dev.write_block = bdev_write_block; | |
672 | bdev->dev.free = bdev_free; | |
673 | bdev->dev.get_filename = bdev_get_filename; | |
674 | ||
675 | return &bdev->dev; | |
676 | ||
677 | fd_dev: | |
678 | udev_device_unref(fd_dev); | |
679 | udev: | |
680 | assert(!udev_unref(udev)); | |
681 | fd: | |
682 | assert(!close(bdev->fd)); | |
683 | filename: | |
684 | free((void *)bdev->filename); | |
685 | bdev: | |
686 | free(bdev); | |
687 | error: | |
688 | return NULL; | |
689 | } | |
690 | ||
691 | struct perf_device { | |
692 | /* This must be the first field. See dev_pdev() for details. */ | |
693 | struct device dev; | |
694 | ||
695 | struct device *shadow_dev; | |
696 | ||
697 | uint64_t read_count; | |
698 | uint64_t read_time_us; | |
699 | uint64_t write_count; | |
700 | uint64_t write_time_us; | |
701 | uint64_t reset_count; | |
702 | uint64_t reset_time_us; | |
703 | }; | |
704 | ||
705 | static inline struct perf_device *dev_pdev(struct device *dev) | |
706 | { | |
707 | return (struct perf_device *)dev; | |
708 | } | |
709 | ||
710 | static inline uint64_t diff_timeval_us(const struct timeval *t1, | |
711 | const struct timeval *t2) | |
712 | { | |
713 | return (t2->tv_sec - t1->tv_sec) * 1000000ULL + | |
714 | t2->tv_usec - t1->tv_usec; | |
715 | } | |
716 | ||
717 | static int pdev_read_block(struct device *dev, char *buf, int length, | |
718 | uint64_t offset) | |
719 | { | |
720 | struct perf_device *pdev = dev_pdev(dev); | |
721 | struct timeval t1, t2; | |
722 | int rc; | |
723 | ||
724 | assert(!gettimeofday(&t1, NULL)); | |
725 | rc = pdev->shadow_dev->read_block(pdev->shadow_dev, buf, | |
726 | length, offset); | |
727 | assert(!gettimeofday(&t2, NULL)); | |
728 | pdev->read_count++; | |
729 | pdev->read_time_us += diff_timeval_us(&t1, &t2); | |
730 | return rc; | |
731 | } | |
732 | ||
733 | static int pdev_write_block(struct device *dev, const char *buf, int length, | |
734 | uint64_t offset) | |
735 | { | |
736 | struct perf_device *pdev = dev_pdev(dev); | |
737 | struct timeval t1, t2; | |
738 | int rc; | |
739 | ||
740 | assert(!gettimeofday(&t1, NULL)); | |
741 | rc = pdev->shadow_dev->write_block(pdev->shadow_dev, buf, | |
742 | length, offset); | |
743 | assert(!gettimeofday(&t2, NULL)); | |
744 | pdev->write_count++; | |
745 | pdev->write_time_us += diff_timeval_us(&t1, &t2); | |
746 | return rc; | |
747 | } | |
748 | ||
749 | static int pdev_reset(struct device *dev) | |
750 | { | |
751 | struct perf_device *pdev = dev_pdev(dev); | |
752 | struct timeval t1, t2; | |
753 | int rc; | |
754 | ||
755 | assert(!gettimeofday(&t1, NULL)); | |
756 | rc = dev_reset(pdev->shadow_dev); | |
757 | assert(!gettimeofday(&t2, NULL)); | |
758 | pdev->reset_count++; | |
759 | pdev->reset_time_us += diff_timeval_us(&t1, &t2); | |
760 | return rc; | |
761 | } | |
762 | ||
763 | static void pdev_free(struct device *dev) | |
764 | { | |
765 | struct perf_device *pdev = dev_pdev(dev); | |
766 | free_device(pdev->shadow_dev); | |
767 | } | |
768 | ||
769 | static const char *pdev_get_filename(struct device *dev) | |
770 | { | |
771 | return dev_get_filename(dev_pdev(dev)->shadow_dev); | |
772 | } | |
773 | ||
774 | struct device *pdev_detach_and_free(struct device *dev) | |
775 | { | |
776 | struct perf_device *pdev = dev_pdev(dev); | |
777 | struct device *shadow_dev = pdev->shadow_dev; | |
778 | pdev->shadow_dev = NULL; | |
779 | pdev->dev.free = NULL; | |
780 | free_device(&pdev->dev); | |
781 | return shadow_dev; | |
782 | } | |
783 | ||
784 | struct device *create_perf_device(struct device *dev) | |
785 | { | |
786 | struct perf_device *pdev; | |
787 | ||
788 | pdev = malloc(sizeof(*pdev)); | |
789 | if (!pdev) | |
790 | return NULL; | |
791 | ||
792 | pdev->shadow_dev = dev; | |
793 | pdev->read_count = 0; | |
794 | pdev->read_time_us = 0; | |
795 | pdev->write_count = 0; | |
796 | pdev->write_time_us = 0; | |
797 | pdev->reset_count = 0; | |
798 | pdev->reset_time_us = 0; | |
799 | ||
800 | pdev->dev.size_byte = dev->size_byte; | |
801 | pdev->dev.block_order = dev->block_order; | |
802 | pdev->dev.read_block = pdev_read_block; | |
803 | pdev->dev.write_block = pdev_write_block; | |
804 | pdev->dev.reset = pdev_reset; | |
805 | pdev->dev.free = pdev_free; | |
806 | pdev->dev.get_filename = pdev_get_filename; | |
807 | ||
808 | return &pdev->dev; | |
809 | } | |
810 | ||
811 | void perf_device_sample(struct device *dev, | |
812 | uint64_t *pread_count, uint64_t *pread_time_us, | |
813 | uint64_t *pwrite_count, uint64_t *pwrite_time_us, | |
814 | uint64_t *preset_count, uint64_t *preset_time_us) | |
815 | { | |
816 | struct perf_device *pdev = dev_pdev(dev); | |
817 | ||
818 | if (pread_count) | |
819 | *pread_count = pdev->read_count; | |
820 | if (pread_time_us) | |
821 | *pread_time_us = pdev->read_time_us; | |
822 | ||
823 | if (pwrite_count) | |
824 | *pwrite_count = pdev->write_count; | |
825 | if (pwrite_time_us) | |
826 | *pwrite_time_us = pdev->write_time_us; | |
827 | ||
828 | if (preset_count) | |
829 | *preset_count = pdev->reset_count; | |
830 | if (preset_time_us) | |
831 | *preset_time_us = pdev->reset_time_us; | |
832 | } | |
833 | ||
834 | #define SDEV_BITMAP_WORD long | |
835 | #define SDEV_BITMAP_BITS_PER_WORD (8*sizeof(SDEV_BITMAP_WORD)) | |
836 | struct safe_device { | |
837 | /* This must be the first field. See dev_sdev() for details. */ | |
838 | struct device dev; | |
839 | ||
840 | struct device *shadow_dev; | |
841 | ||
842 | char *saved_blocks; | |
843 | uint64_t *sb_offsets; | |
844 | SDEV_BITMAP_WORD *sb_bitmap; | |
845 | int sb_n; | |
846 | int sb_max; | |
847 | }; | |
848 | ||
849 | static inline struct safe_device *dev_sdev(struct device *dev) | |
850 | { | |
851 | return (struct safe_device *)dev; | |
852 | } | |
853 | ||
854 | static int sdev_read_block(struct device *dev, char *buf, int length, | |
855 | uint64_t offset) | |
856 | { | |
857 | struct safe_device *sdev = dev_sdev(dev); | |
858 | return sdev->shadow_dev->read_block(sdev->shadow_dev, buf, | |
859 | length, offset); | |
860 | } | |
861 | ||
862 | static int sdev_save_block(struct safe_device *sdev, | |
863 | int length, uint64_t offset) | |
864 | { | |
865 | const int block_order = dev_get_block_order(sdev->shadow_dev); | |
866 | lldiv_t idx = lldiv(offset >> block_order, SDEV_BITMAP_BITS_PER_WORD); | |
867 | SDEV_BITMAP_WORD set_bit = (SDEV_BITMAP_WORD)1 << idx.rem; | |
868 | char *block; | |
869 | int rc; | |
870 | ||
871 | /* The current implementation doesn't support variable lengths. */ | |
872 | assert(length == dev_get_block_size(sdev->shadow_dev)); | |
873 | ||
874 | /* Is this block already saved? */ | |
875 | if (!sdev->sb_bitmap) { | |
876 | int i; | |
877 | /* Running without bitmap. */ | |
878 | for (i = 0; i < sdev->sb_n; i++) | |
879 | if (sdev->sb_offsets[i] == offset) { | |
880 | /* The block at @offset is already saved. */ | |
881 | return 0; | |
882 | } | |
883 | } else if (sdev->sb_bitmap[idx.quot] & set_bit) { | |
884 | /* The block at @offset is already saved. */ | |
885 | return 0; | |
886 | } | |
887 | ||
888 | /* The block at @offset hasn't been saved before. Save this block. */ | |
889 | assert(sdev->sb_n < sdev->sb_max); | |
890 | block = (char *)align_512(sdev->saved_blocks) + | |
891 | (sdev->sb_n << block_order); | |
892 | rc = sdev->shadow_dev->read_block(sdev->shadow_dev, block, | |
893 | length, offset); | |
894 | if (rc) | |
895 | return rc; | |
896 | ||
897 | /* Bookkeeping. */ | |
898 | if (sdev->sb_bitmap) | |
899 | sdev->sb_bitmap[idx.quot] |= set_bit; | |
900 | sdev->sb_offsets[sdev->sb_n] = offset; | |
901 | sdev->sb_n++; | |
902 | return 0; | |
903 | } | |
904 | ||
905 | static int sdev_write_block(struct device *dev, const char *buf, int length, | |
906 | uint64_t offset) | |
907 | { | |
908 | struct safe_device *sdev = dev_sdev(dev); | |
909 | int rc; | |
910 | ||
911 | rc = sdev_save_block(sdev, length, offset); | |
912 | if (rc) | |
913 | return rc; | |
914 | ||
915 | return sdev->shadow_dev->write_block(sdev->shadow_dev, buf, | |
916 | length, offset); | |
917 | } | |
918 | ||
919 | static int sdev_reset(struct device *dev) | |
920 | { | |
921 | return dev_reset(dev_sdev(dev)->shadow_dev); | |
922 | } | |
923 | ||
924 | static void sdev_free(struct device *dev) | |
925 | { | |
926 | struct safe_device *sdev = dev_sdev(dev); | |
927 | ||
928 | if (sdev->sb_n > 0) { | |
929 | char *first_block = align_512(sdev->saved_blocks); | |
930 | char *block = first_block + | |
931 | ((sdev->sb_n - 1) << | |
932 | dev_get_block_order(sdev->shadow_dev)); | |
933 | uint64_t *poffset = &sdev->sb_offsets[sdev->sb_n - 1]; | |
934 | int block_size = dev_get_block_size(sdev->shadow_dev); | |
935 | ||
936 | /* Restore blocks in reverse order to cope with | |
937 | * wraparound and chain drives. | |
938 | */ | |
939 | do { | |
940 | int rc = sdev->shadow_dev->write_block( | |
941 | sdev->shadow_dev, block, block_size, *poffset); | |
942 | if (rc) { | |
943 | /* Do not abort, try to recover all bocks. */ | |
944 | warn("Failed to recover block at offset 0x%" | |
945 | PRIx64 " due to a write error", | |
946 | *poffset); | |
947 | } | |
948 | block -= block_size; | |
949 | poffset--; | |
950 | } while (block >= first_block); | |
951 | } | |
952 | ||
953 | free(sdev->sb_bitmap); | |
954 | free(sdev->sb_offsets); | |
955 | free(sdev->saved_blocks); | |
956 | free_device(sdev->shadow_dev); | |
957 | } | |
958 | ||
959 | static const char *sdev_get_filename(struct device *dev) | |
960 | { | |
961 | return dev_get_filename(dev_sdev(dev)->shadow_dev); | |
962 | } | |
963 | ||
964 | struct device *create_safe_device(struct device *dev, int max_blocks, | |
965 | int min_memory) | |
966 | { | |
967 | struct safe_device *sdev; | |
968 | const int block_order = dev_get_block_order(dev); | |
969 | uint64_t length; | |
970 | ||
971 | sdev = malloc(sizeof(*sdev)); | |
972 | if (!sdev) | |
973 | goto error; | |
974 | ||
975 | length = 511 + (max_blocks << block_order); | |
976 | sdev->saved_blocks = malloc(length); | |
977 | if (!sdev->saved_blocks) | |
978 | goto sdev; | |
979 | ||
980 | sdev->sb_offsets = malloc(max_blocks * sizeof(*sdev->sb_offsets)); | |
981 | if (!sdev->sb_offsets) | |
982 | goto saved_blocks; | |
983 | ||
984 | if (!min_memory) { | |
985 | lldiv_t idx = lldiv(dev_get_size_byte(dev) >> block_order, | |
986 | SDEV_BITMAP_BITS_PER_WORD); | |
987 | length = (idx.quot + (idx.rem ? 1 : 0)) * | |
988 | sizeof(SDEV_BITMAP_WORD); | |
989 | sdev->sb_bitmap = malloc(length); | |
990 | if (!sdev->sb_bitmap) | |
991 | goto offsets; | |
992 | memset(sdev->sb_bitmap, 0, length); | |
993 | } else { | |
994 | sdev->sb_bitmap = NULL; | |
995 | } | |
996 | ||
997 | sdev->shadow_dev = dev; | |
998 | sdev->sb_n = 0; | |
999 | sdev->sb_max = max_blocks; | |
1000 | ||
1001 | sdev->dev.size_byte = dev->size_byte; | |
1002 | sdev->dev.block_order = block_order; | |
1003 | sdev->dev.read_block = sdev_read_block; | |
1004 | sdev->dev.write_block = sdev_write_block; | |
1005 | sdev->dev.reset = sdev_reset; | |
1006 | sdev->dev.free = sdev_free; | |
1007 | sdev->dev.get_filename = sdev_get_filename; | |
1008 | ||
1009 | return &sdev->dev; | |
1010 | ||
1011 | offsets: | |
1012 | free(sdev->sb_offsets); | |
1013 | saved_blocks: | |
1014 | free(sdev->saved_blocks); | |
1015 | sdev: | |
1016 | free(sdev); | |
1017 | error: | |
1018 | return NULL; | |
1019 | } |
0 | #ifndef HEADER_LIBDEVS_H | |
1 | #define HEADER_LIBDEVS_H | |
2 | ||
3 | #include <stdint.h> | |
4 | ||
5 | /* | |
6 | * Device model | |
7 | */ | |
8 | ||
9 | enum fake_type { | |
10 | /* Device is good. */ | |
11 | FKTY_GOOD, | |
12 | ||
13 | /* Device is at least partially damaged. */ | |
14 | FKTY_BAD, | |
15 | ||
16 | /* Device discards data after a given limit. */ | |
17 | FKTY_LIMBO, | |
18 | ||
19 | /* Device overwrites data after a given limit. */ | |
20 | FKTY_WRAPAROUND, | |
21 | ||
22 | /* Device is a sequence of wraparound and limbo regions. */ | |
23 | FKTY_CHAIN, | |
24 | ||
25 | FKTY_MAX | |
26 | }; | |
27 | ||
28 | const char *fake_type_to_name(enum fake_type fake_type); | |
29 | ||
30 | int dev_param_valid(uint64_t real_size_byte, | |
31 | uint64_t announced_size_byte, int wrap, int block_order); | |
32 | ||
33 | enum fake_type dev_param_to_type(uint64_t real_size_byte, | |
34 | uint64_t announced_size_byte, int wrap, int block_order); | |
35 | ||
36 | /* | |
37 | * Abstract device | |
38 | */ | |
39 | ||
40 | struct device; | |
41 | ||
42 | /* Properties. */ | |
43 | uint64_t dev_get_size_byte(struct device *dev); | |
44 | int dev_get_block_order(struct device *dev); | |
45 | int dev_get_block_size(struct device *dev); | |
46 | ||
47 | /* Methods. */ | |
48 | int dev_read_block(struct device *dev, char *buf, uint64_t block); | |
49 | int dev_write_block(struct device *dev, const char *buf, uint64_t block); | |
50 | int dev_reset(struct device *dev); | |
51 | void free_device(struct device *dev); | |
52 | /* File name of the device. | |
53 | * This information is important because the filename may change due to resets. | |
54 | */ | |
55 | const char *dev_get_filename(struct device *dev); | |
56 | ||
57 | static inline int dev_write_and_reset(struct device *dev, const char *buf, | |
58 | uint64_t block) | |
59 | { | |
60 | int rc = dev_write_block(dev, buf, block); | |
61 | return rc ? rc : dev_reset(dev); | |
62 | } | |
63 | ||
64 | /* | |
65 | * Concrete devices | |
66 | */ | |
67 | ||
68 | struct device *create_file_device(const char *filename, | |
69 | uint64_t real_size_byte, uint64_t fake_size_byte, int wrap, | |
70 | int block_order, int keep_file); | |
71 | ||
72 | /* XXX Add support for block devices backed by SCSI and ATA. */ | |
73 | enum reset_type { | |
74 | RT_MANUAL_USB = 0, | |
75 | RT_USB, | |
76 | RT_MAX | |
77 | }; | |
78 | #define RT_DEFAULT RT_MANUAL_USB | |
79 | ||
80 | struct device *create_block_device(const char *filename, int block_order, | |
81 | enum reset_type rt); | |
82 | ||
83 | struct device *create_perf_device(struct device *dev); | |
84 | void perf_device_sample(struct device *dev, | |
85 | uint64_t *pread_count, uint64_t *pread_time_us, | |
86 | uint64_t *pwrite_count, uint64_t *pwrite_time_us, | |
87 | uint64_t *preset_count, uint64_t *preset_time_us); | |
88 | /* Detach the shadow device of @pdev, free @pdev, and return | |
89 | * the shadow device. | |
90 | */ | |
91 | struct device *pdev_detach_and_free(struct device *dev); | |
92 | ||
93 | struct device *create_safe_device(struct device *dev, int max_blocks, | |
94 | int min_memory); | |
95 | ||
96 | #endif /* HEADER_LIBDEVS_H */ |
0 | #include <stdlib.h> | |
1 | #include <string.h> | |
2 | #include <stdbool.h> | |
3 | #include <assert.h> | |
4 | #include <math.h> | |
5 | #include <errno.h> | |
6 | ||
7 | #include "libutils.h" | |
8 | #include "libprobe.h" | |
9 | ||
10 | static inline int equal_blk(struct device *dev, const char *b1, const char *b2) | |
11 | { | |
12 | return !memcmp(b1, b2, dev_get_block_size(dev)); | |
13 | } | |
14 | ||
15 | /* Return true if @b1 and b2 are at most @tolerance_byte bytes different. */ | |
16 | static int similar_blk(struct device *dev, const char *b1, const char *b2, | |
17 | int tolerance_byte) | |
18 | { | |
19 | const int block_size = dev_get_block_size(dev); | |
20 | int i; | |
21 | ||
22 | for (i = 0; i < block_size; i++) { | |
23 | if (*b1 != *b2) { | |
24 | tolerance_byte--; | |
25 | if (tolerance_byte <= 0) | |
26 | return false; | |
27 | } | |
28 | b1++; | |
29 | b2++; | |
30 | } | |
31 | return true; | |
32 | } | |
33 | ||
34 | /* Return true if the block at @pos is damaged. */ | |
35 | static int test_block(struct device *dev, | |
36 | const char *stamp_blk, char *probe_blk, uint64_t pos) | |
37 | { | |
38 | /* Write block. */ | |
39 | if (dev_write_block(dev, stamp_blk, pos) && | |
40 | dev_write_block(dev, stamp_blk, pos)) | |
41 | return true; | |
42 | ||
43 | /* Reset. */ | |
44 | if (dev_reset(dev) && dev_reset(dev)) | |
45 | return true; | |
46 | ||
47 | /* | |
48 | * Test block. | |
49 | */ | |
50 | ||
51 | if (dev_read_block(dev, probe_blk, pos) && | |
52 | dev_read_block(dev, probe_blk, pos)) | |
53 | return true; | |
54 | ||
55 | if (equal_blk(dev, stamp_blk, probe_blk)) | |
56 | return false; | |
57 | ||
58 | /* Save time with certainly damaged blocks. */ | |
59 | if (!similar_blk(dev, stamp_blk, probe_blk, 8)) { | |
60 | /* The probe block is damaged. */ | |
61 | return true; | |
62 | } | |
63 | ||
64 | /* The probe block seems to be damaged. | |
65 | * Trying a second time... | |
66 | */ | |
67 | return dev_write_and_reset(dev, stamp_blk, pos) || | |
68 | dev_read_block(dev, probe_blk, pos) || | |
69 | !equal_blk(dev, stamp_blk, probe_blk); | |
70 | } | |
71 | ||
72 | /* Minimum size of the memory chunk used to build flash drives. | |
73 | * It must be a power of two. | |
74 | */ | |
75 | static inline uint64_t initial_high_bit_block(struct device *dev) | |
76 | { | |
77 | int block_order = dev_get_block_order(dev); | |
78 | assert(block_order <= 20); | |
79 | return 1ULL << (20 - block_order); | |
80 | } | |
81 | ||
82 | /* Caller must guarantee that the left bock is good, and written. */ | |
83 | static int search_wrap(struct device *dev, | |
84 | uint64_t left_pos, uint64_t *pright_pos, | |
85 | const char *stamp_blk, char *probe_blk) | |
86 | { | |
87 | uint64_t high_bit = initial_high_bit_block(dev); | |
88 | uint64_t pos = high_bit + left_pos; | |
89 | ||
90 | /* The left block must be in the first memory chunk. */ | |
91 | assert(left_pos < high_bit); | |
92 | ||
93 | /* Check that the drive has at least one memory chunk. */ | |
94 | assert((high_bit - 1) <= *pright_pos); | |
95 | ||
96 | while (pos < *pright_pos) { | |
97 | if (dev_read_block(dev, probe_blk, pos) && | |
98 | dev_read_block(dev, probe_blk, pos)) | |
99 | return true; | |
100 | /* XXX Deal with flipped bit on reception. */ | |
101 | if (equal_blk(dev, stamp_blk, probe_blk)) { | |
102 | /* XXX Test wraparound hypothesis. */ | |
103 | *pright_pos = high_bit - 1; | |
104 | return false; | |
105 | } | |
106 | high_bit <<= 1; | |
107 | pos = high_bit + left_pos; | |
108 | } | |
109 | ||
110 | return false; | |
111 | } | |
112 | ||
113 | #define MAX_N_BLOCK_ORDER 10 | |
114 | ||
115 | static uint64_t estimate_best_n_block(struct device *dev) | |
116 | { | |
117 | uint64_t write_count, write_time_us; | |
118 | uint64_t reset_count, reset_time_us; | |
119 | double t_w_us, t_2w_us, t_r_us; | |
120 | uint64_t n_block_order; | |
121 | ||
122 | perf_device_sample(dev, NULL, NULL, &write_count, &write_time_us, | |
123 | &reset_count, &reset_time_us); | |
124 | if (write_count < 3 || reset_count < 2) { | |
125 | /* There is not enough measurements. */ | |
126 | return (1 << 2) - 1; | |
127 | } | |
128 | ||
129 | /* Let 2^n be the total number of blocks on the drive. | |
130 | * Let p be the total number of passes. | |
131 | * Let w = (2^m - 1) be the number of blocks written on each pass, | |
132 | * where m >= 1. | |
133 | * | |
134 | * A pass is an iteration of the loop in search_edge(), that is, | |
135 | * a call to write_test_blocks(), dev_reset(), and probe_test_blocks(). | |
136 | * | |
137 | * The reason to have w = (2^m - 1) instead of w = 2^m is because | |
138 | * the former leads to a clean relationship between n, p, and m | |
139 | * when m is constant: 2^n / (w + 1)^p = 1 => p = n/m | |
140 | * | |
141 | * Let Tr be the time to reset the device. | |
142 | * Let Tw be the time to write a block to @dev. | |
143 | * Let Tw' be the time to write a block to the underlying device | |
144 | * of @dev, that is, without overhead due to chaining multiple | |
145 | * struct device. For example, when struct safe_device is used | |
146 | * Tw > Tw'. | |
147 | * Let Trd be the time to read a block from @dev. | |
148 | * | |
149 | * Notice that each single-block pass reduces the search space in half, | |
150 | * and that to reduce the search space in half writing blocks, | |
151 | * one has to increase m of one. | |
152 | * | |
153 | * Thus, in order to be better writing more blocks than | |
154 | * going for another pass, the following relation must be true: | |
155 | * | |
156 | * Tr + Tw + Tw' >= (w - 1)(Tw + Tw') | |
157 | * | |
158 | * The relation above assumes Trd = 0. | |
159 | * | |
160 | * The left side of the relation above is the time to do _another_ | |
161 | * pass writing a single block, whereas the right side is the time to | |
162 | * stay in the same pass and write (w - 1) more blocks. | |
163 | * In order words, if there is no advantage to write more blocks, | |
164 | * we stick to single-block passes. | |
165 | * | |
166 | * Tw' is there to account for any operation that writes | |
167 | * the blocks back (e.g. using struct safe_device), otherwise | |
168 | * processing operations related per written blocks that is not | |
169 | * being accounted for (e.g. reading the blocks back to test). | |
170 | * | |
171 | * Solving the relation for w: w <= Tr/(Tw + Tw') + 2 | |
172 | * | |
173 | * However, we are not interested in any w, but only those of | |
174 | * of the form (2^m - 1) to make sure that we are not better off | |
175 | * calling another pass. Thus, solving the previous relation for m: | |
176 | * | |
177 | * m <= log_2(Tr/(Tw + Tw') + 3) | |
178 | * | |
179 | * We approximate Tw' making it equal to Tw. | |
180 | */ | |
181 | t_w_us = (double)write_time_us / write_count; | |
182 | t_r_us = (double)reset_time_us / reset_count; | |
183 | t_2w_us = t_w_us > 0. ? 2. * t_w_us : 1.; /* Avoid zero division. */ | |
184 | n_block_order = ilog2(round(t_r_us / t_2w_us + 3.)); | |
185 | ||
186 | /* Bound the maximum number of blocks per pass to limit | |
187 | * the necessary amount of memory struct safe_device pre-allocates. | |
188 | */ | |
189 | if (n_block_order > MAX_N_BLOCK_ORDER) | |
190 | n_block_order = MAX_N_BLOCK_ORDER; | |
191 | ||
192 | return (1 << n_block_order) - 1; | |
193 | } | |
194 | ||
195 | /* Write blocks whose offsets are after @left_pos but | |
196 | * less or equal to @right_pos. | |
197 | */ | |
198 | static int write_test_blocks(struct device *dev, const char *stamp_blk, | |
199 | uint64_t left_pos, uint64_t right_pos, | |
200 | uint64_t *pa, uint64_t *pb, uint64_t *pmax_idx) | |
201 | { | |
202 | uint64_t pos, last_pos; | |
203 | uint64_t n_block = estimate_best_n_block(dev); | |
204 | ||
205 | assert(n_block >= 1); | |
206 | ||
207 | /* Find coeficients of function a*idx + b where idx <= max_idx. */ | |
208 | assert(left_pos < right_pos); | |
209 | *pb = left_pos + 1; | |
210 | *pa = round((right_pos - *pb) / (n_block + 1.)); | |
211 | *pa = !*pa ? 1ULL : *pa; | |
212 | *pmax_idx = (right_pos - *pb) / *pa; | |
213 | if (*pmax_idx >= n_block) { | |
214 | /* Shift the zero of the function to the right. | |
215 | * This avoids picking the leftmost block when a more | |
216 | * informative block to the right is available. | |
217 | * This also biases toward righter blocks, | |
218 | * what improves the time to test good flash drives. | |
219 | */ | |
220 | *pb += *pa; | |
221 | ||
222 | *pmax_idx = n_block - 1; | |
223 | } | |
224 | last_pos = *pa * *pmax_idx + *pb; | |
225 | assert(last_pos <= right_pos); | |
226 | ||
227 | /* Write test blocks. */ | |
228 | for (pos = *pb; pos <= last_pos; pos += *pa) | |
229 | if (dev_write_block(dev, stamp_blk, pos) && | |
230 | dev_write_block(dev, stamp_blk, pos)) | |
231 | return true; | |
232 | return false; | |
233 | } | |
234 | ||
235 | /* Return true if the test block at @pos is damaged. */ | |
236 | static int test_test_block(struct device *dev, | |
237 | const char *stamp_blk, char *probe_blk, uint64_t pos) | |
238 | { | |
239 | if (dev_read_block(dev, probe_blk, pos) && | |
240 | dev_read_block(dev, probe_blk, pos)) | |
241 | return true; | |
242 | ||
243 | return !equal_blk(dev, stamp_blk, probe_blk); | |
244 | } | |
245 | ||
246 | static int probe_test_blocks(struct device *dev, | |
247 | const char *stamp_blk, char *probe_blk, | |
248 | uint64_t *pleft_pos, uint64_t *pright_pos, | |
249 | uint64_t a, uint64_t b, uint64_t max_idx) | |
250 | { | |
251 | /* Signed variables. */ | |
252 | int64_t left_idx = 0; | |
253 | int64_t right_idx = max_idx; | |
254 | int64_t idx = right_idx; | |
255 | while (left_idx <= right_idx) { | |
256 | uint64_t pos = a * idx + b; | |
257 | if (test_test_block(dev, stamp_blk, probe_blk, pos)) { | |
258 | right_idx = idx - 1; | |
259 | *pright_pos = pos; | |
260 | } else { | |
261 | left_idx = idx + 1; | |
262 | *pleft_pos = pos; | |
263 | } | |
264 | idx = (left_idx + right_idx) / 2; | |
265 | } | |
266 | return false; | |
267 | } | |
268 | ||
269 | /* Caller must guarantee that the left bock is good, and written. */ | |
270 | static int search_edge(struct device *dev, | |
271 | uint64_t *pleft_pos, uint64_t right_pos, | |
272 | const char *stamp_blk, char *probe_blk) | |
273 | { | |
274 | uint64_t gap = right_pos - *pleft_pos; | |
275 | uint64_t prv_gap = gap + 1; | |
276 | while (prv_gap > gap && gap >= 1) { | |
277 | uint64_t a, b, max_idx; | |
278 | if (write_test_blocks(dev, stamp_blk, *pleft_pos, right_pos, | |
279 | &a, &b, &max_idx)) | |
280 | return true; | |
281 | /* Reset. */ | |
282 | if (dev_reset(dev) && dev_reset(dev)) | |
283 | return true; | |
284 | if (probe_test_blocks(dev, stamp_blk, probe_blk, | |
285 | pleft_pos, &right_pos, a, b, max_idx)) | |
286 | return true; | |
287 | ||
288 | prv_gap = gap; | |
289 | gap = right_pos - *pleft_pos; | |
290 | } | |
291 | return false; | |
292 | } | |
293 | ||
294 | /* XXX Write random data to make it harder for fake chips to become "smarter". | |
295 | * There would be a random seed. | |
296 | * Buffer cannot be all 0x00 or all 0xFF. | |
297 | */ | |
298 | static void fill_buffer(char *buf, int len) | |
299 | { | |
300 | memset(buf, 0xAA, len); | |
301 | } | |
302 | ||
303 | int probe_device_max_blocks(struct device *dev) | |
304 | { | |
305 | uint64_t num_blocks = dev_get_size_byte(dev) >> | |
306 | dev_get_block_order(dev); | |
307 | int n = ceiling_log2(num_blocks); | |
308 | ||
309 | /* Make sure that there is no overflow in the formula below. | |
310 | * The number 10 is arbitrary here, that is, it's not tight. | |
311 | */ | |
312 | assert(MAX_N_BLOCK_ORDER < sizeof(int) - 10); | |
313 | ||
314 | return | |
315 | /* search_wrap() */ | |
316 | 1 + | |
317 | /* Search_edge() | |
318 | * | |
319 | * The number of used blocks is (p * w); see comments in | |
320 | * estimate_best_n_block() for the definition of the variables. | |
321 | * | |
322 | * p * w = n/m * (2^m - 1) < n/m * 2^m = n * (2^m / m) | |
323 | * | |
324 | * Let f(m) be 2^m / m. One can prove that f(m + 1) >= f(m) | |
325 | * for all m >= 1. Therefore, the following bound is true. | |
326 | * | |
327 | * p * w < n * f(max_m) | |
328 | */ | |
329 | ((n << MAX_N_BLOCK_ORDER) / MAX_N_BLOCK_ORDER); | |
330 | } | |
331 | ||
332 | /* XXX Properly handle read and write errors. | |
333 | * Review each assert to check if them can be removed. | |
334 | */ | |
335 | int probe_device(struct device *dev, uint64_t *preal_size_byte, | |
336 | uint64_t *pannounced_size_byte, int *pwrap, int *pblock_order) | |
337 | { | |
338 | uint64_t dev_size_byte = dev_get_size_byte(dev); | |
339 | const int block_size = dev_get_block_size(dev); | |
340 | const int block_order = dev_get_block_order(dev); | |
341 | char stack[511 + (2 << block_order)]; | |
342 | char *stamp_blk, *probe_blk; | |
343 | /* XXX Don't write at the very beginning of the card to avoid | |
344 | * losing the partition table. | |
345 | * But write at a random locations to make harder for fake chips | |
346 | * to become "smarter". | |
347 | * And try a couple of blocks if they keep failing. | |
348 | */ | |
349 | uint64_t left_pos = 10; | |
350 | uint64_t right_pos = (dev_size_byte >> block_order) - 1; | |
351 | struct device *pdev; | |
352 | ||
353 | assert(dev_size_byte % block_size == 0); | |
354 | assert(left_pos < right_pos); | |
355 | ||
356 | pdev = create_perf_device(dev); | |
357 | if (!pdev) | |
358 | return -ENOMEM; | |
359 | ||
360 | /* Aligning these pointers is necessary to directly read and write | |
361 | * the block device. | |
362 | * For the file device, this is superfluous. | |
363 | */ | |
364 | stamp_blk = align_512(stack); | |
365 | probe_blk = stamp_blk + block_size; | |
366 | ||
367 | fill_buffer(stamp_blk, block_size); | |
368 | ||
369 | /* Make sure that there is at least a good block at the beginning | |
370 | * of the drive. | |
371 | */ | |
372 | if (test_block(pdev, stamp_blk, probe_blk, left_pos)) | |
373 | goto bad; | |
374 | ||
375 | if (search_wrap(pdev, left_pos, &right_pos, stamp_blk, probe_blk)) | |
376 | goto bad; | |
377 | ||
378 | if (search_edge(pdev, &left_pos, right_pos, stamp_blk, probe_blk)) | |
379 | goto bad; | |
380 | ||
381 | *preal_size_byte = (left_pos + 1) << block_order; | |
382 | *pannounced_size_byte = dev_size_byte; | |
383 | *pwrap = ceiling_log2(right_pos << block_order); | |
384 | *pblock_order = block_order; | |
385 | goto out; | |
386 | ||
387 | bad: | |
388 | *preal_size_byte = 0; | |
389 | *pannounced_size_byte = dev_size_byte; | |
390 | *pwrap = ceiling_log2(dev_size_byte); | |
391 | *pblock_order = block_order; | |
392 | ||
393 | out: | |
394 | pdev_detach_and_free(pdev); | |
395 | return 0; | |
396 | } |
0 | #ifndef HEADER_LIBPROBE_H | |
1 | #define HEADER_LIBPROBE_H | |
2 | ||
3 | #include <stdint.h> | |
4 | ||
5 | #include "libdevs.h" | |
6 | ||
7 | int probe_device_max_blocks(struct device *dev); | |
8 | ||
9 | int probe_device(struct device *dev, uint64_t *preal_size_byte, | |
10 | uint64_t *pannounced_size_byte, int *pwrap, int *block_order); | |
11 | ||
12 | #endif /* HEADER_LIBPROBE_H */ |
0 | #include "libutils.h" | |
1 | ||
2 | /* Count the number of 1 bits. */ | |
3 | static int pop(uint64_t x) | |
4 | { | |
5 | int n = 0; | |
6 | while (x) { | |
7 | n++; | |
8 | x = x & (x - 1); | |
9 | } | |
10 | return n; | |
11 | } | |
12 | ||
13 | int ilog2(uint64_t x) | |
14 | { | |
15 | x = x | (x >> 1); | |
16 | x = x | (x >> 2); | |
17 | x = x | (x >> 4); | |
18 | x = x | (x >> 8); | |
19 | x = x | (x >> 16); | |
20 | x = x | (x >> 32); | |
21 | return pop(x) - 1; | |
22 | } | |
23 | ||
24 | /* Least power of 2 greater than or equal to x. */ | |
25 | static uint64_t clp2(uint64_t x) | |
26 | { | |
27 | x = x - 1; | |
28 | x = x | (x >> 1); | |
29 | x = x | (x >> 2); | |
30 | x = x | (x >> 4); | |
31 | x = x | (x >> 8); | |
32 | x = x | (x >> 16); | |
33 | x = x | (x >> 32); | |
34 | return x + 1; | |
35 | } | |
36 | ||
37 | int ceiling_log2(uint64_t x) | |
38 | { | |
39 | return ilog2(clp2(x)); | |
40 | } |
0 | #ifndef HEADER_LIBUTILS_H | |
1 | #define HEADER_LIBUTILS_H | |
2 | ||
3 | #include <stdint.h> | |
4 | ||
5 | int ilog2(uint64_t x); | |
6 | int ceiling_log2(uint64_t x); | |
7 | ||
8 | static inline void *align_512(void *p) | |
9 | { | |
10 | uintptr_t ip = (uintptr_t)p; | |
11 | return (void *)( (ip + 511) & ~511 ); | |
12 | } | |
13 | ||
14 | #endif /* HEADER_LIBUTILS_H */ |
0 | 0 | #define _GNU_SOURCE |
1 | ||
2 | #if __APPLE__ && __MACH__ | |
3 | ||
4 | #define _DARWIN_C_SOURCE | |
5 | ||
6 | #include <fcntl.h> /* For function fcntl. */ | |
7 | ||
8 | #endif /* Apple Macintosh */ | |
1 | 9 | |
2 | 10 | #include <stdio.h> |
3 | 11 | #include <stdlib.h> |
4 | 12 | #include <string.h> |
13 | #include <ctype.h> | |
14 | #include <assert.h> | |
15 | #include <limits.h> | |
5 | 16 | #include <sys/types.h> |
6 | 17 | #include <dirent.h> |
7 | 18 | #include <errno.h> |
8 | 19 | #include <err.h> |
9 | 20 | |
21 | #include "version.h" | |
10 | 22 | #include "utils.h" |
11 | 23 | |
12 | 24 | const char *adjust_unit(double *ptr_bytes) |
208 | 220 | void print_header(FILE *f, const char *name) |
209 | 221 | { |
210 | 222 | fprintf(f, |
211 | "F3 %s 4.0\n" | |
223 | "F3 %s " F3_STR_VERSION "\n" | |
212 | 224 | "Copyright (C) 2010 Digirati Internet LTDA.\n" |
213 | 225 | "This is free software; see the source for copying conditions.\n" |
214 | 226 | "\n", name); |
215 | 227 | } |
228 | ||
229 | #if __APPLE__ && __MACH__ | |
230 | ||
231 | /* This function is a _rough_ approximation of fdatasync(2). */ | |
232 | int fdatasync(int fd) | |
233 | { | |
234 | return fcntl(fd, F_FULLFSYNC); | |
235 | } | |
236 | ||
237 | /* This function is a _rough_ approximation of posix_fadvise(2). */ | |
238 | int posix_fadvise(int fd, off_t offset, off_t len, int advice) | |
239 | { | |
240 | UNUSED(offset); | |
241 | UNUSED(len); | |
242 | switch (advice) { | |
243 | case POSIX_FADV_SEQUENTIAL: | |
244 | return fcntl(fd, F_RDAHEAD, 1); | |
245 | case POSIX_FADV_DONTNEED: | |
246 | return fcntl(fd, F_NOCACHE, 1); | |
247 | default: | |
248 | assert(0); | |
249 | } | |
250 | } | |
251 | ||
252 | #endif /* Apple Macintosh */ |
0 | 0 | #ifndef HEADER_UTILS_H |
1 | 1 | #define HEADER_UTILS_H |
2 | 2 | |
3 | #include <ctype.h> | |
4 | #include <assert.h> | |
5 | #include <sys/time.h> | |
6 | #include <limits.h> | |
7 | #include <stdint.h> | |
3 | #include <stdio.h> /* For type FILE. */ | |
4 | #include <sys/time.h> /* For struct timeval. */ | |
5 | #include <stdint.h> /* For type uint64_t. */ | |
8 | 6 | |
9 | 7 | #define SECTOR_SIZE (512) |
10 | 8 | #define GIGABYTES (1024 * 1024 * 1024) |
35 | 33 | return prv_number * 4294967311ULL + 17; |
36 | 34 | } |
37 | 35 | |
36 | #define UNUSED(x) ((void)x) | |
37 | ||
38 | 38 | #if __APPLE__ && __MACH__ |
39 | 39 | |
40 | /* For function fcntl. */ | |
41 | #include <fcntl.h> | |
42 | /* For type off_t. */ | |
43 | #include <unistd.h> | |
44 | ||
45 | /* This function is a _rough_ approximation of fdatasync(2). */ | |
46 | static inline int fdatasync(int fd) | |
47 | { | |
48 | return fcntl(fd, F_FULLFSYNC); | |
49 | } | |
40 | #include <unistd.h> /* For type off_t. */ | |
50 | 41 | |
51 | 42 | #define POSIX_FADV_SEQUENTIAL 2 /* Expect sequential page references. */ |
52 | 43 | #define POSIX_FADV_DONTNEED 4 /* Don't need these pages. */ |
53 | 44 | |
54 | /* This function is a _rough_ approximation of posix_fadvise(2). */ | |
55 | static inline int posix_fadvise(int fd, off_t offset, off_t len, int advice) | |
56 | { | |
57 | switch (advice) { | |
58 | case POSIX_FADV_SEQUENTIAL: | |
59 | return fcntl(fd, F_RDAHEAD, 1); | |
60 | case POSIX_FADV_DONTNEED: | |
61 | return fcntl(fd, F_NOCACHE, 1); | |
62 | default: | |
63 | assert(0); | |
64 | } | |
65 | } | |
45 | int fdatasync(int fd); | |
46 | int posix_fadvise(int fd, off_t offset, off_t len, int advice); | |
66 | 47 | |
67 | 48 | #endif /* Apple Macintosh */ |
68 | 49 |