/* $NetBSD$ */

/*-
 * Copyright (c) 2015 Jared D. McNeill <jmcneill@invisible.ca>
 * All rights reserved.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/*
 * cc -Wall -Werror -O2 uenv.c -o uenv -lutil -lz
 */

#include <sys/param.h>
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <zlib.h>

// ODROID-C1
#define CONFIG_ENV_SIZE                 (32 * 1024)     // unit: bytes
#define CONFIG_ENV_OFFSET               (512 * 1024)    // unit: bytes

struct environment {
	uint32_t	crc;
	u_char		data[CONFIG_ENV_SIZE - 4];
};

static void
usage(const char *pn)
{
	fprintf(stderr, "usage: %s <disk> [key] [value]\n", pn);
	exit(EXIT_FAILURE);
}

static struct environment *
open_uenv(int fd, int flags)
{
	struct environment *env;
	int prot = PROT_READ;

	if (flags == O_RDWR)
		prot |= PROT_WRITE;

	env = mmap(NULL, CONFIG_ENV_SIZE, prot, MAP_FILE|MAP_SHARED, fd,
	    CONFIG_ENV_OFFSET);
	if (env == MAP_FAILED)
		err(EXIT_FAILURE, "couldn't mmap device");

	return env;
}

static void
close_uenv(struct environment *env)
{
	munmap(env, CONFIG_ENV_SIZE);
}

static void
print_uenv(struct environment *env, const char *key)
{
	const char *p = (const char *)env->data;
	uint32_t crc;

	crc = crc32(0, env->data, sizeof(env->data));
	if (env->crc != crc) {
		errx(EXIT_FAILURE, "bad uenv crc (expected %#x got %#x)",
		    crc, env->crc);
	}

	while (*p) {
		if (key == NULL ||
		    (strstr(p, key) == p && p[strlen(key)] == '=')) {
			printf("%s\n", p);
		}
		p += strlen(p) + 1;
	}
}

static void
update_uenv(struct environment *env, const char *key, const char *val)
{
	const char *p = (const char *)env->data;
	struct environment new_env;
	char *new_p;
	bool existing;
	uint32_t crc;

	memset(&new_env, 0, sizeof(new_env));
	new_p = (char *)new_env.data;
	existing = false;

	crc = crc32(0, env->data, sizeof(env->data));
	if (env->crc == crc) {
		while (*p) {
			if (strstr(p, key) == p && p[strlen(key)] == '=') {
				strcpy(new_p, key);
				strcat(new_p, "=");
				strcat(new_p, val);
				existing = true;
			} else {
				strcat(new_p, p);
			}
			new_p += strlen(new_p) + 1;
			p += strlen(p) + 1;
		}
	}

	if (!existing) {
		strcpy(new_p, key);
		strcat(new_p, "=");
		strcat(new_p, val);
		new_p += strlen(new_p) + 1;
	}

	new_env.crc = crc32(0, new_env.data, sizeof(new_env.data));
	*env = new_env;
}

int
main(int argc, char *argv[])
{
	struct environment *env;
	const char *key = NULL, *val = NULL;
	char path[MAXPATHLEN];
	int fd, flags;

	if (argc < 2 || argc > 4) {
		usage(argv[0]);
		/* NOTREACHED */
	}
	if (argc > 2) {
		key = argv[2];
	}
	if (argc > 3) {
		val = argv[3];
	}

	flags = val ? O_RDWR : O_RDONLY;

	fd = opendisk(argv[1], flags, path, sizeof(path), 1);
	if (fd == -1)
		err(EXIT_FAILURE, "couldn't open %s", argv[1]);

	env = open_uenv(fd, flags);

	if (val) {
		update_uenv(env, key, val);
	} else {
		print_uenv(env, key);
	}

	close_uenv(env);

	close(fd);

	return EXIT_SUCCESS;
}