1/*
2 * Copyright (C) 1996 Wolfgang Solfrank.
3 * Copyright (C) 1996 TooLs GmbH.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 *    must display the following acknowledgement:
16 *	This product includes software developed by TooLs GmbH.
17 * 4. The name of TooLs GmbH may not be used to endorse or promote products
18 *    derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
26 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
28 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
29 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/* Originally derived from libsa/cd9660.c: */
33/*	$NetBSD: cd9660.c,v 1.5 1997/06/26 19:11:33 drochner Exp $	*/
34
35#include <sys/cdefs.h>
36__FBSDID("$FreeBSD$");
37
38#include <sys/param.h>
39#include <fs/cd9660/iso.h>
40#include <fs/cd9660/cd9660_rrip.h>
41
42static uint64_t cd9660_lookup(const char *);
43static ssize_t cd9660_fsread(uint64_t, void *, size_t);
44
45#define	SUSP_CONTINUATION	"CE"
46#define	SUSP_PRESENT		"SP"
47#define	SUSP_STOP		"ST"
48#define	SUSP_EXTREF		"ER"
49#define	RRIP_NAME		"NM"
50
51typedef struct {
52	ISO_SUSP_HEADER		h;
53	u_char signature	[ISODCL (  5,    6)];
54	u_char len_skp		[ISODCL (  7,    7)]; /* 711 */
55} ISO_SUSP_PRESENT;
56
57static int
58read_iso_block(void *buffer, daddr_t blkno)
59{
60
61	return (drvread(&dsk, buffer, blkno * 4, 4));
62}
63
64static ISO_SUSP_HEADER *
65susp_lookup_record(const char *identifier, struct iso_directory_record *dp,
66    int lenskip)
67{
68	static char susp_buffer[ISO_DEFAULT_BLOCK_SIZE];
69	ISO_SUSP_HEADER *sh;
70	ISO_RRIP_CONT *shc;
71	char *p, *end;
72	int error;
73
74	p = dp->name + isonum_711(dp->name_len) + lenskip;
75	/* Names of even length have a padding byte after the name. */
76	if ((isonum_711(dp->name_len) & 1) == 0)
77		p++;
78	end = (char *)dp + isonum_711(dp->length);
79	while (p + 3 < end) {
80		sh = (ISO_SUSP_HEADER *)p;
81		if (bcmp(sh->type, identifier, 2) == 0)
82			return (sh);
83		if (bcmp(sh->type, SUSP_STOP, 2) == 0)
84			return (NULL);
85		if (bcmp(sh->type, SUSP_CONTINUATION, 2) == 0) {
86			shc = (ISO_RRIP_CONT *)sh;
87			error = read_iso_block(susp_buffer,
88			    isonum_733(shc->location));
89
90			/* Bail if it fails. */
91			if (error != 0)
92				return (NULL);
93			p = susp_buffer + isonum_733(shc->offset);
94			end = p + isonum_733(shc->length);
95		} else {
96			/* Ignore this record and skip to the next. */
97			p += isonum_711(sh->length);
98
99			/* Avoid infinite loops with corrupted file systems */
100			if (isonum_711(sh->length) == 0)
101				return (NULL);
102		}
103	}
104	return (NULL);
105}
106
107static const char *
108rrip_lookup_name(struct iso_directory_record *dp, int lenskip, size_t *len)
109{
110	ISO_RRIP_ALTNAME *p;
111
112	if (len == NULL)
113		return (NULL);
114
115	p = (ISO_RRIP_ALTNAME *)susp_lookup_record(RRIP_NAME, dp, lenskip);
116	if (p == NULL)
117		return (NULL);
118	switch (*p->flags) {
119	case ISO_SUSP_CFLAG_CURRENT:
120		*len = 1;
121		return (".");
122	case ISO_SUSP_CFLAG_PARENT:
123		*len = 2;
124		return ("..");
125	case 0:
126		*len = isonum_711(p->h.length) - 5;
127		return ((char *)p + 5);
128	default:
129		/*
130		 * We don't handle hostnames or continued names as they are
131		 * too hard, so just bail and use the default name.
132		 */
133		return (NULL);
134	}
135}
136
137static int
138rrip_check(struct iso_directory_record *dp, int *lenskip)
139{
140	ISO_SUSP_PRESENT *sp;
141	ISO_RRIP_EXTREF *er;
142	char *p;
143
144	/* First, see if we can find a SP field. */
145	p = dp->name + isonum_711(dp->name_len);
146	if (p > (char *)dp + isonum_711(dp->length)) {
147		return (0);
148	}
149	sp = (ISO_SUSP_PRESENT *)p;
150	if (bcmp(sp->h.type, SUSP_PRESENT, 2) != 0) {
151		return (0);
152	}
153	if (isonum_711(sp->h.length) != sizeof(ISO_SUSP_PRESENT)) {
154		return (0);
155	}
156	if (sp->signature[0] != 0xbe || sp->signature[1] != 0xef) {
157		return (0);
158	}
159	*lenskip = isonum_711(sp->len_skp);
160
161	/*
162	 * Now look for an ER field.  If RRIP is present, then there must
163	 * be at least one of these.  It would be more pedantic to walk
164	 * through the list of fields looking for a Rock Ridge ER field.
165	 */
166	er = (ISO_RRIP_EXTREF *)susp_lookup_record(SUSP_EXTREF, dp, 0);
167	if (er == NULL) {
168		return (0);
169	}
170	return (1);
171}
172
173static int
174dirmatch(const char *path, struct iso_directory_record *dp, int use_rrip,
175    int lenskip)
176{
177	size_t len;
178	const char *cp = NULL;
179	int i, icase;
180
181	if (use_rrip)
182		cp = rrip_lookup_name(dp, lenskip, &len);
183	else
184		cp = NULL;
185	if (cp == NULL) {
186		len = isonum_711(dp->name_len);
187		cp = dp->name;
188		icase = 1;
189	} else
190		icase = 0;
191	for (i = len; --i >= 0; path++, cp++) {
192		if (!*path || *path == '/')
193			break;
194		if (*path == *cp)
195			continue;
196		if (!icase && toupper(*path) == *cp)
197			continue;
198		return 0;
199	}
200	if (*path && *path != '/') {
201		return 0;
202	}
203	/*
204	 * Allow stripping of trailing dots and the version number.
205	 * Note that this will find the first instead of the last version
206	 * of a file.
207	 */
208	if (i >= 0 && (*cp == ';' || *cp == '.')) {
209		/* This is to prevent matching of numeric extensions */
210		if (*cp == '.' && cp[1] != ';') {
211			return 0;
212		}
213		while (--i >= 0)
214			if (*++cp != ';' && (*cp < '0' || *cp > '9')) {
215				return 0;
216			}
217	}
218	return 1;
219}
220
221static uint64_t
222cd9660_lookup(const char *path)
223{
224	static char blkbuf[MAX(ISO_DEFAULT_BLOCK_SIZE,
225	    sizeof(struct iso_primary_descriptor))];
226	struct iso_primary_descriptor *vd;
227	struct iso_directory_record rec;
228	struct iso_directory_record *dp = NULL;
229	size_t dsize, off;
230	daddr_t bno, boff;
231	int rc, first, use_rrip, lenskip;
232	uint64_t cookie;
233
234	for (bno = 16;; bno++) {
235		rc = read_iso_block(blkbuf, bno);
236		vd = (struct iso_primary_descriptor *)blkbuf;
237
238		if (bcmp(vd->id, ISO_STANDARD_ID, sizeof vd->id) != 0)
239			return (0);
240		if (isonum_711(vd->type) == ISO_VD_END)
241			return (0);
242		if (isonum_711(vd->type) == ISO_VD_PRIMARY)
243			break;
244	}
245
246	bcopy(vd->root_directory_record, &rec, sizeof(rec));
247	if (*path == '/') path++; /* eat leading '/' */
248
249	first = 1;
250	use_rrip = 0;
251	lenskip = 0;
252	while (*path) {
253		bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
254		dsize = isonum_733(rec.size);
255		off = 0;
256		boff = 0;
257
258		while (off < dsize) {
259			if ((off % ISO_DEFAULT_BLOCK_SIZE) == 0) {
260				rc = read_iso_block(blkbuf, bno + boff);
261				if (rc) {
262					return (0);
263				}
264				boff++;
265				dp = (struct iso_directory_record *) blkbuf;
266			}
267			if (isonum_711(dp->length) == 0) {
268				/* skip to next block, if any */
269				off = boff * ISO_DEFAULT_BLOCK_SIZE;
270				continue;
271			}
272
273			/* See if RRIP is in use. */
274			if (first)
275				use_rrip = rrip_check(dp, &lenskip);
276
277			if (dirmatch(path, dp, use_rrip,
278			    first ? 0 : lenskip)) {
279				first = 0;
280				break;
281			} else
282				first = 0;
283
284			dp = (struct iso_directory_record *)
285			    ((char *) dp + isonum_711(dp->length));
286			/* If the new block has zero length, it is padding. */
287			if (isonum_711(dp->length) == 0) {
288				/* Skip to next block, if any. */
289				off = boff * ISO_DEFAULT_BLOCK_SIZE;
290				continue;
291			}
292			off += isonum_711(dp->length);
293		}
294		if (off >= dsize) {
295			return (0);
296		}
297
298		rec = *dp;
299		while (*path && *path != '/') /* look for next component */
300			path++;
301		if (*path) path++; /* skip '/' */
302	}
303
304	if ((isonum_711(rec.flags) & 2) != 0) {
305		return (0);
306	}
307
308	cookie = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
309	cookie = (cookie << 32) | isonum_733(rec.size);
310
311	return (cookie);
312}
313
314static ssize_t
315cd9660_fsread(uint64_t cookie, void *buf, size_t nbytes)
316{
317	static char blkbuf[ISO_DEFAULT_BLOCK_SIZE];
318	static daddr_t curstart = 0, curblk = 0;
319	daddr_t blk, blk_off;
320	off_t byte_off;
321	size_t size, remaining, n;
322	char *s;
323
324	size = cookie & 0xffffffff;
325	blk = (cookie >> 32) & 0xffffffff;
326
327	/* Make sure we're looking at the right file. */
328	if (((blk << 32) | size) != cookie) {
329		return (-1);
330	}
331
332	if (blk != curstart) {
333		curstart = blk;
334		fs_off = 0;
335	}
336
337	size -= fs_off;
338	if (size < nbytes) {
339		nbytes = size;
340	}
341	remaining = nbytes;
342	s = buf;
343
344	while (remaining > 0) {
345		blk_off = fs_off >> ISO_DEFAULT_BLOCK_SHIFT;
346		byte_off = fs_off & (ISO_DEFAULT_BLOCK_SIZE - 1);
347
348		if (curblk != curstart + blk_off) {
349			curblk = curstart + blk_off;
350			read_iso_block(blkbuf, curblk);
351		}
352
353		if (remaining < ISO_DEFAULT_BLOCK_SIZE - byte_off) {
354			n = remaining;
355		} else {
356			n = ISO_DEFAULT_BLOCK_SIZE - byte_off;
357		}
358		memcpy(s, blkbuf + byte_off, n);
359		remaining -= n;
360		s += n;
361
362		fs_off += n;
363	}
364
365	return (nbytes);
366}
367