key/key-io.c: Add low-level `key_mergeline' and `key_extractline' functions.
authorMark Wooding <mdw@distorted.org.uk>
Thu, 3 Oct 2019 13:33:36 +0000 (14:33 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 9 May 2020 19:57:33 +0000 (20:57 +0100)
Python 3 doesn't use C `stdio' streams.  In order to integrate properly,
we need to provide some other way to do I/O of key material.  Introduce
new functions which work in terms of lines in the keyring, which a
Python wrapper can transfer between us and a Python file.

debian/catacomb2.symbols
key/key-io.c
key/key.h

index ceec3f2..23ca209 100644 (file)
@@ -4427,7 +4427,9 @@ libcatacomb.so.2 catacomb2 #MINVER#
        key_close@Base 2.1.1
        key_discard@Base 2.1.1
        key_extract@Base 2.3.1
+       key_extractline@Base 2.5.99~
        key_merge@Base 2.1.1
+       key_mergeline@Base 2.5.99~
 
 ## key-misc
        key_byid@Base 2.1.1
index 5a7ce5c..bf3bf95 100644 (file)
@@ -150,226 +150,286 @@ static time_t exptime(const char *p)
     return (atol(p));
 }
 
-/* --- @key_merge@ --- *
+/* --- @merge_core@ --- *
  *
  * Arguments:  @key_file *f@ = pointer to file structure
  *             @const char *file@ = name of file (for error messages)
- *             @FILE *fp@ = file handle to read from
+ *             @int lno@ = line number
+ *             @char *p@ = pointer into the line buffer (which will be
+ *                     clobbered)
  *             @key_reporter *rep@ = error reporting function
  *             @void *arg@ = argument for function
+ *             @dstr *n, *v@ = scratch dynamic strings, which must be empty
+ *                     on entry and are left empty on exit
  *
- * Returns:    Error code (one of the @KERR@ constants).
+ * Returns:    ---
+ *
+ * Use:                This is the common core of @key_merge@ and @key_mergeline@.
  *
- * Use:                Reads keys from a file, and inserts them into the file.
+ *             It is assumed that the caller has already verified that the
+ *             keyring is writable.
  */
 
-int key_merge(key_file *f, const char *file, FILE *fp,
-             key_reporter *rep, void *arg)
+static void merge_core(key_file *f, const char *file, int lno, char *p,
+                      key_reporter *rep, void *arg, dstr *n, dstr *v)
 {
-  int line = 0;
-  dstr l = DSTR_INIT;
-  dstr n = DSTR_INIT, v = DSTR_INIT;
+  char *vf[6];
+  key *k = 0;
 
-  if (!(f->f & KF_WRITE))
-    return (KERR_READONLY);
+  /* --- Skip blank lines and comments --- *
+   *
+   * Quite what they're doing in what ought to be an automatically-
+   * maintained file I don't know.
+   */
+
+  while (isspace((unsigned char)*p))
+    p++;
+  if (!*p || *p == '#')
+    goto skip;
+
+  /* --- Break the line into fields --- *
+   *
+   * There are currently six fields of interest:
+   *
+   *    * The key's identification (id, tag and type).
+   *    * The actual key data itself.
+   *    * The key expiry time.
+   *    * The key deletion time.
+   *    * The attributes field.
+   *    * Any further comments.
+   *
+   * All but the last field can contain no spaces.
+   */
 
-  for (; dstr_putline(&l, fp) != EOF; DRESET(&l)) {
-    char *vf[6];
-    char *p = l.buf;
-    key *k;
-
-    /* --- Skip blank lines and comments --- *
-     *
-     * Quite what they're doing in what ought to be an automatically-
-     * maintained file I don't know.
-     */
-
-    line++;
-    while (isspace((unsigned char)*p))
-      p++;
-    if (!*p || *p == '#')
-      continue;
-
-    /* --- Break the line into fields --- *
-     *
-     * There are currently six fields of interest:
-     *
-     *  * The key's identification (id, tag and type).
-     *  * The actual key data itself.
-     *  * The key expiry time.
-     *  * The key deletion time.
-     *  * The attributes field.
-     *  * Any further comments.
-     *
-     * All but the last field can contain no spaces.
-     */
-
-    {
-      int n = str_split(p, vf, 5, &vf[5]);
-      if (n < 4) {
-       if (rep)
-         rep(file, line, "too few fields", arg);
-       goto skip_0;
-      }
+  {
+    int n = str_split(p, vf, 5, &vf[5]);
+    if (n < 4) {
+      if (rep)
+       rep(file, lno, "too few fields", arg);
+      goto skip;
     }
+  }
 
-    /* --- Allocate a new key block --- */
+  /* --- Allocate a new key block --- */
 
-    k = CREATE(key);
+  k = CREATE(key);
+  k->k = 0;
+  k->tag = 0;
+  k->type = 0;
 
-    /* --- Extract the key data into the block --- */
+  /* --- Extract the key data into the block --- */
 
-    if ((k->k = key_read(vf[1], 0)) == 0) {
-      if (rep)
-       rep(file, line, "bad key data", arg);
-      goto skip_1;
-    }
+  if ((k->k = key_read(vf[1], 0)) == 0) {
+    if (rep)
+      rep(file, lno, "bad key data", arg);
+    goto skip;
+  }
 
-    /* --- Decode the identification field --- *
-     *
-     * For compatibility, derive a keyid from the key data.  This can only be
-     * done if the key encoding is binary (and presumably old-encoding binary
-     * at that).
-     */
-
-    {
-      char *q = strchr(vf[0], ':');
-      char *qq;
-
-      if (!q) {
-       if (k->k->e != KENC_BINARY) {
-         if (rep)
-           rep(file, line, "new-style key encoding but no keyid", arg);
-         goto skip_2;
-       }
-       k->id = crc32(0, k->k->u.k.k, k->k->u.k.sz);
-       k->type = xstrdup(vf[0]);
+  /* --- Decode the identification field --- *
+   *
+   * For compatibility, derive a keyid from the key data.  This can only be
+   * done if the key encoding is binary (and presumably old-encoding binary
+   * at that).
+   */
+
+  {
+    char *q = strchr(vf[0], ':');
+    char *qq;
+
+    if (!q) {
+      if (k->k->e != KENC_BINARY) {
+       if (rep)
+         rep(file, lno, "new-style key encoding but no keyid", arg);
+       goto skip;
+      }
+      k->id = crc32(0, k->k->u.k.k, k->k->u.k.sz);
+      k->type = xstrdup(vf[0]);
+      k->tag = 0;
+    } else {
+      *q++ = 0;
+      k->id = strtoul(p, 0, 16);
+      if ((qq = strchr(q, ':')) == 0 || !qq[1]) {
+       if (qq)
+         *qq = 0;
        k->tag = 0;
       } else {
-       *q++ = 0;
-       k->id = strtoul(p, 0, 16);
-       if ((qq = strchr(q, ':')) == 0 || !qq[1]) {
-         if (qq)
-           *qq = 0;
-         k->tag = 0;
-       } else {
-         *qq++ = 0;
-         k->tag = xstrdup(qq);
-       }
-       k->type = xstrdup(q);
+       *qq++ = 0;
+       k->tag = xstrdup(qq);
       }
+      k->type = xstrdup(q);
     }
+  }
 
-    /* --- Get a key block for the new key --- */
+  /* --- Get a key block for the new key --- */
 
-    k->exp = exptime(vf[2]);
-    k->del = exptime(vf[3]);
+  k->exp = exptime(vf[2]);
+  k->del = exptime(vf[3]);
 
-    /* --- Insert the key block into the table --- */
+  /* --- Insert the key block into the table --- */
 
-    {
-      int err;
+  {
+    int err;
 
-    again:
-      if ((err = insert(f, k)) < 0) {
-       if (err == KERR_DUPTAG) {
-         if (rep)
-           rep(file, line, "duplicate key tag stripped", arg);
-         xfree(k->tag);
-         k->tag = 0;
-         goto again;
-       }
+  again:
+    if ((err = insert(f, k)) < 0) {
+      if (err == KERR_DUPTAG) {
        if (rep)
-         rep(file, line, key_strerror(err), arg);
-       goto skip_3;
+         rep(file, lno, "duplicate key tag stripped", arg);
+       xfree(k->tag);
+       k->tag = 0;
+       goto again;
       }
+      if (rep)
+       rep(file, lno, key_strerror(err), arg);
+      goto skip;
     }
+  }
 
-    /* --- Parse up the attributes, if specified --- */
+  /* --- Parse up the attributes, if specified --- */
 
-    sym_create(&k->a);
-    if (vf[4] && strcmp(vf[4], "-") != 0) {
-      url_dctx uc;
-      for (url_initdec(&uc, vf[4]); url_dec(&uc, &n, &v); ) {
-       key_putattr(f, k, n.buf, v.buf);
-       DRESET(&n); DRESET(&v);
-      }
+  sym_create(&k->a);
+  if (vf[4] && strcmp(vf[4], "-") != 0) {
+    url_dctx uc;
+    for (url_initdec(&uc, vf[4]); url_dec(&uc, n, v); ) {
+      key_putattr(f, k, n->buf, v->buf);
+      DRESET(n); DRESET(v);
     }
+  }
 
-    /* --- Insert the comment --- */
+  /* --- Insert the comment --- */
 
-    if (vf[5])
-      k->c = xstrdup(vf[5]);
-    else
-      k->c = 0;
-    continue;
+  if (vf[5])
+    k->c = xstrdup(vf[5]);
+  else
+    k->c = 0;
 
-    /* --- Tidy up after something going wrong --- */
+  /* --- Done --- */
 
-  skip_3:
-    if (k->tag)
-      xfree(k->tag);
-    xfree(k->type);
-  skip_2:
-    key_drop(k->k);
-  skip_1:
+  f->f |= KF_MODIFIED;
+  return;
+
+  /* --- Tidy up after something going wrong --- */
+
+skip:
+  if (k) {
+    if (k->tag) xfree(k->tag);
+    if (k->type) xfree(k->type);
+    if (k->k) key_drop(k->k);
     DESTROY(k);
-  skip_0:;
   }
+}
+
+/* --- @key_merge@, @key_mergeline@ --- *
+ *
+ * Arguments:  @key_file *f@ = pointer to file structure
+ *             @const char *file@ = name of file (for error messages)
+ *             @int lno@ = line number (for error messages, @key_mergeline@)
+ *             @FILE *fp@ = file handle to read from (@key_merge@)
+ *             @const char *line@ = line from the input (@key_mergeline@)
+ *             @key_reporter *rep@ = error reporting function
+ *             @void *arg@ = argument for function
+ *
+ * Returns:    Error code (one of the @KERR@ constants).
+ *
+ * Use:                The @key_merge@ function reads keys from a file, and inserts
+ *             them into the keyring.
+ *
+ *             The @key_mergeline@ function reads a key from a single input
+ *             line (which may, but need not, have a final newline), and
+ *             adds it to the keyring.
+ *
+ *             The @key_mergeline@ function is intended to help with
+ *             interfacing Catacomb to runtimes which don't use C's @stdio@
+ *             streams, rather than as a general-purpose service, though if
+ *             it turns out to be useful in other ways then so much the
+ *             better.
+ */
 
-  /* --- Extensive tidying up now required --- */
+int key_merge(key_file *f, const char *file, FILE *fp,
+             key_reporter *rep, void *arg)
+{
+  dstr n = DSTR_INIT, v = DSTR_INIT;
+  dstr l = DSTR_INIT;
+  int lno = 1;
+
+  if (!(f->f & KF_WRITE))
+    return (KERR_READONLY);
+
+  for (; dstr_putline(&l, fp) != EOF; DRESET(&l))
+    merge_core(f, file, lno++, l.buf, rep, arg, &n, &v);
 
   dstr_destroy(&l);
-  dstr_destroy(&n);
-  dstr_destroy(&v);
-  f->f |= KF_MODIFIED;
+  dstr_destroy(&n); dstr_destroy(&v);
   return (0);
 }
 
-/* --- @key_extract@ --- *
+int key_mergeline(key_file *f, const char *file, int lno, const char *line,
+                 key_reporter *rep, void *arg)
+{
+  dstr n = DSTR_INIT, v = DSTR_INIT;
+  size_t len = strlen(line);
+  char *p;
+
+  if (!(f->f & KF_WRITE)) return (KERR_READONLY);
+
+  if (len && line[len - 1] == '\n') len--;
+  p = xmalloc(len); memcpy(p, line, len); p[len] = 0;
+  merge_core(f, file, lno, p, rep, arg, &n, &v);
+  xfree(p); dstr_destroy(&n); dstr_destroy(&v);
+  return (0);
+}
+
+/* --- @key_extract@, @key_extractline@ --- *
  *
  * Arguments:  @key_file *f@ = pointer to file structure
  *             @key *k@ = key to extract
- *             @FILE *fp@ = file to write on
+ *             @FILE *fp@ = file to write on (@key_extract@)
+ *             @dstr *d@ = string to write on (@key_extractline@)
  *             @const key_filter *kf@ = pointer to key selection block
  *
- * Returns:    Zero if OK, EOF on error.
+ * Returns:    @key_extract@ returns zero if OK, EOF on error.
+ *             @key_extractline@ does not return a value.
+ *
+ * Use:                Extracts a key to an ouptut file or buffer.
+ *
+ *             The @key_extractline@ includes a final newline in its output.
  *
- * Use:                Extracts a key to an ouptut file.
+ *             The @key_extractline@ function is intended to help with
+ *             interfacing Catacomb to runtimes which don't use C's @stdio@
+ *             streams, rather than as a general-purpose service, though if
+ *             it turns out to be useful in other ways then so much the
+ *             better.
  */
 
-int key_extract(key_file *f, key *k, FILE *fp, const key_filter *kf)
+void key_extractline(key_file *f, key *k, dstr *d, const key_filter *kf)
 {
-  dstr d = DSTR_INIT;
   time_t t = time(0);
 
   /* --- Skip the key if it's deleted or unselected--- */
 
   if (KEY_EXPIRED(t, k->del) || !key_match(k->k, kf))
-    return (0);
+    return;
 
   /* --- Encode the key and write the easy stuff --- */
 
-  key_fulltag(k, &d);
-  DPUTC(&d, ' ');
-  if (!key_write(k->k, &d, kf)) dstr_puts(&d, "struct:[]");
-  DPUTC(&d, ' ');
-  dstr_write(&d, fp);
-  DRESET(&d);
+  key_fulltag(k, d);
+  DPUTC(d, ' ');
+  if (!key_write(k->k, d, kf)) dstr_puts(d, "struct:[]");
+  DPUTC(d, ' ');
 
   /* --- Write out the expiry and deletion times --- */
 
   if (KEY_EXPIRED(t, k->exp))
-    fputs("expired ", fp);
+    dstr_puts(d, "expired ");
   else if (k->exp == KEXP_FOREVER)
-    fputs("forever ", fp);
+    dstr_puts(d, "forever ");
   else
-    fprintf(fp, "%li ", (long)k->exp);
+    dstr_putf(d, "%li ", (long)k->exp);
 
   if (k->del == KEXP_FOREVER)
-    fputs("forever ", fp);
+    dstr_puts(d, "forever ");
   else
-    fprintf(fp, "%li ", (long)k->del);
+    dstr_putf(d, "%li ", (long)k->del);
 
   /* --- Output the attributes --- */
 
@@ -382,19 +442,24 @@ int key_extract(key_file *f, key *k, FILE *fp, const key_filter *kf)
     url_initenc(&uc);
     for (sym_mkiter(&i, &k->a); (a = sym_next(&i)) != 0; ) {
       none = 0;
-      url_enc(&uc, &d, SYM_NAME(a), a->p);
+      url_enc(&uc, d, SYM_NAME(a), a->p);
     }
     if (none)
-      DPUTS(&d, "-");
-    DWRITE(&d, fp);
+      DPUTC(d, '-');
   }
 
-  dstr_destroy(&d);
-  if (k->c) {
-    putc(' ', fp);
-    fputs(k->c, fp);
-  }
-  putc('\n', fp);
+  if (k->c)
+    dstr_putf(d, " %s", k->c);
+
+  DPUTC(d, '\n'); DPUTZ(d);
+}
+
+int key_extract(key_file *f, key *k, FILE *fp, const key_filter *kf)
+{
+  dstr d = DSTR_INIT;
+
+  key_extractline(f, k, &d, kf);
+  dstr_write(&d, fp);
   return (ferror(fp) ? EOF : 0);
 }
 
index bed3632..96a2b5a 100644 (file)
--- a/key/key.h
+++ b/key/key.h
@@ -177,36 +177,64 @@ typedef void key_reporter(const char */*file*/, int /*line*/,
 
 /*----- Reading and writing keys and files --------------------------------*/
 
-/* --- @key_merge@ --- *
+/* --- @key_merge@, @key_mergeline@ --- *
  *
  * Arguments:  @key_file *f@ = pointer to file structure
  *             @const char *file@ = name of file (for error messages)
- *             @FILE *fp@ = file handle to read from
+ *             @int lno@ = line number (for error messages, @key_mergeline@)
+ *             @FILE *fp@ = file handle to read from (@key_merge@)
+ *             @const char *line@ = line from the input (@key_mergeline@)
  *             @key_reporter *rep@ = error reporting function
  *             @void *arg@ = argument for function
  *
  * Returns:    Error code (one of the @KERR@ constants).
  *
- * Use:                Reads keys from a file, and inserts them into the file.
+ * Use:                The @key_merge@ function reads keys from a file, and inserts
+ *             them into the keyring.
+ *
+ *             The @key_mergeline@ function reads a key from a single input
+ *             line (which may, but need not, have a final newline), and
+ *             adds it to the keyring.
+ *
+ *             The @key_mergeline@ function is intended to help with
+ *             interfacing Catacomb to runtimes which don't use C's @stdio@
+ *             streams, rather than as a general-purpose service, though if
+ *             it turns out to be useful in other ways then so much the
+ *             better.
  */
 
 extern int key_merge(key_file */*f*/, const char */*file*/, FILE */*fp*/,
                     key_reporter */*rep*/, void */*arg*/);
+extern int key_mergeline(key_file */*f*/, const char */*file*/, int /*lno*/,
+                        const char */*line*/,
+                        key_reporter */*rep*/, void */*arg*/);
 
-/* --- @key_extract@ --- *
+/* --- @key_extract@, @key_extractline@ --- *
  *
  * Arguments:  @key_file *f@ = pointer to file structure
  *             @key *k@ = key to extract
- *             @FILE *fp@ = file to write on
+ *             @FILE *fp@ = file to write on (@key_extract@)
+ *             @dstr *d@ = string to write on (@key_extractline@)
  *             @const key_filter *kf@ = pointer to key selection block
  *
- * Returns:    Zero if OK, EOF on error.
+ * Returns:    @key_extract@ returns zero if OK, EOF on error.
+ *             @key_extractline@ does not return a value.
+ *
+ * Use:                Extracts a key to an ouptut file or buffer.
+ *
+ *             The @key_extractline@ includes a final newline in its output.
  *
- * Use:                Extracts a key to an ouptut file.
+ *             The @key_extractline@ function is intended to help with
+ *             interfacing Catacomb to runtimes which don't use C's @stdio@
+ *             streams, rather than as a general-purpose service, though if
+ *             it turns out to be useful in other ways then so much the
+ *             better.
  */
 
 extern int key_extract(key_file */*f*/, key */*k*/, FILE */*fp*/,
                       const key_filter */*kf*/);
+extern void key_extractline(key_file */*f*/, key */*k*/,
+                           dstr */*d*/, const key_filter */*kf*/);
 
 /* --- @key_open@ --- *
  *