vdr  2.4.7
plugin.c
Go to the documentation of this file.
1 /*
2  * plugin.c: The VDR plugin interface
3  *
4  * See the main source file 'vdr.c' for copyright information and
5  * how to reach the author.
6  *
7  * $Id: plugin.c 4.4 2020/12/16 11:54:06 kls Exp $
8  */
9 
10 #include "plugin.h"
11 #include <ctype.h>
12 #include <dirent.h>
13 #include <dlfcn.h>
14 #include <stdlib.h>
15 #include <time.h>
16 #include "config.h"
17 #include "interface.h"
18 #include "thread.h"
19 
20 #define LIBVDR_PREFIX "libvdr-"
21 #define SO_INDICATOR ".so."
22 
23 #define MAXPLUGINARGS 1024
24 #define HOUSEKEEPINGDELTA 10 // seconds
25 
26 // --- cPlugin ---------------------------------------------------------------
27 
31 
33 {
34  name = NULL;
35  started = false;
36 }
37 
39 {
40 }
41 
42 void cPlugin::SetName(const char *s)
43 {
44  name = s;
46 }
47 
48 const char *cPlugin::CommandLineHelp(void)
49 {
50  return NULL;
51 }
52 
53 bool cPlugin::ProcessArgs(int argc, char *argv[])
54 {
55  return true;
56 }
57 
59 {
60  return true;
61 }
62 
63 bool cPlugin::Start(void)
64 {
65  return true;
66 }
67 
68 void cPlugin::Stop(void)
69 {
70 }
71 
73 {
74 }
75 
77 {
78 }
79 
81 {
82  return NULL;
83 }
84 
85 time_t cPlugin::WakeupTime(void)
86 {
87  return 0;
88 }
89 
90 const char *cPlugin::MainMenuEntry(void)
91 {
92  return NULL;
93 }
94 
96 {
97  return NULL;
98 }
99 
101 {
102  return NULL;
103 }
104 
105 bool cPlugin::SetupParse(const char *Name, const char *Value)
106 {
107  return false;
108 }
109 
110 void cPlugin::SetupStore(const char *Name, const char *Value)
111 {
112  Setup.Store(Name, Value, this->Name());
113 }
114 
115 void cPlugin::SetupStore(const char *Name, int Value)
116 {
117  Setup.Store(Name, Value, this->Name());
118 }
119 
120 bool cPlugin::Service(const char *Id, void *Data)
121 {
122  return false;
123 }
124 
125 const char **cPlugin::SVDRPHelpPages(void)
126 {
127  return NULL;
128 }
129 
130 cString cPlugin::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
131 {
132  return NULL;
133 }
134 
135 void cPlugin::SetConfigDirectory(const char *Dir)
136 {
137  configDirectory = Dir;
138 }
139 
140 const char *cPlugin::ConfigDirectory(const char *PluginName)
141 {
142  static cString buffer;
143  if (!cThread::IsMainThread())
144  esyslog("ERROR: plugin '%s' called cPlugin::ConfigDirectory(), which is not thread safe!", PluginName ? PluginName : "<no name given>");
145  buffer = cString::sprintf("%s/plugins%s%s", *configDirectory, PluginName ? "/" : "", PluginName ? PluginName : "");
146  return MakeDirs(buffer, true) ? *buffer : NULL;
147 }
148 
149 void cPlugin::SetCacheDirectory(const char *Dir)
150 {
151  cacheDirectory = Dir;
152 }
153 
154 const char *cPlugin::CacheDirectory(const char *PluginName)
155 {
156  static cString buffer;
157  if (!cThread::IsMainThread())
158  esyslog("ERROR: plugin '%s' called cPlugin::CacheDirectory(), which is not thread safe!", PluginName ? PluginName : "<no name given>");
159  buffer = cString::sprintf("%s/plugins%s%s", *cacheDirectory, PluginName ? "/" : "", PluginName ? PluginName : "");
160  return MakeDirs(buffer, true) ? *buffer : NULL;
161 }
162 
163 void cPlugin::SetResourceDirectory(const char *Dir)
164 {
165  resourceDirectory = Dir;
166 }
167 
168 const char *cPlugin::ResourceDirectory(const char *PluginName)
169 {
170  static cString buffer;
171  if (!cThread::IsMainThread())
172  esyslog("ERROR: plugin '%s' called cPlugin::ResourceDirectory(), which is not thread safe!", PluginName ? PluginName : "<no name given>");
173  buffer = cString::sprintf("%s/plugins%s%s", *resourceDirectory, PluginName ? "/" : "", PluginName ? PluginName : "");
174  return MakeDirs(buffer, true) ? *buffer : NULL;
175 }
176 
177 // --- cDll ------------------------------------------------------------------
178 
179 cDll::cDll(const char *FileName, const char *Args)
180 {
181  fileName = strdup(FileName);
182  args = Args ? strdup(Args) : NULL;
183  handle = NULL;
184  plugin = NULL;
185  destroy = NULL;
186 }
187 
189 {
190  if (destroy)
191  destroy(plugin);
192  else
193  delete plugin; // silently fall back for plugins compiled with VDR version <= 2.4.3
194  if (handle)
195  dlclose(handle);
196  free(args);
197  free(fileName);
198 }
199 
200 static char *SkipQuote(char *s)
201 {
202  char c = *s;
203  memmove(s, s + 1, strlen(s));
204  while (*s && *s != c) {
205  if (*s == '\\')
206  memmove(s, s + 1, strlen(s));
207  if (*s)
208  s++;
209  }
210  if (*s) {
211  memmove(s, s + 1, strlen(s));
212  return s;
213  }
214  esyslog("ERROR: missing closing %c", c);
215  fprintf(stderr, "vdr: missing closing %c\n", c);
216  return NULL;
217 }
218 
219 bool cDll::Load(bool Log)
220 {
221  if (Log)
222  isyslog("loading plugin: %s", fileName);
223  if (handle) {
224  esyslog("attempt to load plugin '%s' twice!", fileName);
225  return false;
226  }
227  handle = dlopen(fileName, RTLD_NOW);
228  const char *error = dlerror();
229  if (!error) {
230  typedef cPlugin *create_t(void);
231  create_t *create = (create_t *)dlsym(handle, "VDRPluginCreator");
232  error = dlerror();
233  if (!error && create) {
234  plugin = create();
235  destroy = (destroy_t *)dlsym(handle, "VDRPluginDestroyer");
236  error = dlerror();
237  if (error) {
238  error = NULL;
239  isyslog("plugin %s: missing symbol VDRPluginDestroyer(), please rebuild", fileName);
240  }
241  }
242  }
243  if (!error) {
244  if (plugin && args) {
245  int argc = 0;
246  char *argv[MAXPLUGINARGS];
247  char *p = skipspace(stripspace(args));
248  char *q = NULL;
249  bool done = false;
250  while (!done) {
251  if (!q)
252  q = p;
253  switch (*p) {
254  case '\\': memmove(p, p + 1, strlen(p));
255  if (*p)
256  p++;
257  else {
258  esyslog("ERROR: missing character after \\");
259  fprintf(stderr, "vdr: missing character after \\\n");
260  return false;
261  }
262  break;
263  case '"':
264  case '\'': if ((p = SkipQuote(p)) == NULL)
265  return false;
266  break;
267  default: if (!*p || isspace(*p)) {
268  done = !*p;
269  *p = 0;
270  if (q) {
271  if (argc < MAXPLUGINARGS - 1)
272  argv[argc++] = q;
273  else {
274  esyslog("ERROR: plugin argument list too long");
275  fprintf(stderr, "vdr: plugin argument list too long\n");
276  return false;
277  }
278  q = NULL;
279  }
280  }
281  if (!done)
282  p = *p ? p + 1 : skipspace(p + 1);
283  }
284  }
285  argv[argc] = NULL;
286  if (argc)
287  plugin->SetName(argv[0]);
288  optind = 0; // to reset the getopt() data
289  return !Log || !argc || plugin->ProcessArgs(argc, argv);
290  }
291  }
292  else {
293  esyslog("ERROR: %s", error);
294  fprintf(stderr, "vdr: %s\n", error);
295  }
296  return !error && plugin;
297 }
298 
299 // --- cPluginManager --------------------------------------------------------
300 
302 
303 cPluginManager::cPluginManager(const char *Directory)
304 {
305  directory = NULL;
306  lastHousekeeping = time(NULL);
307  nextHousekeeping = -1;
308  if (pluginManager) {
309  fprintf(stderr, "vdr: attempt to create more than one plugin manager - exiting!\n");
310  exit(2);
311  }
312  SetDirectory(Directory);
313  pluginManager = this;
314 }
315 
317 {
318  Shutdown();
319  free(directory);
320  if (pluginManager == this)
321  pluginManager = NULL;
322 }
323 
324 void cPluginManager::SetDirectory(const char *Directory)
325 {
326  free(directory);
327  directory = Directory ? strdup(Directory) : NULL;
328 }
329 
330 void cPluginManager::AddPlugin(const char *Args)
331 {
332  if (strcmp(Args, "*") == 0) {
333  cFileNameList Files(directory);
334  for (int i = 0; i < Files.Size(); i++) {
335  char *FileName = Files.At(i);
336  if (strstr(FileName, LIBVDR_PREFIX) == FileName) {
337  char *p = strstr(FileName, SO_INDICATOR);
338  if (p) {
339  *p = 0;
340  p += strlen(SO_INDICATOR);
341  if (strcmp(p, APIVERSION) == 0) {
342  char *name = FileName + strlen(LIBVDR_PREFIX);
343  if (strcmp(name, "*") != 0) { // let's not get into a loop!
344  AddPlugin(FileName + strlen(LIBVDR_PREFIX));
345  }
346  }
347  }
348  }
349  }
350  return;
351  }
352  char *s = strdup(skipspace(Args));
353  char *p = strchr(s, ' ');
354  if (p)
355  *p = 0;
356  struct stat st;
357  if (stat (cString::sprintf("%s/%s%s%s%s", directory, LIBVDR_PREFIX, s, SO_INDICATOR, APIVERSION), &st) && errno == ENOENT) {
358  esyslog("WARN: missing plugin '%s'", s);
359  fprintf(stderr, "vdr: missing plugin '%s'\n", s);
360  }
361  else
362  dlls.Add(new cDll(cString::sprintf("%s/%s%s%s%s", directory, LIBVDR_PREFIX, s, SO_INDICATOR, APIVERSION), Args));
363  free(s);
364 }
365 
367 {
368  for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) {
369  if (!dll->Load(Log))
370  /*return false*/;
371  }
372  return true;
373 }
374 
376 {
377  for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) {
378  cPlugin *p = dll->Plugin();
379  if (p) {
380  isyslog("initializing plugin: %s (%s): %s", p->Name(), p->Version(), p->Description());
381  if (!p->Initialize())
382  return false;
383  }
384  }
385  return true;
386 }
387 
389 {
390  for (cDll *dll = dlls.First(); dll; dll = dlls.Next(dll)) {
391  cPlugin *p = dll->Plugin();
392  if (p) {
393  isyslog("starting plugin: %s", p->Name());
394  if (!p->Start())
395  return false;
396  p->started = true;
397  }
398  }
399  return true;
400 }
401 
403 {
404  if (time(NULL) - lastHousekeeping > HOUSEKEEPINGDELTA) {
405  if (++nextHousekeeping >= dlls.Count())
406  nextHousekeeping = 0;
407  cDll *dll = dlls.Get(nextHousekeeping);
408  if (dll) {
409  cPlugin *p = dll->Plugin();
410  if (p) {
411  p->Housekeeping();
412  }
413  }
414  lastHousekeeping = time(NULL);
415  }
416 }
417 
419 {
420  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
421  cPlugin *p = dll->Plugin();
422  if (p)
423  p->MainThreadHook();
424  }
425 }
426 
427 bool cPluginManager::Active(const char *Prompt)
428 {
429  if (pluginManager) {
430  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
431  cPlugin *p = dll->Plugin();
432  if (p) {
433  cString s = p->Active();
434  if (!isempty(*s)) {
435  if (!Prompt || !Interface->Confirm(cString::sprintf("%s - %s", *s, Prompt)))
436  return true;
437  }
438  }
439  }
440  }
441  return false;
442 }
443 
445 {
446  cPlugin *NextPlugin = NULL;
447  if (pluginManager) {
448  time_t Now = time(NULL);
449  time_t Next = 0;
450  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
451  cPlugin *p = dll->Plugin();
452  if (p) {
453  time_t t = p->WakeupTime();
454  if (t > Now && (!Next || t < Next)) {
455  Next = t;
456  NextPlugin = p;
457  }
458  }
459  }
460  }
461  return NextPlugin;
462 }
463 
465 {
466  return pluginManager && pluginManager->dlls.Count();
467 }
468 
470 {
471  cDll *dll = pluginManager ? pluginManager->dlls.Get(Index) : NULL;
472  return dll ? dll->Plugin() : NULL;
473 }
474 
476 {
477  if (pluginManager && Name) {
478  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
479  cPlugin *p = dll->Plugin();
480  if (p && strcmp(p->Name(), Name) == 0)
481  return p;
482  }
483  }
484  return NULL;
485 }
486 
487 cPlugin *cPluginManager::CallFirstService(const char *Id, void *Data)
488 {
489  if (pluginManager) {
490  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
491  cPlugin *p = dll->Plugin();
492  if (p && p->Service(Id, Data))
493  return p;
494  }
495  }
496  return NULL;
497 }
498 
499 bool cPluginManager::CallAllServices(const char *Id, void *Data)
500 {
501  bool found=false;
502  if (pluginManager) {
503  for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) {
504  cPlugin *p = dll->Plugin();
505  if (p && p->Service(Id, Data))
506  found = true;
507  }
508  }
509  return found;
510 }
511 
513 {
514  for (cDll *dll = dlls.Last(); dll; dll = dlls.Prev(dll)) {
515  cPlugin *p = dll->Plugin();
516  if (p && p->started) {
517  isyslog("stopping plugin: %s", p->Name());
518  p->Stop();
519  p->started = false;
520  }
521  }
522 }
523 
525 {
526  cDll *dll;
527  while ((dll = dlls.Last()) != NULL) {
528  cPlugin *p = dll->Plugin();
529  if (p && Log)
530  isyslog("deleting plugin: %s", p->Name());
531  dlls.Del(dll);
532  }
533 }
Definition: plugin.h:70
void * handle
Definition: plugin.h:74
cPlugin * plugin
Definition: plugin.h:75
void destroy_t(cPlugin *)
Definition: plugin.h:76
cPlugin * Plugin(void)
Definition: plugin.h:82
destroy_t * destroy
Definition: plugin.h:77
cDll(const char *FileName, const char *Args)
Definition: plugin.c:179
char * args
Definition: plugin.h:73
virtual ~cDll()
Definition: plugin.c:188
bool Load(bool Log=false)
Definition: plugin.c:219
char * fileName
Definition: plugin.h:72
bool Confirm(const char *s, int Seconds=10, bool WaitForTimeout=false)
Definition: interface.c:59
void Del(cListObject *Object, bool DeleteObject=true)
Definition: tools.c:2190
int Count(void) const
Definition: tools.h:594
void Add(cListObject *Object, cListObject *After=NULL)
Definition: tools.c:2158
const T * Next(const T *Object) const
< Returns the element immediately before Object in this list, or NULL if Object is the first element ...
Definition: tools.h:617
const T * Last(void) const
Returns the last element in this list, or NULL if the list is empty.
Definition: tools.h:612
const T * First(void) const
Returns the first element in this list, or NULL if the list is empty.
Definition: tools.h:610
const T * Get(int Index) const
Returns the list element at the given Index, or NULL if no such element exists.
Definition: tools.h:607
const T * Prev(const T *Object) const
Definition: tools.h:614
static cPluginManager * pluginManager
Definition: plugin.h:89
void StopPlugins(void)
Definition: plugin.c:512
static cPlugin * GetNextWakeupPlugin(void)
Definition: plugin.c:444
void MainThreadHook(void)
Definition: plugin.c:418
bool StartPlugins(void)
Definition: plugin.c:388
cPluginManager(const char *Directory)
Definition: plugin.c:303
void SetDirectory(const char *Directory)
Definition: plugin.c:324
bool InitializePlugins(void)
Definition: plugin.c:375
void AddPlugin(const char *Args)
Definition: plugin.c:330
static bool Active(const char *Prompt=NULL)
Definition: plugin.c:427
static bool HasPlugins(void)
Definition: plugin.c:464
cDlls dlls
Definition: plugin.h:93
int nextHousekeeping
Definition: plugin.h:92
static bool CallAllServices(const char *Id, void *Data=NULL)
Definition: plugin.c:499
bool LoadPlugins(bool Log=false)
Definition: plugin.c:366
void Shutdown(bool Log=false)
Definition: plugin.c:524
static cPlugin * CallFirstService(const char *Id, void *Data=NULL)
Definition: plugin.c:487
char * directory
Definition: plugin.h:90
virtual ~cPluginManager()
Definition: plugin.c:316
void Housekeeping(void)
Definition: plugin.c:402
time_t lastHousekeeping
Definition: plugin.h:91
static cPlugin * GetPlugin(int Index)
Definition: plugin.c:469
Definition: plugin.h:22
virtual time_t WakeupTime(void)
Definition: plugin.c:85
virtual bool Initialize(void)
Definition: plugin.c:58
virtual void Stop(void)
Definition: plugin.c:68
virtual cMenuSetupPage * SetupMenu(void)
Definition: plugin.c:100
virtual const char * CommandLineHelp(void)
Definition: plugin.c:48
virtual ~cPlugin()
Definition: plugin.c:38
virtual const char * Description(void)=0
virtual bool Start(void)
Definition: plugin.c:63
const char * Name(void)
Definition: plugin.h:36
virtual void Housekeeping(void)
Definition: plugin.c:72
static cString cacheDirectory
Definition: plugin.h:27
bool started
Definition: plugin.h:30
void SetName(const char *s)
Definition: plugin.c:42
void SetupStore(const char *Name, const char *Value=NULL)
Definition: plugin.c:110
virtual const char * MainMenuEntry(void)
Definition: plugin.c:90
static cString resourceDirectory
Definition: plugin.h:28
static void SetCacheDirectory(const char *Dir)
Definition: plugin.c:149
virtual bool Service(const char *Id, void *Data=NULL)
Definition: plugin.c:120
virtual const char * Version(void)=0
const char * name
Definition: plugin.h:29
virtual cOsdObject * MainMenuAction(void)
Definition: plugin.c:95
static void SetConfigDirectory(const char *Dir)
Definition: plugin.c:135
static void SetResourceDirectory(const char *Dir)
Definition: plugin.c:163
static const char * CacheDirectory(const char *PluginName=NULL)
Definition: plugin.c:154
static const char * ResourceDirectory(const char *PluginName=NULL)
Definition: plugin.c:168
static const char * ConfigDirectory(const char *PluginName=NULL)
Definition: plugin.c:140
cPlugin(void)
Definition: plugin.c:32
virtual void MainThreadHook(void)
Definition: plugin.c:76
virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode)
Definition: plugin.c:130
virtual cString Active(void)
Definition: plugin.c:80
virtual const char ** SVDRPHelpPages(void)
Definition: plugin.c:125
static cString configDirectory
Definition: plugin.h:26
virtual bool SetupParse(const char *Name, const char *Value)
Definition: plugin.c:105
virtual bool ProcessArgs(int argc, char *argv[])
Definition: plugin.c:53
void Store(const char *Name, const char *Value, const char *Plugin=NULL, bool AllowMultiple=false)
Definition: config.c:522
Definition: tools.h:174
static cString sprintf(const char *fmt,...) __attribute__((format(printf
Definition: tools.c:1133
static tThreadId IsMainThread(void)
Definition: thread.h:131
int Size(void) const
Definition: tools.h:721
T & At(int Index) const
Definition: tools.h:698
cSetup Setup
Definition: config.c:372
#define APIVERSION
Definition: config.h:30
void I18nRegister(const char *Plugin)
Registers the named plugin, so that it can use internationalized texts.
Definition: i18n.c:209
cInterface * Interface
Definition: interface.c:20
#define HOUSEKEEPINGDELTA
Definition: plugin.c:24
static char * SkipQuote(char *s)
Definition: plugin.c:200
#define LIBVDR_PREFIX
Definition: plugin.c:20
#define MAXPLUGINARGS
Definition: plugin.c:23
#define SO_INDICATOR
Definition: plugin.c:21
bool isempty(const char *s)
Definition: tools.c:333
bool MakeDirs(const char *FileName, bool IsDirectory)
Definition: tools.c:483
char * stripspace(char *s)
Definition: tools.c:203
char * skipspace(const char *s)
Definition: tools.h:207
#define esyslog(a...)
Definition: tools.h:35
#define isyslog(a...)
Definition: tools.h:36