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