3 # mythconverg_restore.pl
5 # Restores a backup of the MythTV database.
8 # mythconverg_restore.pl --help
12 use File::Temp qw/ tempfile /;
15 $NAME = 'MythTV Database Restore Script';
18 # Some variables we'll use here
19 our ($username, $homedir, $mythconfdir, $database_information_file);
20 our ($partial_restore, $with_plugin_data, $restore_xmltvids);
21 our ($mysql_client, $uncompress, $drop_database, $create_database);
22 our ($change_hostname, $old_hostname, $new_hostname);
23 our ($usage, $debug, $show_version, $show_version_script, $dbh);
24 our ($d_mysql_client, $d_db_name, $d_uncompress);
25 our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
26 # This script does not accept a database password on the command-line.
27 # Any packager who enables the functionality should modify the --help output.
29 our ($backup_directory, $backup_filename);
30 our ($verbose_level_always, $verbose_level_debug, $verbose_level_error);
32 our %mysql_conf = ('db_host' => '',
39 our %backup_conf = ('directory' => '',
44 $verbose_level_always = 0;
45 $verbose_level_debug = 1;
46 $verbose_level_error = 255;
49 $d_db_name = 'mythconverg';
50 $d_mysql_client = 'mysql';
51 $d_uncompress = 'gzip -d';
53 # Provide default values for GetOptions
54 $mysql_client = $d_mysql_client;
55 $uncompress = $d_uncompress;
60 # Load the cli options
61 GetOptions('hostname|DBHostName=s' => \$db_hostname,
62 'port|DBPort=i' => \$db_port,
63 'username|DBUserName=s' => \$db_username,
64 # This script does not accept a database password on the command-line.
65 # 'password|DBPassword=s' => \$db_password,
66 'name|DBName=s' => \$db_name,
67 'schemaver|DBSchemaVer=s' => \$db_schema_version,
68 'directory|DBBackupDirectory=s' => \$backup_directory,
69 'filename|DBBackupFilename=s' => \$backup_filename,
70 'partial_restore|new_hardware|'.
71 'partial-restore|new-hardware' => \$partial_restore,
72 'with_plugin_data|plugin_data|'.
73 'with-plugin-data|plugin-data' => \$with_plugin_data,
75 'restore-xmltvids|xmltvids' => \$restore_xmltvids,
76 'mysql_client|mysql-client|client=s' => \$mysql_client,
77 'uncompress=s' => \$uncompress,
78 'drop_database|drop_db|'.
79 'drop-database|drop-db' => \$drop_database,
80 'create_database|create_db|mc_sql|'.
81 'create-database|create-db|mc-sql' => \$create_database,
82 'change_hostname|change-hostname' => \$change_hostname,
83 'new_hostname|new-hostname=s' => \$new_hostname,
84 'old_hostname|old-hostname=s' => \$old_hostname,
85 'usage|help|h+' => \$usage,
86 'version' => \$show_version,
87 'script_version|script-version|v' => \$show_version_script,
88 'verbose|debug|d+' => \$debug
91 $partial_restore ||= $restore_xmltvids;
93 # Print version information
94 sub print_version_information
96 my $script_name = substr $0, rindex($0, '/') + 1;
97 print "$NAME\n$script_name\nversion: $VERSION\n";
100 if ($show_version_script)
102 print "$NAME,$VERSION,,\n";
105 elsif ($show_version)
107 print_version_information;
114 print_version_information;
118 $0 [options|database_information_file]
120 Restores a backup of the MythTV database.
124 Create a file ~/.mythtv/backuprc with a single line,
125 "DBBackupDirectory=/home/mythtv" (no quotes). For example:
127 # echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
129 To do a full restore:
130 Ensure you have an empty database. If you are replacing an existing database,
131 you must first drop the old database. You may do this using the mysql client
132 executable by issuing the command:
134 # mysql -umythtv -p mythconverg -e "DROP DATABASE IF EXISTS mythconverg;"
136 (fix the database name and username, as required). Then, execute the mc.sql
137 script as described in the MythTV HOWTO ( http://www.mythtv.org/docs/ ) to
138 prepare a new (empty) database. Alternatively, you may specify the
139 --drop_database and/or --create_database arguments to automatically drop and/or
140 create the database for you (see the command-line argument descriptions in the
141 detailed help for more information).
143 Then, run this script to restore the most-recent backup in the directory
144 specified in ~/.mythtv/backuprc . Use the --verbose argument to see what is
149 or specify a backup file with:
151 # $0 --directory=/path/to/backups/ --filename=backup_file.sql.gz --verbose
153 (You may leave out the --directory argument if you've specified the directory
154 in the ~/.mythtv/backuprc .)
156 Once the restore completes successfully, you may start mythtv-setup or
157 mythbackend. If you restored a backup from an older version of MythTV,
158 mythtv-setup will upgrade the database for you.
160 To change the hostname of a MythTV frontend or backend:
162 Ensure that the database exists (restore an old database, as above, if
163 necessary) and execute the following command, replacing "XXXX" and "YYYY"
164 with appropriate values for the old and new hostnames, respectively:
166 # $0 --change_hostname --old_hostname="XXXX" --new_hostname="YYYY"
170 Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
171 script with the --restore_xmltvids argument.
173 # $0 --restore_xmltvids
180 DETAILED DESCRIPTION:
182 This script is used to restore a backup of the MythTV database (as created by
183 the mythconverg_backup.pl script). It can be called with a single command-line
184 argument specifying the name of a "database information file" (see DATABASE
185 INFORMATION FILE, below), which contains sufficient information about the
186 database and the backup to allow the script to restore a backup without needing
187 any additional configuration files. In this mode, all other MythTV
188 configuration files (including config.xml, mysql.txt) are ignored, but the
189 backup resource file (see RESOURCE FILE, below) and the MySQL option files
190 (i.e. /etc/my.cnf or ~/.my.cnf) will be honored.
192 The script can also be called using command-line arguments to specify the
193 required information. If no database information file is specified, the script
194 will attempt to determine the appropriate configuration by using the MythTV
195 configuration file(s) (preferring config.xml, but falling back to mysql.txt if
196 no config.xml exists). Once the MythTV configuration file has been parsed, the
197 backup resource file (see RESOURCE FILE, below) will be parsed, then
198 command-line arguments will be applied (thus overriding any values determined
199 from the configuration files).
201 The only information required by the script is the directory in which the
202 backup exists (the script will attempt to find the most current backup file,
203 based on the filename). Therefore, when using a database information file, the
204 DBBackupDirectory should be specified, or if running manually, the --directory
205 command-line argument should be specified. The DBBackupDirectory may be
206 specified in a backup resource file (see RESOURCE FILE, below). If the
207 specified directory is not readable, the script will terminate. Likewise, if
208 the backup file cannot be read, the script will terminate.
210 If the database name is not specified, the script will attempt to use the
211 MythTV default database name, $d_db_name. Note that the same is not true for
212 the database username and database password. These must be explicitly
213 specified. The password must be specified in a database information file, a
214 backup resource file, or a MySQL options file. The username may be specified
215 the same way or may be specified using a command-line argument if not using a
216 database information file.
218 If attempting to perform a full restore, the database must be empty (no
219 tables). To automatically drop any existing database and create an empty
220 database, specify the --drop_database and the --create_database arguments.
222 If you have a corrupt database, you may be able to recover some information
223 using a partial restore. To do a partial restore, you must have a
224 fully-populated database schema (but without the data you wish to import) from
225 the version of MythTV used to create the backup. You may create and populate
226 the database by running the mc.sql script (see the description of the
227 --create_database argument) to create the database. Then, start and exit
228 mythtv-setup to populate the database. And, finally, do the partial restore
231 # $0 --partial_restore
233 Include the --with_plugin_data argument if you would like to keep the data used
234 by MythTV plugins. Note that this approach cannot be used to "merge"
235 databases from different MythTV databases nor to import recordings from other
238 If you would like to do a partial/new-hardware restore and have upgraded
239 MythTV, you must first do a full restore, then start and exit mythtv-setup (to
240 upgrade the database), then create a backup, then drop the database, then
241 follow the instructions for doing a partial restore with the new (upgraded)
244 DATABASE INFORMATION FILE
246 The database information file contains information about the database and the
247 backup. The information within the file is specified as name=value pairs using
248 the same names as used by the MythTV config.xml and mysql.txt configuration
249 files. The following variables are recognized:
251 DBHostName - The hostname (or IP address) which should be used to find the
253 DBPort - The TCP/IP port number to use for the connection. This may have a
254 value of 0, i.e. if the hostname is localhost or if the server is
255 using the default MySQL port or the port specified in a MySQL
257 DBUserName - The database username to use when connecting to the server.
258 DBPassword - The password to use when connecting to the server.
259 DBName - The name of the database that contains the MythTV data.
260 DBSchemaVer - The MythTV schema version of the database. This value will be
261 used to create the backup filename, but only if the filename
262 has not been specified using DBBackupFilename or the --filename
264 DBBackupDirectory - The directory in which the backup file should be
265 created. This directory may have been specially
266 configured by the user as the "DB Backups" storage
267 group. It is recommended that this directory be
268 used--especially in "common-use" scripts such as those
269 provided by distributions.
270 DBBackupFilename - The name of the file in which the backup should be
271 created. Additional extensions may be added by this
272 script as required (i.e. adding an appropriate suffix,
273 such as ".gz", to the file if it is compressed). If the
274 filename recommended by mythbackend is used, it will be
275 displayed in the GUI messages provided for the user. If
276 the recommended filename is not used, the user will not be
277 told where to find the backup file. If no value is
278 provided, a filename using the default filename format
280 partial_restore - Do a partial restore (as would be required when setting
281 up MythTV on new hardware) of only the MythTV recordings
283 with_plugin_data - When doing a partial restore, include plugin data.
284 Ignored, unless the --partial_restore argument is given.i
285 Note that you will still need to configure all plugins
286 after the restore completes.
287 mysql_client - The path (including filename) of the mysql client
289 uncompress - The command (including path, if necessary) to use to
290 uncompress the backup. If you specify an uncompress
291 program, the backup file will be assumed to be compressed,
292 so the command will be run on the file regardless.
293 If no value is specified for uncompress or if the value
294 '$d_uncompress' or 'gunzip' is specified, the script will
295 check to see if the file is actually a gzip-compressed
296 file, and if so, will first attempt to use the
297 IO::Uncompress::Gunzip module to uncompress the backup
298 file, but, if not available, will run the command
299 specified. Therefore, if IO::Uncompress::Gunzip is
300 installed and functional, specifying a value for
301 uncompress is unnecessary.
305 The backup resource file specifies values using the same format as described
306 for the database information file, above, but is intended as a "permanent,"
307 user-created configuration file. The database information file is intended as a
308 "single-use" configuration file, often created automatically (i.e. by a
309 program, such as a script). The backup resource file should be placed at
310 "~/.mythtv/backuprc" and given appropriate permissions. To be usable by the
311 script, it must be readable. However, it should be protected as required--i.e.
312 if the DBPassword is specified, it should be made readable only by the owner.
314 When specifying a database information file, the resource file is parsed before
315 the database information file to prevent the resource file from overriding the
316 information in the database information file. When no database information
317 file is specified, the resource file is parsed after the MythTV configuration
318 files, but before the command-line arguments to allow the resource file to
319 override values in the configuration files and to allow command-line arguments
320 to override resource file defaults.
324 --hostname [database hostname]
326 The hostname (or IP address) which should be used to find the MySQL server.
327 See DBHostName, above.
329 --port [database port]
331 The TCP/IP port number to use for connection to the MySQL server. See
334 --username [database username]
336 The MySQL username to use for connection to the MySQL server. See
339 --name [database name]
341 The name of the database containing the MythTV data. See DBName, above.
345 --schemaver [MythTV database schema version]
347 The MythTV schema version. See DBSchemaVer, above.
349 --directory [directory]
351 The directory in which the backup file should be stored. See
352 DBBackupDirectory, above.
354 --filename [database backup filename]
356 The name to use for the database backup file. If not provided, a filename
357 using a default format will be chosen. See DBBackupFilename, above.
361 Do a partial restore (as would be required when setting up MythTV on new
362 hardware) of only the MythTV recordings and recording rules.
366 When doing a partial restore, include plugin data. Ignored, unless the
367 --partial_restore argument is given. Note that you will still need to
368 configure all plugins after the restore completes.
372 Restore channel xmltvids from a backup created with
373 mythconverg_backup.pl --backup_xmltvids
375 --mysql_client [path]
377 The path (including filename) of the mysql client executable. See
378 mysql_client in the DATABASE INFORMATION FILE description, above.
380 Default: $d_mysql_client
384 The command (including path, if necessary) to use to uncompress the
385 backup. See uncompress in the DATABASE INFORMATION FILE description, above.
387 Default: $d_uncompress
391 If specified, and if the database already exists, the script will attempt
392 to drop the database. This argument may only be used when the
393 --create_database argument is also specified (see below).
397 If specified, and if the database does not exist or the --drop_database
398 argument is specified, the script will attempt to create the initial
399 database. Note that database creation requires a properly configured MySQL
400 user and permissions. See, also, the MythTV HOWTO (
401 http://www.mythtv.org/docs/ ) for details on "Setting up the initial
406 Specifies that the script should change the hostname of a MythTV frontend
407 or backend in the database rather than restore a database backup. It is
408 critical that no MythTV frontends or backends are running when a hostname
413 Specifies the new hostname. The new_hostname is only used when the
414 --change_hostname argument is specified.
418 Specifies the old hostname. The old_hostname is only used when the
419 --change_hostname argument is specified.
427 Show version information.
431 Show what is happening.
433 --script_version | -v
435 Show script version information. This is primarily useful for scripts
436 or programs needing to parse the version information.
442 print "For detailed help:\n\n# $0 --help --help\n\n";
451 if ($level == $verbose_level_error)
457 return unless ($debug >= $level);
459 print { $error ? STDERR : STDOUT } join("\n", @_), "\n";
462 sub print_configuration
464 verbose($verbose_level_debug,
466 'Database Information:',
467 " DBHostName: $mysql_conf{'db_host'}",
468 " DBPort: $mysql_conf{'db_port'}",
469 " DBUserName: $mysql_conf{'db_user'}",
471 ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
472 # "$mysql_conf{'db_pass'}",
473 " DBName: $mysql_conf{'db_name'}",
474 " DBSchemaVer: $mysql_conf{'db_schemaver'}",
475 " DBBackupDirectory: $backup_conf{'directory'}",
476 " DBBackupFilename: $backup_conf{'filename'}",
477 ' drop_database: '.($drop_database ? 'yes' : 'no'),
478 ' create_database: '.($create_database ? 'yes' : 'no'));
479 verbose($verbose_level_debug,
482 " mysql_client: $mysql_client",
483 " uncompress: $uncompress");
484 verbose($verbose_level_debug,
487 ' partial_restore: '.($partial_restore ? 'yes' : 'no'));
488 if ($partial_restore)
490 verbose($verbose_level_debug,
491 ' with_plugin_data: '.($with_plugin_data ?
494 verbose($verbose_level_debug,
495 ' restore_xmltvids: '.($restore_xmltvids ? 'yes' : 'no'),
496 ' change_hostname: '.($change_hostname ? 'yes' : 'no'));
497 if ($change_hostname)
499 verbose($verbose_level_debug,
500 ' - old_hostname: '.$old_hostname,
501 ' - new_hostname: '.$new_hostname);
505 sub configure_environment
507 verbose($verbose_level_debug,
508 '', 'Configuring environment:');
510 # Get the user's login and home directory, so we can look for config files
511 ($username, $homedir) = (getpwuid $>)[0,7];
512 $username = $ENV{'USER'} if ($ENV{'USER'});
513 $homedir = $ENV{'HOME'} if ($ENV{'HOME'});
514 if ($username && !$homedir)
516 $homedir = "/home/$username";
517 if (!-e $homedir && -e "/Users/$username")
519 $homedir = "/Users/$username";
522 verbose($verbose_level_debug,
523 " - username: $username",
524 " - HOME: $homedir");
526 # Find the config directory
527 $mythconfdir = $ENV{'MYTHCONFDIR'}
528 ? $ENV{'MYTHCONFDIR'}
532 verbose($verbose_level_debug,
533 " - MYTHCONFDIR: $mythconfdir");
536 # Though much of the configuration file parsing could be done by the MythTV
537 # Perl bindings, using them to retrieve database information is not appropriate
538 # for a backup script. The Perl bindings require the backend to be running and
539 # use UPnP for autodiscovery. Also, parsing the files "locally" allows
540 # supporting even the old MythTV database configuration file, mysql.txt.
541 sub parse_database_information
544 verbose($verbose_level_debug,
545 " - checking: $file");
546 return 0 unless ($file && -e $file);
547 verbose($verbose_level_debug,
549 open(CONF, $file) or die("\nERROR: Unable to read $file: $!".
551 while (my $line = <CONF>)
554 next if ($line =~ m/^\s*#/);
559 # Split off the var=val pairs
560 my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
561 # Also look for <var>val</var> from config.xml
562 if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
564 $var = $1; $val = $2;
566 next unless ($var && $var =~ m/\w/);
567 if (($var eq 'Host') || ($var eq 'DBHostName'))
569 $mysql_conf{'db_host'} = $val;
571 elsif (($var eq 'Port') || ($var eq 'DBPort'))
573 $mysql_conf{'db_port'} = $val;
575 elsif (($var eq 'UserName') || ($var eq 'DBUserName'))
577 $mysql_conf{'db_user'} = $val;
579 elsif (($var eq 'Password') || ($var eq 'DBPassword'))
581 $mysql_conf{'db_pass'} = $val;
582 $mysql_conf{'db_pass'} =~ s/&/&/sg;
583 $mysql_conf{'db_pass'} =~ s/>/>/sg;
584 $mysql_conf{'db_pass'} =~ s/</</sg;
586 elsif (($var eq 'DatabaseName') || ($var eq 'DBName'))
588 $mysql_conf{'db_name'} = $val;
590 elsif ($var eq 'DBSchemaVer')
592 $mysql_conf{'db_schemaver'} = $val;
594 elsif ($var eq 'DBBackupDirectory')
596 $backup_conf{'directory'} = $val;
598 elsif ($var eq 'DBBackupFilename')
600 $backup_conf{'filename'} = $val;
602 elsif ($var eq 'partial_restore')
604 $partial_restore = $val;
606 elsif ($var eq 'with_plugin_data')
608 $with_plugin_data = $val;
610 elsif ($var eq 'mysql_client')
612 $mysql_client = $val;
614 elsif ($var eq 'uncompress')
625 # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
626 # couple places, so try the usual suspects in the same order that mythtv
627 # does in libs/libmyth/mythcontext.cpp
630 my @mysql = ('/usr/local/share/mythtv/mysql.txt',
631 '/usr/share/mythtv/mysql.txt',
632 '/usr/local/etc/mythtv/mysql.txt',
633 '/etc/mythtv/mysql.txt',
634 $homedir ? "$homedir/.mythtv/mysql.txt" : '',
636 $mythconfdir ? "$mythconfdir/mysql.txt" : '',
638 foreach my $file (@mysql)
640 $found = parse_database_information($file);
641 $result = $result + $found;
646 sub read_resource_file
648 parse_database_information("$mythconfdir/backuprc");
653 verbose($verbose_level_debug,
654 '', 'Applying command-line arguments.');
657 $mysql_conf{'db_host'} = $db_hostname;
661 $mysql_conf{'db_port'} = $db_port;
665 $mysql_conf{'db_user'} = $db_username;
667 # This script does not accept a database password on the command-line.
670 # $mysql_conf{'db_pass'} = $db_password;
674 $mysql_conf{'db_name'} = $db_name;
676 if ($db_schema_version)
678 $mysql_conf{'db_schemaver'} = $db_schema_version;
680 if ($backup_directory)
682 $backup_conf{'directory'} = $backup_directory;
684 if ($backup_filename)
686 $backup_conf{'filename'} = $backup_filename;
693 # If specified, use only the database information file
694 if ($database_information_file)
696 verbose($verbose_level_debug,
697 '', 'Database Information File specified. Ignoring all'.
698 ' command-line arguments');
699 verbose($verbose_level_debug,
700 '', 'Database Information File: '.
701 $database_information_file);
702 unless (-T "$database_information_file")
704 verbose($verbose_level_always,
705 '', 'The argument you supplied for the database'.
706 ' information file is invalid.',
707 'If you were trying to specify a backup filename,'.
708 ' please use the --directory ',
709 'and --filename arguments.');
710 die("\nERROR: Invalid database information file, stopped");
712 # When using a database information file, parse the resource file first
713 # so it cannot override database information file settings
715 $result = parse_database_information($database_information_file);
719 # No database information file, so try the MythTV configuration files.
720 verbose($verbose_level_debug,
721 '', 'Parsing configuration files:');
722 # Prefer the config.xml file
723 my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
724 $result = parse_database_information($file);
727 # Use the "legacy" mysql.txt file as a fallback
728 $result = read_mysql_txt;
730 # Read the resource file next to override the config file information, but
731 # to allow command-line arguments to override resource file "defaults"
733 # Apply the command-line arguments to override the information provided
734 # by the config file(s).
739 sub create_defaults_extra_file
741 return '' if (!$mysql_conf{'db_pass'});
742 verbose($verbose_level_debug,
743 '', "Attempting to use supplied password for $mysql_client".
744 ' command-line client.',
745 'Any [client] or [mysql] password specified in the MySQL'.
746 ' options file will',
748 # Let tempfile handle unlinking on exit so we don't have to verify that the
749 # file with $filename is the file we created
750 my ($fh, $filename) = tempfile(UNLINK => 1);
751 # Quote the password if it contains # or whitespace or quotes.
752 # Quoting of values in MySQL options files is only supported on MySQL
753 # 4.0.16 and above, so only quote if required.
755 my $safe_password = $mysql_conf{'db_pass'};
756 if ($safe_password =~ /[#'"\s]/)
759 $safe_password =~ s/'/\\'/g;
761 print $fh "[client]\npassword=${quote}${safe_password}${quote}\n".
762 "[mysqldump]\npassword=${quote}${safe_password}${quote}\n";
766 sub check_file_config
768 if (!$backup_conf{'directory'})
770 if (!$backup_conf{'filename'} || (!-r "/$backup_conf{'filename'}"))
773 die("\nERROR: DBBackupDirectory not specified, stopped");
775 # The user must have specified an absolute path for the
776 # DBBackupFilename. Though this is not how the script is meant to be
778 $backup_conf{'directory'} = '';
780 elsif (!-d $backup_conf{'directory'})
783 verbose($verbose_level_error,
784 '', 'ERROR: DBBackupDirectory is not a directory. Please'.
785 ' specify a directory in',
786 ' your database information file using'.
787 ' DBBackupDirectory.',
788 ' If not using a database information file,' .
789 ' please specify the ',
790 ' --directory command-line option.');
791 die("\nInvalid backup directory, stopped");
793 if (!$backup_conf{'filename'})
795 # Look for most current backup file
796 verbose($verbose_level_debug,
797 '', 'No filename specified. Attempting to find the newest'.
798 ' database backup.');
799 if ($restore_xmltvids)
801 $backup_conf{'filename'} = 'mythtv_xmltvid_backup';
805 $backup_conf{'filename'} = $mysql_conf{'db_name'};
806 if (!$backup_conf{'filename'})
808 $backup_conf{'filename'} = $d_db_name;
811 my @files = <$backup_conf{'directory'}/$backup_conf{'filename'}*>;
812 @files = grep(!/.*mythconverg_(backup|restore).*\.pl$/, @files);
813 my $num_files = @files;
816 verbose($verbose_level_error,
817 'ERROR: Unable to find any backup files in'.
818 ' DBBackupDir and none specified.');
822 my @sorted_files = sort { lc($b) cmp lc($a) } @files;
823 $backup_conf{'filename'} = $sorted_files[0];
824 $backup_conf{'filename'} =~ s#^$backup_conf{'directory'}/?##;
825 verbose($verbose_level_debug,
826 'Using database backup file:',
827 "$backup_conf{'directory'}/$backup_conf{'filename'}");
830 if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
832 my $temp_filename = $backup_conf{'filename'};
833 # Perhaps the user specified some unnecessary path information in the
834 # filename (i.e. using the shell's filename completion)
835 $temp_filename =~ s#^.*/##;
836 if (-e "$backup_conf{'directory'}/$temp_filename")
838 $backup_conf{'filename'} = $temp_filename;
842 verbose($verbose_level_error,
843 '', 'ERROR: The specified backup file does not exist.',
844 "$backup_conf{'directory'}/$backup_conf{'filename'}");
845 die("\nInvalid backup filename, stopped");
848 if ((-d "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
849 (-p "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
850 (-S "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
851 (-b "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
852 (-c "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
853 (!-s "$backup_conf{'directory'}/$backup_conf{'filename'}"))
855 verbose($verbose_level_error,
856 '', 'ERROR: The specified backup file is empty or is'.
858 "$backup_conf{'directory'}/$backup_conf{'filename'}");
859 die("\nInvalid backup filename, stopped");
861 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
863 verbose($verbose_level_error,
864 '', 'ERROR: The specified backup file cannot be read.');
865 die("\nInvalid backup filename, stopped");
867 if (!$mysql_conf{'db_name'})
869 verbose($verbose_level_debug,
870 '', "WARNING: DBName not specified. Using $d_db_name");
871 $mysql_conf{'db_name'} = $d_db_name;
877 verbose($verbose_level_debug,
878 '', 'Checking configuration.');
880 if (!defined($change_hostname))
882 # Check directory/filename
885 # Though the script will attempt a restore even if no other database
886 # information is provided (i.e. using "defaults" from the MySQL options
887 # file, warning the user that some "normally-necessary" information is not
888 # provided may be nice.
890 if (!$mysql_conf{'db_host'})
892 verbose($verbose_level_debug,
893 '', 'WARNING: DBHostName not specified.',
894 ' Assuming it is specified in the MySQL'.
897 if (!$mysql_conf{'db_user'})
899 verbose($verbose_level_debug,
900 '', 'WARNING: DBUserName not specified.',
901 ' Assuming it is specified in the MySQL'.
904 if (!$mysql_conf{'db_pass'})
906 verbose($verbose_level_debug,
907 '', 'WARNING: DBPassword not specified.',
908 ' Assuming it is specified in the MySQL'.
913 sub connect_to_database
916 my $show_errors = shift;
918 my $connect_string = 'dbi:mysql';
919 my $temp_host = $mysql_conf{'db_host'};
920 if ($temp_host =~ /:/)
922 if ($temp_host =~ /^(?!\[).*(?!\])$/)
924 $temp_host = "[$temp_host]";
927 $connect_string .= ":host=$temp_host";
928 $connect_string .= ":port=$mysql_conf{'db_port'}";
931 $connect_string .= ":database=$mysql_conf{'db_name'}";
933 $dbh->disconnect if (defined($dbh));
934 $dbh = DBI->connect($connect_string,
935 "$mysql_conf{'db_user'}",
936 "$mysql_conf{'db_pass'}",
937 { PrintError => 1 });
938 $result = 0 if (!defined($dbh));
939 if ($show_errors && !defined($dbh))
941 verbose($verbose_level_always,
942 '', 'Unable to connect to database.',
943 " database: $mysql_conf{'db_name'}",
944 " host: $mysql_conf{'db_host'}",
945 " username: $mysql_conf{'db_user'}"
947 if ($debug < $verbose_level_debug)
949 verbose($verbose_level_always,
950 'To see the password used, please re-run the script '.
951 'with the --verbose',
954 # Connection issues will only occur with improper user configuration
955 # Because they should be rare, output the password with --verbose
956 verbose($verbose_level_debug,
957 " password: $mysql_conf{'db_pass'}");
958 verbose($verbose_level_always,
959 '', 'Please check your configuration files to verify the'.
960 ' database connection',
961 'information is correct. The files that are used to'.
962 ' retrieve connection',
963 'information are prefixed with "parsing" in the "Parsing'.
964 ' configuration files"',
965 'section of the --verbose output.');
966 verbose($verbose_level_always,
967 '', 'Also note that any [client] or [mysql] password'.
968 ' specified in the MySQL options',
969 'file (/etc/my.cnf or /etc/mysql/my.cnf or ~/.my.cnf)'.
970 ' will take precedence over',
971 'the password specified in the MythTV configuration'.
977 sub is_database_empty
980 connect_to_database(1, 1);
983 verbose($verbose_level_error,
984 '', 'ERROR: Unable to connect to database.');
990 my $sth = $dbh->table_info('', '', '', 'TABLE');
991 my $num_tables = keys %{$sth->fetchall_hashref('TABLE_NAME')};
992 verbose($verbose_level_debug,
993 '', "Found $num_tables tables in the database.");
996 if (!defined($change_hostname) && !defined($partial_restore))
998 verbose($verbose_level_debug,
999 'WARNING: Database not empty.');
1007 sub create_initial_database
1009 return 0 if (!$create_database && !$drop_database);
1011 my $database_exists = (connect_to_database(1, 0) && defined($dbh));
1012 if ($database_exists)
1014 if ($drop_database && !$create_database)
1016 verbose($verbose_level_error,
1017 '', 'ERROR: Refusing to drop the database without'.
1018 ' the --create_database argument.',
1019 'If you really want to drop the database, please '.
1020 're-run the script and specify',
1021 'the --create_database argument, too.');
1027 if (!$create_database)
1029 verbose($verbose_level_error,
1030 '', 'ERROR: The database does not exist.');
1035 verbose($verbose_level_debug,
1036 '', 'Preparing initial database.');
1040 if ($database_exists && $drop_database)
1042 verbose($verbose_level_debug, 'Dropping database.');
1043 connect_to_database(0, 1);
1046 verbose($verbose_level_error,
1047 '', 'ERROR: Unable to connect to database.');
1051 $query = qq{DROP DATABASE $mysql_conf{'db_name'};};
1052 $sth = $dbh->prepare($query);
1053 if (! $sth->execute())
1055 verbose($verbose_level_error,
1056 '', 'ERROR: Unable to drop database.',
1062 connect_to_database(0, 1) if (!defined($dbh));
1066 verbose($verbose_level_error,
1067 '', 'ERROR: Unable to connect to database.');
1071 verbose($verbose_level_debug, 'Creating database.');
1072 $query = qq{CREATE DATABASE $mysql_conf{'db_name'};};
1073 $sth = $dbh->prepare($query);
1074 if (! $sth->execute())
1076 verbose($verbose_level_error,
1077 '', 'ERROR: Unable to create database.',
1082 verbose($verbose_level_debug, 'Setting database character set.');
1083 $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
1084 DEFAULT CHARACTER SET latin1
1085 COLLATE latin1_swedish_ci;};
1086 $sth = $dbh->prepare($query);
1087 if (! $sth->execute())
1089 verbose($verbose_level_error,
1090 '', 'ERROR: Unable to create database.',
1098 sub check_database_libs
1100 # Try to load the DBI library if available (but don't require it)
1110 verbose($verbose_level_debug,
1111 '', 'DBI is not installed.') if (!$has_dbi);
1112 # Try to load the DBD::mysql library if available (but don't # require it)
1116 eval 'use DBD::mysql;';
1122 verbose($verbose_level_debug,
1123 '', 'DBD::mysql is not installed.') if (!$has_dbd);
1124 return ($has_dbi + $has_dbd);
1129 my $have_database_libs = check_database_libs;
1130 if ($have_database_libs < 2)
1132 if ($create_database || $drop_database)
1134 verbose($verbose_level_error,
1135 '', 'ERROR: Unable to drop or create the initial '.
1136 'database without Perl database',
1138 'Please ensure the Perl DBI and DBD::mysql modules'.
1140 die("\nPerl database libraries missing, stopped");
1142 if ($change_hostname)
1144 verbose($verbose_level_error,
1145 '', 'ERROR: Unable to change hostname without Perl'.
1146 ' database libraries.',
1147 'Please ensure the Perl DBI and DBD::mysql modules'.
1149 die("\nPerl database libraries missing, stopped");
1153 verbose($verbose_level_debug,
1154 'Blindly assuming your database is prepared for a'.
1155 ' restore. For better checking,',
1156 'please ensure the Perl DBI and DBD::mysql modules'.
1161 # DBI/DBD::mysql are available; check the DB status
1162 verbose($verbose_level_debug,
1163 '', 'Checking database.');
1164 my $initial_database = create_initial_database;
1165 if ($initial_database)
1169 my $database_empty = is_database_empty;
1170 if ($database_empty == -1)
1172 # Unable to connect to database
1175 if ($change_hostname)
1177 if ($database_empty)
1179 verbose($verbose_level_error,
1180 '', 'ERROR: Unable to change hostname. The database'.
1182 ' Please restore a backup, first, then re-run'.
1187 elsif ($partial_restore)
1189 if ($database_empty)
1191 verbose($verbose_level_error,
1192 '', 'ERROR: Unable to do a partial restore. The'.
1193 ' database is empty.',
1194 ' Please run mythtv-setup, first, then re-run'.
1201 if (!$database_empty)
1203 verbose($verbose_level_error,
1204 '', 'ERROR: Unable to do a full restore. The'.
1205 ' database contains data.');
1214 # Simple magic number verification.
1215 # This naive approach works without requiring File::MMagic or any other
1219 my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
1220 open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
1222 binmode(BACKUPFILE);
1223 read(BACKUPFILE, $magic_number, 2);
1225 return ($gzip_magic_number eq $magic_number);
1228 # Though it's possible to uncompress the file without writing the uncompressed
1229 # data to a file, doing so is complicated by supporting the use of
1230 # IO::Uncompress::Gunzip /and/ external uncompress programs. Also,
1231 # uncompressing the file separately allows for easier and more precise error
1233 sub uncompress_backup_file
1235 if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
1239 verbose($verbose_level_debug,
1240 '', 'Backup file is uncompressed.');
1243 verbose($verbose_level_debug,
1244 '', 'Backup file is compressed.');
1245 # Try to load the IO::Uncompress::Gunzip library if available (but
1249 our $has_uncompress_gunzip = 1;
1250 # Though this does nothing, it prevents an invalid "only used
1251 # once" warning that occurs for users without IO::Uncompress
1254 eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
1257 $has_uncompress_gunzip = 0;
1260 if (!$has_uncompress_gunzip)
1262 verbose($verbose_level_debug,
1263 ' - IO::Uncompress::Gunzip is not installed.');
1267 verbose($verbose_level_debug,
1268 ' - Uncompressing backup file with'.
1269 ' IO::Uncompress::Gunzip.');
1270 my ($bfh, $temp_backup_filename) = tempfile(UNLINK => 1);
1271 my $result = gunzip(
1272 "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
1274 if ((defined($result)) &&
1275 (-f "$temp_backup_filename") &&
1276 (-r "$temp_backup_filename") &&
1277 (-s "$temp_backup_filename"))
1279 $backup_conf{'directory'} = '';
1280 $backup_conf{'filename'} = "$temp_backup_filename";
1283 verbose($verbose_level_error,
1284 " ERROR: $GunzipError");
1289 verbose($verbose_level_debug,
1290 '', 'Unrecognized uncompress program.'.
1291 ' Assuming backup file is compressed.',
1292 ' - If the file is not compressed, please do not specify'.
1293 ' a custom uncompress',
1296 # Try to uncompress the file with the uncompress binary.
1297 # With the approach, the original backup file will be uncompressed and
1298 # left uncompressed.
1299 my $safe_uncompress = $uncompress;
1300 $safe_uncompress =~ s/'/'\\''/sg;
1301 verbose($verbose_level_debug,
1302 " - Uncompressing backup file with $uncompress.",
1303 ' The original backup file will be left uncompressed.'.
1304 ' Please recompress,',
1306 my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
1307 $backup_path =~ s/'/'\\''/sg;
1308 my $output = `'$safe_uncompress' '$backup_path' 2>&1`;
1310 verbose($verbose_level_debug,
1311 '', "$uncompress exited with status: $exit");
1314 verbose($verbose_level_debug,
1315 "$uncompress output:", $output);
1319 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1321 # Assume the final extension was removed by uncompressing.
1322 $backup_conf{'filename'} =~ s/\.\w+$//;
1323 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1325 verbose($verbose_level_error,
1327 'ERROR: Unable to find uncompressed backup file.');
1328 die("\nInvalid backup filename, stopped");
1335 sub do_hostname_change
1341 verbose($verbose_level_error,
1342 '', 'ERROR: Cannot change hostname without --new_hostname'.
1348 verbose($verbose_level_error,
1349 '', 'ERROR: Cannot change hostname without --old_hostname'.
1354 die("\nInvalid --old/--new_hostname value(s) for".
1355 ' --change_hostname, stopped');
1357 # Get a list of all tables in the DB.
1371 $sth_tables = $dbh->table_info('', '', '', 'TABLE');
1372 while (my $table = $sth_tables->fetchrow_hashref)
1374 # Loop over all tables in the DB, checking for a hostname column.
1376 $table_cat = $table->{'TABLE_CAT'};
1377 $table_schema = $table->{'TABLE_SCHEM'};
1378 $table_name = $table->{'TABLE_NAME'};
1379 $sth_columns = $dbh->column_info($table_cat, $table_schema,
1381 while (my $column = $sth_columns->fetchrow_hashref)
1383 # If a hostname column exists, change its value.
1384 $column_name = $column->{'COLUMN_NAME'};
1385 if (($column_name eq 'hostname') ||
1386 ($column_name eq 'host'))
1388 verbose($verbose_level_debug,
1389 "Found '$column_name' column in $table_name.");
1390 $query = "UPDATE $table_name SET $column_name = ?".
1391 " WHERE $column_name = ?";
1392 $sth_update = $dbh->prepare($query);
1393 $sth_update->bind_param(1, $new_hostname);
1394 $sth_update->bind_param(2, $old_hostname);
1395 $result = $sth_update->execute;
1396 if (!defined($result))
1398 verbose($verbose_level_always,
1399 "Unable to update $column_name in table: ".
1401 $sth_update->errstr);
1406 verbose($verbose_level_debug,
1408 (($result == 0E0) ? '0' : $result)
1409 ." rows in table: $table_name");
1415 if ($num_tables == 0)
1417 verbose($verbose_level_always,
1418 'Database is empty. Cannot change hostname.');
1421 # delete (orphaned) rows with hostname coded into chainid in tvchain
1422 # live-<hostname>-2008-06-26T18:43:18
1423 $table_name = 'tvchain';
1424 $query = "DELETE FROM $table_name WHERE chainid LIKE ?";
1425 $sth_update = $dbh->prepare($query);
1426 $sth_update->bind_param(1, '%'.$old_hostname.'%');
1427 $result = $sth_update->execute;
1428 if (!defined($result))
1430 verbose($verbose_level_debug,
1431 "Unable to remove orphaned $table_name rows.",
1432 $sth_update->errstr);
1436 verbose($verbose_level_debug,
1438 (($result == 0E0) ? '0' : $result)
1439 ." orphaned entries in table: $table_name");
1441 # hostname coded into SGweightPerDir setting in settings (modify)
1442 # SGweightPerDir:<hostname>:<directory>
1443 $table_name = 'settings';
1444 $query = "UPDATE $table_name SET value = REPLACE(value, ?, ?)".
1445 " WHERE value LIKE ?";
1446 $sth_update = $dbh->prepare($query);
1447 $sth_update->bind_param(1, 'SGweightPerDir:'.$old_hostname.':');
1448 $sth_update->bind_param(2, 'SGweightPerDir:'.$new_hostname.':');
1449 $sth_update->bind_param(3, 'SGweightPerDir:'.$old_hostname.':%');
1450 $result = $sth_update->execute;
1451 if (!defined($result))
1453 verbose($verbose_level_always,
1454 'Unable to update SGweightPerDir setting for host.',
1455 $sth_update->errstr);
1459 verbose($verbose_level_debug,
1461 (($result == 0E0) ? '0' : $result)
1462 .' SGweightPerDir settings.');
1468 sub get_db_schema_ver
1470 connect_to_database(1, 1) if (!defined($dbh));
1473 verbose($verbose_level_error,
1474 '', 'ERROR: Unable to connect to database.');
1477 my $query = 'SELECT data FROM settings WHERE value = ?';
1480 my $sth = $dbh->prepare($query);
1481 if ($sth->execute('DBSchemaVer'))
1483 while (my @data = $sth->fetchrow_array)
1485 $mysql_conf{'db_schemaver'} = $data[0];
1486 verbose($verbose_level_debug,
1487 '', 'Found DBSchemaVer:'.
1488 " $mysql_conf{'db_schemaver'}.");
1493 verbose($verbose_level_debug,
1494 "Unable to retrieve DBSchemaVer from".
1502 sub set_database_charset
1504 return 0 if (!$create_database && !$drop_database);
1506 if (get_db_schema_ver && ! $mysql_conf{'db_schemaver'})
1508 verbose($verbose_level_error,
1509 "Unknown database schema version. Assuming current.");
1510 $mysql_conf{'db_schemaver'} = '1216';
1513 if ($mysql_conf{'db_schemaver'} > 1215)
1515 connect_to_database(0, 1) if (!defined($dbh));
1518 verbose($verbose_level_error,
1519 '', 'ERROR: Unable to connect to database.');
1523 verbose($verbose_level_debug, 'Setting database character set.');
1526 $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
1527 DEFAULT CHARACTER SET utf8
1528 COLLATE utf8_general_ci;};
1529 $sth = $dbh->prepare($query);
1530 if (! $sth->execute())
1532 verbose($verbose_level_error,
1533 '', 'ERROR: Unable to set database character set.',
1545 my $defaults_extra_file = create_defaults_extra_file;
1550 if ($defaults_extra_file)
1552 $defaults_arg = " --defaults-extra-file='$defaults_extra_file'";
1558 my $safe_mysql_client = $mysql_client;
1559 $safe_mysql_client =~ s/'/'\\''/g;
1560 # Create the args for host, port, and user, shell-escaping values, as
1563 if ($mysql_conf{'db_host'})
1565 $safe_string = $mysql_conf{'db_host'};
1566 $safe_string =~ s/'/'\\''/g;
1567 $host_arg = " --host='$safe_string'";
1569 if ($mysql_conf{'db_port'} > 0)
1571 $safe_string = $mysql_conf{'db_port'};
1572 $safe_string =~ s/'/'\\''/g;
1573 $port_arg = " --port='$safe_string'";
1575 if ($mysql_conf{'db_user'})
1577 $safe_string = $mysql_conf{'db_user'};
1578 $safe_string =~ s/'/'\\''/g;
1579 $user_arg = " --user='$safe_string'";
1581 # Configure a filter for a partial/new-host restore
1582 if ($partial_restore)
1584 my @partial_restore_tables;
1585 if (defined($with_plugin_data))
1587 # Blacklist the MythTV tables we don't want to keep
1588 # This may result in keeping old tables that were dropped in
1589 # previous DB schema updates if the user is running a restore
1590 # script from an older version of MythTV, but the extra data will
1591 # only take a bit of hard drive space.
1592 @partial_restore_tables = ('callsignnetworkmap', # historic
1597 'channelgroupnames',
1599 'channelscan_channel',
1600 'channelscan_dtv_multiplex',
1602 'conflictresolutionany', # historic
1603 'conflictresolutionoverride', # hst
1604 'conflictresolutionsingle', # hst
1609 'displayprofilegroups',
1613 'dvb_channel', # historic
1614 'dvb_pids', # historic
1615 'dvb_sat', # historic
1616 'dvb_signal_quality', # historic
1617 'dvb_transport', # historic
1629 'networkiconmap', # historic
1644 'recordingprofiles',
1646 'recordoverride', # historic
1651 'transcoding', # historic
1655 'videobookmarks', # historic
1657 'xvmc_buffer_settings' # historic
1662 # Whitelist the tables we want to keep
1663 @partial_restore_tables = ('oldrecorded',
1671 if (!defined($restore_xmltvids))
1673 $filter = '^INSERT INTO \`?(' .
1674 join('|', @partial_restore_tables).')\`? ';
1675 # If doing a whitelist restore, ensure we keep the character
1676 # set info to prevent data corruption
1677 if (!defined($with_plugin_data))
1679 $filter = '(40101 SET NAMES |'.$filter.')';
1681 verbose($verbose_level_debug,
1682 '', 'Restoring partial backup with filter:', $filter);
1685 my $safe_db_name = $mysql_conf{'db_name'};
1686 $safe_db_name =~ s/'/'\\''/g;
1687 my $command = "'${safe_mysql_client}'${defaults_arg}${host_arg}".
1688 "${port_arg}${user_arg} '$safe_db_name'";
1689 verbose($verbose_level_debug,
1690 '', 'Executing command:', $command);
1691 my $read_status = open(BACKUP,
1692 "<$backup_conf{'directory'}/$backup_conf{'filename'}");
1693 if (!defined($read_status))
1695 verbose($verbose_level_error,
1696 '', 'ERROR: Unable to read backup file.');
1699 my $write_status = open(COMMAND, "| $command");
1700 if (!defined($write_status))
1702 verbose($verbose_level_error,
1703 '', "ERROR: Unable to execute $mysql_client.");
1706 my $lines_total = 0;
1707 my $lines_restored = 0;
1711 if ($partial_restore)
1713 if ($restore_xmltvids)
1715 # Send all lines through
1717 if ($with_plugin_data)
1719 # Skip tables in the blacklist
1724 # Skip tables not in the whitelist
1729 print COMMAND or die("\nERROR: Cannot write to ".
1730 "$mysql_client, stopped");
1735 verbose($verbose_level_debug,
1736 '', "$mysql_client exited with status: $exit",
1737 '', "Restored $lines_restored of $lines_total lines.");
1741 ##############################################################################
1742 # Main functionality
1743 ##############################################################################
1745 # The first argument after option parsing, if it exists, should be a database
1747 $database_information_file = shift;
1749 configure_environment;
1753 print_configuration;
1758 if ($change_hostname)
1760 $status = do_hostname_change;
1761 verbose($verbose_level_always,
1762 '', 'Successfully changed hostname.') if (!$status);
1764 elsif (!uncompress_backup_file)
1766 $status = restore_backup;
1769 verbose($verbose_level_always,
1770 '', 'Successfully restored backup.');
1771 $status = set_database_charset;
1776 $dbh->disconnect if (defined($dbh));