3 # mythconverg_backup.pl
5 # Creates a backup of the MythTV database.
8 # mythconverg_backup.pl --help
15 $NAME =
'MythTV Database Backup Script';
18 # Some variables we'll use here
21 our ($usage,
$debug, $show_version, $show_version_script,
$dbh);
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.
26 our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
27 our ($backup_directory, $backup_filename);
41 # Variables used to untaint data
65 # Provide default values for GetOptions
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,
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
94 # Print version information
95 sub print_version_information
98 print "$NAME\n$script_name\nversion: $VERSION\n";
101 if ($show_version_script)
103 print "$NAME,$VERSION,,\n";
108 print_version_information;
116 print_version_information;
120 $0 [options|database_information_file]
122 Creates a backup of the
MythTV database.
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.
130 # echo
"DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
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.
138 Ensure you have a ~/.mythtv/backuprc file, as described above, and execute
this
139 script with the --backup_xmltvids argument.
141 # $0 --backup_xmltvids
148 DETAILED DESCRIPTION:
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.
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
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).
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
182 If the database
name is not specified, the script will attempt to use the
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.
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.
206 DATABASE INFORMATION FILE
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
211 files. The following variables are recognized:
213 DBHostName - The hostname (or IP address) which should be used to
find the
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
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
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
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
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
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
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.
299 --hostname [database hostname]
301 The hostname (or IP address) which should be used to
find the MySQL server.
302 See DBHostName, above.
304 --port [database port]
306 The TCP/IP port number to use for connection to the MySQL server. See
309 --username [database username]
311 The MySQL username to use for connection to the MySQL server. See
314 --name [database name]
316 The name of the database containing the
MythTV data. See DBName, above.
324 --directory [directory]
326 The directory in which the backup file should be stored. See
327 DBBackupDirectory, above.
329 --filename [database backup filename]
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.
336 The path (including filename) of the mysqldump executable. See mysqldump
337 in the DATABASE INFORMATION FILE description, above.
343 The command (including path,
if necessary) to use to compress the backup.
344 See compress in the DATABASE INFORMATION FILE description, above.
349 The number of backups to keep when rotating. To disable rotation, specify
350 -1. See rotate in the DATABASE INFORMATION FILE description, above.
355 The sh-like glob used to identify files within DBBackupDirectory to be
356 considered for rotation. See rotateglob in the DATABASE INFORMATION FILE
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
380 Show what is happening.
382 --script_version | -v
384 Show script
version information. This is primarily useful for scripts
391 print "For detailed help:\n\n# $0 --help --help\n\n";
408 print { $error ? STDERR : STDOUT } join(
"\n", @
_),
"\n";
415 'Database Information:',
416 " DBHostName: $mysql_conf{'db_host'}",
417 " DBPort: $mysql_conf{'db_port'}",
418 " DBUserName: $mysql_conf{'db_user'}",
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'}");
429 " mysqldump: $mysqldump",
430 " compress: $compress");
436 '',
'Configuring environment:');
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)
444 $homedir =
"/home/$username";
445 if (!-e $homedir && -e
"/Users/$username")
447 $homedir =
"/Users/$username";
451 " - username: $username",
452 " - HOME: $homedir");
454 # Find the config directory
455 $mythconfdir = $ENV{
'MYTHCONFDIR'}
456 ? $ENV{
'MYTHCONFDIR'}
461 " - MYTHCONFDIR: $mythconfdir");
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
473 " - checking: $file");
474 return 0
unless ($file && -e $file);
477 open(CONF, $file) or
die("\nERROR: Unable to read $file: $!".
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)
492 $var = $1; $val = $2;
495 if (($var eq
'Host') || ($var eq
'DBHostName'))
497 $mysql_conf{
'db_host'} = $val;
499 elsif (($var eq
'Port') || ($var eq
'DBPort'))
501 $mysql_conf{
'db_port'} = $val;
503 elsif (($var eq
'UserName') || ($var eq
'DBUserName'))
505 $mysql_conf{
'db_user'} = $val;
507 elsif (($var eq
'Password') || ($var eq
'DBPassword'))
509 $mysql_conf{
'db_pass'} = $val;
510 $mysql_conf{
'db_pass'} =~ s/&/&/sg;
511 $mysql_conf{
'db_pass'} =~ s/>/>/sg;
512 $mysql_conf{
'db_pass'} =~ s/</</sg;
514 elsif (($var eq
'DatabaseName') || ($var eq
'DBName'))
516 $mysql_conf{
'db_name'} = $val;
518 elsif ($var eq
'DBSchemaVer')
520 $mysql_conf{
'db_schemaver'} = $val;
522 elsif ($var eq
'DBBackupDirectory')
524 $backup_conf{
'directory'} = $val;
526 elsif ($var eq
'DBBackupFilename')
528 $backup_conf{
'filename'} = $val;
530 elsif ($var eq
'mysqldump')
534 elsif ($var eq
'compress')
538 elsif ($var eq
'rotate')
542 elsif ($var eq
'rotateglob')
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
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" :
'',
564 $mythconfdir ?
"$mythconfdir/mysql.txt" :
'',
568 $found = parse_database_information(
$file);
569 $result = $result + $found;
574 sub read_resource_file
576 parse_database_information(
"$mythconfdir/backuprc");
582 '',
'Applying command-line arguments.');
585 $mysql_conf{
'db_host'} = $db_hostname;
589 $mysql_conf{
'db_port'} = $db_port;
593 $mysql_conf{
'db_user'} = $db_username;
595 # This script does not accept a database password on the command-line.
598 # $mysql_conf{'db_pass'} = $db_password;
602 $mysql_conf{
'db_name'} = $db_name;
604 if ($db_schema_version)
606 $mysql_conf{
'db_schemaver'} = $db_schema_version;
608 if ($backup_directory)
610 $backup_conf{
'directory'} = $backup_directory;
612 if ($backup_filename)
614 $backup_conf{
'filename'} = $backup_filename;
621 # If specified, use only the database information file
625 '',
'Database Information File specified. Ignoring all'.
626 ' command-line arguments');
628 '',
'Database Information File: '.
630 unless (-T
"$database_information_file")
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");
640 # When using a database information file, parse the resource file first
641 # so it cannot override database information file settings
647 # No database information file, so try the MythTV configuration files.
649 '',
'Parsing configuration files:');
650 # Prefer the config.xml file
651 my $file = $mythconfdir ?
"$mythconfdir/config.xml" :
'';
652 $result = parse_database_information($file);
655 # Use the "legacy" mysql.txt file as a fallback
656 $result = read_mysql_txt;
658 # Read the resource file next to override the config file information, but
659 # to allow command-line arguments to override resource file "defaults"
661 # Apply the command-line arguments to override the information provided
662 # by the config file(s).
667 sub check_database_libs
669 # Try to load the DBI library if available (but don't require it)
680 '',
'DBI is not installed.')
if (!$has_dbi);
681 # Try to load the DBD::mysql library if available (but don't
686 eval
'use DBD::mysql;';
693 '',
'DBD::mysql is not installed.')
if (!$has_dbd);
694 return ($has_dbi + $has_dbd);
701 my $have_database_libs = check_database_libs;
702 return 0
if ($have_database_libs < 2);
703 $dbh =
DBI->connect(
"dbi:mysql:".
704 "database=$mysql_conf{'db_name'}:".
705 "host=$mysql_conf{'db_host'}",
706 "$mysql_conf{'db_user'}",
707 "$mysql_conf{'db_pass'}",
708 { PrintError => 0 });
713 sub create_backup_filename
715 # Create a default backup filename
716 $backup_conf{
'filename'} = $mysql_conf{
'db_name'};
717 if (!$backup_conf{
'filename'})
721 if ((!$mysql_conf{
'db_schemaver'}) &&
722 ($mysql_conf{
'db_host'}) && ($mysql_conf{
'db_name'}) &&
723 ($mysql_conf{
'db_user'}) && ($mysql_conf{
'db_pass'}))
725 # If DBI is available, query the DB for the schema version
729 '',
'No DBSchemaVer specified, querying database.');
730 my $query =
'SELECT data FROM settings WHERE value = ?';
733 my $sth =
$dbh->prepare($query);
734 if ($sth->execute(
'DBSchemaVer'))
736 while (
my @
data = $sth->fetchrow_array)
738 $mysql_conf{
'db_schemaver'} =
$data[0];
740 "Found DBSchemaVer:".
741 " $mysql_conf{'db_schemaver'}.");
747 "Unable to retrieve DBSchemaVer from".
748 " database. Filename will not contain ",
756 '',
'No DBSchemaVer specified.',
757 'DBI and/or DBD:mysql is not available. Unable'.
758 ' to query database to determine ',
759 'DBSchemaVer. DBSchemaVer will not be included'.
760 ' in backup filename.',
761 'Please ensure DBI and DBD::mysql are'.
765 if ($mysql_conf{
'db_schemaver'})
767 $backup_conf{
'filename'} .=
'-'.$mysql_conf{
'db_schemaver'};
769 # Format the time using localtime data so we don't have to bring in
770 # another dependency.
772 $backup_conf{
'filename'} .= sprintf(
'-%04d%02d%02d%02d%02d%02d.sql',
773 ($timeData[5] + 1900),
775 $timeData[3], $timeData[2],
776 $timeData[1], $timeData[0]);
779 sub check_backup_directory
782 if ($backup_conf{
'directory'})
786 elsif (check_database)
787 # If DBI is available, query the DB for the backup directory
790 '',
'No DBBackupDirectory specified, querying database.');
791 my $query =
'SELECT dirname, hostname FROM storagegroup '.
792 ' WHERE groupname = ?';
796 my $sth =
$dbh->prepare($query);
797 if ($sth->execute(
'DB Backups'))
799 # We don't know the hostname associated with this host, and
800 # since it's not worth parsing the mysql.txt/config.xml
801 # LocalHostName (unique identifier), with fallback to the
802 # system hostname, and handling issues along the way, just look
803 # for any available DB Backups directory and, if none are
804 # usable, look for a Default group directory
805 while (
my @
data = $sth->fetchrow_array)
807 $directory =
$data[0];
808 if (-
d $directory && -
w $directory)
810 $backup_conf{
'directory'} = $directory;
812 "Found DB Backups directory:".
813 " $backup_conf{'directory'}.");
820 if ($result == 0 && $sth->execute(
'Default'))
822 while (
my @
data = $sth->fetchrow_array)
824 $directory =
$data[0];
825 if (-
d $directory && -
w $directory)
827 $backup_conf{
'directory'} = $directory;
829 "Found Default directory:".
830 " $backup_conf{'directory'}.");
841 "Unable to retrieve DBBackupDirectory from".
851 '',
'Checking configuration.');
852 # Check directory/filename
853 if (!check_backup_directory)
856 die(
"\nERROR: DBBackupDirectory not specified, stopped");
858 if ((!-
d $backup_conf{
'directory'}) ||
859 (!-
w $backup_conf{
'directory'}))
863 '',
'ERROR: DBBackupDirectory is not a directory or is '.
864 'not writable. Please specify',
865 ' a directory in your database information file'.
866 ' using DBBackupDirectory.',
867 ' If not using a database information file,'.
868 ' please specify the ',
869 ' --directory command-line option.');
870 die(
"\nInvalid backup directory, stopped");
872 if (!$backup_conf{
'filename'})
874 if ($backup_xmltvids)
876 my $file =
'mythtv_xmltvid_backup';
877 # Format the time using localtime data so we don't have to bring in
878 # another dependency.
880 $file .= sprintf(
'-%04d%02d%02d%02d%02d%02d.sql',
881 ($timeData[5] + 1900),
883 $timeData[3], $timeData[2],
884 $timeData[1], $timeData[0]);
885 $backup_conf{
'filename'} =
$file;
889 create_backup_filename;
892 if ( -e
"$backup_conf{'directory'}/$backup_conf{'filename'}")
895 '',
'ERROR: The specified file already exists.');
896 die(
"\nInvalid backup filename, stopped");
898 if (!$mysql_conf{
'db_name'})
901 '',
"WARNING: DBName not specified. Using $d_db_name");
904 # Though the script will attempt a backup even if no other database
905 # information is provided (i.e. using "defaults" from the MySQL options
906 # file, warning the user that some "normally-necessary" information is not
907 # provided may be nice.
909 if (!$mysql_conf{
'db_host'})
912 '',
'WARNING: DBHostName not specified.',
913 ' Assuming it is specified in the MySQL'.
916 if (!$mysql_conf{
'db_user'})
919 '',
'WARNING: DBUserName not specified.',
920 ' Assuming it is specified in the MySQL'.
923 if (!$mysql_conf{
'db_pass'})
926 '',
'WARNING: DBPassword not specified.',
927 ' Assuming it is specified in the MySQL'.
932 sub create_defaults_extra_file
934 return '' if (!$mysql_conf{
'db_pass'});
936 '',
"Attempting to use supplied password for $mysqldump.",
937 'Any [client] or [mysqldump] password specified in the MySQL'.
938 ' options file will',
940 # Let tempfile handle unlinking on exit so we don't have to verify that the
941 # file with $filename is the file we created
943 # Quote the password if it contains # or whitespace or quotes.
944 # Quoting of values in MySQL options files is only supported on MySQL
945 # 4.0.16 and above, so only quote if required.
947 my $safe_password = $mysql_conf{
'db_pass'};
948 if ($safe_password =~ /[#
'"\s]/)
951 $safe_password =~ s/'/\\'/g;
953 print $fh "[client]\
npassword=${quote}${safe_password}${quote}\n
".
954 "[mysqldump]\npassword=${quote}${safe_password}${quote}\n
";
958 sub do_xmltvid_backup
963 my ($chanid, $channum, $callsign, $name, $xmltvid);
964 my $query = " SELECT chanid, channum, callsign,
name, xmltvid
".
966 "ORDER BY CAST(channum AS SIGNED),
".
967 " CAST(SUBSTRING(channum
".
969 " LOCATE(
'_', channum) +
".
970 " LOCATE(
'-', channum) +
".
971 " LOCATE(
'#', channum) +
".
972 " LOCATE(
'.', channum)))
".
974 my $sth = $dbh->prepare($query);
975 verbose($verbose_level_debug,
976 '', 'Querying database for xmltvid information.');
977 my $file = "$backup_conf{
'directory'}/$backup_conf{
'filename'}
";
978 open BACKUP, '>', $file or die("\nERROR: Unable to
open".
980 for ($section = 0; $section < 2; $section++)
984 while (my @data = $sth->fetchrow_array)
988 $callsign = $data[2];
991 verbose($verbose_level_debug,
992 "Found channel: $chanid, $channum, $callsign,
".
993 " $name, $xmltvid.
") if ($section == 0);
994 if ($xmltvid && $callsign)
998 print BACKUP "-- Start
Channel Data\n
".
999 "-- ID:
'$chanid'\n
".
1000 "-- Number:
'$channum'\n
".
1002 "--
Name:
'$name'\n
".
1003 "-- XMLTVID:
'$xmltvid'\n
".
1005 print BACKUP "UPDATE channel
".
1006 " SET xmltvid =
'$xmltvid'".
1007 " WHERE callsign =
'$callsign'".
1012 print BACKUP "UPDATE channel
".
1013 " SET xmltvid =
'$xmltvid'".
1014 " WHERE channum =
'$channum'".
1015 " AND name =
'$name';\n
";
1021 verbose($verbose_level_debug,
1022 '', 'Successfully backed up xmltvid'.
1024 '', '', 'Creating alternate format backup.');
1025 print BACKUP "\n\n\n
".
1031 verbose($verbose_level_debug,
1032 'Successfully created alternate format'.
1039 verbose($verbose_level_error,
1040 '', 'ERROR: Unable to retrieve xmltvid information'.
1042 die("\nError retrieving xmltvid information,
stopped");
1049 verbose($verbose_level_error,
1050 '', 'ERROR: Unable to backup xmltvids without Perl'.
1051 ' database libraries.',
1052 ' Please ensure the Perl DBI and DBD::mysql'.
1053 ' modules are installed.');
1054 die("\nPerl database libraries missing,
stopped");
1059 # This subroutine performs limited checking of a command and untaints the
1060 # command (and the environment) if the command seems to use an absolute path
1061 # containing no . or .. references or if it's a simple command name referencing
1062 # an executable in a "normal
" directory for binaries. It should only be called
1063 # after careful consideration of the effects of doing so and of whether it
1064 # makes sense to override taint-mode runtime checking of the value.
1067 my $command = shift;
1068 my $allow_untaint = 0;
1069 # Only allow directories from @d_allowed_paths that exist in the PATH
1070 if (!defined(@allowed_paths))
1072 foreach my $path (split(/:/, $old_env_path))
1074 if (grep(/^$path$/, @d_allowed_paths))
1076 push(@allowed_paths, $path);
1079 verbose($verbose_level_debug + 2,
1080 '', 'Allowing paths:', @allowed_paths,
1081 'From PATH: '.$old_env_path);
1084 verbose($verbose_level_debug + 2, '', 'Verifying command: '.$command);
1085 if ($command =~ /^\//)
1087 verbose($verbose_level_debug + 2, ' - Command starts with /.');
1088 if (! ($command =~ /\/\.+\//))
1090 verbose($verbose_level_debug + 2,
1091 ' - Command does not contain dir refs.');
1094 # Seems to be a valid executable specified with a path starting
1095 # with / and having no current/previous directory references
1096 verbose($verbose_level_debug + 2,
1097 'Unmodified command meets untaint requirements.',
1105 foreach my $path (@allowed_paths)
1110 # Seems to be a valid executable in a "normal
" directory for
1113 verbose($verbose_level_debug + 2,
1114 'Command seems to be a simple command in a'.
1115 ' normal directory for binaries: '.$command);
1122 if ($command =~ /^(.*)$/)
1124 verbose($verbose_level_debug + 1,
1125 'Untainting command: '.$command);
1128 $is_env_tainted = 0;
1134 # This subroutine performs limited checking of file or directory paths and
1135 # untaints the path if it seems to be an absolute path to a normal file or
1136 # directory and contains no . or .. references. It should only be called after
1137 # careful consideration of the effects of doing so and of whether it makes
1138 # sense to override taint-mode runtime checking of the value.
1142 verbose($verbose_level_debug + 2, '', 'Verifying path: '.$path);
1145 verbose($verbose_level_debug + 2, ' - Path starts with /.');
1146 if (! ($path =~ /\/\.+\//))
1148 verbose($verbose_level_debug + 2,
1149 ' - Path contains no dir refs.');
1152 # Seems to be a file or directory path starting with / and
1153 # having no current/previous directory references
1154 if ($path =~ /^(.*)$/)
1156 verbose($verbose_level_debug + 1,
1157 'Untainting path: '.$path);
1166 # This subroutine does absolutely no data checking. It blindly accepts a
1167 # possibly-tainted value and "untaints
" it. It should only be called after
1168 # careful consideration of the effects of doing so and of whether it makes
1169 # sense to override taint-mode runtime checking of the value.
1173 if ($value =~ /^(.*)$/)
1175 verbose($verbose_level_debug + 1, 'Untainting data: '.$value);
1181 sub reset_environment
1183 if (!$is_env_tainted)
1185 $is_env_tainted = 1;
1186 $ENV{'PATH'} = $old_env_path;
1192 my $defaults_extra_file = create_defaults_extra_file;
1196 if ($defaults_extra_file)
1198 $defaults_arg = " --defaults-extra-file=
'$defaults_extra_file'";
1204 # For users running in environments where taint mode is activated (i.e.
1205 # running mythtv-setup or mythbackend as root), executing a command line
1206 # built with tainted data will fail. Therefore, try to untaint data if it
1207 # meets certain basic requirements.
1208 my $safe_mysqldump = $mysqldump;
1209 $safe_mysqldump = untaint_command($safe_mysqldump);
1210 $safe_mysqldump =~ s/'/'\\''/sg;
1211 $mysql_conf{'db_name'} = untaint_data($mysql_conf{'db_name'});
1212 $mysql_conf{'db_host'} = untaint_data($mysql_conf{'db_host'});
1213 $mysql_conf{'db_port'} = untaint_data($mysql_conf{'db_port'});
1214 $mysql_conf{'db_user'} = untaint_data($mysql_conf{'db_user'});
1215 $backup_conf{'directory'} = untaint_path($backup_conf{'directory'});
1216 # Can't use untaint_path because the filename is not a full path and the
1217 # file doesn't yet exist, anyway
1218 $backup_conf{'filename'} =~ s/'/'\\''/g;
1219 $backup_conf{'filename'} = untaint_data($backup_conf{'filename'});
1220 my $output_file = "$backup_conf{
'directory'}/$backup_conf{
'filename'}
";
1221 $output_file =~ s/'/'\\''/sg;
1222 # Create the args for host, port, and user, shell-escaping values, as
1224 my $safe_db_name = $mysql_conf{'db_name'};
1225 $safe_db_name =~ s/'/'\\''/g;
1227 if ($mysql_conf{'db_host'})
1229 $safe_string = $mysql_conf{'db_host'};
1230 $safe_string =~ s/'/'\\''/g;
1231 $host_arg = " --host=
'$safe_string'";
1233 if ($mysql_conf{'db_port'} > 0)
1235 $safe_string = $mysql_conf{'db_port'};
1236 $safe_string =~ s/'/'\\''/g;
1237 $port_arg = " --port=
'$safe_string'";
1239 if ($mysql_conf{'db_user'})
1241 $safe_string = $mysql_conf{'db_user'};
1242 $safe_string =~ s/'/'\\''/g;
1243 $user_arg = " --user=
'$safe_string'";
1246 # Use redirects to capture stderr (for debug) and send stdout (the backup)
1248 my $command = "'${safe_mysqldump}'${defaults_arg}${host_arg}
".
1249 "${port_arg}${user_arg} --
add-drop-table --
add-locks
".
1250 "--allow-keywords --complete-insert --extended-insert
".
1251 "--lock-tables --no-create-db --quick --
add-drop-table
".
1252 "'$safe_db_name' 2>&1 1>
'$output_file'";
1253 verbose($verbose_level_debug,
1254 '', 'Executing command:', $command);
1255 my $result = `$command`;
1257 verbose($verbose_level_debug,
1259 verbose($verbose_level_debug,
1267 if (!-e "$backup_conf{
'directory'}/$backup_conf{
'filename'}
")
1269 verbose($verbose_level_debug,
1270 '', 'Unable to find backup file to compress');
1274 verbose($verbose_level_debug,
1275 '', 'Attempting to compress backup file.');
1276 if ($d_compress eq $compress)
1278 # Try to load the IO::Compress::Gzip library if available (but don't
1282 our $has_compress_gzip = 1;
1283 # Though this does nothing, it prevents an invalid "only used
1284 # once" warning that occurs for users without IO::Compress
1287 eval
'use IO::Compress::Gzip qw(gzip $GzipError);';
1290 $has_compress_gzip = 0;
1293 if (!$has_compress_gzip)
1296 " - IO::Compress::Gzip is not installed.");
1300 if (-e
"$backup_conf{'directory'}/$backup_conf{'filename'}.gz")
1303 '',
'A file whose name is the backup filename'.
1304 ' with the \'.gz\' extension already',
1305 'exists. Leaving backup uncompressed.');
1309 " - Compressing backup file with IO::Compress::Gzip.");
1311 "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
1312 "$backup_conf{'directory'}/$backup_conf{'filename'}.gz");
1313 if ((defined($result)) &&
1314 (-e
"$backup_conf{'directory'}/".
1315 "$backup_conf{'filename'}.gz"))
1317 # For users running in environments where taint mode is
1318 # activated (i.e. running mythtv-setup or mythbackend as
1319 # root), unlinking a file whose path is built with tainted data
1320 # will fail. Therefore, try to untaint the path if it meets
1321 # certain basic requirements.
1322 my $uncompressed_file = $backup_conf{
'directory'}.
"/".
1323 $backup_conf{
'filename'};
1324 $uncompressed_file = untaint_path($uncompressed_file);
1325 $uncompressed_file =~ s/
'/'\\
''/sg;
1327 "Unlinking uncompressed file: $uncompressed_file");
1328 unlink
"$uncompressed_file";
1329 $backup_conf{
'filename'} =
"$backup_conf{'filename'}.gz";
1331 '',
'Successfully compressed backup to file:',
1332 "$backup_conf{'directory'}/".
1333 "$backup_conf{'filename'}");
1337 " Error: $GzipError");
1340 # Try to compress the file with the compress binary.
1342 " - Compressing backup file with $compress.");
1343 my $backup_path =
"$backup_conf{'directory'}/$backup_conf{'filename'}";
1344 # For users running in environments where taint mode is activated (i.e.
1345 # running mythtv-setup or mythbackend as root), executing a command line
1346 # built with tainted data will fail. Therefore, try to untaint data if it
1347 # meets certain basic requirements.
1350 $backup_path = untaint_path($backup_path);
1351 $backup_path =~ s/
'/'\\
''/sg;
1352 my $command =
"'$compress' '$backup_path' 2>&1";
1354 '',
'Executing command:', $command);
1355 my $output = `$command`;
1358 '',
"$compress exited with status: $exit");
1362 "$compress output:", $output);
1366 $backup_conf{
'filename'} =
"$backup_conf{'filename'}.gz";
1377 '',
'Backup file rotation disabled.');
1381 '',
'Rotating backups.');
1383 '',
'Searching for files matching pattern:',
1384 "$backup_conf{'directory'}/$rotateglob");
1386 my @sorted_files = sort { lc($a) cmp lc($b) } @files;
1387 my $num_files = @sorted_files;
1389 " - Found $num_files matching files.");
1390 $num_files = $num_files -
$rotate;
1391 $num_files = 0
if ($num_files < 0);
1393 '', "Deleting $num_files and keeping (up to) $rotate backup".
1398 if ($index++ < $num_files)
1401 "$backup_conf{'directory'}/$backup_conf{'filename'}")
1403 # This is the just-created backup. Warn the user that older
1404 # backups with newer schema versions may cause rotation to
1407 '',
'WARNING: You seem to have reverted to an'.
1408 ' older database schema version.',
1409 'You should move all backups from newer schema'.
1410 ' versions to another directory or',
1411 'delete them to prevent your new backups from'.
1412 ' being deleted on rotation.',
'');
1414 " - Keeping backup file: $file");
1420 " - Deleting old backup file: $file");
1421 # For users running in environments where taint mode is
1422 # activated (i.e. running mythtv-setup or mythbackend as
1423 # root), unlinking a file whose path is built with tainted data
1424 # will fail. Therefore, try to untaint the path if it meets
1425 # certain basic requirements.
1426 $file = untaint_path($file);
1427 $file =~ s/
'/'\\
''/sg;
1434 " - Keeping backup file: $file");
1440 ##############################################################################
1441 # Main functionality
1442 ##############################################################################
1444 # The first argument after option parsing, if it exists, should be a database
1455 if ($backup_xmltvids)
1457 $status = do_xmltvid_backup;
1461 $status = do_backup;
1469 $dbh->disconnect
if (defined(
$dbh));