cd9660.c revision 346477
1/*	$NetBSD: cd9660.c,v 1.5 1997/06/26 19:11:33 drochner Exp $	*/
2
3/*
4 * Copyright (C) 1996 Wolfgang Solfrank.
5 * Copyright (C) 1996 TooLs GmbH.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by TooLs GmbH.
19 * 4. The name of TooLs GmbH may not be used to endorse or promote products
20 *    derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
28 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35__FBSDID("$FreeBSD: stable/11/stand/libsa/cd9660.c 346477 2019-04-21 03:43:27Z kevans $");
36
37/*
38 * Stand-alone ISO9660 file reading package.
39 *
40 * Note: This doesn't support Rock Ridge extensions, extended attributes,
41 * blocksizes other than 2048 bytes, multi-extent files, etc.
42 */
43#include <sys/param.h>
44#include <string.h>
45#include <stdbool.h>
46#include <sys/dirent.h>
47#include <fs/cd9660/iso.h>
48#include <fs/cd9660/cd9660_rrip.h>
49
50#include "stand.h"
51
52#define	SUSP_CONTINUATION	"CE"
53#define	SUSP_PRESENT		"SP"
54#define	SUSP_STOP		"ST"
55#define	SUSP_EXTREF		"ER"
56#define	RRIP_NAME		"NM"
57
58typedef struct {
59	ISO_SUSP_HEADER		h;
60	u_char signature	[ISODCL (  5,    6)];
61	u_char len_skp		[ISODCL (  7,    7)]; /* 711 */
62} ISO_SUSP_PRESENT;
63
64static int	buf_read_file(struct open_file *f, char **buf_p,
65		    size_t *size_p);
66static int	cd9660_open(const char *path, struct open_file *f);
67static int	cd9660_close(struct open_file *f);
68static int	cd9660_read(struct open_file *f, void *buf, size_t size,
69		    size_t *resid);
70static off_t	cd9660_seek(struct open_file *f, off_t offset, int where);
71static int	cd9660_stat(struct open_file *f, struct stat *sb);
72static int	cd9660_readdir(struct open_file *f, struct dirent *d);
73static int	dirmatch(struct open_file *f, const char *path,
74		    struct iso_directory_record *dp, int use_rrip, int lenskip);
75static int	rrip_check(struct open_file *f, struct iso_directory_record *dp,
76		    int *lenskip);
77static char	*rrip_lookup_name(struct open_file *f,
78		    struct iso_directory_record *dp, int lenskip, size_t *len);
79static ISO_SUSP_HEADER *susp_lookup_record(struct open_file *f,
80		    const char *identifier, struct iso_directory_record *dp,
81		    int lenskip);
82
83struct fs_ops cd9660_fsops = {
84	"cd9660",
85	cd9660_open,
86	cd9660_close,
87	cd9660_read,
88	null_write,
89	cd9660_seek,
90	cd9660_stat,
91	cd9660_readdir
92};
93
94#define	F_ISDIR		0x0001		/* Directory */
95#define	F_ROOTDIR	0x0002		/* Root directory */
96#define	F_RR		0x0004		/* Rock Ridge on this volume */
97
98struct file {
99	int 		f_flags;	/* file flags */
100	off_t 		f_off;		/* Current offset within file */
101	daddr_t 	f_bno;		/* Starting block number */
102	off_t 		f_size;		/* Size of file */
103	daddr_t		f_buf_blkno;	/* block number of data block */
104	char		*f_buf;		/* buffer for data block */
105	int		f_susp_skip;	/* len_skip for SUSP records */
106};
107
108struct ptable_ent {
109	char namlen	[ISODCL( 1, 1)];	/* 711 */
110	char extlen	[ISODCL( 2, 2)];	/* 711 */
111	char block	[ISODCL( 3, 6)];	/* 732 */
112	char parent	[ISODCL( 7, 8)];	/* 722 */
113	char name	[1];
114};
115#define	PTFIXSZ		8
116#define	PTSIZE(pp)	roundup(PTFIXSZ + isonum_711((pp)->namlen), 2)
117
118#define	cdb2devb(bno)	((bno) * ISO_DEFAULT_BLOCK_SIZE / DEV_BSIZE)
119
120static ISO_SUSP_HEADER *
121susp_lookup_record(struct open_file *f, const char *identifier,
122    struct iso_directory_record *dp, int lenskip)
123{
124	static char susp_buffer[ISO_DEFAULT_BLOCK_SIZE];
125	ISO_SUSP_HEADER *sh;
126	ISO_RRIP_CONT *shc;
127	char *p, *end;
128	int error;
129	size_t read;
130
131	p = dp->name + isonum_711(dp->name_len) + lenskip;
132	/* Names of even length have a padding byte after the name. */
133	if ((isonum_711(dp->name_len) & 1) == 0)
134		p++;
135	end = (char *)dp + isonum_711(dp->length);
136	while (p + 3 < end) {
137		sh = (ISO_SUSP_HEADER *)p;
138		if (bcmp(sh->type, identifier, 2) == 0)
139			return (sh);
140		if (bcmp(sh->type, SUSP_STOP, 2) == 0)
141			return (NULL);
142		if (bcmp(sh->type, SUSP_CONTINUATION, 2) == 0) {
143			shc = (ISO_RRIP_CONT *)sh;
144			error = f->f_dev->dv_strategy(f->f_devdata, F_READ,
145			    cdb2devb(isonum_733(shc->location)),
146			    ISO_DEFAULT_BLOCK_SIZE, susp_buffer, &read);
147
148			/* Bail if it fails. */
149			if (error != 0 || read != ISO_DEFAULT_BLOCK_SIZE)
150				return (NULL);
151			p = susp_buffer + isonum_733(shc->offset);
152			end = p + isonum_733(shc->length);
153		} else {
154			/* Ignore this record and skip to the next. */
155			p += isonum_711(sh->length);
156
157			/* Avoid infinite loops with corrupted file systems */
158			if (isonum_711(sh->length) == 0)
159				return (NULL);
160		}
161	}
162	return (NULL);
163}
164
165static char *
166rrip_lookup_name(struct open_file *f, struct iso_directory_record *dp,
167    int lenskip, size_t *len)
168{
169	ISO_RRIP_ALTNAME *p;
170
171	if (len == NULL)
172		return (NULL);
173
174	p = (ISO_RRIP_ALTNAME *)susp_lookup_record(f, RRIP_NAME, dp, lenskip);
175	if (p == NULL)
176		return (NULL);
177	switch (*p->flags) {
178	case ISO_SUSP_CFLAG_CURRENT:
179		*len = 1;
180		return (".");
181	case ISO_SUSP_CFLAG_PARENT:
182		*len = 2;
183		return ("..");
184	case 0:
185		*len = isonum_711(p->h.length) - 5;
186		return ((char *)p + 5);
187	default:
188		/*
189		 * We don't handle hostnames or continued names as they are
190		 * too hard, so just bail and use the default name.
191		 */
192		return (NULL);
193	}
194}
195
196static int
197rrip_check(struct open_file *f, struct iso_directory_record *dp, int *lenskip)
198{
199	ISO_SUSP_PRESENT *sp;
200	ISO_RRIP_EXTREF *er;
201	char *p;
202
203	/* First, see if we can find a SP field. */
204	p = dp->name + isonum_711(dp->name_len);
205	if (p > (char *)dp + isonum_711(dp->length))
206		return (0);
207	sp = (ISO_SUSP_PRESENT *)p;
208	if (bcmp(sp->h.type, SUSP_PRESENT, 2) != 0)
209		return (0);
210	if (isonum_711(sp->h.length) != sizeof(ISO_SUSP_PRESENT))
211		return (0);
212	if (sp->signature[0] != 0xbe || sp->signature[1] != 0xef)
213		return (0);
214	*lenskip = isonum_711(sp->len_skp);
215
216	/*
217	 * Now look for an ER field.  If RRIP is present, then there must
218	 * be at least one of these.  It would be more pedantic to walk
219	 * through the list of fields looking for a Rock Ridge ER field.
220	 */
221	er = (ISO_RRIP_EXTREF *)susp_lookup_record(f, SUSP_EXTREF, dp, 0);
222	if (er == NULL)
223		return (0);
224	return (1);
225}
226
227static int
228dirmatch(struct open_file *f, const char *path, struct iso_directory_record *dp,
229    int use_rrip, int lenskip)
230{
231	size_t len, plen;
232	char *cp, *sep;
233	int i, icase;
234
235	if (use_rrip)
236		cp = rrip_lookup_name(f, dp, lenskip, &len);
237	else
238		cp = NULL;
239	if (cp == NULL) {
240		len = isonum_711(dp->name_len);
241		cp = dp->name;
242		icase = 1;
243	} else
244		icase = 0;
245
246	sep = strchr(path, '/');
247	if (sep != NULL) {
248		plen = sep - path;
249	} else {
250		plen = strlen(path);
251	}
252
253	if (plen != len)
254		return (0);
255
256	for (i = len; --i >= 0; path++, cp++) {
257		if (!*path || *path == '/')
258			break;
259		if (*path == *cp)
260			continue;
261		if (!icase && toupper(*path) == *cp)
262			continue;
263		return 0;
264	}
265	if (*path && *path != '/')
266		return 0;
267	/*
268	 * Allow stripping of trailing dots and the version number.
269	 * Note that this will find the first instead of the last version
270	 * of a file.
271	 */
272	if (i >= 0 && (*cp == ';' || *cp == '.')) {
273		/* This is to prevent matching of numeric extensions */
274		if (*cp == '.' && cp[1] != ';')
275			return 0;
276		while (--i >= 0)
277			if (*++cp != ';' && (*cp < '0' || *cp > '9'))
278				return 0;
279	}
280	return 1;
281}
282
283static int
284cd9660_open(const char *path, struct open_file *f)
285{
286	struct file *fp = NULL;
287	void *buf;
288	struct iso_primary_descriptor *vd;
289	size_t buf_size, read, dsize, off;
290	daddr_t bno, boff;
291	struct iso_directory_record rec;
292	struct iso_directory_record *dp = NULL;
293	int rc, first, use_rrip, lenskip;
294	bool isdir = false;
295
296	/* First find the volume descriptor */
297	buf = malloc(buf_size = ISO_DEFAULT_BLOCK_SIZE);
298	vd = buf;
299	for (bno = 16;; bno++) {
300		twiddle(1);
301		rc = f->f_dev->dv_strategy(f->f_devdata, F_READ, cdb2devb(bno),
302					ISO_DEFAULT_BLOCK_SIZE, buf, &read);
303		if (rc)
304			goto out;
305		if (read != ISO_DEFAULT_BLOCK_SIZE) {
306			rc = EIO;
307			goto out;
308		}
309		rc = EINVAL;
310		if (bcmp(vd->id, ISO_STANDARD_ID, sizeof vd->id) != 0)
311			goto out;
312		if (isonum_711(vd->type) == ISO_VD_END)
313			goto out;
314		if (isonum_711(vd->type) == ISO_VD_PRIMARY)
315			break;
316	}
317	if (isonum_723(vd->logical_block_size) != ISO_DEFAULT_BLOCK_SIZE)
318		goto out;
319
320	bcopy(vd->root_directory_record, &rec, sizeof(rec));
321	if (*path == '/') path++; /* eat leading '/' */
322
323	first = 1;
324	use_rrip = 0;
325	lenskip = 0;
326	while (*path) {
327		bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
328		dsize = isonum_733(rec.size);
329		off = 0;
330		boff = 0;
331
332		while (off < dsize) {
333			if ((off % ISO_DEFAULT_BLOCK_SIZE) == 0) {
334				twiddle(1);
335				rc = f->f_dev->dv_strategy
336					(f->f_devdata, F_READ,
337					 cdb2devb(bno + boff),
338					 ISO_DEFAULT_BLOCK_SIZE,
339					 buf, &read);
340				if (rc)
341					goto out;
342				if (read != ISO_DEFAULT_BLOCK_SIZE) {
343					rc = EIO;
344					goto out;
345				}
346				boff++;
347				dp = (struct iso_directory_record *) buf;
348			}
349			if (isonum_711(dp->length) == 0) {
350			    /* skip to next block, if any */
351			    off = boff * ISO_DEFAULT_BLOCK_SIZE;
352			    continue;
353			}
354
355			/* See if RRIP is in use. */
356			if (first)
357				use_rrip = rrip_check(f, dp, &lenskip);
358
359			if (dirmatch(f, path, dp, use_rrip,
360			    first ? 0 : lenskip)) {
361				first = 0;
362				break;
363			} else
364				first = 0;
365
366			dp = (struct iso_directory_record *)
367				((char *) dp + isonum_711(dp->length));
368			/* If the new block has zero length, it is padding. */
369			if (isonum_711(dp->length) == 0) {
370				/* Skip to next block, if any. */
371				off = boff * ISO_DEFAULT_BLOCK_SIZE;
372				continue;
373			}
374			off += isonum_711(dp->length);
375		}
376		if (off >= dsize) {
377			rc = ENOENT;
378			goto out;
379		}
380
381		rec = *dp;
382		while (*path && *path != '/') /* look for next component */
383			path++;
384
385		if (*path)	/* this component was directory */
386			isdir = true;
387
388		while (*path == '/')
389			path++;	/* skip '/' */
390
391		if (*path)	/* We do have next component. */
392			isdir = false;
393	}
394
395	/*
396	 * if the path had trailing / but the path does point to file,
397	 * report the error ENOTDIR.
398	 */
399	if (isdir == true && (isonum_711(rec.flags) & 2) == 0) {
400		rc = ENOTDIR;
401		goto out;
402	}
403
404	/* allocate file system specific data structure */
405	fp = malloc(sizeof(struct file));
406	bzero(fp, sizeof(struct file));
407	f->f_fsdata = (void *)fp;
408
409	if ((isonum_711(rec.flags) & 2) != 0) {
410		fp->f_flags = F_ISDIR;
411	}
412	if (first) {
413		fp->f_flags |= F_ROOTDIR;
414
415		/* Check for Rock Ridge since we didn't in the loop above. */
416		bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
417		twiddle(1);
418		rc = f->f_dev->dv_strategy(f->f_devdata, F_READ, cdb2devb(bno),
419		    ISO_DEFAULT_BLOCK_SIZE, buf, &read);
420		if (rc)
421			goto out;
422		if (read != ISO_DEFAULT_BLOCK_SIZE) {
423			rc = EIO;
424			goto out;
425		}
426		dp = (struct iso_directory_record *)buf;
427		use_rrip = rrip_check(f, dp, &lenskip);
428	}
429	if (use_rrip) {
430		fp->f_flags |= F_RR;
431		fp->f_susp_skip = lenskip;
432	}
433	fp->f_off = 0;
434	fp->f_bno = isonum_733(rec.extent) + isonum_711(rec.ext_attr_length);
435	fp->f_size = isonum_733(rec.size);
436	free(buf);
437
438	return 0;
439
440out:
441	if (fp)
442		free(fp);
443	free(buf);
444
445	return rc;
446}
447
448static int
449cd9660_close(struct open_file *f)
450{
451	struct file *fp = (struct file *)f->f_fsdata;
452
453	f->f_fsdata = NULL;
454	free(fp);
455
456	return 0;
457}
458
459static int
460buf_read_file(struct open_file *f, char **buf_p, size_t *size_p)
461{
462	struct file *fp = (struct file *)f->f_fsdata;
463	daddr_t blkno, blkoff;
464	int rc = 0;
465	size_t read;
466
467	blkno = fp->f_off / ISO_DEFAULT_BLOCK_SIZE + fp->f_bno;
468	blkoff = fp->f_off % ISO_DEFAULT_BLOCK_SIZE;
469
470	if (blkno != fp->f_buf_blkno) {
471		if (fp->f_buf == (char *)0)
472			fp->f_buf = malloc(ISO_DEFAULT_BLOCK_SIZE);
473
474		twiddle(16);
475		rc = f->f_dev->dv_strategy(f->f_devdata, F_READ,
476		    cdb2devb(blkno), ISO_DEFAULT_BLOCK_SIZE,
477		    fp->f_buf, &read);
478		if (rc)
479			return (rc);
480		if (read != ISO_DEFAULT_BLOCK_SIZE)
481			return (EIO);
482
483		fp->f_buf_blkno = blkno;
484	}
485
486	*buf_p = fp->f_buf + blkoff;
487	*size_p = ISO_DEFAULT_BLOCK_SIZE - blkoff;
488
489	if (*size_p > fp->f_size - fp->f_off)
490		*size_p = fp->f_size - fp->f_off;
491	return (rc);
492}
493
494static int
495cd9660_read(struct open_file *f, void *start, size_t size, size_t *resid)
496{
497	struct file *fp = (struct file *)f->f_fsdata;
498	char *buf, *addr;
499	size_t buf_size, csize;
500	int rc = 0;
501
502	addr = start;
503	while (size) {
504		if (fp->f_off < 0 || fp->f_off >= fp->f_size)
505			break;
506
507		rc = buf_read_file(f, &buf, &buf_size);
508		if (rc)
509			break;
510
511		csize = size > buf_size ? buf_size : size;
512		bcopy(buf, addr, csize);
513
514		fp->f_off += csize;
515		addr += csize;
516		size -= csize;
517	}
518	if (resid)
519		*resid = size;
520	return (rc);
521}
522
523static int
524cd9660_readdir(struct open_file *f, struct dirent *d)
525{
526	struct file *fp = (struct file *)f->f_fsdata;
527	struct iso_directory_record *ep;
528	size_t buf_size, reclen, namelen;
529	int error = 0;
530	int lenskip;
531	char *buf, *name;
532
533again:
534	if (fp->f_off >= fp->f_size)
535		return (ENOENT);
536	error = buf_read_file(f, &buf, &buf_size);
537	if (error)
538		return (error);
539	ep = (struct iso_directory_record *)buf;
540
541	if (isonum_711(ep->length) == 0) {
542		daddr_t blkno;
543
544		/* skip to next block, if any */
545		blkno = fp->f_off / ISO_DEFAULT_BLOCK_SIZE;
546		fp->f_off = (blkno + 1) * ISO_DEFAULT_BLOCK_SIZE;
547		goto again;
548	}
549
550	if (fp->f_flags & F_RR) {
551		if (fp->f_flags & F_ROOTDIR && fp->f_off == 0)
552			lenskip = 0;
553		else
554			lenskip = fp->f_susp_skip;
555		name = rrip_lookup_name(f, ep, lenskip, &namelen);
556	} else
557		name = NULL;
558	if (name == NULL) {
559		namelen = isonum_711(ep->name_len);
560		name = ep->name;
561		if (namelen == 1) {
562			if (ep->name[0] == 0)
563				name = ".";
564			else if (ep->name[0] == 1) {
565				namelen = 2;
566				name = "..";
567			}
568		}
569	}
570	reclen = sizeof(struct dirent) - (MAXNAMLEN+1) + namelen + 1;
571	reclen = (reclen + 3) & ~3;
572
573	d->d_fileno = isonum_733(ep->extent);
574	d->d_reclen = reclen;
575	if (isonum_711(ep->flags) & 2)
576		d->d_type = DT_DIR;
577	else
578		d->d_type = DT_REG;
579	d->d_namlen = namelen;
580
581	bcopy(name, d->d_name, d->d_namlen);
582	d->d_name[d->d_namlen] = 0;
583
584	fp->f_off += isonum_711(ep->length);
585	return (0);
586}
587
588static off_t
589cd9660_seek(struct open_file *f, off_t offset, int where)
590{
591	struct file *fp = (struct file *)f->f_fsdata;
592
593	switch (where) {
594	case SEEK_SET:
595		fp->f_off = offset;
596		break;
597	case SEEK_CUR:
598		fp->f_off += offset;
599		break;
600	case SEEK_END:
601		fp->f_off = fp->f_size - offset;
602		break;
603	default:
604		return -1;
605	}
606	return fp->f_off;
607}
608
609static int
610cd9660_stat(struct open_file *f, struct stat *sb)
611{
612	struct file *fp = (struct file *)f->f_fsdata;
613
614	/* only important stuff */
615	sb->st_mode = S_IRUSR | S_IRGRP | S_IROTH;
616	if (fp->f_flags & F_ISDIR)
617		sb->st_mode |= S_IFDIR;
618	else
619		sb->st_mode |= S_IFREG;
620	sb->st_uid = sb->st_gid = 0;
621	sb->st_size = fp->f_size;
622	return 0;
623}
624