1 /*---------------------------------------------------------------------------*
2 
3   Copyright (C) Nintendo.  All rights reserved.
4 
5   These coded instructions, statements, and computer programs contain
6   proprietary information of Nintendo of America Inc. and/or Nintendo
7   Company Ltd., and are protected by Federal copyright law.  They may
8   not be disclosed to third parties or copied or duplicated in any form,
9   in whole or in part, without the prior written consent of Nintendo.
10 
11  *---------------------------------------------------------------------------*/
12 using System;
13 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.IO;
16 using System.Linq;
17 using System.Text;
18 using System.Xml;
19 
20 namespace CafeX
21 {
22     /// <summary>
23     /// This class encapsulates the environment variables required by Cafe
24     /// </summary>
25     internal class EnvVar
26     {
27         private string var_name;
28         private string var_value;
29         private static Dictionary<string, EnvVar> dictionaryOfVariables = new Dictionary<string, EnvVar>();
30         private static Dictionary<string, string> dictionaryOfSavedValues = new Dictionary<string, string>();
31 
BackUpCurrentEnvironment()32         public static void BackUpCurrentEnvironment()
33         {
34             #if DEBUG
35                 Log.WriteLine("BackUpCurrentEnvironment started.");
36             #endif
37             dictionaryOfSavedValues.Clear();
38 
39             foreach (string variableName in dictionaryOfVariables.Keys)
40             {
41                 dictionaryOfSavedValues.Add(variableName, dictionaryOfVariables[variableName].value);
42             }
43             #if DEBUG
44                 Log.WriteLine("BackUpCurrentEnvironment completed.");
45             #endif
46         }
47 
RestoreSavedEnvironment()48         public static void RestoreSavedEnvironment()
49         {
50             #if DEBUG
51                 Log.WriteLine("RestoreSavedEnvironment started.");
52             #endif
53             if (dictionaryOfSavedValues.Count == 0)
54             {
55                 #if DEBUG
56                     Log.WriteLine("RestoreSavedEnvironment ignored. No backup available.");
57                 #endif
58 
59                 // No backup had been made, so there is no way to restore. Just return.
60                 return;
61             }
62 
63             foreach (string variableName in dictionaryOfVariables.Keys)
64             {
65                 // Set the respective envVar instance in the dictionary of variables.
66                 if (dictionaryOfSavedValues.ContainsKey(variableName))
67                 {
68                     EnvVar temp = dictionaryOfVariables[variableName];
69                     temp.value = dictionaryOfSavedValues[variableName];
70                 }
71             }
72             #if DEBUG
73                 Log.WriteLine("RestoreSavedEnvironment completed successfully.");
74             #endif
75         }
76 
EnvVar(string name)77         public EnvVar(string name)
78         {
79             if (string.IsNullOrEmpty(name))
80             {
81                 throw new ArgumentNullException("name cannot be null.");
82             }
83 
84             var_name = name;
85             var_value = Environment.GetEnvironmentVariable(var_name);
86             // Store the environment variable, so it can be backed-up and restored at will
87             EnvVar.dictionaryOfVariables.Add(name, this);
88         }
89 
90         public string value
91         {
92             get
93             {
94                 var_value = Environment.GetEnvironmentVariable(var_name);
95                 return var_value;
96             }
97 
98             set
99             {
100                 var_value = value;
101                 Environment.SetEnvironmentVariable(var_name, var_value);
102             }
103         }
104 
AddToVar(string addition, char delimiter)105         public void AddToVar(string addition, char delimiter)
106         {
107             if (value == null)
108                 value = addition;
109             else
110                 value = value + delimiter + addition;
111         }
112 
RemoveFromVar(string removal, char delimiter)113         public void RemoveFromVar(string removal, char delimiter)
114         {
115             if (string.IsNullOrEmpty(value))
116             {
117 #if DEBUG
118                 Log.WriteLine("RemoveFromVar: removal is empty!");
119 #endif
120 
121                 return;
122             }
123             if (string.IsNullOrEmpty(delimiter.ToString()))
124             {
125 #if DEBUG
126                 Log.WriteLine("RemoveFromVar: delimiter is null!");
127 #endif
128 
129                 return;
130             }
131 
132             int position = value.IndexOf(removal);
133 
134             if (position >= 0)
135             {
136                 int len = removal.Length;
137                 if (position > 0)
138                 {
139                     // There is more text in value than only the one we want to remove.
140                     // ex: data1 data2 removal data3
141                     // ex: data1 removal.
142                     // In those scenarios, we need to remove the delimiter char before, since AddToVar does value = value + delimiter + addition;
143                     --position;
144                     ++len;
145                 }
146 
147                 value = value.Remove(position, len);
148             }
149             else
150             {
151 #if DEBUG
152                 Log.WriteLine("RemoveFromVar: \"" + removal + "\" does not exist in the arg list!");
153 #endif
154                 return;
155             }
156         }
157 
158 
159     }
160 
161     /// <summary>
162     /// This class provides enables CafeX to work in both Windows and Cygwin environments by converting paths to the Windows format.
163     /// </summary>
164     internal static class PathConverter
165     {
166         /// <summary>
167         /// Ensure the provide path is rooted.
168         /// </summary>
169         /// <param name="input">Path to a file.</param>
170         /// <returns>Rooted path.</returns>
Root(string input)171         internal static string Root(string input)
172         {
173             if (Path.IsPathRooted(input))
174             {
175                 return input;
176             }
177             else
178             {
179                 return Environment.CurrentDirectory + "\\" + input;
180             }
181         }
182 
183         /// <summary>
184         /// Convert a Cygwin path to a Windows path.
185         /// If the input is already a valid Windows path, it is returned unchanged.
186         /// </summary>
187         /// <param name="input">Cywin path.</param>
188         /// <returns>Windows equivalent of the provided Cygwin path.</returns>
Windowsify(string input)189         internal static string Windowsify(string input)
190         {
191 #if DEBUG
192             Log.WriteLine("Windowsify input = " + input);
193 #endif
194 			if (string.IsNullOrEmpty(input))
195 			{
196 				return input;
197 			}
198 
199             int cygdriverIndex = -1;
200 
201             cygdriverIndex = input.IndexOf(@"/cygdrive///");
202 
203             if ((cygdriverIndex == 0 || cygdriverIndex == 1 ) &&
204                 !string.IsNullOrEmpty(CafeX.Program.CYGWIN_PATH.value) )
205             {
206                 // Special treatment
207                 input = input.Replace( @"/cygdrive///", Path.Combine(CafeX.Program.CYGWIN_PATH.value, "cygdrive/") );
208                 // it should continue with normal conversion to fix the \\
209             }
210 
211             // Normal conversion -> path starts with /cygdriver/<stuff>  or "/cygdriver/<stuff>"
212             cygdriverIndex = input.IndexOf("/cygdrive/");
213             if (cygdriverIndex == 0 || cygdriverIndex == 1)
214             {
215                 char drive_letter = input.ToCharArray()[10 + cygdriverIndex];
216                 input = input.Remove(0, 11 + cygdriverIndex);
217                 input = input.Replace('/', '\\');
218                 input = drive_letter + ":" + input;
219             }
220             else
221             {
222                 // This should be a path that doesn't start with cygdrive,
223                 // so just make sure it has the proper \\ instead of /
224                 input = input.Replace('/', '\\');
225             }
226 
227 #if DEBUG
228             Log.WriteLine("Windowsify return = " + input);
229 #endif
230             return input;
231         }
232     }
233 
234     /// <summary>
235     /// This class enables external processes to be started and monitored.
236     /// </summary>
237     internal static class ProcessExecutor
238     {
239         /// <summary>
240         /// Creates a process, starts it, wait for its execution and return its exit code.
241         /// </summary>
242         /// <param name="filename">Path to the executable to start on a different process.</param>
243         /// <param name="arguments">Arguments to the executable.</param>
244         /// <returns>The process' exit code.</returns>
DoProcess(string filename, string arguments)245         internal static int DoProcess(string filename, string arguments)
246         {
247             #if DEBUG
248                 Log.WriteLine("ProcessorExecutor.DoProcess : " + filename + " " + arguments);
249             #endif
250 
251             return DoProcess_WaitTime(filename, arguments, Int32.MaxValue);
252         }
253 
254         /// <summary>
255         /// Creates and starts a new process, but return immediately the execution to the caller.
256         /// The child's stdout and errout is printed to this process' stdout.
257         /// </summary>
258         /// <param name="filename">Path to the executable to start on a different process.</param>
259         /// <param name="arguments">Arguments to the executable.</param>
260         /// <returns>0 if the process is created, -1 otherwise.</returns>
DoProcess_DontWait(string filename, string arguments)261         internal static int DoProcess_DontWait(string filename, string arguments)
262         {
263             #if DEBUG
264                 Log.WriteLine("ProcessorExecutor.DoProcess_DontWait : " + filename + " " + arguments);
265             #endif
266 
267             try
268             {
269                 Process p = new Process();
270                 ProcessStartInfo si = new ProcessStartInfo(filename);
271                 si.UseShellExecute = false;
272                 si.CreateNoWindow = true;
273                 si.Arguments = arguments;
274                 si.RedirectStandardError = true;
275                 si.RedirectStandardOutput = true;
276                 p.StartInfo = si;
277                 // Won't wait, but need to connect stdout and stderr to this parent process.
278                 p.OutputDataReceived += (sender, args) => Console.WriteLine(args.Data);
279                 p.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data);
280                 p.Start();
281                 p.BeginOutputReadLine();
282                 p.BeginErrorReadLine();
283                 return 0;
284             }
285             catch (Exception e)
286             {
287                 Console.WriteLine("CafeX Error: Exception in process executor");
288                 Console.WriteLine(filename + " " + arguments);
289                 Console.WriteLine(e.Message);
290                 return -1;
291             }
292         }
293 
294         /// <summary>
295         /// Creates a process, starts it, and waits for a user-specified amount of time, then returns
296         /// the process's exit code
297         /// </summary>
298         /// <param name="filename">Path to the executable to start on a different process.</param>
299         /// <param name="arguments">Arguments to the executable</param>
300         /// <param name="time">Time in milliseconds to wait.  Max 32-bit number implies forever.</param>
301         /// <returns>The process' exit code</returns>
DoProcess_WaitTime(string filename, string arguments, int time)302         internal static int DoProcess_WaitTime(string filename, string arguments, int time)
303         {
304 #if DEBUG
305             Log.WriteLine(String.Format("ProcessorExecutor.DoProcessWaitTime : {0} {1} {2}",filename,arguments,time));
306 #endif
307             try
308             {
309                 Process p = new Process();
310                 ProcessStartInfo si = new ProcessStartInfo(filename);
311                 si.UseShellExecute = false;
312                 si.CreateNoWindow = true;
313                 si.Arguments = arguments;
314                 si.RedirectStandardError = true;
315                 si.RedirectStandardOutput = true;
316                 p.StartInfo = si;
317 
318                 p.Start();
319 
320                 if (!p.WaitForExit(time))
321                 {
322                     //Well this sucks.  Kill the process and hopefully the caller will try again
323                     Log.WriteLine(String.Format("{0} didn't exit in time, so force kill it.", filename));
324                     p.Kill();
325                     p.WaitForExit(); // This had better work this time!
326                 }
327                 int ret = p.ExitCode;
328                 p.Close();
329                 p.Dispose();
330                 p = null;
331 
332                 return ret;
333             }
334             catch (Exception e)
335             {
336                 Console.WriteLine("CafeX Error: Exception in process executor");
337                 Console.WriteLine(filename + " " + arguments);
338                 Console.WriteLine(e.Message);
339                 return -1;
340             }
341         }
342 
343         /// <summary>
344         /// Creates a process, starts it, wait for its execution and return its exit code.
345         /// The child's process stdout is captured and returned to the caller in the ouput variable.
346         /// </summary>
347         /// <param name="filename">Path to the executable to start on a different process.</param>
348         /// <param name="arguments">Arguments to the executable.</param>
349         /// <param name="output">Returns the child's process captured output the the caller.</param>
350         /// <returns>Chid's process exit code.</returns>
DoProcess_GetOutput(string filename, string arguments, out string output)351         internal static int DoProcess_GetOutput(string filename, string arguments, out string output)
352         {
353             #if DEBUG
354                 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments);
355             #endif
356 
357             return DoProcess_GetOutput(filename, arguments, false, -1, out output);
358         }
359 
360         /// <summary>
361         /// Creates a process, starts it, wait for its execution and return its exit code.
362         /// The child's process stdout is captured and returned to the caller in the ouput variable.
363         /// </summary>
364         /// <param name="filename">Path to the executable to start on a different process.</param>
365         /// <param name="arguments">Arguments to the executable.</param>
366         /// <param name="timeout">How long (in ms) to wait for the process to end. Set to values less than 1 to wait indefinately.</param>
367         /// <param name="output">Returns the child's process captured output the the caller.</param>
368         /// <returns>Chid's process exit code.</returns>
DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output)369         internal static int DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output)
370         {
371             #if DEBUG
372                 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments);
373             #endif
374 
375             try
376             {
377                 int ret;
378                 bool isDataReceived = !(waitDataReceived); // If we want to wait for data received event (waitDataReceived=true), need to set isDataReceived = false.
379                 System.Threading.ManualResetEvent dataReceivedEvent = new System.Threading.ManualResetEvent(false);
380                 TimeSpan DATA_RECEIVE_FLUSH_TIMEOUT = new TimeSpan(0, 0, 2);
381                 Process p = new Process();
382                 ProcessStartInfo si = new ProcessStartInfo(filename);
383                 si.UseShellExecute = false;
384                 si.CreateNoWindow = true;
385                 si.Arguments = arguments;
386                 StringBuilder outputTmp = new StringBuilder();
387                 DataReceivedEventHandler callback = (sender, e) =>
388                 {
389                     if (e != null && e.Data != null)
390                     {
391                         outputTmp.AppendLine( e.Data );
392                         if (!isDataReceived)
393                         {
394                             isDataReceived = true;
395                             dataReceivedEvent.Set();
396                         }
397                     }
398                 };
399 
400                 si.RedirectStandardError = true;
401                 si.RedirectStandardOutput = true;
402                 p.StartInfo = si;
403                 p.OutputDataReceived += callback;
404                 p.ErrorDataReceived += callback;
405                 p.Start();
406                 p.BeginOutputReadLine();
407                 p.BeginErrorReadLine();
408                 bool exitedProperly = true;
409 
410                 if (timeout > 0)
411                 {
412                     exitedProperly = p.WaitForExit(timeout);
413                     if (!exitedProperly)
414                     {
415                         Console.WriteLine("ERROR: Process started for '" + filename + " " + arguments + "' did not finish in the expected timeout (" + timeout.ToString() + "ms).");
416                         Console.WriteLine("Dumping process std/out std/err until now:\n {0} \n", outputTmp.ToString());
417                     }
418                 }
419                 else
420                 {
421                     p.WaitForExit();
422                 }
423 
424                 if (exitedProperly)
425                 {
426                     ret = p.ExitCode;
427                 }
428                 else
429                 {
430                     ret = -1;
431                 }
432 
433                 // Some output should have been received either in stdout or stderr, so wait
434                 if (isDataReceived == false)
435                 {
436                     dataReceivedEvent.WaitOne(DATA_RECEIVE_FLUSH_TIMEOUT);
437                 }
438 
439                 p.CancelOutputRead();
440                 p.CancelErrorRead();
441                 p.StartInfo.RedirectStandardOutput = false;
442                 p.StartInfo.RedirectStandardError = false;
443                 p.OutputDataReceived -= callback;
444                 p.ErrorDataReceived -= callback;
445 
446                 p.Close();
447                 p.Dispose();
448                 p = null;
449 
450                 output = outputTmp.ToString();
451 
452                 return ret;
453             }
454             catch (Exception e)
455             {
456                 output = string.Empty;
457                 Console.WriteLine("CafeX Error: Exception in process executor");
458                 Console.WriteLine(filename + " " + arguments);
459                 Console.WriteLine(e.Message);
460                 return -1;
461             }
462         }
463 
464         /// <summary>
465         /// Creates a process, starts it, wait for its execution and return its exit code.
466         /// The child's process stdout and errout are set to a callback handler provided.
467         /// </summary>
468         /// <param name="filename">Path to the executable to start on a different process.</param>
469         /// <param name="arguments">Arguments to the executable.</param>
470         /// <param name="callback">An instance of DataReceivedEventHandler provided by the caller.</param>
471         /// <returns>Chid's process exit code.</returns>
StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion)472         internal static int StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion)
473         {
474             #if DEBUG
475             Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments);
476             #endif
477 
478             try
479             {
480                 Process p = new Process();
481                 ProcessStartInfo si = new ProcessStartInfo(filename);
482                 si.UseShellExecute = false;
483                 si.CreateNoWindow = true;
484                 si.Arguments = arguments;
485                 si.RedirectStandardError = true;
486                 si.RedirectStandardOutput = true;
487                 p.StartInfo = si;
488                 p.OutputDataReceived += callback;
489                 p.ErrorDataReceived += callback;
490 
491                 p.Start();
492 
493                 p.BeginOutputReadLine();
494                 p.BeginErrorReadLine();
495 
496                 if (waitCompletion)
497                 {
498                     p.WaitForExit();
499                     int ret = p.ExitCode;
500                     p.CancelOutputRead();
501                     p.CancelErrorRead();
502                     p.StartInfo.RedirectStandardOutput = false;
503                     p.StartInfo.RedirectStandardError = false;
504                     p.OutputDataReceived -= callback;
505                     p.ErrorDataReceived -= callback;
506                     p.Close();
507                     p.Dispose();
508                     p = null;
509 
510                     return ret;
511 
512                 }
513                 else
514                 {
515                     return 0;
516                 }
517 
518             }
519             catch (Exception e)
520             {
521                 Console.WriteLine("CafeX Error: Exception in process executor");
522                 Console.WriteLine(filename + " " + arguments);
523                 Console.WriteLine(e.Message);
524                 return -1;
525             }
526         }
527 
StartProcess_RedirectOutput(string filename, string arguments)528         internal static int StartProcess_RedirectOutput(string filename, string arguments)
529         {
530 #if DEBUG
531             Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments);
532 #endif
533 
534             try
535             {
536                 Process p = new Process();
537                 ProcessStartInfo si = new ProcessStartInfo(filename);
538                 si.UseShellExecute = false;
539                 si.CreateNoWindow = true;
540                 si.Arguments = arguments;
541                 si.RedirectStandardError = true;
542                 si.RedirectStandardOutput = true;
543                 si.RedirectStandardInput = true;
544                 p.StartInfo = si;
545 
546                 p.Start();
547 
548                 p.BeginOutputReadLine();
549 
550                 return 0;
551             }
552             catch (Exception e)
553             {
554                 Console.WriteLine("CafeX Error: Exception in process executor");
555                 Console.WriteLine(filename + " " + arguments);
556                 Console.WriteLine(e.Message);
557                 return -1;
558             }
559         }
560     }
561 
562     /// <summary>
563     /// This class allows string arrays to be combined into a single string with the delimiter of choice.
564     /// </summary>
565     internal static class StringCombiner
566     {
MakeDelimitedString(string[] arr, char delimiter)567         internal static string MakeDelimitedString(string[] arr, char delimiter)
568         {
569             string ret = string.Empty;
570             if (arr != null)
571             {
572                 for (int i = 0; i < arr.Length; ++i)
573                 {
574                     if (i == 0)
575                     {
576                         ret += arr[i];
577                     }
578                     else
579                     {
580                         ret += delimiter + arr[i];
581                     }
582                 }
583             }
584 
585             return ret;
586         }
587     }
588 
589     /// <summary>
590     /// This class is used to edit xml content files.
591     /// </summary>
592     internal static class XmlHandler
593     {
594         /// <summary>
595         /// Traverses the xml document from the provided root node until it finds the node that matches the provided name.
596         /// </summary>
597         /// <param name="root_name">Root node</param>
598         /// <param name="node_name">Name of the node being searched</param>
599         /// <returns>The node that matches the provided node name, null if it was not found.</returns>
GetXmlNodeRecursive(XmlNode root_name, string node_name)600         internal static XmlNode GetXmlNodeRecursive(XmlNode root_name, string node_name)
601         {
602             if (root_name.Name == node_name)
603                 return root_name;
604 
605             XmlNode ret = null;
606             foreach (XmlNode child in root_name.ChildNodes)
607             {
608                 ret = GetXmlNodeRecursive(child, node_name);
609                 if (ret != null)
610                     break;
611             }
612 
613             return ret;
614         }
615 
616         /// <summary>
617         /// Check if a node by the provided name exists inside an xml document.
618         /// </summary>
619         /// <param name="file_name">Full path to the xml document.</param>
620         /// <param name="node_name">Name of the node to search.</param>
621         /// <returns>True if the node was found, false otherwise.</returns>
NodeExists(string file_name, string node_name)622         internal static bool NodeExists(string file_name, string node_name)
623         {
624 #if DEBUG
625             Log.WriteLine(string.Format("NodeExists: File: {0}, Node: {1}", file_name, node_name));
626 #endif
627             XmlDocument document = new XmlDocument();
628             document.Load(file_name);
629             XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name);
630             return (node == null) ? false : true;
631         }
632 
633         /// <summary>
634         /// Returns the value of a node from an existing xml document.
635         /// </summary>
636         /// <param name="file_name">Full path to the xml document.</param>
637         /// <param name="node_name">Name of the node to search.</param>
638         /// <returns>String representing the node's value, null if the node was not found.</returns>
GetNodeValue(string file_name, string node_name)639         internal static string GetNodeValue(string file_name, string node_name)
640         {
641             #if DEBUG
642                 Log.WriteLine("GetNodeValue called. file_name=" + file_name + ", node_name=" + node_name);
643             #endif
644             XmlDocument document = new XmlDocument();
645             document.Load(file_name);
646             XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name);
647             if (node == null)
648             {
649                 Console.WriteLine("cafex xmlhandler getnodevalue failed: Node " + node_name + " does not exist in " + file_name);
650                 return null;
651             }
652             #if DEBUG
653                 Log.WriteLine("GetNodeValue return = " + node.InnerText);
654             #endif
655             return node.InnerText;
656         }
657 
658         /// <summary>
659         /// Updates the value of a node inside an xml document.
660         /// </summary>
661         /// <param name="file_name">Full path to the xml document.</param>
662         /// <param name="node_name">Name of the node to search.</param>
663         /// <param name="new_value">The new value for the node.</param>
UpdateNodeValue(string file_name, string node_name, string new_value)664         internal static void UpdateNodeValue(string file_name, string node_name, string new_value)
665         {
666             #if DEBUG
667                 Log.WriteLine("UpdateNodeValue called. file_name=" + file_name + ", node_name=" + node_name + ", new_value=" + new_value);
668             #endif
669             XmlDocument document = new XmlDocument();
670             document.Load(file_name);
671             XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name);
672             if (node == null)
673             {
674                 Console.WriteLine("cafex xmlhandler updatenodevalue failed: Node " + node_name + " does not exist in " + file_name);
675                 return;
676             }
677 
678             // Only update if necessary.
679             if (node.InnerText != new_value)
680             {
681                 if (File.Exists(file_name))
682                 {
683                     // Strip the read-only attribute so we can overwrite it if it exists
684                     File.SetAttributes(file_name, File.GetAttributes(file_name) & ~FileAttributes.ReadOnly);
685                 }
686 
687                 // Before the update, check if the file is not locked for 3 times
688                 uint retries = Program.RETRY_ATTEMPS;
689                 while (FileUtil.IsFileLocked(file_name) && retries > 0)
690                 {
691                     System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN);
692                     --retries;
693                 }
694 
695                 node.InnerText = new_value;
696                 #if DEBUG
697                     Log.WriteLine("updateNodeValue: file: " + file_name + ", node: " + node_name + ", newvalue: " + new_value);
698                 #endif
699                 XmlWriterSettings settings = new XmlWriterSettings();
700                 settings.NewLineChars = "\n";
701                 settings.Indent = true;
702                 settings.Encoding = new UTF8Encoding(false);
703                 using (XmlWriter writer = XmlWriter.Create(file_name, settings))
704                 {
705                     document.WriteTo(writer);
706                     writer.Flush();
707                     writer.Close();
708                 }
709 
710                 File.SetAttributes(file_name, FileAttributes.Normal);
711                 #if DEBUG
712                     Log.WriteLine("updateNodeValue completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name));
713                 #endif
714             }
715         }
716 
717         /// <summary>
718         /// Add an xml node to an xml document.
719         /// </summary>
720         /// <param name="file_name">Full path to the xml document.</param>
721         /// <param name="node_name">Name for the new xml node.</param>
722         /// <param name="value">Node's value.</param>
723         /// <param name="attributes">Array of strings representing the node's attributes.</param>
AddNode(string file_name, string node_name, string value, string[] attributes)724         internal static void AddNode(string file_name, string node_name, string value, string[] attributes)
725         {
726 #if DEBUG
727             Log.WriteLine("AddNode called. file_name=" + file_name + ", node_name=" + node_name + ", value=" + value);
728             string attrString = null;
729             foreach (string attribute in attributes)
730             {
731                 attrString += attribute + " ";
732             }
733             Log.WriteLine("Attributes = " + attrString);
734 #endif
735             XmlDocument document = new XmlDocument();
736             document.Load(file_name);
737             XmlElement node = document.CreateElement(node_name);
738             node.InnerText = value;
739 
740             for (int i = 0; i < attributes.Length; i += 2)
741             {
742                 node.SetAttribute(attributes[i], attributes[i + 1]);
743             }
744 
745             document.DocumentElement.AppendChild(node);
746             #if DEBUG
747                 Log.WriteLine("AddNode: file: " + file_name + ", node: " + node_name + ", value: " + value);
748             #endif
749             XmlWriterSettings settings = new XmlWriterSettings();
750             settings.NewLineChars = "\n";
751             settings.Indent = true;
752             settings.Encoding = new UTF8Encoding(false);
753             // Before the update, check if the file is not locked for 3 times
754             uint retries = Program.RETRY_ATTEMPS;
755             while (FileUtil.IsFileLocked(file_name) && retries > 0)
756             {
757                 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s
758                 --retries;
759             }
760 
761             using (XmlWriter writer = XmlWriter.Create(file_name, settings))
762             {
763                 document.WriteTo(writer);
764                 writer.Flush();
765                 writer.Close();
766             }
767 
768             File.SetAttributes(file_name, FileAttributes.Normal);
769             #if DEBUG
770                 Log.WriteLine("AddNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name));
771             #endif
772         }
773 
774         /// <summary>
775         /// Remove a node from an xml document.
776         /// </summary>
777         /// <param name="file_name">Full path to the xml document.</param>
778         /// <param name="node_name">Name for the node to search.</param>
RemoveNode(string file_name, string node_name)779         internal static void RemoveNode(string file_name, string node_name)
780         {
781 #if DEBUG
782             Log.WriteLine("RemoveNode called. file_name=" + file_name + ", node_name=" + node_name);
783 #endif
784             XmlDocument document = new XmlDocument();
785             document.Load(file_name);
786             XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name);
787             if (node == null)
788             {
789                 return;
790             }
791             #if DEBUG
792                 Log.WriteLine("RemoveNode: file: " + file_name + ", node: " + node_name);
793             #endif
794             document.DocumentElement.RemoveChild(node);
795 
796             XmlWriterSettings settings = new XmlWriterSettings();
797             settings.NewLineChars = "\n";
798             settings.Indent = true;
799             settings.Encoding = new UTF8Encoding(false);
800             // Before the update, check if the file is not locked for 3 times
801             uint retries = Program.RETRY_ATTEMPS;
802             while (FileUtil.IsFileLocked(file_name) && retries > 0)
803             {
804                 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s
805                 --retries;
806             }
807 
808             using (XmlWriter writer = XmlWriter.Create(file_name, settings))
809             {
810                 document.WriteTo(writer);
811                 writer.Flush();
812                 writer.Close();
813             }
814 
815             File.SetAttributes(file_name, FileAttributes.Normal);
816             #if DEBUG
817                 Log.WriteLine("RemoveNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name));
818             #endif
819         }
820 
UpdateNodeValueByPath(string file_name, string xpath, string new_value)821         internal static void UpdateNodeValueByPath(string file_name, string xpath, string new_value)
822         {
823 #if DEBUG
824             Log.WriteLine("UpdateNodeValueByPath called.  file_name=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value);
825 #endif
826 
827             XmlDocument document = new XmlDocument();
828             document.Load(file_name);
829 
830             try
831             {
832                 XmlNode target = document.SelectSingleNode(xpath);
833 
834                 if (target == null)
835                 {
836                     Console.WriteLine("cafex xmlhandler UpdateNodeValueByPath failed: Node specified by path " + xpath + " does not exist in " + file_name);
837                     return;
838                 }
839 
840                 if (target.InnerText != new_value)
841                 {
842                     uint retries = Program.RETRY_ATTEMPS;
843                     while (FileUtil.IsFileLocked(file_name) && retries > 0)
844                     {
845                         System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN);
846                         --retries;
847                     }
848 
849                     target.InnerText = new_value;
850 #if DEBUG
851                     Log.WriteLine("UpdateNodeValueByPath:  file=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value);
852 #endif
853                     XmlWriterSettings settings = new XmlWriterSettings();
854                     settings.NewLineChars = "\n";
855                     settings.Indent = true;
856                     settings.Encoding = new UTF8Encoding(false);
857                     using (XmlWriter writer = XmlWriter.Create(file_name, settings))
858                     {
859                         document.WriteTo(writer);
860                         writer.Flush();
861                         writer.Close();
862                     }
863 
864                     File.SetAttributes(file_name, FileAttributes.Normal);
865 #if DEBUG
866                     Log.WriteLine("UpdateNodeValueByPath completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name));
867 #endif
868                 }
869             }
870             catch (System.Xml.XPath.XPathException)
871             {
872                 Console.WriteLine("UpdateNodeValueByPath contained an invalid xpath parameter! xpath={0}\nNode will not be updated!",xpath);
873                 return;
874             }
875         }
876     }
877 
878     /// <summary>
879     /// Represents a title id hex string.
880     /// </summary>
881     internal class TitleId
882     {
883         private const int TitleIdLength = 16;
884         private const int TitleIdHalfLength = TitleIdLength / 2;
885         private static readonly string[] SystemTitleRanges = { "00050010", "0005001b", "00050030" };
886         private const string HexPrefix = "0x";
887 
888         private string titleIdAsString;
889 
890         /// <summary>
891         /// Creates new instance of TitleId, verifying that the input string is of the correct format.
892         /// </summary>
893         /// <param name="titleId">Title ID as string</param>
894         /// <remarks>Throws <see cref="ArgumentException"/> on parsing failure.</remarks>
TitleId(string titleId)895         public TitleId(string titleId)
896         {
897             ulong titleIdAsLong;
898 
899             if (titleId.StartsWith(HexPrefix))
900             {
901                 titleId = titleId.Remove(0, HexPrefix.Length);
902             }
903 
904             if (titleId.Length != TitleIdLength)
905             {
906                 throw new ArgumentException("Title ID has incorrect length. A correct ID is 18 characters with the leading 0x, or 16 characters without 0x.");
907             }
908 
909             if (!UInt64.TryParse(titleId, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out titleIdAsLong))
910             {
911                 throw new ArgumentException("Title ID has invalid characters. Only hexadecimal characters are allowed.");
912             }
913 
914             this.titleIdAsString = titleId.StartsWith(HexPrefix) ? titleId.ToLowerInvariant() : string.Format("{0}{1}", HexPrefix, titleId).ToLowerInvariant();
915         }
916 
917         /// <summary>
918         /// Returns true if the input string is a valid title ID.
919         /// </summary>
920         /// <param name="input">Title ID as string</param>
921         /// <returns>True if parsed, else false.</returns>
TryParse(string input, out TitleId titleId)922         public static bool TryParse(string input, out TitleId titleId)
923         {
924             try
925             {
926                 titleId = new TitleId(input);
927                 return true;
928             }
929             catch (ArgumentException)
930             {
931                 titleId = null;
932                 return false;
933             }
934         }
935 
936         /// <summary>
937         /// Returns true if the high value of the title ID matches one of the system title high values.
938         /// </summary>
939         public bool IsSystemTitle
940         {
941             get
942             {
943                 return SystemTitleRanges.Any(x => x == HighValue);
944             }
945         }
946 
947         /// <summary>
948         /// Returns value of the title ID, with leading hex prefix.
949         /// </summary>
950         public string Value
951         {
952             get
953             {
954                 return this.titleIdAsString;
955             }
956         }
957 
958         /// <summary>
959         /// Returns value of the title ID, without the leading hex prefix.
960         /// </summary>
961         public string ValueWithoutPrefix
962         {
963             get
964             {
965                 return this.titleIdAsString.Substring(HexPrefix.Length, TitleIdLength);
966             }
967         }
968 
969         /// <summary>
970         /// High value (most significant bits) of the title ID.
971         /// </summary>
972         public string HighValue
973         {
974             get
975             {
976                 return this.titleIdAsString.Substring(HexPrefix.Length, TitleIdHalfLength);
977             }
978         }
979 
980         /// <summary>
981         /// Low value (least significant bits) of the title ID.
982         /// </summary>
983         public string LowValue
984         {
985             get
986             {
987                 return this.titleIdAsString.Substring(HexPrefix.Length + TitleIdHalfLength, TitleIdHalfLength);
988             }
989         }
990     }
991 
992     /// <summary>
993     /// This class is a wrapper on HEX conversion functions used by CafeX.
994     /// </summary>
995     internal static class HexHandler
996     {
997         /// <summary>
998         /// Transform the input string into its integer representation.
999         /// Strings that are invalid representations of integers will cause exceptions.
1000         /// </summary>
1001         /// <param name="input">String representing a number.</param>
1002         /// <returns>The integer that represents the provided string.</returns>
GetNumberFromString(string input)1003         internal static uint GetNumberFromString(string input)
1004         {
1005 #if DEBUG
1006             Log.WriteLine("GetNumberFromString called. input=" + input);
1007 #endif
1008             if (input.StartsWith("0x"))
1009             {
1010                 return Convert.ToUInt32(input, 16);
1011             }
1012             else
1013             {
1014                 foreach (char c in input)
1015                 {
1016                     if ((c >= 'a' || c >= 'A') &&
1017                         (c <= 'f' || c <= 'F'))
1018                     {
1019                         return Convert.ToUInt32(input, 16);
1020                     }
1021                 }
1022 
1023                 return Convert.ToUInt32(input);
1024             }
1025         }
1026 
1027         /// <summary>
1028         /// Transform the input string into its long representation.
1029         /// Strings that are invalid representations of longs will cause exceptions.
1030         /// </summary>
1031         /// <param name="input">String representing a number.</param>
1032         /// <returns>The long that represents the provided string.</returns>
GetLongNumberFromString(string input)1033         internal static long GetLongNumberFromString(string input)
1034         {
1035 #if DEBUG
1036             Log.WriteLine("GetLongNumberFromString called. input=" + input);
1037 #endif
1038 
1039             if (input.StartsWith("0x"))
1040             {
1041                 return Convert.ToInt64(input, 16);
1042             }
1043             else
1044             {
1045                 foreach (char c in input)
1046                 {
1047                     if ((c >= 'a' || c >= 'A') &&
1048                         (c <= 'f' || c <= 'F'))
1049                     {
1050                         return Convert.ToInt64(input, 16);
1051                     }
1052                 }
1053                 return Convert.ToInt64(input);
1054             }
1055         }
1056     }
1057 
1058     /// <summary>
1059     /// Utilities for file verification.
1060     /// </summary>
1061     internal static class FileUtil
1062     {
1063         private static string cafex_system_wide_mtx_name = "Global\\cafex_system_wide_mtx";
1064         private static DateTime entryAssemblyTs;
1065         private static bool bRunningFromCygwin = false;
1066         private static string assemblyFileName;
1067 
1068 
1069         /// <summary>
1070         /// Initializes one-shot only utility variables
1071         /// </summary>
GatherEnvironment()1072         internal static void GatherEnvironment()
1073         {
1074             string cygcheck;
1075             assemblyFileName = System.Reflection.Assembly.GetExecutingAssembly().Location;
1076             entryAssemblyTs = File.GetLastWriteTimeUtc(assemblyFileName);
1077             cygcheck = Environment.GetEnvironmentVariable("CYGCHECK");
1078             if ( !(string.IsNullOrEmpty(cygcheck)) )
1079             {
1080                 if( cygcheck.StartsWith("cygwin" ) )
1081                 {
1082                     bRunningFromCygwin = true;
1083                 }
1084             }
1085         }
1086 
1087 
1088         /// <summary>
1089         /// Determine whether we are running from within cygwin
1090         /// </summary>
1091         internal static bool RunningFromCygwin
1092         {
1093             get { return bRunningFromCygwin; }
1094         }
1095 
1096         /// <summary>
1097         /// Check if a file is locked for R/W and/or delete.
1098         /// </summary>
1099         /// <param name="filename">Full path to the file.</param>
1100         /// <returns>True if the file is locked, false otherwise.</returns>
IsFileLocked(string filename)1101         internal static bool IsFileLocked(string filename)
1102         {
1103             FileStream stream = null;
1104             bool result = false;
1105 
1106             try
1107             {
1108                 FileInfo file = new FileInfo(filename);
1109                 stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
1110                 result = false;
1111             }
1112             catch (IOException)
1113             {
1114                 //the file is unavailable because it is:
1115                 //a) still being written to
1116                 //b) or being processed by another thread (maybe held by PCFS)
1117                 //c) or does not exist (has already been processed)
1118 #if DEBUG
1119                 // Collect debug information to help troubleshoot who has this file handle
1120                 try
1121                 {
1122                     Process handleTool = new Process();
1123                     handleTool.StartInfo.FileName = "handle.exe";
1124                     handleTool.StartInfo.Arguments = filename + " /accepteula";
1125                     handleTool.StartInfo.UseShellExecute = false;
1126                     handleTool.StartInfo.RedirectStandardOutput = true;
1127                     handleTool.Start();
1128                     handleTool.WaitForExit();
1129                     string handleOutput = handleTool.StandardOutput.ReadToEnd();
1130                     Log.WriteLine("############## FILE LOCKED WARNING REPORT ##############");
1131                     Log.WriteLine("Filename = " + filename);
1132                     Log.WriteLine("Handles opened:");
1133                     Log.WriteLine(handleOutput);
1134                     Log.WriteLine("############## FILE LOCKED END OF REPORT  ##############");
1135 
1136 
1137                 }
1138                 catch (Exception)
1139                 {
1140                     Log.WriteLine("Unable to get handle information on file " + filename);
1141                 }
1142                 finally
1143 #endif
1144                 {
1145                     result = true;
1146                 }
1147             }
1148             finally
1149             {
1150                 if (stream != null)
1151                 {
1152                     stream.Close();
1153                 }
1154             }
1155 
1156             return result;
1157 
1158         }
1159 
1160 
1161 
1162         /// <summary>
1163         /// Expand an executable to the temp folder from a resource location
1164         /// </summary>
1165         /// <param name="payload">Executable bytes from resource</param>
1166         /// <param name="filename">File to be written to disk</param>
1167         /// <returns>Full path to temporary file written to disk.</returns>
ExpandExecutable(byte[] payload, string filename)1168         internal static string ExpandExecutable(byte[] payload, string filename)
1169         {
1170             System.Threading.Mutex mtx = null;
1171             string full_tmp_path;
1172 
1173             full_tmp_path = Environment.GetEnvironmentVariable("CAFE_TEMP");
1174 
1175             if( string.IsNullOrEmpty(full_tmp_path) )
1176             {
1177                 full_tmp_path = Environment.GetEnvironmentVariable("TEMP");
1178                 if( string.IsNullOrEmpty(full_tmp_path)  )
1179                 {
1180                     full_tmp_path = Environment.GetEnvironmentVariable("TMP");
1181                     if ( string.IsNullOrEmpty(full_tmp_path) )
1182                     {
1183                         full_tmp_path = Path.GetDirectoryName(assemblyFileName);
1184                     }
1185                 }
1186             }
1187 
1188 
1189             full_tmp_path = PathConverter.Windowsify(full_tmp_path) + "\\" + filename;
1190 
1191             // the kinds of things that can prevent this from happening are all fatal
1192 			// errors, don't try to catch anything
1193             mtx = new System.Threading.Mutex(false, cafex_system_wide_mtx_name);
1194 
1195             mtx.WaitOne();
1196 
1197             // from this point on, we must release the mutex no matter what happens!
1198 
1199             try
1200             {
1201                 DateTime tempExeTs;
1202                 bool doOverwrite = false;
1203                 try
1204                 {
1205                     tempExeTs = File.GetLastWriteTimeUtc(full_tmp_path);
1206 
1207                     // if the internal resource is newer than the file on disk
1208                     if (entryAssemblyTs >= tempExeTs)
1209                     {
1210                         doOverwrite = true;
1211                     }
1212                     // else, we will use the file that's there, as it is newer than we are
1213                 }
1214                 catch (FileNotFoundException)
1215                 {
1216                     // file's not there - we will overwrite it
1217                     doOverwrite = true;
1218                 }
1219 
1220                 if (doOverwrite)
1221                 {
1222 					try
1223 					{
1224                     	File.WriteAllBytes(full_tmp_path, payload);
1225 					}
1226 					catch( System.IO.DirectoryNotFoundException )
1227 					{
1228 						try
1229 						{
1230 							Directory.CreateDirectory( Path.GetDirectoryName(full_tmp_path) );
1231 						}
1232 						catch
1233 						{
1234 						}
1235 						File.WriteAllBytes(full_tmp_path, payload);
1236 					}
1237 
1238                 }
1239             }
1240             finally
1241             {
1242                 mtx.ReleaseMutex();
1243             }
1244 
1245             return full_tmp_path;
1246 
1247         }
1248 
1249         /// <summary>
1250         /// Check if the provide string is a valid file name.
1251         /// </summary>
1252         /// <param name="filename">String to check.</param>
1253         /// <returns>True if it is a valid file name, false otherwise.</returns>
IsValidFile(string filename, bool checkExistence)1254         internal static bool IsValidFile(string filename, bool checkExistence)
1255         {
1256 #if DEBUG
1257             Log.WriteLine("IsValidFile called. filename=" + filename + ", checkExistence=" + checkExistence.ToString());
1258 #endif
1259             if (string.IsNullOrEmpty(filename))
1260             {
1261                 return false;
1262             }
1263 
1264             // Convert to windows, if it was a cygwin path
1265             filename = PathConverter.Windowsify(filename);
1266             bool returnVal = false;
1267             try
1268             {
1269                 FileInfo file = new FileInfo(filename);
1270                 // A path without a filename resolves to a valid FileInfo instance
1271                 // so we need to check if there is a file at the end of the path.
1272                 if (Path.GetFileName(file.FullName).Length == 0)
1273                 {
1274                     // There is no filename, only a valid path!
1275                     return false;
1276                 }
1277 
1278                 if (!checkExistence)
1279                 {
1280                     returnVal = true;
1281                 }
1282                 else
1283                 {
1284                     returnVal = file.Exists;
1285                 }
1286             }
1287             catch (ArgumentException)
1288             {
1289             }
1290             catch (System.IO.PathTooLongException)
1291             {
1292             }
1293             catch (NotSupportedException)
1294             {
1295             }
1296 
1297 #if DEBUG
1298             Log.WriteLine("IsValidFile return " + returnVal.ToString());
1299 #endif
1300             return returnVal;
1301         }
1302 
AddQuotes(ref string fileNameOrListWithSpaces)1303         internal static void AddQuotes(ref string fileNameOrListWithSpaces)
1304         {
1305 #if DEBUG
1306             Log.WriteLine("AddQuotes called. " + "fileNameOrListWithSpaces=" + fileNameOrListWithSpaces);
1307 #endif
1308             if (string.IsNullOrEmpty(fileNameOrListWithSpaces))
1309             {
1310                 return;
1311             }
1312 
1313             if (fileNameOrListWithSpaces.StartsWith("\"") && fileNameOrListWithSpaces.EndsWith("\""))
1314             {
1315                 return;
1316             }
1317 
1318             fileNameOrListWithSpaces = "\"" + fileNameOrListWithSpaces + "\"";
1319         }
1320 
1321 
1322         /// <summary>
1323         /// Creates a directory if it does not already exist.
1324         /// </summary>
1325         /// <param name="directoryPath">Directory to create</param>
DirectoryCreate(string directoryPath)1326         internal static void DirectoryCreate(string directoryPath)
1327         {
1328 #if DEBUG
1329             Console.WriteLine("FileUtil::DirectoryCreate: Creating '{0}' if it does not already exist", directoryPath);
1330 #endif
1331             if (!Directory.Exists(directoryPath))
1332             {
1333                 Directory.CreateDirectory(directoryPath);
1334             }
1335         }
1336 
1337 
1338         /// <summary>
1339         /// Copy a directory from one location to another with optional recursion.
1340         /// </summary>
1341         /// <param name="sourceDirName">Source folder name.</param>
1342         /// <param name="destDirName">Destination folder name.</param>
1343         /// <param name="copySubDirs">Flag to copy all of the sub-folders recurively.</param>
DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteFiles)1344         internal static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteFiles)
1345         {
1346 #if DEBUG
1347             Console.WriteLine("FileUtil::DirectoryCopy: Copying '{0}' => '{1}'{2}", sourceDirName, destDirName, copySubDirs ? " recursively" : "");
1348 #endif
1349             // Get the subdirectories for the specified directory.
1350             DirectoryInfo dir = new DirectoryInfo(sourceDirName);
1351             DirectoryInfo[] dirs = dir.GetDirectories();
1352 
1353             if (!dir.Exists)
1354             {
1355                 throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName);
1356             }
1357 
1358             // If the destination directory doesn't exist, create it.
1359             if (!Directory.Exists(destDirName))
1360             {
1361                 Directory.CreateDirectory(destDirName);
1362             }
1363 
1364             // Get the files in the directory and copy them to the new location.
1365             FileInfo[] files = dir.GetFiles();
1366             foreach (FileInfo file in files)
1367             {
1368                 string temppath = Path.Combine(destDirName, file.Name);
1369                 file.CopyTo(temppath, overwriteFiles);
1370             }
1371 
1372             // If copying subdirectories, copy them and their contents to new location.
1373             if (copySubDirs)
1374             {
1375                 foreach (DirectoryInfo subdir in dirs)
1376                 {
1377                     string temppath = Path.Combine(destDirName, subdir.Name);
1378                     DirectoryCopy(subdir.FullName, temppath, copySubDirs, overwriteFiles);
1379                 }
1380             }
1381         }
1382 
1383         /// <summary>
1384         /// Deletes the content of a directory with optional recursion
1385         /// </summary>
1386         /// <param name="dirName">Directory folder name.</param>
1387         /// <param name="recursive">Flag to remove subdirectories recursively.</param>
DirectoryDelete(string dirName, bool recursive)1388         internal static void DirectoryDelete(string dirName, bool recursive)
1389         {
1390             if (Directory.Exists(dirName))
1391             {
1392 #if DEBUG
1393                 Console.WriteLine("FileUtil::DirectoryDelete: Deleting '{0}'{1}", dirName, recursive ? " recursively" : "");
1394 #endif
1395                 DirectoryInfo di = new DirectoryInfo(dirName);
1396 
1397                 RemoveReadOnlyAttribute(di, recursive);
1398 
1399                 // If the directory is a junction, this command will still work correctly.
1400                 // That is, only the junction is deleted and not the contents of the linked directory.
1401                 // Otherwise, the directory and its contents delete normally.
1402                 di.Delete(recursive);
1403             }
1404         }
1405 
RemoveReadOnlyAttribute(DirectoryInfo dirInfo, bool recursive)1406         internal static void RemoveReadOnlyAttribute(DirectoryInfo dirInfo, bool recursive)
1407         {
1408             // Change attributes of source directory
1409             if ((dirInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
1410             {
1411                 dirInfo.Attributes = FileAttributes.Normal;
1412             }
1413             // Change all of the attributes in the directory specified by dirInfo
1414             foreach (FileInfo fi in dirInfo.GetFiles())
1415             {
1416                 if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
1417                 {
1418                     //don't modify any other flags.
1419                     fi.Attributes &= ~(FileAttributes.ReadOnly);
1420                 }
1421             }
1422             // Recursively change attributes in all files in all directories if specified
1423             if (recursive)
1424             {
1425                 //Can't use actual recursion because there would be a stack overflow.
1426                 foreach (DirectoryInfo di in dirInfo.GetDirectories())
1427                 {
1428                     // Change attributes of current directory
1429                     if ((di.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
1430                     {
1431                         //don't modify any other flags.
1432                         di.Attributes &= ~(FileAttributes.ReadOnly);
1433                     }
1434                     // Change all of the attributes in the directory specified by dirInfo
1435                     foreach (FileInfo fi in di.GetFiles())
1436                     {
1437                         if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
1438                         {
1439                             //don't modify any other flags.
1440                             fi.Attributes &= ~(FileAttributes.ReadOnly);
1441                         }
1442                     }
1443                 }
1444             }
1445         }
1446 
FileDelete(string fileName)1447         internal static void FileDelete(string fileName)
1448         {
1449 #if DEBUG
1450             Console.WriteLine("FileUtil::FileDelete: Deleting '{0}'", fileName);
1451 #endif
1452             if (File.Exists(fileName))
1453             {
1454                 // Strip any readonly, hidden and system attributes
1455                 FileInfo fileInfo = new FileInfo(fileName);
1456                 fileInfo.Attributes = FileAttributes.Normal;
1457 
1458                 // Now delete the file
1459                 File.Delete(fileName);
1460             }
1461         }
1462 
1463         /// <summary>
1464         /// Copies a file exactly (preserves timestamp). Creates target directory for file if needed.
1465         /// </summary>
1466         /// <param name="copyFromPath">Source file</param>
1467         /// <param name="copyToPath">Destination file</param>
FileCopy(string copyFromPath, string copyToPath)1468         internal static void FileCopy(string copyFromPath, string copyToPath)
1469         {
1470 #if DEBUG
1471             Console.WriteLine("FileUtil::FileCopy: Copying '{0}' to '{1}'", copyFromPath, copyToPath);
1472 #endif
1473             // Strip read-only attriute if destination file already exists.
1474             if (File.Exists(copyToPath))
1475             {
1476                 var target = new FileInfo(copyToPath);
1477                 if (target.IsReadOnly)
1478                     target.IsReadOnly = false;
1479             }
1480 
1481             // Create destination dir if it doesn't already exist.
1482             string copyToDir = Path.GetDirectoryName(copyToPath);
1483             DirectoryCreate(copyToDir);
1484 
1485             // Now do the copy.
1486             var origin = new FileInfo(copyFromPath);
1487             origin.CopyTo(copyToPath, true);
1488 
1489             // And preserve its timestamp info and read-only attribute.
1490             var destination = new FileInfo(copyToPath);
1491             if (destination.IsReadOnly)
1492             {
1493                 destination.IsReadOnly = false;
1494                 destination.CreationTime = origin.CreationTime;
1495                 destination.LastWriteTime = origin.LastWriteTime;
1496                 destination.LastAccessTime = origin.LastAccessTime;
1497                 destination.IsReadOnly = true;
1498             }
1499             else
1500             {
1501                 destination.CreationTime = origin.CreationTime;
1502                 destination.LastWriteTime = origin.LastWriteTime;
1503                 destination.LastAccessTime = origin.LastAccessTime;
1504             }
1505         }
1506     }
1507 
1508     /// <summary>
1509     /// Log to the stdout and debug.  This is only used in debug builds.
1510     /// </summary>
1511     internal static class Log
1512     {
1513         internal static StreamWriter sw = null;
1514 
OpenLogFile(string logFilePath)1515         internal static bool OpenLogFile(string logFilePath)
1516         {
1517             try
1518             {
1519                 if (sw == null)
1520                 {
1521                     string path = Environment.ExpandEnvironmentVariables(logFilePath);
1522                     path = PathConverter.Windowsify(path);
1523                     string directory = path.Substring(0, path.LastIndexOf('\\'));
1524                     if (!Directory.Exists(directory))
1525                     {
1526                         Directory.CreateDirectory(directory);
1527                     }
1528                     sw = new StreamWriter(path);
1529                 }
1530             }
1531             catch (Exception ex)
1532             {
1533                 Console.WriteLine("Failed to open the log file. InnerException: {0}", ex.Message);
1534                 sw = null;
1535             }
1536             return (sw != null);
1537         }
WriteLine(string message)1538         internal static void WriteLine(string message)
1539         {
1540             message = string.Format("[ {0} - DEBUG: {1} ] {2}", System.Diagnostics.Process.GetCurrentProcess().Id, DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff"), message);
1541             Console.WriteLine(message);
1542             Debug.WriteLine(message);
1543             if (sw != null)
1544             {
1545                 sw.WriteLine(message);
1546             }
1547         }
1548 
WriteLine(string format, object arg0)1549         internal static void WriteLine(string format, object arg0)
1550         {
1551             WriteLine(string.Format(format, arg0));
1552         }
1553 
WriteLine(string format, object arg0, object arg1)1554         internal static void WriteLine(string format, object arg0, object arg1)
1555         {
1556             WriteLine(string.Format(format, arg0, arg1));
1557         }
1558 
WriteLine(string format, object arg0, object arg1, object arg2)1559         internal static void WriteLine(string format, object arg0, object arg1, object arg2)
1560         {
1561             WriteLine(string.Format(format, arg0, arg1, arg2));
1562         }
1563 
WriteLineRaw(string message)1564         internal static void WriteLineRaw(string message)
1565         {
1566             Console.WriteLine(message);
1567             Debug.WriteLine(message);
1568             if (sw != null)
1569             {
1570                 sw.WriteLine(message);
1571             }
1572         }
1573 
WriteLineRaw(string format, object arg0)1574         internal static void WriteLineRaw(string format, object arg0)
1575         {
1576             WriteLineRaw(string.Format(format, arg0));
1577         }
1578 
WriteLineRaw(string format, object arg0, object arg1)1579         internal static void WriteLineRaw(string format, object arg0, object arg1)
1580         {
1581             WriteLineRaw(string.Format(format, arg0, arg1));
1582         }
1583 
WriteLineRaw(string format, object arg0, object arg1, object arg2)1584         internal static void WriteLineRaw(string format, object arg0, object arg1, object arg2)
1585         {
1586             WriteLineRaw(string.Format(format, arg0, arg1, arg2));
1587         }
1588 
CloseLogFile()1589         internal static void CloseLogFile()
1590         {
1591             if (sw != null)
1592             {
1593                 sw.Close();
1594             }
1595         }
1596     }
1597 
1598     /// <summary>
1599     /// This class provides tools for argument validation.
1600     /// </summary>
1601     internal static class ArgumentChecks
1602     {
IsArgumentValidSetting(string[] args, int index)1603         internal static bool IsArgumentValidSetting(string[] args, int index)
1604         {
1605 #if DEBUG
1606             Log.WriteLine("IsArgumentValidSetting called. Checking index=" + index.ToString());
1607             string argString = null;
1608             for (int counter = 0; counter < args.Length; ++counter)
1609             {
1610                 argString += "{" + counter.ToString() + "}" + args[counter] + " ";
1611             }
1612             Log.WriteLine("arguments=" + argString);
1613 #endif
1614             bool returnVal = false;
1615             // If the argument doesn't exist, it is not valid.
1616             if (!ArgumentExists(args, index))
1617             {
1618                 returnVal = false;
1619             }
1620             else
1621             {
1622                 // If the argument starts with '-' it is another option, not a setting.
1623                 if (!args[index].StartsWith("-"))
1624                 {
1625                     returnVal = true;
1626                 }
1627                 else
1628                 {
1629                     returnVal = false;
1630                 }
1631             }
1632 
1633 #if DEBUG
1634             Log.WriteLine("IsArgumentValidSetting return " + returnVal.ToString());
1635 #endif
1636             return returnVal;
1637         }
1638 
1639         /// <summary>
1640         /// Check if the provided index exists in the argument list.
1641         /// </summary>
1642         /// <param name="args">Array of string representing an argument list.</param>
1643         /// <param name="index">The index of the argument that will be checked.</param>
1644         /// <returns>True if the argument at the provide index exists, false otherwise.</returns>
ArgumentExists(string[] args, int index)1645         internal static bool ArgumentExists(string[] args, int index)
1646         {
1647             if (args == null || args.Length == 0 ||
1648                  index < 0 || index > (args.Length - 1))
1649             {
1650                 return false;
1651             }
1652             else
1653             {
1654                 return true;
1655             }
1656         }
1657     }
1658 
1659     internal class PerfTimer
1660     {
1661         private System.Diagnostics.Stopwatch _clock;
1662         private string _name;
1663 
1664         public string Name
1665         {
1666             get { return _name;}
1667         }
1668 
PerfTimer(string name)1669         public PerfTimer(string name)
1670         {
1671             _name = name;
1672             _clock = new Stopwatch();
1673         }
1674 
Start()1675         public void Start()
1676         {
1677             Log.WriteLine("[TIMER]: Starting PerfTimer " + _name);
1678             _clock.Start();
1679         }
1680 
Report()1681         public void Report()
1682         {
1683             Log.WriteLine("[TIMER]: PerfTimer " + _name + " current time is " + _clock.ElapsedMilliseconds + " ms.");
1684         }
1685 
Stop()1686         public void Stop()
1687         {
1688             _clock.Stop();
1689             Log.WriteLine("[TIMER]: PerfTimer " + _name + " stopped.");
1690             this.Report();
1691         }
1692 
Reset()1693         public void Reset()
1694         {
1695             _clock.Reset();
1696             Log.WriteLine("[TIMER]: PerfTimer " + _name + " reset.");
1697         }
1698     }
1699 }