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