vacfs.c 13 KB

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