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