// Put
// 8c -w actionfs.c && 8l actionfs.8 && mv 8.out /usr/maht/bin/386/actionfs
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <regexp.h>
#include <stdio.h>
typedef struct Path Path;
struct Path
{
Qid qid;
char *name;
Path *next;
};
Reprog *freg;
Path *root = nil;
int nmatches;
int client = 0;
char *aread = "action-read";
char *awrite = "action-write";
char *astat = "action-stat";
static void
fsattach(Req *r)
{
r->ofcall.qid = (Qid){0, ++client, QTDIR};
r->fid->qid = r->ofcall.qid;
respond(r, nil);
}
static void
print_qid(Qid *q) {
print("p %x v %d f %x\n", q->path, q->vers, q->type);
}
static void
print_matches(Resub *matches) {
if(!matches) {
print("No match\n");
return;
}
char *bit;
int i, k;
for(i = 0; i < nmatches; i++) {
k = (matches[i].ep - matches[i].sp) + 1;
bit = (char*)malloc(k);
strecpy(bit, bit + k, matches[i].sp);
free(bit);
}
print("\n");
}
static void
print_path(Path *p) {
print("Name: %s\n", p->name);
print_qid(&p->qid);
print("Next: %x\n", p->next);
}
static Resub*
re(char *txt) {
Resub* matches = (Resub*)calloc(nmatches, sizeof(Resub));
if(regexec(freg, txt, matches, nmatches))
return matches;
free(matches);
return nil;
}
static Path*
find_path(Qid *qid) {
Path *p;
for(p = root; p; p = p->next)
if(qid->path == p->qid.path)
break;
return p;
}
static Path*
find_prev_path(Qid *qid) {
Path *p;
for(p = root; p; p = p->next) {
if(p->next && (qid->path == p->next->qid.path))
break;
}
return p;
}
static Qid*
find_qid(char *name) {
Path *p;
Resub *m;
for(p = root; p; p = p->next)
if(strcmp(name, p->name) == 0)
return &p->qid;
if(!(m = re(name)))
return nil;
free(m);
p = (Path*)mallocz(sizeof(Path), 1);
p->qid.path = root ? root->qid.path +1 : 1;
p->qid.vers = 0;
p->next = root;
p->name = strdup(name);
root = p;
return &root->qid;
}
static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
Qid *q;
if(!(q = find_qid(name)))
return "Not Found";
q->vers++;
memcpy(qid, q, sizeof(Qid));
memcpy(&fid->qid, q, sizeof(Qid));
return nil;
}
static void
fsstat(Req *r)
{
Path *p;
p = find_path(&r->fid->qid);
if(p) {
Dir *d = &r->d;
memset(d, 0, sizeof *d);
d->uid = strdup("inband");
d->gid = strdup("inband");
d->name = strdup(p->name);
d->mode = 0444;
d->qid.path = r->fid->qid.path;
d->qid.vers = r->fid->qid.vers;
d->qid.type = r->fid->qid.type;
d->length = 0;
d->atime = d->mtime = time(0);
respond(r, nil);
} else {
respond(r, "Not found");
}
}
char **
build_argv(int fd, char *name) {
char **argv = malloc(sizeof(char*) * (nmatches + 3));
if(fd > 0)
argv[0] = smprint("%s", aread);
else
argv[0] = smprint("%s", awrite);
argv[1] = smprint("%d", abs(fd));
Resub *matches = re(name);
int i, j, k;
for(i = 0, j = 2; i < nmatches; i++, j++) {
k = (matches[i].ep - matches[i].sp) + 1;
argv[j] = (char*)mallocz(k + 1, 1);
strecpy(argv[j], argv[j] + k, matches[i].sp);
}
argv[j] = nil;
return argv;
}
static char *
do_action(char *action, int fd, Path *p) {
char **argv = build_argv(fd, p->name);
char *error = nil;
int i;
switch(fork()) {
case 0 :
exec(action, argv);
error = "exec failed";
break;
case -1 :
error = "fork failed";
break;
default :
wait();
for(i = 0; i < nmatches+2; i++)
free(argv[i]);
free(argv);
break;
}
return error;
}
static void
fsopen(Req *r)
{
int fd;
switch(r->ifcall.mode & 1) { // discard OTRUNC etc.
case OREAD :
fd = create(tmpnam(nil), ORDWR|ORCLOSE, 0600);
if(fd < 1) { // assume fd 0 is taken !
respond(r, "/tmp/$file create failed");
return;
}
break;
case OWRITE :
fd = create(tmpnam(nil), ORDWR|ORCLOSE, 0600);
if(fd < 1) { // assume fd 0 is taken !
respond(r, "/tmp/$file create failed");
return;
}
fd = -fd;
break;
default :
respond(r, "permission denied");
return;
}
r->fid->aux = (void*)fd;
Path *p = find_path(&r->fid->qid);
char *error = nil;
if(fd > 0)
error = do_action(aread, fd, p);
respond(r, error);
}
static void
remove_path(Path *p) {
Path *pp;
pp = find_prev_path(&p->qid);
if(pp)
pp->next = p->next;
else
root = nil;
free(p->name);
free(p);
}
static void
fsclose(Fid *fid) {
if(fid->aux)
close(abs((int)fid->aux));
Path *p = find_path(&fid->qid);
if(p && p->qid.path) // p *should* always be non null
if(--p->qid.vers == 0)
remove_path(p);
}
static void
fsclunk(Fid *fid) {
Path *p = find_path(&fid->qid);
if((int)fid->aux < 0) {
seek(abs((int)fid->aux), 0, 0);
do_action(awrite, (int)fid->aux, p);
}
if(fid->aux)
close(abs((int)fid->aux));
if(p && p->qid.path) // p *should* always be non null
if(--p->qid.vers == 0)
remove_path(p);
}
static void
fsread(Req *r)
{
seek((int)r->fid->aux, r->ifcall.offset, 0);
int k = read((int)r->fid->aux, r->ofcall.data, r->ifcall.count);
if(k < 0)
respond(r, "Read failed");
r->ofcall.count = k;
respond(r, nil);
}
static void
fswrite(Req *r)
{
seek(abs((int)r->fid->aux), r->ifcall.offset, 0);
int k = write(abs((int)r->fid->aux), r->ifcall.data, r->ifcall.count);
if(k < 0)
respond(r, "Write failed");
r->ofcall.count = k;
respond(r, nil);
}
Srv numsrv = {
.attach= fsattach,
.walk1= fswalk1,
.open= fsopen,
.read= fsread,
.write= fswrite,
.stat= fsstat,
.destroyfid = fsclunk,
};
static int
num_matches(char *txt){
int i = 0;
char *p;
for(p = txt; p ; i++) {
p = strchr(p, '(');
if(p) p++;
}
return i ? i : 1;
}
extern int chatty9p;
void
usage(void)
{
fprint(2, "usage: actionfs [-v servicename] [-m mountpoint] [-t tmpdir] [-r file] [-w file] [-s file] regex\n");
exits("usage");
}
void
main(int argc, char **argv)
{
char *reg;
char *tmp = "/tmp";
char *service = "actionfs";
char *mtpt = nil;
ARGBEGIN{
case 'D':
chatty9p++;
break;
case 'r' :
aread = EARGF(usage());
break;
case 'w':
awrite = EARGF(usage());
break;
case 's' :
astat = EARGF(usage());
break;
case 'v' :
service = EARGF(usage());
if(mtpt == nil)
mtpt = smprint("/n/%s", service);
break;
case 'm' :
mtpt = smprint("%s", EARGF(usage()));
break;
case 't' :
tmp = EARGF(usage());
break;
}ARGEND
if(argc == 1)
reg = argv[0];
else
reg = ".*";
nmatches = num_matches(reg);
freg = regcomp(reg);
if(mtpt == nil)
mtpt=(smprint("/n/actionfs"));
chdir(tmp);
postmountsrv(&numsrv, service, mtpt, MREPL);
free(mtpt);
exits(nil);
}
/*
action fs is invoked with a regular expression
when reads & writes are performed on /n/actionfs/ the actions specified [default /bin/action-read /bin/action-write /bin/action-stat] are executed command line args : fd matches
/bin/tst
#!/bin/rc
{
date
echo $*
echo hello
} > /fd/$1
rm /srv/actionfs; actionfs -r /bin/tst [0-9]+; cat /n/actionfs/41df
Sun Aug 16 08:09:55 GMT 2009
3 41
hello
for reads, the command is executed on open
for writes, the command is executed on clunk
3 3 Zx 3 x
which is 'match[0], matched text, match[0], match[1]'
BUGS
/bin/action-stat not implemented
*/
|