#include #include #include #include "dat.h" #include "fns.h" #include "linux.h" typedef struct Rfile Rfile; typedef struct Rpath Rpath; struct Rfile { Ufile; struct { Dir *d; int i; int n; } diraux; }; struct Rpath { Ref; Rpath *hash; int deleted; char str[]; }; static Rpath *rpathtab[64]; static QLock rpathtablk; static Rpath ** rpathent(char *path) { Rpath **prp; prp = &rpathtab[hashpath(path) % nelem(rpathtab)]; while(*prp){ if(strcmp(path, (*prp)->str) == 0) break; prp = &((*prp)->hash); } return prp; } static char* linkname(char *name) { if(strncmp(name, ".udir.L.", 8) == 0) name += 8; return name; } static char* udirpath(char *base, char *name, char type) { char buf[9]; strcpy(buf, ".udir.T."); buf[6] = type; return allocpath(base, buf, name); } static int udirget(char *base, char *name, char type, char **val) { char *f, *b; int n, r, s; int fd; r = -1; f = udirpath(base, name, type); if((fd = open(shortpath(current->kcwd, f), OREAD)) < 0) goto out; if(val == nil){ r = 0; goto out; } if((s = seek(fd, 0, 2)) < 0) goto out; b = kmalloc(s+1); n = 0; if(s > 0){ seek(fd, 0, 0); if((n = read(fd, b, s)) < 0){ free(b); goto out; } } b[n] = 0; r = 0; *val = b; out: free(f); close(fd); return r; } static char* resolvepath1(char *path, int link) { char *r, *b, *p, *o, *e; char **a; int n; int i; r = nil; a = nil; n = 0; b = kstrdup(path); for(p=b; *p; p++){ if(*p == '/'){ if((n % 16) == 0) a = krealloc(a, sizeof(a[0]) * (n+16)); a[n++] = p; } } e = nil; for(i=n-1; i>=0; i--){ char *t; char *f; o = e; e = a[i]; *e++ = 0; f = linkname(e); t = nil; if(!udirget(b, f, 'L', &t)){ if(t == nil) break; if(link && o==nil){ free(t); if(f != e) break; t = udirpath(b, e, 'L'); } r = fullpath(b, t); free(t); if(o && o[1]){ t = r; r = fullpath(t, &o[1]); free(t); } break; } --e; if(o) *o = '/'; } free(b); free(a); return r; } static char * resolvepath(char *path, int link) { char *t; int x; x = 0; path = kstrdup(path); while(t = resolvepath1(path, link)){ if(++x > 8){ free(t); free(path); return nil; } free(path); path = t; } return path; } static int ropen(char *path, int mode, int perm, Ufile **pf) { Ufile *f; int err; char *s, *t; int mode9, perm9; int fd; char *base; char *name; Rpath **prp; trace("ropen(%s, %#o, %#o, ...)", path, mode, perm); base = nil; name = nil; mode9 = mode & 3; perm9 = (perm & ~current->umask) & 0777; s = shortpath(current->kcwd, path); if(mode & O_CREAT) { Dir *d; err = -EINVAL; if((base = basepath(path, &name)) == nil) goto out; /* resolve base directory */ if((d = dirstat(shortpath(current->kcwd, base))) == nil){ err = mkerror(); if(t = resolvepath1(base, 0)){ free(base); base = t; t = allocpath(t, nil, name); err = fsopen(t, mode, perm, pf); free(t); } goto out; } err = -ENOTDIR; if((d->mode & DMDIR) == 0){ free(d); goto out; } free(d); /* check if here is a symlink in the way */ t = udirpath(base, name, 'L'); if((fd = open(shortpath(current->kcwd, t), OREAD)) >= 0){ free(t); close(fd); if(mode & O_EXCL){ err = -EEXIST; goto out; } if((t = resolvepath1(path, 0)) == nil) goto out; err = fsopen(t, mode, perm, pf); free(t); goto out; } free(t); if(mode & (O_EXCL | O_TRUNC)){ if(mode & O_EXCL) mode9 |= OEXCL; fd = create(s, mode9, perm9); } else { /* try open first to avoid truncating existing the file */ if((fd = open(s, mode9)) < 0) fd = create(s, mode9, perm9); } if(fd < 0){ err = mkerror(); goto out; } } else { if(((mode & 3) == O_RDWR) || ((mode & 3) == O_WRONLY)) if(mode & O_TRUNC) mode9 |= OTRUNC; if((fd = open(s, mode9)) < 0){ err = mkerror(); if(t = resolvepath1(path, 0)){ err = fsopen(t, mode, perm, pf); free(t); } goto out; } } qlock(&rpathtablk); prp = rpathent(path); if(*prp != nil){ incref(*prp); } else { Rpath *rp; rp = kmalloc(sizeof(*rp) + strlen(path) + 1); rp->ref = 1; rp->hash = nil; rp->deleted = 0; strcpy(rp->str, path); *prp = rp; } qunlock(&rpathtablk); f = kmallocz(sizeof(Rfile), 1); f->ref = 1; f->path = kstrdup(path); f->dev = ROOTDEV; f->mode = mode; f->fd = fd; f->off = 0; *pf = f; err = 0; out: free(base); free(name); return err; } static int rclose(Ufile *f) { Rpath **prp; Rfile *file = (Rfile*)f; static char path[1024]; /* protected by rpathtablk */ qlock(&rpathtablk); prp = rpathent(file->path); if(!decref(*prp)){ Rpath *rp = *prp; *prp = rp->hash; if(rp->deleted){ if(fd2path(file->fd, path, sizeof(path)) == 0) remove(shortpath(current->kcwd, path)); } free(rp); } qunlock(&rpathtablk); close(file->fd); return 0; } static int rread(Ufile *f, void *buf, int len, vlong off) { Rfile *file = (Rfile*)f; int ret, n; n = ret = 0; if(notifyme(1)) return -ERESTART; while((n < len) && ((ret = pread(file->fd, (uchar*)buf + n, len - n, off + n)) > 0)) n += ret; notifyme(0); if(ret < 0) return mkerror(); return n; } static int rwrite(Ufile *f, void *buf, int len, vlong off) { Rfile *file = (Rfile*)f; int ret; if(notifyme(1)) return -ERESTART; ret = pwrite(file->fd, buf, len, off); notifyme(0); if(ret < 0) ret = mkerror(); return ret; } static vlong rsize(Ufile *f) { Rfile *file = (Rfile*)f; return seek(file->fd, 0, 2); } static int raccess(char *path, int mode) { static char omode[] = { 0, // --- OEXEC, // --x OWRITE, // -w- ORDWR, // -wx OREAD, // r-- OEXEC, // r-x ORDWR, // rw- ORDWR // rwx }; int err; int fd; Dir *d; char *s; err = -EINVAL; if(mode & ~07) return err; s = shortpath(current->kcwd, path); if((d = dirstat(s)) == nil){ err = mkerror(); if(path = resolvepath1(path, 0)){ err = fsaccess(path, mode); free(path); } goto out; } /* ignore the exec bit... firefox gets confused */ mode &= ~01; if((mode == 0) || (d->mode & DMDIR)){ err = 0; } else { err = -EACCES; if((mode & 01) && ((d->mode & 0111) == 0)) goto out; if((mode & 02) && ((d->mode & 0222) == 0)) goto out; if((mode & 04) && ((d->mode & 0444) == 0)) goto out; if((fd = open(s, omode[mode])) >= 0){ close(fd); err = 0; } } out: free(d); return err; } static ulong dir2statmode(Dir *d) { ulong mode; mode = d->mode & 0777; if(d->mode & DMDIR) mode |= S_IFDIR; else if(strcmp(d->name, "cons") == 0) mode |= S_IFCHR; else if(strncmp(d->name, "PTS.", 4) == 0) mode |= S_IFCHR; else if(strcmp(d->name, "zero") == 0) mode |= S_IFCHR | 0222; else if(strcmp(d->name, "null") == 0) mode |= S_IFCHR | 0222; else if(strncmp(d->name, ".udir.", 6) == 0){ switch(d->name[6]){ case 'L': mode |= S_IFLNK; break; case 'S': mode |= S_IFSOCK; break; case 'F': mode |= S_IFIFO; break; case 'C': mode |= S_IFCHR; break; case 'B': mode |= S_IFBLK; break; } } else if(d->type == '|') mode |= S_IFIFO; else if(d->type == 'H') mode |= S_IFBLK; else mode |= S_IFREG; return mode; } static void dir2ustat(Dir *d, Ustat *s) { s->mode = dir2statmode(d); s->uid = current->uid; s->gid = current->gid; s->size = d->length; s->atime = d->atime; s->mtime = d->mtime; s->ctime = d->mtime; s->ino = 0; // use d->qid? s->dev = 0; s->rdev = 0; } static int rstat(char *path, int link, Ustat *s) { Dir *d; int err; char *t; if((d = dirstat(shortpath(current->kcwd, path))) == nil){ if(link){ char *base; char *name; if(base = basepath(path, &name)){ t = udirpath(base, name, 'L'); free(name); free(base); d = dirstat(shortpath(current->kcwd, t)); free(t); } } } if(d == nil){ err = mkerror(); if(t = resolvepath1(path, 0)){ err = fsstat(t, link, s); free(t); } return err; } dir2ustat(d, s); s->ino = hashpath(path); free(d); return 0; } static int rfstat(Ufile *f, Ustat *s) { Dir *d; if((d = dirfstat(f->fd)) == nil) return mkerror(); dir2ustat(d, s); s->ino = hashpath(f->path); free(d); return 0; } static char* fixname(char *name) { if(name == nil) return nil; if(strncmp(name, ".udir.", 6) == 0){ if(name[6] && name[7]=='.') name += 8; } return name; } static int rreaddir(Ufile *f, Udirent **pd) { Dir *d; int i, n; seek(f->fd, 0, 0); n = dirreadall(f->fd, &d); if(n < 0) return mkerror(); for(i=0; ipath, fixname(d[i].name), dir2statmode(&d[i]))) == nil) break; pd = &((*pd)->next); } free(d); return i; } static int rreadlink(char *path, char *buf, int len) { int err; int fd; char *t; char *name; char *base; trace("rreadlink(%s)", path); if((base = basepath(path, &name)) == nil) return -EINVAL; /* resolve base path */ if((fd = open(shortpath(current->kcwd, base), OREAD)) < 0){ err = mkerror(); if(t = resolvepath1(base, 0)){ free(base); base = t; t = allocpath(base, nil, name); err = fsreadlink(t, buf, len); free(t); } goto out; } close(fd); /* check if path is regular file */ if((fd = open(shortpath(current->kcwd, path), OREAD)) >= 0){ close(fd); err = -EINVAL; goto out; } t = udirpath(base, name, 'L'); if((fd = open(shortpath(current->kcwd, t), OREAD)) < 0){ err = mkerror(); free(t); goto out; } free(t); if((err = read(fd, buf, len)) < 0) err = mkerror(); close(fd); out: free(base); free(name); return err; } enum { COPYSIZE = 8*1024, }; static int copyfile(char *from, char *to) { int err, fromfd, tofd; char *buf, *s; Dir *ent; Dir *dir; dir = nil; buf = nil; ent = nil; tofd = -1; trace("copyfile(%s, %s)", from, to); if((fromfd = open(shortpath(current->kcwd, from), OREAD)) < 0){ err = mkerror(); goto out; } if((dir = dirfstat(fromfd)) == nil){ err = mkerror(); goto out; } s = shortpath(current->kcwd, to); if((err = open(s, OREAD)) >= 0){ close(err); err = -EEXIST; goto out; } if(dir->mode & DMDIR){ int n; if((tofd = create(s, OREAD, dir->mode)) < 0){ err = mkerror(); goto out; } close(tofd); tofd = -1; while((n = dirread(fromfd, &ent)) > 0){ int i; for(i=0; imode)) < 0){ err = mkerror(); goto out; } buf = kmalloc(COPYSIZE); for(;;){ err = read(fromfd, buf, COPYSIZE); if(err == 0) break; if(err < 0){ err = mkerror(); goto out; } if(write(tofd, buf, err) != err){ err = mkerror(); goto out; } } } err = 0; out: free(ent); free(dir); free(buf); close(fromfd); close(tofd); return err; } static int removefile(char *path) { int err; int n; Dir *d; int fd; char *s; trace("removefile(%s)", path); s = shortpath(current->kcwd, path); if((d = dirstat(s)) == nil) return mkerror(); if(remove(s) == 0){ free(d); return 0; } if((d->mode & DMDIR) == 0){ free(d); return mkerror(); } free(d); if((fd = open(s, OREAD)) < 0) return mkerror(); err = 0; d = nil; while((n = dirread(fd, &d)) > 0){ char *t; int i; for(i=0; ikcwd, from), &d) < 0) err = mkerror(); goto out; } } t = ksmprint("%s%d%d.tmp", to, current->pid, current->tid); if((err = copyfile(from, t)) == 0){ Dir d; nulldir(&d); d.name = &y[1]; remove(shortpath(current->kcwd, to)); if(dirwstat(shortpath(current->kcwd, t), &d) < 0) { err = mkerror(); } else { removefile(from); } } if(err != 0) removefile(t); free(t); out: free(from); free(to); return err; } static int rmkdir(char *path, int mode) { int err; Dir *d; int fd; int mode9; char *base; char *name; char *t; trace("rmkdir(%s, %#o)", path, mode); if((base = basepath(path, &name)) == nil) return -EINVAL; if((d = dirstat(shortpath(current->kcwd, base))) == nil){ err = mkerror(); if(t = resolvepath1(base, 0)){ free(base); base = t; t = allocpath(base, nil, name); err = fsmkdir(t, mode); free(t); } goto out; } err = -ENOTDIR; if((d->mode & DMDIR) == 0){ free(d); goto out; } free(d); err = -EEXIST; t = udirpath(base, name, 'L'); if(d = dirstat(shortpath(current->kcwd, t))){ free(d); free(t); goto out; } free(t); mode9 = DMDIR | ((mode & ~current->umask) & 0777); if((fd = create(shortpath(current->kcwd, path), OREAD|OEXCL, mode9)) < 0){ err = mkerror(); goto out; } close(fd); err = 0; out: free(name); free(base); return err; } static void combinedir(Dir *ndir, Dir *odir) { if(ndir->mode != ~0) ndir->mode = (odir->mode & ~0777) | (ndir->mode & 0777); } static int uwstat(char *path, Dir *ndir, int link) { int err; Dir *dir; char *s; trace("uwstat(%s, ..., %d)", path, link); s = shortpath(current->kcwd, path); if((dir = dirstat(s)) == nil){ err = mkerror(); if(link){ char *base; char *name; if(base = basepath(path, &name)){ char *t; t = udirpath(base, name, 'L'); free(base); free(name); err = uwstat(t, ndir, 0); free(t); } } return err; } combinedir(ndir, dir); err = 0; if(dirwstat(s, ndir) < 0) err = mkerror(); free(dir); return err; } static int uwfstat(Ufile *f, Dir *ndir) { int err; Dir *dir; if((dir = dirfstat(f->fd)) == nil){ err = mkerror(); goto out; } combinedir(ndir, dir); err = 0; if(dirfwstat(f->fd, ndir) < 0) err = mkerror(); out: free(dir); return err; } static int rutime(char *path, long atime, long mtime) { Dir ndir; int err; trace("rutime(%s, %ld, %ld)", path, atime, mtime); nulldir(&ndir); ndir.atime = atime; ndir.mtime = mtime; if((err = uwstat(path, &ndir, 1)) < 0){ char *t; if(t = resolvepath1(path, 0)){ err = fsutime(t, atime, mtime); free(t); } } return err; } static int rchmod(char *path, int mode) { Dir ndir; int err; trace("rchmod(%s, %#o)", path, mode); nulldir(&ndir); ndir.mode = mode; if((err = uwstat(path, &ndir, 1)) < 0){ char *t; if(t = resolvepath1(path, 0)){ err = fschmod(t, mode); free(t); } } return err; } static int rchown(char *path, int uid, int gid, int link) { Ustat s; USED(uid); USED(gid); /* FIXME, just return the right errorcode for now */ return fsstat(path, link, &s); } static int rtruncate(char *path, vlong size) { Dir ndir; int err; trace("rtruncate(%s, %lld)", path, size); nulldir(&ndir); ndir.length = size; if((err = uwstat(path, &ndir, 0)) < 0){ char *t; if(t = resolvepath1(path, 0)){ err = fstruncate(t, size); free(t); } } return err; } static int rfchmod(Ufile *f, int mode) { Dir ndir; nulldir(&ndir); ndir.mode = mode; return uwfstat(f, &ndir); } static int rfchown(Ufile *f, int uid, int gid) { USED(f); USED(uid); USED(gid); return 0; } static int rftruncate(Ufile *f, vlong size) { Dir ndir; nulldir(&ndir); ndir.length = size; return uwfstat(f, &ndir); } static int runlink(char *path, int rmdir) { int err; Dir *dir; char *t, *s; char *base; char *name; char *rpath; Rpath **prp; trace("runlink(%s, %d)", path, rmdir); rpath = nil; dir = nil; err = -EINVAL; if((base = basepath(path, &name)) == nil) goto out; if(dir = dirstat(shortpath(current->kcwd, path))){ rpath = kstrdup(path); } else { rpath = udirpath(base, name, 'L'); dir = dirstat(shortpath(current->kcwd, rpath)); } if(dir == nil){ err = mkerror(); if(t = resolvepath1(path, 0)){ err = fsunlink(t, rmdir); free(t); } goto out; } if(rmdir){ if((dir->mode & DMDIR) == 0){ err = -ENOTDIR; goto out; } } else { if(dir->mode & DMDIR){ err = -EISDIR; goto out; } } s = shortpath(current->kcwd, rpath); qlock(&rpathtablk); prp = rpathent(path); if(*prp){ Dir ndir; t = ksmprint(".%s.%d.deleted", name, current->kpid); nulldir(&ndir); ndir.name = t; trace("runlink: file %s still in use renaming to -> %s", path, t); if(dirwstat(s, &ndir) < 0){ qunlock(&rpathtablk); err = mkerror(); free(t); goto out; } free(t); (*prp)->deleted = 1; qunlock(&rpathtablk); } else { int x; qunlock(&rpathtablk); x = 0; while(remove(s) < 0){ err = mkerror(); if(++x > 8){ /* old debian bug clashes with mntgen */ if(strcmp(base, "/")==0 && strstr(path, ".dpkg-")) err = -ENOENT; goto out; } } } err = 0; out: free(dir); free(name); free(base); free(rpath); return err; } static int rlink(char *old, char *new, int sym) { int err; int fd; char *base; char *name; char *t; trace("rlink(%s, %s, %d)", old, new, sym); if((base = basepath(new, &name)) == nil) return -EINVAL; /* resolve base directory */ if((fd = open(shortpath(current->kcwd, base), OREAD)) < 0){ err = mkerror(); if(t = resolvepath1(base, 0)){ free(base); base = t; t = allocpath(base, nil, name); err = fslink(old, t, sym); free(t); } goto out; } close(fd); if(sym == 0){ if((err = resolvefromtopath(&old, &new)) == 0) err = copyfile(old, new); free(old); free(new); goto out; } /* check if regular file is in the way */ err = -EEXIST; if((fd = open(shortpath(current->kcwd, new), OREAD)) >= 0){ close(fd); goto out; } /* try to create the link, will fail if alreadt exists */ t = udirpath(base, name, 'L'); if((fd = create(shortpath(current->kcwd, t), OWRITE|OEXCL, 0777)) < 0){ err = mkerror(); free(t); goto out; } free(t); if(write(fd, old, strlen(old)) < 0){ err = mkerror(); close(fd); goto out; } close(fd); err = 0; out: free(base); free(name); return err; } static Udev rootdev = { .open = ropen, .access = raccess, .stat = rstat, .link = rlink, .unlink = runlink, .rename = rrename, .mkdir = rmkdir, .utime = rutime, .chmod = rchmod, .chown = rchown, .truncate = rtruncate, .read = rread, .write = rwrite, .size = rsize, .close = rclose, .fstat = rfstat, .readdir = rreaddir, .readlink = rreadlink, .fchmod = rfchmod, .fchown = rfchown, .ftruncate = rftruncate, }; void rootdevinit(void) { devtab[ROOTDEV] = &rootdev; fsmount(&rootdev, ""); }