1 // Test for MinGW.
2 #if defined(__MINGW32__) || defined(__MINGW64__)
3 # define MINGW
4 #endif
5
6 // Decide if the writev(2) system call needs to be emulated as a series of
7 // write(2) calls. At least MinGW does not support writev(2).
8 #ifdef MINGW
9 # define EMULATE_WRITEV
10 #endif
11
12 // Include the necessary system header when using the system's writev(2).
13 #ifndef EMULATE_WRITEV
14 # define _XOPEN_SOURCE // Unlock IOV_MAX
15 # include <sys/uio.h>
16 #endif
17
18 #include <stdbool.h>
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <getopt.h>
24 #include <errno.h>
25 #include <limits.h>
26
27 #include "../include/libbase64.h"
28
29 // Size of the buffer for the "raw" (not base64-encoded) data in bytes.
30 #define BUFFER_RAW_SIZE (1024 * 1024)
31
32 // Size of the buffer for the base64-encoded data in bytes. The base64-encoded
33 // data is 4/3 the size of the input, with some margin to be sure.
34 #define BUFFER_ENC_SIZE (BUFFER_RAW_SIZE * 4 / 3 + 16)
35
36 // Global config structure.
37 struct config {
38
39 // Name by which the program was called on the command line.
40 const char *name;
41
42 // Name of the input file for logging purposes.
43 const char *file;
44
45 // Input file handle.
46 FILE *fp;
47
48 // Wrap width in characters, for encoding only.
49 size_t wrap;
50
51 // Whether to run in decode mode.
52 bool decode;
53
54 // Whether to just print the help text and exit.
55 bool print_help;
56 };
57
58 // Input/output buffer structure.
59 struct buffer {
60
61 // Runtime-allocated buffer for raw (unencoded) data.
62 char *raw;
63
64 // Runtime-allocated buffer for base64-encoded data.
65 char *enc;
66 };
67
68 // Optionally emulate writev(2) as a series of write calls.
69 #ifdef EMULATE_WRITEV
70
71 // Quick and dirty definition of IOV_MAX as it is probably not defined.
72 #ifndef IOV_MAX
73 # define IOV_MAX 1024
74 #endif
75
76 // Quick and dirty definition of this system struct, for local use only.
77 struct iovec {
78
79 // Opaque data pointer.
80 void *iov_base;
81
82 // Length of the data in bytes.
83 size_t iov_len;
84 };
85
86 static ssize_t
writev(const int fd,const struct iovec * iov,int iovcnt)87 writev (const int fd, const struct iovec *iov, int iovcnt)
88 {
89 ssize_t r, nwrite = 0;
90
91 // Reset the error marker.
92 errno = 0;
93
94 while (iovcnt-- > 0) {
95
96 // Write the vector; propagate errors back to the caller. Note
97 // that this loses information about how much vectors have been
98 // successfully written, but that also seems to be the case
99 // with the real function. The API is somewhat flawed.
100 if ((r = write(fd, iov->iov_base, iov->iov_len)) < 0) {
101 return r;
102 }
103
104 // Update the total write count.
105 nwrite += r;
106
107 // Return early after a partial write; the caller should retry.
108 if ((size_t) r != iov->iov_len) {
109 break;
110 }
111
112 // Move to the next vector.
113 iov++;
114 }
115
116 return nwrite;
117 }
118
119 #endif // EMULATE_WRITEV
120
121 static bool
buffer_alloc(const struct config * config,struct buffer * buf)122 buffer_alloc (const struct config *config, struct buffer *buf)
123 {
124 if ((buf->raw = malloc(BUFFER_RAW_SIZE)) == NULL ||
125 (buf->enc = malloc(BUFFER_ENC_SIZE)) == NULL) {
126 free(buf->raw);
127 fprintf(stderr, "%s: malloc: %s\n",
128 config->name, strerror(errno));
129 return false;
130 }
131
132 return true;
133 }
134
135 static void
buffer_free(struct buffer * buf)136 buffer_free (struct buffer *buf)
137 {
138 free(buf->raw);
139 free(buf->enc);
140 }
141
142 static bool
writev_retry(const struct config * config,struct iovec * iov,size_t nvec)143 writev_retry (const struct config *config, struct iovec *iov, size_t nvec)
144 {
145 // Writing nothing always succeeds.
146 if (nvec == 0) {
147 return true;
148 }
149
150 while (true) {
151 ssize_t nwrite;
152
153 // Try to write the vectors to stdout.
154 if ((nwrite = writev(1, iov, nvec)) < 0) {
155
156 // Retry on EINTR.
157 if (errno == EINTR) {
158 continue;
159 }
160
161 // Quit on other errors.
162 fprintf(stderr, "%s: writev: %s\n",
163 config->name, strerror(errno));
164 return false;
165 }
166
167 // The return value of `writev' is the number of bytes written.
168 // To check for success, we traverse the list and remove all
169 // written vectors. The call succeeded if the list is empty.
170 while (true) {
171
172 // Retry if this vector is not or partially written.
173 if (iov->iov_len > (size_t) nwrite) {
174 char *base = iov->iov_base;
175
176 iov->iov_base = (size_t) nwrite + base;
177 iov->iov_len -= (size_t) nwrite;
178 break;
179 }
180
181 // Move to the next vector.
182 nwrite -= iov->iov_len;
183 iov++;
184
185 // Return successfully if all vectors were written.
186 if (--nvec == 0) {
187 return true;
188 }
189 }
190 }
191 }
192
193 static inline bool
iov_append(const struct config * config,struct iovec * iov,size_t * nvec,char * base,const size_t len)194 iov_append (const struct config *config, struct iovec *iov,
195 size_t *nvec, char *base, const size_t len)
196 {
197 // Add the buffer to the IO vector array.
198 iov[*nvec].iov_base = base;
199 iov[*nvec].iov_len = len;
200
201 // Increment the array index. Flush the array if it is full.
202 if (++(*nvec) == IOV_MAX) {
203 if (writev_retry(config, iov, IOV_MAX) == false) {
204 return false;
205 }
206 *nvec = 0;
207 }
208
209 return true;
210 }
211
212 static bool
write_stdout(const struct config * config,const char * buf,size_t len)213 write_stdout (const struct config *config, const char *buf, size_t len)
214 {
215 while (len > 0) {
216 ssize_t nwrite;
217
218 // Try to write the buffer to stdout.
219 if ((nwrite = write(1, buf, len)) < 0) {
220
221 // Retry on EINTR.
222 if (errno == EINTR) {
223 continue;
224 }
225
226 // Quit on other errors.
227 fprintf(stderr, "%s: write: %s\n",
228 config->name, strerror(errno));
229 return false;
230 }
231
232 // Update the buffer position.
233 buf += (size_t) nwrite;
234 len -= (size_t) nwrite;
235 }
236
237 return true;
238 }
239
240 static bool
write_wrapped(const struct config * config,char * buf,size_t len)241 write_wrapped (const struct config *config, char *buf, size_t len)
242 {
243 static size_t col = 0;
244
245 // Special case: if buf is NULL, print final trailing newline.
246 if (buf == NULL) {
247 if (config->wrap > 0 && col > 0) {
248 return write_stdout(config, "\n", 1);
249 }
250 return true;
251 }
252
253 // If no wrap width is given, write the entire buffer.
254 if (config->wrap == 0) {
255 return write_stdout(config, buf, len);
256 }
257
258 // Statically allocated IO vector buffer.
259 static struct iovec iov[IOV_MAX];
260 size_t nvec = 0;
261
262 while (len > 0) {
263
264 // Number of characters to fill the current line.
265 size_t nwrite = config->wrap - col;
266
267 // Do not write more data than is available.
268 if (nwrite > len) {
269 nwrite = len;
270 }
271
272 // Append the data to the IO vector array.
273 if (iov_append(config, iov, &nvec, buf, nwrite) == false) {
274 return false;
275 }
276
277 // Advance the buffer.
278 len -= nwrite;
279 buf += nwrite;
280 col += nwrite;
281
282 // If the line is full, append a newline.
283 if (col == config->wrap) {
284 if (iov_append(config, iov, &nvec, "\n", 1) == false) {
285 return false;
286 }
287 col = 0;
288 }
289 }
290
291 // Write the remaining vectors.
292 if (writev_retry(config, iov, nvec) == false) {
293 return false;
294 }
295
296 return true;
297 }
298
299 static bool
encode(const struct config * config,struct buffer * buf)300 encode (const struct config *config, struct buffer *buf)
301 {
302 size_t nread, nout;
303 struct base64_state state;
304
305 // Initialize the encoder's state structure.
306 base64_stream_encode_init(&state, 0);
307
308 // Read raw data into the buffer.
309 while ((nread = fread(buf->raw, 1, BUFFER_RAW_SIZE, config->fp)) > 0) {
310
311 // Encode the raw input into the encoded buffer.
312 base64_stream_encode(&state, buf->raw, nread, buf->enc, &nout);
313
314 // Append the encoded data to the output stream.
315 if (write_wrapped(config, buf->enc, nout) == false) {
316 return false;
317 }
318 }
319
320 // Check for stream errors.
321 if (ferror(config->fp)) {
322 fprintf(stderr, "%s: %s: read error\n",
323 config->name, config->file);
324 return false;
325 }
326
327 // Finalize the encoding by adding proper stream terminators.
328 base64_stream_encode_final(&state, buf->enc, &nout);
329
330 // Append this tail to the output stream.
331 if (write_wrapped(config, buf->enc, nout) == false) {
332 return false;
333 }
334
335 // Print optional trailing newline.
336 if (write_wrapped(config, NULL, 0) == false) {
337 return false;
338 }
339
340 return true;
341 }
342
343 static inline size_t
find_newline(const char * p,const size_t avail)344 find_newline (const char *p, const size_t avail)
345 {
346 // This is very naive and can probably be improved by vectorization.
347 for (size_t len = 0; len < avail; len++) {
348 if (p[len] == '\n') {
349 return len;
350 }
351 }
352
353 return avail;
354 }
355
356 static bool
decode(const struct config * config,struct buffer * buf)357 decode (const struct config *config, struct buffer *buf)
358 {
359 size_t avail;
360 struct base64_state state;
361
362 // Initialize the decoder's state structure.
363 base64_stream_decode_init(&state, 0);
364
365 // Read encoded data into the buffer. Use the smallest buffer size to
366 // be on the safe side: the decoded output will fit the raw buffer.
367 while ((avail = fread(buf->enc, 1, BUFFER_RAW_SIZE, config->fp)) > 0) {
368 char *start = buf->enc;
369 char *outbuf = buf->raw;
370 size_t ototal = 0;
371
372 // By popular demand, this utility tries to be bug-compatible
373 // with GNU `base64'. That includes silently ignoring newlines
374 // in the input. Tokenize the input on newline characters.
375 while (avail > 0) {
376
377 // Find the offset of the next newline character, which
378 // is also the length of the next chunk.
379 size_t outlen, len = find_newline(start, avail);
380
381 // Ignore empty chunks.
382 if (len == 0) {
383 start++;
384 avail--;
385 continue;
386 }
387
388 // Decode the chunk into the raw buffer.
389 if (base64_stream_decode(&state, start, len,
390 outbuf, &outlen) == 0) {
391 fprintf(stderr, "%s: %s: decoding error\n",
392 config->name, config->file);
393 return false;
394 }
395
396 // Update the output buffer pointer and total size.
397 outbuf += outlen;
398 ototal += outlen;
399
400 // Bail out if the whole string has been consumed.
401 if (len == avail) {
402 break;
403 }
404
405 // Move the start pointer past the newline.
406 start += len + 1;
407 avail -= len + 1;
408 }
409
410 // Append the raw data to the output stream.
411 if (write_stdout(config, buf->raw, ototal) == false) {
412 return false;
413 }
414 }
415
416 // Check for stream errors.
417 if (ferror(config->fp)) {
418 fprintf(stderr, "%s: %s: read error\n",
419 config->name, config->file);
420 return false;
421 }
422
423 return true;
424 }
425
426 static void
usage(FILE * fp,const struct config * config)427 usage (FILE *fp, const struct config *config)
428 {
429 const char *usage =
430 "Usage: %s [OPTION]... [FILE]\n"
431 "If no FILE is given or is specified as '-', "
432 "read from standard input.\n"
433 "Options:\n"
434 " -d, --decode Decode a base64 stream.\n"
435 " -h, --help Print this help text.\n"
436 " -w, --wrap=COLS Wrap encoded lines at this column. "
437 "Default 76, 0 to disable.\n";
438
439 fprintf(fp, usage, config->name);
440 }
441
442 static bool
get_wrap(struct config * config,const char * str)443 get_wrap (struct config *config, const char *str)
444 {
445 char *eptr;
446
447 // Reject empty strings.
448 if (*str == '\0') {
449 return false;
450 }
451
452 // Convert the input string to a signed long.
453 const long wrap = strtol(str, &eptr, 10);
454
455 // Reject negative numbers.
456 if (wrap < 0) {
457 return false;
458 }
459
460 // Reject strings containing non-digits.
461 if (*eptr != '\0') {
462 return false;
463 }
464
465 config->wrap = (size_t) wrap;
466 return true;
467 }
468
469 static bool
parse_opts(int argc,char ** argv,struct config * config)470 parse_opts (int argc, char **argv, struct config *config)
471 {
472 int c;
473 static const struct option opts[] = {
474 { "decode", no_argument, NULL, 'd' },
475 { "help", no_argument, NULL, 'h' },
476 { "wrap", required_argument, NULL, 'w' },
477 { NULL }
478 };
479
480 // Remember the program's name.
481 config->name = *argv;
482
483 // Parse command line options.
484 while ((c = getopt_long(argc, argv, ":dhw:", opts, NULL)) != -1) {
485 switch (c) {
486 case 'd':
487 config->decode = true;
488 break;
489
490 case 'h':
491 config->print_help = true;
492 return true;
493
494 case 'w':
495 if (get_wrap(config, optarg) == false) {
496 fprintf(stderr,
497 "%s: invalid wrap value '%s'\n",
498 config->name, optarg);
499 return false;
500 }
501 break;
502
503 case ':':
504 fprintf(stderr, "%s: missing argument for '%c'\n",
505 config->name, optopt);
506 return false;
507
508 default:
509 fprintf(stderr, "%s: unknown option '%c'\n",
510 config->name, optopt);
511 return false;
512 }
513 }
514
515 // Return successfully if no filename was given.
516 if (optind >= argc) {
517 return true;
518 }
519
520 // Return unsuccessfully if more than one filename was given.
521 if (optind + 1 < argc) {
522 fprintf(stderr, "%s: too many files\n", config->name);
523 return false;
524 }
525
526 // For compatibility with GNU Coreutils base64, treat a filename of '-'
527 // as standard input.
528 if (strcmp(argv[optind], "-") == 0) {
529 return true;
530 }
531
532 // Save the name of the file.
533 config->file = argv[optind];
534
535 // Open the file.
536 if ((config->fp = fopen(config->file, "rb")) == NULL) {
537 fprintf(stderr, "%s: %s: %s\n",
538 config->name, config->file, strerror(errno));
539 return false;
540 }
541
542 return true;
543 }
544
545 int
main(int argc,char ** argv)546 main (int argc, char **argv)
547 {
548 // Default program config.
549 struct config config = {
550 .file = "stdin",
551 .fp = stdin,
552 .wrap = 76,
553 .decode = false,
554 .print_help = false,
555 };
556 struct buffer buf;
557
558 // Parse options from the command line.
559 if (parse_opts(argc, argv, &config) == false) {
560 usage(stderr, &config);
561 return 1;
562 }
563
564 // Return early if the user just wanted the help text.
565 if (config.print_help) {
566 usage(stdout, &config);
567 return 0;
568 }
569
570 // Allocate buffers.
571 if (buffer_alloc(&config, &buf) == false) {
572 return 1;
573 }
574
575 // Encode or decode the input based on the user's choice.
576 const bool ret = config.decode
577 ? decode(&config, &buf)
578 : encode(&config, &buf);
579
580 // Free the buffers.
581 buffer_free(&buf);
582
583 // Close the input file.
584 fclose(config.fp);
585
586 // Close the output stream.
587 fclose(stdout);
588
589 // That's all, folks.
590 return ret ? 0 : 1;
591 }
592