/*
 * env.c
 *
 * Copyright (C) 2005 Andreas Oberritter
 *
 * Homepage: http://www.saftware.de/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

#define list_for_each(elem, list)	\
	for (elem = list; elem != NULL; elem = elem->next)

struct nv_var {
	unsigned char flags;
	char *name;
	char *value;
	struct nv_var *next;
};

static struct nv_var *vars;

static void hd(const char *text, const char *buf, size_t len)
{
	unsigned int i;

	printf("%s", text);
	for (i = 0; i < len; i++)
		printf(" %02x", buf[i]);
	printf("\n");
}

static void nv_env(void)
{
	struct nv_var *ptr;

	list_for_each(ptr, vars)
		printf("%s=\"%s\"\n", ptr->name, ptr->value);
}

static bool nv_printenv(const char *name)
{
	struct nv_var *ptr;

	list_for_each(ptr, vars)
		if (!strcmp(ptr->name, name)) {
			printf("%s\n", ptr->value);
			return true;
		}

	return false;
}

static bool _nv_setenv(unsigned char flags, const char *name, size_t nlen, const char *value, size_t vlen)
{
	struct nv_var *ptr;

	list_for_each(ptr, vars)
		if (!strcmp(ptr->name, name)) {
			free(ptr->value);
			ptr->flags = flags;
			ptr->value = strndup(value, vlen);
			return true;
		}

	ptr = malloc(sizeof(struct nv_var));
	if (!ptr) {
		perror("malloc");
		return false;
	}

	/* insert at end to keep the order */
	if (!vars) {
		vars = ptr;
	} else {
		struct nv_var *tmp;
		for (tmp = vars; tmp->next != NULL; tmp = tmp->next);
		tmp->next = ptr;
	}

	ptr->flags = flags;
	ptr->name = strndup(name, nlen);
	ptr->value = strndup(value, vlen);
	ptr->next = NULL;

	return true;
}

static bool nv_setenv(const char *name, const char *value)
{
	return _nv_setenv(0x00, name, strlen(name), value, strlen(value));
}

static bool nv_readenv(const char *cfgarea, unsigned int len)
{
	const char *ptr = cfgarea;
	const char *delim;
	unsigned int dtag;
	unsigned int dlen;

	for (ptr = cfgarea; ; ptr += dlen + 2) {
		dtag = ptr[0] & 0xff;
		if (dtag == 0x00)
			break;
		dlen = ptr[1] & 0xff;
		if (dtag == 0x01) {
			delim = strchr(&ptr[3], '=');
			if (delim) {
				if (_nv_setenv(ptr[2], &ptr[3], delim - &ptr[3], &ptr[3 + (delim - &ptr[3]) + 1], dlen - 2 - (delim - &ptr[3])) == false) {
					fprintf(stderr, "could not set variable\n");
					return false;
				}
			} else {
				fprintf(stderr, "not a variable\n");
				return false;
			}
		} else {
			hd("tag:", cfgarea, len);
			hd("err:", ptr, dlen + 2);
			fprintf(stderr, "unhandled tag %02x\n", dtag);
			return false;
		}
	}

	return true;
}

static void nv_erase(unsigned char *cfgarea, unsigned int len)
{
	memset(cfgarea, 0xff, len);
}

static void nv_writeenv(unsigned char *cfgarea, unsigned int len)
{
	struct nv_var *ptr;
	unsigned int nlen, vlen;

	list_for_each(ptr, vars) {
		nlen = strlen(ptr->name);
		vlen = strlen(ptr->value);
		*cfgarea++ = 0x01;
		*cfgarea++ = nlen + vlen + 2;
		*cfgarea++ = ptr->flags;
		memcpy(cfgarea, ptr->name, nlen);
		cfgarea += nlen;
		*cfgarea++ = '=';
		memcpy(cfgarea, ptr->value, vlen);
		cfgarea += vlen;
	}

	*cfgarea++ = 0x00;
}

static void help(FILE *f)
{
	fprintf(f, "Usage: env [OPTION]...\n\n");
	fprintf(f, "printenv\n");
	fprintf(f, "\tenv [-f <filename>]\n\n");
	fprintf(f, "getenv\n");
	fprintf(f, "\tenv [-f <filename>] -n <name>\n\n");
	fprintf(f, "setenv\n");
	fprintf(f, "\tenv [-f <filename>] -n <name> -v <value>\n\n");
}

int main(int argc, char *argv[])
{
	const char *filename = "mtd4";
	unsigned int cfglen = 0x1000;
	int fd, ret = EXIT_FAILURE;
	void *cfgarea;
	extern char *optarg;
	int flags;
	int c;
	char *name = NULL;
	char *value = NULL;

	while ((c = getopt(argc, argv, "f:hn:v:")) != -1) {
		switch (c) {
		case 'f':
			filename = strdup(optarg);
			break;
		case 'h':
			help(stdout);
			return EXIT_SUCCESS;
		case 'n':
			name = strdup(optarg);
			break;
		case 'v':
			value = strdup(optarg);
			break;
		default:
			help(stderr);
			return EXIT_FAILURE;
		}
	}

	if (((name == NULL) && (value != NULL)) || (optind < argc)) {
		help(stderr);
		return EXIT_FAILURE;
	}

	flags = value ? O_RDWR : O_RDONLY;
	fd = open(filename, flags);
	if (fd == -1) {
		perror(filename);
		return EXIT_FAILURE;
	}

	flags = value ? PROT_READ | PROT_WRITE : PROT_READ;
	cfgarea = mmap(NULL, cfglen, flags, MAP_SHARED, fd, 0x1e000);
	if (cfgarea == MAP_FAILED) {
		perror("mmap");
		goto out_close;
	}

	if (nv_readenv(cfgarea, cfglen) == false) {
		fprintf(stderr, "readenv failed\n");
		goto out_munmap;
	}

	if (name && value) {
		if (nv_setenv(name, value) == false) {
			fprintf(stderr, "setenv failed\n");
			goto out_munmap;
		}
		nv_erase(cfgarea, cfglen);
		nv_writeenv(cfgarea, cfglen);
	} else if (name) {
		if (nv_printenv(name) == false)
			fprintf(stderr, "printenv failed\n");
	} else {
		nv_env();
	}

out_munmap:
	munmap(cfgarea, cfglen);
out_close:
	close(fd);

	return ret;
}


