/* Copyright 1990,1991,1992 Eric R. Smith. Copyright 1992,1993,1994 Atari Corp. All rights reserved. */ /* * various file system interface things */ #include "mint.h" #define PATH2COOKIE_DB(x) TRACE(x) FILESYS *active_fs; FILESYS *drives[NUM_DRIVES]; extern FILESYS tos_filesys; /* declaration needed for debugging only */ /* "aliased" drives are different names * for real drives/directories * if drive d is an alias for c:\usr, * then alias_drv[3] == 2 (the real * drive) and aliases has bit (1L << 3) * set. * NOTE: if aliasdrv[d] is 0, then d is not an aliased drive, * otherwise d is aliased to drive aliasdrv[d]-1 * (e.g. if drive A: is aliased to B:\FOO, then * aliasdrv[0] == 'B'-'A'+1 == 2). Always remember to * compensate for the extra 1 when dereferencing aliasdrv! */ int aliasdrv[NUM_DRIVES]; FILEPTR *flist; /* a list of free file pointers */ /* 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; release_cookie(&root_dir); break; } } } /* * initialize the file system */ #define NUMFPS 40 /* initial number of file pointers */ void init_filesys() { static FILEPTR initial[NUMFPS+1]; 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(); } /* * 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. */ typedef FILESYS * ARGS_ON_STACK (*FSFUNC) P_((struct kerinfo *)); /* uk: made this lie outside of functions, as load_filesys() and * load_devdriver() need access to it. */ #define NPATHS 3 static const char *const ext_paths[NPATHS] = {"", "\\MINT", "\\MULTITOS"}; void load_filesys() { long r; BASEPAGE *b; FILESYS *fs; FSFUNC initf; static DTABUF dta; int i; extern struct kerinfo kernelinfo; /* in main.c */ char curpath[PATH_MAX]; MEMREGION *xfsreg; curproc->dta = &dta; d_getpath(curpath,0); for (i = 0; i < NPATHS; i++) { if (*ext_paths[i]) { /* don't bother checking the current directory twice! */ if (!stricmp(ext_paths[i],curpath)) r = -1; else r = d_setpath(ext_paths[i]); } else r = 0; if (r == 0) 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)); r = f_snext(); 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; TRACE(("initializing %s", dta.dta_name)); fs = (*initf)(&kernelinfo); if (fs) { TRACE(("%s loaded OK", dta.dta_name)); /* put the loaded XFS into super accesible memory */ xfsreg = addr2region( (long) b ); mark_region(xfsreg, PROT_S); /* link it into the list of drivers */ /* uk: but only if it has not installed itself via Dcntl() * after checking if file system is already installed, * so we know for sure that each file system in at most * once in the chain (important for removal!) * also note: this doesn't preclude loading two different * instances of the same file system driver, e.g. it's perfectly * OK to have a "cdromy1.xfs" and "cdromz2.xfs"; the check below * just makes sure that a given instance of a file system is * installed at most once. I.e., it prevents cdromy1.xfs from being * installed twice. */ if ((FILESYS*)1L != fs) { FILESYS *f = active_fs; for (; f; f = f->next) if (f == fs) break; if (!f) { /* we ran completly through the list */ fs->next = active_fs; active_fs = fs; } } } else { DEBUG(("%s returned null", dta.dta_name)); m_free((virtaddr)b); } r = f_snext(); } } #if 0 /* 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 */ } #endif } /* * uk: load device driver in files called *.xdd (external device driver) * from disk * maybe this should go into biosfs.c ?? * * this routine is called after process 0 is set up, but before any user * processes are run, but before the loadable file systems come in, * so they can make use of external device drivers * * 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. * ??? what d_lock() calls ??? */ typedef DEVDRV * ARGS_ON_STACK (*DEVFUNC) P_((struct kerinfo *)); #define DEV_SELFINST ((DEVDRV*)1L) /* dev driver did dcntl() already */ void load_devdriver() { long r; BASEPAGE *b; DEVDRV *dev; DEVFUNC initf; struct dev_descr the_dev; static DTABUF dta; int i; extern struct kerinfo kernelinfo; /* in main.c */ char curpath[PATH_MAX]; char dev_name[PATH_MAX]; /* a bit long, but one never knows... */ char ch, *p; MEMREGION *xddreg; curproc->dta = &dta; d_getpath(curpath,0); for (i = 0; i < NPATHS; i++) { if (*ext_paths[i]) { /* don't bother checking the current directory twice! */ if (!stricmp(ext_paths[i],curpath)) r = -1; else r = d_setpath(ext_paths[i]); } else r = 0; if (r == 0) r = f_sfirst("*.xdd", 0); while (r == 0) { b = (BASEPAGE *)p_exec(3, dta.dta_name, (char *)"", (char *)0); if ( ((long)b) < 0 ) { DEBUG(("Error loading device driver %s", dta.dta_name)); r = f_snext(); 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 = (DEVFUNC)b->p_tbase; TRACE(("initializing %s", dta.dta_name)); dev = (*initf)(&kernelinfo); if (dev) { if (DEV_SELFINST != dev) { /* we need to install the device driver ourselves */ the_dev.driver = dev; the_dev.dinfo = 0; the_dev.flags = 0; the_dev.tty = (struct tty*)0L; the_dev.reserved[0] = the_dev.reserved[1] = 0; the_dev.reserved[2] = 0; p = dta.dta_name; /* copy the dev. driver name, converting to lower case */ while (*p && *p != '.') { *p = tolower(*p); p++; } ch = *p; *p = '\0'; /* we dont want the extension */ strcpy(dev_name, "u:\\dev\\"); strcat(dev_name, dta.dta_name); *p = ch; r = d_cntl(DEV_INSTALL, dev_name, (long)&the_dev); if (r <= 0) { DEBUG(("Error installing device driver %s", dta.dta_name)); r = f_snext(); continue; } } TRACE(("%s loaded OK", dta.dta_name)); /* put the loaded XDD into super accesible memory */ xddreg = addr2region( (long) b ); mark_region(xddreg, PROT_S); } else { DEBUG(("%s returned null", dta.dta_name)); m_free((virtaddr)b); } r = f_snext(); } } } 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 ARGS_ON_STACK changedrv(d) unsigned d; { PROC *p; int i; FILEPTR *f; FILESYS *fs; SHTEXT *stext, **old; extern SHTEXT *text_reg; /* in mem.c */ DIR *dirh; fcookie dir; int warned = (d & 0xf000) == PROC_RDEV_BASE; long r; /* if an aliased drive, change the *real* device */ if (d < NUM_DRIVES && aliasdrv[d]) { d = aliasdrv[d] - 1; /* see NOTE above */ } /* 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 != (FILEPTR *)1) && (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); /* we could just zero the handle, but this could lead to confusion if * a process doesn't realize that there's been a media change, Fopens * a new file, and gets the same handle back. So, we force the * handle to point to /dev/null. */ p->handle[i] = do_open("U:\\DEV\\NULL", O_RDWR, 0, (XATTR *)0); } } /* terminate any active directory searches on the drive */ for (i = 0; i < NUM_SEARCH; i++) { dirh = &p->srchdir[i]; if (p->srchdta[i] && dirh->fc.fs && dirh->fc.dev == d) { TRACE(("closing search for process %d", p->pid)); release_cookie(&dirh->fc); dirh->fc.fs = 0; p->srchdta[i] = 0; } } for (dirh = p->searches; dirh; dirh = dirh->next) { /* If this search is on the changed drive, release the cookie, but do *not* free it, since the user could later call closedir on it. */ if (dirh->fc.fs && dirh->fc.dev == d) { release_cookie (&dirh->fc); dirh->fc.fs = 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) { release_cookie(&p->root[i]); dup_cookie(&p->root[i], &dir); } if (p->curdir[i].dev == d) { release_cookie(&p->curdir[i]); dup_cookie(&p->curdir[i], &dir); } } release_cookie(&dir); } /* free any file descriptors associated with shared text regions */ for (old = &text_reg; 0 != (stext = *old);) { f = stext->f; if (f->fc.dev == d) { f->dev = NULL; do_pclose(rootproc, f); stext->f = 0; /* free region if unattached */ if (stext->text->links == 0xffff) { stext->text->links = 0; stext->text->mflags &= ~(M_SHTEXT|M_SHTEXT_T); free_region(stext->text); *old = stext->next; kfree(stext); continue; } /* else clear `sticky bit' */ stext->text->mflags &= ~M_SHTEXT_T; } old = &stext->next; } } /* * 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, negative number for error */ 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; /* watch out for aliased drives */ if (aliasdrv[d]) { d = aliasdrv[d] - 1; if (d < 0 || d >= NUM_DRIVES) return 0; } /* has the drive been initialized yet? If not, then initialize it and return * "no change" */ fs = drives[d]; if (!fs) { 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! */ TRACE(("calling mediach(%d)",d)); r = (int)mediach(d); TRACE(("mediach(%d) == %d", d, r)); if (r < 0) { /* KLUDGE: some .XFS drivers don't install BIOS vectors, and so we'll * always get EUNDEV back from them. This isn't recommended (since there * are other programs than MiNT that may ask for BIOS functions from * any installed drives). This is a temporary work-around until those * .XFSes are changed to either install BIOS vectors or to use the * new U: Dcntl() calls to install themselves. * Note that EUNDEV must be tested for drives A-C, or else booting may * not work properly. */ if (d > 2 && r == EUNDEV) return 0; /* assume no change */ else return r; } 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 */ TRACE(("rwabs returned %d", r)); return (r < 0) ? r : 0; } r = 2; /* drive was definitely changed */ } if (r == 2) { TRACE(("definite media change")); fs = drives[d]; /* get filesystem associated with drive */ if ((*fs->dskchng)(d)) { /* does the fs agree that it changed? */ drives[d] = 0; changedrv(d); /* yes -- do the change */ return 1; } } return 0; } /* * routines for parsing path 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. * */ #define MAX_LINKS 4 long relpath2cookie(relto, path, lastname, res, depth) fcookie *relto; const char *path; char *lastname; fcookie *res; int depth; { 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; /* 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; if (!lastname) { dolast = 1; lastname = temp2; } else if (lastname == follow_links) { dolast = 2; lastname = temp2; } *lastname = 0; PATH2COOKIE_DB(("relpath2cookie(%s, dolast=%d, depth=%d)", path, dolast, depth)); if (depth > MAX_LINKS) { DEBUG(("Too many symbolic links")); return ELOOP; } /* 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] == ':') { 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++; dup_cookie(&dir, &curproc->root[drv]); } else { if (i) { /* an explicit drive letter was given */ dup_cookie(&dir, &curproc->curdir[drv]); } else dup_cookie(&dir, relto); } if (!dir.fs) { changedrv(dir.dev); dup_cookie(&dir, &curproc->root[drv]); } if (!dir.fs) { DEBUG(("path2cookie: no file system: returning EDRIVE")); return EDRIVE; } /* here's where we come when we've gone across a mount point */ restart_mount: if (!*path) { /* nothing more to do */ PATH2COOKIE_DB(("relpath2cookie: no more path, returning 0")); *res = dir; return 0; } /* 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 ((r = disk_changed(dir.dev)) != 0) { release_cookie(&dir); if (r > 0) r = E_CHNG; PATH2COOKIE_DB(("relpath2cookie: returning %d", r)); return r; } if (dir.fs->fsflags & FS_KNOPARSE) { if (!dolast) { PATH2COOKIE_DB(("fs is a KNOPARSE, nothing to do")); strncpy(lastname, path, PATH_MAX-1); lastname[PATH_MAX - 1] = 0; r = 0; *res = dir; } else { PATH2COOKIE_DB(("fs is a KNOPARSE, calling lookup")); r = (*dir.fs->lookup)(&dir, path, res); 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) { release_cookie(&dir); release_cookie(&mounteddir); dup_cookie(&dir, &curproc->root[UNIDRV]); TRACE(("path2cookie: restarting from mount point")); goto restart_mount; } } else { if (r == 0) release_cookie(&mounteddir); r = 0; } } release_cookie(&dir); } PATH2COOKIE_DB(("relpath2cookie: returning %ld", r)); return r; } /* parse all but (possibly) the last component of the path name */ /* rules here: at the top of the loop, &dir is the cookie of * the directory we're in now, xattr is its attributes, and res is unset * at the end of the loop, &dir is unset, and either r is nonzero * (to indicate an error) or res is set to the final result */ r = (dir.fs->getxattr)(&dir, &xattr); if (r) { DEBUG(("couldn't get directory attributes")); release_cookie(&dir); return EINTRN; } while (*path) { /* now we must have a directory, since there are more things in the path */ if ((xattr.mode & S_IFMT) != S_IFDIR) { PATH2COOKIE_DB(("relpath2cookie: not a directory, returning EPTHNF")); release_cookie(&dir); r = EPTHNF; break; } /* we must also have search permission for the directory */ if (denyaccess(&xattr, S_IXOTH)) { DEBUG(("search permission in directory denied")); release_cookie(&dir); r = EPTHNF; break; } /* if there's nothing left in the path, we can break here */ if (!*path) { PATH2COOKIE_DB(("relpath2cookie: no more path, breaking (1)")); *res = dir; break; } /* 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; /* if there are no more names in the path, and we don't want * to actually look up the last name, then we're done */ if (dolast == 0 && !*path) { *res = dir; PATH2COOKIE_DB(("relpath2cookie: no more path, breaking (2)")); break; } /* * skip trailing slashes */ while (DIRSEP(*path)) path++; PATH2COOKIE_DB(("relpath2cookie: looking up [%s]", lastname)); r = (*dir.fs->lookup)(&dir, lastname, res); if (r == EMOUNT) { fcookie mounteddir; r = (*dir.fs->root)(dir.dev, &mounteddir); if (r == 0 && drv == UNIDRV) { if (samefile(&dir, &mounteddir)) { release_cookie(&dir); release_cookie(&mounteddir); dup_cookie(&dir, &curproc->root[UNIDRV]); TRACE(("path2cookie: restarting from mount point")); goto restart_mount; } else if (r == 0) { r = EINTRN; release_cookie(&mounteddir); release_cookie(&dir); break; } } else if (r == 0) { release_cookie(&mounteddir); } else { release_cookie(&dir); break; } } else if (r) { if (r == EFILNF && *path) { /* the "file" we didn't find was treated as a directory */ r = EPTHNF; } release_cookie(&dir); break; } /* check for a symbolic link */ r = (res->fs->getxattr)(res, &xattr); if (r != 0) { DEBUG(("path2cookie: couldn't get file attributes")); release_cookie(&dir); release_cookie(res); break; } /* if the file is a link, and we're following links, follow it */ if ( (xattr.mode & S_IFMT) == S_IFLNK && (*path || dolast > 1)) { r = (res->fs->readlink)(res, linkstuff, PATH_MAX); release_cookie(res); if (r) { DEBUG(("error reading symbolic link")); release_cookie(&dir); break; } r = relpath2cookie(&dir, linkstuff, follow_links, res, depth+1); release_cookie(&dir); if (r) { DEBUG(("error following symbolic link")); break; } dir = *res; (void)(res->fs->getxattr)(res, &xattr); } else { release_cookie(&dir); dir = *res; } } PATH2COOKIE_DB(("relpath2cookie: returning %ld", r)); 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 of retrys to avoid hanging the system */ int trycnt = 0; dir = &curproc->curdir[curproc->curdrv]; do { r = relpath2cookie(dir, path, lastname, res, 0); if (r == E_CHNG) DEBUG(("path2cookie: restarting due to media change")); } while (r == E_CHNG && trycnt++ < MAX_TRYS); return r; } /* * release_cookie: tell the file system owner that a cookie is no * longer in use by the kernel */ void release_cookie(fc) fcookie *fc; { FILESYS *fs; if (fc) { fs = fc->fs; if (fs && fs->release) { (void)(*fs->release)(fc); } } } /* * Make a new cookie (newc) which is a duplicate of the old cookie * (oldc). This may be something the file system is interested in, * so we give it a chance to do the duplication; if it doesn't * want to, we just copy. */ void dup_cookie(newc, oldc) fcookie *newc, *oldc; { FILESYS *fs = oldc->fs; if (fs && fs->release && fs->dupcookie) { (void)(*fs->dupcookie)(newc, oldc); } else { *newc = *oldc; } } /* * new_fileptr, dispose_fileptr: allocate (deallocate) a file pointer */ FILEPTR * new_fileptr() { FILEPTR *f; if ((f = flist) != 0) { 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 ARGS_ON_STACK denyshare(list, f) FILEPTR *list, *f; { int newrm, newsm; /* new read and sharing mode */ int oldrm, oldsm; /* read and sharing mode of already opened file */ extern MEMREGION *tofreed; MEMREGION *m = tofreed; int i; newrm = f->flags & O_RWMODE; newsm = f->flags & O_SHMODE; /* * O_EXEC gets treated the same as O_RDONLY for our purposes */ if (newrm == O_EXEC) newrm = O_RDONLY; /* New meaning for O_COMPAT: deny write access to all _other_ * processes. */ for ( ; list; list = list->next) { oldrm = list->flags & O_RWMODE; if (oldrm == O_EXEC) oldrm = O_RDONLY; oldsm = list->flags & O_SHMODE; if (oldsm == O_DENYW || oldsm == O_DENYRW) { if (newrm != O_RDONLY) { /* conflict because of unattached shared text region? */ if (!m && NULL != (m = find_text_seg(list))) { if (m->links == 0xffff) continue; m = 0; } 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 writes to any other processes"). This isn't quite the same as the Atari spec, which says O_COMPAT means "deny access to other processes." We should fix the spec. */ if ((newsm == O_COMPAT && newrm != O_RDONLY && oldrm != O_RDONLY) || (oldsm == O_COMPAT && newrm != O_RDONLY)) { 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 for writing by another process")); return 1; found: ; /* everything is OK */ } } /* cannot close shared text regions file here... have open do it. */ if (m) tofreed = m; 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 ngroupmatch(group) int group; { int i; for (i=0; ingroups; i++) if (curproc->ngroup[i] == group) return 1; return 0; } 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; else if (ngroupmatch(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 * ARGS_ON_STACK 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 = 0xffffffffL; else lend = lstart + lck->l.l_len - 1; for (t = list; t; t = t->next) { tstart = t->l.l_start; if (t->l.l_len == 0) tend = 0xffffffffL; else tend = tstart + t->l.l_len - 1; /* 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) { DEBUG(("dir_access: file system returned %ld", 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++) != 0) { 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++) != 0) { 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; }