Serenity Operating System
1/*
2 * Copyright (c) 2021, the SerenityOS developers.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Random.h>
8#include <LibCore/ArgsParser.h>
9#include <errno.h>
10#include <fcntl.h>
11#include <inttypes.h>
12#include <string.h>
13#include <sys/stat.h>
14#include <unistd.h>
15
16bool verify_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer);
17bool write_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer);
18
19bool verify_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer)
20{
21 auto offset = block * buffer.size();
22 auto rs = lseek(fd, offset, SEEK_SET);
23 if (rs < 0) {
24 fprintf(stderr, "Couldn't seek to block %" PRIi64 " (offset %" PRIi64 ") while verifying: %s\n", block, offset, strerror(errno));
25 return false;
26 }
27 auto rw = read(fd, buffer.data(), buffer.size());
28 if (rw != static_cast<int>(buffer.size())) {
29 fprintf(stderr, "Failure to read block %" PRIi64 ": %s\n", block, strerror(errno));
30 return false;
31 }
32 srand((seed + 1) * (block + 1));
33 for (size_t i = 0; i < buffer.size(); i++) {
34 if (buffer[i] != rand() % 256) {
35 fprintf(stderr, "Discrepancy detected at block %" PRIi64 " offset %zd\n", block, i);
36 return false;
37 }
38 }
39 return true;
40}
41
42bool write_block(int fd, int seed, off_t block, AK::ByteBuffer& buffer)
43{
44 auto offset = block * buffer.size();
45 auto rs = lseek(fd, offset, SEEK_SET);
46 if (rs < 0) {
47 fprintf(stderr, "Couldn't seek to block %" PRIi64 " (offset %" PRIi64 ") while verifying: %s\n", block, offset, strerror(errno));
48 return false;
49 }
50 srand((seed + 1) * (block + 1));
51 for (size_t i = 0; i < buffer.size(); i++)
52 buffer[i] = rand();
53 auto rw = write(fd, buffer.data(), buffer.size());
54 if (rw != static_cast<int>(buffer.size())) {
55 fprintf(stderr, "Failure to write block %" PRIi64 ": %s\n", block, strerror(errno));
56 return false;
57 }
58 return true;
59}
60
61int main(int argc, char** argv)
62{
63 Vector<StringView> arguments;
64 arguments.ensure_capacity(argc);
65 for (auto i = 0; i < argc; ++i)
66 arguments.append({ argv[i], strlen(argv[i]) });
67
68 DeprecatedString target;
69 int min_block_offset = 0;
70 int block_length = 2048;
71 int block_size = 512;
72 int count = 1024;
73 int rng_seed = 0;
74 bool paranoid_mode = false;
75 bool random_mode = false;
76 bool stop_mode = false;
77 bool uninitialized_mode = false;
78
79 Core::ArgsParser args_parser;
80 args_parser.add_option(min_block_offset, "Minimum block offset to consider", "min-offset", 'o', "size");
81 args_parser.add_option(block_length, "Number of blocks to consider", "length", 's', "size");
82 args_parser.add_option(block_size, "Block size", "block-size", 'b', "size");
83 args_parser.add_option(count, "Number of write/read cycles to run", "number", 'n', "number");
84 args_parser.add_option(rng_seed, "Random number generator seed", "seed", 'S', "number");
85 args_parser.add_option(paranoid_mode, "Check entire range for consistency after each write", "paranoid", 'p');
86 args_parser.add_option(random_mode, "Write one block inside range at random", "random", 'r');
87 args_parser.add_option(stop_mode, "Stop after first error", "abort-on-error", 'a');
88 args_parser.add_option(uninitialized_mode, "Don't pre-initialize block range", "uninitialized", 'u');
89 args_parser.add_positional_argument(target, "Target device/file path", "target");
90 args_parser.parse(arguments);
91
92 auto buffer_result = AK::ByteBuffer::create_zeroed(block_size);
93 if (buffer_result.is_error()) {
94 warnln("Failed to allocate a buffer of {} bytes", block_size);
95 return EXIT_FAILURE;
96 }
97 auto buffer = buffer_result.release_value();
98
99 int fd = open(target.characters(), O_CREAT | O_RDWR, 0666);
100 if (fd < 0) {
101 perror("Couldn't create target file");
102 return EXIT_FAILURE;
103 }
104
105 if (!uninitialized_mode) {
106 int old_percent = -100;
107 for (int i = min_block_offset; i < min_block_offset + block_length; i++) {
108 int percent;
109 if (block_length <= 1)
110 percent = 100;
111 else
112 percent = 100 * (i - min_block_offset) / (block_length - 1);
113 if (old_percent != percent) {
114 printf("Pre-initializing entire block range (%3d%%)...\n", percent);
115 old_percent = percent;
116 }
117
118 if (!write_block(fd, rng_seed, i, buffer))
119 return EXIT_FAILURE;
120 }
121 }
122
123 int r = EXIT_SUCCESS;
124 for (int i = 0; i < count; i++) {
125 printf("(%d/%d)\tPass %d...\n", i + 1, count, i + 1);
126
127 for (int j = min_block_offset; j < min_block_offset + block_length; j++) {
128 off_t block;
129 if (random_mode)
130 while ((block = AK::get_random<off_t>()) < 0)
131 ;
132 else
133 block = j;
134 block = min_block_offset + block % block_length;
135
136 if (paranoid_mode) {
137 for (int k = min_block_offset; j < min_block_offset + block_length; j++) {
138 if (!verify_block(fd, rng_seed, k, buffer)) {
139 if (stop_mode)
140 return EXIT_FAILURE;
141 else
142 r = EXIT_FAILURE;
143 }
144 }
145 } else {
146 if (!verify_block(fd, rng_seed, block, buffer)) {
147 if (stop_mode)
148 return EXIT_FAILURE;
149 else
150 r = EXIT_FAILURE;
151 }
152 }
153
154 if (!write_block(fd, rng_seed, block, buffer)) {
155 if (stop_mode)
156 return EXIT_FAILURE;
157 else
158 r = EXIT_FAILURE;
159 }
160 }
161 }
162
163 close(fd);
164 return r;
165}