blob: 45e19416316cf9b6bf770002fc8af6b85de5a278 [file] [log] [blame]
/*
* Remote file system editing client.
* Only talks to acme - external programs do all the hard work.
*
* If you add a plumbing rule:
# /n/ paths go to simulator in acme
kind is text
data matches '[a-zA-Z0-9_\-./]+('$addr')?'
data matches '(/n/[a-zA-Z0-9_\-./]+)('$addr')?'
plumb to netfileedit
plumb client Netfiles
* then plumbed paths starting with /n/ will find their way here.
*
* Perhaps on startup should look for windows named /n/ and attach to them?
* Or might that be too aggressive?
*/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <9pclient.h>
#include <plumb.h>
#include "acme.h"
char *root = "/n/";
void
usage(void)
{
fprint(2, "usage: Netfiles\n");
threadexitsall("usage");
}
extern int chatty9pclient;
int debug;
#define dprint if(debug>1)print
#define cprint if(debug)print
Win *mkwin(char*);
int do3(Win *w, char *arg);
enum {
STACK = 128*1024
};
enum {
Put,
Get,
Del,
Delete,
Debug,
XXX
};
char *cmds[] = {
"Put",
"Get",
"Del",
"Delete",
"Debug",
nil
};
char *debugstr[] = {
"off",
"minimal",
"chatty"
};
typedef struct Arg Arg;
struct Arg
{
char *file;
char *addr;
Channel *c;
};
Arg*
arg(char *file, char *addr, Channel *c)
{
Arg *a;
a = emalloc(sizeof *a);
a->file = estrdup(file);
a->addr = estrdup(addr);
a->c = c;
return a;
}
Win*
winbyid(int id)
{
Win *w;
for(w=windows; w; w=w->next)
if(w->id == id)
return w;
return nil;
}
/*
* return Win* of a window named name or name/
* assumes name is cleaned.
*/
Win*
nametowin(char *name)
{
char *index, *p, *next;
int len, n;
Win *w;
index = winindex();
len = strlen(name);
for(p=index; p && *p; p=next){
if((next = strchr(p, '\n')) != nil)
*next++ = 0;
if(strlen(p) <= 5*12)
continue;
if(memcmp(p+5*12, name, len)!=0)
continue;
if(p[5*12+len]!=' ' && (p[5*12+len]!='/' || p[5*12+len+1]!=' '))
continue;
n = atoi(p);
if((w = winbyid(n)) != nil){
free(index);
return w;
}
}
free(index);
return nil;
}
/*
* look for s in list
*/
int
lookup(char *s, char **list)
{
int i;
for(i=0; list[i]; i++)
if(strcmp(list[i], s) == 0)
return i;
return -1;
}
/*
* move to top of file
*/
void
wintop(Win *w)
{
winaddr(w, "#0");
winctl(w, "dot=addr");
winctl(w, "show");
}
int
isdot(Win *w, uint xq0, uint xq1)
{
uint q0, q1;
winctl(w, "addr=dot");
q0 = winreadaddr(w, &q1);
return xq0==q0 && xq1==q1;
}
/*
* Expand the click further than acme usually does -- all non-white space is okay.
*/
char*
expandarg(Win *w, Event *e)
{
uint q0, q1;
if(e->c2 == 'l') /* in tag - no choice but to accept acme's expansion */
return estrdup(e->text);
winaddr(w, ",");
winctl(w, "addr=dot");
q0 = winreadaddr(w, &q1);
cprint("acme expanded %d-%d into %d-%d (dot %d-%d)\n",
e->oq0, e->oq1, e->q0, e->q1, q0, q1);
if(e->oq0 == e->oq1 && e->q0 != e->q1 && !isdot(w, e->q0, e->q1)){
winaddr(w, "#%ud+#1-/[^ \t\\n]*/,#%ud-#1+/[^ \t\\n]*/", e->q0, e->q1);
q0 = winreadaddr(w, &q1);
cprint("\tre-expand to %d-%d\n", q0, q1);
}else
winaddr(w, "#%ud,#%ud", e->q0, e->q1);
return winmread(w, "xdata");
}
/*
* handle a plumbing message
*/
void
doplumb(void *vm)
{
char *addr;
Plumbmsg *m;
Win *w;
m = vm;
if(m->ndata >= 1024){
fprint(2, "insanely long file name (%d bytes) in plumb message (%.32s...)\n",
m->ndata, m->data);
plumbfree(m);
return;
}
addr = plumblookup(m->attr, "addr");
w = nametowin(m->data);
if(w == nil)
w = mkwin(m->data);
winaddr(w, "%s", addr);
winctl(w, "dot=addr");
winctl(w, "show");
/* windecref(w); */
plumbfree(m);
}
/*
* dispatch messages from the plumber
*/
void
plumbthread(void *v)
{
CFid *fid;
Plumbmsg *m;
threadsetname("plumbthread");
fid = plumbopenfid("netfileedit", OREAD);
if(fid == nil){
fprint(2, "cannot open plumb/netfileedit: %r\n");
return;
}
while((m = plumbrecvfid(fid)) != nil)
threadcreate(doplumb, m, STACK);
fsclose(fid);
}
/*
* parse /n/system/path
*/
int
parsename(char *name, char **server, char **path)
{
char *p, *nul;
cleanname(name);
if(strncmp(name, "/n/", 3) != 0 && name[3] == 0)
return -1;
nul = nil;
if((p = strchr(name+3, '/')) == nil)
*path = estrdup("/");
else{
*path = estrdup(p);
*p = 0;
nul = p;
}
p = name+3;
if(p[0] == 0){
free(*path);
*server = *path = nil;
if(nul)
*nul = '/';
return -1;
}
*server = estrdup(p);
if(nul)
*nul = '/';
return 0;
}
/*
* shell out to find the type of a given file
*/
char*
filestat(char *server, char *path)
{
char *type;
static struct {
char *server;
char *path;
char *type;
} cache;
if(cache.server && strcmp(server, cache.server) == 0)
if(cache.path && strcmp(path, cache.path) == 0){
type = estrdup(cache.type);
cprint("9 netfilestat %q %q => %s (cached)\n", server, path, type);
return type;
}
type = sysrun(2, "9 netfilestat %q %q", server, path);
free(cache.server);
free(cache.path);
free(cache.type);
cache.server = estrdup(server);
cache.path = estrdup(path);
cache.type = estrdup(type);
cprint("9 netfilestat %q %q => %s\n", server, path, type);
return type;
}
/*
* manage a single window
*/
void
filethread(void *v)
{
char *arg, *name, *p, *server, *path, *type;
Arg *a;
Channel *c;
Event *e;
Win *w;
a = v;
threadsetname("file %s", a->file);
w = newwin();
winname(w, a->file);
winprint(w, "tag", "Get Put Look ");
c = wineventchan(w);
goto caseGet;
while((e=recvp(c)) != nil){
if(e->c1!='K')
dprint("acme %E\n", e);
if(e->c1=='M')
switch(e->c2){
case 'x':
case 'X':
switch(lookup(e->text, cmds)){
caseGet:
case Get:
server = nil;
path = nil;
if(parsename(name=wingetname(w), &server, &path) < 0){
fprint(2, "Netfiles: bad name %s\n", name);
goto out;
}
type = filestat(server, path);
if(type == nil)
type = estrdup("");
if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
winaddr(w, ",");
winprint(w, "data", "[reading...]");
winaddr(w, ",");
cprint("9 netfileget %s%q %q\n",
strcmp(type, "file") == 0 ? "" : "-d", server, path);
if(strcmp(type, "file")==0)
twait(pipetowin(w, "data", 2, "9 netfileget %q %q", server, path));
else
twait(pipetowin(w, "data", 2, "9 netfileget -d %q %q | winid=%d mc", server, path, w->id));
cleanname(name);
if(strcmp(type, "directory")==0){
p = name+strlen(name);
if(p[-1] != '/'){
p[0] = '/';
p[1] = 0;
}
}
winname(w, name);
wintop(w);
winctl(w, "clean");
if(a && a->addr){
winaddr(w, "%s", a->addr);
winctl(w, "dot=addr");
winctl(w, "show");
}
}
free(type);
out:
free(server);
free(path);
if(a){
if(a->c){
sendp(a->c, w);
a->c = nil;
}
free(a->file);
free(a->addr);
free(a);
a = nil;
}
break;
case Put:
server = nil;
path = nil;
if(parsename(name=wingetname(w), &server, &path) < 0){
fprint(2, "Netfiles: bad name %s\n", name);
goto out;
}
cprint("9 netfileput %q %q\n", server, path);
if(twait(pipewinto(w, "body", 2, "9 netfileput %q %q", server, path)) >= 0){
cleanname(name);
winname(w, name);
winctl(w, "clean");
}
free(server);
free(path);
break;
case Del:
winctl(w, "del");
break;
case Delete:
winctl(w, "delete");
break;
case Debug:
debug = (debug+1)%3;
print("Netfiles debug %s\n", debugstr[debug]);
break;
default:
winwriteevent(w, e);
break;
}
break;
case 'l':
case 'L':
arg = expandarg(w, e);
if(arg!=nil && do3(w, arg) < 0)
winwriteevent(w, e);
free(arg);
break;
}
}
winfree(w);
}
/*
* handle a button 3 click
*/
int
do3(Win *w, char *text)
{
char *addr, *name, *type, *server, *path, *p, *q;
static char lastfail[1000];
if(text[0] == '/'){
p = nil;
name = estrdup(text);
}else{
p = wingetname(w);
if(text[0] != ':'){
q = strrchr(p, '/');
*(q+1) = 0;
}
name = emalloc(strlen(p)+1+strlen(text)+1);
strcpy(name, p);
if(text[0] != ':')
strcat(name, "/");
strcat(name, text);
}
cprint("button3 %s %s => %s\n", p, text, name);
if((addr = strchr(name, ':')) != nil)
*addr++ = 0;
cleanname(name);
cprint("b3 \t=> name=%s addr=%s\n", name, addr);
if(strcmp(name, lastfail) == 0){
cprint("b3 \t=> nonexistent (cached)\n");
free(name);
return -1;
}
if(parsename(name, &server, &path) < 0){
cprint("b3 \t=> parsename failed\n");
free(name);
return -1;
}
type = filestat(server, path);
free(server);
free(path);
if(strcmp(type, "file")==0 || strcmp(type, "directory")==0){
w = nametowin(name);
if(w == nil){
w = mkwin(name);
cprint("b3 \t=> creating new window %d\n", w->id);
}else
cprint("b3 \t=> reusing window %d\n", w->id);
if(addr){
winaddr(w, "%s", addr);
winctl(w, "dot=addr");
}
winctl(w, "show");
free(name);
free(type);
return 0;
}
/*
* remember last name that didn't exist so that
* only the first right-click is slow when searching for text.
*/
cprint("b3 caching %s => type %s\n", name, type);
strecpy(lastfail, lastfail+sizeof lastfail, name);
free(name);
free(type);
return -1;
}
Win*
mkwin(char *name)
{
Arg *a;
Channel *c;
Win *w;
c = chancreate(sizeof(void*), 0);
a = arg(name, nil, c);
threadcreate(filethread, a, STACK);
w = recvp(c);
chanfree(c);
return w;
}
void
loopthread(void *v)
{
QLock lk;
threadsetname("loopthread");
qlock(&lk);
qlock(&lk);
}
void
threadmain(int argc, char **argv)
{
ARGBEGIN{
case '9':
chatty9pclient = 1;
break;
case 'D':
debug = 1;
break;
default:
usage();
}ARGEND
if(argc)
usage();
cprint("netfiles starting\n");
threadnotify(nil, 0); /* set up correct default handlers */
fmtinstall('E', eventfmt);
doquote = needsrcquote;
quotefmtinstall();
twaitinit();
threadcreate(plumbthread, nil, STACK);
threadcreate(loopthread, nil, STACK);
threadexits(nil);
}