io.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  1. /*
  2. * I/O for a Wiki document set.
  3. *
  4. * The files are kept in one flat directory.
  5. * There are three files for each document:
  6. * nnn - current version of the document
  7. * nnn.hist - history (all old versions) of the document
  8. * append-only
  9. * L.nnn - write lock file for the document
  10. *
  11. * At the moment, since we don't have read/write locks
  12. * in the file system, we use the L.nnn file as a read lock too.
  13. * It's a hack but there aren't supposed to be many readers
  14. * anyway.
  15. *
  16. * The nnn.hist file is in the format read by Brdwhist.
  17. * The nnn file is in that format too, but only contains the
  18. * last entry of the nnn.hist file.
  19. *
  20. * In addition to this set of files, there is an append-only
  21. * map file that provides a mapping between numbers and titles.
  22. * The map file is a sequence of lines of the form
  23. * nnn Title Here
  24. * The lock file L.map must be held to add to the end, to
  25. * make sure that the numbers are allocated sequentially.
  26. *
  27. * We assume that writes to the map file will fit in one message,
  28. * so that we don't have to read-lock the file.
  29. */
  30. #include <u.h>
  31. #include <libc.h>
  32. #include <bio.h>
  33. #include <String.h>
  34. #include <thread.h>
  35. #include "wiki.h"
  36. enum {
  37. Nhash = 64,
  38. Mcache = 128,
  39. };
  40. typedef struct Wcache Wcache;
  41. struct Wcache {
  42. int n;
  43. ulong use;
  44. RWLock;
  45. ulong tcurrent;
  46. ulong thist;
  47. Whist *hist;
  48. Whist *current;
  49. Qid qid;
  50. Qid qidhist;
  51. Wcache *hash;
  52. };
  53. static RWLock cachelock;
  54. static Wcache *tab[Nhash];
  55. int ncache;
  56. void
  57. closewhist(Whist *wh)
  58. {
  59. int i;
  60. if(wh && decref(wh) == 0){
  61. free(wh->title);
  62. for(i=0; i<wh->ndoc; i++){
  63. free(wh->doc[i].author);
  64. free(wh->doc[i].comment);
  65. freepage(wh->doc[i].wtxt);
  66. }
  67. free(wh->doc);
  68. free(wh);
  69. }
  70. }
  71. void
  72. freepage(Wpage *p)
  73. {
  74. Wpage *next;
  75. for(; p; p=next){
  76. next = p->next;
  77. free(p->text);
  78. free(p->url);
  79. free(p);
  80. }
  81. }
  82. static Wcache*
  83. findcache(int n)
  84. {
  85. Wcache *w;
  86. for(w=tab[n%Nhash]; w; w=w->hash)
  87. if(w->n == n){
  88. w->use = time(0);
  89. return w;
  90. }
  91. return nil;
  92. }
  93. static int
  94. getlock(char *lock)
  95. {
  96. char buf[ERRMAX];
  97. int i, fd;
  98. enum { SECS = 200 };
  99. for(i=0; i<SECS*10; i++){
  100. fd = wcreate(lock, ORDWR, DMEXCL|0666);
  101. if(fd >= 0)
  102. return fd;
  103. buf[0] = '\0';
  104. rerrstr(buf, sizeof buf);
  105. if(strstr(buf, "locked") == nil)
  106. break;
  107. sleep(1000/10);
  108. }
  109. werrstr("couldn't acquire lock");
  110. return -1;
  111. }
  112. static Whist*
  113. readwhist(char *file, char *lock, Qid *qid)
  114. {
  115. int lfd;
  116. Biobuf *b;
  117. Dir *d;
  118. Whist *wh;
  119. if((lfd=getlock(lock)) < 0) // LOG?
  120. return nil;
  121. if(qid){
  122. if((d = wdirstat(file)) == nil){
  123. close(lfd);
  124. return nil;
  125. }
  126. *qid = d->qid;
  127. free(d);
  128. }
  129. if((b = wBopen(file, OREAD)) == nil){ //LOG?
  130. close(lfd);
  131. return nil;
  132. }
  133. wh = Brdwhist(b);
  134. Bterm(b);
  135. close(lfd);
  136. return wh;
  137. }
  138. static void
  139. gencurrent(Wcache *w, Qid *q, char *file, char *lock, ulong *t, Whist **wp, int n)
  140. {
  141. Dir *d;
  142. Whist *wh;
  143. if(*wp && *t+Tcache >= time(0))
  144. return;
  145. wlock(w);
  146. if(*wp && *t+Tcache >= time(0)){
  147. wunlock(w);
  148. return;
  149. }
  150. if(((d = wdirstat(file)) == nil) || (d->qid.path==q->path && d->qid.vers==q->vers)){
  151. *t = time(0);
  152. wunlock(w);
  153. free(d);
  154. return;
  155. }
  156. free(d);
  157. if(wh = readwhist(file, lock, q)){
  158. wh->n = n;
  159. *t = time(0);
  160. closewhist(*wp);
  161. *wp = wh;
  162. }
  163. else fprint(2, "error file=%s lock=%s %r\n", file, lock);
  164. wunlock(w);
  165. }
  166. static void
  167. current(Wcache *w)
  168. {
  169. char tmp[40];
  170. char tmplock[40];
  171. sprint(tmplock, "d/L.%d", w->n);
  172. sprint(tmp, "d/%d", w->n);
  173. gencurrent(w, &w->qid, tmp, tmplock, &w->tcurrent, &w->current, w->n);
  174. }
  175. static void
  176. currenthist(Wcache *w)
  177. {
  178. char hist[40], lock[40];
  179. sprint(hist, "d/%d.hist", w->n);
  180. sprint(lock, "d/L.%d", w->n);
  181. gencurrent(w, &w->qidhist, hist, lock, &w->thist, &w->hist, w->n);
  182. }
  183. void
  184. voidcache(int n)
  185. {
  186. Wcache *c;
  187. rlock(&cachelock);
  188. if(c = findcache(n)){
  189. wlock(c);
  190. c->tcurrent = 0;
  191. c->thist = 0;
  192. /* aggressively free memory */
  193. closewhist(c->hist);
  194. c->hist = nil;
  195. closewhist(c->current);
  196. c->current = nil;
  197. wunlock(c);
  198. }
  199. runlock(&cachelock);
  200. }
  201. static Whist*
  202. getcache(int n, int hist)
  203. {
  204. int i, isw;
  205. ulong t;
  206. Wcache *c, **cp, **evict;
  207. Whist *wh;
  208. isw = 0;
  209. rlock(&cachelock);
  210. if(c = findcache(n)){
  211. Found:
  212. current(c);
  213. if(hist)
  214. currenthist(c);
  215. rlock(c);
  216. if(hist)
  217. wh = c->hist;
  218. else
  219. wh = c->current;
  220. if(wh)
  221. incref(wh);
  222. runlock(c);
  223. if(isw)
  224. wunlock(&cachelock);
  225. else
  226. runlock(&cachelock);
  227. return wh;
  228. }
  229. runlock(&cachelock);
  230. wlock(&cachelock);
  231. if(c = findcache(n)){
  232. isw = 1; /* better to downgrade lock but can't */
  233. goto Found;
  234. }
  235. if(ncache < Mcache){
  236. Alloc:
  237. c = emalloc(sizeof *c);
  238. ncache++;
  239. }else{
  240. /* find something to evict. */
  241. t = ~0;
  242. evict = nil;
  243. for(i=0; i<Nhash; i++){
  244. for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){
  245. if(c->use < t
  246. && (!c->hist || c->hist->ref==1)
  247. && (!c->current || c->current->ref==1)){
  248. evict = cp;
  249. t = c->use;
  250. }
  251. }
  252. }
  253. if(evict == nil){
  254. fprint(2, "wikifs: nothing to evict\n");
  255. goto Alloc;
  256. }
  257. c = *evict;
  258. *evict = c->hash;
  259. closewhist(c->current);
  260. closewhist(c->hist);
  261. memset(c, 0, sizeof *c);
  262. }
  263. c->n = n;
  264. c->hash = tab[n%Nhash];
  265. tab[n%Nhash] = c;
  266. isw = 1;
  267. goto Found;
  268. }
  269. Whist*
  270. getcurrent(int n)
  271. {
  272. return getcache(n, 0);
  273. }
  274. Whist*
  275. gethistory(int n)
  276. {
  277. return getcache(n, 1);
  278. }
  279. RWLock maplock;
  280. Map *map;
  281. static int
  282. mapcmp(const void *va, const void *vb)
  283. {
  284. Mapel *a, *b;
  285. a = (Mapel*)va;
  286. b = (Mapel*)vb;
  287. return strcmp(a->s, b->s);
  288. }
  289. void
  290. closemap(Map *m)
  291. {
  292. if(decref(m)==0){
  293. free(m->buf);
  294. free(m->el);
  295. free(m);
  296. }
  297. }
  298. void
  299. currentmap(int force)
  300. {
  301. char *p, *q, *r;
  302. int lfd, fd, m, n;
  303. Dir *d;
  304. Map *nmap;
  305. char *err = nil;
  306. lfd = -1;
  307. fd = -1;
  308. d = nil;
  309. nmap = nil;
  310. if(!force && map && map->t+Tcache >= time(0))
  311. return;
  312. wlock(&maplock);
  313. if(!force && map && map->t+Tcache >= time(0))
  314. goto Return;
  315. if((lfd = getlock("d/L.map")) < 0){
  316. err = "can't lock";
  317. goto Return;
  318. }
  319. if((d = wdirstat("d/map")) == nil)
  320. goto Return;
  321. if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
  322. map->t = time(0);
  323. goto Return;
  324. }
  325. if(d->length > Maxmap){
  326. //LOG
  327. err = "too long";
  328. goto Return;
  329. }
  330. if((fd = wopen("d/map", OREAD)) < 0)
  331. goto Return;
  332. nmap = emalloc(sizeof *nmap);
  333. nmap->buf = emalloc(d->length+1);
  334. n = readn(fd, nmap->buf, d->length);
  335. if(n != d->length){
  336. err = "bad length";
  337. goto Return;
  338. }
  339. nmap->buf[n] = '\0';
  340. n = 0;
  341. for(p=nmap->buf; p; p=strchr(p+1, '\n'))
  342. n++;
  343. nmap->el = emalloc(n*sizeof(nmap->el[0]));
  344. m = 0;
  345. for(p=nmap->buf; p && *p && m < n; p=q){
  346. if(q = strchr(p+1, '\n'))
  347. *q++ = '\0';
  348. nmap->el[m].n = strtol(p, &r, 10);
  349. if(*r == ' ')
  350. r++;
  351. else
  352. {}//LOG?
  353. nmap->el[m].s = strcondense(r, 1);
  354. m++;
  355. }
  356. //LOG if m != n
  357. nmap->qid = d->qid;
  358. nmap->t = time(0);
  359. nmap->nel = m;
  360. qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
  361. if(map)
  362. closemap(map);
  363. map = nmap;
  364. incref(map);
  365. nmap = nil;
  366. Return:
  367. free(d);
  368. if(nmap){
  369. free(nmap->el);
  370. free(nmap->buf);
  371. free(nmap);
  372. }
  373. if(map == nil)
  374. sysfatal("cannot get map: %s: %r", err);
  375. if(fd >= 0)
  376. close(fd);
  377. if(lfd >= 0)
  378. close(lfd);
  379. wunlock(&maplock);
  380. }
  381. int
  382. allocnum(char *title, int mustbenew)
  383. {
  384. char *p, *q;
  385. int lfd, fd, n;
  386. Biobuf b;
  387. if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
  388. werrstr("reserved title name");
  389. return -1;
  390. }
  391. if(title[0]=='\0' || strpbrk(title, "/<>:?")){
  392. werrstr("invalid character in name");
  393. return -1;
  394. }
  395. if((n = nametonum(title)) >= 0){
  396. if(mustbenew){
  397. werrstr("duplicate title");
  398. return -1;
  399. }
  400. return n;
  401. }
  402. title = estrdup(title);
  403. strcondense(title, 1);
  404. strlower(title);
  405. if(strchr(title, '\n') || strlen(title) > 200){
  406. werrstr("bad title");
  407. free(title);
  408. return -1;
  409. }
  410. if((lfd = getlock("d/L.map")) < 0){
  411. free(title);
  412. return -1;
  413. }
  414. if((fd = wopen("d/map", ORDWR)) < 0){ // LOG?
  415. close(lfd);
  416. free(title);
  417. return -1;
  418. }
  419. /*
  420. * What we really need to do here is make sure the
  421. * map is up-to-date, then make sure the title isn't
  422. * taken, and then add it, all without dropping the locks.
  423. *
  424. * This turns out to be a mess when you start adding
  425. * all the necessary dolock flags, so instead we just
  426. * read through the file ourselves, and let our
  427. * map catch up on its own.
  428. */
  429. Binit(&b, fd, OREAD);
  430. n = 0;
  431. while(p = Brdline(&b, '\n')){
  432. p[Blinelen(&b)-1] = '\0';
  433. n = atoi(p)+1;
  434. q = strchr(p, ' ');
  435. if(q == nil)
  436. continue;
  437. if(strcmp(q+1, title) == 0){
  438. free(title);
  439. close(fd);
  440. close(lfd);
  441. if(mustbenew){
  442. werrstr("duplicate title");
  443. return -1;
  444. }else
  445. return n;
  446. }
  447. }
  448. seek(fd, 0, 2); /* just in case it's not append only */
  449. fprint(fd, "%d %s\n", n, title);
  450. close(fd);
  451. close(lfd);
  452. free(title);
  453. /* kick the map */
  454. currentmap(1);
  455. return n;
  456. }
  457. int
  458. nametonum(char *s)
  459. {
  460. char *p;
  461. int i, lo, hi, m, rv;
  462. s = estrdup(s);
  463. strlower(s);
  464. for(p=s; *p; p++)
  465. if(*p=='_')
  466. *p = ' ';
  467. currentmap(0);
  468. rlock(&maplock);
  469. lo = 0;
  470. hi = map->nel;
  471. while(hi-lo > 1){
  472. m = (lo+hi)/2;
  473. i = strcmp(s, map->el[m].s);
  474. if(i < 0)
  475. hi = m;
  476. else
  477. lo = m;
  478. }
  479. if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
  480. rv = map->el[lo].n;
  481. else
  482. rv = -1;
  483. runlock(&maplock);
  484. free(s);
  485. return rv;
  486. }
  487. char*
  488. numtoname(int n)
  489. {
  490. int i;
  491. char *s;
  492. currentmap(0);
  493. rlock(&maplock);
  494. for(i=0; i<map->nel; i++){
  495. if(map->el[i].n==n)
  496. break;
  497. }
  498. if(i==map->nel){
  499. runlock(&maplock);
  500. return nil;
  501. }
  502. s = estrdup(map->el[i].s);
  503. runlock(&maplock);
  504. return s;
  505. }
  506. Whist*
  507. getcurrentbyname(char *s)
  508. {
  509. int n;
  510. if((n = nametonum(s)) < 0)
  511. return nil;
  512. return getcache(n, 0);
  513. }
  514. static String*
  515. Brdstring(Biobuf *b)
  516. {
  517. long len;
  518. String *s;
  519. Dir *d;
  520. d = dirfstat(Bfildes(b));
  521. if (d == nil) /* shouldn't happen, we just opened it */
  522. len = 0;
  523. else
  524. len = d->length;
  525. free(d);
  526. s = s_newalloc(len);
  527. s_read(b, s, len);
  528. return s;
  529. }
  530. /*
  531. * Attempt to install a new page. If t==0 we are creating.
  532. * Otherwise, we are editing and t must be set to the current
  533. * version (t is the version we started with) to avoid conflicting
  534. * writes.
  535. *
  536. * If there is a conflicting write, we still write the page to
  537. * the history file, but mark it as a failed write.
  538. */
  539. int
  540. writepage(int num, ulong t, String *s, char *title)
  541. {
  542. char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
  543. int conflict, lfd, fd;
  544. Biobuf *b;
  545. String *os;
  546. sprint(tmp, "d/%d", num);
  547. sprint(tmplock, "d/L.%d", num);
  548. sprint(hist, "d/%d.hist", num);
  549. if((lfd = getlock(tmplock)) < 0)
  550. return -1;
  551. conflict = 0;
  552. if(b = wBopen(tmp, OREAD)){
  553. Brdline(b, '\n'); /* title */
  554. if(p = Brdline(b, '\n')) /* version */
  555. p[Blinelen(b)-1] = '\0';
  556. if(p==nil || p[0] != 'D'){
  557. snprint(err, sizeof err, "bad format in extant file");
  558. conflict = 1;
  559. }else if(strtoul(p+1, 0, 0) != t){
  560. os = Brdstring(b); /* why read the whole file? */
  561. p = strchr(s_to_c(s), '\n');
  562. if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */
  563. close(lfd);
  564. s_free(os);
  565. Bterm(b);
  566. return 0;
  567. }
  568. s_free(os);
  569. snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
  570. conflict = 1;
  571. }
  572. Bterm(b);
  573. }else{
  574. if(t != 0){
  575. close(lfd);
  576. werrstr("did not expect to create");
  577. return -1;
  578. }
  579. }
  580. if((fd = wopen(hist, OWRITE)) < 0){
  581. if((fd = wcreate(hist, OWRITE, 0666)) < 0){
  582. close(lfd);
  583. return -1;
  584. }else
  585. fprint(fd, "%s\n", title);
  586. }
  587. if(seek(fd, 0, 2) < 0
  588. || (conflict && write(fd, "X\n", 2) != 2)
  589. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  590. close(fd);
  591. close(lfd);
  592. return -1;
  593. }
  594. close(fd);
  595. if(conflict){
  596. close(lfd);
  597. voidcache(num);
  598. werrstr(err);
  599. return -1;
  600. }
  601. if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
  602. close(lfd);
  603. voidcache(num);
  604. return -1;
  605. }
  606. if(write(fd, title, strlen(title)) != strlen(title)
  607. || write(fd, "\n", 1) != 1
  608. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  609. close(fd);
  610. close(lfd);
  611. voidcache(num);
  612. return -1;
  613. }
  614. close(fd);
  615. close(lfd);
  616. voidcache(num);
  617. return 0;
  618. }