Merge pull request #2816 from xmcclure/profile-clean-0
[mono.git] / mcs / tools / gacutil / driver.cs
1 //
2 // Mono.Tools.GacUtil
3 //
4 // Author(s):
5 //  Todd Berman <tberman@sevenl.net>
6 //  Jackson Harper <jackson@ximian.com>
7 //
8 // Copyright 2003, 2004 Todd Berman 
9 // Copyright 2004 Novell, Inc (http://www.novell.com)
10 //
11
12
13 using System;
14 using System.IO;
15 using System.Diagnostics;
16 using System.Text;
17 using System.Reflection;
18 using System.Collections;
19 using System.Globalization;
20 using System.Runtime.InteropServices;
21 using System.Security.Cryptography;
22
23 using Mono.Security;
24 using Mono.Security.Cryptography;
25
26 namespace Mono.Tools {
27
28         public class Driver {
29
30                 private enum Command {
31                         Unknown,
32                         Install,
33                         InstallFromList,
34                         Uninstall,
35                         UninstallFromList,
36                         UninstallSpecific,
37                         List,
38                         Help
39                 }
40
41                 private enum VerificationResult
42                 {
43                         StrongNamed,
44                         WeakNamed,
45                         DelaySigned,
46                         Skipped
47                 }
48
49                 private static bool silent;
50                 static bool in_bootstrap;
51
52                 public static int Main (string [] args)
53                 {
54                         if (args.Length == 0)
55                                 Usage ();
56
57                         Command command = Command.Unknown;
58                         string command_str = null;
59
60                         string libdir;
61                         string name, package, gacdir, root;
62                         name = package = root = gacdir = null;
63                         bool check_refs = false;
64
65                         // Check for silent arg first so we can suppress
66                         // warnings during command line parsing
67                         if (Array.IndexOf (args, "/silent") > -1 || Array.IndexOf (args, "-silent") > -1)
68                                 silent = true;
69
70                         for (int i=0; i<args.Length; i++) {
71                                 if (IsSwitch (args [i])) {
72
73                                         // for cmd line compatibility with other gacutils
74                                         // we always force it though
75                                         if (args [i] == "-f" || args [i] == "/f")
76                                                 continue;
77
78                                         // Ignore this option for now, although we might implement it someday
79                                         if (args [i] == "/r") {
80                                                 WriteLine ("WARNING: gacutil does not support traced references." +
81                                                         "This option is being ignored.");
82                                                 i += 3;
83                                                 continue;
84                                         }
85
86                                         // This is already handled we just dont want to choke on it
87                                         if (args [i] == "-silent" || args [i] == "/silent")
88                                                 continue; 
89
90                                         if (args [i] == "-check_refs" || args [i] == "/check_refs") {
91                                                 check_refs = true;
92                                                 continue;
93                                         }
94
95                                         if (args [i] == "-bootstrap" || args [i] == "/bootstrap") {
96                                                 in_bootstrap = true;
97                                                 continue;
98                                         }
99
100                                         if (command == Command.Unknown) {
101                                                 command = GetCommand (args [i]);
102                                                 if (command != Command.Unknown) {
103                                                         command_str = args [i];
104                                                         continue;
105                                                 }
106                                         }
107
108                                         if (i + 1 >= args.Length) {
109                                                 Console.WriteLine ("Option " + args [i] + " takes 1 argument");
110                                                 return 1;
111                                         }
112
113                                         switch (args [i]) {
114                                         case "-package":
115                                         case "/package":
116                                                 package = args [++i];
117                                                 continue;
118                                         case "-root":
119                                         case "/root":
120                                                 root = args [++i];
121                                                 continue;
122                                         case "-gacdir":
123                                         case "/gacdir":
124                                                 gacdir = args [++i];
125                                                 continue;
126                                         case "/nologo":
127                                         case "-nologo":
128                                                 // we currently don't display a
129                                                 // logo banner, so ignore it
130                                                 // for command-line compatibility
131                                                 // with MS gacutil
132                                                 continue;
133                                         }
134                                 }
135                                 if (name == null)
136                                         name = args [i];
137                                 else
138                                         name += args [i];
139                         }
140
141                         if (command == Command.Unknown && IsSwitch (args [0])) {
142                                 Console.WriteLine ("Unknown command: " + args [0]);
143                                 return 1;
144                         } else if (command == Command.Unknown) {
145                                 Usage ();
146                         } else if (command == Command.Help) {
147                                 ShowHelp (true);
148                                 return 1;
149                         }
150
151                         if (gacdir == null) {
152                                 gacdir = GetGacDir ();
153                                 libdir = GetLibDir ();
154                         } else {
155                                 gacdir = EnsureLib (gacdir);
156                                 libdir = Path.Combine (gacdir, "mono");
157                                 gacdir = Path.Combine (libdir, "gac");
158                         }
159
160                         string link_gacdir = gacdir;
161                         string link_libdir = libdir;
162                         if (root != null) {
163                                 libdir = Path.Combine (root, "mono");
164                                 gacdir = Path.Combine (libdir, "gac");
165                         }
166
167                         LoadConfig (silent);
168
169                         switch (command) {
170                         case Command.Install:
171                                 if (name == null) {
172                                         WriteLine ("Option " + command_str + " takes 1 argument");
173                                         return 1;
174                                 }
175                                 if (!Install (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
176                                         return 1;
177                                 break;
178                         case Command.InstallFromList:
179                                 if (name == null) {
180                                         WriteLine ("Option " + command_str + " takes 1 argument");
181                                         return 1;
182                                 }
183                                 if (!InstallFromList (check_refs, name, package, gacdir, link_gacdir, libdir, link_libdir))
184                                         return 1;
185                                 break;
186                         case Command.Uninstall:
187                                 if (name == null) {
188                                         WriteLine ("Option " + command_str + " takes 1 argument");
189                                         return 1;
190                                 }
191                                 int uninstallCount = 0;
192                                 int uninstallFailures = 0;
193                                 Uninstall (name, package, gacdir, libdir, false,
194                                         ref uninstallCount, ref uninstallFailures);
195                                 WriteLine ("Assemblies uninstalled = {0}", uninstallCount);
196                                 WriteLine ("Failures = {0}", uninstallFailures);
197                                 if (uninstallFailures > 0)
198                                         return 1;
199                                 break;
200                         case Command.UninstallFromList:
201                                 if (name == null) {
202                                         WriteLine ("Option " + command_str + " takes 1 argument");
203                                         return 1;
204                                 }
205                                 if (!UninstallFromList (name, package, gacdir, libdir))
206                                         return 1;
207                                 break;
208                         case Command.UninstallSpecific:
209                                 if (name == null) {
210                                         WriteLine ("Option " + command_str + " takes 1 argument");
211                                         return 1;
212                                 }
213                                 if (!UninstallSpecific (name, package, gacdir, libdir))
214                                         return 1;
215                                 break;
216                         case Command.List:
217                                 List (name, gacdir);
218                                 break;
219                         }
220
221                         return 0;
222                 }
223
224                 static void Copy (string source, string target, bool v)
225                 {
226                         try {
227                                 File.Delete (target);
228                         } catch {}
229                         File.Copy (source, target, v);
230                 }
231                 
232                 private static bool Install (bool check_refs, string name, string package,
233                                 string gacdir, string link_gacdir, string libdir, string link_libdir)
234                 {
235                         string failure_msg = "Failure adding assembly {0} to the cache: ";
236                         ArrayList resources;
237
238                         if (!File.Exists (name)) {
239                                 WriteLine (string.Format (failure_msg, name) + "The system cannot find the file specified.");
240                                 return false;
241                         }
242
243                         Assembly assembly = null;
244                         AssemblyName an = null;
245
246                         try {
247                                 assembly = Assembly.LoadFrom (name);
248                         } catch {
249                                 WriteLine (string.Format (failure_msg, name) + "The file specified is not a valid assembly.");
250                                 return false;
251                         }
252
253                         an = assembly.GetName ();
254
255                         switch (VerifyStrongName (an, name)) {
256                         case VerificationResult.StrongNamed:
257                         case VerificationResult.Skipped:
258                                 break;
259                         case VerificationResult.WeakNamed:
260                                 WriteLine (string.Format (failure_msg, name) + "Attempt to install an assembly without a strong name"
261                                         + (in_bootstrap ? "(continuing anyway)" : string.Empty));
262                                 if (!in_bootstrap)
263                                         return false;
264                                 break;
265                         case VerificationResult.DelaySigned:
266                                 WriteLine (string.Format (failure_msg, name) + "Strong name cannot be verified for delay-signed assembly"
267                                         + (in_bootstrap ? "(continuing anyway)" : string.Empty));
268                                 if (!in_bootstrap)
269                                         return false;
270                                 break;
271                         }
272
273                         resources = new ArrayList ();
274                         foreach (string res_name in assembly.GetManifestResourceNames ()) {
275                                 ManifestResourceInfo res_info = assembly.GetManifestResourceInfo (res_name);
276                                 
277                                 if ((res_info.ResourceLocation & ResourceLocation.Embedded) == 0) {
278                                         if (!File.Exists (res_info.FileName)) {
279                                                 WriteLine (string.Format (failure_msg, name) + "The system cannot find resource " + res_info.FileName);
280                                                 return false;
281                                         }
282
283                                         resources.Add (res_info);
284                                 }
285                         }
286
287                         if (check_refs && !CheckReferencedAssemblies (an)) {
288                                 WriteLine (string.Format (failure_msg, name) +
289                                         "Attempt to install an assembly that " +
290                                         "references non strong named assemblies " +
291                                         "with -check_refs enabled.");
292                                 return false;
293                         }
294
295                         string [] siblings = { ".config", ".mdb" };
296                         string version_token = an.Version + "_" +
297                                                an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) + "_" +
298                                                GetStringToken (an.GetPublicKeyToken ());
299                         string full_path = Path.Combine (Path.Combine (gacdir, an.Name), version_token);
300                         string asmb_file = Path.GetFileName (name);
301                         string asmb_path = Path.Combine (full_path, asmb_file);
302                         string asmb_name = assembly.GetName ().Name;
303                         
304                         if (Path.GetFileNameWithoutExtension (asmb_file) != asmb_name) {
305                                 WriteLine (string.Format (failure_msg, name) +
306                                     string.Format ("the filename \"{0}\" doesn't match the assembly name \"{1}\"",
307                                         asmb_file, asmb_name));
308                                 return false;
309                         }
310
311                         try {
312                                 if (Directory.Exists (full_path)) {
313                                         // Wipe out the directory. This way we ensure old assemblies    
314                                         // config files, and AOTd files are removed.
315                                         Directory.Delete (full_path, true);
316                                 }
317                                 Directory.CreateDirectory (full_path);
318                         } catch {
319                                 WriteLine (string.Format (failure_msg, name) +
320                                         "gac directories could not be created, " +
321                                         "possibly permission issues.");
322                                 return false;
323                         }
324
325                         Copy (name, asmb_path, true);
326
327                         var name_pdb = Path.ChangeExtension (name, ".pdb");
328                         if (File.Exists (name_pdb)) {
329                                 Copy (name_pdb, Path.ChangeExtension (asmb_path, ".pdb"), true);
330                         }
331
332                         foreach (string ext in siblings) {
333                                 string sibling = String.Concat (name, ext);
334                                 if (File.Exists (sibling))
335                                         Copy (sibling, String.Concat (asmb_path, ext), true);
336                         }
337
338                         foreach (ManifestResourceInfo resource_info in resources) {
339                                 try {
340                                         Copy (resource_info.FileName, Path.Combine (full_path, Path.GetFileName (resource_info.FileName)), true);
341                                 } catch {
342                                         WriteLine ("ERROR: Could not install resource file " + resource_info.FileName);
343                                         Environment.Exit (1);
344                                 }
345                         }
346
347                         if (package != null) {
348                                 string ref_dir = Path.Combine (libdir, package);
349                                 string ref_path = Path.Combine (ref_dir, asmb_file);
350
351                                 if (File.Exists (ref_path))
352                                         File.Delete (ref_path);
353                                 try {
354                                         Directory.CreateDirectory (ref_dir);
355                                 } catch {
356                                         WriteLine ("ERROR: Could not create package dir file.");
357                                         Environment.Exit (1);
358                                 }
359                                 if (Path.DirectorySeparatorChar == '/') {
360                                         string pkg_path_abs = Path.Combine (gacdir, Path.Combine (an.Name, Path.Combine (version_token, asmb_file)));
361                                         string pkg_path = AbsoluteToRelativePath (ref_dir, pkg_path_abs);
362                                         symlink (pkg_path, ref_path);
363
364                                         var pdb_pkg_path = Path.ChangeExtension (pkg_path, ".pdb");
365                                         var pdb_ref_path = Path.ChangeExtension (ref_path, ".pdb");
366
367                                         if (File.Exists (pdb_pkg_path)) {
368                                                 symlink (pdb_pkg_path, pdb_ref_path);
369                                         } else {
370                                                 try {
371                                                         File.Delete (pdb_ref_path);
372                                                 } catch {
373                                                         // Ignore error, just delete files that should not be there.
374                                                 }
375                                         }
376
377                                         foreach (string ext in siblings) {
378                                                 string sibling = String.Concat (pkg_path, ext);
379                                                 string sref = String.Concat (ref_path, ext);
380
381                                                 if (File.Exists (sibling))
382                                                         symlink (sibling, sref);
383                                                 else {
384                                                         try {
385                                                                 File.Delete (sref);
386                                                         } catch {
387                                                                 // Ignore error, just delete files that should not be there.
388                                                         }
389                                                 }
390                                         }
391                                         WriteLine ("Package exported to: {0} -> {1}", ref_path, pkg_path);
392                                 } else {
393                                         // string link_path = Path.Combine (Path.Combine (link_gacdir, an.Name), version_token);
394                                         //
395                                         // We can't use 'link_path' here, since it need not be a valid path at the time 'gacutil'
396                                         // is run, esp. when invoked in a DESTDIR install.
397                                         Copy (name, ref_path, true);
398                                         WriteLine ("Package exported to: " + ref_path);
399                                 }
400                         }
401
402                         WriteLine ("Installed {0} into the gac ({1})", name,
403                                 gacdir);
404
405                         return true;
406                 }
407
408                 //from MonoDevelop.Core.FileService
409                 unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
410                 {
411                         if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
412                                 return absPath;
413
414                         absPath = Path.GetFullPath (absPath);
415                         baseDirectoryPath = Path.GetFullPath (baseDirectoryPath).TrimEnd (Path.DirectorySeparatorChar);
416
417                         fixed (char* bPtr = baseDirectoryPath, aPtr = absPath) {
418                                 var bEnd = bPtr + baseDirectoryPath.Length;
419                                 var aEnd = aPtr + absPath.Length;
420                                 char* lastStartA = aEnd;
421                                 char* lastStartB = bEnd;
422
423                                 int indx = 0;
424                                 // search common base path
425                                 var a = aPtr;
426                                 var b = bPtr;
427                                 while (a < aEnd) {
428                                         if (*a != *b)
429                                                 break;
430                                         if (IsSeparator (*a)) {
431                                                 indx++;
432                                                 lastStartA = a + 1;
433                                                 lastStartB = b;
434                                         }
435                                         a++;
436                                         b++;
437                                         if (b >= bEnd) {
438                                                 if (a >= aEnd || IsSeparator (*a)) {
439                                                         indx++;
440                                                         lastStartA = a + 1;
441                                                         lastStartB = b;
442                                                 }
443                                                 break;
444                                         }
445                                 }
446                                 if (indx == 0)
447                                         return absPath;
448
449                                 if (lastStartA >= aEnd)
450                                         return ".";
451
452                                 // handle case a: some/path b: some/path/deeper...
453                                 if (a >= aEnd) {
454                                         if (IsSeparator (*b)) {
455                                                 lastStartA = a + 1;
456                                                 lastStartB = b;
457                                         }
458                                 }
459
460                                 // look how many levels to go up into the base path
461                                 int goUpCount = 0;
462                                 while (lastStartB < bEnd) {
463                                         if (IsSeparator (*lastStartB))
464                                                 goUpCount++;
465                                         lastStartB++;
466                                 }
467                                 var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
468                                 var result = new char [size];
469                                 fixed (char* rPtr = result) {
470                                         // go paths up
471                                         var r = rPtr;
472                                         for (int i = 0; i < goUpCount; i++) {
473                                                 *(r++) = '.';
474                                                 *(r++) = '.';
475                                                 *(r++) = Path.DirectorySeparatorChar;
476                                         }
477                                         // copy the remaining absulute path
478                                         while (lastStartA < aEnd)
479                                                 *(r++) = *(lastStartA++);
480                                 }
481                                 return new string (result);
482                         }
483                 }
484
485                 static bool IsSeparator (char ch)
486                 {
487                         return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
488                 }
489
490                 private static void Uninstall (string name, string package, string gacdir, string libdir, bool listMode, ref int uninstalled, ref int failures)
491                 {
492                         string [] assembly_pieces = name.Split (new char[] { ',' });
493                         Hashtable asm_info = new Hashtable ();
494
495                         foreach (string item in assembly_pieces) {
496                                 if (item == String.Empty) continue;
497                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
498                                 if(pieces.Length == 1)
499                                         asm_info ["assembly"] = pieces [0];
500                                 else
501                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
502                         }
503
504                         string assembly_name = (string) asm_info ["assembly"];
505                         string asmdir = Path.Combine (gacdir, assembly_name);
506                         if (!Directory.Exists (asmdir)) {
507                                 if (listMode) {
508                                         failures++;
509                                         WriteLine ("Assembly: " + name);
510                                 }
511                                 WriteLine ("No assemblies found that match: " + name);
512                                 return;
513                         }
514
515                         string searchString = GetSearchString (asm_info);
516                         string [] directories = Directory.GetDirectories (asmdir, searchString);
517
518                         if (directories.Length == 0) {
519                                 if (listMode) {
520                                         failures++;
521                                         WriteLine ("Assembly: " + name);
522                                         WriteLine ("No assemblies found that match: " + name);
523                                 }
524                                 return;
525                         }
526
527                         for (int i = 0; i < directories.Length; i++) {
528                                 if (listMode && i > 0)
529                                         break;
530
531                                 string dir = directories [i];
532                                 string extension = null;
533
534                                 if (File.Exists (Path.Combine (dir, assembly_name + ".dll"))) {
535                                         extension = ".dll";
536                                 } else if (File.Exists (Path.Combine (dir, assembly_name + ".exe"))) {
537                                         extension = ".exe";
538                                 } else {
539                                         failures++;
540                                         WriteLine("Cannot find the assembly: " + assembly_name);
541                                         continue;
542                                 }
543
544                                 string assembly_filename = assembly_name + extension;
545
546                                 AssemblyName an = AssemblyName.GetAssemblyName (
547                                         Path.Combine (dir, assembly_filename));
548                                 WriteLine ("Assembly: " + an.FullName);
549
550                                 Directory.Delete (dir, true);
551                                 if (package != null) {
552                                         string link_dir = Path.Combine (libdir, package);
553                                         string link = Path.Combine (link_dir, assembly_filename);
554
555                                         try { 
556                                                 File.Delete (link);
557                                         } catch {
558                                                 // The file might not exist, happens with
559                                                 // the debugger on make uninstall
560                                         }
561                                         
562                                         if (Directory.GetFiles (link_dir).Length == 0) {
563                                                 WriteLine ("Cleaning package directory, it is empty.");
564                                                 try {
565                                                         Directory.Delete (link_dir);
566                                                 } catch {
567                                                         // Workaround: GetFiles does not list Symlinks
568                                                 }
569                                         }
570                                 }
571
572                                 uninstalled++;
573                                 WriteLine ("Uninstalled: " + an.FullName);
574                         }
575
576                         if (Directory.GetDirectories (asmdir).Length == 0) {
577                                 WriteLine ("Cleaning assembly dir, it is empty");
578                                 try {
579                                         Directory.Delete (asmdir);
580                                 } catch {
581                                         // Workaround: GetFiles does not list Symlinks
582                                 }
583                         }
584                 }
585
586                 private static bool UninstallSpecific (string name, string package,
587                                 string gacdir, string libdir)
588                 {
589                         string failure_msg = "Failure to remove assembly from the cache: ";
590
591                         if (!File.Exists (name)) {
592                                 WriteLine (failure_msg + "The system cannot find the file specified.");
593                                 return false;
594                         }
595
596                         AssemblyName an = null;
597
598                         try {
599                                 an = AssemblyName.GetAssemblyName (name);
600                         } catch {
601                                 WriteLine (failure_msg + "The file specified is not a valid assembly.");
602                                 return false;
603                         }
604
605                         int uninstallCount = 0;
606                         int uninstallFailures = 0;
607                         Uninstall (an.FullName.Replace (" ", String.Empty),
608                                 package, gacdir, libdir, true, ref uninstallCount,
609                                 ref uninstallFailures);
610                         WriteLine ("Assemblies uninstalled = {0}", uninstallCount);
611                         WriteLine ("Failures = {0}", uninstallFailures);
612                         return (uninstallFailures == 0);
613                 }
614
615                 private static void List (string name, string gacdir)
616                 {
617                         WriteLine ("The following assemblies are installed into the GAC:");
618
619                         if (name != null) {
620                                 FilteredList (name, gacdir);
621                                 return;
622                         }
623
624                         int count = 0;
625                         DirectoryInfo gacinfo = new DirectoryInfo (gacdir);
626                         foreach (DirectoryInfo parent in gacinfo.GetDirectories ()) {
627                                 foreach (DirectoryInfo dir in parent.GetDirectories ()) {
628                                         string asmb = Path.Combine (Path.Combine (parent.FullName, dir.Name), parent.Name) + ".dll";
629                                         if (File.Exists (asmb)) {
630                                                 WriteLine (AsmbNameFromVersionString (parent.Name, dir.Name));
631                                                 count++;
632                                         }
633                                 }
634                         }
635                         WriteLine ("Number of items = " + count);
636                 }
637
638                 private static void FilteredList (string name, string gacdir)
639                 {
640                         string [] assembly_pieces = name.Split (new char[] { ',' });
641                         Hashtable asm_info = new Hashtable ();
642
643                         foreach (string item in assembly_pieces) {
644                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
645                                 if(pieces.Length == 1)
646                                         asm_info ["assembly"] = pieces [0];
647                                 else
648                                         asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
649                         }
650                         
651                         string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
652                         if (!Directory.Exists (asmdir)) {
653                                 WriteLine ("Number of items = 0");
654                                 return;
655                         }
656                         string search = GetSearchString (asm_info);
657                         string [] dir_list = Directory.GetDirectories (asmdir, search);
658
659                         int count = 0;
660                         foreach (string dir in dir_list) {
661                                 string asmb = Path.Combine (dir, (string) asm_info ["assembly"]) + ".dll";
662                                 if (File.Exists (asmb)) {
663                                         WriteLine (AsmbNameFromVersionString ((string) asm_info ["assembly"],
664                                                                    new DirectoryInfo (dir).Name));
665                                         count++;
666                                 }
667                         }
668                         WriteLine ("Number of items = " + count);
669                 }
670
671                 private static bool InstallFromList (bool check_refs, string list_file, string package,
672                                 string gacdir, string link_gacdir, string libdir, string link_libdir)
673                 {
674                         StreamReader s = null;
675                         int processed, failed;
676                         string listdir = Path.GetDirectoryName (
677                                 Path.GetFullPath (list_file));
678
679                         processed = failed = 0;
680
681                         try {
682                                 s = new StreamReader (list_file);
683
684                                 string line;
685                                 while ((line = s.ReadLine ()) != null) {
686                                         string file = line.Trim ();
687                                         if (file.Length == 0)
688                                                 continue;
689
690                                         string assemblyPath = Path.Combine (listdir,
691                                                 file);
692
693                                         if (!Install (check_refs, assemblyPath, package, gacdir,
694                                                      link_gacdir, libdir, link_libdir))
695                                                 failed++;
696                                         processed++;
697                                 }
698
699                                 WriteLine ("Assemblies processed = {0}", processed);
700                                 WriteLine ("Assemblies installed = {0}", processed - failed);
701                                 WriteLine ("Failures = {0}", failed);
702
703                                 return (failed == 0);
704                         } catch (IOException) {
705                                 WriteLine ("Failed to open assemblies list file " + list_file + ".");
706                                 return false;
707                         } finally {
708                                 if (s != null)
709                                         s.Close ();
710                         }
711                 }
712
713                 private static bool UninstallFromList (string list_file, string package,
714                                 string gacdir, string libdir)
715                 {
716                         StreamReader s = null;
717                         int failed, uninstalled;
718
719                         failed = uninstalled = 0;
720
721                         try {
722                                 s = new StreamReader (list_file);
723
724                                 string line;
725                                 while ((line = s.ReadLine ()) != null) {
726                                         string name = line.Trim ();
727                                         if (name.Length == 0)
728                                                 continue;
729                                         Uninstall (line, package, gacdir, libdir,
730                                                 true, ref uninstalled, ref failed);
731                                 }
732
733                                 WriteLine ("Assemblies processed = {0}", uninstalled+failed);
734                                 WriteLine ("Assemblies uninstalled = {0}", uninstalled);
735                                 WriteLine ("Failures = {0}", failed);
736
737                                 return (failed == 0);
738                         } catch (IOException) {
739                                 WriteLine ("Failed to open assemblies list file " + list_file + ".");
740                                 return false;
741                         } finally {
742                                 if (s != null)
743                                         s.Close ();
744                         }
745                 }
746
747                 private static bool CheckReferencedAssemblies (AssemblyName an)
748                 {
749                         AppDomain d = null;
750                         try {
751                                 Assembly a = Assembly.LoadFrom (an.CodeBase);
752                                 AssemblyName corlib = typeof (object).Assembly.GetName ();
753
754                                 foreach (AssemblyName ref_an in a.GetReferencedAssemblies ()) {
755                                         if (ref_an.Name == corlib.Name) // Just do a string compare so we can install on diff versions
756                                                 continue;
757                                         byte [] pt = ref_an.GetPublicKeyToken ();
758                                         if (pt == null || pt.Length == 0) {
759                                                 WriteLine ("Assembly " + ref_an.Name + " is not strong named.");
760                                                 return false;
761                                         }
762                                 }
763                         } catch (Exception e) {
764                                 WriteLine (e.ToString ()); // This should be removed pre beta3
765                                 return false;
766                         } finally {
767                                 if (d != null) {
768                                         try {
769                                                 AppDomain.Unload (d);
770                                         } catch { }
771                                 }
772                         }
773
774                         return true;
775                 }
776
777                 private static string GetSearchString (Hashtable asm_info)
778                 {
779                         if (asm_info.Keys.Count == 1)
780                                 return "*";
781                         string version, culture, token;
782
783                         version = asm_info ["version"] as string;
784                         version = (version == null ? "*" : version + "*");
785                         culture = asm_info ["culture"] as string;
786                         culture = (culture == null ? "*" : (culture == "neutral") ? String.Empty : culture.ToLower (CultureInfo.InvariantCulture));
787                         token = asm_info ["publickeytoken"] as string;
788                         token = (token == null ? "*" : token.ToLower (CultureInfo.InvariantCulture));
789                         
790                         return String.Format ("{0}_{1}_{2}", version, culture, token);
791                 }
792
793                 private static string AsmbNameFromVersionString (string name, string str)
794                 {
795                         string [] pieces = str.Split ('_');
796                         return String.Format ("{0}, Version={1}, Culture={2}, PublicKeyToken={3}",
797                                         name, pieces [0], (pieces [1] == String.Empty ? "neutral" : pieces [1]),
798                                         pieces [2]);
799                 }
800
801                 static bool LoadConfig (bool quiet)
802                 {
803                         MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
804                                 BindingFlags.Static | BindingFlags.NonPublic);
805
806                         if (config != null) {
807                                 string path = (string) config.Invoke (null, null);
808
809                                 bool exist = File.Exists (path);
810                                 if (!quiet && !exist)
811                                         Console.WriteLine ("Couldn't find machine.config");
812
813                                 StrongNameManager.LoadConfig (path);
814                                 return exist;
815                         } else if (!quiet)
816                                 Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
817
818                         // default CSP
819                         return false;
820                 }
821
822                 // modified copy from sn
823                 private static VerificationResult VerifyStrongName (AssemblyName an, string assemblyFile)
824                 {
825                         byte [] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
826                         if ((publicKey == null) || (publicKey.Length < 12)) {
827                                 // no mapping
828                                 publicKey = an.GetPublicKey ();
829                                 if ((publicKey == null) || (publicKey.Length < 12))
830                                         return VerificationResult.WeakNamed;
831                         }
832
833                         // Note: MustVerify is based on the original token (by design). Public key
834                         // remapping won't affect if the assembly is verified or not.
835                         if (StrongNameManager.MustVerify (an)) {
836                                 RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
837                                 StrongName sn = new StrongName (rsa);
838                                 if (sn.Verify (assemblyFile)) {
839                                         return VerificationResult.StrongNamed;
840                                 } else {
841                                         return VerificationResult.DelaySigned;
842                                 }
843                         } else {
844                                 return VerificationResult.Skipped;
845                         }
846                 }
847
848                 private static bool IsSwitch (string arg)
849                 {
850                         return (arg [0] == '-' || (arg [0] == '/' && !arg.EndsWith (".dll") && !arg.EndsWith (".exe") && arg.IndexOf ('/', 1) < 0 ) );
851                 }
852
853                 private static Command GetCommand (string arg)
854                 {
855                         Command c = Command.Unknown;
856
857                         switch (arg) {
858                         case "-i":
859                         case "/i":
860                         case "--install":
861                                 c = Command.Install;
862                                 break;
863                         case "-il":
864                         case "/il":
865                         case "--install-from-list":
866                                 c = Command.InstallFromList;
867                                 break;
868                         case "-u":
869                         case "/u":
870                         case "/uf":
871                         case "--uninstall":
872                                 c = Command.Uninstall;
873                                 break;
874                         case "-ul":
875                         case "/ul":
876                         case "--uninstall-from-list":
877                                 c = Command.UninstallFromList;
878                                 break;
879                         case "-us":
880                         case "/us":
881                         case "--uninstall-specific":
882                                 c = Command.UninstallSpecific;
883                                 break;
884                         case "-l":
885                         case "/l":
886                         case "--list":
887                                 c = Command.List;
888                                 break;
889                         case "-?":
890                         case "/?":
891                         case "--help":
892                                 c = Command.Help;
893                                 break;
894                         }
895                         return c;        
896                 }
897
898                 [DllImport ("libc", SetLastError=true)]
899                 public static extern int symlink (string oldpath, string newpath);
900
901                 private static string GetGacDir () {
902                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath",
903                                         BindingFlags.Static|BindingFlags.NonPublic);
904                         if (gac == null) {
905                                 WriteLine ("ERROR: Mono runtime not detected, please use " +
906                                                 "the mono runtime for gacutil.exe");
907                                 Environment.Exit (1);
908                         }
909                         MethodInfo get_gac = gac.GetGetMethod (true);
910                         return (string) get_gac.Invoke (null, null);
911                 }
912
913                 private static string GetLibDir () {
914                         MethodInfo libdir = typeof (System.Environment).GetMethod ("internalGetGacPath",
915                                         BindingFlags.Static|BindingFlags.NonPublic);
916                         if (libdir == null) {
917                                 WriteLine ("ERROR: Mono runtime not detected, please use " +
918                                                 "the mono runtime for gacutil.exe");
919                                 Environment.Exit (1);
920                         }
921                         return Path.Combine ((string)libdir.Invoke (null, null), "mono");
922                 }
923
924                 private static string GetStringToken (byte[] tok)
925                 {
926                         StringBuilder sb = new StringBuilder ();
927                         for (int i = 0; i < tok.Length ; i++)
928                                 sb.Append (tok[i].ToString ("x2"));
929                         return sb.ToString ();
930                 }
931
932                 private static string EnsureLib (string dir)
933                 {
934                         DirectoryInfo d = new DirectoryInfo (dir);
935                         if (d.Name == "lib")
936                                 return dir;
937                         return Path.Combine (dir, "lib");
938                 }
939
940                 private static void WriteLine ()
941                 {
942                         if (silent)
943                                 return;
944                         Console.WriteLine ();
945                 }
946
947                 private static void WriteLine (string line)
948                 {
949                         if (silent)
950                                 return;
951                         Console.WriteLine (line);
952                 }
953
954                 private static void WriteLine (string line, params object [] p)
955                 {
956                         if (silent)
957                                 return; 
958                         Console.WriteLine (line, p);
959                 }
960
961                 private static void Usage ()
962                 {
963                         ShowHelp (false);
964                         Environment.Exit (1);
965                 }
966
967                 private static void ShowHelp (bool detailed)
968                 {
969                         WriteLine ("Usage: gacutil.exe <commands> [ <options> ]");
970                         WriteLine ("Commands:");
971
972                         WriteLine ("-i <assembly_path> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
973                         WriteLine ("\tInstalls an assembly into the global assembly cache.");
974                         if (detailed) {
975                                 WriteLine ("\t<assembly_path> is the name of the file that contains the " +
976                                                 "\tassembly manifest\n" +
977                                                 "\tExample: -i myDll.dll");
978                         }
979                         WriteLine ();
980
981                         WriteLine ("-il <assembly_list_file> [-check_refs] [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
982                         WriteLine ("\tInstalls one or more assemblies into the global assembly cache.");
983                         if (detailed) {
984                                 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
985                                                 "\tassembly file paths on separate lines.\n" +
986                                                 "\tExample -il assembly_list.txt\n" +
987                                                 "\t\tassembly_list.txt contents:\n" +
988                                                 "\t\tassembly1.dll\n" +
989                                                 "\t\tassembly2.dll");
990                         }
991                         WriteLine ();
992                         
993                         WriteLine ("-u <assembly_display_name> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
994                         WriteLine ("\tUninstalls an assembly from the global assembly cache.");
995                         if (detailed) {
996                                 WriteLine ("\t<assembly_display_name> is the name of the assembly (partial or\n" +
997                                                 "\tfully qualified) to remove from the global assembly cache. If a \n" +
998                                                 "\tpartial name is specified all matching assemblies will be uninstalled.\n" +
999                                                 "\tExample: -u myDll,Version=1.2.1.0");
1000                         }
1001                         WriteLine ();
1002
1003                         WriteLine ("-ul <assembly_list_file> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
1004                         WriteLine ("\tUninstalls one or more assemblies from the global assembly cache.");
1005                         if (detailed) {
1006                                 WriteLine ("\t<assembly_list_file> is the path to a test file containing a list of\n" +
1007                                                 "\tassembly names on separate lines.\n" +
1008                                                 "\tExample -ul assembly_list.txt\n" +
1009                                                 "\t\tassembly_list.txt contents:\n" +
1010                                                 "\t\tassembly1,Version=1.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef\n" +
1011                                                 "\t\tassembly2,Version=2.0.0.0,Culture=en,PublicKeyToken=0123456789abcdef");
1012                         }
1013                         WriteLine ();
1014
1015                         WriteLine ("-us <assembly_path> [-package NAME] [-root ROOTDIR] [-gacdir GACDIR]");
1016                         WriteLine ("\tUninstalls an assembly using the specifed assemblies full name.");
1017                         if (detailed) {
1018                                 WriteLine ("\t<assembly path> is the path to an assembly. The full assembly name\n" +
1019                                                 "\tis retrieved from the specified assembly if there is an assembly in\n" +
1020                                                 "\tthe GAC with a matching name, it is removed.\n" +
1021                                                 "\tExample: -us myDll.dll");
1022                         }
1023                         WriteLine ();
1024
1025                         WriteLine ("-l [assembly_name] [-root ROOTDIR] [-gacdir GACDIR]");
1026                         WriteLine ("\tLists the contents of the global assembly cache.");
1027                         if (detailed) {
1028                                 WriteLine ("\tWhen the <assembly_name> parameter is specified only matching\n" +
1029                                                 "\tassemblies are listed.");
1030                         }
1031                         WriteLine ();
1032
1033                         WriteLine ("-?");
1034                         WriteLine ("\tDisplays a detailed help screen");
1035                         WriteLine ();
1036
1037                         if (!detailed)
1038                                 return;
1039
1040                         WriteLine ("Options:");
1041                         WriteLine ("-package <NAME>");
1042                         WriteLine ("\tUsed to create a directory in prefix/lib/mono with the name NAME, and a\n" +
1043                                         "\tsymlink is created from NAME/assembly_name to the assembly on the GAC.\n" +
1044                                         "\tThis is used so developers can reference a set of libraries at once.");
1045                         WriteLine ();
1046
1047                         WriteLine ("-gacdir <GACDIR>");
1048                         WriteLine ("\tUsed to specify the GACs base directory. Once an assembly has been installed\n" +
1049                                         "\tto a non standard gacdir the MONO_GAC_PREFIX environment variable must be used\n" +
1050                                         "\tto access the assembly.");
1051                         WriteLine ();
1052
1053                         WriteLine ("-root <ROOTDIR>");
1054                         WriteLine ("\tUsed by developers integrating this with automake tools or packaging tools\n" +
1055                                         "\tthat require a prefix directory to  be specified. The root represents the\n" +
1056                                         "\t\"libdir\" component of a prefix (typically prefix/lib).");
1057                         WriteLine ();
1058
1059                         WriteLine ("-check_refs");
1060                         WriteLine ("\tUsed to ensure that the assembly being installed into the GAC does not\n" +
1061                                         "\treference any non strong named assemblies. Assemblies being installed to\n" +
1062                                         "\tthe GAC should not reference non strong named assemblies, however the is\n" +
1063                                         "\tan optional check.");
1064
1065                         WriteLine ();
1066                         WriteLine ("Ignored Options:");
1067                         WriteLine ("-f");
1068                         WriteLine ("\tThe Mono gacutil ignores the -f option to maintain commandline compatibility with");
1069                         WriteLine ("\tother gacutils. gacutil will always force the installation of a new assembly.");
1070
1071                         WriteLine ();
1072                         WriteLine ("-r <reference_scheme> <reference_id> <description>");
1073                         WriteLine ("\tThe Mono gacutil has not implemented traced references and will emit a warning");
1074                         WriteLine ("\twhen this option is used.");
1075                 }
1076         }
1077 }
1078