+/*---------- Other compaction-related entrypoints ----------*/
+
+static int compact_keepopen(Tcl_Interp *ip, Rw *rw, int force) {
+ off_t logsz;
+ long reccount;
+ int rc, r;
+
+ logsz= ftello(rw->logfile);
+ if (logsz < 0) return cht_posixerr(ip, errno, "ftell .jrn"
+ " during compact check or force");
+
+ if (!force && logsz < rw->mainsz / 3 + 1000) return TCL_OK;
+ /* Test case: ^^^ testing best value for this
+ * main=9690434by nrecs=122803 read all in one go
+ * no autocompact, : 6.96user 0.68system 0:08.93elapsed
+ * auto, mulitplier 2: 7.10user 0.79system 0:09.54elapsed
+ * auto, unity: 7.80user 0.98system 0:11.84elapsed
+ * auto, divisor 2: 8.23user 1.05system 0:13.30elapsed
+ * auto, divisor 3: 8.55user 1.12system 0:12.88elapsed
+ * auto, divisor 5: 9.95user 1.43system 0:15.72elapsed
+ */
+
+ rc= compact_core(ip, rw, logsz, &reccount); if (rc) goto x_rc;
+
+ rw_cdb_close(ip,rw);
+ ht_destroy(&rw->logincore);
+ ht_setup(&rw->logincore);
+
+ rw->cdb_fd= open(pathbuf_sfx(&rw->pbsome,".cdb"), O_RDONLY);
+ if (rw->cdb_fd < 0) PE("reopen .cdb after compact");
+
+ rc= cdbinit(ip, rw); if (rc) goto x_rc;
+
+ rw->logfile= fopen(pathbuf_sfx(&rw->pbsome,".jrn"), "w");
+ if (!rw->logfile) PE("reopen .jrn after compact");
+
+ r= fsync(fileno(rw->logfile)); if (r) PE("fsync .jrn after compact reopen");
+
+ return TCL_OK;
+
+x_rc:
+ /* doom! all updates fail after this (because rw->logfile is 0), and
+ * we may be using a lot more RAM than would be ideal. Program will
+ * have to reopen if it really wants sanity. */
+ return rc;
+}
+
+int cht_do_cdbwr_compact_force(ClientData cd, Tcl_Interp *ip, void *rw_v) {
+ return compact_keepopen(ip, rw_v, 1);
+}
+int cht_do_cdbwr_compact_check(ClientData cd, Tcl_Interp *ip, void *rw_v) {
+ return compact_keepopen(ip, rw_v, 0);
+}
+
+int cht_do_cdbwr_compact_explicit(ClientData cd, Tcl_Interp *ip, void *rw_v) {
+ Rw *rw= rw_v;
+ rw->autocompact= 0;
+ return TCL_OK;
+}
+int cht_do_cdbwr_compact_auto(ClientData cd, Tcl_Interp *ip, void *rw_v) {
+ Rw *rw= rw_v;
+ rw->autocompact= 1;
+ return TCL_OK;
+}
+
+/*---------- Updateing ----------*/
+
+static int update(Tcl_Interp *ip, Rw *rw, const char *key,
+ const Byte *data, int dlen) {
+ HashValue *val;
+ const char *failed;
+ int rc, r;
+ off_t recstart;
+
+ if (strlen(key) >= KEYLEN_MAX)
+ return cht_staticerr(ip, "key too long", "CDB KEYOVERFLOW");
+
+ if (!rw->logfile) return cht_staticerr
+ (ip, "failure during previous compact or error recovery;"
+ " cdbwr must be closed and reopened before any further updates",
+ "CDB BROKEN");
+
+ recstart= ftello(rw->logfile);
+ if (recstart < 0)
+ return cht_posixerr(ip, errno, "failed to ftello .jrn during update");
+
+ val= htv_prep(dlen); assert(val);
+ memcpy(htv_fillptr(val), data, dlen);
+
+ r= writerecord(rw->logfile, key, val);
+ if (!r) r= fflush(rw->logfile);
+ if (r) PE("write update to logfile");
+
+ ht_update(&rw->logincore, key, val);
+
+ if (!rw->autocompact) return TCL_OK;
+ return compact_keepopen(ip, rw, 0);
+
+ x_rc:
+ TFREE(val);
+ assert(rc);
+
+ /* Now, we have to try to sort out the journal so that it's
+ * truncated and positioned to where this abortively-written record
+ * started, with no buffered output and the error indicator clear.
+ *
+ * There seems to be no portable way to ensure the buffered unwritten
+ * output is discarded, so we close and reopen the stream.
+ */
+ fclose(rw->logfile);
+
+ rw->logfile= fopen(pathbuf_sfx(&rw->pbsome,".jrn"), "r+");
+ if (!rw->logfile) { failed= "fopen"; goto reset_fail; }
+
+ r= ftruncate(fileno(rw->logfile), recstart);
+ if (r) { failed= "ftruncate"; goto reset_fail; }
+
+ r= fseeko(rw->logfile, recstart, SEEK_SET);
+ if (r) { failed= "fseeko"; goto reset_fail; }
+
+ return rc;
+
+ reset_fail:
+ Tcl_AppendResult(ip, " (additionally, ", failed, " failed"
+ " in error recovery: ", strerror(errno), ")", (char*)0);
+ if (rw->logfile) { fclose(rw->logfile); rw->logfile= 0; }
+
+ return rc;
+}
+
+int cht_do_cdbwr_update(ClientData cd, Tcl_Interp *ip,
+ void *rw_v, const char *key, Tcl_Obj *value) {
+ int dlen;
+ const char *data;
+ data= Tcl_GetStringFromObj(value, &dlen); assert(data);
+ return update(ip, rw_v, key, data, dlen);
+}
+
+int cht_do_cdbwr_update_hb(ClientData cd, Tcl_Interp *ip,
+ void *rw_v, const char *key, HBytes_Value value) {
+ return update(ip, rw_v, key, cht_hb_data(&value), cht_hb_len(&value));
+}
+
+int cht_do_cdbwr_delete(ClientData cd, Tcl_Interp *ip, void *rw_v,
+ const char *key) {
+ return update(ip, rw_v, key, 0, 0);
+}
+
+/*---------- Lookups ----------*/
+
+static int lookup_rw(Tcl_Interp *ip, void *rw_v, const char *key,
+ const Byte **data_r, int *len_r /* -1 => notfound */) {
+ Rw *rw= rw_v;
+ const HashValue *val;
+
+ val= ht_lookup(&rw->logincore, key);
+ if (val) {
+ if (val->len) { *data_r= val->data; *len_r= val->len; return TCL_OK; }
+ else goto not_found;
+ }
+
+ if (rw->cdb_fd<0) goto not_found;
+
+ return cht_cdb_lookup_cdb(ip, &rw->cdb, key, strlen(key), data_r, len_r);
+
+ not_found:
+ *data_r= 0;
+ *len_r= -1;
+ return TCL_OK;
+}
+
+int cht_do_cdbwr_lookup(ClientData cd, Tcl_Interp *ip, void *rw_v,
+ const char *key, Tcl_Obj *def,
+ Tcl_Obj **result) {
+ const Byte *data;
+ int dlen, r;
+
+ r= lookup_rw(ip, rw_v, key, &data, &dlen); if (r) return r;
+ return cht_cdb_donesomelookup(ip, rw_v, def, result, data, dlen,
+ cht_cdb_storeanswer_string);
+}