/*
 * zipl - zSeries Initial Program Loader tool
 *
 * Functions to manipulate with environment block
 *
 * Copyright IBM Corp. 2020
 *
 * s390-tools is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "lib/util_libc.h"
#include "bootmap.h"
#include "envblk.h"
#include "error.h"
#include "misc.h"
#include <assert.h>

/* from linux/fs.h */
#define FIGETBSZ	_IO(0x00, 2)
#define KEYWORD_SIZE    strlen("[site X]\n")
#define MAX_NR_SITES    10
#define LINE_HASH_SIZE  16
#define PARAM_SIZE      8

struct iter_info {
	struct line_hash_entry **table;
	const char *site_id;
	char *envblk;
	int envblk_len;
	int indent;
};

void envblk_blank(char *envblk, int envblk_len)
{
	memset(envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1, '\0',
	       envblk_len - sizeof(ZIPL_ENVBLK_SIGNATURE) + 1);
}

void envblk_create_blank(char *envblk, int envblk_len)
{
	memcpy(envblk, ZIPL_ENVBLK_SIGNATURE,
	       sizeof(ZIPL_ENVBLK_SIGNATURE) - 1);
	envblk_blank(envblk, envblk_len);
}

/**
 * Find out environment block location.
 * MFD: bootmap file descriptor.
 * On success the searched location is saved in variable pointed out by OFF
 */
int envblk_offset_get(struct misc_fd *mfd, off_t *off)
{
	struct bootmap_header bh;

	if (bootmap_header_read(mfd, &bh)) {
		error_reason("Could not read bootmap_header");
		return -1;
	}
	*off = bh.envblk_offset;
	return 0;
}

/**
 * Save environment block location specified by OFF
 * MFD: bootmap file descriptor.
 * On success the location is saved in the bootmap header.
 */
int envblk_offset_set(struct misc_fd *mfd, off_t off)
{
	struct bootmap_header bh;

	if (bootmap_header_read(mfd, &bh)) {
		error_reason("Could not read bootmap header");
		return -1;
	}
	bh.envblk_offset = off;

	if (bootmap_header_write(mfd, &bh)) {
		error_reason("Could not write bootmap header");
		return -1;
	}
	return 0;
}

/**
 * Find out environment block size.
 * MFD: bootmap file descriptor.
 * On success the searched size is stored in RESULT
 */
int envblk_size_get(struct misc_fd *mfd, int *result)
{
	if (ioctl(mfd->fd, FIGETBSZ, result) == -1) {
		error_reason(strerror(errno));
		return -1;
	}
	return 0;
}

/*
 * This defines "extended string", an object consisting of
 * an optional logical prefix and a substring
 */
struct estr {
	char *prefix;    /* pointer to the logical prefix */
	char *str;       /* pointer to the substring */
	int hashed_len;  /* number of bytes in the substring,
			  * participating in hash calculation
			  */
};

/**
 * Hash function on "extended strings"
 */
static unsigned int hash_fn(struct estr *estr)
{
	int len = estr->hashed_len;
	char *str = estr->str;
	unsigned int sum;

	sum = 0;
	if (estr->prefix)
		sum += 5 * *estr->prefix;
	while (len) {
		sum += 5 * *(str++);
		len--;
	}
	return sum % LINE_HASH_SIZE;
}

/**
 * Compare a string STR with an "extended string" ESTR
 */
static int cmp_fn(char *str, struct estr *estr)
{
	if (!estr->prefix)
		return strncmp(str, estr->str, estr->hashed_len);
	if (str[0] != *estr->prefix)
		return -1;
	return strncmp(str + 1, estr->str, estr->hashed_len);
}

struct line_hash_entry {
	struct line_hash_entry *next;
	char *line; /* pointer to a string containing a pair 'KEY=VALUE'
		     * with an optional prefix and an optional trailing
		     * new line character
		     */
};

/**
 * Populate a hash TABLE with an item built by an "extended string" ESTR
 */
static int hash_table_add(struct line_hash_entry **table, struct estr *estr)
{
	struct line_hash_entry *new;
	unsigned int hash;
	int len;

	new = malloc(sizeof(*new));
	if (!new)
		return -ENOMEM;
	memset(new, 0, sizeof(*new));

	len = strlen(estr->str);
	if (estr->prefix) {
		new->line = misc_malloc(len + 2);
		if (!new->line) {
			free(new);
			return -ENOMEM;
		}
		new->line[0] = *estr->prefix;
		memcpy(new->line + 1, estr->str, len);
		new->line[len + 1] = '\0';
	} else {
		new->line = misc_strdup(estr->str);
		if (!new->line) {
			free(new);
			return -ENOMEM;
		}
	}
	hash = hash_fn(estr);
	new->next = table[hash];
	table[hash] = new;
	return 0;
}

/**
 * Search an item in the hash table by an "extended string" ESTR
 */
static struct line_hash_entry *hash_table_find(struct line_hash_entry **table,
					       struct estr *estr)
{
	struct line_hash_entry *item;

	if (!table)
		return NULL;
	item = table[hash_fn(estr)];
	while (item) {
		if (!cmp_fn(item->line, estr))
			return item;
		item = item->next;
	}
	return NULL;
}

/**
 * Replace an ITEM with another one built by an "extended string" ESTR
 */
static int hash_entry_replace(struct line_hash_entry *item, struct estr *estr,
			      int *offset)
{
	int len;
	char *new;

	len = strlen(estr->str);
	if (estr->prefix) {
		len++;
		new = misc_malloc(len + 1);
		if (!new)
			return -ENOMEM;
		new[0] = *estr->prefix;
		memcpy(new + 1, estr->str, len - 1);
		new[len] = '\0';
	} else {
		new = misc_strdup(estr->str);
		if (!new)
			return -ENOMEM;
	}
	*offset -= strlen(item->line);
	free(item->line);
	item->line = new;
	*offset += len;
	return 1;
}

/**
 * Scan hash table TABLE, and call ACTOR for each its entry.
 * The ACTOR is allowed even to release the entry.
 */
static int hash_table_scan(struct line_hash_entry **table,
			   int (*actor)(struct line_hash_entry *entry,
					void *data), void *data)
{
	int ret;
	int i;

	if (!table)
		return 0;
	for (i = 0; i < LINE_HASH_SIZE; i++) {
		struct line_hash_entry *item = table[i];

		while (item) {
			struct line_hash_entry *next = item->next;

			ret = actor(item, data);
			if (ret)
				return ret;
			item = next;
		}
	}
	return 0;
}

static int hash_entry_free(struct line_hash_entry *this,
			   __attribute__ ((unused))void *data)
{
	free(this->line);
	free(this);
	return 0;
}

static void hash_table_free(struct line_hash_entry **table)
{
	hash_table_scan(table, hash_entry_free, NULL);
}

/**
 * Scan hash table TABLE, and for each its entry copy its line to the
 * in-memory environment block specified by DST at the offset OFF.
 * Insert a new line character, if needed. The caller should guarantee
 * enough space in the buffer DST
 */
static void hash_table_flush(struct line_hash_entry **table, char *dst,
			     int off)
{
	int i;

	for (i = 0; i < LINE_HASH_SIZE; i++) {
		struct line_hash_entry *item = table[i];

		while (item) {
			int str_len;

			str_len = strlen(item->line);
			memcpy(dst + off, item->line, str_len);
			off += str_len;
			if (dst[off - 1] != '\n') {
				dst[off] = '\n';
				off += 1;
			}
			item = item->next;
		}
	}
}

enum keyword_status {
	KW_UNKNOWN,
	KW_VALID,
	KW_MALFORMED,
	KW_UNSUPP_ID
};

/**
 * Return 1, if the line's TAIL is empty, or is a whitespace
 * (may be empty) followed by a comment. Otherwise, return 0.
 */
static int ignore_tail(const char *tail)
{
	if (*tail == '\n')
		return 1;
	do {
		if (*tail == '#')
			return 1;
		if (*tail == '\t' || *tail == ' ') {
			tail++;
			continue;
		}
		return 0;
	} while (*tail != '\n');
	return 0;
}

/**
 * Check if LINE represents a keyword to identify a section in the
 * environment file. Return the result of the check. If the LINE
 * represents a valid keyword (KW_VALID is returned), then SITE_ID
 * contains ID of the namespace represented by that section.
 *
 * LINE: a null-terminated string with optional new line character
 * LEN: number of characters in the LINE excluding NULL-termination
 */
static enum keyword_status check_keyword(char *line, size_t len, char **site_id)
{
	char *endptr;
	long val;

	if (len < KEYWORD_SIZE - 1 || line[len - 1] != '\n')
		return KW_UNKNOWN;

	if ((line[0] == '[') &&
	    (line[1] == 's' || line[1] == 'S') &&
	    (line[2] == 'i' || line[2] == 'I') &&
	    (line[3] == 't' || line[3] == 'T') &&
	    (line[4] == 'e' || line[4] == 'E')) {
		if (line[5] != ' ')
			return KW_MALFORMED;

		*site_id = &line[6];
		val = strtol(&line[6], &endptr, 10);
		if (endptr == &line[6])
			/* not a number */
			return KW_MALFORMED;
		if (val < 0) {
			/* negative number */
			*endptr = '\0';
			return KW_UNSUPP_ID;
		}
		if (*endptr == ']') {
			if (line[6] == '0' && endptr - &line[6] > 1) {
				/* leading zeros are not allowed */
				*endptr = '\0';
				return KW_UNSUPP_ID;
			}
			if (!ignore_tail(endptr + 1))
				/* unacceptable trailing characters */
				return KW_MALFORMED;
			*endptr = '\0';
			return val < MAX_NR_SITES ? KW_VALID : KW_UNSUPP_ID;
		}
		return KW_MALFORMED;
	}
	return KW_UNKNOWN;
}

/**
 * Check if @name meets the requirements for shell environment variables
 * (IEEE Std 1003.1-2001), mitigated with lowercase letter allowance
 *
 * Return 0, if it meets, and 1 otherwise
 */
int envblk_check_name(const char *name, int len)
{
	int i;

	if (len == 0)
		return 1;
	if (!isalpha(name[0]) && name[0] != '_')
		return 1;
	for (i = 1; i < len; i++)
		if (!isalpha(name[i]) && !isdigit(name[i]) && name[i] != '_')
			return 1;
	return 0;
}

/**
 * Acceps a string STR, containing a pair 'KEY=VALUE'.
 * Returns length of the KEY.
 */
static int get_key_len(const char *str)
{
	return strchr(str, '=') - str;
}

/**
 * Acceps a string STR, containing a pair 'KEY=VALUE'.
 * Returns number of characters in the string, participating in hash
 * calculation.
 */
static int get_hashed_len(const char *str)
{
	/* hash is calculated by the key with a trailing '=' */
	return get_key_len(str) + 1;
}

/**
 * Put a line containing a pair 'NAME=VALUE' to a namespace referenced
 * by NS_ID
 *
 * If the line was successfully imported without replacement a line with
 * the same NAME, that was added before, then return 0.
 * If the line should be ignored, or it replaced previously added line with
 * the same NAME, then return > 0. On errors return < 0.
 *
 * THIS: pointer to a NULL-terminated string, which contains the line to
 * be imported.
 * NS_ID: Pointer to a numerical character, indicating namespace ID
 */
static int import_one_line(char *this, struct line_hash_entry **table,
			   int *offset, char *ns_id)
{
	struct line_hash_entry *item;
	struct estr estr;
	char *p;

	if (*this == '#')
		/* Ignore "commented" line */
		return 1;

	p = strchr(this, '=');
	if (p == NULL || p == this)
		/* Could not identify NAME in the line. Ignore */
		return 1;
	if (envblk_check_name(this, p - this)) {
		error_reason("Unacceptable name '%.*s'", p - this, this);
		return -EINVAL;
	}
	estr.prefix = ns_id;
	estr.str = this;
	estr.hashed_len = get_hashed_len(this);

	item = hash_table_find(table, &estr);
	return item != NULL ? hash_entry_replace(item, &estr, offset) :
		hash_table_add(table, &estr);
}

/**
 * Import environment line-by-line from a regular file specified
 * by its name FROM_FILE to a pre-allocated buffer TO_BUF. Ignore
 * malformed, or "commented" lines. If the file doesn't end with
 * new line character, then append that character in the destination
 * buffer. Return 0 on success.
 *
 * ENVBLK_SIZE: size of the destination buffer TO_BUF.
 */
int envblk_import(char *from_file, char *to_buf, int envblk_size)
{
	const char *err_prefix = "Failed to import environment file";
	struct line_hash_entry *table[LINE_HASH_SIZE];
	unsigned int lines_imported = 0;
	char *line = NULL;
	char site_id = 0;
	char *site_idp;
	size_t len = 0;
	FILE *stream;
	ssize_t read;
	int ret = 0;
	int offset;

	stream = fopen(from_file, "r");
	if (stream == NULL)
		return 0;

	memset(table, 0, sizeof(table));
	offset = sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;

	while ((read = getline(&line, &len, stream)) != -1) {
		if (read > envblk_size - offset) {
			error_reason("%s %s: maximal size (%d) exceeded",
				     err_prefix, from_file,
				     ENVBLK_MAX_IMPORT_SIZE(envblk_size));
			ret = -EINVAL;
			break;
		}
		if (read < 2)
			/* malformed line. Ignored */
			continue;

		switch (check_keyword(line, read, &site_idp)) {
		case KW_VALID:
			site_id = *site_idp;
			continue;
		case KW_MALFORMED:
			/*
			 * any line not ended with a new line character
			 * has to be evaluated as KW_UNKNOWN
			 */
			assert(line[read - 1] == '\n');
			line[read - 1] = '\0';
			error_reason("%s %s: bad section title '%s'",
				     err_prefix, from_file, line);
			ret = -EINVAL;
			goto error;
		case KW_UNSUPP_ID:
			error_reason("%s %s: unsupported site ID '%s'",
				     err_prefix, from_file, site_idp);
			ret = -EINVAL;
			goto error;
		case KW_UNKNOWN:
			break;
		}
		/* LINE is not a secton title */

		if (line[read - 1] != '\n' && read == envblk_size - offset) {
			/*
			 * file's tail doesn't contain a new line charactere at
			 * the end, whereas there is no enough space in the
			 * destination buffer to insert that character, which is
			 * mandatory according to the environment block format
			 */
			error_reason("%s %s: maximal size (%d) exceeded",
				     err_prefix, from_file,
				     ENVBLK_MAX_IMPORT_SIZE(envblk_size));
			ret = -EINVAL;
			break;
		}
		ret = import_one_line(line, table, &offset,
				      site_id ? &site_id : NULL);
		if (ret < 0) {
			/* error */
			error_text("%s %s", err_prefix, from_file);
			break;
		} else if (ret > 0) {
			/*
			 * either this line was ignored, or previously
			 * imported line was overwritten by it
			 */
			ret = 0;
			continue;
		}
		if (lines_imported >= ENVBLK_MAX_LINES) {
			error_reason("%s %s: maximum number of lines (%d) exceeded",
				     err_prefix, from_file, ENVBLK_MAX_LINES);
			ret = -EINVAL;
			break;
		}
		offset += read;
		lines_imported++;
	}
	hash_table_flush(table, to_buf, sizeof(ZIPL_ENVBLK_SIGNATURE) - 1);
error:
	hash_table_free(table);
	free(line);
	fclose(stream);
	return ret;
}

char *envblk_next_line(char *s, const char *end)
{
	while (s < end) {
		if (*s == '\n')
			break;
		s++;
	}
	return s + 1;
}

/**
 * Scan installed environment block and call ACTOR for each found NAME.
 * Return -1 on corrupted environment blocks. Otherwise, return 0
 */
int envblk_scan(char *envblk, unsigned int envblk_size,
		int (*actor)(char *name, void *data), void *data)
{
	unsigned int lines_scanned = 0;
	const char *reason;
	char *s, *end;
	char *name;
	int ret;

	s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;
	end = envblk + envblk_size;

	while (s < end && *s != 0) {
		if (lines_scanned > ENVBLK_MAX_LINES) {
			reason = "maximum number of lines exceeded";
			goto corrupted;
		}
		name = s;
		while (s < end && *s != '=')
			s++;
		if (s == end) {
			/* delimiter "=" not found */
			reason = "missed delimiter";
			goto corrupted;
		}
		/* here @s points to "=" */
		s++;

		while (s < end && *s != '\n')
			s++;
		if (s == end) {
			/* end of line not found */
			reason = "missed EOL";
			goto corrupted;
		}
		*s = '\0';
		ret = actor(name, data);
		*s = '\n';
		if (ret)
			return ret;
		lines_scanned++;
		s = envblk_next_line(s, end);
	}
	return 0;
corrupted:
	error_reason("Found corrupted environment block (%s) - please run zipl",
		     reason);
	return -1;
}

static const char *indent_by_size(int size)
{
	switch (size) {
	case 1:
		return "  ";
	case 2:
		return "    ";
	default:
		return "";
	}
}

/**
 * Put a line containing a pair 'NAME=VALUE' to a respective namespace.
 *
 * THIS: line containing a pair 'NAME=VALUE', optionally prefixed
 * with one numerical character indicating namespace ID
 */
static int add_line_namespace(char *this, void *data)
{
	struct line_hash_entry ***tables = data;
	struct estr estr;
	char *endptr;
	int val;

	val = strtol(this, &endptr, 10);
	if (endptr == this)
		val = MAX_NR_SITES;
	if (!tables[val]) {
		tables[val] =
			util_zalloc(LINE_HASH_SIZE * sizeof(*tables));
		if (!tables[val])
			return -ENOMEM;
	}
	estr.prefix = NULL;
	estr.str = endptr;
	estr.hashed_len = get_hashed_len(endptr);

	return hash_table_add(tables[val], &estr);
}

static int hash_table_print_line(struct line_hash_entry *item, void *data)
{
	struct iter_info *ii = data;

	printf("%s%s\n", indent_by_size(ii->indent), item->line);
	return 0;
}

static int hash_table_print_line_cond(struct line_hash_entry *item, void *data)
{
	struct iter_info *ii = data;
	struct estr estr;

	estr.prefix = NULL;
	estr.str = item->line;
	estr.hashed_len = get_hashed_len(item->line);

	if (!hash_table_find(ii->table, &estr))
		hash_table_print_line(item, data);
	return 0;
}

static int print_namespace(struct line_hash_entry **table, const char *title,
			   int indent)
{
	struct iter_info ii;

	if (!table)
		return 0;
	printf("%s%s", indent_by_size(indent), title);
	ii.table = table;
	ii.indent = indent + 1;
	hash_table_scan(table, hash_table_print_line, &ii);
	return 0;
}

/**
 * Dump content of the environment block in human-readable form,
 * sorting it by "sections"
 */
int envblk_list_all(char *envblk, unsigned int envblk_size, int indent)
{
	struct line_hash_entry **tables[MAX_NR_SITES + 1];
	int nr_sites = 0;
	int ret = 0;
	int i;

	memset(tables, 0, sizeof(tables));
	/*
	 * Scan environment block and sort the pairs 'NAME=VALUE'
	 * by namespaces
	 */
	ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
	if (ret)
		goto out;
	for (i = 0; i < MAX_NR_SITES; i++) {
		if (tables[i])
			nr_sites++;
	}
	/* print common namespace */
	print_namespace(tables[MAX_NR_SITES],
			nr_sites ? "Common variables:\n" : "",
			nr_sites ? indent : indent - 1);
	/* print site-specific namespaces */
	for (i = 0; i < MAX_NR_SITES; i++) {
		char title[10];

		sprintf(title, "Site %d:\n", i);
		print_namespace(tables[i], title, indent);
	}
out:
	for (i = 0; i <= MAX_NR_SITES; i++) {
		hash_table_free(tables[i]);
		free(tables[i]);
	}
	return ret;
}

/**
 * Print environment determined by effective SITE_ID
 */
int envblk_list_effective_site(char *envblk, unsigned int envblk_size, int site_id)
{
	struct line_hash_entry **tables[MAX_NR_SITES + 1];
	struct iter_info ii;
	int ret = 0;
	int i;

	memset(tables, 0, sizeof(tables));
	/*
	 * Scan environment block and sort out the pairs 'NAME=VALUE'
	 * by namespaces
	 */
	ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
	if (ret)
		goto out;
	/*
	 * print all variables of the common namespace, which are not
	 * defined in the SITE_ID namespace
	 */
	ii.table = tables[site_id];
	ii.indent = 0;
	hash_table_scan(tables[MAX_NR_SITES], hash_table_print_line_cond,
			&ii);
	/*
	 * print all variables of the SITE_ID namespace
	 */
	ii.table = NULL;
	ii.indent = 0;
	hash_table_scan(tables[site_id], hash_table_print_line, &ii);
out:
	for (i = 0; i <= MAX_NR_SITES; i++) {
		hash_table_free(tables[i]);
		free(tables[i]);
	}
	return ret;
}

/**
 * List all variables defined by the namespace with SITE_ID
 */
int envblk_list_site(char *envblk, unsigned int envblk_size, int site_id)
{
	struct line_hash_entry **tables[MAX_NR_SITES + 1];
	struct iter_info ii;
	int ret = 0;
	int i;

	memset(tables, 0, sizeof(tables));
	/*
	 * Scan environment block and sort out the pairs 'NAME=VALUE'
	 * by namespaces
	 */
	ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
	if (ret)
		goto out;
	ii.table = NULL;
	ii.indent = 0;
	hash_table_scan(tables[site_id], hash_table_print_line, &ii);
out:
	for (i = 0; i <= MAX_NR_SITES; i++) {
		hash_table_free(tables[i]);
		free(tables[i]);
	}
	return ret;
}

void envblk_print(char *envblk, unsigned int envblk_size)
{
	printf("zIPL environment block content:\n");
	envblk_list_all(envblk, envblk_size, 1 /* indent */);
}

int envblk_set(char *envblk, unsigned int envblk_size, const char *name,
	       const char *new_val)
{
	unsigned int name_len, new_val_len;
	char *fss; /* free space start */
	unsigned int lines_scanned = 0;
	int name_found = 0;
	char *s, *end;

	name_len = strlen(name);
	new_val_len = strlen(new_val);

	s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;
	end = envblk + envblk_size;

	/*
	 * find the start of free space
	 */
	for (fss = end - 1; *fss == '\0'; fss--)
		;
	if (*fss != '\n') {
		error_reason("Found corrupted environment block - please run zipl");
		return -1;
	}
	fss++;

	while (fss - s > name_len) {
		if (lines_scanned >= ENVBLK_MAX_LINES) {
			error_reason("Found corrupted environment block - please run zipl");
			return -1;
		}
		if (memcmp(s, name, name_len) == 0 && s[name_len] == '=') {
			unsigned int cur_val_len;
			/*
			 * such name exists, replace its current value
			 */
			s += (name_len + 1);

			cur_val_len = 0;
			while (s + cur_val_len < end && s[cur_val_len] != '\n')
				cur_val_len++;
			if (s + cur_val_len >= end) {
				error_reason("Found corrupted environment block - please run zipl");
				return -1;
			}
			if (new_val_len > cur_val_len &&
			    end - fss < new_val_len - cur_val_len) {
				error_reason("Not enough space for new value");
				return -1;
			}
			/*
			 * make a precise-sized room for the new value
			 */
			if (new_val_len < cur_val_len) {
				memmove(s + new_val_len, s + cur_val_len,
					end - (s + cur_val_len));

				memset(fss + cur_val_len - new_val_len, '\0',
				       cur_val_len - new_val_len);
			} else {
				memmove(s + new_val_len, s + cur_val_len,
					end - (s + new_val_len));
			}
			name_found = 1;
			break;
		}
		lines_scanned++;
		s = envblk_next_line(s, end);
	}
	assert(lines_scanned <= ENVBLK_MAX_LINES);

	if (!name_found) {
		if (lines_scanned == ENVBLK_MAX_LINES) {
			error_reason("Maximum number of lines reached");
			return -1;
		}
		/*
		 * append a new variable
		 */
		if (end - fss < name_len + new_val_len + 2) {
			error_reason("Not enough space in environment block");
			return -1;
		}
		memcpy(fss, name, name_len);
		s = fss + name_len;
		*s++ = '=';
	}
	/*
	 * copy the new value and terminate it with a new line character
	 */
	memcpy(s, new_val, new_val_len);
	s[new_val_len] = '\n';
	return 0;
}

int envblk_unset(char *envblk, int envblk_len,
		 const char *pname, const char *site_id)
{
	unsigned int pname_len;
	char *s, *end;

	pname_len = strlen(pname);
	s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1;
	end = envblk + envblk_len;

	while (end - s >= pname_len + 2 /* minimal length of
					 * pattern "name=foo"
					 */) {
		if (memcmp(s, pname, pname_len) == 0 && s[pname_len] == '=') {
			/*
			 * prefixed name was found. Locate the whole
			 * "named" line and cut it including
			 * the trailing "\n"
			 */
			unsigned int cut_len = pname_len + 1;

			while (s + cut_len < end) {
				if (s[cut_len] == '\n')
					break;
				cut_len++;
			}
			if (s + cut_len >= end) {
				/*
				 * trailing "\n" not found
				 */
				error_reason("Found corrupted environment block - please run zipl");
				return -1;
			}
			cut_len++;
			memmove(s, s + cut_len, end - (s + cut_len));
			memset(end - cut_len, '\0', cut_len);
			return 0;
		}
		s = envblk_next_line(s, end);
	}
	error_reason("Name '%s' not found in %s%s namespace",
		     get_name(site_id, pname),
		     site_id ? "site " : "common",
		     site_id ?: "");
	return -1;
}

static int remove_name_value(struct line_hash_entry *item, void *data)
{
	struct iter_info *ii = data;
	char *pname;
	int len;
	int ret;

	/*
	 * construct a prefixed null-terminated name by a line 'KEY=VALUE'
	 */
	len = get_key_len(item->line);
	pname = misc_malloc(len + 2);
	if (!pname) {
		error_reason(strerror(errno));
		return -ENOMEM;
	}
	pname[0] = *ii->site_id;
	memcpy(pname + 1, item->line, len);
	pname[len + 1] = '\0';

	ret = envblk_unset(ii->envblk,
			   strnlen(ii->envblk, ii->envblk_len),
			   pname, ii->site_id);
	free(pname);
	return ret;
}

/**
 * Remove all lines prefixed with SITE_ID from environment block
 */
int envblk_remove_namespace(char *envblk, unsigned int envblk_size,
			    const char *site_id)
{
	struct line_hash_entry **tables[MAX_NR_SITES + 1];
	struct iter_info ii;
	int ret = 0;
	int i;

	memset(tables, 0, sizeof(tables));
	/*
	 * Scan environment block and populate hash tables
	 * with the pairs 'NAME=VALUE'
	 */
	ret = envblk_scan(envblk, envblk_size, add_line_namespace, tables);
	if (ret)
		goto out;

	ii.table = tables[atoi(site_id)];
	ii.envblk_len = envblk_size;
	ii.envblk = envblk;
	ii.site_id = site_id;
	ii.indent = 0;

	ret = hash_table_scan(tables[atoi(site_id)], remove_name_value, &ii);
out:
	for (i = 0; i <= MAX_NR_SITES; i++) {
		hash_table_free(tables[i]);
		free(tables[i]);
	}
	return ret;
}
