/*
   env-update
   Create /etc/profile.env (sh), /etc/csh.env from /etc/env.d
   Run ldconfig as required

   Copyright 2007 Gentoo Foundation
   Released under the GPLv2

*/

#define APPLET "env-update"

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "builtins.h"
#include "einfo.h"
#include "rc.h"
#include "rc-misc.h"
#include "strlist.h"

#define ENVDIR      "/etc/env.d"
#define PROFILE_ENV "/etc/profile.env"
#define CSH_ENV     "/etc/csh.env"
#define LDSOCONF    "/etc/ld.so.conf"

#define NOTICE      "# THIS FILE IS AUTOMATICALLY GENERATED BY env-update.\n" \
                    "# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES\n" \
                    "# GO INTO %s NOT %s\n\n"

#define LDNOTICE    "# ld.so.conf autogenerated by env-update; make all\n" \
                    "# changes to contents of /etc/env.d directory\n"

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#define LD_MESSAGE	"Regenerating /var/run/ld-elf.so.hints"
#define LD_SYSTEM	"/sbin/ldconfig -elf -i '" LDSOCONF "'"
#else
#define LD_MESSAGE	"Regenerating /etc/ld.so.cache"
#define LD_SYSTEM	"/sbin/ldconfig"
#endif

static const char *colon_separated[] = {
	"ADA_INCLUDE_PATH",
	"ADA_OBJECTS_PATH",
	"CLASSPATH",
	"INFOPATH",
	"KDEDIRS",
	"LDPATH",
	"MANPATH",
	"PATH",
	"PKG_CONFIG_PATH",
	"PRELINK_PATH",
	"PRELINK_PATH_MASK",
	"PYTHONPATH",
	"ROOTPATH",
	NULL
};

static const char *space_separated[] = { 
	"CONFIG_PROTECT",
	"CONFIG_PROTECT_MASK",
	NULL,
};

static char *applet = NULL;

#include "_usage.h"
#define getoptstring "lL" getoptstring_COMMON
static struct option longopts[] = {
	{ "fork-ldconfig",  0, NULL, 'l'},
	{ "no-ldconfig",    0, NULL, 'L'},
	longopts_COMMON
	{ NULL,             0, NULL, 0}
};
#include "_usage.c"

int env_update (int argc, char **argv)
{
	char **files = rc_ls_dir (NULL, ENVDIR, 0);
	char *file;
	char **envs = NULL;
	char *env;
	int i = 0;
	int j;
	FILE *fp;
	bool ld = true;
	char *ldent;
	char **ldents = NULL;
	char **config = NULL;
	char *entry;
	char **mycolons = NULL;
	char **myspaces = NULL;
	int opt;
	bool ldconfig = true;
	bool fork_ldconfig = false;
	
	applet = argv[0];

	while ((opt = getopt_long (argc, argv, getoptstring,
							   longopts, (int *) 0)) != -1)
	{
		switch (opt) {
			case 'l':
				fork_ldconfig = true;
				break;
			case 'L':
				ldconfig = false;
				break;

			case_RC_COMMON_GETOPT
		}
	}

	if (! files)
		eerrorx ("%s: no files in " ENVDIR " to process", applet);

	STRLIST_FOREACH (files, file, i) {
		char *path = rc_strcatpaths (ENVDIR, file, (char *) NULL);
		char **entries = NULL;

		j = strlen (file);
		if (! rc_is_dir (path) &&
			j > 2 &&
			*file >= '0' &&
			*file <= '9' &&
			*(file + 1) >= '0' &&
			*(file + 1) <= '9' &&
			*(file + j - 1) != '~' &&
			(j < 4 || strcmp (file + j - 4, ".bak") != 0) &&
			(j < 5 || strcmp (file + j - 5, ".core") != 0))
			entries = rc_get_config (NULL, path);
		free (path);

		STRLIST_FOREACH (entries, entry, j) {
			char *tmpent = rc_xstrdup (entry);
			char *value = tmpent;
			char *var = strsep (&value, "=");

			if (strcmp (var, "COLON_SEPARATED") == 0)
				while ((var = strsep (&value, " ")))
					mycolons = rc_strlist_addu (mycolons, var);
			else if (strcmp (var, "SPACE_SEPARATED") == 0)
				while ((var = strsep (&value, " ")))
					myspaces = rc_strlist_addu (myspaces, var);
			else
				config = rc_strlist_add (config, entry);
			free (tmpent);
		}

		rc_strlist_free (entries);
	}

	STRLIST_FOREACH (config, entry, i) {
		char *tmpent = rc_xstrdup (entry);
		char *value = tmpent;
		char *var = strsep (&value, "=");
		char *match;
		bool colon = false;
		bool space = false;
		bool replaced = false;

		for (j = 0; colon_separated[j]; j++)
			if (strcmp (colon_separated[j], var) == 0) {
				colon = true;
				break;
			}

		if (! colon)
			STRLIST_FOREACH  (mycolons, match, j) {
				if (strcmp (match, var) == 0) {
					colon = true;
					break;
				} }

		if (! colon)
			for (j = 0; space_separated[j]; j++)
				if (strcmp (space_separated[j], var) == 0) {
					space = true;
					break;
				}

		if (! colon && ! space)
			STRLIST_FOREACH  (myspaces, match, j)
				if (strcmp (match, var) == 0) {
					space = true;
					break;
				}

		/* Skip blank vars */
		if ((colon || space) &&
			(! value || strlen (value)) == 0)
		{
			free (tmpent);
			continue;
		}

		STRLIST_FOREACH (envs, env, j) {
			char *tmpenv = rc_xstrdup (env);
			char *tmpvalue = tmpenv;
			char *tmpentry = strsep (&tmpvalue, "=");

			if (strcmp (tmpentry, var) == 0) {
				if (colon || space) {
					int len =  strlen (envs[j - 1]) + strlen (entry) + 1;
					envs[j - 1] = rc_xrealloc (envs[j - 1], len);
					snprintf (envs[j - 1] + strlen (envs[j - 1]), len,
							  "%s%s", colon ? ":" : " ", value);
				} else {
					free (envs[j - 1]);
					envs[j - 1] = rc_xstrdup (entry);
				}
				replaced = true;
			}
			free (tmpenv);

			if (replaced)
				break;
		}

		if (! replaced)
			envs = rc_strlist_addsort (envs, entry);

		free (tmpent);
	}
	rc_strlist_free (mycolons);
	rc_strlist_free (myspaces);
	rc_strlist_free (config);
	rc_strlist_free (files);

	if ((fp = fopen (PROFILE_ENV, "w")) == NULL)
		eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno));
	fprintf (fp, NOTICE, "/etc/profile", PROFILE_ENV);

	STRLIST_FOREACH (envs, env, i) {
		char *tmpent = rc_xstrdup (env);
		char *value = tmpent;
		char *var = strsep (&value, "=");
		if (strcmp (var, "LDPATH") != 0) {
			if (*value == '$')
				fprintf (fp, "export %s=%s\n", var, value);
			else
				fprintf (fp, "export %s='%s'\n", var, value);
		}
		free (tmpent);
	}
	fclose (fp);

	if ((fp = fopen (CSH_ENV, "w")) == NULL)
		eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno));
	fprintf (fp, NOTICE, "/etc/csh.cshrc", PROFILE_ENV);

	STRLIST_FOREACH (envs, env, i) {
		char *tmpent = rc_xstrdup (env);
		char *value = tmpent;
		char *var = strsep (&value, "=");
		if (strcmp (var, "LDPATH") != 0) {
			if (*value == '$')
				fprintf (fp, "setenv %s %s\n", var, value);
			else
				fprintf (fp, "setenv %s '%s'\n", var, value);
		}
		free (tmpent);
	}
	fclose (fp);

	ldent = rc_get_config_entry (envs, "LDPATH");

	if (! ldent ||
		(argc > 1 && argv[1] && strcmp (argv[1], "--no-ldconfig") == 0))
	{
		rc_strlist_free (envs);
		return (EXIT_SUCCESS);
	}

	while ((file = strsep (&ldent, ":"))) {
		if (strlen (file) == 0)
			continue;

		ldents = rc_strlist_addu (ldents, file);
	}

	if (ldconfig) {
		/* Update ld.so.conf only if different */
		if (rc_exists (LDSOCONF)) {
			char **lines = rc_get_list (NULL, LDSOCONF);
			char *line;
			int nents = 0;
			ld = false;

			STRLIST_FOREACH (ldents, line, i)
				nents ++;

			STRLIST_FOREACH (lines, line, i)
				if (i > nents || strcmp (line, ldents[i - 1]) != 0)
				{
					ld = true;
					break;
				}
			rc_strlist_free (lines);
			if (i - 1 != nents)
				ld = true;
		}

		if (ld) {
			int retval = 0;
			pid_t pid = 0;

			if ((fp = fopen (LDSOCONF, "w")) == NULL)
				eerrorx ("%s: fopen `%s': %s", applet, LDSOCONF,
						 strerror (errno));
			fprintf (fp, LDNOTICE);
			STRLIST_FOREACH (ldents, ldent, i)
				fprintf (fp, "%s\n", ldent);
			fclose (fp);

			ebegin (LD_MESSAGE);
			if (fork_ldconfig) {
				if ((pid = fork ()) == -1)
					eerror ("%s: failed to fork: %s", applet,
							strerror (errno));
				else if (pid == 0) {
					/* Become a proper daemon for a little bit */
					int fd = open ("/dev/null", O_RDWR);
					setsid ();
					dup2 (fd, fileno (stdin));
					dup2 (fd, fileno (stdout));
					dup2 (fd, fileno (stderr));
				}
			}
		
			if (pid == 0)
				retval = system (LD_SYSTEM);
			eend (retval, NULL);
		}
	}

	rc_strlist_free (ldents);
	rc_strlist_free (envs);
	return(EXIT_SUCCESS);
}