ctx: fetch `wpa_cli status` into ctx.wifi.*

### gather context data

# note: attr aren't available yet! upside is, `req -a ctx.foo=bar` can override this files code:

BEGIN {
    # interactive terminal
    if(attr["term"])
	setCtx("term", ENVIRON["TERM"])

    # x11 display
    if(ENVIRON["DISPLAY"]) {
	setCtx("display", ENVIRON["DISPLAY"])
	# x11 selection
	setCtx("sel", trim(btick("xclip -o")))
	# current x11 win id
	setCtx("xwin", word(btick("xprop -root _NET_ACTIVE_WINDOW"), 5))
	split_xprop(btick("xprop -id " get("ctx.xwin")), ctx, "ctx.xprop")
    }

    # x11 applications
    if(get("ctx.xprop.wm_class_name") == "Emacs") {
	setCtx("app.exe", "emacs")
	f = get("ctx.xprop.wm_name")
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "XTerm" &&
    match(get("ctx.xprop.wm_name"), /^([^@]*)@([^:]*):(.*)$/, m)) {
	setCtx("app.exe", "xterm")
	setCtx("app.user", m[1])
	setCtx("app.host", m[2])
	setCtx("app.dir", m[3])
	split_file(m[3], ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Rox" &&
    match(get("ctx.xprop.wm_name"), /^(.*) \+[ST]$/, m)) {
	setCtx("app.exe", "rox")
	f = gensub(/^~\//, HOME "/", 1, m[1])
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Sonata" &&
    match(get("ctx.xprop.wm_name"), /^(.*)\/([^/]*)$/, m)) {
	setCtx("app.exe", "sonata")
	setCtx("audio_time", m[3])
	setCtx("audio_total", m[4])
	f = "/home/rfigura/mnt/wirsing/home/rfigura/pub/audio/" m[1] "/" m[2]
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Hgtk" &&
    match(get("ctx.xprop.wm_name"), /^(.*) - (.*)$/, m)) {
	setCtx("app.exe", "hgtk")
	split_file(m[1], ctx, "ctx.file")
    }

    # working directory
    setCtx("wdir", ENVIRON["PWD"])
    if(get("ctx.file.dirname"))
	setCtx("wdir", get("ctx.file.dirname"))

    # current wifi
    split(btick("wpa_cli status"), m, "\n")
    for(i = 1; i <= length(m); ++i)
	if(match(m[i], /^([^=]*)=(.*)$/, a))
	    setCtx("wifi_" a[1], a[2])
}

### gather context data

# note: attr aren't available yet! upside is, `req -a ctx.foo=bar` can override this files code:

BEGIN {
    # interactive terminal
    if(attr["term"])
	setCtx("term", ENVIRON["TERM"])

    # x11 display
    if(ENVIRON["DISPLAY"]) {
	setCtx("display", ENVIRON["DISPLAY"])
	# x11 selection
	setCtx("sel", trim(btick("xclip -o")))
	# current x11 win id
	setCtx("xwin", word(btick("xprop -root _NET_ACTIVE_WINDOW"), 5))
	split_xprop(btick("xprop -id " get("ctx.xwin")), ctx, "ctx.xprop")
    }

    # x11 applications
    if(get("ctx.xprop.wm_class_name") == "Emacs") {
	setCtx("app.exe", "emacs")
	f = get("ctx.xprop.wm_name")
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "XTerm" &&
    match(get("ctx.xprop.wm_name"), /^([^@]*)@([^:]*):(.*)$/, m)) {
	setCtx("app.exe", "xterm")
	setCtx("app.user", m[1])
	setCtx("app.host", m[2])
	setCtx("app.dir", m[3])
	split_file(m[3], ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Rox" &&
    match(get("ctx.xprop.wm_name"), /^(.*) \+[ST]$/, m)) {
	setCtx("app.exe", "rox")
	f = gensub(/^~\//, HOME "/", 1, m[1])
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Sonata" &&
    match(get("ctx.xprop.wm_name"), /^(.*)\/([^/]*)$/, m)) {
	setCtx("app.exe", "sonata")
	setCtx("audio_time", m[3])
	setCtx("audio_total", m[4])
	f = "/home/rfigura/mnt/wirsing/home/rfigura/pub/audio/" m[1] "/" m[2]
	split_file(f, ctx, "ctx.file")
    }
    if(get("ctx.xprop.wm_class_name") == "Hgtk" &&
    match(get("ctx.xprop.wm_name"), /^(.*) - (.*)$/, m)) {
	setCtx("app.exe", "hgtk")
	split_file(m[1], ctx, "ctx.file")
    }

    # working directory
    setCtx("wdir", ENVIRON["PWD"])
    if(get("ctx.file.dirname"))
	setCtx("wdir", get("ctx.file.dirname"))
}


BUG#15734: Fix undefined awk behaviour breaking compilation on Debian.

#!/bin/sh -
#
# See the file LICENSE for redistribution information.
#
# Copyright (c) 1996-2002
#	Sleepycat Software.  All rights reserved.
#
# $Id: gen_rec.awk,v 11.70 2002/08/08 15:44:47 bostic Exp $
#

# This awk script generates all the log, print, and read routines for the DB
# logging. It also generates a template for the recovery functions (these
# functions must still be edited, but are highly stylized and the initial
# template gets you a fair way along the path).
#
# For a given file prefix.src, we generate a file prefix_auto.c, and a file
# prefix_auto.h that contains:
#
#	external declarations for the file's functions
# 	defines for the physical record types
#	    (logical types are defined in each subsystem manually)
#	structures to contain the data unmarshalled from the log.
#
# This awk script requires that four variables be set when it is called:
#
#	source_file	-- the C source file being created
#	header_file	-- the C #include file being created
#	template_file	-- the template file being created
#
# And stdin must be the input file that defines the recovery setup.
#
# Within each file prefix.src, we use a number of public keywords (documented
# in the reference guide) as well as the following ones which are private to
# DB:
# 	DBPRIVATE	Indicates that a file will be built as part of DB,
#			rather than compiled independently, and so can use
#			DB-private interfaces (such as DB_NOCOPY).
#	DB		A DB handle.  Logs the dbreg fileid for that handle,
#			and makes the *_log interface take a DB * instead of a
#			DB_ENV *.
#	PGDBT		Just like DBT, only we know it stores a page or page
#			header, so we can byte-swap it (once we write the
#			byte-swapping code, which doesn't exist yet).
#	WRLOCK
#	WRLOCKNZ	An ARG that stores a db_pgno_t, which the getpgnos
#			function should acquire a lock on.  WRLOCK implies
#			that we should always get the lock;  WRLOCKNZ implies
#			that we should do so if and only if the pgno is non-zero
#			(unfortunately, 0 is both PGNO_INVALID and the main
#			metadata page number).

BEGIN {
	if (source_file == "" ||
	    header_file == "" || template_file == "") {
	    print "Usage: gen_rec.awk requires three variables to be set:"
	    print "\tsource_file\t-- the C source file being created"
	    print "\theader_file\t-- the C #include file being created"
	    print "\ttemplate_file\t-- the template file being created"
	    exit
	}
	FS="[\t ][\t ]*"
	CFILE=source_file
	HFILE=header_file
	TFILE=template_file
	dbprivate = 0
}
/^[ 	]*DBPRIVATE/ {
	dbprivate = 1
}
/^[ 	]*PREFIX/ {
	prefix = $2
	num_funcs = 0;

	# Start .c file.
	printf("/* Do not edit: automatically built by gen_rec.awk. */\n") \
	    > CFILE

	# Start .h file, make the entire file conditional.
	printf("/* Do not edit: automatically built by gen_rec.awk. */\n\n") \
	    > HFILE
	printf("#ifndef\t%s_AUTO_H\n#define\t%s_AUTO_H\n", prefix, prefix) \
	    >> HFILE;

	# Write recovery template file headers
	# This assumes we're doing DB recovery.
	printf("#include \"db_config.h\"\n\n") > TFILE
	printf("#ifndef NO_SYSTEM_INCLUDES\n") >> TFILE
	printf("#include <sys/types.h>\n\n") >> TFILE
	printf("#include <string.h>\n") >> TFILE
	printf("#endif\n\n") >> TFILE
	printf("#include \"db_int.h\"\n") >> TFILE
	printf("#include \"dbinc/db_page.h\"\n") >> TFILE
	printf("#include \"dbinc/%s.h\"\n", prefix) >> TFILE
	printf("#include \"dbinc/log.h\"\n\n") >> TFILE
}
/^[ 	]*INCLUDE/ {
	if ($3 == "")
		printf("%s\n", $2) >> CFILE
	else
		printf("%s %s\n", $2, $3) >> CFILE
}
/^[ 	]*(BEGIN|IGNORED)/ {
	if (in_begin) {
		print "Invalid format: missing END statement"
		exit
	}
	in_begin = 1;
	is_dbt = 0;
	has_dbp = 0;
	is_uint = 0;
	need_log_function = ($1 == "BEGIN");
	nvars = 0;

	# number of locks that the getpgnos functions will return
	nlocks = 0;

	thisfunc = $2;
	funcname = sprintf("%s_%s", prefix, $2);

	rectype = $3;

	funcs[num_funcs] = funcname;
	++num_funcs;
}
/^[ 	]*(DB|ARG|DBT|PGDBT|POINTER|WRLOCK|WRLOCKNZ)/ {
	vars[nvars] = $2;
	types[nvars] = $3;
	atypes[nvars] = $1;
	modes[nvars] = $1;
	formats[nvars] = $NF;
	for (i = 4; i < NF; i++)
		types[nvars] = sprintf("%s %s", types[nvars], $i);

	if ($1 == "DB") {
		has_dbp = 1;
	}

	if ($1 == "DB" || $1 == "ARG" || $1 == "WRLOCK" || $1 == "WRLOCKNZ") {
		sizes[nvars] = sprintf("sizeof(u_int32_t)");
		is_uint = 1;
	} else if ($1 == "POINTER")
		sizes[nvars] = sprintf("sizeof(*%s)", $2);
	else { # DBT, PGDBT
		sizes[nvars] = \
		    sprintf("sizeof(u_int32_t) + (%s == NULL ? 0 : %s->size)", \
		    $2, $2);
		is_dbt = 1;
	}
	nvars++;
}
/^[	]*(WRLOCK|WRLOCKNZ)/ {
	nlocks++;

	if ($1 == "WRLOCK") {
		lock_if_zero[nlocks] = 1;
	} else {
		lock_if_zero[nlocks] = 0;
	}

	lock_pgnos[nlocks] = $2;
}
/^[ 	]*END/ {
	if (!in_begin) {
		print "Invalid format: missing BEGIN statement"
		exit;
	}

	# Declare the record type.
	printf("#define\tDB_%s\t%d\n", funcname, rectype) >> HFILE

	# Structure declaration.
	printf("typedef struct _%s_args {\n", funcname) >> HFILE

	# Here are the required fields for every structure
	printf("\tu_int32_t type;\n\tDB_TXN *txnid;\n") >> HFILE
	printf("\tDB_LSN prev_lsn;\n") >>HFILE

	# Here are the specified fields.
	for (i = 0; i < nvars; i++) {
		t = types[i];
		if (modes[i] == "POINTER") {
			ndx = index(t, "*");
			t = substr(types[i], 1, ndx - 2);
		}
		printf("\t%s\t%s;\n", t, vars[i]) >> HFILE
	}
	printf("} %s_args;\n\n", funcname) >> HFILE

	# Output the log, print, read, and getpgnos functions.
	if (need_log_function) {
		log_function();

		# The getpgnos function calls DB-private (__rep_*) functions,
		# so we only generate it for our own logging functions,
		# not application-specific ones.
		if (dbprivate) {
			getpgnos_function();
		}
	}
	print_function();
	read_function();

	# Recovery template
	cmd = sprintf(\
    "sed -e s/PREF/%s/ -e s/FUNC/%s/ < template/rec_ctemp >> %s",
	    prefix, thisfunc, TFILE)
	system(cmd);

	# Done writing stuff, reset and continue.
	in_begin = 0;
}

END {
	# End the conditional for the HFILE
	printf("#endif\n") >> HFILE;

	# Print initialization routine; function prototype
	p[1] = sprintf("int %s_init_print %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call __db_add_recovery(print_fn, id)
	printf("int\n%s_init_print(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n") >> CFILE;
	# If application-specific, the user will need a prototype for
	# __db_add_recovery, since they won't have DB's.
	if (!dbprivate) {
		printf("\tint __db_add_recovery __P((DB_ENV *,\n") >> CFILE;
		printf(\
"\t    int (***)(DB_ENV *, DBT *, DB_LSN *, db_recops, void *),\n") >> CFILE;
		printf("\t    size_t *,\n") >> CFILE;
		printf(\
"\t    int (*)(DB_ENV *, DBT *, DB_LSN *, db_recops, void *), u_int32_t));\n") \
		    >> CFILE;
	}

	printf("\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_print, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n\n") >> CFILE;

	# We only want to generate *_init_{getpgnos,recover} functions
	# if this is a DB-private, rather than application-specific,
	# set of recovery functions.  Application-specific recovery functions
	# should be dispatched using the DB_ENV->set_app_dispatch callback
	# rather than a DB dispatch table ("dtab").
	if (!dbprivate)
		exit

	# Page number initialization routine; function prototype
	p[1] = sprintf("int %s_init_getpgnos %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call db_add_recovery(pgno_fn, id)
	printf("int\n%s_init_getpgnos(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_getpgnos, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n\n") >> CFILE;

	# Recover initialization routine
	p[1] = sprintf("int %s_init_recover %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call db_add_recovery(func, id)
	printf("int\n%s_init_recover(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_recover, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n") >> CFILE;
}

function log_function() {
	# Write the log function; function prototype
	pi = 1;
	p[pi++] = sprintf("int %s_log", funcname);
	p[pi++] = " ";
	if (has_dbp == 1) {
		p[pi++] = "__P((DB *, DB_TXN *, DB_LSN *, u_int32_t";
	} else {
		p[pi++] = "__P((DB_ENV *, DB_TXN *, DB_LSN *, u_int32_t";
	}
	for (i = 0; i < nvars; i++) {
		if (modes[i] == "DB")
			continue;
		p[pi++] = ", ";
		p[pi++] = sprintf("%s%s%s",
		    (modes[i] == "DBT" || modes[i] == "PGDBT") ? "const " : "",
		    types[i],
		    (modes[i] == "DBT" || modes[i] == "PGDBT") ? " *" : "");
	}
	p[pi++] = "";
	p[pi++] = "));";
	p[pi++] = "";
	proto_format(p);

	# Function declaration
	if (has_dbp == 1) {
		printf("int\n%s_log(dbp, txnid, ret_lsnp, flags", \
		    funcname) >> CFILE;
	} else {
		printf("int\n%s_log(dbenv, txnid, ret_lsnp, flags", \
		    funcname) >> CFILE;
	}
	for (i = 0; i < nvars; i++) {
		if (modes[i] == "DB") {
			# We pass in fileids on the dbp, so if this is one,
			# skip it.
			continue;
		}
		printf(",") >> CFILE;
		if ((i % 6) == 0)
			printf("\n    ") >> CFILE;
		else
			printf(" ") >> CFILE;
		printf("%s", vars[i]) >> CFILE;
	}
	printf(")\n") >> CFILE;

	# Now print the parameters
	if (has_dbp == 1) {
		printf("\tDB *dbp;\n") >> CFILE;
	} else {
		printf("\tDB_ENV *dbenv;\n") >> CFILE;
	}
	printf("\tDB_TXN *txnid;\n\tDB_LSN *ret_lsnp;\n") >> CFILE;
	printf("\tu_int32_t flags;\n") >> CFILE;
	for (i = 0; i < nvars; i++) {
		# We just skip for modes == DB.
		if (modes[i] == "DBT" || modes[i] == "PGDBT")
			printf("\tconst %s *%s;\n", types[i], vars[i]) >> CFILE;
		else if (modes[i] != "DB")
			printf("\t%s %s;\n", types[i], vars[i]) >> CFILE;
	}

	# Function body and local decls
	printf("{\n") >> CFILE;
	printf("\tDBT logrec;\n") >> CFILE;
	if (has_dbp == 1)
		printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDB_LSN *lsnp, null_lsn;\n") >> CFILE;
	if (is_dbt == 1)
		printf("\tu_int32_t zero;\n") >> CFILE;
	if (is_uint == 1)
		printf("\tu_int32_t uinttmp;\n") >> CFILE;
	printf("\tu_int32_t npad, rectype, txn_num;\n") >> CFILE;
	printf("\tint ret;\n") >> CFILE;
	printf("\tu_int8_t *bp;\n\n") >> CFILE;

	# Initialization
	if (has_dbp == 1)
		printf("\tdbenv = dbp->dbenv;\n") >> CFILE;
	printf("\trectype = DB_%s;\n", funcname) >> CFILE;
	printf("\tnpad = 0;\n\n") >> CFILE;

	printf("\tif (txnid == NULL) {\n") >> CFILE;
	printf("\t\ttxn_num = 0;\n") >> CFILE;
	printf("\t\tnull_lsn.file = 0;\n") >> CFILE;
	printf("\t\tnull_lsn.offset = 0;\n") >> CFILE;
	printf("\t\tlsnp = &null_lsn;\n") >> CFILE;
	printf("\t} else {\n") >> CFILE;
	if (funcname != "__db_debug" && dbprivate) {
		printf(\
		    "\t\tif (TAILQ_FIRST(&txnid->kids) != NULL &&\n") >> CFILE;
		printf("\t\t    (ret = __txn_activekids(") >> CFILE;
		printf("dbenv, rectype, txnid)) != 0)\n") >> CFILE;
		printf("\t\t\treturn (ret);\n") >> CFILE;
	}
	printf("\t\ttxn_num = txnid->txnid;\n") >> CFILE;
	printf("\t\tlsnp = &txnid->last_lsn;\n") >> CFILE;
	printf("\t}\n\n") >> CFILE;

	# Malloc
	printf("\tlogrec.size = sizeof(rectype) + ") >> CFILE;
	printf("sizeof(txn_num) + sizeof(DB_LSN)") >> CFILE;
	for (i = 0; i < nvars; i++)
		printf("\n\t    + %s", sizes[i]) >> CFILE;
	printf(";\n") >> CFILE
	if (dbprivate) {
		printf("\tif (CRYPTO_ON(dbenv)) {\n") >> CFILE;
		printf("\t\tnpad =\n") >> CFILE
		printf(\
"\t\t    ((DB_CIPHER *)dbenv->crypto_handle)->adj_size(logrec.size);\n")\
		    >> CFILE;
		printf("\t\tlogrec.size += npad;\n\t}\n\n") >> CFILE
	}
	write_malloc("logrec.data", "logrec.size", CFILE)
	printf("\tif (npad > 0)\n") >> CFILE;
	printf("\t\tmemset((u_int8_t *)logrec.data + logrec.size ") >> CFILE;
	printf("- npad, 0, npad);\n\n") >> CFILE;

	# Copy args into buffer
	printf("\tbp = logrec.data;\n\n") >> CFILE;
	printf("\tmemcpy(bp, &rectype, sizeof(rectype));\n") >> CFILE;
	printf("\tbp += sizeof(rectype);\n\n") >> CFILE;
	printf("\tmemcpy(bp, &txn_num, sizeof(txn_num));\n") >> CFILE;
	printf("\tbp += sizeof(txn_num);\n\n") >> CFILE;
	printf("\tmemcpy(bp, lsnp, sizeof(DB_LSN));\n") >> CFILE;
	printf("\tbp += sizeof(DB_LSN);\n\n") >> CFILE;

	for (i = 0; i < nvars; i ++) {
		if (modes[i] == "ARG" || modes[i] == "WRLOCK" || \
		    modes[i] == "WRLOCKNZ") {
			printf("\tuinttmp = (u_int32_t)%s;\n", \
			    vars[i]) >> CFILE;
			printf("\tmemcpy(bp, &uinttmp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n\n") >> CFILE;
		} else if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tif (%s == NULL) {\n", vars[i]) >> CFILE;
			printf("\t\tzero = 0;\n") >> CFILE;
			printf("\t\tmemcpy(bp, &zero, sizeof(u_int32_t));\n") \
				>> CFILE;
			printf("\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
			printf("\t} else {\n") >> CFILE;
			printf("\t\tmemcpy(bp, &%s->size, ", vars[i]) >> CFILE;
			printf("sizeof(%s->size));\n", vars[i]) >> CFILE;
			printf("\t\tbp += sizeof(%s->size);\n", vars[i]) \
			    >> CFILE;
			printf("\t\tmemcpy(bp, %s->data, %s->size);\n", \
			    vars[i], vars[i]) >> CFILE;
			printf("\t\tbp += %s->size;\n\t}\n\n", \
			    vars[i]) >> CFILE;
		} else if (modes[i] == "DB") {
			# We need to log a DB handle.  To do this, we
			# actually just log its fileid;  from that, we'll
			# be able to acquire an open handle at recovery time.
			printf("\tDB_ASSERT(dbp->log_filename != NULL);\n") \
			    >> CFILE;
			printf("\tif (dbp->log_filename->id == ") >> CFILE;
			printf("DB_LOGFILEID_INVALID &&\n\t    ") >> CFILE
			printf("(ret = __dbreg_lazy_id(dbp)) != 0)\n") \
			    >> CFILE;
			printf("\t\treturn (ret);\n\n") >> CFILE;

			printf("\tuinttmp = ") >> CFILE;
			printf("(u_int32_t)dbp->log_filename->id;\n") >> CFILE;
			printf("\tmemcpy(bp, &uinttmp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n\n") >> CFILE;
		} else { # POINTER
			printf("\tif (%s != NULL)\n", vars[i]) >> CFILE;
			printf("\t\tmemcpy(bp, %s, %s);\n", vars[i], \
			    sizes[i]) >> CFILE;
			printf("\telse\n") >> CFILE;
			printf("\t\tmemset(bp, 0, %s);\n", sizes[i]) >> CFILE;
			printf("\tbp += %s;\n\n", sizes[i]) >> CFILE;
		}
	}

	# Error checking.  User code won't have DB_ASSERT available, but
	# this is a pretty unlikely assertion anyway, so we just leave it out
	# rather than requiring assert.h.
	if (dbprivate) {
		printf("\tDB_ASSERT((u_int32_t)") >> CFILE;
		printf("(bp - (u_int8_t *)logrec.data) <= logrec.size);\n") \
		    >> CFILE;
	}

	# Issue log call
	# We didn't call the crypto alignment function when we created this
	# log record (because we don't have the right header files to find
	# the function), so we have to copy the log record to make sure the
	# alignment is correct.
	printf(\
	    "\tret = dbenv->log_put(dbenv,\n\t   ret_lsnp, (DBT *)&logrec, ") \
	    >> CFILE;
	if (dbprivate) {
		printf("flags | DB_NOCOPY);\n") >> CFILE;
	} else {
		printf("flags);\n") >> CFILE;
	}

	# Update the transactions last_lsn
	printf("\tif (txnid != NULL && ret == 0)\n") >> CFILE;
	printf("\t\ttxnid->last_lsn = *ret_lsnp;\n") >> CFILE;

	# If out of disk space log writes may fail.  If we are debugging
	# that print out which records did not make it to disk.
	printf("#ifdef LOG_DIAGNOSTIC\n") >> CFILE
	printf("\tif (ret != 0)\n") >> CFILE;
	printf("\t\t(void)%s_print(dbenv,\n", funcname) >> CFILE;
	printf("\t\t    (DBT *)&logrec, ret_lsnp, NULL, NULL);\n") >> CFILE
	printf("#endif\n") >> CFILE

	# Free and return
	write_free("logrec.data", CFILE)
	printf("\treturn (ret);\n}\n\n") >> CFILE;
}

function print_function() {
	# Write the print function; function prototype
	p[1] = sprintf("int %s_print", funcname);
	p[2] = " ";
	p[3] = "__P((DB_ENV *, DBT *, DB_LSN *, db_recops, void *));";
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_print(dbenv, ", funcname) >> CFILE;
	printf("dbtp, lsnp, notused2, notused3)\n") >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDBT *dbtp;\n") >> CFILE;
	printf("\tDB_LSN *lsnp;\n") >> CFILE;
	printf("\tdb_recops notused2;\n\tvoid *notused3;\n{\n") >> CFILE;

	# Locals
	printf("\t%s_args *argp;\n", funcname) >> CFILE;
	for (i = 0; i < nvars; i ++)
		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tu_int32_t i;\n") >> CFILE
			printf("\tint ch;\n") >> CFILE
			break;
		}

	printf("\tint ret;\n\n") >> CFILE;

	# Get rid of complaints about unused parameters.
	printf("\tnotused2 = DB_TXN_ABORT;\n\tnotused3 = NULL;\n\n") >> CFILE;

	# Call read routine to initialize structure
	printf("\tif ((ret = %s_read(dbenv, dbtp->data, &argp)) != 0)\n", \
	    funcname) >> CFILE;
	printf("\t\treturn (ret);\n") >> CFILE;

	# Print values in every record
	printf("\t(void)printf(\n\t    \"[%%lu][%%lu]%s: ", funcname) >> CFILE;
	printf("rec: %%lu txnid %%lx ") >> CFILE;
	printf("prevlsn [%%lu][%%lu]\\n\",\n") >> CFILE;
	printf("\t    (u_long)lsnp->file,\n") >> CFILE;
	printf("\t    (u_long)lsnp->offset,\n") >> CFILE;
	printf("\t    (u_long)argp->type,\n") >> CFILE;
	printf("\t    (u_long)argp->txnid->txnid,\n") >> CFILE;
	printf("\t    (u_long)argp->prev_lsn.file,\n") >> CFILE;
	printf("\t    (u_long)argp->prev_lsn.offset);\n") >> CFILE;

	# Now print fields of argp
	for (i = 0; i < nvars; i ++) {
		printf("\t(void)printf(\"\\t%s: ", vars[i]) >> CFILE;

		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\");\n") >> CFILE;
			printf("\tfor (i = 0; i < ") >> CFILE;
			printf("argp->%s.size; i++) {\n", vars[i]) >> CFILE;
			printf("\t\tch = ((u_int8_t *)argp->%s.data)[i];\n", \
			    vars[i]) >> CFILE;
			printf("\t\tprintf(isprint(ch) || ch == 0x0a") >> CFILE;
			printf(" ? \"%%c\" : \"%%#x \", ch);\n") >> CFILE;
			printf("\t}\n\t(void)printf(\"\\n\");\n") >> CFILE;
		} else if (types[i] == "DB_LSN *") {
			printf("[%%%s][%%%s]\\n\",\n", \
			    formats[i], formats[i]) >> CFILE;
			printf("\t    (u_long)argp->%s.file,", \
			    vars[i]) >> CFILE;
			printf(" (u_long)argp->%s.offset);\n", \
			    vars[i]) >> CFILE;
		} else {
			if (formats[i] == "lx")
				printf("0x") >> CFILE;
			printf("%%%s\\n\", ", formats[i]) >> CFILE;
			if (formats[i] == "lx" || formats[i] == "lu")
				printf("(u_long)") >> CFILE;
			if (formats[i] == "ld")
				printf("(long)") >> CFILE;
			printf("argp->%s);\n", vars[i]) >> CFILE;
		}
	}
	printf("\t(void)printf(\"\\n\");\n") >> CFILE;
	write_free("argp", CFILE);
	printf("\treturn (0);\n") >> CFILE;
	printf("}\n\n") >> CFILE;
}

function read_function() {
	# Write the read function; function prototype
	p[1] = sprintf("int %s_read __P((DB_ENV *, void *,", funcname);
	p[2] = " ";
	p[3] = sprintf("%s_args **));", funcname);
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_read(dbenv, recbuf, argpp)\n", funcname) >> CFILE;

	# Now print the parameters
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tvoid *recbuf;\n") >> CFILE;
	printf("\t%s_args **argpp;\n", funcname) >> CFILE;

	# Function body and local decls
	printf("{\n\t%s_args *argp;\n", funcname) >> CFILE;
	if (is_uint == 1)
		printf("\tu_int32_t uinttmp;\n") >> CFILE;
	printf("\tu_int8_t *bp;\n") >> CFILE;

	if (dbprivate) {
		# We only use dbenv and ret in the private malloc case.
		printf("\tint ret;\n\n") >> CFILE;
	} else {
		printf("\t/* Keep the compiler quiet. */\n") >> CFILE;
		printf("\n\tdbenv = NULL;\n") >> CFILE;
	}

	malloc_size = sprintf("sizeof(%s_args) + sizeof(DB_TXN)", funcname)
	write_malloc("argp", malloc_size, CFILE)

	# Set up the pointers to the txnid.
	printf("\targp->txnid = (DB_TXN *)&argp[1];\n\n") >> CFILE;

	# First get the record type, prev_lsn, and txnid fields.

	printf("\tbp = recbuf;\n") >> CFILE;
	printf("\tmemcpy(&argp->type, bp, sizeof(argp->type));\n") >> CFILE;
	printf("\tbp += sizeof(argp->type);\n\n") >> CFILE;
	printf("\tmemcpy(&argp->txnid->txnid,  bp, ") >> CFILE;
	printf("sizeof(argp->txnid->txnid));\n") >> CFILE;
	printf("\tbp += sizeof(argp->txnid->txnid);\n\n") >> CFILE;
	printf("\tmemcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));\n") >> CFILE;
	printf("\tbp += sizeof(DB_LSN);\n\n") >> CFILE;

	# Now get rest of data.
	for (i = 0; i < nvars; i ++) {
		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tmemset(&argp->%s, 0, sizeof(argp->%s));\n", \
			    vars[i], vars[i]) >> CFILE;
			printf("\tmemcpy(&argp->%s.size, ", vars[i]) >> CFILE;
			printf("bp, sizeof(u_int32_t));\n") >> CFILE;
			printf("\tbp += sizeof(u_int32_t);\n") >> CFILE;
			printf("\targp->%s.data = bp;\n", vars[i]) >> CFILE;
			printf("\tbp += argp->%s.size;\n", vars[i]) >> CFILE;
		} else if (modes[i] == "ARG" || modes[i] == "WRLOCK" || \
		    modes[i] == "WRLOCKNZ" || modes[i] == "DB") {
			printf("\tmemcpy(&uinttmp, bp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\targp->%s = (%s)uinttmp;\n", vars[i], \
			    types[i]) >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n") >> CFILE;
		} else { # POINTER
			printf("\tmemcpy(&argp->%s, bp, ", vars[i]) >> CFILE;
			printf(" sizeof(argp->%s));\n", vars[i]) >> CFILE;
			printf("\tbp += sizeof(argp->%s);\n", vars[i]) >> CFILE;
		}
		printf("\n") >> CFILE;
	}

	# Free and return
	printf("\t*argpp = argp;\n") >> CFILE;
	printf("\treturn (0);\n}\n\n") >> CFILE;
}

function getpgnos_function() {
	# Write the getpgnos function;  function prototype
	p[1] = sprintf("int %s_getpgnos", funcname);
	p[2] = " ";
	p[3] = "__P((DB_ENV *, DBT *, DB_LSN *, db_recops, void *));";
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_getpgnos(dbenv, ", funcname) >> CFILE;
	printf("rec, lsnp, notused1, summary)\n") >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDBT *rec;\n") >> CFILE;
	printf("\tDB_LSN *lsnp;\n") >> CFILE;
	printf("\tdb_recops notused1;\n") >> CFILE;
	printf("\tvoid *summary;\n{\n") >> CFILE;

	# If there are no locks, return this fact.
	if (nlocks == 0) {
		printf("\tTXN_RECS *t;\n") >> CFILE;
		printf("\tint ret;\n") >> CFILE;
		printf("\tCOMPQUIET(rec, NULL);\n") >> CFILE;
		printf("\tCOMPQUIET(notused1, DB_TXN_ABORT);\n") >> CFILE;

		printf("\n\tt = (TXN_RECS *)summary;\n") >> CFILE;
		printf("\n\tif ((ret = __rep_check_alloc(dbenv, ") >> CFILE;
		printf("t, 1)) != 0)\n") >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;

		printf("\n\tt->array[t->npages].flags = LSN_PAGE_NOLOCK;\n") \
			>> CFILE;
		printf("\tt->array[t->npages].lsn = *lsnp;\n") >> CFILE;
		printf("\tt->array[t->npages].fid = DB_LOGFILEID_INVALID;\n") \
			>> CFILE;
		printf("\tmemset(&t->array[t->npages].pgdesc, 0,\n") >> CFILE;
		printf("\t    sizeof(t->array[t->npages].pgdesc));\n") >> CFILE;
		printf("\n\tt->npages++;\n") >> CFILE;

		printf("\n") >> CFILE;
		printf("\treturn (0);\n") >> CFILE;
		printf("}\n\n") >> CFILE;
		return;
	}

	# Locals
	printf("\tDB *dbp;\n") >> CFILE;
	printf("\tTXN_RECS *t;\n") >> CFILE;
	printf("\t%s_args *argp;\n", funcname) >> CFILE;
	printf("\tu_int32_t ret;\n\n") >> CFILE;

	# Shut up compiler.
	printf("\tCOMPQUIET(notused1, DB_TXN_ABORT);\n\n") >> CFILE;

	printf("\targp = NULL;\n") >> CFILE;
	printf("\tt = (TXN_RECS *)summary;\n\n") >> CFILE;

	printf("\tif ((ret = %s_read(dbenv, rec->data, &argp)) != 0)\n", \
		funcname) >> CFILE;
	printf("\t\treturn (ret);\n") >> CFILE;

	# Get file ID.
	printf("\n\tif ((ret = __dbreg_id_to_db(dbenv,\n\t    ") >> CFILE;
	printf("argp->txnid, &dbp, argp->fileid, 0)) != 0)\n") >> CFILE;
	printf("\t\tgoto err;\n") >> CFILE;

	printf("\n\tif ((ret = __rep_check_alloc(dbenv, t, %d)) != 0)\n", \
		nlocks) >> CFILE;
	printf("\t\tgoto err;\n\n") >> CFILE;

	for (i = 1; i <= nlocks; i++) {
		if (lock_if_zero[i]) {
			indent = "\t";
		} else {
			indent = "\t\t";
			printf("\tif (argp->%s != PGNO_INVALID) {\n", \
				lock_pgnos[i]) >> CFILE;
		}
		printf("%st->array[t->npages].flags = 0;\n", indent) >> CFILE;
		printf("%st->array[t->npages].fid = argp->fileid;\n", indent) \
		    >> CFILE;
		printf("%st->array[t->npages].lsn = *lsnp;\n", indent) >> CFILE;
		printf("%st->array[t->npages].pgdesc.pgno = argp->%s;\n", \
			indent, lock_pgnos[i]) >> CFILE;
		printf("%st->array[t->npages].pgdesc.type = DB_PAGE_LOCK;\n", \
			indent) >> CFILE;
		printf("%smemcpy(t->array[t->npages].pgdesc.fileid, ", indent) \
			>> CFILE;
		printf("dbp->fileid,\n%s    DB_FILE_ID_LEN);\n", \
			indent, indent) >> CFILE;
		printf("%st->npages++;\n", indent) >> CFILE;
		if (!lock_if_zero[i]) {
			printf("\t}\n") >> CFILE;
		}
	}

	printf("\nerr:\tif (argp != NULL)\n") >> CFILE;
	write_free("argp", CFILE);

	printf("\treturn (ret);\n") >> CFILE;

	printf("}\n\n") >> CFILE;
}

# proto_format --
#	Pretty-print a function prototype.
function proto_format(p)
{
	printf("/*\n") >> CFILE;

	s = "";
	for (i = 1; i in p; ++i)
		s = s p[i];

	t = " * PUBLIC: "
	if (length(s) + length(t) < 80)
		printf("%s%s", t, s) >> CFILE;
	else {
		split(s, p, "__P");
		len = length(t) + length(p[1]);
		printf("%s%s", t, p[1]) >> CFILE

		n = split(p[2], comma, ",");
		comma[1] = "__P" comma[1];
		for (i = 1; i <= n; i++) {
			if (len + length(comma[i]) > 70) {
				printf("\n * PUBLIC:    ") >> CFILE;
				len = 0;
			}
			printf("%s%s", comma[i], i == n ? "" : ",") >> CFILE;
			len += length(comma[i]) + 2;
		}
	}
	printf("\n */\n") >> CFILE;
	delete p;
}

function write_malloc(ptr, size, file)
{
	if (dbprivate) {
		printf("\tif ((ret = ") >> file;
		printf(\
		    "__os_malloc(dbenv,\n\t    " size ", &" ptr ")) != 0)\n") \
		    >> file
		printf("\t\treturn (ret);\n\n") >> file;
	} else {
		printf("\tif ((" ptr " = malloc(" size ")) == NULL)\n") >> file
		printf("\t\treturn (ENOMEM);\n\n") >> file
	}
}

function write_free(ptr, file)
{
	if (dbprivate) {
		printf("\t__os_free(dbenv, " ptr ");\n") >> file
	} else {
		printf("\tfree(" ptr ");\n") >> file
	}
}

#!/bin/sh -
#
# See the file LICENSE for redistribution information.
#
# Copyright (c) 1996-2002
#	Sleepycat Software.  All rights reserved.
#
# $Id: gen_rec.awk,v 11.70 2002/08/08 15:44:47 bostic Exp $
#

# This awk script generates all the log, print, and read routines for the DB
# logging. It also generates a template for the recovery functions (these
# functions must still be edited, but are highly stylized and the initial
# template gets you a fair way along the path).
#
# For a given file prefix.src, we generate a file prefix_auto.c, and a file
# prefix_auto.h that contains:
#
#	external declarations for the file's functions
# 	defines for the physical record types
#	    (logical types are defined in each subsystem manually)
#	structures to contain the data unmarshalled from the log.
#
# This awk script requires that four variables be set when it is called:
#
#	source_file	-- the C source file being created
#	header_file	-- the C #include file being created
#	template_file	-- the template file being created
#
# And stdin must be the input file that defines the recovery setup.
#
# Within each file prefix.src, we use a number of public keywords (documented
# in the reference guide) as well as the following ones which are private to
# DB:
# 	DBPRIVATE	Indicates that a file will be built as part of DB,
#			rather than compiled independently, and so can use
#			DB-private interfaces (such as DB_NOCOPY).
#	DB		A DB handle.  Logs the dbreg fileid for that handle,
#			and makes the *_log interface take a DB * instead of a
#			DB_ENV *.
#	PGDBT		Just like DBT, only we know it stores a page or page
#			header, so we can byte-swap it (once we write the
#			byte-swapping code, which doesn't exist yet).
#	WRLOCK
#	WRLOCKNZ	An ARG that stores a db_pgno_t, which the getpgnos
#			function should acquire a lock on.  WRLOCK implies
#			that we should always get the lock;  WRLOCKNZ implies
#			that we should do so if and only if the pgno is non-zero
#			(unfortunately, 0 is both PGNO_INVALID and the main
#			metadata page number).

BEGIN {
	if (source_file == "" ||
	    header_file == "" || template_file == "") {
	    print "Usage: gen_rec.awk requires three variables to be set:"
	    print "\tsource_file\t-- the C source file being created"
	    print "\theader_file\t-- the C #include file being created"
	    print "\ttemplate_file\t-- the template file being created"
	    exit
	}
	FS="[\t ][\t ]*"
	CFILE=source_file
	HFILE=header_file
	TFILE=template_file
	dbprivate = 0
}
/^[ 	]*DBPRIVATE/ {
	dbprivate = 1
}
/^[ 	]*PREFIX/ {
	prefix = $2
	num_funcs = 0;

	# Start .c file.
	printf("/* Do not edit: automatically built by gen_rec.awk. */\n") \
	    > CFILE

	# Start .h file, make the entire file conditional.
	printf("/* Do not edit: automatically built by gen_rec.awk. */\n\n") \
	    > HFILE
	printf("#ifndef\t%s_AUTO_H\n#define\t%s_AUTO_H\n", prefix, prefix) \
	    >> HFILE;

	# Write recovery template file headers
	# This assumes we're doing DB recovery.
	printf("#include \"db_config.h\"\n\n") > TFILE
	printf("#ifndef NO_SYSTEM_INCLUDES\n") >> TFILE
	printf("#include <sys/types.h>\n\n") >> TFILE
	printf("#include <string.h>\n") >> TFILE
	printf("#endif\n\n") >> TFILE
	printf("#include \"db_int.h\"\n") >> TFILE
	printf("#include \"dbinc/db_page.h\"\n") >> TFILE
	printf("#include \"dbinc/%s.h\"\n", prefix) >> TFILE
	printf("#include \"dbinc/log.h\"\n\n") >> TFILE
}
/^[ 	]*INCLUDE/ {
	if ($3 == "")
		printf("%s\n", $2) >> CFILE
	else
		printf("%s %s\n", $2, $3) >> CFILE
}
/^[ 	]*(BEGIN|IGNORED)/ {
	if (in_begin) {
		print "Invalid format: missing END statement"
		exit
	}
	in_begin = 1;
	is_dbt = 0;
	has_dbp = 0;
	is_uint = 0;
	need_log_function = ($1 == "BEGIN");
	nvars = 0;

	# number of locks that the getpgnos functions will return
	nlocks = 0;

	thisfunc = $2;
	funcname = sprintf("%s_%s", prefix, $2);

	rectype = $3;

	funcs[num_funcs] = funcname;
	++num_funcs;
}
/^[ 	]*(DB|ARG|DBT|PGDBT|POINTER|WRLOCK|WRLOCKNZ)/ {
	vars[nvars] = $2;
	types[nvars] = $3;
	atypes[nvars] = $1;
	modes[nvars] = $1;
	formats[nvars] = $NF;
	for (i = 4; i < NF; i++)
		types[nvars] = sprintf("%s %s", types[nvars], $i);

	if ($1 == "DB") {
		has_dbp = 1;
	}

	if ($1 == "DB" || $1 == "ARG" || $1 == "WRLOCK" || $1 == "WRLOCKNZ") {
		sizes[nvars] = sprintf("sizeof(u_int32_t)");
		is_uint = 1;
	} else if ($1 == "POINTER")
		sizes[nvars] = sprintf("sizeof(*%s)", $2);
	else { # DBT, PGDBT
		sizes[nvars] = \
		    sprintf("sizeof(u_int32_t) + (%s == NULL ? 0 : %s->size)", \
		    $2, $2);
		is_dbt = 1;
	}
	nvars++;
}
/^[	]*(WRLOCK|WRLOCKNZ)/ {
	nlocks++;

	if ($1 == "WRLOCK") {
		lock_if_zero[nlocks] = 1;
	} else {
		lock_if_zero[nlocks] = 0;
	}

	lock_pgnos[nlocks] = $2;
}
/^[ 	]*END/ {
	if (!in_begin) {
		print "Invalid format: missing BEGIN statement"
		exit;
	}

	# Declare the record type.
	printf("#define\tDB_%s\t%d\n", funcname, rectype) >> HFILE

	# Structure declaration.
	printf("typedef struct _%s_args {\n", funcname) >> HFILE

	# Here are the required fields for every structure
	printf("\tu_int32_t type;\n\tDB_TXN *txnid;\n") >> HFILE
	printf("\tDB_LSN prev_lsn;\n") >>HFILE

	# Here are the specified fields.
	for (i = 0; i < nvars; i++) {
		t = types[i];
		if (modes[i] == "POINTER") {
			ndx = index(t, "*");
			t = substr(types[i], 0, ndx - 2);
		}
		printf("\t%s\t%s;\n", t, vars[i]) >> HFILE
	}
	printf("} %s_args;\n\n", funcname) >> HFILE

	# Output the log, print, read, and getpgnos functions.
	if (need_log_function) {
		log_function();

		# The getpgnos function calls DB-private (__rep_*) functions,
		# so we only generate it for our own logging functions,
		# not application-specific ones.
		if (dbprivate) {
			getpgnos_function();
		}
	}
	print_function();
	read_function();

	# Recovery template
	cmd = sprintf(\
    "sed -e s/PREF/%s/ -e s/FUNC/%s/ < template/rec_ctemp >> %s",
	    prefix, thisfunc, TFILE)
	system(cmd);

	# Done writing stuff, reset and continue.
	in_begin = 0;
}

END {
	# End the conditional for the HFILE
	printf("#endif\n") >> HFILE;

	# Print initialization routine; function prototype
	p[1] = sprintf("int %s_init_print %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call __db_add_recovery(print_fn, id)
	printf("int\n%s_init_print(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n") >> CFILE;
	# If application-specific, the user will need a prototype for
	# __db_add_recovery, since they won't have DB's.
	if (!dbprivate) {
		printf("\tint __db_add_recovery __P((DB_ENV *,\n") >> CFILE;
		printf(\
"\t    int (***)(DB_ENV *, DBT *, DB_LSN *, db_recops, void *),\n") >> CFILE;
		printf("\t    size_t *,\n") >> CFILE;
		printf(\
"\t    int (*)(DB_ENV *, DBT *, DB_LSN *, db_recops, void *), u_int32_t));\n") \
		    >> CFILE;
	}

	printf("\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_print, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n\n") >> CFILE;

	# We only want to generate *_init_{getpgnos,recover} functions
	# if this is a DB-private, rather than application-specific,
	# set of recovery functions.  Application-specific recovery functions
	# should be dispatched using the DB_ENV->set_app_dispatch callback
	# rather than a DB dispatch table ("dtab").
	if (!dbprivate)
		exit

	# Page number initialization routine; function prototype
	p[1] = sprintf("int %s_init_getpgnos %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call db_add_recovery(pgno_fn, id)
	printf("int\n%s_init_getpgnos(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_getpgnos, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n\n") >> CFILE;

	# Recover initialization routine
	p[1] = sprintf("int %s_init_recover %s%s", prefix,
	    "__P((DB_ENV *, int (***)(DB_ENV *, DBT *, DB_LSN *, ",
	    "db_recops, void *), size_t *));");
	p[2] = "";
	proto_format(p);

	# Create the routine to call db_add_recovery(func, id)
	printf("int\n%s_init_recover(dbenv, dtabp, dtabsizep)\n", \
	    prefix) >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tint (***dtabp)__P((DB_ENV *, DBT *, DB_LSN *,") >> CFILE;
	printf(" db_recops, void *));\n") >> CFILE;
	printf("\tsize_t *dtabsizep;\n{\n\tint ret;\n\n") >> CFILE;
	for (i = 0; i < num_funcs; i++) {
		printf("\tif ((ret = __db_add_recovery(dbenv, ") >> CFILE;
		printf("dtabp, dtabsizep,\n") >> CFILE;
		printf("\t    %s_recover, DB_%s)) != 0)\n", \
		    funcs[i], funcs[i]) >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;
	}
	printf("\treturn (0);\n}\n") >> CFILE;
}

function log_function() {
	# Write the log function; function prototype
	pi = 1;
	p[pi++] = sprintf("int %s_log", funcname);
	p[pi++] = " ";
	if (has_dbp == 1) {
		p[pi++] = "__P((DB *, DB_TXN *, DB_LSN *, u_int32_t";
	} else {
		p[pi++] = "__P((DB_ENV *, DB_TXN *, DB_LSN *, u_int32_t";
	}
	for (i = 0; i < nvars; i++) {
		if (modes[i] == "DB")
			continue;
		p[pi++] = ", ";
		p[pi++] = sprintf("%s%s%s",
		    (modes[i] == "DBT" || modes[i] == "PGDBT") ? "const " : "",
		    types[i],
		    (modes[i] == "DBT" || modes[i] == "PGDBT") ? " *" : "");
	}
	p[pi++] = "";
	p[pi++] = "));";
	p[pi++] = "";
	proto_format(p);

	# Function declaration
	if (has_dbp == 1) {
		printf("int\n%s_log(dbp, txnid, ret_lsnp, flags", \
		    funcname) >> CFILE;
	} else {
		printf("int\n%s_log(dbenv, txnid, ret_lsnp, flags", \
		    funcname) >> CFILE;
	}
	for (i = 0; i < nvars; i++) {
		if (modes[i] == "DB") {
			# We pass in fileids on the dbp, so if this is one,
			# skip it.
			continue;
		}
		printf(",") >> CFILE;
		if ((i % 6) == 0)
			printf("\n    ") >> CFILE;
		else
			printf(" ") >> CFILE;
		printf("%s", vars[i]) >> CFILE;
	}
	printf(")\n") >> CFILE;

	# Now print the parameters
	if (has_dbp == 1) {
		printf("\tDB *dbp;\n") >> CFILE;
	} else {
		printf("\tDB_ENV *dbenv;\n") >> CFILE;
	}
	printf("\tDB_TXN *txnid;\n\tDB_LSN *ret_lsnp;\n") >> CFILE;
	printf("\tu_int32_t flags;\n") >> CFILE;
	for (i = 0; i < nvars; i++) {
		# We just skip for modes == DB.
		if (modes[i] == "DBT" || modes[i] == "PGDBT")
			printf("\tconst %s *%s;\n", types[i], vars[i]) >> CFILE;
		else if (modes[i] != "DB")
			printf("\t%s %s;\n", types[i], vars[i]) >> CFILE;
	}

	# Function body and local decls
	printf("{\n") >> CFILE;
	printf("\tDBT logrec;\n") >> CFILE;
	if (has_dbp == 1)
		printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDB_LSN *lsnp, null_lsn;\n") >> CFILE;
	if (is_dbt == 1)
		printf("\tu_int32_t zero;\n") >> CFILE;
	if (is_uint == 1)
		printf("\tu_int32_t uinttmp;\n") >> CFILE;
	printf("\tu_int32_t npad, rectype, txn_num;\n") >> CFILE;
	printf("\tint ret;\n") >> CFILE;
	printf("\tu_int8_t *bp;\n\n") >> CFILE;

	# Initialization
	if (has_dbp == 1)
		printf("\tdbenv = dbp->dbenv;\n") >> CFILE;
	printf("\trectype = DB_%s;\n", funcname) >> CFILE;
	printf("\tnpad = 0;\n\n") >> CFILE;

	printf("\tif (txnid == NULL) {\n") >> CFILE;
	printf("\t\ttxn_num = 0;\n") >> CFILE;
	printf("\t\tnull_lsn.file = 0;\n") >> CFILE;
	printf("\t\tnull_lsn.offset = 0;\n") >> CFILE;
	printf("\t\tlsnp = &null_lsn;\n") >> CFILE;
	printf("\t} else {\n") >> CFILE;
	if (funcname != "__db_debug" && dbprivate) {
		printf(\
		    "\t\tif (TAILQ_FIRST(&txnid->kids) != NULL &&\n") >> CFILE;
		printf("\t\t    (ret = __txn_activekids(") >> CFILE;
		printf("dbenv, rectype, txnid)) != 0)\n") >> CFILE;
		printf("\t\t\treturn (ret);\n") >> CFILE;
	}
	printf("\t\ttxn_num = txnid->txnid;\n") >> CFILE;
	printf("\t\tlsnp = &txnid->last_lsn;\n") >> CFILE;
	printf("\t}\n\n") >> CFILE;

	# Malloc
	printf("\tlogrec.size = sizeof(rectype) + ") >> CFILE;
	printf("sizeof(txn_num) + sizeof(DB_LSN)") >> CFILE;
	for (i = 0; i < nvars; i++)
		printf("\n\t    + %s", sizes[i]) >> CFILE;
	printf(";\n") >> CFILE
	if (dbprivate) {
		printf("\tif (CRYPTO_ON(dbenv)) {\n") >> CFILE;
		printf("\t\tnpad =\n") >> CFILE
		printf(\
"\t\t    ((DB_CIPHER *)dbenv->crypto_handle)->adj_size(logrec.size);\n")\
		    >> CFILE;
		printf("\t\tlogrec.size += npad;\n\t}\n\n") >> CFILE
	}
	write_malloc("logrec.data", "logrec.size", CFILE)
	printf("\tif (npad > 0)\n") >> CFILE;
	printf("\t\tmemset((u_int8_t *)logrec.data + logrec.size ") >> CFILE;
	printf("- npad, 0, npad);\n\n") >> CFILE;

	# Copy args into buffer
	printf("\tbp = logrec.data;\n\n") >> CFILE;
	printf("\tmemcpy(bp, &rectype, sizeof(rectype));\n") >> CFILE;
	printf("\tbp += sizeof(rectype);\n\n") >> CFILE;
	printf("\tmemcpy(bp, &txn_num, sizeof(txn_num));\n") >> CFILE;
	printf("\tbp += sizeof(txn_num);\n\n") >> CFILE;
	printf("\tmemcpy(bp, lsnp, sizeof(DB_LSN));\n") >> CFILE;
	printf("\tbp += sizeof(DB_LSN);\n\n") >> CFILE;

	for (i = 0; i < nvars; i ++) {
		if (modes[i] == "ARG" || modes[i] == "WRLOCK" || \
		    modes[i] == "WRLOCKNZ") {
			printf("\tuinttmp = (u_int32_t)%s;\n", \
			    vars[i]) >> CFILE;
			printf("\tmemcpy(bp, &uinttmp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n\n") >> CFILE;
		} else if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tif (%s == NULL) {\n", vars[i]) >> CFILE;
			printf("\t\tzero = 0;\n") >> CFILE;
			printf("\t\tmemcpy(bp, &zero, sizeof(u_int32_t));\n") \
				>> CFILE;
			printf("\t\tbp += sizeof(u_int32_t);\n") >> CFILE;
			printf("\t} else {\n") >> CFILE;
			printf("\t\tmemcpy(bp, &%s->size, ", vars[i]) >> CFILE;
			printf("sizeof(%s->size));\n", vars[i]) >> CFILE;
			printf("\t\tbp += sizeof(%s->size);\n", vars[i]) \
			    >> CFILE;
			printf("\t\tmemcpy(bp, %s->data, %s->size);\n", \
			    vars[i], vars[i]) >> CFILE;
			printf("\t\tbp += %s->size;\n\t}\n\n", \
			    vars[i]) >> CFILE;
		} else if (modes[i] == "DB") {
			# We need to log a DB handle.  To do this, we
			# actually just log its fileid;  from that, we'll
			# be able to acquire an open handle at recovery time.
			printf("\tDB_ASSERT(dbp->log_filename != NULL);\n") \
			    >> CFILE;
			printf("\tif (dbp->log_filename->id == ") >> CFILE;
			printf("DB_LOGFILEID_INVALID &&\n\t    ") >> CFILE
			printf("(ret = __dbreg_lazy_id(dbp)) != 0)\n") \
			    >> CFILE;
			printf("\t\treturn (ret);\n\n") >> CFILE;

			printf("\tuinttmp = ") >> CFILE;
			printf("(u_int32_t)dbp->log_filename->id;\n") >> CFILE;
			printf("\tmemcpy(bp, &uinttmp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n\n") >> CFILE;
		} else { # POINTER
			printf("\tif (%s != NULL)\n", vars[i]) >> CFILE;
			printf("\t\tmemcpy(bp, %s, %s);\n", vars[i], \
			    sizes[i]) >> CFILE;
			printf("\telse\n") >> CFILE;
			printf("\t\tmemset(bp, 0, %s);\n", sizes[i]) >> CFILE;
			printf("\tbp += %s;\n\n", sizes[i]) >> CFILE;
		}
	}

	# Error checking.  User code won't have DB_ASSERT available, but
	# this is a pretty unlikely assertion anyway, so we just leave it out
	# rather than requiring assert.h.
	if (dbprivate) {
		printf("\tDB_ASSERT((u_int32_t)") >> CFILE;
		printf("(bp - (u_int8_t *)logrec.data) <= logrec.size);\n") \
		    >> CFILE;
	}

	# Issue log call
	# We didn't call the crypto alignment function when we created this
	# log record (because we don't have the right header files to find
	# the function), so we have to copy the log record to make sure the
	# alignment is correct.
	printf(\
	    "\tret = dbenv->log_put(dbenv,\n\t   ret_lsnp, (DBT *)&logrec, ") \
	    >> CFILE;
	if (dbprivate) {
		printf("flags | DB_NOCOPY);\n") >> CFILE;
	} else {
		printf("flags);\n") >> CFILE;
	}

	# Update the transactions last_lsn
	printf("\tif (txnid != NULL && ret == 0)\n") >> CFILE;
	printf("\t\ttxnid->last_lsn = *ret_lsnp;\n") >> CFILE;

	# If out of disk space log writes may fail.  If we are debugging
	# that print out which records did not make it to disk.
	printf("#ifdef LOG_DIAGNOSTIC\n") >> CFILE
	printf("\tif (ret != 0)\n") >> CFILE;
	printf("\t\t(void)%s_print(dbenv,\n", funcname) >> CFILE;
	printf("\t\t    (DBT *)&logrec, ret_lsnp, NULL, NULL);\n") >> CFILE
	printf("#endif\n") >> CFILE

	# Free and return
	write_free("logrec.data", CFILE)
	printf("\treturn (ret);\n}\n\n") >> CFILE;
}

function print_function() {
	# Write the print function; function prototype
	p[1] = sprintf("int %s_print", funcname);
	p[2] = " ";
	p[3] = "__P((DB_ENV *, DBT *, DB_LSN *, db_recops, void *));";
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_print(dbenv, ", funcname) >> CFILE;
	printf("dbtp, lsnp, notused2, notused3)\n") >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDBT *dbtp;\n") >> CFILE;
	printf("\tDB_LSN *lsnp;\n") >> CFILE;
	printf("\tdb_recops notused2;\n\tvoid *notused3;\n{\n") >> CFILE;

	# Locals
	printf("\t%s_args *argp;\n", funcname) >> CFILE;
	for (i = 0; i < nvars; i ++)
		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tu_int32_t i;\n") >> CFILE
			printf("\tint ch;\n") >> CFILE
			break;
		}

	printf("\tint ret;\n\n") >> CFILE;

	# Get rid of complaints about unused parameters.
	printf("\tnotused2 = DB_TXN_ABORT;\n\tnotused3 = NULL;\n\n") >> CFILE;

	# Call read routine to initialize structure
	printf("\tif ((ret = %s_read(dbenv, dbtp->data, &argp)) != 0)\n", \
	    funcname) >> CFILE;
	printf("\t\treturn (ret);\n") >> CFILE;

	# Print values in every record
	printf("\t(void)printf(\n\t    \"[%%lu][%%lu]%s: ", funcname) >> CFILE;
	printf("rec: %%lu txnid %%lx ") >> CFILE;
	printf("prevlsn [%%lu][%%lu]\\n\",\n") >> CFILE;
	printf("\t    (u_long)lsnp->file,\n") >> CFILE;
	printf("\t    (u_long)lsnp->offset,\n") >> CFILE;
	printf("\t    (u_long)argp->type,\n") >> CFILE;
	printf("\t    (u_long)argp->txnid->txnid,\n") >> CFILE;
	printf("\t    (u_long)argp->prev_lsn.file,\n") >> CFILE;
	printf("\t    (u_long)argp->prev_lsn.offset);\n") >> CFILE;

	# Now print fields of argp
	for (i = 0; i < nvars; i ++) {
		printf("\t(void)printf(\"\\t%s: ", vars[i]) >> CFILE;

		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\");\n") >> CFILE;
			printf("\tfor (i = 0; i < ") >> CFILE;
			printf("argp->%s.size; i++) {\n", vars[i]) >> CFILE;
			printf("\t\tch = ((u_int8_t *)argp->%s.data)[i];\n", \
			    vars[i]) >> CFILE;
			printf("\t\tprintf(isprint(ch) || ch == 0x0a") >> CFILE;
			printf(" ? \"%%c\" : \"%%#x \", ch);\n") >> CFILE;
			printf("\t}\n\t(void)printf(\"\\n\");\n") >> CFILE;
		} else if (types[i] == "DB_LSN *") {
			printf("[%%%s][%%%s]\\n\",\n", \
			    formats[i], formats[i]) >> CFILE;
			printf("\t    (u_long)argp->%s.file,", \
			    vars[i]) >> CFILE;
			printf(" (u_long)argp->%s.offset);\n", \
			    vars[i]) >> CFILE;
		} else {
			if (formats[i] == "lx")
				printf("0x") >> CFILE;
			printf("%%%s\\n\", ", formats[i]) >> CFILE;
			if (formats[i] == "lx" || formats[i] == "lu")
				printf("(u_long)") >> CFILE;
			if (formats[i] == "ld")
				printf("(long)") >> CFILE;
			printf("argp->%s);\n", vars[i]) >> CFILE;
		}
	}
	printf("\t(void)printf(\"\\n\");\n") >> CFILE;
	write_free("argp", CFILE);
	printf("\treturn (0);\n") >> CFILE;
	printf("}\n\n") >> CFILE;
}

function read_function() {
	# Write the read function; function prototype
	p[1] = sprintf("int %s_read __P((DB_ENV *, void *,", funcname);
	p[2] = " ";
	p[3] = sprintf("%s_args **));", funcname);
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_read(dbenv, recbuf, argpp)\n", funcname) >> CFILE;

	# Now print the parameters
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tvoid *recbuf;\n") >> CFILE;
	printf("\t%s_args **argpp;\n", funcname) >> CFILE;

	# Function body and local decls
	printf("{\n\t%s_args *argp;\n", funcname) >> CFILE;
	if (is_uint == 1)
		printf("\tu_int32_t uinttmp;\n") >> CFILE;
	printf("\tu_int8_t *bp;\n") >> CFILE;

	if (dbprivate) {
		# We only use dbenv and ret in the private malloc case.
		printf("\tint ret;\n\n") >> CFILE;
	} else {
		printf("\t/* Keep the compiler quiet. */\n") >> CFILE;
		printf("\n\tdbenv = NULL;\n") >> CFILE;
	}

	malloc_size = sprintf("sizeof(%s_args) + sizeof(DB_TXN)", funcname)
	write_malloc("argp", malloc_size, CFILE)

	# Set up the pointers to the txnid.
	printf("\targp->txnid = (DB_TXN *)&argp[1];\n\n") >> CFILE;

	# First get the record type, prev_lsn, and txnid fields.

	printf("\tbp = recbuf;\n") >> CFILE;
	printf("\tmemcpy(&argp->type, bp, sizeof(argp->type));\n") >> CFILE;
	printf("\tbp += sizeof(argp->type);\n\n") >> CFILE;
	printf("\tmemcpy(&argp->txnid->txnid,  bp, ") >> CFILE;
	printf("sizeof(argp->txnid->txnid));\n") >> CFILE;
	printf("\tbp += sizeof(argp->txnid->txnid);\n\n") >> CFILE;
	printf("\tmemcpy(&argp->prev_lsn, bp, sizeof(DB_LSN));\n") >> CFILE;
	printf("\tbp += sizeof(DB_LSN);\n\n") >> CFILE;

	# Now get rest of data.
	for (i = 0; i < nvars; i ++) {
		if (modes[i] == "DBT" || modes[i] == "PGDBT") {
			printf("\tmemset(&argp->%s, 0, sizeof(argp->%s));\n", \
			    vars[i], vars[i]) >> CFILE;
			printf("\tmemcpy(&argp->%s.size, ", vars[i]) >> CFILE;
			printf("bp, sizeof(u_int32_t));\n") >> CFILE;
			printf("\tbp += sizeof(u_int32_t);\n") >> CFILE;
			printf("\targp->%s.data = bp;\n", vars[i]) >> CFILE;
			printf("\tbp += argp->%s.size;\n", vars[i]) >> CFILE;
		} else if (modes[i] == "ARG" || modes[i] == "WRLOCK" || \
		    modes[i] == "WRLOCKNZ" || modes[i] == "DB") {
			printf("\tmemcpy(&uinttmp, bp, sizeof(uinttmp));\n") \
			    >> CFILE;
			printf("\targp->%s = (%s)uinttmp;\n", vars[i], \
			    types[i]) >> CFILE;
			printf("\tbp += sizeof(uinttmp);\n") >> CFILE;
		} else { # POINTER
			printf("\tmemcpy(&argp->%s, bp, ", vars[i]) >> CFILE;
			printf(" sizeof(argp->%s));\n", vars[i]) >> CFILE;
			printf("\tbp += sizeof(argp->%s);\n", vars[i]) >> CFILE;
		}
		printf("\n") >> CFILE;
	}

	# Free and return
	printf("\t*argpp = argp;\n") >> CFILE;
	printf("\treturn (0);\n}\n\n") >> CFILE;
}

function getpgnos_function() {
	# Write the getpgnos function;  function prototype
	p[1] = sprintf("int %s_getpgnos", funcname);
	p[2] = " ";
	p[3] = "__P((DB_ENV *, DBT *, DB_LSN *, db_recops, void *));";
	p[4] = "";
	proto_format(p);

	# Function declaration
	printf("int\n%s_getpgnos(dbenv, ", funcname) >> CFILE;
	printf("rec, lsnp, notused1, summary)\n") >> CFILE;
	printf("\tDB_ENV *dbenv;\n") >> CFILE;
	printf("\tDBT *rec;\n") >> CFILE;
	printf("\tDB_LSN *lsnp;\n") >> CFILE;
	printf("\tdb_recops notused1;\n") >> CFILE;
	printf("\tvoid *summary;\n{\n") >> CFILE;

	# If there are no locks, return this fact.
	if (nlocks == 0) {
		printf("\tTXN_RECS *t;\n") >> CFILE;
		printf("\tint ret;\n") >> CFILE;
		printf("\tCOMPQUIET(rec, NULL);\n") >> CFILE;
		printf("\tCOMPQUIET(notused1, DB_TXN_ABORT);\n") >> CFILE;

		printf("\n\tt = (TXN_RECS *)summary;\n") >> CFILE;
		printf("\n\tif ((ret = __rep_check_alloc(dbenv, ") >> CFILE;
		printf("t, 1)) != 0)\n") >> CFILE;
		printf("\t\treturn (ret);\n") >> CFILE;

		printf("\n\tt->array[t->npages].flags = LSN_PAGE_NOLOCK;\n") \
			>> CFILE;
		printf("\tt->array[t->npages].lsn = *lsnp;\n") >> CFILE;
		printf("\tt->array[t->npages].fid = DB_LOGFILEID_INVALID;\n") \
			>> CFILE;
		printf("\tmemset(&t->array[t->npages].pgdesc, 0,\n") >> CFILE;
		printf("\t    sizeof(t->array[t->npages].pgdesc));\n") >> CFILE;
		printf("\n\tt->npages++;\n") >> CFILE;

		printf("\n") >> CFILE;
		printf("\treturn (0);\n") >> CFILE;
		printf("}\n\n") >> CFILE;
		return;
	}

	# Locals
	printf("\tDB *dbp;\n") >> CFILE;
	printf("\tTXN_RECS *t;\n") >> CFILE;
	printf("\t%s_args *argp;\n", funcname) >> CFILE;
	printf("\tu_int32_t ret;\n\n") >> CFILE;

	# Shut up compiler.
	printf("\tCOMPQUIET(notused1, DB_TXN_ABORT);\n\n") >> CFILE;

	printf("\targp = NULL;\n") >> CFILE;
	printf("\tt = (TXN_RECS *)summary;\n\n") >> CFILE;

	printf("\tif ((ret = %s_read(dbenv, rec->data, &argp)) != 0)\n", \
		funcname) >> CFILE;
	printf("\t\treturn (ret);\n") >> CFILE;

	# Get file ID.
	printf("\n\tif ((ret = __dbreg_id_to_db(dbenv,\n\t    ") >> CFILE;
	printf("argp->txnid, &dbp, argp->fileid, 0)) != 0)\n") >> CFILE;
	printf("\t\tgoto err;\n") >> CFILE;

	printf("\n\tif ((ret = __rep_check_alloc(dbenv, t, %d)) != 0)\n", \
		nlocks) >> CFILE;
	printf("\t\tgoto err;\n\n") >> CFILE;

	for (i = 1; i <= nlocks; i++) {
		if (lock_if_zero[i]) {
			indent = "\t";
		} else {
			indent = "\t\t";
			printf("\tif (argp->%s != PGNO_INVALID) {\n", \
				lock_pgnos[i]) >> CFILE;
		}
		printf("%st->array[t->npages].flags = 0;\n", indent) >> CFILE;
		printf("%st->array[t->npages].fid = argp->fileid;\n", indent) \
		    >> CFILE;
		printf("%st->array[t->npages].lsn = *lsnp;\n", indent) >> CFILE;
		printf("%st->array[t->npages].pgdesc.pgno = argp->%s;\n", \
			indent, lock_pgnos[i]) >> CFILE;
		printf("%st->array[t->npages].pgdesc.type = DB_PAGE_LOCK;\n", \
			indent) >> CFILE;
		printf("%smemcpy(t->array[t->npages].pgdesc.fileid, ", indent) \
			>> CFILE;
		printf("dbp->fileid,\n%s    DB_FILE_ID_LEN);\n", \
			indent, indent) >> CFILE;
		printf("%st->npages++;\n", indent) >> CFILE;
		if (!lock_if_zero[i]) {
			printf("\t}\n") >> CFILE;
		}
	}

	printf("\nerr:\tif (argp != NULL)\n") >> CFILE;
	write_free("argp", CFILE);

	printf("\treturn (ret);\n") >> CFILE;

	printf("}\n\n") >> CFILE;
}

# proto_format --
#	Pretty-print a function prototype.
function proto_format(p)
{
	printf("/*\n") >> CFILE;

	s = "";
	for (i = 1; i in p; ++i)
		s = s p[i];

	t = " * PUBLIC: "
	if (length(s) + length(t) < 80)
		printf("%s%s", t, s) >> CFILE;
	else {
		split(s, p, "__P");
		len = length(t) + length(p[1]);
		printf("%s%s", t, p[1]) >> CFILE

		n = split(p[2], comma, ",");
		comma[1] = "__P" comma[1];
		for (i = 1; i <= n; i++) {
			if (len + length(comma[i]) > 70) {
				printf("\n * PUBLIC:    ") >> CFILE;
				len = 0;
			}
			printf("%s%s", comma[i], i == n ? "" : ",") >> CFILE;
			len += length(comma[i]) + 2;
		}
	}
	printf("\n */\n") >> CFILE;
	delete p;
}

function write_malloc(ptr, size, file)
{
	if (dbprivate) {
		printf("\tif ((ret = ") >> file;
		printf(\
		    "__os_malloc(dbenv,\n\t    " size ", &" ptr ")) != 0)\n") \
		    >> file
		printf("\t\treturn (ret);\n\n") >> file;
	} else {
		printf("\tif ((" ptr " = malloc(" size ")) == NULL)\n") >> file
		printf("\t\treturn (ENOMEM);\n\n") >> file
	}
}

function write_free(ptr, file)
{
	if (dbprivate) {
		printf("\t__os_free(dbenv, " ptr ");\n") >> file
	} else {
		printf("\tfree(" ptr ");\n") >> file
	}
}


Clean up series mismatch messages in Acorn

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Empire | Acorn TV</title>
#  ---
#        Empire
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>/,"")
    sub (/ \| Acorn TV<.*/,"")
    gsub (/&/,"\\&")
    gsub (/"/,"\"\"")
    gsub (/’/,"'")
    # gsub (/'/,"'")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if ((numSeasons + 0) == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    gsub (/'/,"'",description)
    gsub (/’/,"'",description)
    # fix unmatched quotes
    numQuotes = gsub(/"/,"\"",description)
    if ((numQuotes % 2) == 1) {
        printf ("==> Corrected unmatched quote (%d): %d\t%s\n", numQuotes, SERIES_NUMBER, \
                seriesTitle) >> ERROR_FILE
        description = description " \""
    }
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL) >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus, parttwo, and christmasspecials episodeType to "X"
    if (episodeURL ~ /bonus\/|[0-9]{1,2}parttwo\/|christmasspecial\//)
        episodeType = "X"
    # If episode is a Trailer, set episodeType to "T"
    if (episodeURL ~ /\/trailer/)
        episodeType = "T"
    # jamaicainn, newworlds & newtonslaw bonus or trailer seasonNumber should be 1
    if (episodeURL ~ \
        /\/jamaicainn\/bonus\/|\/jamaicainn\/trailer\/|\/newworlds\/bonus\/|\/newtonslaw\/bonus\// \
        && seasonNumber != 1) {
        split (episodeURL, part, "/")
        printf ("==> Corrected S%02d: https://acorn.tv/%s/%s to S01\n", \
               seasonNumber, part[4], part[5]) >> ERROR_FILE
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        split (episodeURL, part, "/")
        printf ("==> Corrected S%02d: https://acorn.tv/%s/%s to S%02d\n", \
               seasonNumber, part[4], part[5], seasonNumber-1) >> ERROR_FILE
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|[0-9]{1,2}parttwo\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected S%02d: https://acorn.tv/%s/%s to S%02d\n", \
               seasonNumber, part[4], part[5], URLseasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }

    if ((episodeNumber + 0) == 0)
        print "==> Episode number is 00: " episodeURL >> ERROR_FILE
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}

# Create column files with lists of series titles, descriptions, number
# of seasons, and number of episodes, to be pasted into a spreadsheet.
# Note that IN_CANADA affects processing.
#
# Create a separate file with a line for each episode containing
# seriesNumber, episodeURL, seriesTitle, seasonNumber, episodeNumber,
# episodeTitle, & episodeDuration with the same columns as the primary
# spreadsheet so they can be combined into one.
#
# Create a separate file with a line for each series duration
#
# Return the total duration of all series
#
# INVOCATION:
#       curl -s https://acorn.tv/192/ \
#           | awk -v TITLE_FILE=$TITLE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v NUM_SEASONS_FILE=$NUM_SEASONS_FILE -v NUM_EPISODES_FILE=$NUM_EPISODES_FILE \
#           -v EPISODE_CURL_FILE=$EPISODE_CURL_FILE -v IN_CANADA=$IN_CANADA \
#           -v EPISODE_INFO_FILE=$EPISODE_INFO_FILE -v SERIES_NUMBER=$lastRow \
#           -v DURATION_FILE=$DURATION_FILE -v ERROR_FILE=$ERROR_FILE -f getAcornFrom-seriesPages.awk
#
# INPUT:
#        <title>Empire | Acorn TV</title>
#  ---
#        Empire
#        <meta itemprop="numberOfEpisodes" content="30" />
#        <meta itemprop="numberOfSeasons" content="3" />
#  ---
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#        <meta itemprop="numberOfEpisodes" content="10" />
#  ---
#
"The writing is sublime" (New \
#        York Times) in this anything-but-a-procedural cop drama ... as reluctant partners \
#        patrolling the streets of Montreal. Not Available in Canada. CC Available.

#  ---
#
We're sorry, but Acorn TV is not available in this territory.

#  ---
#        <meta itemprop="seasonNumber" content="1" />
#  ---
#
#  ---
#        <meta itemprop="timeRequired" content="T44M06S" />
#  ---
#
Partners

#  ---
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

#
#
#
#  OUTPUT:
#       $EPISODE_INFO_FILE
#

# Extract the series title
/<title>/ {
    sub (/.*<title>/,"")
    sub (/ \| Acorn TV<.*/,"")
    gsub (/&/,"\\&")
    gsub (/"/,"\"\"")
    gsub (/’/,"'")
    # gsub (/'/,"'")
    if (match ($0, /^The /)) {
        $0 = substr($0, 5) ", The"
    }
    episodeLinesFound = 0
    numEpisodesStr = ""
    seriesTitle = $0
    print seriesTitle >> TITLE_FILE
    next
}

# Extract the number of episodes in the series
/itemprop="numberOfEpisodes"/ {
    split ($0,fld,"\"")
    episodeLinesFound += 1
    if (episodeLinesFound == 1)
        totalEpisodes = fld[4]
    if (episodeLinesFound != 1)
        numEpisodesStr = numEpisodesStr "+" fld[4]
    next
}

# Extract the number of seasons in the series
/itemprop="numberOfSeasons"/ {
    split ($0,fld,"\"")
    numSeasons = fld[4]
    if ((numSeasons + 0) == 0)
        printf ("==> No seasons: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    print numSeasons >> NUM_SEASONS_FILE
    next
}

/Acorn TV is not available in this territory/ {
    printf ("==> Not available here: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
}

# Extract the series description
/id="franchise-description"/ {
    # get rid of boilerplate
    split ($0,fld,"[<>]")
    description = fld[3]
    # get rid of unnecessary characters and text
    gsub (/\\/,"",description)
    if (IN_CANADA != "yes") {
        sub (/Series [[:digit:]]* not available in Canada\./,"",description)
        sub (/Not [Aa]vailable in Canada\./,"",description)
        sub (/NOT AVAILABLE IN CANADA\./,"",description)
    }
    sub (/CC Available\. CC Available/,"CC Available",description)
    # fix sloppy input spacing
    sub (/\.CC Available/,". CC Available",description)
    gsub (/ \./,".",description)
    gsub (/  */," ",description)
    sub (/^ */,"",description)
    sub (/ *$/,"",description)
    # fix funky HTML characters
    gsub (/'/,"'",description)
    gsub (/'/,"'",description)
    gsub (/’/,"'",description)
    # fix unmatched quotes
    numQuotes = gsub(/"/,"\"",description)
    if ((numQuotes % 2) == 1) {
        printf ("==> Corrected unmatched quote (%d): %d\t%s\n", numQuotes, SERIES_NUMBER, \
                seriesTitle) >> ERROR_FILE
        description = description " \""
    }
    print description >> DESCRIPTION_FILE
    next
}

# Extract the seasonNumber
# Note that Acorn "season" numbers may not correspond to actual season numbers.
# They simply refer to the number of seasons available on Acorn.
/<meta itemprop="seasonNumber" content="/ {
    split ($0,fld,"\"")
    seasonNumber = fld[4]
    next
}

# Extract the episode URL
/> EPISODE_CURL_FILE
    next
}

# Extract the episode duration
/<meta itemprop="timeRequired"/ {
    split ($0,fld,"\"")
    split (fld[4],tm,/[TMS]/)
    secs = tm[3]
    mins = tm[2] + int(secs / 60)
    hrs =  int(mins / 60)
    secs %= 60; mins %= 60
    #
    seriesSecs += secs
    seriesMins += mins + int(seriesSecs / 60)
    seriesHrs += hrs + int(seriesMins / 60)
    seriesSecs %= 60; seriesMins %= 60
    #
    totalSecs += secs
    totalMins += mins + int(totalSecs / 60)
    totalHrs += hrs + int(totalMins / 60)
    totalSecs %= 60; totalMins %= 60
    #
    episodeDuration = sprintf ("%02d:%02d:%02d", hrs, mins, secs)
    if (episodeDuration == "00:00:00")
        printf ("==> No duration: %d\t%s  %s\n", SERIES_NUMBER, seriesTitle, episodeURL) >> ERROR_FILE
    next
}

# Extract the episode title
/
/ {
    split ($0,fld,"[<>]")
    episodeTitle = fld[3]
    gsub (/&/,"\\&", episodeTitle)
    next
}

# There are a number of variants in the "episodeNumber" string
#
#
Season 1: Episode 1

#
Season 9 Christmas Special: Episode 1

#
#
Series 1: Episode 1

#
Series 1 (Set 1): Episode 1

#
Series 10 (Sets 13 - 14): Episode 1

#
Series 19 (Part One): Episode 1

#
#
Set 1: Episode 1

#
#
Mini Series: Episode 1

#
Miniseries: Episode 1

#
#
Black Widows Season 1: Episode 1

#
#
A Dance to the Music of Time: Episode 1

#
Christmas Special : Episode 1

#
Feature Film: Episode 1

# Extract episode number
/
.*span itemprop="episodeNumber">/ {
    # If show is a Prequel, use "P"
    if (episodeURL ~ /\/prequel/)
        showType = "P"
    # If show is a Miniseries, use "M"
    if (episodeURL ~ /\/miniseries/)
        showType = "M"
    # If we don't know what kind of show it is, use "S"
    if (showType == "")
        showType = "S"
    # Default episodeType to "E"
    episodeType = "E"
    # Default bonus, parttwo, and christmasspecials episodeType to "X"
    if (episodeURL ~ /bonus\/|[0-9]{1,2}parttwo\/|christmasspecial\//)
        episodeType = "X"
    # If episode is a Trailer, set episodeType to "T"
    if (episodeURL ~ /\/trailer/)
        episodeType = "T"
    # jamaicainn, newworlds & newtonslaw bonus or trailer seasonNumber should be 1
    if (episodeURL ~ \
        /\/jamaicainn\/bonus\/|\/jamaicainn\/trailer\/|\/newworlds\/bonus\/|\/newtonslaw\/bonus\// \
        && seasonNumber != 1) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber = 1
    }
    # Plain christmasspecial, seriesfinale don't increment seasonNumber
    if (episodeURL ~ /\/christmasspecial\/|\/seriesfinale\//) {
        split (episodeURL, part, "/")
        printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
        seasonNumber -= 1
    }
    #
    split ($0,fld,"[<>]")
    episodeNumber = fld[5]
    # Feature films don't have episodes
    if (episodeURL ~ /\/featurefilm\//)
        next
    # neither should single episode series
    if (totalEpisodes == 1)
        next
    # The season number should match that in the URL
    # Birds of a Feather, Doc Martin, Murdoch, Poirot, Rebus, Vera, and others have problems
    if (episodeURL ~ \
           /\/series[0-9]{1,2}\/|[0-9]{1,2}bonus\/|[0-9]{1,2}parttwo\/|\/murdoch\/season[0-9]{1,2}/) {
        split (episodeURL, part, "/")
        URLseasonNumber = part[5]
        sub (/bonus/,"",URLseasonNumber)
        # Foyle's War has sets: foyleswar/series9set8bonus/
        sub (/set[0-9]/,"",URLseasonNumber)
        # Midsomer Murders has two parts: midsomermurders/series19parttwo/
        sub (/parttwo/,"",URLseasonNumber)
        sub (/christmasspecial/,"",URLseasonNumber)
        sub (/[[:alpha:]]*/,"",URLseasonNumber)
        if (URLseasonNumber != seasonNumber) {
            printf ("==> Corrected mismatch: https://acorn.tv/%s/%s was series %d\n", \
               part[4], part[5], seasonNumber) >> ERROR_FILE
            seasonNumber = URLseasonNumber
        }
    }

    if ((episodeNumber + 0) == 0)
        print "==> Episode number is 00: " episodeURL >> ERROR_FILE
    printf ("%d\t=HYPERLINK(\"%s\";\"%s, %s%02d%s%02d, %s\"\)\t\t\t%s\n", \
        SERIES_NUMBER, episodeURL, seriesTitle, showType, seasonNumber, episodeType, episodeNumber, \
        episodeTitle, episodeDuration) >> EPISODE_INFO_FILE
    showType = ""
    next
}

/-- Viewers Also Watched --/ {
    print (episodeLinesFound == 1 ? "=0" : "=" numEpisodesStr) >> NUM_EPISODES_FILE
    if (description == "")
        printf ("==> No description: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    description = ""
    #
    seriesDuration = sprintf ("%02d:%02d:%02d", seriesHrs, seriesMins, seriesSecs)
    print seriesDuration >> DURATION_FILE
    if (seriesDuration == "00:00:00")
        printf ("==> No duration: %d\t%s\n", SERIES_NUMBER, seriesTitle) >> ERROR_FILE
    seriesSecs = 0
    seriesMins = 0
    seriesHrs = 0
    seriesDuration = ""
    #
    SERIES_NUMBER += 1
}

END {
    # Return the total duration of all series
    printf ("%02d:%02d:%02d\n", totalHrs, totalMins, totalSecs)
}


Fix MHz site's change to page=2 link

Fix MHz site's change to page=2 link

# Generate series Marquees, Descriptions, Headers from MHz series pages
#       (Headers include Genre, Country, Language, Rating)
# and return the list of Episode URLs for further processing
#
# INVOCATION:
#       curl -s https://watch.mhzchoice.com/a-french-village/ \
#           | awk -v MARQUEE_FILE=$MARQUEE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v HEADER_FILE=$HEADER_FILE -v EPISODE_URL_FILE=$EPISODE_URL_FILE \
#           -v ERROR_FILE=$ERROR_FILE -f getMHzFrom-seriesPages.awk
#
# INPUT:
#       <meta name="apple-itunes-app" content="app-id=1096194231, \
#           app-argument=https://mhzchoice.vhx.tv/agatha-christie-s-criminal-games">
#       <title>A French Village - MHz Choice</title>
#       <meta name="description" content="DRAMA | FRANCE | FRENCH WITH ENGLISH SUBTITLES | TV-MA^M
#       This acclaimed drama is about the German...all in a day's work... its inhabitants.">
# ---
#       <select class="js-switch-season btn-dropdown-black margin-right-medium" data-switch-season >
#           <option value="https://watch.mhzchoice.com/a-french-village/season:1" selected>
#             Season 1
#           </option>
#           <option value="https://watch.mhzchoice.com/a-french-village/season:2">
#             Season 2
#           </option>
#       </select>
# ---
#       <a data-load-more="infinite_scroll: false;" \
#       data-load-more-target="js-load-more-items-container" \
#       class="js-load-more-link btn btn-site-secondary btn-nowrap"
#       href="/detective-montalbano?html=1&page=2">
#
# OUTPUT:
#       $MARQUEE_FILE, $DESCRIPTION_FILE, $HEADER_FILE,
#       list of Episode URLs
#

/meta name="apple-itunes-app" content=/,/>/ {
    seriesURL = $0
    sub (/.*app-argument=/,"",seriesURL)
    sub (/">/,"",seriesURL)
    sub (/mhzchoice.vhx.tv/,"watch.mhzchoice.com",seriesURL)
    shortURL = seriesURL
    sub (/.*watch/,"watch",shortURL)
}

/title>/ {
    seriesTitle = $0
    sub (/.*<title>/,"",seriesTitle)
    sub (/ - MHz Choice<.*/,"",seriesTitle)
    gsub (/'/,"'",seriesTitle)
    if (match (seriesTitle, /^The /)) {
        seriesTitle = substr(seriesTitle, 5) ", The"
    }
    print seriesTitle >> MARQUEE_FILE
    next
}

/meta name="description" content=/,/>/ {
    # if we're on the first line of this block ...
    if ($0 ~ /name="description" content="/) {
        headerPrinted = "no"
        sub (/.*name="description" content="/,"")
    }
    # If we find a header, clean it up and print it
    if ($0 ~ /\| {1,2}TV/) {
        sub (/WITH ENGLISH SUBTITLES /,"")
        sub (/SCANDINAVIAN CRIME FICTION/,"Sweden")
        sub (/NONFICTION - DOCUMENTARY/,"Documentary")
        gsub (/ \| {1,2}/,"|")
        sub (/\r/,"")
        # Split out header fields
        numFields = split ($0,fld,"\|")
        # lowercase everything but the first character in all fields but the last
        for (i = 1; i < numFields; ++i) {
            fld[i] = substr(fld[i],1,1) tolower(substr(fld[i],2))
            # uppercase the first character of any second word
            if (match (fld[i],/ /) > 0)
                fld[i] = substr(fld[i],1,RSTART) toupper(substr(fld[i],RSTART+1,1)) \
                (substr(fld[i],RSTART+2))
        }
        # print the finalized header
        if (numFields > 3 ) {
            print fld[1] "\t" fld[2] "\t" fld[(numFields-1)] "\t" fld[(numFields)] >> HEADER_FILE
            headerPrinted = "yes"
        }
    } else {
    # We found a description, clean it up and print it
    gsub (/  */," ")
    gsub (/'/,"'")
    gsub (/"/,"\"")
    gsub (/&/,"\\&")
    gsub (/‘/,"’")
    gsub (/’/,"’")
    gsub (/“/,"“")
    gsub (/”/,"”")
    gsub (/\r/," ")
    # if it's not the last line of the description, print it without a newline
    if ($0 !~ /"\>/) {
        printf ("%s", $0) >> DESCRIPTION_FILE
    } else {
    # if it's the last line of the description, print it with a newline
    sub (/"\>/,"")
    print $0 >> DESCRIPTION_FILE
    # if we didn't find a header in this block, print a blank one
    if (headerPrinted == "no") {
        print "==> No genre/country line: " shortURL "  " seriesTitle >> ERROR_FILE
        print "\t\t\t" >> HEADER_FILE
    }
}
    }
}

# Return the list of Episode URLs for further processing
/<select class="js-switch-season/,/<\/select>/ {
    sub (/^ */,"")
    if ($0 ~ /^<option value="/) {
        split ($0,fld,"\"")
        print fld[2] >> EPISODE_URL_FILE
    }
}

# If there is more to load ...
#       href="/detective-montalbano?html=1&page=2">
/class="js-load-more-link/ {
    if (getline <= 0) {
        print "==> Missing page=2 link: " shortURL "  " seriesTitle >> ERROR_FILE
    }
    sub (/.*href="/,"https://watch.mhzchoice.com")
    sub (/">.*/,"")
    print $0 >> EPISODE_URL_FILE
}

# Generate series Marquees, Descriptions, Headers from MHz series pages
#       (Headers include Genre, Country, Language, Rating)
# and return the list of Episode URLs for further processing
#
# INVOCATION:
#       curl -s https://watch.mhzchoice.com/a-french-village/ \
#           | awk -v MARQUEE_FILE=$MARQUEE_FILE -v DESCRIPTION_FILE=$DESCRIPTION_FILE \
#           -v HEADER_FILE=$HEADER_FILE -v EPISODE_URL_FILE=$EPISODE_URL_FILE \
#           -v ERROR_FILE=$ERROR_FILE -f getMHzFrom-seriesPages.awk
#
# INPUT:
#       <meta name="apple-itunes-app" content="app-id=1096194231, \
#           app-argument=https://mhzchoice.vhx.tv/agatha-christie-s-criminal-games">
#       <title>A French Village - MHz Choice</title>
#       <meta name="description" content="DRAMA | FRANCE | FRENCH WITH ENGLISH SUBTITLES | TV-MA^M
#       This acclaimed drama is about the German...all in a day's work... its inhabitants.">
# ---
#       <select class="js-switch-season btn-dropdown-black margin-right-medium" data-switch-season >
#           <option value="https://watch.mhzchoice.com/a-french-village/season:1" selected>
#             Season 1
#           </option>
#           <option value="https://watch.mhzchoice.com/a-french-village/season:2">
#             Season 2
#           </option>
#       </select>
# ---
#       Show More
#
# OUTPUT:
#       $MARQUEE_FILE, $DESCRIPTION_FILE, $HEADER_FILE,
#       list of Episode URLs
#

/meta name="apple-itunes-app" content=/,/>/ {
    seriesURL = $0
    sub (/.*app-argument=/,"",seriesURL)
    sub (/">/,"",seriesURL)
    sub (/mhzchoice.vhx.tv/,"watch.mhzchoice.com",seriesURL)
    shortURL = seriesURL
    sub (/.*watch/,"watch",shortURL)
}

/title>/ {
    seriesTitle = $0
    sub (/.*<title>/,"",seriesTitle)
    sub (/ - MHz Choice<.*/,"",seriesTitle)
    gsub (/'/,"'",seriesTitle)
    if (match (seriesTitle, /^The /)) {
        seriesTitle = substr(seriesTitle, 5) ", The"
    }
    print seriesTitle >> MARQUEE_FILE
    next
}

/meta name="description" content=/,/>/ {
    # if we're on the first line of this block ...
    if ($0 ~ /name="description" content="/) {
        headerPrinted = "no"
        sub (/.*name="description" content="/,"")
    }
    # If we find a header, clean it up and print it
    if ($0 ~ /\| {1,2}TV/) {
        sub (/WITH ENGLISH SUBTITLES /,"")
        sub (/SCANDINAVIAN CRIME FICTION/,"Sweden")
        sub (/NONFICTION - DOCUMENTARY/,"Documentary")
        gsub (/ \| {1,2}/,"|")
        sub (/\r/,"")
        # Split out header fields
        numFields = split ($0,fld,"\|")
        # lowercase everything but the first character in all fields but the last
        for (i = 1; i < numFields; ++i) {
            fld[i] = substr(fld[i],1,1) tolower(substr(fld[i],2))
            # uppercase the first character of any second word
            if (match (fld[i],/ /) > 0)
                fld[i] = substr(fld[i],1,RSTART) toupper(substr(fld[i],RSTART+1,1)) \
                (substr(fld[i],RSTART+2))
        }
        # print the finalized header
        if (numFields > 3 ) {
            print fld[1] "\t" fld[2] "\t" fld[(numFields-1)] "\t" fld[(numFields)] >> HEADER_FILE
            headerPrinted = "yes"
        }
    } else {
    # We found a description, clean it up and print it
    gsub (/  */," ")
    gsub (/'/,"'")
    gsub (/"/,"\"")
    gsub (/&/,"\\&")
    gsub (/‘/,"’")
    gsub (/’/,"’")
    gsub (/“/,"“")
    gsub (/”/,"”")
    gsub (/\r/," ")
    # if it's not the last line of the description, print it without a newline
    if ($0 !~ /"\>/) {
        printf ("%s", $0) >> DESCRIPTION_FILE
    } else {
    # if it's the last line of the description, print it with a newline
    sub (/"\>/,"")
    print $0 >> DESCRIPTION_FILE
    # if we didn't find a header in this block, print a blank one
    if (headerPrinted == "no") {
        print "==> No genre/country line: " shortURL "  " seriesTitle >> ERROR_FILE
        print "\t\t\t" >> HEADER_FILE
    }
}
    }
}

# Return the list of Episode URLs for further processing
/<select class="js-switch-season/,/<\/select>/ {
    sub (/^ */,"")
    if ($0 ~ /^<option value="/) {
        split ($0,fld,"\"")
        print fld[2] >> EPISODE_URL_FILE
    }
}

# If there is more to load ...
/class="js-load-more-link/ {
    sub (/.*href="/,"https://watch.mhzchoice.com")
    sub (/">.*/,"")
    print $0 >> EPISODE_URL_FILE
}


Remove translations from SIP server config (will move to sip2proxy)

BEGIN{
  FS=","
}
{
	print "\t<login encoding=\""$1"\" id=\""$2"\" password=\""$3"\" delimiter=\"|\" error-detect=\"enabled\" institution=\""$4"\" checked_in_ok=\"1\">"
	print "\t</login>"
}

BEGIN{
  FS=","
}
{
	print "\t<login encoding=\""$1"\" id=\""$2"\" password=\""$3"\" delimiter=\"|\" error-detect=\"enabled\" institution=\""$4"\" checked_in_ok=\"1\">"
	print "\t\t<!-- General messages -->"
	print "\t\t<screen_msg_regex find=\"^.*Invalid password.*$\" replace=\"Feil passord\" />"
	print "\t\t<!-- Checkout screen messages we can translate directly -->"
	print "\t\t<screen_msg_regex find=\"Item checked out to another patron.*$\" replace=\"Materialet er lånt ut til en annen låner.\" />"
	print "\t\t<screen_msg_regex find=\"Item already checked out to you: renewing item.\" replace=\"Denne fornyes siden du allerede har lånt den.\" />"
	print "\t\t<screen_msg_regex find=\"Item was reserved for you.\" replace=\"Du hadde reservert denne.\" />"
	print "\t\t<screen_msg_regex find=\"Item is reserved for another patron upon return.\" replace=\"Materialet er reservert av en annen låner.\" />"
	print "\t\t<screen_msg_regex find=\"Item already checked out to another patron.  Please return item for check-in.\" replace=\"Materialet er allerede lånt ut til en annen låner. Vennligst returner det.\" />"
	print "\t\t<screen_msg_regex find=\"Outstanding Fines block issue.*\" replace=\"Du kan ikke låne grunnet utestående gebyrer.\" />"
	print "\t\t<screen_msg_regex find=\"Loan period reduced for high-demand item\" replace=\"Låneperioden er redusert pga stor etterspørsel\" />"
	print "\t\t<screen_msg_regex find=\"Item is on hold shelf for another patron\" replace=\"Materialet står til avhenting av en annen låner\" />"
	print "\t\t<screen_msg_regex find=\"Invalid Item\" replace=\"Ugyldig eksemplar\" />"
	print "\t\t<screen_msg_regex find=\"Invalid Patron\" replace=\"Ugyldig lånekort\" />"
	print "\t\t<screen_msg_regex find=\"Invalid cardnumber\" replace=\"Ugyldig lånekort\" />"
	print "\t\t<screen_msg_regex find=\"^.*Borrower.s card reported lost.*$\" replace=\"Lånekortet er meldt tapt. Vennligst kontakt personalet.\" />"
	print "\t\t<screen_msg_regex find=\"^.*Borrower has no valid address.*$\" replace=\"Feil i adresse. Vennligst kontakt personalet.\" />"
	print "\t\t<!-- Renew -->"
	print "\t\t<screen_msg_regex find=\"Item unavailable due to outstanding holds\" replace=\"Materialet er utilgjengelig pga ventende reservasjoner\" />"
	print "\t\t<screen_msg_regex find=\"Item has reached maximum renewals\" replace=\"Materialet kan ikke lånes flere ganger\" />"
	print "\t\t<screen_msg_regex find=\"NO_MORE_RENEWALS[\\:]*\" replace=\"Maks antall fornyelser oversteget.\" />"
	print "\t\t<!-- Missing screen messages in Koha and other quirks -->"
	print "\t\t<screen_msg_regex find=\"^.*Patron owes.+$\" replace=\"Du har utestående gebyrer - henvend deg til personalet.\" />"
	print "\t\t<screen_msg_regex find=\"^Patron Blocked$\" replace=\"Lånekortet er sperret - henvend deg til personalet.\" />"
	print "\t\t<screen_msg_regex find=\"Aldersgrense\" replace=\"Du kan ikke låne denne - aldersgrense\" />"
	print "\t\t<screen_msg_regex find=\"TOO_MANY_CHECKOUTS\" replace=\"For mange utlån\" />"
	print "\t\t<screen_msg_regex find=\"NOT_FOR_LOAN.*$\" replace=\"Materialet er ikke til utlån.\" />"
	print "\t\t<screen_msg_regex find=\"item_notforloan.*$\" replace=\"Materialet er ikke til utlån.\" />"
	print "\t\t<!-- Bug 15438 - Checking out an on-hold item sends holder's borrowernumber in AF (screen message) field. -->"
	print "\t\t<!-- gjelder også utlån av samme eksemplar to ganger -->"
	print "\t\t<screen_msg_regex find=\"^\d{1,}$\" replace=\"Materialet er allerede utlånt/reservert.\" />"
	print "\t\t<!-- Last resort -->"
	print "\t\t<screen_msg_regex find=\"Greetings from Koha.\" replace=\"Melding fra biblioteket:\" />"
	print "\t</login>"
}


Fixed formatting again.

Fixed formatting again.

BEGIN{ FS="\t"; OFS="\t"; }
{
	possible_labels=$1;
	rowid=$2;
	label=$3;
	features=$4;

	label_count = split(possible_labels, label_array, ",");

	for(i = 1; i <= label_count; i++) {
		if (label_array[i] == label)
			print rowid, label, 1, features;
		else
			print rowid, label_array[i], -1, features;
	}
}
END{}


fixed space detection

fixed space detection

# fetch libssh2 version number from input file and write them to STDOUT
BEGIN {
  while ((getline < ARGV[1]) > 0) {
    if (match ($0, /^#define LIBSSH2_VERSION[ |\t]+"[^"]+"/)) {
      my_ver_str = substr($3, 2, length($3) - 2);
      split(my_ver_str, v, ".");
      if (v[3])
        gsub("[^0-9].*$", "", v[3]);
      else
        v[3] = 0;
      if (v[2])
        gsub("[^0-9].*$", "", v[2]);
      else
        v[2] = 0;
      my_ver = v[1] "," v[2] "," v[3];
    }
  }
  print "LIBSSH2_VERSION = " my_ver "";
  print "LIBSSH2_VERSION_STR = " my_ver_str "";
}

# fetch libssh2 version number from input file and write them to STDOUT
BEGIN {
  while ((getline < ARGV[1]) > 0) {
    if (match ($0, /^#define LIBSSH2_VERSION[\s|\t]+"[^"]+"/)) {
      my_ver_str = substr($3, 2, length($3) - 2);
      split(my_ver_str, v, ".");
      if (v[3])
        gsub("[^0-9].*$", "", v[3]);
      else
        v[3] = 0;
      if (v[2])
        gsub("[^0-9].*$", "", v[2]);
      else
        v[2] = 0;
      my_ver = v[1] "," v[2] "," v[3];
    }
  }
  print "LIBSSH2_VERSION = " my_ver "";
  print "LIBSSH2_VERSION_STR = " my_ver_str "";
}


fix comments in the git log massager to explain what format it processes

#!/bin/awk
#
# This script parses the git log output from the following git command, and turns it into csv
#
#	git log --reverse --numstat --date=short
#
BEGIN {
	stderr = "/dev/stderr"

#	printf("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
#		"Date",
#		"Number of Insertions",
#		"Number of Deletions",
#		"Author",
#		"Filename")

	last = "none"
}

/^commit[ 	].*/ {
	commit = $2

	last = "commit"
	next
}

/^Author:[ 	].*/ {
	author = $0
	sub(/^Author: /, "", author)

	last = "author"
	next
}

/^Date:[ 	].*/ {
	date = $2

	last = "date"
	next
}

/^Merge:[ 	].*/ {
	ismerge = 1
	last = "merge"
	next						# don't track merge history
}

/^[ 	]+.*/ {
	last = "logmsg"
	next						# don't track log message
}

/^-[ 	]+-[ 	]+.*/ {
	last = "binary"
	next						# don't track binary files
}

/^[0-9]+[ 	]+[0-9]+[ 	]+.*/ {
	ninsertions = $1
	ndeletions = $2
	file = $0
	sub(/^[0-9]+[ 	]+[0-9]+[ 	]+/, "", file)	# easier this way because the filename might contain spaces

	gsub(/"/, "'", file)
	gsub(/"/, "'", author)

	if(ninsertions > 0 || ndeletions > 0) {
		printf("\"%s\",%d,%d,\"%s\",\"%s\",\"%s\"\n",
			date,
			ninsertions,
			ndeletions,
			commit,
			author,
			file)
	}

	last = "nlines"
	next
}

/^$/ {
	if(last == "date")
		next
	if(last == "logmsg")
		next
	if(last == "nlines" || last == "binary") {
		commit = ""
		author = ""
		date=""
		skip = 0
		ninsertions = 0
		ndeletions = 0
		file = ""
		ismerge = 0
		last = "blank"
		next
	}

	printf("ERROR: unexpected blank line %d last %s\n", NR, last) > stderr
	exit 1
}

{
	printf("ERROR: Line %d: unhandled\n", NR) > stderr
	printf("ERROR: \"%s\"\n", $0) > stderr
	exit 1
}

#!/bin/awk
# commit 989fb6102add218589e8779c76fd61f91e113684
# Author: Tad Hunt <tadhunt@tad-hunts-macbook-pro.local>
# Date:   2009-11-30
#
#     ignore objects
#
# 4       0       .gitignore
#
# commit 54f97a5baf887f0d2a73745efaa0a647ed20512e
# Author: Tad Hunt <tadhunt@tad-hunts-macbook-pro.local>
# Date:   2009-11-30
#
#     reorg & add pitch
#
# 0       46      Makefile
# 0       62      bwcalc.c
# 0       32      dedupgain.sh
# 0       56      dedupgaintree.sh
# 0       391     sha1block.c
# 0       73      sha1speed.c
# 46      0       src/Makefile
# 62      0       src/bwcalc.c
# 32      0       src/dedupgain.sh
# 56      0       src/dedupgaintree.sh
# 391     0       src/sha1block.c
# 73      0       src/sha1speed.c
#
# commit 509aff7e53df7406749274d943ed573a0a78af02
# ...
#
BEGIN {
	stderr = "/dev/stderr"

#	printf("\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n",
#		"Date",
#		"Number of Insertions",
#		"Number of Deletions",
#		"Author",
#		"Date",
#		"Filename")

	last = "none"
}

/^commit[ 	].*/ {
	commit = $2

	last = "commit"
	next
}

/^Author:[ 	].*/ {
	author = $0
	sub(/^Author: /, "", author)

	last = "author"
	next
}

/^Date:[ 	].*/ {
	date = $2

	last = "date"
	next
}

/^Merge:[ 	].*/ {
	ismerge = 1
	last = "merge"
	next						# don't track merge history
}

/^[ 	]+.*/ {
	last = "logmsg"
	next						# don't track log message
}

/^-[ 	]+-[ 	]+.*/ {
	last = "binary"
	next						# don't track binary files
}

/^[0-9]+[ 	]+[0-9]+[ 	]+.*/ {
	ninsertions = $1
	ndeletions = $2
	file = $0
	sub(/^[0-9]+[ 	]+[0-9]+[ 	]+/, "", file)	# easier this way because the filename might contain spaces

	gsub(/"/, "'", file)
	gsub(/"/, "'", author)

	if(ninsertions > 0 || ndeletions > 0) {
		printf("\"%s\",%d,%d,\"%s\",\"%s\",\"%s\"\n",
			date,
			ninsertions,
			ndeletions,
			commit,
			author,
			file)
	}

	last = "nlines"
	next
}

/^$/ {
	if(last == "date")
		next
	if(last == "logmsg")
		next
	if(last == "nlines" || last == "binary") {
		commit = ""
		author = ""
		date=""
		skip = 0
		ninsertions = 0
		ndeletions = 0
		file = ""
		ismerge = 0
		last = "blank"
		next
	}

	printf("ERROR: unexpected blank line %d last %s\n", NR, last) > stderr
	exit 1
}

{
	printf("ERROR: Line %d: unhandled\n", NR) > stderr
	printf("ERROR: \"%s\"\n", $0) > stderr
	exit 1
}