1 /*
2  * The ARAnyM MetaDOS driver.
3  *
4  * 2002 STan
5  *
6  * Based on:
7  * filesys.c,v 1.24 2002/01/25 09:23:52 fna Exp
8  *
9  * This file has been modified as part of the FreeMiNT project. See
10  * the file Changes.MH for details and dates.
11  *
12  *
13  * Copyright 1990,1991,1992 Eric R. Smith.
14  * Copyright 1992,1993,1994 Atari Corp.
15  * All rights reserved.
16  *
17  */
18 
19 /*
20  * various file system interface things
21  */
22 
23 #include "hostfs.h"
24 #include "mint/assert.h"
25 #include "mint/string.h"
26 #include "mint/errno.h"
27 #include "mint/ctype.h"
28 #include "mint/credentials.h"
29 
30 #if 1
31 #define PATH2COOKIE_DB(x) TRACE(x)
32 #else
33 #define PATH2COOKIE_DB(x) DEBUG(x)
34 #endif
35 
36 
37 long
getxattr(FILESYS * fs,fcookie * fc,XATTR * xattr)38 getxattr(FILESYS *fs, fcookie *fc, XATTR *xattr)
39 {
40 	STAT stat;
41 	long r;
42 
43 	assert(fs->fsflags & FS_EXT_3);
44 
45 	r = xfs_stat64(fs, fc, &stat);
46 	if (!r)
47 	{
48 		xattr->mode	= stat.mode;
49 		xattr->index	= stat.ino;
50 		xattr->dev	= stat.dev;
51 		xattr->rdev	= stat.rdev;
52 		xattr->nlink	= stat.nlink;
53 		xattr->uid	= stat.uid;
54 		xattr->gid	= stat.gid;
55 		xattr->size	= stat.size;
56 		xattr->blksize	= stat.blksize;
57 		xattr->nblocks	= (stat.blksize < 512) ? stat.blocks :
58 					stat.blocks / (stat.blksize >> 9);
59 
60 		SET_XATTR_TD(xattr,m,stat.mtime.time);
61 		SET_XATTR_TD(xattr,a,stat.atime.time);
62 		SET_XATTR_TD(xattr,c,stat.ctime.time);
63 		xattr->attr	= 0;
64 
65 		/* fake attr field a little bit */
66 		if (S_ISDIR(stat.mode))
67 			xattr->attr = FA_DIR;
68 		else if (!(stat.mode & 0222))
69 			xattr->attr = FA_RDONLY;
70 
71 		xattr->reserved2 = 0;
72 		xattr->reserved3[0] = 0;
73 		xattr->reserved3[1] = 0;
74 	}
75 
76 	return r;
77 }
78 
79 long
getstat64(FILESYS * fs,fcookie * fc,STAT * stat)80 getstat64(FILESYS *fs, fcookie *fc, STAT *stat)
81 {
82 	XATTR xattr;
83 	long r;
84 
85 	assert(fs->getxattr);
86 
87 	r = xfs_getxattr(fs, fc, &xattr);
88 	if (!r)
89 	{
90 		stat->dev	= xattr.dev;
91 		stat->ino	= xattr.index;
92 		stat->mode	= xattr.mode;
93 		stat->nlink	= xattr.nlink;
94 		stat->uid	= xattr.uid;
95 		stat->gid	= xattr.gid;
96 		stat->rdev	= xattr.rdev;
97 
98 		/* no native UTC extension
99 		 * -> convert to unix UTC
100 		 */
101 		stat->atime.high_time = 0;
102 		stat->atime.time = unixtime (xattr.atime, xattr.adate) + timezone;
103 		stat->atime.nanoseconds = 0;
104 
105 		stat->mtime.high_time = 0;
106 		stat->mtime.time = unixtime (xattr.mtime, xattr.mdate) + timezone;
107 		stat->mtime.nanoseconds = 0;
108 
109 		stat->ctime.high_time = 0;
110 		stat->ctime.time = unixtime (xattr.ctime, xattr.cdate) + timezone;
111 		stat->ctime.nanoseconds = 0;
112 
113 		stat->size	= xattr.size;
114 		stat->blocks	= (xattr.blksize < 512) ? xattr.nblocks :
115 					xattr.nblocks * (xattr.blksize >> 9);
116 		stat->blksize	= xattr.blksize;
117 
118 		stat->flags	= 0;
119 		stat->gen	= 0;
120 
121 		bzero(stat->res, sizeof(stat->res));
122 	}
123 
124 	return r;
125 }
126 
127 
128 /*
129  * routines for parsing path names
130  */
131 
132 /*
133  * relpath2cookie converts a TOS file name into a file cookie representing
134  * the directory the file resides in, and a character string representing
135  * the name of the file in that directory. The character string is
136  * copied into the "lastname" array. If lastname is NULL, then the cookie
137  * returned actually represents the file, instead of just the directory
138  * the file is in.
139  *
140  * note that lastname, if non-null, should be big enough to contain all the
141  * characters in "path", since if the file system doesn't want the kernel
142  * to do path name parsing we may end up just copying path to lastname
143  * and returning the current or root directory, as appropriate
144  *
145  * "relto" is the directory relative to which the search should start.
146  * if you just want the current directory, use path2cookie instead.
147  *
148  */
149 
150 long _cdecl
relpath2cookie(struct proc * p,fcookie * relto,const char * path,char * lastname,fcookie * res,int depth)151 relpath2cookie(struct proc *p, fcookie *relto, const char *path, char *lastname,
152 	       fcookie *res, int depth)
153 {
154 	char newpath[16];
155 
156 	struct cwd *cwd = p->p_cwd;
157 
158 	char temp2[PATH_MAX];
159 	char linkstuff[PATH_MAX];
160 
161 	fcookie dir;
162 	int drv;
163 	XATTR xattr;
164 	long r;
165 
166 
167 	/* dolast: 0 == return a cookie for the directory the file is in
168 	 *         1 == return a cookie for the file itself, don't follow links
169 	 *	   2 == return a cookie for whatever the file points at
170 	 */
171 	int dolast = 0;
172 	int i = 0;
173 
174 	if (!path)
175 		return ENOTDIR;
176 
177 	if (*path == '\0')
178 		return ENOENT;
179 
180 	if (!lastname)
181 	{
182 		dolast = 1;
183 		lastname = temp2;
184 	}
185 	else if (lastname == follow_links)
186 	{
187 		dolast = 2;
188 		lastname = temp2;
189 	}
190 
191 	*lastname = '\0';
192 
193 	PATH2COOKIE_DB (("relpath2cookie(%s, dolast=%d, depth=%d [relto %p, %i])",
194 		path, dolast, depth, relto->fs, relto->dev));
195 
196 	if (depth > MAX_LINKS)
197 	{
198 		DEBUG (("Too many symbolic links"));
199 		return ELOOP;
200 	}
201 
202 	/* special cases: CON:, AUX:, etc. should be converted to U:\DEV\CON,
203 	 * U:\DEV\AUX, etc.
204 	 */
205 # if 1
206 	if (path[0] && path[1] && path[2] && (path[3] == ':') && !path[4])
207 # else
208 	if (strlen (path) == 4 && path[3] == ':')
209 # endif
210 	{
211 		strcpy(newpath, "U:\\DEV\\");
212 		newpath[7] = path[0];
213 		newpath[8] = path[1];
214 		newpath[9] = path[2];
215 
216 		if ((path[0] == 'N' || path[0] == 'n') &&
217 		    (path[1] == 'U' || path[1] == 'u') &&
218 		    (path[2] == 'L' || path[2] == 'l'))
219 		{
220 			/* the device file is u:\dev\null */
221 			newpath[10] = 'l';
222 			newpath[11] = '\0';
223 		} else
224 		if ((path[0] == 'M' || path[0] == 'm') &&
225 		    (path[1] == 'I' || path[1] == 'i') &&
226 		    (path[2] == 'D' || path[2] == 'd'))
227 		{
228 			/* the device file is u:\dev\midi */
229 			newpath[10] = 'i';
230 			newpath[11] = '\0';
231 		} else {
232 			/* add the NULL terminator */
233 			newpath[10] = '\0';
234 		}
235 
236 		path = newpath;
237 	}
238 
239 	/* first, check for a drive letter
240 	 *
241 	 * BUG: a '\' at the start of a symbolic link is relative to the
242 	 * current drive of the process, not the drive the link is located on
243 	 */
244 	/* The check if the process runs chroot used to be inside the
245 	 * conditional (triggering ENOTDIR for drive specs.  IMHO its
246 	 * cleaner to interpret it as a regular filename instead.
247 	 * Rationale: The path "c:/auto" is actually the same as
248 	 * "/c:/auto".  If the process' root directory is not "/" but
249 	 * maybe "/home/ftp" then we should interpret the same filename
250 	 * now as "/home/ftp/c:/auto".
251 	 */
252 	if (path[2] != ':' && path[1] == ':' && !cwd->root_dir)
253 	{
254 		char c = tolower ((int)path[0] & 0xff);
255 
256 		if (c >= 'a' && c <= 'z')
257 			drv = c - 'a';
258 		else if (c >= '1' && c <= '6')
259 			drv = 26 + (c - '1');
260 		else
261 			goto nodrive;
262 
263 # if 1
264 		/* if root_dir is set drive references are forbidden
265 		 */
266 		if (cwd->root_dir)
267 			return ENOTDIR;
268 # endif
269 
270 		path += 2;
271 
272 		/* remember that we saw a drive letter
273 		 */
274 		i = 1;
275 	}
276 	else
277 	{
278 nodrive:
279 		drv = cwd->curdrv;
280 	}
281 
282 	/* see if the path is rooted from '\\'
283 	 */
284 	if (DIRSEP (*path))
285 	{
286 		while (DIRSEP (*path))
287 			path++;
288 
289 		/* if root_dir is set this is our start point
290 		 */
291 		if (cwd->root_dir)
292 			dup_cookie (&dir, &cwd->rootdir);
293 		else
294 			dup_cookie (&dir, &cwd->root[drv]);
295 	}
296 	else
297 	{
298 		if (i)
299 		{
300 			/* an explicit drive letter was given
301 			 */
302 			dup_cookie (&dir, &cwd->curdir[drv]);
303 		}
304 		else
305 		{
306 			PATH2COOKIE_DB (("relpath2cookie: using relto (%p, %li, %i) for dir", relto->fs, relto->index, relto->dev));
307 			dup_cookie (&dir, relto);
308 		}
309 	}
310 
311 	if (!dir.fs && !cwd->root_dir)
312 	{
313 		dup_cookie (&dir, &cwd->root[drv]);
314 	}
315 
316 	if (!dir.fs)
317 	{
318 		DEBUG (("relpath2cookie: no file system: returning ENXIO"));
319 		return ENXIO;
320 	}
321 
322 	/* here's where we come when we've gone across a mount point
323 	 */
324 restart_mount:
325 
326 	if (!*path)
327 	{
328 		/* nothing more to do
329 		 */
330 		PATH2COOKIE_DB (("relpath2cookie: no more path, returning 0"));
331 
332 		*res = dir;
333 		return 0;
334 	}
335 
336 
337 	if (dir.fs->fsflags & FS_KNOPARSE)
338 	{
339 		if (!dolast)
340 		{
341 			PATH2COOKIE_DB (("fs is a KNOPARSE, nothing to do"));
342 
343 			strncpy (lastname, path, PATH_MAX-1);
344 			lastname[PATH_MAX - 1] = 0;
345 			r = 0;
346 			*res = dir;
347 		}
348 		else
349 		{
350 			PATH2COOKIE_DB (("fs is a KNOPARSE, calling lookup"));
351 
352 			r = xfs_lookup (dir.fs, &dir, path, res);
353 			if (r == EMOUNT)
354 			{
355 				/* hmmm... a ".." at a mount point, maybe
356 				 */
357 				fcookie mounteddir;
358 
359 				r = xfs_root (dir.fs, dir.dev, &mounteddir);
360 				if (r == 0 && drv == UNIDRV)
361 				{
362 					if (dir.fs == mounteddir.fs
363 						&& dir.index == mounteddir.index
364 						&& dir.dev == mounteddir.dev)
365 					{
366 						release_cookie (&dir);
367 						release_cookie (&mounteddir);
368 						dup_cookie (&dir, &cwd->root[UNIDRV]);
369 						DEBUG(("path2cookie: restarting from mount point"));
370 						goto restart_mount;
371 					}
372 				}
373 				else
374 				{
375 					if (r == 0)
376 						release_cookie (&mounteddir);
377 
378 					r = 0;
379 				}
380 			}
381 
382 			release_cookie (&dir);
383 		}
384 
385 		PATH2COOKIE_DB (("relpath2cookie(2): returning %ld", r));
386 		return r;
387 	}
388 
389 
390 	/* parse all but (possibly) the last component of the path name
391 	 *
392 	 * rules here: at the top of the loop, &dir is the cookie of
393 	 * the directory we're in now, xattr is its attributes, and res is
394 	 * unset at the end of the loop, &dir is unset, and either r is
395 	 * nonzero (to indicate an error) or res is set to the final result
396 	 */
397 	r = xfs_getxattr (dir.fs, &dir, &xattr);
398 	if (r)
399 	{
400 		DEBUG (("couldn't get directory attributes"));
401 		release_cookie (&dir);
402 		return EINTERNAL;
403 	}
404 
405 	while (*path)
406 	{
407 		/* we must have a directory, since there are more things
408 		 * in the path
409 		 */
410 		if (!S_ISDIR(xattr.mode))
411 		{
412 			PATH2COOKIE_DB (("relpath2cookie: not a directory, returning ENOTDIR"));
413 			release_cookie (&dir);
414 			r = ENOTDIR;
415 			break;
416 		}
417 
418 #if 0
419 		/* we must also have search permission for the directory
420 		 */
421 		if (denyaccess (p->p_cred->ucr, &xattr, S_IXOTH))
422 		{
423 			DEBUG (("search permission in directory denied"));
424 			release_cookie (&dir);
425 			/* r = ENOTDIR; */
426 			r = EACCES;
427 			break;
428 		}
429 #endif
430 
431 		/* skip slashes
432 		 */
433 		while (DIRSEP (*path))
434 			path++;
435 
436 		/* next, peel off the next name in the path
437 		 */
438 		{
439 			register int len;
440 			register char c, *s;
441 
442 			len = 0;
443 			s = lastname;
444 			c = *path;
445 			while (c && !DIRSEP (c))
446 			{
447 				if (len++ < PATH_MAX)
448 					*s++ = c;
449 				c = *++path;
450 			}
451 
452 			*s = '\0';
453 		}
454 
455 		/* if there are no more names in the path, and we don't want
456 		 * to actually look up the last name, then we're done
457 		 */
458 		if (dolast == 0)
459 		{
460 			register const char *s = path;
461 
462 			while (DIRSEP (*s))
463 				s++;
464 
465 			if (!*s) {
466 				PATH2COOKIE_DB (("relpath2cookie: no more path, breaking"));
467 				*res = dir;
468 				PATH2COOKIE_DB (("relpath2cookie: *res = [%p, %i]", res->fs, res->dev));
469 				break;
470 			}
471 		}
472 
473 		if (cwd->root_dir)
474 		{
475 			if (samefile (&dir, &cwd->rootdir)
476 				&& lastname[0] == '.'
477 				&& lastname[1] == '.'
478 				&& lastname[2] == '\0')
479 			{
480 				PATH2COOKIE_DB (("relpath2cookie: can't leave root [%s] -> forward to '.'", cwd->root_dir));
481 
482 				lastname[1] = '\0';
483 			}
484 		}
485 
486 		PATH2COOKIE_DB (("relpath2cookie: looking up [%s]", lastname));
487 
488 		r = xfs_lookup (dir.fs, &dir, lastname, res);
489 		if (r == EMOUNT)
490 		{
491 			fcookie mounteddir;
492 
493 			r = xfs_root (dir.fs, dir.dev, &mounteddir);
494 			if (r == 0 && drv == UNIDRV)
495 			{
496 				if (samefile (&dir, &mounteddir))
497 				{
498 					release_cookie (&dir);
499 					release_cookie (&mounteddir);
500 					dup_cookie (&dir, &cwd->root[UNIDRV]);
501 					TRACE(("path2cookie: restarting from mount point"));
502 					goto restart_mount;
503 				}
504 				else if (r == 0)
505 				{
506 					r = EINTERNAL;
507 					release_cookie (&mounteddir);
508 					release_cookie (&dir);
509 					break;
510 				}
511 			}
512 			else if (r == 0)
513 			{
514 				*res = mounteddir;
515 			}
516 			else
517 			{
518 				release_cookie (&dir);
519 				break;
520 			}
521 		}
522 		else if (r)
523 		{
524 			release_cookie (&dir);
525 			break;
526 		}
527 
528 		/* read the file attribute
529 		 */
530 		r = xfs_getxattr (res->fs, res, &xattr);
531 		if (r != 0)
532 		{
533 			DEBUG (("path2cookie: couldn't get file attributes"));
534 			release_cookie (&dir);
535 			release_cookie (res);
536 			break;
537 		}
538 
539 		/* check for a symbolic link
540 		 * - if the file is a link, and we're following links, follow it
541 		 */
542 		if (S_ISLNK(xattr.mode) && (*path || dolast > 1))
543 		{
544 			{
545 				r = xfs_readlink (res->fs, res, linkstuff, PATH_MAX);
546 				release_cookie (res);
547 				if (r)
548 				{
549 					DEBUG (("error reading symbolic link"));
550 					release_cookie (&dir);
551 					break;
552 				}
553 				r = relpath2cookie (p, &dir, linkstuff, follow_links, res, depth + 1);
554 				release_cookie (&dir);
555 				if (r)
556 				{
557 					DEBUG (("error following symbolic link"));
558 					break;
559 				}
560 			}
561 			dir = *res;
562 			xfs_getxattr (res->fs, res, &xattr);
563 		}
564 		else
565 		{
566 			TRACE(("relpath2cookie: lookup ok, mode 0x%x", xattr.mode));
567 
568 			release_cookie (&dir);
569 			dir = *res;
570 		}
571 	}
572 
573 	PATH2COOKIE_DB (("relpath2cookie(3): returning %ld", r));
574 	return r;
575 }
576 
577 long _cdecl
path2cookie(struct proc * p,const char * path,char * lastname,fcookie * res)578 path2cookie(struct proc *p, const char *path, char *lastname, fcookie *res)
579 {
580 	struct cwd *cwd = p->p_cwd;
581 
582 	/* AHDI sometimes will keep insisting that a media change occured;
583 	 * we limit the number of retrys to avoid hanging the system
584 	 */
585 # define MAX_TRYS 4
586 	int trycnt = MAX_TRYS - 1;
587 
588 	fcookie *dir = &cwd->curdir[cwd->curdrv];
589 	long r;
590 
591 restart:
592 	r = relpath2cookie(p, dir, path, lastname, res, 0);
593 	if (r == ECHMEDIA && trycnt--)
594 	{
595 		DEBUG(("path2cookie: restarting due to media change"));
596 		goto restart;
597 	}
598 
599 	return r;
600 }
601 
602 /*
603  * release_cookie: tell the file system owner that a cookie is no
604  * longer in use by the kernel
605  *
606  * release_cookie doesn't release anymore unless there is no entry in
607  * the cookie cache.  Otherwise, we just let the cookie get released
608  * through clobber_cookie when the cache fills or is killed through the
609  * routine above - EKL
610  */
611 void _cdecl
release_cookie(fcookie * fc)612 release_cookie (fcookie *fc)
613 {
614 	if (fc)
615 	{
616 		FILESYS *fs;
617 
618 		fs = fc->fs;
619 		if (fs && fs->release)
620 			xfs_release (fs, fc);
621 	}
622 }
623 
624 /*
625  * Make a new cookie (newc) which is a duplicate of the old cookie
626  * (oldc). This may be something the file system is interested in,
627  * so we give it a chance to do the duplication; if it doesn't
628  * want to, we just copy.
629  */
630 
631 void
dup_cookie(fcookie * newc,fcookie * oldc)632 dup_cookie (fcookie *newc, fcookie *oldc)
633 {
634 	FILESYS *fs;
635 
636 	fs = oldc->fs;
637 	if (fs && fs->dupcookie)
638 		xfs_dupcookie (fs, newc, oldc);
639 	else
640 		*newc = *oldc;
641 }
642 
643 
644 /*
645  * check to see that a file is a directory, and that write permission
646  * is granted; return an error code, or 0 if everything is ok.
647  */
648 long
dir_access(struct ucred * cred,fcookie * dir,ushort perm,ushort * mode)649 dir_access(struct ucred *cred, fcookie *dir, ushort perm, ushort *mode)
650 {
651 	XATTR xattr;
652 	long r;
653 
654 	r = xfs_getxattr(dir->fs, dir, &xattr);
655 	if (r)
656 	{
657 		DEBUG(("dir_access: file system returned %ld", r));
658 		return r;
659 	}
660 
661 	if (!S_ISDIR(xattr.mode))
662 	{
663 		DEBUG(("file is not a directory"));
664 		return ENOTDIR;
665 	}
666 
667 #if 0
668 	if (denyaccess(cred, &xattr, perm))
669 	{
670 		DEBUG(("no permission for directory"));
671 		return EACCES;
672 	}
673 #endif
674 
675 	*mode = xattr.mode;
676 
677 	return 0;
678 }
679 
680 /*
681  * returns 1 if the given name contains a wildcard character
682  */
683 int
has_wild(const char * name)684 has_wild(const char *name)
685 {
686 	char c;
687 
688 	while ((c = *name++) != 0)
689 		if (c == '*' || c == '?')
690 			return 1;
691 
692 	return 0;
693 }
694 
695 /*
696  * void copy8_3(dest, src): convert a file name (src) into DOS 8.3 format
697  * (in dest). Note the following things:
698  * if a field has less than the required number of characters, it is
699  * padded with blanks
700  * a '*' means to pad the rest of the field with '?' characters
701  * special things to watch for:
702  *	"." and ".." are more or less left alone
703  *	"*.*" is recognized as a special pattern, for which dest is set
704  *	to just "*"
705  * Long names are truncated. Any extensions after the first one are
706  * ignored, i.e. foo.bar.c -> foo.bar, foo.c.bar->foo.c.
707  */
708 void
copy8_3(char * dest,const char * src)709 copy8_3(char *dest, const char *src)
710 {
711 	char fill = ' ', c;
712 	int i;
713 
714 	if (src[0] == '.')
715 	{
716 		if (src[1] == 0)
717 		{
718 			strcpy(dest, ".       .   ");
719 			return;
720 		}
721 
722 		if (src[1] == '.' && src[2] == 0)
723 		{
724 			strcpy(dest, "..      .   ");
725 			return;
726 		}
727 	}
728 
729 	if (src[0] == '*' && src[1] == '.' && src[2] == '*' && src[3] == 0)
730 	{
731 		dest[0] = '*';
732 		dest[1] = 0;
733 		return;
734 	}
735 
736 	for (i = 0; i < 8; i++)
737 	{
738 		c = *src++;
739 
740 		if (!c || c == '.')
741 			break;
742 		if (c == '*')
743 			fill = c = '?';
744 
745 		*dest++ = toupper((int)c & 0xff);
746 	}
747 
748 	while (i++ < 8)
749 		*dest++ = fill;
750 
751 	*dest++ = '.';
752 	i = 0;
753 	fill = ' ';
754 	while (c && c != '.')
755 		c = *src++;
756 
757 	if (c)
758 	{
759 		for( ;i < 3; i++)
760 		{
761 			c = *src++;
762 
763 			if (!c || c == '.')
764 				break;
765 
766 			if (c == '*')
767 				c = fill = '?';
768 
769 			*dest++ = toupper((int)c & 0xff);
770 		}
771 	}
772 
773 	while (i++ < 3)
774 		*dest++ = fill;
775 
776 	*dest = 0;
777 }
778 
779 /*
780  * int pat_match(name, patrn): returns 1 if "name" matches the template in
781  * "patrn", 0 if not. "patrn" is assumed to have been expanded in 8.3
782  * format by copy8_3; "name" need not be. Any '?' characters in patrn
783  * will match any character in name. Note that if "patrn" has a '*' as
784  * the first character, it will always match; this will happen only if
785  * the original pattern (before copy8_3 was applied) was "*.*".
786  *
787  * BUGS: acts a lot like the silly TOS pattern matcher.
788  */
789 int
pat_match(const char * name,const char * template)790 pat_match(const char *name, const char *template)
791 {
792 	char expname[TOS_NAMELEN+1];
793 	register char *s;
794 	register char c;
795 
796 	if (*template == '*')
797 		return 1;
798 
799 	copy8_3(expname, name);
800 
801 	s = expname;
802 	while ((c = *template++) != 0)
803 	{
804 		if (c != *s && c != '?')
805 			return 0;
806 		s++;
807 	}
808 
809 	return 1;
810 }
811 
812 /*
813  * int samefile(fcookie *a, fcookie *b): returns 1 if the two cookies
814  * refer to the same file or directory, 0 otherwise
815  */
816 int
samefile(fcookie * a,fcookie * b)817 samefile(fcookie *a, fcookie *b)
818 {
819 	if (a->fs == b->fs && a->dev == b->dev && a->index == b->index)
820 		return 1;
821 
822 	return 0;
823 }
824