io.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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. wunlock(c);
  193. }
  194. runlock(&cachelock);
  195. }
  196. static Whist*
  197. getcache(int n, int hist)
  198. {
  199. int i, isw;
  200. ulong t;
  201. Wcache *c, **cp, **evict;
  202. Whist *wh;
  203. isw = 0;
  204. rlock(&cachelock);
  205. if(c = findcache(n)){
  206. Found:
  207. current(c);
  208. if(hist)
  209. currenthist(c);
  210. rlock(c);
  211. if(hist)
  212. wh = c->hist;
  213. else
  214. wh = c->current;
  215. if(wh)
  216. incref(wh);
  217. runlock(c);
  218. if(isw)
  219. wunlock(&cachelock);
  220. else
  221. runlock(&cachelock);
  222. return wh;
  223. }
  224. runlock(&cachelock);
  225. wlock(&cachelock);
  226. if(c = findcache(n)){
  227. isw = 1; /* better to downgrade lock but can't */
  228. goto Found;
  229. }
  230. if(ncache < Mcache){
  231. Alloc:
  232. c = emalloc(sizeof *c);
  233. ncache++;
  234. }else{
  235. /* find something to evict. */
  236. t = ~0;
  237. evict = nil;
  238. for(i=0; i<Nhash; i++){
  239. for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){
  240. if(c->use < t
  241. && (!c->hist || c->hist->ref==1)
  242. && (!c->current || c->current->ref==1)){
  243. evict = cp;
  244. t = c->use;
  245. }
  246. }
  247. }
  248. if(evict == nil){
  249. fprint(2, "wikifs: nothing to evict\n");
  250. goto Alloc;
  251. }
  252. c = *evict;
  253. *evict = c->hash;
  254. closewhist(c->current);
  255. closewhist(c->hist);
  256. memset(c, 0, sizeof *c);
  257. }
  258. c->n = n;
  259. c->hash = tab[n%Nhash];
  260. tab[n%Nhash] = c;
  261. isw = 1;
  262. goto Found;
  263. }
  264. Whist*
  265. getcurrent(int n)
  266. {
  267. return getcache(n, 0);
  268. }
  269. Whist*
  270. gethistory(int n)
  271. {
  272. return getcache(n, 1);
  273. }
  274. RWLock maplock;
  275. Map *map;
  276. static int
  277. mapcmp(const void *va, const void *vb)
  278. {
  279. Mapel *a, *b;
  280. a = (Mapel*)va;
  281. b = (Mapel*)vb;
  282. return strcmp(a->s, b->s);
  283. }
  284. void
  285. closemap(Map *m)
  286. {
  287. if(decref(m)==0){
  288. free(m->buf);
  289. free(m->el);
  290. free(m);
  291. }
  292. }
  293. void
  294. currentmap(int force)
  295. {
  296. char *p, *q, *r;
  297. int lfd, fd, m, n;
  298. Dir *d;
  299. Map *nmap;
  300. lfd = -1;
  301. fd = -1;
  302. d = nil;
  303. nmap = nil;
  304. if(!force && map && map->t+Tcache >= time(0))
  305. return;
  306. wlock(&maplock);
  307. if(!force && map && map->t+Tcache >= time(0))
  308. goto Return;
  309. if((lfd = getlock("d/L.map")) < 0)
  310. goto Return;
  311. if((d = wdirstat("d/map")) == nil)
  312. goto Return;
  313. if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
  314. map->t = time(0);
  315. goto Return;
  316. }
  317. if(d->length > Maxmap){
  318. //LOG
  319. goto Return;
  320. }
  321. if((fd = wopen("d/map", OREAD)) < 0)
  322. goto Return;
  323. nmap = emalloc(sizeof *nmap);
  324. nmap->buf = emalloc(d->length+1);
  325. n = readn(fd, nmap->buf, d->length);
  326. if(n != d->length)
  327. goto Return;
  328. nmap->buf[n] = '\0';
  329. n = 0;
  330. for(p=nmap->buf; p; p=strchr(p+1, '\n'))
  331. n++;
  332. nmap->el = emalloc(n*sizeof(nmap->el[0]));
  333. m = 0;
  334. for(p=nmap->buf; p && *p && m < n; p=q){
  335. if(q = strchr(p+1, '\n'))
  336. *q++ = '\0';
  337. nmap->el[m].n = strtol(p, &r, 10);
  338. if(*r == ' ')
  339. r++;
  340. else
  341. {}//LOG?
  342. nmap->el[m].s = strcondense(r, 1);
  343. m++;
  344. }
  345. //LOG if m != n
  346. nmap->qid = d->qid;
  347. nmap->t = time(0);
  348. nmap->nel = m;
  349. qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
  350. if(map)
  351. closemap(map);
  352. map = nmap;
  353. incref(map);
  354. nmap = nil;
  355. Return:
  356. free(d);
  357. if(nmap){
  358. free(nmap->el);
  359. free(nmap->buf);
  360. free(nmap);
  361. }
  362. if(map == nil)
  363. sysfatal("cannot get map");
  364. if(fd >= 0)
  365. close(fd);
  366. if(lfd >= 0)
  367. close(lfd);
  368. wunlock(&maplock);
  369. }
  370. int
  371. allocnum(char *title, int mustbenew)
  372. {
  373. char *p, *q;
  374. int lfd, fd, n;
  375. Biobuf b;
  376. if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
  377. werrstr("reserved title name");
  378. return -1;
  379. }
  380. if(title[0]=='\0' || strpbrk(title, "/<>:")){
  381. werrstr("invalid character in name");
  382. return -1;
  383. }
  384. if((n = nametonum(title)) >= 0){
  385. if(mustbenew){
  386. werrstr("duplicate title");
  387. return -1;
  388. }
  389. return n;
  390. }
  391. title = estrdup(title);
  392. strcondense(title, 1);
  393. strlower(title);
  394. if(strchr(title, '\n') || strlen(title) > 200){
  395. werrstr("bad title");
  396. free(title);
  397. return -1;
  398. }
  399. if((lfd = getlock("d/L.map")) < 0){
  400. free(title);
  401. return -1;
  402. }
  403. if((fd = wopen("d/map", ORDWR)) < 0){ // LOG?
  404. close(lfd);
  405. free(title);
  406. return -1;
  407. }
  408. /*
  409. * What we really need to do here is make sure the
  410. * map is up-to-date, then make sure the title isn't
  411. * taken, and then add it, all without dropping the locks.
  412. *
  413. * This turns out to be a mess when you start adding
  414. * all the necessary dolock flags, so instead we just
  415. * read through the file ourselves, and let our
  416. * map catch up on its own.
  417. */
  418. Binit(&b, fd, OREAD);
  419. n = 0;
  420. while(p = Brdline(&b, '\n')){
  421. p[Blinelen(&b)-1] = '\0';
  422. n = atoi(p)+1;
  423. q = strchr(p, ' ');
  424. if(q == nil)
  425. continue;
  426. if(strcmp(q+1, title) == 0){
  427. free(title);
  428. close(fd);
  429. close(lfd);
  430. if(mustbenew){
  431. werrstr("duplicate title");
  432. return -1;
  433. }else
  434. return n;
  435. }
  436. }
  437. seek(fd, 0, 2); /* just in case it's not append only */
  438. fprint(fd, "%d %s\n", n, title);
  439. close(fd);
  440. close(lfd);
  441. free(title);
  442. /* kick the map */
  443. currentmap(1);
  444. return n;
  445. }
  446. int
  447. nametonum(char *s)
  448. {
  449. char *p;
  450. int i, lo, hi, m, rv;
  451. s = estrdup(s);
  452. strlower(s);
  453. for(p=s; *p; p++)
  454. if(*p=='_')
  455. *p = ' ';
  456. currentmap(0);
  457. rlock(&maplock);
  458. lo = 0;
  459. hi = map->nel;
  460. while(hi-lo > 1){
  461. m = (lo+hi)/2;
  462. i = strcmp(s, map->el[m].s);
  463. if(i < 0)
  464. hi = m;
  465. else
  466. lo = m;
  467. }
  468. if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
  469. rv = map->el[lo].n;
  470. else
  471. rv = -1;
  472. runlock(&maplock);
  473. free(s);
  474. return rv;
  475. }
  476. char*
  477. numtoname(int n)
  478. {
  479. int i;
  480. char *s;
  481. currentmap(0);
  482. rlock(&maplock);
  483. for(i=0; i<map->nel; i++){
  484. if(map->el[i].n==n)
  485. break;
  486. }
  487. if(i==map->nel){
  488. runlock(&maplock);
  489. return nil;
  490. }
  491. s = estrdup(map->el[i].s);
  492. runlock(&maplock);
  493. return s;
  494. }
  495. Whist*
  496. getcurrentbyname(char *s)
  497. {
  498. int n;
  499. if((n = nametonum(s)) < 0)
  500. return nil;
  501. return getcache(n, 0);
  502. }
  503. static String*
  504. Brdstring(Biobuf *b)
  505. {
  506. String *s;
  507. s = s_new();
  508. while(s_read(b, s, 8192) > 0)
  509. ;
  510. return s;
  511. }
  512. /*
  513. * Attempt to install a new page. If t==0 we are creating.
  514. * Otherwise, we are editing and t must be set to the current
  515. * version (t is the version we started with) to avoid conflicting
  516. * writes.
  517. *
  518. * If there is a conflicting write, we still write the page to
  519. * the history file, but mark it as a failed write.
  520. */
  521. int
  522. writepage(int num, ulong t, String *s, char *title)
  523. {
  524. char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
  525. int conflict, lfd, fd;
  526. Biobuf *b;
  527. String *os;
  528. sprint(tmp, "d/%d", num);
  529. sprint(tmplock, "d/L.%d", num);
  530. sprint(hist, "d/%d.hist", num);
  531. if((lfd = getlock(tmplock)) < 0)
  532. return -1;
  533. conflict = 0;
  534. if(b = wBopen(tmp, OREAD)){
  535. Brdline(b, '\n'); /* title */
  536. if(p = Brdline(b, '\n')) /* version */
  537. p[Blinelen(b)-1] = '\0';
  538. if(p==nil || p[0] != 'D'){
  539. snprint(err, sizeof err, "bad format in extant file");
  540. conflict = 1;
  541. }else if(strtoul(p+1, 0, 0) != t){
  542. os = Brdstring(b);
  543. p = strchr(s_to_c(s), '\n');
  544. if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */
  545. close(lfd);
  546. return 0;
  547. }
  548. s_free(os);
  549. snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
  550. conflict = 1;
  551. }
  552. Bterm(b);
  553. }else{
  554. if(t != 0){
  555. close(lfd);
  556. werrstr("did not expect to create");
  557. return -1;
  558. }
  559. }
  560. if((fd = wopen(hist, OWRITE)) < 0){
  561. if((fd = wcreate(hist, OWRITE, 0666)) < 0){
  562. close(lfd);
  563. return -1;
  564. }else
  565. fprint(fd, "%s\n", title);
  566. }
  567. if(seek(fd, 0, 2) < 0
  568. || (conflict && write(fd, "X\n", 2) != 2)
  569. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  570. close(fd);
  571. close(lfd);
  572. return -1;
  573. }
  574. close(fd);
  575. if(conflict){
  576. close(lfd);
  577. voidcache(num);
  578. werrstr(err);
  579. return -1;
  580. }
  581. if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
  582. close(lfd);
  583. voidcache(num);
  584. return -1;
  585. }
  586. if(write(fd, title, strlen(title)) != strlen(title)
  587. || write(fd, "\n", 1) != 1
  588. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  589. close(fd);
  590. close(lfd);
  591. voidcache(num);
  592. return -1;
  593. }
  594. close(fd);
  595. close(lfd);
  596. voidcache(num);
  597. return 0;
  598. }