summaryrefslogtreecommitdiff
path: root/sys/src/ape/cmd/pax/paxdir.c
blob: 851d81b3153cbb68f4859be76ad5918dd31fdb6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
/*
	opendir -- open a directory stream
  
	last edit:	16-Jun-1987	D A Gwyn
*/

#include	<sys/errno.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	"paxdir.h"

#ifdef BSD_SYSV
/*
	<sys/_dir.h> -- definitions for 4.2,4.3BSD directories
  
	last edit:	25-Apr-1987	D A Gwyn
  
	A directory consists of some number of blocks of DIRBLKSIZ bytes each,
	where DIRBLKSIZ is chosen such that it can be transferred to disk in a
	single atomic operation (e.g., 512 bytes on most machines).
  
	Each DIRBLKSIZ-byte block contains some number of directory entry
	structures, which are of variable length.  Each directory entry has the
	beginning of a (struct direct) at the front of it, containing its
	filesystem-unique ident number, the length of the entry, and the length
	of the name contained in the entry.  These are followed by the NUL-
	terminated name padded to a (long) boundary with 0 bytes.  The maximum
	length of a name in a directory is MAXNAMELEN.
  
	The macro DIRSIZ(dp) gives the amount of space required to represent a
	directory entry.  Free space in a directory is represented by entries
	that have dp->d_reclen > DIRSIZ(dp).  All DIRBLKSIZ bytes in a
	directory block are claimed by the directory entries; this usually
	results in the last entry in a directory having a large dp->d_reclen.
	When entries are deleted from a directory, the space is returned to the
	previous entry in the same directory block by increasing its
	dp->d_reclen.  If the first entry of a directory block is free, then
	its dp->d_fileno is set to 0; entries other than the first in a
	directory do not normally have 	dp->d_fileno set to 0.
  
	prerequisite:	<sys/types.h>
*/

#if defined(accel) || defined(sun) || defined(vax)
#define	DIRBLKSIZ	512	/* size of directory block */
#else
#ifdef alliant
#define	DIRBLKSIZ	4096	/* size of directory block */
#else
#ifdef gould
#define	DIRBLKSIZ	1024	/* size of directory block */
#else
#ifdef ns32000			/* Dynix System V */
#define	DIRBLKSIZ	2600	/* size of directory block */
#else				/* be conservative; multiple blocks are okay
				 * but fractions are not */
#define	DIRBLKSIZ	4096	/* size of directory block */
#endif
#endif
#endif
#endif

#define	MAXNAMELEN	255	/* maximum filename length */
/* NOTE:  not MAXNAMLEN, which has been preempted by SVR3 <dirent.h> */

struct direct {			/* data from read()/_getdirentries() */
    unsigned long   d_fileno;	/* unique ident of entry */
    unsigned short  d_reclen;	/* length of this record */
    unsigned short  d_namlen;	/* length of string in d_name */
    char            d_name[MAXNAMELEN + 1];	/* NUL-terminated filename */
};

/*
	The DIRSIZ macro gives the minimum record length which will hold the
	directory entry.  This requires the amount of space in a (struct
	direct) without the d_name field, plus enough space for the name with a
	terminating NUL character, rounded up to a (long) boundary.
  
	(Note that Berkeley didn't properly compensate for struct padding,
	but we nevertheless have to use the same size as the actual system.)
*/

#define	DIRSIZ( dp )	((sizeof(struct direct) - (MAXNAMELEN+1) \
			+ sizeof(long) + (dp)->d_namlen) \
			/ sizeof(long) * sizeof(long))

#else
#include	<sys/dir.h>
#ifdef SYSV3
#undef	MAXNAMLEN		/* avoid conflict with SVR3 */
#endif
 /* Good thing we don't need to use the DIRSIZ() macro! */
#ifdef d_ino			/* 4.3BSD/NFS using d_fileno */
#undef	d_ino			/* (not absolutely necessary) */
#else
#define	d_fileno	d_ino	/* (struct direct) member */
#endif
#endif
#ifdef UNK
#ifndef UFS
#include "***** ERROR ***** UNK applies only to UFS"
/* One could do something similar for getdirentries(), but I didn't bother. */
#endif
#include	<signal.h>
#endif

#if defined(UFS) + defined(BFS) + defined(NFS) != 1	/* sanity check */
#include "***** ERROR ***** exactly one of UFS, BFS, or NFS must be defined"
#endif

#ifdef UFS
#define	RecLen( dp )	(sizeof(struct direct))	/* fixed-length entries */
#else				/* BFS || NFS */
#define	RecLen( dp )	((dp)->d_reclen)	/* variable-length entries */
#endif

#ifdef NFS
#ifdef BSD_SYSV
#define	getdirentries	_getdirentries	/* package hides this system call */
#endif
extern int      getdirentries();
static long     dummy;		/* getdirentries() needs basep */
#define	GetBlock( fd, buf, n )	getdirentries( fd, buf, (unsigned)n, &dummy )
#else				/* UFS || BFS */
#ifdef BSD_SYSV
#define read	_read		/* avoid emulation overhead */
#endif
extern int      read();
#define	GetBlock( fd, buf, n )	read( fd, buf, (unsigned)n )
#endif

#ifdef UNK
extern int      _getdents();	/* actual system call */
#endif

extern char    *strncpy();
extern int      fstat();
extern OFFSET   lseek();

extern int      errno;

#ifndef DIRBLKSIZ
#define	DIRBLKSIZ	4096	/* directory file read buffer size */
#endif

#ifndef NULL
#define	NULL	0
#endif

#ifndef SEEK_CUR
#define	SEEK_CUR	1
#endif

#ifndef S_ISDIR			/* macro to test for directory file */
#define	S_ISDIR( mode )		(((mode) & S_IFMT) == S_IFDIR)
#endif


#ifndef SEEK_CUR
#define	SEEK_CUR	1
#endif

#ifdef BSD_SYSV
#define open	_open		/* avoid emulation overhead */
#endif

extern int      getdents();	/* SVR3 system call, or emulation */

typedef char   *pointer;	/* (void *) if you have it */

extern void     free();
extern pointer  malloc();
extern int
open(), close(), fstat();

extern int      errno;
extern OFFSET   lseek();

#ifndef SEEK_SET
#define	SEEK_SET	0
#endif

typedef int     bool;		/* Boolean data type */
#define	false	0
#define	true	1


#ifndef NULL
#define	NULL	0
#endif

#ifndef O_RDONLY
#define	O_RDONLY	0
#endif

#ifndef S_ISDIR			/* macro to test for directory file */
#define	S_ISDIR( mode )		(((mode) & S_IFMT) == S_IFDIR)
#endif

#ifdef __STDC__

DIR *opendir(char *dirname)

#else
    
DIR *opendir(dirname)
char           *dirname;	/* name of directory */

#endif
{
    register DIR   *dirp;	/* -> malloc'ed storage */
    register int    fd;		/* file descriptor for read */
    struct stat     sbuf;	/* result of fstat() */

    if ((fd = open(dirname, O_RDONLY)) < 0)
	return ((DIR *)NULL);		/* errno set by open() */

    if (fstat(fd, &sbuf) != 0 || !S_ISDIR(sbuf.st_mode)) {
	close(fd);
	errno = ENOTDIR;
	return ((DIR *)NULL);		/* not a directory */
    }
    if ((dirp = (DIR *) malloc(sizeof(DIR))) == (DIR *)NULL
	|| (dirp->dd_buf = (char *) malloc((unsigned) DIRBUF)) == (char *)NULL
	) {
	register int    serrno = errno;
	/* errno set to ENOMEM by sbrk() */

	if (dirp != (DIR *)NULL)
	    free((pointer) dirp);

	close(fd);
	errno = serrno;
	return ((DIR *)NULL);		/* not enough memory */
    }
    dirp->dd_fd = fd;
    dirp->dd_loc = dirp->dd_size = 0;	/* refill needed */

    return dirp;
}


/*
 *	closedir -- close a directory stream
 *
 *	last edit:	11-Nov-1988	D A Gwyn
 */

#ifdef __STDC__

int closedir(register DIR *dirp)

#else
    
int closedir(dirp)
register DIR	*dirp;		/* stream from opendir() */

#endif
{
    register int	fd;

    if ( dirp == (DIR *)NULL || dirp->dd_buf == (char *)NULL ) {
	errno = EFAULT;
	return -1;			/* invalid pointer */
    }

    fd = dirp->dd_fd;			/* bug fix thanks to R. Salz */
    free( (pointer)dirp->dd_buf );
    free( (pointer)dirp );
    return close( fd );
}


/*
	readdir -- read next entry from a directory stream
  
	last edit:	25-Apr-1987	D A Gwyn
*/

#ifdef __STDC__

struct dirent  *readdir(register DIR *dirp)

#else
    
struct dirent  *readdir(dirp)
register DIR   *dirp;		/* stream from opendir() */

#endif
{
    register struct dirent *dp;	/* -> directory data */

    if (dirp == (DIR *)NULL || dirp->dd_buf == (char *)NULL) {
	errno = EFAULT;
	return (struct dirent *)NULL;		/* invalid pointer */
    }
    do {
	if (dirp->dd_loc >= dirp->dd_size)	/* empty or obsolete */
	    dirp->dd_loc = dirp->dd_size = 0;

	if (dirp->dd_size == 0	/* need to refill buffer */
	    && (dirp->dd_size =
		getdents(dirp->dd_fd, dirp->dd_buf, (unsigned) DIRBUF)
		) <= 0
	    )
	    return ((struct dirent *)NULL);	/* EOF or error */

	dp = (struct dirent *) & dirp->dd_buf[dirp->dd_loc];
	dirp->dd_loc += dp->d_reclen;
    }
    while (dp->d_ino == 0L);	/* don't rely on getdents() */

    return dp;
}


/*
	seekdir -- reposition a directory stream
  
	last edit:	24-May-1987	D A Gwyn
  
	An unsuccessful seekdir() will in general alter the current
	directory position; beware.
  
	NOTE:	4.nBSD directory compaction makes seekdir() & telldir()
		practically impossible to do right.  Avoid using them!
*/

#ifdef __STDC__

void seekdir(register DIR *dirp, register OFFSET loc)

#else
    
void seekdir(dirp, loc)
register DIR   *dirp;		/* stream from opendir() */
register OFFSET  loc;		/* position from telldir() */

#endif
{
    register bool   rewind;	/* "start over when stymied" flag */

    if (dirp == (DIR *)NULL || dirp->dd_buf == (char *)NULL) {
	errno = EFAULT;
	return;			/* invalid pointer */
    }
    /*
     * A (struct dirent)'s d_off is an invented quantity on 4.nBSD
     * NFS-supporting systems, so it is not safe to lseek() to it. 
     */

    /* Monotonicity of d_off is heavily exploited in the following. */

    /*
     * This algorithm is tuned for modest directory sizes.  For huge
     * directories, it might be more efficient to read blocks until the first
     * d_off is too large, then back up one block, or even to use binary
     * search on the directory blocks.  I doubt that the extra code for that
     * would be worthwhile. 
     */

    if (dirp->dd_loc >= dirp->dd_size	/* invalid index */
	|| ((struct dirent *) & dirp->dd_buf[dirp->dd_loc])->d_off > loc
    /* too far along in buffer */
	)
	dirp->dd_loc = 0;	/* reset to beginning of buffer */
    /* else save time by starting at current dirp->dd_loc */

    for (rewind = true;;) {
	register struct dirent *dp;

	/* See whether the matching entry is in the current buffer. */

	if ((dirp->dd_loc < dirp->dd_size	/* valid index */
	     || readdir(dirp) != (struct dirent *)NULL	/* next buffer read */
	     && (dirp->dd_loc = 0, true)	/* beginning of buffer set */
	     )
	    && (dp = (struct dirent *) & dirp->dd_buf[dirp->dd_loc])->d_off
	    <= loc		/* match possible in this buffer */
	    ) {
	    for ( /* dp initialized above */ ;
		 (char *) dp < &dirp->dd_buf[dirp->dd_size];
		 dp = (struct dirent *) ((char *) dp + dp->d_reclen)
		)
		if (dp->d_off == loc) {	/* found it! */
		    dirp->dd_loc =
			(char *) dp - dirp->dd_buf;
		    return;
		}
	    rewind = false;	/* no point in backing up later */
	    dirp->dd_loc = dirp->dd_size;	/* set end of buffer */
	} else
	 /* whole buffer past matching entry */ if (!rewind) {	/* no point in searching
								 * further */
	    errno = EINVAL;
	    return;		/* no entry at specified loc */
	} else {		/* rewind directory and start over */
	    rewind = false;	/* but only once! */

	    dirp->dd_loc = dirp->dd_size = 0;

	    if (lseek(dirp->dd_fd, (OFFSET) 0, SEEK_SET)
		!= 0
		)
		return;		/* errno already set (EBADF) */

	    if (loc == 0)
		return;		/* save time */
	}
    }
}


/* telldir - report directory stream position
 *
 * DESCRIPTION
 *
 *	Returns the offset of the next directory entry in the
 *	directory associated with dirp.
 *
 *	NOTE:	4.nBSD directory compaction makes seekdir() & telldir()
 *		practically impossible to do right.  Avoid using them!
 *
 * PARAMETERS
 *
 *	DIR	*dirp	- stream from opendir()
 *
 * RETURNS
 *
 * 	Return offset of next entry 
 */


#ifdef __STDC__

OFFSET telldir(DIR *dirp)

#else
    
OFFSET telldir(dirp)			
DIR            *dirp;		/* stream from opendir() */

#endif
{
    if (dirp == (DIR *)NULL || dirp->dd_buf == (char *)NULL) {
	errno = EFAULT;
	return -1;		/* invalid pointer */
    }
    if (dirp->dd_loc < dirp->dd_size)	/* valid index */
	return ((struct dirent *) & dirp->dd_buf[dirp->dd_loc])->d_off;
    else			/* beginning of next directory block */
	return lseek(dirp->dd_fd, (OFFSET) 0, SEEK_CUR);
}


#ifdef UFS

/*
	The following routine is necessary to handle DIRSIZ-long entry names.
	Thanks to Richard Todd for pointing this out.
*/


/* return # chars in embedded name */

#ifdef __STDC__

static int NameLen(char *name)

#else
    
static int NameLen(name)
char            *name;		/* -> name embedded in struct direct */

#endif
{
    register char  *s;		/* -> name[.] */
    register char  *stop = &name[DIRSIZ];	/* -> past end of name field */

    for (s = &name[1];		/* (empty names are impossible) */
	 *s != '\0'		/* not NUL terminator */
	 && ++s < stop;		/* < DIRSIZ characters scanned */
	);

    return s - name;		/* # valid characters in name */
}

#else				/* BFS || NFS */

extern int      strlen();

#define	NameLen( name )	strlen( name )	/* names are always NUL-terminated */

#endif

#ifdef UNK
static enum {
    maybe, no, yes
} state = maybe;


/* sig_catch - used to catch signals
 *
 * DESCRIPTION
 *
 *	Used to catch signals.
 */

/*ARGSUSED*/

#ifdef __STDC__

static void sig_catch(int sig)

#else
    
static void sig_catch(sig)
int             sig;		/* must be SIGSYS */

#endif
{
    state = no;			/* attempted _getdents() faulted */
}
#endif


/* getdents - get directory entries
 *
 * DESCRIPTION
 *
 *	Gets directory entries from the filesystem in an implemenation
 *	defined way.
 *
 * PARAMETERS
 *
 *	int             fildes	- directory file descriptor 
 *	char           *buf	- where to put the (struct dirent)s 
 *	unsigned	nbyte	- size of buf[] 
 *
 * RETURNS
 * 
 *	Returns number of bytes read; 0 on EOF, -1 on error 
 */

#ifdef __STDC__

int getdents(int fildes, char *buf, unsigned nbyte)

#else
    
int getdents(fildes, buf, nbyte)	
int             fildes;		/* directory file descriptor */
char           *buf;		/* where to put the (struct dirent)s */
unsigned        nbyte;		/* size of buf[] */

#endif
{
    int             serrno;	/* entry errno */
    OFFSET          offset;	/* initial directory file offset */
    struct stat     statb;	/* fstat() info */
    union {
	/* directory file block buffer */
#ifdef UFS
	char		dblk[DIRBLKSIZ + 1];
#else
	char            dblk[DIRBLKSIZ];
#endif
	struct direct   dummy;	/* just for alignment */
    } u;		/* (avoids having to malloc()) */
    register struct direct *dp;	/* -> u.dblk[.] */
    register struct dirent *bp;	/* -> buf[.] */

#ifdef UNK
    switch (state) {
	SIG_T         (*shdlr)();	/* entry SIGSYS handler */
	register int    retval;		/* return from _getdents() if any */

    case yes:			/* _getdents() is known to work */
	return _getdents(fildes, buf, nbyte);

    case maybe:		/* first time only */
	shdlr = signal(SIGSYS, sig_catch);
	retval = _getdents(fildes, buf, nbyte);	/* try it */
	signal(SIGSYS, shdlr);

	if (state == maybe) {	/* SIGSYS did not occur */
	    state = yes;	/* so _getdents() must have worked */
	    return retval;
	}
	/* else fall through into emulation */

/*	case no:	/* fall through into emulation */
    }
#endif

    if (buf == (char *)NULL
#ifdef ATT_SPEC
	|| (unsigned long) buf % sizeof(long) != 0	/* ugh */
#endif
	) {
	errno = EFAULT;		/* invalid pointer */
	return -1;
    }
    if (fstat(fildes, &statb) != 0) {
	return -1;		/* errno set by fstat() */
    }

    if (!S_ISDIR(statb.st_mode)) {
	errno = ENOTDIR;	/* not a directory */
	return -1;
    }
    if ((offset = lseek(fildes, (OFFSET) 0, SEEK_CUR)) < 0) {
	return -1;		/* errno set by lseek() */
    }

#ifdef BFS			/* no telling what remote hosts do */
    if ((unsigned long) offset % DIRBLKSIZ != 0) {
	errno = ENOENT;		/* file pointer probably misaligned */
	return -1;
    }
#endif

    serrno = errno;		/* save entry errno */

    for (bp = (struct dirent *) buf; bp == (struct dirent *) buf;) {	

    	/* convert next directory block */
	int             size;

	do {
	    size = GetBlock(fildes, u.dblk, DIRBLKSIZ);
	} while (size == -1 && errno == EINTR);

	if (size <= 0) {
	    return size;	/* EOF or error (EBADF) */
	}

	for (dp = (struct direct *) u.dblk;
	     (char *) dp < &u.dblk[size];
	     dp = (struct direct *) ((char *) dp + RecLen(dp))
	    ) {
#ifndef UFS
	    if (dp->d_reclen <= 0) {
		errno = EIO;	/* corrupted directory */
		return -1;
	    }
#endif

	    if (dp->d_fileno != 0) {	/* non-empty; copy to user buffer */
		register int    reclen =
		DIRENTSIZ(NameLen(dp->d_name));

		if ((char *) bp + reclen > &buf[nbyte]) {
		    errno = EINVAL;
		    return -1;	/* buf too small */
		}
		bp->d_ino = dp->d_fileno;
		bp->d_off = offset + ((char *) dp - u.dblk);
		bp->d_reclen = reclen;

		{
#ifdef UFS
		    /* Is the following kludge ugly?  You bet. */

		    register char   save = dp->d_name[DIRSIZ];
		    /* save original data */

		    dp->d_name[DIRSIZ] = '\0';
		    /* ensure NUL termination */
#endif
		    /* adds NUL padding */
		    strncpy(bp->d_name, dp->d_name, reclen - DIRENTBASESIZ);
#ifdef UFS
		    dp->d_name[DIRSIZ] = save;
		    /* restore original data */
#endif
		}

		bp = (struct dirent *) ((char *) bp + reclen);
	    }
	}

#ifndef BFS			/* 4.2BSD screwed up; fixed in 4.3BSD */
	if ((char *) dp > &u.dblk[size]) {
	    errno = EIO;	/* corrupted directory */
	    return -1;
	}
#endif
    }

    errno = serrno;		/* restore entry errno */
    return (char *) bp - buf;	/* return # bytes read */
}