/* Copyright 1990,1991,1992 Eric R. Smith. All rights reserved. */ /* * various file system interface things */ #include "mint.h" FILESYS *active_fs; FILESYS *drives[NUM_DRIVES]; FILEPTR *flist; /* a list of free file pointers */ char follow_links[1]; /* dummy "name" used as a parameter to path2cookie */ /* vector of valid drives, according to GEMDOS */ /* note that this isn't necessarily the same as what the BIOS thinks of * as valid */ long dosdrvs; /* * Initialize a specific drive. This is called whenever a new drive * is accessed, or when media change occurs on an old drive. * Assumption: at this point, active_fs is a valid pointer * to a list of file systems. */ /* table of processes holding locks on drives */ extern PROC *dlockproc[]; /* in dosdir.c */ void init_drive(i) int i; { long r; FILESYS *fs; fcookie root_dir; TRACE("init_drive(%c)", i+'A'); drives[i] = 0; /* no file system */ if (i >= 0 && i < NUM_DRIVES) { if (dlockproc[i]) return; } for (fs = active_fs; fs; fs = fs->next) { r = (*fs->root)(i, &root_dir); if (r == 0) { drives[i] = root_dir.fs; break; } } } /* * initialize the file system */ #define NUMFPS 40 /* initial number of file pointers */ void init_filesys() { static FILEPTR initial[NUMFPS+1]; long drv; int i; extern FILESYS tos_filesys, bios_filesys, pipe_filesys, proc_filesys, uni_filesys; /* get the vector of connected GEMDOS drives */ dosdrvs = Dsetdrv(Dgetdrv()) | drvmap(); /* set up some initial file pointers */ for (i = 0; i < NUMFPS; i++) { initial[i].devinfo = (ulong) (&initial[i+1]); } initial[NUMFPS].devinfo = 0; flist = initial; /* set up the file systems */ tos_filesys.next = 0; bios_filesys.next = &tos_filesys; pipe_filesys.next = &bios_filesys; proc_filesys.next = &pipe_filesys; uni_filesys.next = &proc_filesys; active_fs = &uni_filesys; /* initialize the BIOS file system */ biosfs_init(); /* initialize the unified file system */ unifs_init(); drv = dosdrvs | PSEUDODRVS; #if 0 /* now run through the systems and see who's interested in what drives */ /* THIS IS NOW DONE AUTOMAGICALLY IN path2cookie */ for (i = 0; i < NUM_DRIVES; i++) { if ( drv & (1L << i) ) init_drive(i); } #endif } /* * load file systems from disk * this routine is called after process 0 is set up, but before any user * processes are run * * NOTE that a number of directory changes take place here: we look first * in the current directory, then in the directory \mint, and finally * the d_lock() calls force us into the root directory. */ typedef FILESYS * (*FSFUNC) P_((struct kerinfo *)); void load_filesys() { long r; BASEPAGE *b; FILESYS *fs; FSFUNC initf; static DTABUF dta; int i; extern struct kerinfo kernelinfo; /* in main.c */ #define NPATHS 2 static const char *paths[NPATHS] = {"", "\\mint"}; curproc->dta = &dta; for (i = 0; i < NPATHS; i++) { if (*paths[i]) d_setpath(paths[i]); r = f_sfirst("*.xfs", 0); while (r == 0) { b = (BASEPAGE *)p_exec(3, dta.dta_name, (char *)"", (char *)0); if ( ((long)b) < 0 ) { DEBUG("Error loading file system %s", dta.dta_name); continue; } /* we leave a little bit of slop at the end of the loaded stuff */ m_shrink(0, (virtaddr)b, 512 + b->p_tlen + b->p_dlen + b->p_blen); initf = (FSFUNC)b->p_tbase; fs = (*initf)(&kernelinfo); TRACE("initializing %s", dta.dta_name); if (fs) { TRACE("%s loaded OK", dta.dta_name); fs->next = active_fs; active_fs = fs; } else { DEBUG("%s returned null", dta.dta_name); } r = f_snext(); } } /* here, we invalidate all old drives EXCEPT for ones we're already using (at * this point, only the bios devices should be open) * this gives newly loaded file systems a chance to replace the * default tosfs.c */ for (i = 0; i < NUM_DRIVES; i++) { if (d_lock(1, i) == 0) /* lock if possible */ d_lock(0, i); /* and then unlock */ } } void close_filesys() { PROC *p; FILEPTR *f; int i; TRACE("close_filesys"); /* close every open file */ for (p = proclist; p; p = p->gl_next) { for (i = MIN_HANDLE; i < MAX_OPEN; i++) { if ( (f = p->handle[i]) != 0) { if (p->wait_q == TSR_Q || p->wait_q == ZOMBIE_Q) ALERT("Open file for dead process?"); do_pclose(p, f); } } } } /* * "media change" routine: called when a media change is detected on device * d, which may or may not be a BIOS device. All handles associated with * the device are closed, and all directories invalidated. This routine * does all the dirty work, and is called automatically when * disk_changed detects a media change. */ void changedrv(d) unsigned d; { PROC *p; int i; FILEPTR *f; FILESYS *fs; DIR *dirh; fcookie dir; int warned = (d & 0xf000) == PROC_BASE_DEV; int r; /* re-initialize the device, if it was a BIOS device */ if (d < NUM_DRIVES) { fs = drives[d]; if (fs) { (void)(*fs->dskchng)(d); } init_drive(d); } for (p = proclist; p; p = p->gl_next) { /* invalidate all open files on this device */ for (i = MIN_HANDLE; i < MAX_OPEN; i++) { if (((f = p->handle[i]) != 0) && (f->fc.dev == d)) { if (!warned) { ALERT( "Files were open on a changed drive (0x%x)!", d); warned++; } /* we set f->dev to NULL to indicate to do_pclose that this is an * emergency close, and that it shouldn't try to make any * calls to the device driver since the file has gone away */ f->dev = NULL; (void)do_pclose(p, f); p->handle[i] = 0; } } /* terminate any active directory searches on the drive */ /* BUG: This handles only Fsfirst/Fsnext searches! */ for (i = 0; i < NUM_SEARCH; i++) { dirh = &curproc->srchdir[i]; if (dirh->fc.fs && dirh->fc.dev == d) { dirh->fc.fs = 0; curproc->srchdta[i] = 0; } } if (d >= NUM_DRIVES) continue; /* change any active directories on the device to the (new) root */ fs = drives[d]; if (fs) { r = (*fs->root)(d, &dir); if (r != E_OK) dir.fs = 0; } else { dir.fs = 0; dir.dev = d; } for (i = 0; i < NUM_DRIVES; i++) { if (p->root[i].dev == d) p->root[i] = dir; if (p->curdir[i].dev == d) p->curdir[i] = dir; } } } /* * check for media change: if the drive has changed, call changedrv to * invalidate any open files and file handles associated with it, and * call the file system's media change routine. * returns: 0 if no change, 1 if change */ int disk_changed(d) int d; { short r; FILESYS *fs; static char tmpbuf[8192]; /* for now, only check BIOS devices */ if (d < 0 || d >= NUM_DRIVES) return 0; /* has the drive been initialized yet? If not, then initialize it and return * "no change" */ if (!(fs = drives[d])) { TRACE("drive %c not yet initialized", d+'A'); changedrv(d); return 0; } /* We have to do this stuff no matter what, because someone may have installed * vectors to force a media change... * PROBLEM: AHDI may get upset if the drive isn't valid. * SOLUTION: don't change the default PSEUDODRIVES setting! */ r = mediach(d); if (r == 1) { /* drive _may_ have changed */ r = rwabs(0, tmpbuf, 1, 0, d, 0L); /* check the BIOS */ if (r != E_CHNG) { /* nope, no change */ return 0; } r = 2; /* drive was definitely changed */ } if (r == 2) { fs = drives[d]; /* get filesystem associated with drive */ if ((*fs->dskchng)(d)) { /* does the fs agree that it changed? */ changedrv(d); /* yes -- do the change */ return 1; } } return 0; } /* * routines for parsing path names */ char temp1[PATH_MAX]; /* temporary storage for file names */ #define DIRSEP(p) ((p) == '\\') /* * relpath2cookie converts a TOS file name into a file cookie representing * the directory the file resides in, and a character string representing * the name of the file in that directory. The character string is * copied into the "lastname" array. If lastname is NULL, then the cookie * returned actually represents the file, instead of just the directory * the file is in. * * note that lastname, if non-null, should be big enough to contain all the * characters in "path", since if the file system doesn't want the kernel * to do path name parsing we may end up just copying path to lastname * and returning the current or root directory, as appropriate * * "relto" is the directory relative to which the search should start. * if you just want the current directory, use path2cookie instead. * * "depth" is used to control recursion in symbolic links; if it exceeds * MAX_LINKS, we return ELOOP. * * N.B.: "depth" is also overloaded to control whether drive letter * interpretation is performed; if drive == 0, it is assumed that * drive letters should _not_ be interpreted; if drive > 0, it * is assumed that they should be, since we are in this case following * a symbolic link. */ #define MAX_LINKS 4 long relpath2cookie(relto, path, lastname, res, depth) fcookie *relto; const char *path; char *lastname; fcookie *res; int depth; { static fcookie dir; int drv; int len; char c, *s; XATTR xattr; static char newpath[16] = "U:\\DEV\\"; char temp2[PATH_MAX]; char linkstuff[PATH_MAX]; long r = 0; /* dolast: 0 == return a cookie for the directory the file is in * 1 == return a cookie for the file itself, don't follow links * 2 == return a cookie for whatever the file points at */ int dolast = 0; int i = 0; TRACE("relpath2cookie(%s)", path); if (depth > MAX_LINKS) return ELOOP; if (!lastname) { dolast = 1; lastname = temp2; } else if (lastname == follow_links) { dolast = 2; lastname = temp2; } *lastname = 0; /* special cases: CON:, AUX:, etc. should be converted to U:\DEV\CON, * U:\DEV\AUX, etc. */ if (strlen(path) == 4 && path[3] == ':') { strncpy(newpath+7, path, 3); path = newpath; } /* first, check for a drive letter */ /* BUG: a '\' at the start of a symbolic link is relative to the current * drive of the process, not the drive the link is located on */ if (path[1] == ':' && depth > 0) { c = path[0]; if (c >= 'a' && c <= 'z') drv = c - 'a'; else if (c >= 'A' && c <= 'Z') drv = c - 'A'; else goto nodrive; path += 2; i = 1; /* remember that we saw a drive letter */ } else { nodrive: drv = curproc->curdrv; } /* see if the path is rooted from '\\' */ if (DIRSEP(*path)) { while(DIRSEP(*path))path++; dir = curproc->root[drv]; } else { if (i) { /* an explicit drive letter was given */ dir = curproc->curdir[drv]; } else dir = *relto; } if (!dir.fs) { changedrv(dir.dev); dir = curproc->root[drv]; } if (!dir.fs) { DEBUG("path2cookie: no file system"); return EDRIVE; } *res = dir; if (!*path) { /* nothing more to do */ return 0; } /* here's where we come when we've gone across a mount point */ restart_mount: /* see if there has been a disk change; if so, return E_CHNG. * path2cookie will restart the search automatically; other functions * that call relpath2cookie directly will have to fail gracefully */ if (disk_changed(dir.dev)) { return E_CHNG; } if (dir.fs->fsflags & FS_KNOPARSE) { if (!dolast) { strncpy(lastname, path, PATH_MAX-1); lastname[PATH_MAX - 1] = 0; r = 0; } else { r = (*dir.fs->lookup)(&dir, path, res); } goto check_for_mount; } /* parse all but (possibly) the last component of the path name */ for(;;) { /* if nothing left in path, and we don't care about links, * then we're finished */ if (dolast < 2 && !*path) { dir = *res; break; } /* first, check to see if we're allowed to read this link/directory * NOTE: at this point, "res" contains the new 'directory', and * "dir" contains the old directory we were in (in case we need * to call relpath2cookie on a link) */ r = (res->fs->getxattr)(res, &xattr); if (r != 0) { DEBUG("path2cookie: couldn't get file attributes"); break; } /* if the "directory" is a link, follow it */ i = depth; while ( (xattr.mode & S_IFMT) == S_IFLNK ) { if (i++ > MAX_LINKS) return ELOOP; r = (res->fs->readlink)(res, linkstuff, PATH_MAX); if (r) { DEBUG("error reading symbolic link"); break; } r = relpath2cookie(&dir, linkstuff, follow_links, res, depth+1); if (r) { DEBUG("error following symbolic link"); break; } (void)(res->fs->getxattr)(res, &xattr); } /* if there's nothing left in the path, we can break here */ if (!*path) { dir = *res; break; } /* the "directory" had better, in fact, be a directory */ if ( (xattr.mode & S_IFMT) != S_IFDIR ) { return EPTHNF; } /* and we had better have search permission to it */ if (denyaccess(&xattr, S_IXOTH)) { DEBUG("search permission in directory denied"); return EPTHNF; } dir = *res; /* next, peel off the next name in the path */ len = 0; s = lastname; c = *path; while (c && !DIRSEP(c)) { if (len++ < PATH_MAX) *s++ = c; c = *++path; } *s = 0; while(DIRSEP(*path))path++; /* if there are no more names in the path, then we may be done */ if (dolast == 0 && !*path) break; r = (*dir.fs->lookup)(&dir, lastname, res); if (r) { /* error? */ DEBUG("path2cookie: lookup returned %ld", r); dir = *res; break; } } check_for_mount: if (r == EMOUNT) { /* hmmm... a ".." at a mount point, maybe */ fcookie mounteddir; r = (*dir.fs->root)(dir.dev, &mounteddir); if (r == 0 && drv == UNIDRV) { if (dir.fs == mounteddir.fs && dir.index == mounteddir.index && dir.dev == mounteddir.dev) { *res = dir = curproc->root[UNIDRV]; TRACE("path2cookie: restarting from mount point"); goto restart_mount; } } else r = 0; } return r; } #define MAX_TRYS 8 long path2cookie(path, lastname, res) const char *path; char *lastname; fcookie *res; { fcookie *dir; long r; /* AHDI sometimes will keep insisting that a media change occured; * we limit the number or retrys to avoid hanging the system */ int trycnt = 0; dir = &curproc->curdir[curproc->curdrv]; do { /* NOTE: depth == 1 is necessary; see the comments before relpath2cookie */ r = relpath2cookie(dir, path, lastname, res, 1); if (r == E_CHNG) DEBUG("path2cookie: restarting due to media change"); } while (r == E_CHNG && trycnt++ < MAX_TRYS); return r; } /* * new_fileptr, dispose_fileptr: allocate (deallocate) a file pointer */ FILEPTR * new_fileptr() { FILEPTR *f; if ((f = flist)) { flist = f->next; f->next = 0; return f; } f = kmalloc(SIZEOF(FILEPTR)); if (!f) { FATAL("new_fileptr: out of memory"); } else { f->next = 0; } return f; } void dispose_fileptr(f) FILEPTR *f; { if (f->links != 0) { FATAL("dispose_fileptr: f->links == %d", f->links); } f->next = flist; flist = f; } /* * denyshare(list, f): "list" points at the first FILEPTR in a * chained list of open FILEPTRS referring to the same file; * f is a newly opened FILEPTR. Every FILEPTR in the given list is * checked to see if its "open" mode (in list->flags) is compatible with * the open mode in f->flags. If not (for example, if f was opened with * a "read" mode and some other file has the O_DENYREAD share mode), * then 1 is returned. If all the open FILEPTRs in the list are * compatible with f, then 0 is returned. * This is not as complicated as it sounds. In practice, just keep a * list of open FILEPTRs attached to each file, and put something like * if (denyshare(thisfile->openfileptrlist, newfileptr)) * return EACCDN; * in the device open routine. */ int denyshare(list, f) FILEPTR *list, *f; { int newrm, newsm; int oldrm, oldsm; int i; newrm = f->flags & O_RWMODE; newsm = f->flags & O_SHMODE; for ( ; list; list = list->next) { oldrm = list->flags & O_RWMODE; oldsm = list->flags & O_SHMODE; if (oldsm == O_DENYW || oldsm == O_DENYRW) { if (newrm != O_RDONLY) { DEBUG("write access denied"); return 1; } } if (oldsm == O_DENYR || oldsm == O_DENYRW) { if (newrm != O_WRONLY) { DEBUG("read access denied"); return 1; } } if (newsm == O_DENYW || newsm == O_DENYRW) { if (oldrm != O_RDONLY) { DEBUG("couldn't deny writes"); return 1; } } if (newsm == O_DENYR || newsm == O_DENYRW) { if (oldrm != O_WRONLY) { DEBUG("couldn't deny reads"); return 1; } } /* If either sm == O_COMPAT, then we check to make sure that the file pointers are owned by the same process (O_COMPAT means "deny access to any other processes"). Also, once a file is opened in compatibility mode, it can't be opened in any other mode. */ if (newsm == O_COMPAT || oldsm == O_COMPAT) { if (newsm != O_COMPAT || oldsm != O_COMPAT) { DEBUG("O_COMPAT mode conflict"); return 1; } for (i = MIN_HANDLE; i < MAX_OPEN; i++) { if (curproc->handle[i] == list) goto found; } /* old file pointer is not open by this process */ DEBUG("O_COMPAT file was opened by another process"); return 1; found: ; /* everything is OK */ } } return 0; } /* * denyaccess(XATTR *xattr, unsigned perm): checks to see if the access * specified by perm (which must be some combination of S_IROTH, S_IWOTH, * and S_IXOTH) should be granted to the current process * on a file with the given extended attributes. Returns 0 if access * by the current process is OK, 1 if not. */ int denyaccess(xattr, perm) XATTR *xattr; unsigned perm; { unsigned mode; /* the super-user can do anything! */ if (curproc->euid == 0) return 0; mode = xattr->mode; if (curproc->euid == xattr->uid) perm = perm << 6; else if (curproc->egid == xattr->gid) perm = perm << 3; if ((mode & perm) != perm) return 1; /* access denied */ return 0; } /* * Checks a lock against a list of locks to see if there is a conflict. * This is a utility to be used by file systems, somewhat like denyshare * above. Returns 0 if there is no conflict, or a pointer to the * conflicting LOCK structure if there is. * * Conflicts occur for overlapping locks if the process id's are * different and if at least one of the locks is a write lock. * * NOTE: we assume before being called that the locks have been converted * so that l_start is absolute. not relative to the current position or * end of file. */ LOCK * denylock(list, lck) LOCK *list, *lck; { LOCK *t; unsigned long tstart, tend; unsigned long lstart, lend; int pid = curproc->pid; int ltype; ltype = lck->l.l_type; lstart = lck->l.l_start; if (lck->l.l_len == 0) lend = 0xffffffff; else lend = lstart + lck->l.l_len; for (t = list; t; t = t->next) { tstart = t->l.l_start; if (t->l.l_len == 0) tend = 0xffffffff; else tend = tstart + t->l.l_len; /* look for overlapping locks */ if (tstart <= lstart && tend >= lstart && t->l.l_pid != pid && (ltype == F_WRLCK || t->l.l_type == F_WRLCK)) break; if (lstart <= tstart && lend >= tstart && t->l.l_pid != pid && (ltype == F_WRLCK || t->l.l_type == F_WRLCK)) break; } return t; } /* * check to see that a file is a directory, and that write permission * is granted; return an error code, or 0 if everything is ok. */ long dir_access(dir, perm) fcookie *dir; unsigned perm; { XATTR xattr; long r; r = (*dir->fs->getxattr)(dir, &xattr); if (r) return r; if ( (xattr.mode & S_IFMT) != S_IFDIR ) { DEBUG("file is not a directory"); return EPTHNF; } if (denyaccess(&xattr, perm)) { DEBUG("no permission for directory"); return EACCDN; } return 0; } /* * returns 1 if the given name contains a wildcard character */ int has_wild(name) const char *name; { char c; while (c = *name++) { if (c == '*' || c == '?') return 1; } return 0; } /* * void copy8_3(dest, src): convert a file name (src) into DOS 8.3 format * (in dest). Note the following things: * if a field has less than the required number of characters, it is * padded with blanks * a '*' means to pad the rest of the field with '?' characters * special things to watch for: * "." and ".." are more or less left alone * "*.*" is recognized as a special pattern, for which dest is set * to just "*" * Long names are truncated. Any extensions after the first one are * ignored, i.e. foo.bar.c -> foo.bar, foo.c.bar->foo.c. */ void copy8_3(dest, src) char *dest; const char *src; { char fill = ' ', c; int i; if (src[0] == '.') { if (src[1] == 0) { strcpy(dest, ". . "); return; } if (src[1] == '.' && src[2] == 0) { strcpy(dest, ".. . "); return; } } if (src[0] == '*' && src[1] == '.' && src[2] == '*' && src[3] == 0) { dest[0] = '*'; dest[1] = 0; return; } for (i = 0; i < 8; i++) { c = *src++; if (!c || c == '.') break; if (c == '*') { fill = c = '?'; } *dest++ = toupper(c); } while (i++ < 8) { *dest++ = fill; } *dest++ = '.'; i = 0; fill = ' '; while (c && c != '.') c = *src++; if (c) { for( ;i < 3; i++) { c = *src++; if (!c || c == '.') break; if (c == '*') c = fill = '?'; *dest++ = toupper(c); } } while (i++ < 3) *dest++ = fill; *dest++ = 0; } /* * int pat_match(name, patrn): returns 1 if "name" matches the template in * "patrn", 0 if not. "patrn" is assumed to have been expanded in 8.3 * format by copy8_3; "name" need not be. Any '?' characters in patrn * will match any character in name. Note that if "patrn" has a '*' as * the first character, it will always match; this will happen only if * the original pattern (before copy8_3 was applied) was "*.*". * * BUGS: acts a lot like the silly TOS pattern matcher. */ int pat_match(name, template) const char *name, *template; { register char *s, c; char expname[TOS_NAMELEN+1]; if (*template == '*') return 1; copy8_3(expname, name); s = expname; while (c = *template++) { if (c != *s && c != '?') return 0; s++; } return 1; } /* * int samefile(fcookie *a, fcookie *b): returns 1 if the two cookies * refer to the same file or directory, 0 otherwise */ int samefile(a, b) fcookie *a, *b; { if (a->fs == b->fs && a->dev == b->dev && a->index == b->index) return 1; return 0; }