From 77725291efd609339cc560bec646782bc09ffb90 Mon Sep 17 00:00:00 2001
From: David Oberhollenzer <david.oberhollenzer@tele2.at>
Date: Wed, 11 Apr 2018 00:04:33 +0200
Subject: Make line buffer static, move error reporting to rdline

 - Expose rdline_t struct and move a lot of extra processing
   to rdline code
 - Make line buffer statically allocated
 - Simplify rdsvc code

Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
---
 lib/include/util.h |  48 +++++++++++---
 lib/src/rdline.c   | 106 +++++++++++++++--------------
 lib/src/rdsvc.c    | 191 +++++++++++++++++++++++------------------------------
 lib/src/unescape.c |   1 +
 4 files changed, 177 insertions(+), 169 deletions(-)

diff --git a/lib/include/util.h b/lib/include/util.h
index a9d111b..c13b942 100644
--- a/lib/include/util.h
+++ b/lib/include/util.h
@@ -36,13 +36,40 @@ typedef struct {
 } enum_map_t;
 
 
+typedef struct {
+	int fd;			/* input file descriptor */
+	const char *argstr;	/* if not NULL, read from this instead */
+
+	const char *filename;	/* input file name */
+	size_t lineno;		/* current line number */
+
+	size_t i;		/* buffer offset */
+	char buffer[256];	/* current line, null-terminated */
+
+	int argc;
+	const char *const *argv;
+
+	bool string;		/* inside a string? */
+	bool escape;		/* reading an escape sequence? */
+	bool comment;		/* inside a comment */
+} rdline_t;
+
+
 /*
-	Read from fd until end-of-file or a line feed is encountered.
+	Initialize the config line scanner.
 
-	Returns NULL with errno set on failure. Returns NULL with errno
-	cleared if end-of-file is reached.
+	The scanner reads from the provided fd. The filename is used for
+	error reporting. An argument count and vector can be set for argument
+	substitution in rdline.
+*/
+void rdline_init(rdline_t *t, int fd, const char *filename,
+		 int argc, const char *const *argv);
+
+/*
+	Read from file until end-of-file or a line feed is encountered.
 
-	The line must be deallocated with free().
+	Returns -1 on failure, +1 if end of file was reached,
+	0 if data was read successfully.
 
 	The following transformations are applied:
 	 - Space characters are replaced with regular white space characters.
@@ -53,19 +80,18 @@ typedef struct {
 	 - If a '"' is encounterd, the above rules are disabled, until a
 	   after the matching '"' is read. A '"' can be escaped by preceeding
 	   it with a backslash.
-	 - If a second, coresponding '"' is not found, processing fails with
-	   errno set to EILSEQ.
+	 - If a second, coresponding '"' is not found, processing fails.
 	 - If a '%' character is encountered, the next character is expected
 	   to be a single digit index into argv. If it is not a digit or
-	   outside the bounds set by argc, processing fails and sets errno
-	   to EINVAL. On success, the argv value is inserted and processed
-	   as described above.
+	   outside the bounds set by argc, processing fails. On success,
+	   the argv value is inserted and processed as described above.
 	 - A '%' character can be escaped by writing '%%' or, if inside
 	   a double quite string, by writing \%.
 	 - An attempt to use such an indexed argument inside an argument
-	   expansion, results in failure with errno set to ELOOP.
+	   expansion, results in failure.
+	 - If the resulting line is empty, processing is restarted.
 */
-char *rdline(int fd, int argc, const char *const *argv);
+int rdline(rdline_t *t);
 
 /*
 	Remove double quotes ('"') from a string and substitute escape
diff --git a/lib/src/rdline.c b/lib/src/rdline.c
index 18c9c85..47a9d2a 100644
--- a/lib/src/rdline.c
+++ b/lib/src/rdline.c
@@ -20,22 +20,10 @@
 #include <string.h>
 #include <errno.h>
 #include <ctype.h>
+#include <stdio.h>
 
 #include "util.h"
 
-typedef struct {
-	int fd;			/* input file descriptor */
-	const char *argstr;	/* if not NULL, read from this instead */
-
-	size_t i;		/* buffer offset */
-	size_t bufsiz;		/* buffer size */
-	char *buffer;
-
-	bool string;		/* inside a string? */
-	bool escape;		/* reading an escape sequence? */
-	bool comment;		/* inside a comment */
-} rdline_t;
-
 static int rdline_getc(rdline_t *t)
 {
 	int ret;
@@ -69,9 +57,6 @@ out:
 
 static int rdline_append(rdline_t *t, int c)
 {
-	size_t newsz;
-	char *new;
-
 	if (t->comment) {
 		if (c != '\0')
 			return 0;
@@ -102,67 +87,88 @@ static int rdline_append(rdline_t *t, int c)
 			t->i -= 1;
 	}
 
-	if (t->i == t->bufsiz) {
-		newsz = t->bufsiz ? t->bufsiz * 2 : 16;
-		new = realloc(t->buffer, newsz);
-
-		if (new == NULL)
-			return -1;
-
-		t->buffer = new;
-		t->bufsiz = newsz;
-	}
+	if (t->i == sizeof(t->buffer))
+		return -1;
 
 	t->buffer[t->i++] = c;
 	return 0;
 }
 
-char *rdline(int fd, int argc, const char *const *argv)
+void rdline_init(rdline_t *t, int fd, const char *filename,
+		 int argc, const char *const *argv)
 {
-	rdline_t rd;
-	int c, ret;
+	memset(t, 0, sizeof(*t));
+	t->fd = fd;
+	t->filename = filename;
+	t->argc = argc;
+	t->argv = argv;
+}
 
-	memset(&rd, 0, sizeof(rd));
-	rd.fd = fd;
+int rdline(rdline_t *t)
+{
+	const char *errstr;
+	int c;
+retry:
+	t->i = 0;
+	t->argstr = NULL;
+	t->string = t->escape = t->comment = false;
+	t->lineno += 1;
 
 	do {
-		c = rdline_getc(&rd);
-		if (c < 0)
+		errno = 0;
+		c = rdline_getc(t);
+		if (c < 0) {
+			if (errno == 0)
+				return 1;
+			errstr = strerror(errno);
 			goto fail;
-		if (c == 0 && rd.string) {
-			errno = EILSEQ;
+		}
+		if (c == 0 && t->string) {
+			errstr = "missing \"";
 			goto fail;
 		}
 
 		if (c == '%') {
-			c = rdline_getc(&rd);
-			if (c == 0)
-				errno = EILSEQ;
-			if (c <= 0)
+			c = rdline_getc(t);
+			if (c == 0) {
+				errstr = "unexpected end of line after '%%'";
 				goto fail;
+			}
+			if (c < 0) {
+				errstr = strerror(errno);
+				goto fail;
+			}
 
 			if (c != '%') {
-				if (!isdigit(c) || (c - '0') >= argc) {
-					errno = EINVAL;
+				if (!isdigit(c)) {
+					errstr = "exptected digit after '%%'";
 					goto fail;
 				}
-				if (rd.argstr != NULL) {
-					errno = ELOOP;
+				if ((c - '0') >= t->argc) {
+					errstr = "argument out of range";
 					goto fail;
 				}
-				rd.argstr = argv[c - '0'];
+				if (t->argstr != NULL) {
+					errstr = "recursive argument "
+						 "expansion";
+					goto fail;
+				}
+				t->argstr = t->argv[c - '0'];
 				continue;
 			}
 		}
 
-		if (rdline_append(&rd, c))
+		if (rdline_append(t, c)) {
+			errstr = "line too long";
 			goto fail;
+		}
 	} while (c != '\0');
 
-	return rd.buffer;
+	if (t->buffer[0] == '\0')
+		goto retry;
+
+	return 0;
 fail:
-	ret = errno;
-	free(rd.buffer);
-	errno = ret;
-	return NULL;
+	fprintf(stderr, "%s: %zu: %s\n", t->filename, t->lineno, errstr);
+	return -1;
 }
diff --git a/lib/src/rdsvc.c b/lib/src/rdsvc.c
index 29c9019..e911724 100644
--- a/lib/src/rdsvc.c
+++ b/lib/src/rdsvc.c
@@ -28,17 +28,17 @@
 #include "service.h"
 #include "util.h"
 
-static int try_unescape(char *arg, const char *filename, size_t lineno)
+static int try_unescape(char *arg, rdline_t *rd)
 {
 	if (unescape(arg)) {
 		fprintf(stderr, "%s: %zu: malformed string constant\n",
-			filename, lineno);
+			rd->filename, rd->lineno);
 		return -1;
 	}
 	return 0;
 }
 
-static char **try_split_argv(char *str, const char *filename, size_t lineno)
+static char **try_split_argv(char *str, rdline_t *rd)
 {
 	char **argv = split_argv(str);
 
@@ -46,11 +46,11 @@ static char **try_split_argv(char *str, const char *filename, size_t lineno)
 		switch (errno) {
 		case EINVAL:
 			fprintf(stderr, "%s: %zu: malformed string constant\n",
-				filename, lineno);
+				rd->filename, rd->lineno);
 			break;
 		default:
-			fprintf(stderr, "%s: %zu: %s\n", filename, lineno,
-				strerror(errno));
+			fprintf(stderr, "%s: %zu: %s\n",
+				rd->filename, rd->lineno, strerror(errno));
 			break;
 		}
 	}
@@ -58,43 +58,56 @@ static char **try_split_argv(char *str, const char *filename, size_t lineno)
 	return argv;
 }
 
-static int svc_desc(service_t *svc, char *arg,
-		    const char *filename, size_t lineno)
+static char *try_strdup(const char *str, rdline_t *rd)
 {
-	if (try_unescape(arg, filename, lineno))
+	char *out = strdup(str);
+
+	if (out == NULL) {
+		fprintf(stderr, "%s: %zu: out of memory\n",
+			rd->filename, rd->lineno);
+	}
+	return out;
+}
+
+static int svc_desc(service_t *svc, char *arg, rdline_t *rd)
+{
+	if (try_unescape(arg, rd))
 		return -1;
-	svc->desc = arg;
-	return 0;
+	svc->desc = try_strdup(arg, rd);
+	return svc->desc == NULL ? -1 : 0;
 }
 
-static int svc_tty(service_t *svc, char *arg,
-		   const char *filename, size_t lineno)
+static int svc_tty(service_t *svc, char *arg, rdline_t *rd)
 {
-	if (try_unescape(arg, filename, lineno))
+	if (try_unescape(arg, rd))
 		return -1;
-	svc->ctty = arg;
-	return 0;
+	svc->ctty = try_strdup(arg, rd);
+	return svc->ctty == NULL ? -1 : 0;
 }
 
-static int svc_exec(service_t *svc, char *arg,
-		    const char *filename, size_t lineno)
+static int svc_exec(service_t *svc, char *arg, rdline_t *rd)
 {
 	exec_t *e, *end;
-	char **argv;
-
-	argv = try_split_argv(arg, filename, lineno);
-	if (argv == NULL)
-		return -1;
 
 	e = calloc(1, sizeof(*e));
 	if (e == NULL) {
-		fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno);
-		free(argv);
+		fprintf(stderr, "%s: %zu: out of memory\n",
+			rd->filename, rd->lineno);
 		return -1;
 	}
 
-	e->argv = argv;
-	e->raw_argv = arg;
+	e->raw_argv = try_strdup(arg, rd);
+	if (e->raw_argv == NULL) {
+		free(e);
+		return -1;
+	}
+
+	e->argv = try_split_argv(e->raw_argv, rd);
+	if (e->argv == NULL) {
+		free(e->raw_argv);
+		free(e);
+		return -1;
+	}
 
 	if (svc->exec == NULL) {
 		svc->exec = e;
@@ -106,47 +119,50 @@ static int svc_exec(service_t *svc, char *arg,
 	return 0;
 }
 
-static int svc_before(service_t *svc, char *arg,
-		      const char *filename, size_t lineno)
+static int svc_before(service_t *svc, char *arg, rdline_t *rd)
 {
 	if (svc->before != NULL) {
 		fprintf(stderr, "%s: %zu: 'before' dependencies respecified\n",
-			filename, lineno);
+			rd->filename, rd->lineno);
 		return -1;
 	}
 
-	svc->before = try_split_argv(arg, filename, lineno);
+	svc->raw_before = try_strdup(arg, rd);
+	if (svc->raw_before == NULL)
+		return -1;
+
+	svc->before = try_split_argv(svc->raw_before, rd);
 	if (svc->before == NULL)
 		return -1;
 
-	svc->raw_before = arg;
 	return 0;
 }
 
-static int svc_after(service_t *svc, char *arg,
-		     const char *filename, size_t lineno)
+static int svc_after(service_t *svc, char *arg, rdline_t *rd)
 {
 	if (svc->after != NULL) {
 		fprintf(stderr, "%s: %zu: 'after' dependencies respecified\n",
-			filename, lineno);
+			rd->filename, rd->lineno);
 		return -1;
 	}
 
-	svc->after = try_split_argv(arg, filename, lineno);
+	svc->raw_after = try_strdup(arg, rd);
+	if (svc->raw_after == NULL)
+		return -1;
+
+	svc->after = try_split_argv(svc->raw_after, rd);
 	if (svc->after == NULL)
 		return -1;
 
-	svc->raw_after = arg;
 	return 0;
 }
 
-static int svc_type(service_t *svc, char *arg,
-		    const char *filename, size_t lineno)
+static int svc_type(service_t *svc, char *arg, rdline_t *rd)
 {
 	char **args;
 	int i, type;
 
-	args = try_split_argv(arg, filename, lineno);
+	args = try_split_argv(arg, rd);
 
 	if (args == NULL)
 		return -1;
@@ -155,7 +171,7 @@ static int svc_type(service_t *svc, char *arg,
 
 	if (type == -1) {
 		fprintf(stderr, "%s: %zu: unknown service type '%s'\n",
-			filename, lineno, args[0]);
+			rd->filename, rd->lineno, args[0]);
 		free(args);
 		return -1;
 	}
@@ -183,48 +199,45 @@ static int svc_type(service_t *svc, char *arg,
 			/* fall-through */
 		default:
 			fprintf(stderr, "%s: %zu: unexpected extra arguments "
-				"for type '%s'\n", filename, lineno, arg);
+				"for type '%s'\n",
+				rd->filename, rd->lineno, arg);
 			return -1;
 		}
 	}
 
 	svc->type = type;
 	free(args);
-	free(arg);
 	return 0;
 fail_limit:
 	fprintf(stderr, "%s: %zu: expected 'limit <value>' after 'respawn'\n",
-		filename, lineno);
+		rd->filename, rd->lineno);
 	free(args);
 	return -1;
 }
 
-static int svc_target(service_t *svc, char *arg,
-		      const char *filename, size_t lineno)
+static int svc_target(service_t *svc, char *arg, rdline_t *rd)
 {
 	int target;
 
-	if (try_unescape(arg, filename, lineno))
+	if (try_unescape(arg, rd))
 		return -1;
 
 	target = svc_target_from_string(arg);
 
 	if (target == -1) {
 		fprintf(stderr, "%s: %zu: unknown service target '%s'\n",
-			filename, lineno, arg);
+			rd->filename, rd->lineno, arg);
 		return -1;
 	}
 
 	svc->target = target;
-	free(arg);
 	return 0;
 }
 
 static const struct svc_param {
 	const char *key;
 
-	int (*handle)(service_t *svc, char *arg,
-		      const char *filename, size_t lineno);
+	int (*handle)(service_t *svc, char *arg, rdline_t *rd);
 } svc_params[] = {
 	{ "description", svc_desc },
 	{ "exec", svc_exec },
@@ -235,49 +248,15 @@ static const struct svc_param {
 	{ "after", svc_after },
 };
 
-
-static char *get_line(int fd, const char *filename, size_t *lineno,
-		      int argc, const char *const *args)
+static int splitkv(rdline_t *rd, char **k, char **v)
 {
-	const char *error;
-	char *line;
-	int ret;
-retry:
-	errno = 0;
-	line = rdline(fd, argc, args);
-	ret = errno;
-
-	if (line == NULL && errno != 0) {
-		switch (errno) {
-		case EINVAL: error = "error in argument expansion";  break;
-		case ELOOP:  error = "recursive argument expansion"; break;
-		case EILSEQ: error = "missing \"";                   break;
-		default:     error = strerror(errno);                break;
-		}
-
-		fprintf(stderr, "%s: %zu: %s\n", filename, *lineno, error);
-	}
-
-	if (line != NULL && strlen(line) == 0) {
-		free(line);
-		(*lineno) += 1;
-		goto retry;
-	}
-
-	errno = ret;
-	return line;
-}
-
-static int splitkv(const char *filename, size_t lineno,
-		   char *line, char **k, char **v)
-{
-	char *key = line, *value = line;
+	char *key = rd->buffer, *value = rd->buffer;
 
 	while (*value != ' ' && *value != '\0') {
 		if (!isalpha(*value)) {
 			fprintf(stderr,
 				"%s: %zu: unexpected '%c' in keyword\n",
-				filename, lineno, *value);
+				rd->filename, rd->lineno, *value);
 			return -1;
 		}
 		++value;
@@ -285,7 +264,7 @@ static int splitkv(const char *filename, size_t lineno,
 
 	if (*value != ' ') {
 		fprintf(stderr, "%s: %zu: expected argument after '%s'\n",
-			filename, lineno, key);
+			rd->filename, rd->lineno, key);
 		return -1;
 	}
 
@@ -296,8 +275,7 @@ static int splitkv(const char *filename, size_t lineno,
 	return 0;
 }
 
-static const struct svc_param *find_param(const char *filename, size_t lineno,
-					  const char *name)
+static const struct svc_param *find_param(rdline_t *rd, const char *name)
 {
 	size_t i;
 
@@ -307,19 +285,20 @@ static const struct svc_param *find_param(const char *filename, size_t lineno,
 	}
 
 	fprintf(stderr, "%s: %zu: unknown keyword '%s'\n",
-		filename, lineno, name);
+		rd->filename, rd->lineno, name);
 	return NULL;
 }
 
 
 service_t *rdsvc(int dirfd, const char *filename)
 {
-	char *line = NULL, *key, *value;
 	const struct svc_param *p;
 	const char *arg, *args[1];
 	service_t *svc = NULL;
-	size_t argc, lineno;
-	int fd;
+	char *key, *value;
+	size_t argc;
+	rdline_t rd;
+	int fd, ret;
 
 	fd = openat(dirfd, filename, O_RDONLY);
 	if (fd < 0) {
@@ -335,6 +314,8 @@ service_t *rdsvc(int dirfd, const char *filename)
 		argc = 0;
 	}
 
+	rdline_init(&rd, fd, filename, argc, args);
+
 	svc = calloc(1, sizeof(*svc));
 	if (svc == NULL)
 		goto fail_oom;
@@ -348,32 +329,26 @@ service_t *rdsvc(int dirfd, const char *filename)
 	if (svc->name == NULL)
 		goto fail_oom;
 
-	for (lineno = 1; ; ++lineno) {
-		line = get_line(fd, filename, &lineno, argc, args);
-		if (line == NULL) {
-			if (errno == 0)
-				break;
-			goto fail;
-		}
-
-		if (splitkv(filename, lineno, line, &key, &value))
+	while ((ret = rdline(&rd)) == 0) {
+		if (splitkv(&rd, &key, &value))
 			goto fail;
 
-		p = find_param(filename, lineno, key);
+		p = find_param(&rd, key);
 		if (p == NULL)
 			goto fail;
 
-		memmove(line, value, strlen(value) + 1);
-		if (p->handle(svc, line, filename, lineno))
+		if (p->handle(svc, value, &rd))
 			goto fail;
 	}
 
+	if (ret < 0)
+		goto fail;
+
 	close(fd);
 	return svc;
 fail_oom:
 	fputs("out of memory\n", stderr);
 fail:
-	free(line);
 	delsvc(svc);
 	close(fd);
 	return NULL;
diff --git a/lib/src/unescape.c b/lib/src/unescape.c
index 7508e2f..ea5c9e2 100644
--- a/lib/src/unescape.c
+++ b/lib/src/unescape.c
@@ -59,6 +59,7 @@ int unescape(char *src)
 				case 't': c = '\t'; break;
 				case '\\':
 				case '"':
+				case '%':
 					break;
 				case 'x':
 					c = 0;
-- 
cgit v1.2.3