1/*
2 * Copyright (c) 2018 iXsystems, Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <err.h>
28#include <getopt.h>
29#include <libgen.h>
30#include <stdbool.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <errno.h>
35#include <sys/queue.h>
36#include <endian.h>
37
38#include <sys/fs/hsfs_isospec.h>
39#include "cd9660_eltorito.h"
40
41#include "etdump.h"
42
43#define	ISO_DEFAULT_BLOCK_SHIFT	11
44#define	ISO_DEFAULT_BLOCK_SIZE	(1 << ISO_DEFAULT_BLOCK_SHIFT)
45
46/* Little endian */
47void
48cd9660_721(uint16_t w, unsigned char *twochar)
49{
50#if BYTE_ORDER == BIG_ENDIAN
51	w = htole16(w);
52#endif
53	memcpy(twochar, &w, 2);
54}
55
56const char *
57system_id_string(uchar_t system_id)
58{
59
60	switch (system_id) {
61	case ET_SYS_X86:
62		return ("i386");
63	case ET_SYS_PPC:
64		return ("powerpc");
65	case ET_SYS_MAC:
66		return ("mac");
67	case ET_SYS_EFI:
68		return ("efi");
69	default:
70		return ("invalid");
71	}
72}
73
74const char *
75media_type_string(uchar_t media_type)
76{
77
78	switch (media_type) {
79	case ET_MEDIA_NOEM:
80		return ("no emulation");
81	case ET_MEDIA_12FDD:
82		return ("1.2MB FDD");
83	case ET_MEDIA_144FDD:
84		return ("1.44MB FDD");
85	case ET_MEDIA_288FDD:
86		return ("2.88MB FDD");
87	case ET_MEDIA_HDD:
88		return ("HDD");
89	default:
90		return ("invalid");
91	}
92}
93
94static int
95read_sector(FILE *iso, daddr_t sector, char *buffer)
96{
97
98	fseek(iso, sector * ISO_DEFAULT_BLOCK_SIZE, SEEK_SET);
99	if (fread(buffer, ISO_DEFAULT_BLOCK_SIZE, 1, iso) != 1) {
100		return (errno);
101	}
102	return (0);
103}
104
105static bool
106boot_catalog_valid(char *entry)
107{
108	boot_catalog_validation_entry *ve;
109	int16_t		checksum, sum;
110	unsigned char	*csptr;
111	size_t		i;
112
113	ve = (boot_catalog_validation_entry *)entry;
114
115	checksum = isonum_721(ve->checksum);
116	cd9660_721(0, ve->checksum);
117	csptr = (unsigned char *)ve;
118
119	for (i = sum = 0; i < sizeof (*ve); i += 2) {
120		sum += (int16_t)csptr[i];
121		sum += 256 * (int16_t)csptr[i + 1];
122	}
123	if (sum + checksum != 0) {
124		return (false);
125	}
126
127	cd9660_721(checksum, ve->checksum);
128	return (true);
129}
130
131static int
132dump_section(char *buffer, size_t offset, FILE *outfile, const char *filename,
133    struct outputter *outputter)
134{
135	boot_catalog_section_header *sh;
136	uchar_t platform_id;
137	int i;
138	size_t entry_offset;
139	boot_catalog_section_entry *entry;
140
141	sh = (boot_catalog_section_header *)&buffer[offset];
142	if (outputter->output_section != NULL) {
143		outputter->output_section(outfile, filename, sh);
144	}
145
146	platform_id = sh->platform_id[0];
147
148	if (outputter->output_entry != NULL) {
149		for (i = 1; i <= (int)sh->num_section_entries[0]; i++) {
150			entry_offset = offset + i * ET_BOOT_ENTRY_SIZE;
151			entry =
152			    (boot_catalog_section_entry *)&buffer[entry_offset];
153			outputter->output_entry(outfile, filename, entry,
154			    platform_id, false);
155		}
156	}
157
158	return (1 + (int)sh->num_section_entries[0]);
159}
160
161static void
162dump_eltorito(FILE *iso, const char *filename, FILE *outfile,
163    struct outputter *outputter)
164{
165	char buffer[ISO_DEFAULT_BLOCK_SIZE], *entry;
166	boot_volume_descriptor *bvd;
167	daddr_t boot_catalog;
168	size_t offset;
169	int entry_count;
170
171	if (read_sector(iso, 17, buffer) != 0)
172		err(1, "failed to read from image");
173
174	bvd = (boot_volume_descriptor *)buffer;
175	if (memcmp(bvd->identifier, ISO_ID_STRING, 5) != 0)
176		warnx("%s: not a valid ISO", filename);
177	if (bvd->boot_record_indicator[0] != ISO_VD_BOOT)
178		warnx("%s: not an El Torito bootable ISO", filename);
179	if (memcmp(bvd->boot_system_identifier, ET_ID, 23) != 0)
180		warnx("%s: not an El Torito bootable ISO", filename);
181
182	boot_catalog = isonum_731(bvd->boot_catalog_pointer);
183
184	if (read_sector(iso, boot_catalog, buffer) != 0)
185		err(1, "failed to read from image");
186
187	entry = buffer;
188	offset = 0;
189
190	if (!boot_catalog_valid(entry))
191		warnx("%s: boot catalog checksum is invalid", filename);
192
193	if (outputter->output_image != NULL)
194		outputter->output_image(outfile, filename, bvd);
195
196	offset += ET_BOOT_ENTRY_SIZE;
197	entry = &buffer[offset];
198	if (outputter->output_entry != NULL)
199		outputter->output_entry(outfile, filename,
200		    (boot_catalog_section_entry *)entry, 0, true);
201
202	offset += ET_BOOT_ENTRY_SIZE;
203
204	while (offset < ISO_DEFAULT_BLOCK_SIZE) {
205		entry = &buffer[offset];
206
207		if ((uint8_t)entry[0] != ET_SECTION_HEADER_MORE &&
208		    (uint8_t)entry[0] != ET_SECTION_HEADER_LAST)
209			break;
210
211		entry_count = dump_section(buffer, offset, outfile, filename,
212		    outputter);
213
214		offset += entry_count * ET_BOOT_ENTRY_SIZE;
215	}
216}
217
218static void
219usage(const char *progname)
220{
221	char *path;
222
223	path = strdup(progname);
224
225	fprintf(stderr, "usage: %s [-f format] [-o filename] filename [...]\n",
226	    basename(path));
227	fprintf(stderr, "\tsupported output formats: shell, text\n");
228	exit(1);
229}
230
231int
232main(int argc, char **argv)
233{
234	int ch, i;
235	FILE *outfile, *iso;
236	struct outputter *outputter;
237
238	outfile = stdout;
239	outputter = output_text;
240
241	static struct option longopts[] = {
242		{ "format",	required_argument,	NULL,	'f' },
243		{ "output",	required_argument,	NULL,	'o' },
244		{ NULL,		0,			NULL,	0 },
245	};
246
247	while ((ch = getopt_long(argc, argv, "f:o:", longopts, NULL)) != -1) {
248		switch (ch) {
249		case 'f':
250			if (strcmp(optarg, "shell") == 0)
251				outputter = output_shell;
252			else if (strcmp(optarg, "text") == 0)
253				outputter = output_text;
254			else
255				usage(argv[0]);
256			break;
257		case 'o':
258			if (strcmp(optarg, "-") == 0) {
259				outfile = stdout;
260			} else if ((outfile = fopen(optarg, "w")) == NULL) {
261				err(1, "unable to open %s for output", optarg);
262			}
263			break;
264		default:
265			usage(argv[0]);
266		}
267	}
268
269	argc -= optind;
270	argv += optind;
271
272	for (i = 0; i < argc; i++) {
273		if (strcmp(argv[i], "-") == 0) {
274			iso = stdin;
275		} else {
276			iso = fopen(argv[i], "r");
277			if (iso == NULL)
278				err(1, "could not open %s", argv[1]);
279		}
280		dump_eltorito(iso, argv[i], outfile, outputter);
281	}
282	return (0);
283}
284