context: //------------------------------------------------------------------------------
  // 
  // System.Environment.cs 
  //
  // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
  // 
  // Author:         Jim Richardson, develop@wtfo-guru.com
  //                 Dan Lewis (dihlewis@yahoo.co.uk)
  // Created:        Saturday, August 11, 2001 
  //
  //------------------------------------------------------------------------------
  //
  // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System.IO;
  using System.Collections;
  using System.Runtime.CompilerServices;
  using System.Security;
  using System.Security.Permissions;
  using System.Text;
  using System.Runtime.InteropServices;
  using System.Threading;
  using System.Diagnostics.Contracts;
  
  namespace System {
  
  	[ComVisible (true)]
  	public static partial class Environment {
  
  		/*
  		 * This is the version number of the corlib-runtime interface. When
  		 * making changes to this interface (by changing the layout
  		 * of classes the runtime knows about, changing icall signature or
  		 * semantics etc), increment this variable. Also increment the
  		 * pair of this variable in the runtime in metadata/appdomain.c.
  		 * Changes which are already detected at runtime, like the addition
  		 * of icalls, do not require an increment.
  		 */
  #pragma warning disable 169
  		private const int mono_corlib_version = 134;
  #pragma warning restore 169
  
  		[ComVisible (true)]
  		public enum SpecialFolder
  		{	
  			MyDocuments = 0x05,
  			Desktop = 0x00,
  			MyComputer = 0x11,
  			Programs = 0x02,
  			Personal = 0x05,
  			Favorites = 0x06,
  			Startup = 0x07,
  			Recent = 0x08,
  			SendTo = 0x09,
  			StartMenu = 0x0b,
  			MyMusic = 0x0d,
  			DesktopDirectory = 0x10,
  			Templates = 0x15,
  			ApplicationData	= 0x1a,
  			LocalApplicationData = 0x1c,
  			InternetCache = 0x20,
  			Cookies = 0x21,
  			History = 0x22,
  			CommonApplicationData	= 0x23,
  			System = 0x25,
  			ProgramFiles = 0x26,
  			MyPictures = 0x27,
  			CommonProgramFiles = 0x2b,
  			MyVideos = 0x0e,
  			NetworkShortcuts = 0x13,
  			Fonts = 0x14,
  			CommonStartMenu = 0x16,
  			CommonPrograms = 0x17,
  			CommonStartup = 0x18,
  			CommonDesktopDirectory = 0x19,
  			PrinterShortcuts = 0x1b,
  			Windows = 0x24,
  			UserProfile = 0x28,
  			SystemX86 = 0x29,
  			ProgramFilesX86 = 0x2a,
  			CommonProgramFilesX86 = 0x2c,
  			CommonTemplates = 0x2d,
  			CommonDocuments = 0x2e,
  			CommonAdminTools = 0x2f,
  			AdminTools = 0x30,
  			CommonMusic = 0x35,
  			CommonPictures = 0x36,
  			CommonVideos = 0x37,
  			Resources = 0x38,
  			LocalizedResources = 0x39,
  			CommonOemLinks = 0x3a,
  			CDBurning = 0x3b,
  		}
  
  		public
  		enum SpecialFolderOption {
  			None = 0,
  			DoNotVerify = 0x4000,
  			Create = 0x8000
  		}
  
  		/// <summary>
  		/// Gets the command line for this process
  		/// </summary>
  		public static string CommandLine {
  			// note: security demand inherited from calling GetCommandLineArgs
  			get {
  				StringBuilder sb = new StringBuilder ();
  				foreach (string str in GetCommandLineArgs ()) {
  					bool escape = false;
  					string quote = "";
  					string s = str;
  					for (int i = 0; i < s.Length; i++) {
  						if (quote.Length == 0 && Char.IsWhiteSpace (s [i])) {
  							quote = "\"";
  						} else if (s [i] == '"') {
  							escape = true;
  						}
  					}
  					if (escape && quote.Length != 0) {
  						s = s.Replace ("\"", "\\\"");
  					}
  					sb.AppendFormat ("{0}{1}{0} ", quote, s);
  				}
  				if (sb.Length > 0)
  					sb.Length--;
  				return sb.ToString ();
  			}
  		}
  
  		/// <summary>
  		/// Gets or sets the current directory. Actually this is supposed to get
  		/// and/or set the process start directory acording to the documentation
  		/// but actually test revealed at beta2 it is just Getting/Setting the CurrentDirectory
  		/// </summary>
  		public static string CurrentDirectory
  		{
  			get {
  				return Directory.GetCurrentDirectory ();
  			}
  			set {
  				Directory.SetCurrentDirectory (value);
  			}
  		}
  		
  		public static int CurrentManagedThreadId {
  			get {
  				return Thread.CurrentThread.ManagedThreadId;
  			}
  		}
  
  		/// <summary>
  		/// Gets or sets the exit code of this process
  		/// </summary>
  		public extern static int ExitCode
  		{	
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			get;
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			set;
  		}
  
  		static public extern bool HasShutdownStarted
  		{
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			get;
  		}
  		
  
  		/// <summary>
  		/// Gets the name of the local computer
  		/// </summary>
  		public extern static string MachineName {
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			[EnvironmentPermission (SecurityAction.Demand, Read="COMPUTERNAME")]
  			[SecurityPermission (SecurityAction.Demand, UnmanagedCode=true)]
  			get;
  		}
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		extern static string GetNewLine ();
  
  		static string nl;
  		/// <summary>
  		/// Gets the standard new line value
  		/// </summary>
  		public static string NewLine {
  			get {
  				if (nl != null)
  					return nl;
  
  				nl = GetNewLine ();
  				return nl;
  			}
  		}
  
  		//
  		// Support methods and fields for OSVersion property
  		//
  		static OperatingSystem os;
  
  		static extern PlatformID Platform {
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			get;
  		}
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		internal static extern string GetOSVersionString ();
  
  		/// <summary>
  		/// Gets the current OS version information
  		/// </summary>
  		public static OperatingSystem OSVersion {
  			get {
  				if (os == null) {
  					Version v = CreateVersionFromString (GetOSVersionString ());
  					PlatformID p = Platform;
  					if (p == PlatformID.MacOSX)
  						p = PlatformID.Unix;
  					os = new OperatingSystem (p, v);
  				}
  				return os;
  			}
  		}
  
  
  		// a very gentle way to construct a Version object which takes 
  		// the first four numbers in a string as the version
  		internal static Version CreateVersionFromString (string info)
  		{
  			int major = 0;
  			int minor = 0;
  			int build = 0;
  			int revision = 0;
  			int state = 1;
  			int number = -1; // string may not begin with a digit
  
  			if (info == null)
  				return new Version (0, 0, 0, 0);
  
  			for (int i=0; i < info.Length; i++) {
  				char c = info [i];
  				if (Char.IsDigit (c)) {
  					if (number < 0) {
  						number = (c - '0');
  					}
  					else {
  						number = (number * 10) + (c - '0');
  					}
  				}
  				else if (number >= 0) {
  					// assign
  					switch (state) {
  					case 1:
  						major = number;
  						break;
  					case 2:
  						minor = number;
  						break;
  					case 3:
  						build = number;
  						break;
  					case 4:
  						revision = number;
  						break;
  					}
  					number = -1;
  					state ++;
  				}
  				// ignore end of string
  				if (state == 5)
  					break;
  			}
  
  			// Last number
  			if (number >= 0) {
  				switch (state) {
  				case 1:
  					major = number;
  					break;
  				case 2:
  					minor = number;
  					break;
  				case 3:
  					build = number;
  					break;
  				case 4:
  					revision = number;
  					break;
  				}
  			}
  			return new Version (major, minor, build, revision);
  		}
  
  		/// <summary>
  		/// Get StackTrace
  		/// </summary>
  		public static string StackTrace {
  			[EnvironmentPermission (SecurityAction.Demand, Unrestricted=true)]
  			get {
  				System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace (0, true);
  				return trace.ToString ();
  			}
  		}
  #if !NET_2_1
  		/// <summary>
  		/// Get a fully qualified path to the system directory
  		/// </summary>
  		public static string SystemDirectory {
  			get {
  				return GetFolderPath (SpecialFolder.System);
  			}
  		}
  #endif
  		/// <summary>
  		/// Get the number of milliseconds that have elapsed since the system was booted
  		/// </summary>
  		public extern static int TickCount {
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			get;
  		}
  
  		/// <summary>
  		/// Get UserDomainName
  		/// </summary>
  		public static string UserDomainName {
  			// FIXME: this variable doesn't exist (at least not on WinXP) - reported to MS as FDBK20562
  			[EnvironmentPermission (SecurityAction.Demand, Read="USERDOMAINNAME")]
  			get {
  				return MachineName;
  			}
  		}
  
  		/// <summary>
  		/// Gets a flag indicating whether the process is in interactive mode
  		/// </summary>
  		[MonoTODO ("Currently always returns false, regardless of interactive state")]
  		public static bool UserInteractive {
  			get {
  				return false;
  			}
  		}
  
  		/// <summary>
  		/// Get the user name of current process is running under
  		/// </summary>
  		public extern static string UserName {
  			[MethodImplAttribute (MethodImplOptions.InternalCall)]
  			[EnvironmentPermission (SecurityAction.Demand, Read="USERNAME;USER")]
  			get;
  		}
  
  		/// <summary>
  		/// Get the version of the common language runtime 
  		/// </summary>
  		public static Version Version {
  			get {
  				return new Version (Consts.FxFileVersion);
  			}
  		}
  
  		/// <summary>
  		/// Get the amount of physical memory mapped to process
  		/// </summary>
  		[MonoTODO ("Currently always returns zero")]
  		public static long WorkingSet {
  			[EnvironmentPermission (SecurityAction.Demand, Unrestricted=true)]
  			get { return 0; }
  		}
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		[SecurityPermission (SecurityAction.Demand, UnmanagedCode=true)]
  		public extern static void Exit (int exitCode);
  
  		internal static void _Exit (int exitCode)
  		{
  			Exit (exitCode);
  		}
  
  		/// <summary>
  		/// Substitute environment variables in the argument "name"
  		/// </summary>
  		public static string ExpandEnvironmentVariables (string name)
  		{
  			if (name == null)
  				throw new ArgumentNullException ("name");
  
  			int off1 = name.IndexOf ('%');
  			if (off1 == -1)
  				return name;
  
  			int len = name.Length;
  			int off2 = 0;
  			if (off1 == len - 1 || (off2 = name.IndexOf ('%', off1 + 1)) == -1)
  				return name;
  
  			StringBuilder result = new StringBuilder ();
  			result.Append (name, 0, off1);
  			Hashtable tbl = null;
  			do {
  				string var = name.Substring (off1 + 1, off2 - off1 - 1);
  				string value = GetEnvironmentVariable (var);
  				if (value == null && Environment.IsRunningOnWindows) {
  					// On windows, env. vars. are case insensitive
  					if (tbl == null)
  						tbl = GetEnvironmentVariablesNoCase ();
  
  					value = tbl [var] as string;
  				}
  				
  				// If value not found, add %FOO to stream,
  				//  and use the closing % for the next iteration.
  				// If value found, expand it in place of %FOO%
  				int realOldOff2 = off2;
  				if (value == null) {
  					result.Append ('%');
  					result.Append (var);
  					off2--;
  				} else {
  					result.Append (value);
  				}
  				int oldOff2 = off2;
  				off1 = name.IndexOf ('%', off2 + 1);
  				// If no % found for off1, don't look for one for off2
  				off2 = (off1 == -1 || off2 > len-1)? -1 :name.IndexOf ('%', off1 + 1);
  				// textLen is the length of text between the closing % of current iteration
  				//  and the starting % of the next iteration if any. This text is added to output
  				int textLen;
  				// If no new % found, use all the remaining text
  				if (off1 == -1 || off2 == -1)
  					textLen = len - oldOff2 - 1;
  				// If value found in current iteration, use text after current closing % and next %
  				else if(value != null)
  					textLen = off1 - oldOff2 - 1;
  				// If value not found in current iteration, but a % was found for next iteration,
  				//  use text from current closing % to the next %.
  				else
  					textLen = off1 - realOldOff2;
  				if(off1 >= oldOff2 || off1 == -1)
  					result.Append (name, oldOff2+1, textLen);
  			} while (off2 > -1 && off2 < len);
  				
  			return result.ToString ();
  
  		}
  
  		/// <summary>
  		/// Return an array of the command line arguments of the current process
  		/// </summary>
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		[EnvironmentPermissionAttribute (SecurityAction.Demand, Read = "PATH")]
  		public extern static string[] GetCommandLineArgs ();
  
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		internal extern static string internalGetEnvironmentVariable (string variable);
  
  		/// <summary>
  		/// Return a string containing the value of the environment
  		/// variable identifed by parameter "variable"
  		/// </summary>
  		public static string GetEnvironmentVariable (string variable)
  		{
  #if !NET_2_1
  			if (SecurityManager.SecurityEnabled) {
  				new EnvironmentPermission (EnvironmentPermissionAccess.Read, variable).Demand ();
  			}
  #endif
  			return internalGetEnvironmentVariable (variable);
  		}
  
  		static Hashtable GetEnvironmentVariablesNoCase ()
  		{
  			Hashtable vars = new Hashtable (CaseInsensitiveHashCodeProvider.Default,
  							CaseInsensitiveComparer.Default);
  
  			foreach (string name in GetEnvironmentVariableNames ()) {
  				vars [name] = internalGetEnvironmentVariable (name);
  			}
  
  			return vars;
  		}
  
  		/// <summary>
  		/// Return a set of all environment variables and their values
  		/// </summary>
  #if !NET_2_1
  		public static IDictionary GetEnvironmentVariables ()
  		{
  			StringBuilder sb = null;
  			if (SecurityManager.SecurityEnabled) {
  				// we must have access to each variable to get the lot
  				sb = new StringBuilder ();
  				// but (performance-wise) we do not want a stack-walk
  				// for each of them so we concatenate them
  			}
  
  			Hashtable vars = new Hashtable ();
  			foreach (string name in GetEnvironmentVariableNames ()) {
  				vars [name] = internalGetEnvironmentVariable (name);
  				if (sb != null) {
  					sb.Append (name);
  					sb.Append (";");
  				}
  			}
  
  			if (sb != null) {
  				new EnvironmentPermission (EnvironmentPermissionAccess.Read, sb.ToString ()).Demand ();
  			}
  			return vars;
  		}
  #else
  		[EnvironmentPermission (SecurityAction.Demand, Unrestricted=true)]
  		public static IDictionary GetEnvironmentVariables ()
  		{
  			Hashtable vars = new Hashtable ();
  			foreach (string name in GetEnvironmentVariableNames ()) {
  				vars [name] = internalGetEnvironmentVariable (name);
  			}
  			return vars;
  		}
  #endif
  
  		/// <summary>
  		/// Returns the fully qualified path of the
  		/// folder specified by the "folder" parameter
  		/// </summary>
  		public static string GetFolderPath (SpecialFolder folder)
  		{
  			return GetFolderPath (folder, SpecialFolderOption.None);
  		}
  
  // for monotouch, not monotouch_runtime
  #if !(MONOTOUCH && FULL_AOT_RUNTIME)
  		[MethodImplAttribute (MethodImplOptions.InternalCall)]
  		private extern static string GetWindowsFolderPath (int folder);
  
  		public
  		static string GetFolderPath(SpecialFolder folder, SpecialFolderOption option)
  		{
  			SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
  
  			string dir = null;
  
  			if (Environment.IsRunningOnWindows)
  				dir = GetWindowsFolderPath ((int) folder);
  			else
  				dir = UnixGetFolderPath (folder, option);
  
  #if !NET_2_1
  			if ((dir != null) && (dir.Length > 0) && SecurityManager.SecurityEnabled) {
  				new FileIOPermission (FileIOPermissionAccess.PathDiscovery, dir).Demand ();
  			}
  #endif
  			return dir;
  		}
  
  		private static string ReadXdgUserDir (string config_dir, string home_dir, string key, string fallback)
  		{
  			string env_path = internalGetEnvironmentVariable (key);
  			if (env_path != null && env_path != String.Empty) {
  				return env_path;
  			}
  
  			string user_dirs_path = Path.Combine (config_dir, "user-dirs.dirs");
  
  			if (!File.Exists (user_dirs_path)) {
  				return Path.Combine (home_dir, fallback);
  			}
  
  			try {
  				using(StreamReader reader = new StreamReader (user_dirs_path)) {
  					string line;
  					while ((line = reader.ReadLine ()) != null) {
  						line = line.Trim ();
  						int delim_index = line.IndexOf ('=');
  						if(delim_index > 8 && line.Substring (0, delim_index) == key) {
  							string path = line.Substring (delim_index + 1).Trim ('"');
  							bool relative = false;
  							
  							if (path.StartsWithOrdinalUnchecked ("$HOME/")) {
  								relative = true;
  								path = path.Substring (6);
  							} else if (!path.StartsWithOrdinalUnchecked ("/")) {
  								relative = true;
  							}
  							
  							return relative ? Path.Combine (home_dir, path) : path;
  						}
  					}
  				}
  			} catch (FileNotFoundException) {
  			}
  
  			return Path.Combine (home_dir, fallback);
  		}
  
  
  		// the security runtime (and maybe other parts of corlib) needs the
  		// information to initialize themselves before permissions can be checked
  		internal static string UnixGetFolderPath (SpecialFolder folder, SpecialFolderOption option)
  		{
  			string home = internalGetHome ();
  
  			// http://freedesktop.org/Standards/basedir-spec/basedir-spec-0.6.html
  
  			// note: skip security check for environment variables
  			string data = internalGetEnvironmentVariable ("XDG_DATA_HOME");
  			if ((data == null) || (data == String.Empty)) {
  				data = Path.Combine (home, ".local");
  				data = Path.Combine (data, "share");
  			}
  
  			// note: skip security check for environment variables
  			string config = internalGetEnvironmentVariable ("XDG_CONFIG_HOME");
  			if ((config == null) || (config == String.Empty)) {
  				config = Path.Combine (home, ".config");
  			}
  
  			switch (folder) {
  			// MyComputer is a virtual directory
  			case SpecialFolder.MyComputer:
  				return String.Empty;
  
  			// personal == ~
  			case SpecialFolder.Personal:
  				return home;
  
  			// use FDO's CONFIG_HOME. This data will be synced across a network like the windows counterpart.
  			case SpecialFolder.ApplicationData:
  				return config;
  
  			//use FDO's DATA_HOME. This is *NOT* synced
  			case SpecialFolder.LocalApplicationData:
  				return data;
  
  			case SpecialFolder.Desktop:
  			case SpecialFolder.DesktopDirectory:
  				return ReadXdgUserDir (config, home, "XDG_DESKTOP_DIR", "Desktop");
  
  			case SpecialFolder.MyMusic:
  				if (Platform == PlatformID.MacOSX)
  					return Path.Combine (home, "Music");
  				else
  					return ReadXdgUserDir (config, home, "XDG_MUSIC_DIR", "Music");
  
  			case SpecialFolder.MyPictures:
  				if (Platform == PlatformID.MacOSX)
  					return Path.Combine (home, "Pictures");
  				else
  					return ReadXdgUserDir (config, home, "XDG_PICTURES_DIR", "Pictures");
  			
  			case SpecialFolder.Templates:
  				return ReadXdgUserDir (config, home, "XDG_TEMPLATES_DIR", "Templates");
  			case SpecialFolder.MyVideos:
  				return ReadXdgUserDir (config, home, "XDG_VIDEOS_DIR", "Videos");
  			case SpecialFolder.CommonTemplates:
  				return "/usr/share/templates";
  			case SpecialFolder.Fonts:
  				if (Platform == PlatformID.MacOSX)
  					return Path.Combine (home, "Library", "Fonts");
  				
  				return Path.Combine (home, ".fonts");
  			// these simply dont exist on Linux
  			// The spec says if a folder doesnt exist, we
  			// should return ""
  			case SpecialFolder.Favorites:
  				if (Platform == PlatformID.MacOSX)
  					return Path.Combine (home, "Library", "Favorites");
  				else
  					return String.Empty;
  				
  			case SpecialFolder.ProgramFiles:
  
gt: 				if (Platform == PlatformID.MacOSX)