+ new->serial = serial;
+ new->ret = 0;
+ new->group_start = 0;
+}
+
+static void op_add_nothing(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (words[2])
+ fail(filename, op_num+1, "Expected no arguments");
+ op[op_num].key = tdb_null;
+}
+
+static void op_add_key(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (words[2] == NULL || words[3])
+ fail(filename, op_num+1, "Expected just a key");
+
+ op[op_num].key = make_tdb_data(op, filename, op_num+1, words[2]);
+ total_keys++;
+}
+
+static void op_add_key_ret(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || words[5]
+ || !streq(words[3], "="))
+ fail(filename, op_num+1, "Expected <key> = <ret>");
+ op[op_num].ret = atoi(words[4]);
+ op[op_num].key = make_tdb_data(op, filename, op_num+1, words[2]);
+ /* May only be a unique key if it fails */
+ if (op[op_num].ret != 0)
+ total_keys++;
+}
+
+static void op_add_key_data(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || words[5]
+ || !streq(words[3], "="))
+ fail(filename, op_num+1, "Expected <key> = <data>");
+ op[op_num].key = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[4]);
+ /* May only be a unique key if it fails */
+ if (!op[op_num].data.dptr)
+ total_keys++;
+}
+
+/* We don't record the keys or data for a traverse, as we don't use them. */
+static void op_add_traverse(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || words[5]
+ || !streq(words[3], "="))
+ fail(filename, op_num+1, "Expected <key> = <data>");
+ op[op_num].key = tdb_null;
+}
+
+/* <serial> tdb_store <rec> <rec> <flag> = <ret> */
+static void op_add_store(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || !words[5] || !words[6]
+ || words[7] || !streq(words[5], "="))
+ fail(filename, op_num+1, "Expect <key> <data> <flag> = <ret>");
+
+ op[op_num].flag = strtoul(words[4], NULL, 0);
+ op[op_num].ret = atoi(words[6]);
+ op[op_num].key = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[3]);
+ total_keys++;
+}
+
+/* <serial> tdb_append <rec> <rec> = <rec> */
+static void op_add_append(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || !words[5] || words[6]
+ || !streq(words[4], "="))
+ fail(filename, op_num+1, "Expect <key> <data> = <rec>");
+
+ op[op_num].key = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[3]);
+
+ op[op_num].append.post
+ = make_tdb_data(op, filename, op_num+1, words[5]);
+
+ /* By subtraction, figure out what previous data was. */
+ op[op_num].append.pre.dptr = op[op_num].append.post.dptr;
+ op[op_num].append.pre.dsize
+ = op[op_num].append.post.dsize - op[op_num].data.dsize;
+ total_keys++;
+}
+
+/* <serial> tdb_get_seqnum = <ret> */
+static void op_add_seqnum(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (!words[2] || !words[3] || words[4] || !streq(words[2], "="))
+ fail(filename, op_num+1, "Expect = <ret>");
+
+ op[op_num].key = tdb_null;
+ op[op_num].ret = atoi(words[3]);
+}
+
+static void op_add_traverse_start(const char *filename,
+ struct op op[],
+ unsigned int op_num, char *words[])
+{
+ if (words[2])
+ fail(filename, op_num+1, "Expect no arguments");
+
+ op[op_num].key = tdb_null;
+ op[op_num].group_len = 0;
+}
+
+static void op_add_transaction(const char *filename, struct op op[],
+ unsigned int op_num, char *words[])
+{
+ if (words[2])
+ fail(filename, op_num+1, "Expect no arguments");
+
+ op[op_num].key = tdb_null;
+ op[op_num].group_len = 0;
+}
+
+static void op_add_chainlock(const char *filename,
+ struct op op[], unsigned int op_num, char *words[])
+{
+ if (words[2] == NULL || words[3])
+ fail(filename, op_num+1, "Expected just a key");
+
+ /* A chainlock key isn't a key in the normal sense; it doesn't
+ * have to be in the db at all. Also, we don't want to hash this op. */
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].key = tdb_null;
+ op[op_num].group_len = 0;
+}
+
+static void op_add_chainlock_ret(const char *filename,
+ struct op op[], unsigned int op_num,
+ char *words[])
+{
+ if (!words[2] || !words[3] || !words[4] || words[5]
+ || !streq(words[3], "="))
+ fail(filename, op_num+1, "Expected <key> = <ret>");
+ op[op_num].ret = atoi(words[4]);
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].key = tdb_null;
+ op[op_num].group_len = 0;
+ total_keys++;
+}
+
+static int op_find_start(struct op op[], unsigned int op_num, enum op_type type)
+{
+ unsigned int i;
+
+ for (i = op_num-1; i > 0; i--) {
+ if (op[i].op == type && !op[i].group_len)
+ return i;
+ }
+ return 0;
+}
+
+static void op_analyze_transaction(const char *filename,
+ struct op op[], unsigned int op_num,
+ char *words[])
+{
+ unsigned int start, i;
+
+ op[op_num].key = tdb_null;
+
+ if (words[2])
+ fail(filename, op_num+1, "Expect no arguments");
+
+ start = op_find_start(op, op_num, OP_TDB_TRANSACTION_START);
+ if (!start)
+ fail(filename, op_num+1, "no transaction start found");
+
+ op[start].group_len = op_num - start;
+
+ /* This rolls in nested transactions. I think that's right. */
+ for (i = start; i <= op_num; i++)
+ op[i].group_start = start;
+}
+
+/* We treat chainlocks a lot like transactions, even though that's overkill */
+static void op_analyze_chainlock(const char *filename,
+ struct op op[], unsigned int op_num,
+ char *words[])
+{
+ unsigned int i, start;
+
+ if (words[2] == NULL || words[3])
+ fail(filename, op_num+1, "Expected just a key");
+
+ op[op_num].data = make_tdb_data(op, filename, op_num+1, words[2]);
+ op[op_num].key = tdb_null;
+ total_keys++;
+
+ start = op_find_start(op, op_num, OP_TDB_CHAINLOCK);
+ if (!start)
+ start = op_find_start(op, op_num, OP_TDB_CHAINLOCK_READ);
+ if (!start)
+ fail(filename, op_num+1, "no initial chainlock found");
+
+ /* FIXME: We'd have to do something clever to make this work
+ * vs. deadlock. */
+ if (!key_eq(op[start].data, op[op_num].data))
+ fail(filename, op_num+1, "nested chainlock calls?");
+
+ op[start].group_len = op_num - start;
+ for (i = start; i <= op_num; i++)
+ op[i].group_start = start;
+}
+
+static void op_analyze_traverse(const char *filename,
+ struct op op[], unsigned int op_num,
+ char *words[])
+{
+ int i, start;
+
+ op[op_num].key = tdb_null;
+
+ /* = %u means traverse function terminated. */
+ if (words[2]) {
+ if (!streq(words[2], "=") || !words[3] || words[4])
+ fail(filename, op_num+1, "expect = <num>");
+ op[op_num].ret = atoi(words[3]);
+ } else
+ op[op_num].ret = 0;
+
+ start = op_find_start(op, op_num, OP_TDB_TRAVERSE_START);
+ if (!start)
+ start = op_find_start(op, op_num, OP_TDB_TRAVERSE_READ_START);
+ if (!start)
+ fail(filename, op_num+1, "no traversal start found");
+
+ op[start].group_len = op_num - start;
+
+ /* Don't roll in nested traverse/chainlock */
+ for (i = start; i <= op_num; i++)
+ if (!op[i].group_start)
+ op[i].group_start = start;
+}
+
+/* Keep -Wmissing-declarations happy: */
+const struct op_table *
+find_keyword (register const char *str, register unsigned int len);
+
+#include "keywords.c"
+
+struct depend {
+ /* We can have more than one */
+ struct list_node pre_list;
+ struct list_node post_list;
+ unsigned int needs_file;
+ unsigned int needs_opnum;
+ unsigned int satisfies_file;
+ unsigned int satisfies_opnum;
+};
+
+static void check_deps(const char *filename, struct op op[], unsigned int num)
+{
+#ifdef DEBUG_DEPS
+ unsigned int i;
+
+ for (i = 1; i < num; i++)
+ if (!list_empty(&op[i].pre))
+ fail(filename, i+1, "Still has dependencies");
+#endif
+}
+
+static void dump_pre(char *filename[], struct op *op[],
+ unsigned int file, unsigned int i)
+{
+ struct depend *dep;
+
+ printf("%s:%u (%u) still waiting for:\n", filename[file], i+1,
+ op[file][i].serial);
+ list_for_each(&op[file][i].pre, dep, pre_list)
+ printf(" %s:%u (%u)\n",
+ filename[dep->satisfies_file], dep->satisfies_opnum+1,
+ op[dep->satisfies_file][dep->satisfies_opnum].serial);
+ check_deps(filename[file], op[file], i);
+}
+
+/* We simply read/write pointers, since we all are children. */
+static bool do_pre(struct tdb_context *tdb,
+ char *filename[], struct op *op[],
+ unsigned int file, int pre_fd, unsigned int i,
+ bool backoff)
+{
+ while (!list_empty(&op[file][i].pre)) {
+ struct depend *dep;
+
+#if DEBUG_DEPS
+ printf("%s:%u:waiting for pre\n", filename[file], i+1);
+ fflush(stdout);
+#endif
+ if (backoff)
+ alarm(2);
+ else
+ alarm(10);
+ while (read(pre_fd, &dep, sizeof(dep)) != sizeof(dep)) {
+ if (errno == EINTR) {
+ if (backoff) {
+ warnx("%s:%u:avoiding deadlock",
+ filename[file], i+1);
+ return false;
+ }
+ dump_pre(filename, op, file, i);
+ exit(1);
+ } else
+ errx(1, "Reading from pipe");
+ }
+ alarm(0);
+
+#if DEBUG_DEPS
+ printf("%s:%u:got pre %u from %s:%u\n", filename[file], i+1,
+ dep->needs_opnum+1, filename[dep->satisfies_file],
+ dep->satisfies_opnum+1);
+ fflush(stdout);
+#endif
+ /* This could be any op, not just this one. */
+ talloc_free(dep);
+ }
+ return true;
+}
+
+static void do_post(char *filename[], struct op *op[],
+ unsigned int file, unsigned int i)
+{
+ struct depend *dep;
+
+ list_for_each(&op[file][i].post, dep, post_list) {
+#if DEBUG_DEPS
+ printf("%s:%u:sending to file %s:%u\n", filename[file], i+1,
+ filename[dep->needs_file], dep->needs_opnum+1);
+#endif
+ if (write(pipes[dep->needs_file].fd[1], &dep, sizeof(dep))
+ != sizeof(dep))
+ err(1, "%s:%u failed to tell file %s",
+ filename[file], i+1, filename[dep->needs_file]);
+ }