Initial import from FreeBSD RELENG_4:
[dragonfly.git] / sys / i386 / boot / cdboot / cdrom.c
1 /*
2  * Copyright © 1997 Pluto Technologies International, Inc.  Boulder CO
3  * Copyright © 1997 interface business GmbH, Dresden.
4  *      All rights reserved.
5  *
6  * This code was written by Jörg Wunsch, Dresden.
7  * Direct comments to <joerg_wunsch@interface-business.de>.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
22  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  * $FreeBSD: src/sys/i386/boot/cdboot/cdrom.c,v 1.4 1999/08/28 00:43:17 peter Exp $
31  */
32
33
34 #include "boot.h"
35
36 #include <isofs/cd9660/iso.h>
37
38 #define BLKSIZE 2048            /* CD-ROM data block size */
39 #define BIOSSEC 512             /* BIOS sector size */
40
41 #define CD2LBA(rba)     ((rba) << 2) /* CD-ROM relative block to BIOS LBA */
42
43 u_int32_t sessionstart;
44
45 static struct iso_primary_descriptor pdesc;
46
47 static char *rootdirbuf;
48 static size_t rootdirsize;
49 static char xbuf[BLKSIZE];
50 static u_int32_t curblk, startblk, filesize, offset;
51
52 static int bread(u_int32_t rba, size_t nblks, void *buf);
53 static void badread(const char *msg, u_int32_t blkno);
54 static struct iso_directory_record *find(const char *path, int list_only);
55 static char *get_rr_name(struct iso_directory_record *dirp, size_t *len_ret);
56 static int iread(u_char *buf, size_t len,
57                  void (*copyfun)(const void *src, void *dst, size_t size));
58
59 static struct daddrpacket dpkt = { 0x10 };
60
61 int
62 devopen(u_int32_t session)
63 {
64         int rv;
65         u_int32_t rootdirblk;
66         struct iso_directory_record *rootdirp;
67
68         if ((rv = bread(session + 16, 1, &pdesc)) != 0) {
69                 printf("Error reading primary ISO descriptor: %d\n", rv);
70                 return -1;
71         }
72         rootdirp = (struct iso_directory_record *)pdesc.root_directory_record;
73         rootdirblk = isonum_733(rootdirp->extent);
74         rootdirsize = isonum_733(rootdirp->size);
75
76         /* just in case, round up */
77         rootdirsize = (rootdirsize + BLKSIZE - 1) & ~(BLKSIZE - 1);
78
79         if (rootdirbuf != NULL)
80                 free(rootdirbuf);
81         if ((rootdirbuf = malloc(rootdirsize)) == 0) {
82                 printf("Cannot allocate memory for the root "
83                        "directory buffer.\n");
84                 return -1;
85         }
86         if ((rv = bread(rootdirblk, rootdirsize / BLKSIZE, rootdirbuf))
87             != 0) {
88                 printf("Error reading root directory: %d\n", rv);
89                 return -1;
90         }
91
92         DPRINTF(("Root directory is 0x%x bytes @ %d\n",
93                  rootdirsize, rootdirblk));
94
95         return 0;
96 }
97
98 static int
99 bread(u_int32_t rba, size_t nblks, void *buf)
100 {
101         int i, rv;
102
103         for (i = 0, rv = -1; rv != 0 && i < 3; i++) {
104                 dpkt.nblocks = nblks * (BLKSIZE / BIOSSEC);
105                 dpkt.boffs = (u_int16_t)((int)buf & 0xffff);
106                 dpkt.bseg = BOOTSEG;
107                 dpkt.lba = CD2LBA(rba);
108
109 #ifdef DEBUG_VERBOSE
110                 DPRINTF(("Calling biosreadlba(%d blocks, lba %d) = ",
111                          dpkt.nblocks, dpkt.lba));
112 #endif
113
114                 rv = biosreadlba(&dpkt);
115
116 #ifdef DEBUG_VERBOSE
117                 DPRINTF(("%d\n", rv));
118 #endif
119         }
120         return rv;
121 }
122
123
124 void
125 seek(u_int32_t offs)
126 {
127         offset = offs;
128 }
129
130 static void
131 badread(const char *msg, u_int32_t blkno)
132 {
133         printf("Error reading block %d from CD-ROM: %s\n",
134                blkno, msg);
135 }
136
137 static __inline size_t
138 minlen(size_t a, size_t b)
139 {
140         return a < b? a: b;
141 }
142
143 /*
144  * Internal form of read()/xread().
145  */
146 static int
147 iread(u_char *buf, size_t len,
148       void (*copyfun)(const void *src, void *dst, size_t size))
149 {
150         u_int32_t newblk, ptr;
151         size_t bsize;
152
153         newblk = offset / BLKSIZE + startblk;
154
155         if (newblk != curblk) {
156                 if (offset + len >= filesize) {
157                         badread("access beyond file limit", newblk);
158                         return -1;
159                 }
160                 if (bread(newblk, 1, xbuf)) {
161                         badread("BIOS read error", newblk);
162                         return -1;
163                 }
164                 curblk = newblk;
165         }
166         ptr = offset & (BLKSIZE - 1);
167         if (ptr > 0) {
168                 /* initial short transfer */
169                 bsize = minlen(BLKSIZE - ptr, len);
170                 copyfun(xbuf + ptr, buf, bsize);
171                 buf += bsize;
172                 len -= bsize;
173                 offset += bsize;
174         }
175         for (; len > 0; len -= bsize) {
176                 bsize = minlen(len, BLKSIZE);
177                 newblk = offset / BLKSIZE + startblk;
178
179                 if (newblk != curblk) {
180                         if (offset + bsize > filesize) {
181                                 badread("access beyond file limit", newblk);
182                                 return -1;
183                         }
184                         if (bread(newblk, 1, xbuf)) {
185                                 badread("BIOS read error", newblk);
186                                 return -1;
187                         }
188                         curblk = newblk;
189                 }
190                 copyfun(xbuf, buf, bsize);
191                 buf += bsize;
192                 offset += bsize;
193         }
194         return 0;
195 }
196
197 int
198 read(u_char *buf, size_t len)
199 {
200         DPRINTF(("read(0x%x, %d)\n", (int)buf, len));
201         return iread(buf, len, bcopy);
202 }
203
204 int
205 xread(u_char *buf, size_t len)
206 {
207         DPRINTF(("xread(0x%x, %d)\n", (int)buf, len));
208         return iread(buf, len, pcpy);
209 }
210
211 static char *
212 get_rr_name(struct iso_directory_record *dirp, size_t *len_ret)
213 {
214         struct rr_header {
215                 char    type[2];
216                 u_char  len;
217                 u_char  version;
218         } *rrp;
219         struct rr_nm_header {
220                 struct rr_header rrh;
221                 u_char  flags;
222                 char    name[0]; /* XXX -- using gcc extension */
223         } *rrnmp;
224         char *cp;
225
226         cp = dirp->name + (u_char)dirp->name_len[0];
227         /* round up to 16-bit boundary; ugly */
228         cp = (char *)(((int)cp + 1) & ~1);
229         rrp = (struct rr_header *)cp;
230
231         if (rrp->type[0] != 'R' || rrp->type[1] != 'R') {
232                 DPRINTF(("no RR, "));
233                 return 0;
234         }
235
236         DPRINTF(("RR attribs: "));
237         cp += rrp->len;
238         while (cp - (char *)dirp <= (u_char)dirp->length[0]) {
239                 rrp = (struct rr_header *)cp;
240                 DPRINTF(("%c%c ", rrp->type[0], rrp->type[1]));
241                 if (rrp->type[0] == 'N' && rrp->type[1] == 'M') {
242                         rrnmp = (struct rr_nm_header *)rrp;
243                         *len_ret = rrp->len - sizeof(struct rr_nm_header);
244                         return rrnmp->name;
245                 }
246                 cp += rrp->len;
247         }
248
249         return 0;
250 }
251
252 static struct iso_directory_record *
253 find(const char *path, int list_only)
254 {
255         struct iso_directory_record *dirp;
256         char *ptr, *rrname;
257         size_t len, entrylen;
258         char namebuf[256];
259         int i;
260         int (*comp)(const char *, const char *);
261
262         while (*path && *path == '/')
263                 path++;
264
265         for (ptr = rootdirbuf, i = 1;
266              ptr < rootdirbuf + rootdirsize;
267              ptr += entrylen, i++) {
268                 dirp = (struct iso_directory_record *)ptr;
269                 entrylen = (u_char)dirp->length[0];
270                 len = (u_char)dirp->name_len[0];
271
272                 DPRINTF(("# %d: offset 0x%x, length 0x%x = %d, ",
273                          i, (int)(ptr - rootdirbuf), entrylen, entrylen));
274
275                 if (entrylen == 0) {
276                         /*
277                          * Dir entry of length 0.  That's the last
278                          * entry in this block, advance to the next
279                          * block (if any).  In case we get beyond the
280                          * end of the directory, we'll fall off the
281                          * loop due to the rootdirsize condition in
282                          * the `for' statement.
283                          */
284                         DPRINTF(("entrylen 0\n"));
285                         entrylen = (~((ptr - rootdirbuf) + BLKSIZE - 1))
286                                 & (BLKSIZE - 1);
287                         continue;
288                 }
289                 if (len == 0) {
290                         DPRINTF(("name_len 0\n"));
291                         continue;
292                 }
293                 if (len == 1 &&
294                     (dirp->name[0] == '\0' || dirp->name[1] == '\1')) {
295                         DPRINTF(("dot/dot-dot entry\n"));
296                         continue;
297                 }
298                 /* don't consider directories */
299                 if (dirp->flags[0] & 2) {
300                         DPRINTF(("directory\n"));
301                         continue;
302                 }
303                 rrname = get_rr_name(dirp, &len);
304                 comp = rrname? strcmp: strcasecmp;
305
306                 bcopy(rrname? rrname: dirp->name, namebuf, len);
307                 namebuf[len] = 0;
308                 DPRINTF(("name `%s'\n", namebuf));
309
310                 if (list_only) {
311 #ifndef DEBUG
312                         printf("%s ", namebuf);
313 #endif
314                 } else if (comp(path, namebuf) == 0)
315                         return dirp;
316         }
317 #ifndef DEBUG
318         if (list_only)
319                 printf("\n");
320 #endif
321         return 0;
322 }
323
324 int
325 openrd(char *name)
326 {
327         char *cp;
328         const char *fname;
329         u_int32_t oldsession;
330         int session, list_only;
331         struct iso_directory_record *dirp;
332
333         session = 0;
334         fname = name;
335
336         /*
337          * We accept the following boot string:
338          *
339          * [@sessionstart] name
340          */
341         for (cp = name; *cp; cp++)
342                 switch (*cp) {
343                 /* we don't support filenames with spaces */
344                 case ' ':       case '\t':
345                         break;
346
347                 case '@':
348                         if (session) {
349                                 printf("Syntax error\n");
350                                 return -1;
351                         }
352                         session++;
353                         oldsession = sessionstart;
354                         sessionstart = 0;
355                         break;
356
357                 case '0':       case '1':       case '2':
358                 case '3':       case '4':       case '5':
359                 case '6':       case '7':       case '8':
360                 case '9':
361                         if (session == 1) {
362                                 sessionstart *= 10;
363                                 sessionstart += *cp - '0';
364                         }
365                         break;
366
367                 default:
368                         if (session == 1) {
369                                 session++;
370                                 fname = cp;
371                         }
372                 }
373
374         if (session && devopen(sessionstart) == -1) {
375                 (void)devopen(oldsession);
376                 sessionstart = oldsession;
377         }
378         if (session == 1)
379                 /* XXX no filename, only session arg */
380                 return -1;
381
382         list_only = fname[0] == '?' && fname[1] == 0;
383
384         DPRINTF(("Calling find(%s, %d):\n", fname, list_only));
385         dirp = find(fname, list_only);
386         DPRINTF(("find() returned 0x%x\n", (int)dirp));
387
388         if (list_only)
389                 return -1;
390         if (dirp == 0)
391                 return 1;
392
393         startblk = isonum_733(dirp->extent);
394         filesize = isonum_733(dirp->size);
395
396         DPRINTF(("startblk = %d, filesize = %d\n", startblk, filesize));
397
398         curblk = 0;     /* force a re-read, 0 is impossible file start */
399         seek(0);
400
401         return 0;
402 }