1 // 2 // Copyright (c) 2010-2018 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using System.Collections.Generic; 10 using Antmicro.Renode.Exceptions; 11 using Antmicro.Renode.Utilities; 12 using System.Linq; 13 using Antmicro.Renode.Logging; 14 using Antmicro.Renode.UserInterface; 15 using Mono.Cecil; 16 17 namespace Antmicro.Renode.Plugins 18 { 19 public sealed class PluginManager : IDisposable 20 { DisableAllPlugins()21 public void DisableAllPlugins() 22 { 23 Dispose(); 24 SaveConfiguration(); 25 } 26 DisablePlugin(string name)27 public void DisablePlugin(string name) 28 { 29 DisablePlugin(FindPluginFromName(name)); 30 } 31 DisablePlugin(PluginDescriptor plugin)32 public void DisablePlugin(PluginDescriptor plugin) 33 { 34 DisablePlugin(plugin, new HashSet<PluginDescriptor>(), false); 35 } 36 EnablePlugin(string name)37 public void EnablePlugin(string name) 38 { 39 EnablePlugin(FindPluginFromName(name)); 40 } 41 EnablePlugin(PluginDescriptor plugin)42 public void EnablePlugin(PluginDescriptor plugin) 43 { 44 if(plugin.Modes.Any() && !(enabledModes.Any(x => plugin.Modes.Contains(x)))) 45 { 46 throw new RecoverableException(string.Format("Plugin {0} is not suitable for any of available modes: {1}.", plugin.FullName, string.Join(", ", enabledModes))); 47 } 48 EnablePlugin(plugin, new HashSet<PluginDescriptor>(), false); 49 } 50 GetPlugins()51 public string[,] GetPlugins() 52 { 53 var table = new Table().AddRow("Name", "Description", "Vendor", "Version", "Mode", "State"); 54 table.AddRows(TypeManager.Instance.AvailablePlugins.Where(x => !x.IsHidden), 55 x => x.Name, 56 x => x.Description, 57 x => x.Vendor, 58 x => x.Version.ToString(), 59 x => string.Join(", ", x.Modes), 60 x => activePlugins.ContainsKey(x) ? "enabled" : "disabled" 61 ); 62 return table.ToArray(); 63 } 64 Dispose()65 public void Dispose() 66 { 67 foreach(var plugin in activePlugins.Values.OfType<IDisposable>()) 68 { 69 plugin.Dispose(); 70 } 71 72 activePlugins.Clear(); 73 } 74 75 [HideInMonitor] GetPluginsMap()76 public IDictionary<PluginDescriptor, bool> GetPluginsMap() 77 { 78 return TypeManager.Instance.AvailablePlugins.ToDictionary(key => key, value => activePlugins.ContainsKey(value)); 79 } 80 81 [HideInMonitor] Init(params string[] modes)82 public void Init(params string[] modes) 83 { 84 enabledModes = new HashSet<string>(modes); 85 var enabledPlugins = ConfigurationManager.Instance.Get(ConfigSection, ConfigOption, string.Empty); 86 foreach(var pluginName in enabledPlugins.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 87 { 88 try 89 { 90 EnablePlugin(pluginName.Trim()); 91 } 92 catch(RecoverableException e) 93 { 94 Logger.LogAs(this, LogLevel.Warning, "Could not load plugin. {0}", e.Message); 95 } 96 } 97 } 98 DisablePlugin(PluginDescriptor plugin, HashSet<PluginDescriptor> disabledPlugins, bool disableHidden)99 private void DisablePlugin(PluginDescriptor plugin, HashSet<PluginDescriptor> disabledPlugins, bool disableHidden) 100 { 101 if(plugin.IsHidden && !disableHidden) 102 { 103 throw new RecoverableException(string.Format("This plugin cannot be disabled directly: {0}.", plugin.FullName)); 104 } 105 106 var availablePlugins = TypeManager.Instance.AvailablePlugins; 107 108 if(!availablePlugins.Contains(plugin)) 109 { 110 throw new RecoverableException(string.Format("There is no plugin named {0}.", plugin.FullName)); 111 } 112 if(activePlugins.ContainsKey(plugin)) 113 { 114 disabledPlugins.Add(plugin); 115 foreach(var dependantPlugin in availablePlugins.Where(x => x != plugin && x.Dependencies != null && x.Dependencies.Contains(plugin.ThisType, typeComparer))) 116 { 117 if(!disabledPlugins.Contains(dependantPlugin)) 118 { 119 DisablePlugin(dependantPlugin, disabledPlugins, true); 120 } 121 } 122 var disposable = activePlugins[plugin] as IDisposable; 123 if(disposable != null) 124 { 125 disposable.Dispose(); 126 } 127 activePlugins.Remove(plugin); 128 disabledPlugins.Remove(plugin); 129 SaveConfiguration(); 130 } 131 } 132 EnablePlugin(PluginDescriptor plugin, HashSet<PluginDescriptor> enabledPlugins, bool enableHidden)133 private void EnablePlugin(PluginDescriptor plugin, HashSet<PluginDescriptor> enabledPlugins, bool enableHidden) 134 { 135 if(plugin.IsHidden && !enableHidden) 136 { 137 throw new RecoverableException(string.Format("This plugin cannot be enabled directly: {0}.", plugin.FullName)); 138 } 139 140 var availablePlugins = TypeManager.Instance.AvailablePlugins; 141 if(!availablePlugins.Contains(plugin)) 142 { 143 throw new RecoverableException(string.Format("There is no plugin named {0}.", plugin.FullName)); 144 } 145 if(!activePlugins.ContainsKey(plugin)) 146 { 147 enabledPlugins.Add(plugin); 148 if(plugin.Dependencies != null) 149 { 150 foreach(var referencedPlugin in plugin.Dependencies) 151 { 152 var referencedPluginDescriptor = availablePlugins.SingleOrDefault(x => typeComparer.Equals(x.ThisType, referencedPlugin)); 153 if(referencedPluginDescriptor == null) 154 { 155 throw new RecoverableException("Plugin {0} not found.".FormatWith(referencedPlugin.GetFullNameOfMember())); 156 } 157 if(enabledPlugins.Contains(referencedPluginDescriptor)) 158 { 159 throw new RecoverableException("Circular plugin dependency between {0} and {1}.".FormatWith(plugin.FullName, referencedPluginDescriptor.FullName)); 160 } 161 EnablePlugin(referencedPluginDescriptor, enabledPlugins, true); 162 } 163 } 164 activePlugins[plugin] = plugin.CreatePlugin(); 165 enabledPlugins.Remove(plugin); 166 SaveConfiguration(); 167 } 168 } 169 FindPluginFromName(string name)170 private PluginDescriptor FindPluginFromName(string name) 171 { 172 var pluginNameComponents = name.Split(SplitSeparator); 173 var availablePlugins = TypeManager.Instance.AvailablePlugins; 174 IEnumerable<PluginDescriptor> plugins = null; 175 switch(pluginNameComponents.Length) 176 { 177 case 3: 178 plugins = availablePlugins.Where(x => x.FullName == name); 179 break; 180 case 2: 181 plugins = availablePlugins.Where(x => x.Name == pluginNameComponents[0] && x.Version.ToString() == pluginNameComponents[1]); 182 break; 183 case 1: 184 plugins = availablePlugins.Where(x => x.Name == pluginNameComponents[0]); 185 break; 186 default: 187 throw new RecoverableException("Malformed plugin name \"{0}\"".FormatWith(name)); 188 } 189 if(plugins.Count() == 0) 190 { 191 throw new RecoverableException(string.Format("There is no plugin named {0}.", name)); 192 } 193 if(plugins.Count() > 1) 194 { 195 throw new RecoverableException(string.Format("Ambiguous choice for \"{0}\". The possible choices are: {1}." 196 .FormatWith(name, plugins.Select(x => "\"{0}\"".FormatWith(x.FullName)).Stringify(", ")))); 197 } 198 return plugins.First(); 199 } 200 SaveConfiguration()201 private void SaveConfiguration() 202 { 203 ConfigurationManager.Instance.Set(ConfigSection, ConfigOption, activePlugins.Any() ? activePlugins.Select(x => x.Key.FullName).Aggregate((curr, next) => curr + "," + next) : string.Empty); 204 } 205 206 private readonly TypeDefinitionComparer typeComparer = new TypeDefinitionComparer(); 207 private readonly Dictionary<PluginDescriptor, object> activePlugins = new Dictionary<PluginDescriptor, object>(); 208 209 private HashSet<string> enabledModes; 210 211 private const string ConfigOption = "enabled-plugins"; 212 private const string ConfigSection = "plugins"; 213 private const char SplitSeparator = ':'; 214 215 private class TypeDefinitionComparer : IEqualityComparer<TypeDefinition> 216 { Equals(TypeDefinition t1, TypeDefinition t2)217 public bool Equals(TypeDefinition t1, TypeDefinition t2) 218 { 219 if(t1.HasGenericParameters || t2.HasGenericParameters) 220 { 221 throw new ArgumentException("Generic parameters in plugin of type {0} not supported.".FormatWith(t1.HasGenericParameters ? t1.Name : t2.Name)); 222 } 223 return t1.Module.Mvid == t2.Module.Mvid && t1.MetadataToken == t2.MetadataToken; 224 } 225 GetHashCode(TypeDefinition obj)226 public int GetHashCode(TypeDefinition obj) 227 { 228 var hash = obj.Module.Mvid.GetHashCode(); 229 hash = (hash * 397) ^ obj.MetadataToken.GetHashCode(); 230 hash = (hash * 397) ^ obj.HasGenericParameters.GetHashCode(); 231 return hash; 232 } 233 } 234 } 235 } 236 237