vacfs.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. #include "stdinc.h"
  2. #include <auth.h>
  3. #include <fcall.h>
  4. #include "vac.h"
  5. typedef struct Fid Fid;
  6. typedef struct DirBuf DirBuf;
  7. enum
  8. {
  9. OPERM = 0x3, /* mask of all permission types in open mode */
  10. };
  11. enum
  12. {
  13. DirBufSize = 20,
  14. };
  15. struct Fid
  16. {
  17. short busy;
  18. short open;
  19. int fid;
  20. char *user;
  21. Qid qid;
  22. VacFile *file;
  23. DirBuf *db;
  24. Fid *next;
  25. };
  26. struct DirBuf
  27. {
  28. VacDirEnum *vde;
  29. VacDir buf[DirBufSize];
  30. int i, n;
  31. int eof;
  32. };
  33. enum
  34. {
  35. Pexec = 1,
  36. Pwrite = 2,
  37. Pread = 4,
  38. Pother = 1,
  39. Pgroup = 8,
  40. Powner = 64,
  41. };
  42. Fid *fids;
  43. uchar *data;
  44. int mfd[2];
  45. char *user;
  46. uchar mdata[8192+IOHDRSZ];
  47. int messagesize = sizeof mdata;
  48. Fcall rhdr;
  49. Fcall thdr;
  50. VacFS *fs;
  51. VtSession *session;
  52. int noperm;
  53. Fid * newfid(int);
  54. void error(char*);
  55. void io(void);
  56. void shutdown(void);
  57. void usage(void);
  58. int perm(Fid*, int);
  59. int permf(VacFile*, char*, int);
  60. ulong getl(void *p);
  61. void init(char*, char*, long, int);
  62. DirBuf *dirBufAlloc(VacFile*);
  63. VacDir *dirBufGet(DirBuf*);
  64. int dirBufUnget(DirBuf*);
  65. void dirBufFree(DirBuf*);
  66. int vacdirread(Fid *f, char *p, long off, long cnt);
  67. int vdStat(VacDir *vd, uchar *p, int np);
  68. char *rflush(Fid*), *rversion(Fid*),
  69. *rauth(Fid*), *rattach(Fid*), *rwalk(Fid*),
  70. *ropen(Fid*), *rcreate(Fid*),
  71. *rread(Fid*), *rwrite(Fid*), *rclunk(Fid*),
  72. *rremove(Fid*), *rstat(Fid*), *rwstat(Fid*);
  73. char *(*fcalls[])(Fid*) = {
  74. [Tflush] rflush,
  75. [Tversion] rversion,
  76. [Tattach] rattach,
  77. [Tauth] rauth,
  78. [Twalk] rwalk,
  79. [Topen] ropen,
  80. [Tcreate] rcreate,
  81. [Tread] rread,
  82. [Twrite] rwrite,
  83. [Tclunk] rclunk,
  84. [Tremove] rremove,
  85. [Tstat] rstat,
  86. [Twstat] rwstat,
  87. };
  88. char Eperm[] = "permission denied";
  89. char Enotdir[] = "not a directory";
  90. char Enotexist[] = "file does not exist";
  91. char Einuse[] = "file in use";
  92. char Eexist[] = "file exists";
  93. char Enotowner[] = "not owner";
  94. char Eisopen[] = "file already open for I/O";
  95. char Excl[] = "exclusive use file already open";
  96. char Ename[] = "illegal name";
  97. char Erdonly[] = "read only file system";
  98. char Eio[] = "i/o error";
  99. char Eempty[] = "directory is not empty";
  100. char Emode[] = "illegal mode";
  101. int dflag;
  102. void
  103. notifyf(void *a, char *s)
  104. {
  105. USED(a);
  106. if(strncmp(s, "interrupt", 9) == 0)
  107. noted(NCONT);
  108. noted(NDFLT);
  109. }
  110. void
  111. main(int argc, char *argv[])
  112. {
  113. char *defmnt;
  114. int p[2];
  115. char buf[12];
  116. int fd;
  117. int stdio = 0;
  118. char *host = nil;
  119. long ncache = 1000;
  120. int readOnly = 1;
  121. defmnt = "/n/vac";
  122. ARGBEGIN{
  123. case 'd':
  124. fmtinstall('F', fcallfmt);
  125. dflag = 1;
  126. break;
  127. case 'c':
  128. ncache = atoi(ARGF());
  129. break;
  130. case 'i':
  131. defmnt = 0;
  132. stdio = 1;
  133. mfd[0] = 0;
  134. mfd[1] = 1;
  135. break;
  136. case 'h':
  137. host = ARGF();
  138. break;
  139. case 's':
  140. defmnt = 0;
  141. break;
  142. case 'p':
  143. noperm = 1;
  144. break;
  145. case 'm':
  146. defmnt = ARGF();
  147. break;
  148. default:
  149. usage();
  150. }ARGEND
  151. if(argc != 1)
  152. usage();
  153. vtAttach();
  154. init(argv[0], host, ncache, readOnly);
  155. if(pipe(p) < 0)
  156. sysfatal("pipe failed: %r");
  157. if(!stdio){
  158. mfd[0] = p[0];
  159. mfd[1] = p[0];
  160. if(defmnt == 0){
  161. fd = create("#s/vacfs", OWRITE, 0666);
  162. if(fd < 0)
  163. sysfatal("create of /srv/vacfs failed: %r");
  164. sprint(buf, "%d", p[1]);
  165. if(write(fd, buf, strlen(buf)) < 0)
  166. sysfatal("writing /srv/vacfs: %r");
  167. }
  168. }
  169. switch(rfork(RFFDG|RFPROC|RFNAMEG|RFNOTEG)){
  170. case -1:
  171. sysfatal("fork: %r");
  172. case 0:
  173. vtAttach();
  174. close(p[1]);
  175. io();
  176. shutdown();
  177. break;
  178. default:
  179. close(p[0]); /* don't deadlock if child fails */
  180. if(defmnt && mount(p[1], -1, defmnt, MREPL|MCREATE, "") < 0)
  181. sysfatal("mount failed: %r");
  182. }
  183. vtDetach();
  184. exits(0);
  185. }
  186. void
  187. usage(void)
  188. {
  189. fprint(2, "usage: %s [-sd] [-h host] [-c ncache] [-m mountpoint] vacfile\n", argv0);
  190. exits("usage");
  191. }
  192. char*
  193. rversion(Fid *unused)
  194. {
  195. Fid *f;
  196. USED(unused);
  197. for(f = fids; f; f = f->next)
  198. if(f->busy)
  199. rclunk(f);
  200. if(rhdr.msize < 256)
  201. return "version: message size too small";
  202. messagesize = rhdr.msize;
  203. if(messagesize > sizeof mdata)
  204. messagesize = sizeof mdata;
  205. thdr.msize = messagesize;
  206. if(strncmp(rhdr.version, "9P2000", 6) != 0)
  207. return "unrecognized 9P version";
  208. thdr.version = "9P2000";
  209. return nil;
  210. }
  211. char*
  212. rflush(Fid *f)
  213. {
  214. USED(f);
  215. return 0;
  216. }
  217. char*
  218. rauth(Fid *f)
  219. {
  220. USED(f);
  221. return "vacfs: authentication not required";
  222. }
  223. char*
  224. rattach(Fid *f)
  225. {
  226. /* no authentication for the momment */
  227. VacFile *file;
  228. file = vfsGetRoot(fs);
  229. if(file == nil)
  230. return vtGetError();
  231. f->busy = 1;
  232. f->file = file;
  233. f->qid = (Qid){vfGetId(f->file), 0, QTDIR};
  234. thdr.qid = f->qid;
  235. if(rhdr.uname[0])
  236. f->user = vtStrDup(rhdr.uname);
  237. else
  238. f->user = "none";
  239. return 0;
  240. }
  241. VacFile*
  242. _vfWalk(VacFile *file, char *name)
  243. {
  244. VacFile *n;
  245. n = vfWalk(file, name);
  246. if(n)
  247. return n;
  248. if(strcmp(name, "SLASH") == 0)
  249. return vfWalk(file, "/");
  250. return nil;
  251. }
  252. char*
  253. rwalk(Fid *f)
  254. {
  255. VacFile *file, *nfile;
  256. Fid *nf;
  257. int nqid, nwname;
  258. Qid qid;
  259. if(f->busy == 0)
  260. return Enotexist;
  261. nf = nil;
  262. if(rhdr.fid != rhdr.newfid){
  263. if(f->open)
  264. return Eisopen;
  265. if(f->busy == 0)
  266. return Enotexist;
  267. nf = newfid(rhdr.newfid);
  268. if(nf->busy)
  269. return Eisopen;
  270. nf->busy = 1;
  271. nf->open = 0;
  272. nf->qid = f->qid;
  273. nf->file = vfIncRef(f->file);
  274. nf->user = vtStrDup(f->user);
  275. f = nf;
  276. }
  277. nwname = rhdr.nwname;
  278. /* easy case */
  279. if(nwname == 0) {
  280. thdr.nwqid = 0;
  281. return 0;
  282. }
  283. file = f->file;
  284. vfIncRef(file);
  285. qid = f->qid;
  286. for(nqid = 0; nqid < nwname; nqid++){
  287. if((qid.type & QTDIR) == 0){
  288. vtSetError(Enotdir);
  289. break;
  290. }
  291. if(!permf(file, f->user, Pexec)) {
  292. vtSetError(Eperm);
  293. break;
  294. }
  295. nfile = _vfWalk(file, rhdr.wname[nqid]);
  296. if(nfile == nil)
  297. break;
  298. vfDecRef(file);
  299. file = nfile;
  300. qid.type = QTFILE;
  301. if(vfIsDir(file))
  302. qid.type = QTDIR;
  303. qid.vers = vfGetMcount(file);
  304. qid.path = vfGetId(file);
  305. thdr.wqid[nqid] = qid;
  306. }
  307. thdr.nwqid = nqid;
  308. if(nqid == nwname){
  309. /* success */
  310. f->qid = thdr.wqid[nqid-1];
  311. vfDecRef(f->file);
  312. f->file = file;
  313. return 0;
  314. }
  315. vfDecRef(file);
  316. if(nf != nil)
  317. rclunk(nf);
  318. /* only error on the first element */
  319. if(nqid == 0)
  320. return vtGetError();
  321. return 0;
  322. }
  323. char *
  324. ropen(Fid *f)
  325. {
  326. int mode, trunc;
  327. if(f->open)
  328. return Eisopen;
  329. if(!f->busy)
  330. return Enotexist;
  331. mode = rhdr.mode;
  332. thdr.iounit = messagesize - IOHDRSZ;
  333. if(f->qid.type & QTDIR){
  334. if(mode != OREAD)
  335. return Eperm;
  336. if(!perm(f, Pread))
  337. return Eperm;
  338. thdr.qid = f->qid;
  339. f->db = nil;
  340. f->open = 1;
  341. return 0;
  342. }
  343. if(mode & ORCLOSE)
  344. return Erdonly;
  345. trunc = mode & OTRUNC;
  346. mode &= OPERM;
  347. if(mode==OWRITE || mode==ORDWR || trunc)
  348. if(!perm(f, Pwrite))
  349. return Eperm;
  350. if(mode==OREAD || mode==ORDWR)
  351. if(!perm(f, Pread))
  352. return Eperm;
  353. if(mode==OEXEC)
  354. if(!perm(f, Pexec))
  355. return Eperm;
  356. thdr.qid = f->qid;
  357. thdr.iounit = messagesize - IOHDRSZ;
  358. f->open = 1;
  359. return 0;
  360. }
  361. char*
  362. rcreate(Fid* fid)
  363. {
  364. VacFile *vf;
  365. ulong mode;
  366. if(fid->open)
  367. return Eisopen;
  368. if(!fid->busy)
  369. return Enotexist;
  370. if(vfsIsReadOnly(fs))
  371. return Erdonly;
  372. vf = fid->file;
  373. if(!vfIsDir(vf))
  374. return Enotdir;
  375. if(!permf(vf, fid->user, Pwrite))
  376. return Eperm;
  377. mode = rhdr.perm & 0777;
  378. if(rhdr.perm & DMDIR){
  379. if((rhdr.mode & OTRUNC) || (rhdr.perm & DMAPPEND))
  380. return Emode;
  381. switch(rhdr.mode & OPERM){
  382. default:
  383. return Emode;
  384. case OEXEC:
  385. case OREAD:
  386. break;
  387. case OWRITE:
  388. case ORDWR:
  389. return Eperm;
  390. }
  391. mode |= ModeDir;
  392. }
  393. vf = vfCreate(vf, rhdr.name, mode, "none");
  394. if(vf == nil)
  395. return vtGetError();
  396. vfDecRef(fid->file);
  397. fid->file = vf;
  398. fid->qid.type = QTFILE;
  399. if(vfIsDir(vf))
  400. fid->qid.type = QTDIR;
  401. fid->qid.vers = vfGetMcount(vf);
  402. fid->qid.path = vfGetId(vf);
  403. thdr.qid = fid->qid;
  404. thdr.iounit = messagesize - IOHDRSZ;
  405. return 0;
  406. }
  407. char*
  408. rread(Fid *f)
  409. {
  410. char *buf;
  411. vlong off;
  412. int cnt;
  413. VacFile *vf;
  414. char *err;
  415. int n;
  416. if(!f->busy)
  417. return Enotexist;
  418. vf = f->file;
  419. thdr.count = 0;
  420. off = rhdr.offset;
  421. buf = thdr.data;
  422. cnt = rhdr.count;
  423. if(f->qid.type & QTDIR)
  424. n = vacdirread(f, buf, off, cnt);
  425. else
  426. n = vfRead(vf, buf, cnt, off);
  427. if(n < 0) {
  428. err = vtGetError();
  429. if(err == nil)
  430. err = "unknown error!";
  431. return err;
  432. }
  433. thdr.count = n;
  434. return 0;
  435. }
  436. char*
  437. rwrite(Fid *f)
  438. {
  439. char *buf;
  440. vlong off;
  441. int cnt;
  442. VacFile *vf;
  443. if(!f->busy)
  444. return Enotexist;
  445. vf = f->file;
  446. thdr.count = 0;
  447. off = rhdr.offset;
  448. buf = rhdr.data;
  449. cnt = rhdr.count;
  450. if(f->qid.type & QTDIR)
  451. return "file is a directory";
  452. thdr.count = vfWrite(vf, buf, cnt, off, "none");
  453. if(thdr.count < 0) {
  454. fprint(2, "write failed: %s\n", vtGetError());
  455. return vtGetError();
  456. }
  457. return 0;
  458. }
  459. char *
  460. rclunk(Fid *f)
  461. {
  462. f->busy = 0;
  463. f->open = 0;
  464. vtMemFree(f->user);
  465. f->user = nil;
  466. vfDecRef(f->file);
  467. f->file = nil;
  468. dirBufFree(f->db);
  469. f->db = nil;
  470. return 0;
  471. }
  472. char *
  473. rremove(Fid *f)
  474. {
  475. VacFile *vf, *vfp;
  476. char *err = nil;
  477. if(!f->busy)
  478. return Enotexist;
  479. vf = f->file;
  480. vfp = vfGetParent(vf);
  481. if(!permf(vfp, f->user, Pwrite)) {
  482. err = Eperm;
  483. goto Exit;
  484. }
  485. if(!vfRemove(vf, "none")) {
  486. print("vfRemove failed\n");
  487. err = vtGetError();
  488. }
  489. Exit:
  490. vfDecRef(vfp);
  491. rclunk(f);
  492. return err;
  493. }
  494. char *
  495. rstat(Fid *f)
  496. {
  497. VacDir dir;
  498. static uchar statbuf[1024];
  499. if(!f->busy)
  500. return Enotexist;
  501. vfGetDir(f->file, &dir);
  502. thdr.stat = statbuf;
  503. thdr.nstat = vdStat(&dir, thdr.stat, sizeof statbuf);
  504. vdCleanup(&dir);
  505. return 0;
  506. }
  507. char *
  508. rwstat(Fid *f)
  509. {
  510. if(!f->busy)
  511. return Enotexist;
  512. return Erdonly;
  513. }
  514. int
  515. vdStat(VacDir *vd, uchar *p, int np)
  516. {
  517. Dir dir;
  518. memset(&dir, 0, sizeof(dir));
  519. /*
  520. * Where do path and version come from
  521. */
  522. dir.qid.path = vd->qid;
  523. dir.qid.vers = vd->mcount;
  524. dir.mode = vd->mode & 0777;
  525. if(vd->mode & ModeAppend){
  526. dir.qid.type |= QTAPPEND;
  527. dir.mode |= DMAPPEND;
  528. }
  529. if(vd->mode & ModeExclusive){
  530. dir.qid.type |= QTEXCL;
  531. dir.mode |= DMEXCL;
  532. }
  533. if(vd->mode & ModeDir){
  534. dir.qid.type |= QTDIR;
  535. dir.mode |= DMDIR;
  536. }
  537. dir.atime = vd->atime;
  538. dir.mtime = vd->mtime;
  539. dir.length = vd->size;
  540. dir.name = vd->elem;
  541. dir.uid = vd->uid;
  542. dir.gid = vd->gid;
  543. dir.muid = vd->mid;
  544. return convD2M(&dir, p, np);
  545. }
  546. DirBuf*
  547. dirBufAlloc(VacFile *vf)
  548. {
  549. DirBuf *db;
  550. db = vtMemAllocZ(sizeof(DirBuf));
  551. db->vde = vfDirEnum(vf);
  552. return db;
  553. }
  554. VacDir *
  555. dirBufGet(DirBuf *db)
  556. {
  557. VacDir *vd;
  558. int n;
  559. if(db->eof)
  560. return nil;
  561. if(db->i >= db->n) {
  562. n = vdeRead(db->vde, db->buf, DirBufSize);
  563. if(n < 0)
  564. return nil;
  565. db->i = 0;
  566. db->n = n;
  567. if(n == 0) {
  568. db->eof = 1;
  569. return nil;
  570. }
  571. }
  572. vd = db->buf + db->i;
  573. db->i++;
  574. return vd;
  575. }
  576. int
  577. dirBufUnget(DirBuf *db)
  578. {
  579. assert(db->i > 0);
  580. db->i--;
  581. return 1;
  582. }
  583. void
  584. dirBufFree(DirBuf *db)
  585. {
  586. int i;
  587. if(db == nil)
  588. return;
  589. for(i=db->i; i<db->n; i++)
  590. vdCleanup(db->buf + i);
  591. vdeFree(db->vde);
  592. vtMemFree(db);
  593. }
  594. int
  595. vacdirread(Fid *f, char *p, long off, long cnt)
  596. {
  597. int n, nb;
  598. VacDir *vd;
  599. /*
  600. * special case of rewinding a directory
  601. * otherwise ignore the offset
  602. */
  603. if(off == 0 && f->db) {
  604. dirBufFree(f->db);
  605. f->db = nil;
  606. }
  607. if(f->db == nil)
  608. f->db = dirBufAlloc(f->file);
  609. for(nb = 0; nb < cnt; nb += n) {
  610. vd = dirBufGet(f->db);
  611. if(vd == nil) {
  612. if(!f->db->eof)
  613. return -1;
  614. break;
  615. }
  616. n = vdStat(vd, (uchar*)p, cnt-nb);
  617. if(n <= BIT16SZ) {
  618. dirBufUnget(f->db);
  619. break;
  620. }
  621. vdCleanup(vd);
  622. p += n;
  623. }
  624. return nb;
  625. }
  626. Fid *
  627. newfid(int fid)
  628. {
  629. Fid *f, *ff;
  630. ff = 0;
  631. for(f = fids; f; f = f->next)
  632. if(f->fid == fid)
  633. return f;
  634. else if(!ff && !f->busy)
  635. ff = f;
  636. if(ff){
  637. ff->fid = fid;
  638. return ff;
  639. }
  640. f = vtMemAllocZ(sizeof *f);
  641. f->fid = fid;
  642. f->next = fids;
  643. fids = f;
  644. return f;
  645. }
  646. void
  647. io(void)
  648. {
  649. char *err;
  650. int n;
  651. for(;;){
  652. /*
  653. * reading from a pipe or a network device
  654. * will give an error after a few eof reads
  655. * however, we cannot tell the difference
  656. * between a zero-length read and an interrupt
  657. * on the processes writing to us,
  658. * so we wait for the error
  659. */
  660. n = read9pmsg(mfd[0], mdata, sizeof mdata);
  661. if(n == 0)
  662. continue;
  663. if(n < 0)
  664. break;
  665. if(convM2S(mdata, n, &rhdr) != n)
  666. sysfatal("convM2S conversion error");
  667. if(dflag)
  668. fprint(2, "vacfs:<-%F\n", &rhdr);
  669. thdr.data = (char*)mdata + IOHDRSZ;
  670. if(!fcalls[rhdr.type])
  671. err = "bad fcall type";
  672. else
  673. err = (*fcalls[rhdr.type])(newfid(rhdr.fid));
  674. if(err){
  675. thdr.type = Rerror;
  676. thdr.ename = err;
  677. }else{
  678. thdr.type = rhdr.type + 1;
  679. thdr.fid = rhdr.fid;
  680. }
  681. thdr.tag = rhdr.tag;
  682. if(dflag)
  683. fprint(2, "vacfs:->%F\n", &thdr);
  684. n = convS2M(&thdr, mdata, messagesize);
  685. if(write(mfd[1], mdata, n) != n)
  686. sysfatal("mount write: %r");
  687. }
  688. }
  689. int
  690. permf(VacFile *vf, char *user, int p)
  691. {
  692. VacDir dir;
  693. ulong perm;
  694. if(!vfGetDir(vf, &dir))
  695. return 0;
  696. perm = dir.mode & 0777;
  697. if((p*Pother) & perm)
  698. goto Good;
  699. if((noperm || strcmp(user, dir.gid)==0) && ((p*Pgroup) & perm))
  700. goto Good;
  701. if((noperm || strcmp(user, dir.uid)==0) && ((p*Powner) & perm))
  702. goto Good;
  703. vdCleanup(&dir);
  704. return 0;
  705. Good:
  706. vdCleanup(&dir);
  707. return 1;
  708. }
  709. int
  710. perm(Fid *f, int p)
  711. {
  712. return permf(f->file, f->user, p);
  713. }
  714. void
  715. init(char *file, char *host, long ncache, int readOnly)
  716. {
  717. notify(notifyf);
  718. user = getuser();
  719. fmtinstall('V', vtScoreFmt);
  720. fmtinstall('R', vtErrFmt);
  721. session = vtDial(host, 0);
  722. if(session == nil)
  723. vtFatal("could not connect to server: %s", vtGetError());
  724. if(!vtConnect(session, 0))
  725. vtFatal("vtConnect: %s", vtGetError());
  726. fs = vfsOpen(session, file, readOnly, ncache);
  727. if(fs == nil)
  728. vtFatal("vfsOpen: %s", vtGetError());
  729. }
  730. void
  731. shutdown(void)
  732. {
  733. Fid *f;
  734. for(f = fids; f; f = f->next) {
  735. if(!f->busy)
  736. continue;
  737. fprint(2, "open fid: %d\n", f->fid);
  738. rclunk(f);
  739. }
  740. vfsClose(fs);
  741. vtClose(session);
  742. }