Serenity Operating System
1/*
2 * Copyright (c) 2021, János Tóth <toth-janos@outlook.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/DeprecatedString.h>
8#include <AK/Optional.h>
9#include <AK/Vector.h>
10#include <LibMain/Main.h>
11#include <ctype.h>
12#include <fcntl.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <sys/types.h>
16#include <unistd.h>
17
18char const* usage = "usage:\n"
19 "\tdd <options>\n"
20 "options:\n"
21 "\tif=<file>\tinput file (default: stdin)\n"
22 "\tof=<file>\toutput file (default: stdout)\n"
23 "\tbs=<size>\tblocks size may be followed by multiplicate suffixes: k=1024, M=1024*1024, G=1024*1024*1024 (default: 512)\n"
24 "\tcount=<size>\t<size> blocks to copy (default: 0 (until end-of-file))\n"
25 "\tseek=<size>\tskip <size> blocks at start of output (default: 0)\n"
26 "\tskip=<size>\tskip <size> blocks at start of input (default: 0)\n"
27 "\tstatus=<level>\tlevel of output (default: default)\n"
28 "\t\t\tdefault - error messages + final statistics\n"
29 "\t\t\tnone - just error messages\n"
30 "\t\t\tnoxfer - no final statistics\n"
31 "\t--help\t\tshows this text\n";
32
33enum Status {
34 Default,
35 None,
36 Noxfer
37};
38
39static StringView split_at_equals(StringView argument)
40{
41 auto values = argument.split_view('=', SplitBehavior::KeepEmpty);
42 if (values.size() < 2) {
43 warnln("Unable to parse: {}", argument);
44 return {};
45 } else {
46 return values[1];
47 }
48}
49
50static int handle_io_file_arguments(int& fd, int flags, StringView argument)
51{
52 auto value = split_at_equals(argument);
53 if (value.is_empty()) {
54 return -1;
55 }
56
57 fd = open(value.to_deprecated_string().characters(), flags, 0666);
58 if (fd == -1) {
59 warnln("Unable to open: {}", value);
60 return -1;
61 } else {
62 return 0;
63 }
64}
65
66static int handle_size_arguments(size_t& numeric_value, StringView argument)
67{
68 auto value = split_at_equals(argument);
69 if (value.is_empty()) {
70 return -1;
71 }
72
73 unsigned suffix_multiplier = 1;
74 auto suffix = value[value.length() - 1];
75 switch (tolower(suffix)) {
76 case 'k':
77 suffix_multiplier = KiB;
78 value = value.substring_view(0, value.length() - 1);
79 break;
80 case 'm':
81 suffix_multiplier = MiB;
82 value = value.substring_view(0, value.length() - 1);
83 break;
84 case 'g':
85 suffix_multiplier = GiB;
86 value = value.substring_view(0, value.length() - 1);
87 break;
88 }
89
90 Optional<unsigned> numeric_optional = value.to_uint();
91 if (!numeric_optional.has_value()) {
92 warnln("Invalid size-value: {}", value);
93 return -1;
94 }
95
96 numeric_value = numeric_optional.value() * suffix_multiplier;
97 if (numeric_value < 1) {
98 warnln("Invalid size-value: {}", numeric_value);
99 return -1;
100 } else {
101 return 0;
102 }
103}
104
105static int handle_status_arguments(Status& status, StringView argument)
106{
107 auto value = split_at_equals(argument);
108 if (value.is_empty()) {
109 return -1;
110 }
111
112 if (value == "default") {
113 status = Default;
114 return 0;
115 } else if (value == "noxfer") {
116 status = Noxfer;
117 return 0;
118 } else if (value == "none") {
119 status = None;
120 return 0;
121 } else {
122 warnln("Unknown status: {}", value);
123 return -1;
124 }
125}
126
127ErrorOr<int> serenity_main(Main::Arguments arguments)
128{
129 int input_fd = 0;
130 int input_flags = O_RDONLY;
131 int output_fd = 1;
132 int output_flags = O_CREAT | O_WRONLY | O_TRUNC;
133 size_t block_size = 512;
134 size_t count = 0;
135 size_t skip = 0;
136 size_t seek = 0;
137 Status status = Default;
138
139 size_t total_bytes_copied = 0;
140 size_t total_blocks_in = 0, partial_blocks_in = 0;
141 size_t total_blocks_out = 0, partial_blocks_out = 0;
142 uint8_t* buffer = nullptr;
143 ssize_t nread = 0, nwritten = 0;
144
145 for (size_t a = 1; a < arguments.strings.size(); a++) {
146 auto argument = arguments.strings[a];
147
148 if (argument == "--help") {
149 out("{}", usage);
150 return 0;
151 } else if (argument.starts_with("if="sv)) {
152 if (handle_io_file_arguments(input_fd, input_flags, argument) < 0) {
153 return 1;
154 }
155 } else if (argument.starts_with("of="sv)) {
156 if (handle_io_file_arguments(output_fd, output_flags, argument) < 0) {
157 return 1;
158 }
159 } else if (argument.starts_with("bs="sv)) {
160 if (handle_size_arguments(block_size, argument) < 0) {
161 return 1;
162 }
163 } else if (argument.starts_with("count="sv)) {
164 if (handle_size_arguments(count, argument) < 0) {
165 return 1;
166 }
167 } else if (argument.starts_with("seek="sv)) {
168 if (handle_size_arguments(seek, argument) < 0) {
169 return 1;
170 }
171 } else if (argument.starts_with("skip="sv)) {
172 if (handle_size_arguments(skip, argument) < 0) {
173 return 1;
174 }
175 } else if (argument.starts_with("status="sv)) {
176 if (handle_status_arguments(status, argument) < 0) {
177 return 1;
178 }
179 } else {
180 warn("{}", usage);
181 return 1;
182 }
183 }
184
185 if ((buffer = (uint8_t*)malloc(block_size)) == nullptr) {
186 warnln("Unable to allocate {} bytes for the buffer.", block_size);
187 return -1;
188 }
189
190 if (seek > 0) {
191 if (lseek(output_fd, seek * block_size, SEEK_SET) < 0) {
192 warnln("Unable to seek {} bytes.", seek * block_size);
193 return -1;
194 }
195 }
196
197 while (1) {
198 nread = read(input_fd, buffer, block_size);
199 if (nread < 0) {
200 warnln("Cannot read from the input.");
201 break;
202 } else if (nread == 0) {
203 break;
204 } else {
205 if ((size_t)nread != block_size) {
206 partial_blocks_in++;
207 } else {
208 total_blocks_in++;
209 }
210
211 if (partial_blocks_in + total_blocks_in <= skip) {
212 continue;
213 }
214
215 nwritten = write(output_fd, buffer, nread);
216 if (nwritten < 0) {
217 warnln("Cannot write to the output.");
218 break;
219 } else if (nwritten == 0) {
220 break;
221 } else {
222 if ((size_t)nwritten < block_size) {
223 partial_blocks_out++;
224 } else {
225 total_blocks_out++;
226 }
227
228 total_bytes_copied += nwritten;
229
230 if (count > 0 && (partial_blocks_out + total_blocks_out) >= count) {
231 break;
232 }
233 }
234 }
235 }
236
237 if (status == Default) {
238 warnln("{}+{} blocks in", total_blocks_in, partial_blocks_in);
239 warnln("{}+{} blocks out", total_blocks_out, partial_blocks_out);
240 warnln("{} bytes copied.", total_bytes_copied);
241 }
242
243 free(buffer);
244
245 if (input_fd != 0) {
246 close(input_fd);
247 }
248
249 if (output_fd != 1) {
250 close(output_fd);
251 }
252
253 return 0;
254}