wmii

git clone git://oldgit.suckless.org/wmii/
Log | Files | Refs | README | LICENSE

fs.c (14835B)


      1 /* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
      2  * See LICENSE file for license details.
      3  */
      4 #include "dat.h"
      5 #include <ctype.h>
      6 #include <stdarg.h>
      7 #include <time.h>
      8 #include "fns.h"
      9 
     10 typedef union IxpFileIdU IxpFileIdU;
     11 union IxpFileIdU {
     12 	Bar*		bar;
     13 	Bar**		bar_p;
     14 	CTuple*		col;
     15 	Client*		client;
     16 	Ruleset*	rule;
     17 	View*		view;
     18 	char*		buf;
     19 	void*		ref;
     20 };
     21 
     22 #include <ixp_srvutil.h>
     23 
     24 static IxpPending	events;
     25 static IxpPending	pdebug[NDebugOpt];
     26 
     27 /* Constants */
     28 enum {	/* Dirs */
     29 	FsDBars,
     30 	FsDClient,
     31 	FsDClients,
     32 	FsDDebug,
     33 	FsDTag,
     34 	FsDTags,
     35 	FsRoot,
     36 	/* Files */
     37 	FsFBar,
     38 	FsFCctl,
     39 	FsFClabel,
     40 	FsFColRules,
     41 	FsFCtags,
     42 	FsFDebug,
     43 	FsFEvent,
     44 	FsFKeys,
     45 	FsFRctl,
     46 	FsFRules,
     47 	FsFTctl,
     48 	FsFTindex,
     49 	FsFprops,
     50 };
     51 
     52 /* Error messages */
     53 static char
     54 	Enoperm[] = "permission denied",
     55 	Enofile[] = "file not found",
     56 	Ebadvalue[] = "bad value",
     57 	Einterrupted[] = "interrupted";
     58 
     59 /* Macros */
     60 #define QID(t, i) (((vlong)((t)&0xFF)<<32)|((i)&0xFFFFFFFF))
     61 
     62 /* Global Vars */
     63 /***************/
     64 Ixp9Srv p9srv = {
     65 	.open=	fs_open,
     66 	.walk=	fs_walk,
     67 	.read=	fs_read,
     68 	.stat=	fs_stat,
     69 	.write=	fs_write,
     70 	.clunk=	fs_clunk,
     71 	.flush=	fs_flush,
     72 	.attach=fs_attach,
     73 	.create=fs_create,
     74 	.remove=fs_remove,
     75 	.freefid=fs_freefid
     76 };
     77 
     78 /* ad-hoc file tree. Empty names ("") indicate dynamic entries to be filled
     79  * in by lookup_file
     80  */
     81 static IxpDirtab
     82 dirtab_root[]=	 {{".",		QTDIR,		FsRoot,		0500|DMDIR },
     83 		  {"rbar",	QTDIR,		FsDBars,	0700|DMDIR },
     84 		  {"lbar",	QTDIR,		FsDBars,	0700|DMDIR },
     85 		  {"debug",	QTDIR,		FsDDebug,	0500|DMDIR, FLHide },
     86 		  {"client",	QTDIR,		FsDClients,	0500|DMDIR },
     87 		  {"tag",	QTDIR,		FsDTags,	0500|DMDIR },
     88 		  {"ctl",	QTAPPEND,	FsFRctl,	0600|DMAPPEND },
     89 		  {"colrules",	QTFILE,		FsFColRules,	0600 },
     90 		  {"event",	QTFILE,		FsFEvent,	0600 },
     91 		  {"keys",	QTFILE,		FsFKeys,	0600 },
     92 		  {"rules",	QTFILE,		FsFRules,	0600 },
     93 		  {nil}},
     94 dirtab_clients[]={{".",		QTDIR,		FsDClients,	0500|DMDIR },
     95 		  {"",		QTDIR,		FsDClient,	0500|DMDIR },
     96 		  {nil}},
     97 dirtab_client[]= {{".",		QTDIR,		FsDClient,	0500|DMDIR },
     98 		  {"ctl",	QTAPPEND,	FsFCctl,	0600|DMAPPEND },
     99 		  {"label",	QTFILE,		FsFClabel,	0600 },
    100 		  {"tags",	QTFILE,		FsFCtags,	0600 },
    101 		  {"props",	QTFILE,		FsFprops,	0400 },
    102 		  {nil}},
    103 dirtab_debug[]=  {{".",		QTDIR,		FsDDebug,	0500|DMDIR, FLHide },
    104 		  {"",		QTFILE,		FsFDebug,	0400 },
    105 		  {nil}},
    106 dirtab_bars[]=	 {{".",		QTDIR,		FsDBars,	0700|DMDIR },
    107 		  {"",		QTFILE,		FsFBar,		0600 },
    108 		  {nil}},
    109 dirtab_tags[]=	 {{".",		QTDIR,		FsDTags,	0500|DMDIR },
    110 		  {"",		QTDIR,		FsDTag,		0500|DMDIR },
    111 		  {nil}},
    112 dirtab_tag[]=	 {{".",		QTDIR,		FsDTag,		0500|DMDIR },
    113 		  {"ctl",	QTAPPEND,	FsFTctl,	0600|DMAPPEND },
    114 		  {"index",	QTFILE,		FsFTindex,	0400 },
    115 		  {nil}};
    116 static IxpDirtab* dirtab[] = {
    117 	[FsRoot] = dirtab_root,
    118 	[FsDBars] = dirtab_bars,
    119 	[FsDClients] = dirtab_clients,
    120 	[FsDClient] = dirtab_client,
    121 	[FsDDebug] = dirtab_debug,
    122 	[FsDTags] = dirtab_tags,
    123 	[FsDTag] = dirtab_tag,
    124 };
    125 typedef char* (*MsgFunc)(void*, IxpMsg*);
    126 typedef char* (*BufFunc)(void*);
    127 
    128 typedef struct ActionTab ActionTab;
    129 static struct ActionTab {
    130 	MsgFunc		msg;
    131 	BufFunc		read;
    132 	size_t		buffer;
    133 	size_t		size;
    134 	int		max;
    135 } actiontab[] = {
    136 	[FsFBar]      = { .msg = (MsgFunc)message_bar,          .read = (BufFunc)readctl_bar },
    137 	[FsFCctl]     = { .msg = (MsgFunc)message_client,     	.read = (BufFunc)readctl_client },
    138 	[FsFRctl]     = { .msg = (MsgFunc)message_root,       	.read = (BufFunc)readctl_root },
    139 	[FsFTctl]     = { .msg = (MsgFunc)message_view,       	.read = (BufFunc)readctl_view },
    140 	[FsFTindex]   = { .msg = (MsgFunc)0,		    	.read = (BufFunc)view_index },
    141 	[FsFColRules] = { .buffer = offsetof(Ruleset, string),	.size = offsetof(Ruleset, size) },
    142 	[FsFKeys]     = { .buffer = offsetof(Defs, keys),	.size = offsetof(Defs, keyssz) },
    143 	[FsFRules]    = { .buffer = offsetof(Ruleset, string), 	.size = offsetof(Ruleset, size) },
    144 	[FsFClabel]   = { .buffer = offsetof(Client, name),	.max = sizeof ((Client*)0)->name },
    145 	[FsFCtags]    = { .buffer = offsetof(Client, tags),   	.max = sizeof ((Client*)0)->tags },
    146 	[FsFprops]    = { .buffer = offsetof(Client, props),  	.max = sizeof ((Client*)0)->props },
    147 };
    148 
    149 void
    150 event(const char *format, ...) {
    151 	va_list ap;
    152 
    153 	va_start(ap, format);
    154 	vsnprint(buffer, sizeof buffer, format, ap);
    155 	va_end(ap);
    156 
    157 	ixp_pending_write(&events, buffer, strlen(buffer));
    158 }
    159 
    160 static int dflags;
    161 
    162 bool
    163 setdebug(int flag) {
    164 	dflags = flag;
    165 	return true;
    166 }
    167 
    168 void
    169 vdebug(int flag, const char *fmt, va_list ap) {
    170 	char *s;
    171 
    172 	if(flag == 0)
    173 		flag = dflags;
    174 
    175 	if(!((debugflag|debugfile) & flag))
    176 		return;
    177 
    178 	s = vsmprint(fmt, ap);
    179 	dwrite(flag, s, strlen(s), false);
    180 	free(s);
    181 }
    182 
    183 void
    184 debug(int flag, const char *fmt, ...) {
    185 	va_list ap;
    186 
    187 	va_start(ap, fmt);
    188 	vdebug(flag, fmt, ap);
    189 	va_end(ap);
    190 }
    191 
    192 void
    193 dwrite(int flag, void *buf, int n, bool always) {
    194 	int i;
    195 
    196 	if(flag == 0)
    197 		flag = dflags;
    198 
    199 	if(always || debugflag&flag)
    200 		write(2, buf, n);
    201 
    202 	if(debugfile&flag)
    203 	for(i=0; i < nelem(pdebug); i++)
    204 		if(flag & (1<<i))
    205 			ixp_pending_write(pdebug+i, buf, n);
    206 }
    207 
    208 static uint	fs_size(IxpFileId*);
    209 
    210 static void
    211 dostat(IxpStat *s, IxpFileId *f) {
    212 	s->type = 0;
    213 	s->dev = 0;
    214 	s->qid.path = QID(f->tab.type, f->id);
    215 	s->qid.version = 0;
    216 	s->qid.type = f->tab.qtype;
    217 	s->mode = f->tab.perm;
    218 	s->atime = time(nil);
    219 	s->mtime = s->atime;
    220 	s->length = fs_size(f);;
    221 	s->name = f->tab.name;
    222 	s->uid = user;
    223 	s->gid = user;
    224 	s->muid = user;
    225 }
    226 
    227 /*
    228  * All lookups and directory organization should be performed through
    229  * lookup_file, mostly through the dirtab[] tree.
    230  */
    231 static IxpFileId*
    232 lookup_file(IxpFileId *parent, char *name)
    233 {
    234 	IxpFileId *ret, *file, **last;
    235 	IxpDirtab *dir;
    236 	Client *c;
    237 	View *v;
    238 	Bar *b;
    239 	uint id;
    240 	int i;
    241 
    242 
    243 	if(!(parent->tab.perm & DMDIR))
    244 		return nil;
    245 	dir = dirtab[parent->tab.type];
    246 	last = &ret;
    247 	ret = nil;
    248 	for(; dir->name; dir++) {
    249 #               define push_file(nam, id_, vol)   \
    250 			file = ixp_srv_getfile(); \
    251 			file->id = id_;           \
    252 			file->volatil = vol;      \
    253 			*last = file;             \
    254 			last = &file->next;       \
    255 			file->tab = *dir;         \
    256 			file->tab.name = estrdup(nam)
    257 		/* Dynamic dirs */
    258 		if(dir->name[0] == '\0') {
    259 			switch(parent->tab.type) {
    260 			case FsDClients:
    261 				if(!name || !strcmp(name, "sel")) {
    262 					if((c = selclient())) {
    263 						push_file("sel", c->w.xid, true);
    264 						file->p.client = c;
    265 						file->index = c->w.xid;
    266 					}
    267 					if(name)
    268 						goto LastItem;
    269 				}
    270 				SET(id);
    271 				if(name) {
    272 					id = (uint)strtol(name, &name, 16);
    273 					if(*name)
    274 						goto NextItem;
    275 				}
    276 				for(c=client; c; c=c->next) {
    277 					if(!name || c->w.xid == id) {
    278 						push_file(sxprint("%#C", c), c->w.xid, true);
    279 						file->p.client = c;
    280 						file->index = c->w.xid;
    281 						assert(file->tab.name);
    282 						if(name)
    283 							goto LastItem;
    284 					}
    285 				}
    286 				break;
    287 			case FsDDebug:
    288 				for(i=0; i < nelem(pdebug); i++)
    289 					if(!name || !strcmp(name, debugtab[i])) {
    290 						push_file(debugtab[i], i, false);
    291 						if(name)
    292 							goto LastItem;
    293 					}
    294 				break;
    295 			case FsDTags:
    296 				if(!name || !strcmp(name, "sel")) {
    297 					if(selview) {
    298 						push_file("sel", selview->id, true);
    299 						file->p.view = selview;
    300 					}
    301 					if(name)
    302 						goto LastItem;
    303 				}
    304 				for(v=view; v; v=v->next) {
    305 					if(!name || !strcmp(name, v->name)) {
    306 						push_file(v->name, v->id, true);
    307 						file->p.view = v;
    308 						if(name)
    309 							goto LastItem;
    310 					}
    311 				}
    312 				break;
    313 			case FsDBars:
    314 				for(b=*parent->p.bar_p; b; b=b->next) {
    315 					if(!name || !strcmp(name, b->name)) {
    316 						push_file(b->name, b->id, true);
    317 						file->p.bar = b;
    318 						if(name)
    319 							goto LastItem;
    320 					}
    321 				}
    322 				break;
    323 			}
    324 		}else /* Static dirs */
    325 		if(!name && !(dir->flags & FLHide) || name && !strcmp(name, dir->name)) {
    326 			push_file(file->tab.name, 0, false);
    327 			file->p.ref = parent->p.ref;
    328 			file->index = parent->index;
    329 			/* Special considerations: */
    330 			switch(file->tab.type) {
    331 			case FsDBars:
    332 				if(!strcmp(file->tab.name, "lbar"))
    333 					file->p.bar_p = &screen[0].bar[BLeft];
    334 				else
    335 					file->p.bar_p = &screen[0].bar[BRight];
    336 				file->id = (int)(uintptr_t)file->p.bar_p;
    337 				break;
    338 			case FsFColRules:
    339 				file->p.rule = &def.colrules;
    340 				break;
    341 			case FsFKeys:
    342 				file->p.ref = &def;
    343 				break;
    344 			case FsFRules:
    345 				file->p.rule = &def.rules;
    346 				break;
    347 			}
    348 			if(name)
    349 				goto LastItem;
    350 		}
    351 	NextItem:
    352 		continue;
    353 #		undef push_file
    354 	}
    355 LastItem:
    356 	*last = nil;
    357 	return ret;
    358 }
    359 
    360 /* Service Functions */
    361 void
    362 fs_attach(Ixp9Req *r) {
    363 	IxpFileId *f;
    364 
    365 	f = ixp_srv_getfile();
    366 	f->tab = dirtab[FsRoot][0];
    367 	f->tab.name = estrdup("/");
    368 	r->fid->aux = f;
    369 	r->fid->qid.type = f->tab.qtype;
    370 	r->fid->qid.path = QID(f->tab.type, 0);
    371 	r->ofcall.rattach.qid = r->fid->qid;
    372 	ixp_respond(r, nil);
    373 }
    374 
    375 void
    376 fs_walk(Ixp9Req *r) {
    377 
    378 	ixp_srv_walkandclone(r, lookup_file);
    379 }
    380 
    381 static uint
    382 fs_size(IxpFileId *f) {
    383 	ActionTab *t;
    384 
    385 	t = &actiontab[f->tab.type];
    386 	if(f->tab.type < nelem(actiontab))
    387 		if(t->size)
    388 			return structmember(f->p.ref, int, t->size);
    389 		else if(t->buffer && t->max)
    390 			return strlen(structptr(f->p.ref, char, t->buffer));
    391 		else if(t->buffer)
    392 			return strlen(structmember(f->p.ref, char*, t->buffer));
    393 		else if(t->read)
    394 			return strlen(t->read(f->p.ref));
    395 	return 0;
    396 }
    397 
    398 void
    399 fs_stat(Ixp9Req *r) {
    400 	IxpMsg m;
    401 	IxpStat s;
    402 	int size;
    403 	char *buf;
    404 	IxpFileId *f;
    405 
    406 	f = r->fid->aux;
    407 
    408 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    409 		ixp_respond(r, Enofile);
    410 		return;
    411 	}
    412 
    413 	dostat(&s, f);
    414 	size = ixp_sizeof_stat(&s);
    415 	r->ofcall.rstat.nstat = size;
    416 	buf = emallocz(size);
    417 
    418 	m = ixp_message(buf, size, MsgPack);
    419 	ixp_pstat(&m, &s);
    420 
    421 	r->ofcall.rstat.stat = (uchar*)m.data;
    422 	ixp_respond(r, nil);
    423 }
    424 
    425 void
    426 fs_read(Ixp9Req *r) {
    427 	char *buf;
    428 	IxpFileId *f;
    429 	ActionTab *t;
    430 	int n, found;
    431 
    432 	f = r->fid->aux;
    433 	found = 0;
    434 
    435 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    436 		ixp_respond(r, Enofile);
    437 		return;
    438 	}
    439 
    440 	if(f->tab.perm & DMDIR && f->tab.perm & 0400) {
    441 		ixp_srv_readdir(r, lookup_file, dostat);
    442 		return;
    443 	}
    444 	else{
    445 		if(f->pending) {
    446 			ixp_pending_respond(r);
    447 			return;
    448 		}
    449 		t = &actiontab[f->tab.type];
    450 		if(f->tab.type < nelem(actiontab)) {
    451 			if(t->read)
    452 				buf = t->read(f->p.ref);
    453 			else if(t->buffer && t->max)
    454 				buf = structptr(f->p.ref, char, t->buffer);
    455 			else if(t->buffer)
    456 				buf = structmember(f->p.ref, char*, t->buffer);
    457 			else
    458 				goto done;
    459 			n = t->size ? structmember(f->p.ref, int, t->size) : strlen(buf);
    460 			ixp_srv_readbuf(r, buf, n);
    461 			ixp_respond(r, nil);
    462 			found++;
    463 		}
    464 	done:
    465 		switch(f->tab.type) {
    466 		default:
    467 			if(found)
    468 				return;
    469 		}
    470 	}
    471 	/* This should not be called if the file is not open for reading. */
    472 	die("Read called on an unreadable file");
    473 }
    474 
    475 void
    476 fs_write(Ixp9Req *r) {
    477 	IxpFileId *f;
    478 	ActionTab *t;
    479 	char *errstr;
    480 	int found;
    481 
    482 	found = 0;
    483 	errstr = nil;
    484 	if(r->ifcall.io.count == 0) {
    485 		ixp_respond(r, nil);
    486 		return;
    487 	}
    488 	f = r->fid->aux;
    489 
    490 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    491 		ixp_respond(r, Enofile);
    492 		return;
    493 	}
    494 
    495 	switch(f->tab.type) {
    496 	case FsFCtags:
    497 		r->ofcall.io.count = r->ifcall.io.count;
    498 		ixp_srv_data2cstring(r);
    499 		client_applytags(f->p.client, r->ifcall.io.data);
    500 		ixp_respond(r, nil);
    501 		return;
    502 	}
    503 
    504 	if(waserror()) {
    505 		ixp_respond(r, ixp_errbuf());
    506 		return;
    507 	}
    508 
    509 	t = &actiontab[f->tab.type];
    510 	if(f->tab.type < nelem(actiontab)) {
    511 		if(t->msg) {
    512 			errstr = ixp_srv_writectl(r, t->msg);
    513 			r->ofcall.io.count = r->ifcall.io.count;
    514 		}
    515 		else if(t->buffer && t->max)
    516 			ixp_srv_writebuf(r, (char*[]){ structptr(f->p.ref, char, t->buffer) },
    517 					 t->size ? structptr(f->p.ref, uint, t->size)
    518 					         : (uint[]){ strlen(structptr(f->p.ref, char, t->buffer)) },
    519 					 t->max);
    520 		else if(t->buffer)
    521 			ixp_srv_writebuf(r, structptr(f->p.ref, char*, t->buffer),
    522 					 t->size ? structptr(f->p.ref, uint, t->size) : nil,
    523 					 t->max);
    524 		else
    525 			goto done;
    526 		ixp_respond(r, errstr);
    527 		found++;
    528 	}
    529 done:
    530 	switch(f->tab.type) {
    531 	case FsFClabel:
    532 		frame_draw(f->p.client->sel);
    533 		update_class(f->p.client);
    534 		break;
    535 	case FsFCtags:
    536 		client_applytags(f->p.client, f->p.client->tags);
    537 		break;
    538 	case FsFEvent:
    539 		if(r->ifcall.io.data[r->ifcall.io.count-1] == '\n')
    540 			event("%.*s", (int)r->ifcall.io.count, r->ifcall.io.data);
    541 		else
    542 			event("%.*s\n", (int)r->ifcall.io.count, r->ifcall.io.data);
    543 		r->ofcall.io.count = r->ifcall.io.count;
    544 		ixp_respond(r, nil);
    545 		break;
    546 	default:
    547 		/* This should not be called if the file is not open for writing. */
    548 		if(!found)
    549 			die("Write called on an unwritable file");
    550 	}
    551 	poperror();
    552 	return;
    553 }
    554 
    555 void
    556 fs_open(Ixp9Req *r) {
    557 	IxpFileId *f;
    558 
    559 	f = r->fid->aux;
    560 
    561 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    562 		ixp_respond(r, Enofile);
    563 		return;
    564 	}
    565 
    566 	switch(f->tab.type) {
    567 	case FsFEvent:
    568 		ixp_pending_pushfid(&events, r->fid);
    569 		break;
    570 	case FsFDebug:
    571 		ixp_pending_pushfid(pdebug+f->id, r->fid);
    572 		debugfile |= 1<<f->id;
    573 		break;
    574 	}
    575 
    576 	if((r->ifcall.topen.mode&3) == OEXEC
    577 	|| (r->ifcall.topen.mode&3) != OREAD && !(f->tab.perm & 0200)
    578 	|| (r->ifcall.topen.mode&3) != OWRITE && !(f->tab.perm & 0400)
    579 	|| (r->ifcall.topen.mode & ~(3|OAPPEND|OTRUNC)))
    580 		ixp_respond(r, Enoperm);
    581 	else
    582 		ixp_respond(r, nil);
    583 }
    584 
    585 void
    586 fs_create(Ixp9Req *r) {
    587 	IxpFileId *f;
    588 
    589 	f = r->fid->aux;
    590 
    591 	switch(f->tab.type) {
    592 	default:
    593 		ixp_respond(r, Enoperm);
    594 		return;
    595 	case FsDBars:
    596 		if(!strlen(r->ifcall.tcreate.name)) {
    597 			ixp_respond(r, Ebadvalue);
    598 			return;
    599 		}
    600 		bar_create(f->p.bar_p, r->ifcall.tcreate.name);
    601 		f = lookup_file(f, r->ifcall.tcreate.name);
    602 		if(!f) {
    603 			ixp_respond(r, Enofile);
    604 			return;
    605 		}
    606 		r->ofcall.ropen.qid.type = f->tab.qtype;
    607 		r->ofcall.ropen.qid.path = QID(f->tab.type, f->id);
    608 		f->next = r->fid->aux;
    609 		r->fid->aux = f;
    610 		ixp_respond(r, nil);
    611 		break;
    612 	}
    613 }
    614 
    615 void
    616 fs_remove(Ixp9Req *r) {
    617 	IxpFileId *f;
    618 	WMScreen *s;
    619 
    620 	f = r->fid->aux;
    621 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    622 		ixp_respond(r, Enofile);
    623 		return;
    624 	}
    625 
    626 	switch(f->tab.type) {
    627 	default:
    628 		ixp_respond(r, Enoperm);
    629 		return;
    630 	case FsFBar:
    631 		s = f->p.bar->screen;
    632 		bar_destroy(f->next->p.bar_p, f->p.bar);
    633 		bar_draw(s);
    634 		break;
    635 	case FsDClient:
    636 		client_kill(f->p.client, true);
    637 		break;
    638 	}
    639 	ixp_respond(r, nil);
    640 }
    641 
    642 void
    643 fs_clunk(Ixp9Req *r) {
    644 	IxpFileId *f;
    645 
    646 	f = r->fid->aux;
    647 	if(!ixp_srv_verifyfile(f, lookup_file)) {
    648 		ixp_respond(r, nil);
    649 		return;
    650 	}
    651 
    652 	if(f->pending) {
    653 		/* Should probably be in freefid */
    654 		if(ixp_pending_clunk(r)) {
    655 			if(f->tab.type == FsFDebug)
    656 				debugfile &= ~(1<<f->id);
    657 		}
    658 		return;
    659 	}
    660 
    661 	switch(f->tab.type) {
    662 	case FsFColRules:
    663 	case FsFRules:
    664 		update_rules(&f->p.rule->rule, f->p.rule->string);
    665 		break;
    666 	case FsFKeys:
    667 		update_keys();
    668 		break;
    669 	}
    670 	ixp_respond(r, nil);
    671 }
    672 
    673 void
    674 fs_flush(Ixp9Req *r) {
    675 	Ixp9Req *or;
    676 	IxpFileId *f;
    677 
    678 	or = r->oldreq;
    679 	f = or->fid->aux;
    680 	if(f->pending)
    681 		ixp_pending_flush(r);
    682 	/* else die() ? */
    683 	ixp_respond(r->oldreq, Einterrupted);
    684 	ixp_respond(r, nil);
    685 }
    686 
    687 void
    688 fs_freefid(IxpFid *f) {
    689 	IxpFileId *id, *tid;
    690 
    691 	tid = f->aux;
    692 	while((id = tid)) {
    693 		tid = id->next;
    694 		ixp_srv_freefile(id);
    695 	}
    696 }
    697