vdr  1.7.31
vdr.c
Go to the documentation of this file.
1 /*
2  * vdr.c: Video Disk Recorder main program
3  *
4  * Copyright (C) 2000, 2003, 2006, 2008 Klaus Schmidinger
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  * Or, point your browser to http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  *
21  * The author can be reached at kls@tvdr.de
22  *
23  * The project's page is at http://www.tvdr.de
24  *
25  * $Id: vdr.c 2.40 2012/09/24 12:43:04 kls Exp $
26  */
27 
28 #include <getopt.h>
29 #include <grp.h>
30 #include <langinfo.h>
31 #include <locale.h>
32 #include <pwd.h>
33 #include <signal.h>
34 #include <stdlib.h>
35 #include <sys/capability.h>
36 #include <sys/prctl.h>
37 #include <termios.h>
38 #include <unistd.h>
39 #include "audio.h"
40 #include "channels.h"
41 #include "config.h"
42 #include "cutter.h"
43 #include "device.h"
44 #include "diseqc.h"
45 #include "dvbdevice.h"
46 #include "eitscan.h"
47 #include "epg.h"
48 #include "filetransfer.h"
49 #include "i18n.h"
50 #include "interface.h"
51 #include "keys.h"
52 #include "libsi/si.h"
53 #include "lirc.h"
54 #include "menu.h"
55 #include "osdbase.h"
56 #include "plugin.h"
57 #include "recording.h"
58 #include "shutdown.h"
59 #include "skinclassic.h"
60 #include "skinlcars.h"
61 #include "skinsttng.h"
62 #include "sourceparams.h"
63 #include "sources.h"
64 #include "themes.h"
65 #include "timers.h"
66 #include "tools.h"
67 #include "transfer.h"
68 #include "videodir.h"
69 
70 #define MINCHANNELWAIT 10 // seconds to wait between failed channel switchings
71 #define ACTIVITYTIMEOUT 60 // seconds before starting housekeeping
72 #define SHUTDOWNWAIT 300 // seconds to wait in user prompt before automatic shutdown
73 #define SHUTDOWNRETRY 360 // seconds before trying again to shut down
74 #define SHUTDOWNFORCEPROMPT 5 // seconds to wait in user prompt to allow forcing shutdown
75 #define SHUTDOWNCANCELPROMPT 5 // seconds to wait in user prompt to allow canceling shutdown
76 #define RESTARTCANCELPROMPT 5 // seconds to wait in user prompt before restarting on SIGHUP
77 #define MANUALSTART 600 // seconds the next timer must be in the future to assume manual start
78 #define CHANNELSAVEDELTA 600 // seconds before saving channels.conf after automatic modifications
79 #define DEVICEREADYTIMEOUT 30 // seconds to wait until all devices are ready
80 #define MENUTIMEOUT 120 // seconds of user inactivity after which an OSD display is closed
81 #define TIMERCHECKDELTA 10 // seconds between checks for timers that need to see their channel
82 #define TIMERDEVICETIMEOUT 8 // seconds before a device used for timer check may be reused
83 #define TIMERLOOKAHEADTIME 60 // seconds before a non-VPS timer starts and the channel is switched if possible
84 #define VPSLOOKAHEADTIME 24 // hours within which VPS timers will make sure their events are up to date
85 #define VPSUPTODATETIME 3600 // seconds before the event or schedule of a VPS timer needs to be refreshed
86 
87 #define EXIT(v) { ShutdownHandler.Exit(v); goto Exit; }
88 
89 static int LastSignal = 0;
90 
91 static bool SetUser(const char *UserName, bool UserDump)//XXX name?
92 {
93  if (UserName) {
94  struct passwd *user = getpwnam(UserName);
95  if (!user) {
96  fprintf(stderr, "vdr: unknown user: '%s'\n", UserName);
97  return false;
98  }
99  if (setgid(user->pw_gid) < 0) {
100  fprintf(stderr, "vdr: cannot set group id %u: %s\n", (unsigned int)user->pw_gid, strerror(errno));
101  return false;
102  }
103  if (initgroups(user->pw_name, user->pw_gid) < 0) {
104  fprintf(stderr, "vdr: cannot set supplemental group ids for user %s: %s\n", user->pw_name, strerror(errno));
105  return false;
106  }
107  if (setuid(user->pw_uid) < 0) {
108  fprintf(stderr, "vdr: cannot set user id %u: %s\n", (unsigned int)user->pw_uid, strerror(errno));
109  return false;
110  }
111  if (UserDump && prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0)
112  fprintf(stderr, "vdr: warning - cannot set dumpable: %s\n", strerror(errno));
113  }
114  return true;
115 }
116 
117 static bool DropCaps(void)
118 {
119  // drop all capabilities except selected ones
120  cap_t caps = cap_from_text("= cap_sys_nice,cap_sys_time,cap_net_raw=ep");
121  if (!caps) {
122  fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno));
123  return false;
124  }
125  if (cap_set_proc(caps) == -1) {
126  fprintf(stderr, "vdr: cap_set_proc failed: %s\n", strerror(errno));
127  cap_free(caps);
128  return false;
129  }
130  cap_free(caps);
131  return true;
132 }
133 
134 static bool SetKeepCaps(bool On)
135 {
136  // set keeping capabilities during setuid() on/off
137  if (prctl(PR_SET_KEEPCAPS, On ? 1 : 0, 0, 0, 0) != 0) {
138  fprintf(stderr, "vdr: prctl failed\n");
139  return false;
140  }
141  return true;
142 }
143 
144 static void SignalHandler(int signum)
145 {
146  switch (signum) {
147  case SIGPIPE:
148  break;
149  case SIGHUP:
150  LastSignal = signum;
151  break;
152  default:
153  LastSignal = signum;
154  Interface->Interrupt();
156  }
157  signal(signum, SignalHandler);
158 }
159 
160 static void Watchdog(int signum)
161 {
162  // Something terrible must have happened that prevented the 'alarm()' from
163  // being called in time, so let's get out of here:
164  esyslog("PANIC: watchdog timer expired - exiting!");
165  exit(1);
166 }
167 
168 int main(int argc, char *argv[])
169 {
170  // Save terminal settings:
171 
172  struct termios savedTm;
173  bool HasStdin = (tcgetpgrp(STDIN_FILENO) == getpid() || getppid() != (pid_t)1) && tcgetattr(STDIN_FILENO, &savedTm) == 0;
174 
175  // Initiate locale:
176 
177  setlocale(LC_ALL, "");
178  setlocale(LC_NUMERIC, "C"); // makes sure any floating point numbers written use a decimal point
179 
180  // Command line options:
181 
182 #define dd(a, b) (*a ? a : b)
183 #define DEFAULTSVDRPPORT 6419
184 #define DEFAULTWATCHDOG 0 // seconds
185 #define DEFAULTVIDEODIR VIDEODIR
186 #define DEFAULTCONFDIR dd(CONFDIR, VideoDirectory)
187 #define DEFAULTCACHEDIR dd(CACHEDIR, VideoDirectory)
188 #define DEFAULTRESDIR dd(RESDIR, ConfigDirectory)
189 #define DEFAULTPLUGINDIR PLUGINDIR
190 #define DEFAULTLOCDIR LOCDIR
191 #define DEFAULTEPGDATAFILENAME "epg.data"
192 
193  bool StartedAsRoot = false;
194  const char *VdrUser = NULL;
195  bool UserDump = false;
196  int SVDRPport = DEFAULTSVDRPPORT;
197  const char *AudioCommand = NULL;
198  const char *VideoDirectory = DEFAULTVIDEODIR;
199  const char *ConfigDirectory = NULL;
200  const char *CacheDirectory = NULL;
201  const char *ResourceDirectory = NULL;
202  const char *LocaleDirectory = DEFAULTLOCDIR;
203  const char *EpgDataFileName = DEFAULTEPGDATAFILENAME;
204  bool DisplayHelp = false;
205  bool DisplayVersion = false;
206  bool DaemonMode = false;
207  int SysLogTarget = LOG_USER;
208  bool MuteAudio = false;
209  int WatchdogTimeout = DEFAULTWATCHDOG;
210  const char *Terminal = NULL;
211 
212  bool UseKbd = true;
213  const char *LircDevice = NULL;
214 #if !defined(REMOTE_KBD)
215  UseKbd = false;
216 #endif
217 #if defined(REMOTE_LIRC)
218  LircDevice = LIRC_DEVICE;
219 #endif
220 #if defined(VDR_USER)
221  VdrUser = VDR_USER;
222 #endif
223 
224  cPluginManager PluginManager(DEFAULTPLUGINDIR);
225 
226  static struct option long_options[] = {
227  { "audio", required_argument, NULL, 'a' },
228  { "cachedir", required_argument, NULL, 'c' | 0x100 },
229  { "config", required_argument, NULL, 'c' },
230  { "daemon", no_argument, NULL, 'd' },
231  { "device", required_argument, NULL, 'D' },
232  { "edit", required_argument, NULL, 'e' | 0x100 },
233  { "epgfile", required_argument, NULL, 'E' },
234  { "filesize", required_argument, NULL, 'f' | 0x100 },
235  { "genindex", required_argument, NULL, 'g' | 0x100 },
236  { "grab", required_argument, NULL, 'g' },
237  { "help", no_argument, NULL, 'h' },
238  { "instance", required_argument, NULL, 'i' },
239  { "lib", required_argument, NULL, 'L' },
240  { "lirc", optional_argument, NULL, 'l' | 0x100 },
241  { "localedir",required_argument, NULL, 'l' | 0x200 },
242  { "log", required_argument, NULL, 'l' },
243  { "mute", no_argument, NULL, 'm' },
244  { "no-kbd", no_argument, NULL, 'n' | 0x100 },
245  { "plugin", required_argument, NULL, 'P' },
246  { "port", required_argument, NULL, 'p' },
247  { "record", required_argument, NULL, 'r' },
248  { "resdir", required_argument, NULL, 'r' | 0x100 },
249  { "shutdown", required_argument, NULL, 's' },
250  { "split", no_argument, NULL, 's' | 0x100 },
251  { "terminal", required_argument, NULL, 't' },
252  { "user", required_argument, NULL, 'u' },
253  { "userdump", no_argument, NULL, 'u' | 0x100 },
254  { "version", no_argument, NULL, 'V' },
255  { "vfat", no_argument, NULL, 'v' | 0x100 },
256  { "video", required_argument, NULL, 'v' },
257  { "watchdog", required_argument, NULL, 'w' },
258  { NULL, no_argument, NULL, 0 }
259  };
260 
261  int c;
262  while ((c = getopt_long(argc, argv, "a:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) {
263  switch (c) {
264  case 'a': AudioCommand = optarg;
265  break;
266  case 'c' | 0x100:
267  CacheDirectory = optarg;
268  break;
269  case 'c': ConfigDirectory = optarg;
270  break;
271  case 'd': DaemonMode = true; break;
272  case 'D': if (isnumber(optarg)) {
273  int n = atoi(optarg);
274  if (0 <= n && n < MAXDEVICES) {
276  break;
277  }
278  }
279  fprintf(stderr, "vdr: invalid DVB device number: %s\n", optarg);
280  return 2;
281  break;
282  case 'e' | 0x100:
283  return CutRecording(optarg) ? 0 : 2;
284  case 'E': EpgDataFileName = (*optarg != '-' ? optarg : NULL);
285  break;
286  case 'f' | 0x100:
287  Setup.MaxVideoFileSize = StrToNum(optarg) / MEGABYTE(1);
292  break;
293  case 'g' | 0x100:
294  return GenerateIndex(optarg) ? 0 : 2;
295  case 'g': cSVDRP::SetGrabImageDir(*optarg != '-' ? optarg : NULL);
296  break;
297  case 'h': DisplayHelp = true;
298  break;
299  case 'i': if (isnumber(optarg)) {
300  InstanceId = atoi(optarg);
301  if (InstanceId >= 0)
302  break;
303  }
304  fprintf(stderr, "vdr: invalid instance id: %s\n", optarg);
305  return 2;
306  case 'l': {
307  char *p = strchr(optarg, '.');
308  if (p)
309  *p = 0;
310  if (isnumber(optarg)) {
311  int l = atoi(optarg);
312  if (0 <= l && l <= 3) {
313  SysLogLevel = l;
314  if (!p)
315  break;
316  if (isnumber(p + 1)) {
317  int l = atoi(p + 1);
318  if (0 <= l && l <= 7) {
319  int targets[] = { LOG_LOCAL0, LOG_LOCAL1, LOG_LOCAL2, LOG_LOCAL3, LOG_LOCAL4, LOG_LOCAL5, LOG_LOCAL6, LOG_LOCAL7 };
320  SysLogTarget = targets[l];
321  break;
322  }
323  }
324  }
325  }
326  if (p)
327  *p = '.';
328  fprintf(stderr, "vdr: invalid log level: %s\n", optarg);
329  return 2;
330  }
331  break;
332  case 'L': if (access(optarg, R_OK | X_OK) == 0)
333  PluginManager.SetDirectory(optarg);
334  else {
335  fprintf(stderr, "vdr: can't access plugin directory: %s\n", optarg);
336  return 2;
337  }
338  break;
339  case 'l' | 0x100:
340  LircDevice = optarg ? optarg : LIRC_DEVICE;
341  break;
342  case 'l' | 0x200:
343  if (access(optarg, R_OK | X_OK) == 0)
344  LocaleDirectory = optarg;
345  else {
346  fprintf(stderr, "vdr: can't access locale directory: %s\n", optarg);
347  return 2;
348  }
349  break;
350  case 'm': MuteAudio = true;
351  break;
352  case 'n' | 0x100:
353  UseKbd = false;
354  break;
355  case 'p': if (isnumber(optarg))
356  SVDRPport = atoi(optarg);
357  else {
358  fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
359  return 2;
360  }
361  break;
362  case 'P': PluginManager.AddPlugin(optarg);
363  break;
364  case 'r': cRecordingUserCommand::SetCommand(optarg);
365  break;
366  case 'r' | 0x100:
367  ResourceDirectory = optarg;
368  break;
369  case 's': ShutdownHandler.SetShutdownCommand(optarg);
370  break;
371  case 's' | 0x100:
373  break;
374  case 't': Terminal = optarg;
375  if (access(Terminal, R_OK | W_OK) < 0) {
376  fprintf(stderr, "vdr: can't access terminal: %s\n", Terminal);
377  return 2;
378  }
379  break;
380  case 'u': if (*optarg)
381  VdrUser = optarg;
382  break;
383  case 'u' | 0x100:
384  UserDump = true;
385  break;
386  case 'V': DisplayVersion = true;
387  break;
388  case 'v' | 0x100:
389  VfatFileSystem = true;
390  break;
391  case 'v': VideoDirectory = optarg;
392  while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/')
393  optarg[strlen(optarg) - 1] = 0;
394  break;
395  case 'w': if (isnumber(optarg)) {
396  int t = atoi(optarg);
397  if (t >= 0) {
398  WatchdogTimeout = t;
399  break;
400  }
401  }
402  fprintf(stderr, "vdr: invalid watchdog timeout: %s\n", optarg);
403  return 2;
404  break;
405  default: return 2;
406  }
407  }
408 
409  // Set user id in case we were started as root:
410 
411  if (VdrUser && geteuid() == 0) {
412  StartedAsRoot = true;
413  if (strcmp(VdrUser, "root")) {
414  if (!SetKeepCaps(true))
415  return 2;
416  if (!SetUser(VdrUser, UserDump))
417  return 2;
418  if (!SetKeepCaps(false))
419  return 2;
420  if (!DropCaps())
421  return 2;
422  }
423  }
424 
425  // Help and version info:
426 
427  if (DisplayHelp || DisplayVersion) {
428  if (!PluginManager.HasPlugins())
429  PluginManager.AddPlugin("*"); // adds all available plugins
430  PluginManager.LoadPlugins();
431  if (DisplayHelp) {
432  printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80|
433  " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n"
434  " --cachedir=DIR save cache files in DIR (default: %s)\n"
435  " -c DIR, --config=DIR read config files from DIR (default: %s)\n"
436  " -d, --daemon run in daemon mode\n"
437  " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n"
438  " there may be several -D options (default: all DVB\n"
439  " devices will be used)\n"
440  " --edit=REC cut recording REC and exit\n"
441  " -E FILE, --epgfile=FILE write the EPG data into the given FILE (default is\n"
442  " /var/cache/vdr/%s)\n"
443  " '-E-' disables this\n"
444  " if FILE is a directory, the default EPG file will be\n"
445  " created in that directory\n"
446  " --filesize=SIZE limit video files to SIZE bytes (default is %dM)\n"
447  " only useful in conjunction with --edit\n"
448  " --genindex=REC generate index for recording REC and exit\n"
449  " -g DIR, --grab=DIR write images from the SVDRP command GRAB into the\n"
450  " given DIR; DIR must be the full path name of an\n"
451  " existing directory, without any \"..\", double '/'\n"
452  " or symlinks (default: none, same as -g-)\n"
453  " -h, --help print this help and exit\n"
454  " -i ID, --instance=ID use ID as the id of this VDR instance (default: 0)\n"
455  " -l LEVEL, --log=LEVEL set log level (default: 3)\n"
456  " 0 = no logging, 1 = errors only,\n"
457  " 2 = errors and info, 3 = errors, info and debug\n"
458  " if logging should be done to LOG_LOCALn instead of\n"
459  " LOG_USER, add '.n' to LEVEL, as in 3.7 (n=0..7)\n"
460  " -L DIR, --lib=DIR search for plugins in DIR (default is %s)\n"
461  " --lirc[=PATH] use a LIRC remote control device, attached to PATH\n"
462  " (default: %s)\n"
463  " --localedir=DIR search for locale files in DIR (default is\n"
464  " %s)\n"
465  " -m, --mute mute audio of the primary DVB device at startup\n"
466  " --no-kbd don't use the keyboard as an input device\n"
467  " -p PORT, --port=PORT use PORT for SVDRP (default: %d)\n"
468  " 0 turns off SVDRP\n"
469  " -P OPT, --plugin=OPT load a plugin defined by the given options\n"
470  " -r CMD, --record=CMD call CMD before and after a recording, and after\n"
471  " a recording has been edited or deleted\n"
472  " --resdir=DIR read resource files from DIR (default: %s)\n"
473  " -s CMD, --shutdown=CMD call CMD to shutdown the computer\n"
474  " --split split edited files at the editing marks (only\n"
475  " useful in conjunction with --edit)\n"
476  " -t TTY, --terminal=TTY controlling tty\n"
477  " -u USER, --user=USER run as user USER; only applicable if started as\n"
478  " root\n"
479  " --userdump allow coredumps if -u is given (debugging)\n"
480  " -v DIR, --video=DIR use DIR as video directory (default: %s)\n"
481  " -V, --version print version information and exit\n"
482  " --vfat encode special characters in recording names to\n"
483  " avoid problems with VFAT file systems\n"
484  " -w SEC, --watchdog=SEC activate the watchdog timer with a timeout of SEC\n"
485  " seconds (default: %d); '0' disables the watchdog\n"
486  "\n",
492  LIRC_DEVICE,
498  );
499  }
500  if (DisplayVersion)
501  printf("vdr (%s/%s) - The Video Disk Recorder\n", VDRVERSION, APIVERSION);
502  if (PluginManager.HasPlugins()) {
503  if (DisplayHelp)
504  printf("Plugins: vdr -P\"name [OPTIONS]\"\n\n");
505  for (int i = 0; ; i++) {
506  cPlugin *p = PluginManager.GetPlugin(i);
507  if (p) {
508  const char *help = p->CommandLineHelp();
509  printf("%s (%s) - %s\n", p->Name(), p->Version(), p->Description());
510  if (DisplayHelp && help) {
511  printf("\n");
512  puts(help);
513  }
514  }
515  else
516  break;
517  }
518  }
519  return 0;
520  }
521 
522  // Log file:
523 
524  if (SysLogLevel > 0)
525  openlog("vdr", LOG_CONS, SysLogTarget); // LOG_PID doesn't work as expected under NPTL
526 
527  // Check the video directory:
528 
529  if (!DirectoryOk(VideoDirectory, true)) {
530  fprintf(stderr, "vdr: can't access video directory %s\n", VideoDirectory);
531  return 2;
532  }
533 
534  // Daemon mode:
535 
536  if (DaemonMode) {
537  if (daemon(1, 0) == -1) {
538  fprintf(stderr, "vdr: %m\n");
539  esyslog("ERROR: %m");
540  return 2;
541  }
542  }
543  else if (Terminal) {
544  // Claim new controlling terminal
545  stdin = freopen(Terminal, "r", stdin);
546  stdout = freopen(Terminal, "w", stdout);
547  stderr = freopen(Terminal, "w", stderr);
548  HasStdin = true;
549  tcgetattr(STDIN_FILENO, &savedTm);
550  }
551 
552  isyslog("VDR version %s started", VDRVERSION);
553  if (StartedAsRoot && VdrUser)
554  isyslog("switched to user '%s'", VdrUser);
555  if (DaemonMode)
556  dsyslog("running as daemon (tid=%d)", cThread::ThreadId());
558 
559  // Set the system character table:
560 
561  char *CodeSet = NULL;
562  if (setlocale(LC_CTYPE, ""))
563  CodeSet = nl_langinfo(CODESET);
564  else {
565  char *LangEnv = getenv("LANG"); // last resort in case locale stuff isn't installed
566  if (LangEnv) {
567  CodeSet = strchr(LangEnv, '.');
568  if (CodeSet)
569  CodeSet++; // skip the dot
570  }
571  }
572  if (CodeSet) {
573  bool known = SI::SetSystemCharacterTable(CodeSet);
574  isyslog("codeset is '%s' - %s", CodeSet, known ? "known" : "unknown");
576  }
577 
578  // Initialize internationalization:
579 
580  I18nInitialize(LocaleDirectory);
581 
582  // Main program loop variables - need to be here to have them initialized before any EXIT():
583 
584  cEpgDataReader EpgDataReader;
585  cOsdObject *Menu = NULL;
586  int LastChannel = 0;
587  int LastTimerChannel = -1;
588  int PreviousChannel[2] = { 1, 1 };
589  int PreviousChannelIndex = 0;
590  time_t LastChannelChanged = time(NULL);
591  time_t LastInteract = 0;
592  int MaxLatencyTime = 0;
593  bool InhibitEpgScan = false;
594  bool IsInfoMenu = false;
595  cSkin *CurrentSkin = NULL;
596 
597  // Load plugins:
598 
599  if (!PluginManager.LoadPlugins(true))
600  EXIT(2);
601 
602  // Directories:
603 
604  SetVideoDirectory(VideoDirectory);
605  if (!ConfigDirectory)
606  ConfigDirectory = DEFAULTCONFDIR;
607  cPlugin::SetConfigDirectory(ConfigDirectory);
608  if (!CacheDirectory)
609  CacheDirectory = DEFAULTCACHEDIR;
610  cPlugin::SetCacheDirectory(CacheDirectory);
611  if (!ResourceDirectory)
612  ResourceDirectory = DEFAULTRESDIR;
613  cPlugin::SetResourceDirectory(ResourceDirectory);
614  cThemes::SetThemesDirectory("/var/lib/vdr/data/themes");
615 
616  // Configuration data:
617 
618  Setup.Load(AddDirectory(ConfigDirectory, "setup.conf"));
619  Sources.Load(AddDirectory(ConfigDirectory, "sources.conf"), true, true);
620  Diseqcs.Load(AddDirectory(ConfigDirectory, "diseqc.conf"), true, Setup.DiSEqC);
621  Scrs.Load(AddDirectory(ConfigDirectory, "scr.conf"), true);
622  Channels.Load(AddDirectory(ConfigDirectory, "channels.conf"), false, true);
623  Timers.Load(AddDirectory(ConfigDirectory, "timers.conf"));
624  Commands.Load(AddDirectory(ConfigDirectory, "commands.conf"));
625  RecordingCommands.Load(AddDirectory(ConfigDirectory, "reccmds.conf"));
626  TimerCommands.Load(AddDirectory(ConfigDirectory, "timercmds.conf"));
627  SVDRPhosts.Load(AddDirectory(ConfigDirectory, "svdrphosts.conf"), true);
628  Keys.Load(AddDirectory(ConfigDirectory, "remote.conf"));
629  KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true);
630  Folders.Load(AddDirectory(ConfigDirectory, "folders.conf"));
631 
633  const char *msg = "no fonts available - OSD will not show any text!";
634  fprintf(stderr, "vdr: %s\n", msg);
635  esyslog("ERROR: %s", msg);
636  }
637 
638  // Recordings:
639 
640  Recordings.Update();
642 
643  // EPG data:
644 
645  if (EpgDataFileName) {
646  const char *EpgDirectory = NULL;
647  if (DirectoryOk(EpgDataFileName)) {
648  EpgDirectory = EpgDataFileName;
649  EpgDataFileName = DEFAULTEPGDATAFILENAME;
650  }
651  else if (*EpgDataFileName != '/' && *EpgDataFileName != '.')
652  EpgDirectory = CacheDirectory;
653  if (EpgDirectory)
654  cSchedules::SetEpgDataFileName(AddDirectory(EpgDirectory, EpgDataFileName));
655  else
656  cSchedules::SetEpgDataFileName(EpgDataFileName);
657  EpgDataReader.Start();
658  }
659 
660  // DVB interfaces:
661 
664 
665  // Initialize plugins:
666 
667  if (!PluginManager.InitializePlugins())
668  EXIT(2);
669 
670  // Primary device:
671 
673  if (!cDevice::PrimaryDevice() || !cDevice::PrimaryDevice()->HasDecoder()) {
674  if (cDevice::PrimaryDevice() && !cDevice::PrimaryDevice()->HasDecoder())
675  isyslog("device %d has no MPEG decoder", cDevice::PrimaryDevice()->DeviceNumber() + 1);
676  for (int i = 0; i < cDevice::NumDevices(); i++) {
677  cDevice *d = cDevice::GetDevice(i);
678  if (d && d->HasDecoder()) {
679  isyslog("trying device number %d instead", i + 1);
680  if (cDevice::SetPrimaryDevice(i + 1)) {
681  Setup.PrimaryDVB = i + 1;
682  break;
683  }
684  }
685  }
686  if (!cDevice::PrimaryDevice()) {
687  const char *msg = "no primary device found - using first device!";
688  fprintf(stderr, "vdr: %s\n", msg);
689  esyslog("ERROR: %s", msg);
691  EXIT(2);
692  if (!cDevice::PrimaryDevice()) {
693  const char *msg = "no primary device found - giving up!";
694  fprintf(stderr, "vdr: %s\n", msg);
695  esyslog("ERROR: %s", msg);
696  EXIT(2);
697  }
698  }
699  }
700 
701  // Check for timers in automatic start time window:
702 
704 
705  // User interface:
706 
707  Interface = new cInterface(SVDRPport);
708 
709  // Default skins:
710 
711  new cSkinLCARS;
712  new cSkinSTTNG;
713  new cSkinClassic;
716  CurrentSkin = Skins.Current();
717 
718  // Start plugins:
719 
720  if (!PluginManager.StartPlugins())
721  EXIT(2);
722 
723  // Set skin and theme in case they're implemented by a plugin:
724 
725  if (!CurrentSkin || CurrentSkin == Skins.Current() && strcmp(Skins.Current()->Name(), Setup.OSDSkin) != 0) {
728  }
729 
730  // Remote Controls:
731  if (LircDevice)
732  new cLircRemote(LircDevice);
733  if (!DaemonMode && HasStdin && UseKbd)
734  new cKbdRemote;
735  Interface->LearnKeys();
736 
737  // External audio:
738 
739  if (AudioCommand)
740  new cExternalAudio(AudioCommand);
741 
742  // Channel:
743 
745  dsyslog("not all devices ready after %d seconds", DEVICEREADYTIMEOUT);
746  if (*Setup.InitialChannel) {
747  if (isnumber(Setup.InitialChannel)) { // for compatibility with old setup.conf files
748  if (cChannel *Channel = Channels.GetByNumber(atoi(Setup.InitialChannel)))
749  Setup.InitialChannel = Channel->GetChannelID().ToString();
750  }
752  Setup.CurrentChannel = Channel->Number();
753  }
754  if (Setup.InitialVolume >= 0)
757  if (MuteAudio)
759  else
761 
762  // Signal handlers:
763 
764  if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
765  if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
766  if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
767  if (signal(SIGPIPE, SignalHandler) == SIG_IGN) signal(SIGPIPE, SIG_IGN);
768  if (WatchdogTimeout > 0)
769  if (signal(SIGALRM, Watchdog) == SIG_IGN) signal(SIGALRM, SIG_IGN);
770 
771  // Watchdog:
772 
773  if (WatchdogTimeout > 0) {
774  dsyslog("setting watchdog timer to %d seconds", WatchdogTimeout);
775  alarm(WatchdogTimeout); // Initial watchdog timer start
776  }
777 
778  // Main program loop:
779 
780 #define DELETE_MENU ((IsInfoMenu &= (Menu == NULL)), delete Menu, Menu = NULL)
781 
782  while (!ShutdownHandler.DoExit()) {
783 #ifdef DEBUGRINGBUFFERS
784  cRingBufferLinear::PrintDebugRBL();
785 #endif
786  // Attach launched player control:
788 
789  time_t Now = time(NULL);
790 
791  // Make sure we have a visible programme in case device usage has changed:
793  static time_t lastTime = 0;
794  if (!CamMenuActive() && Now - lastTime > MINCHANNELWAIT) { // !CamMenuActive() to avoid interfering with the CAM if a CAM menu is open
796  if (Channel && (Channel->Vpid() || Channel->Apid(0) || Channel->Dpid(0))) {
797  if (cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(Channel->Number())) // try to switch to the original channel...
798  ;
799  else if (LastTimerChannel > 0) {
800  Channel = Channels.GetByNumber(LastTimerChannel);
801  if (Channel && cDevice::GetDeviceForTransponder(Channel, LIVEPRIORITY) && Channels.SwitchTo(LastTimerChannel)) // ...or the one used by the last timer
802  ;
803  }
804  }
805  lastTime = Now; // don't do this too often
806  LastTimerChannel = -1;
807  }
808  }
809  // Update the OSD size:
810  {
811  static time_t lastOsdSizeUpdate = 0;
812  if (Now != lastOsdSizeUpdate) { // once per second
814  lastOsdSizeUpdate = Now;
815  }
816  }
817  // Restart the Watchdog timer:
818  if (WatchdogTimeout > 0) {
819  int LatencyTime = WatchdogTimeout - alarm(WatchdogTimeout);
820  if (LatencyTime > MaxLatencyTime) {
821  MaxLatencyTime = LatencyTime;
822  dsyslog("max. latency time %d seconds", MaxLatencyTime);
823  }
824  }
825  // Handle channel and timer modifications:
826  if (!Channels.BeingEdited() && !Timers.BeingEdited()) {
827  int modified = Channels.Modified();
828  static time_t ChannelSaveTimeout = 0;
829  static int TimerState = 0;
830  // Channels and timers need to be stored in a consistent manner,
831  // therefore if one of them is changed, we save both.
832  if (modified == CHANNELSMOD_USER || Timers.Modified(TimerState))
833  ChannelSaveTimeout = 1; // triggers an immediate save
834  else if (modified && !ChannelSaveTimeout)
835  ChannelSaveTimeout = Now + CHANNELSAVEDELTA;
836  bool timeout = ChannelSaveTimeout == 1 || ChannelSaveTimeout && Now > ChannelSaveTimeout && !cRecordControls::Active();
837  if ((modified || timeout) && Channels.Lock(false, 100)) {
838  if (timeout) {
839  Channels.Save();
840  Timers.Save();
841  ChannelSaveTimeout = 0;
842  }
843  for (cChannel *Channel = Channels.First(); Channel; Channel = Channels.Next(Channel)) {
844  if (Channel->Modification(CHANNELMOD_RETUNE)) {
846  if (Channel->Number() == cDevice::CurrentChannel()) {
848  if (cDevice::ActualDevice()->ProvidesTransponder(Channel)) { // avoids retune on devices that don't really access the transponder
849  isyslog("retuning due to modification of channel %d", Channel->Number());
850  Channels.SwitchTo(Channel->Number());
851  }
852  }
853  }
854  }
855  }
856  Channels.Unlock();
857  }
858  }
859  // Channel display:
860  if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) {
861  if (!Menu)
862  Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel >= 0);
863  LastChannel = cDevice::CurrentChannel();
864  LastChannelChanged = Now;
865  }
866  if (Now - LastChannelChanged >= Setup.ZapTimeout && LastChannel != PreviousChannel[PreviousChannelIndex])
867  PreviousChannel[PreviousChannelIndex ^= 1] = LastChannel;
868  // Timers and Recordings:
869  if (!Timers.BeingEdited()) {
870  // Assign events to timers:
871  Timers.SetEvents();
872  // Must do all following calls with the exact same time!
873  // Process ongoing recordings:
875  // Start new recordings:
876  cTimer *Timer = Timers.GetMatch(Now);
877  if (Timer) {
878  if (!cRecordControls::Start(Timer))
879  Timer->SetPending(true);
880  else
881  LastTimerChannel = Timer->Channel()->Number();
882  }
883  // Make sure timers "see" their channel early enough:
884  static time_t LastTimerCheck = 0;
885  if (Now - LastTimerCheck > TIMERCHECKDELTA) { // don't do this too often
886  InhibitEpgScan = false;
887  for (cTimer *Timer = Timers.First(); Timer; Timer = Timers.Next(Timer)) {
888  bool InVpsMargin = false;
889  bool NeedsTransponder = false;
890  if (Timer->HasFlags(tfActive) && !Timer->Recording()) {
891  if (Timer->HasFlags(tfVps)) {
892  if (Timer->Matches(Now, true, Setup.VpsMargin)) {
893  InVpsMargin = true;
894  Timer->SetInVpsMargin(InVpsMargin);
895  }
896  else if (Timer->Event()) {
897  InVpsMargin = Timer->Event()->StartTime() <= Now && Timer->Event()->RunningStatus() == SI::RunningStatusUndefined;
898  NeedsTransponder = Timer->Event()->StartTime() - Now < VPSLOOKAHEADTIME * 3600 && !Timer->Event()->SeenWithin(VPSUPTODATETIME);
899  }
900  else {
901  cSchedulesLock SchedulesLock;
902  const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock);
903  if (Schedules) {
904  const cSchedule *Schedule = Schedules->GetSchedule(Timer->Channel());
905  InVpsMargin = !Schedule; // we must make sure we have the schedule
906  NeedsTransponder = Schedule && !Schedule->PresentSeenWithin(VPSUPTODATETIME);
907  }
908  }
909  InhibitEpgScan |= InVpsMargin | NeedsTransponder;
910  }
911  else
912  NeedsTransponder = Timer->Matches(Now, true, TIMERLOOKAHEADTIME);
913  }
914  if (NeedsTransponder || InVpsMargin) {
915  // Find a device that provides the required transponder:
917  if (!Device && InVpsMargin)
919  // Switch the device to the transponder:
920  if (Device) {
921  bool HadProgramme = cDevice::PrimaryDevice()->HasProgramme();
922  if (!Device->IsTunedToTransponder(Timer->Channel())) {
923  if (Device == cDevice::ActualDevice() && !Device->IsPrimaryDevice())
924  cDevice::PrimaryDevice()->StopReplay(); // stop transfer mode
925  dsyslog("switching device %d to channel %d", Device->DeviceNumber() + 1, Timer->Channel()->Number());
926  if (Device->SwitchChannel(Timer->Channel(), false))
928  }
929  if (cDevice::PrimaryDevice()->HasDecoder() && HadProgramme && !cDevice::PrimaryDevice()->HasProgramme())
930  Skins.Message(mtInfo, tr("Upcoming recording!")); // the previous SwitchChannel() has switched away the current live channel
931  }
932  }
933  }
934  LastTimerCheck = Now;
935  }
936  // Delete expired timers:
938  }
939  if (!Menu && Recordings.NeedsUpdate()) {
940  Recordings.Update();
942  }
943  // CAM control:
944  if (!Menu && !cOsd::IsOpen())
945  Menu = CamControl();
946  // Queued messages:
947  if (!Skins.IsOpen())
949  // User Input:
950  cOsdObject *Interact = Menu ? Menu : cControl::Control();
951  eKeys key = Interface->GetKey(!Interact || !Interact->NeedsFastResponse());
952  if (ISREALKEY(key)) {
954  // Cancel shutdown countdown:
957  // Set user active for MinUserInactivity time in the future:
959  }
960  // Keys that must work independent of any interactive mode:
961  switch (int(key)) {
962  // Menu control:
963  case kMenu: {
964  key = kNone; // nobody else needs to see this key
965  bool WasOpen = Interact != NULL;
966  bool WasMenu = Interact && Interact->IsMenu();
967  if (Menu)
968  DELETE_MENU;
969  else if (cControl::Control()) {
970  if (cOsd::IsOpen())
971  cControl::Control()->Hide();
972  else
973  WasOpen = false;
974  }
975  if (!WasOpen || !WasMenu && !Setup.MenuKeyCloses)
976  Menu = new cMenuMain;
977  }
978  break;
979  // Info:
980  case kInfo: {
981  if (IsInfoMenu) {
982  key = kNone; // nobody else needs to see this key
983  DELETE_MENU;
984  }
985  else if (!Menu) {
986  IsInfoMenu = true;
987  if (cControl::Control()) {
988  cControl::Control()->Hide();
989  Menu = cControl::Control()->GetInfo();
990  if (Menu)
991  Menu->Show();
992  else
993  IsInfoMenu = false;
994  }
995  else {
996  cRemote::Put(kOk, true);
997  cRemote::Put(kSchedule, true);
998  }
999  key = kNone; // nobody else needs to see this key
1000  }
1001  }
1002  break;
1003  // Direct main menu functions:
1004  #define DirectMainFunction(function)\
1005  DELETE_MENU;\
1006  if (cControl::Control())\
1007  cControl::Control()->Hide();\
1008  Menu = new cMenuMain(function);\
1009  key = kNone; // nobody else needs to see this key
1010  case kSchedule: DirectMainFunction(osSchedule); break;
1011  case kChannels: DirectMainFunction(osChannels); break;
1012  case kTimers: DirectMainFunction(osTimers); break;
1014  case kSetup: DirectMainFunction(osSetup); break;
1015  case kCommands: DirectMainFunction(osCommands); break;
1016  case kUser0 ... kUser9: cRemote::PutMacro(key); key = kNone; break;
1017  case k_Plugin: {
1018  const char *PluginName = cRemote::GetPlugin();
1019  if (PluginName) {
1020  DELETE_MENU;
1021  if (cControl::Control())
1022  cControl::Control()->Hide();
1023  cPlugin *plugin = cPluginManager::GetPlugin(PluginName);
1024  if (plugin) {
1025  Menu = plugin->MainMenuAction();
1026  if (Menu)
1027  Menu->Show();
1028  }
1029  else
1030  esyslog("ERROR: unknown plugin '%s'", PluginName);
1031  }
1032  key = kNone; // nobody else needs to see these keys
1033  }
1034  break;
1035  // Channel up/down:
1036  case kChanUp|k_Repeat:
1037  case kChanUp:
1038  case kChanDn|k_Repeat:
1039  case kChanDn:
1040  if (!Interact)
1041  Menu = new cDisplayChannel(NORMALKEY(key));
1042  else if (cDisplayChannel::IsOpen() || cControl::Control()) {
1043  Interact->ProcessKey(key);
1044  continue;
1045  }
1046  else
1047  cDevice::SwitchChannel(NORMALKEY(key) == kChanUp ? 1 : -1);
1048  key = kNone; // nobody else needs to see these keys
1049  break;
1050  // Volume control:
1051  case kVolUp|k_Repeat:
1052  case kVolUp:
1053  case kVolDn|k_Repeat:
1054  case kVolDn:
1055  case kMute:
1056  if (key == kMute) {
1057  if (!cDevice::PrimaryDevice()->ToggleMute() && !Menu) {
1058  key = kNone; // nobody else needs to see these keys
1059  break; // no need to display "mute off"
1060  }
1061  }
1062  else
1064  if (!Menu && !cOsd::IsOpen())
1065  Menu = cDisplayVolume::Create();
1067  key = kNone; // nobody else needs to see these keys
1068  break;
1069  // Audio track control:
1070  case kAudio:
1071  if (cControl::Control())
1072  cControl::Control()->Hide();
1073  if (!cDisplayTracks::IsOpen()) {
1074  DELETE_MENU;
1075  Menu = cDisplayTracks::Create();
1076  }
1077  else
1079  key = kNone;
1080  break;
1081  // Subtitle track control:
1082  case kSubtitles:
1083  if (cControl::Control())
1084  cControl::Control()->Hide();
1086  DELETE_MENU;
1088  }
1089  else
1091  key = kNone;
1092  break;
1093  // Pausing live video:
1094  case kPause:
1095  if (!cControl::Control()) {
1096  DELETE_MENU;
1097  if (Setup.PauseKeyHandling) {
1098  if (Setup.PauseKeyHandling > 1 || Interface->Confirm(tr("Pause live video?"))) {
1100  Skins.Message(mtError, tr("No free DVB device to record!"));
1101  }
1102  }
1103  key = kNone; // nobody else needs to see this key
1104  }
1105  break;
1106  // Instant recording:
1107  case kRecord:
1108  if (!cControl::Control()) {
1109  if (cRecordControls::Start())
1110  Skins.Message(mtInfo, tr("Recording started"));
1111  key = kNone; // nobody else needs to see this key
1112  }
1113  break;
1114  // Power off:
1115  case kPower:
1116  isyslog("Power button pressed");
1117  DELETE_MENU;
1118  // Check for activity, request power button again if active:
1119  if (!ShutdownHandler.ConfirmShutdown(false) && Skins.Message(mtWarning, tr("VDR will shut down later - press Power to force"), SHUTDOWNFORCEPROMPT) != kPower) {
1120  // Not pressed power - set VDR to be non-interactive and power down later:
1122  break;
1123  }
1124  // No activity or power button pressed twice - ask for confirmation:
1125  if (!ShutdownHandler.ConfirmShutdown(true)) {
1126  // Non-confirmed background activity - set VDR to be non-interactive and power down later:
1128  break;
1129  }
1130  // Ask the final question:
1131  if (!Interface->Confirm(tr("Press any key to cancel shutdown"), SHUTDOWNCANCELPROMPT, true))
1132  // If final question was canceled, continue to be active:
1133  break;
1134  // Ok, now call the shutdown script:
1136  // Set VDR to be non-interactive and power down again later:
1138  // Do not attempt to automatically shut down for a while:
1140  break;
1141  default: break;
1142  }
1143  Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time
1144  if (Interact) {
1145  LastInteract = Now;
1146  eOSState state = Interact->ProcessKey(key);
1147  if (state == osUnknown && Interact != cControl::Control()) {
1148  if (ISMODELESSKEY(key) && cControl::Control()) {
1149  state = cControl::Control()->ProcessKey(key);
1150  if (state == osEnd) {
1151  // let's not close a menu when replay ends:
1153  continue;
1154  }
1155  }
1156  else if (Now - cRemote::LastActivity() > MENUTIMEOUT)
1157  state = osEnd;
1158  }
1159  switch (state) {
1160  case osPause: DELETE_MENU;
1162  Skins.Message(mtError, tr("No free DVB device to record!"));
1163  break;
1164  case osRecord: DELETE_MENU;
1165  if (cRecordControls::Start())
1166  Skins.Message(mtInfo, tr("Recording started"));
1167  break;
1168  case osRecordings:
1169  DELETE_MENU;
1171  Menu = new cMenuMain(osRecordings);
1172  break;
1173  case osReplay: DELETE_MENU;
1176  break;
1177  case osStopReplay:
1178  DELETE_MENU;
1180  break;
1181  case osSwitchDvb:
1182  DELETE_MENU;
1184  Skins.Message(mtInfo, tr("Switching primary DVB..."));
1186  break;
1187  case osPlugin: DELETE_MENU;
1188  Menu = cMenuMain::PluginOsdObject();
1189  if (Menu)
1190  Menu->Show();
1191  break;
1192  case osBack:
1193  case osEnd: if (Interact == Menu)
1194  DELETE_MENU;
1195  else
1197  break;
1198  default: ;
1199  }
1200  }
1201  else {
1202  // Key functions in "normal" viewing mode:
1203  if (key != kNone && KeyMacros.Get(key)) {
1204  cRemote::PutMacro(key);
1205  key = kNone;
1206  }
1207  switch (int(key)) {
1208  // Toggle channels:
1209  case kChanPrev:
1210  case k0: {
1211  if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel || LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])
1212  PreviousChannelIndex ^= 1;
1213  Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]);
1214  break;
1215  }
1216  // Direct Channel Select:
1217  case k1 ... k9:
1218  // Left/Right rotates through channel groups:
1219  case kLeft|k_Repeat:
1220  case kLeft:
1221  case kRight|k_Repeat:
1222  case kRight:
1223  // Previous/Next rotates through channel groups:
1224  case kPrev|k_Repeat:
1225  case kPrev:
1226  case kNext|k_Repeat:
1227  case kNext:
1228  // Up/Down Channel Select:
1229  case kUp|k_Repeat:
1230  case kUp:
1231  case kDown|k_Repeat:
1232  case kDown:
1233  Menu = new cDisplayChannel(NORMALKEY(key));
1234  break;
1235  // Viewing Control:
1236  case kOk: LastChannel = -1; break; // forces channel display
1237  // Instant resume of the last viewed recording:
1238  case kPlay:
1242  }
1243  break;
1244  default: break;
1245  }
1246  }
1247  if (!Menu) {
1248  if (!InhibitEpgScan)
1249  EITScanner.Process();
1250  if (!cCutter::Active() && cCutter::Ended()) {
1251  if (cCutter::Error())
1252  Skins.Message(mtError, tr("Editing process failed!"));
1253  else
1254  Skins.Message(mtInfo, tr("Editing process finished"));
1255  }
1257  if (cFileTransfer::Error())
1258  Skins.Message(mtError, tr("File transfer failed!"));
1259  else
1260  Skins.Message(mtInfo, tr("File transfer finished"));
1261  }
1262  }
1263 
1264  // SIGHUP shall cause a restart:
1265  if (LastSignal == SIGHUP) {
1266  if (ShutdownHandler.ConfirmRestart(true) && Interface->Confirm(tr("Press any key to cancel restart"), RESTARTCANCELPROMPT, true))
1267  EXIT(1);
1268  LastSignal = 0;
1269  }
1270 
1271  // Update the shutdown countdown:
1273  if (!ShutdownHandler.ConfirmShutdown(false))
1275  }
1276 
1278  // Handle housekeeping tasks
1279 
1280  // Shutdown:
1281  // Check whether VDR will be ready for shutdown in SHUTDOWNWAIT seconds:
1282  time_t Soon = Now + SHUTDOWNWAIT;
1284  if (ShutdownHandler.ConfirmShutdown(false))
1285  // Time to shut down - start final countdown:
1286  ShutdownHandler.countdown.Start(tr("VDR will shut down in %s minutes"), SHUTDOWNWAIT); // the placeholder is really %s!
1287  // Dont try to shut down again for a while:
1289  }
1290  // Countdown run down to 0?
1291  if (ShutdownHandler.countdown.Done()) {
1292  // Timed out, now do a final check:
1294  ShutdownHandler.DoShutdown(false);
1295  // Do this again a bit later:
1297  }
1298 
1299  // Disk housekeeping:
1302  // Plugins housekeeping:
1303  PluginManager.Housekeeping();
1304  }
1305 
1307 
1308  // Main thread hooks of plugins:
1309  PluginManager.MainThreadHook();
1310  }
1311 
1313  esyslog("emergency exit requested - shutting down");
1314 
1315 Exit:
1316 
1317  // Reset all signal handlers to default before Interface gets deleted:
1318  signal(SIGHUP, SIG_DFL);
1319  signal(SIGINT, SIG_DFL);
1320  signal(SIGTERM, SIG_DFL);
1321  signal(SIGPIPE, SIG_DFL);
1322  signal(SIGALRM, SIG_DFL);
1323 
1324  PluginManager.StopPlugins();
1327  cCutter::Stop();
1328  delete Menu;
1330  delete Interface;
1332  Remotes.Clear();
1333  Audios.Clear();
1334  Skins.Clear();
1335  SourceParams.Clear();
1336  if (ShutdownHandler.GetExitCode() != 2) {
1339  Setup.Save();
1340  }
1342  EpgHandlers.Clear();
1343  PluginManager.Shutdown(true);
1344  cSchedules::Cleanup(true);
1345  ReportEpgBugFixStats(true);
1346  if (WatchdogTimeout > 0)
1347  dsyslog("max. latency time %d seconds", MaxLatencyTime);
1348  if (LastSignal)
1349  isyslog("caught signal %d", LastSignal);
1351  esyslog("emergency exit!");
1352  isyslog("exiting, exit code %d", ShutdownHandler.GetExitCode());
1353  if (SysLogLevel > 0)
1354  closelog();
1355  if (HasStdin)
1356  tcsetattr(STDIN_FILENO, TCSANOW, &savedTm);
1357  return ShutdownHandler.GetExitCode();
1358 }