/* Copyright 1991,1992 Eric R. Smith. Copyright 1992,1993,1994 Atari Corporation. All rights reserved. */ /* a simple unified file system */ #include "mint.h" extern FILESYS bios_filesys, proc_filesys, pipe_filesys, shm_filesys; static long ARGS_ON_STACK uni_root P_((int drv, fcookie *fc)); static long ARGS_ON_STACK uni_lookup P_((fcookie *dir, const char *name, fcookie *fc)); static long ARGS_ON_STACK uni_getxattr P_((fcookie *fc, XATTR *xattr)); static long ARGS_ON_STACK uni_chattr P_((fcookie *fc, int attrib)); static long ARGS_ON_STACK uni_chown P_((fcookie *fc, int uid, int gid)); static long ARGS_ON_STACK uni_chmode P_((fcookie *fc, unsigned mode)); static long ARGS_ON_STACK uni_rmdir P_((fcookie *dir, const char *name)); static long ARGS_ON_STACK uni_remove P_((fcookie *dir, const char *name)); static long ARGS_ON_STACK uni_getname P_((fcookie *root, fcookie *dir, char *pathname, int size)); static long ARGS_ON_STACK uni_rename P_((fcookie *olddir, char *oldname, fcookie *newdir, const char *newname)); static long ARGS_ON_STACK uni_opendir P_((DIR *dirh, int flags)); static long ARGS_ON_STACK uni_readdir P_((DIR *dirh, char *nm, int nmlen, fcookie *)); static long ARGS_ON_STACK uni_rewinddir P_((DIR *dirh)); static long ARGS_ON_STACK uni_closedir P_((DIR *dirh)); static long ARGS_ON_STACK uni_pathconf P_((fcookie *dir, int which)); static long ARGS_ON_STACK uni_dfree P_((fcookie *dir, long *buf)); static DEVDRV * ARGS_ON_STACK uni_getdev P_((fcookie *fc, long *devsp)); static long ARGS_ON_STACK uni_symlink P_((fcookie *dir, const char *name, const char *to)); static long ARGS_ON_STACK uni_readlink P_((fcookie *fc, char *buf, int buflen)); static long ARGS_ON_STACK uni_fscntl P_((fcookie *dir, const char *name, int cmd, long arg)); FILESYS uni_filesys = { (FILESYS *)0, FS_LONGPATH, uni_root, uni_lookup, nocreat, uni_getdev, uni_getxattr, uni_chattr, uni_chown, uni_chmode, nomkdir, uni_rmdir, uni_remove, uni_getname, uni_rename, uni_opendir, uni_readdir, uni_rewinddir, uni_closedir, uni_pathconf, uni_dfree, nowritelabel, noreadlabel, uni_symlink, uni_readlink, nohardlink, uni_fscntl, nodskchng }; /* * structure that holds files * if (mode & S_IFMT == S_IFDIR), then this is an alias for a drive: * "dev" holds the appropriate BIOS device number, and * "data" is meaningless * if (mode & S_IFMT == S_IFLNK), then this is a symbolic link: * "dev" holds the user id of the owner, and * "data" points to the actual link data */ typedef struct unifile { char name[NAME_MAX+1]; ushort mode; ushort dev; FILESYS *fs; void *data; struct unifile *next; short cdate, ctime; } UNIFILE; static UNIFILE u_drvs[UNI_NUM_DRVS]; static UNIFILE *u_root = 0; static long do_ulookup P_((fcookie *, const char *, fcookie *, UNIFILE **)); FILESYS * get_filesys (dev) int dev; { UNIFILE *u; for (u = u_root; u; u = u->next) if (u->dev == dev) return u->fs; return (FILESYS *) 0L; } void unifs_init() { UNIFILE *u = u_drvs; int i; u_root = u; for (i = 0; i < UNI_NUM_DRVS; i++,u++) { u->next = u+1; u->mode = S_IFDIR|DEFAULT_DIRMODE; u->dev = i; u->cdate = datestamp; u->ctime = timestamp; if (i == PROCDRV) { strcpy(u->name, "proc"); u->fs = &proc_filesys; } else if (i == PIPEDRV) { strcpy(u->name, "pipe"); u->fs = &pipe_filesys; } else if (i == BIOSDRV) { strcpy(u->name, "dev"); u->fs = &bios_filesys; } else if (i == UNIDRV) { (u-1)->next = u->next; /* skip this drive */ } else if (i == SHMDRV) { strcpy(u->name, "shm"); u->fs = &shm_filesys; } else { u->name[0] = i + 'a'; u->name[1] = 0; u->fs = 0; } } --u; /* oops, we went too far */ u->next = 0; } static long ARGS_ON_STACK uni_root(drv, fc) int drv; fcookie *fc; { if (drv == UNIDRV) { fc->fs = &uni_filesys; fc->dev = drv; fc->index = 0L; return 0; } fc->fs = 0; return EINTRN; } static long ARGS_ON_STACK uni_lookup(dir, name, fc) fcookie *dir; const char *name; fcookie *fc; { return do_ulookup(dir, name, fc, (UNIFILE **)0); } /* worker function for uni_lookup; can also return the UNIFILE * pointer for the root directory */ static long do_ulookup(dir, name, fc, up) fcookie *dir; const char *name; fcookie *fc; UNIFILE **up; { UNIFILE *u; long drvs; FILESYS *fs; fcookie *tmp; extern long dosdrvs; TRACE(("uni_lookup(%s)", name)); if (dir->index != 0) { DEBUG(("uni_lookup: bad directory")); return EPTHNF; } /* special case: an empty name in a directory means that directory */ /* so do "." and ".." */ if (!*name || !strcmp(name, ".") || !strcmp(name, "..")) { dup_cookie(fc, dir); return 0; } drvs = drvmap() | dosdrvs | PSEUDODRVS; /* * OK, check the list of aliases and special directories */ for (u = u_root; u; u = u->next) { if (!strnicmp(name, u->name, NAME_MAX)) { if ( (u->mode & S_IFMT) == S_IFDIR ) { if (u->dev >= NUM_DRIVES) { fs = u->fs; if (up) *up = u; return (*fs->root)(u->dev,fc); } if ((drvs & (1L << u->dev)) == 0) return EPTHNF; tmp = &curproc->root[u->dev]; if (!tmp->fs) { /* drive changed? */ changedrv(tmp->dev); tmp = &curproc->root[u->dev]; if (!tmp->fs) return EPTHNF; } dup_cookie(fc, tmp); } else { /* a symbolic link */ fc->fs = &uni_filesys; fc->dev = UNIDRV; fc->index = (long)u; } if (up) *up = u; return 0; } } DEBUG(("uni_lookup: name (%s) not found", name)); return EFILNF; } static long ARGS_ON_STACK uni_getxattr(fc, xattr) fcookie *fc; XATTR *xattr; { UNIFILE *u = (UNIFILE *)fc->index; if (fc->fs != &uni_filesys) { ALERT("ERROR: wrong file system getxattr called"); return EINTRN; } xattr->index = fc->index; xattr->dev = xattr->rdev = fc->dev; xattr->nlink = 1; xattr->blksize = 1; /* If "u" is null, then we have the root directory, otherwise * we use the UNIFILE structure to get the info about it */ if (!u || ( (u->mode & S_IFMT) == S_IFDIR )) { xattr->uid = xattr->gid = 0; xattr->size = xattr->nblocks = 0; xattr->mode = S_IFDIR | DEFAULT_DIRMODE; xattr->attr = FA_DIR; } else { xattr->uid = u->dev; xattr->gid = 0; xattr->size = xattr->nblocks = strlen(u->data) + 1; xattr->mode = u->mode; xattr->attr = 0; } xattr->mtime = xattr->atime = xattr->ctime = u->ctime; xattr->mdate = xattr->adate = xattr->cdate = u->cdate; return 0; } static long ARGS_ON_STACK uni_chattr(dir, attrib) fcookie *dir; int attrib; { UNUSED(dir); UNUSED(attrib); return EACCDN; } static long ARGS_ON_STACK uni_chown(dir, uid, gid) fcookie *dir; int uid, gid; { UNUSED(dir); UNUSED(uid); UNUSED(gid); return EINVFN; } static long ARGS_ON_STACK uni_chmode(dir, mode) fcookie *dir; unsigned mode; { UNUSED(dir); UNUSED(mode); return EINVFN; } static long ARGS_ON_STACK uni_rmdir(dir, name) fcookie *dir; const char *name; { long r; r = uni_remove(dir, name); if (r == EFILNF) r = EPTHNF; return r; } static long ARGS_ON_STACK uni_remove(dir, name) fcookie *dir; const char *name; { UNIFILE *u, *lastu; UNUSED(dir); lastu = 0; u = u_root; while (u) { if (!strnicmp(u->name, name, NAME_MAX)) { if ( (u->mode & S_IFMT) != S_IFLNK ) return EFILNF; if (curproc->euid && (u->dev != curproc->euid)) return EACCDN; kfree(u->data); if (lastu) lastu->next = u->next; else u_root = u->next; kfree(u); return 0; } lastu = u; u = u->next; } return EFILNF; } static long ARGS_ON_STACK uni_getname(root, dir, pathname, size) fcookie *root, *dir; char *pathname; int size; { FILESYS *fs; UNIFILE *u; char *n; fcookie relto; char tmppath[PATH_MAX]; long r; UNUSED(root); if (size <= 0) return ERANGE; fs = dir->fs; if (dir->dev == UNIDRV) { *pathname = 0; return 0; } for (u = u_root; u; u = u->next) { if (dir->dev == u->dev && (u->mode & S_IFMT) == S_IFDIR) { *pathname++ = '\\'; if (--size <= 0) return ERANGE; for (n = u->name; *n; ) { *pathname++ = *n++; if (--size <= 0) return ERANGE; } break; } } if (!u) { ALERT("unifs: couldn't match a drive with a directory"); return EPTHNF; } if (dir->dev >= NUM_DRIVES) { if ((*fs->root)(dir->dev, &relto) == 0) { if (!(fs->fsflags & FS_LONGPATH)) { r = (*fs->getname)(&relto, dir, tmppath, PATH_MAX); release_cookie(&relto); if (r) { return r; } if (strlen(tmppath) < size) { strcpy(pathname, tmppath); return 0; } else { return ERANGE; } } r = (*fs->getname)(&relto, dir, pathname, size); release_cookie(&relto); return r; } else { *pathname = 0; return EINTRN; } } if (curproc->root[dir->dev].fs != fs) { ALERT("unifs: drive's file system doesn't match directory's"); return EINTRN; } if (!fs) { *pathname = 0; return 0; } if (!(fs->fsflags & FS_LONGPATH)) { r = (*fs->getname)(&curproc->root[dir->dev], dir, tmppath, PATH_MAX); if (r) return r; if (strlen(tmppath) < size) { strcpy(pathname, tmppath); return 0; } else { return ERANGE; } } return (*fs->getname)(&curproc->root[dir->dev], dir, pathname, size); } static long ARGS_ON_STACK uni_rename(olddir, oldname, newdir, newname) fcookie *olddir; char *oldname; fcookie *newdir; const char *newname; { UNIFILE *u; fcookie fc; long r; UNUSED(olddir); for (u = u_root; u; u = u->next) { if (!strnicmp(u->name, oldname, NAME_MAX)) break; } if (!u) { DEBUG(("uni_rename: old file not found")); return EFILNF; } /* the new name is not allowed to exist! */ r = uni_lookup(newdir, newname, &fc); if (r == 0) release_cookie(&fc); if (r != EFILNF) { DEBUG(("uni_rename: error %ld", r)); return (r == 0) ? EACCDN : r; } (void)strncpy(u->name, newname, NAME_MAX); return 0; } static long ARGS_ON_STACK uni_opendir(dirh, flags) DIR *dirh; int flags; { UNUSED(flags); if (dirh->fc.index != 0) { DEBUG(("uni_opendir: bad directory")); return EPTHNF; } dirh->index = 0; return 0; } static long ARGS_ON_STACK uni_readdir(dirh, name, namelen, fc) DIR *dirh; char *name; int namelen; fcookie *fc; { long map; char *dirname; int i; int giveindex = (dirh->flags == 0); UNIFILE *u; long index; extern long dosdrvs; long r; map = dosdrvs | drvmap() | PSEUDODRVS; i = dirh->index++; u = u_root; while (i > 0) { --i; u = u->next; if (!u) break; } tryagain: if (!u) return ENMFIL; dirname = u->name; index = (long)u; if ( (u->mode & S_IFMT) == S_IFDIR ) { /* make sure the drive really exists */ if ( u->dev >= NUM_DRIVES) { r = (*u->fs->root)(u->dev,fc); if (r) { fc->fs = &uni_filesys; fc->index = 0; fc->dev = u->dev; } } else { if ((map & (1L << u->dev)) == 0 ) { dirh->index++; u = u->next; goto tryagain; } dup_cookie(fc, &curproc->root[u->dev]); if (!fc->fs) { /* drive not yet initialized */ /* use default attributes */ fc->fs = &uni_filesys; fc->index = 0; fc->dev = u->dev; } } } else { /* a symbolic link */ fc->fs = &uni_filesys; fc->dev = UNIDRV; fc->index = (long)u; } if (giveindex) { namelen -= (int)sizeof(long); if (namelen <= 0) { release_cookie(fc); return ERANGE; } *((long *)name) = index; name += sizeof(long); } strncpy(name, dirname, namelen-1); if (strlen(name) < strlen(dirname)) { release_cookie(fc); return ENAMETOOLONG; } return 0; } static long ARGS_ON_STACK uni_rewinddir(dirh) DIR *dirh; { dirh->index = 0; return 0; } static long ARGS_ON_STACK uni_closedir(dirh) DIR *dirh; { UNUSED(dirh); return 0; } static long ARGS_ON_STACK uni_pathconf(dir, which) fcookie *dir; int which; { UNUSED(dir); switch(which) { case -1: return DP_MAXREQ; case DP_IOPEN: return 0; /* no files to open */ case DP_MAXLINKS: return 1; /* no hard links available */ case DP_PATHMAX: return PATH_MAX; case DP_NAMEMAX: return NAME_MAX; case DP_ATOMIC: return 1; /* no atomic writes */ case DP_TRUNC: return DP_AUTOTRUNC; case DP_CASE: return DP_CASEINSENS; case DP_MODEATTR: return DP_FT_DIR|DP_FT_LNK; case DP_XATTRFIELDS: return DP_INDEX|DP_DEV|DP_NLINK|DP_SIZE; default: return EINVFN; } } static long ARGS_ON_STACK uni_dfree(dir, buf) fcookie *dir; long *buf; { UNUSED(dir); buf[0] = 0; /* number of free clusters */ buf[1] = 0; /* total number of clusters */ buf[2] = 1; /* sector size (bytes) */ buf[3] = 1; /* cluster size (sectors) */ return 0; } static DEVDRV * ARGS_ON_STACK uni_getdev(fc, devsp) fcookie *fc; long *devsp; { UNUSED(fc); *devsp = EACCDN; return 0; } static long ARGS_ON_STACK uni_symlink(dir, name, to) fcookie *dir; const char *name; const char *to; { UNIFILE *u; fcookie fc; long r; r = uni_lookup(dir, name, &fc); if (r == 0) { release_cookie(&fc); return EACCDN; /* file already exists */ } if (r != EFILNF) return r; /* some other error */ if (curproc->egid) return EACCDN; /* only members of admin group may do that */ u = kmalloc(SIZEOF(UNIFILE)); if (!u) return EACCDN; strncpy(u->name, name, NAME_MAX); u->name[NAME_MAX] = 0; u->data = kmalloc((long)strlen(to)+1); if (!u->data) { kfree(u); return EACCDN; } strcpy(u->data, to); u->mode = S_IFLNK | DEFAULT_DIRMODE; u->dev = curproc->euid; u->next = u_root; u->fs = &uni_filesys; u->cdate = datestamp; u->ctime = timestamp; u_root = u; return 0; } static long ARGS_ON_STACK uni_readlink(fc, buf, buflen) fcookie *fc; char *buf; int buflen; { UNIFILE *u; u = (UNIFILE *)fc->index; assert(u); assert((u->mode & S_IFMT) == S_IFLNK); assert(u->data); strncpy(buf, u->data, buflen); if (strlen(u->data) >= buflen) return ENAMETOOLONG; return 0; } /* uk: use these Dcntl's to install a new filesystem which is only visible * on drive u: * * FS_INSTALL: let the kernel know about the file system; it does NOT * get a device number. * FS_MOUNT: use Dcntl(FS_MOUNT, "u:\\foo", &descr) to make a directory * foo where the filesytem resides in; the file system now * gets its device number which is also written into the * dev_no field of the fs_descr structure. * FS_UNMOUNT: remove a file system's directory; this call closes all * open files, directory searches and directories on this * device. Make sure that the FS will not recognise any * accesses to this device, as fs->root will be called * during the reinitalisation! * FS_UNINSTALL: remove a file system completely from the kernel list, * but that will only be possible if there is no directory * associated with this file system. * This function allows it to write file systems as demons * which stay in memory only as long as needed. * * BUG: it is not possible yet to lock such a filesystem. */ /* here we start with gemdos only file system device numbers */ static curr_dev_no = 0x100; static long ARGS_ON_STACK uni_fscntl(dir, name, cmd, arg) fcookie *dir; const char *name; int cmd; long arg; { fcookie fc; long r; extern struct kerinfo kernelinfo; extern FILESYS *active_fs; if (cmd == (int)FS_INSTALL) { /* install a new filesystem */ struct fs_descr *d = (struct fs_descr*)arg; FILESYS *fs; /* check if FS is installed already */ for (fs = active_fs; fs; fs = fs->next) if (d->file_system == fs) return 0L; /* include new file system into chain of file systems */ d->file_system->next = active_fs; active_fs = d->file_system; return (long)&kernelinfo; /* return pointer to kernel info as OK */ } else if (cmd == (int)FS_MOUNT) { /* install a new gemdos-only device for this FS */ struct fs_descr *d = (struct fs_descr*)arg; FILESYS *fs; UNIFILE *u; /* first check for existing names */ r = uni_lookup(dir, name, &fc); if (r == 0) { release_cookie(&fc); return EACCDN; /* name exists already */ } if (r != EFILNF) return r; /* some other error */ if (!d) return EACCDN; if (!d->file_system) return EACCDN; /* check if FS is installed */ for (fs = active_fs; fs; fs = fs->next) if (d->file_system == fs) break; if (!fs) return EACCDN; /* not installed, so return an error */ u = kmalloc(SIZEOF(UNIFILE)); if (!u) return EACCDN; strncpy(u->name, name, NAME_MAX); u->name[NAME_MAX] = 0; u->mode = S_IFDIR|DEFAULT_DIRMODE; u->data = 0; u->fs = d->file_system; /* now get the file system its own device number */ u->dev = d->dev_no = curr_dev_no++; /* chain new entry into unifile list */ u->next = u_root; u_root = u; return (long)u->dev; } else if (cmd == (int)FS_UNMOUNT) { /* remove a file system's directory */ struct fs_descr *d = (struct fs_descr*)arg; FILESYS *fs; UNIFILE *u; /* first check that directory exists */ /* use special uni_lookup mode to get the unifile entry */ r = do_ulookup(dir, name, &fc, &u); if (r != 0) return EFILNF; /* name does not exist */ if (!d) return EFILNF; if (!d->file_system) return EFILNF; if (d->file_system != fc.fs) return EFILNF; /* not the right name! */ release_cookie(&fc); if (!u || (u->fs != d->file_system)) return EFILNF; /* check if FS is installed */ for (fs = active_fs; fs; fs = fs->next) if (d->file_system == fs) break; if (!fs) return EACCDN; /* not installed, so return an error */ /* here comes the difficult part: we have to close all files on that * device, so we have to call changedrv(). The file system driver * has to make sure that further calls to fs.root() with this device * number will fail! * * Kludge: mark the directory as a link, so uni_remove will remove it. */ changedrv(u->dev); u->mode &= ~S_IFMT; u->mode |= S_IFLNK; return uni_remove(dir, name); } else if (cmd == (int)FS_UNINSTALL) { /* remove file system from kernel list */ struct fs_descr *d = (struct fs_descr*)arg; FILESYS *fs, *last_fs; UNIFILE *u; /* first check if there are any files or directories associated with * this file system */ for (u = u_root; u; u = u->next) if (u->fs == d->file_system) return EACCDN; /* we cannot remove it before unmount */ last_fs = 0; fs = active_fs; while (fs) { /* go through the list and remove the file system */ if (fs == d->file_system) { if (last_fs) last_fs->next = fs->next; else active_fs = fs->next; d->file_system->next = 0; return 0; } last_fs = fs; fs = fs->next; } return EFILNF; } else { /* see if we should just pass this along to another file system */ r = uni_lookup(dir, name, &fc); if (r == 0) { if (fc.fs != &uni_filesys) { r = (*fc.fs->fscntl)(&fc, ".", cmd, arg); release_cookie(&fc); return r; } release_cookie(&fc); } } return EINVFN; }