xref: /plan9/sys/src/cmd/9660srv/9660srv.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4 #include <fcall.h>
5 #include "dat.h"
6 #include "fns.h"
7 #include "iso9660.h"
8 
9 static void	ireset(void);
10 static int	iattach(Xfile*);
11 static void	iclone(Xfile*, Xfile*);
12 static void	iwalkup(Xfile*);
13 static void	iwalk(Xfile*, char*);
14 static void	iopen(Xfile*, int);
15 static void	icreate(Xfile*, char*, long, int);
16 static long	ireaddir(Xfile*, uchar*, long, long);
17 static long	iread(Xfile*, char*, long, long);
18 static long	iwrite(Xfile*, char*, long, long);
19 static void	iclunk(Xfile*);
20 static void	iremove(Xfile*);
21 static void	istat(Xfile*, Dir*);
22 static void	iwstat(Xfile*, Dir*);
23 
24 static char*	nstr(uchar*, int);
25 static char*	rdate(uchar*, int);
26 static int	getcontin(Xdata*, uchar*, uchar**);
27 static int	getdrec(Xfile*, void*);
28 static void	ungetdrec(Xfile*);
29 static int	opendotdot(Xfile*, Xfile*);
30 static int	showdrec(int, int, void*);
31 static long	gtime(uchar*);
32 static long	l16(void*);
33 static long	l32(void*);
34 static void	newdrec(Xfile*, Drec*);
35 static int	rzdir(Xfs*, Dir*, int, Drec*);
36 
37 Xfsub	isosub =
38 {
39 	ireset, iattach, iclone, iwalkup, iwalk, iopen, icreate,
40 	ireaddir, iread, iwrite, iclunk, iremove, istat, iwstat
41 };
42 
43 static void
44 ireset(void)
45 {}
46 
47 static int
48 iattach(Xfile *root)
49 {
50 	Xfs *cd = root->xf;
51 	Iobuf *p; Voldesc *v; Isofile *fp; Drec *dp;
52 	int fmt, blksize, i, n, l, haveplan9;
53 	Iobuf *dirp;
54 	uchar dbuf[256];
55 	Drec *rd = (Drec *)dbuf;
56 	uchar *q, *s;
57 
58 	dirp = nil;
59 	blksize = 0;
60 	fmt = 0;
61 	dp = nil;
62 	haveplan9 = 0;
63 	for(i=VOLDESC;i<VOLDESC+100; i++){	/* +100 for sanity */
64 		p = getbuf(cd->d, i);
65 		v = (Voldesc*)(p->iobuf);
66 		if(memcmp(v->byte, "\01CD001\01", 7) == 0){		/* iso */
67 			if(dirp)
68 				putbuf(dirp);
69 			dirp = p;
70 			fmt = 'z';
71 			dp = (Drec*)v->z.desc.rootdir;
72 			blksize = l16(v->z.desc.blksize);
73 			chat("iso, blksize=%d...", blksize);
74 
75 			v = (Voldesc*)(dirp->iobuf);
76 			haveplan9 = (strncmp((char*)v->z.boot.sysid, "PLAN 9", 6)==0);
77 			if(haveplan9){
78 				if(noplan9) {
79 					chat("ignoring plan9");
80 					haveplan9 = 0;
81 				} else {
82 					fmt = '9';
83 					chat("plan9 iso...");
84 				}
85 			}
86 			continue;
87 		}
88 
89 		if(memcmp(&v->byte[8], "\01CDROM\01", 7) == 0){	/* high sierra */
90 			if(dirp)
91 				putbuf(dirp);
92 			dirp = p;
93 			fmt = 'r';
94 			dp = (Drec*)v->r.desc.rootdir;
95 			blksize = l16(v->r.desc.blksize);
96 			chat("high sierra, blksize=%d...", blksize);
97 			continue;
98 		}
99 
100 		if(haveplan9==0 && !nojoliet
101 		&& memcmp(v->byte, "\02CD001\01", 7) == 0){
102 chat("%d %d\n", haveplan9, nojoliet);
103 			/*
104 			 * The right thing to do is walk the escape sequences looking
105 			 * for one of 25 2F 4[035], but Microsoft seems to not honor
106 			 * the format, which makes it hard to walk over.
107 			 */
108 			q = v->z.desc.escapes;
109 			if(q[0] == 0x25 && q[1] == 0x2F && (q[2] == 0x40 || q[2] == 0x43 || q[2] == 0x45)){	/* Joliet, it appears */
110 				if(dirp)
111 					putbuf(dirp);
112 				dirp = p;
113 				fmt = 'J';
114 				dp = (Drec*)v->z.desc.rootdir;
115 				if(blksize != l16(v->z.desc.blksize))
116 					fprint(2, "warning: suspicious Joliet blocksize\n");
117 				chat("joliet...");
118 				continue;
119 			}
120 		}
121 		putbuf(p);
122 		if(v->byte[0] == 0xFF)
123 			break;
124 	}
125 
126 	if(fmt == 0){
127 		if(dirp)
128 			putbuf(dirp);
129 		return -1;
130 	}
131 	assert(dirp != nil);
132 
133 	if(chatty)
134 		showdrec(2, fmt, dp);
135 	if(blksize > Sectorsize){
136 		chat("blksize too big...");
137 		putbuf(dirp);
138 		return -1;
139 	}
140 	if(waserror()){
141 		putbuf(dirp);
142 		nexterror();
143 	}
144 	root->len = sizeof(Isofile) - sizeof(Drec) + dp->reclen;
145 	root->ptr = fp = ealloc(root->len);
146 
147 	if(haveplan9)
148 		root->xf->isplan9 = 1;
149 
150 	fp->fmt = fmt;
151 	fp->blksize = blksize;
152 	fp->offset = 0;
153 	fp->doffset = 0;
154 	memmove(&fp->d, dp, dp->reclen);
155 	root->qid.path = l32(dp->addr);
156 	root->qid.type = QTDIR;
157 	putbuf(dirp);
158 	poperror();
159 	if(getdrec(root, rd) >= 0){
160 		n = rd->reclen-(34+rd->namelen);
161 		s = (uchar*)rd->name + rd->namelen;
162 		if((ulong)s & 1){
163 			s++;
164 			n--;
165 		}
166 		if(n >= 7 && s[0] == 'S' && s[1] == 'P' && s[2] == 7 &&
167 		   s[3] == 1 && s[4] == 0xBE && s[5] == 0xEF){
168 			root->xf->issusp = 1;
169 			root->xf->suspoff = s[6];
170 			n -= root->xf->suspoff;
171 			s += root->xf->suspoff;
172 			for(; n >= 4; s += l, n -= l){
173 				l = s[2];
174 				if(s[0] == 'E' && s[1] == 'R'){
175 					if(!norock && s[4] == 10 && memcmp(s+8, "RRIP_1991A", 10) == 0)
176 						root->xf->isrock = 1;
177 					break;
178 				} else if(s[0] == 'C' && s[1] == 'E' && s[2] >= 28){
179 					n = getcontin(root->xf->d, s, &s);
180 					continue;
181 				} else if(s[0] == 'R' && s[1] == 'R'){
182 					if(!norock)
183 						root->xf->isrock = 1;
184 					break;
185 				} else if(s[0] == 'S' && s[1] == 'T')
186 					break;
187 			}
188 		}
189 	}
190 	if(root->xf->isrock)
191 		chat("Rock Ridge...");
192 	fp->offset = 0;
193 	fp->doffset = 0;
194 	return 0;
195 }
196 
197 static void
198 iclone(Xfile *of, Xfile *nf)
199 {
200 	USED(of, nf);
201 }
202 
203 static void
204 iwalkup(Xfile *f)
205 {
206 	long paddr;
207 	uchar dbuf[256];
208 	Drec *d = (Drec *)dbuf;
209 	Xfile pf, ppf;
210 	Isofile piso, ppiso;
211 
212 	memset(&pf, 0, sizeof pf);
213 	memset(&ppf, 0, sizeof ppf);
214 	pf.ptr = &piso;
215 	ppf.ptr = &ppiso;
216 	if(opendotdot(f, &pf) < 0)
217 		error("can't open pf");
218 	paddr = l32(pf.ptr->d.addr);
219 	if(l32(f->ptr->d.addr) == paddr)
220 		return;
221 	if(opendotdot(&pf, &ppf) < 0)
222 		error("can't open ppf");
223 	while(getdrec(&ppf, d) >= 0){
224 		if(l32(d->addr) == paddr){
225 			newdrec(f, d);
226 			f->qid.path = paddr;
227 			f->qid.type = QTDIR;
228 			return;
229 		}
230 	}
231 	error("can't find addr of ..");
232 }
233 
234 static int
235 casestrcmp(int isplan9, char *a, char *b)
236 {
237 	int ca, cb;
238 
239 	if(isplan9)
240 		return strcmp(a, b);
241 	for(;;) {
242 		ca = *a++;
243 		cb = *b++;
244 		if(ca >= 'A' && ca <= 'Z')
245 			ca += 'a' - 'A';
246 		if(cb >= 'A' && cb <= 'Z')
247 			cb += 'a' - 'A';
248 		if(ca != cb) {
249 			if(ca > cb)
250 				return 1;
251 			return -1;
252 		}
253 		if(ca == 0)
254 			return 0;
255 	}
256 }
257 
258 static void
259 iwalk(Xfile *f, char *name)
260 {
261 	Isofile *ip = f->ptr;
262 	uchar dbuf[256];
263 	char nbuf[4*Maxname];
264 	Drec *d = (Drec*)dbuf;
265 	Dir dir;
266 	char *p;
267 	int len, vers, dvers;
268 
269 	vers = -1;
270 	if(p = strchr(name, ';')) {	/* assign = */
271 		len = p-name;
272 		if(len >= Maxname)
273 			len = Maxname-1;
274 		memmove(nbuf, name, len);
275 		vers = strtoul(p+1, 0, 10);
276 		name = nbuf;
277 	}
278 /*
279 	len = strlen(name);
280 	if(len >= Maxname){
281 		len = Maxname-1;
282 		if(name != nbuf){
283 			memmove(nbuf, name, len);
284 			name = nbuf;
285 		}
286 		name[len] = 0;
287 	}
288 */
289 
290 	chat("%d \"%s\"...", strlen(name), name);
291 	ip->offset = 0;
292 	setnames(&dir, nbuf);
293 	while(getdrec(f, d) >= 0) {
294 		dvers = rzdir(f->xf, &dir, ip->fmt, d);
295 		if(casestrcmp(f->xf->isplan9||f->xf->isrock, name, dir.name) != 0)
296 			continue;
297 		newdrec(f, d);
298 		f->qid.path = dir.qid.path;
299 		f->qid.type = dir.qid.type;
300 		USED(dvers);
301 		return;
302 	}
303 	USED(vers);
304 	error(Enonexist);
305 }
306 
307 static void
308 iopen(Xfile *f, int mode)
309 {
310 	mode &= ~OCEXEC;
311 	if(mode != OREAD && mode != OEXEC)
312 		error(Eperm);
313 	f->ptr->offset = 0;
314 	f->ptr->doffset = 0;
315 }
316 
317 static void
318 icreate(Xfile *f, char *name, long perm, int mode)
319 {
320 	USED(f, name, perm, mode);
321 	error(Eperm);
322 }
323 
324 static long
325 ireaddir(Xfile *f, uchar *buf, long offset, long count)
326 {
327 	Isofile *ip = f->ptr;
328 	Dir d;
329 	char names[4*Maxname];
330 	uchar dbuf[256];
331 	Drec *drec = (Drec *)dbuf;
332 	int n, rcnt;
333 
334 	if(offset==0){
335 		ip->offset = 0;
336 		ip->doffset = 0;
337 	}else if(offset != ip->doffset)
338 		error("seek in directory not allowed");
339 
340 	rcnt = 0;
341 	setnames(&d, names);
342 	while(rcnt < count && getdrec(f, drec) >= 0){
343 		if(drec->namelen == 1){
344 			if(drec->name[0] == 0)
345 				continue;
346 			if(drec->name[0] == 1)
347 				continue;
348 		}
349 		rzdir(f->xf, &d, ip->fmt, drec);
350 		d.qid.vers = f->qid.vers;
351 		if((n = convD2M(&d, buf+rcnt, count-rcnt)) <= BIT16SZ){
352 			ungetdrec(f);
353 			break;
354 		}
355 		rcnt += n;
356 	}
357 	ip->doffset += rcnt;
358 	return rcnt;
359 }
360 
361 static long
362 iread(Xfile *f, char *buf, long offset, long count)
363 {
364 	Isofile *ip = f->ptr;
365 	long size, addr, o, n;
366 	int rcnt = 0;
367 	Iobuf *p;
368 
369 	size = l32(ip->d.size);
370 	if(offset >= size)
371 		return 0;
372 	if(offset+count > size)
373 		count = size - offset;
374 	addr = (l32(ip->d.addr)+ip->d.attrlen)*ip->blksize + offset;
375 	o = (ulong)addr % Sectorsize;
376 	addr = (ulong)addr / Sectorsize;
377 	/*chat("d.addr=0x%x, addr=0x%x, o=0x%x...", l32(ip->d.addr), addr, o);*/
378 	n = Sectorsize - o;
379 
380 	while(count > 0){
381 		if(n > count)
382 			n = count;
383 		p = getbuf(f->xf->d, addr);
384 		memmove(&buf[rcnt], &p->iobuf[o], n);
385 		putbuf(p);
386 		count -= n;
387 		rcnt += n;
388 		++addr;
389 		o = 0;
390 		n = Sectorsize;
391 	}
392 	return rcnt;
393 }
394 
395 static long
396 iwrite(Xfile *f, char *buf, long offset, long count)
397 {
398 	USED(f, buf, offset, count);
399 	error(Eperm);
400 	return 0;
401 }
402 
403 static void
404 iclunk(Xfile *f)
405 {
406 	USED(f);
407 }
408 
409 static void
410 iremove(Xfile *f)
411 {
412 	USED(f);
413 	error(Eperm);
414 }
415 
416 static void
417 istat(Xfile *f, Dir *d)
418 {
419 	Isofile *ip = f->ptr;
420 
421 	rzdir(f->xf, d, ip->fmt, &ip->d);
422 	d->qid.vers = f->qid.vers;
423 	if(d->qid.path==f->xf->rootqid.path){
424 		d->qid.path = 0;
425 		d->qid.type = QTDIR;
426 	}
427 }
428 
429 static void
430 iwstat(Xfile *f, Dir *d)
431 {
432 	USED(f, d);
433 	error(Eperm);
434 }
435 
436 static int
437 showdrec(int fd, int fmt, void *x)
438 {
439 	Drec *d = (Drec *)x;
440 	int namelen;
441 	int syslen;
442 
443 	if(d->reclen == 0)
444 		return 0;
445 	fprint(fd, "%d %d %ld %ld ",
446 		d->reclen, d->attrlen, l32(d->addr), l32(d->size));
447 	fprint(fd, "%s 0x%2.2x %d %d %ld ",
448 		rdate(d->date, fmt), (fmt=='z' ? d->flags : d->r_flags),
449 		d->unitsize, d->gapsize, l16(d->vseqno));
450 	fprint(fd, "%d %s", d->namelen, nstr(d->name, d->namelen));
451 	if(fmt != 'J'){
452 		namelen = d->namelen + (1-(d->namelen&1));
453 		syslen = d->reclen - 33 - namelen;
454 		if(syslen != 0)
455 			fprint(fd, " %s", nstr(&d->name[namelen], syslen));
456 	}
457 	fprint(fd, "\n");
458 	return d->reclen + (d->reclen&1);
459 }
460 
461 static void
462 newdrec(Xfile *f, Drec *dp)
463 {
464 	Isofile *x = f->ptr;
465 	Isofile *n;
466 	int len;
467 
468 	len = sizeof(Isofile) - sizeof(Drec) + dp->reclen;
469 	n = ealloc(len);
470 	n->fmt = x->fmt;
471 	n->blksize = x->blksize;
472 	n->offset = 0;
473 	n->doffset = 0;
474 	memmove(&n->d, dp, dp->reclen);
475 	free(x);
476 	f->ptr = n;
477 	f->len = len;
478 }
479 
480 static void
481 ungetdrec(Xfile *f)
482 {
483 	Isofile *ip = f->ptr;
484 
485 	if(ip->offset >= ip->odelta){
486 		ip->offset -= ip->odelta;
487 		ip->odelta = 0;
488 	}
489 }
490 
491 static int
492 getdrec(Xfile *f, void *buf)
493 {
494 	Isofile *ip = f->ptr;
495 	int len = 0, boff = 0;
496 	long size, addr;
497 	Iobuf *p = 0;
498 
499 	if(!ip)
500 		return -1;
501 	size = l32(ip->d.size);
502 	while(ip->offset<size){
503 		addr = (l32(ip->d.addr)+ip->d.attrlen)*ip->blksize + ip->offset;
504 		boff = (ulong)addr % Sectorsize;
505 		if(boff > Sectorsize-34){
506 			ip->offset += Sectorsize-boff;
507 			continue;
508 		}
509 		p = getbuf(f->xf->d, (ulong)addr/Sectorsize);
510 		len = p->iobuf[boff];
511 		if(len >= 34)
512 			break;
513 		putbuf(p);
514 		p = 0;
515 		ip->offset += Sectorsize-boff;
516 	}
517 	if(p) {
518 		memmove(buf, &p->iobuf[boff], len);
519 		putbuf(p);
520 		ip->odelta = len + (len&1);
521 		ip->offset += ip->odelta;
522 	}
523 	if(p)
524 		return 0;
525 	return -1;
526 }
527 
528 static int
529 opendotdot(Xfile *f, Xfile *pf)
530 {
531 	uchar dbuf[256];
532 	Drec *d = (Drec *)dbuf;
533 	Isofile *ip = f->ptr, *pip = pf->ptr;
534 
535 	ip->offset = 0;
536 	if(getdrec(f, d) < 0){
537 		chat("opendotdot: getdrec(.) failed...");
538 		return -1;
539 	}
540 	if(d->namelen != 1 || d->name[0] != 0){
541 		chat("opendotdot: no . entry...");
542 		return -1;
543 	}
544 	if(l32(d->addr) != l32(ip->d.addr)){
545 		chat("opendotdot: bad . address...");
546 		return -1;
547 	}
548 	if(getdrec(f, d) < 0){
549 		chat("opendotdot: getdrec(..) failed...");
550 		return -1;
551 	}
552 	if(d->namelen != 1 || d->name[0] != 1){
553 		chat("opendotdot: no .. entry...");
554 		return -1;
555 	}
556 
557 	pf->xf = f->xf;
558 	pip->fmt = ip->fmt;
559 	pip->blksize = ip->blksize;
560 	pip->offset = 0;
561 	pip->doffset = 0;
562 	pip->d = *d;
563 	return 0;
564 }
565 
566 enum {
567 	Hname = 1,
568 	Hmode = 2,
569 };
570 
571 static int
572 rzdir(Xfs *fs, Dir *d, int fmt, Drec *dp)
573 {
574 	int n, flags, i, j, lj, nl, vers, sysl, mode, l, have;
575 	uchar *s;
576 	char *p;
577 	char buf[Maxname+UTFmax+1];
578 	uchar *q;
579 	Rune r;
580 	enum { ONAMELEN = 28 };	/* old Plan 9 directory name length */
581 
582 	have = 0;
583 	flags = 0;
584 	vers = -1;
585 	d->qid.path = l32(dp->addr);
586 	d->qid.type = 0;
587 	d->qid.vers = 0;
588 	n = dp->namelen;
589 	memset(d->name, 0, Maxname);
590 	if(n == 1) {
591 		switch(dp->name[0]){
592 		case 1:
593 			d->name[1] = '.';
594 			/* fall through */
595 		case 0:
596 			d->name[0] = '.';
597 			have = Hname;
598 			break;
599 		default:
600 			d->name[0] = tolower(dp->name[0]);
601 		}
602 	} else {
603 		if(fmt == 'J'){	/* Joliet, 16-bit Unicode */
604 			q = (uchar*)dp->name;
605 			for(i=j=lj=0; i<n && j<Maxname; i+=2){
606 				lj = j;
607 				r = (q[i]<<8)|q[i+1];
608 				j += runetochar(buf+j, &r);
609 			}
610 			if(j >= Maxname)
611 				j = lj;
612 			memmove(d->name, buf, j);
613 		}else{
614 			if(n >= Maxname)
615 				n = Maxname-1;
616 			for(i=0; i<n; i++)
617 				d->name[i] = tolower(dp->name[i]);
618 		}
619 	}
620 
621 	sysl = dp->reclen-(34+dp->namelen);
622 	s = (uchar*)dp->name + dp->namelen;
623 	if(((ulong)s) & 1) {
624 		s++;
625 		sysl--;
626 	}
627 	if(fs->isplan9 && sysl > 0) {
628 		/*
629 		 * get gid, uid, mode and possibly name
630 		 * from plan9 directory extension
631 		 */
632 		nl = *s;
633 		if(nl >= ONAMELEN)
634 			nl = ONAMELEN-1;
635 		if(nl) {
636 			memset(d->name, 0, ONAMELEN);
637 			memmove(d->name, s+1, nl);
638 		}
639 		s += 1 + *s;
640 		nl = *s;
641 		if(nl >= ONAMELEN)
642 			nl = ONAMELEN-1;
643 		memset(d->uid, 0, ONAMELEN);
644 		memmove(d->uid, s+1, nl);
645 		s += 1 + *s;
646 		nl = *s;
647 		if(nl >= ONAMELEN)
648 			nl = ONAMELEN-1;
649 		memset(d->gid, 0, ONAMELEN);
650 		memmove(d->gid, s+1, nl);
651 		s += 1 + *s;
652 		if(((ulong)s) & 1)
653 			s++;
654 		d->mode = l32(s);
655 		if(d->mode & DMDIR)
656 			d->qid.type |= QTDIR;
657 	} else {
658 		d->mode = 0444;
659 		switch(fmt) {
660 		case 'z':
661 			if(fs->isrock)
662 				strcpy(d->gid, "ridge");
663 			else
664 				strcpy(d->gid, "iso9660");
665 			flags = dp->flags;
666 			break;
667 		case 'r':
668 			strcpy(d->gid, "sierra");
669 			flags = dp->r_flags;
670 			break;
671 		case 'J':
672 			strcpy(d->gid, "joliet");
673 			flags = dp->flags;
674 			break;
675 		case '9':
676 			strcpy(d->gid, "plan9");
677 			flags = dp->flags;
678 			break;
679 		}
680 		if(flags & 0x02){
681 			d->qid.type |= QTDIR;
682 			d->mode |= DMDIR|0111;
683 		}
684 		strcpy(d->uid, "cdrom");
685 		if(fmt!='9' && !(d->mode&DMDIR)){
686 			/*
687 			 * ISO 9660 actually requires that you always have a . and a ;,
688 			 * even if there is no version and no extension.  Very few writers
689 			 * do this.  If the version is present, we use it for qid.vers.
690 			 * If there is no extension but there is a dot, we strip it off.
691 			 * (VMS heads couldn't comprehend the dot as a file name character
692 			 * rather than as just a separator between name and extension.)
693 			 *
694 			 * We don't do this for directory names because directories are
695 			 * not allowed to have extensions and versions.
696 			 */
697 			if((p=strchr(d->name, ';')) != nil){
698 				vers = strtoul(p+1, 0, 0);
699 				d->qid.vers = vers;
700 				*p = '\0';
701 			}
702 			if((p=strchr(d->name, '.')) != nil && *(p+1)=='\0')
703 				*p = '\0';
704 		}
705 		if(fs->issusp){
706 			nl = 0;
707 			s += fs->suspoff;
708 			sysl -= fs->suspoff;
709 			for(; sysl >= 4 && have != (Hname|Hmode); sysl -= l, s += l){
710 				if(s[0] == 0 && ((ulong)s & 1)){
711 					/* MacOS pads individual entries, contrary to spec */
712 					s++;
713 					sysl--;
714 				}
715 				l = s[2];
716 				if(s[0] == 'P' && s[1] == 'X' && s[3] == 1){
717 					/* posix file attributes */
718 					mode = l32(s+4);
719 					d->mode = mode & 0777;
720 					if((mode & 0170000) == 040000){
721 						d->mode |= DMDIR;
722 						d->qid.type |= QTDIR;
723 					}
724 					have |= Hmode;
725 				} else if(s[0] == 'N' && s[1] == 'M' && s[3] == 1){
726 					/* alternative name */
727 					if((s[4] & ~1) == 0){
728 						i = nl+l-5;
729 						if(i >= Maxname)
730 							i = Maxname-1;
731 						if((i -= nl) > 0){
732 							memmove(d->name+nl, s+5, i);
733 							nl += i;
734 						}
735 						if(s[4] == 0)
736 							have |= Hname;
737 					}
738 				} else if(s[0] == 'C' && s[1] == 'E' && s[2] >= 28){
739 					sysl = getcontin(fs->d, s, &s);
740 					continue;
741 				} else if(s[0] == 'S' && s[1] == 'T')
742 					break;
743 			}
744 		}
745 	}
746 	d->length = 0;
747 	if((d->mode & DMDIR) == 0)
748 		d->length = l32(dp->size);
749 	d->type = 0;
750 	d->dev = 0;
751 	d->atime = gtime(dp->date);
752 	d->mtime = d->atime;
753 	return vers;
754 }
755 
756 static int
757 getcontin(Xdata *dev, uchar *p, uchar **s)
758 {
759 	long bn, off, len;
760 	Iobuf *b;
761 
762 	bn = l32(p+4);
763 	off = l32(p+12);
764 	len = l32(p+20);
765 	chat("getcontin %d...", bn);
766 	b = getbuf(dev, bn);
767 	if(b == 0){
768 		*s = 0;
769 		return 0;
770 	}
771 	*s = b->iobuf+off;
772 	putbuf(b);
773 	return len;
774 }
775 
776 static char *
777 nstr(uchar *p, int n)
778 {
779 	static char buf[132];
780 	char *q = buf;
781 
782 	while(--n >= 0){
783 		if(*p == '\\')
784 			*q++ = '\\';
785 		if(' ' <= *p && *p <= '~')
786 			*q++ = *p++;
787 		else
788 			q += sprint(q, "\\%2.2ux", *p++);
789 	}
790 	*q = 0;
791 	return buf;
792 }
793 
794 static char *
795 rdate(uchar *p, int fmt)
796 {
797 	static char buf[64];
798 	int htz, s, n;
799 
800 	n = sprint(buf, "%2.2d.%2.2d.%2.2d %2.2d:%2.2d:%2.2d",
801 		p[0], p[1], p[2], p[3], p[4], p[5]);
802 	if(fmt == 'z'){
803 		htz = p[6];
804 		if(htz >= 128){
805 			htz = 256-htz;
806 			s = '-';
807 		}else
808 			s = '+';
809 		sprint(&buf[n], " (%c%.1f)", s, (float)htz/2);
810 	}
811 	return buf;
812 }
813 
814 static char
815 dmsize[12] =
816 {
817 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
818 };
819 
820 static int
821 dysize(int y)
822 {
823 
824 	if((y%4) == 0)
825 		return 366;
826 	return 365;
827 }
828 
829 static long
830 gtime(uchar *p)	/* yMdhmsz */
831 {
832 	long t;
833 	int i, y, M, d, h, m, s, tz;
834 	y=p[0]; M=p[1]; d=p[2];
835 	h=p[3]; m=p[4]; s=p[5]; tz=p[6];
836 	USED(tz);
837 	if (y < 70)
838 		return 0;
839 	if (M < 1 || M > 12)
840 		return 0;
841 	if (d < 1 || d > dmsize[M-1])
842 		return 0;
843 	if (h > 23)
844 		return 0;
845 	if (m > 59)
846 		return 0;
847 	if (s > 59)
848 		return 0;
849 	y += 1900;
850 	t = 0;
851 	for(i=1970; i<y; i++)
852 		t += dysize(i);
853 	if (dysize(y)==366 && M >= 3)
854 		t++;
855 	while(--M)
856 		t += dmsize[M-1];
857 	t += d-1;
858 	t = 24*t + h;
859 	t = 60*t + m;
860 	t = 60*t + s;
861 	return t;
862 }
863 
864 #define	p	((uchar*)arg)
865 
866 static long
867 l16(void *arg)
868 {
869 	long v;
870 
871 	v = ((long)p[1]<<8)|p[0];
872 	if (v >= 0x8000L)
873 		v -= 0x10000L;
874 	return v;
875 }
876 
877 static long
878 l32(void *arg)
879 {
880 	return ((((((long)p[3]<<8)|p[2])<<8)|p[1])<<8)|p[0];
881 }
882 
883 #undef	p
884