Merge pull request #2698 from esdrubal/iosxmlarray
[mono.git] / mcs / tools / gacutil / driver.cs
index 0a19fe66b0ae5398af3f8d652ef29340b60ed840..13f686e22c64ec5dadc09c136838db3ed7c74a43 100644 (file)
 
 using System;
 using System.IO;
+using System.Diagnostics;
 using System.Text;
 using System.Reflection;
 using System.Collections;
 using System.Globalization;
 using System.Runtime.InteropServices;
+using System.Security.Cryptography;
 
 using Mono.Security;
+using Mono.Security.Cryptography;
 
 namespace Mono.Tools {
 
@@ -35,7 +38,16 @@ namespace Mono.Tools {
                        Help
                }
 
+               private enum VerificationResult
+               {
+                       StrongNamed,
+                       WeakNamed,
+                       DelaySigned,
+                       Skipped
+               }
+
                private static bool silent;
+               static bool in_bootstrap;
 
                public static int Main (string [] args)
                {
@@ -80,6 +92,11 @@ namespace Mono.Tools {
                                                continue;
                                        }
 
+                                       if (args [i] == "-bootstrap" || args [i] == "/bootstrap") {
+                                               in_bootstrap = true;
+                                               continue;
+                                       }
+
                                        if (command == Command.Unknown) {
                                                command = GetCommand (args [i]);
                                                if (command != Command.Unknown) {
@@ -106,6 +123,13 @@ namespace Mono.Tools {
                                        case "/gacdir":
                                                gacdir = args [++i];
                                                continue;
+                                       case "/nologo":
+                                       case "-nologo":
+                                               // we currently don't display a
+                                               // logo banner, so ignore it
+                                               // for command-line compatibility
+                                               // with MS gacutil
+                                               continue;
                                        }
                                }
                                if (name == null)
@@ -140,6 +164,8 @@ namespace Mono.Tools {
                                gacdir = Path.Combine (libdir, "gac");
                        }
 
+                       LoadConfig (silent);
+
                        switch (command) {
                        case Command.Install:
                                if (name == null) {
@@ -162,8 +188,14 @@ namespace Mono.Tools {
                                        WriteLine ("Option " + command_str + " takes 1 argument");
                                        return 1;
                                }
-                               if (!Uninstall (name, package, gacdir, libdir))
-                                       Environment.Exit (1);
+                               int uninstallCount = 0;
+                               int uninstallFailures = 0;
+                               Uninstall (name, package, gacdir, libdir, false,
+                                       ref uninstallCount, ref uninstallFailures);
+                               WriteLine ("Assemblies uninstalled = {0}", uninstallCount);
+                               WriteLine ("Failures = {0}", uninstallFailures);
+                               if (uninstallFailures > 0)
+                                       return 1;
                                break;
                        case Command.UninstallFromList:
                                if (name == null) {
@@ -200,30 +232,42 @@ namespace Mono.Tools {
                private static bool Install (bool check_refs, string name, string package,
                                string gacdir, string link_gacdir, string libdir, string link_libdir)
                {
-                       string failure_msg = "Failure adding assembly to the cache: ";
+                       string failure_msg = "Failure adding assembly {0} to the cache: ";
                        ArrayList resources;
 
                        if (!File.Exists (name)) {
-                               WriteLine (failure_msg + "The system cannot find the file specified.");
-                               Environment.Exit (1);
+                               WriteLine (string.Format (failure_msg, name) + "The system cannot find the file specified.");
+                               return false;
                        }
 
                        Assembly assembly = null;
                        AssemblyName an = null;
-                       byte [] pub_tok;
 
                        try {
                                assembly = Assembly.LoadFrom (name);
                        } catch {
-                               WriteLine (failure_msg + "The file specified is not a valid assembly.");
+                               WriteLine (string.Format (failure_msg, name) + "The file specified is not a valid assembly.");
                                return false;
                        }
 
                        an = assembly.GetName ();
-                       pub_tok = an.GetPublicKeyToken ();
-                       if (pub_tok == null || pub_tok.Length == 0) {
-                               WriteLine (failure_msg + "Attempt to install an assembly without a strong name.");
-                               return false;
+
+                       switch (VerifyStrongName (an, name)) {
+                       case VerificationResult.StrongNamed:
+                       case VerificationResult.Skipped:
+                               break;
+                       case VerificationResult.WeakNamed:
+                               WriteLine (string.Format (failure_msg, name) + "Attempt to install an assembly without a strong name"
+                                       + (in_bootstrap ? "(continuing anyway)" : string.Empty));
+                               if (!in_bootstrap)
+                                       return false;
+                               break;
+                       case VerificationResult.DelaySigned:
+                               WriteLine (string.Format (failure_msg, name) + "Strong name cannot be verified for delay-signed assembly"
+                                       + (in_bootstrap ? "(continuing anyway)" : string.Empty));
+                               if (!in_bootstrap)
+                                       return false;
+                               break;
                        }
 
                        resources = new ArrayList ();
@@ -232,7 +276,7 @@ namespace Mono.Tools {
                                
                                if ((res_info.ResourceLocation & ResourceLocation.Embedded) == 0) {
                                        if (!File.Exists (res_info.FileName)) {
-                                               WriteLine (failure_msg + "The system cannot find resource " + res_info.FileName);
+                                               WriteLine (string.Format (failure_msg, name) + "The system cannot find resource " + res_info.FileName);
                                                return false;
                                        }
 
@@ -241,18 +285,28 @@ namespace Mono.Tools {
                        }
 
                        if (check_refs && !CheckReferencedAssemblies (an)) {
-                               WriteLine (failure_msg + "Attempt to install an assembly that references non " +
-                                               "strong named assemblies with -check_refs enabled.");
+                               WriteLine (string.Format (failure_msg, name) +
+                                       "Attempt to install an assembly that " +
+                                       "references non strong named assemblies " +
+                                       "with -check_refs enabled.");
                                return false;
                        }
 
                        string [] siblings = { ".config", ".mdb" };
                        string version_token = an.Version + "_" +
                                               an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) + "_" +
-                                              GetStringToken (pub_tok);
+                                              GetStringToken (an.GetPublicKeyToken ());
                        string full_path = Path.Combine (Path.Combine (gacdir, an.Name), version_token);
                        string asmb_file = Path.GetFileName (name);
                        string asmb_path = Path.Combine (full_path, asmb_file);
+                       string asmb_name = assembly.GetName ().Name;
+                       
+                       if (Path.GetFileNameWithoutExtension (asmb_file) != asmb_name) {
+                               WriteLine (string.Format (failure_msg, name) +
+                                   string.Format ("the filename \"{0}\" doesn't match the assembly name \"{1}\"",
+                                       asmb_file, asmb_name));
+                               return false;
+                       }
 
                        try {
                                if (Directory.Exists (full_path)) {
@@ -262,13 +316,19 @@ namespace Mono.Tools {
                                }
                                Directory.CreateDirectory (full_path);
                        } catch {
-                               WriteLine (failure_msg + "gac directories could not be created, " +
-                                               "possibly permission issues.");
+                               WriteLine (string.Format (failure_msg, name) +
+                                       "gac directories could not be created, " +
+                                       "possibly permission issues.");
                                return false;
                        }
 
                        Copy (name, asmb_path, true);
 
+                       var name_pdb = Path.ChangeExtension (name, ".pdb");
+                       if (File.Exists (name_pdb)) {
+                               Copy (name_pdb, Path.ChangeExtension (asmb_path, ".pdb"), true);
+                       }
+
                        foreach (string ext in siblings) {
                                string sibling = String.Concat (name, ext);
                                if (File.Exists (sibling))
@@ -297,12 +357,27 @@ namespace Mono.Tools {
                                        Environment.Exit (1);
                                }
                                if (Path.DirectorySeparatorChar == '/') {
-                                       string pkg_path = "../gac/" + an.Name + "/" + version_token + "/" + asmb_file;
+                                       string pkg_path_abs = Path.Combine (gacdir, Path.Combine (an.Name, Path.Combine (version_token, asmb_file)));
+                                       string pkg_path = AbsoluteToRelativePath (ref_dir, pkg_path_abs);
                                        symlink (pkg_path, ref_path);
 
+                                       var pdb_pkg_path = Path.ChangeExtension (pkg_path, ".pdb");
+                                       var pdb_ref_path = Path.ChangeExtension (ref_path, ".pdb");
+
+                                       if (File.Exists (pdb_pkg_path)) {
+                                               symlink (pdb_pkg_path, pdb_ref_path);
+                                       } else {
+                                               try {
+                                                       File.Delete (pdb_ref_path);
+                                               } catch {
+                                                       // Ignore error, just delete files that should not be there.
+                                               }
+                                       }
+
                                        foreach (string ext in siblings) {
                                                string sibling = String.Concat (pkg_path, ext);
                                                string sref = String.Concat (ref_path, ext);
+
                                                if (File.Exists (sibling))
                                                        symlink (sibling, sref);
                                                else {
@@ -314,7 +389,7 @@ namespace Mono.Tools {
                                                }
                                        }
                                        WriteLine ("Package exported to: {0} -> {1}", ref_path, pkg_path);
-                               } else {
+                               } else {
                                        // string link_path = Path.Combine (Path.Combine (link_gacdir, an.Name), version_token);
                                        //
                                        // We can't use 'link_path' here, since it need not be a valid path at the time 'gacutil'
@@ -324,11 +399,95 @@ namespace Mono.Tools {
                                }
                        }
 
-                       WriteLine ("{0} installed into the gac ({1})", an.Name, gacdir);
+                       WriteLine ("Installed {0} into the gac ({1})", name,
+                               gacdir);
+
                        return true;
                }
 
-               private static bool Uninstall (string name, string package, string gacdir, string libdir)
+               //from MonoDevelop.Core.FileService
+               unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
+               {
+                       if (!Path.IsPathRooted (absPath) || string.IsNullOrEmpty (baseDirectoryPath))
+                               return absPath;
+
+                       absPath = Path.GetFullPath (absPath);
+                       baseDirectoryPath = Path.GetFullPath (baseDirectoryPath).TrimEnd (Path.DirectorySeparatorChar);
+
+                       fixed (char* bPtr = baseDirectoryPath, aPtr = absPath) {
+                               var bEnd = bPtr + baseDirectoryPath.Length;
+                               var aEnd = aPtr + absPath.Length;
+                               char* lastStartA = aEnd;
+                               char* lastStartB = bEnd;
+
+                               int indx = 0;
+                               // search common base path
+                               var a = aPtr;
+                               var b = bPtr;
+                               while (a < aEnd) {
+                                       if (*a != *b)
+                                               break;
+                                       if (IsSeparator (*a)) {
+                                               indx++;
+                                               lastStartA = a + 1;
+                                               lastStartB = b;
+                                       }
+                                       a++;
+                                       b++;
+                                       if (b >= bEnd) {
+                                               if (a >= aEnd || IsSeparator (*a)) {
+                                                       indx++;
+                                                       lastStartA = a + 1;
+                                                       lastStartB = b;
+                                               }
+                                               break;
+                                       }
+                               }
+                               if (indx == 0)
+                                       return absPath;
+
+                               if (lastStartA >= aEnd)
+                                       return ".";
+
+                               // handle case a: some/path b: some/path/deeper...
+                               if (a >= aEnd) {
+                                       if (IsSeparator (*b)) {
+                                               lastStartA = a + 1;
+                                               lastStartB = b;
+                                       }
+                               }
+
+                               // look how many levels to go up into the base path
+                               int goUpCount = 0;
+                               while (lastStartB < bEnd) {
+                                       if (IsSeparator (*lastStartB))
+                                               goUpCount++;
+                                       lastStartB++;
+                               }
+                               var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
+                               var result = new char [size];
+                               fixed (char* rPtr = result) {
+                                       // go paths up
+                                       var r = rPtr;
+                                       for (int i = 0; i < goUpCount; i++) {
+                                               *(r++) = '.';
+                                               *(r++) = '.';
+                                               *(r++) = Path.DirectorySeparatorChar;
+                                       }
+                                       // copy the remaining absulute path
+                                       while (lastStartA < aEnd)
+                                               *(r++) = *(lastStartA++);
+                               }
+                               return new string (result);
+                       }
+               }
+
+               static bool IsSeparator (char ch)
+               {
+                       return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
+               }
+
+               private static void Uninstall (string name, string package, string gacdir, string libdir, bool listMode, ref int uninstalled, ref int failures)
                {
                        string [] assembly_pieces = name.Split (new char[] { ',' });
                        Hashtable asm_info = new Hashtable ();
@@ -342,21 +501,64 @@ namespace Mono.Tools {
                                        asm_info [pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces [1];
                        }
 
-                       string asmdir = Path.Combine (gacdir, (string) asm_info ["assembly"]);
+                       string assembly_name = (string) asm_info ["assembly"];
+                       string asmdir = Path.Combine (gacdir, assembly_name);
                        if (!Directory.Exists (asmdir)) {
+                               if (listMode) {
+                                       failures++;
+                                       WriteLine ("Assembly: " + name);
+                               }
                                WriteLine ("No assemblies found that match: " + name);
-                               return false;
+                               return;
                        }
 
                        string searchString = GetSearchString (asm_info);
                        string [] directories = Directory.GetDirectories (asmdir, searchString);
 
-                       foreach (string dir in directories) {
+                       if (directories.Length == 0) {
+                               if (listMode) {
+                                       failures++;
+                                       WriteLine ("Assembly: " + name);
+                                       WriteLine ("No assemblies found that match: " + name);
+                               }
+                               return;
+                       }
+
+                       for (int i = 0; i < directories.Length; i++) {
+                               if (listMode && i > 0)
+                                       break;
+
+                               string dir = directories [i];
+                               string extension = null;
+
+                               if (File.Exists (Path.Combine (dir, assembly_name + ".dll"))) {
+                                       extension = ".dll";
+                               } else if (File.Exists (Path.Combine (dir, assembly_name + ".exe"))) {
+                                       extension = ".exe";
+                               } else {
+                                       failures++;
+                                       WriteLine("Cannot find the assembly: " + assembly_name);
+                                       continue;
+                               }
+
+                               string assembly_filename = assembly_name + extension;
+
+                               AssemblyName an = AssemblyName.GetAssemblyName (
+                                       Path.Combine (dir, assembly_filename));
+                               WriteLine ("Assembly: " + an.FullName);
+
                                Directory.Delete (dir, true);
                                if (package != null) {
                                        string link_dir = Path.Combine (libdir, package);
-                                       string link = Path.Combine (link_dir, (string) asm_info ["assembly"] + ".dll");
-                                       File.Delete (link);
+                                       string link = Path.Combine (link_dir, assembly_filename);
+
+                                       try { 
+                                               File.Delete (link);
+                                       } catch {
+                                               // The file might not exist, happens with
+                                               // the debugger on make uninstall
+                                       }
+                                       
                                        if (Directory.GetFiles (link_dir).Length == 0) {
                                                WriteLine ("Cleaning package directory, it is empty.");
                                                try {
@@ -366,10 +568,12 @@ namespace Mono.Tools {
                                                }
                                        }
                                }
-                               WriteLine ("Assembly removed from the gac.");
+
+                               uninstalled++;
+                               WriteLine ("Uninstalled: " + an.FullName);
                        }
 
-                       if(Directory.GetDirectories (asmdir).Length == 0) {
+                       if (Directory.GetDirectories (asmdir).Length == 0) {
                                WriteLine ("Cleaning assembly dir, it is empty");
                                try {
                                        Directory.Delete (asmdir);
@@ -377,8 +581,6 @@ namespace Mono.Tools {
                                        // Workaround: GetFiles does not list Symlinks
                                }
                        }
-
-                       return true;
                }
 
                private static bool UninstallSpecific (string name, string package,
@@ -400,8 +602,14 @@ namespace Mono.Tools {
                                return false;
                        }
 
-                       return Uninstall (an.FullName.Replace (" ", String.Empty),
-                                       package, gacdir, libdir);
+                       int uninstallCount = 0;
+                       int uninstallFailures = 0;
+                       Uninstall (an.FullName.Replace (" ", String.Empty),
+                               package, gacdir, libdir, true, ref uninstallCount,
+                               ref uninstallFailures);
+                       WriteLine ("Assemblies uninstalled = {0}", uninstallCount);
+                       WriteLine ("Failures = {0}", uninstallFailures);
+                       return (uninstallFailures == 0);
                }
 
                private static void List (string name, string gacdir)
@@ -465,6 +673,8 @@ namespace Mono.Tools {
                {
                        StreamReader s = null;
                        int processed, failed;
+                       string listdir = Path.GetDirectoryName (
+                               Path.GetFullPath (list_file));
 
                        processed = failed = 0;
 
@@ -473,49 +683,65 @@ namespace Mono.Tools {
 
                                string line;
                                while ((line = s.ReadLine ()) != null) {
-                                       if (!Install (check_refs, line, package, gacdir, link_gacdir,
-                                                           libdir, link_libdir))
+                                       string file = line.Trim ();
+                                       if (file.Length == 0)
+                                               continue;
+
+                                       string assemblyPath = Path.Combine (listdir,
+                                               file);
+
+                                       if (!Install (check_refs, assemblyPath, package, gacdir,
+                                                    link_gacdir, libdir, link_libdir))
                                                failed++;
                                        processed++;
-                                                       
                                }
-                       } catch (IOException ioe) {
+
+                               WriteLine ("Assemblies processed = {0}", processed);
+                               WriteLine ("Assemblies installed = {0}", processed - failed);
+                               WriteLine ("Failures = {0}", failed);
+
+                               return (failed == 0);
+                       } catch (IOException) {
                                WriteLine ("Failed to open assemblies list file " + list_file + ".");
                                return false;
                        } finally {
                                if (s != null)
                                        s.Close ();
                        }
-
-                       return true;
                }
 
                private static bool UninstallFromList (string list_file, string package,
                                string gacdir, string libdir)
                {
                        StreamReader s = null;
-                       int processed, failed;
+                       int failed, uninstalled;
 
-                       processed = failed = 0;
+                       failed = uninstalled = 0;
 
                        try {
                                s = new StreamReader (list_file);
 
                                string line;
                                while ((line = s.ReadLine ()) != null) {
-                                       if (!Uninstall (line, package, gacdir, libdir))
-                                               failed++;
-                                       processed++;
+                                       string name = line.Trim ();
+                                       if (name.Length == 0)
+                                               continue;
+                                       Uninstall (line, package, gacdir, libdir,
+                                               true, ref uninstalled, ref failed);
                                }
-                       } catch (IOException ioe) {
+
+                               WriteLine ("Assemblies processed = {0}", uninstalled+failed);
+                               WriteLine ("Assemblies uninstalled = {0}", uninstalled);
+                               WriteLine ("Failures = {0}", failed);
+
+                               return (failed == 0);
+                       } catch (IOException) {
                                WriteLine ("Failed to open assemblies list file " + list_file + ".");
                                return false;
                        } finally {
                                if (s != null)
                                        s.Close ();
                        }
-
-                       return true;
                }
 
                private static bool CheckReferencedAssemblies (AssemblyName an)
@@ -534,7 +760,7 @@ namespace Mono.Tools {
                                                return false;
                                        }
                                }
-                       } catch  (Exception e) {
+                       } catch (Exception e) {
                                WriteLine (e.ToString ()); // This should be removed pre beta3
                                return false;
                        } finally {
@@ -555,7 +781,7 @@ namespace Mono.Tools {
                        string version, culture, token;
 
                        version = asm_info ["version"] as string;
-                       version = (version == null ? "*" : version);
+                       version = (version == null ? "*" : version + "*");
                        culture = asm_info ["culture"] as string;
                        culture = (culture == null ? "*" : (culture == "neutral") ? String.Empty : culture.ToLower (CultureInfo.InvariantCulture));
                        token = asm_info ["publickeytoken"] as string;
@@ -572,9 +798,56 @@ namespace Mono.Tools {
                                        pieces [2]);
                }
 
+               static bool LoadConfig (bool quiet)
+               {
+                       MethodInfo config = typeof (System.Environment).GetMethod ("GetMachineConfigPath",
+                               BindingFlags.Static | BindingFlags.NonPublic);
+
+                       if (config != null) {
+                               string path = (string) config.Invoke (null, null);
+
+                               bool exist = File.Exists (path);
+                               if (!quiet && !exist)
+                                       Console.WriteLine ("Couldn't find machine.config");
+
+                               StrongNameManager.LoadConfig (path);
+                               return exist;
+                       } else if (!quiet)
+                               Console.WriteLine ("Couldn't resolve machine.config location (corlib issue)");
+
+                       // default CSP
+                       return false;
+               }
+
+               // modified copy from sn
+               private static VerificationResult VerifyStrongName (AssemblyName an, string assemblyFile)
+               {
+                       byte [] publicKey = StrongNameManager.GetMappedPublicKey (an.GetPublicKeyToken ());
+                       if ((publicKey == null) || (publicKey.Length < 12)) {
+                               // no mapping
+                               publicKey = an.GetPublicKey ();
+                               if ((publicKey == null) || (publicKey.Length < 12))
+                                       return VerificationResult.WeakNamed;
+                       }
+
+                       // Note: MustVerify is based on the original token (by design). Public key
+                       // remapping won't affect if the assembly is verified or not.
+                       if (StrongNameManager.MustVerify (an)) {
+                               RSA rsa = CryptoConvert.FromCapiPublicKeyBlob (publicKey, 12);
+                               StrongName sn = new StrongName (rsa);
+                               if (sn.Verify (assemblyFile)) {
+                                       return VerificationResult.StrongNamed;
+                               } else {
+                                       return VerificationResult.DelaySigned;
+                               }
+                       } else {
+                               return VerificationResult.Skipped;
+                       }
+               }
+
                private static bool IsSwitch (string arg)
                {
-                       return (arg [0] == '-' || arg [0] == '/');
+                       return (arg [0] == '-' || (arg [0] == '/' && !arg.EndsWith (".dll") && !arg.EndsWith (".exe") && arg.IndexOf ('/', 1) < 0 ) );
                }
 
                private static Command GetCommand (string arg)
@@ -656,14 +929,6 @@ namespace Mono.Tools {
                        return sb.ToString ();
                }
 
-               private static string CombinePaths (string a, string b)
-               {
-                       string dsc = Path.DirectorySeparatorChar.ToString ();
-                       string sep = (a.EndsWith (dsc) ? String.Empty : dsc);
-                       string end = (b.StartsWith (dsc) ? b.Substring (1) : b);
-                       return String.Concat (a, sep, end);
-               }
-
                private static string EnsureLib (string dir)
                {
                        DirectoryInfo d = new DirectoryInfo (dir);