/*
libmawk changes (C) 2009-2014, Tibor 'Igor2' Palinkas;

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
*/

/*

ENVIRON[] implementation
~~~~~~~~~~~~~~~~~~~~~~~~
Although POSIX doesn't require this, in libmawk ENVIRON[] should affect
any exec'd child process (getline and system()). Since there may be multiple
mawk_state_t instances, ENVIRON[] can not directly manipulate the global
char **environ. Instead each context has its own array.

Unlike in the original code, the ENVIRON[] array is not initialized
before the first runtime reference to it. This saves some memory and
some time, but more importantly it makes libmawk able to run in
situations where there's no standard environment (e.g. the Linux kernel).

*/

#include <stdio.h>
#include <string.h>
#include "mawk.h"
#include "symtype.h"
#include "memory.h"
#include "field.h"
#include "bi_vars.h"
#include "num.h"
#include "array_orig.h"
#include "array_generic.h"
#include "cell.h"

/* bits of array state */
enum {
	ST_LOADED = 1,
	ST_MODIFIED = 2
};

/* copy the process' environ to ENVIRON */
static void mawk_load_environ(mawk_state_t *MAWK, mawk_array_t ENV)
{
	mawk_cell_t ci, cv;
	extern char **environ;
	register char **p = environ;  /* walks environ */
	char *s;                      /* looks for the '=' */

	ci.type = C_STRING;
	cv.type = C_MBSTRN;

	while (*p) {
		if ((s = strchr(*p, '='))) {	/* shouldn't fail */
			int len = s - *p;
			ci.ptr = (PTR) mawk_new_STRING0(MAWK, len);
			memcpy(string(&ci)->str, *p, len);
			s++;

			cv.ptr = (PTR) mawk_new_STRING(MAWK, s);
			mawk_array_orig_imp.set(MAWK, ENV, &ci, &cv);

			free_STRING(string(&ci));
		}
		p++;
	}
}

static void chkenv(mawk_state_t *MAWK, mawk_array_t A)
{
	if ((A->state.i & ST_LOADED) == 0) {
		A->state.i |= ST_LOADED;
		mawk_load_environ(MAWK, A);
	}
}


int mawk_array_find_environ(mawk_state_t *MAWK, mawk_array_t arr, const mawk_cell_t *idx, mawk_cell_t *result, int create)
{
	chkenv(MAWK, arr);
	if ((create) && ((arr->state.i & ST_MODIFIED) == 0)) {
		/* we are allowed to create the item in a so-far-unmodofied ENVIRON[]:
		   check if the item exists and mark the array modified only if it
		   really would be created - this keeps the array un-modified after
		   read-only operations
		   NOTE: maybe idx == result, so we don't want result to be destroyed
		   in this call, thus result is NULL.
		*/
		if (mawk_array_orig_imp.find(MAWK, arr, idx, NULL, 0) < 1)
			arr->state.i |= ST_MODIFIED;
	}
	return mawk_array_orig_imp.find(MAWK, arr, idx, result, create);
}


void mawk_array_set_environ(mawk_state_t *MAWK, mawk_array_t arr, const mawk_cell_t *idx, mawk_cell_t *val)
{
	chkenv(MAWK, arr);
	arr->state.i |= ST_MODIFIED;
	mawk_array_orig_imp.set(MAWK, arr, idx, val);
}


static void mawk_array_delete_environ(mawk_state_t *MAWK, mawk_array_t arr, const mawk_cell_t *idx)
{
	chkenv(MAWK, arr);
	arr->state.i |= ST_MODIFIED;
	mawk_array_orig_imp.delet(MAWK, arr, idx);
}

static mawk_string_t **mawk_array_loop_vector_environ(mawk_state_t *MAWK, mawk_array_t A, unsigned *size)
{
	chkenv(MAWK, A);
	/* could use the _generic() as well, but that'd be a tiny bit slower and
	   there's no side effect other than chkenv() */
/*	return mawk_array_loop_vector_generic(MAWK, A, size);*/
	return mawk_array_orig_imp.loop_vect(MAWK, A, size);
}

/* mark array initialized before clearning, so if a delete ENVIRON
   precedes any other call, the environment is not loaded at all. */
void mawk_array_clear_environ(mawk_state_t *MAWK, mawk_array_t arr)
{
	arr->state.i |= ST_LOADED | ST_MODIFIED;
	mawk_array_clear_generic(MAWK, arr);
}

array_imp_t mawk_array_environ_imp = {
	mawk_array_find_environ,
	mawk_array_set_environ,
	mawk_array_delete_environ,

	mawk_array_clear_environ,
	mawk_array_loop_vector_environ,
	mawk_array_load_generic,

	mawk_array_it_start_orig,
	mawk_array_it_next_orig,
	mawk_array_it_stop_orig
};


void mawk_environ_init(mawk_state_t *MAWK)
{
	SYMTAB *s;
	s = mawk_insert(MAWK, "ENVIRON");
	s->type = ST_ARRAY;
	s->stval.array = mawk_array_new(MAWK, &mawk_array_environ_imp);
}

#ifdef MAWK_MEM_PEDANTIC
void mawk_environ_uninit(mawk_state_t *MAWK)
{
	SYMTAB *s;
	s = mawk_find(MAWK, "ENVIRON", 0);
	if (s != NULL) {
		if (s->type == ST_ARRAY)
			mawk_array_destroy(MAWK, s->stval.array);
		mawk_delete(MAWK, "ENVIRON", 0);
	}
}
#endif

extern char **environ;
#define grow() \
do { \
	if (used >= alloced) { \
		alloced += 128; \
		ret = mawk_realloc(MAWK, ret, sizeof(char *) * alloced); \
	} \
} while(0)

char **mawk_environ_extract(mawk_state_t *MAWK)
{
	void *it;
	const mawk_cell_t *idx;
	char **ret = NULL;
	unsigned used = 0, alloced = 0;
	mawk_array_t arr;
	SYMTAB *s;

	s = mawk_find(MAWK, "ENVIRON", 0);
	/* ENVIRON[] is not set up - use the global environ[] */
	if (s == NULL)
		return environ;

	arr = s->stval.array;

	/* ENVIRON[] is not loaded - use the global environ[] */
	if ((arr->state.i & ST_LOADED) == 0)
		return environ;

	/* ENVIRON[] is not modified - use the global environ[], cheaper than to copy */
	if ((arr->state.i & ST_MODIFIED) == 0)
		return environ;


	it = arr->imp.it_start(MAWK, arr);
	for(;;) {
		char *si, *sv;
		mawk_cell_t cv;
		int sil, svl;

		idx = arr->imp.it_next(MAWK, arr, it);
		if (idx == NULL)
			break;
		si = string(idx)->str;
		if (mawk_array_find_environ(MAWK, arr, idx, &cv, 0) <= 0)
			sv = "";
		else
			sv = string(&cv)->str;

		grow();
		sil = strlen(si);
		svl = strlen(sv);
		ret[used] = mawk_malloc(MAWK, sil+svl+2);
		memcpy(ret[used], si, sil);
		ret[used][sil] = '=';
		memcpy(ret[used]+sil+1, sv, svl+1);
		used++;
	}

	grow();
	ret[used] = NULL;
	used++;

	arr->imp.it_stop(MAWK, arr, it);
	return ret;
}

