MythTV  master
mythconverg_backup.pl
Go to the documentation of this file.
1 #!/usr/bin/perl -w
2 #
3 # mythconverg_backup.pl
4 #
5 # Creates a backup of the MythTV database.
6 #
7 # For details, see:
8 # mythconverg_backup.pl --help
9 
10 # Includes
11  use Getopt::Long;
12  use File::Temp qw/ tempfile /;
13 
14 # Script info
15  $NAME = 'MythTV Database Backup Script';
16  $VERSION = '1.0.14';
17 
18 # Some variables we'll use here
19  our ($username, $homedir, $mythconfdir, $database_information_file);
20  our ($mysqldump, $compress, $rotate, $rotateglob, $backup_xmltvids);
21  our ($usage, $debug, $show_version, $show_version_script, $dbh);
22  our ($d_db_name, $d_mysqldump, $d_compress, $d_rotate, $d_rotateglob);
23 # This script does not accept a database password on the command-line.
24 # Any packager who enables the functionality should modify the --help output.
25 # our ($db_password);
26  our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
27  our ($backup_directory, $backup_filename);
28  our ($verbose_level_always, $verbose_level_debug, $verbose_level_error);
29 
30  our %mysql_conf = ('db_host' => '',
31  'db_port' => -1,
32  'db_user' => '',
33  'db_pass' => '',
34  'db_name' => '',
35  'db_schemaver' => ''
36  );
37  our %backup_conf = ('directory' => '',
38  'filename' => ''
39  );
40 
41 # Variables used to untaint data
42  our $is_env_tainted = 1;
43  our $old_env_path = $ENV{"PATH"};
44  our @d_allowed_paths = ("/bin",
45  "/usr/bin",
46  "/usr/local/bin",
47  "/sbin",
48  "/usr/sbin",
49  "/usr/local/sbin"
50  );
51  our @allowed_paths;
52 
53 # Debug levels
54  $verbose_level_always = 0;
55  $verbose_level_debug = 1;
56  $verbose_level_error = 255;
57 
58 # Defaults
59  $d_db_name = 'mythconverg';
60  $d_mysqldump = 'mysqldump';
61  $d_compress = 'gzip';
62  $d_rotate = 5;
63  $d_rotateglob = $d_db_name.'-????-??????????????.sql*';
64 
65 # Provide default values for GetOptions
66  $mysqldump = $d_mysqldump;
67  $compress = $d_compress;
68  $rotate = $d_rotate;
69  $rotateglob = $d_rotateglob;
70  $debug = 0;
71 
72 # Load the cli options
73  GetOptions('hostname|DBHostName=s' => \$db_hostname,
74  'port|DBPort=i' => \$db_port,
75  'username|DBUserName=s' => \$db_username,
76 # This script does not accept a database password on the command-line.
77 # 'password|DBPassword=s' => \$db_password,
78  'name|DBName=s' => \$db_name,
79  'schemaver|DBSchemaVer=s' => \$db_schema_version,
80  'directory|DBBackupDirectory=s' => \$backup_directory,
81  'filename|DBBackupFilename=s' => \$backup_filename,
82  'mysqldump=s' => \$mysqldump,
83  'compress=s' => \$compress,
84  'rotate=i' => \$rotate,
85  'rotateglob|glob=s' => \$rotateglob,
86  'backup_xmltvids|backup-xmltvids|'.
87  'xmltvids' => \$backup_xmltvids,
88  'usage|help|h+' => \$usage,
89  'version' => \$show_version,
90  'script_version|script-version|v' => \$show_version_script,
91  'verbose|debug|d+' => \$debug
92  );
93 
94 # Print version information
95  sub print_version_information
96  {
97  my $script_name = substr $0, rindex($0, '/') + 1;
98  print "$NAME\n$script_name\nversion: $VERSION\n";
99  }
100 
101  if ($show_version_script)
102  {
103  print "$NAME,$VERSION,,\n";
104  exit;
105  }
106  elsif ($show_version)
107  {
108  print_version_information;
109  exit;
110  }
111 
112 
113 # Print usage
114  if ($usage)
115  {
116  print_version_information;
117  print <<EOF;
118 
119 Usage:
120  $0 [options|database_information_file]
121 
122 Creates a backup of the MythTV database.
123 
124 QUICK START:
125 
126 Create a file ~/.mythtv/backuprc with a single line,
127 "DBBackupDirectory=/home/mythtv" (no quotes), and run this script to create a
128 database backup. Use the --verbose argument to see what is happening.
129 
130 # echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
131 # $0 --verbose
132 
133 Make sure you keep the backuprc file for next time. Once you have successfully
134 created a backup, the script may be run without the --verbose argument.
135 
136 To backup xmltvids:
137 
138 Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
139 script with the --backup_xmltvids argument.
140 
141 # $0 --backup_xmltvids
142 
143 EOF
144 
145  if ($usage > 1)
146  {
147  print <<EOF;
148 DETAILED DESCRIPTION:
149 
150 This script can be called by MythTV for creating automatic database backups.
151 In this mode, it is always exected with a single command-line argument
152 specifying the name of a "database information file" (see DATABASE INFORMATION
153 FILE, below), which contains sufficient information about the database and the
154 backup to allow the script to create a backup without needing any additional
155 configuration files. In this mode, all other MythTV configuration files
156 (including config.xml, mysql.txt) are ignored, but the backup resource file
157 (see RESOURCE FILE, below) and the MySQL option files (i.e. /etc/my.cnf or
158 ~/.my.cnf) will be honored.
159 
160 The script can also be called interactively (i.e. "manually") by the user to
161 create a database backup on demand. Required information may be passed into
162 the script using command-line arguments or with a database information file.
163 If a database information file is specified, all command-line arguments will be
164 ignored. If no database information file is specified, the script will attempt
165 to determine the appropriate configuration by using the MythTV configuration
166 file(s) (preferring config.xml, but falling back to mysql.txt if no config.xml
167 exists). Once the MythTV configuration file has been parsed, the backup
168 resource file (see RESOURCE FILE, below) will be parsed, then command-line
169 arguments will be applied (thus overriding any values determined from the
170 configuration files).
171 
172 The only information required by the script is the directory in which the
173 backup should be created. Therefore, when using a database information file,
174 the DBBackupDirectory should be specified, or if running manually, the
175 --directory command-line argument should be specified. The DBBackupDirectory
176 may be specified in a backup resource file (see RESOURCE FILE, below). Doing
177 so is especially useful for manual backups. If the specified directory is not
178 writable, the script will terminate. Likewise, if a file whose name matches
179 the name to be used for the backup file already exists, the script will
180 terminate.
181 
182 If the database name is not specified, the script will attempt to use the
183 MythTV default database name, $d_db_name. Note that the same is not true for
184 the database username and database password. These must be explicitly
185 specified. The password must be specified in a database information file, a
186 backup resource file, or a MySQL options file. The username may be specified
187 the same way or may be specified using a command-line argument if not using a
188 database information file.
189 
190 While this script may be called while MythTV is running, there is a possibility
191 of creating a backup with data integrity errors (i.e. if MythTV updates data in
192 multiple tables between the time the script backs up the first and subsequent
193 tables). Also, depending on your system configuration, performing a backup
194 (which may result in locking a table while it is being backed up) while
195 recording may cause corruption of the recording or inability to properly write
196 recording data (such as the recording seek table) to the database.
197 Therefore, if configuring this script to run in a cron job, try to ensure it
198 runs at a time when recordings are least likely to occur. Alternatively, by
199 choosing to run the script in a system start/shutdown script (i.e. an init
200 script), you may call the script before starting mythbackend or after stopping
201 mythbackend. Note, however, that checking whether to perform the backup is the
202 responsibility of the init script (not this script)--i.e. in a system with
203 multiple frontends/backends, the init script should ensure the backup is
204 created only on the master backend.
205 
206 DATABASE INFORMATION FILE
207 
208 The database information file contains information about the database and the
209 backup. The information within the file is specified as name=value pairs using
210 the same names as used by the MythTV config.xml and mysql.txt configuration
211 files. The following variables are recognized:
212 
213  DBHostName - The hostname (or IP address) which should be used to find the
214  MySQL server.
215  DBPort - The TCP/IP port number to use for the connection. This may have a
216  value of 0, i.e. if the hostname is localhost or if the server is
217  using the default MySQL port or the port specified in a MySQL
218  options file.
219  DBUserName - The database username to use when connecting to the server.
220  DBPassword - The password to use when connecting to the server.
221  DBName - The name of the database that contains the MythTV data.
222  DBSchemaVer - The MythTV schema version of the database. This value will be
223  used to create the backup filename, but only if the filename
224  has not been specified using DBBackupFilename or the --filename
225  argument.
226  DBBackupDirectory - The directory in which the backup file should be
227  created. This directory may have been specially
228  configured by the user as the "DB Backups" storage
229  group. It is recommended that this directory be
230  used--especially in "common-use" scripts such as those
231  provided by distributions.
232  DBBackupFilename - The name of the file in which the backup should be
233  created. Additional extensions may be added by this
234  script as required (i.e. adding an appropriate suffix,
235  such as ".gz", to the file if it is compressed). If the
236  filename recommended by mythbackend is used, it will be
237  displayed in the GUI messages provided for the user. If
238  the recommended filename is not used, the user will not be
239  told where to find the backup file. If no value is
240  provided, a filename using the default filename format
241  will be chosen.
242  mysqldump - The path (including filename) of the mysqldump executable.
243  compress - The command (including path, if necessary) to use to
244  compress the backup. Using gzip is significantly less
245  resource intensive on an SQL backup file than using bzip2,
246  at the cost of a slightly (about 33%) larger compressed
247  filesize, a difference which should be irrelevant at the
248  filesizes involved (especially when compared to the size
249  of recording files). If you decide to use another
250  compression algorithm, please ensure you test it
251  appropriately to verify it does not negatively affect
252  operation of your system. If no value is specified for
253  compress or if the value '$d_compress' is specified, the
254  script will first attempt to use the IO::Compress::Gzip
255  module to compress the backup file, but, if not available,
256  will run the command specified. Therefore, if
257  IO::Compress::Gzip is installed and functional, specifying
258  a value for compress is unnecessary. If neither approach
259  works, the backup file will be left uncompressed.
260  rotate - The number of backups to keep when rotating. To disable
261  rotation, specify -1. Backup rotation is performed by
262  identifying all files in DBBackupDirectory whose names
263  match the glob specified by rotateglob. It is critical
264  that the chosen backup filenames can be sorted properly
265  using an alphabetical sort. If using the default filename
266  format--which contains the DBSchemaVer--and you downgrade
267  MythTV and restore a backup from an older DBSchemaVer,
268  make sure you move the backups from the newer DBSchemaVer
269  out of the DBBackupDirectory or they may cause your new
270  backups to be deleted.
271  rotateglob - The sh-like glob used to identify files within
272  DBBackupDirectory to be considered for rotation. Be
273  very careful with the value--especially if using a
274  DBBackupDirectory that contains any files other than
275  backups.
276 
277 RESOURCE FILE
278 
279 The backup resource file specifies values using the same format as described
280 for the database information file, above, but is intended as a "permanent,"
281 user-created configuration file. The database information file is intended as
282 a "single-use" configuration file, often created automatically (i.e. by a
283 program, such as mythbackend, or a script). The backup resource file should be
284 placed at "~/.mythtv/backuprc" and given appropriate permissions. To be usable
285 by the script, it must be readable. However, it should be protected as
286 required--i.e. if the DBPassword is specified, it should be made readable only
287 by the owner.
288 
289 When specifying a database information file, the resource file is parsed before
290 the database information file to prevent the resource file from overriding the
291 information in the database information file. When no database information
292 file is specified, the resource file is parsed after the MythTV configuration
293 files, but before the command-line arguments to allow the resource file to
294 override values in the configuration files and to allow command-line arguments
295 to override resource file defaults.
296 
297 options:
298 
299 --hostname [database hostname]
300 
301  The hostname (or IP address) which should be used to find the MySQL server.
302  See DBHostName, above.
303 
304 --port [database port]
305 
306  The TCP/IP port number to use for connection to the MySQL server. See
307  DBPort, above.
308 
309 --username [database username]
310 
311  The MySQL username to use for connection to the MySQL server. See
312  DBUserName, above.
313 
314 --name [database name]
315 
316  The name of the database containing the MythTV data. See DBName, above.
317 
318  Default: $d_db_name
319 
320 --schemaver [MythTV database schema version]
321 
322  The MythTV schema version. See DBSchemaVer, above.
323 
324 --directory [directory]
325 
326  The directory in which the backup file should be stored. See
327  DBBackupDirectory, above.
328 
329 --filename [database backup filename]
330 
331  The name to use for the database backup file. If not provided, a filename
332  using a default format will be chosen. See DBBackupFilename, above.
333 
334 --mysqldump [path]
335 
336  The path (including filename) of the mysqldump executable. See mysqldump
337  in the DATABASE INFORMATION FILE description, above.
338 
339  Default: $d_mysqldump
340 
341 --compress [path]
342 
343  The command (including path, if necessary) to use to compress the backup.
344  See compress in the DATABASE INFORMATION FILE description, above.
345 
346  Default: $d_compress
347 
348 --rotate [number]
349  The number of backups to keep when rotating. To disable rotation, specify
350  -1. See rotate in the DATABASE INFORMATION FILE description, above.
351 
352  Default: $d_rotate
353 
354 --rotateglob [glob]
355  The sh-like glob used to identify files within DBBackupDirectory to be
356  considered for rotation. See rotateglob in the DATABASE INFORMATION FILE
357  description, above.
358 
359  Default: $d_rotateglob
360 
361 --backup_xmltvids
362  Rather than creating a backup of the entire database, create a backup of
363  xmltvids. This is useful when doing a full channel scan. The resulting
364  backup is a series of SQL UPDATE statements that can be executed to set
365  the xmltvid for channels whose callsign is the same before and after
366  the scan. Note that the backup file will contain comments with additional
367  channel information, which you can use to identify channels in case the
368  callsign changes.
369 
370 --help
371 
372  Show this help text.
373 
374 --version
375 
376  Show version information.
377 
378 --verbose
379 
380  Show what is happening.
381 
382 --script_version | -v
383 
384  Show script version information. This is primarily useful for scripts
385  or programs needing to parse the version information.
386 
387 EOF
388  }
389  else
390  {
391  print "For detailed help:\n\n# $0 --help --help\n\n";
392  }
393  exit;
394  }
395 
396  sub verbose
397  {
398  my $level = shift;
399  my $error = 0;
400  if ($level == $verbose_level_error)
401  {
402  $error = 1;
403  }
404  else
405  {
406  return unless ($debug >= $level);
407  }
408  print { $error ? STDERR : STDOUT } join("\n", @_), "\n";
409  }
410 
411  sub print_configuration
412  {
413  verbose($verbose_level_debug,
414  '',
415  'Database Information:',
416  " DBHostName: $mysql_conf{'db_host'}",
417  " DBPort: $mysql_conf{'db_port'}",
418  " DBUserName: $mysql_conf{'db_user'}",
419  ' DBPassword: ' .
420  ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
421  # "$mysql_conf{'db_pass'}",
422  " DBName: $mysql_conf{'db_name'}",
423  " DBSchemaVer: $mysql_conf{'db_schemaver'}",
424  " DBBackupDirectory: $backup_conf{'directory'}",
425  " DBBackupFilename: $backup_conf{'filename'}");
426  verbose($verbose_level_debug,
427  '',
428  'Executables:',
429  " mysqldump: $mysqldump",
430  " compress: $compress");
431  }
432 
433  sub configure_environment
434  {
435  verbose($verbose_level_debug,
436  '', 'Configuring environment:');
437 
438  # Get the user's login and home directory, so we can look for config files
439  ($username, $homedir) = (getpwuid $>)[0,7];
440  $username = $ENV{'USER'} if ($ENV{'USER'});
441  $homedir = $ENV{'HOME'} if ($ENV{'HOME'});
442  if ($username && !$homedir)
443  {
444  $homedir = "/home/$username";
445  if (!-e $homedir && -e "/Users/$username")
446  {
447  $homedir = "/Users/$username";
448  }
449  }
450  verbose($verbose_level_debug,
451  " - username: $username",
452  " - HOME: $homedir");
453 
454  # Find the config directory
455  $mythconfdir = $ENV{'MYTHCONFDIR'}
456  ? $ENV{'MYTHCONFDIR'}
457  : "$homedir/.mythtv"
458  ;
459 
460  verbose($verbose_level_debug,
461  " - MYTHCONFDIR: $mythconfdir");
462  }
463 
464 # Though much of the configuration file parsing could be done by the MythTV
465 # Perl bindings, using them to retrieve database information is not appropriate
466 # for a backup script. The Perl bindings require the backend to be running and
467 # use UPnP for autodiscovery. Also, parsing the files "locally" allows
468 # supporting even the old MythTV database configuration file, mysql.txt.
469  sub parse_database_information
470  {
471  my $file = shift;
472  verbose($verbose_level_debug,
473  " - checking: $file");
474  return 0 unless ($file && -e $file);
475  verbose($verbose_level_debug,
476  " parsing: $file");
477  open(CONF, $file) or die("\nERROR: Unable to read $file: $!".
478  ', stopped');
479  while (my $line = <CONF>)
480  {
481  # Cleanup
482  next if ($line =~ m/^\s*#/);
483  $line =~ s/^str //;
484  chomp($line);
485  $line =~ s/^\s+//;
486  $line =~ s/\s+$//;
487  # Split off the var=val pairs
488  my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
489  # Also look for <var>val</var> from config.xml
490  if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
491  {
492  $var = $1; $val = $2;
493  }
494  next unless ($var && $var =~ m/\w/);
495  if (($var eq 'Host') || ($var eq 'DBHostName'))
496  {
497  $mysql_conf{'db_host'} = $val;
498  }
499  elsif (($var eq 'Port') || ($var eq 'DBPort'))
500  {
501  $mysql_conf{'db_port'} = $val;
502  }
503  elsif (($var eq 'UserName') || ($var eq 'DBUserName'))
504  {
505  $mysql_conf{'db_user'} = $val;
506  }
507  elsif (($var eq 'Password') || ($var eq 'DBPassword'))
508  {
509  $mysql_conf{'db_pass'} = $val;
510  $mysql_conf{'db_pass'} =~ s/&amp;/&/sg;
511  $mysql_conf{'db_pass'} =~ s/&gt;/>/sg;
512  $mysql_conf{'db_pass'} =~ s/&lt;/</sg;
513  }
514  elsif (($var eq 'DatabaseName') || ($var eq 'DBName'))
515  {
516  $mysql_conf{'db_name'} = $val;
517  }
518  elsif ($var eq 'DBSchemaVer')
519  {
520  $mysql_conf{'db_schemaver'} = $val;
521  }
522  elsif ($var eq 'DBBackupDirectory')
523  {
524  $backup_conf{'directory'} = $val;
525  }
526  elsif ($var eq 'DBBackupFilename')
527  {
528  $backup_conf{'filename'} = $val;
529  }
530  elsif ($var eq 'mysqldump')
531  {
532  $mysqldump = $val;
533  }
534  elsif ($var eq 'compress')
535  {
536  $compress = $val;
537  }
538  elsif ($var eq 'rotate')
539  {
540  $rotate = $val;
541  }
542  elsif ($var eq 'rotateglob')
543  {
544  $rotateglob = $val;
545  }
546  }
547  close CONF;
548  return 1;
549  }
550 
551  sub read_mysql_txt
552  {
553  # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
554  # couple places, so try the usual suspects in the same order that mythtv
555  # does in libs/libmyth/mythcontext.cpp
556  my $found = 0;
557  my $result = 0;
558  my @mysql = ('/usr/local/share/mythtv/mysql.txt',
559  '/usr/share/mythtv/mysql.txt',
560  '/usr/local/etc/mythtv/mysql.txt',
561  '/etc/mythtv/mysql.txt',
562  $homedir ? "$homedir/.mythtv/mysql.txt" : '',
563  'mysql.txt',
564  $mythconfdir ? "$mythconfdir/mysql.txt" : '',
565  );
566  foreach my $file (@mysql)
567  {
568  $found = parse_database_information($file);
569  $result = $result + $found;
570  }
571  return $result;
572  }
573 
574  sub read_resource_file
575  {
576  parse_database_information("$mythconfdir/backuprc");
577  }
578 
579  sub apply_arguments
580  {
581  verbose($verbose_level_debug,
582  '', 'Applying command-line arguments.');
583  if ($db_hostname)
584  {
585  $mysql_conf{'db_host'} = $db_hostname;
586  }
587  if ($db_port)
588  {
589  $mysql_conf{'db_port'} = $db_port;
590  }
591  if ($db_username)
592  {
593  $mysql_conf{'db_user'} = $db_username;
594  }
595  # This script does not accept a database password on the command-line.
596 # if ($db_password)
597 # {
598 # $mysql_conf{'db_pass'} = $db_password;
599 # }
600  if ($db_name)
601  {
602  $mysql_conf{'db_name'} = $db_name;
603  }
604  if ($db_schema_version)
605  {
606  $mysql_conf{'db_schemaver'} = $db_schema_version;
607  }
608  if ($backup_directory)
609  {
610  $backup_conf{'directory'} = $backup_directory;
611  }
612  if ($backup_filename)
613  {
614  $backup_conf{'filename'} = $backup_filename;
615  }
616  }
617 
618  sub read_config
619  {
620  my $result = 0;
621  # If specified, use only the database information file
622  if ($database_information_file)
623  {
624  verbose($verbose_level_debug,
625  '', 'Database Information File specified. Ignoring all'.
626  ' command-line arguments');
627  verbose($verbose_level_debug,
628  '', 'Database Information File: '.
629  $database_information_file);
630  unless (-T "$database_information_file")
631  {
632  verbose($verbose_level_always,
633  '', 'The argument you supplied for the database'.
634  ' information file is invalid.',
635  'If you were trying to specify a backup filename,'.
636  ' please use the --directory ',
637  'and --filename arguments.');
638  die("\nERROR: Invalid database information file, stopped");
639  }
640  # When using a database information file, parse the resource file first
641  # so it cannot override database information file settings
642  read_resource_file;
643  $result = parse_database_information($database_information_file);
644  return $result;
645  }
646 
647  # No database information file, so try the MythTV configuration files.
648  verbose($verbose_level_debug,
649  '', 'Parsing configuration files:');
650  # Prefer the config.xml file
651  my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
652  $result = parse_database_information($file);
653  if (!$result)
654  {
655  # Use the "legacy" mysql.txt file as a fallback
656  $result = read_mysql_txt;
657  }
658  # Read the resource file next to override the config file information, but
659  # to allow command-line arguments to override resource file "defaults"
660  read_resource_file;
661  # Apply the command-line arguments to override the information provided
662  # by the config file(s).
663  apply_arguments;
664  return $result;
665  }
666 
667  sub check_database_libs
668  {
669  # Try to load the DBI library if available (but don't require it)
670  BEGIN
671  {
672  our $has_dbi = 1;
673  eval 'use DBI;';
674  if ($@)
675  {
676  $has_dbi = 0;
677  }
678  }
679  verbose($verbose_level_debug,
680  '', 'DBI is not installed.') if (!$has_dbi);
681  # Try to load the DBD::mysql library if available (but don't
682  # require it)
683  BEGIN
684  {
685  our $has_dbd = 1;
686  eval 'use DBD::mysql;';
687  if ($@)
688  {
689  $has_dbd = 0;
690  }
691  }
692  verbose($verbose_level_debug,
693  '', 'DBD::mysql is not installed.') if (!$has_dbd);
694  return ($has_dbi + $has_dbd);
695  }
696 
697  sub check_database
698  {
699  if (!defined($dbh))
700  {
701  my $have_database_libs = check_database_libs;
702  return 0 if ($have_database_libs < 2);
703  my $temp_host = $mysql_conf{'db_host'};
704  if ($temp_host =~ /:/)
705  {
706  if ($temp_host =~ /^(?!\[).*(?!\])$/)
707  {
708  $temp_host = "[$temp_host]";
709  }
710  }
711  $dbh = DBI->connect("dbi:mysql:".
712  "host=$temp_host:".
713  "port=$mysql_conf{'db_port'}:".
714  "database=$mysql_conf{'db_name'}",
715  "$mysql_conf{'db_user'}",
716  "$mysql_conf{'db_pass'}",
717  { PrintError => 1 });
718  }
719  return 1;
720  }
721 
722  sub create_backup_filename
723  {
724  # Create a default backup filename
725  $backup_conf{'filename'} = $mysql_conf{'db_name'};
726  if (!$backup_conf{'filename'})
727  {
728  $backup_conf{'filename'} = $d_db_name;
729  }
730  if ((!$mysql_conf{'db_schemaver'}) &&
731  ($mysql_conf{'db_host'}) && ($mysql_conf{'db_name'}) &&
732  ($mysql_conf{'db_user'}) && ($mysql_conf{'db_pass'}))
733  {
734  # If DBI is available, query the DB for the schema version
735  if (check_database)
736  {
737  verbose($verbose_level_debug,
738  '', 'No DBSchemaVer specified, querying database.');
739  my $query = 'SELECT data FROM settings WHERE value = ?';
740  if (defined($dbh))
741  {
742  my $sth = $dbh->prepare($query);
743  if ($sth->execute('DBSchemaVer'))
744  {
745  while (my @data = $sth->fetchrow_array)
746  {
747  $mysql_conf{'db_schemaver'} = $data[0];
748  verbose($verbose_level_debug,
749  "Found DBSchemaVer:".
750  " $mysql_conf{'db_schemaver'}.");
751  }
752  }
753  else
754  {
755  verbose($verbose_level_debug,
756  "Unable to retrieve DBSchemaVer from".
757  " database. Filename will not contain ",
758  "DBSchemaVer.");
759  }
760  }
761  }
762  else
763  {
764  verbose($verbose_level_debug,
765  '', 'No DBSchemaVer specified.',
766  'DBI and/or DBD:mysql is not available. Unable'.
767  ' to query database to determine ',
768  'DBSchemaVer. DBSchemaVer will not be included'.
769  ' in backup filename.',
770  'Please ensure DBI and DBD::mysql are'.
771  ' installed.');
772  }
773  }
774  if ($mysql_conf{'db_schemaver'})
775  {
776  $backup_conf{'filename'} .= '-'.$mysql_conf{'db_schemaver'};
777  }
778  # Format the time using localtime data so we don't have to bring in
779  # another dependency.
780  my @timeData = localtime(time);
781  $backup_conf{'filename'} .= sprintf('-%04d%02d%02d%02d%02d%02d.sql',
782  ($timeData[5] + 1900),
783  ($timeData[4] + 1),
784  $timeData[3], $timeData[2],
785  $timeData[1], $timeData[0]);
786  }
787 
788  sub check_backup_directory
789  {
790  my $result = 0;
791  if ($backup_conf{'directory'})
792  {
793  $result = 1;
794  }
795  elsif (check_database)
796  # If DBI is available, query the DB for the backup directory
797  {
798  verbose($verbose_level_debug,
799  '', 'No DBBackupDirectory specified, querying database.');
800  my $query = 'SELECT dirname, hostname FROM storagegroup '.
801  ' WHERE groupname = ?';
802  if (defined($dbh))
803  {
804  my $directory;
805  my $sth = $dbh->prepare($query);
806  if ($sth->execute('DB Backups'))
807  {
808  # We don't know the hostname associated with this host, and
809  # since it's not worth parsing the mysql.txt/config.xml
810  # LocalHostName (unique identifier), with fallback to the
811  # system hostname, and handling issues along the way, just look
812  # for any available DB Backups directory and, if none are
813  # usable, look for a Default group directory
814  while (my @data = $sth->fetchrow_array)
815  {
816  $directory = $data[0];
817  if (-d $directory && -w $directory)
818  {
819  $backup_conf{'directory'} = $directory;
820  verbose($verbose_level_debug,
821  "Found DB Backups directory:".
822  " $backup_conf{'directory'}.");
823  $result = 1;
824  $sth->finish;
825  last;
826  }
827  }
828  }
829  if ($result == 0 && $sth->execute('Default'))
830  {
831  while (my @data = $sth->fetchrow_array)
832  {
833  $directory = $data[0];
834  if (-d $directory && -w $directory)
835  {
836  $backup_conf{'directory'} = $directory;
837  verbose($verbose_level_debug,
838  "Found Default directory:".
839  " $backup_conf{'directory'}.");
840  $result = 1;
841  $sth->finish;
842  last;
843  }
844  }
845  }
846  }
847  if ($result == 0)
848  {
849  verbose($verbose_level_debug,
850  "Unable to retrieve DBBackupDirectory from".
851  " database.");
852  }
853  }
854  return $result;
855  }
856 
857  sub check_config
858  {
859  verbose($verbose_level_debug,
860  '', 'Checking configuration.');
861  # Check directory/filename
862  if (!check_backup_directory)
863  {
864  print_configuration;
865  die("\nERROR: DBBackupDirectory not specified, stopped");
866  }
867  if ((!-d $backup_conf{'directory'}) ||
868  (!-w $backup_conf{'directory'}))
869  {
870  print_configuration;
871  verbose($verbose_level_error,
872  '', 'ERROR: DBBackupDirectory is not a directory or is '.
873  'not writable. Please specify',
874  ' a directory in your database information file'.
875  ' using DBBackupDirectory.',
876  ' If not using a database information file,'.
877  ' please specify the ',
878  ' --directory command-line option.');
879  die("\nInvalid backup directory, stopped");
880  }
881  if (!$backup_conf{'filename'})
882  {
883  if ($backup_xmltvids)
884  {
885  my $file = 'mythtv_xmltvid_backup';
886  # Format the time using localtime data so we don't have to bring in
887  # another dependency.
888  my @timeData = localtime(time);
889  $file .= sprintf('-%04d%02d%02d%02d%02d%02d.sql',
890  ($timeData[5] + 1900),
891  ($timeData[4] + 1),
892  $timeData[3], $timeData[2],
893  $timeData[1], $timeData[0]);
894  $backup_conf{'filename'} = $file;
895  }
896  else
897  {
898  create_backup_filename;
899  }
900  }
901  if ( -e "$backup_conf{'directory'}/$backup_conf{'filename'}")
902  {
903  verbose($verbose_level_error,
904  '', 'ERROR: The specified file already exists.');
905  die("\nInvalid backup filename, stopped");
906  }
907  if (!$mysql_conf{'db_name'})
908  {
909  verbose($verbose_level_debug,
910  '', "WARNING: DBName not specified. Using $d_db_name");
911  $mysql_conf{'db_name'} = $d_db_name;
912  }
913  # Though the script will attempt a backup even if no other database
914  # information is provided (i.e. using "defaults" from the MySQL options
915  # file, warning the user that some "normally-necessary" information is not
916  # provided may be nice.
917  return if (!$debug);
918  if (!$mysql_conf{'db_host'})
919  {
920  verbose($verbose_level_debug,
921  '', 'WARNING: DBHostName not specified.',
922  ' Assuming it is specified in the MySQL'.
923  ' options file.');
924  }
925  if (!$mysql_conf{'db_user'})
926  {
927  verbose($verbose_level_debug,
928  '', 'WARNING: DBUserName not specified.',
929  ' Assuming it is specified in the MySQL'.
930  ' options file.');
931  }
932  if (!$mysql_conf{'db_pass'})
933  {
934  verbose($verbose_level_debug,
935  '', 'WARNING: DBPassword not specified.',
936  ' Assuming it is specified in the MySQL'.
937  ' options file.');
938  }
939  }
940 
941  sub create_defaults_extra_file
942  {
943  return '' if (!$mysql_conf{'db_pass'});
944  verbose($verbose_level_debug,
945  '', "Attempting to use supplied password for $mysqldump.",
946  'Any [client] or [mysqldump] password specified in the MySQL'.
947  ' options file will',
948  'take precedence.');
949  # Let tempfile handle unlinking on exit so we don't have to verify that the
950  # file with $filename is the file we created
951  my ($fh, $filename) = tempfile(UNLINK => 1);
952  # Quote the password if it contains # or whitespace or quotes.
953  # Quoting of values in MySQL options files is only supported on MySQL
954  # 4.0.16 and above, so only quote if required.
955  my $quote = '';
956  my $safe_password = $mysql_conf{'db_pass'};
957  if ($safe_password =~ /[#'"\s]/)
958  {
959  $quote = "'";
960  $safe_password =~ s/'/\\'/g;
961  }
962  print $fh "[client]\npassword=${quote}${safe_password}${quote}\n".
963  "[mysqldump]\npassword=${quote}${safe_password}${quote}\n";
964  return $filename;
965  }
966 
967  sub do_xmltvid_backup
968  {
969  my $exit = 1;
970  if (check_database)
971  {
972  my ($chanid, $channum, $callsign, $name, $xmltvid);
973  my $query = " SELECT chanid, channum, callsign, name, xmltvid".
974  " FROM channel ".
975  "ORDER BY CAST(channum AS SIGNED),".
976  " CAST(SUBSTRING(channum".
977  " FROM (1 +".
978  " LOCATE('_', channum) +".
979  " LOCATE('-', channum) +".
980  " LOCATE('#', channum) +".
981  " LOCATE('.', channum)))".
982  " AS SIGNED)";
983  my $sth = $dbh->prepare($query);
984  verbose($verbose_level_debug,
985  '', 'Querying database for xmltvid information.');
986  my $file = "$backup_conf{'directory'}/$backup_conf{'filename'}";
987  open BACKUP, '>', $file or die("\nERROR: Unable to open".
988  " $file: $!, stopped");
989  for ($section = 0; $section < 2; $section++)
990  {
991  if ($sth->execute)
992  {
993  while (my @data = $sth->fetchrow_array)
994  {
995  $chanid = $data[0];
996  $channum = $data[1];
997  $callsign = $data[2];
998  $name = $data[3];
999  $xmltvid = $data[4];
1000  verbose($verbose_level_debug,
1001  "Found channel: $chanid, $channum, $callsign,".
1002  " $name, $xmltvid.") if ($section == 0);
1003  if ($xmltvid && $callsign)
1004  {
1005  if ($section == 0)
1006  {
1007  print BACKUP "-- Start Channel Data\n".
1008  "-- ID: '$chanid'\n".
1009  "-- Number: '$channum'\n".
1010  "-- Callsign: '$callsign'\n".
1011  "-- Name: '$name'\n".
1012  "-- XMLTVID: '$xmltvid'\n".
1013  "-- End Channel Data\n";
1014  print BACKUP "UPDATE channel".
1015  " SET xmltvid = '$xmltvid'".
1016  " WHERE callsign = '$callsign'".
1017  ";\n";
1018  }
1019  else
1020  {
1021  print BACKUP "UPDATE channel".
1022  " SET xmltvid = '$xmltvid'".
1023  " WHERE channum = '$channum'".
1024  " AND name = '$name';\n";
1025  }
1026  }
1027  }
1028  if ($section == 0)
1029  {
1030  verbose($verbose_level_debug,
1031  '', 'Successfully backed up xmltvid'.
1032  ' information.'.
1033  '', '', 'Creating alternate format backup.');
1034  print BACKUP "\n\n/* Alternate format */\n".
1035  "/*\n";
1036  }
1037  else
1038  {
1039  print BACKUP "*/\n";
1040  verbose($verbose_level_debug,
1041  'Successfully created alternate format'.
1042  ' backup.');
1043  }
1044  $exit = 0;
1045  }
1046  else
1047  {
1048  verbose($verbose_level_error,
1049  '', 'ERROR: Unable to retrieve xmltvid information'.
1050  ' from database.');
1051  die("\nError retrieving xmltvid information, stopped");
1052  }
1053  }
1054  close BACKUP;
1055  }
1056  else
1057  {
1058  verbose($verbose_level_error,
1059  '', 'ERROR: Unable to backup xmltvids without Perl'.
1060  ' database libraries.',
1061  ' Please ensure the Perl DBI and DBD::mysql'.
1062  ' modules are installed.');
1063  die("\nPerl database libraries missing, stopped");
1064  }
1065  return $exit;
1066  }
1067 
1068 # This subroutine performs limited checking of a command and untaints the
1069 # command (and the environment) if the command seems to use an absolute path
1070 # containing no . or .. references or if it's a simple command name referencing
1071 # an executable in a "normal" directory for binaries. It should only be called
1072 # after careful consideration of the effects of doing so and of whether it
1073 # makes sense to override taint-mode runtime checking of the value.
1074  sub untaint_command
1075  {
1076  my $command = shift;
1077  my $allow_untaint = 0;
1078  # Only allow directories from @d_allowed_paths that exist in the PATH
1079  unless (@allowed_paths)
1080  {
1081  foreach my $path (split(/:/, $old_env_path))
1082  {
1083  if (grep(/^$path$/, @d_allowed_paths))
1084  {
1085  push(@allowed_paths, $path);
1086  }
1087  }
1088  verbose($verbose_level_debug + 2,
1089  '', 'Allowing paths:', @allowed_paths,
1090  'From PATH: '.$old_env_path);
1091  }
1092 
1093  verbose($verbose_level_debug + 2, '', 'Verifying command: '.$command);
1094  if ($command =~ /^\//)
1095  {
1096  verbose($verbose_level_debug + 2, ' - Command starts with /.');
1097  if (! ($command =~ /\/\.+\//))
1098  {
1099  verbose($verbose_level_debug + 2,
1100  ' - Command does not contain dir refs.');
1101  if (-e "$command" && -f "$command" && -x "$command")
1102  {
1103  # Seems to be a valid executable specified with a path starting
1104  # with / and having no current/previous directory references
1105  verbose($verbose_level_debug + 2,
1106  'Unmodified command meets untaint requirements.',
1107  $command);
1108  $allow_untaint = 1;
1109  }
1110  }
1111  }
1112  else
1113  {
1114  foreach my $path (@allowed_paths)
1115  {
1116  if (-e "$path/$command" && -f "$path/$command" &&
1117  -x "$path/$command")
1118  {
1119  # Seems to be a valid executable in a "normal" directory for
1120  # binaries
1121  $command = "$path/$command";
1122  verbose($verbose_level_debug + 2,
1123  'Command seems to be a simple command in a'.
1124  ' normal directory for binaries: '.$command);
1125  $allow_untaint = 1;
1126  }
1127  }
1128  }
1129  if ($allow_untaint)
1130  {
1131  if ($command =~ /^(.*)$/)
1132  {
1133  verbose($verbose_level_debug + 1,
1134  'Untainting command: '.$command);
1135  $command = $1;
1136  $ENV{'PATH'} = '';
1137  $is_env_tainted = 0;
1138  }
1139  }
1140  return $command;
1141  }
1142 
1143 # This subroutine performs limited checking of file or directory paths and
1144 # untaints the path if it seems to be an absolute path to a normal file or
1145 # directory and contains no . or .. references. It should only be called after
1146 # careful consideration of the effects of doing so and of whether it makes
1147 # sense to override taint-mode runtime checking of the value.
1148  sub untaint_path
1149  {
1150  my $path = shift;
1151  verbose($verbose_level_debug + 2, '', 'Verifying path: '.$path);
1152  if ($path =~ /^\//)
1153  {
1154  verbose($verbose_level_debug + 2, ' - Path starts with /.');
1155  if (! ($path =~ /\/\.+\//))
1156  {
1157  verbose($verbose_level_debug + 2,
1158  ' - Path contains no dir refs.');
1159  if (-e "$path" && (-f "$path" || -d "$path"))
1160  {
1161  # Seems to be a file or directory path starting with / and
1162  # having no current/previous directory references
1163  if ($path =~ /^(.*)$/)
1164  {
1165  verbose($verbose_level_debug + 1,
1166  'Untainting path: '.$path);
1167  $path = $1;
1168  }
1169  }
1170  }
1171  }
1172  return $path;
1173  }
1174 
1175 # This subroutine does absolutely no data checking. It blindly accepts a
1176 # possibly-tainted value and "untaints" it. It should only be called after
1177 # careful consideration of the effects of doing so and of whether it makes
1178 # sense to override taint-mode runtime checking of the value.
1179  sub untaint_data
1180  {
1181  my $value = shift;
1182  if ($value =~ /^(.*)$/)
1183  {
1184  verbose($verbose_level_debug + 1, 'Untainting data: '.$value);
1185  $value = $1;
1186  }
1187  return $value;
1188  }
1189 
1190  sub reset_environment
1191  {
1192  if (!$is_env_tainted)
1193  {
1194  $is_env_tainted = 1;
1195  $ENV{'PATH'} = $old_env_path;
1196  }
1197  }
1198 
1199  sub do_backup
1200  {
1201  my $defaults_extra_file = create_defaults_extra_file;
1202  my $host_arg = '';
1203  my $port_arg = '';
1204  my $user_arg = '';
1205  if ($defaults_extra_file)
1206  {
1207  $defaults_arg = " --defaults-extra-file='$defaults_extra_file'";
1208  }
1209  else
1210  {
1211  $defaults_arg = '';
1212  }
1213  # For users running in environments where taint mode is activated (i.e.
1214  # running mythtv-setup or mythbackend as root), executing a command line
1215  # built with tainted data will fail. Therefore, try to untaint data if it
1216  # meets certain basic requirements.
1217  my $safe_mysqldump = $mysqldump;
1218  $safe_mysqldump = untaint_command($safe_mysqldump);
1219  $safe_mysqldump =~ s/'/'\\''/sg;
1220  $mysql_conf{'db_name'} = untaint_data($mysql_conf{'db_name'});
1221  $mysql_conf{'db_host'} = untaint_data($mysql_conf{'db_host'});
1222  $mysql_conf{'db_port'} = untaint_data($mysql_conf{'db_port'});
1223  $mysql_conf{'db_user'} = untaint_data($mysql_conf{'db_user'});
1224  $backup_conf{'directory'} = untaint_path($backup_conf{'directory'});
1225  # Can't use untaint_path because the filename is not a full path and the
1226  # file doesn't yet exist, anyway
1227  $backup_conf{'filename'} =~ s/'/'\\''/g;
1228  $backup_conf{'filename'} = untaint_data($backup_conf{'filename'});
1229  my $output_file = "$backup_conf{'directory'}/$backup_conf{'filename'}";
1230  $output_file =~ s/'/'\\''/sg;
1231  # Create the args for host, port, and user, shell-escaping values, as
1232  # necessary.
1233  my $safe_db_name = $mysql_conf{'db_name'};
1234  $safe_db_name =~ s/'/'\\''/g;
1235  my $safe_string;
1236  if ($mysql_conf{'db_host'})
1237  {
1238  $safe_string = $mysql_conf{'db_host'};
1239  $safe_string =~ s/'/'\\''/g;
1240  $host_arg = " --host='$safe_string'";
1241  }
1242  if ($mysql_conf{'db_port'} > 0)
1243  {
1244  $safe_string = $mysql_conf{'db_port'};
1245  $safe_string =~ s/'/'\\''/g;
1246  $port_arg = " --port='$safe_string'";
1247  }
1248  if ($mysql_conf{'db_user'})
1249  {
1250  $safe_string = $mysql_conf{'db_user'};
1251  $safe_string =~ s/'/'\\''/g;
1252  $user_arg = " --user='$safe_string'";
1253  }
1254 
1255  # Use redirects to capture stderr (for debug) and send stdout (the backup)
1256  # to a file
1257  my $command = "'${safe_mysqldump}'${defaults_arg}${host_arg}".
1258  "${port_arg}${user_arg} --add-drop-table --add-locks ".
1259  "--allow-keywords --complete-insert --extended-insert ".
1260  "--lock-tables --no-create-db --quick --add-drop-table ".
1261  "'$safe_db_name' 2>&1 1>'$output_file'";
1262  verbose($verbose_level_debug,
1263  '', 'Executing command:', $command);
1264  my $result = `$command`;
1265  my $exit = $? >> 8;
1266  verbose($verbose_level_debug,
1267  '', "$mysqldump exited with status: $exit");
1268  verbose($verbose_level_debug,
1269  "$mysqldump output:", $result) if ($exit);
1270  reset_environment;
1271  return $exit;
1272  }
1273 
1274  sub compress_backup
1275  {
1276  if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
1277  {
1278  verbose($verbose_level_debug,
1279  '', 'Unable to find backup file to compress');
1280  return 1;
1281  }
1282  my $result = 0;
1283  verbose($verbose_level_debug,
1284  '', 'Attempting to compress backup file.');
1285  if ($d_compress eq $compress)
1286  {
1287  # Try to load the IO::Compress::Gzip library if available (but don't
1288  # require it)
1289  BEGIN
1290  {
1291  our $has_compress_gzip = 1;
1292  # Though this does nothing, it prevents an invalid "only used
1293  # once" warning that occurs for users without IO::Compress
1294  # installed.
1295  undef $GzipError;
1296  eval 'use IO::Compress::Gzip qw(gzip $GzipError);';
1297  if ($@)
1298  {
1299  $has_compress_gzip = 0;
1300  }
1301  }
1302  if (!$has_compress_gzip)
1303  {
1304  verbose($verbose_level_debug,
1305  " - IO::Compress::Gzip is not installed.");
1306  }
1307  else
1308  {
1309  if (-e "$backup_conf{'directory'}/$backup_conf{'filename'}.gz")
1310  {
1311  verbose($verbose_level_debug,
1312  '', 'A file whose name is the backup filename'.
1313  ' with the \'.gz\' extension already',
1314  'exists. Leaving backup uncompressed.');
1315  return 1;
1316  }
1317  verbose($verbose_level_debug,
1318  " - Compressing backup file with IO::Compress::Gzip.");
1319  $result = gzip(
1320  "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
1321  "$backup_conf{'directory'}/$backup_conf{'filename'}.gz");
1322  if ((defined($result)) &&
1323  (-e "$backup_conf{'directory'}/".
1324  "$backup_conf{'filename'}.gz"))
1325  {
1326  # For users running in environments where taint mode is
1327  # activated (i.e. running mythtv-setup or mythbackend as
1328  # root), unlinking a file whose path is built with tainted data
1329  # will fail. Therefore, try to untaint the path if it meets
1330  # certain basic requirements.
1331  my $uncompressed_file = $backup_conf{'directory'}."/".
1332  $backup_conf{'filename'};
1333  $uncompressed_file = untaint_path($uncompressed_file);
1334  $uncompressed_file =~ s/'/'\\''/sg;
1335  verbose($verbose_level_debug + 2,
1336  "Unlinking uncompressed file: $uncompressed_file");
1337  unlink "$uncompressed_file";
1338  $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
1339  verbose($verbose_level_debug,
1340  '', 'Successfully compressed backup to file:',
1341  "$backup_conf{'directory'}/".
1342  "$backup_conf{'filename'}");
1343  return 0;
1344  }
1345  verbose($verbose_level_debug,
1346  " Error: $GzipError");
1347  }
1348  }
1349  # Try to compress the file with the compress binary.
1350  verbose($verbose_level_debug,
1351  " - Compressing backup file with $compress.");
1352  my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
1353  # For users running in environments where taint mode is activated (i.e.
1354  # running mythtv-setup or mythbackend as root), executing a command line
1355  # built with tainted data will fail. Therefore, try to untaint data if it
1356  # meets certain basic requirements.
1357  $compress = untaint_command($compress);
1358  $compress =~ s/'/'\\''/sg;
1359  $backup_path = untaint_path($backup_path);
1360  $backup_path =~ s/'/'\\''/sg;
1361  my $command = "'$compress' '$backup_path' 2>&1";
1362  verbose($verbose_level_debug,
1363  '', 'Executing command:', $command);
1364  my $output = `$command`;
1365  my $exit = $? >> 8;
1366  verbose($verbose_level_debug,
1367  '', "$compress exited with status: $exit");
1368  if ($exit)
1369  {
1370  verbose($verbose_level_debug,
1371  "$compress output:", $output);
1372  }
1373  else
1374  {
1375  $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
1376  }
1377  reset_environment;
1378  return $exit;
1379  }
1380 
1381  sub rotate_backups
1382  {
1383  if (($rotate < 1) || (!defined($rotateglob)) || (!$rotateglob))
1384  {
1385  verbose($verbose_level_debug,
1386  '', 'Backup file rotation disabled.');
1387  return 0;
1388  }
1389  verbose($verbose_level_debug,
1390  '', 'Rotating backups.');
1391  verbose($verbose_level_debug,
1392  '', 'Searching for files matching pattern:',
1393  "$backup_conf{'directory'}/$rotateglob");
1394  my @files = <$backup_conf{'directory'}/$rotateglob>;
1395  my @sorted_files = sort { lc($a) cmp lc($b) } @files;
1396  my $num_files = @sorted_files;
1397  verbose($verbose_level_debug,
1398  " - Found $num_files matching files.");
1399  $num_files = $num_files - $rotate;
1400  $num_files = 0 if ($num_files < 0);
1401  verbose($verbose_level_debug,
1402  '', "Deleting $num_files and keeping (up to) $rotate backup".
1403  ' files.');
1404  my $index = 0;
1405  foreach my $file (@sorted_files)
1406  {
1407  if ($index++ < $num_files)
1408  {
1409  if ($file eq
1410  "$backup_conf{'directory'}/$backup_conf{'filename'}")
1411  {
1412  # This is the just-created backup. Warn the user that older
1413  # backups with newer schema versions may cause rotation to
1414  # fail.
1415  verbose($verbose_level_debug,
1416  '', 'WARNING: You seem to have reverted to an'.
1417  ' older database schema version.',
1418  'You should move all backups from newer schema'.
1419  ' versions to another directory or',
1420  'delete them to prevent your new backups from'.
1421  ' being deleted on rotation.', '');
1422  verbose($verbose_level_debug,
1423  " - Keeping backup file: $file");
1424 
1425  }
1426  else
1427  {
1428  verbose($verbose_level_debug,
1429  " - Deleting old backup file: $file");
1430  # For users running in environments where taint mode is
1431  # activated (i.e. running mythtv-setup or mythbackend as
1432  # root), unlinking a file whose path is built with tainted data
1433  # will fail. Therefore, try to untaint the path if it meets
1434  # certain basic requirements.
1435  $file = untaint_path($file);
1436  $file =~ s/'/'\\''/sg;
1437  unlink "$file";
1438  }
1439  }
1440  else
1441  {
1442  verbose($verbose_level_debug,
1443  " - Keeping backup file: $file");
1444  }
1445  }
1446  return 1;
1447  }
1448 
1449 ##############################################################################
1450 # Main functionality
1451 ##############################################################################
1452 
1453 # The first argument after option parsing, if it exists, should be a database
1454 # information file.
1455  $database_information_file = shift;
1456 
1457  configure_environment;
1458  read_config;
1459  check_config;
1460 
1461  print_configuration;
1462 
1463  my $status = 1;
1464  if ($backup_xmltvids)
1465  {
1466  $status = do_xmltvid_backup;
1467  }
1468  else
1469  {
1470  $status = do_backup;
1471  if (!$status)
1472  {
1473  compress_backup;
1474  rotate_backups;
1475  }
1476  }
1477 
1478  $dbh->disconnect if (defined($dbh));
1479 
1480  exit $status;
1481