chiark / gitweb /
chiark-tcl (1.1.1+nmu1) unstable; urgency=low
authorSergei Golovan <sgolovan@debian.org>
Tue, 15 Oct 2013 17:12:46 +0000 (18:12 +0100)
committerSergei Golovan <sgolovan@debian.org>
Tue, 15 Oct 2013 17:12:46 +0000 (18:12 +0100)
  * Non-maintainer upload.
  * Build against the default Tcl version instead of deprecated 8.4
    (closes: #725248).

# imported from the archive

69 files changed:
Makefile [new file with mode: 0644]
adns/Makefile [new file with mode: 0644]
adns/adns.c [new file with mode: 0644]
adns/adns.tct [new file with mode: 0644]
adns/chiark_tcl_adns.h [new file with mode: 0644]
base/Makefile [new file with mode: 0644]
base/base.tct [new file with mode: 0644]
base/chiark-tcl-base.h [new file with mode: 0644]
base/chiark-tcl.h [new file with mode: 0644]
base/common.make [new file with mode: 0644]
base/enum.c [new file with mode: 0644]
base/extension.make [new file with mode: 0644]
base/final.make [new file with mode: 0644]
base/hook.c [new file with mode: 0644]
base/idtable.c [new file with mode: 0644]
base/parse.c [new file with mode: 0644]
base/scriptinv.c [new file with mode: 0644]
base/shlib.make [new file with mode: 0644]
base/tcmdifgen [new file with mode: 0755]
base/tcmdiflib.c [new file with mode: 0644]
cdb/Makefile [new file with mode: 0644]
cdb/cdb.tct [new file with mode: 0644]
cdb/chiark_tcl_cdb.h [new file with mode: 0644]
cdb/lookup.c [new file with mode: 0644]
cdb/readonly.c [new file with mode: 0644]
cdb/writeable.c [new file with mode: 0644]
crypto/Makefile [new file with mode: 0644]
crypto/algtables.c [new file with mode: 0644]
crypto/bcmode.c [new file with mode: 0644]
crypto/chiark_tcl_crypto.h [new file with mode: 0644]
crypto/crypto.c [new file with mode: 0644]
crypto/crypto.h [new file with mode: 0644]
crypto/crypto.tct [new file with mode: 0644]
crypto/hash.c [new file with mode: 0644]
crypto/hook.c [new file with mode: 0644]
debian/README [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/extractdoc [new file with mode: 0644]
debian/lintian-overrides [new file with mode: 0644]
debian/rules [new file with mode: 0755]
dgram/Makefile [new file with mode: 0644]
dgram/chiark_tcl_dgram.h [new file with mode: 0644]
dgram/dgram.c [new file with mode: 0644]
dgram/dgram.h [new file with mode: 0644]
dgram/dgram.tct [new file with mode: 0644]
dgram/hook.c [new file with mode: 0644]
dgram/misc.c [new file with mode: 0644]
dgram/sockaddr.c [new file with mode: 0644]
hbytes/Makefile [new file with mode: 0644]
hbytes/chiark_tcl_hbytes.h [new file with mode: 0644]
hbytes/chop.c [new file with mode: 0644]
hbytes/hbytes-base.tct [new file with mode: 0644]
hbytes/hbytes.c [new file with mode: 0644]
hbytes/hbytes.h [new file with mode: 0644]
hbytes/hbytes.tct [new file with mode: 0644]
hbytes/hook.c [new file with mode: 0644]
hbytes/parse.c [new file with mode: 0644]
hbytes/ulongs.c [new file with mode: 0644]
maskmap/addrmap.c [new file with mode: 0644]
maskmap/maskmap-bits.c [new file with mode: 0644]
maskmap/maskmap.c [new file with mode: 0644]
maskmap/maskmap.tct [new file with mode: 0644]
tuntap/Makefile [new file with mode: 0644]
tuntap/chiark_tcl_tuntap.h [new file with mode: 0644]
tuntap/tuntap.c [new file with mode: 0644]
tuntap/tuntap.tct [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..e815676
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+
+SUBDIRS=       base adns hbytes cdb crypto dgram tuntap
+
+default: all
+
+clean all:
+       set -e; for d in $(SUBDIRS); do $(MAKE) -C $$d $@; done
+
+# To find undefined symbols when implementing, for example:
+#
+# liberator:chiark-tcl> LD_LIBRARY_PATH=:adns:base:cdb:crypto:dgram:hbytes:tuntap tclsh8.3
+# % load chiark_tcl_tuntap-1.so
+# couldn't load file "chiark_tcl_tuntap-1.so": tuntap/chiark_tcl_tuntap-1.so: undefined symbol: cht_tunsocket_entries
+# % 
diff --git a/adns/Makefile b/adns/Makefile
new file mode 100644 (file)
index 0000000..869d890
--- /dev/null
@@ -0,0 +1,7 @@
+BASE_DIR =     ../base
+EXTBASE =      adns
+CFILES =       adns
+LDLIBS +=      -ladns
+
+include ../base/extension.make
+
diff --git a/adns/adns.c b/adns/adns.c
new file mode 100644 (file)
index 0000000..7dde69c
--- /dev/null
@@ -0,0 +1,814 @@
+/*
+ * adns lookup TYPE DOMAIN [QUERY-OPTIONS]                    => [list RDATA]
+ *    if no or dontknow, throws an exception, with errorCode one of
+ *         ADNS permfail 300 nxdomain {No such domain}
+ *         ADNS permfail 301 nodata {No such data}
+ *         ADNS tempfail ERROR-CODE ERROR-NAME ERROR-STRING
+ *    where
+ *         ERROR-CODE is the numerical adns status value
+ *         ERROR-NAME is the symbolic adns status value (in lowercase)
+ *         ERROR-STRING is the result of adns_strstatus
+ *
+ * adns synch TYPE DOMAIN [QUERY-OPTIONS]                     => RESULTS
+ *        RESULTS is [list ok|permfail|tempfail
+ *                         ERROR-CODE ERROR-NAME ERROR-STRING  \
+ *                         OWNER CNAME                         \
+ *                         [list RDATA ...]]
+ *        OWNER is the RR owner
+ *        CNAME is the empty string or the canonical name if we went
+ *                  via a CNAME
+ *
+ * adns asynch ON-YES ON-NO ON-DONTKNOW XARGS \
+ *             TYPE DOMAIN \
+ *             [QUERY-OPTIONS...]                            => QUERY-ID
+ *        calls, later,
+ *           [concat ON-YES|ON-NO|ON-DONTKNOW XARGS RESULTS]
+ * adns asynch-cancel QUERY-ID
+ *
+ * QUERY-OPTIONS are zero or more of
+ *         -resolver RESOLVER  (see adns new-resolver)
+ *                 default is to use a default resolver
+ *         -search
+ *         -usevc
+ *         -quoteok-query
+ *         -quoteok-anshost
+ *         -quotefail-cname
+ *         -cname-loose
+ *         -cname-forbid
+ *         -reverse
+ *         -reverse-any ZONE-A-LIKE
+ *
+ * adns new-resolver [RES-OPTIONS...]                         => RESOLVER
+ *        options:
+ *         -errfile stdout|stderr       (stderr is the default)
+ *         -noerrprint
+ *         -errcallback CALLBACK    results in  eval CALLBACK [list MESSAGE]
+ *         -noenv|-debug|-logpid
+ *         -checkc-entex
+ *         -checkc-freq
+ *         -config CONFIG-STRING
+ *
+ * adns set-default-resolver RESOLVER
+ *        cancels any outstanding queries from a previous anonymous
+ *        default resolver
+ *
+ * adns destroy-resolver RESOLVER
+ *        cancels outstanding queries
+ *
+ */
+/* ---8<--- end of documentation comment --8<-- */
+
+/*
+ * adns.c - adns binding for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+
+#include <adns.h>
+
+#include "chiark_tcl_adns.h"
+
+/*---------- important types and forward declarations ----------*/
+
+typedef struct Query Query;
+typedef struct Resolver Resolver;
+typedef struct OptionInfo OptionInfo;
+
+static void asynch_sethandlers(Resolver *res);
+static void asynch_cancelhandlers(Resolver *res);
+static void asynch_perturbed(Resolver *res);
+
+static void asynch_query_dispose(Tcl_Interp *interp, Query *query);
+
+#define ASSOC_DEFAULTRES "adns-defaultresolver"
+
+/*---------- common resolver/query option processing ----------*/
+
+typedef struct {
+  /* this struct type is used to hold both resolver and query options */
+  /* common to resolver and query: */
+  unsigned long aflags;
+  unsigned long sflags;
+  /* resolver: */
+  FILE *errfile;
+  Tcl_Obj *errcallback;
+  const char *config_string;
+  /* query: */
+  Resolver *resolver;
+  const char *reverseany;
+} OptionParse;
+
+struct OptionInfo {
+  const char *name;
+  int (*fn)(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
+           OptionParse *op);
+  int takesarg;
+  unsigned long flags_add, flags_remove;
+};
+
+enum {
+  oisf_reverse=     0x0002
+};
+
+static int oiufn_f(const OptionInfo *oi, unsigned long *flags) {
+  *flags &= ~oi->flags_remove;
+  *flags |= oi->flags_add;
+  return TCL_OK;
+}
+static int oifn_fa(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
+                  OptionParse *op) { return oiufn_f(oi,&op->aflags); }
+static int oifn_fs(Tcl_Interp *ip, const OptionInfo *oi, Tcl_Obj *arg,
+                  OptionParse *op) { return oiufn_f(oi,&op->sflags); }
+
+static int oifn_reverse_any(Tcl_Interp *ip, const OptionInfo *oi,
+                           Tcl_Obj *arg, OptionParse *op) {
+  return cht_pat_string(ip,arg,&op->reverseany);
+}
+
+#define OIFA1(t,f,r) { "-" #f, oifn_fa, 0, adns_##t##_##f, r }
+#define OIFA2(t,f,g) { "-" #f "-" #g, oifn_fa, 0, adns_##t##_##f##_##g, 0 }
+#define OIFS(f) { "-" #f, oifn_fs, 0, oisf_##f, 0 }
+#define OICA(o) { "-" #o, oifn_##o, 1 }
+
+static void optparse_blank(OptionParse *op) {
+  memset(op,0,sizeof(*op));
+  op->errfile= stderr;
+  op->errcallback= 0;
+  op->config_string= 0;
+}
+
+static int parse_options(Tcl_Interp *ip, int objc, Tcl_Obj *const *objv,
+                        const OptionInfo opttable[], OptionParse *op) {
+  const OptionInfo *oi;
+  const void *oi_v;
+  Tcl_Obj *arg;
+  int rc;
+
+  objc--; objv++;
+  for (;;) {
+    if (!objc--) break;
+    rc= cht_pat_enum(ip, *objv++, &oi_v, opttable, sizeof(OptionInfo),
+                "query or resolver option");
+    if (rc) return rc;
+    oi= oi_v;
+
+    if (oi->takesarg) {
+      if (!objc--) {
+       cht_setstringresult(ip,"missing value for option");
+       return TCL_ERROR;
+      }
+      arg= *objv++;
+    } else {
+      arg= 0;
+    }
+    rc= oi->fn(ip,oi,arg,op);
+    if (rc) return rc;
+  }
+  return TCL_OK;
+}
+
+/*---------- resolver management ----------*/
+
+struct Resolver {
+  int ix; /* first! */
+  Tcl_Interp *interp;
+  adns_state ads;
+  Tcl_TimerToken timertoken;
+  int maxfd;
+  fd_set handling[3];
+  ScriptToInvoke errcallback;
+  Tcl_Obj *errstring_accum;
+};
+
+struct Query {
+  int ix; /* first! */
+  Resolver *res;
+  adns_query aqu;
+  ScriptToInvoke on_yes, on_no, on_fail;
+  Tcl_Obj *xargs;
+};
+
+/* The default resolver is recorded using Tcl_SetAssocData with key
+ * ASSOC_DEFAULTRES to record the Resolver*.  If it was explicitly
+ * created with `adns new-resolver' then ix will be >=0, and the
+ * resolver will be destroyed - leaving no default - when explicitly
+ * requested.  If it was implicitly created (by starting a query when
+ * there is no default) then ix will be -1.  */
+
+static int oifn_errfile(Tcl_Interp *ip, const OptionInfo *oi,
+                       Tcl_Obj *arg, OptionParse *op) {
+  int rc;
+  const char *str;
+  
+  rc= cht_pat_string(ip,arg,&str);  if (rc) return rc;
+  if (!strcmp(str,"stderr")) op->errfile= stderr;
+  else if (!strcmp(str,"stdout")) op->errfile= stdout;
+  else return cht_staticerr(ip,"-errfile argument must be stderr or stdout",0);
+
+  op->aflags &= ~adns_if_noerrprint;
+  op->errcallback= 0;
+  return TCL_OK;
+}
+
+static int oifn_errcallback(Tcl_Interp *ip, const OptionInfo *oi,
+                           Tcl_Obj *arg, OptionParse *op) {
+  op->errcallback= arg;
+  op->aflags &= ~adns_if_noerrprint;
+  op->errfile= 0;
+  return TCL_OK;
+}
+
+static int oifn_config(Tcl_Interp *ip, const OptionInfo *oi,
+                      Tcl_Obj *arg, OptionParse *op) {
+  return cht_pat_string(ip,arg,&op->config_string);
+}
+
+static const OptionInfo resolver_optioninfos[]= {
+  OIFA1(if,noenv, 0),
+  OIFA1(if,debug, adns_if_noerrprint),
+  OIFA1(if,logpid, adns_if_noerrprint),
+  OIFA1(if,noerrprint, adns_if_debug),
+  OIFA2(if,checkc,entex),
+  OIFA2(if,checkc,freq),
+  OICA(errfile),
+  OICA(errcallback),
+  OICA(config),
+  { 0 }
+};
+
+static void adnslogfn_flushmessage(Resolver *res) {
+  cht_scriptinv_invoke(&res->errcallback, 1, &res->errstring_accum);
+  Tcl_SetObjLength(res->errstring_accum, 0);
+}
+
+static void adnslogfn_callback(adns_state ads, void *logfndata,
+                              const char *fmt, va_list al) {
+  Resolver *res= logfndata;
+  int l, newline;
+  char *str;
+
+  l= vasprintf(&str,fmt,al);
+  if (l<0) {
+    cht_posixerr(res->interp,errno,"construct adns log callback string");
+    Tcl_BackgroundError(res->interp);
+  }
+
+  if (l==0) { free(str); return; }
+  if ((newline= l>0 && str[l-1]=='\n')) l--;
+
+  if (!res->errstring_accum) {
+    res->errstring_accum= Tcl_NewStringObj(str,l);
+    Tcl_IncrRefCount(res->errstring_accum);
+  } else {
+    Tcl_AppendToObj(res->errstring_accum,str,l);
+  }
+  free(str);
+
+  if (newline)
+    adnslogfn_flushmessage(res);
+}
+
+static Resolver *default_resolver(Tcl_Interp *ip) {
+  return Tcl_GetAssocData(ip,ASSOC_DEFAULTRES,0);
+}
+
+static void destroy_resolver(Tcl_Interp *ip, Resolver *res) {
+  void *query_v;
+  Query *query;
+  int logstring_len;
+  char *rstr;
+  adns_query aqu;
+  if (res == default_resolver(ip))
+    Tcl_DeleteAssocData(ip,ASSOC_DEFAULTRES);
+  if (res->errstring_accum) {
+    rstr= Tcl_GetStringFromObj(res->errstring_accum, &logstring_len);
+    assert(rstr);
+    if (logstring_len)
+      adnslogfn_flushmessage(res);
+  }
+
+  if (res->ads) {
+    /* although adns would throw these away for us, we need to
+     * destroy our own data too and only adns has a list of them */
+    for (;;) {
+      adns_forallqueries_begin(res->ads);
+      aqu= adns_forallqueries_next(res->ads, &query_v);
+      if (!aqu) break;
+      query= query_v;
+      assert(query->aqu == aqu);
+      query->aqu= 0; /* avoid disrupting the adns query list */
+      asynch_query_dispose(ip, query_v);
+    }
+    adns_finish(res->ads);
+    res->ads= 0;
+  }
+  asynch_cancelhandlers(res);
+  cht_scriptinv_cancel(&res->errcallback);
+  Tcl_EventuallyFree(res, Tcl_Free);
+}
+
+static void destroy_resolver_idtabcb(Tcl_Interp *ip, void *resolver_v) {
+  destroy_resolver(ip,resolver_v);
+}
+static void destroy_resolver_defcb(ClientData resolver_v, Tcl_Interp *ip) {
+  destroy_resolver(ip,resolver_v);
+}
+
+int cht_do_adns_destroy_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) {
+  cht_tabledataid_disposing(ip,res_v,&cht_adnstcl_resolvers);
+  destroy_resolver(ip,res_v);
+  return TCL_OK;
+}
+
+static int create_resolver(Tcl_Interp *ip, const OptionParse *op,
+                          Resolver **res_r) {
+  Resolver *res=0;
+  int rc, i, ec;
+  
+  res= TALLOC(sizeof(*res));  assert(res);
+  res->ix= -1;
+  res->interp= ip;
+  res->ads= 0;
+  res->timertoken= 0;
+  res->maxfd= 0;
+  for (i=0; i<3; i++) FD_ZERO(&res->handling[i]);
+  cht_scriptinv_init(&res->errcallback);
+  res->errstring_accum= 0;
+
+  if (op->errcallback)
+    cht_scriptinv_set(&res->errcallback, ip, op->errcallback, 0);
+
+  ec= adns_init_logfn(&res->ads,
+                     op->aflags | adns_if_noautosys,
+                     op->config_string,
+                     op->errcallback ? adnslogfn_callback : 0,
+                     op->errcallback ? (void*)res : (void*)op->errfile);
+  if (ec) { rc= cht_posixerr(ip,ec,"create adns resolver"); goto x_rc; }
+
+  *res_r= res;
+  return TCL_OK;
+
+ x_rc:
+  if (res) {
+    if (res->ads) adns_finish(res->ads);
+    TFREE(res);
+  }
+  return rc;
+}  
+
+int cht_do_adns_new_resolver(ClientData cd, Tcl_Interp *ip,
+                        int objc, Tcl_Obj *const *objv,
+                        void **result) {
+  OptionParse op;
+  Resolver *res=0;
+  int rc;
+
+  optparse_blank(&op);
+  rc= parse_options(ip,objc,objv,resolver_optioninfos,&op);
+  if (rc) return rc;
+
+  if (op.aflags & adns_if_noerrprint) {
+    op.errfile= 0;
+    op.errcallback= 0;
+  }
+
+  rc= create_resolver(ip, &op, &res);
+  if (rc) return rc;
+
+  *result= res;
+  return TCL_OK;
+}
+
+int cht_do_adns_set_default_resolver(ClientData cd, Tcl_Interp *ip, void *res_v) {
+  Resolver *res= res_v;
+  Tcl_DeleteAssocData(ip,ASSOC_DEFAULTRES);
+  Tcl_SetAssocData(ip, ASSOC_DEFAULTRES, 0, res);
+  return TCL_OK;
+}
+
+const IdDataSpec cht_adnstcl_resolvers= {
+  "adns-res", "adns-resolvers-table", destroy_resolver_idtabcb
+};
+
+/*---------- query, query option and answers - common stuff ----------*/
+
+#define RRTYPE_EXACTLY(t) { #t, adns_r_##t }
+#define RRTYPE_RAW(t) { #t, adns_r_##t##_raw }
+#define RRTYPE_PLUS(t) { #t "+", adns_r_##t }
+#define RRTYPE_MINUS(t) { #t "-", adns_r_##t##_raw }
+
+const AdnsTclRRTypeInfo cht_adnstclrrtypeinfo_entries[]= {
+  RRTYPE_EXACTLY(a),
+  RRTYPE_EXACTLY(cname),
+  RRTYPE_EXACTLY(hinfo),
+  RRTYPE_EXACTLY(addr),
+
+  RRTYPE_RAW(ns),
+  RRTYPE_RAW(mx),
+  RRTYPE_EXACTLY(txt),
+
+  RRTYPE_EXACTLY(soa),
+  RRTYPE_EXACTLY(ptr),
+  RRTYPE_EXACTLY(rp),
+
+  RRTYPE_MINUS(soa),
+  RRTYPE_MINUS(ptr),
+  RRTYPE_MINUS(rp),
+  { 0 }
+};
+
+static int oifn_resolver(Tcl_Interp *ip, const OptionInfo *oi,
+                        Tcl_Obj *arg, OptionParse *op) {
+  void *val_v;
+  int rc;
+  
+  rc= cht_pat_iddata(ip,arg,&val_v,&cht_adnstcl_resolvers);
+  if (rc) return rc;
+  op->resolver= val_v;
+  return TCL_OK;
+}
+
+static const OptionInfo query_optioninfos[]= {
+  OIFA1(qf,search,0),
+  OIFA1(qf,usevc,0),
+  OIFA2(qf,quoteok,query),
+  OIFA2(qf,quoteok,anshost),
+  OIFA2(qf,quotefail,cname),
+  OIFA2(qf,cname,loose),
+  OIFA2(qf,cname,forbid),
+  OICA(resolver),
+  OIFS(reverse),
+  { "-reverse-any", oifn_reverse_any, 1 },
+  { 0 }
+};
+
+static int query_submit(Tcl_Interp *ip,
+                       const AdnsTclRRTypeInfo *type, const char *domain,
+                       int queryopts_objc, Tcl_Obj *const *queryopts_objv,
+                       adns_query *aqu_r, void *context, Resolver **res_r) {
+  struct sockaddr sa;
+  static const int aftry[]= { AF_INET, AF_INET6 };
+  OptionParse op;
+  OptionParse res_op;
+  int rc, r, ec;
+  adns_state ads;
+  
+  op.aflags= adns_qf_owner;
+  op.sflags= 0;
+  op.resolver= 0;
+  op.reverseany= 0;
+  rc= parse_options(ip, queryopts_objc,queryopts_objv, query_optioninfos,&op);
+  if (rc) return rc;
+
+  if (!op.resolver) {
+    op.resolver= default_resolver(ip);
+    if (!op.resolver) {
+      optparse_blank(&res_op);
+      rc= create_resolver(ip, &res_op, &op.resolver);
+      if (rc) return rc;
+
+      Tcl_SetAssocData(ip, ASSOC_DEFAULTRES,
+                      destroy_resolver_defcb, op.resolver);
+    }
+  }
+
+  *res_r= op.resolver;
+  
+  if (op.reverseany || (op.sflags & oisf_reverse)) {
+    const int *af;
+    for (af=aftry; af < af + sizeof(af)/sizeof(*af); af++) {
+      memset(&sa,0,sizeof(sa));
+      sa.sa_family= *af;
+      r= inet_pton(*af,domain,&sa);
+      if (!r) goto af_found;
+    }
+    return cht_staticerr(ip,"invalid address for adns reverse submit",
+                        "ADNS REVERSE INVALID");
+  af_found:;
+  }
+
+  ads= op.resolver->ads;
+
+  if (op.reverseany) {
+    ec= adns_submit_reverse_any(ads, &sa, op.reverseany,
+                               type->number, op.aflags, context, aqu_r);
+  } else if (op.sflags & oisf_reverse) {
+    ec= adns_submit_reverse(ads, &sa,
+                           type->number, op.aflags, context, aqu_r);
+  } else {
+    ec= adns_submit(ads, domain,
+                   type->number, op.aflags, context, aqu_r);
+  }
+  if (ec)
+    return cht_posixerr(ip,ec,"submit adns query");
+
+  return TCL_OK;
+}
+
+#define RESULTSTATUS_LLEN 4
+#define RESULTLIST_LLEN 7
+
+static void make_resultstatus(Tcl_Interp *ip, adns_status status,
+                             Tcl_Obj *results[RESULTSTATUS_LLEN]) {
+  results[0]= cht_ret_string(ip, adns_errtypeabbrev(status));
+  results[1]= cht_ret_int(ip, status);
+  results[2]= cht_ret_string(ip, adns_errabbrev(status));
+  results[3]= cht_ret_string(ip, adns_strerror(status));
+  assert(RESULTSTATUS_LLEN==4);
+}
+
+static Tcl_Obj *make_resultrdata(Tcl_Interp *ip, adns_answer *answer) {
+  Tcl_Obj **rdata, *rl;
+  int i, rrsz;
+  adns_status st;
+  char *datap, *rdatastring;
+  
+  rdata= TALLOC(sizeof(*rdata) * answer->nrrs);
+  for (i=0, datap=answer->rrs.untyped;
+       i<answer->nrrs;
+       i++, datap += rrsz) {
+    st= adns_rr_info(answer->type, 0,0, &rrsz, datap, &rdatastring);
+    assert(!st);
+    rdata[i]= cht_ret_string(ip, rdatastring);
+    free(rdatastring);
+  }
+  rl= Tcl_NewListObj(answer->nrrs, rdata);
+  TFREE(rdata);
+  return rl;
+}
+
+static void make_resultlist(Tcl_Interp *ip, adns_answer *answer,
+                           Tcl_Obj *results[RESULTLIST_LLEN]) {
+
+  make_resultstatus(ip, answer->status, results);
+  assert(RESULTSTATUS_LLEN==4);
+  results[4]= cht_ret_string(ip, answer->owner);
+  results[5]= cht_ret_string(ip, answer->cname ? answer->cname : "");
+  results[6]= make_resultrdata(ip, answer);
+  assert(RESULTLIST_LLEN==7);
+}
+
+/*---------- synchronous query handling ----------*/
+
+static int synch(Tcl_Interp *ip, const AdnsTclRRTypeInfo *rrtype,
+                const char *domain,
+                int objc, Tcl_Obj *const *objv, adns_answer **answer_r) {
+  adns_query aqu;
+  Resolver *res;
+  int rc, ec;
+  
+  rc= query_submit(ip,rrtype,domain,objc,objv,&aqu,0,&res);
+  if (rc) return rc;
+
+  ec= adns_wait(res->ads,&aqu,answer_r,0);
+  assert(!ec);
+
+  asynch_perturbed(res);
+  return TCL_OK;
+}
+
+int cht_do_adns_lookup(ClientData cd, Tcl_Interp *ip,
+                  const AdnsTclRRTypeInfo *rrtype,
+                  const char *domain,
+                  int objc, Tcl_Obj *const *objv,
+                  Tcl_Obj **result) {
+  int rc;
+  adns_answer *answer;
+  
+  rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
+
+  if (answer->status) {
+    Tcl_Obj *problem[RESULTSTATUS_LLEN];
+    make_resultstatus(ip, answer->status, problem);
+    *result= Tcl_NewListObj(RESULTSTATUS_LLEN, problem);
+  } else {
+    *result= make_resultrdata(ip, answer);
+  }
+  free(answer);
+  return TCL_OK;
+}
+
+int cht_do_adns_synch(ClientData cd, Tcl_Interp *ip,
+                 const AdnsTclRRTypeInfo *rrtype,
+                 const char *domain,
+                 int objc, Tcl_Obj *const *objv,
+                 Tcl_Obj **result) {
+  int rc;
+  adns_answer *answer;
+  Tcl_Obj *results[RESULTLIST_LLEN];
+
+  rc= synch(ip,rrtype,domain,objc,objv,&answer);  if (rc) return rc;
+  make_resultlist(ip,answer,results);
+  free(answer);
+  *result= Tcl_NewListObj(RESULTLIST_LLEN, results);
+  return TCL_OK;
+}
+
+/*---------- asynchronous query handling ----------*/
+
+static void asynch_check_now(Resolver *res);
+
+static void asynch_timerhandler(void *res_v) {
+  Resolver *res= res_v;
+  res->timertoken= 0;
+  adns_processtimeouts(res->ads,0);
+  asynch_check_now(res);
+}
+
+static void asynch_filehandler(void *res_v, int mask) {
+  Resolver *res= res_v;
+  int ec;
+  
+  ec= adns_processany(res->ads);
+  if (ec) adns_globalsystemfailure(res->ads);
+  asynch_check_now(res);
+}
+
+static void asynch_sethandlers_generic(Resolver *res,
+                                      int shutdown /*from _cancelhandlers*/,
+                                      int immediate /*from _perturbed*/) {
+  fd_set want[3];
+  int maxfd;
+  struct timeval tv_buf, *timeout;
+  int i, fd;
+
+  timeout= 0;
+  maxfd= 0;
+  for (i=0; i<3; i++) FD_ZERO(&want[i]);
+
+  if (!shutdown)
+    adns_beforeselect(res->ads,&maxfd,&want[0],&want[1],&want[2],
+                     &timeout,&tv_buf,0);
+
+  for (fd= 0; fd < maxfd || fd < res->maxfd; fd++)
+    for (i=0; i<3; i++)
+      if (!!FD_ISSET(fd, &res->handling[i])
+         != !!FD_ISSET(fd, &want[i])) {
+       int mask=0;
+       if (FD_ISSET(fd, &want[0])) mask |= TCL_READABLE;
+       if (FD_ISSET(fd, &want[1])) mask |= TCL_WRITABLE;
+       if (FD_ISSET(fd, &want[2])) mask |= TCL_EXCEPTION;
+       if (mask) {
+         Tcl_CreateFileHandler(fd,mask,asynch_filehandler,res);
+         FD_SET(fd, &res->handling[i]);
+       } else {
+         Tcl_DeleteFileHandler(fd);
+         FD_CLR(fd, &res->handling[i]);
+       }
+      }
+  res->maxfd= maxfd;
+
+  Tcl_DeleteTimerHandler(res->timertoken);
+
+  if (immediate) {
+    res->timertoken= Tcl_CreateTimerHandler(0,asynch_timerhandler,res);
+  } else if (timeout) {
+    int milliseconds;
+
+    if (timeout->tv_sec >= INT_MAX/1000 - 1)
+      milliseconds= INT_MAX;
+    else
+      milliseconds= timeout->tv_sec * 1000 +
+       (timeout->tv_usec + 999) / 1000;
+    
+    res->timertoken=
+      Tcl_CreateTimerHandler(milliseconds,asynch_timerhandler,res);
+  }
+}
+
+static void asynch_sethandlers(Resolver *res) {
+  asynch_sethandlers_generic(res,0,0);
+}
+static void asynch_cancelhandlers(Resolver *res) {
+  asynch_sethandlers_generic(res,1,0);
+}
+static void asynch_perturbed(Resolver *res) {
+  asynch_sethandlers_generic(res,0,1);
+}
+
+static void asynch_check_now(Resolver *res) {
+  Tcl_Interp *interp= res->interp;
+  adns_query aqu;
+  adns_answer *answer;
+  void *query_v;
+  Query *query;
+  ScriptToInvoke *si;
+  int ec;
+  Tcl_Obj *results[RESULTLIST_LLEN];
+
+  Tcl_Preserve(res);
+  
+  for (;;) {
+    if (!res->ads) { /* oh, it has been destroyed! */
+      Tcl_Release(res);
+      return;
+    }
+
+    aqu= 0;
+    ec= adns_check(res->ads, &aqu, &answer, &query_v);
+    if (ec==ESRCH || ec==EAGAIN) break;
+    assert(!ec);
+    query= query_v;
+
+    query->aqu= 0;
+    cht_tabledataid_disposing(interp, query, &cht_adnstcl_queries);
+
+    si= (!answer->status ? &query->on_yes
+        : answer->status > adns_s_max_tempfail ? &query->on_no
+        : &query->on_fail);
+
+    make_resultlist(interp, answer, results);
+    free(answer);
+    cht_scriptinv_invoke(si, RESULTLIST_LLEN, results);
+    asynch_query_dispose(interp, query);
+  }
+
+  asynch_sethandlers(res);
+
+  Tcl_Release(res);
+}
+    
+int cht_do_adns_asynch(ClientData cd, Tcl_Interp *ip,
+                  Tcl_Obj *on_yes, Tcl_Obj *on_no,
+                  Tcl_Obj *on_fail, Tcl_Obj *xargs,
+                  const AdnsTclRRTypeInfo *rrtype, const char *domain,
+                  int objc, Tcl_Obj *const *objv, void **result) {
+  Query *query;
+  int rc;
+  Resolver *res= 0;
+  
+  query= TALLOC(sizeof(*query));
+  query->ix= -1;
+  query->aqu= 0;
+  cht_scriptinv_init(&query->on_yes);
+  cht_scriptinv_init(&query->on_no);
+  cht_scriptinv_init(&query->on_fail);
+  query->xargs= 0;
+
+  rc= query_submit(ip,rrtype,domain,objc,objv,&query->aqu,query,&query->res);
+  if (rc) goto x_rc;
+
+  res= query->res;
+
+  rc= cht_scriptinv_set(&query->on_yes, ip,on_yes, xargs);  if (rc) goto x_rc;
+  rc= cht_scriptinv_set(&query->on_no,  ip,on_no,  xargs);  if (rc) goto x_rc;
+  rc= cht_scriptinv_set(&query->on_fail,ip,on_fail,xargs);  if (rc) goto x_rc;
+  query->xargs= xargs;
+  Tcl_IncrRefCount(xargs);
+  *result= query;
+  query= 0; /* do not dispose */
+  rc= TCL_OK;
+
+ x_rc:
+  if (query) asynch_query_dispose(ip, query);
+  if (res) asynch_perturbed(res);
+  return rc;
+}
+
+int cht_do_adns_asynch_cancel(ClientData cd, Tcl_Interp *ip, void *query_v) {
+  Query *query= query_v;
+  Resolver *res= query->res;
+  asynch_query_dispose(ip, query);
+  asynch_perturbed(res);
+  return TCL_OK;
+}
+
+static void asynch_query_dispose(Tcl_Interp *interp, Query *query) {
+  cht_tabledataid_disposing(interp, query, &cht_adnstcl_queries);
+  cht_scriptinv_cancel(&query->on_yes);
+  cht_scriptinv_cancel(&query->on_no);
+  cht_scriptinv_cancel(&query->on_fail);
+  if (query->xargs) Tcl_DecrRefCount(query->xargs);
+  if (query->aqu) adns_cancel(query->aqu);
+  TFREE(query);
+}
+
+static void destroy_query_idtabcb(Tcl_Interp *interp, void *query_v) {
+  asynch_query_dispose(interp, query_v);
+}
+
+const IdDataSpec cht_adnstcl_queries= {
+  "adns", "adns-query-table", destroy_query_idtabcb
+};
+
+/*---------- main hooks for tcl ----------*/
+
+CHT_INIT(adns, {}, CHTI_COMMANDS(cht_adnstoplevel_entries))
diff --git a/adns/adns.tct b/adns/adns.tct
new file mode 100644 (file)
index 0000000..74ef451
--- /dev/null
@@ -0,0 +1,55 @@
+# adns binding for Tcl
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Type adnsresults:              adns_answer *@
+Init adnsresults               @=0;
+Fini adnsresults               free(@);
+
+Table *adnstoplevel TopLevel_Command
+       adns
+               dispatch(Adns/_SubCommand, "adns subcommand")
+
+Table adns Adns_SubCommand
+       lookup
+               rrtype  enum(AdnsTclRRTypeInfo/, "rrtype")
+               domain  string
+               ...     obj
+               =>      obj
+       synch
+               rrtype  enum(AdnsTclRRTypeInfo/, "rrtype")
+               domain  string
+               ...     obj
+               =>      obj
+       asynch
+               on_yes  obj
+               on_no   obj
+               on_fail obj
+               xargs   obj
+               rrtype  enum(AdnsTclRRTypeInfo/, "rrtype")
+               domain  string
+               ...     obj
+               =>      iddata(&cht_adnstcl_queries)
+       asynch-cancel
+               query   iddata(&cht_adnstcl_queries)
+       new-resolver
+               ...     obj
+               =>      iddata(&cht_adnstcl_resolvers)
+       set-default-resolver
+               res     iddata(&cht_adnstcl_resolvers)
+       destroy-resolver
+               res     iddata(&cht_adnstcl_resolvers)
+
diff --git a/adns/chiark_tcl_adns.h b/adns/chiark_tcl_adns.h
new file mode 100644 (file)
index 0000000..40cb735
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * adns binding for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ADNSTCL_H
+#define ADNSTCL_H
+
+#include "chiark-tcl.h"
+
+typedef struct {
+  const char *name;
+  adns_rrtype number;
+} AdnsTclRRTypeInfo;
+
+extern const IdDataSpec cht_adnstcl_queries, cht_adnstcl_resolvers;
+
+#include "adns+tcmdif.h"
+
+#endif /*ADNSTCL_H*/
diff --git a/base/Makefile b/base/Makefile
new file mode 100644 (file)
index 0000000..1bbfd21
--- /dev/null
@@ -0,0 +1,32 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+default:       all
+
+CFILES =       enum hook idtable parse scriptinv tcmdiflib
+BASE_DIR =     .
+
+AUTO_HDRS +=   base+tcmdif.h
+
+include common.make
+
+SHLIB =                $(BASE_SHLIB)
+
+base+tcmdif.h: $(BASE_TCT) $(TCMDIFGEN)
+               $(TCMDIFGEN) -wh -o$@ $<
+
+include shlib.make
+include final.make
diff --git a/base/base.tct b/base/base.tct
new file mode 100644 (file)
index 0000000..27ea098
--- /dev/null
@@ -0,0 +1,26 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Type iddata(const IdDataSpec *idds):   void *@
+Type ulong:                    uint32_t @
+Type long:                     long @
+Type string:                   const char *@
+Type constv(Tcl_ObjType*):     Tcl_Obj *@
+
+Type charfrom(const char *opts, const char *what):     int
+
+NoEntryDefine  TopLevel_Command
diff --git a/base/chiark-tcl-base.h b/base/chiark-tcl-base.h
new file mode 100644 (file)
index 0000000..303e72d
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <string.h>
+#include <errno.h>
+
+#include "chiark-tcl.h"
+#include "base+tcmdif.h"
diff --git a/base/chiark-tcl.h b/base/chiark-tcl.h
new file mode 100644 (file)
index 0000000..2f4ae7d
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHIARK_TCL_H
+#define CHIARK_TCL_H
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#ifndef _TCL /* if someone already included some tcl.h, use that */
+#include <tcl.h>
+#endif /*_TCL*/
+
+#include <adns.h>
+
+typedef unsigned char Byte;
+
+/* for assisting tcmdifgen and tcmdiflib.c */
+
+typedef struct TopLevel_Command TopLevel_Command;
+
+struct TopLevel_Command {
+  const char *name;
+  Tcl_ObjCmdProc *func;
+};
+
+void cht_setstringresult(Tcl_Interp*, const char*);
+int cht_pat_enum(Tcl_Interp*, Tcl_Obj*, const void**,
+                const void*, size_t, const char *what);
+
+/* from scriptinv.c */
+
+typedef struct { /* opaque; comments are for scriptinv.c impl'n only */
+  /* states:                Cancelled       Set                          */
+  Tcl_Interp *ipq;     /*    0               valid, non-0, useable       */
+  Tcl_Obj *script;     /*    0               valid, non-0                */
+  Tcl_Obj *xargs;      /*    0               valid, may be 0             */
+  int llen;            /*    undefined       llength of script + xargs   */
+} ScriptToInvoke;
+
+void cht_scriptinv_init(ScriptToInvoke *si); /* undefined -> Cancelled */
+int cht_scriptinv_set(ScriptToInvoke *si, Tcl_Interp *ip,
+                     Tcl_Obj *newscript, Tcl_Obj *xargs);
+  /* Cancelled/Set -> Set (newscript!=0, ok) / Cancelled (otherwise) */
+void cht_scriptinv_cancel(ScriptToInvoke *si);
+  /* Cancelled/Set -> Cancelled.  No separate free function - just cancel. */
+#define cht_scriptinv_interp(si) ((si)->ipq)
+  /* int cht_scriptinv_interp(ScriptToInvoke *si);  returns 0 if Cancelled */
+
+int cht_scriptinv_invoke_fg(ScriptToInvoke *si, int argc,
+                           Tcl_Obj *const *argv);
+  /* is a no-op if Cancelled rather than Set */
+  /* if script fails, returns that error */
+
+void cht_scriptinv_invoke(ScriptToInvoke *si, int argc, Tcl_Obj *const *argv);
+  /* if script fails, reports it with Tcl_BackgroundError */
+
+/* from idtable.c */
+
+typedef struct {
+  const char *valprefix, *assockey;
+  void (*destroyitem)(Tcl_Interp *ip, void *val);
+} IdDataSpec;
+
+/* The stored struct must start with a single int, conventionally
+ * named `ix'.  When the struct is returned for the first time ix must
+ * be -1; on subsequent occasions it must be >=0.  ix will be -1 iff
+ * the struct is registered by the iddatatable machinery. */
+
+extern Tcl_ObjType cht_tabledataid_nearlytype;
+int cht_tabledataid_parse(Tcl_Interp *ip, Tcl_Obj *o, const IdDataSpec *idds);
+void cht_tabledataid_disposing(Tcl_Interp *ip, void *val, const IdDataSpec *idds);
+  /* call this when you destroy the struct, to remove its name;
+   * _disposing is idempotent */
+
+/* from hook.c */
+
+int cht_staticerr(Tcl_Interp *ip, const char *m, const char *ec);
+int cht_posixerr(Tcl_Interp *ip, int errnoval, const char *m);
+int cht_newfdposixerr(Tcl_Interp *ip, int fd, const char *m);
+void cht_objfreeir(Tcl_Obj *o);
+int cht_get_urandom(Tcl_Interp *ip, Byte *buffer, int l);
+
+void cht_obj_updatestr_vstringls(Tcl_Obj *o, ...);
+  /* const char*, size_t, const char*, size_t, ..., (const char*)0 */
+void cht_obj_updatestr_string_len(Tcl_Obj *o, const char *str, int l);
+void cht_obj_updatestr_string(Tcl_Obj *o, const char *str);
+
+void cht_prepare__basic(Tcl_Interp *ip);
+void cht_setup__commands(Tcl_Interp *ip, const TopLevel_Command *cmds);
+  /* ... for use by CHT_INIT and CHTI_... macros only */
+
+/* from parse.c */
+
+typedef struct {
+  Tcl_Obj *obj, *var;
+  int copied;
+} Something_Var;
+
+void cht_init_somethingv(Something_Var *sth);
+void cht_fini_somethingv(Tcl_Interp *ip, int rc, Something_Var *sth);
+int cht_pat_somethingv(Tcl_Interp *ip, Tcl_Obj *var,
+                  Something_Var *sth, Tcl_ObjType *type);
+
+/* from enum.c */
+
+extern Tcl_ObjType cht_enum_nearlytype;
+extern Tcl_ObjType cht_enum1_nearlytype;
+
+const void *cht_enum_lookup_cached_func(Tcl_Interp *ip, Tcl_Obj *o,
+                                   const void *firstentry, size_t entrysize,
+                                   const char *what);
+#define enum_lookup_cached(ip,o,table,what)                    \
+    (cht_enum_lookup_cached_func((ip),(o),                             \
+                            &(table)[0],sizeof((table)[0]),    \
+                            (what)))
+  /* table should be a pointer to an array of structs of size
+   * entrysize, the first member of which should be a const char*.
+   * The table should finish with a null const char *.
+   * On error, 0 is returned and the ip->result will have been
+   * set to the error message.
+   */
+
+int cht_enum1_lookup_cached_func(Tcl_Interp *ip, Tcl_Obj *o,
+                            const char *opts, const char *what);
+  /* -1 => error */
+
+/* useful macros */
+
+#define TALLOC(s) ((void*)Tcl_Alloc((s)))
+#define TFREE(f) (Tcl_Free((void*)(f)))
+#define TREALLOC(p,l) ((void*)Tcl_Realloc((void*)(p),(l)))
+
+/* macros for Chiark_tcl_FOOBAR_Init et al */
+
+  /*
+   * use these macros like this:
+   *    CHT_INIT(<extbase>,
+   *             <preparations>,
+   *             <results>)
+   * where
+   *
+   *   <extbase> is the short name eg `hbytes'
+   *     and should correspond to EXTBASE from the Makefile.
+   *
+   *   <results> are the initialisations which cause new commands
+   *     etc. to appear in the Tcl namespace.  Eg, CHTI_COMMANDS,
+   *     These initialisations are called only when a Tcl `load'
+   *     command loads this extension.
+   *
+   *   <preparations> are the initialisations that we need but which
+   *     do not interfere with the Tcl namespaces.  For example,
+   *     OBJECT types we used (CHTI_TYPE), and other chiark_tcl
+   *     extensions (CHTI_OTHER).  These initialisations are called
+   *     both as a result of Tcl `load' (before the <results>
+   *     initialisations) and also when another extension declares a
+   *     dependency on this one with CHTI_OTHER.
+   *
+   *   Both <results> and <preparations> are whitespace-separated
+   *   lists of calls to CHTI_... macros.  If the list is to be empty,
+   *   write `{ }' instead to prevent an empty macro argument.  The
+   *   preparations and results currently supported are:
+   *
+   *      CHTI_COMMANDS(cht_<somethingtoplevel>_entries)
+   *          where the .tct file contains
+   *            Table *<somethingtoplevel> TopLevel_Command
+   *
+   *      CHTI_OTHER(<extbase-of-underlying-extension>)
+   *          which does the <preparations> of that extension
+   *          (if they have not already been done).
+   *
+   *      CHTI_TYPE(cht_<something>_type)
+   *          where   extern Tcl_ObjType cht_<something>_type;
+   *          Note that CHTI_TYPE should only be called by the
+   *          extension which actually implements the type.  Other
+   *          extensions which need it should use CHTI_OTHER to bring
+   *          in the implementing extension.
+   */
+
+#define CHT_INIT(e, preparations, results)                                  \
+  extern void cht_prepare_##e(Tcl_Interp *ip);                              \
+  void cht_prepare_##e(Tcl_Interp *ip) {                                    \
+    static int prepared;                                                    \
+    if (prepared) return;                                                   \
+    cht_prepare__basic(ip);                                                 \
+    { preparations }                                                        \
+    prepared= 1;                                                            \
+  }                                                                         \
+  extern int Chiark_tcl_##e##_Init(Tcl_Interp *ip); /*called by load(3tcl)*/ \
+  int Chiark_tcl_##e##_Init(Tcl_Interp *ip) {                               \
+    static int initd;                                                       \
+    if (initd) return TCL_OK;                                               \
+    cht_prepare_##e(ip);                                                    \
+    { results }                                                                     \
+    initd= 1;                                                               \
+    return TCL_OK;                                                          \
+  }
+
+#define CHTI_OTHER(e)                                                   \
+  { extern void cht_prepare_##e(Tcl_Interp *ip); cht_prepare_##e(ip); }
+
+#define CHTI_TYPE(ot)      { Tcl_RegisterObjType(&(ot)); }
+
+#define CHTI_COMMANDS(cl)  { cht_setup__commands(ip,cl); }
+
+#endif /*CHIARK_TCL_H*/
diff --git a/base/common.make b/base/common.make
new file mode 100644 (file)
index 0000000..b0d4bae
--- /dev/null
@@ -0,0 +1,47 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+VERSION ?=     1
+FAMILY ?=      chiark_tcl
+TCL_VERSION ?= 8.4
+TCL_INCLUDEDIR ?= /usr/include/tcl$(TCL_VERSION)
+
+OPTIMISE ?=            -O2
+TCL_MEM_DEBUG ?=       -DTCL_MEM_DEBUG
+
+TCMDIFGEN ?=   $(BASE_DIR)/tcmdifgen
+BASE_TCT ?=    $(BASE_DIR)/base.tct
+BASE_SHLIB ?=  lib$(FAMILY)-$(VERSION)
+
+CFLAGS +=      -g -Wall -Wmissing-prototypes -Wstrict-prototypes -Werror \
+               $(OPTIMISE)
+
+ifeq ($(shell $(CC) -Wno-pointer-sign -E -x c /dev/null >/dev/null || echo x),)
+CFLAGS +=      -Wno-pointer-sign
+endif
+
+ifeq ($(shell $(CC) -fno-strict-aliasing -E -x c /dev/null >/dev/null || echo x),)
+CFLAGS +=      -fno-strict-aliasing
+endif
+
+CPPFLAGS +=    -I$(TCL_INCLUDEDIR) -I$(BASE_DIR)
+CPPFLAGS +=    $(TCL_MEM_DEBUG)
+
+AUTOS +=       $(AUTO_SRCS) $(AUTO_HDRS)
+
+default:       all
+
diff --git a/base/enum.c b/base/enum.c
new file mode 100644 (file)
index 0000000..28189c7
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+static void enum_nt_dup(Tcl_Obj *src, Tcl_Obj *dup) {
+  dup->internalRep= src->internalRep;
+  dup->typePtr= src->typePtr;
+}
+
+static void enum_nt_ustr(Tcl_Obj *o) {
+  abort();
+}
+
+static int enum_nt_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  abort();
+}
+
+Tcl_ObjType cht_enum_nearlytype = {
+  "enum-nearly",
+  0, enum_nt_dup, enum_nt_ustr, enum_nt_sfa
+};
+
+Tcl_ObjType cht_enum1_nearlytype = {
+  "enum1-nearly",
+  0, enum_nt_dup, enum_nt_ustr, enum_nt_sfa
+};
+
+static void report_bad(Tcl_Interp *ip, const char *what, const char *supplied,
+                      const void *first, size_t each,
+                      int (*isvalid)(const void *entry),
+                      void (*appres)(Tcl_Interp *ip, const void *entry)) {
+  int count, i;
+  const Byte *entry;
+
+  for (entry=first; isvalid(entry); entry+=each);
+  count= (entry - (const Byte*)first) / each;
+
+  Tcl_ResetResult(ip);
+  Tcl_AppendResult(ip, "bad ",what," \"",supplied,"\": must be",(char*)0);
+
+  for (i=0, entry=first; i<count; i++, entry+=each) {
+    Tcl_AppendResult(ip,
+                    (char*)(i==0 ? " " :
+                            i+1==count ? ", or " :
+                            ", "),
+                    (char*)0);
+    appres(ip,entry);
+  }
+}
+
+static const char *enum_str(const void *p) { return *(const char*const*)p; }
+static int isvalid_enum(const void *p) { return !!enum_str(p); }
+static void appres_enum(Tcl_Interp *ip, const void *p) {
+  Tcl_AppendResult(ip, enum_str(p), (char*)0);
+}
+
+const void *cht_enum_lookup_cached_func(Tcl_Interp *ip, Tcl_Obj *o,
+                                   const void *firstentry, size_t entrysize,
+                                   const char *what) {
+  const char *supplied, *found;
+  const char *ep;
+  
+  if (o->typePtr == &cht_enum_nearlytype &&
+      o->internalRep.twoPtrValue.ptr1 == firstentry)
+    return o->internalRep.twoPtrValue.ptr2;
+
+  supplied= Tcl_GetStringFromObj(o,0);  assert(supplied);
+  for (ep= firstentry;
+       (found= *(const char*const*)ep) && strcmp(supplied,found);
+       ep += entrysize);
+
+  if (found) {
+    cht_objfreeir(o);
+    o->typePtr= &cht_enum_nearlytype;
+    o->internalRep.twoPtrValue.ptr1= (void*)firstentry;
+    o->internalRep.twoPtrValue.ptr2= (void*)ep;
+    return ep;
+  }
+
+  report_bad(ip,what,supplied, firstentry,entrysize, isvalid_enum,appres_enum);
+  return 0;
+}
+
+static int isvalid_enum1(const void *p) { return !!*(const char*)p; }
+static void appres_enum1(Tcl_Interp *ip, const void *p) {
+  char buf[2];
+  buf[0]= *(const char*)p;
+  buf[1]= 0;
+  Tcl_AppendResult(ip, buf, (char*)0);
+}
+
+int cht_enum1_lookup_cached_func(Tcl_Interp *ip, Tcl_Obj *o,
+                            const char *opts, const char *what) {
+  const char *supplied, *fp;
+  
+  if (o->typePtr != &cht_enum1_nearlytype ||
+      o->internalRep.twoPtrValue.ptr1 != opts) {
+
+    supplied= Tcl_GetStringFromObj(o,0);  assert(supplied);
+    
+    if (!(strlen(supplied) == 1 &&
+         (fp= strchr(opts, supplied[0])))) {
+      report_bad(ip,what,supplied, opts,1, isvalid_enum1,appres_enum1);
+      return -1;
+    }
+    
+    cht_objfreeir(o);
+    o->typePtr= &cht_enum1_nearlytype;
+    o->internalRep.twoPtrValue.ptr1= (void*)opts;
+    o->internalRep.twoPtrValue.ptr2= (void*)fp;
+  }
+  return (const char*)o->internalRep.twoPtrValue.ptr2 - opts;
+}
diff --git a/base/extension.make b/base/extension.make
new file mode 100644 (file)
index 0000000..23e72ee
--- /dev/null
@@ -0,0 +1,44 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+
+EXTPREFIX ?=   $(FAMILY)_
+EXTENSION ?=   $(EXTPREFIX)$(EXTBASE)
+SHLIB ?=       $(EXTENSION)-$(VERSION)
+TABLE ?=       $(EXTBASE)
+
+AUTO_HDRS +=   $(TABLE)+tcmdif.h
+AUTO_SRCS +=   $(TABLE)+tcmdif.c
+CFILES +=      $(TABLE)+tcmdif
+
+CPPFLAGS += $(foreach o, $(OTHER_EXTS), -I../$(dir $o))
+LDLIBS += $(foreach o, $(OTHER_EXTS), ../$(dir $o)$(EXTPREFIX)$(notdir $o)-$(VERSION).so)
+
+LDLIBS +=      $(BASE_DIR)/$(BASE_SHLIB).so
+
+include                $(BASE_DIR)/common.make
+include                $(BASE_DIR)/shlib.make
+
+TCMDIFARGS ?=  -p$(FAMILY)_$(EXTBASE) -o$@ $(BASE_TCT) $(OTHER_TCTS) $<
+
+%+tcmdif.c:    %.tct $(BASE_TCT) $(OTHER_TCTS) $(TCMDIFGEN)
+               $(TCMDIFGEN) -wc $(TCMDIFARGS)
+
+%+tcmdif.h:    %.tct $(BASE_TCT) $(OTHER_TCTS) $(TCMDIFGEN)
+               $(TCMDIFGEN) -wh $(TCMDIFARGS)
+
+include                $(BASE_DIR)/final.make
diff --git a/base/final.make b/base/final.make
new file mode 100644 (file)
index 0000000..12db93d
--- /dev/null
@@ -0,0 +1,30 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+all:           $(TARGETS) $(AUTOS)
+
+$(OBJS_CFILES):        $(AUTO_HDRS)
+
+%.o:           %.c
+               $(CC) $(CFLAGS) $(CPPFLAGS) -MMD -o $@ -c $<
+
+clean:
+               rm -f $(AUTOS) *~ ./#*# *.d *+tcmdif.*
+               rm -f *.o *.so $(CLEANS)
+
+-include $(patsubst %.o,%.d, $(OBJS))
+
diff --git a/base/hook.c b/base/hook.c
new file mode 100644 (file)
index 0000000..6e4b3a1
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+int cht_staticerr(Tcl_Interp *ip, const char *m, const char *ec) {
+  Tcl_SetResult(ip, (char*)m, TCL_STATIC);
+  if (ec) Tcl_SetObjErrorCode(ip, Tcl_NewStringObj(ec,-1));
+  return TCL_ERROR;
+}
+
+int cht_posixerr(Tcl_Interp *ip, int errnoval, const char *m) {
+  const char *em;
+  
+  Tcl_ResetResult(ip);
+  errno= errnoval;
+  em= Tcl_PosixError(ip);
+  Tcl_AppendResult(ip, m, ": ", em, (char*)0);
+  return TCL_ERROR;
+}
+
+int cht_newfdposixerr(Tcl_Interp *ip, int fd, const char *m) {
+  int e;
+  e= errno;
+  close(fd);
+  return cht_posixerr(ip,e,m);
+}
+
+void cht_objfreeir(Tcl_Obj *o) {
+  if (o->typePtr && o->typePtr->freeIntRepProc)
+    o->typePtr->freeIntRepProc(o);
+  o->typePtr= 0;
+}  
+
+void cht_obj_updatestr_vstringls(Tcl_Obj *o, ...) {
+  va_list al;
+  char *p;
+  const char *part;
+  int l;
+  size_t pl;
+
+  va_start(al,o);
+  for (l=0; (part= va_arg(al, const char*)); ) {
+    pl= va_arg(al, size_t);
+    assert(pl <= INT_MAX/2 - l);
+    l += pl;
+  }
+  va_end(al);
+  
+  o->length= l;
+  o->bytes= TALLOC(l+1);
+
+  va_start(al,o);
+  for (p= o->bytes; (part= va_arg(al, const char*)); p += pl) {
+    pl= va_arg(al, size_t);
+    memcpy(p, part, pl);
+  }
+  va_end(al);
+
+  *p= 0;
+}
+
+void cht_obj_updatestr_string(Tcl_Obj *o, const char *str) {
+  cht_obj_updatestr_vstringls(o, str, strlen(str), (char*)0);
+}
+
+#define URANDOM "/dev/urandom"
+
+int cht_get_urandom(Tcl_Interp *ip, Byte *buffer, int l) {
+  static FILE *urandom;
+
+  int r;
+
+  if (!urandom) {
+    urandom= fopen(URANDOM,"rb");
+    if (!urandom) return cht_posixerr(ip,errno,"open " URANDOM);
+  }
+  r= fread(buffer,1,l,urandom);
+  if (r==l) return 0;
+
+  if (ferror(urandom)) {
+    r = cht_posixerr(ip,errno,"read " URANDOM);
+  } else {
+    assert(feof(urandom));
+    r = cht_staticerr(ip, URANDOM " gave eof!", 0);
+  }
+  fclose(urandom); urandom=0;
+  return r;
+}
+
+void cht_prepare__basic(Tcl_Interp *ip) {
+  static int prepared;
+
+  if (prepared) return;
+  Tcl_RegisterObjType(&cht_tabledataid_nearlytype);
+  Tcl_RegisterObjType(&cht_enum_nearlytype);
+  Tcl_RegisterObjType(&cht_enum1_nearlytype);
+  prepared= 1;
+}
+
+void cht_setup__commands(Tcl_Interp *ip, const TopLevel_Command *cmds) {
+  const TopLevel_Command *cmd;
+  
+  for (cmd= cmds;
+       cmd->name;
+       cmd++)
+    Tcl_CreateObjCommand(ip, (char*)cmd->name, cmd->func, 0,0);
+}
diff --git a/base/idtable.c b/base/idtable.c
new file mode 100644 (file)
index 0000000..6e7aafa
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+/* Arg parsing */
+
+typedef struct {
+  const IdDataSpec *idds;
+  int n;
+  void **a;
+} IdDataAssocData;
+
+typedef struct {
+  Tcl_Interp *interp;
+  IdDataAssocData *assoc;
+  int ix;
+} IdDataValue;
+
+static void assoc_del(ClientData assoc_cd, Tcl_Interp *ip) {
+  IdDataAssocData *assoc;
+  int ix;
+  void **p, *v;
+
+  assoc= assoc_cd;
+  for (ix=0, p=assoc->a; ix<assoc->n; ix++, p++) {
+    v= *p;
+    if (!v) continue;
+    assert(*(int*)v == ix);
+    *(int*)v= -1;
+    assoc->idds->destroyitem(ip,v);
+    *p= 0;
+  }
+  TFREE(assoc->a);
+  TFREE(assoc);
+}
+
+static void setobjdataid(Tcl_Interp *interp, Tcl_Obj *o,
+                        int ix, const IdDataSpec *idds) {
+  IdDataValue *dv;
+  IdDataAssocData *assoc;
+
+  assoc= Tcl_GetAssocData(interp, (char*)idds->assockey, 0);
+  if (!assoc) {
+    assoc= TALLOC(sizeof(*assoc));
+    assoc->idds= idds;
+    assoc->n= 0;
+    assoc->a= 0;
+    Tcl_SetAssocData(interp, (char*)idds->assockey, assoc_del, assoc);
+  }
+  
+  dv= TALLOC(sizeof(*dv));
+  dv->interp= interp;
+  dv->assoc= assoc;
+  dv->ix= ix;
+  
+  o->typePtr= &cht_tabledataid_nearlytype;
+  o->internalRep.otherValuePtr= dv;
+}
+
+int cht_tabledataid_parse(Tcl_Interp *ip, Tcl_Obj *o, const IdDataSpec *idds) {
+  int l;
+  unsigned long ul;
+  IdDataValue *dv;
+  char *ep, *str;
+
+  if (o->typePtr != &cht_tabledataid_nearlytype) goto convert;
+
+  dv= o->internalRep.otherValuePtr;
+  if (dv->interp != ip) goto convert;
+  if (dv->assoc->idds != idds) goto convert;
+
+  return TCL_OK;
+
+convert:
+  l= strlen(idds->valprefix);
+  str= Tcl_GetStringFromObj(o,0);
+  if (memcmp(str,idds->valprefix,l))
+    return cht_staticerr(ip,"bad id (wrong prefix)",0);
+
+  errno=0; ul=strtoul(str+l,&ep,10);
+  if (errno || *ep) return cht_staticerr(ip,"bad id number",0);
+  if (ul > INT_MAX) return cht_staticerr(ip,"out of range id number",0);
+
+  cht_objfreeir(o);
+  setobjdataid(ip,o,ul,idds);
+  return TCL_OK;
+}
+
+int cht_pat_iddata(Tcl_Interp *ip, Tcl_Obj *o, void **rv, const IdDataSpec *idds) {
+  int rc, ix;
+  IdDataValue *dv;
+  IdDataAssocData *assoc;
+  void *r;
+  
+  rc= cht_tabledataid_parse(ip,o,idds);
+  if (rc) return rc;
+
+  dv= o->internalRep.otherValuePtr;
+  ix= dv->ix;
+  assoc= dv->assoc;
+
+  if (ix >= assoc->n || !(r= assoc->a[ix]))
+    return cht_staticerr(ip,"id not in use",0);
+
+  assert(*(int*)r == ix);
+
+  *rv= r;
+  return TCL_OK;
+}
+
+Tcl_Obj *cht_ret_iddata(Tcl_Interp *ip, void *val, const IdDataSpec *idds) {
+  /* Command procedure implementation may set val->ix,
+   * ie *(int*)val, to -1, to mean it's a new struct.  Otherwise
+   * it had better be an old one !
+   */
+  Tcl_Obj *o;
+  IdDataValue *dv;
+  IdDataAssocData *assoc;
+  int ix;
+
+  o= Tcl_NewObj();
+  setobjdataid(ip,o,0,idds);
+  dv= o->internalRep.otherValuePtr;
+  assoc= dv->assoc;
+
+  ix= *(int*)val;
+  if (ix==-1) {
+    for (ix=0; ix<assoc->n && assoc->a[ix]; ix++);
+    if (ix>=assoc->n) {
+      assert(assoc->n < INT_MAX/4);
+      assoc->n += 2;
+      assoc->n *= 2;
+      assoc->a= TREALLOC(assoc->a, assoc->n*sizeof(*assoc->a));
+      while (ix<assoc->n) assoc->a[ix++]=0;
+      ix--;
+    }
+    assoc->a[ix]= val;
+    *(int*)val= ix;
+  } else {
+    assert(val == assoc->a[ix]);
+  }
+  dv->ix= ix;
+  Tcl_InvalidateStringRep(o);
+  return o;
+}
+
+void cht_tabledataid_disposing(Tcl_Interp *ip, void *val, const IdDataSpec *idds) {
+  IdDataAssocData *assoc;
+  int ix;
+
+  ix= *(int*)val;
+  if (ix==-1) return;
+
+  assoc= Tcl_GetAssocData(ip, (char*)idds->assockey, 0);
+  assert(assoc->a[ix] == val);
+  assoc->a[ix]= 0;
+  *(int*)val= -1;
+}
+
+static int tabledataid_nt_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  abort();
+}
+
+static void tabledataid_nt_free(Tcl_Obj *o) {
+  TFREE(o->internalRep.otherValuePtr);
+  o->internalRep.otherValuePtr= 0;
+}
+
+static void tabledataid_nt_dup(Tcl_Obj *src, Tcl_Obj *dup) {
+  IdDataValue *sv, *dv;
+
+  sv= src->internalRep.otherValuePtr;
+  dv= TALLOC(sizeof(*dv));
+  *dv= *sv;
+  dup->typePtr= &cht_tabledataid_nearlytype;
+  dup->internalRep.otherValuePtr= dv;
+}
+
+static void tabledataid_nt_ustr(Tcl_Obj *o) {
+  const IdDataValue *dv;
+  const IdDataAssocData *assoc;
+  const IdDataSpec *idds;
+  char buf[75];
+
+  dv= o->internalRep.otherValuePtr;
+  assoc= dv->assoc;
+  idds= assoc->idds;
+
+  snprintf(buf,sizeof(buf), "%d", dv->ix);
+  cht_obj_updatestr_vstringls(o,
+                         idds->valprefix, strlen(idds->valprefix),
+                         buf, strlen(buf),
+                         (char*)0);
+}
+
+Tcl_ObjType cht_tabledataid_nearlytype = {
+  "tabledataid",
+  tabledataid_nt_free, tabledataid_nt_dup,
+  tabledataid_nt_ustr, tabledataid_nt_sfa
+};
diff --git a/base/parse.c b/base/parse.c
new file mode 100644 (file)
index 0000000..ad9b7e7
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+int cht_pat_charfrom(Tcl_Interp *ip, Tcl_Obj *obj, int *val,
+                const char *opts, const char *what) {
+  *val= cht_enum1_lookup_cached_func(ip,obj,opts,what);
+  if (*val==-1) return TCL_ERROR;
+  return TCL_OK;
+}
+
+int cht_pat_int(Tcl_Interp *ip, Tcl_Obj *obj, int *val) {
+  return Tcl_GetIntFromObj(ip, obj, val);
+}
+  
+int cht_pat_long(Tcl_Interp *ip, Tcl_Obj *obj, long *val) {
+  return Tcl_GetLongFromObj(ip, obj, val);
+}
+  
+int cht_pat_string(Tcl_Interp *ip, Tcl_Obj *obj, const char **val) {
+  *val= Tcl_GetString(obj);
+  return TCL_OK;
+}
+
+int cht_pat_constv(Tcl_Interp *ip, Tcl_Obj *var,
+              Tcl_Obj **val_r, Tcl_ObjType *type) {
+  int rc;
+  Tcl_Obj *val;
+  
+  val= Tcl_ObjGetVar2(ip,var,0,TCL_LEAVE_ERR_MSG);
+  if (!val) return TCL_ERROR;
+
+  if (type) {
+    rc= Tcl_ConvertToType(ip,val,type);
+    if (rc) return rc;
+  }
+
+  *val_r= val;
+  return TCL_OK;
+}
+
+void cht_init_somethingv(Something_Var *sth) {
+  sth->obj=0; sth->var=0; sth->copied=0;
+}
+
+int cht_pat_somethingv(Tcl_Interp *ip, Tcl_Obj *var,
+                  Something_Var *sth, Tcl_ObjType *type) {
+  int rc;
+  Tcl_Obj *val;
+
+  sth->var= var;
+
+  val= Tcl_ObjGetVar2(ip,var,0,TCL_LEAVE_ERR_MSG);
+  if (!val) return TCL_ERROR;
+
+  rc= Tcl_ConvertToType(ip,val,type);
+  if (rc) return rc;
+
+  if (Tcl_IsShared(val)) {
+    val= Tcl_DuplicateObj(val);
+    sth->copied= 1;
+  }
+  Tcl_InvalidateStringRep(val);
+  sth->obj= val;
+
+  return TCL_OK;
+}
+
+void cht_fini_somethingv(Tcl_Interp *ip, int rc, Something_Var *sth) {
+  Tcl_Obj *ro;
+  
+  if (!rc) {
+    assert(sth->obj);
+    ro= Tcl_ObjSetVar2(ip,sth->var,0,sth->obj,TCL_LEAVE_ERR_MSG);
+    if (!ro) rc= TCL_ERROR;
+  }
+  if (rc && sth->copied)
+    Tcl_DecrRefCount(sth->obj);
+}
+
+Tcl_Obj *cht_ret_long(Tcl_Interp *ip, long val) {
+  return Tcl_NewLongObj(val);
+}
+
+Tcl_Obj *cht_ret_string(Tcl_Interp *ip, const char *val) {
+  return Tcl_NewStringObj(val,-1);
+}
diff --git a/base/scriptinv.c b/base/scriptinv.c
new file mode 100644 (file)
index 0000000..7b67d29
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+void cht_scriptinv_init(ScriptToInvoke *si) {
+  si->ipq= 0;
+  si->script= 0;
+  si->xargs= 0;
+}
+
+void cht_scriptinv_cancel(ScriptToInvoke *si) {
+  if (si->script) { Tcl_DecrRefCount(si->script); si->script= 0; }
+  if (si->xargs) { Tcl_DecrRefCount(si->xargs); si->xargs= 0; }
+  si->ipq= 0;
+}
+
+int cht_scriptinv_set(ScriptToInvoke *si, Tcl_Interp *ip,
+                 Tcl_Obj *newscript, Tcl_Obj *xargs) {
+  int rc, xlength;
+  
+  cht_scriptinv_cancel(si);
+  if (!newscript) return 0;
+
+  rc= Tcl_ListObjLength(ip, newscript, &si->llen);  if (rc) return rc;
+  Tcl_IncrRefCount(newscript);
+
+  if (xargs) {
+    rc= Tcl_ListObjLength(ip, xargs, &xlength);  if (rc) return rc;
+    Tcl_IncrRefCount(xargs);
+    assert(si->llen < INT_MAX/2 && xlength < INT_MAX/2);
+    si->llen += xlength;
+  }
+
+  si->script= newscript;
+  si->xargs= xargs;
+  si->ipq= ip;
+  return 0;
+}  
+  
+int cht_scriptinv_invoke_fg(ScriptToInvoke *si, int argc,
+                           Tcl_Obj *const *argv) {
+  Tcl_Obj *invoke=0;
+  int i, rc;
+
+  if (!si->ipq) return TCL_OK;
+
+  for (i=0; i<argc; i++) Tcl_IncrRefCount(argv[i]);
+
+  invoke= Tcl_DuplicateObj(si->script);
+  Tcl_IncrRefCount(invoke);
+
+  if (si->xargs) {
+    rc= Tcl_ListObjAppendList(si->ipq, invoke, si->xargs);
+    if (rc) goto x_rc;
+  }
+
+  rc= Tcl_ListObjReplace(si->ipq, invoke,si->llen,0, argc,argv);
+  if (rc) goto x_rc;
+
+  rc= Tcl_EvalObjEx(si->ipq, invoke, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
+  if (rc) goto x_rc;
+
+  rc= 0;
+  
+x_rc:
+  for (i=0; i<argc; i++) Tcl_DecrRefCount(argv[i]);
+  if (invoke) Tcl_DecrRefCount(invoke);
+  return rc;
+}
+
+void cht_scriptinv_invoke(ScriptToInvoke *si, int argc, Tcl_Obj *const *argv) {
+  int rc;
+  rc= cht_scriptinv_invoke_fg(si, argc, argv);
+  if (rc) Tcl_BackgroundError(si->ipq);
+}  
diff --git a/base/shlib.make b/base/shlib.make
new file mode 100644 (file)
index 0000000..c48fe4c
--- /dev/null
@@ -0,0 +1,27 @@
+# base code for various Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+
+OBJS_CFILES += $(addsuffix .o, $(CFILES))
+OBJS +=                $(OBJS_CFILES)
+CFLAGS +=      -fPIC
+
+TARGETS +=     $(SHLIB).so
+SHLIB_LDFLAGS ?= $(LDFLAGS) -o $@ -shared -Xlinker -soname=$@
+
+$(SHLIB).so:   $(OBJS)
+               $(CC) $(CFLAGS) $(SHLIB_LDFLAGS) $(OBJS) $(LDLIBS)
diff --git a/base/tcmdifgen b/base/tcmdifgen
new file mode 100755 (executable)
index 0000000..f944799
--- /dev/null
@@ -0,0 +1,575 @@
+#!/usr/bin/perl
+
+# code generator to help with writing Tcl extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+# Input format is line-based, ws-significant, offside rule (some kind
+#  of, anyway).
+#
+#  Type TYPE:       C-TYPE-DECLARATOR
+#     Defines TYPE as a type (for arguments and return values)
+#     which corresponds to the C type specified.  C-TYPE-DECLARATOR
+#     must contain one `@' where the identifier would go.
+#     The type may contain allocated memory, etc., in which case
+#     `Init' and `Fini' must be used.
+#
+#     TYPE may be either TYPENAME or TYPENAME(ARGS) - in this case,
+#     ARGS should be C argument declarations as for in a function
+#     prototype, of extra arguments for the application-supplied
+#     parser/returner functions.  Each time a TYPE is used elsewhere,
+#     the ARGS should be the actual arguments to pass, and will be
+#     textually copied into the calls to the parser/returner
+#     functions.
+#
+#     `Type' causes declarations in the .h file of these functions:
+#        int cht_pat_TYPENAME(Tcl_Interp*, Tcl_Obj *obj, C-TYPE *val, ARGS);
+#        Tcl_Obj *cht_ret_TYPENAME(Tcl_Interp*, C-TYPE val, ARGS);
+#
+#     cht_pat_... must attempt to parse obj into the appropriate type.
+#     val will already have been initialised with `Init' statements if
+#     relevant.  Whether cht_pat_... fails or succeeds it may allocate
+#     memory into the object and must leave the object valid (for
+#     `Fini').
+#
+#     cht_ret_... must convert the value back to a new Tcl_Obj.  It may
+#     not fail.
+#
+#  Init TYPENAME    C-STATEMENTS
+#     Provides some statements which are used to initialise a variable
+#     of type TYPENAME.  C-STATEMENTS should contain one or more `@',
+#     which will be replaced by the actual variable name.  The
+#     variable will have been declared with the C declarator specified
+#     with `Type'.  C-STATEMENTS may not fail or longjmp, and they may
+#     not allocate memory or other resources.  If no `Init' is
+#     supplied then there is no invariant (so no `Fini' may be
+#     supplied either, and the type is `flat' - no memory, external
+#     refs, etc.)
+#
+#  Fini TYPENAME    C-STATEMENTS
+#     Provides some statements (like `Init') which are used to free a
+#     variable of type TYPENAME.  The variable will already have been
+#     initialised with the `Init' statements, and may have been
+#     modified since by application per-type or per-command code.  Its
+#     invariant will be satisfied before C-STATEMENTS.  Afterwards the
+#     invariant may or may not be satisfied, but it may not have any
+#     memory or other resources allocated.  C-STATEMENTS may not fail
+#     or longjmp.
+#
+#  H-Include    C-INCLUDE-SPECIFIER
+#     Arranges for generated .h files to #include the specified
+#     file.  C-INCLUDE-SPECIFIER should include the <..> or "..".
+#
+#  Table [*]TABLENAME C-ENTRY-TYPE
+#     Starts a table of commands or subcommands.  The generated .h
+#     will contain a definition of C-ENTRY-TYPE containing
+#         const char *name;
+#         Tcl_ObjCmdProc *func;
+#     and the generated .c will contain
+#         const C-ENTRY-TYPE C-ARRAY-NAME[];
+#     where C-ARRAY-NAME is TABLENAME, with `_entries' appended
+#     and `cht_' prepended.  The entries are indented one level (one
+#     or more spaces) and look like this:
+#        ENTRYNAME [ C-EXTRA-ENTRY-VALUES ]
+#            FORMALARGNAME   TYPE
+#            ...
+#          [ =>  RESULT-TYPE ]
+#     This will cause the declaration of
+#        int cht_do_TABLENAME_ENTRYNAME(ClientData cd, Tcl_Interp *ip,
+#                                   FORMAL-ARGUMENTS, RESULT-C-TYPE*);
+#     which is the procedure which the application must supply to
+#     implement the function.  If the `=> RESULT-TYPE' is omitted, so
+#     is the result argument to the function.  Each argument to the
+#     function is of the C type corresponding to the specified type.
+#     TYPE may be `...', in which case the C function will be passed
+#     two args (int objc, Tcl_Obj *const *objv) for the remaining
+#     arguments.
+#
+#     The cht_do_... function should not eat any memory associated with
+#     the arguments.  The result buffer (if any) will be initialised
+#     using the `Init' and should on success contain the relevant
+#     result.  On failure it should leave the result unmodified (or at
+#     least, not in need of freeing).
+#
+#     As an alternative, the arguments can be replaced with just
+#            dispatch(TYPE-ARGS-FOR-ENUM)
+#     which is a shorthand for
+#            subcmd   enum(TYPE-ARGS-FOR-ENUM)
+#            args     ...
+#     and also generates and uses a standard dispatch function.
+#
+#     There will be an entry in C-ARRAY-NAME for every table entry.
+#     The name will be ENTRYNAME, and the func will be a function
+#     suitable for use as a Tcl command procedure, which parses the
+#     arguments, processes the command, and sets any result, as
+#     applicable.
+#
+#     `*' should be used if the table name is not useful for error
+#     messages.  It suppresses `TABLENAME ' from the front of the
+#     autogenerated argument parsing error strings.
+#
+#  EntryExtra C-ENTRY-TYPE
+#     Introduces a section of additional C code which will be inserted
+#     into the definition of C-ENTRY-TYPE by `Table'.  The C
+#     code, which follows on several indented lines, should be
+#     structure member definitions.
+#
+#     When EntryExtra is used, in the corresponding Table, each
+#     ENTRYNAME should be followed on the same line by whitespace and
+#     EXTRA-VALUES; the EXTRA-VALUES are used as initialisers for the
+#     additional structure elements.
+#
+#  NoEntryDefine C-ENTRY-TYPE
+#     Prevents the definition of C-ENTRY-TYPE by Table.
+#     The C type must be defined elsewhere.
+#
+#  Also expected are these functions:
+#    void cht_setstringresult(Tcl_Interp*, const char*);
+#        sets the Tcl result from the supplied string
+#    int cht_pat_enum(Tcl_Interp*, Tcl_Obj*, const void **c_e_t_array,
+#                 const void *c_e_t_return, size_t c_e_t_sz, const char *what);
+#        scans a table of C-ENTRY-TYPEs looking for the
+#        string matching the string supplied by the script
+#        (as a Tcl_Obj).  On error sets the result, using
+#        what (a noun phrase describing the type of thing).
+#        Assumes (unportably!) that the name and func members
+#        are in the same places no matter what the rest of
+#        the struct contains.
+#  and the two predefined types `int' (C `int') and `obj' (Tcl_Obj*,
+#  unmodified.)  The corresponding definitions are in tcmdiflib.c.
+
+use IO;
+use Data::Dumper;
+
+parse('builtins','DATA');
+
+while (@ARGV) {
+    $_= shift @ARGV;
+    if (m/^\-p([-_0-9a-z]+)$/) {
+       $prefix= $1;
+       $prefix =~ y/-/_/;
+    } elsif (m/^\-w(c|h)$/) {
+       $write= $1;
+    } elsif (m/^\-o(.+)$/) {
+       $output= $1;
+    } elsif (m/^\-/) {
+       die "unknown option $_\n";
+    } else {
+       if (!defined $prefix) { $prefix= $_;  $prefix =~ s/\.[^.]+$//; }
+       $x= new IO::File $_,'r' or die "$_: $!\n";
+       parse($_,$x);
+    }
+}
+
+die "must say -w<something>\n" if !defined $write;
+
+sub zilch () {
+    undef $c_table;
+    undef $c_entryextra;
+    undef $c_of;
+}
+
+sub enumargs ($) {
+    my ($a) = @_;
+    $a =~ m:/(.*),: or die "invalid enum type \`$a'\n";
+    my ($a_tab, $ee_type, $estr) = ($`,$1,$');
+    if ($ee_type !~ m/^[^_]/) {
+       $ee_type= $a_tab.$ee_type;
+       $a_tab= lc($a_tab).'_entries';
+    }
+    return ($a_tab, $ee_type, $estr);
+}
+
+sub parse ($$) {
+    my ($wh,$f) = @_;
+    while (defined($_= $f->getline)) {
+       chomp; s/\s+$//;
+       next if m/^\s*\#/;
+       next if !m/\S/;
+       while (s/\t/ ' 'x(8 - (length $`) % 8) /e) { }
+
+       s/^\s*//;
+       $this_indent= length $&;
+       while (@i && $this_indent < $i[0]) {
+           shift @i;
+       }
+       if ($this_indent && (!@i || $this_indent > $i[0])) {
+           unshift @i, $this_indent;
+       }
+
+       if (@i==0 && m/^Table\s+(\*?)(\w+)\s+(\w+)$/) {
+           zilch();
+           $c_table= $2;
+           $table_x{$c_table}{T}= $1;
+           $table_x{$c_table}{C}= $3;
+           $entrytype_x{$3}= '' unless exists $entrytype_x{$3};
+       } elsif (@i==0 && m/^Untabled$/) {
+           zilch();
+           $c_table= '';
+       } elsif (@i==0 && m/^(C|H)\-Include\s+(\S.*)$/) {
+           o(lc $1, 30, "#include $2\n");
+       } elsif (@i==0 && m/^EntryExtra\s+(\w+)$/) {
+           zilch();
+           $c_entryextra= $1;
+       } elsif (@i==0 && m/^NoEntryDefine\s+(\w+)$/) {
+           zilch();
+           $entrytype_x{$1}= " ";
+       } elsif (@i>=1 && defined $c_entryextra) {
+           $entrytype_x{$c_entryextra} .= "  $_\n";
+       } elsif (@i==1 && m/^[a-z].*$/ && defined $c_table) {
+           if (m/^[-_0-9A-Za-z]+$/) {
+               $c_entry= $_;
+           } elsif (m/^([-_0-9A-Za-z]+)\s+(\S.*)$/) {
+               $c_entry= $1;
+               $tables{$c_table}{$c_entry}{I} .= ", $2";
+           } else {
+               badsyntax($wh,$.,"bad entry");
+           }
+           $tables{$c_table}{$c_entry}{A} = [ ];
+       } elsif (@i==2 && m/^\.\.\.\s+(\w+)$/ && defined $c_entry) {
+           $tables{$c_table}{$c_entry}{V}= $1;
+       } elsif (@i==2 && m:^dispatch\(((.*)/(.*)\,.*)\)$: && defined $c_entry) {
+           my $enumargs= $1;
+           my $subcmdtype= $2.$3;
+           $tables{$c_table}{$c_entry}{D}= $subcmdtype;
+           $tables{$c_table}{$c_entry}{V}= 'obj';
+           push @{ $tables{$c_table}{$c_entry}{A} },
+               { N => 'subcmd', T => 'enum', A => $enumargs, O => '' };
+       } elsif (@i==2 && m/^(\??)([a-z]\w*)\s*(\S.*)/
+                && defined $c_entry) {
+           ($opt, $var, $type) = ($1,$2,$3);
+           ($type, $xtypeargs) = split_type_args($type);
+           push @{ $tables{$c_table}{$c_entry}{A} },
+               { N => $var, T => $type, A => $xtypeargs, O => ($opt eq '?') };
+       } elsif (@i==2 && m/^\=\>\s*(\S.*)$/ && defined $c_entry) {
+           ($type, $xtypeargs) = split_type_args($1);
+           $tables{$c_table}{$c_entry}{R}= $type;
+           $tables{$c_table}{$c_entry}{X}= $xtypeargs;
+       } elsif (@i==0 && m/^Type\s+([^\:]+)\:\s+(\S.*)$/) {
+           ($typename,$ctype)= ($1,$2);
+           $ctype .= ' @' unless $ctype =~ m/\@/;
+           ($typename,$xtypeargs) = split_type_args($typename);
+           $types{$typename}= { C => $ctype, X => $xtypeargs };
+       } elsif (@i==0 && s/^Init\s+(\w+)\s+(\S.*)//) {
+           $type_init{$1}= $2;
+       } elsif (@i==0 && s/^Fini\s+(\w+)\s+(\S.*)//) {
+           $type_fini{$1}= $2;
+       } else {
+           badsyntax($wh,$., sprintf
+                     "bad directive (indent level %d)", scalar @i);
+       }
+    }
+    $f->error and die $!;
+    $f->close;
+}
+
+#print Dumper(\%tables),"\n";
+#print Dumper(\%types),"\n";
+
+foreach $t (sort keys %types) {
+    $type= $types{$t};
+    $c= $type->{C};
+    $xta= $type->{X};
+    $decl= "int cht_pat_$t(Tcl_Interp *ip, Tcl_Obj *obj, ";
+    $decl .= subst_in_decl('*val', $c, "type $t");
+    $decl .= ", $xta",  if length $xta;
+    $decl .= ");\n";
+    o('h',160, $decl);
+
+    $decl= "Tcl_Obj *cht_ret_$t(Tcl_Interp *ip, ".subst_in_decl('val',$c);
+    $decl .= ", $xta" if length $xta;
+    $decl .= ");\n";
+    o('h',170, $decl);
+}
+
+foreach $c_entrytype (sort keys %entrytype_x) {
+    next if $entrytype_x{$c_entrytype} =~ m/^\s$/;
+    o('h', 20, "typedef struct $c_entrytype $c_entrytype;\n");
+    o('h', 100,
+      "struct $c_entrytype {\n".
+      "  const char *name;\n".
+      "  Tcl_ObjCmdProc *func;\n".
+      $entrytype_x{$c_entrytype}.
+      "};\n\n");
+}
+
+foreach $c_table (sort keys %tables) {
+    $r_table= $tables{$c_table};
+    $x_table= $table_x{$c_table};
+    $op_tab= '';
+
+    foreach $c_entry (sort keys %$r_table) {
+       $c_entry_c= $c_entry; $c_entry_c =~ y/-/_/;
+       $r_entry= $r_table->{$c_entry};
+       $pa_decl= "int pa_${c_table}_${c_entry_c}(ClientData cd,".
+           " Tcl_Interp *ip, int objc, Tcl_Obj *const *objv)";
+       $pa_func= "cht_do_${c_table}_${c_entry_c}";
+       if (exists $r_entry->{D}) {
+           $pa_func= "cht_dispatch_$r_entry->{D}";
+       }
+       $do_decl= "int $pa_func(";
+       @do_al= ('ClientData cd', 'Tcl_Interp *ip');
+       @do_aa= qw(cd ip);
+       $pa_init= '';
+       $pa_argc= "  objc--; objv++;\n";
+       $pa_vars= "  int rc;\n";
+       $pa_body= '';
+       $pa_rslt= '';
+       $pa_free= '';
+       $pa_fini= '';
+       $any_mand= 0;
+       $any_optl= 0;
+       $any_eerr= 0;
+       $any_eargc= 0;
+       $pa_hint= '';
+       $pa_hint .= "$c_table " if length $c_table &&
+           !length $table_x{$c_table}{T};
+       $pa_hint.= $c_entry;
+       foreach $arg (@{ $r_entry->{A} }) {
+           $n= $arg->{N};
+           $t= $arg->{T};
+           $a= $arg->{A};
+           push @do_al, make_decl($n, $t, $arg->{A},
+                                  "table $c_table entry $c_entry arg $n");
+           $pa_vars .= make_decl_init("a_$n", $t, $a, \$pa_init, "pa_vars");
+           if ($arg->{O}) {
+               $pa_hint .= " ?$n?";
+               if ($any_mand) {
+                   $any_mand= 0;
+                   $any_eerr= 1;
+               }
+               $pa_body .= "  if (!objc--) goto end_optional;\n";
+               $any_optl= 1;
+           } else {
+               $pa_hint .= " $n";
+               $pa_body .= "  if (!objc--) goto wrong_count_args;\n";
+               $any_mand++;
+               $any_eargc= 1;
+               die if $any_optl;
+           }
+           $paarg= "&a_$n";
+           $pafin= '';
+           if ($t eq 'enum') {
+               $pa_vars .= "  const void *v_$n= 0;\n";
+               $paarg= "&v_$n";
+               $pafin= "\n  a_$n= v_$n; ";
+               ($a_tab, $ee_type, $estr) = enumargs($a);
+               $a = "cht_$a_tab, sizeof($ee_type), $estr";
+               o('h', 210, "extern const $ee_type cht_$a_tab".'[]'.";\n");
+           }
+           if (exists $type_fini{$t}) {
+               $pa_fini .= '  '.subst_in("a_$n", $type_fini{$t})."\n";
+           }
+           $pa_body .= "  rc= cht_pat_$t(ip, *objv++, $paarg";
+           $pa_body .= ", ".$a if length $a;
+           $pa_body .= ");$pafin if (rc) goto rc_err;\n";
+           push @do_aa, "a_$n";
+       }
+       if (exists $r_entry->{V}) {
+           $pa_hint .= " ...";
+           $va= $r_entry->{V};
+           push @do_al, subst_in_decl("${va}c", 'int @');
+           push @do_al, subst_in_decl("${va}v", 'Tcl_Obj *const *@');
+           push @do_aa, "objc+1", "objv-1";
+       } else {
+           if (!$any_optl) {
+               $pa_body .= "  if (objc) goto wrong_count_args;\n";
+               $any_eargc= 1;
+           }
+       }
+       if ($any_optl) {
+           $pa_body .= "end_optional:\n";
+       }
+       if (exists $r_entry->{R}) {
+           $t= $r_entry->{R};
+           $xta= $r_entry->{X};
+           push @do_al, make_decl("*result", $t, "cht_do_al result");
+           $pa_vars .= make_decl_init("result", $t, $xta, \$pa_init,
+                                      "pa_vars result");
+           push @do_aa, "&result";
+           $pa_rslt .= "  Tcl_SetObjResult(ip, cht_ret_$t(ip, result";
+           $pa_rslt .= ", $xta" if length $xta;
+           $pa_rslt .= "));\n";
+       }
+       $pa_body .= "\n";
+       $pa_body .= "  rc= $pa_func(";
+       $pa_body .= join ', ', @do_aa;
+       $pa_body .= ");\n";
+       $pa_body .= "  if (rc) goto rc_err;\n";
+
+       $pa_rslt .= "  rc= TCL_OK;\n\n";
+       $pa_rslt .= "rc_err:\n";
+       
+       $pa_fini .= "  return rc;\n";
+       if ($any_eargc) {
+           $pa_fini .= "\nwrong_count_args:\n";
+           $pa_fini .= "  e=\"wrong # args: should be \\\"$pa_hint\\\"\";\n";
+           $pa_fini .= "  goto e_err;";
+           $any_eerr= 1;
+       }
+       if ($any_eerr) {
+           $pa_vars .= "  const char *e;\n";
+           $pa_fini .= "\n";
+           $pa_fini .= "e_err:\n";
+           $pa_fini .= "  cht_setstringresult(ip,e);\n";
+           $pa_fini .= "  rc= TCL_ERROR; goto rc_err;\n";
+       }
+       $pa_vars .= "\n";
+       $pa_init .= "\n" if length $pa_init;
+       $pa_fini .= "}\n\n";
+
+       if (length $c_table) {
+           $static= 'static ';
+       } else {
+           $static= '';
+           o('h',90, "$pa_decl;\n");
+       }
+       o('c',100,
+         $static.$pa_decl." {\n".
+         $pa_vars.
+         $pa_init.
+         $pa_argc.
+         $pa_body.
+         $pa_rslt.
+          $pa_free.
+         $pa_fini);
+       $do_decl .= join ', ', @do_al;
+       $do_decl .= ")";
+
+       if (exists $r_entry->{D}) {
+           my $subcmdtype= $r_entry->{D};
+           if (!exists $dispatch_done{$subcmdtype}) {
+               my $di_body='';
+               $di_body .= "static $do_decl {\n";
+               $di_body .= "  return subcmd->func(0,ip,objc,objv);\n";
+               $di_body .= "}\n";
+               o('c',50, $di_body) or die $!;
+           }
+       } else {
+           o('h',100, $do_decl.";\n") or die $!;
+       }
+
+       $op_tab .= sprintf("  { %-20s %-40s%s },\n",
+                          "\"$c_entry\",",
+                          "pa_${c_table}_${c_entry_c}",
+                          $r_entry->{I});
+    }
+    if (length $c_table) {
+       $decl= "const $x_table->{C} cht_${c_table}_entries[]";
+       o('h', 500, "extern $decl;\n");
+       o('c', 100,
+         "$decl = {\n".
+         $op_tab.
+         "  { 0 }\n".
+         "};\n\n");
+    }
+}
+
+o(c, 0, "#include \"$prefix.h\"\n");
+
+o(h, 0,
+  "#ifndef INCLUDED_\U${prefix}_H\n".
+  "#define INCLUDED_\U${prefix}_H\n\n");
+
+o(h, 999,
+  "#endif /*INCLUDED_\U${prefix}_H*/\n");
+
+if (defined $output) {
+    $oh= new IO::File "$output.tmp", 'w' or die "$output.tmp: $!\n";
+} else {
+    $oh= 'STDOUT';
+}
+
+print $oh "/* AUTOGENERATED - DO NOT EDIT */\n" or die $!;
+foreach $pr (sort keys %{ $o{$write} }) {
+    print $oh "\n" or die $!;
+    print $oh $o{$write}{$pr} or die $!;
+}
+
+die if $oh->error;
+die $! unless $oh->close;
+
+if (defined $output) {
+    rename "$output.tmp", $output or die $!;
+}
+
+sub o ($$) {
+    my ($wh,$pr,$s) = @_;
+    $o{$wh}{sprintf "%010d", $pr} .= $s;
+}
+
+sub split_type_args ($) {
+    my ($type) = @_;
+    my ($xtypeargs);
+    if ($type =~ m/^\w+$/) {
+       $xtypeargs='';
+    } elsif ($type =~ m/^(\w+)\((.+)\)$/) {
+       $type= $1;
+       $xtypeargs= $2;
+    } else {
+       badsyntax($wh,$.,"bad type name/args \`$type'\n");
+    }
+    return ($type,$xtypeargs);
+}
+
+sub make_decl_init ($$$$$) {
+    my ($n, $t, $a, $initcode, $why) = @_;
+    my ($o,$init);
+    $o= make_decl($n,$t,$a,"$why _init");
+    if (exists $type_init{$t}) {
+       $init= $type_init{$t};
+       $$initcode .= "  ".subst_in("$n", $init)."\n"
+           if length $init;
+    } else {
+       $o .= ' =0';
+    }
+    return "  ".$o.";\n";
+}
+
+sub make_decl ($$$$) {
+    my ($n, $t, $ta, $why) = @_;
+    my ($type);
+    if ($t eq 'enum') {
+       ($a_tab, $ee_type, $estr) = enumargs($ta);
+       $c= "const $ee_type* @";
+    } else { 
+       defined $types{$t} or die "unknown type $t ($why)\n";
+       $c= $types{$t}{C};
+    }
+    return subst_in_decl($n,$c);
+}
+
+sub subst_in_decl ($$$) {
+    my ($val, $pat, $why) = @_;
+    local ($_) = subst_in($val, $pat, $why);
+    s/ *(\**) *$/$1/;
+    return $_;
+}
+    
+sub subst_in ($$$) {
+    my ($val, $pat, $why) = @_;
+    $pat =~ m/\@/ or die "$pat for $val in $why ?";
+    $pat =~ s/\@/$val/g;
+    return $pat;
+}
+
+sub badsyntax ($$$) {
+    die "$_[0]:$_[1]: $_[2]\n";
+}
+
+__DATA__
+Type int:      int
+Type obj:      Tcl_Obj *@
diff --git a/base/tcmdiflib.c b/base/tcmdiflib.c
new file mode 100644 (file)
index 0000000..e14eba0
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * base code for various Tcl extensions
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark-tcl-base.h"
+
+int cht_pat_enum(Tcl_Interp *ip, Tcl_Obj *obj, const void **val,
+            const void *opts, size_t sz, const char *what) {
+  *val= cht_enum_lookup_cached_func(ip,obj,opts,sz,what);
+  if (!*val) return TCL_ERROR;
+  return TCL_OK;
+}
+  
+int cht_pat_obj(Tcl_Interp *ip, Tcl_Obj *obj, Tcl_Obj **val) {
+  *val= obj;
+  return TCL_OK;
+}
+
+Tcl_Obj *cht_ret_int(Tcl_Interp *ip, int val) {
+  return Tcl_NewIntObj(val);
+}
+
+Tcl_Obj *cht_ret_obj(Tcl_Interp *ip, Tcl_Obj *val) {
+  return val;
+}
+
+void cht_setstringresult(Tcl_Interp *ip, const char *m) {
+  Tcl_ResetResult(ip);
+  Tcl_AppendResult(ip, m, (char*)0);
+}
diff --git a/cdb/Makefile b/cdb/Makefile
new file mode 100644 (file)
index 0000000..9f4cfa4
--- /dev/null
@@ -0,0 +1,35 @@
+# cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+BASE_DIR =     ../base
+EXTBASE =      cdb
+CFILES =       readonly writeable lookup
+OTHER_TCTS =   ../hbytes/hbytes-base.tct
+OTHER_EXTS +=  hbytes/hbytes
+LDLIBS +=      -lcdb
+
+include ../base/extension.make
+
+# eg, for testing:
+# liberator:cdb> LD_LIBRARY_PATH=../base:../hbytes:. tclsh8.3
+# % load chiark_tcl_cdb.so
+# % cdb
+# wrong # args: should be "cdb subcmd ..."
+# % cdb open
+# wrong # args: should be "cdb open path"
+# % 
+# liberator:cdb> 
diff --git a/cdb/cdb.tct b/cdb/cdb.tct
new file mode 100644 (file)
index 0000000..889e72a
--- /dev/null
@@ -0,0 +1,135 @@
+# cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Table *cdbtoplevel TopLevel_Command
+       cdb
+               dispatch(Cdb/_SubCommand, "cdb subcommand")
+       cdb-wr
+               dispatch(Cdbwr/_SubCommand, "cdb-wr subcommand")
+
+Table cdb Cdb_SubCommand
+       open
+               path    string
+               =>      iddata(&cdbtcl_databases)
+       lookup
+               db      iddata(&cdbtcl_databases)
+               key     obj
+               ?def    obj
+               =>      obj
+       lookup-hb
+               db      iddata(&cdbtcl_databases)
+               key     hb
+               ?def    obj
+               =>      obj
+       close
+               db      iddata(&cdbtcl_databases)
+
+Table cdbwr Cdbwr_SubCommand
+       create-empty 0
+               pathb   string
+               # files:
+               #       <pathb>.main
+               #       <pathb>.lock
+               #       <pathb>.cdb
+               #       <pathb>.jrn
+               #       <pathb>.tmp (might be new .main or new .cdb)
+               # invariants:
+               #       .lock is an empty file
+               #         which is locked with fcntl by open
+               #       .main is a cdb native text file
+               #         and always exists
+               #       .cdb is a cdb database containing data
+               #         equivalent to and at least as recent as .main
+               #         (maybe not identical, because .cdb may
+               #         have been updated with data from .jrn but
+               #         .main not yet); if .jrn does not exist then
+               #         they are identical)
+               #       .cdb may not exist; in which case it is to
+               #         be treated as if it existed and was empty
+               #         but this is maximally early (so main must
+               #         exist and be empty since .main is never
+               #         newer than .cdb)
+               #       if .jrn exists, it is a cdb native
+               #         text file _without the trailing newline_;
+               #         its contents override values from .main or .cdb
+               #       if .main.tmp or .cdb.tmp exists it is irrelevant
+               # zero length values mean record is deleted (in .jrn only;
+               #  forbidden elsewhere)
+               # while db is open:
+               #       .lock is locked
+               #       .jrn and open hash table contain same info
+       open 0
+               pathb   string
+               on_info obj
+               ?on_lexminval obj
+               # on_lexminval present and not empty list: provides a
+               #  script which returns the current lexminval.  In
+               #  this case, occasionally,
+               #  on_lexminval will be called and then entries whose
+               #  value is lexically strictly less than lexminval
+               #  will be deleted automatically.  The comparison
+               #  is bytewise on the UTF-8 representations.
+               =>      iddata(&cdbtcl_rwdatabases)
+       open-okjunk RWSCF_OKJUNK
+               pathb   string
+               on_info obj
+               ?on_lexminval obj
+               =>      iddata(&cdbtcl_rwdatabases)
+               # on_info <event> <xinfo>...:
+               # on_info open-clean <statistics-info-string>
+               # on_info open-dirty-start <statistics-info-string>
+               # on_info open-dirty-junk <problem-info-string> \
+               #               <problem-error-code-list>
+               # on_info open-dirty-done <statistics-info-string>
+               # on_info compact-start <statistics-info-string>
+               # on_info compact-done <statistics-info-string>
+               # on_info close <statistics-info-string>
+       lookup 0
+               db      iddata(&cdbtcl_rwdatabases)
+               key     string
+               ?def    obj
+               =>      obj
+       lookup-hb 0
+               db      iddata(&cdbtcl_rwdatabases)
+               key     string
+               ?def    obj
+               =>      obj
+       delete 0
+               db      iddata(&cdbtcl_rwdatabases)
+               key     string
+       update 0
+               db      iddata(&cdbtcl_rwdatabases)
+               key     string
+               value   obj
+       update-hb 0
+               db      iddata(&cdbtcl_rwdatabases)
+               key     string
+               value   hb
+       compact-force 0
+               db      iddata(&cdbtcl_rwdatabases)
+       compact-check 0
+               db      iddata(&cdbtcl_rwdatabases)
+       compact-auto 0
+               # this is the default
+               db      iddata(&cdbtcl_rwdatabases)
+       compact-explicit 0
+               db      iddata(&cdbtcl_rwdatabases)
+       close 0
+               db      iddata(&cdbtcl_rwdatabases)
+
+EntryExtra Cdbwr_SubCommand
+       unsigned flags;
diff --git a/cdb/chiark_tcl_cdb.h b/cdb/chiark_tcl_cdb.h
new file mode 100644 (file)
index 0000000..fef668c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CHIARK_TCL_CDB_H
+#define CHIARK_TCL_CDB_H
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include <cdb.h>
+
+#include "hbytes.h"
+#include "cdb+tcmdif.h"
+
+#define RWSCF_OKJUNK 002
+
+extern const IdDataSpec cdbtcl_databases, cdbtcl_rwdatabases;
+
+/*---------- from lookup.c ----------*/
+
+int cht_cdb_donesomelookup(Tcl_Interp *ip, void *db_v,
+                          Tcl_Obj *def, Tcl_Obj **result,
+                          const Byte *data, int dlen,
+                          int (*storeanswer)(Tcl_Interp *ip, Tcl_Obj **result,
+                                             const Byte *data, int len));
+int cht_cdb_storeanswer_string(Tcl_Interp *ip, Tcl_Obj **result,
+                              const Byte *data, int len);
+int cht_cdb_storeanswer_hb(Tcl_Interp *ip, Tcl_Obj **result,
+                          const Byte *data, int len);
+int cht_cdb_lookup_cdb(Tcl_Interp *ip, struct cdb *cdb,
+                      const Byte *key, int klen,
+                      const Byte **data_r, int *dlen_r);
+
+/*---------- macros ----------*/
+
+#define PE(m) do{                                              \
+    rc= cht_posixerr(ip, errno, "failed to " m); goto x_rc;    \
+  }while(0)
+
+#endif /*CHIARK_TCL_CDB_H*/
diff --git a/cdb/lookup.c b/cdb/lookup.c
new file mode 100644 (file)
index 0000000..fb73f1f
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_cdb.h"
+
+int cht_cdb_donesomelookup(Tcl_Interp *ip, void *db_v,
+                          Tcl_Obj *def, Tcl_Obj **result,
+                          const Byte *data, int dlen,
+                          int (*storeanswer)(Tcl_Interp *ip, Tcl_Obj **result,
+                                             const Byte *data, int len)) {
+  if (dlen>0) return storeanswer(ip, result, data, dlen);
+  if (def) { *result= def; return TCL_OK; }
+  return cht_staticerr(ip, "cdbwr lookup key not found", "CDB NOTFOUND");
+}
+
+int cht_cdb_storeanswer_string(Tcl_Interp *ip, Tcl_Obj **result,
+                              const Byte *data, int len) {
+  *result= Tcl_NewStringObj(data, len);
+  if (!*result) return cht_staticerr(ip, "Tcl_NewStringObj failed for"
+       " lookup (utf-8 encoding problem?)", "CDB BADSTRING");
+  return TCL_OK;
+}
+  
+int cht_cdb_storeanswer_hb(Tcl_Interp *ip, Tcl_Obj **result,
+                          const Byte *data, int len) {
+  HBytes_Value val;
+  cht_hb_array(&val, data, len);
+  *result= cht_ret_hb(ip, val);
+  return TCL_OK;
+}
+
+int cht_cdb_lookup_cdb(Tcl_Interp *ip, struct cdb *cdb,
+                      const Byte *key, int klen,
+                      const Byte **data_r, int *len_r) {
+  int r;
+  
+  r= cdb_find(cdb, key, klen);
+  if (!r) { *data_r= 0; *len_r= -1; return TCL_OK; }
+  if (r<0) return cht_posixerr(ip, errno, "cdb_find failed");
+  assert(r==1);
+  *len_r= cdb_datalen(cdb);
+  assert(*len_r > 0);
+  *data_r= cdb_getdata(cdb);
+  if (!*data_r) return cht_posixerr(ip, errno, "cdb_getdata failed");
+  return TCL_OK;
+}
+
+CHT_INIT(cdb,
+        CHTI_OTHER(hbytes),
+        CHTI_COMMANDS(cht_cdbtoplevel_entries))
diff --git a/cdb/readonly.c b/cdb/readonly.c
new file mode 100644 (file)
index 0000000..53823b8
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_cdb.h"
+
+typedef struct Ro {
+  int ix, fd;
+  struct cdb cdb;
+} Ro;
+
+static void ro_close(Ro *ro) {
+  cdb_free(&ro->cdb);
+  close(ro->fd);
+}
+
+static void destroy_cdb_idtabcb(Tcl_Interp *ip, void *ro_v) {
+  ro_close(ro_v);
+  TFREE(ro_v);
+}
+
+const IdDataSpec cdbtcl_databases= {
+  "cdb-db", "cdb-opendatabases-table", destroy_cdb_idtabcb
+};
+
+int cht_do_cdb_open(ClientData cd, Tcl_Interp *ip,
+                   const char *path, void **result) {
+  Ro *ro;
+  int rc, r;
+
+  ro= TALLOC(sizeof(*ro));
+  ro->ix= -1;
+  ro->fd= open(path, O_RDONLY);
+  if (ro->fd<0) PE("open database file");
+  r= cdb_init(&ro->cdb, ro->fd);
+  if (r) PE("initialise cdb");
+  *result= ro;
+  return TCL_OK;
+
+ x_rc:
+  if (ro->fd >= 0) close(ro->fd);
+  return rc;
+}
+
+int cht_do_cdb_close(ClientData cd, Tcl_Interp *ip, void *ro_v) {
+  ro_close(ro_v);
+  cht_tabledataid_disposing(ip, ro_v, &cdbtcl_databases);
+  TFREE(ro_v);
+  return TCL_OK;
+}
+
+int cht_do_cdb_lookup(ClientData cd, Tcl_Interp *ip, void *ro_v,
+                     Tcl_Obj *keyo, Tcl_Obj *def, Tcl_Obj **result) {
+  Ro *ro= ro_v;
+  const Byte *key;
+  const Byte *data;
+  int r, dlen, klen;
+
+  key= Tcl_GetStringFromObj(keyo, &klen);  assert(key);
+  
+  r= cht_cdb_lookup_cdb(ip, &ro->cdb, key, klen, &data, &dlen);
+  if (r) return r;
+  
+  return cht_cdb_donesomelookup(ip, ro_v, def, result, data, dlen,
+                               cht_cdb_storeanswer_string);
+}
+
+int cht_do_cdb_lookup_hb(ClientData cd, Tcl_Interp *ip, void *ro_v,
+                        HBytes_Value key, Tcl_Obj *def, Tcl_Obj **result) {
+  Ro *ro= ro_v;
+  const Byte *data;
+  int r, dlen;
+  
+  r= cht_cdb_lookup_cdb(ip, &ro->cdb,
+                       cht_hb_data(&key), cht_hb_len(&key),
+                       &data, &dlen);
+  if (r) return r;
+  
+  return cht_cdb_donesomelookup(ip, ro_v, def, result, data, dlen,
+                               cht_cdb_storeanswer_hb);
+}
diff --git a/cdb/writeable.c b/cdb/writeable.c
new file mode 100644 (file)
index 0000000..6b072ff
--- /dev/null
@@ -0,0 +1,985 @@
+/*
+ * cdb, cdb-wr - Tcl bindings for tinycdb and a journalling write extension
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_cdb.h"
+
+#define KEYLEN_MAX (INT_MAX/2)
+
+#define ftello ftell
+#define fseeko fseek
+
+/*---------- Forward declarations ----------*/
+
+struct ht_forall_ctx;
+
+/*---------- Useful routines ----------*/
+
+static void maybe_close(int fd) {
+  if (fd>=0) close(fd);
+}
+
+/*==================== Subsystems and subtypes ====================*/
+
+/*---------- Pathbuf ----------*/
+
+typedef struct Pathbuf {
+  char *buf, *sfx;
+} Pathbuf;
+
+#define MAX_SUFFIX 5
+
+static void pathbuf_init(Pathbuf *pb, const char *pathb) {
+  size_t l= strlen(pathb);
+  assert(l < INT_MAX);
+  pb->buf= TALLOC(l + MAX_SUFFIX + 1);
+  memcpy(pb->buf, pathb, l);
+  pb->sfx= pb->buf + l;
+}
+static const char *pathbuf_sfx(Pathbuf *pb, const char *suffix) {
+  assert(strlen(suffix) <= MAX_SUFFIX);
+  strcpy(pb->sfx, suffix);
+  return pb->buf;
+}
+static void pathbuf_free(Pathbuf *pb) {
+  TFREE(pb->buf);
+  pb->buf= 0;
+}
+
+/*---------- Our hash table ----------*/
+
+typedef struct HashTable {
+  Tcl_HashTable t;
+  Byte padding[128]; /* allow for expansion by Tcl, urgh */
+  Byte confound[16];
+} HashTable;
+
+typedef struct HashValue {
+  int len;
+  Byte data[1];
+} HashValue;
+
+static HashValue *htv_prep(int len) {
+  HashValue *hd;
+  hd= TALLOC((hd->data - (Byte*)hd) + len);
+  hd->len= len;
+  return hd;
+}  
+static Byte *htv_fillptr(HashValue *hd) {
+  return hd->data;
+}
+
+static void ht_setup(HashTable *ht) {
+  Tcl_InitHashTable(&ht->t, TCL_STRING_KEYS);
+}
+static void ht_update(HashTable *ht, const char *key, HashValue *val_eat) {
+  Tcl_HashEntry *he;
+  int new;
+
+  he= Tcl_CreateHashEntry(&ht->t, (char*)key, &new);
+  if (!new) TFREE(Tcl_GetHashValue(he));
+  Tcl_SetHashValue(he, val_eat);
+    /* eats the value since the data structure owns the memory */
+}
+static void ht_maybeupdate(HashTable *ht, const char *key,
+                          HashValue *val_eat) {
+  /* like ht_update except does not overwrite existing values */
+  Tcl_HashEntry *he;
+  int new;
+
+  he= Tcl_CreateHashEntry(&ht->t, (char*)key, &new);
+  if (!new) { TFREE(val_eat); return; }
+  Tcl_SetHashValue(he, val_eat);
+}
+
+static const HashValue *ht_lookup(HashTable *ht, const char *key) {
+  Tcl_HashEntry *he;
+  
+  he= Tcl_FindHashEntry(&ht->t, key);
+  if (!he) return 0;
+  
+  return Tcl_GetHashValue(he);
+}
+
+static int ht_forall(HashTable *ht,
+                    int (*fn)(const char *key, HashValue *val,
+                              struct ht_forall_ctx *ctx),
+                    struct ht_forall_ctx *ctx) {
+  /* Returns first positive value returned by any call to fn, or 0. */
+  Tcl_HashSearch sp;
+  Tcl_HashEntry *he;
+  const char *key;
+  HashValue *val;
+  int r;
+  
+  for (he= Tcl_FirstHashEntry(&ht->t, &sp);
+       he;
+       he= Tcl_NextHashEntry(&sp)) {
+    val= Tcl_GetHashValue(he);
+    if (!val->len) continue;
+
+    key= Tcl_GetHashKey(&ht->t, he);
+    
+    r= fn(key, val, ctx);
+    if (r) return r;
+  }
+  return 0;
+}
+
+static void ht_destroy(HashTable *ht) {
+  Tcl_HashSearch sp;
+  Tcl_HashEntry *he;
+  
+  for (he= Tcl_FirstHashEntry(&ht->t, &sp);
+       he;
+       he= Tcl_NextHashEntry(&sp)) {
+    /* ht_forall skips empty (deleted) entries so is no good for this */
+    TFREE(Tcl_GetHashValue(he));
+  }
+  Tcl_DeleteHashTable(&ht->t);
+}
+
+/*==================== Existential ====================*/
+
+/*---------- Rw data structure ----------*/
+
+typedef struct Rw {
+  int ix, autocompact;
+  int cdb_fd, lock_fd;
+  struct cdb cdb; /* valid iff cdb_fd >= 0 */
+  FILE *logfile; /* may be 0; if so, is broken */
+  HashTable logincore;
+  Pathbuf pbsome, pbother;
+  off_t mainsz;
+  ScriptToInvoke on_info, on_lexminval;
+} Rw;
+
+static void rw_cdb_close(Tcl_Interp *ip, Rw *rw) {
+  if (rw->cdb_fd >= 0) cdb_free(&rw->cdb);
+  maybe_close(rw->cdb_fd);
+}
+
+static int rw_close(Tcl_Interp *ip, Rw *rw) {
+  int rc, r;
+
+  rc= TCL_OK;
+  ht_destroy(&rw->logincore);
+  rw_cdb_close(ip,rw);
+  maybe_close(rw->lock_fd);
+
+  if (rw->logfile) {
+    r= fclose(rw->logfile);
+    if (r && ip) { rc= cht_posixerr(ip, errno, "probable data loss! failed to"
+                                   " fclose logfile during untidy close"); }
+  }
+
+  pathbuf_free(&rw->pbsome); pathbuf_free(&rw->pbother);
+  return rc;
+}
+
+static void destroy_cdbrw_idtabcb(Tcl_Interp *ip, void *rw_v) {
+  rw_close(0,rw_v);
+  TFREE(rw_v);
+}
+const IdDataSpec cdbtcl_rwdatabases= {
+  "cdb-rwdb", "cdb-openrwdatabases-table", destroy_cdbrw_idtabcb
+};
+
+/*---------- File handling ----------*/
+
+static int acquire_lock(Tcl_Interp *ip, Pathbuf *pb, int *lockfd_r) {
+  /* *lockfd_r must be -1 on entry.  If may be set to >=0 even
+   * on error, and must be closed by the caller. */
+  mode_t um, lockmode;
+  struct flock fl;
+  int r;
+
+  um= umask(~(mode_t)0);
+  umask(um);
+
+  lockmode= 0666 & ~((um & 0444)>>1);
+  /* Remove r where umask would remove w;
+   * eg umask intending 0664 here gives 0660 */
+  
+  *lockfd_r= open(pathbuf_sfx(pb,".lock"), O_RDWR|O_CREAT, lockmode);
+  if (*lockfd_r < 0)
+    return cht_posixerr(ip, errno, "could not open/create lockfile");
+
+  fl.l_type= F_WRLCK;
+  fl.l_whence= SEEK_SET;
+  fl.l_start= 0;
+  fl.l_len= 0;
+  fl.l_pid= getpid();
+
+  r= fcntl(*lockfd_r, F_SETLK, &fl);
+  if (r == -1) {
+    if (errno == EACCES || errno == EAGAIN)
+      return cht_staticerr(ip, "lock held by another process", "CDB LOCKED");
+    else return cht_posixerr(ip, errno, "unexpected error from fcntl while"
+                            " acquiring lock");
+  }
+
+  return TCL_OK;
+}
+
+/*---------- Log reading and writing ----------*/
+
+static int readlognum(FILE *f, int delim, int *num_r) {
+  int c;
+  char numbuf[20], *p, *ep;
+  unsigned long ul;
+
+  p= numbuf;
+  for (;;) {
+    c= getc(f);  if (c==EOF) return -2;
+    if (c == delim) break;
+    if (!isdigit((unsigned char)c)) return -2;
+    *p++= c;
+    if (p == numbuf+sizeof(numbuf)) return -2;
+  }
+  if (p == numbuf) return -2;
+  *p= 0;
+
+  errno=0; ul= strtoul(numbuf, &ep, 10);
+  if (*ep || errno || ul >= KEYLEN_MAX) return -2;
+  *num_r= ul;
+  return 0;
+}
+
+static int readstorelogrecord(FILE *f, HashTable *ht,
+                             int (*omitfn)(const HashValue*,
+                                           struct ht_forall_ctx *ctx),
+                             struct ht_forall_ctx *ctx,
+                             void (*updatefn)(HashTable*, const char*,
+                                              HashValue*)) {
+  /* returns:
+   *      0     for OK
+   *     -1     eof
+   *     -2     corrupt or error
+   *     -3     got newline indicating end
+   *     >0     value from omitfn
+   */
+  int keylen, vallen;
+  char *key;
+  HashValue *val;
+  int c, rc, r;
+
+  c= getc(f);
+  if (c==EOF) { return feof(f) ? -1 : -2; }
+  if (c=='\n') return -3;
+  if (c!='+') return -2;
+
+  rc= readlognum(f, ',', &keylen);  if (rc) return rc;
+  rc= readlognum(f, ':', &vallen);  if (rc) return rc;
+
+  key= TALLOC(keylen+1);
+  val= htv_prep(vallen);
+
+  r= fread(key, 1,keylen, f);
+  if (r!=keylen) goto x2_free_keyval;
+  if (memchr(key,0,keylen)) goto x2_free_keyval;
+  key[keylen]= 0;
+
+  c= getc(f);  if (c!='-') goto x2_free_keyval;
+  c= getc(f);  if (c!='>') goto x2_free_keyval;
+  
+  r= fread(htv_fillptr(val), 1,vallen, f);
+  if (r!=vallen) goto x2_free_keyval;
+
+  c= getc(f);  if (c!='\n') goto x2_free_keyval;
+
+  rc= omitfn ? omitfn(val, ctx) : TCL_OK;
+  if (rc) { assert(rc>0); TFREE(val); }
+  else updatefn(ht, key, val);
+  
+  TFREE(key);
+  return rc;
+
+ x2_free_keyval:
+  TFREE(val);
+  TFREE(key);
+  return -2;
+}
+
+static int writerecord(FILE *f, const char *key, const HashValue *val) {
+  int r;
+
+  r= fprintf(f, "+%d,%d:%s->", (int)strlen(key), val->len, key);
+  if (r<0) return -1;
+  
+  r= fwrite(val->data, 1, val->len, f);
+  if (r != val->len) return -1;
+
+  r= putc('\n', f);
+  if (r==EOF) return -1;
+
+  return 0;
+}
+
+/*---------- Creating ----------*/
+
+int cht_do_cdbwr_create_empty(ClientData cd, Tcl_Interp *ip,
+                             const char *pathb) {
+  static const char *const toremoves[]= { ".cdb", ".jrn", ".tmp", 0 };
+
+  Pathbuf pb, pbmain;
+  int lock_fd=-1, rc, r;
+  FILE *f= 0;
+  const char *const *toremove;
+  struct stat stab;
+
+  pathbuf_init(&pb, pathb);
+  pathbuf_init(&pbmain, pathb);
+
+  rc= acquire_lock(ip, &pb, &lock_fd);  if (rc) goto x_rc;
+
+  r= lstat(pathbuf_sfx(&pbmain, ".main"), &stab);
+  if (!r) { rc= cht_staticerr(ip, "database already exists during creation",
+                             "CDB ALREADY-EXISTS");  goto x_rc; }
+  if (errno != ENOENT) PE("check for existing database .main during creation");
+
+  for (toremove=toremoves; *toremove; toremove++) {
+    r= remove(pathbuf_sfx(&pb, *toremove));
+    if (r && errno != ENOENT)
+      PE("delete possible spurious file during creation");
+  }
+  
+  f= fopen(pathbuf_sfx(&pb, ".tmp"), "w");
+  if (!f) PE("create new database .tmp");  
+  r= putc('\n', f);  if (r==EOF) PE("write sentinel to new database .tmp");
+  r= fclose(f);  f=0;  if (r) PE("close new database .tmp during creation");
+
+  r= rename(pb.buf, pbmain.buf);
+  if (r) PE("install new database .tmp as .main (finalising creation)");
+  
+  rc= TCL_OK;
+
+ x_rc:
+  if (f) fclose(f);
+  maybe_close(lock_fd);
+  pathbuf_free(&pb);
+  pathbuf_free(&pbmain);
+  return rc;
+}
+
+/*---------- Info callbacks ----------*/
+
+static int infocbv3(Tcl_Interp *ip, Rw *rw, const char *arg1,
+                   const char *arg2fmt, const char *arg3, va_list al) {
+  Tcl_Obj *aa[3];
+  int na;
+  char buf[200];
+  vsnprintf(buf, sizeof(buf), arg2fmt, al);
+
+  na= 0;
+  aa[na++]= cht_ret_string(ip, arg1);
+  aa[na++]= cht_ret_string(ip, buf);
+  if (arg3) aa[na++]= cht_ret_string(ip, arg3);
+  
+  return cht_scriptinv_invoke_fg(&rw->on_info, na, aa);
+}
+  
+static int infocb3(Tcl_Interp *ip, Rw *rw, const char *arg1,
+                  const char *arg2fmt, const char *arg3, ...) {
+  int rc;
+  va_list al;
+  va_start(al, arg3);
+  rc= infocbv3(ip,rw,arg1,arg2fmt,arg3,al);
+  va_end(al);
+  return rc;
+}
+  
+static int infocb(Tcl_Interp *ip, Rw *rw, const char *arg1,
+                 const char *arg2fmt, ...) {
+  int rc;
+  va_list al;
+  va_start(al, arg2fmt);
+  rc= infocbv3(ip,rw,arg1,arg2fmt,0,al);
+  va_end(al);
+  return rc;
+}
+  
+/*---------- Opening ----------*/
+
+static int cdbinit(Tcl_Interp *ip, Rw *rw) {
+  /* On entry, cdb_fd >=0 but cdb is _undefined_
+   * On exit, either cdb_fd<0 or cdb is initialised */
+  int r, rc;
+  
+  r= cdb_init(&rw->cdb, rw->cdb_fd);
+  if (r) {
+    rc= cht_posixerr(ip, errno, "failed to initialise cdb reader");
+    close(rw->cdb_fd);  rw->cdb_fd= -1;  return rc;
+  }
+  return TCL_OK;
+}
+
+int cht_do_cdbwr_open(ClientData cd, Tcl_Interp *ip, const char *pathb,
+                     Tcl_Obj *on_info, Tcl_Obj *on_lexminval,
+                     void **result) {
+  const Cdbwr_SubCommand *subcmd= cd;
+  int r, rc, mainfd=-1;
+  Rw *rw;
+  struct stat stab;
+  off_t logrecstart, logjunkpos;
+
+  rw= TALLOC(sizeof(*rw));
+  rw->ix= -1;
+  ht_setup(&rw->logincore);
+  cht_scriptinv_init(&rw->on_info);
+  cht_scriptinv_init(&rw->on_lexminval);
+  rw->cdb_fd= rw->lock_fd= -1;  rw->logfile= 0;
+  pathbuf_init(&rw->pbsome, pathb);
+  pathbuf_init(&rw->pbother, pathb);
+  rw->autocompact= 1;
+
+  rc= cht_scriptinv_set(&rw->on_info, ip, on_info, 0);
+  if (rc) goto x_rc;
+
+  rc= cht_scriptinv_set(&rw->on_lexminval, ip, on_lexminval, 0);
+  if (rc) goto x_rc;
+
+  mainfd= open(pathbuf_sfx(&rw->pbsome,".main"), O_RDONLY);
+  if (mainfd<0) PE("open existing database file .main");
+  rc= acquire_lock(ip, &rw->pbsome, &rw->lock_fd);  if (rc) goto x_rc;
+
+  r= fstat(mainfd, &stab);  if (r) PE("fstat .main");
+  rw->mainsz= stab.st_size;
+
+  rw->cdb_fd= open(pathbuf_sfx(&rw->pbsome,".cdb"), O_RDONLY);
+  if (rw->cdb_fd >=0) {
+    rc= cdbinit(ip, rw);  if (rc) goto x_rc;
+  } else if (errno == ENOENT) {
+    if (rw->mainsz > 1) {
+      rc= cht_staticerr(ip, ".cdb does not exist but .main is >1byte -"
+                       " .cdb must have been accidentally deleted!",
+                       "CDB CDBMISSING");
+      goto x_rc;
+    }
+    /* fine */
+  } else {
+    PE("open .cdb");
+  }
+
+  rw->logfile= fopen(pathbuf_sfx(&rw->pbsome,".jrn"), "r+");
+  if (!rw->logfile) {
+    if (errno != ENOENT) PE("failed to open .jrn during open");
+    rw->logfile= fopen(rw->pbsome.buf, "w");
+    if (!rw->logfile) PE("create .jrn during (clean) open");
+  } else { /* rw->logfile */
+    r= fstat(fileno(rw->logfile), &stab);
+    if (r==-1) PE("fstat .jrn during open");
+    rc= infocb(ip, rw, "open-dirty-start", "log=%luby",
+              (unsigned long)stab.st_size);
+    if (rc) goto x_rc;
+
+    for (;;) {
+      logrecstart= ftello(rw->logfile);
+      if (logrecstart < 0) PE("ftello .jrn during (dirty) open");
+      r= readstorelogrecord(rw->logfile, &rw->logincore, 0,0, ht_update);
+      if (ferror(rw->logfile)) {
+       rc= cht_posixerr(ip, errno, "error reading .jrn during (dirty) open");
+       goto x_rc;
+      }
+      if (r==-1) {
+       break;
+      } else if (r==-2 || r==-3) {
+       char buf[100];
+       logjunkpos= ftello(rw->logfile);
+       if(logjunkpos<0) PE("ftello .jrn during report of junk in dirty open");
+
+       snprintf(buf,sizeof(buf), "CDB SYNTAX LOG %lu %lu",
+                (unsigned long)logjunkpos, (unsigned long)logrecstart);
+
+       if (!(subcmd->flags & RWSCF_OKJUNK)) {
+         Tcl_SetObjErrorCode(ip, Tcl_NewStringObj(buf,-1));
+         snprintf(buf,sizeof(buf),"%lu",(unsigned long)logjunkpos);
+         Tcl_ResetResult(ip);
+         Tcl_AppendResult(ip, "syntax error (junk) in .jrn during"
+                          " (dirty) open, at file position ", buf, (char*)0);
+         rc= TCL_ERROR;
+         goto x_rc;
+       }
+       rc= infocb3(ip, rw, "open-dirty-junk", "errorfpos=%luby", buf,
+                   (unsigned long)logjunkpos);
+       if (rc) goto x_rc;
+
+       r= fseeko(rw->logfile, logrecstart, SEEK_SET);
+       if (r) PE("failed to fseeko .jrn before junk during dirty open");
+
+       r= ftruncate(fileno(rw->logfile), logrecstart);
+       if (r) PE("ftruncate .jrn to chop junk during dirty open");
+      } else {
+       assert(!r);
+      }
+    }
+  }
+  /* now log is positioned for appending and everything is read */
+
+  *result= rw;
+  maybe_close(mainfd);
+  return TCL_OK;
+
+ x_rc:
+  rw_close(0,rw);
+  TFREE(rw);
+  maybe_close(mainfd);
+  return rc;
+}
+
+int cht_do_cdbwr_open_okjunk(ClientData cd, Tcl_Interp *ip, const char *pathb,
+                     Tcl_Obj *on_info, Tcl_Obj *on_lexminval,
+                     void **result) {
+  return cht_do_cdbwr_open(cd,ip,pathb,on_info,on_lexminval,result);
+}
+
+/*==================== COMPACTION ====================*/
+
+struct ht_forall_ctx {
+  struct cdb_make cdbm;
+  FILE *mainfile;
+  long *reccount;
+  int lexminvall;
+  const char *lexminval; /* may be invalid if lexminvall <= 0 */
+};
+
+/*---------- helper functions ----------*/
+
+static int expiredp(const HashValue *val, struct ht_forall_ctx *a) {
+  int r, l;
+  if (!val->len || a->lexminvall<=0) return 0;
+  l= val->len < a->lexminvall ? val->len : a->lexminvall;
+  r= memcmp(val->data, a->lexminval, l);
+  if (r>0) return 0;
+  if (r<0) return 1;
+  return val->len < a->lexminvall;
+}
+
+static int delete_ifexpired(const char *key, HashValue *val,
+                           struct ht_forall_ctx *a) {
+  if (!expiredp(val, a)) return 0;
+  val->len= 0;
+  /* we don't actually need to realloc it to free the memory because
+   * this will shortly all be deleted as part of the compaction */
+  return 0;
+}
+
+static int addto_cdb(const char *key, HashValue *val,
+                    struct ht_forall_ctx *a) {
+  return cdb_make_add(&a->cdbm, key, strlen(key), val->data, val->len);
+}
+
+static int addto_main(const char *key, HashValue *val,
+                     struct ht_forall_ctx *a) {
+  (*a->reccount)++;
+  return writerecord(a->mainfile, key, val);
+}
+
+/*---------- compact main entrypoint ----------*/
+
+static int compact_core(Tcl_Interp *ip, Rw *rw, unsigned long logsz,
+                       long *reccount_r) {
+  /* creates new .cdb and .main
+   * closes logfile
+   * leaves .jrn with old data
+   * leaves cdb fd open onto old db
+   * leaves logincore full of crap
+   */
+  int r, rc;
+  int cdbfd, cdbmaking;
+  off_t errpos, newmainsz;
+  char buf[100];
+  Tcl_Obj *res;
+  struct ht_forall_ctx a;
+
+  a.mainfile= 0;
+  cdbfd= -1;
+  cdbmaking= 0;
+  *reccount_r= 0;
+  a.reccount= reccount_r;
+
+  r= fclose(rw->logfile);
+  rw->logfile= 0;
+  if (r) { rc= cht_posixerr(ip, errno, "probable data loss!  failed to fclose"
+                           " logfile during compact");  goto x_rc; }
+  
+  rc= infocb(ip, rw, "compact-start", "log=%luby main=%luby",
+            logsz, (unsigned long)rw->mainsz);
+  if (rc) goto x_rc;
+
+  if (cht_scriptinv_interp(&rw->on_lexminval)) {
+    rc= cht_scriptinv_invoke_fg(&rw->on_lexminval, 0,0);
+    if (rc) goto x_rc;
+
+    res= Tcl_GetObjResult(ip);  assert(res);
+    a.lexminval= Tcl_GetStringFromObj(res, &a.lexminvall);
+    assert(a.lexminval);
+
+    /* we rely not calling Tcl_Eval during the actual compaction;
+     * if we did Tcl_Eval then the interp result would be trashed.
+     */
+    rc= ht_forall(&rw->logincore, delete_ifexpired, &a);
+
+  } else {
+    a.lexminvall= 0;
+  }
+
+  /* merge unsuperseded records from main into hash table */
+
+  a.mainfile= fopen(pathbuf_sfx(&rw->pbsome,".main"), "r");
+  if (!a.mainfile) PE("failed to open .main for reading during compact");
+
+  for (;;) {
+    r= readstorelogrecord(a.mainfile, &rw->logincore,
+                         expiredp, &a,
+                         ht_maybeupdate);
+    if (ferror(a.mainfile)) { rc= cht_posixerr(ip, errno, "error reading"
+                         " .main during compact"); goto x_rc; }
+    if (r==-3) {
+      break;
+    } else if (r==-1 || r==-2) {
+      errpos= ftello(a.mainfile);
+      if (errpos<0) PE("ftello .main during report of syntax error");
+      snprintf(buf,sizeof(buf), "CDB %s MAIN %lu",
+              r==-1 ? "TRUNCATED" : "SYNTAX", (unsigned long)errpos);
+      Tcl_SetObjErrorCode(ip, Tcl_NewStringObj(buf,-1));
+      snprintf(buf,sizeof(buf), "%lu", (unsigned long)errpos);
+      Tcl_ResetResult(ip);
+      Tcl_AppendResult(ip,
+                      r==-1 ? "unexpected eof (truncated file)"
+                      " in .main during compact, at file position "
+                      : "syntax error"
+                      " in .main during compact, at file position ",
+                      buf, (char*)0);
+      rc= TCL_ERROR;
+      goto x_rc;
+    } else {
+      assert(!rc);
+    }
+  }
+  fclose(a.mainfile);
+  a.mainfile= 0;
+
+  /* create new cdb */
+
+  cdbfd= open(pathbuf_sfx(&rw->pbsome,".tmp"), O_WRONLY|O_CREAT|O_TRUNC, 0666);
+  if (cdbfd<0) PE("create .tmp for new cdb during compact");
+
+  r= cdb_make_start(&a.cdbm, cdbfd);
+  if (r) PE("cdb_make_start during compact");
+  cdbmaking= 1;
+
+  r= ht_forall(&rw->logincore, addto_cdb, &a);
+  if (r) PE("cdb_make_add during compact");
+
+  r= cdb_make_finish(&a.cdbm);
+  if(r) PE("cdb_make_finish during compact");
+  cdbmaking= 0;
+
+  r= fdatasync(cdbfd);  if (r) PE("fdatasync new cdb during compact");
+  r= close(cdbfd);  if (r) PE("close new cdb during compact");
+  cdbfd= -1;
+
+  r= rename(rw->pbsome.buf, pathbuf_sfx(&rw->pbother,".cdb"));
+  if (r) PE("install new .cdb during compact");
+
+  /* create new main */
+
+  a.mainfile= fopen(pathbuf_sfx(&rw->pbsome,".tmp"), "w");
+  if (!a.mainfile) PE("create .tmp for new main during compact");
+
+  r= ht_forall(&rw->logincore, addto_main, &a);
+  if (r) { rc= cht_posixerr(ip, errno, "error writing to new .main"
+                           " during compact");  goto x_rc; }
+
+  r= putc('\n', a.mainfile);
+  if (r==EOF) PE("write trailing \n to main during compact");
+  
+  r= fflush(a.mainfile);  if (r) PE("fflush new main during compact");
+  r= fdatasync(fileno(a.mainfile));
+  if (r) PE("fdatasync new main during compact");
+
+  newmainsz= ftello(a.mainfile);
+  if (newmainsz<0) PE("ftello new main during compact");
+  
+  r= fclose(a.mainfile);  if (r) PE("fclose new main during compact");
+  a.mainfile= 0;
+
+  r= rename(rw->pbsome.buf, pathbuf_sfx(&rw->pbother,".main"));
+  if (r) PE("install new .main during compact");
+
+  rw->mainsz= newmainsz;
+
+  /* done! */
+  
+  rc= infocb(ip, rw, "compact-end", "main=%luby nrecs=%ld",
+            (unsigned long)rw->mainsz, *a.reccount);
+  if (rc) goto x_rc;
+
+  return rc;
+
+x_rc:
+  if (a.mainfile) fclose(a.mainfile);
+  if (cdbmaking) cdb_make_finish(&a.cdbm);
+  maybe_close(cdbfd);
+  remove(pathbuf_sfx(&rw->pbsome,".tmp")); /* for tidyness */
+  return rc;
+}
+
+/*---------- Closing ----------*/
+
+static int compact_forclose(Tcl_Interp *ip, Rw *rw, long *reccount_r) {
+  off_t logsz;
+  int r, rc;
+
+  logsz= ftello(rw->logfile);
+  if (logsz < 0) PE("ftello logfile (during tidy close)");
+
+  rc= compact_core(ip, rw, logsz, reccount_r);  if (rc) goto x_rc;
+
+  r= remove(pathbuf_sfx(&rw->pbsome,".jrn"));
+  if (r) PE("remove .jrn (during tidy close)");
+
+  return TCL_OK;
+
+x_rc: return rc;
+}
+  
+int cht_do_cdbwr_close(ClientData cd, Tcl_Interp *ip, void *rw_v) {
+  Rw *rw= rw_v;
+  int rc, rc_close;
+  long reccount= -1;
+  off_t logsz;
+
+  if (rw->autocompact) rc= compact_forclose(ip, rw, &reccount);
+  else rc= TCL_OK;
+
+  if (!rc) {
+    if (rw->logfile) {
+      logsz= ftello(rw->logfile);
+      if (logsz < 0)
+       rc= cht_posixerr(ip, errno, "ftell logfile during close info");
+      else
+       rc= infocb(ip, rw, "close", "main=%luby log=%luby",
+                  rw->mainsz, logsz);
+    } else if (reccount>=0) {
+      rc= infocb(ip, rw, "close", "main=%luby nrecs=%ld",
+                rw->mainsz, reccount);
+    } else {
+      rc= infocb(ip, rw, "close", "main=%luby", rw->mainsz);
+    }
+  }
+  rc_close= rw_close(ip,rw);
+  if (rc_close) rc= rc_close;
+  
+  cht_tabledataid_disposing(ip, rw_v, &cdbtcl_rwdatabases);
+  TFREE(rw);
+  return rc;
+}
+
+/*---------- 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);
+}
+  
+int cht_do_cdbwr_lookup_hb(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_hb);
+}
diff --git a/crypto/Makefile b/crypto/Makefile
new file mode 100644 (file)
index 0000000..8633082
--- /dev/null
@@ -0,0 +1,26 @@
+# crypto - Tcl bindings for parts of the `nettle' crypto library
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+BASE_DIR =     ../base
+EXTBASE =      crypto
+CFILES =       algtables bcmode crypto hook hash
+OTHER_TCTS =   ../hbytes/hbytes-base.tct
+OTHER_EXTS =   hbytes/hbytes
+LDLIBS +=      -lnettle
+
+include ../base/extension.make
+
diff --git a/crypto/algtables.c b/crypto/algtables.c
new file mode 100644 (file)
index 0000000..e0d6cc8
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_crypto.h"
+
+#include <nettle/md5.h>
+#include <nettle/sha.h>
+#include <nettle/serpent.h>
+#include <nettle/twofish.h>
+#include <nettle/aes.h>
+#include <nettle/blowfish.h>
+
+#define NETTLE_BLOCKCIPHERS                    \
+  DO(serpent,  SERPENT)                                \
+  DO(twofish,  TWOFISH)                                \
+/* DO(aes,      AES) */                                \
+  DO(blowfish, BLOWFISH)                       \
+  /*  ALIAS(rijndael, aes, AES)*/
+
+#define ALIAS(alias,name,NAME)
+#define DO(name,NAME)                                                        \
+  static void alg_##name##_makekey(void *sch, const void *key, int keylen) {  \
+    name##_set_key(sch, keylen, key);                                        \
+  }                                                                          \
+  static void alg_##name##_encr(const void *sch, const void *in, void *out) { \
+    name##_encrypt((void*)sch, NAME##_BLOCK_SIZE, out, in);                  \
+  }                                                                          \
+  static void alg_##name##_decr(const void *sch, const void *in, void *out) { \
+    name##_decrypt((void*)sch, NAME##_BLOCK_SIZE, out, in);                  \
+  }
+  NETTLE_BLOCKCIPHERS
+#undef DO
+#undef ALIAS
+
+const BlockCipherAlgInfo cht_blockcipheralginfo_entries[]= {
+#define ALIAS(alias,name,NAME)                                 \
+  { #alias, NAME##_BLOCK_SIZE, sizeof(struct name##_ctx),      \
+       NAME##_MIN_KEY_SIZE, NAME##_MAX_KEY_SIZE,               \
+    { alg_##name##_makekey, alg_##name##_encr },               \
+    { alg_##name##_makekey, alg_##name##_decr }                        \
+  },
+#define DO(name,NAME) ALIAS(name,name,NAME)
+  NETTLE_BLOCKCIPHERS
+#undef DO
+#undef ALIAS
+  { 0 }
+};
+
+const BlockCipherPropInfo cht_blockcipherpropinfo_entries[]= {
+  { "blocklen",  offsetof(BlockCipherAlgInfo, blocksize) },
+  { "minkeylen", offsetof(BlockCipherAlgInfo, key_min)   },
+  { "maxkeylen", offsetof(BlockCipherAlgInfo, key_max)   },
+  { 0 }
+};
+
+#define NETTLE_DIGESTS                         \
+  DO(sha1,   SHA1)                             \
+  DO(sha256, SHA256)                           \
+  DO(md5,    MD5)
+
+#define DO(name,NAME)                                                        \
+  static void alg_##name##_init(void *state) {                               \
+    name##_init(state);                                                              \
+  }                                                                          \
+  static void alg_##name##_update(void *state, const void *data, int len) {   \
+    name##_update(state, len, data);                                         \
+  }                                                                          \
+  static void alg_##name##_final(void *state, void *digest) {                \
+    name##_digest(state,NAME##_DIGEST_SIZE,digest);                          \
+  }                                                                          \
+  static void alg_##name##_oneshot(void *digest, const void *data, int len) { \
+    struct name##_ctx ctx;                                                   \
+    name##_init(&ctx);                                                       \
+    name##_update(&ctx, len, data);                                          \
+    name##_digest(&ctx,NAME##_DIGEST_SIZE,digest);                           \
+  }
+  NETTLE_DIGESTS
+#undef DO
+
+const HashAlgPropInfo cht_hashalgpropinfo_entries[]= {
+  { "hashlen",  offsetof(HashAlgInfo, hashsize)  },
+  { "blocklen", offsetof(HashAlgInfo, blocksize) },
+  { 0 }
+};
+
+const HashAlgInfo cht_hashalginfo_entries[]= {
+#define DO(name,NAME)                                                      \
+  { #name, NAME##_DIGEST_SIZE, NAME##_DATA_SIZE, sizeof(struct name##_ctx), \
+    alg_##name##_init, alg_##name##_update, alg_##name##_final,                    \
+    alg_##name##_oneshot },
+  NETTLE_DIGESTS
+#undef DO
+  { 0 }
+};
diff --git a/crypto/bcmode.c b/crypto/bcmode.c
new file mode 100644 (file)
index 0000000..9545af3
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+  
+#include "chiark_tcl_crypto.h"
+
+static const char *mode_cbc_encrypt(Byte *data, int blocks,
+                                   const Byte *iv, Byte *chain,
+                                   const BlockCipherAlgInfo *alg, int encr,
+                                   const void *sch) {
+  int blocksize= alg->blocksize;
+  memcpy(chain,iv,blocksize);
+  
+  while (blocks > 0) {
+    memxor(data, chain, blocksize);
+    alg->encrypt.crypt(sch, data, data);
+    memcpy(chain, data, blocksize);
+
+    blocks--; data += blocksize;
+  }
+  return 0;
+}
+
+static const char *mode_cbc_decrypt(Byte *data, int blocks,
+                                   const Byte *iv, Byte *chain,
+                                   const BlockCipherAlgInfo *alg, int encr,
+                                   const void *sch) {
+  int blocksize= alg->blocksize;
+  int cchain= 0;
+
+  memcpy(chain,iv,blocksize);
+  
+  while (blocks > 0) {
+    memcpy(chain + (cchain^blocksize), data, blocksize);
+    alg->decrypt.crypt(sch, data, data);
+    memxor(data, chain + cchain, blocksize);
+    cchain ^= blocksize;
+
+    blocks--; data += blocksize;
+  }
+  return 0;
+}
+
+static void cbcmac_core(const Byte *data, int blocks,
+                       const Byte *iv, Byte *buf,
+                       const BlockCipherAlgInfo *alg,
+                       const void *sch) {
+  int blocksize= alg->blocksize;
+
+  memcpy(buf,iv,blocksize);
+  
+  while (blocks > 0) {
+    memcpy(buf + blocksize, data, blocksize);
+    memxor(buf, buf + blocksize, blocksize);
+    
+    alg->encrypt.crypt(sch, buf, buf);
+
+    blocks--; data += blocksize;
+  }
+}  
+
+static const char *mode_cbc_mac(const Byte *data, int blocks,
+                               const Byte *iv, Byte *buf,
+                               const BlockCipherAlgInfo *alg,
+                               const void *sch) {
+  cbcmac_core(data,blocks,iv,buf,alg,sch);
+  return 0;
+}
+
+static const char *mode_cbc_mac2(const Byte *data, int blocks,
+                                const Byte *iv, Byte *buf,
+                                const BlockCipherAlgInfo *alg,
+                                const void *sch) {
+  cbcmac_core(data,blocks,iv,buf,alg,sch);
+  alg->encrypt.crypt(sch, buf, buf);
+  return 0;
+}
+
+static const char *mode_ecb(Byte *data, int blocks,
+                           const Byte *iv, Byte *chain,
+                           const BlockCipherAlgInfo *alg, int encr,
+                           const void *sch) {
+  int blocksize= alg->blocksize;
+  
+  while (blocks > 0) {
+    (encr ? &alg->encrypt : &alg->decrypt)->crypt(sch, data, data);
+    blocks--; data += blocksize;
+  }
+  return 0;
+}
+
+static const char *mode_ctr(Byte *data, int blocks,
+                           const Byte *iv, Byte *counter,
+                           const BlockCipherAlgInfo *alg, int encr,
+                           const void *sch) {
+  int blocksize= alg->blocksize;
+  Byte *cipher= counter + blocksize;
+  int byte;
+
+  memcpy(counter, iv, blocksize);
+  while (blocks > 0) {
+    alg->encrypt.crypt(sch, counter, cipher);
+    memxor(data, cipher, blocksize);
+    for (byte=blocksize-1; byte>=0; byte--) {
+      if (++counter[byte]) break;
+      /* new value of zero implies carry, so increment next byte */
+    }
+    blocks--;
+    data += blocksize;
+  }
+  return 0;
+}
+
+const BlockCipherModeInfo cht_blockciphermodeinfo_entries[]= {
+  { "cbc",      1, 2, 1, mode_cbc_encrypt, mode_cbc_decrypt, mode_cbc_mac  },
+  { "cbc-mac2", 1, 2, 1, 0,                0,                mode_cbc_mac2 },
+  { "ecb",      0, 0, 0, mode_ecb,         mode_ecb,         0             },
+  { "ctr-sif",  1, 2, 0, mode_ctr,         mode_ctr,         0             },
+  { 0 }
+};
diff --git a/crypto/chiark_tcl_crypto.h b/crypto/chiark_tcl_crypto.h
new file mode 100644 (file)
index 0000000..8ff4cbf
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <stdint.h>
+#include <stddef.h>
+#include <netinet/in.h>
+
+#include <endian.h>
+
+#include "hbytes.h"
+#include "crypto.h"
+#include "crypto+tcmdif.h"
diff --git a/crypto/crypto.c b/crypto/crypto.c
new file mode 100644 (file)
index 0000000..aee2556
--- /dev/null
@@ -0,0 +1,453 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_crypto.h"
+
+const PadOp cht_padop_entries[]= {
+  { "un", 0, 0 },
+  { "ua", 0, 1 },
+  { "pn", 1, 0 },
+  { "pa", 1, 1 },
+  { 0 }
+};
+
+typedef struct {
+  HBytes_Value *hb;
+  int pad, blocksize; /* 0 or 1 */
+} PadMethodClientData;
+
+int cht_do_hbcrypto_pad(ClientData cd, Tcl_Interp *ip, const PadOp *op,
+                 HBytes_Var v, Tcl_Obj *blocksz, const PadMethodInfo *meth,
+                 int methargsc, Tcl_Obj *const *methargsv) {
+  PadMethodClientData pmcd;
+  int rc;
+  
+  if (op->use_algname) {
+    const BlockCipherAlgInfo *alg;
+    alg= enum_lookup_cached(ip,blocksz, cht_blockcipheralginfo_entries,
+                           "blockcipher alg for pad");
+    if (!alg) return TCL_ERROR;
+    pmcd.blocksize= alg->blocksize;
+  } else {
+    rc= Tcl_GetIntFromObj(ip, blocksz, &pmcd.blocksize);  if (rc) return rc;
+    if (pmcd.blocksize < 1) cht_staticerr(ip, "block size must be at least 1", 0);
+  }
+
+  pmcd.hb= v.hb;
+  pmcd.pad= op->pad;
+
+  return meth->func(&pmcd,ip,methargsc,methargsv);
+}
+  
+int cht_do_padmethodinfo_rfc2406(ClientData cd, Tcl_Interp *ip,
+                            Tcl_Obj *nxthdr_arg, int *ok) {
+  const PadMethodClientData *pmcd= (const void*)cd;
+  int i, rc, padlen, old_len;
+  
+  if (pmcd->blocksize > 256)
+    return cht_staticerr(ip, "block size too large for RFC2406 padding", 0);
+
+  if (pmcd->pad) {
+    Byte *padding;
+    HBytes_Value nxthdr;
+
+    rc= cht_pat_hb(ip,nxthdr_arg,&nxthdr);
+    if (rc) return rc;
+
+    if (cht_hb_len(&nxthdr) != 1) return
+      cht_staticerr(ip, "RFC2406 next header field must be exactly 1 byte", 0);
+    padlen= pmcd->blocksize-1 - ((cht_hb_len(pmcd->hb)+1) % pmcd->blocksize);
+    padding= cht_hb_append(pmcd->hb, padlen+2);
+    for (i=1; i<=padlen; i++)
+      *padding++ = i;
+    *padding++ = padlen;
+    *padding++ = cht_hb_data(&nxthdr)[0];
+    *ok= 1;
+    
+  } else {
+    const Byte *padding, *trailer;
+    HBytes_Value nxthdr;
+    Tcl_Obj *nxthdr_valobj, *ro;
+    
+    *ok= 0;
+    old_len= cht_hb_len(pmcd->hb);  if (old_len % pmcd->blocksize) goto quit;
+    trailer= cht_hb_unappend(pmcd->hb, 2); if (!trailer) goto quit;
+
+    padlen= trailer[0];
+    cht_hb_array(&nxthdr,trailer+1,1);
+    nxthdr_valobj= cht_ret_hb(ip,nxthdr);
+    ro= Tcl_ObjSetVar2(ip,nxthdr_arg,0,nxthdr_valobj,TCL_LEAVE_ERR_MSG);
+    if (!ro) { Tcl_DecrRefCount(nxthdr_valobj); return TCL_ERROR; }
+
+    padding= cht_hb_unappend(pmcd->hb, padlen);
+    for (i=1; i<=padlen; i++)
+      if (*padding++ != i) goto quit;
+
+    *ok= 1;
+
+  quit:;
+
+  }
+
+  return TCL_OK;
+}
+
+int cht_do_padmethodinfo_pkcs5(ClientData cd, Tcl_Interp *ip, int *ok) {
+  const PadMethodClientData *pmcd= (const void*)cd;
+  int padlen, old_len, i;
+  
+  if (pmcd->blocksize > 255)
+    return cht_staticerr(ip, "block size too large for pkcs#5", 0);
+
+  if (pmcd->pad) {
+
+    Byte *padding;
+
+    padlen= pmcd->blocksize - (cht_hb_len(pmcd->hb) % pmcd->blocksize);
+    padding= cht_hb_append(pmcd->hb, padlen);
+    memset(padding, padlen, padlen);
+
+  } else {
+
+    const Byte *padding;
+
+    old_len= cht_hb_len(pmcd->hb);  if (old_len % pmcd->blocksize) goto bad;
+    padding= cht_hb_unappend(pmcd->hb, 1);  if (!padding) goto bad;
+    padlen= *padding;
+    if (padlen < 1 || padlen > pmcd->blocksize) goto bad;
+    padding= cht_hb_unappend(pmcd->hb, padlen-1);  if (!padding) goto bad;
+
+    for (i=0; i<padlen-1; i++, padding++) if (*padding != padlen) goto bad;
+
+  }
+
+  *ok= 1;
+  return TCL_OK;
+
+ bad:
+  *ok= 0;
+  return TCL_OK;
+}
+
+#define OBJ_CIPHKEY(o) ((CiphKeyValue*)(o)->internalRep.otherValuePtr)
+
+typedef struct {
+  int valuelen, bufferslen;
+  Byte *value, *buffers;
+  const void *alg;
+  void *alpha, *beta; /* key schedules etc.; each may be 0 */
+} CiphKeyValue;
+
+static void freealg(CiphKeyValue *key) {
+  TFREE(key->alpha);
+  TFREE(key->beta);
+}
+
+static void key_t_free(Tcl_Obj *obj) {
+  CiphKeyValue *key= OBJ_CIPHKEY(obj);
+  freealg(key);
+  TFREE(key->value);
+  TFREE(key->buffers);
+}
+
+static void noalg(CiphKeyValue *key) {
+  key->alg= 0;
+  key->alpha= key->beta= 0;
+}
+
+static void key_t_dup(Tcl_Obj *src_obj, Tcl_Obj *dup_obj) {
+  CiphKeyValue *src= OBJ_CIPHKEY(src_obj);
+  CiphKeyValue *dup= TALLOC(sizeof(*dup));
+  dup->valuelen= src->valuelen;
+  dup->value= src->valuelen ? TALLOC(src->valuelen) : 0;
+  dup->buffers= 0; dup->bufferslen= 0;
+  memcpy(dup->value, src->value, src->valuelen);
+  noalg(dup);
+  dup_obj->internalRep.otherValuePtr= dup;
+  dup_obj->typePtr= &cht_blockcipherkey_type;
+}
+
+static void key_t_ustr(Tcl_Obj *o) {
+  cht_obj_updatestr_array(o, OBJ_CIPHKEY(o)->value, OBJ_CIPHKEY(o)->valuelen);
+}
+
+static int key_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  int rc, l;
+  CiphKeyValue *val;
+
+  rc= Tcl_ConvertToType(ip,o,&cht_hbytes_type);  if (rc) return rc;
+  val= TALLOC(sizeof(*val));
+  val->valuelen= l= cht_hb_len(OBJ_HBYTES(o));
+  val->value= TALLOC(l);
+  val->buffers= 0;
+  val->bufferslen= 0;
+  memcpy(val->value, cht_hb_data(OBJ_HBYTES(o)), l);
+  noalg(val);
+
+  cht_objfreeir(o);
+  o->internalRep.otherValuePtr= val;
+  o->typePtr= &cht_blockcipherkey_type;
+
+  return TCL_OK;
+}
+  
+Tcl_ObjType cht_blockcipherkey_type = {
+  "blockcipher-key",
+  key_t_free, key_t_dup, key_t_ustr, key_t_sfa
+};
+
+static CiphKeyValue *get_key(Tcl_Interp *ip, Tcl_Obj *key_obj,
+                            const void *alg, int want_bufferslen) {
+  CiphKeyValue *key;
+  int rc;
+  
+  rc= Tcl_ConvertToType(ip,key_obj,&cht_blockcipherkey_type);  if (rc) return 0;
+  key= OBJ_CIPHKEY(key_obj);
+
+  if (key->alg != alg) {
+    freealg(key);
+    noalg(key);
+    key->alg= alg;
+  }
+  
+  if (key->bufferslen < want_bufferslen) {
+    TFREE(key->buffers);
+    key->buffers= TALLOC(want_bufferslen);
+    key->bufferslen= want_bufferslen;
+  }
+  return key;
+}
+
+int cht_do_hbcrypto_blockcipher(ClientData cd, Tcl_Interp *ip,
+                         const BlockCipherOp *op,
+                         int objc, Tcl_Obj *const *objv) {
+  return op->func((void*)op,ip,objc,objv);
+}
+
+static int blockcipher_prep(Tcl_Interp *ip, Tcl_Obj *key_obj,
+                           const HBytes_Value *iv, int decrypt,
+                           const BlockCipherAlgInfo *alg,
+                           const BlockCipherModeInfo *mode, int data_len,
+                           const CiphKeyValue **key_r, const void **sched_r,
+                           const Byte **iv_r, int *iv_lenbytes_r,
+                           Byte **buffers_r, int *nblocks_r) {
+  void *sched, **schedp;
+  int want_bufferslen, want_iv;
+  int rc;
+  CiphKeyValue *key;
+
+  if (data_len % alg->blocksize)
+    return cht_staticerr(ip, "block cipher input not whole number of blocks",
+                    "HBYTES BLOCKCIPHER LENGTH");
+
+  want_bufferslen= alg->blocksize * (mode->buf_blocks + mode->iv_blocks);
+  key= get_key(ip, key_obj, alg, want_bufferslen);  if (!key) return TCL_ERROR;
+
+  schedp= (alg->decrypt.make_schedule==alg->encrypt.make_schedule
+          || !decrypt) ? &key->alpha : &key->beta;
+  sched= *schedp;
+  if (!sched) {
+    if (key->valuelen < alg->key_min)
+      return cht_staticerr(ip, "key too short", "HBYTES BLOCKCIPHER PARAMS");
+    if (key->valuelen > alg->key_max)
+      return cht_staticerr(ip, "key too long", "HBYTES BLOCKCIPHER PARAMS");
+
+    sched= TALLOC(alg->schedule_size);
+    (decrypt ? &alg->decrypt : &alg->encrypt)->make_schedule
+      (sched, key->value, key->valuelen);
+    *schedp= sched;
+  }
+
+  want_iv= alg->blocksize * mode->iv_blocks;
+  if (!want_iv) {
+    if (!cht_hb_issentinel(iv))
+      return cht_staticerr(ip,"iv supplied but mode does not take one", 0);
+  } else if (cht_hb_issentinel(iv)) {
+    if (decrypt) return cht_staticerr(ip,"must supply iv when decrypting", 0);
+    rc= cht_get_urandom(ip, key->buffers, want_iv);
+    if (rc) return rc;
+  } else {
+    int iv_supplied= cht_hb_len(iv);
+    if (iv_supplied > want_iv)
+      return cht_staticerr(ip, "iv too large for algorithm and mode",
+                      "HBYTES BLOCKCIPHER PARAMS");
+    memcpy(key->buffers, cht_hb_data(iv), iv_supplied);
+    memset(key->buffers + iv_supplied, 0, want_iv - iv_supplied);
+  }
+
+  *key_r= key;
+  *sched_r= sched;
+
+  *iv_r= key->buffers;
+  *iv_lenbytes_r= want_iv;
+
+  *buffers_r= key->buffers + want_iv;
+  *nblocks_r= data_len / alg->blocksize;
+  
+  return TCL_OK;
+}
+
+int cht_do_blockcipherop_d(ClientData cd, Tcl_Interp *ip,
+                      HBytes_Var v, const BlockCipherAlgInfo *alg,
+                      Tcl_Obj *key_obj, const BlockCipherModeInfo *mode,
+                      HBytes_Value iv, HBytes_Value *result) {
+  return cht_do_blockcipherop_e(cd,ip,v,alg,key_obj,mode,iv,result);
+}
+
+int cht_do_blockcipherop_e(ClientData cd, Tcl_Interp *ip,
+                      HBytes_Var v, const BlockCipherAlgInfo *alg,
+                      Tcl_Obj *key_obj, const BlockCipherModeInfo *mode,
+                      HBytes_Value iv, HBytes_Value *result) {
+  const BlockCipherOp *op= (const void*)cd;
+  int encrypt= op->encrypt;
+  int rc, iv_lenbytes;
+  const CiphKeyValue *key;
+  const char *failure;
+  const Byte *ivbuf;
+  Byte *buffers;
+  const void *sched;
+  int nblocks;
+
+  if (!mode->encrypt)
+    return cht_staticerr(ip, "mode does not support encrypt/decrypt", 0);
+
+  rc= blockcipher_prep(ip,key_obj,&iv,!encrypt,
+                      alg,mode, cht_hb_len(v.hb),
+                      &key,&sched,
+                      &ivbuf,&iv_lenbytes,
+                      &buffers,&nblocks);
+  if (rc) return rc;
+  
+  failure=
+    (encrypt ? mode->encrypt : mode->decrypt)
+    (cht_hb_data(v.hb), nblocks, ivbuf, buffers, alg, encrypt, sched);
+
+  if (failure)
+    return cht_staticerr(ip, failure, "HBYTES BLOCKCIPHER CRYPTFAIL CRYPT");
+
+  cht_hb_array(result, ivbuf, iv_lenbytes);
+
+  return TCL_OK;
+}
+
+int cht_do_blockcipherop_mac(ClientData cd, Tcl_Interp *ip,
+                        HBytes_Value msg, const BlockCipherAlgInfo *alg,
+                        Tcl_Obj *key_obj, const BlockCipherModeInfo *mode,
+                        HBytes_Value iv, HBytes_Value *result) {
+  const CiphKeyValue *key;
+  const char *failure;
+  const Byte *ivbuf;
+  Byte *buffers;
+  const void *sched;
+  int nblocks, iv_lenbytes;
+  int rc;
+
+  if (!mode->mac)
+    return cht_staticerr(ip, "mode does not support mac generation", 0);
+  
+  rc= blockcipher_prep(ip,key_obj,&iv,0,
+                      alg,mode, cht_hb_len(&msg),
+                      &key,&sched,
+                      &ivbuf,&iv_lenbytes,
+                      &buffers,&nblocks);
+  if (rc) return rc;
+
+  failure= mode->mac(cht_hb_data(&msg), nblocks, ivbuf, buffers, alg, sched);
+  if (failure)
+    return cht_staticerr(ip,failure, "HBYTES BLOCKCIPHER CRYPTFAIL MAC");
+
+  cht_hb_array(result, buffers, alg->blocksize * mode->mac_blocks);
+
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hmac(ClientData cd, Tcl_Interp *ip, const HashAlgInfo *alg,
+                  HBytes_Value message, Tcl_Obj *key_obj,
+                  Tcl_Obj *maclen_obj, HBytes_Value *result) {
+  /* key->alpha = state after H(K XOR ipad <unfinished>
+   * key->beta = state after H(K XOR opad <unfinished>
+   * key->buffers = room for one block, or one state
+   */
+  CiphKeyValue *key;
+  Byte *dest;
+  int i, ml, rc;
+
+  if (maclen_obj) {
+    rc= Tcl_GetIntFromObj(ip, maclen_obj, &ml);  if (rc) return rc;
+    if (ml<0 || ml>alg->hashsize)
+      return cht_staticerr(ip, "requested hmac output size out of range",
+                      "HBYTES HMAC PARAMS");
+  } else {
+    ml= alg->hashsize;
+  }
+
+  key= get_key(ip, key_obj, alg,
+              alg->blocksize > alg->statesize
+              ? alg->blocksize : alg->statesize);
+
+  if (!key->alpha) {
+    assert(!key->beta);
+    
+    if (key->valuelen > alg->blocksize)
+      return cht_staticerr(ip, "key to hmac longer than hash block size",
+                      "HBYTES HMAC PARAMS");
+
+    memcpy(key->buffers, key->value, key->valuelen);
+    memset(key->buffers + key->valuelen, 0, alg->blocksize - key->valuelen);
+    for (i=0; i<alg->blocksize; i++) key->buffers[i] ^= 0x36;
+
+    key->alpha= TALLOC(alg->statesize);
+    alg->init(key->alpha);
+    alg->update(key->alpha, key->buffers, alg->blocksize);
+    
+    key->beta= TALLOC(alg->statesize);
+    alg->init(key->beta);
+    for (i=0; i<alg->blocksize; i++) key->buffers[i] ^= (0x5c ^ 0x36);
+    alg->update(key->beta, key->buffers, alg->blocksize);
+  }
+  assert(key->beta);
+
+  dest= cht_hb_arrayspace(result, alg->hashsize);
+
+  memcpy(key->buffers, key->alpha, alg->statesize);
+  alg->update(key->buffers, cht_hb_data(&message), cht_hb_len(&message));
+  alg->final(key->buffers, dest);
+
+  memcpy(key->buffers, key->beta, alg->statesize);
+  alg->update(key->buffers, dest, alg->hashsize);
+  alg->final(key->buffers, dest);
+
+  cht_hb_unappend(result, alg->hashsize - ml);
+
+  return TCL_OK;
+}
+
+int cht_do_blockcipherop_prop(ClientData cd, Tcl_Interp *ip,
+                         const BlockCipherPropInfo *prop,
+                         const BlockCipherAlgInfo *alg, int *result) {
+  *result= *(const int*)((const char*)alg + prop->int_offset);
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hash_prop(ClientData cd, Tcl_Interp *ip,
+                       const HashAlgPropInfo *prop,
+                       const HashAlgInfo *alg, int *result) {
+  *result= *(const int*)((const char*)alg + prop->int_offset);
+  return TCL_OK;
+}
diff --git a/crypto/crypto.h b/crypto/crypto.h
new file mode 100644 (file)
index 0000000..13b5654
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef CRYPTO_H
+#define CRYPTO_H
+
+#include "chiark-tcl.h"
+
+/* from crypto.c */
+
+void memxor(Byte *dest, const Byte *src, int l);
+
+typedef struct {
+  const char *name;
+  int pad, use_algname;
+} PadOp;
+
+extern Tcl_ObjType cht_blockcipherkey_type;
+
+/* from algtables.c */
+
+typedef struct {
+  const char *name;
+  int int_offset;
+} BlockCipherPropInfo, HashAlgPropInfo;
+
+typedef struct {
+  const char *name;
+  int hashsize, blocksize, statesize;
+  void (*init)(void *state);
+  void (*update)(void *state, const void *data, int len);
+  void (*final)(void *state, void *digest);
+  void (*oneshot)(void *digest, const void *data, int len);
+} HashAlgInfo;
+
+extern const HashAlgInfo cht_hashalginfo_entries[];
+
+typedef struct {
+  void (*make_schedule)(void *schedule, const void *key, int keylen);
+  void (*crypt)(const void *schedule, const void *in, void *out);
+     /* in and out may be the same, but if they aren't they may not overlap */
+     /* in and out for crypt will have been through block_byteswap */
+} BlockCipherPerDirectionInfo;
+
+typedef struct {
+  const char *name;
+  int blocksize, schedule_size, key_min, key_max;
+  BlockCipherPerDirectionInfo encrypt, decrypt;
+} BlockCipherAlgInfo;
+
+extern const BlockCipherAlgInfo cht_blockcipheralginfo_entries[];
+
+/* from bcmode.c */
+
+typedef struct {
+  const char *name;
+  int iv_blocks, buf_blocks, mac_blocks;
+
+  /* Each function is allowed to use up to buf_blocks * blocksize
+   * bytes of space in buf.  data is blocks * blocksize bytes
+   * long.  data should be modified in place by encrypt and decrypt;
+   * modes may not change the size of data.  iv is always provided and
+   * is always of length iv_blocks * blocksize; encrypt and
+   * decrypt may modify the iv value (in which case the Tcl caller
+   * will get the modified IV) but this is not recommended.  mac
+   * should leave the mac, which must be mac_blocks * blocksize
+   * bytes, in buf.  (Therefore mac_blocks must be at least
+   * buf_blocks.)
+   */
+  const char *(*encrypt)(Byte *data, int nblocks,
+                        const Byte *iv, Byte *buf,
+                        const BlockCipherAlgInfo *alg, int encr,
+                        const void *sch);
+  const char *(*decrypt)(Byte *data, int nblocks,
+                        const Byte *iv, Byte *buf,
+                        const BlockCipherAlgInfo *alg, int encr,
+                        const void *sch);
+  const char *(*mac)(const Byte *data, int nblocks,
+                    const Byte *iv, Byte *buf,
+                    const BlockCipherAlgInfo *alg,
+                    const void *sch);
+} BlockCipherModeInfo;
+
+extern const IdDataSpec cht_hash_states;
+extern const BlockCipherModeInfo cht_blockciphermodeinfo_entries[];
+
+#include "crypto+tcmdif.h"
+
+#endif /*CRYPTO_H*/
diff --git a/crypto/crypto.tct b/crypto/crypto.tct
new file mode 100644 (file)
index 0000000..37794db
--- /dev/null
@@ -0,0 +1,96 @@
+# crypto - Tcl bindings for parts of the `nettle' crypto library
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Table *hbcryptotoplevel TopLevel_Command
+       hbcrypto
+               dispatch(HBCrypto/_SubCommand, "hbcrypto subcommand")
+
+Table hbcrypto HBCrypto_SubCommand
+       pad
+               op      enum(PadOp/, "hbcrypto pad subcommand")
+               v       hbv
+               blocksz obj
+               meth    enum(PadMethodInfo/, "pad method")
+               ...     methargs
+       blockcipher
+               op      enum(BlockCipherOp/, "op")
+               ...     obj
+       hash
+               alg     enum(HashAlgInfo/, "hash alg")
+               message hb
+               =>      hb
+       hash-init
+               alg     enum(HashAlgInfo/, "hash alg")
+               =>      iddata(&cht_hash_states)
+       hash-update
+               stateh  iddata(&cht_hash_states)
+               message hb
+       hash-final
+               stateh  iddata(&cht_hash_states)
+               =>      hb
+       hash-discard
+               stateh  iddata(&cht_hash_states)
+       hash-clonestate
+               stateh  iddata(&cht_hash_states)
+               =>      iddata(&cht_hash_states)
+       hmac
+               alg     enum(HashAlgInfo/, "hash alg for hmac")
+               message hb
+               key     obj
+               ?maclen obj
+               =>      hb
+       hash-prop
+               prop    enum(HashAlgPropInfo/, "prop")
+               alg     enum(HashAlgInfo/, "alg")
+               =>      int
+
+Table padmethodinfo PadMethodInfo
+       pkcs5
+               =>      int
+       rfc2406
+               nxthdr  obj
+               =>      int
+
+Table blockcipherop BlockCipherOp
+       e       1
+               v       hbv
+               alg     enum(BlockCipherAlgInfo/, "alg")
+               key     obj
+               mode    enum(BlockCipherModeInfo/, "mode")
+               ?iv     hb
+               =>      hb
+       d       0
+               v       hbv
+               alg     enum(BlockCipherAlgInfo/, "alg")
+               key     obj
+               mode    enum(BlockCipherModeInfo/, "mode")
+               ?iv     hb
+               =>      hb
+       mac     -1
+               msg     hb
+               alg     enum(BlockCipherAlgInfo/, "alg")
+               key     obj
+               mode    enum(BlockCipherModeInfo/, "mode")
+               iv      hb
+               =>      hb
+       prop    -1
+               prop    enum(BlockCipherPropInfo/, "prop")
+               alg     enum(BlockCipherAlgInfo/, "alg")
+               =>      int
+
+EntryExtra BlockCipherOp
+       int encrypt;
diff --git a/crypto/hash.c b/crypto/hash.c
new file mode 100644 (file)
index 0000000..f7c6c44
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_crypto.h"
+
+typedef struct {
+  int ix;
+  const HashAlgInfo *alg;
+  Byte d[1];
+} HashState;
+
+int cht_do_hbcrypto_hash(ClientData cd, Tcl_Interp *ip, const HashAlgInfo *alg,
+                  HBytes_Value message, HBytes_Value *result) {
+  Byte *dest;
+
+  dest= cht_hb_arrayspace(result,alg->hashsize);
+  alg->oneshot(dest, cht_hb_data(&message), cht_hb_len(&message));
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hash_init(ClientData cd, Tcl_Interp *ip,
+                             const HashAlgInfo *alg, void **state_r) {
+  HashState *state= TALLOC(sizeof(*state) + alg->statesize - 1);
+  state->ix= -1;
+  state->alg= alg;
+  alg->init(state->d);
+  *state_r= state;
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hash_update(ClientData cd, Tcl_Interp *ip,
+                               void *state_v, HBytes_Value data) {
+  HashState *state= state_v;
+  state->alg->update(&state->d, cht_hb_data(&data), cht_hb_len(&data));
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hash_final(ClientData cd, Tcl_Interp *ip,
+                              void *state_v, HBytes_Value *result) {
+  HashState *state= state_v;
+  Byte *digest;
+
+  digest= cht_hb_arrayspace(result,state->alg->hashsize);
+  state->alg->final(&state->d, digest);
+  return cht_do_hbcrypto_hash_discard(cd,ip,state_v);
+}
+
+int cht_do_hbcrypto_hash_discard(ClientData cd, Tcl_Interp *ip,
+                                void *state_v) {
+  cht_tabledataid_disposing(ip,state_v,&cht_hash_states);
+  free(state_v);
+  return TCL_OK;
+}
+
+int cht_do_hbcrypto_hash_clonestate(ClientData cd, Tcl_Interp *ip,
+                                   void *old_v, void **new_r) {
+  HashState *old= old_v;
+  int len= sizeof(*old) + old->alg->statesize - 1;
+  void *new_v= TALLOC(len);
+  memcpy(new_v, old, len);
+  ((HashState*)new_v)->ix= -1;
+  *new_r= new_v;
+  return TCL_OK;
+}
+
+
+static void destroy_idtabcb(Tcl_Interp *ip, void *state_v) {
+  free(state_v);
+}
+
+const IdDataSpec cht_hash_states= {
+  "hashstate", "hashstate-table", destroy_idtabcb
+};
diff --git a/crypto/hook.c b/crypto/hook.c
new file mode 100644 (file)
index 0000000..00ca7c5
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * crypto - Tcl bindings for parts of the `nettle' crypto library
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_crypto.h"
+
+CHT_INIT(crypto,
+        CHTI_OTHER(hbytes) CHTI_TYPE(cht_blockcipherkey_type),
+        CHTI_COMMANDS(cht_hbcryptotoplevel_entries))
diff --git a/debian/README b/debian/README
new file mode 100644 (file)
index 0000000..7102c8a
--- /dev/null
@@ -0,0 +1,44 @@
+        chiark-tcl - some useful Tcl bindings
+        -------------------------------------
+
+This package contains, basically, shared libraries
+  chiark_tcl_<something>-1.so
+in /usr/lib.
+
+Each of these is a Tcl extension which can be loaded into a Tcl
+interpreter with
+  load chiark_tcl_<something>-1.so
+and then the new commands will immediately be available.
+
+The documentation for each extension is regrettably rather sketchy but
+the following information should be enough to get you started:
+
+  <something>.tct
+
+      This is the input file to the automatic Tcl<->C glue generator
+      used by all of the extensions provided in this package.  This
+      lists the commands and subcommands available.
+
+  <something>.[ch].txt
+
+      Some of the extensions have additional usage documentation in a
+      source code comment.  This comment has been mechanically
+      extracted from the source file for your comfort and convenience.
+
+To gain an understanding of the way the *.tct files work, take a
+look at adns.c.txt which describes the `adns' command provided by
+the adns binding.
+
+Note that the file /usr/lib/libchiark_tcl-1.so is NOT an amalgam of
+all of the extensions.  It is a set of common routines which will be
+loaded automatically when required.  Each extension must be loaded
+explicitly with the Tcl `load' command to bring the additional
+commands into the Tcl command namespace.
+
+To use the adns and nettle bindings you need to have the appropriate
+libraries installed too, although these are not listed as
+dependencies.  Programs which use these extensions should list
+dependencies on (currently) libadns1 and libnettle2.  Regrettably,
+there is no clear way to get the dependencies completely correct in
+the case where chiark-tcl is rebuilt against some other versions of
+adns and/or nettle.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..9368743
--- /dev/null
@@ -0,0 +1,119 @@
+chiark-tcl (1.1.1+nmu1) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * Build against the default Tcl version instead of deprecated 8.4
+    (closes: #725248).
+
+ -- Sergei Golovan <sgolovan@debian.org>  Tue, 15 Oct 2013 21:12:46 +0400
+
+chiark-tcl (1.1.1) unstable; urgency=low
+
+  Bugfix:
+  * Handling of errors reading /dev/urandom fixed.
+
+  User-visible change:
+  * Mention dgram, tuntap and maskmap in Description.  Fix typo.
+
+  Build improvements:
+  * Fix FTBFS in sid due to warning from recent versions of gcc
+    about set but not used variables.
+  * Update default TCL_VERSION for upstream build to 8.4.
+
+  Packaging improvements:
+  * Remove the pointless copyright notice from the end of the changelog.
+  * Add ${misc:Depends} to Depends field (has no effect on the .deb).
+  * Provide build-arch and build-indep targets.
+  * Suppress lintian warning about package-name-doesnt-match-sonames.
+  * Remove lintian override for non-PIC cdb, due to tinycdb not having
+    PIC code in it.  This is now fixed in libcdb-dev.
+  * Remove linda overrides.
+  * Add a .gitignore.
+  * Update debian/compat to 5.  No changes needed.
+  * Update Standards-Version.  No changes needed.
+  * Update Copyright dates.
+  * Remove FSF street address from copyright notices.
+  * Change my email address.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 02 Jun 2012 14:20:35 +0100
+
+chiark-tcl (1.1.0+nmu2) unstable; urgency=low
+
+  * Non-maintainer upload.
+  * debian/rules: add invocation of dh_makeshlibs and dh_installdeb, so
+    that ldconfig is invoked in postinst (Closes: #553122)
+  * debian/control: replace libnettle-dev by nettle-dev (which replaces
+    the former) in build-dependencies
+
+ -- Stefano Zacchiroli <zack@debian.org>  Thu, 19 Nov 2009 09:09:57 +0100
+
+chiark-tcl (1.1.0+nmu1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Fix FTBFS with gcc-4.3 by adding <limits.h> include to
+    base/chiark-tcl.h as suggested by Michael Bienia (Closes: #489901).
+  * Set urgency to “medium” as this bug affects testing too.
+
+ -- Cyril Brulebois <kibi@debian.org>  Sun, 19 Jul 2009 18:23:54 +0200
+
+chiark-tcl (1.1.0) unstable; urgency=high
+
+  New features:
+  * hbcrypto hash-{init,update,final} etc. for incremental hashing.
+
+  Bugfixes:
+  * Do not adns_cancel in the middle of adns_forallqueries.
+  * cdb: When cdbwr update writerecord fails, try to recover the
+    situation to sanity so we don't corrupt the log later; if this
+    fails, mark the cdb broken.
+  * strlen returns size_t, not int; fixed up everywhere relevant.
+    Closes #393970.  (Bug exists only where int and ssize_t differ.)
+  * Use correct errno value for error writing to new .main during compact.
+  * Do not coredump if fclose journal fails during compact.
+  * Do not fail lookups on cdb-wr's opened from just-created dbs.
+  * Do not leak cdb innards on compact.
+
+  Portability fixes:
+  * Remove unecessary assertion of val<=0xffffffffUL where uint32_t val;
+    Closes: #394039 (FTBFS due to unhelpful GCC warning).
+  * Use -fno-strict-aliasing because gcc-4.3 apparently ignores
+    -Wno-strict-aliasing!  Closes: #471004.
+
+  Internal improvements:
+  * Add a few assertions about *_LLEN in adns.c.
+  * Comprehensive review of use of `int' and defence against overflow.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri, 20 Jun 2008 22:50:25 +0100
+
+chiark-tcl (1.0.1) unstable; urgency=low
+
+  New features:
+  * adns: Provide txt RRs.
+  * dgram: New extension for datagram sockets; dgram-socket command.
+  * tuntap: New extension for tun/tap interfaces (currently, tun only).
+
+  Documentation, build and packaging fixes:
+  * Correct doc comment for supplying query options to adns asynch.
+  * Replace #include <tcl8.3/tcl.h> with <tcl.h> and in build system find
+    Tcl version and pass appropriate -I option.  Closes: #362806.
+  * Declare versioned build-dependency on libadns1-dev >= 1.2
+    since we need adns_init_logfn.  Closes: #382287.
+  * Declare build-dependency on libcdb-dev | tinycdb (<= 0.75)
+    since cdb.h etc. is in libcdb-dev off nowadays.  Closes: #387904.
+  * Pass -Wno-strict-aliasing.  The compiler is wrong.
+  * Do not run dpkg-shlibdeps on adns and nettle plugins.
+    This prevents them turning up in Depends - see the README.
+  * Use correct syntax for avoiding compressing doc/*/*.[ch].txt.
+  * Use correct variable name for cht_adnstcl_{queries,resolvers}
+    everywhere (prevents coredump accessing uninitialised version).
+  
+  Internal changes:
+  * New way of doing toplevels with tcmdifgen dispatch() primitive.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 18 Oct 2006 17:05:03 +0100
+
+chiark-tcl (1.0.0) unstable; urgency=low
+
+  * Initial release.  Extensions included:  adns cdb crypto hbytes
+    (of which cdb and adns will be needed for new SAUCE).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 30 Mar 2006 18:34:51 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..7aa0918
--- /dev/null
@@ -0,0 +1,40 @@
+Source: chiark-tcl
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Priority: optional
+Section: interpreters
+Standards-Version: 3.9.1
+Build-Depends: libadns1-dev (>= 1.2), nettle-dev, libcdb-dev | tinycdb (<= 0.75), tcl-dev | tcl8.4-dev | tcl8.3-dev | tcl8.2-dev, debhelper (>= 5)
+
+Package: libtcl-chiark-1
+Architecture: any
+Description: Tcl interfaces for adns, cdb, crypto, etc.
+ Tcl bindings for:
+  * adns (resolver library)
+  * cdb (constant database) plus journalling writable database
+  * crypto: the nettle cryptographic library
+  * hbytes: bytestrings with hex as string representation but efficient
+  * dgram: datagram sockets
+  * tuntap: tun/tap interfaces
+  * maskmap: address masks and maps
+ To make sensible use of these you will need a version of Tcl installed
+ (this package is compatible with at least Tcl 8.0 to 8.4 inclusive).
+ To use the adns and nettle bindings you need to have the
+ appropriate libraries installed too.
+Depends: ${shlibs:Depends}, ${misc:Depends}
+
+
+# chiark-tcl - various Tcl bindings and extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..24265ae
--- /dev/null
@@ -0,0 +1,23 @@
+chiark-tcl is a collection of Tcl extensions
+This Debian package was prepared by Ian Jackson, also the upstream
+author.
+
+
+Copyright 2006-2012 Ian Jackson
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+A copy of the GNU General Public License, version 2, should be
+installed on your Debian system in /usr/share/common-licenses/GPL.
diff --git a/debian/extractdoc b/debian/extractdoc
new file mode 100644 (file)
index 0000000..318c8cc
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+$o= '';
+for (;;) {
+    exit 0 unless defined ($_= <STDIN>);
+    $o .= $_;
+    last if / \-\-\-8\<\-\-\- end of documentation /;
+}
+($before, $_, $after)= @ARGV;
+s,.*/,,;
+$of= $before.$_.$after;
+open F, "> $of" or die $!;
+print F $o or die $!;
+close F or die $!;
+print " wrote $of\n" or die $!;
diff --git a/debian/lintian-overrides b/debian/lintian-overrides
new file mode 100644 (file)
index 0000000..654fd7b
--- /dev/null
@@ -0,0 +1,14 @@
+# These things are not linkable against with ld; they're plugin modules
+# for use with dlopen but want to be on the default load path for Tcl's
+# convenience:
+libtcl-chiark-1 binary: no-shlibs-control-file usr/lib/chiark_tcl_adns-1.so
+libtcl-chiark-1 binary: no-shlibs-control-file usr/lib/chiark_tcl_hbytes-1.so
+libtcl-chiark-1 binary: no-shlibs-control-file usr/lib/chiark_tcl_crypto-1.so
+libtcl-chiark-1 binary: no-shlibs-control-file usr/lib/chiark_tcl_cdb-1.so
+libtcl-chiark-1 binary: no-shlibs-control-file usr/lib/libchiark_tcl-1.so
+
+# Our Description ends in `etc.' which makes lintian think it's a
+# sentence.
+libtcl-chiark-1 binary: description-synopsis-might-not-be-phrased-properly
+
+libtcl-chiark-1: package-name-doesnt-match-sonames
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..faa763a
--- /dev/null
@@ -0,0 +1,95 @@
+#!/usr/bin/make -f
+
+# chiark-tcl - various Tcl bindings and extensions
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+majversion=1
+srcpackage=chiark-tcl
+libpackage=libtcl-chiark-$(majversion)
+docpackage=libtcl-chiark-$(majversion)
+docdir=usr/share/doc/$(docpackage)
+
+tclh:=$(firstword $(wildcard /usr/include/tcl8.*/tcl.h))
+tclversion:=$(patsubst /usr/include/tcl%/tcl.h,%,$(tclh))
+
+define checkdir
+       test -f hbytes/hbytes.tct
+endef
+
+build: build-arch build-indep
+build-arch:
+       $(checkdir)
+       $(MAKE) prefix=/usr VERSION=$(majversion) TCL_VERSION=$(tclversion)
+
+build-indep:
+
+clean:
+       $(checkdir)
+       $(MAKE) clean
+       rm -rf *~ debian/tmp debian/*~ debian/files* debian/substvars*
+       dh_clean
+
+binary-indep:
+
+binary-arch:   checkroot build
+       $(checkdir)
+       -rm -rf debian/$(docpackage) debian/$(libpackage)
+       install -d debian/$(libpackage)/usr/lib
+       install -d debian/$(docpackage)/usr/share/doc/$(docpackage)
+
+       set -e; for f in lintian; do                                         \
+               install -d debian/$(libpackage)/usr/share/$$f/overrides;     \
+               cp debian/$$f-overrides                                      \
+                debian/$(libpackage)/usr/share/$$f/overrides/$(libpackage); \
+               done
+
+       cp */*.so debian/$(libpackage)/usr/lib/.
+
+       set -e; for f in */*.[ch]; do                                   \
+               perl debian/extractdoc <$$f                             \
+                       debian/$(docpackage)/$(docdir)/ $$f .txt;       \
+               done
+       cp */*.tct debian/README debian/copyright \
+               debian/$(docpackage)/$(docdir)
+
+       dh_installchangelogs
+       dh_strip
+
+       dh_makeshlibs
+       dh_shlibdeps -Xchiark_tcl_adns -Xchiark_tcl_crypto
+
+       # be consistent about what we compress:
+       dh_compress -X.c.txt -X.h.txt
+
+       dh_fixperms
+       dh_installdeb
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+# Below here is fairly generic really
+
+binary:                binary-indep binary-arch
+
+source diff:
+       @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+checkroot:
+       $(checkdir)
+       dh_testroot
+
+.PHONY: binary binary-arch binary-indep clean checkroot
diff --git a/dgram/Makefile b/dgram/Makefile
new file mode 100644 (file)
index 0000000..e07cfca
--- /dev/null
@@ -0,0 +1,25 @@
+# dgram - Tcl extension for udp datagrams
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+BASE_DIR =     ../base
+EXTBASE =      dgram
+CFILES =       dgram misc sockaddr hook
+OTHER_TCTS =   ../hbytes/hbytes-base.tct
+OTHER_EXTS =   hbytes/hbytes
+
+include ../base/extension.make
+
diff --git a/dgram/chiark_tcl_dgram.h b/dgram/chiark_tcl_dgram.h
new file mode 100644 (file)
index 0000000..2fd88e4
--- /dev/null
@@ -0,0 +1,21 @@
+/* dgram - Tcl extension for udp datagrams
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "hbytes.h"
+#include "dgram.h"
+#include "dgram+tcmdif.h"
diff --git a/dgram/dgram.c b/dgram/dgram.c
new file mode 100644 (file)
index 0000000..24c5446
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ */
+/*
+ * dgram-socket create <local>                        => <sockid>
+ * dgram-socket close <sockid>
+ * dgram-socket transmit <sockid> <data> <remote>
+ * dgram-socket on-receive <sockid> [<script>]
+ *    calls, effectively,  eval <script> [list <data> <remote-addr> <socket>]
+ *    if script not supplied, cancel
+ */
+
+#include "dgram.h"
+
+typedef struct DgramSocket {
+  int ix; /* first ! */
+  int fd;
+  Tcl_Interp *ip;
+  ScriptToInvoke script;
+  void *addr_buf, *msg_buf;
+  int addr_buflen, msg_buflen;
+} DgramSocket;
+
+int cht_do_dgramsocket_create(ClientData cd, Tcl_Interp *ip,
+                          SockAddr_Value local, void **sock_r) {
+  int fd, al, r;
+  DgramSocket *sock;
+  const struct sockaddr *sa;
+
+  sa= cht_sockaddr_addr(&local);
+  al= cht_sockaddr_len(&local);
+
+  fd= socket(sa->sa_family, SOCK_DGRAM, 0);
+  if (fd<0) return cht_posixerr(ip,errno,"socket");
+  r= bind(fd, sa, al);  if (r) return cht_newfdposixerr(ip,fd,"bind");
+  r= cht_setnonblock(fd, 1);  if (r) return cht_newfdposixerr(ip,fd,"setnonblock");
+
+  sock= TALLOC(sizeof(DgramSocket));
+  sock->ix= -1;
+  sock->fd= fd;
+  sock->addr_buflen= al+1;
+  sock->addr_buf= TALLOC(sock->addr_buflen);
+  sock->msg_buflen= 0;
+  sock->msg_buf= 0;
+  cht_scriptinv_init(&sock->script);
+
+  *sock_r= sock;
+  return TCL_OK;
+}
+
+int cht_do_dgramsocket_transmit(ClientData cd, Tcl_Interp *ip,
+                            void *sock_v, HBytes_Value data,
+                            SockAddr_Value remote) {
+  DgramSocket *sock= sock_v;
+  int l, r;
+
+  r= sendto(sock->fd,
+           cht_hb_data(&data), l=cht_hb_len(&data),
+           0,
+           cht_sockaddr_addr(&remote), cht_sockaddr_len(&remote));
+  if (r==-1) return cht_posixerr(ip,errno,"sendto");
+  else if (r!=l) return cht_staticerr(ip,"sendto gave wrong answer",0);
+  return TCL_OK;
+}
+
+static void cancel(DgramSocket *sock) {
+  if (sock->script.script) {
+    cht_scriptinv_cancel(&sock->script);
+    Tcl_DeleteFileHandler(sock->fd);
+  }
+}
+
+static void recv_call(ClientData sock_cd, int mask) {
+  DgramSocket *sock= (void*)sock_cd;
+  Tcl_Interp *ip= sock->script.ipq;
+  int sz, rc, peek;
+  HBytes_Value message_val;
+  SockAddr_Value peer_val;
+  Tcl_Obj *args[3];
+  struct msghdr mh;
+  struct iovec iov;
+
+  cht_hb_empty(&message_val);
+  cht_sockaddr_clear(&peer_val);
+
+  mh.msg_iov= &iov;
+  mh.msg_iovlen= 1;
+  mh.msg_control= 0;
+  mh.msg_controllen= 0;
+  mh.msg_flags= 0;
+
+  peek= MSG_PEEK;
+  
+  for (;;) {
+    mh.msg_name= sock->addr_buf;
+    mh.msg_namelen= sock->addr_buflen;
+
+    iov.iov_base= sock->msg_buf;
+    iov.iov_len= sock->msg_buflen;
+
+    sz= recvmsg(sock->fd, &mh, peek);
+    if (sz==-1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) rc=0;
+      else rc= cht_posixerr(ip,errno,"recvmsg");
+      goto x_rc;
+    }
+
+    assert(mh.msg_namelen < sock->addr_buflen);
+
+    if (!(mh.msg_flags & MSG_TRUNC)) {
+      if (!peek) break;
+      peek= 0;
+      continue;
+    }
+
+    TFREE(sock->msg_buf);
+    assert(sock->msg_buflen < INT_MAX/4);
+    sock->msg_buflen *= 2;
+    sock->msg_buflen += 100;
+    sock->msg_buf= TALLOC(sock->msg_buflen);
+  }
+
+  cht_hb_array(&message_val, iov.iov_base, sz);
+  cht_sockaddr_create(&peer_val, mh.msg_name, mh.msg_namelen);
+
+  args[0]= cht_ret_hb(ip, message_val);  cht_hb_empty(&message_val);
+  args[1]= cht_ret_sockaddr(ip, peer_val);  cht_sockaddr_clear(&peer_val);
+  args[2]= cht_ret_iddata(ip, sock, &cht_dgram_socks);
+  cht_scriptinv_invoke(&sock->script,3,args);
+
+  rc= 0;
+  
+x_rc:
+  if (rc)
+    Tcl_BackgroundError(ip);
+}
+
+int cht_do_dgramsocket_on_receive(ClientData cd, Tcl_Interp *ip,
+                              void *sock_v, Tcl_Obj *newscript) {
+  DgramSocket *sock= sock_v;
+  int rc;
+  
+  cancel(sock);
+  
+  if (newscript) {
+    rc= cht_scriptinv_set(&sock->script, ip, newscript, 0);
+    if (rc) return rc;
+  }
+  
+  Tcl_CreateFileHandler(sock->fd, TCL_READABLE, recv_call, sock);
+  return TCL_OK;
+}
+
+static void destroy(DgramSocket *sock) {
+  cancel(sock);
+  close(sock->fd); /* nothing useful to be done with errors */
+  TFREE(sock->addr_buf);
+  TFREE(sock->msg_buf);
+  TFREE(sock);
+}
+
+static void destroy_idtabcb(Tcl_Interp *ip, void *sock_v) {
+  destroy(sock_v);
+}
+
+int cht_do_dgramsocket_close(ClientData cd, Tcl_Interp *ip, void *sock_v) {
+  cht_tabledataid_disposing(ip,sock_v,&cht_dgram_socks);
+  destroy(sock_v);
+  return TCL_OK;
+}
+
+const IdDataSpec cht_dgram_socks= {
+  "dgramsock", "dgramsock-table", destroy_idtabcb
+};
diff --git a/dgram/dgram.h b/dgram/dgram.h
new file mode 100644 (file)
index 0000000..6c31f18
--- /dev/null
@@ -0,0 +1,47 @@
+/* dgram - Tcl extension for udp datagrams
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DGRAM_H
+#define DGRAM_H
+
+#include "hbytes.h"
+
+/* from sockaddr.c */
+
+typedef struct {
+  Byte *begin, *end;
+} SockAddr_Value;
+
+extern Tcl_ObjType sockaddr_type;
+
+void cht_sockaddr_clear(SockAddr_Value*);
+void cht_sockaddr_create(SockAddr_Value*, const struct sockaddr *addr, int al);
+int cht_sockaddr_len(const SockAddr_Value*);
+const struct sockaddr *cht_sockaddr_addr(const SockAddr_Value*);
+void cht_sockaddr_free(const SockAddr_Value*);
+
+/* from dgram.c */
+
+extern const IdDataSpec cht_dgram_socks;
+
+/* from misc.c */
+
+int cht_setnonblock(int fd, int isnonblock);
+
+#include "dgram+tcmdif.h"
+
+#endif /*DGRAM_H*/
diff --git a/dgram/dgram.tct b/dgram/dgram.tct
new file mode 100644 (file)
index 0000000..119e354
--- /dev/null
@@ -0,0 +1,37 @@
+# dgram - Tcl extension for udp datagrams
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Type sockaddr:                 SockAddr_Value @
+Init sockaddr                  cht_sockaddr_clear(&@);
+
+Table *dgramsockettoplevel TopLevel_Command
+       dgram-socket
+               dispatch(DgramSocket/_SubCommand,"dgram-socket subcommand")
+
+Table dgramsocket DgramSocket_SubCommand
+       create
+               local   sockaddr
+               =>      iddata(&cht_dgram_socks)
+       close
+               sock    iddata(&cht_dgram_socks)
+       transmit
+               sock    iddata(&cht_dgram_socks)
+               data    hb
+               remote  sockaddr
+       on-receive
+               sock    iddata(&cht_dgram_socks)
+               ?script obj
diff --git a/dgram/hook.c b/dgram/hook.c
new file mode 100644 (file)
index 0000000..6f5a157
--- /dev/null
@@ -0,0 +1,21 @@
+/* dgram - Tcl extension for udp datagrams
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dgram.h"
+
+CHT_INIT(dgram, CHTI_TYPE(sockaddr_type),
+        CHTI_COMMANDS(cht_dgramsockettoplevel_entries))
diff --git a/dgram/misc.c b/dgram/misc.c
new file mode 100644 (file)
index 0000000..8c60633
--- /dev/null
@@ -0,0 +1,14 @@
+/**/
+
+#include "dgram.h"
+
+int cht_setnonblock(int fd, int isnonblock) {
+  int r;
+
+  r= fcntl(fd,F_GETFL); 
+  if (r==-1) return -1;
+  r= fcntl(fd,F_SETFL, isnonblock ? r|O_NONBLOCK : r&~O_NONBLOCK);
+  if (r==-1) return -1;
+  return 0;
+}
+
diff --git a/dgram/sockaddr.c b/dgram/sockaddr.c
new file mode 100644 (file)
index 0000000..7373629
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * struct sockaddr
+ *
+ * syntaxes:
+ *    ddd.ddd.ddd.ddd,nnnn       IPv4 address and port
+ *                                 ... host or port may be `*'
+ *    /abs/path/to/socket        AF_UNIX
+ *    ./rel/path/to/socket       AF_UNIX
+ */
+
+#include "dgram.h"
+
+#define SOCKADDR_LEN(sa) ((sa)->end - (sa)->begin)
+
+/* parsing */
+
+int cht_pat_sockaddr(Tcl_Interp *ip, Tcl_Obj *o, SockAddr_Value *val) {
+  int rc;
+
+  rc= Tcl_ConvertToType(ip,o,&sockaddr_type);
+  if (rc) return rc;
+
+  *val= *OBJ_SOCKADDR(o);
+  return TCL_OK;
+}
+  
+Tcl_Obj *cht_ret_sockaddr(Tcl_Interp *ip, SockAddr_Value val) {
+  Tcl_Obj *o;
+
+  o= Tcl_NewObj();
+  Tcl_InvalidateStringRep(o);
+  *OBJ_SOCKADDR(o)= val;
+  o->typePtr= &sockaddr_type;
+  return o;
+}
+
+/* native type methods */
+
+void cht_sockaddr_clear(SockAddr_Value *v) { v->begin= v->end= 0; }
+
+void cht_sockaddr_create(SockAddr_Value *v, const struct sockaddr *a, int al) {
+  Byte *begin;
+  
+  v->begin= begin= TALLOC(al);
+  memcpy(begin, a, al);
+  v->end= begin + al;
+}  
+
+int cht_sockaddr_len(const SockAddr_Value *v) {
+  return SOCKADDR_LEN(v);
+}
+
+const struct sockaddr *cht_sockaddr_addr(const SockAddr_Value *v) {
+  return (const void*)v->begin;
+}
+  
+void cht_sockaddr_free(const SockAddr_Value *v) {
+  TFREE(v->begin);
+}
+
+/* Sockaddr Tcl type */
+
+static void sockaddr_t_dup(Tcl_Obj *src, Tcl_Obj *dup) {
+  cht_sockaddr_create(OBJ_SOCKADDR(dup),
+                 cht_sockaddr_addr(OBJ_SOCKADDR(src)),
+                 cht_sockaddr_len(OBJ_SOCKADDR(src)));
+  dup->typePtr= &cht_hbytes_type;
+}
+
+static void sockaddr_t_free(Tcl_Obj *o) {
+  cht_sockaddr_free(OBJ_SOCKADDR(o));
+}
+
+static void sockaddr_t_ustr(Tcl_Obj *o) {
+  const struct sockaddr *sa;
+  char i46buf[INET6_ADDRSTRLEN], portbuf[50];
+  const struct sockaddr_in *sin;
+  int al;
+  const char *string, *prepend;
+  
+  sa= cht_sockaddr_addr(OBJ_SOCKADDR(o));
+  al= cht_sockaddr_len(OBJ_SOCKADDR(o));
+    
+  switch (sa->sa_family) {
+  case AF_INET:
+  case AF_INET6:
+    assert(sizeof(i46buf) >= INET_ADDRSTRLEN);
+    assert(al >= sizeof(struct sockaddr_in));
+    sin= (const void*)sa;
+    inet_ntop(sa->sa_family, &sin->sin_addr, i46buf, al);
+    snprintf(portbuf,sizeof(portbuf),",%d",(int)ntohs(sin->sin_port));
+    prepend= i46buf;
+    string= portbuf;
+    break;
+
+  case AF_UNIX:
+    string= ((const struct sockaddr_un*)sa)->sun_path;
+    prepend= "";
+    if (!string[0]) string="//"; 
+    else if (string[0] != '/' || string[1] == '/') prepend= "./";
+    break;
+
+  default: /* ouch ! */
+    cht_obj_updatestr_array_prefix(o,(const void*)sa,al,"?");
+    return;
+  }
+
+  cht_obj_updatestr_vstringls(o,
+                         prepend, strlen(prepend),
+                         string, strlen(string),
+                         (char*)0);
+}
+
+static int sockaddr_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  union {
+    struct sockaddr_un sun;
+    struct sockaddr_in sin;
+  } s;
+  unsigned long port_l;
+  
+  char *str, *ep, *copy;
+  int sl, pl, iprv;
+  const char *comma, *path;
+
+  str= Tcl_GetStringFromObj(o,0);  assert(str);
+  cht_objfreeir(o);
+  memset(&s,0,sizeof(s));
+
+  if (str[0]=='/' || (str[0]=='.' && str[1]=='/')) {
+    
+    sl= sizeof(s.sun);
+    s.sun.sun_family= AF_UNIX;
+
+    if (!strcmp(str,"//")) path= "";
+    else if (!memcmp(str,"./",2) && str[2]) path= str+2;
+    else { assert(str[0]=='/' && str[1]!='/'); path=str; }
+
+    if (strlen(str) >= sizeof(s.sun.sun_path))
+      return cht_staticerr(ip, "AF_UNIX path too long", "SOCKADDR AFUNIX LENGTH");
+
+    strcpy(s.sun.sun_path, path);
+
+  } else if ((comma= strchr(str, ','))) {
+
+    sl= sizeof(s.sin);
+    s.sin.sin_family= AF_INET;
+
+    pl= comma - str;
+    copy= TALLOC(pl+1);
+    memcpy(copy, str, pl);
+    copy[pl]= 0;
+
+    if (!strcmp(copy,"*")) {
+      s.sin.sin_addr.s_addr= INADDR_ANY;
+      iprv= 1;
+    } else {
+      iprv= inet_pton(AF_INET, copy, &s.sin.sin_addr);
+    }
+    TFREE(copy);
+
+    if (!iprv)
+      return cht_staticerr(ip, "bad IPv4 address syntax", "SOCKADDR SYNTAX IPV4");
+
+    comma++;
+    if (!strcmp(comma,"*")) {
+      s.sin.sin_port= 0;
+    } else {
+      errno=0; port_l=strtoul(comma,&ep,10);
+      if (errno || *ep)
+       return cht_staticerr(ip, "bad IPv4 port", "SOCKADDR SYNTAX IPV4");
+      if (port_l > 65535)
+       return cht_staticerr(ip, "IPv4 port out of range", "SOCKADDR SYNTAX IPV4");
+      s.sin.sin_port= htons(port_l);
+    }
+
+  } else {
+
+    return cht_staticerr(ip, "bad socket address syntax", "SOCKADDR SYNTAX OTHER");
+
+  }
+
+  cht_sockaddr_create(OBJ_SOCKADDR(o), (void*)&s, sl);
+  
+  o->typePtr = &sockaddr_type;
+  return TCL_OK;
+}
+
+Tcl_ObjType sockaddr_type = {
+  "sockaddr",
+  sockaddr_t_free, sockaddr_t_dup, sockaddr_t_ustr, sockaddr_t_sfa
+};
diff --git a/hbytes/Makefile b/hbytes/Makefile
new file mode 100644 (file)
index 0000000..3a7feeb
--- /dev/null
@@ -0,0 +1,24 @@
+# hbytes - hex-stringrep efficient byteblocks for Tcl
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+BASE_DIR =     ../base
+EXTBASE =      hbytes
+CFILES =       chop hbytes hook parse ulongs
+OTHER_TCTS =   hbytes-base.tct
+
+include ../base/extension.make
+
diff --git a/hbytes/chiark_tcl_hbytes.h b/hbytes/chiark_tcl_hbytes.h
new file mode 100644 (file)
index 0000000..bf3cb57
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef CHIARK_TCL_HBYTES_H
+#define CHIARK_TCL_HBYTES_H
+
+#include "hbytes.h"
+
+typedef struct {
+  Byte *start; /* byl bytes */
+  Tcl_Obj *data; /* may be 0 to mean empty */
+} AddrMap_Entry;
+
+struct AddrMap_Value {
+  int byl, used, space;
+  AddrMap_Entry *entries;
+  /* Entries are sorted by start.  Each entry gives value (or lack of
+   * it) for all A st START <= A < NEXT-START.  Last entry has value
+   * (or lack of it) for all A >= START.  First entry is always
+   * present and always has start all-bits-0. */
+}; /* internalRep.otherValuePtr */
+
+#include "hbytes+tcmdif.h"
+
+#endif /*CHIARK_TCL_HBYTES_H*/
diff --git a/hbytes/chop.c b/hbytes/chop.c
new file mode 100644 (file)
index 0000000..3a17201
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_hbytes.h"
+
+static int strs1(Tcl_Interp *ip, int strc, Tcl_Obj *const *strv, int *l_r) {
+  int rc, l, i, pl;
+
+  l= 0;
+  for (i=1; i<strc; i++) {
+    rc= Tcl_ConvertToType(ip,strv[i],&cht_hbytes_type);
+    if (rc) return rc;
+    pl= cht_hb_len(OBJ_HBYTES(strv[i]));
+    assert(l < INT_MAX/2 && pl < INT_MAX/2);
+    l += pl;
+  }
+  *l_r= l;
+  return TCL_OK;
+}
+
+static void strs2(Byte *dest, int strc, Tcl_Obj *const *strv) {
+  int tl, i;
+  
+  for (i=1; i<strc; i++) {
+    tl= cht_hb_len(OBJ_HBYTES(strv[i]));
+    memcpy(dest, cht_hb_data(OBJ_HBYTES(strv[i])), tl);
+    dest += tl;
+  }
+}
+
+int cht_do_hbytes_prepend(ClientData cd, Tcl_Interp *ip,
+                     HBytes_Var v, int strc, Tcl_Obj *const *strv) {
+  int rc, el;
+  Byte *dest;
+  
+  rc= strs1(ip,strc,strv,&el);  if (rc) return rc;
+  dest= cht_hb_prepend(v.hb, el);
+  strs2(dest, strc,strv);
+  return TCL_OK;
+}
+  
+int cht_do_hbytes_append(ClientData cd, Tcl_Interp *ip,
+                    HBytes_Var v, int strc, Tcl_Obj *const *strv) {
+  int rc, el;
+  Byte *dest;
+
+  rc= strs1(ip,strc,strv,&el);  if (rc) return rc;
+  dest= cht_hb_append(v.hb, el);
+  strs2(dest,  strc,strv);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_concat(ClientData cd, Tcl_Interp *ip,
+                    int strc, Tcl_Obj *const *strv, HBytes_Value *result) {
+  int rc, l;
+  Byte *dest;
+  
+  rc= strs1(ip,strc,strv,&l);  if (rc) return rc;
+  dest= cht_hb_arrayspace(result,l);
+  strs2(dest, strc,strv);
+  return TCL_OK;
+}
+
+static int underrun(Tcl_Interp *ip) {
+  return cht_staticerr(ip,"data underrun","HBYTES LENGTH UNDERRUN");
+}
+
+int cht_do_hbytes_unprepend(ClientData cd, Tcl_Interp *ip,
+                       HBytes_Var v, int preflength, HBytes_Value *result) {
+  const Byte *rdata= cht_hb_unprepend(v.hb, preflength);
+  if (!rdata) return underrun(ip);
+  cht_hb_array(result, rdata, preflength);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_unappend(ClientData cd, Tcl_Interp *ip,
+                      HBytes_Var v, int suflength, HBytes_Value *result) {
+  const Byte *rdata= cht_hb_unappend(v.hb, suflength);
+  if (!rdata) return underrun(ip);
+  cht_hb_array(result, rdata, suflength);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_chopto(ClientData cd, Tcl_Interp *ip,
+                    HBytes_Var v, int newlength, HBytes_Value *result) {
+  int suflength= cht_hb_len(v.hb) - newlength;
+  return cht_do_hbytes_unappend(0,ip,v, suflength, result);
+}
diff --git a/hbytes/hbytes-base.tct b/hbytes/hbytes-base.tct
new file mode 100644 (file)
index 0000000..6c3b17c
--- /dev/null
@@ -0,0 +1,27 @@
+# hbytes - hex-stringrep efficient byteblocks for Tcl
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Type hb:                       HBytes_Value @
+Init hb                                cht_hb_sentinel(&@);
+
+Type hbv:                      HBytes_Var @
+Init hbv                       @.hb=0; cht_init_somethingv(&@.sth);
+Fini hbv                       cht_fini_somethingv(ip, rc, &@.sth);
+
+Type addrmapv:                 AddrMap_Var @
+Init addrmapv                  @.am=0; cht_init_somethingv(&@.sth);
+Fini addrmapv                  cht_fini_somethingv(ip, rc, &@.sth);
diff --git a/hbytes/hbytes.c b/hbytes/hbytes.c
new file mode 100644 (file)
index 0000000..dc1c19b
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "hbytes.h"
+
+#define COMPLEX(hb) ((HBytes_ComplexValue*)hb->begin_complex)
+#define SIMPLE_LEN(hb) ((Byte*)(hb)->end_0 - (Byte*)(hb)->begin_complex)
+
+/* enquirers */
+
+int cht_hb_len(const HBytes_Value *hb) {
+  if (HBYTES_ISEMPTY(hb)) return 0;
+  else if (HBYTES_ISCOMPLEX(hb)) return COMPLEX(hb)->len;
+  else return SIMPLE_LEN(hb);
+}
+
+Byte *cht_hb_data(const HBytes_Value *hb) {
+  if (HBYTES_ISEMPTY(hb)) return 0;
+  else if (HBYTES_ISCOMPLEX(hb)) return COMPLEX(hb)->dstart;
+  else return hb->begin_complex;
+}
+
+int cht_hb_issentinel(const HBytes_Value *hb) {
+  return HBYTES_ISSENTINEL(hb);
+}
+
+/* constructors */
+
+void cht_hb_empty(HBytes_Value *returns) {
+  returns->begin_complex= returns->end_0= 0;
+}
+
+void cht_hb_sentinel(HBytes_Value *returns) {
+  returns->begin_complex= 0;
+  returns->end_0= (void*)&cht_hbytes_type;
+}
+
+Byte *cht_hb_arrayspace(HBytes_Value *returns, int l) {
+  if (!l) { cht_hb_empty(returns); return 0; }
+  returns->begin_complex= TALLOC(l);
+  returns->end_0= returns->begin_complex + l;
+  return returns->begin_complex;
+}
+  
+void cht_hb_array(HBytes_Value *returns, const Byte *array, int l) {
+  memcpy(cht_hb_arrayspace(returns,l), array, l);
+}
+
+/* destructor */
+
+void cht_hb_free(const HBytes_Value *frees) {
+  if (HBYTES_ISCOMPLEX(frees)) {
+    HBytes_ComplexValue *cx= COMPLEX(frees);
+    TFREE(cx->dstart - cx->prespace);
+  }
+  TFREE(frees->begin_complex);
+}
+
+/* mutators */
+
+static HBytes_ComplexValue *complex(HBytes_Value *hb) {
+  HBytes_ComplexValue *cx;
+
+  if (HBYTES_ISCOMPLEX(hb)) return hb->begin_complex;
+
+  cx= TALLOC(sizeof(*cx));
+  cx->dstart= hb->begin_complex;
+  cx->len= cx->avail= SIMPLE_LEN(hb);
+  cx->prespace= 0;
+
+  hb->begin_complex= cx;
+  hb->end_0= 0;
+
+  return cx;
+}
+
+Byte *cht_hb_prepend(HBytes_Value *hb, int el) {
+  HBytes_ComplexValue *cx;
+  int new_prespace;
+  Byte *old_block, *new_block, *new_dstart;
+
+  cx= complex(hb);
+
+  assert(el < INT_MAX/4 && cx->len < INT_MAX/2);
+  
+  if (cx->prespace < el) {
+    new_prespace= el*2 + cx->len;
+    old_block= cx->dstart - cx->prespace;
+    new_block= Tcl_Realloc(old_block, new_prespace + cx->avail);
+    new_dstart= new_block + new_prespace;
+    memmove(new_dstart, new_block + cx->prespace, cx->len);
+    cx->prespace= new_prespace;
+    cx->dstart= new_dstart;
+  }
+  cx->dstart -= el;
+  cx->prespace -= el;
+  cx->len += el;
+  cx->avail += el;
+  return cx->dstart;
+}
+
+Byte *cht_hb_append(HBytes_Value *hb, int el) {
+  HBytes_ComplexValue *cx;
+  int new_len, new_avail;
+  Byte *newpart, *new_block, *old_block;
+
+  cx= complex(hb);
+  assert(el < INT_MAX/4 && cx->len < INT_MAX/4);
+
+  new_len= cx->len + el;
+  if (new_len > cx->avail) {
+    new_avail= new_len*2;
+    old_block= cx->dstart - cx->prespace;
+    new_block= Tcl_Realloc(old_block, cx->prespace + new_avail);
+    cx->dstart= new_block + cx->prespace;
+    cx->avail= new_avail;
+  }
+  newpart= cx->dstart + cx->len;
+  cx->len= new_len;
+  return newpart;
+}
+
+static HBytes_ComplexValue*
+prechop(HBytes_Value *hb, int cl, const Byte **rv) {
+  HBytes_ComplexValue *cx;
+
+  if (cl<0) { *rv=0; return 0; }
+  if (cl==0) { *rv= (const void*)&cht_hbytes_type; return 0; }
+  
+  cx= complex(hb);
+  if (cl > cx->len) { *rv=0; return 0; }
+  return cx;
+}
+
+const Byte *cht_hb_unprepend(HBytes_Value *hb, int pl) {
+  const Byte *chopped;
+  HBytes_ComplexValue *cx= prechop(hb,pl,&chopped);
+  if (!cx) return chopped;
+
+  chopped= cx->dstart;
+  cx->dstart += pl;
+  cx->prespace += pl;
+  cx->len -= pl;
+  cx->avail -= pl;
+  return chopped;
+}
+
+const Byte *cht_hb_unappend(HBytes_Value *hb, int sl) {
+  const Byte *chopped;
+  HBytes_ComplexValue *cx= prechop(hb,sl,&chopped);
+  if (!cx) return chopped;
+
+  cx->len -= sl;
+  return cx->dstart + cx->len;
+}
+
+void memxor(Byte *dest, const Byte *src, int l) {
+  while (l--) *dest++ ^= *src++;
+}
diff --git a/hbytes/hbytes.h b/hbytes/hbytes.h
new file mode 100644 (file)
index 0000000..0ed871c
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ *  hbytes raw2h BINARY                          => hex
+ *  hbytes h2raw HEX                             => binary
+ *
+ *  hbytes length VALUE                          => count
+ *  hbytes prepend VAR [VALUE ...]         = set VAR [concat VALUE ... $VAR]
+ *  hbytes append VAR [VALUE ...]          = set VAR [concat $VAR VALUE ...]
+ *  hbytes concat VAR [VALUE ...]          = set VAR [concat VALUE ...]
+ *  hbytes unprepend VAR PREFIXLENGTH            => prefix (removed from VAR)
+ *  hbytes unappend VAR SUFFIXLENGTH             => suffix (removed from VAR)
+ *  hbytes chopto VAR NEWVARLENGTH               => suffix (removed from VAR)
+ *                                                  (too short? error)
+ *
+ *  hbytes range VALUE START SIZE                => substring (or error)
+ *  hbytes overwrite VAR START VALUE
+ *  hbytes trimleft VAR                         removes any leading 0 octets
+ *  hbytes repeat VALUE COUNT                    => COUNT copies of VALUE
+ *  hbytes zeroes COUNT                          => COUNT zero bytes
+ *  hbytes random COUNT                          => COUNT random bytes
+ *  hbytes xor VAR VALUE                         $VAR (+)= VALUE
+ *
+ *  hbytes ushort2h LONG           => LONG must be <2^16, returns as hex
+ *  hbytes h2ushort HEX            => |HEX| must be 2 bytes, returns as ulong
+ *
+ *  hbytes compare A B
+ *      =>  -2   A is lexically earlier than B and not a prefix of B  (A<B)
+ *          -1   A is prefix of B but not equal                       (A<B)
+ *           0   A == B
+ *          +1   A is B plus a nonempty suffix (ie, A has B as a prefix)
+ *          +2   A is lexically later than B and does not have B as a prefix
+ *
+ *  hbytes pad pa|ua VAR ALG METH [METHARGS]       => worked? (always 1 for p)
+ *  hbytes pad pn|un VAR BS METH [METHARGS]        => worked? (always 1 for p)
+ *  hbytes pad pa|pn VAR ALG|BS pkcs5              => 1
+ *  hbytes pad ua|un VAR ALG|BS pkcs5              => worked?
+ *  hbytes pad pa|pn VAR ALG|BS rfc2406 NXTHDR     => 1
+ *  hbytes pad ua|un VAR ALG|BS rfc2406 NXTHDRVAR  => worked?
+ *
+ *  hbytes blockcipher d|e VAR ALG KEY MODE [IV] => IV
+ *  hbytes blockcipher mac MSG ALG KEY MODE IV   => final block
+ *  hbytes blockcipher prop PROPERTY ALG         => property value
+ *
+ *  hbytes hash ALG MESSAGE                      => hash
+ *  hbytes hmac ALG MESSAGE KEY [MACLENGTH]      => mac
+ *  hbytes hash-prop PROPERTY ALG                => property value
+ *
+ *  ulong ul2int ULONG    => INT           can fail if >INT_MAX
+ *  ulong int2ul INT      => ULONG         can fail if <0
+ *  ulong mask A B                         => A & B
+ *  ulong add A B                          => A + B  (mod 2^32)
+ *  ulong subtract A B                     => A - B  (mod 2^32)
+ *  ulong compare A B                      => 0  -1 (A<B)  +1 (A>B)
+ *  ulong shift l|r ULONG BITS             fails if BITS >32
+ *
+ *  ulong ul2bitfields VALUE [SIZE TYPE [TYPE-ARG...] ...]  => 0/1
+ *  ulong bitfields2ul BASE  [SIZE TYPE [TYPE-ARG...] ...]  => ULONG
+ *      goes from left (MSbit) to right (LSbit) where
+ *            SIZE is size in bits
+ *            TYPE [TYPE-ARGS...] is as below
+ *               zero
+ *               ignore
+ *               fixed ULONG-VALUE
+ *               uint VARNAME/VALUE         (VARNAME if ul2bitfields;
+ *               ulong VARNAME/VALUE         VALUE if bitfields2ul)
+ *
+ * Address ranges (addrmap.c):
+ *
+ *  An address range is a slightly efficient partial mapping from
+ *  addresses to arbitrary data values.  An address is a number of
+ *  octets expressed as an hbytes.  All the addresses covered by the
+ *  same addrmap should have the same length.
+ *
+ *  hbytes addr-map lookup MAP-VAR ADDRESS [DEFAULT]   => DATA
+ *     Error on missing default or if any prefix longer than ADDRESS.
+ *
+ *  hbytes addr-map amend-range MAP-VAR START END DATA
+ *  hbytes addr-map amend-mask MAP-VAR PREFIX PREFIX-LENGTH DATA
+ *     Sets all of the addresses in PREFIX/PREFIX-LENGTH to the
+ *     relevant value.
+ *
+ *  Representation:
+ *     An address map MAP is
+ *           [list BIT-LENGTH                           \
+ *                 [list START END DATA-VALUE]          \
+ *                 [list START' END' DATA-VALUE']       \
+ *                 ...
+ *            ]
+ *     The list is sorted by ascending START and entries do not overlap.
+ *     START and END are both inclusive.  BIT-LENGTH is in usual Tcl
+ *     integer notation and must be a multiple of 8.
+ *
+ * Error codes
+ *
+ * HBYTES BLOCKCIPHER CRYPTFAIL CRYPT  block cipher mode failed somehow (!)
+ * HBYTES BLOCKCIPHER CRYPTFAIL MAC    HMAC failed somehow (!)
+ * HBYTES BLOCKCIPHER LENGTH           block cipher input has unsuitable length
+ * HBYTES BLOCKCIPHER PARAMS           key or iv not suitable
+ * HBYTES HMAC PARAMS                  key, input or output size not suitable
+ * HBYTES LENGTH OVERRUN               block too long
+ * HBYTES LENGTH RANGE                 input length or offset is -ve or silly
+ * HBYTES LENGTH UNDERRUN              block too short (or offset too big)
+ * HBYTES LENGTH MISMATCH              when blocks must be exactly same length
+ * HBYTES SYNTAX                       supposed hex block had wrong syntax
+ * HBYTES VALUE OVERFLOW               value to be conv'd to hex too big/long
+ * HBYTES ADDRMAP NOMATCH              no addr/mask matches address for lookup
+ * HBYTES ADDRMAP UNDERRUN             addr for lookup or amend is too short
+ * HBYTES ADDRMAP OVERRUN              addr for lookup or amend is too long
+ * HBYTES ADDRMAP EXCLBITS             amend-mask 1-bits outside prefix len
+ * HBYTES ADDRMAP BADRANGE             amend-range start > end
+ * HBYTES ADDRMAP VALUE                addr-map string value is erroneous
+ * SOCKADDR AFUNIX LENGTH              path for AF_UNIX socket too long
+ * SOCKADDR SYNTAX IPV4                bad IPv4 socket address &/or port
+ * SOCKADDR SYNTAX OTHER               bad socket addr, couldn't tell what kind
+ * ULONG BITCOUNT NEGATIVE             -ve bitcount specified where not allowed
+ * ULONG BITCOUNT OVERRUN              attempt to use more than 32 bits
+ * ULONG BITCOUNT UNDERRUN             bitfields add up to less than 32
+ * ULONG VALUE NEGATIVE                attempt convert -ve integers to ulong
+ * ULONG VALUE OVERFLOW                converted value does not fit in result
+ * TUNTAP IFNAME LENGTH                tun/tap interface name too long
+ * TUNTAP MTU OVERRUN                  tun/tap mtu limited to 2^16 bytes
+ *
+ * Refs: HMAC: RFC2104
+ */
+/* ---8<--- end of documentation comment --8<-- */
+
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef HBYTES_H
+#define HBYTES_H
+
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <string.h>
+
+#include "chiark-tcl.h"
+
+/* from hbytes.c */
+
+int Hbytes_Init(Tcl_Interp *ip); /* called by Tcl's "load" */
+
+/* Internal representation details: */
+#define HBYTES_ISEMPTY(hb)    (!(hb)->begin_complex && !(hb)->end_0)
+#define HBYTES_ISSENTINEL(hb) (!(hb)->begin_complex && (hb)->end_0)
+#define HBYTES_ISSIMPLE(hb)   ((hb)->begin_complex && (hb)->end_0)
+#define HBYTES_ISCOMPLEX(hb)  ((hb)->begin_complex && !(hb)->end_0)
+
+typedef struct {
+  void *begin_complex, *end_0;
+} HBytes_Value; /* overlays internalRep */
+
+typedef struct {
+  Byte *dstart; /* always allocated dynamically */
+  int prespace, len, avail;
+  /*        
+   * | SPARE      | USED  | SPARE |
+   * |<-prespace->|<-len->|       |
+   * |            |<----avail---->|
+   *              ^start
+   */
+} HBytes_ComplexValue; /* pointed to from internalRep.otherValuePtr */
+
+void memxor(Byte *dest, const Byte *src, int l);
+extern int Chiark_tcl_hbytes_Init(Tcl_Interp *ip);
+  /* called by load(3tcl) and also by extensions which depend on this one */
+
+/* Public interfaces: */
+
+extern Tcl_ObjType cht_hbytes_type;
+
+int cht_hb_len(const HBytes_Value *v);
+Byte *cht_hb_data(const HBytes_Value *v); /* caller may then modify data! */
+int cht_hb_issentinel(const HBytes_Value *v);
+
+Byte *cht_hb_prepend(HBytes_Value *upd, int el);
+Byte *cht_hb_append(HBytes_Value *upd, int el);
+  /* return value is where to put the data */
+
+const Byte *cht_hb_unprepend(HBytes_Value *upd, int rl);
+const Byte *cht_hb_unappend(HBytes_Value *upd, int rl);
+  /* return value points to the removed data, which remains valid
+   * until next op on the HBytes_Value.  If original value is
+   * shorter than rl or negative, returns 0 and does nothing. */
+
+void cht_hb_empty(HBytes_Value *returns);
+void cht_hb_sentinel(HBytes_Value *returns);
+void cht_hb_array(HBytes_Value *returns, const Byte *array, int l);
+Byte *cht_hb_arrayspace(HBytes_Value *returns, int l);
+void cht_hb_free(const HBytes_Value *frees);
+  /* _empty, _sentinel and _array do not free or read the old value;
+   * _free it first if needed.  _free leaves it garbage, so you
+   * have to call _empty to reuse it.  _arrayspace doesn't fill
+   * the array; you get a pointer and must fill it with data
+   * yourself. */
+
+/* The value made by cht_hb_sentinel should not be passed to
+ * anything except HBYTES_IS..., and cht_hb_free. */
+
+/* from hook.c */
+
+void cht_obj_updatestr_array(Tcl_Obj *o, const Byte *array, int l);
+void cht_obj_updatestr_array_prefix(Tcl_Obj *o, const Byte *byte,
+                               int l, const char *prefix);
+
+/* from parse.c */
+
+typedef struct {
+  HBytes_Value *hb;
+  Something_Var sth;
+} HBytes_Var;
+
+/* from addrmap.c */
+
+typedef struct AddrMap_Value AddrMap_Value;
+
+typedef struct {
+  AddrMap_Value *am;
+  Something_Var sth;
+} AddrMap_Var;
+
+extern Tcl_ObjType cht_addrmap_type;
+
+/* from chop.c */
+  /* only do_... functions declared in tables.h */
+
+/* from ulong.c */
+
+Tcl_ObjType cht_ulong_type;
+
+/* useful macros */
+
+#define OBJ_HBYTES(o) ((HBytes_Value*)&(o)->internalRep.twoPtrValue)
+#define OBJ_SOCKADDR(o) ((SockAddr_Value*)&(o)->internalRep.twoPtrValue)
+
+#endif /*HBYTES_H*/
diff --git a/hbytes/hbytes.tct b/hbytes/hbytes.tct
new file mode 100644 (file)
index 0000000..dc6980e
--- /dev/null
@@ -0,0 +1,132 @@
+# hbytes - hex-stringrep efficient byteblocks for Tcl
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Table *hbytestoplevel TopLevel_Command
+       hbytes
+               dispatch(HBytes/_SubCommand, "hbytes subcommand")
+       ulong
+               dispatch(ULong/_SubCommand, "ulong subcommand")
+
+Table ulong ULong_SubCommand
+       ul2int
+               v       ulong
+               =>      int
+       int2ul
+               v       int
+               =>      ulong
+       mask
+               a       ulong
+               b       ulong
+               =>      ulong
+       add
+               a       ulong
+               b       ulong
+               =>      ulong
+       multiply
+               a       ulong
+               b       ulong
+               =>      ulong
+       subtract
+               a       ulong
+               b       ulong
+               =>      ulong
+       compare
+               a       ulong
+               b       ulong
+               =>      int
+       shift
+               right   charfrom("lr", "shift direction")
+               v       ulong
+               bits    int
+               =>      ulong
+       ul2bitfields
+               value   ulong
+               ...     obj
+               =>      int
+       bitfields2ul
+               base    ulong
+               ...     obj
+               =>      ulong
+
+Table hbytes HBytes_SubCommand
+       raw2h
+               binary  obj
+               =>      hb
+       h2raw
+               hex     hb
+               =>      obj
+       ushort2h
+               value   long
+               =>      hb
+       h2ushort
+               hex     hb
+               =>      long
+       length
+               v       hb
+               =>      int
+       compare
+               a       hb
+               b       hb
+               =>      int
+       range
+               v       hb
+               start   int
+               size    int
+               =>      hb
+       prepend
+               v       hbv
+               ...     str
+       append
+               v       hbv
+               ...     str
+       rep-info
+               v       obj
+               =>      obj
+       concat
+               ...     str
+               =>      hb
+       unprepend
+               v       hbv
+               length  int
+               =>      hb
+       unappend
+               v       hbv
+               length  int
+               =>      hb
+       chopto
+               v       hbv
+               length  int
+               =>      hb
+       overwrite
+               v       hbv
+               start   int
+               sub     hb
+       trimleft
+               v       hbv
+       zeroes
+               length  int
+               =>      hb
+       repeat
+               v       hb
+               count   int
+               =>      hb
+       xor
+               v       hbv
+               d       hb
+       random
+               length  int
+               =>      hb
diff --git a/hbytes/hook.c b/hbytes/hook.c
new file mode 100644 (file)
index 0000000..f570709
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <errno.h>
+
+#include "chiark_tcl_hbytes.h"
+
+int cht_do_hbytes_rep_info(ClientData cd, Tcl_Interp *ip,
+                      Tcl_Obj *obj, Tcl_Obj **result) {
+  const char *tn;
+  int nums[3], i, lnl;
+  Tcl_Obj *objl[4];
+
+  if (obj->typePtr == &cht_hbytes_type) {
+    HBytes_Value *v= OBJ_HBYTES(obj);
+    memset(nums,0,sizeof(nums));
+    nums[1]= cht_hb_len(v);
+  
+    if (HBYTES_ISEMPTY(v)) tn= "empty";
+    else if (HBYTES_ISSENTINEL(v)) tn= "sentinel!";
+    else if (HBYTES_ISSIMPLE(v)) tn= "simple";
+    else {
+      HBytes_ComplexValue *cx= v->begin_complex;
+      tn= "complex";
+      nums[0]= cx->prespace;
+      nums[2]= cx->avail - cx->len;
+    }
+    lnl= 3;
+  } else {
+    tn= "other";
+    lnl= 0;
+  }
+    
+  objl[0]= Tcl_NewStringObj((char*)tn,-1);
+  for (i=0; i<lnl; i++) objl[i+1]= Tcl_NewIntObj(nums[i]);
+  *result= Tcl_NewListObj(lnl+1,objl);
+    
+  return TCL_OK;
+}
+
+static void hbytes_t_dup(Tcl_Obj *src, Tcl_Obj *dup) {
+  cht_hb_array(OBJ_HBYTES(dup),
+              cht_hb_data(OBJ_HBYTES(src)),
+              cht_hb_len(OBJ_HBYTES(src)));
+  dup->typePtr= &cht_hbytes_type;
+}
+
+static void hbytes_t_free(Tcl_Obj *o) {
+  cht_hb_free(OBJ_HBYTES(o));
+}
+
+void cht_obj_updatestr_array_prefix(Tcl_Obj *o, const Byte *byte,
+                               int l, const char *prefix) {
+  char *str;
+  int pl;
+
+  pl= strlen(prefix);
+  assert(l < INT_MAX/2 - 1 - pl);
+  o->length= l*2+pl;
+  str= o->bytes= TALLOC(o->length+1);
+  
+  memcpy(str,prefix,pl);
+  str += pl;
+
+  while (l>0) {
+    sprintf(str,"%02x",*byte);
+    str+=2; byte++; l--;
+  }
+  *str= 0;
+}
+
+void cht_obj_updatestr_array(Tcl_Obj *o, const Byte *byte, int l) {
+  cht_obj_updatestr_array_prefix(o,byte,l,"");
+}
+
+static void hbytes_t_ustr(Tcl_Obj *o) {
+  cht_obj_updatestr_array(o,
+                     cht_hb_data(OBJ_HBYTES(o)),
+                     cht_hb_len(OBJ_HBYTES(o)));
+}
+
+static int hbytes_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  char *str, *ep;
+  Byte *bytes;
+  int l;
+  char cbuf[3];
+
+  if (o->typePtr == &cht_ulong_type) {
+    uint32_t ul;
+
+    ul= htonl(*(const uint32_t*)&o->internalRep.longValue);
+    cht_hb_array(OBJ_HBYTES(o), (const Byte*)&ul, 4);
+
+  } else {
+  
+    str= Tcl_GetStringFromObj(o,&l);  assert(str);
+    cht_objfreeir(o);
+
+    if (l & 1) return cht_staticerr(ip, "hbytes: conversion from hex:"
+                               " odd length in hex", "HBYTES SYNTAX");
+
+    bytes= cht_hb_arrayspace(OBJ_HBYTES(o), l/2);
+
+    cbuf[2]= 0;
+    while (l>0) {
+      cbuf[0]= *str++;
+      cbuf[1]= *str++;
+      *bytes++= strtoul(cbuf,&ep,16);
+      if (ep != cbuf+2) {
+       cht_hb_free(OBJ_HBYTES(o));
+       return cht_staticerr(ip, "hbytes: conversion from hex:"
+                        " bad hex digit", "HBYTES SYNTAX");
+      }
+      l -= 2;
+    }
+
+  }
+
+  o->typePtr = &cht_hbytes_type;
+  return TCL_OK;
+}
+
+Tcl_ObjType cht_hbytes_type = {
+  "hbytes",
+  hbytes_t_free, hbytes_t_dup, hbytes_t_ustr, hbytes_t_sfa
+};
+
+int cht_do_hbytes_raw2h(ClientData cd, Tcl_Interp *ip,
+                   Tcl_Obj *binary, HBytes_Value *result) {
+  const unsigned char *str;
+  int l;
+
+  str= Tcl_GetByteArrayFromObj(binary,&l);
+  cht_hb_array(result, str, l);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_h2raw(ClientData cd, Tcl_Interp *ip,
+                   HBytes_Value hex, Tcl_Obj **result) {
+  *result= Tcl_NewByteArrayObj(cht_hb_data(&hex), cht_hb_len(&hex));
+  return TCL_OK;
+}
+
+int cht_do_hbytes_length(ClientData cd, Tcl_Interp *ip,
+                    HBytes_Value v, int *result) {
+  *result= cht_hb_len(&v);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_random(ClientData cd, Tcl_Interp *ip,
+                    int length, HBytes_Value *result) {
+  Byte *space;
+  int rc;
+  
+  space= cht_hb_arrayspace(result, length);
+  rc= cht_get_urandom(ip, space, length);
+  if (rc) { cht_hb_free(result); return rc; }
+  return TCL_OK;
+}  
+  
+int cht_do_hbytes_overwrite(ClientData cd, Tcl_Interp *ip,
+                       HBytes_Var v, int start, HBytes_Value sub) {
+  int sub_l;
+
+  sub_l= cht_hb_len(&sub);
+  if (start < 0)
+    return cht_staticerr(ip, "hbytes overwrite start -ve",
+                    "HBYTES LENGTH RANGE");
+  if (start + sub_l > cht_hb_len(v.hb))
+    return cht_staticerr(ip, "hbytes overwrite out of range",
+                    "HBYTES LENGTH UNDERRUN");
+  memcpy(cht_hb_data(v.hb) + start, cht_hb_data(&sub), sub_l);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_trimleft(ClientData cd, Tcl_Interp *ip, HBytes_Var v) {
+  const Byte *o, *p, *e;
+  o= p= cht_hb_data(v.hb);
+  e= p + cht_hb_len(v.hb);
+
+  while (p<e && !*p) p++;
+  if (p != o)
+    cht_hb_unprepend(v.hb, p-o);
+
+  return TCL_OK;
+}
+
+int cht_do_hbytes_repeat(ClientData cd, Tcl_Interp *ip,
+                    HBytes_Value sub, int count, HBytes_Value *result) {
+  int sub_l;
+  Byte *data;
+  const Byte *sub_d;
+
+  sub_l= cht_hb_len(&sub);
+  if (count < 0) return cht_staticerr(ip, "hbytes repeat count -ve",
+                                 "HBYTES LENGTH RANGE");
+  if (count > INT_MAX/sub_l) return cht_staticerr(ip, "hbytes repeat too long", 0);
+
+  data= cht_hb_arrayspace(result, sub_l*count);
+  sub_d= cht_hb_data(&sub);
+  while (count) {
+    memcpy(data, sub_d, sub_l);
+    count--; data += sub_l;
+  }
+  return TCL_OK;
+}  
+
+int cht_do_hbytes_xor(ClientData cd, Tcl_Interp *ip,
+                 HBytes_Var v, HBytes_Value d) {
+  int l;
+  Byte *dest;
+  const Byte *source;
+
+  l= cht_hb_len(v.hb);
+  if (cht_hb_len(&d) != l) return
+    cht_staticerr(ip, "hbytes xor lengths do not match", "HBYTES LENGTH MISMATCH");
+
+  dest= cht_hb_data(v.hb);
+  source= cht_hb_data(&d);
+  memxor(dest,source,l);
+  return TCL_OK;
+}
+  
+int cht_do_hbytes_zeroes(ClientData cd, Tcl_Interp *ip,
+                    int length, HBytes_Value *result) {
+  Byte *space;
+  space= cht_hb_arrayspace(result, length);
+  memset(space,0,length);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_compare(ClientData cd, Tcl_Interp *ip,
+                     HBytes_Value a, HBytes_Value b, int *result) {
+  int al, bl, minl, r;
+
+  al= cht_hb_len(&a);
+  bl= cht_hb_len(&b);
+  minl= al<bl ? al : bl;
+
+  r= memcmp(cht_hb_data(&a), cht_hb_data(&b), minl);
+  
+  if (r<0) *result= -2;
+  else if (r>0) *result= +2;
+  else {
+    if (al<bl) *result= -1;
+    else if (al>bl) *result= +1;
+    else *result= 0;
+  }
+  return TCL_OK;
+}
+
+int cht_do_hbytes_range(ClientData cd, Tcl_Interp *ip,
+                   HBytes_Value v, int start, int size,
+                   HBytes_Value *result) {
+  const Byte *data;
+  int l;
+
+  l= cht_hb_len(&v);
+  if (start<0 || size<0)
+    return cht_staticerr(ip,"hbytes range subscript(s) -ve","HBYTES LENGTH RANGE");
+  if (l<start+size)
+    return cht_staticerr(ip, "hbytes range subscripts too big",
+                    "HBYTES LENGTH UNDERRUN");
+
+  data= cht_hb_data(&v);
+  cht_hb_array(result, data+start, size);
+  return TCL_OK;
+}
+
+/* hbytes representing uint16_t's */
+
+int cht_do_hbytes_h2ushort(ClientData cd, Tcl_Interp *ip,
+                      HBytes_Value hex, long *result) {
+  const Byte *data;
+  int l;
+
+  l= cht_hb_len(&hex);
+  if (l>2)
+    return cht_staticerr(ip, "hbytes h2ushort input more than 4 hex digits",
+                    "HBYTES VALUE OVERFLOW");
+
+  data= cht_hb_data(&hex);
+  *result= data[l-1] | (l>1 ? data[0]<<8 : 0);
+  return TCL_OK;
+}
+
+int cht_do_hbytes_ushort2h(ClientData cd, Tcl_Interp *ip,
+                      long input, HBytes_Value *result) {
+  uint16_t us;
+
+  if (input > 0x0ffff)
+    return cht_staticerr(ip, "hbytes ushort2h input >2^16",
+                    "HBYTES VALUE OVERFLOW");
+
+  us= htons(input);
+  cht_hb_array(result,(const Byte*)&us,2);
+  return TCL_OK;
+}
+
+/* toplevel functions */
+
+CHT_INIT(hbytes,
+        CHTI_TYPE(cht_hbytes_type) CHTI_TYPE(cht_ulong_type),
+        CHTI_COMMANDS(cht_hbytestoplevel_entries))
diff --git a/hbytes/parse.c b/hbytes/parse.c
new file mode 100644 (file)
index 0000000..c384eb4
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_hbytes.h"
+
+int cht_pat_hbv(Tcl_Interp *ip, Tcl_Obj *var, HBytes_Var *agg) {
+  int rc;
+  rc= cht_pat_somethingv(ip,var,&agg->sth,&cht_hbytes_type);
+  if (rc) return rc;
+  agg->hb= OBJ_HBYTES(agg->sth.obj);
+  return TCL_OK;
+}
+int cht_pat_hb(Tcl_Interp *ip, Tcl_Obj *obj, HBytes_Value *val) {
+  int rc;
+  rc= Tcl_ConvertToType(ip,obj,&cht_hbytes_type);  if (rc) return rc;
+  *val= *OBJ_HBYTES(obj);
+  return TCL_OK;
+}
+
+Tcl_Obj *cht_ret_hb(Tcl_Interp *ip, HBytes_Value val) {
+  Tcl_Obj *obj;
+  obj= Tcl_NewObj();
+  Tcl_InvalidateStringRep(obj);
+  *OBJ_HBYTES(obj)= val;
+  obj->typePtr= &cht_hbytes_type;
+  return obj;
+}
diff --git a/hbytes/ulongs.c b/hbytes/ulongs.c
new file mode 100644 (file)
index 0000000..16e3050
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * hbytes - hex-stringrep efficient byteblocks for Tcl
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_hbytes.h"
+
+/* nice simple functions */
+
+int cht_do_ulong_int2ul(ClientData cd, Tcl_Interp *ip, int v,
+                   uint32_t *result) {
+  if (v<0) return cht_staticerr(ip,"cannot convert"
+      " -ve integer to ulong","ULONG VALUE NEGATIVE");
+  *result= v;
+  return TCL_OK;
+}
+  
+int cht_do_ulong_add(ClientData cd, Tcl_Interp *ip,
+                uint32_t a, uint32_t b, uint32_t *result) {
+  *result= a + b;
+  return TCL_OK;
+}
+  
+int cht_do_ulong_multiply(ClientData cd, Tcl_Interp *ip,
+                     uint32_t a, uint32_t b, uint32_t *result) {
+  *result= a * b;
+  return TCL_OK;
+}
+  
+int cht_do_ulong_subtract(ClientData cd, Tcl_Interp *ip,
+                     uint32_t a, uint32_t b, uint32_t *result) {
+  *result= a - b;
+  return TCL_OK;
+}
+  
+int cht_do_ulong_compare(ClientData cd, Tcl_Interp *ip,
+                    uint32_t a, uint32_t b, int *result) {
+  *result=
+    a == b ? 0 :
+    a < b ? -1 : 1;
+  return TCL_OK;
+}
+
+int cht_do_ulong_ul2int(ClientData cd, Tcl_Interp *ip,
+                   uint32_t v, int *result) {
+  if (v>INT_MAX) return
+                  cht_staticerr(ip,"ulong too large"
+                                " to fit in an int", "ULONG VALUE OVERFLOW");
+  *result= v;
+  return TCL_OK;
+}
+
+int cht_do_ulong_mask(ClientData cd, Tcl_Interp *ip,
+                 uint32_t a, uint32_t b, uint32_t *result) {
+  *result= a & b;
+  return TCL_OK;
+}
+  
+int cht_do_ulong_shift(ClientData cd, Tcl_Interp *ip, int right,
+                  uint32_t v, int bits, uint32_t *result) {
+  if (bits < 0) { bits= -bits; right= !right; }
+  if (bits > 32) return cht_staticerr(ip,"shift out of range (32) bits",
+                                     "ULONG BITCOUNT OVERRUN");
+  *result= (bits==32 ? 0 :
+           right ? v >> bits : v << bits);
+  return TCL_OK;
+}
+
+/* bitfields */
+
+typedef struct {
+  const char *name;
+  int want_arg;
+  int (*reader_writer[2])(Tcl_Interp *ip, uint32_t *value_io,
+                         int *ok_io, Tcl_Obj *arg);
+} BitFieldType;
+
+static int bf_zero_read(Tcl_Interp *ip, uint32_t *value_io,
+                       int *ok_io, Tcl_Obj *arg) {
+  if (*value_io) *ok_io= 0;
+  return TCL_OK;
+}
+
+static int bf_zero_write(Tcl_Interp *ip, uint32_t *value_io,
+                        int *ok_io, Tcl_Obj *arg) {
+  *value_io= 0;
+  return TCL_OK;
+}
+
+static int bf_ignore(Tcl_Interp *ip, uint32_t *value_io,
+                    int *ok_io, Tcl_Obj *arg) {
+  return TCL_OK;
+}
+
+static int bf_fixed_read(Tcl_Interp *ip, uint32_t *value_io,
+                        int *ok_io, Tcl_Obj *arg) {
+  uint32_t ul;
+  int rc;
+  
+  rc= cht_pat_ulong(ip, arg, &ul);  if (rc) return rc;
+  if (*value_io != ul) *ok_io= 0;
+  return TCL_OK;
+}
+
+static int bf_ulong_write(Tcl_Interp *ip, uint32_t *value_io,
+                         int *ok_io, Tcl_Obj *arg) {
+  uint32_t ul;
+  int rc;
+  
+  rc= cht_pat_ulong(ip, arg, &ul);  if (rc) return rc;
+  *value_io= ul;
+  return TCL_OK;
+}
+
+static int bf_var_read(Tcl_Interp *ip, Tcl_Obj *varname, Tcl_Obj *val) {
+  Tcl_Obj *rp;
+  rp= Tcl_ObjSetVar2(ip,varname,0,val,TCL_LEAVE_ERR_MSG);
+  if (!rp) return TCL_ERROR;
+  return TCL_OK;
+}
+
+static int bf_ulong_read(Tcl_Interp *ip, uint32_t *value_io,
+                        int *ok_io, Tcl_Obj *arg) {
+  return bf_var_read(ip,arg, cht_ret_ulong(ip,*value_io));
+}
+
+static int bf_uint_write(Tcl_Interp *ip, uint32_t *value_io,
+                        int *ok_io, Tcl_Obj *arg) {
+  int rc, v;
+  rc= cht_pat_int(ip, arg, &v);  if (rc) return rc;
+  if (v<0) return cht_staticerr(ip,"value for bitfield is -ve",
+                               "ULONG VALUE NEGATIVE");
+  *value_io= v;
+  return TCL_OK;
+}
+
+static int bf_uint_read(Tcl_Interp *ip, uint32_t *value_io,
+                       int *ok_io, Tcl_Obj *arg) {
+  if (*value_io > INT_MAX)
+    return cht_staticerr(ip,"value from bitfield"
+                        " exceeds INT_MAX","ULONG VALUE OVERFLOW");
+  return bf_var_read(ip,arg, cht_ret_int(ip,*value_io));
+}
+
+#define BFT(t,a) { #t, a, { bf_read_##t, bf_write_##t } }
+static const BitFieldType bitfieldtypes[]= {
+  { "zero",   0, { bf_zero_read,  bf_zero_write  } },
+  { "ignore", 0, { bf_ignore,     bf_ignore      } },
+  { "fixed",  1, { bf_fixed_read, bf_ulong_write } },
+  { "ulong",  1, { bf_ulong_read, bf_ulong_write } },
+  { "uint",   1, { bf_uint_read,  bf_uint_write  } },
+  { 0 }
+};
+
+static int do_bitfields(Tcl_Interp *ip, int writing, int *ok_r,
+                       uint32_t *value_io,
+                       int objc, Tcl_Obj *const *objv) {
+  const BitFieldType *ftype;
+  Tcl_Obj *arg;
+  int sz, pos, rc;
+  uint32_t value, sz_mask, this_mask, this_field;
+  
+  pos= 32;
+  value= *value_io;
+  *ok_r= 1;
+
+  while (--objc) {
+    rc= Tcl_GetIntFromObj(ip,*++objv,&sz);  if (rc) return rc;
+    if (!--objc)
+      return cht_staticerr(ip,"wrong # args: missing bitfield type",0);
+
+    if (sz<0)
+      return cht_staticerr(ip,"bitfield size is -ve",
+                          "ULONG BITCOUNT NEGATIVE");
+    if (sz>pos)
+      return cht_staticerr(ip,"total size of bitfields >32",
+                          "ULONG BITCOUNT OVERRUN");
+
+    pos -= sz;
+
+    sz_mask= ~(~0UL << sz);
+    this_mask= (sz_mask << pos);
+    this_field= (value & this_mask) >> pos;
+    
+    ftype= enum_lookup_cached(ip,*++objv,bitfieldtypes,"bitfield type");
+    if (!ftype) return TCL_ERROR;
+
+    if (ftype->want_arg) {
+      if (!--objc)
+       return cht_staticerr(ip,"wrong # args: missing arg for bitfield",0);
+      arg= *++objv;
+    } else {
+      arg= 0;
+    }
+    rc= ftype->reader_writer[writing](ip, &this_field, ok_r, arg);
+    if (rc) return rc;
+
+    if (!*ok_r) return TCL_OK;
+
+    if (this_field & ~sz_mask)
+      return cht_staticerr(ip,"bitfield value has more bits than bitfield",
+                      "ULONG VALUE OVERFLOW");
+    
+    value &= ~this_mask;
+    value |= (this_field << pos);
+  }
+
+  if (pos != 0) return
+    cht_staticerr(ip,"bitfield sizes add up to <32","ULONG BITCOUNT UNDERRUN");
+
+  *value_io= value;
+  return TCL_OK;
+}
+
+int cht_do_ulong_bitfields2ul(ClientData cd, Tcl_Interp *ip,
+                         uint32_t base,
+                         int objc, Tcl_Obj *const *objv,
+                         uint32_t *result) {
+  int ok, rc;
+  
+  *result= base;
+  rc= do_bitfields(ip,1,&ok,result,objc,objv);
+  assert(ok);
+  return rc;
+}
+
+int cht_do_ulong_ul2bitfields(ClientData cd, Tcl_Interp *ip,
+                         uint32_t value,
+                         int objc, Tcl_Obj *const *objv,
+                         int *result) {
+  return do_bitfields(ip,0,result,&value,objc,objv);
+}
+
+/* Arg parsing */
+
+int cht_pat_ulong(Tcl_Interp *ip, Tcl_Obj *o, uint32_t *val) {
+  int rc;
+  
+  rc= Tcl_ConvertToType(ip,o,&cht_ulong_type);
+  if (rc) return rc;
+  *val= *(const uint32_t*)&o->internalRep.longValue;
+  return TCL_OK;
+}
+
+Tcl_Obj *cht_ret_ulong(Tcl_Interp *ip, uint32_t val) {
+  Tcl_Obj *o;
+
+  o= Tcl_NewObj();
+  Tcl_InvalidateStringRep(o);
+  *(uint32_t*)&o->internalRep.longValue= val;
+  o->typePtr= &cht_ulong_type;
+  return o;
+}
+
+/* Tcl ulong type */
+
+static void ulong_t_free(Tcl_Obj *o) { }
+
+static void ulong_t_dup(Tcl_Obj *src, Tcl_Obj *dup) {
+  dup->internalRep= src->internalRep;
+  dup->typePtr= &cht_ulong_type;
+}
+
+static void ulong_t_ustr(Tcl_Obj *o) {
+  uint32_t val;
+  char buf[9];
+
+  val= *(const uint32_t*)&o->internalRep.longValue;
+  snprintf(buf,sizeof(buf), "%08lx", (unsigned long)val);
+  cht_obj_updatestr_vstringls(o, buf, sizeof(buf)-1, (char*)0);
+}
+
+static int ulong_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  char *str, *ep;
+  uint32_t ul;
+
+  if (o->typePtr == &cht_hbytes_type) {
+    int l;
+
+    l= cht_hb_len(OBJ_HBYTES(o));
+    if (l > 4) return cht_staticerr(ip,"hbytes as ulong with length >4",
+                                   "HBYTES LENGTH OVERRUN");
+    ul= 0;
+    memcpy((Byte*)&ul + 4 - l, cht_hb_data(OBJ_HBYTES(o)), l);
+    ul= htonl(ul);
+
+  } else {
+
+    str= Tcl_GetString(o);
+    errno=0;
+    if (str[0]=='0' && str[1]=='b' && str[2]) {
+      ul= strtoul(str+2,&ep,2);
+    } else if (str[0]=='0' && str[1]=='d' && str[2]) {
+      ul= strtoul(str+2,&ep,10);
+    } else {
+      ul= strtoul(str,&ep,16);
+    }
+    if (*ep || errno) return cht_staticerr(ip, "bad unsigned long value", 0);
+
+  }
+
+  cht_objfreeir(o);
+  *(uint32_t*)&o->internalRep.longValue= ul;
+  o->typePtr= &cht_ulong_type;
+  return TCL_OK;
+}
+
+Tcl_ObjType cht_ulong_type = {
+  "ulong-nearly",
+  ulong_t_free, ulong_t_dup, ulong_t_ustr, ulong_t_sfa
+};
diff --git a/maskmap/addrmap.c b/maskmap/addrmap.c
new file mode 100644 (file)
index 0000000..8dc08c1
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * maskmap - Tcl extension for address mask map data structures
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "chiark_tcl_hbytes.h"
+
+/*---------- operations on AddrMap_Entry ----------*/
+
+static void ame_free(AddrMap_Entry *ame) {
+  TFREE(ame->start);  ame->start=0;
+  if (ame->data) { Tcl_DecrRefCount(ame->data); ame->data=0; }
+}
+
+static const Byte *ame_parsecheck_addr(Tcl_Interp *ip, const AddrMap_Value *am,
+                                      const HBytes_Value *hb) {
+  int hbl= cht_hb_len(hb);
+  if (hbl < am->byl) {
+    cht_staticerr(ip,"addr-map address too short","HBYTES ADDRMAP UNDERRUN");
+    return 0;
+  }
+  if (hbl > am->byl) {
+    cht_staticerr(ip,"addr-map address too long","HBYTES ADDRMAP OVERRUN");
+    return 0;
+  }
+  return cht_hb_data(hb);
+}
+  
+static int ame_parsecheck_range(Tcl_Interp *ip, const AddrMap_Value *am,
+                               const HBytes_Value *starthb,
+                               const HBytes_Value *endhb,
+                               const Byte *p_r[2]) {
+  p_r[0]= ame_parsecheck_addr(ip,am,starthb);  if (!p_r[0]) return TCL_ERROR;
+  p_r[1]= ame_parsecheck_addr(ip,am,endhb);    if (!p_r[0]) return TCL_ERROR;
+  if (memcmp(p_r[0],p_r[1],am->byl) > 0)
+    return cht_staticerr(ip, "addr-map range start is after end",
+                    "HBYTES ADDRMAP BADRANGE");
+  return TCL_OK;
+}
+  
+static int ame_ba_addsubtractone(Byte *out, const Byte *in, int byl,
+                                unsigned signum, unsigned onoverflow) {
+  /* On entry:
+   *   *in is an array of byl bytes
+   *   signum is 0xff or 0x01
+   *   onoverflow is what counts as overflowed value,
+   *     ie (for unsigned arith) 0x00 for add and 0xff for subtract
+   * On exit:
+   *   *out is the resulting value (subject to overflow truncation)
+   *   return value is TCL_OK, or TCL_ERROR if overflow happened
+   *   (but interpreter result is not set on overflow)
+   */
+  int j;
+      
+  for (j= byl, in += byl, out += byl;
+       in--, out--, j>0;
+       j--) {
+    *out = (*out) + signum;
+    if (*out != onoverflow)
+      return TCL_OK;
+  }
+  return TCL_ERROR;
+}
+
+/*---------- useful operations on AddrMap_Value etc. ----------*/
+
+static void am_init0(AddrMap_Value *am, int byl) {
+  am->byl= byl;
+  am->used= 0;
+  am->space= 0;
+  am->entries= 0;
+}
+
+static void am_reallocentries(AddrMap_Value *am, int len) {
+  AddrMap_Entry *newentries;
+
+  assert(len >= am->space);
+  if (!len) return;
+
+  assert(len < INT_MAX/sizeof(*newentries));
+  newentries= TREALLOC(am->entries, sizeof(*newentries)*len);
+  assert(newentries);
+  
+  am->space= len;
+  am->entries= newentries;
+}
+
+static void am_free(AddrMap_Value *am) {
+  AddrMap_Entry *ame;
+  int i;
+
+  if (!am) return;
+  
+  for (i=0, ame=am->entries; i<am->used; i++, ame++)
+    ame_free(ame);
+
+  TFREE(am->entries);
+  TFREE(am);
+}
+
+/*---------- Tcl type and arg parsing functions ----------*/
+
+int cht_pat_addrmapv(Tcl_Interp *ip, Tcl_Obj *var, AddrMap_Var *agg) {
+  int rc;
+  rc= cht_pat_somethingv(ip,var,&agg->sth,&cht_addrmap_type);
+  if (rc) return rc;
+  agg->am= agg->sth.obj->internalRep.otherValuePtr;
+  return TCL_OK;
+}
+
+static void addrmap_t_free(Tcl_Obj *o) {
+  AddrMap_Value *am= o->internalRep.otherValuePtr;
+  am_free(am);
+}
+
+static void addrmap_t_dup(Tcl_Obj *sob, Tcl_Obj *dob) {
+  AddrMap_Value *sm= sob->internalRep.otherValuePtr;
+  AddrMap_Value *dm;
+  AddrMap_Entry *sme, *dme;
+  int i;
+
+  assert(sob->typePtr == &cht_addrmap_type);
+  cht_objfreeir(dob);
+  dm= TALLOC(sizeof(*dm));
+
+  am_init0(dm,sm->byl);
+  am_reallocentries(dm,sm->used);
+  dm->used= sm->used;
+  for (i=0, sme=sm->entries, dme=dm->entries;
+       i < dm->used;
+       i++, sme++, dme++) {
+    *dme= *sme;
+    dme->start= TALLOC(sm->byl);  assert(dme->start);
+    memcpy(dme->start, sme->start, sm->byl);
+    Tcl_IncrRefCount(dme->data);
+  }
+  dob->internalRep.otherValuePtr= dm;
+  dob->typePtr= &cht_addrmap_type;
+}
+
+static void addrmap_t_ustr(Tcl_Obj *so) {
+  AddrMap_Value *sm= so->internalRep.otherValuePtr;
+  Tcl_Obj **mainlobjsl, *surrogate;
+  AddrMap_Entry *sme;
+  int entnum, listlength;
+
+  assert(so->typePtr == &cht_addrmap_type);
+  mainlobjsl= TALLOC(sizeof(*mainlobjsl) * (sm->used+1));  assert(mainlobjsl);
+  mainlobjsl[0]= Tcl_NewIntObj(sm->byl * 8);
+  listlength= 1;
+
+  for (entnum=0, sme=sm->entries; entnum<sm->used; entnum++, sme++) {
+    HBytes_Value hb;
+    Tcl_Obj *subl[3], *sublo;
+
+    if (!sme->data) continue;
+
+    cht_hb_array(&hb, sme->start, sm->byl);
+    subl[0]= cht_ret_hb(0, hb);  assert(subl[0]);
+
+    if (entnum+1 < sm->used) {
+      ame_ba_addsubtractone(cht_hb_arrayspace(&hb, sm->byl),
+                           (sme+1)->start, sm->byl,
+                           /*subtract:*/ 0x0ffu, 0x0ffu);
+    } else {
+      memset(cht_hb_arrayspace(&hb, sm->byl),
+            0x0ffu, sm->byl);
+    }
+
+    subl[1]= cht_ret_hb(0, hb);  assert(subl[1]);
+    subl[2]= sme->data;
+    
+    sublo= Tcl_NewListObj(3,subl);  assert(sublo);
+    mainlobjsl[listlength++]= sublo;
+  }
+  assert(listlength <= sm->used+1);
+  surrogate= Tcl_NewListObj(listlength,mainlobjsl);  assert(surrogate);
+  assert(surrogate);
+  
+  so->bytes= Tcl_GetStringFromObj(surrogate, &so->length);  assert(so->bytes);
+  surrogate->bytes= 0; surrogate->length= 0; /* we stole it */
+}
+
+static AddrMap_Entry *ame_sfa_alloc(AddrMap_Value *am) {
+  AddrMap_Entry *ame;
+  
+  ame= am->entries + am->used;
+
+  am->used++;
+  assert(am->used <= am->space);
+
+  ame->start= TALLOC(am->byl);  assert(ame->start);
+  ame->data= 0;
+  return ame;
+}
+
+static int addrmap_t_sfa(Tcl_Interp *ip, Tcl_Obj *o) {
+  int rc, inlen, eol, innum, bitlen, cmp;
+  Tcl_Obj *eo, *starto, *endo;
+  HBytes_Value starthb, endhb;
+  const Byte *rangeptrs[2];
+  AddrMap_Value *am;
+  AddrMap_Entry *ame;
+
+  am= TALLOC(sizeof(*am));  assert(am);
+  am_init0(am,0);
+
+  rc= Tcl_ListObjLength(ip,o,&inlen);  if (rc) goto x_badvalue_rc;
+
+  if (inlen<0) {
+    rc= cht_staticerr(ip, "addr-map overall length < 1", 0);
+    goto x_badvalue_rc;
+  }
+
+  rc= Tcl_ListObjIndex(ip,o,0,&eo);  if (rc) goto x_badvalue_rc;
+  rc= Tcl_GetIntFromObj(ip,eo,&bitlen);  if (rc) goto x_badvalue_rc;
+
+  if (bitlen<0 || bitlen % 8) {
+    rc= cht_staticerr(ip, "addr-map overall length < 1", 0);
+    goto x_badvalue_rc;
+  }
+
+  am->byl= bitlen/8;
+  assert(inlen < INT_MAX/2);
+  am_reallocentries(am, (inlen-1)*2+1);
+
+  ame= ame_sfa_alloc(am);
+  memset(ame->start,0,am->byl);
+
+  for (innum=1; innum < inlen; innum++) {
+    rc= Tcl_ListObjIndex(ip,o,innum,&eo);  if (rc) goto x_badvalue_rc;
+    rc= Tcl_ListObjLength(ip,eo,&eol);  if (rc) goto x_badvalue_rc;
+
+    if (eol != 3) {
+      rc= cht_staticerr(ip, "addr-map entry length != 3", 0);
+      goto x_badvalue_rc;
+    }
+    rc= Tcl_ListObjIndex(ip,eo,0,&starto);  if (rc) goto x_badvalue_rc;
+    rc= Tcl_ListObjIndex(ip,eo,1,&endo);    if (rc) goto x_badvalue_rc;
+
+    rc= cht_pat_hb(ip,starto,&starthb);  if (rc) goto x_badvalue_rc;
+    rc= cht_pat_hb(ip,endo,&endhb);  if (rc) goto x_badvalue_rc;
+
+    rc= ame_parsecheck_range(ip,am,&starthb,&endhb,rangeptrs);
+    if (rc) goto x_badvalue_rc;
+
+    cmp= memcmp(ame->start, rangeptrs[0], am->byl);
+    if (cmp < 0) {
+      rc= cht_staticerr(ip, "addr-map entries out of order", 0);
+      goto x_badvalue_rc;
+    }
+    if (cmp > 0) {
+      ame= ame_sfa_alloc(am);
+      memcpy(ame->start, rangeptrs[0], am->byl);
+    }
+    
+    assert(!ame->data);
+    rc= Tcl_ListObjIndex(ip,eo,2,&ame->data);  if (rc) goto x_badvalue_rc;
+    Tcl_IncrRefCount(ame->data);
+
+    ame= ame_sfa_alloc(am);
+    rc= ame_ba_addsubtractone(ame->start, rangeptrs[1], am->byl,
+                             /*add:*/ 0x01u, 0x00u);
+    if (rc) {
+      /* we've overflowed.  it must have been ffffffff.... */
+      if (innum != inlen-1) {
+       rc= cht_staticerr(ip, "addr-map non-last entry end is all-bits-1", 0);
+       goto x_badvalue_rc;
+      }
+      TFREE(ame->start);
+      am->used--;
+      break;
+    }
+  }
+    
+  /* we commit now */
+  cht_objfreeir(o);
+  o->internalRep.otherValuePtr= am;
+  o->typePtr= &cht_addrmap_type;
+  return TCL_OK;
+
+ x_badvalue_rc:
+  if (rc == TCL_ERROR)
+    Tcl_SetObjErrorCode(ip, Tcl_NewStringObj("HBYTES ADDRMAP VALUE", -1));
+
+  am_free(am);
+  return rc;
+}
+
+Tcl_ObjType cht_addrmap_type = {
+  "addr-map",
+  addrmap_t_free, addrmap_t_dup, addrmap_t_ustr, addrmap_t_sfa
+};
diff --git a/maskmap/maskmap-bits.c b/maskmap/maskmap-bits.c
new file mode 100644 (file)
index 0000000..2c6464b
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * maskmap - Tcl extension for address mask map data structures
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+int cht_do_hbytes_addr_map(ClientData cd, Tcl_Interp *ip,
+                      const AddrMap_SubCommand *subcmd,
+                      int objc, Tcl_Obj *const *objv) {
+  return subcmd->func(0,ip,objc,objv);
+}
+
+xxxx
+extern int Chiark_tcl_hbytes_Init(Tcl_Interp *ip); /*called by load(3tcl)*/
+int Chiark_tcl_hbytes_Init(Tcl_Interp *ip) {
+  static int initd;
+  
+  return cht_initextension(ip, cht_hbytestoplevel_entries, &initd,
+                          &cht_addrmap_type,
+                          (Tcl_ObjType*)0);
+}
diff --git a/maskmap/maskmap.c b/maskmap/maskmap.c
new file mode 100644 (file)
index 0000000..1a17681
--- /dev/null
@@ -0,0 +1,389 @@
+/*
+ * maskmap - Tcl extension for address mask map data structures
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "chiark_tcl_hbytes.h"
+
+/*---------- operations on AddrMap_Entry ----------*/
+
+static void ame_init(AddrMap_Entry *ame) {
+  ame->prefixlen= -1;
+  ame->prefix= 0;
+  ame->data= 0;
+}
+
+static unsigned ame_clear_unwanted(AddrMap_Entry *ame, int bytes) {
+  /* returns non-0 iff some bits were cleared */
+  int sparebits;
+  unsigned result, sparemask;
+  Byte *datap;
+
+  sparebits= bytes * 8 - ame->prefixlen;
+  if (!sparebits) return 0;
+
+  sparemask= (1u << sparebits) - 1;
+  datap= &ame->prefix[bytes-1];
+
+  result= *datap & sparemask;
+  *datap &= ~sparemask;
+
+  return result;
+}
+
+static int ame_parsekey(Tcl_Interp *ip, AddrMap_Entry *ame,
+                       Tcl_Obj *prefixo, Tcl_Obj *prefixbitso,
+                       int inmap) {
+ /* *ame should be blank entry; after exit (even error exit) it will be valid
+  * - on errors, it will be blank.  inmap is 1 if we're parsing an existing
+  * map or 0 if it's an entry to be added or modified. */
+  HBytes_Value prefix;
+  int suppliedprefixbytes, prefixbits, wantprefixbytes;
+  const Byte *data;
+  int rc;
+
+  hbytes_empty(&prefix);
+  
+  rc= pat_hb(ip,prefixo,&prefix);  if (rc) goto x_rc;
+  rc= pat_int(ip,prefixbitso,&prefixbits);  if (rc) goto x_rc;
+
+  wantprefixbytes= prefix_bytes(prefixbits);
+  suppliedprefixbytes= hbytes_len(&prefix);
+
+  if (suppliedprefixbytes < wantprefixbytes) {
+    rc= staticerr(ip, "addr-map entry PREFIX too short for PREFIX-LEN",
+                 "HBYTES ADDRMAP SYNTAX UNDERRUN");
+    goto x_rc;
+  }
+  if (inmap && suppliedprefixbytes > wantprefixbytes) {
+    rc= staticerr(ip, "addr-map existing entry PREFIX too long for PREFIX-LEN",
+                 "HBYTES ADDRMAP SYNTAX OVERRUN");
+    goto x_rc;
+  }
+
+  ame->prefixlen= prefixbits;
+  ame->prefix= TALLOC(wantprefixbytes);  assert(ame->prefix);
+  memcpy(ame->prefix, data, wantprefixbytes);
+
+  if (ame_clear_unwanted(ame, wantprefixbytes)) {
+    rc= staticerr(ip, "addr-map entry PREFIX contains bits excluded"
+                 " by PREFIX-LEN", "HBYTES ADDRMAP SYNTAX EXCLBITS");
+    goto x_rc;
+  }
+
+  return TCL_OK;
+
+ x_rc:
+  ame_free(ame);
+  return rc;
+}
+
+static int ame_contains(const AddrMap_Entry *ref, const Byte *addr, int len) {
+  int directbytes, leftoverbits;
+  
+  assert(len >= ref->prefixlen);
+  
+  directbytes= ref->prefixlen / 8;
+  if (memcmp(ref->prefix, addr, directbytes)) return 0;
+
+  leftoverbits= ref->prefixlen % 8;
+  if (leftoverbits)
+    if ((addr[directbytes] & (0xffu << leftoverbits))
+       != search->prefix[directbytes])
+      return 0;
+
+  return 1;
+}  
+
+static int ame_compare(const AddrMap_Entry *a, const AddrMap_Entry *b) {
+  /*    +2 =    a   covers later range of address space than  b
+   *    +1 =    a   wholly contains but is not equal to       b
+   *     0 =    a   is identical to                           b
+   *    -1 =    b   wholly contains but is not equal to       a
+   *    -2 =    b   covers later range of address space than  a
+   */
+  int al= a->prefixlen;
+  int bl= b->prefixlen;
+  int ml, d;
+
+  if (al==bl) { ml=al; }
+  else if (al<bl) { ml=al; if (ame_contains(a,b->prefix,bl)) return +1; }
+  else if (bl<al) { ml=bl; if (ame_contains(b,a->prefix,al)) return -1; }
+
+  d= memcmp(b->prefix, a->prefix, prefix_bytes(ml));
+  return (d > 0 ? +2 :
+         d < 0 ? -2 :
+         0);
+}
+
+/*---------- searching maps ----------*/
+
+typedef enum {
+  sr_notfound,
+  sr_exact,
+  sr_inbig,
+  sr_aroundsmall
+} Search_Result;
+
+static int
+am_binarychop(AddrMap_Value *am, int low_oreq, int high_strict, void *u,
+             int (*test)(AddrMap_Entry *am, void *u) /* -ve => look left */,
+             int *found_r) {
+  int mid, cmp;
+  
+  for (;;) {
+    if (high_strict <= low_oreq) {
+      assert(high_strict == low_oreq);
+      *found_r= 0;
+      return high_strict;
+    }
+
+    mid= (high_strict + low_oreq) / 2;
+    cmp= test(&am->entries[mid], u);
+
+    if (!cmp) {
+      *found_r= 1;
+      return mid;
+    }
+
+    if (cmp < 0)
+      high_strict= mid;
+    else
+      low_oreq= mid+1;
+  }
+}
+
+struct am_search_u {
+  int forbid_aroundsmall;
+  AddrMap_Entry proposed;
+  Search_Result sr;
+};
+
+static int
+am_search_binchoptest(AddrMap_Entry *ame, void *u_v) {
+  struct am_search_u *u= u_v;
+  int cmp;
+
+  cmp= ame_compare(&u.proposed, ame);
+  switch (cmp) {
+  case -1:  u->sr= sr_inbig;        return  0;
+  case  0:  u->sr= sr_exact;        return  0;
+  case +1:  u->sr= sr_aroundsmall;  return  0;
+  default:                          return cmp;
+  }
+}
+
+static Search_Result
+am_search(AddrMap_Value *am, const AddrMap_Entry *proposed, int *place_r) {
+  int place, found;
+  struct am_search_u u;
+
+  u.forbid_aroundsmall= forbid_aroundsmall;
+  u.proposed= proposed;
+  u.sr= sr_notfound;
+
+  *place_r= am_binarychop(am, 0, am.used, &u, am_search_binchoptest, &found);
+
+  assert(!!found == (u.sr != sr_notfound));
+  return u.sr;
+}
+
+/*---------- useful operations on AddrMap_Value etc. ----------*/
+
+/*---------- amendment (complex algorithm) ----------*/
+
+struct am_amend_aroundsmall_u {
+  AddrMap_Entry *new;
+  int sign;
+};
+
+
+static int
+am_amend_aroundsmall_binchoptest(AddrMap_Entry *search, void *u_v) {
+  struct am_amend_aroundsmall_u *u= u_v;
+
+  cmp= u->sign * ame_compare(search, u->new);
+
+  switch (cmp) {
+  case +2:  return -u->sign;
+  case +1:  return +u->sign;
+  default:  abort();
+  }
+}
+
+int do_addrmap_amend(ClientData cd, Tcl_Interp *ip,
+                    AddrMap_Var map, Tcl_Obj *prefix,
+                    Tcl_Obj *preflen, Tcl_Obj *data) {
+  AddrMap_Value *am= map.am;
+  AddrMap_Entry new, *fragment;
+  AddrMap_Entry *breaking, *replacements;
+  int rc, insertat, findend, cmp, nreplacements, new_used;
+  struct am_amend_aroundsmall_u u;
+
+  ame_init(&new);
+  
+  rc= ame_parsekey(ip,&new,prefix,preflen,0);  if (rc) return rc;
+
+  sr= am_search(am, &new, &searched);
+
+  replacements= &new;
+  nreplacements= 1;
+  replace_start= searched;
+  replace_end= searched;
+
+  switch (sr) {
+
+  case sr_notfound:
+    break;
+
+  case sr_exact:
+    replace_end= searched+1;
+    break;
+
+  case sr_aroundsmall:
+    u.ame= new;
+    u.sign= -1;
+    replace_start= am_binarychop(am, 0, searched, &u,
+                                am_amend_aroundsmall_binchoptest, &dummy);
+    u.sign= +1;
+    replace_end= am_binarychop(am, searched+1, am.used, &u,
+                                am_amend_aroundsmall_binchoptest, &dummy);
+    break;
+
+  case sr_inbig:
+    /* Urgh, we need to break it up.  This produces
+     *   - innermost prefix (the new one) as specified
+     *   - one for each bitlength
+     *       <= innermost
+     *        > outermost (the existing one)
+     *     each one specifying the outermost prefix plus zero, one,
+     *     two, etc. bits of the innermost followed by one bit
+     *     opposite to the innermost, with the outermost's data
+     * Eg, if we have ff/8=>A and want to amend so that ffff/16=>B
+     * then we replace ff/8 with ff0/9=>A ff8/10=>A ffc/11=>A ...
+     * ... fff8/14=>A fffc/15=>A fffe/16=>A ffff/16=>B.
+     */
+
+    breaking= &am.entries[searched];
+    nreplacements= new.prefix - breaking->prefixlen + 1;
+    fixme check integer overflow ^
+    replacements= TALLOC(sizeof(*replacements) * nreplacements);
+
+    for (fragmentlen= breaking->prefixlen + 1,
+          left_insert= 0, right_insert= nreplacements;
+        fragmentlen <= new.prefix;
+        fragmentlen++) {
+      int fragmentbytes;
+
+      fragmentbytes= prefix_bytes(fragmentlen)
+      fragment->prefixlen= fragmentlen;
+      fragment->prefix= TALLOC(fragmentbytes);
+      memcpy(fragment->prefix, new.prefix, fragmentbytes);
+      ame_clear_unwanted(fragment, fragmentbytes);
+
+      fragment->prefix[fragmentbytes] ^=
+       0x80u >> ((fragmentlen+7) & 7);
+
+      switch (ame_compare(&fragment, &new)) {
+      case -2:  replacements[left_insert++]=  fragment;  break;
+      case +2:  replacements[--right_insert]= fragment;  break;
+      default: abort();
+      }
+    }
+    assert(left_insert == right_insert-1);
+    replacements[left_insert]= new;
+    ame_init(&new);
+
+    replace_end= searched+1;
+    break;
+
+  }
+
+  new_used= am.used - (replace_end - replace_start) + nreplacements;
+
+  if (new_used > am.space)
+    am_reallocentries(am, new_used * 2);
+
+  for (scan=replacements, i=0;
+       i < nreplacements;
+       scan++, i++) {
+    scan->data= data;
+    Tcl_IncrRefCount(scan->data);
+  }
+
+  for (i= replace_start, scan= am.entries+i;
+       i < replace_end;
+       i++, scan++) {
+    ame_free(scan);
+  }
+
+  memmove(am.entries + replace_start + nreplacements,
+         am.entries + replace_end,
+         sizeof(*am.entries) * (am.used - replace_end));
+
+  memcpy(am.entries + replace_start,
+        replacements,
+        sizeof(*am.entries) * nreplacements);
+
+  am.used= new_used;
+  if (replacements != &new)
+    /* we don't bother freeing the actual array elements because
+     * if replacements!=&new the array is only full if we're
+     * committed and have already copied the values into the actual
+     * AddrMap_Value. */
+    TFREE(replacements);
+
+  return TCL_OK;
+}
+
+/*---------- other substantial operations on mask maps ----------*/
+
+int do_addrmap_lookup(ClientData cd, Tcl_Interp *ip,
+                     Tcl_Obj *mapo, HBytes_Value addrhb, Tcl_Obj *def,
+                     Tcl_Obj **result) {
+  AddrMap_Value *am= (void*)&mapo->internalRep;
+  const Byte *addr= hbytes_data(&addrhb);
+  int addrbytes= hbytes_len(&addrhb);
+  int i, addrbits, place;
+  Search_Result sr;
+
+  addrbits= addrbytes * 8;
+  sr= am_search(am, addr, addrbits, &place);
+
+  switch (sr) {
+
+  case sr_notfound:
+    if (!def) return staticerr(ip, "address not found in addr-map",
+                              "HBYTES ADDRMAP NOMATCH");
+    *result= def;
+    break;
+
+  case sr_aroundsmall:
+    return staticerr(ip, "address shorter than mask in map",
+                    "HBYTES ADDRMAP UNDERRUN");
+
+  case sr_exact:
+  case sr_inbig:
+    *result= am.entres[place].data;
+    break;
+    
+  }
+
+  return TCL_OK;
+}
+
+/*---------- Tcl type and arg parsing functions ----------*/
+
diff --git a/maskmap/maskmap.tct b/maskmap/maskmap.tct
new file mode 100644 (file)
index 0000000..7bd1465
--- /dev/null
@@ -0,0 +1,39 @@
+# maskmap - Tcl extension for address mask map data structures
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Table *maskmaptoplevel TopLevel_Command
+       addr-map
+               subcmd  enum(AddrMap/_SubCommand, "addr-map subcommand")
+               ...     obj
+
+Table addrmap AddrMap_SubCommand
+       lookup
+               map     constv(&cht_addrmap_type)
+               addr    hb
+               ?def    obj
+               =>      obj
+       amend-range
+               map     addrmapv
+               start   hb
+               end     hb
+               data    obj
+       amend-mask
+               map     addrmapv
+               prefix  hb
+               preflen obj
+               data    obj
+
diff --git a/tuntap/Makefile b/tuntap/Makefile
new file mode 100644 (file)
index 0000000..2d6808d
--- /dev/null
@@ -0,0 +1,9 @@
+BASE_DIR =     ../base
+EXTBASE =      tuntap
+CFILES =       tuntap
+OTHER_TCTS =   ../hbytes/hbytes-base.tct
+OTHER_EXTS =   hbytes/hbytes dgram/dgram
+##LDLIBS +=    -ladns
+
+include ../base/extension.make
+
diff --git a/tuntap/chiark_tcl_tuntap.h b/tuntap/chiark_tcl_tuntap.h
new file mode 100644 (file)
index 0000000..30a3f16
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * tuntap - Tcl bindings for tun/tap userspace network interfaces
+ * Copyright 2006-2012 Ian Jackson
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TUNTAPTCL_H
+#define TUNTAPTCL_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <linux/if.h>
+#include <linux/if_tun.h>
+
+#include "hbytes.h"
+#include "dgram.h"
+#include "tuntap+tcmdif.h"
+
+/* from tuntap.c */
+
+extern const IdDataSpec cht_tuntap_socks;
+
+#endif /*TUNTAPTCL_H*/
diff --git a/tuntap/tuntap.c b/tuntap/tuntap.c
new file mode 100644 (file)
index 0000000..b1bad35
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ */
+/*
+ * tuntap-socket-raw create [<ifname>] => <sockid>
+ * tuntap-socket-raw ifname <sockid> => <ifname>
+ * tuntap-socket-raw close <sockid>
+ * tuntap-socket-raw receive <sockid> <data>
+ * tuntap-socket-raw on-transmit <sockid> <mtu> [<script>]
+ *    calls, effectively,  eval <script> [list <data> <socket>]
+ *    if script not supplied, cancel
+ */
+
+#include "chiark_tcl_tuntap.h"
+
+typedef struct TunSocket {
+  int ix, fd, script_llength;
+  Tcl_Interp *ip;
+  ScriptToInvoke script;
+  int mtu;
+  unsigned char *msg_buf;
+  char *ifname;
+} TuntapSocket;
+
+int cht_do_tuntapsocket_create_tun(ClientData cd, Tcl_Interp *ip,
+                               const char *ifname, void **sock_r) {
+  int fd, r;
+  struct ifreq ifr;
+  TuntapSocket *sock;
+
+  memset(&ifr,0,sizeof(ifr));
+  ifr.ifr_flags= IFF_TUN | IFF_NO_PI;
+  
+  if (ifname) {
+    if (strlen(ifname) > IFNAMSIZ-1) return
+      cht_staticerr(ip,"tun interface name too long","TUNTAP IFNAME LENGTH");
+    strcpy(ifr.ifr_name, ifname);
+  }
+
+  fd= open("/dev/net/tun", O_RDWR);
+  if (fd<0) return cht_posixerr(ip,errno,"open /dev/net/tun");
+
+  r= cht_setnonblock(fd,1);
+  if (r) return cht_posixerr(ip,errno,"setnonblock tun");
+  
+  r= ioctl(fd, TUNSETIFF, (void*)&ifr);
+  if (r) return cht_newfdposixerr(ip,fd,"ioctl TUNSETIFF");
+
+  sock= TALLOC(sizeof(TuntapSocket));
+  sock->ix= -1;
+  sock->fd= fd;
+  sock->mtu= 0;
+  sock->msg_buf= 0;
+  sock->ifname= TALLOC(strlen(ifr.ifr_name)+1);
+  strcpy(sock->ifname, ifr.ifr_name);
+  cht_scriptinv_init(&sock->script);
+
+  *sock_r= sock;
+  return TCL_OK;
+}
+
+int cht_do_tuntapsocket_receive(ClientData cd, Tcl_Interp *ip,
+                                void *sock_v, HBytes_Value data) {
+  TuntapSocket *sock= sock_v;
+  int l, r;
+
+  r= write(sock->fd,
+          cht_hb_data(&data), l=cht_hb_len(&data));
+  if (r==-1) return cht_posixerr(ip,errno,"write tuntap");
+  else if (r!=l) return cht_staticerr(ip,"write tuntap gave wrong answer",0);
+  return TCL_OK;
+}
+
+int cht_do_tuntapsocket_ifname(ClientData cd, Tcl_Interp *ip,
+                               void *sock_v, const char **result) {
+  TuntapSocket *sock= sock_v;
+  *result= sock->ifname;
+  return TCL_OK;
+}
+
+static void cancel(TuntapSocket *sock) {
+  if (sock->script.script) {
+    cht_scriptinv_cancel(&sock->script);
+    Tcl_DeleteFileHandler(sock->fd);
+    TFREE(sock->msg_buf);
+    sock->msg_buf= 0;
+  }
+}
+
+static void read_call(ClientData sock_cd, int mask) {
+  TuntapSocket *sock= (void*)sock_cd;
+  Tcl_Interp *ip= sock->ip;
+  int rc;
+  ssize_t sz;
+  HBytes_Value message_val;
+  Tcl_Obj *args[2];
+
+  for (;;) {
+    sz= read(sock->fd, sock->msg_buf, sock->mtu);
+    if (sz == -1) {
+      if (errno == EAGAIN || errno == EWOULDBLOCK) rc=0;
+      else rc= cht_posixerr(ip,errno,"read tuntap");
+      goto x_rc;
+    }
+
+    assert(sz <= sock->mtu);
+
+    cht_hb_array(&message_val, sock->msg_buf, sz);
+    args[0]= cht_ret_hb(ip, message_val);  cht_hb_empty(&message_val);
+    args[1]= cht_ret_iddata(ip, sock, &cht_tuntap_socks);
+    cht_scriptinv_invoke(&sock->script, 2, args);
+  }
+
+x_rc:
+  if (rc) Tcl_BackgroundError(ip);
+}
+
+int cht_do_tuntapsocket_on_transmit(ClientData cd, Tcl_Interp *ip,
+                                    void *sock_v,
+                                    long mtu, Tcl_Obj *newscript) {
+  TuntapSocket *sock= sock_v;
+  int rc;
+
+  if (mtu > 65536)
+    return cht_staticerr(ip,"tuntap mtu >2^16","TUNTAP MTU OVERRUN");
+
+  cancel(sock);
+  
+  if (newscript) {
+    rc= cht_scriptinv_set(&sock->script,ip,newscript,0);
+    if (rc) return rc;
+    
+    sock->mtu= mtu;
+    sock->msg_buf= TALLOC(mtu);
+    Tcl_CreateFileHandler(sock->fd, TCL_READABLE, read_call, sock);
+  }
+  return TCL_OK;
+}
+
+static void destroy(void *sock_v) {
+  TuntapSocket *sock= sock_v;
+  cancel(sock);
+  close(sock->fd); /* nothing useful to be done with errors */
+  TFREE(sock->msg_buf);
+  TFREE(sock);
+}
+
+static void destroy_idtabcb(Tcl_Interp *ip, void *sock_v) {
+  destroy(sock_v);
+}
+
+int cht_do_tuntapsocket_close(ClientData cd, Tcl_Interp *ip, void *sock) {
+  cht_tabledataid_disposing(ip,sock,&cht_tuntap_socks);
+  destroy(sock);
+  return TCL_OK;
+}
+
+const IdDataSpec cht_tuntap_socks= {
+  "tuntap", "tuntap-table", destroy_idtabcb
+};
+
+CHT_INIT(tuntap, { }, CHTI_COMMANDS(cht_tuntaptoplevel_entries))
diff --git a/tuntap/tuntap.tct b/tuntap/tuntap.tct
new file mode 100644 (file)
index 0000000..de66d29
--- /dev/null
@@ -0,0 +1,38 @@
+# tuntap - Tcl extension for tun/tap network device
+# Copyright 2006-2012 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+
+Table *tuntaptoplevel TopLevel_Command
+       tuntap-socket
+           dispatch(TunTapSocket/_SubCommand,"tuntap-socket-raw subcommand")
+
+Table tuntapsocket TunTapSocket_SubCommand
+       create-tun
+               ?ifname string
+               =>      iddata(&cht_tuntap_socks)
+       close
+               sock    iddata(&cht_tuntap_socks)
+       ifname
+               sock    iddata(&cht_tuntap_socks)
+               =>      string
+       receive
+               sock    iddata(&cht_tuntap_socks)
+               data    hb
+       on-transmit
+               sock    iddata(&cht_tuntap_socks)
+               mtu     long
+               ?script obj
+