vacfs.c 13 KB

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