• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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