1832 lines
33 KiB
C
1832 lines
33 KiB
C
/* already in plan9.h #include <sys/types.h> *//* for struct passwd, struct group, struct stat ... */
|
|
/* plan9.h is first to get the large file support definitions as early as possible */
|
|
#include <plan9.h>
|
|
#include <sys/stat.h> /* for stat, umask */
|
|
#include <stdlib.h> /* for malloc */
|
|
#include <string.h> /* for strcpy, memmove */
|
|
#include <pwd.h> /* for getpwnam, getpwuid */
|
|
#include <grp.h> /* for getgrnam, getgrgid */
|
|
#include <unistd.h> /* for gethostname, pread, pwrite, read, write */
|
|
#include <utime.h> /* for utime */
|
|
#include <dirent.h> /* for readdir */
|
|
#include <errno.h> /* for errno */
|
|
#include <stdio.h> /* for remove [sic] */
|
|
#include <fcntl.h> /* for O_RDONLY, etc. */
|
|
#include <limits.h> /* for PATH_MAX */
|
|
|
|
#include <sys/socket.h> /* various networking crud */
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
|
|
#include <fcall.h>
|
|
#include <oldfcall.h>
|
|
#include <u9fs.h>
|
|
|
|
/* #ifndef because can be given in makefile */
|
|
#ifndef DEFAULTLOG
|
|
#define DEFAULTLOG "/tmp/u9fs.log"
|
|
#endif
|
|
|
|
char *logfile = DEFAULTLOG;
|
|
|
|
#define S_ISSPECIAL(m) (S_ISCHR(m) || S_ISBLK(m) || S_ISFIFO(m))
|
|
|
|
enum {
|
|
Tdot = 1,
|
|
Tdotdot
|
|
};
|
|
|
|
enum {
|
|
P9P1,
|
|
P9P2000
|
|
};
|
|
|
|
typedef struct User User;
|
|
struct User {
|
|
int id;
|
|
gid_t defaultgid;
|
|
char *name;
|
|
char **mem; /* group members */
|
|
int nmem;
|
|
User *next;
|
|
};
|
|
|
|
struct Fid {
|
|
int fid;
|
|
char *path;
|
|
struct stat st;
|
|
User *u;
|
|
int omode;
|
|
DIR *dir;
|
|
int diroffset;
|
|
int fd;
|
|
struct dirent *dirent;
|
|
int direof;
|
|
Fid *next;
|
|
Fid *prev;
|
|
int auth;
|
|
void *authmagic;
|
|
};
|
|
|
|
void* emalloc(size_t);
|
|
void* erealloc(void*, size_t);
|
|
char* estrdup(char*);
|
|
char* estrpath(char*, char*, int);
|
|
void sysfatal(char*, ...);
|
|
int okuser(char*);
|
|
|
|
void rversion(Fcall*, Fcall*);
|
|
void rauth(Fcall*, Fcall*);
|
|
void rattach(Fcall*, Fcall*);
|
|
void rflush(Fcall*, Fcall*);
|
|
void rclone(Fcall*, Fcall*);
|
|
void rwalk(Fcall*, Fcall*);
|
|
void ropen(Fcall*, Fcall*);
|
|
void rcreate(Fcall*, Fcall*);
|
|
void rread(Fcall*, Fcall*);
|
|
void rwrite(Fcall*, Fcall*);
|
|
void rclunk(Fcall*, Fcall*);
|
|
void rstat(Fcall*, Fcall*);
|
|
void rwstat(Fcall*, Fcall*);
|
|
void rclwalk(Fcall*, Fcall*);
|
|
void rremove(Fcall*, Fcall*);
|
|
|
|
User* uname2user(char*);
|
|
User* gname2user(char*);
|
|
User* uid2user(int);
|
|
User* gid2user(int);
|
|
|
|
Fid* newfid(int, char**);
|
|
Fid* oldfidex(int, int, char**);
|
|
Fid* oldfid(int, char**);
|
|
int fidstat(Fid*, char**);
|
|
void freefid(Fid*);
|
|
|
|
int userchange(User*, char**);
|
|
int userwalk(User*, char**, char*, Qid*, char**);
|
|
int useropen(Fid*, int, char**);
|
|
int usercreate(Fid*, char*, int, long, char**);
|
|
int userremove(Fid*, char**);
|
|
int userperm(User*, char*, int, int);
|
|
int useringroup(User*, User*);
|
|
|
|
Qid stat2qid(struct stat*);
|
|
|
|
void getfcallold(int, Fcall*, int);
|
|
void putfcallold(int, Fcall*);
|
|
|
|
char Eauth[] = "authentication failed";
|
|
char Ebadfid[] = "fid unknown or out of range";
|
|
char Ebadoffset[] = "bad offset in directory read";
|
|
char Ebadusefid[] = "bad use of fid";
|
|
char Edirchange[] = "wstat can't convert between files and directories";
|
|
char Eexist[] = "file or directory already exists";
|
|
char Efidactive[] = "fid already in use";
|
|
char Enotdir[] = "not a directory";
|
|
char Enotingroup[] = "not a member of proposed group";
|
|
char Enotowner[] = "only owner can change group in wstat";
|
|
char Eperm[] = "permission denied";
|
|
char Especial0[] = "already attached without access to special files";
|
|
char Especial1[] = "already attached with access to special files";
|
|
char Especial[] = "no access to special file";
|
|
char Etoolarge[] = "i/o count too large";
|
|
char Eunknowngroup[] = "unknown group";
|
|
char Eunknownuser[] = "unknown user";
|
|
char Ewstatbuffer[] = "bogus wstat buffer";
|
|
|
|
ulong msize = IOHDRSZ+8192;
|
|
uchar* rxbuf;
|
|
uchar* txbuf;
|
|
void* databuf;
|
|
int connected;
|
|
int devallowed;
|
|
char* autharg;
|
|
char* defaultuser;
|
|
char hostname[256];
|
|
char remotehostname[256];
|
|
int chatty9p = 0;
|
|
int network = 1;
|
|
int old9p = -1;
|
|
int authed;
|
|
char* root;
|
|
User* none;
|
|
|
|
Auth *authmethods[] = { /* first is default */
|
|
&authrhosts,
|
|
&authp9any,
|
|
&authnone,
|
|
};
|
|
|
|
Auth *auth;
|
|
|
|
/*
|
|
* frogs: characters not valid in plan9
|
|
* filenames, keep this list in sync with
|
|
* /sys/src/9/port/chan.c:1656
|
|
*/
|
|
char isfrog[256]={
|
|
/*NUL*/ 1, 1, 1, 1, 1, 1, 1, 1,
|
|
/*BKS*/ 1, 1, 1, 1, 1, 1, 1, 1,
|
|
/*DLE*/ 1, 1, 1, 1, 1, 1, 1, 1,
|
|
/*CAN*/ 1, 1, 1, 1, 1, 1, 1, 1,
|
|
/*' '*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'('*/ 0, 0, 0, 0, 0, 0, 0, 1, /*'/'*/
|
|
/*'0'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'8'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'@'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'H'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'P'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'X'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'`'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'h'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'p'*/ 0, 0, 0, 0, 0, 0, 0, 0,
|
|
/*'x'*/ 0, 0, 0, 0, 0, 0, 0, 1, /*DEL*/
|
|
};
|
|
|
|
char*
|
|
rootpath(char *path)
|
|
{
|
|
static char buf[PATH_MAX];
|
|
|
|
if(root == nil)
|
|
return path;
|
|
snprintf(buf, sizeof buf, "%s%s", root, path);
|
|
return buf;
|
|
}
|
|
|
|
void
|
|
getfcallnew(int fd, Fcall *fc, int have)
|
|
{
|
|
int len;
|
|
|
|
if(have > BIT32SZ)
|
|
sysfatal("cannot happen");
|
|
|
|
if(have < BIT32SZ && readn(fd, rxbuf+have, BIT32SZ-have) != BIT32SZ-have)
|
|
sysfatal("couldn't read message");
|
|
|
|
len = GBIT32(rxbuf);
|
|
if(len <= BIT32SZ)
|
|
sysfatal("bogus message");
|
|
|
|
len -= BIT32SZ;
|
|
if(readn(fd, rxbuf+BIT32SZ, len) != len)
|
|
sysfatal("short message");
|
|
|
|
if(convM2S(rxbuf, len+BIT32SZ, fc) != len+BIT32SZ)
|
|
sysfatal("badly sized message type %d", rxbuf[0]);
|
|
}
|
|
|
|
void
|
|
getfcallold(int fd, Fcall *fc, int have)
|
|
{
|
|
int len, n;
|
|
|
|
if(have > 3)
|
|
sysfatal("cannot happen");
|
|
|
|
if(have < 3 && readn(fd, rxbuf, 3-have) != 3-have)
|
|
sysfatal("couldn't read message");
|
|
|
|
len = oldhdrsize(rxbuf[0]);
|
|
if(len < 3)
|
|
sysfatal("bad message %d", rxbuf[0]);
|
|
if(len > 3 && readn(fd, rxbuf+3, len-3) != len-3)
|
|
sysfatal("couldn't read message");
|
|
|
|
n = iosize(rxbuf);
|
|
if(readn(fd, rxbuf+len, n) != n)
|
|
sysfatal("couldn't read message");
|
|
len += n;
|
|
|
|
if(convM2Sold(rxbuf, len, fc) != len)
|
|
sysfatal("badly sized message type %d", rxbuf[0]);
|
|
}
|
|
|
|
void
|
|
putfcallnew(int wfd, Fcall *tx)
|
|
{
|
|
uint n;
|
|
|
|
if((n = convS2M(tx, txbuf, msize)) == 0)
|
|
sysfatal("couldn't format message type %d", tx->type);
|
|
if(write(wfd, txbuf, n) != n)
|
|
sysfatal("couldn't send message");
|
|
}
|
|
|
|
void
|
|
putfcallold(int wfd, Fcall *tx)
|
|
{
|
|
uint n;
|
|
|
|
if((n = convS2Mold(tx, txbuf, msize)) == 0)
|
|
sysfatal("couldn't format message type %d", tx->type);
|
|
if(write(wfd, txbuf, n) != n)
|
|
sysfatal("couldn't send message");
|
|
}
|
|
|
|
void
|
|
getfcall(int fd, Fcall *fc)
|
|
{
|
|
if(old9p == 1){
|
|
getfcallold(fd, fc, 0);
|
|
return;
|
|
}
|
|
if(old9p == 0){
|
|
getfcallnew(fd, fc, 0);
|
|
return;
|
|
}
|
|
|
|
/* auto-detect */
|
|
if(readn(fd, rxbuf, 3) != 3)
|
|
sysfatal("couldn't read message");
|
|
|
|
/* is it an old (9P1) message? */
|
|
if(50 <= rxbuf[0] && rxbuf[0] <= 87 && (rxbuf[0]&1)==0 && GBIT16(rxbuf+1) == 0xFFFF){
|
|
old9p = 1;
|
|
getfcallold(fd, fc, 3);
|
|
return;
|
|
}
|
|
|
|
getfcallnew(fd, fc, 3);
|
|
old9p = 0;
|
|
}
|
|
|
|
void
|
|
seterror(Fcall *f, char *error)
|
|
{
|
|
f->type = Rerror;
|
|
f->ename = error ? error : "programmer error";
|
|
}
|
|
|
|
int
|
|
isowner(User *u, Fid *f)
|
|
{
|
|
return u->id == f->st.st_uid;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
serve(int rfd, int wfd)
|
|
{
|
|
Fcall rx, tx;
|
|
|
|
for(;;){
|
|
getfcall(rfd, &rx);
|
|
|
|
if(chatty9p)
|
|
fprint(2, "<- %F\n", &rx);
|
|
|
|
memset(&tx, 0, sizeof tx);
|
|
tx.type = rx.type+1;
|
|
tx.tag = rx.tag;
|
|
switch(rx.type){
|
|
case Tflush:
|
|
break;
|
|
case Tversion:
|
|
rversion(&rx, &tx);
|
|
break;
|
|
case Tauth:
|
|
rauth(&rx, &tx);
|
|
break;
|
|
case Tattach:
|
|
rattach(&rx, &tx);
|
|
break;
|
|
case Twalk:
|
|
rwalk(&rx, &tx);
|
|
break;
|
|
case Tstat:
|
|
tx.stat = databuf;
|
|
rstat(&rx, &tx);
|
|
break;
|
|
case Twstat:
|
|
rwstat(&rx, &tx);
|
|
break;
|
|
case Topen:
|
|
ropen(&rx, &tx);
|
|
break;
|
|
case Tcreate:
|
|
rcreate(&rx, &tx);
|
|
break;
|
|
case Tread:
|
|
tx.data = databuf;
|
|
rread(&rx, &tx);
|
|
break;
|
|
case Twrite:
|
|
rwrite(&rx, &tx);
|
|
break;
|
|
case Tclunk:
|
|
rclunk(&rx, &tx);
|
|
break;
|
|
case Tremove:
|
|
rremove(&rx, &tx);
|
|
break;
|
|
default:
|
|
fprint(2, "unknown message %F\n", &rx);
|
|
seterror(&tx, "bad message");
|
|
break;
|
|
}
|
|
|
|
if(chatty9p)
|
|
fprint(2, "-> %F\n", &tx);
|
|
|
|
(old9p ? putfcallold : putfcallnew)(wfd, &tx);
|
|
}
|
|
}
|
|
|
|
void
|
|
rversion(Fcall *rx, Fcall *tx)
|
|
{
|
|
if(msize > rx->msize)
|
|
msize = rx->msize;
|
|
tx->msize = msize;
|
|
if(strncmp(rx->version, "9P", 2) != 0)
|
|
tx->version = "unknown";
|
|
else
|
|
tx->version = "9P2000";
|
|
}
|
|
|
|
void
|
|
rauth(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
|
|
if((e = auth->auth(rx, tx)) != nil)
|
|
seterror(tx, e);
|
|
}
|
|
|
|
void
|
|
rattach(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
User *u;
|
|
|
|
if(rx->aname == nil)
|
|
rx->aname = "";
|
|
|
|
if(strcmp(rx->aname, "device") == 0){
|
|
if(connected && !devallowed){
|
|
seterror(tx, Especial0);
|
|
return;
|
|
}
|
|
devallowed = 1;
|
|
}else{
|
|
if(connected && devallowed){
|
|
seterror(tx, Especial1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(strcmp(rx->uname, "none") == 0){
|
|
if(authed == 0){
|
|
seterror(tx, Eauth);
|
|
return;
|
|
}
|
|
} else {
|
|
if((e = auth->attach(rx, tx)) != nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
authed++;
|
|
}
|
|
|
|
if((fid = newfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
fid->path = estrdup("/");
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
freefid(fid);
|
|
return;
|
|
}
|
|
|
|
if(defaultuser)
|
|
rx->uname = defaultuser;
|
|
|
|
if((u = uname2user(rx->uname)) == nil
|
|
|| (!defaultuser && u->id == 0)){
|
|
/* we don't know anyone named root... */
|
|
seterror(tx, Eunknownuser);
|
|
freefid(fid);
|
|
return;
|
|
}
|
|
|
|
fid->u = u;
|
|
tx->qid = stat2qid(&fid->st);
|
|
return;
|
|
}
|
|
|
|
void
|
|
rwalk(Fcall *rx, Fcall *tx)
|
|
{
|
|
int i;
|
|
char *path, *e;
|
|
Fid *fid, *nfid;
|
|
|
|
e = nil;
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fid->omode != -1){
|
|
seterror(tx, Ebadusefid);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(!S_ISDIR(fid->st.st_mode) && rx->nwname){
|
|
seterror(tx, Enotdir);
|
|
return;
|
|
}
|
|
|
|
nfid = nil;
|
|
if(rx->newfid != rx->fid && (nfid = newfid(rx->newfid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
path = estrdup(fid->path);
|
|
e = nil;
|
|
for(i=0; i<rx->nwname; i++)
|
|
if(userwalk(fid->u, &path, rx->wname[i], &tx->wqid[i], &e) < 0)
|
|
break;
|
|
|
|
if(i == rx->nwname){ /* successful clone or walk */
|
|
tx->nwqid = i;
|
|
if(nfid){
|
|
nfid->path = path;
|
|
nfid->u = fid->u;
|
|
}else{
|
|
free(fid->path);
|
|
fid->path = path;
|
|
}
|
|
}else{
|
|
if(i > 0) /* partial walk? */
|
|
tx->nwqid = i;
|
|
else
|
|
seterror(tx, e);
|
|
|
|
if(nfid) /* clone implicit new fid */
|
|
freefid(nfid);
|
|
free(path);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void
|
|
ropen(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fid->omode != -1){
|
|
seterror(tx, Ebadusefid);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(!devallowed && S_ISSPECIAL(fid->st.st_mode)){
|
|
seterror(tx, Especial);
|
|
return;
|
|
}
|
|
|
|
if(useropen(fid, rx->mode, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
tx->iounit = 0;
|
|
tx->qid = stat2qid(&fid->st);
|
|
}
|
|
|
|
void
|
|
rcreate(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fid->omode != -1){
|
|
seterror(tx, Ebadusefid);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(!S_ISDIR(fid->st.st_mode)){
|
|
seterror(tx, Enotdir);
|
|
return;
|
|
}
|
|
|
|
if(usercreate(fid, rx->name, rx->mode, rx->perm, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
tx->iounit = 0;
|
|
tx->qid = stat2qid(&fid->st);
|
|
}
|
|
|
|
uchar
|
|
modebyte(struct stat *st)
|
|
{
|
|
uchar b;
|
|
|
|
b = 0;
|
|
|
|
if(S_ISDIR(st->st_mode))
|
|
b |= QTDIR;
|
|
|
|
/* no way to test append-only */
|
|
/* no real way to test exclusive use, but mark devices as such */
|
|
if(S_ISSPECIAL(st->st_mode))
|
|
b |= QTEXCL;
|
|
|
|
return b;
|
|
}
|
|
|
|
ulong
|
|
plan9mode(struct stat *st)
|
|
{
|
|
return ((ulong)modebyte(st)<<24) | (st->st_mode & 0777);
|
|
}
|
|
|
|
/*
|
|
* this is for chmod, so don't worry about S_IFDIR
|
|
*/
|
|
mode_t
|
|
unixmode(Dir *d)
|
|
{
|
|
return (mode_t)(d->mode&0777);
|
|
}
|
|
|
|
Qid
|
|
stat2qid(struct stat *st)
|
|
{
|
|
uchar *p, *ep, *q;
|
|
Qid qid;
|
|
|
|
/*
|
|
* For now, ignore the device number.
|
|
*/
|
|
qid.path = 0;
|
|
p = (uchar*)&qid.path;
|
|
ep = p+sizeof(qid.path);
|
|
q = p+sizeof(ino_t);
|
|
if(q > ep){
|
|
fprint(2, "warning: inode number too big\n");
|
|
q = ep;
|
|
}
|
|
memmove(p, &st->st_ino, q-p);
|
|
q = q+sizeof(dev_t);
|
|
if(q > ep){
|
|
/*
|
|
* fprint(2, "warning: inode number + device number too big %d+%d\n",
|
|
* sizeof(ino_t), sizeof(dev_t));
|
|
*/
|
|
q = ep - sizeof(dev_t);
|
|
if(q < p)
|
|
fprint(2, "warning: device number too big by itself\n");
|
|
else
|
|
*(dev_t*)q ^= st->st_dev;
|
|
}
|
|
|
|
qid.vers = st->st_mtime ^ (st->st_size << 8);
|
|
qid.type = modebyte(st);
|
|
return qid;
|
|
}
|
|
|
|
char *
|
|
enfrog(char *src)
|
|
{
|
|
char *d, *dst;
|
|
uchar *s;
|
|
|
|
d = dst = emalloc(strlen(src)*3 + 1);
|
|
for (s = (uchar *)src; *s; s++)
|
|
if(isfrog[*s] || *s == '\\')
|
|
d += sprintf(d, "\\%02x", *s);
|
|
else
|
|
*d++ = *s;
|
|
*d = 0;
|
|
return dst;
|
|
}
|
|
|
|
char *
|
|
defrog(char *s)
|
|
{
|
|
char *d, *dst, buf[3];
|
|
|
|
d = dst = emalloc(strlen(s) + 1);
|
|
for(; *s; s++)
|
|
if(*s == '\\' && strlen(s) >= 3){
|
|
buf[0] = *++s; /* skip \ */
|
|
buf[1] = *++s;
|
|
buf[2] = 0;
|
|
*d++ = strtoul(buf, NULL, 16);
|
|
} else
|
|
*d++ = *s;
|
|
*d = 0;
|
|
return dst;
|
|
}
|
|
|
|
void
|
|
stat2dir(char *path, struct stat *st, Dir *d)
|
|
{
|
|
User *u;
|
|
char *q, *p, *npath;
|
|
|
|
memset(d, 0, sizeof(*d));
|
|
d->qid = stat2qid(st);
|
|
d->mode = plan9mode(st);
|
|
d->atime = st->st_atime;
|
|
d->mtime = st->st_mtime;
|
|
d->length = st->st_size;
|
|
|
|
d->uid = (u = uid2user(st->st_uid)) ? u->name : "???";
|
|
d->gid = (u = gid2user(st->st_gid)) ? u->name : "???";
|
|
d->muid = "";
|
|
|
|
if((q = strrchr(path, '/')) != nil)
|
|
d->name = enfrog(q+1);
|
|
else
|
|
d->name = enfrog(path);
|
|
}
|
|
|
|
void
|
|
rread(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e, *path, *rpath;
|
|
uchar *p, *ep;
|
|
int n;
|
|
Fid *fid;
|
|
Dir d;
|
|
struct stat st;
|
|
|
|
if(rx->count > msize-IOHDRSZ){
|
|
seterror(tx, Etoolarge);
|
|
return;
|
|
}
|
|
|
|
if((fid = oldfidex(rx->fid, -1, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if (fid->auth) {
|
|
char *e;
|
|
e = auth->read(rx, tx);
|
|
if (e)
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fid->omode == -1 || (fid->omode&3) == OWRITE){
|
|
seterror(tx, Ebadusefid);
|
|
return;
|
|
}
|
|
|
|
if(fid->dir){
|
|
if(rx->offset != fid->diroffset){
|
|
if(rx->offset != 0){
|
|
seterror(tx, Ebadoffset);
|
|
return;
|
|
}
|
|
rewinddir(fid->dir);
|
|
fid->diroffset = 0;
|
|
fid->direof = 0;
|
|
}
|
|
if(fid->direof){
|
|
tx->count = 0;
|
|
return;
|
|
}
|
|
|
|
p = (uchar*)tx->data;
|
|
ep = (uchar*)tx->data+rx->count;
|
|
for(;;){
|
|
if(p+BIT16SZ >= ep)
|
|
break;
|
|
if(fid->dirent == nil) /* one entry cache for when convD2M fails */
|
|
if((fid->dirent = readdir(fid->dir)) == nil){
|
|
fid->direof = 1;
|
|
break;
|
|
}
|
|
if(strcmp(fid->dirent->d_name, ".") == 0
|
|
|| strcmp(fid->dirent->d_name, "..") == 0){
|
|
fid->dirent = nil;
|
|
continue;
|
|
}
|
|
rpath = rootpath(fid->path);
|
|
path = estrpath(rpath, fid->dirent->d_name, 0);
|
|
memset(&st, 0, sizeof st);
|
|
if(stat(path, &st) < 0){
|
|
fprint(2, "dirread: stat(%s) failed: %s\n", path, strerror(errno));
|
|
fid->dirent = nil;
|
|
free(path);
|
|
continue;
|
|
}
|
|
free(path);
|
|
stat2dir(fid->dirent->d_name, &st, &d);
|
|
if((n=(old9p ? convD2Mold : convD2M)(&d, p, ep-p)) <= BIT16SZ)
|
|
break;
|
|
p += n;
|
|
fid->dirent = nil;
|
|
}
|
|
tx->count = p - (uchar*)tx->data;
|
|
fid->diroffset += tx->count;
|
|
}else{
|
|
if((n = pread(fid->fd, tx->data, rx->count, rx->offset)) < 0){
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
tx->count = n;
|
|
}
|
|
}
|
|
|
|
void
|
|
rwrite(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
int n;
|
|
|
|
if(rx->count > msize-IOHDRSZ){
|
|
seterror(tx, Etoolarge);
|
|
return;
|
|
}
|
|
|
|
if((fid = oldfidex(rx->fid, -1, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if (fid->auth) {
|
|
char *e;
|
|
e = auth->write(rx, tx);
|
|
if (e)
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fid->omode == -1 || (fid->omode&3) == OREAD || (fid->omode&3) == OEXEC){
|
|
seterror(tx, Ebadusefid);
|
|
return;
|
|
}
|
|
|
|
if((n = pwrite(fid->fd, rx->data, rx->count, rx->offset)) < 0){
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
tx->count = n;
|
|
}
|
|
|
|
void
|
|
rclunk(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e, *rpath;
|
|
Fid *fid;
|
|
|
|
if((fid = oldfidex(rx->fid, -1, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
if (fid->auth) {
|
|
if (auth->clunk) {
|
|
e = (*auth->clunk)(rx, tx);
|
|
if (e) {
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if(fid->omode != -1 && fid->omode&ORCLOSE){
|
|
rpath = rootpath(fid->path);
|
|
remove(rpath);
|
|
}
|
|
freefid(fid);
|
|
}
|
|
|
|
void
|
|
rremove(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
if(userremove(fid, &e) < 0)
|
|
seterror(tx, e);
|
|
freefid(fid);
|
|
}
|
|
|
|
void
|
|
rstat(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e;
|
|
Fid *fid;
|
|
Dir d;
|
|
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
stat2dir(fid->path, &fid->st, &d);
|
|
if((tx->nstat=(old9p ? convD2Mold : convD2M)(&d, tx->stat, msize)) <= BIT16SZ)
|
|
seterror(tx, "convD2M fails");
|
|
}
|
|
|
|
void
|
|
rwstat(Fcall *rx, Fcall *tx)
|
|
{
|
|
char *e, *opath, *npath;
|
|
char *p, *old, *new, *dir;
|
|
gid_t gid;
|
|
Dir d;
|
|
Fid *fid;
|
|
|
|
if((fid = oldfid(rx->fid, &e)) == nil){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* wstat is supposed to be atomic.
|
|
* we check all the things we can before trying anything.
|
|
* still, if we are told to truncate a file and rename it and only
|
|
* one works, we're screwed. in such cases we leave things
|
|
* half broken and return an error. it's hardly perfect.
|
|
*/
|
|
if((old9p ? convM2Dold : convM2D)(rx->stat, rx->nstat, &d, (char*)rx->stat) <= BIT16SZ){
|
|
seterror(tx, Ewstatbuffer);
|
|
return;
|
|
}
|
|
|
|
if(fidstat(fid, &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The casting is necessary because d.mode is ulong and might,
|
|
* on some systems, be 64 bits. We only want to compare the
|
|
* bottom 32 bits, since that's all that gets sent in the protocol.
|
|
*
|
|
* Same situation for d.mtime and d.length (although that last check
|
|
* is admittedly superfluous, given the current lack of 128-bit machines).
|
|
*/
|
|
gid = (gid_t)-1;
|
|
if(d.gid[0] != '\0'){
|
|
User *g;
|
|
|
|
g = gname2user(d.gid);
|
|
if(g == nil){
|
|
seterror(tx, Eunknowngroup);
|
|
return;
|
|
}
|
|
gid = (gid_t)g->id;
|
|
|
|
if(groupchange(fid->u, gid2user(gid), &e) < 0){
|
|
seterror(tx, e);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if((u32int)d.mode != (u32int)~0 && (((d.mode&DMDIR)!=0) ^ (S_ISDIR(fid->st.st_mode)!=0))){
|
|
seterror(tx, Edirchange);
|
|
return;
|
|
}
|
|
|
|
if(strcmp(fid->path, "/") == 0){
|
|
seterror(tx, "no wstat of root");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* try things in increasing order of harm to the file.
|
|
* mtime should come after truncate so that if you
|
|
* do both the mtime actually takes effect, but i'd rather
|
|
* leave truncate until last.
|
|
* (see above comment about atomicity).
|
|
*/
|
|
opath = estrdup(rootpath(fid->path));
|
|
if((u32int)d.mode != (u32int)~0 && chmod(opath, unixmode(&d)) < 0){
|
|
if(chatty9p)
|
|
fprint(2, "chmod(%s, 0%luo) failed\n", opath, unixmode(&d));
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
if((u32int)d.mtime != (u32int)~0){
|
|
struct utimbuf t;
|
|
|
|
t.actime = 0;
|
|
t.modtime = d.mtime;
|
|
if(utime(opath, &t) < 0){
|
|
if(chatty9p)
|
|
fprint(2, "utime(%s) failed\n", opath);
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(gid != (gid_t)-1 && gid != fid->st.st_gid){
|
|
if(chown(opath, (uid_t)-1, gid) < 0){
|
|
if(chatty9p)
|
|
fprint(2, "chgrp(%s, %d) failed\n", opath, gid);
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(d.name[0]){
|
|
old = fid->path;
|
|
dir = estrdup(fid->path);
|
|
if((p = strrchr(dir, '/')) > dir)
|
|
*p = '\0';
|
|
else{
|
|
seterror(tx, "whoops: can't happen in u9fs");
|
|
return;
|
|
}
|
|
new = estrpath(dir, d.name, 1);
|
|
npath = rootpath(new);
|
|
if(strcmp(old, new) != 0 && rename(opath, npath) < 0){
|
|
if(chatty9p)
|
|
fprint(2, "rename(%s, %s) failed\n", old, new);
|
|
seterror(tx, strerror(errno));
|
|
free(new);
|
|
free(dir);
|
|
free(opath);
|
|
return;
|
|
}
|
|
fid->path = new;
|
|
free(old);
|
|
free(dir);
|
|
free(opath);
|
|
}
|
|
|
|
if((u64int)d.length != (u64int)~0 && truncate(opath, d.length) < 0){
|
|
fprint(2, "truncate(%s, %lld) failed\n", opath, d.length);
|
|
seterror(tx, strerror(errno));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* we keep a table by numeric id. by name lookups happen infrequently
|
|
* while by-number lookups happen once for every directory entry read
|
|
* and every stat request.
|
|
*/
|
|
User *utab[64];
|
|
User *gtab[64];
|
|
|
|
User*
|
|
adduser(struct passwd *p)
|
|
{
|
|
User *u;
|
|
|
|
u = emalloc(sizeof(*u));
|
|
u->id = p->pw_uid;
|
|
u->name = estrdup(p->pw_name);
|
|
u->next = utab[p->pw_uid%nelem(utab)];
|
|
u->defaultgid = p->pw_gid;
|
|
utab[p->pw_uid%nelem(utab)] = u;
|
|
return u;
|
|
}
|
|
|
|
int
|
|
useringroup(User *u, User *g)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<g->nmem; i++)
|
|
if(strcmp(g->mem[i], u->name) == 0)
|
|
return 1;
|
|
|
|
/*
|
|
* Hack around common Unix problem that everyone has
|
|
* default group "user" but /etc/group lists no members.
|
|
*/
|
|
if(u->defaultgid == g->id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
User*
|
|
addgroup(struct group *g)
|
|
{
|
|
User *u;
|
|
char **p;
|
|
int n;
|
|
|
|
u = emalloc(sizeof(*u));
|
|
n = 0;
|
|
for(p=g->gr_mem; *p; p++)
|
|
n++;
|
|
u->mem = emalloc(sizeof(u->mem[0])*n);
|
|
n = 0;
|
|
for(p=g->gr_mem; *p; p++)
|
|
u->mem[n++] = estrdup(*p);
|
|
u->nmem = n;
|
|
u->id = g->gr_gid;
|
|
u->name = estrdup(g->gr_name);
|
|
u->next = gtab[g->gr_gid%nelem(gtab)];
|
|
gtab[g->gr_gid%nelem(gtab)] = u;
|
|
return u;
|
|
}
|
|
|
|
User*
|
|
uname2user(char *name)
|
|
{
|
|
int i;
|
|
User *u;
|
|
struct passwd *p;
|
|
|
|
for(i=0; i<nelem(utab); i++)
|
|
for(u=utab[i]; u; u=u->next)
|
|
if(strcmp(u->name, name) == 0)
|
|
return u;
|
|
|
|
if((p = getpwnam(name)) == nil)
|
|
return nil;
|
|
return adduser(p);
|
|
}
|
|
|
|
User*
|
|
uid2user(int id)
|
|
{
|
|
User *u;
|
|
struct passwd *p;
|
|
|
|
for(u=utab[id%nelem(utab)]; u; u=u->next)
|
|
if(u->id == id)
|
|
return u;
|
|
|
|
if((p = getpwuid(id)) == nil)
|
|
return nil;
|
|
return adduser(p);
|
|
}
|
|
|
|
User*
|
|
gname2user(char *name)
|
|
{
|
|
int i;
|
|
User *u;
|
|
struct group *g;
|
|
|
|
for(i=0; i<nelem(gtab); i++)
|
|
for(u=gtab[i]; u; u=u->next)
|
|
if(strcmp(u->name, name) == 0)
|
|
return u;
|
|
|
|
if((g = getgrnam(name)) == nil)
|
|
return nil;
|
|
return addgroup(g);
|
|
}
|
|
|
|
User*
|
|
gid2user(int id)
|
|
{
|
|
User *u;
|
|
struct group *g;
|
|
|
|
for(u=gtab[id%nelem(gtab)]; u; u=u->next)
|
|
if(u->id == id)
|
|
return u;
|
|
|
|
if((g = getgrgid(id)) == nil)
|
|
return nil;
|
|
return addgroup(g);
|
|
}
|
|
|
|
void
|
|
sysfatal(char *fmt, ...)
|
|
{
|
|
char buf[1024];
|
|
va_list va, temp;
|
|
|
|
va_start(va, fmt);
|
|
va_copy(temp, va);
|
|
doprint(buf, buf+sizeof buf, fmt, &temp);
|
|
va_end(temp);
|
|
va_end(va);
|
|
fprint(2, "u9fs: %s\n", buf);
|
|
fprint(2, "last unix error: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
void*
|
|
emalloc(size_t n)
|
|
{
|
|
void *p;
|
|
|
|
if(n == 0)
|
|
n = 1;
|
|
p = malloc(n);
|
|
if(p == 0)
|
|
sysfatal("malloc(%ld) fails", (long)n);
|
|
memset(p, 0, n);
|
|
return p;
|
|
}
|
|
|
|
void*
|
|
erealloc(void *p, size_t n)
|
|
{
|
|
if(p == 0)
|
|
p = malloc(n);
|
|
else
|
|
p = realloc(p, n);
|
|
if(p == 0)
|
|
sysfatal("realloc(..., %ld) fails", (long)n);
|
|
return p;
|
|
}
|
|
|
|
char*
|
|
estrdup(char *p)
|
|
{
|
|
p = strdup(p);
|
|
if(p == 0)
|
|
sysfatal("strdup(%.20s) fails", p);
|
|
return p;
|
|
}
|
|
|
|
char*
|
|
estrpath(char *p, char *q, int frog)
|
|
{
|
|
char *r, *s;
|
|
|
|
if(strcmp(q, "..") == 0){
|
|
r = estrdup(p);
|
|
if((s = strrchr(r, '/')) && s > r)
|
|
*s = '\0';
|
|
else if(s == r)
|
|
s[1] = '\0';
|
|
return r;
|
|
}
|
|
|
|
if(frog)
|
|
q = defrog(q);
|
|
else
|
|
q = strdup(q);
|
|
r = emalloc(strlen(p)+1+strlen(q)+1);
|
|
strcpy(r, p);
|
|
if(r[0]=='\0' || r[strlen(r)-1] != '/')
|
|
strcat(r, "/");
|
|
strcat(r, q);
|
|
free(q);
|
|
return r;
|
|
}
|
|
|
|
Fid *fidtab[1];
|
|
|
|
Fid*
|
|
lookupfid(int fid)
|
|
{
|
|
Fid *f;
|
|
|
|
for(f=fidtab[fid%nelem(fidtab)]; f; f=f->next)
|
|
if(f->fid == fid)
|
|
return f;
|
|
return nil;
|
|
}
|
|
|
|
Fid*
|
|
newfid(int fid, char **ep)
|
|
{
|
|
Fid *f;
|
|
|
|
if(lookupfid(fid) != nil){
|
|
*ep = Efidactive;
|
|
return nil;
|
|
}
|
|
|
|
f = emalloc(sizeof(*f));
|
|
f->next = fidtab[fid%nelem(fidtab)];
|
|
if(f->next)
|
|
f->next->prev = f;
|
|
fidtab[fid%nelem(fidtab)] = f;
|
|
f->fid = fid;
|
|
f->fd = -1;
|
|
f->omode = -1;
|
|
return f;
|
|
}
|
|
|
|
Fid*
|
|
newauthfid(int fid, void *magic, char **ep)
|
|
{
|
|
Fid *af;
|
|
af = newfid(fid, ep);
|
|
if (af == nil)
|
|
return nil;
|
|
af->auth = 1;
|
|
af->authmagic = magic;
|
|
return af;
|
|
}
|
|
|
|
Fid*
|
|
oldfidex(int fid, int auth, char **ep)
|
|
{
|
|
Fid *f;
|
|
|
|
if((f = lookupfid(fid)) == nil){
|
|
*ep = Ebadfid;
|
|
return nil;
|
|
}
|
|
|
|
if (auth != -1 && f->auth != auth) {
|
|
*ep = Ebadfid;
|
|
return nil;
|
|
}
|
|
|
|
if (!f->auth) {
|
|
if(userchange(f->u, ep) < 0)
|
|
return nil;
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
Fid*
|
|
oldfid(int fid, char **ep)
|
|
{
|
|
return oldfidex(fid, 0, ep);
|
|
}
|
|
|
|
Fid*
|
|
oldauthfid(int fid, void **magic, char **ep)
|
|
{
|
|
Fid *af;
|
|
af = oldfidex(fid, 1, ep);
|
|
if (af == nil)
|
|
return nil;
|
|
*magic = af->authmagic;
|
|
return af;
|
|
}
|
|
|
|
void
|
|
freefid(Fid *f)
|
|
{
|
|
if(f->prev)
|
|
f->prev->next = f->next;
|
|
else
|
|
fidtab[f->fid%nelem(fidtab)] = f->next;
|
|
if(f->next)
|
|
f->next->prev = f->prev;
|
|
if(f->dir)
|
|
closedir(f->dir);
|
|
if(f->fd)
|
|
close(f->fd);
|
|
free(f->path);
|
|
free(f);
|
|
}
|
|
|
|
int
|
|
fidstat(Fid *fid, char **ep)
|
|
{
|
|
char *rpath;
|
|
|
|
rpath = rootpath(fid->path);
|
|
if(stat(rpath, &fid->st) < 0){
|
|
fprint(2, "fidstat(%s) failed\n", rpath);
|
|
if(ep)
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
if(S_ISDIR(fid->st.st_mode))
|
|
fid->st.st_size = 0;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
userchange(User *u, char **ep)
|
|
{
|
|
if(defaultuser)
|
|
return 0;
|
|
|
|
if(setreuid(0, 0) < 0){
|
|
fprint(2, "setreuid(0, 0) failed\n");
|
|
*ep = "cannot setuid back to root";
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Initgroups does not appear to be SUSV standard.
|
|
* But it exists on SGI and on Linux, which makes me
|
|
* think it's standard enough. We have to do something
|
|
* like this, and the closest other function I can find is
|
|
* setgroups (which initgroups eventually calls).
|
|
* Setgroups is the same as far as standardization though,
|
|
* so we're stuck using a non-SUSV call. Sigh.
|
|
*/
|
|
if(initgroups(u->name, u->defaultgid) < 0)
|
|
fprint(2, "initgroups(%s) failed: %s\n", u->name, strerror(errno));
|
|
|
|
if(setreuid(-1, u->id) < 0){
|
|
fprint(2, "setreuid(-1, %s) failed\n", u->name);
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We do our own checking here, then switch to root temporarily
|
|
* to set our gid. In a perfect world, you'd be allowed to set your
|
|
* egid to any of the supplemental groups of your euid, but this
|
|
* is not the case on Linux 2.2.14 (and perhaps others).
|
|
*
|
|
* This is a race, of course, but it's a race against processes
|
|
* that can edit the group lists. If you can do that, you can
|
|
* change your own group without our help.
|
|
*/
|
|
int
|
|
groupchange(User *u, User *g, char **ep)
|
|
{
|
|
if(g == nil)
|
|
return -1;
|
|
if(!useringroup(u, g)){
|
|
if(chatty9p)
|
|
fprint(2, "%s not in group %s\n", u->name, g->name);
|
|
*ep = Enotingroup;
|
|
return -1;
|
|
}
|
|
|
|
setreuid(0,0);
|
|
if(setregid(-1, g->id) < 0){
|
|
fprint(2, "setegid(%s/%d) failed in groupchange\n", g->name, g->id);
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
if(userchange(u, ep) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* An attempt to enforce permissions by looking at the
|
|
* file system. Separation of checking permission and
|
|
* actually performing the action is a terrible idea, of
|
|
* course, so we use setreuid for most of the permission
|
|
* enforcement. This is here only so we can give errors
|
|
* on open(ORCLOSE) in some cases.
|
|
*/
|
|
int
|
|
userperm(User *u, char *path, int type, int need)
|
|
{
|
|
char *p, *q, *rpath;
|
|
int i, have;
|
|
struct stat st;
|
|
User *g;
|
|
|
|
switch(type){
|
|
default:
|
|
fprint(2, "bad type %d in userperm\n", type);
|
|
return -1;
|
|
case Tdot:
|
|
rpath = rootpath(path);
|
|
if(stat(rpath, &st) < 0){
|
|
fprint(2, "userperm: stat(%s) failed\n", rpath);
|
|
return -1;
|
|
}
|
|
break;
|
|
case Tdotdot:
|
|
rpath = rootpath(path);
|
|
p = estrdup(rpath);
|
|
if((q = strrchr(p, '/'))==nil){
|
|
fprint(2, "userperm(%s, ..): bad path\n", p);
|
|
free(p);
|
|
return -1;
|
|
}
|
|
if(q > p)
|
|
*q = '\0';
|
|
else
|
|
*(q+1) = '\0';
|
|
if(stat(p, &st) < 0){
|
|
fprint(2, "userperm: stat(%s) (dotdot of %s) failed\n",
|
|
p, rpath);
|
|
free(p);
|
|
return -1;
|
|
}
|
|
free(p);
|
|
break;
|
|
}
|
|
|
|
if(u == none){
|
|
fprint(2, "userperm: none wants %d in 0%luo\n", need, st.st_mode);
|
|
have = st.st_mode&7;
|
|
if((have&need)==need)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
have = st.st_mode&7;
|
|
if((uid_t)u->id == st.st_uid)
|
|
have |= (st.st_mode>>6)&7;
|
|
if((have&need)==need)
|
|
return 0;
|
|
if(((have|((st.st_mode>>3)&7))&need) != need) /* group won't help */
|
|
return -1;
|
|
g = gid2user(st.st_gid);
|
|
for(i=0; i<g->nmem; i++){
|
|
if(strcmp(g->mem[i], u->name) == 0){
|
|
have |= (st.st_mode>>3)&7;
|
|
break;
|
|
}
|
|
}
|
|
if((have&need)==need)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
userwalk(User *u, char **path, char *elem, Qid *qid, char **ep)
|
|
{
|
|
char *npath, *rpath;
|
|
struct stat st;
|
|
|
|
npath = estrpath(*path, elem, 1);
|
|
rpath = rootpath(npath);
|
|
if(stat(rpath, &st) < 0){
|
|
free(npath);
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
*qid = stat2qid(&st);
|
|
free(*path);
|
|
*path = npath;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
useropen(Fid *fid, int omode, char **ep)
|
|
{
|
|
int a, o;
|
|
char *rpath;
|
|
|
|
/*
|
|
* Check this anyway, to try to head off problems later.
|
|
*/
|
|
if((omode&ORCLOSE) && userperm(fid->u, fid->path, Tdotdot, W_OK) < 0){
|
|
*ep = Eperm;
|
|
return -1;
|
|
}
|
|
|
|
switch(omode&3){
|
|
default:
|
|
*ep = "programmer error";
|
|
return -1;
|
|
case OREAD:
|
|
a = R_OK;
|
|
o = O_RDONLY;
|
|
break;
|
|
case ORDWR:
|
|
a = R_OK|W_OK;
|
|
o = O_RDWR;
|
|
break;
|
|
case OWRITE:
|
|
a = W_OK;
|
|
o = O_WRONLY;
|
|
break;
|
|
case OEXEC:
|
|
a = X_OK;
|
|
o = O_RDONLY;
|
|
break;
|
|
}
|
|
if(omode & OTRUNC){
|
|
a |= W_OK;
|
|
o |= O_TRUNC;
|
|
}
|
|
|
|
if(S_ISDIR(fid->st.st_mode)){
|
|
if(a != R_OK){
|
|
fprint(2, "attempt by %s to open dir %d\n", fid->u->name, omode);
|
|
*ep = Eperm;
|
|
return -1;
|
|
}
|
|
rpath = rootpath(fid->path);
|
|
if((fid->dir = opendir(rpath)) == nil){
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
}else{
|
|
/*
|
|
* This is wrong because access used the real uid
|
|
* and not the effective uid. Let the open sort it out.
|
|
*
|
|
if(access(fid->path, a) < 0){
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
*
|
|
*/
|
|
rpath = rootpath(fid->path);
|
|
if((fid->fd = open(rpath, o)) < 0){
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
}
|
|
fid->omode = omode;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
usercreate(Fid *fid, char *elem, int omode, long perm, char **ep)
|
|
{
|
|
int o, m;
|
|
char *opath, *npath, *rpath;
|
|
struct stat st, parent;
|
|
User *u;
|
|
|
|
rpath = rootpath(fid->path);
|
|
if(stat(rpath, &parent) < 0){
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Change group so that created file has expected group
|
|
* by Plan 9 semantics. If that fails, might as well go
|
|
* with the user's default group.
|
|
*/
|
|
if(groupchange(fid->u, gid2user(parent.st_gid), ep) < 0
|
|
&& groupchange(fid->u, gid2user(fid->u->defaultgid), ep) < 0)
|
|
return -1;
|
|
|
|
m = (perm & DMDIR) ? 0777 : 0666;
|
|
perm = perm & (~m | (fid->st.st_mode & m));
|
|
|
|
npath = estrpath(rpath, elem, 1);
|
|
if(perm & DMDIR){
|
|
if((omode&~ORCLOSE) != OREAD){
|
|
*ep = Eperm;
|
|
free(npath);
|
|
return -1;
|
|
}
|
|
if(stat(npath, &st) >= 0 || errno != ENOENT){
|
|
*ep = Eexist;
|
|
free(npath);
|
|
return -1;
|
|
}
|
|
/* race */
|
|
if(mkdir(npath, perm&0777) < 0){
|
|
*ep = strerror(errno);
|
|
free(npath);
|
|
return -1;
|
|
}
|
|
if((fid->dir = opendir(npath)) == nil){
|
|
*ep = strerror(errno);
|
|
remove(npath); /* race */
|
|
free(npath);
|
|
return -1;
|
|
}
|
|
}else{
|
|
o = O_CREAT|O_EXCL;
|
|
switch(omode&3){
|
|
default:
|
|
*ep = "programmer error";
|
|
return -1;
|
|
case OREAD:
|
|
case OEXEC:
|
|
o |= O_RDONLY;
|
|
break;
|
|
case ORDWR:
|
|
o |= O_RDWR;
|
|
break;
|
|
case OWRITE:
|
|
o |= O_WRONLY;
|
|
break;
|
|
}
|
|
if(omode & OTRUNC)
|
|
o |= O_TRUNC;
|
|
if((fid->fd = open(npath, o, perm&0777)) < 0){
|
|
if(chatty9p)
|
|
fprint(2, "create(%s, 0x%x, 0%o) failed\n", npath, o, perm&0777);
|
|
*ep = strerror(errno);
|
|
free(npath);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Change ownership if a default user is specified.
|
|
*/
|
|
if(defaultuser)
|
|
if((u = uname2user(defaultuser)) == nil
|
|
|| chown(npath, u->id, -1) < 0){
|
|
fprint(2, "chown after create on %s failed\n", npath);
|
|
remove(npath); /* race */
|
|
free(npath);
|
|
fid->path = opath;
|
|
if(fid->fd >= 0){
|
|
close(fid->fd);
|
|
fid->fd = -1;
|
|
}else{
|
|
closedir(fid->dir);
|
|
fid->dir = nil;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
opath = fid->path;
|
|
fid->path = estrpath(opath, elem, 1);
|
|
if(fidstat(fid, ep) < 0){
|
|
fprint(2, "stat after create on %s failed\n", npath);
|
|
remove(npath); /* race */
|
|
free(npath);
|
|
fid->path = opath;
|
|
if(fid->fd >= 0){
|
|
close(fid->fd);
|
|
fid->fd = -1;
|
|
}else{
|
|
closedir(fid->dir);
|
|
fid->dir = nil;
|
|
}
|
|
return -1;
|
|
}
|
|
fid->omode = omode;
|
|
free(opath);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
userremove(Fid *fid, char **ep)
|
|
{
|
|
char *rpath;
|
|
|
|
rpath = rootpath(fid->path);
|
|
if(remove(rpath) < 0){
|
|
*ep = strerror(errno);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: u9fs [-Dnz] [-a authmethod] [-m msize] [-u user] [root]\n");
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *authtype;
|
|
int i;
|
|
int fd;
|
|
int logflag;
|
|
|
|
auth = authmethods[0];
|
|
logflag = O_WRONLY|O_APPEND|O_CREAT;
|
|
ARGBEGIN{
|
|
case 'D':
|
|
chatty9p = 1;
|
|
break;
|
|
case 'a':
|
|
authtype = EARGF(usage());
|
|
auth = nil;
|
|
for(i=0; i<nelem(authmethods); i++)
|
|
if(strcmp(authmethods[i]->name, authtype)==0)
|
|
auth = authmethods[i];
|
|
if(auth == nil)
|
|
sysfatal("unknown auth type '%s'", authtype);
|
|
break;
|
|
case 'A':
|
|
autharg = EARGF(usage());
|
|
break;
|
|
case 'l':
|
|
logfile = EARGF(usage());
|
|
break;
|
|
case 'm':
|
|
msize = strtol(EARGF(usage()), 0, 0);
|
|
break;
|
|
case 'n':
|
|
network = 0;
|
|
break;
|
|
case 'u':
|
|
defaultuser = EARGF(usage());
|
|
break;
|
|
case 'z':
|
|
logflag |= O_TRUNC;
|
|
}ARGEND
|
|
|
|
if(argc > 1)
|
|
usage();
|
|
|
|
fd = open(logfile, logflag, 0666);
|
|
if(fd < 0)
|
|
sysfatal("cannot open log '%s'", logfile);
|
|
|
|
if(fd != 2){
|
|
if(dup2(fd, 2) < 0)
|
|
sysfatal("cannot dup fd onto stderr");
|
|
close(fd);
|
|
}
|
|
fprint(2, "u9fs\nkill %d\n", (int)getpid());
|
|
|
|
fmtinstall('F', fcallconv);
|
|
fmtinstall('D', dirconv);
|
|
fmtinstall('M', dirmodeconv);
|
|
|
|
rxbuf = emalloc(msize);
|
|
txbuf = emalloc(msize);
|
|
databuf = emalloc(msize);
|
|
|
|
if(auth->init)
|
|
auth->init();
|
|
|
|
if(network)
|
|
getremotehostname(remotehostname, sizeof remotehostname);
|
|
|
|
if(gethostname(hostname, sizeof hostname) < 0)
|
|
strcpy(hostname, "gnot");
|
|
|
|
umask(0);
|
|
|
|
if(argc == 1)
|
|
root = argv[0];
|
|
|
|
none = uname2user("none");
|
|
|
|
serve(0, 1);
|
|
return 0;
|
|
}
|