Plan 9 from Bell Labs’s /usr/web/sources/patch/sorry/add-sftpfs/sftpfs.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/* Copyright © 2008 Fazlul Shahriar*/

#include "fxp.h"

typedef struct FAux FAux;
struct FAux {
	char	*path;
	FHandle *handle;
	Dir	**dir;
	int	ndir;
};

static ulong Seed;

/*
 * http://9fans.net/archive/2003/05/56
 * http://9fans.net/archive/2003/05/57
 */
static uvlong
hash(char *p, char *n, ulong seed)
{
	ulong x;
	uvlong xx;

	x = seed;
	while(*p)
		x = x*1103515245 + 12345 + *p++;
	if(n){
		x = x*1103515245 + 12345 + '/';		/* for neatness only */
		while(*n)
			x = x*1103515245 + 12345 + *n++;
	}
	xx = x;
	xx <<= 32;
	xx |= seed;
	return xx;
}

static Qid
getqid(char *path, char *name, Dir *d)
{
	Qid q;
	
	q.path = hash(path, name, Seed);
	q.type = d->mode&DMDIR ? QTDIR : QTFILE;
	q.vers = d->mtime;
	return q;
}

FAux*
newfaux(char *path)
{
	FAux *fa;
	
	fa = emalloc9p(sizeof *fa);
	fa->path = estrdup9p(path);
	fa->handle = nil;
	fa->dir = nil;
	fa->ndir = 0;
	
	return fa;
}

void
freefaux(FAux *fa)
{
	int i;
	
	free(fa->path);
	if(fa->dir != nil){
		for(i = 0; i < fa->ndir; i++)
			freedir(fa->dir[i]);
		free(fa->dir);
	}
	free(fa);
}

static void
fsattach(Req *r)
{
	FAux *fa;
	Dir *d;
	
	if((d = fxpstat("/")) == nil){
		responderror(r);
		return;
	}
	r->fid->qid = getqid("/", "", d);
	freedir(d);
	r->ofcall.qid = r->fid->qid;
	fa = newfaux("/");
	r->fid->aux = fa;
	respond(r, nil);
}

static char*
fsclone(Fid *oldfid, Fid *newfid)
{
	FAux *fa, *ofa;
	
	ofa = oldfid->aux;
	if(ofa->handle != nil)
		return Ehand;
	fa = newfaux(ofa->path);
	newfid->aux = fa;
	
	return nil;
}

static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
	FAux *fa;
	char *s, *dot, *err;
	Dir *d;
	
	fa = fid->aux;
	if(fa->handle != nil)
		return Ehand;
	if(strcmp(name, "..") == 0){
		s = strrchr(fa->path, '/');
		if(s == nil)
			return Epath;
		if(s != fa->path)	/* not / */
			*s = 0;
	}else
		fa->path = eappend(fa->path, "/", name);
	
	d = fxpstat1(fa->path, &err);
	if(d == nil)
		return err;
	fid->qid = getqid(fa->path, "", d);
	*qid = fid->qid;
	freedir(d);
	
	if(d->mode&DMDIR){
		/*
		 * /some/path/ is executable iff /some/path/. is
		 * stat-able
		 */
		dot = estrstrdup(fa->path, "/.");
		if((d = fxpstat1(dot, &err)) == nil)
			return err;
		freedir(d);
		free(dot);
	}
	return nil;
}

static void
fsdestroyfid(Fid *fid)
{
	FAux *fa;
	
	fa = fid->aux;
	if(fa == nil)
		return;
	fid->aux = nil;
	
	if(fa->handle != nil)
		fxpclose(fa->handle);
	freefaux(fa);
}

static void
fsopen(Req *r)
{
	FAux *fa;
	FHandle *h;
	
	fa = r->fid->aux;
	if(fa->handle != nil){
		respond(r, Eopen);
		return;
	}
	if(r->fid->qid.type & QTDIR)
		h = fxpopendir(fa->path);
	else
		h = fxpopen(fa->path, r->ifcall.mode);
	if(h == nil){
		responderror(r);
		return;
	}
	fa->handle = h;
	respond(r, nil);
}

static void
fscreate(Req *r)
{
	FAux *fa, *dir;
	u32int perm;
	uchar mode;
	char *s;
	Dir *d;
	
	dir = r->fid->aux;
	s = estr3dup(dir->path, "/", r->ifcall.name);
	fa = newfaux(s);
	free(s);
	
	mode = r->ifcall.mode;
	perm = r->ifcall.perm;
	if(perm & DMDIR){
		if(fxpmkdir(fa->path, perm) < 0){
			freefaux(fa);
			responderror(r);
			return;
		}
		fa->handle = fxpopendir(fa->path);
	}else{
		fa->handle = fxpcreate(fa->path, mode, perm);
	}
	if(fa->handle == nil){
		freefaux(fa);
		responderror(r);
		return;
	}
	if((d = fxpstat(fa->path)) == nil){
		freefaux(fa);
		responderror(r);
		return;
	}
	r->fid->qid = getqid(fa->path, "", d);
	freedir(d);
	r->ofcall.qid = r->fid->qid;
	r->fid->aux = fa;
	respond(r, nil);
}

static void
fsremove(Req *r)
{
	FAux *fa;
	int n;
	
	fa = r->fid->aux;
	if(r->fid->qid.type & QTDIR)
		n = fxprmdir(fa->path);
	else
		n = fxpremove(fa->path);
	if(n < 0){
		responderror(r);
		return;
	}
	respond(r, nil);
}

static int
getdirent(int n, Dir *d, void *aux)
{
	FAux *fa;
	Dir *e;

	fa = aux;
	if(n >= fa->ndir)
		return -1;
	e = fa->dir[n];
	d->qid = getqid(fa->path, e->name, e);
	d->mode = e->mode;
	d->atime = e->atime;
	d->mtime = e->mtime;
	d->length = e->length;
	d->name = estrdup9p(e->name);
	d->uid = estrdup9p(e->uid);
	d->gid = estrdup9p(e->gid);
	d->muid = estrdup9p(e->muid);
	
	return 0;
}

static void
fsread(Req *r)
{
	FAux *fa;
	uchar *buf;
	long msz, nbuf;
	int n, nds, i;
	vlong off;
	Dir **d, **ds, **dp;
	
	fa = r->fid->aux;
	if(fa->handle == nil){
		respond(r, Ehand);
		return;
	}
	if(r->fid->qid.type & QTDIR){
		if(fa->dir == nil){
			ds = nil;
			nds = 0;
			while((n = fxpreaddir(fa->handle, &d)) > 0){
				ds = erealloc9p(ds, (nds+n)*sizeof(Dir*));
				dp = ds+nds;
				for(i = 0; i < n; i++)
					dp[i] = d[i];
				free(d);
				nds += n;
			}
			if(n < 0){
				free(ds);
				responderror(r);
				return;
			}
			fa->dir = ds;
			fa->ndir = nds;
		}
		dirread9p(r, getdirent, fa);
	}else{
		nbuf = r->ifcall.count;
		buf = (uchar*)r->ofcall.data;
		off = r->ifcall.offset;
		if((msz = fxpread(fa->handle, buf, nbuf, off)) < 0){
			free(buf);
			responderror(r);
			return;
		}
		r->ofcall.count = msz;
	}
	respond(r, nil);
}

static void
fswrite(Req *r)
{
	FAux *fa;
	long n;
	
	fa = r->fid->aux;
	if(fa->handle == nil){
		respond(r, Ehand);
		return;
	}
	n = fxpwrite(fa->handle, r->ifcall.data,
		r->ifcall.count, r->ifcall.offset);
	if(n < 0){
		responderror(r);
		return;
	}
	r->ofcall.count = n;
	respond(r, nil);
}

static void
fsstat(Req *r)
{
	FAux *fa;
	Dir *d;
	
	fa = r->fid->aux;
	d = fxpstat(fa->path);
	if(d == nil){
		responderror(r);
		return;
	}
	r->d.qid = getqid(fa->path, "", d);
	r->d.mode = d->mode;
	r->d.atime = d->atime;
	r->d.mtime = d->mtime;
	r->d.length = d->length;
	r->d.name = estrdup9p(d->name);
	r->d.uid = estrdup9p(d->uid);
	r->d.gid = estrdup9p(d->gid);
	r->d.muid = estrdup9p(d->muid);
	freedir(d);
	respond(r, nil);
}

static char*
dirname(char *path)
{
	char *d, *s;
	
	d = estrdup9p(path);
	s = strrchr(d, '/');
	if(s != nil)
		*s = 0;
	return d;
}

static void
fswstat(Req *r)
{
	FAux *fa;
	char *newp, *dir;
	
	fa = r->fid->aux;
	if(r->d.name[0] == 0){
		if(fxpsetstat(fa->path, &r->d) < 0){
			responderror(r);
			return;
		}
	}else{
		dir = dirname(fa->path);
		newp = estr3dup(dir, "/", r->d.name);
		free(dir);
		if(strchr(fa->path, '/') == nil || strchr(newp, '/') == nil){
			respond(r, Epath);
			return;
		}
		if(fxprename(fa->path, newp) < 0){
			free(newp);
			responderror(r);
			return;
		}
		free(fa->path);
		fa->path = newp;
	}
	respond(r, nil);
}

static void
fsend(Srv*)
{
	fxpterm();
}

static Srv fs = {
	.attach	= fsattach,
	.walk1	= fswalk1,
	.clone	= fsclone,
	.open	= fsopen,
	.create	= fscreate,
	.read	= fsread,
	.write	= fswrite,
	.remove	= fsremove,
	.wstat	= fswstat,
	.stat	= fsstat,
	.destroyfid	= fsdestroyfid,
	.end	= fsend,
};

void
usage(void)
{
	fprint(2, "usage: sftpfs [-12oDU] [-m mountpoint] [-p serverpath ] [-s srvname] [-u passwd group] [user@]hostname\n");
	threadexitsall("usage");
}

static char *pfile;
static char *gfile;
static char *srvname;
static char *host;
static char *mntpt;
static char *serverpath = "/usr/lib/sftp-server";
static int bigu;
static char sshver = '2';

void
threadmain(int argc, char **argv)
{
	char mpath[50], ppath[50], gpath[50];

	ARGBEGIN{
	default:
		usage();
		break;
	case '1':
	case '2':
	case 'o':
		sshver = ARGC();
		break;
	case 'D':
		chatty9p++;
		break;
	case 'm':
		mntpt = EARGF(usage());
		break;
	case 'p':
		serverpath = EARGF(usage());
		break;
	case 's':
		srvname = EARGF(usage());
		break;
	case 'u':
		pfile = EARGF(usage());
		gfile = EARGF(usage());
		break;
	case 'U':
		bigu = 1;
		break;
	}ARGEND;
	
	if(argc < 1)
		usage();
	
	host = argv[0];
	if(fxpinit(host, sshver, serverpath) < 0)
		sysfatal("sftp init: %r");
	
	if(mntpt == nil){
		snprint(mpath, sizeof(mpath), "/n/%s", host);
		mntpt = mpath;
	}
	if(bigu){
		snprint(ppath, sizeof(ppath), "%s/etc/passwd", mntpt);
		pfile = ppath;
		snprint(gpath, sizeof(gpath), "%s/etc/group", mntpt);
		gfile = gpath;
	}

	/*
	 * try to ensure two mounts of the same host get the same qids,
	 * but two mounts of similar hosts get different ones.
	 */
	Seed = hash(host, nil, 0);

	threadpostmountsrv(&fs, srvname, mntpt, MREPL|MCREATE);
	
	if(gfile != nil && pfile != nil)
		fxpreadmap(pfile, gfile);
	threadexits(nil);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.