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