chiark / gitweb /
1059a104e330d2c56e9d53569be47b25044190fc
[chiark-tcl.git] / cdb / writeable.c
1 /**/
2
3 #include "chiark_tcl_cdb.h"
4
5 #define ftello ftell
6 #define fseeko fseek
7
8 /*---------- Forward declarations ----------*/
9
10 struct ht_forall_ctx;
11
12 /*---------- Useful routines ----------*/
13
14 static void maybe_close(int fd) {
15   if (fd>=0) close(fd);
16 }
17
18 #define PE(m) do{                                               \
19     rc= cht_posixerr(ip, errno, "failed to " m); goto x_rc;     \
20   }while(0)
21
22 /*==================== Subsystems and subtypes ====================*/
23
24 /*---------- Pathbuf ----------*/
25
26 typedef struct Pathbuf {
27   char *buf, *sfx;
28 } Pathbuf;
29
30 #define MAX_SUFFIX 4
31
32 static void pathbuf_init(Pathbuf *pb, const char *pathb) {
33   int l= strlen(pathb);
34   pb->buf= TALLOC(l + 4);
35   memcpy(pb->buf, pathb, l);
36   pb->sfx= pb->buf + l;
37   *pb->sfx++= '.';
38 }
39 static const char *pathbuf_sfx(Pathbuf *pb, const char *suffix) {
40   assert(strlen(suffix) <= MAX_SUFFIX);
41   strcpy(pb->sfx, suffix);
42   return pb->buf;
43 }
44 static void pathbuf_free(Pathbuf *pb) {
45   TFREE(pb->buf);
46   pb->buf= 0;
47 }
48
49 /*---------- Our hash table ----------*/
50
51 typedef struct HashTable {
52   Tcl_HashTable t;
53   Byte padding[128]; /* allow for expansion by Tcl, urgh */
54   Byte confound[16];
55 } HashTable;
56
57 typedef struct HashValue {
58   int len;
59   Byte data[1];
60 } HashValue;
61
62 static HashValue *htv_prep(int len) {
63   HashValue *hd;
64   hd= TALLOC((hd->data - (Byte*)hd) + len);
65   hd->len= len;
66   return hd;
67 }  
68 static Byte *htv_fillptr(HashValue *hd) {
69   return hd->data;
70 }
71
72 static void ht_setup(HashTable *ht) {
73   Tcl_InitHashTable(&ht->t, TCL_STRING_KEYS);
74 }
75 static void ht_update(HashTable *ht, const char *key, HashValue *val_eat) {
76   Tcl_HashEntry *he;
77   int new;
78
79   he= Tcl_CreateHashEntry(&ht->t, (char*)key, &new);
80   if (!new) TFREE(Tcl_GetHashValue(he));
81   Tcl_SetHashValue(he, val_eat);
82     /* eats the value since the data structure owns the memory */
83 }
84 static void ht_maybeupdate(HashTable *ht, const char *key,
85                            HashValue *val_eat) {
86   /* like ht_update except does not overwrite existing values */
87   Tcl_HashEntry *he;
88   int new;
89
90   he= Tcl_CreateHashEntry(&ht->t, (char*)key, &new);
91   if (!new) { TFREE(val_eat); return; }
92   Tcl_SetHashValue(he, val_eat);
93 }
94
95 static const HashValue *ht_lookup(HashTable *ht, const char *key) {
96   Tcl_HashEntry *he;
97   
98   he= Tcl_FindHashEntry(&ht->t, key);
99   if (!he) return 0;
100   
101   return Tcl_GetHashValue(he);
102 }
103
104 static int ht_forall(HashTable *ht,
105                      int (*fn)(const char *key, HashValue *val,
106                                struct ht_forall_ctx *ctx),
107                      struct ht_forall_ctx *ctx) {
108   /* Returns first positive value returned by any call to fn, or 0. */
109   Tcl_HashSearch sp;
110   Tcl_HashEntry *he;
111   const char *key;
112   HashValue *val;
113   int r;
114   
115   for (he= Tcl_FirstHashEntry(&ht->t, &sp);
116        he;
117        he= Tcl_NextHashEntry(&sp)) {
118     val= Tcl_GetHashValue(he);
119     if (!val->len) continue;
120
121     key= Tcl_GetHashKey(&ht->t, he);
122     
123     r= fn(key, val, ctx);
124     if (r) return r;
125   }
126   return 0;
127 }
128
129 static void ht_destroy(HashTable *ht) {
130   Tcl_HashSearch sp;
131   Tcl_HashEntry *he;
132   
133   for (he= Tcl_FirstHashEntry(&ht->t, &sp);
134        he;
135        he= Tcl_NextHashEntry(&sp)) {
136     /* ht_forall skips empty (deleted) entries so is no good for this */
137     TFREE(Tcl_GetHashValue(he));
138   }
139   Tcl_DeleteHashTable(&ht->t);
140 }
141
142 /*==================== Existential ====================*/
143
144 /*---------- Rw data structure ----------*/
145
146 typedef struct Rw {
147   int ix, autocompact;
148   int cdb_fd, lock_fd;
149   struct cdb cdb; /* valid iff cdb_fd >= 0 */
150   FILE *logfile;
151   HashTable logincore;
152   Pathbuf pbsome, pbother;
153   off_t mainsz;
154   ScriptToInvoke on_info, on_lexminval;
155 } Rw;
156
157 static int rw_close(Tcl_Interp *ip, Rw *rw) {
158   int rc, r;
159
160   rc= TCL_OK;
161   ht_destroy(&rw->logincore);
162   if (rw->cdb_fd >= 0) cdb_free(&rw->cdb);
163   maybe_close(rw->cdb_fd);
164   maybe_close(rw->lock_fd);
165
166   if (rw->logfile) {
167     r= fclose(rw->logfile);
168     if (r && ip) { rc= cht_posixerr(ip, errno, "probable data loss! failed to"
169                                     " fclose logfile during untidy close"); }
170   }
171
172   pathbuf_free(&rw->pbsome); pathbuf_free(&rw->pbother);
173   TFREE(rw);
174   return rc;
175 }
176
177 static void destroy_cdbrw_idtabcb(Tcl_Interp *ip, void *rw) { rw_close(0,rw); }
178 const IdDataSpec cdbtcl_rwdatabases= {
179   "cdb-rwdb", "cdb-openrwdatabases-table", destroy_cdbrw_idtabcb
180 };
181
182 /*---------- File handling ----------*/
183
184 static int acquire_lock(Tcl_Interp *ip, Pathbuf *pb, int *lockfd_r) {
185   /* *lockfd_r must be -1 on entry.  If may be set to >=0 even
186    * on error, and must be closed by the caller. */
187   mode_t um, lockmode;
188   struct flock fl;
189   int r;
190
191   um= umask(~(mode_t)0);
192   umask(um);
193
194   lockmode= 0666 & ~((um & 0444)>>1);
195   /* Remove r where umask would remove w;
196    * eg umask intending 0664 here gives 0660 */
197   
198   *lockfd_r= open(pathbuf_sfx(pb,".lock"), O_RDONLY|O_CREAT, lockmode);
199   if (*lockfd_r < 0)
200     return cht_posixerr(ip, errno, "could not open/create lockfile");
201
202   fl.l_type= F_WRLCK;
203   fl.l_whence= SEEK_SET;
204   fl.l_start= 0;
205   fl.l_len= 0;
206   fl.l_pid= getpid();
207
208   r= fcntl(*lockfd_r, F_SETLK, &fl);
209   if (r == -1) {
210     if (errno == EACCES || errno == EAGAIN)
211       return cht_staticerr(ip, "lock held by another process", "CDB LOCKED");
212     else return cht_posixerr(ip, errno, "unexpected error from fcntl while"
213                              " acquiring lock");
214   }
215
216   return TCL_OK;
217 }
218
219 /*---------- Log reading and writing ----------*/
220
221 static int readlognum(FILE *f, int delim, int *num_r) {
222   int c;
223   char numbuf[20], *p, *ep;
224   unsigned long ul;
225
226   p= numbuf;
227   for (;;) {
228     c= getc(f);  if (c==EOF) return -2;
229     if (c == delim) break;
230     if (!isdigit((unsigned char)c)) return -2;
231     *p++= c;
232     if (p == numbuf+sizeof(numbuf)) return -2;
233   }
234   if (p == numbuf) return -2;
235   *p= 0;
236
237   errno=0; ul= strtoul(numbuf, &ep, 10);
238   if (*ep || errno || ul >= INT_MAX/2) return -2;
239   *num_r= ul;
240   return 0;
241 }
242
243 static int readstorelogrecord(FILE *f, HashTable *ht,
244                               int (*omitfn)(const HashValue*,
245                                             struct ht_forall_ctx *ctx),
246                               struct ht_forall_ctx *ctx,
247                               void (*updatefn)(HashTable*, const char*,
248                                                HashValue*)) {
249   /* returns:
250    *      0     for OK
251    *     -1     eof
252    *     -2     corrupt or error
253    *     -3     got newline indicating end
254    *     >0     value from omitfn
255    */
256   int keylen, vallen;
257   char *key;
258   HashValue *val;
259   int c, rc, r;
260
261   c= getc(f);
262   if (c==EOF) { if (feof(f)) return -1; return -2; }
263   if (c=='\n') return -3;
264   if (c!='+') return -2;
265
266   rc= readlognum(f, ',', &keylen);  if (rc) return rc;
267   rc= readlognum(f, ':', &vallen);  if (rc) return rc;
268
269   key= TALLOC(keylen+1);
270   val= htv_prep(vallen);
271
272   r= fread(key, 1,keylen, f);
273   if (r!=keylen) goto x2_free_keyval;
274   if (memchr(key,0,keylen)) goto x2_free_keyval;
275   key[keylen]= 0;
276
277   c= getc(f);  if (c!='-') goto x2_free_keyval;
278   c= getc(f);  if (c!='>') goto x2_free_keyval;
279   
280   r= fread(htv_fillptr(val), 1,vallen, f);
281   if (r!=vallen) goto x2_free_keyval;
282
283   rc= omitfn ? omitfn(val, ctx) : TCL_OK;
284   if (rc) { assert(rc>0); TFREE(val); }
285   else updatefn(ht, key, val);
286   
287   TFREE(key);
288   return rc;
289
290  x2_free_keyval:
291   TFREE(val);
292   TFREE(key);
293   return -2;
294 }
295
296 static int writerecord(FILE *f, const char *key, const HashValue *val) {
297   int r;
298
299   r= fprintf(f, "+%d,%d:%s->", strlen(key), val->len, key);
300   if (r<0) return -1;
301   
302   r= fwrite(val->data, 1, val->len, f);
303   if (r != val->len) return -1;
304
305   return 0;
306 }
307
308 /*---------- Creating ----------*/
309
310 int cht_do_cdbwr_create_empty(ClientData cd, Tcl_Interp *ip,
311                               const char *pathb) {
312   static const char *const toremoves[]= {
313     ".main", ".cdb", ".log", ".tmp", 0
314   };
315
316   Pathbuf pb;
317   int lock_fd=-1, fd=-1, rc, r;
318   const char *const *toremove;
319
320   pathbuf_init(&pb, pathb);
321   rc= acquire_lock(ip, &pb, &lock_fd);  if (rc) goto x_rc;
322   
323   fd= open(pathbuf_sfx(&pb, ".main"), O_RDWR|O_CREAT|O_EXCL, 0666);
324   if (fd <= 0) PE("create new database file");
325
326   for (toremove=toremoves; *toremove; toremove++) {
327     r= remove(*toremove);
328     if (r && errno != ENOENT)
329       PE("delete possible spurious file during creation");
330   }
331   
332   rc= TCL_OK;
333
334  x_rc:
335   maybe_close(fd);
336   maybe_close(lock_fd);
337   pathbuf_free(&pb);
338   return rc;
339 }
340
341 /*---------- Info callbacks ----------*/
342
343 static int infocbv3(Tcl_Interp *ip, Rw *rw, const char *arg1,
344                     const char *arg2fmt, const char *arg3, va_list al) {
345   Tcl_Obj *aa[3];
346   int na;
347   char buf[200];
348   vsnprintf(buf, sizeof(buf), arg2fmt, al);
349
350   na= 0;
351   aa[na++]= cht_ret_string(ip, arg1);
352   aa[na++]= cht_ret_string(ip, buf);
353   if (arg3) aa[na++]= cht_ret_string(ip, arg3);
354   
355   return cht_scriptinv_invoke_fg(&rw->on_info, na, aa);
356 }
357   
358 static int infocb3(Tcl_Interp *ip, Rw *rw, const char *arg1,
359                    const char *arg2fmt, const char *arg3, ...) {
360   int rc;
361   va_list al;
362   va_start(al, arg3);
363   rc= infocbv3(ip,rw,arg1,arg2fmt,arg3,al);
364   va_end(al);
365   return rc;
366 }
367   
368 static int infocb(Tcl_Interp *ip, Rw *rw, const char *arg1,
369                   const char *arg2fmt, ...) {
370   int rc;
371   va_list al;
372   va_start(al, arg2fmt);
373   rc= infocbv3(ip,rw,arg1,arg2fmt,0,al);
374   va_end(al);
375   return rc;
376 }
377   
378 /*---------- Opening ----------*/
379
380 static int cdbinit(Tcl_Interp *ip, Rw *rw) {
381   /* On entry, cdb_fd >=0 but cdb is _undefined_/
382    * On exit, either cdb_fd<0 or cdb is initialised */
383   int r, rc;
384   
385   r= cdb_init(&rw->cdb, rw->cdb_fd);
386   if (r) {
387     rc= cht_posixerr(ip, errno, "failed to initialise cdb reader");
388     close(rw->cdb_fd);  rw->cdb_fd= -1;  return rc;
389   }
390   return TCL_OK;
391 }
392
393 int cht_do_cdbwr_open(ClientData cd, Tcl_Interp *ip, const char *pathb,
394                       Tcl_Obj *on_info, Tcl_Obj *on_lexminval,
395                       void **result) {
396   const Cdbwr_SubCommand *subcmd= cd;
397   int r, rc, mainfd=-1;
398   Rw *rw;
399   struct stat stab;
400   off_t logrecstart, logjunkpos;
401
402   rw= TALLOC(sizeof(*rw));
403   ht_setup(&rw->logincore);
404   cht_scriptinv_init(&rw->on_info);
405   cht_scriptinv_init(&rw->on_lexminval);
406   rw->cdb_fd= rw->lock_fd= -1;  rw->logfile= 0;
407   pathbuf_init(&rw->pbsome, pathb);
408   pathbuf_init(&rw->pbother, pathb);
409   rw->autocompact= 1;
410
411   if (on_lexminval) {
412     rc= cht_scriptinv_set(&rw->on_lexminval, ip, on_lexminval, 0);
413     if (rc) goto x_rc;
414   } else {
415     rw->on_lexminval.llength= 0;
416   }
417
418   mainfd= open(pathbuf_sfx(&rw->pbsome,".main"), O_RDONLY);
419   if (mainfd<0) PE("open exist3ing database file .main");
420   rc= acquire_lock(ip, &rw->pbsome, &rw->lock_fd);  if (rc) goto x_rc;
421
422   r= fstat(mainfd, &stab);  if (r) PE("fstat .main");
423   rw->mainsz= stab.st_size;
424
425   rw->cdb_fd= open(pathbuf_sfx(&rw->pbsome,".cdb"), O_RDONLY);
426   if (rw->cdb_fd >=0) {
427     rc= cdbinit(ip, rw);  if (rc) goto x_rc;
428   } else if (errno == ENOENT) {
429     if (rw->mainsz) {
430       rc= cht_staticerr(ip, ".cdb does not exist but .main is nonempty -"
431                         " .cdb must have been accidentally deleted!",
432                         "CDB CDBMISSING");
433       goto x_rc;
434     }
435     /* fine */
436   } else {
437     PE("open .cdb");
438   }
439
440   rw->logfile= fopen(pathbuf_sfx(&rw->pbsome,".log"), "r+");
441   if (!rw->logfile) {
442     if (errno != ENOENT) PE("failed to open .log during open");
443     rw->logfile= fopen(rw->pbsome.buf, "w");
444     if (!rw->logfile) PE("create .log during (clean) open");
445   } else { /* rw->logfile */
446     r= fstat(fileno(rw->logfile), &stab);
447     if (r==-1) PE("fstat .log during open");
448     rc= infocb(ip, rw, "open-dirty-start", "log=%luby",
449                (unsigned long)stab.st_size);
450     if (rc) goto x_rc;
451
452     for (;;) {
453       logrecstart= ftello(rw->logfile);
454       if (logrecstart < 0) PE("ftello .log during (dirty) open");
455       r= readstorelogrecord(rw->logfile, &rw->logincore, 0,0, ht_update);
456       if (ferror(rw->logfile)) {
457         rc= cht_posixerr(ip, errno, "error reading .log during (dirty) open");
458         goto x_rc;
459       }
460       if (r==-1) {
461         break;
462       } else if (r==-2 || r==-3) {
463         char buf[100];
464         logjunkpos= ftello(rw->logfile);
465         if(logjunkpos<0) PE("ftello .log during report of junk in dirty open");
466
467         snprintf(buf,sizeof(buf), "CDB SYNTAX LOG %lu %lu",
468                  (unsigned long)logjunkpos, (unsigned long)logrecstart);
469
470         if (!(subcmd->flags & RWSCF_OKJUNK)) {
471           Tcl_SetObjErrorCode(ip, Tcl_NewStringObj(buf,-1));
472           snprintf(buf,sizeof(buf),"%lu",(unsigned long)logjunkpos);
473           Tcl_ResetResult(ip);
474           Tcl_AppendResult(ip, "syntax error (junk) in .log during"
475                            " (dirty) open, at file position ", buf, (char*)0);
476           rc= TCL_ERROR;
477           goto x_rc;
478         }
479         rc= infocb3(ip, rw, "open-dirty-junk", "errorfpos=%luby", buf,
480                     (unsigned long)logjunkpos);
481         if (rc) goto x_rc;
482
483         r= fseeko(rw->logfile, logrecstart, SEEK_SET);
484         if (r) PE("failed to fseeko .log before junk during dirty open");
485
486         r= ftruncate(fileno(rw->logfile), logrecstart);
487         if (r) PE("ftruncate .log to chop junk during dirty open");
488       } else {
489         assert(!r);
490       }
491     }
492   }
493   /* now log is positioned for appending and everything is read */
494
495   *result= rw;
496   maybe_close(mainfd);
497   return TCL_OK;
498
499  x_rc:
500   rw_close(0,rw);
501   maybe_close(mainfd);
502   return rc;
503 }
504
505 int cht_do_cdbwr_open_okjunk(ClientData cd, Tcl_Interp *ip, const char *pathb,
506                       Tcl_Obj *on_info, Tcl_Obj *on_lexminval,
507                       void **result) {
508   return cht_do_cdbwr_open(cd,ip,pathb,on_info,on_lexminval,result);
509 }
510
511 /*==================== COMPACTION ====================*/
512
513 struct ht_forall_ctx {
514   struct cdb_make cdbm;
515   FILE *mainfile;
516   int lexminvall;
517   long *reccount;
518   const char *lexminval;
519 };
520
521 /*---------- helper functions ----------*/
522
523 static int expiredp(const HashValue *val, struct ht_forall_ctx *a) {
524   int r, l;
525   if (!val->len) return 0;
526   l= val->len < a->lexminvall ? val->len : a->lexminvall;
527   r= memcmp(val->data, a->lexminval, l);
528   if (r>0) return 0;
529   if (r<0) return 1;
530   return val->len < a->lexminvall;
531 }
532
533 static int delete_ifexpired(const char *key, HashValue *val,
534                             struct ht_forall_ctx *a) {
535   if (!expiredp(val, a)) return 0;
536   val->len= 0;
537   /* we don't actually need to realloc it to free the memory because
538    * this will shortly all be deleted as part of the compaction */
539   return 0;
540 }
541
542 static int addto_cdb(const char *key, HashValue *val,
543                      struct ht_forall_ctx *a) {
544   return cdb_make_add(&a->cdbm, key, strlen(key), val->data, val->len);
545 }
546
547 static int addto_main(const char *key, HashValue *val,
548                       struct ht_forall_ctx *a) {
549   (*a->reccount)++;
550   return writerecord(a->mainfile, key, val);
551 }
552
553 /*---------- compact main entrypoint ----------*/
554
555 static int compact_core(Tcl_Interp *ip, Rw *rw, unsigned long logsz,
556                         long *reccount_r) {
557   /* creates new .cdb and .main
558    * closes logfile
559    * leaves .log with old data
560    * leaves cdb fd open onto old db
561    * leaves logincore full of crap
562    */
563   int r, rc;
564   int cdbfd, cdbmaking;
565   off_t errpos, newmainsz;
566   char buf[100];
567   Tcl_Obj *res;
568   struct ht_forall_ctx a;
569
570   a.mainfile= 0;
571   cdbfd= -1;
572   cdbmaking= 0;
573   *reccount_r= 0;
574   a.reccount= reccount_r;
575
576   r= fclose(rw->logfile);
577   if (r) { rc= cht_posixerr(ip, errno, "probable data loss!  failed to fclose"
578                             " logfile during compact");  goto x_rc; }
579   rw->logfile= 0;
580   
581   rc= infocb(ip, rw, "compact-start", "log=%luby main=%luby",
582              logsz, (unsigned long)rw->mainsz);
583   if (rc) goto x_rc;
584
585   if (rw->on_lexminval.llength) {
586     rc= cht_scriptinv_invoke_fg(&rw->on_lexminval, 0,0);
587     if (rc) goto x_rc;
588
589     res= Tcl_GetObjResult(ip);  assert(res);
590     a.lexminval= Tcl_GetStringFromObj(res, &a.lexminvall);
591     assert(a.lexminval);
592
593     /* we rely not calling Tcl_Eval during the actual compaction;
594      * if we did Tcl_Eval then the interp result would be trashed.
595      */
596     rc= ht_forall(&rw->logincore, delete_ifexpired, &a);
597
598   } else {
599     a.lexminval= "";
600   }
601
602   /* merge unsuperseded records from main into hash table */
603
604   a.mainfile= fopen(pathbuf_sfx(&rw->pbsome,".main"), "r");
605   if (!a.mainfile) PE("failed to open .main for reading during compact");
606
607   for (;;) {
608     r= readstorelogrecord(a.mainfile, &rw->logincore,
609                           expiredp, &a,
610                           ht_maybeupdate);
611     if (ferror(a.mainfile)) { rc= cht_posixerr(ip, errno, "error reading"
612                          " .main during compact"); goto x_rc;
613     }
614     if (r==-3) {
615       break;
616     } else if (r==-1 || r==-2) {
617       errpos= ftello(a.mainfile);
618       if (errpos<0) PE("ftello .main during report of syntax error");
619       snprintf(buf,sizeof(buf), "CDB SYNTAX MAIN %lu", (unsigned long)errpos);
620       Tcl_SetObjErrorCode(ip, Tcl_NewStringObj(buf,-1));
621       snprintf(buf,sizeof(buf), "%lu", (unsigned long)errpos);
622       Tcl_ResetResult(ip);
623       Tcl_AppendResult(ip, "syntax error in .main during"
624                        " compact, at file position ", buf, (char*)0);
625       rc= TCL_ERROR;
626       goto x_rc;
627     } else {
628       assert(!rc);
629     }
630   }
631   fclose(a.mainfile);
632   a.mainfile= 0;
633
634   /* create new cdb */
635
636   cdbfd= open(pathbuf_sfx(&rw->pbsome,".tmp"), O_WRONLY|O_CREAT|O_TRUNC, 0666);
637   if (cdbfd<0) PE("create .tmp for new cdb during compact");
638
639   r= cdb_make_start(&a.cdbm, cdbfd);
640   if (r) PE("cdb_make_start during compact");
641   cdbmaking= 1;
642
643   r= ht_forall(&rw->logincore, addto_cdb, &a);
644   if (r) PE("cdb_make_add during compact");
645
646   r= cdb_make_finish(&a.cdbm);
647   if(r) PE("cdb_make_finish during compact");
648   cdbmaking= 0;
649
650   r= fdatasync(cdbfd);  if (r) PE("fdatasync new cdb during compact");
651   r= close(cdbfd);  if (r) PE("close new cdb during compact");
652   cdbfd= -1;
653
654   r= rename(rw->pbsome.buf, pathbuf_sfx(&rw->pbother,".cdb"));
655   if (r) PE("install new .cdb during compact");
656
657   /* create new main */
658
659   a.mainfile= fopen(pathbuf_sfx(&rw->pbsome,".tmp"), "w");
660   if (!a.mainfile) PE("create .tmp for new main during compact");
661
662   r= ht_forall(&rw->logincore, addto_main, &a);
663   if (r) { rc= cht_posixerr(ip, r, "error writing to new .main"
664                             " during compact");  goto x_rc; }
665   
666   r= fflush(a.mainfile);  if (r) PE("fflush new main during compact");
667   r= fdatasync(fileno(a.mainfile));
668   if (r) PE("fdatasync new main during compact");
669
670   newmainsz= ftello(a.mainfile);
671   if (newmainsz<0) PE("ftello new main during compact");
672   
673   r= fclose(a.mainfile);  if (r) PE("fclose new main during compact");
674   a.mainfile= 0;
675
676   r= rename(rw->pbsome.buf, pathbuf_sfx(&rw->pbother,".main"));
677   if (r) PE("install new .main during compact");
678
679   rw->mainsz= newmainsz;
680
681   /* done! */
682   
683   rc= infocb(ip, rw, "compact-end", "main=%luby nrecs=%l",
684              (unsigned long)rw->mainsz, *a.reccount);
685   if (rc) goto x_rc;
686
687   return rc;
688
689 x_rc:
690   if (a.mainfile) fclose(a.mainfile);
691   if (cdbmaking) cdb_make_finish(&a.cdbm);
692   maybe_close(cdbfd);
693   remove(pathbuf_sfx(&rw->pbsome,".tmp")); /* for tidyness */
694   return rc;
695 }
696
697 /*---------- Closing ----------*/
698
699 static int compact_forclose(Tcl_Interp *ip, Rw *rw, long *reccount_r) {
700   off_t logsz;
701   int r, rc;
702
703   logsz= ftello(rw->logfile);
704   if (logsz < 0) PE("ftello logfile (during tidy close)");
705
706   rc= compact_core(ip, rw, logsz, reccount_r);  if (rc) goto x_rc;
707
708   r= remove(pathbuf_sfx(&rw->pbsome,".log"));
709   if (r) PE("remove .log (during tidy close)");
710
711   return TCL_OK;
712
713 x_rc: return rc;
714 }
715   
716 int cht_do_cdbwr_close(ClientData cd, Tcl_Interp *ip, void *rw_v) {
717   Rw *rw= rw_v;
718   int rc, rc_close;
719   long reccount= -1;
720   off_t logsz;
721
722   if (rw->autocompact) rc= compact_forclose(ip, rw, &reccount);
723   else rc= TCL_OK;
724
725   if (!rc) {
726     if (!rw->logfile) {
727       logsz= ftello(rw->logfile);
728       if (logsz < 0)
729         rc= cht_posixerr(ip, errno, "ftell logfile during close info");
730       else
731         rc= infocb(ip, rw, "close", "main=%luby log=%luby",
732                    rw->mainsz, logsz);
733     } else if (reccount>=0) {
734       rc= infocb(ip, rw, "close", "main=%luby nrecs=%l", rw->mainsz, reccount);
735     } else {
736       rc= infocb(ip, rw, "close", "main=%luby", rw->mainsz);
737     }
738   }
739   rc_close= rw_close(ip,rw);
740   if (rc_close) rc= rc_close;
741   
742   cht_tabledataid_disposing(ip, rw_v, &cdbtcl_rwdatabases);
743   return rc;
744 }
745
746 /*---------- Other compaction-related entrypoints ----------*/
747
748 static int compact_keepopen(Tcl_Interp *ip, Rw *rw, int force) {
749   off_t logsz;
750   long reccount;
751   int rc, r;
752
753   logsz= ftello(rw->logfile);
754   if (logsz < 0) return cht_posixerr(ip, errno, "ftell .log"
755                                        " during compact check or force");
756
757   if (!force && logsz < rw->mainsz / 10 + 1000) return TCL_OK;
758
759   rc= compact_core(ip, rw, logsz, &reccount);  if (rc) goto x_rc;
760
761   maybe_close(rw->cdb_fd);
762   rw->cdb_fd= -1;
763   ht_destroy(&rw->logincore);
764   ht_setup(&rw->logincore);
765
766   rw->cdb_fd= open(pathbuf_sfx(&rw->pbsome,".cdb"), O_RDONLY);
767   if (rw->cdb_fd < 0) PE("reopen .cdb after compact");
768
769   rc= cdbinit(ip, rw);  if (rc) goto x_rc;
770
771   rw->logfile= fopen(pathbuf_sfx(&rw->pbsome,".log"), "w");
772   if (!rw->logfile) PE("reopen .log after compact");
773
774   r= fsync(fileno(rw->logfile));  if (r) PE("fsync .log after compact reopen");
775
776   return TCL_OK;
777
778 x_rc:
779   /* doom! all updates fail after this (because rw->logfile is 0), and
780    * we may be using a lot more RAM than would be ideal.  Program will
781    * have to reopen if it really wants sanity. */
782   return rc;
783 }
784
785 int cht_do_cdbwr_compact_force(ClientData cd, Tcl_Interp *ip, void *rw_v) {
786   return compact_keepopen(ip, rw_v, 1);
787 }
788 int cht_do_cdbwr_compact_check(ClientData cd, Tcl_Interp *ip, void *rw_v) {
789   return compact_keepopen(ip, rw_v, 0);
790 }
791
792 int cht_do_cdbwr_compact_explicit(ClientData cd, Tcl_Interp *ip, void *rw_v) {
793   Rw *rw= rw_v;
794   rw->autocompact= 0;
795   return TCL_OK;
796 }
797 int cht_do_cdbwr_compact_auto(ClientData cd, Tcl_Interp *ip, void *rw_v) {
798   Rw *rw= rw_v;
799   rw->autocompact= 1;
800   return TCL_OK;
801 }
802
803 /*---------- Updateing ----------*/
804
805 static int update(Tcl_Interp *ip, Rw *rw, const char *key,
806                   const Byte *data, int dlen) {
807   HashValue *val;
808   int rc, r;
809
810   if (!rw->logfile) return cht_staticerr
811     (ip, "previous compact failed; cdbwr must be closed and reopened "
812      "before any further updates", "CDB BROKEN");
813   
814   val= htv_prep(dlen);  assert(val);
815   memcpy(htv_fillptr(val), data, dlen);
816
817   r= writerecord(rw->logfile, key, val);
818   if (!r) r= fflush(rw->logfile);
819   if (r) PE("write update to logfile");
820
821   ht_update(&rw->logincore, key, val);
822   return compact_keepopen(ip, rw, 0);
823
824  x_rc:
825   TFREE(val);
826   return rc;
827 }  
828
829 int cht_do_cdbwr_update(ClientData cd, Tcl_Interp *ip,
830                         void *rw_v, const char *key, Tcl_Obj *value) {
831   int dlen;
832   const char *data;
833   data= Tcl_GetStringFromObj(value, &dlen);  assert(data);
834   return update(ip, rw_v, key, data, dlen);
835 }
836
837 int cht_do_cdbwr_update_hb(ClientData cd, Tcl_Interp *ip,
838                            void *rw_v, const char *key, HBytes_Value value) {
839   return update(ip, rw_v, key, cht_hb_data(&value), cht_hb_len(&value));
840 }
841
842 int cht_do_cdbwr_delete(ClientData cd, Tcl_Interp *ip, void *rw_v,
843                         const char *key) {
844   return update(ip, rw_v, key, 0, 0);
845 }
846
847 /*---------- Lookups ----------*/
848
849 static int lookup_rw(Tcl_Interp *ip, void *rw_v, const char *key,
850                     const Byte **data_r, int *len_r /* -1 => notfound */) {
851   Rw *rw= rw_v;
852   const HashValue *val;
853   int r;
854
855   val= ht_lookup(&rw->logincore, key);
856   if (val) {
857     if (val->len) { *data_r= val->data; *len_r= val->len; return TCL_OK; }
858     else { *data_r= 0; *len_r= -1; return TCL_OK; }
859   }
860
861   r= cdb_find(&rw->cdb, key, strlen(key));
862   if (!r) { *data_r= 0; *len_r= -1; return TCL_OK; }
863   if (r<0) return cht_posixerr(ip, errno, "cdb_find failed");
864   assert(r==1);
865   *len_r= cdb_datalen(&rw->cdb);
866   assert(*len_r > 0);
867   *data_r= cdb_getdata(&rw->cdb);
868   if (!*data_r) return cht_posixerr(ip, errno, "cdb_getdata failed");
869   return TCL_OK;
870
871
872 int cht_do_cdbwr_lookup(ClientData cd, Tcl_Interp *ip, void *rw_v,
873                         const char *key, Tcl_Obj *def,
874                         Tcl_Obj **result) {
875   return cht_cdb_dosomelookup(ip, rw_v, key, def, result,
876                               lookup_rw, cht_cdb_storeanswer_string);
877 }
878   
879 int cht_do_cdbwr_lookup_hb(ClientData cd, Tcl_Interp *ip, void *rw_v,
880                            const char *key, Tcl_Obj *def,
881                            Tcl_Obj **result) {
882   return cht_cdb_dosomelookup(ip, rw_v, key, def, result,
883                               lookup_rw, cht_cdb_storeanswer_hb);
884 }