io.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  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. char *err = nil;
  301. lfd = -1;
  302. fd = -1;
  303. d = nil;
  304. nmap = nil;
  305. if(!force && map && map->t+Tcache >= time(0))
  306. return;
  307. wlock(&maplock);
  308. if(!force && map && map->t+Tcache >= time(0))
  309. goto Return;
  310. if((lfd = getlock("d/L.map")) < 0){
  311. err = "can't lock";
  312. goto Return;
  313. }
  314. if((d = wdirstat("d/map")) == nil)
  315. goto Return;
  316. if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){
  317. map->t = time(0);
  318. goto Return;
  319. }
  320. if(d->length > Maxmap){
  321. //LOG
  322. err = "too long";
  323. goto Return;
  324. }
  325. if((fd = wopen("d/map", OREAD)) < 0)
  326. goto Return;
  327. nmap = emalloc(sizeof *nmap);
  328. nmap->buf = emalloc(d->length+1);
  329. n = readn(fd, nmap->buf, d->length);
  330. if(n != d->length){
  331. err = "bad length";
  332. goto Return;
  333. }
  334. nmap->buf[n] = '\0';
  335. n = 0;
  336. for(p=nmap->buf; p; p=strchr(p+1, '\n'))
  337. n++;
  338. nmap->el = emalloc(n*sizeof(nmap->el[0]));
  339. m = 0;
  340. for(p=nmap->buf; p && *p && m < n; p=q){
  341. if(q = strchr(p+1, '\n'))
  342. *q++ = '\0';
  343. nmap->el[m].n = strtol(p, &r, 10);
  344. if(*r == ' ')
  345. r++;
  346. else
  347. {}//LOG?
  348. nmap->el[m].s = strcondense(r, 1);
  349. m++;
  350. }
  351. //LOG if m != n
  352. nmap->qid = d->qid;
  353. nmap->t = time(0);
  354. nmap->nel = m;
  355. qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp);
  356. if(map)
  357. closemap(map);
  358. map = nmap;
  359. incref(map);
  360. nmap = nil;
  361. Return:
  362. free(d);
  363. if(nmap){
  364. free(nmap->el);
  365. free(nmap->buf);
  366. free(nmap);
  367. }
  368. if(map == nil)
  369. sysfatal("cannot get map: %s: %r", err);
  370. if(fd >= 0)
  371. close(fd);
  372. if(lfd >= 0)
  373. close(lfd);
  374. wunlock(&maplock);
  375. }
  376. int
  377. allocnum(char *title, int mustbenew)
  378. {
  379. char *p, *q;
  380. int lfd, fd, n;
  381. Biobuf b;
  382. if(strcmp(title, "map")==0 || strcmp(title, "new")==0){
  383. werrstr("reserved title name");
  384. return -1;
  385. }
  386. if(title[0]=='\0' || strpbrk(title, "/<>:?")){
  387. werrstr("invalid character in name");
  388. return -1;
  389. }
  390. if((n = nametonum(title)) >= 0){
  391. if(mustbenew){
  392. werrstr("duplicate title");
  393. return -1;
  394. }
  395. return n;
  396. }
  397. title = estrdup(title);
  398. strcondense(title, 1);
  399. strlower(title);
  400. if(strchr(title, '\n') || strlen(title) > 200){
  401. werrstr("bad title");
  402. free(title);
  403. return -1;
  404. }
  405. if((lfd = getlock("d/L.map")) < 0){
  406. free(title);
  407. return -1;
  408. }
  409. if((fd = wopen("d/map", ORDWR)) < 0){ // LOG?
  410. close(lfd);
  411. free(title);
  412. return -1;
  413. }
  414. /*
  415. * What we really need to do here is make sure the
  416. * map is up-to-date, then make sure the title isn't
  417. * taken, and then add it, all without dropping the locks.
  418. *
  419. * This turns out to be a mess when you start adding
  420. * all the necessary dolock flags, so instead we just
  421. * read through the file ourselves, and let our
  422. * map catch up on its own.
  423. */
  424. Binit(&b, fd, OREAD);
  425. n = 0;
  426. while(p = Brdline(&b, '\n')){
  427. p[Blinelen(&b)-1] = '\0';
  428. n = atoi(p)+1;
  429. q = strchr(p, ' ');
  430. if(q == nil)
  431. continue;
  432. if(strcmp(q+1, title) == 0){
  433. free(title);
  434. close(fd);
  435. close(lfd);
  436. if(mustbenew){
  437. werrstr("duplicate title");
  438. return -1;
  439. }else
  440. return n;
  441. }
  442. }
  443. seek(fd, 0, 2); /* just in case it's not append only */
  444. fprint(fd, "%d %s\n", n, title);
  445. close(fd);
  446. close(lfd);
  447. free(title);
  448. /* kick the map */
  449. currentmap(1);
  450. return n;
  451. }
  452. int
  453. nametonum(char *s)
  454. {
  455. char *p;
  456. int i, lo, hi, m, rv;
  457. s = estrdup(s);
  458. strlower(s);
  459. for(p=s; *p; p++)
  460. if(*p=='_')
  461. *p = ' ';
  462. currentmap(0);
  463. rlock(&maplock);
  464. lo = 0;
  465. hi = map->nel;
  466. while(hi-lo > 1){
  467. m = (lo+hi)/2;
  468. i = strcmp(s, map->el[m].s);
  469. if(i < 0)
  470. hi = m;
  471. else
  472. lo = m;
  473. }
  474. if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0)
  475. rv = map->el[lo].n;
  476. else
  477. rv = -1;
  478. runlock(&maplock);
  479. free(s);
  480. return rv;
  481. }
  482. char*
  483. numtoname(int n)
  484. {
  485. int i;
  486. char *s;
  487. currentmap(0);
  488. rlock(&maplock);
  489. for(i=0; i<map->nel; i++){
  490. if(map->el[i].n==n)
  491. break;
  492. }
  493. if(i==map->nel){
  494. runlock(&maplock);
  495. return nil;
  496. }
  497. s = estrdup(map->el[i].s);
  498. runlock(&maplock);
  499. return s;
  500. }
  501. Whist*
  502. getcurrentbyname(char *s)
  503. {
  504. int n;
  505. if((n = nametonum(s)) < 0)
  506. return nil;
  507. return getcache(n, 0);
  508. }
  509. static String*
  510. Brdstring(Biobuf *b)
  511. {
  512. String *s;
  513. s = s_new();
  514. while(s_read(b, s, 8192) > 0)
  515. ;
  516. return s;
  517. }
  518. /*
  519. * Attempt to install a new page. If t==0 we are creating.
  520. * Otherwise, we are editing and t must be set to the current
  521. * version (t is the version we started with) to avoid conflicting
  522. * writes.
  523. *
  524. * If there is a conflicting write, we still write the page to
  525. * the history file, but mark it as a failed write.
  526. */
  527. int
  528. writepage(int num, ulong t, String *s, char *title)
  529. {
  530. char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p;
  531. int conflict, lfd, fd;
  532. Biobuf *b;
  533. String *os;
  534. sprint(tmp, "d/%d", num);
  535. sprint(tmplock, "d/L.%d", num);
  536. sprint(hist, "d/%d.hist", num);
  537. if((lfd = getlock(tmplock)) < 0)
  538. return -1;
  539. conflict = 0;
  540. if(b = wBopen(tmp, OREAD)){
  541. Brdline(b, '\n'); /* title */
  542. if(p = Brdline(b, '\n')) /* version */
  543. p[Blinelen(b)-1] = '\0';
  544. if(p==nil || p[0] != 'D'){
  545. snprint(err, sizeof err, "bad format in extant file");
  546. conflict = 1;
  547. }else if(strtoul(p+1, 0, 0) != t){
  548. os = Brdstring(b);
  549. p = strchr(s_to_c(s), '\n');
  550. if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */
  551. close(lfd);
  552. return 0;
  553. }
  554. s_free(os);
  555. snprint(err, sizeof err, "update conflict %lud != %s", t, p+1);
  556. conflict = 1;
  557. }
  558. Bterm(b);
  559. }else{
  560. if(t != 0){
  561. close(lfd);
  562. werrstr("did not expect to create");
  563. return -1;
  564. }
  565. }
  566. if((fd = wopen(hist, OWRITE)) < 0){
  567. if((fd = wcreate(hist, OWRITE, 0666)) < 0){
  568. close(lfd);
  569. return -1;
  570. }else
  571. fprint(fd, "%s\n", title);
  572. }
  573. if(seek(fd, 0, 2) < 0
  574. || (conflict && write(fd, "X\n", 2) != 2)
  575. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  576. close(fd);
  577. close(lfd);
  578. return -1;
  579. }
  580. close(fd);
  581. if(conflict){
  582. close(lfd);
  583. voidcache(num);
  584. werrstr(err);
  585. return -1;
  586. }
  587. if((fd = wcreate(tmp, OWRITE, 0666)) < 0){
  588. close(lfd);
  589. voidcache(num);
  590. return -1;
  591. }
  592. if(write(fd, title, strlen(title)) != strlen(title)
  593. || write(fd, "\n", 1) != 1
  594. || write(fd, s_to_c(s), s_len(s)) != s_len(s)){
  595. close(fd);
  596. close(lfd);
  597. voidcache(num);
  598. return -1;
  599. }
  600. close(fd);
  601. close(lfd);
  602. voidcache(num);
  603. return 0;
  604. }