1/*-
2 * Copyright (c) 2024 Klara, Inc.
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 *
6 * This program reads a tarball from stdin, recalculates the checksums of
7 * all ustar records within it, and writes the result to stdout.
8 */
9
10#include <err.h>
11#include <stdarg.h>
12#include <stdbool.h>
13#include <stdint.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18
19static bool opt_v;
20
21static int
22verbose(const char *fmt, ...)
23{
24	va_list ap;
25	int ret;
26
27	if (!opt_v)
28		return (0);
29	va_start(ap, fmt);
30	ret = vfprintf(stderr, fmt, ap);
31	va_end(ap);
32	return (ret);
33}
34
35static void
36tarsum(FILE *in, const char *ifn, FILE *out, const char *ofn)
37{
38	union {
39		uint8_t bytes[512];
40		struct {
41			uint8_t	prelude[148];
42			char	checksum[8];
43			uint8_t	interlude[101];
44			char	magic[6];
45			char	version[2];
46			char	postlude[];
47		};
48	} ustar;
49	unsigned long sum;
50	off_t offset = 0;
51	ssize_t ret;
52	size_t i;
53
54	for (;;) {
55		if ((ret = fread(&ustar, 1, sizeof(ustar), in)) < 0)
56			err(1, "%s", ifn);
57		else if (ret == 0)
58			break;
59		else if ((size_t)ret < sizeof(ustar))
60			errx(1, "%s: Short read", ifn);
61		if (strcmp(ustar.magic, "ustar") == 0 &&
62		    ustar.version[0] == '0' && ustar.version[1] == '0') {
63			verbose("header found at offset %#lx\n", offset);
64			verbose("current checksum %.*s\n",
65			    (int)sizeof(ustar.checksum), ustar.checksum);
66			memset(ustar.checksum, ' ', sizeof(ustar.checksum));
67			for (sum = i = 0; i < sizeof(ustar); i++)
68				sum += ustar.bytes[i];
69			verbose("calculated checksum %#lo\n", sum);
70			sprintf(ustar.checksum, "%#lo", sum);
71		}
72		if ((ret = fwrite(&ustar, 1, sizeof(ustar), out)) < 0)
73			err(1, "%s", ofn);
74		else if ((size_t)ret < sizeof(ustar))
75			errx(1, "%s: Short write", ofn);
76		offset += sizeof(ustar);
77	}
78	verbose("%lu bytes processed\n", offset);
79}
80
81static void
82usage(void)
83{
84	fprintf(stderr, "usage: tarsum [-v] [-o output] [input]\n");
85	exit(1);
86}
87
88int
89main(int argc, char *argv[])
90{
91	const char *ifn, *ofn = NULL;
92	FILE *in, *out;
93	int opt;
94
95	while ((opt = getopt(argc, argv, "o:v")) != -1) {
96		switch (opt) {
97		case 'o':
98			ofn = optarg;
99			break;
100		case 'v':
101			opt_v = true;
102			break;
103		default:
104			usage();
105		}
106	}
107	argc -= optind;
108	argv += optind;
109	if (argc == 0 || strcmp(*argv, "-") == 0) {
110		ifn = "stdin";
111		in = stdin;
112	} else if (argc == 1) {
113		ifn = *argv;
114		if ((in = fopen(ifn, "rb")) == NULL)
115			err(1, "%s", ifn);
116	} else {
117		usage();
118	}
119	if (ofn == NULL || strcmp(ofn, "-") == 0) {
120		ofn = "stdout";
121		out = stdout;
122	} else {
123		if ((out = fopen(ofn, "wb")) == NULL)
124			err(1, "%s", ofn);
125	}
126	tarsum(in, ifn, out, ofn);
127	return (0);
128}
129