In tools/corcompare:
[mono.git] / mcs / tools / gacutil / gacutil.cs
1 // GacUtil
2 //
3 // Author: Todd Berman <tberman@gentoo.org>
4 //
5 // (C) 2003 Todd Berman
6
7 using System;
8 using System.IO;
9 using System.Text;
10 using System.Reflection;
11 using System.Collections;
12 using System.Globalization;
13 using System.Runtime.InteropServices;
14
15 using Mono.Security;
16
17 namespace Mono.Tools
18 {
19
20         public class Driver
21         {
22
23                 private string libdir = InternalLibdir () + Path.DirectorySeparatorChar;
24                 private string gac_path = GetGacPath ();
25                 private string package_name = String.Empty;
26                 string installed_gac;
27                 
28                 public static int Main (string[] args)
29                 {
30                         Driver d = new Driver ();
31                         return d.Run (args);
32                 }
33
34                 public int Run (string[] args)
35                 {
36                         if (args.Length == 0) {
37                                 ShowHelp (false);
38                                 return 1;
39                         }
40         
41                         if (args[0] == "/user" || args[0] == "--user") {
42                                 //FIXME: Need to check machine.config to make sure this is legal (potential security hole)
43                                 gac_path = Path.Combine (Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), ".mono"), "gac");
44                                 gac_path += Path.DirectorySeparatorChar;
45                                 libdir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), ".mono");
46                                 libdir += Path.DirectorySeparatorChar;
47
48                                 string[] stripped = new string[args.Length - 1];
49                                 Array.Copy (args, 1, stripped, 0, args.Length - 1);
50                                 args = stripped;
51                         }
52
53                         installed_gac = gac_path;
54                         if (args.Length >= 2 && (args[args.Length - 2] == "/root" || args[args.Length - 2] == "-root" || args[args.Length - 2] == "--root")) {
55                                 gac_path = Path.Combine (Path.Combine (args[args.Length - 1], "mono"), "gac");
56                                 gac_path += Path.DirectorySeparatorChar;
57                                 libdir = Path.Combine (args[args.Length - 1], "mono");
58                                 libdir += Path.DirectorySeparatorChar;
59
60                                 string[] stripped = new string[args.Length - 2];                                Array.Copy (args, 0, stripped, 0, args.Length - 2);
61                                 args = stripped;
62                         }
63
64                         if (args.Length >= 2 && (args[args.Length - 2] == "/package" || args[args.Length - 2] == "--package" || args[args.Length - 2] == "-package")) {
65                                 package_name = args[args.Length - 1];
66                                 string[] stripped = new string[args.Length - 2];
67                                 Array.Copy (args, 0, stripped, 0, args.Length - 2);
68                                 args = stripped;
69                         }
70         
71
72                         string[] remainder_args = new string[args.Length - 1];
73                         
74                         if (args.Length >= 2) {
75                                 Array.Copy (args, 1, remainder_args, 0, args.Length - 1);
76                         }
77         
78                         switch (args[0]) {
79                         case "/?":
80                         case "-?":
81                         case "--help":
82                                 ShowHelp (true);
83                                 return 0;
84                         case "-i":
85                         case "/i":
86                         case "--install":
87                                 return InstallAssembly (remainder_args);
88                         case "/l":
89                         case "-l":
90                         case "--ls":
91                                 return ListAssemblies (remainder_args);
92                         case "/u":
93                         case "-u":
94                         case "--uninstall":
95                                 return UninstallAssemblies (remainder_args);
96                         case "/il":
97                         case "-il":
98                         case "--install-from-list":
99                                 return InstallAssembliesFromList (remainder_args);
100                         case "/ul":
101                         case "-ul":
102                         case "--uninstall-from-list":
103                                 return UninstallAssembliesFromList (remainder_args);
104                         default:
105                                 ShowHelp (false);
106                                 break;
107                         }
108
109                         return 1;
110                 }
111
112                 public int InstallAssembliesFromList (string[] args)
113                 {
114                         if (args.Length == 0) {
115                                 Console.WriteLine ("ERROR: need a file passed");
116                                 return 1;
117                         }
118
119                         if (!File.Exists (args[0])) {
120                                 Console.WriteLine ("ERROR: file '" + args[0] + "' does not exist");
121                                 return 1;
122                         }
123
124                         string[] perFile = args;
125
126                         int result = 0;
127                         using (StreamReader s = File.OpenText (args[0])) {
128                                 string line;
129
130                                 while((line = s.ReadLine()) != null) {
131                                         perFile[0] = line;
132                                         try {
133                                                 if (InstallAssembly (perFile) != 0)
134                                                         result = 1;
135                                         } catch (Exception e) {
136                                                 Console.WriteLine ("Failed for {0}. Reason: {1}", line, e.Message);
137                                                 result = 1;
138                                         }
139                                 }
140                         }
141
142                         return result;
143                 }
144
145                 public int UninstallAssembliesFromList (string[] args)
146                 {
147                         if (args.Length == 0) {
148                                 Console.WriteLine ("ERROR: file must be passed.");
149                                 return 1;
150                         }
151
152                         if (!File.Exists (args[0])) {
153                                 Console.WriteLine ("ERROR: file '" + args[0] + "' does not exist");
154                                 return 1;
155                         }
156
157                         int result = 0;
158                         using (StreamReader s = File.OpenText (args[0])) {
159                                 string line;
160
161                                 while ((line = s.ReadLine ()) != null) {
162                                         if (UninstallAssemblies (new string[] { line } ) != 0)
163                                                 result = 1;
164                                 }
165                         }
166
167                         return result;
168                 }
169
170                 public int UninstallAssemblies (string[] args)
171                 {
172                         if(args.Length == 0) {
173                                 Console.WriteLine ("ERROR: need an argument to uninstall");
174                                 return 1;
175                         }
176
177                         string joinedArgs = String.Join ("", args);
178
179                         string[] assemblyPieces = joinedArgs.Split(new char[] { ',' });
180
181                         Hashtable paramInfo = new Hashtable ();
182
183                         foreach (string item in assemblyPieces) {
184                                 string[] pieces = item.Trim ().Split (new char[] { '=' }, 2);
185                                 if(pieces.Length == 1)
186                                         paramInfo["assembly"] = pieces[0];
187                                 else
188                                         paramInfo[pieces[0].Trim ().ToLower (CultureInfo.InvariantCulture)] = pieces[1];
189                         }
190
191                         if (!Directory.Exists (Path.Combine (gac_path, (string) paramInfo["assembly"]))) {
192                                 Console.WriteLine ("ERROR: Assembly not in gac.");
193                                 return 1;
194                         }
195
196                         string searchString = (string) paramInfo["assembly"] + Path.DirectorySeparatorChar;
197
198                         if (paramInfo.Keys.Count != 1) {
199                                 if (paramInfo["version"] != null) {
200                                         searchString += (string) paramInfo["version"] + "*";
201                                 }
202                         } else {
203                                 searchString += "*";
204                         }
205
206                         string[] directories = Directory.GetDirectories (gac_path, searchString);
207
208                         foreach (string dir in directories) {
209                                 Hashtable info = GetAssemblyInfo (Path.Combine (dir, "__AssemblyInfo__"));
210                                 if(Convert.ToInt32 (info["RefCount"]) == 1) {
211                                         Directory.Delete (dir, true);
212                                         if (package_name != String.Empty) {
213                                                 File.Delete (libdir + package_name + Path.DirectorySeparatorChar + (string)paramInfo["assembly"] + ".dll");
214                                         }
215                                         Console.WriteLine ("Assembly removed from the gac.");
216                                 } else {
217                                         info["RefCount"] = ((int) Convert.ToInt32 (info["RefCount"]) - 1).ToString ();
218                                         WriteAssemblyInfo (Path.Combine (dir, "__AssemblyInfo__"), info);
219                                         Console.WriteLine ("Assembly was not deleted because its still needed by other applications");
220                                 }
221                         }
222                         if(Directory.GetDirectories (Path.Combine (gac_path, (string) paramInfo["assembly"])).Length == 0) {
223                                 Console.WriteLine ("Cleaning assembly dir, its empty");
224                                 Directory.Delete (Path.Combine (gac_path, (string) paramInfo["assembly"]));
225                         }
226
227                         return 0;
228                 }
229
230                 public int ListAssemblies (string[] args)
231                 {
232                         Console.WriteLine ("The following assemblies are installed into the GAC:");
233                         DirectoryInfo d = new DirectoryInfo (gac_path);
234                         foreach (DirectoryInfo namedDir in d.GetDirectories ()) {
235                                 foreach (DirectoryInfo assemblyDir in namedDir.GetDirectories ()) {
236                                         Hashtable assemblyInfo = GetAssemblyInfo (Path.Combine (assemblyDir.FullName, "__AssemblyInfo__"));
237                                         if (assemblyInfo != null){
238                                                 Console.WriteLine ("\t" + assemblyInfo["DisplayName"]);
239                                         }
240                                 }
241                         }
242
243                         return 0;
244                 }
245
246                 private Hashtable GetAssemblyInfo (string filename)
247                 {
248                         try {
249                                 Hashtable infoHash = new Hashtable ();
250                                 using (StreamReader s = new StreamReader (filename)) {
251                                         string line;
252                                         
253                                         while ((line = s.ReadLine ()) != null) {
254                                                 string[] splitStr = line.Split (new char[] { '=' }, 2);
255                                                 infoHash[splitStr[0]] = splitStr[1];
256                                         }
257                                 }
258                                 return infoHash;
259                         } catch {
260                                 return null;
261                         }
262                 }
263
264                 private void WriteAssemblyInfo (string filename, Hashtable info)
265                 {
266                         using (StreamWriter s = File.CreateText (filename)) {
267                                 foreach (string key in info.Keys) {
268                                         s.WriteLine (key + "=" + (string) info[key]);
269                                 }
270                         }
271                 }
272
273                 public int InstallAssembly (string[] args)
274                 {
275                         if(args.Length == 0) {
276                                 Console.WriteLine ("ERROR: You must specify a valid assembly name after the install switch");
277                                 return 1;
278                         }
279
280                         if(!File.Exists (args[0])) {
281                                 Console.WriteLine ("ERROR: The assembly: '" + args[0] + "' does not exist");
282                                 return 1;
283                         }
284
285                         AssemblyName an = AssemblyName.GetAssemblyName (args[0]);
286                         string config_path = null;
287                         byte[] pub_tok = an.GetPublicKeyToken ();
288
289                         if (pub_tok == null || pub_tok.Length == 0) {
290                                 Console.WriteLine ("ERROR: assembly has no valid public key token");
291                                 return 1;
292                         }
293
294                         config_path = args [0] + ".config";
295                         // strong name verification temp. disabled
296                         /*
297                         byte[] akey = an.GetPublicKey ();
298                         if (akey == null || akey.Length < 12) {
299                                 Console.WriteLine ("ERROR: assembly has no valid public key token");
300                                 return;
301                         }
302                         StrongName sn = new StrongName (akey);
303                         if (!sn.Verify (args[0])) {
304                                 Console.WriteLine ("ERROR: invalid strongname signature in assembly");
305                                 return;
306                         }
307                         */
308
309                         //FIXME: force=true per mig's request.
310                         bool force = true;
311
312                         if (Array.IndexOf (args, "/f") != -1 || Array.IndexOf (args, "-f") != -1 ||
313                             Array.IndexOf (args, "--force") != -1) {
314                                 force = true;
315                         }
316                         
317                         string version_token = an.Version + "_" +
318                                 an.CultureInfo.Name.ToLower (CultureInfo.InvariantCulture) +
319                                 "_" + GetStringToken (an.GetPublicKeyToken ());
320
321                         string fullPath = String.Format ("{0}{3}{1}{3}{2}{3}", gac_path, an.Name, version_token, Path.DirectorySeparatorChar);
322                         string linkPath = String.Format ("{0}{3}{1}{3}{2}{3}", installed_gac, an.Name, version_token, Path.DirectorySeparatorChar);
323
324                         if (File.Exists (fullPath + an.Name + ".dll") && force == false) {
325                                 Hashtable assemInfo = GetAssemblyInfo (fullPath + "__AssemblyInfo__");
326
327                                 if (assemInfo != null){
328                                         assemInfo["RefCount"] = ((int) Convert.ToInt32 (assemInfo["RefCount"]) + 1).ToString ();
329                                         WriteAssemblyInfo (fullPath + "__AssemblyInfo__", assemInfo);
330                                         Console.WriteLine ("RefCount of assembly '" + an.Name + "' increased by one.");
331                                         if (File.Exists (config_path))
332                                                 File.Copy (config_path, fullPath + an.Name + ".dll" + ".config", force);
333                                         InstallPackage (libdir, linkPath, an, args [0], Path.GetFileName (args [0]));
334                                 }
335                                 return 0;
336                         }
337
338                         if(!EnsureDirectories (an.Name, version_token)) {
339                                 Console.WriteLine ("ERROR: gac directories could not be created, possibly permission issues");
340                                 return 1;
341                         }
342
343                         File.Copy (args[0], fullPath + an.Name + ".dll", force);
344                         InstallPackage (libdir, linkPath, an, args [0], Path.GetFileName (args [0]));
345                         if (File.Exists (config_path)){
346                                 File.Copy (config_path, fullPath + an.Name + ".dll" + ".config", force);
347                         }
348
349                         Hashtable info = new Hashtable ();
350
351                         info["DisplayName"] = an.FullName;
352                         info["RefCount"] = 1.ToString ();
353
354                         WriteAssemblyInfo (fullPath + "__AssemblyInfo__", info);
355
356                         Console.WriteLine ("{0} installed into the gac ({1})", an.Name, gac_path);
357                         return 0;
358                 }
359
360                 private void InstallPackage (string libdir, string linkPath,
361                                 AssemblyName an, string path, string filename)
362                 {
363                         if (package_name != String.Empty) {
364                                 string ref_file = libdir + package_name +
365                                                 Path.DirectorySeparatorChar + filename;
366                                 if (File.Exists (ref_file)) {
367                                         File.Delete (ref_file);
368                                 }
369                                 if (Path.DirectorySeparatorChar == '/') {
370                                         try {
371                                                 Directory.CreateDirectory (libdir + package_name);
372                                         } catch {}
373                                         
374                                         symlink (linkPath + an.Name + ".dll", ref_file);
375                                 } else {
376                                         
377                                         File.Copy (path, ref_file);
378                                 }
379                                 Console.WriteLine ("Package exported to: " + libdir + package_name);
380                         }
381                 }
382
383                 private bool EnsureDirectories (string name, string tok)
384                 {
385                         //FIXME: Workaround for broken DirectoryInfo.CreateSubdirectory
386                         try {
387                                 DirectoryInfo d = new DirectoryInfo (gac_path);
388
389                                 d.CreateSubdirectory (name);
390                                 d = new DirectoryInfo (Path.Combine (gac_path, name));
391                                 d.CreateSubdirectory (tok);
392                                 if (package_name != String.Empty) {
393                                         d = new DirectoryInfo (libdir);
394                                         d.CreateSubdirectory (package_name);
395                                 }
396                         } catch {
397                                 return false;
398                         }
399                         return true;
400                 }
401
402                 private string GetStringToken (byte[] tok)
403                 {
404                         StringBuilder sb = new StringBuilder ();
405                         for (int i = 0; i < tok.Length ; i++) {
406                                 sb.Append (tok[i].ToString ("x2"));
407                         }
408                         return sb.ToString ();
409                 }
410
411                 public void ShowHelp (bool detailed)
412                 {
413                         StringBuilder sb = new StringBuilder ();
414
415                         sb.Append ("Usage: gacutil.exe <commands> [ <options> ]\n");
416                         sb.Append ("Commands:\n");
417                         sb.Append ("  -i <assembly_path> [ -f ] [-package NAME] [-root ROOTDIR]\n");
418                         if (detailed == false) {
419                                 sb.Append ("    Installs an assembly into the global assembly cache\n");
420                         } else {
421                                 sb.Append ("    Installs an assembly to the global assembly cache. <assembly_path> is the\n     name of the file that contains the assembly manifest.                    \n     Example: -i myDll.dll\n");
422                         }
423
424                         sb.Append ("\n");
425
426                         sb.Append ("  -il <assembly_path_list_file> [ -f ]\n");
427                         if (detailed == false) {
428                                 sb.Append ("    Installs one or more assemblies into the global assembly cache\n");
429                         } else {
430                                 sb.Append ("    Installs on or more assemblies to the global assembly cache.              \n    <assembly_list_file is the path to a text file that contains a list of    \n    assembly manifest file paths. Individual paths in the text file must be   \n    separated by a newline.\n");
431                                 sb.Append ("    Example: -il MyAssemblyList\n");
432                                 sb.Append ("      MyAssemblyList content:\n");
433                                 sb.Append ("      Mydll.dll\n");
434                                 sb.Append ("      Mydll2.dll\n");
435                                 sb.Append ("      path/to/myDll3.dll\n");
436                         }
437
438                         sb.Append ("\n");
439
440                         sb.Append ("  -u <assembly_display_name> [-package NAME] [-root ROOTDIR]\n");
441                         if (detailed == false) {
442                                 sb.Append ("    Uninstalls an assembly from the global assembly cache\n");
443                         } else {
444                                 sb.Append ("    Uninstalls an assembly. <assembly_display_name> is the name of the assembly\n");
445                                 sb.Append ("    (partial or fully qualified) to remove from the global assembly cache.    \n    If a partial name is specified all matching assemblies will be uninstalled.\n");
446                                 sb.Append ("    Example: /u myDll,Version=1.2.1.0\n");
447                         }
448
449                         sb.Append ("\n");
450
451                         sb.Append ("  -ul <assembly_display_name_list_file>\n");
452                         if (detailed == false) {
453                                 sb.Append ("    Uninstalls one or more assemblies from the global assembly cache\n");
454                         } else {
455                                 sb.Append ("    Uninstalls one or more assemblies from the global assembly cache.         \n    <assembly_display_name_list_file> is the path to a text file that contains\n    a list of assembly names. Individual names in the text file must be       \n    separated by a newline.\n");
456                                 sb.Append ("    Example: /ul MyAssemblyList\n");
457                                 sb.Append ("      MyAssemblyList content:\n");
458                                 sb.Append ("      MyDll1.dll,Version=1.0.0.0\n");
459                                 sb.Append ("      MyDll2.dll,Version=1.2.0.0\n");
460                         }
461
462                         sb.Append ("\n");
463
464                         sb.Append ("  -l\n");
465                         if (detailed == false) {
466                                 sb.Append ("    List the global assembly cache\n");
467                         } else {
468                                 sb.Append ("    Lists the contents of the global assembly cache.\n");
469                         }
470
471                         sb.Append ("\n");
472
473                         sb.Append ("  -?\n");
474                         if (detailed == false) {
475                                 sb.Append ("    Displays a detailed help screen\n");
476                         } else {
477                                 sb.Append ("    Displays a detailed help screen\n");
478                         }
479
480                         sb.Append ("\n");
481
482                         if (detailed == true) {
483                                 sb.Append ("Options:\n");
484                                 sb.Append ("  -f\n");
485                                 sb.Append ("    Forces reinstall of assembly, resets reference count\n");
486                                 sb.Append ("\n");
487                                 sb.Append ("\n");
488                                 sb.Append ("Note, mono's gacutil also supports these unix like aliases for its commands:\n");
489                                 sb.Append ("  -i  -> --install\n");
490                                 sb.Append ("  -il -> --install-from-list\n");
491                                 sb.Append ("  -u  -> --uninstall\n");
492                                 sb.Append ("  -ul -> --uninstall-from-list\n");
493                                 sb.Append ("  -l  -> --ls\n");
494                                 sb.Append ("  -f  -> --force\n");
495                                 sb.Append ("\n");
496                                 sb.Append ("Mono also allows a User Assembly Cache, this cache can be accessed by passing\n/user as the first argument to gacutil.exe\n");
497                         }
498
499                         Console.WriteLine (sb.ToString ());
500                 }
501
502                 private static string GetGacPath () {
503                         PropertyInfo gac = typeof (System.Environment).GetProperty ("GacPath", BindingFlags.Static|BindingFlags.NonPublic);
504                         if (gac == null) {
505                                 Console.WriteLine ("ERROR: MS.Net runtime detected, please use the mono runtime for gacutil.exe");
506                                 Environment.Exit (1);
507                         }
508                         MethodInfo getGac = gac.GetGetMethod (true);
509                         return Path.Combine ((string) getGac.Invoke (null, null), "");
510                 }
511
512                 private static string InternalLibdir () {
513                         MethodInfo libdir = typeof (System.Environment).GetMethod ("internalGetGacPath", BindingFlags.Static|BindingFlags.NonPublic);
514                         if (libdir == null) {
515                                 Console.WriteLine ("ERROR: MS.Net runtime detected, please use the mono runtime for gacutil.exe");
516                                 Environment.Exit (1);
517                         }
518                         return Path.Combine (Path.Combine ((string)libdir.Invoke (null, null), "mono"), "");
519                 }
520
521                 [DllImport ("libc", SetLastError=true)]
522                 public static extern int symlink (string oldpath, string newpath);
523         }
524 }