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

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


/* Copyright © 2008 Fazlul Shahriar*/

#include "fxp.h"

enum{
	MaxPktLen	= 34000,
	FMsgid	= 0x99999999,
};

enum{
	/* Fxp.type */
	SSH_FXP_INIT = 1,
	SSH_FXP_VERSION = 2,	/* reply */
	SSH_FXP_OPEN = 3,
	SSH_FXP_CLOSE = 4,
	SSH_FXP_READ = 5,
	SSH_FXP_WRITE = 6,
	SSH_FXP_LSTAT = 7,
	SSH_FXP_FSTAT = 8,
	SSH_FXP_SETSTAT = 9,
	SSH_FXP_FSETSTAT = 10,
	SSH_FXP_OPENDIR = 11,
	SSH_FXP_READDIR = 12,
	SSH_FXP_REMOVE = 13,
	SSH_FXP_MKDIR = 14,
	SSH_FXP_RMDIR = 15,
	SSH_FXP_REALPATH = 16,
	SSH_FXP_STAT = 17,
	SSH_FXP_RENAME = 18,
	SSH_FXP_READLINK = 19,
	SSH_FXP_SYMLINK = 20,
	SSH_FXP_STATUS = 101,	/* reply */
	SSH_FXP_HANDLE = 102,	/* reply */
	SSH_FXP_DATA = 103,	/* reply */
	SSH_FXP_NAME = 104,	/* reply */
	SSH_FXP_ATTRS = 105,	/* reply */
	SSH_FXP_EXTENDED = 200,
	SSH_FXP_EXTENDED_REPLY = 201,
	
	/* Fxp.status */
	SSH_FX_OK = 0,
	SSH_FX_EOF = 1,
	SSH_FX_NO_SUCH_FILE = 2,
	SSH_FX_PERMISSION_DENIED = 3,
	SSH_FX_FAILURE = 4,
	SSH_FX_BAD_MESSAGE = 5,
	SSH_FX_NO_CONNECTION = 6,
	SSH_FX_CONNECTION_LOST = 7,
	SSH_FX_OP_UNSUPPORTED = 8,
	
	/* Fxp.pflags */
	SSH_FXF_READ = 0x00000001,
	SSH_FXF_WRITE = 0x00000002,
	SSH_FXF_APPEND = 0x00000004,
	SSH_FXF_CREAT = 0x00000008,
	SSH_FXF_TRUNC = 0x00000010,
	SSH_FXF_EXCL = 0x00000020,
	
	/* FAttrs.flags */
	SSH_FILEXFER_ATTR_SIZE = 0x00000001,
	SSH_FILEXFER_ATTR_UIDGID = 0x00000002,
	SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004,
	SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008,
	SSH_FILEXFER_ATTR_EXTENDED = 0x80000000,
};

typedef struct FAttrs FAttrs;
typedef struct FName FName;
typedef struct Fxp Fxp;

/* File Attributes */
struct FAttrs{
	u32int	flags;
	u64int	size;
	u32int	uid;
	u32int	gid;
	u32int	perm;
	u32int	atime;
	u32int	mtime;
	/*u32int	nextended; */
};

struct Fxp{
	uchar	type;
	u32int	id;	/* except Init and Version */
	union{
		struct{	/* Init,Version */
			u32int	version;
		};
		struct{	/* Open/Read/Write/Data */
			String	filename;	/* Remove */
			u32int	pflags;
			String	handle;	/* Handle,Close,Readdir,Fstat */
			u64int	offset;
			u32int	len;
			String	data;
			/* Rmdir,Opendir,Realpath,Stat,Lstat,Readlink */
			String	path;
			FAttrs	attrs;		/* Fsetstat,Mkdir,Setstat,Attrs */
		};
		struct{	/* Rename */
			String	oldpath;
			String	newpath;
		};
		struct{ /* Symlink */
			String	linkpath;
			String	targetpath;
		};
		struct{	/* Status */
			u32int	status;
			String	errmsg;
			String	lang;
		};
		struct{	/* Name */
			u32int	count;
			String	*filenames;
			String	*longnames;
			FAttrs	*attrsv;
		};
	};
};


enum{
	STACK	= 8192,
};

static int debug = 0;

typedef struct Conn Conn;
struct Conn{
	char	*host;
	char		sshver;
	char	*serverpath;
	Map		*map;
	
	uchar	buf[MaxPktLen];
	Channel	*rdchan;
	Channel	*wrchan;
	int		sshfd;
	int		fd[2];
};

static Conn conn;

static int
put8(u64int v, uchar *a)
{
	a[0] = v>>56;
	a[1] = v>>48;
	a[2] = v>>40;
	a[3] = v>>32;
	a[4] = v>>24;
	a[5] = v>>16;
	a[6] = v>>8;
	a[7] = v;
	return 8;
}

static int
put4(u32int v, uchar *a)
{
	a[0] = v>>24;
	a[1] = v>>16;
	a[2] = v>>8;
	a[3] = v;
	return 4;
}

static int
put1(uchar v, uchar *a)
{
	a[0] = v;
	return 1;
}

static int
putstring(String *s, uchar *buf)
{
	put4(s->len, buf);
	memmove(buf+4, s->s, s->len);
	
	return s->len+4;
}

static int
putattrs(FAttrs *a, uchar *buf)
{
	uchar *bp;
	
	bp = buf;
	bp += put4(a->flags, bp);
	if(a->flags & SSH_FILEXFER_ATTR_SIZE)
		bp += put8(a->size, bp);
	if(a->flags & SSH_FILEXFER_ATTR_UIDGID){
		bp += put4(a->uid, bp);
		bp += put4(a->gid, bp);
	}
	if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
		bp += put4(a->perm, bp);
	if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){
		bp += put4(a->atime, bp);
		bp += put4(a->mtime, bp);
	}
	return bp-buf;
}

static int
fxpattrslen(FAttrs *a)
{
	int n;
	
	n = 4;
	n += (a->flags & SSH_FILEXFER_ATTR_SIZE) ? 8 : 0;
	n += (a->flags & SSH_FILEXFER_ATTR_UIDGID) ? 8 : 0;
	n += (a->flags & SSH_FILEXFER_ATTR_PERMISSIONS) ? 4 : 0;
	n += (a->flags & SSH_FILEXFER_ATTR_ACMODTIME) ? 8 : 0;
	return n;
}
	
static int
fxpencode(Fxp *m, uchar *buf, int bufsz)
{
	uchar *bp;
	
	if(bufsz < MaxPktLen)
		sysfatal("buffer size (%d) too small", bufsz);
	
	bp = buf;
	switch(m->type){
	default:
		sysfatal("encoding msg of unknown type %d", m->type);
		break;
	
	case SSH_FXP_INIT:
		bp += put4(1+4, bp);
		bp += put1(m->type, bp);
		bp += put4(m->version, bp);
		break;
	
	case SSH_FXP_RMDIR:
	case SSH_FXP_OPENDIR:
	case SSH_FXP_REALPATH:
	case SSH_FXP_STAT:
	case SSH_FXP_LSTAT:
	case SSH_FXP_READLINK:
		bp += put4(1+4+4+m->path.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->path, bp);
		break;
	
	case SSH_FXP_READDIR:
	case SSH_FXP_CLOSE:
	case SSH_FXP_FSTAT:
		bp += put4(1+4+4+m->handle.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->handle, bp);
		break;
	
	case SSH_FXP_OPEN:
		bp += put4(1+4+4+m->filename.len+4+fxpattrslen(&m->attrs), bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->filename, bp);
		bp += put4(m->pflags, bp);
		bp += putattrs(&m->attrs, bp);
		break;
	
	case SSH_FXP_READ:
		bp += put4(1+4+4+m->handle.len+8+4, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->handle, bp);
		bp += put8(m->offset, bp);
		bp += put4(m->len, bp);
		break;
	
	case SSH_FXP_WRITE:
		bp += put4(1+4+4+m->handle.len+8+4+m->data.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->handle, bp);
		bp += put8(m->offset, bp);
		bp += putstring(&m->data, bp);
		break;
	
	case SSH_FXP_REMOVE:
		bp += put4(1+4+4+m->filename.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->filename, bp);
		break;
	
	case SSH_FXP_RENAME:
		bp += put4(1+4+4+m->oldpath.len+4+m->newpath.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->oldpath, bp);
		bp += putstring(&m->newpath, bp);
		break;
	
	case SSH_FXP_MKDIR:
	case SSH_FXP_SETSTAT:
		bp += put4(1+4+4+m->path.len+fxpattrslen(&m->attrs), bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->path, bp);
		bp += putattrs(&m->attrs, bp);
		break;
	
	case SSH_FXP_FSETSTAT:
		bp += put4(1+4+4+m->handle.len+fxpattrslen(&m->attrs), bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->handle, bp);
		bp += putattrs(&m->attrs, bp);
		break;
	
	case SSH_FXP_SYMLINK:
		bp += put4(1+4+4+m->linkpath.len+4+m->targetpath.len, bp);
		bp += put1(m->type, bp);
		bp += put4(m->id, bp);
		bp += putstring(&m->linkpath, bp);
		bp += putstring(&m->targetpath, bp);
		break;
	}
	return bp-buf;
}

static int
get8(u64int *x, uchar *a)
{
	u64int v;
	
	v = (uvlong)a[0]<<56;
	v |= (uvlong)a[1]<<48;
	v |= (uvlong)a[2]<<40;
	v |= (uvlong)a[3]<<32;
	v |= a[4]<<24;
	v |= a[5]<<16;
	v |= a[6]<<8;
	v |= a[7]<<0;
	*x = v;
	return 8;
}

static int
get4(u32int *x, uchar *a)
{
	*x = (a[0]<<24)|(a[1]<<16)|(a[2]<<8)|(a[3]<<0);
	return 4;
}

static int
get1(uchar *x, uchar *a)
{
	*x = a[0];
	return 1;
}

static int
getstring(String *s, uchar *buf)
{
	uchar *bp;
	
	bp = buf;
	bp += get4(&s->len, bp);
	s->s = emalloc9p(s->len+1);
	memmove(s->s, bp, s->len);
	bp += s->len;
	s->s[s->len] = 0;
	
	return bp-buf;
}

static int
getattrs(FAttrs *a, uchar *buf)
{
	uchar *bp;
	
	bp = buf;
	bp += get4(&a->flags, bp);
	if(a->flags & SSH_FILEXFER_ATTR_SIZE)
		bp += get8(&a->size, bp);
	if(a->flags & SSH_FILEXFER_ATTR_UIDGID){
		bp += get4(&a->uid, bp);
		bp += get4(&a->gid, bp);
	}
	if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS)
		bp += get4(&a->perm, bp);
	if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){
		bp += get4(&a->atime, bp);
		bp += get4(&a->mtime, bp);
	}
	return bp-buf;
}

static Fxp*
fxpdecode(uchar *buf)
{
	Fxp *m;
	int i;
	uchar *bp;
	
	bp = buf;
	bp += 4;		/* ignore length */
	m = emalloc9p(sizeof(*m));
	bp += get1(&m->type, bp);
	
	switch(m->type){
	default:
		sysfatal("bad msg type %d when decoding", m->type);
		break;
	
	case SSH_FXP_VERSION:
		bp += get4(&m->version, bp);
		break;
		
	case SSH_FXP_HANDLE:
		bp += get4(&m->id, bp);
		bp += getstring(&m->handle, bp);
		break;
		
	case SSH_FXP_DATA:
		bp += get4(&m->id, bp);
		bp += getstring(&m->data, bp);
		break;
		
	case SSH_FXP_STATUS:
		bp += get4(&m->id, bp);
		bp += get4(&m->status, bp);
		bp += getstring(&m->errmsg, bp);
		bp += getstring(&m->lang, bp);
		break;
	
	case SSH_FXP_NAME:
		bp += get4(&m->id, bp);
		bp += get4(&m->count, bp);
		m->filenames = emalloc9p(m->count*sizeof(*m->filenames));
		m->longnames = emalloc9p(m->count*sizeof(*m->longnames));
		m->attrsv = emalloc9p(m->count*sizeof(*m->attrsv));
		for(i = 0; i < m->count; i++){
			bp += getstring(&m->filenames[i], bp);
			bp += getstring(&m->longnames[i], bp);
			bp += getattrs(&m->attrsv[i], bp);
		}
		break;
	
	case SSH_FXP_ATTRS:
		bp += get4(&m->id, bp);
		bp += getattrs(&m->attrs, bp);
		break;
	}
	USED(bp);
	return m;
}

static void
sshproc(void*)
{
	int *p;
	
	threadsetname("sshproc");
	
	p = conn.fd;
	close(p[0]);
	dup(p[1], 0);
	dup(p[1], 1);
	close(p[1]);
	
	switch(conn.sshver){
	case '1':
		procexecl(nil, "/bin/ssh1", "ssh1", "-P", "-m", "-I", "-f",
			conn.host, conn.serverpath, nil);
		break;
	case '2':
		procexecl(nil, "/bin/ssh", "ssh", "-m", "-i", "-C", "-s", "sftp", conn.host, nil);
		break;
	case 'o':
		procexecl(nil, "/bin/openssh/ssh", "ssh", "-x", "-a",
			"-oClearAllForwardings=yes", "-2", conn.host, "-s", "sftp", nil);
		break;
	}
	sysfatal("exec ssh: %r");
}

static void
serverproc(void*)
{
	uchar *buf;
	Fxp *req, *reply;
	int fd;
	uint n;
	
	threadsetname("serverproc");
	
	fd = conn.sshfd;
	for(;;){
		req = recvp(conn.rdchan);
		if(req == nil)
			threadexits(nil);
		reply = nil;
		
		n = fxpencode(req, conn.buf, sizeof conn.buf);
		buf = conn.buf;
		if(debug)
			hexdump("sending: ", buf, n);
		if(write(fd, buf, n) != n)
			goto sendreply;
		if(readn(fd, buf, 4) != 4)
			goto sendreply;
		get4(&n, buf);
		if(n >= sizeof(conn.buf))
			sysfatal("reply silly big (%d > %d)\n", n, sizeof(conn.buf));
		if(debug)
			fprint(2, "response length: %d\n", n);
		if(readn(fd, buf+4, n) != n)
			goto sendreply;
		if(debug)
			hexdump("response: ", buf, n+4);
		reply = fxpdecode(buf);
		
		if(req->type != SSH_FXP_INIT)
		if(req->id != reply->id)
			sysfatal("reply id doesn't match request id");
sendreply:
		sendp(conn.wrchan, reply);
	}
}

static Fxp*
fxpgetreply(Fxp *msg)
{
	Fxp *r;
	
	sendp(conn.rdchan, msg);
	r = recvp(conn.wrchan);
	if(r == nil)	/* probably ssh exited */
		threadexitsall(nil);
	return r;
}

static void
stringinit(String *s, char *t)
{
	s->s = (uchar*)estrdup9p(t);
	s->len = strlen(t);
}

static void
stringcpy(String *dst, String *src)
{
	dst->len = src->len;
	dst->s = emalloc9p(src->len+1);
	memmove(dst->s, src->s, src->len);
	dst->s[dst->len] = 0;
}

static String*
stringdup(String *s)
{
	String *q;
	
	q = emalloc9p(sizeof *q);
	q->len = s->len;
	q->s = emalloc9p(s->len+1);
	memmove(q->s, s->s, s->len);
	q->s[q->len] = 0;
	return q;
}

static void
freestring(String *s)
{
	if(s){
		free(s->s);
		free(s);
	}
}

struct{
	u32int	status;
	char	*str;
	int	iserror;
} errtab[] = {
	SSH_FX_OK, "", 0,
	SSH_FX_EOF, "", 0,
	SSH_FX_NO_SUCH_FILE, Enofile, 1,
	SSH_FX_PERMISSION_DENIED, Eperm, 1,
	SSH_FX_FAILURE, Efail, 1,
	SSH_FX_BAD_MESSAGE, Emsg, 1, 
	SSH_FX_NO_CONNECTION, Enocn, 1,
	SSH_FX_CONNECTION_LOST, Elostcn, 1,
	SSH_FX_OP_UNSUPPORTED, Eunsup, 1,
};

static char*
errlookup(u32int status, int *err)
{
	int i;
	
	for(i = 0; i < nelem(errtab); i++)
		if(errtab[i].status == status){
			if(err)
				*err = errtab[i].iserror;
			return errtab[i].str;
		}
	if(err)
		*err = 1;
	return Ebotch;
}
	
static int
fxpwerrstr(u32int status)
{
	int i;
	
	for(i = 0; i < nelem(errtab); i++)
		if(errtab[i].status == status){
			werrstr(errtab[i].str);
			return errtab[i].iserror;
		}
	werrstr("%s", Ebotch);
	return 1;
}

static void
freefxp(Fxp *m)
{
	int i;
	
	if(m == nil)
		return;
	switch(m->type){
	default:
		fprint(2, "how to free msg of type %d?\n", m->type);
		break;
	
	case SSH_FXP_INIT:
	case SSH_FXP_VERSION:
	case SSH_FXP_ATTRS:
		break;
	
	case SSH_FXP_OPEN:
	case SSH_FXP_REMOVE:
		free(m->filename.s);
		break;
	
	case SSH_FXP_CLOSE:
	case SSH_FXP_READ:
	case SSH_FXP_READDIR:
	case SSH_FXP_FSTAT:
	case SSH_FXP_FSETSTAT:
	case SSH_FXP_HANDLE:
		free(m->handle.s);
		break;
	
	case SSH_FXP_WRITE:
		free(m->handle.s);
		free(m->data.s);
		break;
		
	case SSH_FXP_RENAME:
		free(m->oldpath.s);
		free(m->newpath.s);
		break;
	
	case SSH_FXP_MKDIR:
	case SSH_FXP_RMDIR:
	case SSH_FXP_OPENDIR:
	case SSH_FXP_STAT:
	case SSH_FXP_LSTAT:
	case SSH_FXP_SETSTAT:
	case SSH_FXP_READLINK:
	case SSH_FXP_REALPATH:
		free(m->path.s);
		break;
	
	case SSH_FXP_SYMLINK:
		free(m->linkpath.s);
		free(m->targetpath.s);
		break;
	
	case SSH_FXP_STATUS:
		free(m->errmsg.s);
		free(m->lang.s);
		break;
	
	case SSH_FXP_DATA:
		free(m->data.s);
		break;
	
	case SSH_FXP_NAME:
		for(i = 0; i < m->count; i++){
			free(m->filenames[i].s);
			free(m->longnames[i].s);
		}
		free(m->filenames);
		free(m->longnames);
		free(m->attrsv);
		break;
	}
	free(m);
}

static FHandle*
replyhandle(Fxp *m)
{
	Fxp *r;
	FHandle *h;
	
	r = fxpgetreply(m);
	freefxp(m);
	switch(r->type){
	default:
		werrstr("%s", Ebotch);
		freefxp(r);
		return nil;
	case SSH_FXP_STATUS:
		fxpwerrstr(r->status);
		freefxp(r);
		return nil;
	case SSH_FXP_HANDLE:
		h = stringdup(&r->handle);
		freefxp(r);
		return h;
	}
}

static int
replystatus(Fxp *r)
{
	int err;
	
	switch(r->type){
	default:
		werrstr("%s", Ebotch);
		freefxp(r);
		return 1;
	case SSH_FXP_STATUS:
		err = fxpwerrstr(r->status);
		freefxp(r);
		return err;
	}
}
	
static Fxp*
newfxp(uchar type)
{
	Fxp *m;
	
	m = emalloc9p(sizeof *m);
	m->type = type;
	m->id = FMsgid;
	return m;
}

int
fxpremove(char *file)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_REMOVE);
	stringinit(&m->filename, file);
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}

int
fxprmdir(char *path)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_RMDIR);
	stringinit(&m->path, path);
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}

int
fxpmkdir(char *path, ulong perm)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_MKDIR);
	stringinit(&m->path, path);
	m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
	m->attrs.perm = perm&0777;
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}

long
fxpwrite(FHandle *h, void *buf, long nbuf, vlong off)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_WRITE);
	stringcpy(&m->handle, h);
	m->offset = off;
	m->data.len = nbuf;
	m->data.s = emalloc9p(nbuf+1);
	memmove(m->data.s, buf, nbuf);
	m->data.s[m->data.len] = 0;
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : nbuf;
}

int
fxpread(FHandle *h, void *buf, long nbuf, vlong off)
{
	Fxp *m, *r;
	int n;
	
	m = newfxp(SSH_FXP_READ);
	stringcpy(&m->handle, h);
	m->offset = off;
	m->len = nbuf;
	r = fxpgetreply(m);
	freefxp(m);
	
	if(r->type == SSH_FXP_DATA){
		n = r->data.len;
		memmove(buf, r->data.s, n);
		freefxp(r);
		return n;
	}
	return replystatus(r) ? -1 : 0;
}

FHandle*
fxpcreate(char *file, int omode, ulong perm)
{
	int mode;
	Fxp *m;
	
	mode = SSH_FXF_CREAT | SSH_FXF_TRUNC;
	if(omode&OREAD)
		mode |= SSH_FXF_READ;
	if(omode&OWRITE)
		mode |= SSH_FXF_WRITE;
	if(omode&ORDWR)
		mode |= SSH_FXF_READ | SSH_FXF_WRITE;
	
	m = newfxp(SSH_FXP_OPEN);
	stringinit(&m->filename, file);
	m->pflags = mode;
	m->attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;
	m->attrs.perm = perm&0777;
	
	return replyhandle(m);
}

FHandle*
fxpopen(char *file, int omode)
{
	Fxp *m;
	int mode;
	
	/*
	 * We can't do any permission checks for OEXEC or
	 * ORCLOSE, as we don't know the UID of the user or what
	 * group(s) the user belongs to.
	 */
	mode = 0;
	if(omode&OREAD)
		mode |= SSH_FXF_READ;
	if(omode&OWRITE)
		mode |= SSH_FXF_WRITE;
	if(omode&ORDWR)
		mode |= SSH_FXF_READ | SSH_FXF_WRITE;
	/*
	 * If file doesn't exists, there should be no Topen,
	 * as Twalk will fail. So, the CREAT flag is here only to
	 * statisfy sftp protocol.
	 */
	if(omode&OTRUNC)
		mode |= SSH_FXF_TRUNC | SSH_FXF_CREAT;
	
	m = newfxp(SSH_FXP_OPEN);
	stringinit(&m->filename, file);
	m->pflags = mode;
	m->attrs.flags = 0;
	
	return replyhandle(m);
}

void
freedir(Dir *d)
{
	if(d){
		free(d->uid);
		free(d->gid);
		free(d->muid);
		free(d->name);
		free(d);
	}
}

enum {
	UIDSz = 20,
	ATDIR = 0040000,
};

static void
attrs2dir(Dir *d, FAttrs *a, char *name)
{
	/* caller should fill in Qid properly later */
	d->qid = (Qid){0, 0, QTFILE};
	d->mode = 0;
	d->atime = 0;
	d->mtime = 0;
	d->length = 0;
	d->muid = estrdup9p("unknown");
	d->name = estrdup9p(name);
	if(conn.map == nil){
		d->uid = estrdup9p("unknown");
		d->gid = estrdup9p("unknown");
	}else{
		d->uid = uidtostr(conn.map, a->uid);
		d->gid = gidtostr(conn.map, a->gid);
	}

	if(a->flags & SSH_FILEXFER_ATTR_SIZE)
		d->length = a->size;
	if(a->flags & SSH_FILEXFER_ATTR_PERMISSIONS){
		d->mode = a->perm&0777;
		if(a->perm & ATDIR){
			d->mode |= DMDIR;
			d->qid.type = QTDIR;
		}
	}
	if(a->flags & SSH_FILEXFER_ATTR_ACMODTIME){
		d->atime = a->atime;
		d->mtime = a->mtime;
	}
}

static char*
basename(char *p)
{
	char *s;
	
	s = strrchr(p, '/');
	if(s == nil)
		s = p;
	else
		s++;
	return estrdup9p(s);
}

Dir*
fxpstat1(char *name, char **err)
{
	Fxp *m, *r;
	Dir *d;
	
	m = newfxp(SSH_FXP_STAT);
	stringinit(&m->path, name);
	r = fxpgetreply(m);
	freefxp(m);
	
	/* walk1 kludge. We can't use responderror there */
	if(err)
		*err = errlookup(r->status, nil);
	
	if(r->type == SSH_FXP_ATTRS){
		d = emalloc9p(sizeof *d);
		name = basename(name);
		attrs2dir(d, &r->attrs, name);
		free(name);
		freefxp(r);
		return d;
	}
	return replystatus(r) ? nil : nil;
}

Dir*
fxpstat(char *name)
{
	return fxpstat1(name, nil);
}

static void
dir2attrs(FAttrs *a, Dir *d, Dir *od)
{
	a->flags = 0;
	if(d->length != ~0){
		a->flags |= SSH_FILEXFER_ATTR_SIZE;
		a->size = d->length;
	}
	if(d->mode != ~0){
		a->flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
		a->perm = d->mode&0777;
	}
	d->atime = od->atime;
	d->mtime = od->mtime;
	if(d->atime != ~0)
		a->atime = d->atime;
	if(d->mtime != ~0)
		a->mtime = d->mtime;
	if(d->atime != ~0 || d->mtime != ~0)
		a->flags |= SSH_FILEXFER_ATTR_ACMODTIME;
}

int
fxpsetstat(char *name, Dir *d)
{
	Fxp *m, *r;
	Dir *od;

	if((od = fxpstat(name)) == nil)
		return -1;
	m = newfxp(SSH_FXP_SETSTAT);
	stringinit(&m->path, name);
	dir2attrs(&m->attrs, d, od);
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}

int
fxprename(char *old, char *new)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_RENAME);
	stringinit(&m->oldpath, old);
	stringinit(&m->newpath, new);
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}

long
fxpreaddir(FHandle *h, Dir ***buf)
{
	Fxp *m, *r;
	int i, j;
	char *name;
	Dir **ds;
	FAttrs *a;
	
	m = newfxp(SSH_FXP_READDIR);
	stringcpy(&m->handle, h);
	r = fxpgetreply(m);
	freefxp(m);

	if(r->type == SSH_FXP_NAME){
		ds = emalloc9p(r->count*sizeof(Dir*));
		j = 0;
		for(i = 0; i < r->count; i++){
			name = (char*)r->filenames[i].s;
			a = &r->attrsv[i];
			if(strcmp(name, ".") != 0 && strcmp(name, "..") != 0){
				ds[j] = emalloc9p(sizeof(Dir));
				attrs2dir(ds[j], a, name);
				j++;
			}
		}
		*buf = ds;
		freefxp(r);
		return j;
	}
	return replystatus(r) ? -1 : 0;
}

int
fxpclose(FHandle *h)
{
	Fxp *m, *r;
	
	m = newfxp(SSH_FXP_CLOSE);
	stringcpy(&m->handle, h);
	freestring(h);
	r = fxpgetreply(m);
	freefxp(m);
	
	return replystatus(r) ? -1 : 0;
}


FHandle*
fxpopendir(char *path)
{
	Fxp *m;
	
	m = newfxp(SSH_FXP_OPENDIR);
	stringinit(&m->path, path);
	
	return replyhandle(m);
}

static int
fxpversion(int ver)
{
	Fxp *m, *r;
	int n;
	
	m = newfxp(SSH_FXP_INIT);
	m->version = ver;
	r = fxpgetreply(m);
	freefxp(m);
	
	if(r->type != SSH_FXP_VERSION){
		freefxp(r);
		return -1;
	}
	n = r->version;
	freefxp(r);
	return n;
}

int
fxpinit(char *host, char ver, char *path)
{
	int *p, n;
	
	p = conn.fd;
	if(pipe(p) < 0)
		sysfatal("pipe: %r");
	conn.host = estrdup9p(host);
	conn.sshver = ver;
	conn.serverpath = estrdup9p(path);
	procrfork(sshproc, nil, STACK, RFFDG);
	close(p[1]);
	conn.sshfd = p[0];
	
	conn.rdchan = chancreate(sizeof(Fxp*), 0);
	conn.wrchan = chancreate(sizeof(Fxp*), 0);
	proccreate(serverproc, nil, STACK);
	
	if((n = fxpversion(3)) != 3){
		werrstr("got protocol version %d; want 3", n);
		return -1;
	}
	return 0;
}

void
fxpreadmap(char *pfile, char *gfile)
{
	conn.map = readmap(pfile, gfile);
}

void
fxpterm(void)
{
	close(conn.sshfd);
	sendp(conn.rdchan, nil); 	/* terminate serverproc */
	chanfree(conn.rdchan);
	chanfree(conn.wrchan);
	free(conn.host);
	free(conn.serverpath);
	if(conn.map)
		closemap(conn.map);
}

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.