view utils/themwi-update-numdb.c @ 10:05a94b08c8e1

add top Makefile
author Mychaela Falconia <falcon@freecalypso.org>
date Wed, 13 Dec 2023 03:56:16 +0000
parents 5bf2648e5413
children
line wrap: on
line source

/*
 * This program reads (parses) ThemWi config file /var/gsm/number-db2,
 * generates the compiled binary form of this database, and then makes
 * it live via atomic rename.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include <themwi/nanp/number_db_v2.h>
#include <themwi/nanp/number_utils.h>

#define	MAX_OWNED_NUMBERS	1000
#define	MAX_SHORT_NUMBERS	1000

static struct owned_number_rec owned_number_buf[MAX_OWNED_NUMBERS];
static struct short_number_rec short_number_buf[MAX_SHORT_NUMBERS];
static unsigned owned_number_count, short_number_count;

static char *system_dir;
static FILE *inf;
static int lineno;
static char linebuf[256];
static int prefix_set, prefix_allows_abbrev;
static uint16_t prefix_buf[2];

static void enter_owned_number(const struct owned_number_rec *rec)
{
	if (owned_number_count >= MAX_OWNED_NUMBERS) {
		fprintf(stderr, "error: MAX_OWNED_NUMBERS exceeded\n");
		exit(1);
	}
	bcopy(rec, owned_number_buf + owned_number_count,
		sizeof(struct owned_number_rec));
	owned_number_count++;
}

static void enter_short_number(const struct short_number_rec *rec)
{
	if (short_number_count >= MAX_SHORT_NUMBERS) {
		fprintf(stderr, "error: MAX_SHORT_NUMBERS exceeded\n");
		exit(1);
	}
	bcopy(rec, short_number_buf + short_number_count,
		sizeof(struct short_number_rec));
	short_number_count++;
}

static void handle_prefix_line(char *cp)
{
	char *np, prefix[7];

	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (grok_number_string(np, 1) != 6) {
		fprintf(stderr,
		      "number-db2 line %d: prefix requires 6-digit argument\n",
			lineno);
		exit(1);
	}
	dehyphen_number_string(np, prefix);
	if (!is_nanp_valid_prefix(prefix)) {
		fprintf(stderr,
			"number-db2 line %d: prefix violates NANP rules\n",
			lineno);
		exit(1);
	}
	prefix_buf[0] = digits3_to_uint16(prefix);
	prefix_buf[1] = digits3_to_uint16(prefix + 3);
	prefix_set = 1;
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
		prefix_allows_abbrev = 0;
		return;
	}
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (!strcmp(np, "allow-abbrev")) {
		prefix_allows_abbrev = 1;
		return;
	}
	fprintf(stderr,
	    "number-db2 line %d: non-understood notation \"%s\" after prefix\n",
		lineno, np);
	exit(1);
}

static int parse_extra_number(struct owned_number_rec *rec, const char *numstr)
{
	char buf[11];
	int rc;

	rc = grok_number_string(numstr, 1);
	switch (rc) {
	case 4:
		if (!prefix_set)
			return(-1);
		dehyphen_number_string(numstr, buf);
		rec->remap[0] = prefix_buf[0];
		rec->remap[1] = prefix_buf[1];
		rec->remap[2] = digits4_to_uint16(buf);
		return(0);
	case 10:
		dehyphen_number_string(numstr, buf);
		rec->remap[0] = digits3_to_uint16(buf);
		rec->remap[1] = digits3_to_uint16(buf + 3);
		rec->remap[2] = digits4_to_uint16(buf + 6);
		return(0);
	default:
		return(-1);
	}
}

static void handle_number_attr(struct owned_number_rec *rec, char *tail)
{
	char *cp, *np;
	int rc;

	for (cp = tail; ; ) {
		while (isspace(*cp))
			cp++;
		if (*cp == '\0' || *cp == '#')
			return;
		for (np = cp; *cp && !isspace(*cp); cp++)
			;
		if (*cp)
			*cp++ = '\0';
		if (!strcmp(np, "sms"))
			rec->number_flags |= NUMBER_FLAG_SMSPROV;
		else if (!strcmp(np, "e911"))
			rec->number_flags |= NUMBER_FLAG_E911PROV;
		else if (!strcmp(np, "gsm-sub"))
			rec->usage = NUMBER_USAGE_TYPE_GSM_SUB;
		else if (!strcmp(np, "map-to")) {
			rec->usage = NUMBER_USAGE_TYPE_ALIAS;
			while (isspace(*cp))
				cp++;
			if (*cp == '\0' || *cp == '#') {
				fprintf(stderr,
			"number-db2 line %d: map-to requires an argument\n",
					lineno);
				exit(1);
			}
			for (np = cp; *cp && !isspace(*cp); cp++)
				;
			if (*cp)
				*cp++ = '\0';
			rc = parse_extra_number(rec, np);
			if (rc < 0) {
				fprintf(stderr,
			"number-db2 line %d: map-to argument is invalid\n",
					lineno);
				exit(1);
			}
		} else if (!strcmp(np, "e911-via")) {
			if (rec->usage != NUMBER_USAGE_TYPE_GSM_SUB) {
				fprintf(stderr,
			"number-db2 line %d: invalid usage of e911-via\n",
					lineno);
				exit(1);
			}
			rec->usage |= NUMBER_USAGE_FLAG_E911_VIA;
			while (isspace(*cp))
				cp++;
			if (*cp == '\0' || *cp == '#') {
				fprintf(stderr,
			"number-db2 line %d: e911-via requires an argument\n",
					lineno);
				exit(1);
			}
			for (np = cp; *cp && !isspace(*cp); cp++)
				;
			if (*cp)
				*cp++ = '\0';
			rc = parse_extra_number(rec, np);
			if (rc < 0) {
				fprintf(stderr,
			"number-db2 line %d: e911-via argument is invalid\n",
					lineno);
				exit(1);
			}
		} else {
			fprintf(stderr,
			"number-db2 line %d: invalid attribute \"%s\"\n",
				lineno, np);
			exit(1);
		}
	}
}

static void handle_suffix_line(char *cp)
{
	char *np;
	uint16_t suffix;
	struct owned_number_rec own_rec;
	struct short_number_rec short_rec;

	if (!prefix_set) {
		fprintf(stderr,
	      "number-db2 line %d: suffix not valid without preceding prefix\n",
			lineno);
		exit(1);
	}
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (grok_number_string(np, 0) != 4) {
		fprintf(stderr,
		      "number-db2 line %d: suffix requires 4-digit argument\n",
			lineno);
		exit(1);
	}
	suffix = digits4_to_uint16(np);
	bzero(&own_rec, sizeof own_rec);
	own_rec.number[0] = prefix_buf[0];
	own_rec.number[1] = prefix_buf[1];
	own_rec.number[2] = suffix;
	handle_number_attr(&own_rec, cp);
	enter_owned_number(&own_rec);
	if (!prefix_allows_abbrev)
		return;
	if ((own_rec.usage & NUMBER_USAGE_MASK) != NUMBER_USAGE_TYPE_GSM_SUB)
		return;
	bzero(&short_rec, sizeof short_rec);
	short_rec.short_num = suffix;
	short_rec.short_num_type = SHORT_NUM_TYPE_ABBREV;
	short_rec.fullnum_flags = own_rec.number_flags;
	short_rec.fullnum_prefix[0] = prefix_buf[0];
	short_rec.fullnum_prefix[1] = prefix_buf[1];
	enter_short_number(&short_rec);
}

static void handle_full10_line(char *cp)
{
	char *np, full10[11];
	struct owned_number_rec own_rec;

	prefix_set = 0;		/* cancel any previous prefix line */
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (grok_number_string(np, 1) != 10) {
		fprintf(stderr,
		"number-db2 line %d: full10 requires 10-digit argument\n",
			lineno);
		exit(1);
	}
	dehyphen_number_string(np, full10);
	if (!is_nanp_valid_prefix(full10)) {
		fprintf(stderr,
			"number-db2 line %d: number violates NANP rules\n",
			lineno);
		exit(1);
	}
	bzero(&own_rec, sizeof own_rec);
	own_rec.number[0] = digits3_to_uint16(full10);
	own_rec.number[1] = digits3_to_uint16(full10 + 3);
	own_rec.number[2] = digits4_to_uint16(full10 + 6);
	handle_number_attr(&own_rec, cp);
	enter_owned_number(&own_rec);
}

static void handle_itn_line(char *cp)
{
	char *np;
	struct short_number_rec short_rec;

	prefix_set = 0;		/* cancel any previous prefix line */
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (grok_number_string(np, 0) != 4) {
		fprintf(stderr,
			"number-db2 line %d: itn requires 4-digit argument\n",
			lineno);
		exit(1);
	}
	bzero(&short_rec, sizeof short_rec);
	short_rec.short_num = digits4_to_uint16(np);
	short_rec.short_num_type = SHORT_NUM_TYPE_ITN;
	enter_short_number(&short_rec);
}

static void handle_test_sink_line(char *cp)
{
	char *np;
	struct short_number_rec short_rec;

	prefix_set = 0;		/* cancel any previous prefix line */
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (grok_number_string(np, 0) != 4) {
		fprintf(stderr,
		"number-db2 line %d: test-sink requires 4-digit argument\n",
			lineno);
		exit(1);
	}
	bzero(&short_rec, sizeof short_rec);
	short_rec.short_num = digits4_to_uint16(np);
	short_rec.short_num_type = SHORT_NUM_TYPE_TEST_SINK;
	enter_short_number(&short_rec);
}

static void process_line(void)
{
	char *cp, *np;
	void (*handler)(char *);

	if (!index(linebuf, '\n')) {
		fprintf(stderr,
			"number-db2 line %d: too long or missing newline\n",
			lineno);
		exit(1);
	}
	for (cp = linebuf; isspace(*cp); cp++)
		;
	if (*cp == '\0' || *cp == '#')
		return;
	for (np = cp; *cp && !isspace(*cp); cp++)
		;
	if (*cp)
		*cp++ = '\0';
	if (!strcmp(np, "prefix"))
		handler = handle_prefix_line;
	else if (!strcmp(np, "suffix"))
		handler = handle_suffix_line;
	else if (!strcmp(np, "full10"))
		handler = handle_full10_line;
	else if (!strcmp(np, "itn"))
		handler = handle_itn_line;
	else if (!strcmp(np, "test-sink"))
		handler = handle_test_sink_line;
	else {
		fprintf(stderr,
			"number-db2 line %d: non-understood keyword \"%s\"\n",
			lineno, np);
		exit(1);
	}
	while (isspace(*cp))
		cp++;
	if (*cp == '\0' || *cp == '#') {
		fprintf(stderr,
		"number-db2 line %d: missing argument after \"%s\" keyword\n",
			lineno, np);
		exit(1);
	}
	handler(cp);
}

static int compare_owned_num(const void *p1v, const void *p2v)
{
	const uint16_t *p1 = p1v, *p2 = p2v;

	if (p1[0] < p2[0])
		return(-1);
	if (p1[0] > p2[0])
		return(1);
	if (p1[1] < p2[1])
		return(-1);
	if (p1[1] > p2[1])
		return(1);
	if (p1[2] < p2[2])
		return(-1);
	if (p1[2] > p2[2])
		return(1);
	return(0);
}

static int compare_short_num(const void *p1v, const void *p2v)
{
	const uint16_t *p1 = p1v, *p2 = p2v;

	if (*p1 < *p2)
		return(-1);
	if (*p1 > *p2)
		return(1);
	return(0);
}

static void owned_num_check_dup(void)
{
	const struct owned_number_rec *p, *endp;

	endp = owned_number_buf + owned_number_count - 1;
	for (p = owned_number_buf; p < endp; p++) {
		if (p[0].number[0] == p[1].number[0] &&
		    p[0].number[1] == p[1].number[1] &&
		    p[0].number[2] == p[1].number[2]) {
			fprintf(stderr,
		"error: NANP number %03u-%03u-%04u appears more than once\n",
				p[0].number[0], p[0].number[1], p[0].number[2]);
			exit(1);
		}
	}
}

static void short_num_check_dup(void)
{
	const struct short_number_rec *p, *endp;

	endp = short_number_buf + short_number_count - 1;
	for (p = short_number_buf; p < endp; p++) {
		if (p[0].short_num == p[1].short_num) {
			fprintf(stderr,
			"error: short number %04u appears more than once\n",
				(unsigned) p->short_num);
			exit(1);
		}
	}
}

static void check_secondnum_mapto(const struct owned_number_rec *src)
{
	const struct owned_number_rec *dest;

	dest = bsearch(src->remap, owned_number_buf, owned_number_count,
			sizeof(struct owned_number_rec), compare_owned_num);
	if (!dest) {
		fprintf(stderr,
	"error: NANP %03u-%03u-%04u map-to target is not in the database\n",
			src->number[0], src->number[1], src->number[2]);
		exit(1);
	}
	if ((dest->usage & NUMBER_USAGE_MASK) != NUMBER_USAGE_TYPE_GSM_SUB) {
		fprintf(stderr,
	"error: NANP %03u-%03u-%04u map-to target is not a gsm-sub number\n",
			src->number[0], src->number[1], src->number[2]);
		exit(1);
	}
}

static void check_secondnum_e911via(const struct owned_number_rec *src)
{
	const struct owned_number_rec *dest;

	dest = bsearch(src->remap, owned_number_buf, owned_number_count,
			sizeof(struct owned_number_rec), compare_owned_num);
	if (!dest) {
		fprintf(stderr,
	"error: NANP %03u-%03u-%04u e911-via target is not in the database\n",
			src->number[0], src->number[1], src->number[2]);
		exit(1);
	}
	if (!(dest->number_flags & NUMBER_FLAG_E911PROV)) {
		fprintf(stderr,
	"error: NANP %03u-%03u-%04u e911-via target is not an E911 number\n",
			src->number[0], src->number[1], src->number[2]);
		exit(1);
	}
}

static void check_secondary_numbers(void)
{
	const struct owned_number_rec *p, *endp;

	endp = owned_number_buf + owned_number_count;
	for (p = owned_number_buf; p < endp; p++) {
		if ((p->usage & NUMBER_USAGE_MASK) == NUMBER_USAGE_TYPE_ALIAS)
			check_secondnum_mapto(p);
		if (p->usage & NUMBER_USAGE_FLAG_E911_VIA)
			check_secondnum_e911via(p);
	}
}

static void emit_output(void)
{
	FILE *outf;
	struct numdb_file_hdr hdr;

	outf = fopen("number-db2.newbin", "w");
	if (!outf) {
		perror("creating number-db2.newbin");
		exit(1);
	}
	hdr.owned_number_count = owned_number_count;
	hdr.short_number_count = short_number_count;
	if (fwrite(&hdr, sizeof hdr, 1, outf) != 1) {
write_err:	fprintf(stderr, "error writing to new binary file\n");
		exit(1);
	}
	if (fwrite(owned_number_buf, sizeof(owned_number_buf[0]),
		   owned_number_count, outf) != owned_number_count)
		goto write_err;
	if (fwrite(short_number_buf, sizeof(short_number_buf[0]),
		   short_number_count, outf) != short_number_count)
		goto write_err;
	fclose(outf);
}

int main(int argc, char **argv)
{
	if (argc > 2) {
		fprintf(stderr, "usage: %s [directory]\n", argv[0]);
		exit(1);
	}
	if (argv[1])
		system_dir = argv[1];
	else
		system_dir = "/var/gsm";
	if (chdir(system_dir) < 0) {
		perror(system_dir);
		exit(1);
	}
	inf = fopen("number-db2", "r");
	if (!inf) {
		perror("opening number-db2");
		exit(1);
	}
	for (lineno = 1; fgets(linebuf, sizeof linebuf, inf); lineno++)
		process_line();
	fclose(inf);
	if (owned_number_count >= 2) {
		qsort(owned_number_buf, owned_number_count,
			sizeof(owned_number_buf[0]), compare_owned_num);
		owned_num_check_dup();
	}
	check_secondary_numbers();
	if (short_number_count >= 2) {
		qsort(short_number_buf, short_number_count,
			sizeof(short_number_buf[0]), compare_short_num);
		short_num_check_dup();
	}
	emit_output();
	/* make it live */
	if (rename("number-db2.newbin", "number-db2.bin") < 0) {
		perror("rename");
		exit(1);
	}
	exit(0);
}