/*******************************************************************************
 * unixoptions.cpp
 *
 * Written by Christoph Hormann <chris_hormann@gmx.de>
 * Based on 3.6 elements by Nicolas Calimet
 *
 * Processing system for options in povray.conf, command line
 * and environment variables.
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/vfe/unix/unixoptions.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#include "unixoptions.h"
#include <fstream>
#include <sys/stat.h>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>

namespace vfePlatform
{
	using std::cerr;
	using std::endl;

	extern bool gShelloutsPermittedFixThis;

	const UnixOptionsProcessor::Option_Info UnixOptionsProcessor::Standard_Options[] =
	{
		// section name, option name, default, has_param, command line parameter, environment variable name, help text
		UnixOptionsProcessor::Option_Info("general", "help", "off", false, "--help|-help|-h|-?", "", "display usage information"),
		UnixOptionsProcessor::Option_Info("general", "temppath", "", true, "", "POV_TEMP_DIR", "directory for temporary files"),
		UnixOptionsProcessor::Option_Info("general", "version", "off", false, "--version|-version|--V", "", "display program version"),
		UnixOptionsProcessor::Option_Info("general", "benchmark", "off", false, "--benchmark|-benchmark", "", "run the standard POV-Ray benchmark"),
		UnixOptionsProcessor::Option_Info("", "", "", false, "", "", "") // has to be last
	};

	// based on 3.6 unix_create_globals()
	UnixOptionsProcessor::UnixOptionsProcessor(vfeSession *session) :
		m_Session(session)
	{
		char* value;
		value = getenv("HOME");
		m_home = value ? value:"";

		// Default values for I/O restrictions: everything is allowed.
		// Any restrictions must come from system or user configuration.
		m_file_io  = IO_UNSET;
		m_shellout = SHL_UNSET;

		// system configuration file
		m_conf    = "";
		m_sysconf = POVCONFDIR "/povray.conf";

		// user configuration file
		if (m_home.length() > 0)
		{
			m_user_dir = m_home + "/." PACKAGE "/" VERSION_BASE;
			m_userconf = m_home + "/." PACKAGE "/" VERSION_BASE "/povray.conf";
		}
		else
		{
			m_user_dir = "";
			m_userconf = "";
		}

		// system ini file
		m_sysini     = POVCONFDIR "/povray.ini";
		m_sysini_old = POVCONFDIR_BACKWARD "/povray.ini";

		// user ini file
		if (m_home.length() > 0)
			m_userini = m_home + "/." PACKAGE "/" VERSION_BASE "/povray.ini";
		else
			m_userini = "";

		if (m_home.length() > 0)
			m_userini_old = m_home + "/.povrayrc";
		else
			m_userini_old = "";

#ifdef UNIX_DEBUG
		cerr << "PATHS" << endl;
		cerr << "  HOME        = " << m_home << endl;
		cerr << "  SYSCONF     = " << m_sysconf << endl;
		cerr << "  USERCONF    = " << m_userconf << endl;
		cerr << "  SYSINI      = " << m_sysini << endl;
		cerr << "  SYSINI_OLD  = " << m_sysini_old << endl;
		cerr << "  USERINI     = " << m_userini << endl;
		cerr << "  USERINI_OLD = " << m_userini_old << endl;
#endif
#if 0
		cerr << "--- tests ---" << endl;
		cerr << "## unix_getcwd: " << unix_getcwd() << endl;
		cerr << "## basename(SYSCONF): " << basename(m_sysconf) << endl;
		cerr << "## dirname(SYSCONF): " << dirname(m_sysconf) << endl;
		cerr << "## basename(/test/path/): " << basename("/test/path/") << endl;
		cerr << "## dirname(/test/path/): " << dirname("/test/path/") << endl;
		cerr << "## CanonicalizePath(../test/to/file/): " << CanonicalizePath("../test/to/file/") << endl;
		cerr << "## CanonicalizePath(/another/dir/../file): " << CanonicalizePath("/another/dir/../file") << endl;
		cerr << "------------" << endl;
#endif

		// register all standard options
		for (int i = 0; Standard_Options[i].Section != ""; i++)
		{
			list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), Standard_Options[i]);

			// add this option if not already there
			if (iter == m_user_options.end())
				m_user_options.push_back(Standard_Options[i]);
		}

		// process system and user povray.conf
		process_povray_conf();
	}

	string UnixOptionsProcessor::GetTemporaryPath(void)
	{
		string path = QueryOptionString("general", "temppath");
		if (path.length() == 0)
		{
			struct stat s;
			path = "/tmp/";
			if (stat (path.c_str(), &s) == 0  &&  S_ISDIR (s.st_mode)  &&  (s.st_mode & S_IRWXU) == S_IRWXU)
				return path;
			path = unix_getcwd();
		}
		if (path[path.length()-1] != '/')
			path = path + "/";
		return path;
	}

	void UnixOptionsProcessor::PrintOptions(void)
	{
		cerr << endl;
		cerr << "Platform specific command line options:" << endl;

		string section("");
		bool section_new = false;

		for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
		{
			if ((*iter).Section != section)
			{
				section = (*iter).Section;
				section_new = true;
			}
			if ((*iter).CmdOption != "")
			{
				if (section_new)
				{
					cerr << endl;
					cerr << "  '" << section << "' options:" << endl << endl;
					section_new = false;
				}
				cerr << "    " << boost::format("%1% %|32t|%2%") % (*iter).CmdOption % (*iter).Comment << endl;
			}
		}
		cerr << endl;
	}

	void UnixOptionsProcessor::Register(const Option_Info options[])
	{
		for (int i = 0; options[i].Section != ""; i++)
		{
			list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), options[i]);

			// add this option if not already there
			if (iter == m_user_options.end())
				m_user_options.push_back(options[i]);
		}
	}

	void UnixOptionsProcessor::QueryOption(Option_Info &option)
	{
		list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), option);

		if (iter != m_user_options.end())
			option.Value = (*iter).Value;
		else
			option.Value = "";
	}

	string UnixOptionsProcessor::QueryOptionString(const string &section, const string &name)
	{
		Option_Info opt(section, name);
		QueryOption(opt);
		return opt.Value;
	}

	int UnixOptionsProcessor::QueryOptionInt(const string &section, const string &name, const int dflt)
	{
		int res;
		try
		{
			res = boost::lexical_cast<int>(QueryOptionString(section, name));
		}
		catch(boost::bad_lexical_cast &)
		{
			res = dflt;
		}
		return res;
	}

	float UnixOptionsProcessor::QueryOptionFloat(const string &section, const string &name, const float dflt)
	{
		float res;
		try
		{
			res = boost::lexical_cast<float>(QueryOptionString(section, name));
		}
		catch(boost::bad_lexical_cast &)
		{
			res = dflt;
		}
		return res;
	}

	bool UnixOptionsProcessor::isOptionSet(const Option_Info &option)
	{
		list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), option);

		if (iter != m_user_options.end())
		{
			if (!(*iter).has_param && ((*iter).Value == ""))
				return true;
			string val = (*iter).Value;
			boost::to_lower(val);
			if (val == "yes" || val == "on" || val == "true" || val == "1")
				return true;
		}

		return false;
	}

	bool UnixOptionsProcessor::isOptionSet(const string &section, const string &name)
	{
		for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
		{
			if (((*iter).Section == section) && ((*iter).Name == name))
				return isOptionSet(*iter);
		}
		return false;
	}

	void UnixOptionsProcessor::ProcessOptions(int *argc, char **argv[])
	{
		// add custom configuration options found in povray.conf files
		for (list<Conf_Option>::iterator iter_c = m_custom_conf_options.begin(); iter_c != m_custom_conf_options.end(); iter_c++)
		{
			Option_Info new_opt((*iter_c).Section, (*iter_c).Name, (*iter_c).Value, ((*iter_c).Value != ""), "", "");

			list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), new_opt);
			if (iter == m_user_options.end())
				m_user_options.push_back(new_opt);
			else
				(*iter).Value = (*iter_c).Value;
		}

		m_user_options.sort();

#ifdef UNIX_DEBUG
		cerr << "OPTIONS (" << m_user_options.size() << ")" << endl;
#endif

		// check command line options and environment variables of all registered options
		// this overrides povray.conf settings
		for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
		{
			// in ascending order of priority:
			// environment variables:
			if ((*iter).EnvVariable != "")
			{
				char *tmp = getenv((*iter).EnvVariable.c_str());
				if (tmp) // variable defined?
					(*iter).Value = tmp;
			}

			// command line options:
			// based on 3.6 XWIN_init_povray()
			if ((*iter).CmdOption != "")
			{
				int oargc = *argc;
				char **oargv = *argv;

				// TODO: free those when no more needed
				char **nargv = (char **)malloc((oargc + 1) * sizeof(char *));
				int nargc = oargc;
				for (int i = 0; i < nargc; i++)
				{
					nargv[i] = (char *)malloc(strlen(oargv[i]) + 1);
					strcpy(nargv[i], oargv[i]);
				}

				nargv[nargc] = NULL;

				vector<string> CmdVariations;
				boost::split(CmdVariations, (*iter).CmdOption, boost::is_any_of("|"));

				for (vector<string>::iterator iter_c = CmdVariations.begin(); iter_c != CmdVariations.end(); iter_c++)
				{
					for (int i = 1; i < nargc;)
					{
						if (string(nargv[i]) == (*iter_c))
						{
							if ((*iter).has_param)
							{
								int j = i + 1;
								if (j < nargc && nargv[j] != NULL)
								{
									(*iter).Value = nargv[j];
									remove_arg(&nargc, nargv, j);
								}
							}
							else
								(*iter).Value = "on";  // FIXME [nc]
							remove_arg(&nargc, nargv, i);
						}
						else
							i++;
						if (nargv[i] == NULL)
							break;
					}
				}

				*argv = nargv;
				*argc = nargc;
			}

#ifdef UNIX_DEBUG
			cerr << "  " << (*iter).Name << " = " << (*iter).Value << "(" << (*iter).CmdOption << ", " << (*iter).EnvVariable << ")" << endl;
#endif
		}
	}

	// based on 3.x RemoveArgs() by Andreas Dilger
	void UnixOptionsProcessor::remove_arg(int *argc, char *argv[], int index)
	{
		if (index >= *argc || index == 0)
			return;

		if (argv[index] != NULL)
			free(argv[index]);

		for (; index < *argc; index++)
			argv[index] = argv[index + 1];

		(*argc)--;
	}

	// based on 3.6 UNIX_getcwd()
	string UnixOptionsProcessor::unix_getcwd(void)
	{
#ifdef HAVE_GETCWD
		size_t len;

		len = 256;  // default buffer size
		char *tmp = (char *) calloc(len, 1);

		while(getcwd(tmp, len) == NULL)  // buffer is too small
		{
			free(tmp);
			len *= 2;  // double buffer size and try again
			tmp = (char *) calloc(len, 1);
		}
#else
		string tmp = getenv("PWD");  // must not be NULL; checked by configure
		if(tmp.length() == 0)        // run-time checks are safer anyway
		{
			// TODO: correct error handling
			char *errormsg =
				"Cannot determine the current working directory.\n"
				"Check that the PWD environment variable does exist and is valid.\n";
			if(no_error_call)
			{
				fprintf(stderr, "%s: %s\n", PACKAGE, errormsg);
				exit(EXIT_FAILURE);
			}
			else
				Error("%s", errormsg);
		}
#endif

		string s = tmp + string("/");  // add final slash

#ifdef HAVE_GETCWD
		free(tmp);
#endif

		return s;
	}

	// based on 3.6 unix_basename()
	string UnixOptionsProcessor::basename(const string &path)
	{
		if(path.length() < 2) // less than two characters
			return path;

		string s = path;
		if (s[s.length()-1] == '/')
			s.erase(s.length()-1);
		string::size_type pos = s.rfind('/');
		if (pos == 0)
			return s;
		s.erase(0, pos+1);
		return s;
	}

	// based on 3.6 unix_dirname()
	string UnixOptionsProcessor::dirname(const string &path)
	{
		if(path.length() < 2)  // less than two characters
			return string("");

		string s = path;
		if (s[s.length()-1] == '/')
			s.erase(s.length()-1);
		string::size_type pos = s.rfind('/');
		if (pos == 0)
			return string("");
		s.erase(pos);
		return s;
	}

	// based on 3.6 unix_readlink()
	string UnixOptionsProcessor::unix_readlink(const string &path)
	{
#ifdef HAVE_READLINK
		char   *tmp;
		size_t  len;
		int     status;

		len = 256;  // default buffer size
		tmp = (char *) calloc(len, 1);  // init with '\0'

		while(true)
		{
			status = readlink(path.c_str(), tmp, len-1);  // without terminating '\0'
			if(status < 0)  // an error occured, return empty string
			{
				free(tmp);
				return string("");
			}
			else if(status == len-1)  // the buffer is probably too small
			{
				free(tmp);
				len *= 2;  // double buffer size and try again
				tmp = (char *) calloc(len, 1);
			}
			else  // all right, let's go further
				break;
		}

		// do we have an absolute path ?
		if(tmp[0] != '/')  // no; concatenate symlink to the path dirname
		{
			string s = dirname(path) + "/" + tmp;
			free(tmp);
			return s;
		}
		else  // yes; just resize buffer
		{
			string s = tmp;
			free(tmp);
			return s;
		}
#else
		return string("");
#endif
	}

	// based on 3.6 UNIX_canonicalize_path()
	string UnixOptionsProcessor::CanonicalizePath(const string &path)
	{
		int   i;
		typedef struct { const char *match, *replace; } subst;
		const subst strings[] = {  // beware: order does matter
			{ "%INSTALLDIR%", POVLIBDIR },
			{ "%HOME%", m_home.c_str() },
			{ "//", "/" },
			{ "/./", "/" },
			{ NULL, NULL }  // sentinel
		};

		// nothing to canonicalize; return an empty string
		if(path.length() == 0)
			return string("");

#ifdef UNIX_DEBUG
		cerr << "  CANONICALIZE '" << path << "'" << endl;
#endif

		string s = path;

		// substitute all occurences of 'match' by 'replace'
		i = 0;
		while(strings[i].match)
		{
			boost::replace_all(s, strings[i].match, strings[i].replace);
			++i;
#ifdef UNIX_DEBUG
			cerr << "    su: " << s << endl;
#endif
		}

		// substitute the current working dir to the first "./"
		// or add the cwd to the first directory or "../"
		if (boost::starts_with(s, "./"))
		{
			s.erase(0, 2);
			s.insert(0, unix_getcwd());
		}
		else if(s[0] != '/' || boost::starts_with(s, "../"))
		{
			s.insert(0, unix_getcwd());
		}

		// iteratively translate all symlinks in the path (dirname and basename)
#ifdef HAVE_READLINK
		i = 0;
		if(s[i] == '/')
			++i;
		do
		{
			while(i < s.length() && s[i] != '/')
				++i;
			string tmp1 = s.substr(0, i);
			string tmp2 = s.substr(i, s.length()-i);
			string tmp3 = unix_readlink(tmp1);
			if (tmp3.length() > 0)
			{
#ifdef UNIX_DEBUG
				cerr << "    ln: " << tmp1 << " -> " << tmp3 << endl;
				cerr << "  " << tmp3 << tmp2 << endl;
#endif
				s = tmp3 + tmp2;
				i = 0;  // start again from beginning
				if(s[i] == '/')
					++i;
			}
			else
			{
#ifdef UNIX_DEBUG
				cerr << "    ln: " << tmp1 << endl;
#endif
				++i;
			}
		} while(i < s.length());
#endif  // HAVE_READLINK

		// substitute all occurences of "dir/.." by ""
		string tmp = s;
		string::size_type pos = s.find("/..");
		while (pos != string::npos)
		{
			string::size_type pos2 = s.rfind('/', pos-1);
			s.erase(pos2+1, pos-pos2+3);
#ifdef UNIX_DEBUG
			cerr << "    su: " << s << endl;
#endif
			pos = s.find("/..", pos+3);
		}

		// remove the last "/." if any
		if (boost::ends_with(s, "/."))
		{
			s.erase(s.length()-3);
#ifdef UNIX_DEBUG
			cerr << "    su: " << s << endl;
#endif
		}

		return s;
	}

	// based on 3.6 pre_process_conf_line()
	string UnixOptionsProcessor::pre_process_conf_line(const string &input)
	{
		string s = boost::trim_copy(input);

		// find comment mark
		string::size_type pos = s.find(";");
		if (pos != string::npos)
		{
			s.erase(pos);
			boost::trim(s);
		}

		return s;
	}

	// based on 3.6 add_permitted_path()
	void UnixOptionsProcessor::add_permitted_path(list<UnixPath> &paths, const string &input, const string &conf_name, unsigned long line_number)
	{
		char quote = 0;
		bool descend = false;
		bool writable = false;

		// read or read+write entry
		if (boost::starts_with(input, "read"))
		{
			int spos = 4;
			// we have a read+write path
			if (boost::starts_with(input, "read+write"))
			{
				writable = true;
				spos = 10;
			}
			// sub-directory search
			if (input[spos] == '*')
				descend = true;

			string::size_type pos = input.find('=');  // find equal sign
			if (pos != string::npos)  // equal sign found
			{
				int i = 0;
				string s = input.substr(pos+1, input.length()-pos-1);
				boost::trim(s);

				if(s[i] == '"' || s[i] == '\'')
				{
					quote = s[i];  // store and skip quote
					++i;
				}
				int begin = i;
				while(i < s.length())  // find next space caracter or closing quote
				{
					if(s[i] == quote  ||  (!quote  &&  isspace((int) s[i])))
						break;
					++i;
				}
				if(quote  &&  s[i] != quote)  // no closing quote
					fprintf(stderr,
						"%s: %s: %lu: ignored entry: missing closing %c quote\n",
						PACKAGE, conf_name.c_str(), line_number, quote
					);
				else if(i-begin)  // store given directory
				{
					string directory = s.substr(begin, i-begin) + "/";
					s = CanonicalizePath(directory);

#ifdef UNIX_DEBUG
					cerr << "PERMITTED ADD '" << s << "'" << endl;
#endif
					paths.push_back(UnixPath(s, descend, writable));
				}
				else  // nothing found after the equal sign
					fprintf(stderr,
						"%s: %s: %lu: ignored entry: missing directory\n",
						PACKAGE, conf_name.c_str(), line_number
					);
			}
			else  // equal sign not found
				fprintf(stderr,
					"%s: %s: %lu: ignored entry: missing equal sign\n",
					PACKAGE, conf_name.c_str(), line_number
				);
		}
		// unknown entry
		else if(input.length() > 0)
			fprintf(stderr,
				"%s: %s: %lu: unknown '%s' setting\n",
				PACKAGE, conf_name.c_str(), line_number, input.c_str()
			);
	}

	// based on 3.6 unix_parse_conf_file()
	void UnixOptionsProcessor::parse_conf_file(std::istream &Stream, const string &conf_name, bool user_mode)
	{
		list<UnixPath> paths;
		string line;
		string Custom_Section;
		unsigned long   line_number;
		bool            user_file_io_rejected;
		FileIO          file_io;
		bool            file_io_is_set;
		ShellOut        shellout;
		bool            shellout_is_set;
		short           i;

		typedef enum { NONE, FILE_IO, SHELLOUT, PERMITTED_PATHS, UNKNOWN } SectionVal;
		SectionVal section;
		typedef struct Section { const char *label; const SectionVal value; } Section;
		const Section sections[] =
		{
			{ ""                   , NONE            },  // init
			{ "[File I/O Security]", FILE_IO         },
			{ "[Shellout Security]", SHELLOUT        },
			{ "[Permitted Paths]"  , PERMITTED_PATHS },
			{ NULL                 , UNKNOWN         }   // sentinel
		};

		typedef struct IOSettings { const char *label; const FileIO value; } IOSettings;
		const IOSettings io_settings[] =
		{
			{ ""          , IO_UNSET      },
			{ "none"      , IO_NONE       },
			{ "read-only" , IO_READONLY   },
			{ "restricted", IO_RESTRICTED },
			{ NULL        , IO_UNKNOWN    }
		};

		typedef struct SHLSettings { const char *label; const ShellOut value; } SHLSettings;
		const SHLSettings shl_settings[] =
		{
			{ ""         , SHL_UNSET     },
			{ "allowed"  , SHL_ALLOWED   },
			{ "forbidden", SHL_FORBIDDEN },
			{ NULL       , SHL_UNKNOWN   }
		};

		// inits
		line_number = 0;
		user_file_io_rejected = false;
		file_io_is_set = shellout_is_set = false;
		section = NONE;
		file_io = IO_UNSET;
		shellout = SHL_UNSET;

#ifdef UNIX_DEBUG
		cerr << "PARSE CONF '" << conf_name << "'" << endl;
#endif

		// Since the file format allows to read permitted paths before
		// setting [File I/O Security], the paths must be stored in a local
		// list which will be used only when the user setting for file I/O
		// is accepted.
		if(!user_mode)
			paths = m_permitted_paths;

		while(std::getline(Stream, line))
		{
			// check for failbit and badbit and eofbit
			if(!Stream.good())
			{
				// Only in case of the badbit we can assume that some lower
				// level system API function has set errno and perror() can be
				// used safely.
				if(Stream.bad())
				{
					fprintf(stderr, "%s: error while reading/opening configuration file ", PACKAGE);
					perror(conf_name.c_str());
				}
				break;
			}
			// preprocess line
			line = pre_process_conf_line(line);
			++line_number;

			// skip empty line
			if(line.length() == 0)
				continue;

			// check section
			if(line[0] == '[')  // new section
			{
				Custom_Section = "";
				// search section
				for(i = 1; sections[i].label; ++i)
					if(boost::starts_with(line, sections[i].label))
						break;

				section = sections[i].value;

				// unknown section
				if(section == UNKNOWN)
				{
					Custom_Section = boost::to_lower_copy(line);
					Custom_Section.erase(0,1);
					Custom_Section.erase(Custom_Section.find(']'));
				}
			}  // check section

			// process custom sections
			else if (Custom_Section.length() != 0)
			{
				// split at '='
				string::size_type pos = line.find('=');
				string option_name;
				string option_val;
				if (pos == string::npos)
				{
					option_name = boost::to_lower_copy(line);
					option_val = "";
				}
				else
				{
					option_name = boost::to_lower_copy(line.substr(0, pos));
					option_val = line.substr(pos+1, line.length()-pos-1);
				}
#ifdef UNIX_DEBUG
				cerr << "CUSTOM OPTION:" << endl;
				cerr << " Section: " << Custom_Section << endl;
				cerr << " Name: " << option_name << endl;
				cerr << " Value: " << option_val << endl;
#endif
				m_custom_conf_options.push_back(Conf_Option(Custom_Section, option_name, option_val));
			}

			// file I/O security
			else if(section == FILE_IO)
			{
				// search setting
				for(i = 0; io_settings[i].label; ++i)
					if(line != string(io_settings[i].label))
						break;
				file_io = io_settings[i].value;

				// multiple settings were found
				if(file_io_is_set)
					fprintf(stderr,
						"%s: %s: %lu: multiple settings for %s\n",
						PACKAGE, conf_name.c_str(), line_number, sections[section].label
					);
				if(file_io != IO_UNSET)
					file_io_is_set = true;

				// unknown setting
				if(file_io == IO_UNKNOWN)
				{
					fprintf(stderr,
						"%s: %s: %lu: unknown '%s' setting for %s: ",
						PACKAGE, conf_name.c_str(), line_number, line.c_str(), sections[section].label
					);
					if(user_mode)
					{
						fprintf(stderr,
							"using system setting '%s'\n",
							io_settings[m_file_io].label
						);
						file_io = m_file_io;
						user_file_io_rejected = true;  // won't account for the user paths
					}
					else
					{
						fprintf(stderr, "I/O restrictions are disabled\n");
						file_io = IO_NONE;
					}
				}

				// user setting not allowed
				if(user_mode  &&  file_io < m_file_io)
				{
					fprintf(stderr,
						"%s: %s: %lu: "
						"the user setting '%s' for %s is less restrictive than "
						"the system setting '%s' in '%s': using system setting\n",
						PACKAGE, conf_name.c_str(), line_number,
						io_settings[  file_io].label, sections[section].label,
						io_settings[m_file_io].label, m_conf.c_str()
					);
					file_io = m_file_io;
					user_file_io_rejected = true;  // won't account for the user paths
				}

				m_file_io = file_io;
			}  // file I/O security

			// shellout security
			else if(section == SHELLOUT)
			{
				// search setting
				for(i = 0; shl_settings[i].label; ++i)
					if(line == string(shl_settings[i].label))
						break;
				shellout = shl_settings[i].value;

				// multiple settings were found
				if(shellout_is_set)
					fprintf(stderr,
						"%s: %s: %lu: multiple settings for %s\n",
						PACKAGE, conf_name.c_str(), line_number, sections[section].label
					);
				if(shellout != SHL_UNSET)
					shellout_is_set = true;

				// unknown setting
				if(shellout == SHL_UNKNOWN)
				{
					fprintf(stderr,
						"%s: %s: %lu: unknown '%s' setting for %s: ",
						PACKAGE, conf_name.c_str(), line_number, line.c_str(), sections[section].label
					);
					if(user_mode)
					{
						fprintf(stderr,
							"using system setting '%s'\n",
							shl_settings[m_shellout].label
						);
						shellout = m_shellout;
					}
					else
					{
						fprintf(stderr, "shellout security is disabled\n");
						shellout = SHL_ALLOWED;
					}
				}

				// user setting not allowed
				if(user_mode
					 && m_shellout == SHL_FORBIDDEN
					 && m_shellout != shellout)
				{
					fprintf(stderr,
						"%s: %s: %lu: "
						"the user setting '%s' for %s is less restrictive than "
						"the system '%s' setting in '%s': using system setting\n",
						PACKAGE, conf_name.c_str(), line_number,
						shl_settings[  shellout].label, sections[section].label,
						shl_settings[m_shellout].label, m_conf.c_str()
					);
					shellout = m_shellout;
				}

				m_shellout = shellout;
				gShelloutsPermittedFixThis = shellout == SHL_ALLOWED;
			}  // shellout security

			// permitted paths
			else if(section == PERMITTED_PATHS)
				add_permitted_path(paths, line, conf_name, line_number);

		}  // Read file loop end

		// Only in case of the badbit we can assume that some lower
		// level system API function has set errno. Then, perror() can be
		// used safely.
		if(Stream.bad())
		{
			fprintf(stderr, "%s: error while reading/opening config file ", PACKAGE);
			perror(conf_name.c_str());
		}

#ifdef UNIX_DEBUG
		fprintf(stderr,
			"I/O RESTRICTIONS\n"
			"  file_io  = %d\tconfig->file_io  = %d\n"
			"  shellout = %d\tconfig->shellout = %d\n",
			file_io, m_file_io,
			shellout, m_shellout
		);
#endif

		// assign user settings and paths
		if(user_mode)
		{
			if(user_file_io_rejected)
			{
				fprintf(stderr,
					"%s: %s: user permitted paths are ignored: using system paths\n",
					PACKAGE, conf_name.c_str()
				);
			}
			else
			{
				m_permitted_paths = paths;  // assign new paths
			}
		}
	}

	// based on 3.6 unix_process_povray_conf()
	void UnixOptionsProcessor::process_povray_conf(void)
	{
		m_Session->ClearPaths();
		m_Session->AddExcludedPath(string(POVCONFDIR));
		if (m_user_dir.length() != 0)
			m_Session->AddExcludedPath(m_user_dir);

		// read system configuration file
		if(m_sysconf.length() != 0)
		{
			std::ifstream stream ( m_sysconf.c_str() );
			if ( stream.is_open() )
			{
				parse_conf_file(stream, m_sysconf, false);
				m_conf = m_sysconf;
			}
			else
			{
				fprintf(stderr, "%s: cannot open the system configuration file ", PACKAGE);
				perror(m_sysconf.c_str());
			}
		}

		// read user configuration file
		if(m_userconf.length() != 0)
		{
			std::ifstream stream ( m_userconf.c_str() );
			if ( stream.is_open() )
			{
				parse_conf_file(stream, m_userconf, true);
				m_conf = m_userconf;
			}
			else
			{
				fprintf(stderr, "%s: cannot open the user configuration file ", PACKAGE);
				perror(m_userconf.c_str());
			}
		}

		// no file was read, disable I/O restrictions
		if(m_conf.length() == 0)
			fprintf(stderr, "%s: I/O restrictions are disabled\n", PACKAGE);

		// if no paths specified, at least include POVLIBDIR and POVCONFDIR
		else if(m_permitted_paths.empty())
		{
			m_permitted_paths.push_back(UnixPath(string(POVLIBDIR "/"), true, false));   // read*
			m_permitted_paths.push_back(UnixPath(string(POVCONFDIR "/"), false, false)); // read
		}

#ifdef UNIX_DEBUG
		cerr << "PERMITTED PATHS" << endl;
#endif

		for(list<UnixPath>::iterator iter = m_permitted_paths.begin(); iter != m_permitted_paths.end(); iter++)
		{
			m_Session->AddReadPath(iter->str, iter->descend);
#ifdef UNIX_DEBUG
			fprintf(stderr,
				"  %s%s = \"%s\"\n",
				iter->writable ? "WRITE" : "READ", iter->descend ? "*" : "", iter->str.c_str()
			);
#endif
		}
	}

	bool UnixOptionsProcessor::file_exist(const string &name)
	{
		FILE *file = fopen(name.c_str(), "r");

		if(file != NULL)
			fclose(file);
		else
			return false;

		return true;
	}

	// based on 3.6 unix_process_povray_ini()
	void UnixOptionsProcessor::Process_povray_ini(vfeRenderOptions &opts)
	{
		// try the file pointed to by POVINI
		string povini;
		char * povini_c = getenv("POVINI");
		if (povini_c)
		{
			povini = povini_c;
			if (file_exist(povini))
			{
				opts.AddINI(povini);
				return;
			}
			else
			{
				fprintf(stderr, "%s: POVINI environment: cannot open ", PACKAGE);
				perror(povini.c_str());
			}
		}

		// try any INI file in the current directory
		povini = "./povray.ini";
		if (file_exist(povini))
		{
			opts.AddINI(povini);
			return;
		}

		// try the user or system INI file
		if ((m_home.length() != 0) && file_exist(m_userini))
		{
			opts.AddINI(m_userini);
			return;
		}
		if (file_exist(m_sysini))
		{
			opts.AddINI(m_sysini);
			return;
		}

		// try older user or system INI files
		if ((m_home.length() != 0) && file_exist(m_userini_old))
		{
			opts.AddINI(m_userini_old);
			return;
		}
		if (file_exist(m_sysini_old))
		{
			opts.AddINI(m_sysini_old);
			return;
		}

		// warn that no INI file was found and add minimal library_path setting
		fprintf(stderr, "%s: cannot open an INI file, adding default library path\n", PACKAGE);
		opts.AddLibraryPath(string(POVLIBDIR "/include"));
	}

#if 0
	// based on 3.6 unix_subdir()
	static bool UnixOptionsProcessor::file_in_permitted_paths (const string &Filename, bool write)
	{
		// NOTE: Filename must be already canonicalized

		// no filename nor paths
		if(Filename.length() == 0  ||  m_permitted_paths.empty())
			return false;

		// scan the list of paths
		for (list<UnixPath>::iterator iter = m_permitted_paths.begin(); iter != m_permitted_paths.end(); iter++)
		{
			if ((*iter).descend)  // allows sub-directories
			{
				if (!write || (*iter).writable)
				{
#ifdef UNIX_DEBUG
					fprintf(stderr,
						"  FILE '%s' <-> %s* '%s'\n",
						Filename.c_str(), path->writable ? "WRITE" : "READ", (*iter).str.c_str()
					);
#endif
					if (boost::starts_with(Filename, (*iter).str))  // match found
					{
#ifdef UNIX_DEBUG
						fprintf(stderr, "  OK\n");
#endif
						return true;
					}
				}
			}
			else  // check for exact match with path->str (without last slash)
			{
				string dirname = dirname(Filename);
				string pathname = (*iter).str;
				pathname.erase(pathname.length()-1);
				if (!write || (*iter).writable)
			 	{
#ifdef UNIX_DEBUG
					fprintf(stderr,
						"  DIRNAME '%s' <-> %s '%s'\n",
						dirname.c_str(), path->writable ? "WRITE" : "READ", pathname.c_str()
					);
#endif
					if(dirname == pathname)
					{
#ifdef UNIX_DEBUG
						fprintf(stderr, "  OK\n");
#endif
						return true;
					}
				}
			}
		}

#ifdef UNIX_DEBUG
		fprintf(stderr, "  BAD\n");
#endif

		return false;
	}
#endif

	bool UnixOptionsProcessor::isIORestrictionsEnabled(bool write)
	{
		if (IO_RESTRICTIONS_DISABLED)
			return false;
		if (!write &&  m_file_io < IO_RESTRICTED)
			return false;
		if(m_file_io < IO_READONLY)
			return false;
		return true;
	}

	void UnixOptionsProcessor::IORestrictionsError(const string &fnm, bool write, bool is_user_setting)
	{
		if (is_user_setting)
		{
			if (write)
				fprintf(stderr, "%s: writing to '%s' is not permitted; check the configuration in '%s'\n", PACKAGE, fnm.c_str(), m_conf.c_str());
			else
				fprintf(stderr, "%s: reading from '%s' is not permitted; check the configuration in '%s'\n", PACKAGE, fnm.c_str(), m_conf.c_str());
		}
		else
		{
			fprintf(stderr, "%s: writing to '%s' is not permitted\n", PACKAGE, fnm.c_str());
		}

	}

}
