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