/*	$NetBSD: var.c,v 1.3 2017/04/20 13:18:23 joerg Exp $	*/

/*-
 * Copyright (c) 2005, 2008 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Dieter Baron, Thomas Klausner, Johnny Lam, and Joerg Sonnenberger.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <nbcompat.h>
#if HAVE_SYS_CDEFS_H
#include <sys/cdefs.h>
#endif
__RCSID("$NetBSD: var.c,v 1.3 2017/04/20 13:18:23 joerg Exp $");

#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_ERR_H
#include <err.h>
#endif
#if HAVE_ERRNO_H
#include <errno.h>
#endif
#if HAVE_STDIO_H
#include <stdio.h>
#endif

#include "lib.h"

static const char *var_cmp(const char *, size_t, const char *, size_t);
static void var_print(FILE *, const char *, const char *);

/*
 * Copy the specified variables from the file fname to stdout.
 */
int
var_copy_list(const char *buf, const char **variables)
{
	const char *eol, *next;
	size_t len;
	int i;

	for (; *buf; buf = next) {
		if ((eol = strchr(buf, '\n')) != NULL) {
			next = eol + 1;
			len = eol - buf;
		} else {
			len = strlen(buf);
			next = buf + len;
		}

		for (i=0; variables[i]; i++) {
			if (var_cmp(buf, len, variables[i],
				       strlen(variables[i])) != NULL) {
				printf("%.*s\n", (int)len, buf);
				break;
			}
		}
	}
	return 0;
}

/*
 * Print the value of variable from the file fname to stdout.
 */
char *
var_get(const char *fname, const char *variable)
{
	FILE   *fp;
	char   *line;
	size_t  len;
	size_t  varlen;
	char   *value;
	size_t  valuelen;
	size_t  thislen;
	const char *p;

	varlen = strlen(variable);
	if (varlen == 0)
		return NULL;

	fp = fopen(fname, "r");
	if (!fp) {
		if (errno != ENOENT)
			warn("var_get: can't open '%s' for reading", fname);
		return NULL;
	}

	value = NULL;
	valuelen = 0;
	
	while ((line = fgetln(fp, &len)) != (char *) NULL) {
		if (line[len - 1] == '\n')
			--len;
		if ((p=var_cmp(line, len, variable, varlen)) == NULL)
			continue;

		thislen = line+len - p;
		if (value) {
			value = xrealloc(value, valuelen+thislen+2);
			value[valuelen++] = '\n';
		}
		else {
			value = xmalloc(thislen+1);
		}
		sprintf(value+valuelen, "%.*s", (int)thislen, p);
		valuelen += thislen;
	}
	(void) fclose(fp);
	return value;
}

/*
 * Print the value of variable from the memory buffer to stdout.
 */
char *
var_get_memory(const char *buf, const char *variable)
{
	const char *eol, *next, *data;
	size_t len, varlen, thislen, valuelen;
	char *value;

	varlen = strlen(variable);
	if (varlen == 0)
		return NULL;

	value = NULL;
	valuelen = 0;

	for (; buf && *buf; buf = next) {
		if ((eol = strchr(buf, '\n')) != NULL) {
			next = eol + 1;
			len = eol - buf;
		} else {
			next = eol;
			len = strlen(buf);
		}
		if ((data = var_cmp(buf, len, variable, varlen)) == NULL)
			continue;

		thislen = buf + len - data;
		if (value) {
			value = xrealloc(value, valuelen+thislen+2);
			value[valuelen++] = '\n';
		}
		else {
			value = xmalloc(thislen+1);
		}
		sprintf(value + valuelen, "%.*s", (int)thislen, data);
		valuelen += thislen;
	}
	return value;
}

/*
 * Add given variable with given value to file, overwriting any
 * previous occurrence.
 */
int
var_set(const char *fname, const char *variable, const char *value)
{
	FILE   *fp;
	FILE   *fout;
	char   *tmpname;
	int     fd;
	char   *line;
	size_t  len;
	size_t  varlen;
	Boolean done;
	struct stat st;

	varlen = strlen(variable);
	if (varlen == 0)
		return 0;

	fp = fopen(fname, "r");
	if (fp == NULL) {
		if (errno != ENOENT) {
			warn("var_set: can't open '%s' for reading", fname);
			return -1;
		}
		if (value == NULL)
			return 0; /* Nothing to do */
	}

	tmpname = xasprintf("%s.XXXXXX", fname);
	if ((fd = mkstemp(tmpname)) < 0) {
		free(tmpname);
		if (fp != NULL)
			fclose(fp);
		warn("var_set: can't open temp file for '%s' for writing",
		      fname);
		return -1;
	}
	if (chmod(tmpname, 0644) < 0) {
		close(fd);
		if (fp != NULL)
			fclose(fp);
		free(tmpname);
		warn("var_set: can't set permissions for temp file for '%s'",
		      fname);
		return -1;
	}
	if ((fout=fdopen(fd, "w")) == NULL) {
		close(fd);
		remove(tmpname);
		free(tmpname);
		if (fp != NULL)
			fclose(fp);
		warn("var_set: can't open temp file for '%s' for writing",
		      fname);
		return -1;
	}

	done = FALSE;

	if (fp) {
		while ((line = fgetln(fp, &len)) != (char *) NULL) {
			if (var_cmp(line, len, variable, varlen) == NULL)
				fprintf(fout, "%.*s", (int)len, line);
			else {
				if (!done && value) {
					var_print(fout, variable, value);
					done = TRUE;
				}
			}
		}
		(void) fclose(fp);
	}

	if (!done && value)
		var_print(fout, variable, value);

	if (fclose(fout) < 0) {
		free(tmpname);
		warn("var_set: write error for '%s'", fname);
		return -1;
	}

	if (stat(tmpname, &st) < 0) {
		free(tmpname);
		warn("var_set: cannot stat tempfile for '%s'", fname);
		return -1;
	}

	if (st.st_size == 0) {
		if (remove(tmpname) < 0) {
			free(tmpname);
			warn("var_set: cannot remove tempfile for '%s'",
			     fname);
			return -1;
		}
		free(tmpname);
		if (remove(fname) < 0) {
			warn("var_set: cannot remove '%s'", fname);
			return -1;
		}
		return 0;
	}

	if (rename(tmpname, fname) < 0) {
		free(tmpname);
		warn("var_set: cannot move tempfile to '%s'", fname);
		return -1;
	}
	free(tmpname);
	return 0;
}

/*
 * Check if line contains variable var, return pointer to its value or NULL.
 */
static const char *
var_cmp(const char *line, size_t linelen, const char *var, size_t varlen)
{
	/*
	 * We expect lines to look like one of the following
	 * forms:
	 *      VAR=value
	 *      VAR= value
	 * We print out the value of VAR, or nothing if it
	 * doesn't exist.
	 */
	if (linelen < varlen+1)
		return NULL;
	if (strncmp(var, line, varlen) != 0)
		return NULL;
	
	line += varlen;
	if (*line != '=')
		return NULL;

	++line;
	linelen -= varlen+1;
	if (linelen > 0 && *line == ' ')
		++line;
	return line;
}

/*
 * Print given variable with value to file f.
 */
static void
var_print(FILE *f, const char *variable, const char *value)
{
	const char *p;

	while ((p=strchr(value, '\n')) != NULL) {
		if (p != value)
			fprintf(f, "%s=%.*s\n", variable, (int)(p-value), value);
		value = p+1;
	}

	if (*value)
		fprintf(f, "%s=%s\n", variable, value);
}