exportfs.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. /*
  2. * This file is part of the UCB release of Plan 9. It is subject to the license
  3. * terms in the LICENSE file found in the top-level directory of this
  4. * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
  5. * part of the UCB release of Plan 9, including this file, may be copied,
  6. * modified, propagated, or distributed except according to the terms contained
  7. * in the LICENSE file.
  8. */
  9. #include <u.h>
  10. #include <libc.h>
  11. #include <fcall.h>
  12. #include "compat.h"
  13. #include "error.h"
  14. typedef struct Fid Fid;
  15. typedef struct Export Export;
  16. typedef struct Exq Exq;
  17. typedef struct Exwork Exwork;
  18. enum
  19. {
  20. Nfidhash = 32,
  21. Maxfdata = 8192,
  22. Maxrpc = IOHDRSZ + Maxfdata,
  23. };
  24. struct Export
  25. {
  26. Ref r;
  27. Exq* work;
  28. Lock fidlock;
  29. Fid* fid[Nfidhash];
  30. int io; /* fd to read/write */
  31. int iounit;
  32. int nroots;
  33. Chan **roots;
  34. };
  35. struct Fid
  36. {
  37. Fid* next;
  38. Fid** last;
  39. Chan* chan;
  40. int32_t offset;
  41. int fid;
  42. int ref; /* fcalls using the fid; locked by Export.Lock */
  43. int attached; /* fid attached or cloned but not clunked */
  44. };
  45. struct Exq
  46. {
  47. Lock lk;
  48. int responding; /* writing out reply message */
  49. int noresponse; /* don't respond to this one */
  50. Exq* next;
  51. int shut; /* has been noted for shutdown */
  52. Export* export;
  53. void* slave;
  54. Fcall rpc;
  55. uint8_t buf[Maxrpc];
  56. };
  57. struct Exwork
  58. {
  59. Lock l;
  60. int ref;
  61. int nwaiters; /* queue of slaves waiting for work */
  62. QLock qwait;
  63. Rendez rwait;
  64. Exq *head; /* work waiting for a slave */
  65. Exq *tail;
  66. };
  67. Exwork exq;
  68. static void exshutdown(Export*);
  69. static void exflush(Export*, int, int);
  70. static void exslave(void*);
  71. static void exfree(Export*);
  72. static void exportproc(Export*);
  73. static char* Exattach(Export*, Fcall*, uint8_t*);
  74. static char* Exauth(Export*, Fcall*, uint8_t*);
  75. static char* Exclunk(Export*, Fcall*, uint8_t*);
  76. static char* Excreate(Export*, Fcall*, uint8_t*);
  77. static char* Exversion(Export*, Fcall*, uint8_t*);
  78. static char* Exopen(Export*, Fcall*, uint8_t*);
  79. static char* Exread(Export*, Fcall*, uint8_t*);
  80. static char* Exremove(Export*, Fcall*, uint8_t*);
  81. static char* Exsession(Export*, Fcall*, uint8_t*);
  82. static char* Exstat(Export*, Fcall*, uint8_t*);
  83. static char* Exwalk(Export*, Fcall*, uint8_t*);
  84. static char* Exwrite(Export*, Fcall*, uint8_t*);
  85. static char* Exwstat(Export*, Fcall*, uint8_t*);
  86. static char *(*fcalls[Tmax])(Export*, Fcall*, uint8_t*);
  87. static char Enofid[] = "no such fid";
  88. static char Eseekdir[] = "can't seek on a directory";
  89. static char Ereaddir[] = "unaligned read of a directory";
  90. static int exdebug = 0;
  91. int
  92. sysexport(int fd, Chan **roots, int nroots)
  93. {
  94. Export *fs;
  95. fs = smalloc(sizeof(Export));
  96. fs->r.ref = 1;
  97. fs->io = fd;
  98. fs->roots = roots;
  99. fs->nroots = nroots;
  100. exportproc(fs);
  101. return 0;
  102. }
  103. static void
  104. exportinit(void)
  105. {
  106. lock(&exq.l);
  107. exq.ref++;
  108. if(fcalls[Tversion] != nil){
  109. unlock(&exq.l);
  110. return;
  111. }
  112. fmtinstall('F', fcallfmt);
  113. fcalls[Tversion] = Exversion;
  114. fcalls[Tauth] = Exauth;
  115. fcalls[Tattach] = Exattach;
  116. fcalls[Twalk] = Exwalk;
  117. fcalls[Topen] = Exopen;
  118. fcalls[Tcreate] = Excreate;
  119. fcalls[Tread] = Exread;
  120. fcalls[Twrite] = Exwrite;
  121. fcalls[Tclunk] = Exclunk;
  122. fcalls[Tremove] = Exremove;
  123. fcalls[Tstat] = Exstat;
  124. fcalls[Twstat] = Exwstat;
  125. unlock(&exq.l);
  126. }
  127. static void
  128. exportproc(Export *fs)
  129. {
  130. Exq *q;
  131. int n, ed;
  132. exportinit();
  133. ed = errdepth(-1);
  134. for(;;){
  135. errdepth(ed);
  136. q = smalloc(sizeof(Exq));
  137. n = read9pmsg(fs->io, q->buf, Maxrpc);
  138. if(n <= 0 || convM2S(q->buf, n, &q->rpc) != n)
  139. goto bad;
  140. if(exdebug)
  141. print("export %d <- %F\n", getpid(), &q->rpc);
  142. if(q->rpc.type == Tflush){
  143. exflush(fs, q->rpc.tag, q->rpc.oldtag);
  144. free(q);
  145. continue;
  146. }
  147. q->export = fs;
  148. incref(&fs->r);
  149. lock(&exq.l);
  150. if(exq.head == nil)
  151. exq.head = q;
  152. else
  153. exq.tail->next = q;
  154. q->next = nil;
  155. exq.tail = q;
  156. n = exq.nwaiters;
  157. if(n)
  158. exq.nwaiters = n - 1;
  159. unlock(&exq.l);
  160. if(!n)
  161. kproc("exportfs", exslave, nil);
  162. rendwakeup(&exq.rwait);
  163. }
  164. bad:
  165. free(q);
  166. if(exdebug)
  167. fprint(2, "export proc shutting down: %r\n");
  168. exshutdown(fs);
  169. exfree(fs);
  170. }
  171. static void
  172. exflush(Export *fs, int flushtag, int tag)
  173. {
  174. Exq *q, **last;
  175. Fcall fc;
  176. uint8_t buf[Maxrpc];
  177. int n;
  178. /* hasn't been started? */
  179. lock(&exq.l);
  180. last = &exq.head;
  181. for(q = exq.head; q != nil; q = q->next){
  182. if(q->export == fs && q->rpc.tag == tag){
  183. *last = q->next;
  184. unlock(&exq.l);
  185. exfree(fs);
  186. free(q);
  187. goto Respond;
  188. }
  189. last = &q->next;
  190. }
  191. unlock(&exq.l);
  192. /* in progress? */
  193. lock(&fs->r);
  194. for(q = fs->work; q != nil; q = q->next){
  195. if(q->rpc.tag == tag){
  196. lock(&q->lk);
  197. q->noresponse = 1;
  198. if(!q->responding)
  199. rendintr(q->slave);
  200. unlock(&q->lk);
  201. break;
  202. }
  203. }
  204. unlock(&fs->r);
  205. Respond:
  206. fc.type = Rflush;
  207. fc.tag = flushtag;
  208. n = convS2M(&fc, buf, Maxrpc);
  209. if(n == 0)
  210. panic("convS2M error on write");
  211. if(write(fs->io, buf, n) != n)
  212. panic("mount write");
  213. }
  214. static void
  215. exshutdown(Export *fs)
  216. {
  217. Exq *q, **last;
  218. lock(&exq.l);
  219. last = &exq.head;
  220. for(q = exq.head; q != nil; q = *last){
  221. if(q->export == fs){
  222. *last = q->next;
  223. exfree(fs);
  224. free(q);
  225. continue;
  226. }
  227. last = &q->next;
  228. }
  229. /*
  230. * cleanly shut down the slaves if this is the last fs around
  231. */
  232. exq.ref--;
  233. if(!exq.ref)
  234. rendwakeup(&exq.rwait);
  235. unlock(&exq.l);
  236. /*
  237. * kick any sleepers
  238. */
  239. lock(&fs->r);
  240. for(q = fs->work; q != nil; q = q->next){
  241. lock(&q->lk);
  242. q->noresponse = 1;
  243. if(!q->responding)
  244. rendintr(q->slave);
  245. unlock(&q->lk);
  246. }
  247. unlock(&fs->r);
  248. }
  249. static void
  250. exfree(Export *fs)
  251. {
  252. Fid *f, *n;
  253. int i;
  254. if(decref(&fs->r) != 0)
  255. return;
  256. for(i = 0; i < Nfidhash; i++){
  257. for(f = fs->fid[i]; f != nil; f = n){
  258. if(f->chan != nil)
  259. cclose(f->chan);
  260. n = f->next;
  261. free(f);
  262. }
  263. }
  264. free(fs);
  265. }
  266. static int
  267. exwork(void *)
  268. {
  269. int work;
  270. lock(&exq.l);
  271. work = exq.head != nil || !exq.ref;
  272. unlock(&exq.l);
  273. return work;
  274. }
  275. static void
  276. exslave(void *)
  277. {
  278. Export *fs;
  279. Exq *q, *t, **last;
  280. char *volatile err;
  281. int n, ed;
  282. while(waserror())
  283. fprint(2, "exslave %d errored out of loop -- heading back in!\n", getpid());
  284. ed = errdepth(-1);
  285. for(;;){
  286. errdepth(ed);
  287. qlock(&exq.qwait);
  288. if(waserror()){
  289. qunlock(&exq.qwait);
  290. nexterror();
  291. }
  292. rendsleep(&exq.rwait, exwork, nil);
  293. lock(&exq.l);
  294. if(!exq.ref){
  295. unlock(&exq.l);
  296. poperror();
  297. qunlock(&exq.qwait);
  298. break;
  299. }
  300. q = exq.head;
  301. if(q == nil){
  302. unlock(&exq.l);
  303. poperror();
  304. qunlock(&exq.qwait);
  305. continue;
  306. }
  307. exq.head = q->next;
  308. if(exq.head == nil)
  309. exq.tail = nil;
  310. poperror();
  311. qunlock(&exq.qwait);
  312. /*
  313. * put the job on the work queue before it's
  314. * visible as off of the head queue, so it's always
  315. * findable for flushes and shutdown
  316. */
  317. q->slave = up;
  318. q->noresponse = 0;
  319. q->responding = 0;
  320. rendclearintr();
  321. fs = q->export;
  322. lock(&fs->r);
  323. q->next = fs->work;
  324. fs->work = q;
  325. unlock(&fs->r);
  326. unlock(&exq.l);
  327. if(exdebug > 1)
  328. print("exslave dispatch %d %F\n", getpid(), &q->rpc);
  329. if(waserror()){
  330. print("exslave err %r\n");
  331. err = up->error;
  332. }else{
  333. if(q->rpc.type >= Tmax || !fcalls[q->rpc.type])
  334. err = "bad fcall type";
  335. else
  336. err = (*fcalls[q->rpc.type])(fs, &q->rpc, &q->buf[IOHDRSZ]);
  337. poperror();
  338. }
  339. q->rpc.type++;
  340. if(err){
  341. q->rpc.type = Rerror;
  342. q->rpc.ename = err;
  343. }
  344. n = convS2M(&q->rpc, q->buf, Maxrpc);
  345. if(exdebug)
  346. print("exslave %d -> %F\n", getpid(), &q->rpc);
  347. lock(&q->lk);
  348. if(!q->noresponse){
  349. q->responding = 1;
  350. unlock(&q->lk);
  351. write(fs->io, q->buf, n);
  352. }else
  353. unlock(&q->lk);
  354. /*
  355. * exflush might set noresponse at this point, but
  356. * setting noresponse means don't send a response now;
  357. * it's okay that we sent a response already.
  358. */
  359. if(exdebug > 1)
  360. print("exslave %d written %d\n", getpid(), q->rpc.tag);
  361. lock(&fs->r);
  362. last = &fs->work;
  363. for(t = fs->work; t != nil; t = t->next){
  364. if(t == q){
  365. *last = q->next;
  366. break;
  367. }
  368. last = &t->next;
  369. }
  370. unlock(&fs->r);
  371. exfree(q->export);
  372. free(q);
  373. rendclearintr();
  374. lock(&exq.l);
  375. exq.nwaiters++;
  376. unlock(&exq.l);
  377. }
  378. if(exdebug)
  379. fprint(2, "export slaveshutting down\n");
  380. kexit();
  381. }
  382. Fid*
  383. Exmkfid(Export *fs, int fid)
  384. {
  385. uint32_t h;
  386. Fid *f, *nf;
  387. nf = mallocz(sizeof(Fid), 1);
  388. if(nf == nil)
  389. return nil;
  390. lock(&fs->fidlock);
  391. h = fid % Nfidhash;
  392. for(f = fs->fid[h]; f != nil; f = f->next){
  393. if(f->fid == fid){
  394. unlock(&fs->fidlock);
  395. free(nf);
  396. return nil;
  397. }
  398. }
  399. nf->next = fs->fid[h];
  400. if(nf->next != nil)
  401. nf->next->last = &nf->next;
  402. nf->last = &fs->fid[h];
  403. fs->fid[h] = nf;
  404. nf->fid = fid;
  405. nf->ref = 1;
  406. nf->attached = 1;
  407. nf->offset = 0;
  408. nf->chan = nil;
  409. unlock(&fs->fidlock);
  410. return nf;
  411. }
  412. Fid*
  413. Exgetfid(Export *fs, int fid)
  414. {
  415. Fid *f;
  416. uint32_t h;
  417. lock(&fs->fidlock);
  418. h = fid % Nfidhash;
  419. for(f = fs->fid[h]; f; f = f->next){
  420. if(f->fid == fid){
  421. if(f->attached == 0)
  422. break;
  423. f->ref++;
  424. unlock(&fs->fidlock);
  425. return f;
  426. }
  427. }
  428. unlock(&fs->fidlock);
  429. return nil;
  430. }
  431. void
  432. Exputfid(Export *fs, Fid *f)
  433. {
  434. lock(&fs->fidlock);
  435. f->ref--;
  436. if(f->ref == 0 && f->attached == 0){
  437. if(f->chan != nil)
  438. cclose(f->chan);
  439. f->chan = nil;
  440. *f->last = f->next;
  441. if(f->next != nil)
  442. f->next->last = f->last;
  443. unlock(&fs->fidlock);
  444. free(f);
  445. return;
  446. }
  447. unlock(&fs->fidlock);
  448. }
  449. static char*
  450. Exversion(Export *fs, Fcall *rpc, uint8_t *)
  451. {
  452. if(rpc->msize > Maxrpc)
  453. rpc->msize = Maxrpc;
  454. if(strncmp(rpc->version, "9P", 2) != 0){
  455. rpc->version = "unknown";
  456. return nil;
  457. }
  458. fs->iounit = rpc->msize - IOHDRSZ;
  459. rpc->version = "9P2000";
  460. return nil;
  461. }
  462. static char*
  463. Exauth(Export *, Fcall *, uint8_t *)
  464. {
  465. return "vnc: authentication not required";
  466. }
  467. static char*
  468. Exattach(Export *fs, Fcall *rpc, uint8_t *)
  469. {
  470. Fid *f;
  471. int w;
  472. w = 0;
  473. if(rpc->aname != nil)
  474. w = strtol(rpc->aname, nil, 10);
  475. if(w < 0 || w > fs->nroots)
  476. error(Ebadspec);
  477. f = Exmkfid(fs, rpc->fid);
  478. if(f == nil)
  479. return Einuse;
  480. if(waserror()){
  481. f->attached = 0;
  482. Exputfid(fs, f);
  483. return up->error;
  484. }
  485. f->chan = cclone(fs->roots[w]);
  486. poperror();
  487. rpc->qid = f->chan->qid;
  488. Exputfid(fs, f);
  489. return nil;
  490. }
  491. static char*
  492. Exclunk(Export *fs, Fcall *rpc, uint8_t *)
  493. {
  494. Fid *f;
  495. f = Exgetfid(fs, rpc->fid);
  496. if(f != nil){
  497. f->attached = 0;
  498. Exputfid(fs, f);
  499. }
  500. return nil;
  501. }
  502. static char*
  503. Exwalk(Export *fs, Fcall *rpc, uint8_t *)
  504. {
  505. Fid *volatile f, *volatile nf;
  506. Walkqid *wq;
  507. Chan *c;
  508. int i, nwname;
  509. int volatile isnew;
  510. f = Exgetfid(fs, rpc->fid);
  511. if(f == nil)
  512. return Enofid;
  513. nf = nil;
  514. if(waserror()){
  515. Exputfid(fs, f);
  516. if(nf != nil)
  517. Exputfid(fs, nf);
  518. return up->error;
  519. }
  520. /*
  521. * optional clone, but don't attach it until the walk succeeds.
  522. */
  523. if(rpc->fid != rpc->newfid){
  524. nf = Exmkfid(fs, rpc->newfid);
  525. if(nf == nil)
  526. error(Einuse);
  527. nf->attached = 0;
  528. isnew = 1;
  529. }else{
  530. nf = Exgetfid(fs, rpc->fid);
  531. isnew = 0;
  532. }
  533. /*
  534. * let the device do the work
  535. */
  536. c = f->chan;
  537. nwname = rpc->nwname;
  538. wq = (*devtab[c->type]->walk)(c, nf->chan, rpc->wname, nwname);
  539. if(wq == nil)
  540. error(Enonexist);
  541. poperror();
  542. /*
  543. * copy qid array
  544. */
  545. for(i = 0; i < wq->nqid; i++)
  546. rpc->wqid[i] = wq->qid[i];
  547. rpc->nwqid = wq->nqid;
  548. /*
  549. * update the channel if everything walked correctly.
  550. */
  551. if(isnew && wq->nqid == nwname){
  552. nf->chan = wq->clone;
  553. nf->attached = 1;
  554. }
  555. free(wq);
  556. Exputfid(fs, f);
  557. Exputfid(fs, nf);
  558. return nil;
  559. }
  560. static char*
  561. Exopen(Export *fs, Fcall *rpc, uint8_t *)
  562. {
  563. Fid *volatile f;
  564. Chan *c;
  565. int iou;
  566. f = Exgetfid(fs, rpc->fid);
  567. if(f == nil)
  568. return Enofid;
  569. if(waserror()){
  570. Exputfid(fs, f);
  571. return up->error;
  572. }
  573. c = f->chan;
  574. c = (*devtab[c->type]->open)(c, rpc->mode);
  575. poperror();
  576. f->chan = c;
  577. f->offset = 0;
  578. rpc->qid = f->chan->qid;
  579. iou = f->chan->iounit;
  580. if(iou > fs->iounit)
  581. iou = fs->iounit;
  582. rpc->iounit = iou;
  583. Exputfid(fs, f);
  584. return nil;
  585. }
  586. static char*
  587. Excreate(Export *fs, Fcall *rpc, uint8_t *)
  588. {
  589. Fid *f;
  590. Chan *c;
  591. int iou;
  592. f = Exgetfid(fs, rpc->fid);
  593. if(f == nil)
  594. return Enofid;
  595. if(waserror()){
  596. Exputfid(fs, f);
  597. return up->error;
  598. }
  599. c = f->chan;
  600. (*devtab[c->type]->create)(c, rpc->name, rpc->mode, rpc->perm);
  601. poperror();
  602. f->chan = c;
  603. rpc->qid = f->chan->qid;
  604. iou = f->chan->iounit;
  605. if(iou > fs->iounit)
  606. iou = fs->iounit;
  607. rpc->iounit = iou;
  608. Exputfid(fs, f);
  609. return nil;
  610. }
  611. static char*
  612. Exread(Export *fs, Fcall *rpc, uint8_t *buf)
  613. {
  614. Fid *f;
  615. Chan *c;
  616. int32_t off;
  617. f = Exgetfid(fs, rpc->fid);
  618. if(f == nil)
  619. return Enofid;
  620. c = f->chan;
  621. if(waserror()){
  622. Exputfid(fs, f);
  623. return up->error;
  624. }
  625. rpc->data = (char*)buf;
  626. off = rpc->offset;
  627. c->offset = off;
  628. rpc->count = (*devtab[c->type]->read)(c, rpc->data, rpc->count, off);
  629. poperror();
  630. Exputfid(fs, f);
  631. return nil;
  632. }
  633. static char*
  634. Exwrite(Export *fs, Fcall *rpc, uint8_t *)
  635. {
  636. Fid *f;
  637. Chan *c;
  638. f = Exgetfid(fs, rpc->fid);
  639. if(f == nil)
  640. return Enofid;
  641. if(waserror()){
  642. Exputfid(fs, f);
  643. return up->error;
  644. }
  645. c = f->chan;
  646. if(c->qid.type & QTDIR)
  647. error(Eisdir);
  648. rpc->count = (*devtab[c->type]->write)(c, rpc->data, rpc->count, rpc->offset);
  649. poperror();
  650. Exputfid(fs, f);
  651. return nil;
  652. }
  653. static char*
  654. Exstat(Export *fs, Fcall *rpc, uint8_t *buf)
  655. {
  656. Fid *f;
  657. Chan *c;
  658. f = Exgetfid(fs, rpc->fid);
  659. if(f == nil)
  660. return Enofid;
  661. if(waserror()){
  662. Exputfid(fs, f);
  663. return up->error;
  664. }
  665. c = f->chan;
  666. rpc->stat = buf;
  667. rpc->nstat = (*devtab[c->type]->stat)(c, rpc->stat, Maxrpc);
  668. poperror();
  669. Exputfid(fs, f);
  670. return nil;
  671. }
  672. static char*
  673. Exwstat(Export *fs, Fcall *rpc, uint8_t *)
  674. {
  675. Fid *f;
  676. Chan *c;
  677. f = Exgetfid(fs, rpc->fid);
  678. if(f == nil)
  679. return Enofid;
  680. if(waserror()){
  681. Exputfid(fs, f);
  682. return up->error;
  683. }
  684. c = f->chan;
  685. (*devtab[c->type]->wstat)(c, rpc->stat, rpc->nstat);
  686. poperror();
  687. Exputfid(fs, f);
  688. return nil;
  689. }
  690. static char*
  691. Exremove(Export *fs, Fcall *rpc, uint8_t *)
  692. {
  693. Fid *f;
  694. Chan *c;
  695. f = Exgetfid(fs, rpc->fid);
  696. if(f == nil)
  697. return Enofid;
  698. if(waserror()){
  699. Exputfid(fs, f);
  700. return up->error;
  701. }
  702. c = f->chan;
  703. (*devtab[c->type]->remove)(c);
  704. poperror();
  705. /*
  706. * chan is already clunked by remove.
  707. * however, we need to recover the chan,
  708. * and follow sysremove's lead in making to point to root.
  709. */
  710. c->type = 0;
  711. f->attached = 0;
  712. Exputfid(fs, f);
  713. return nil;
  714. }