00001 #!/usr/bin/perl -w
00002 #
00003 # mythconverg_restore.pl
00004 #
00005 # Restores a backup of the MythTV database.
00006 #
00007 # For details, see:
00008 # mythconverg_restore.pl --help
00009
00010 # Includes
00011 use Getopt::Long;
00012 use File::Temp qw/ tempfile /;
00013
00014 # Script info
00015 $NAME = 'MythTV Database Restore Script';
00016 $VERSION = '1.0.18';
00017
00018 # Some variables we'll use here
00019 our ($username, $homedir, $mythconfdir, $database_information_file);
00020 our ($partial_restore, $with_plugin_data, $restore_xmltvids);
00021 our ($mysql_client, $uncompress, $drop_database, $create_database);
00022 our ($change_hostname, $old_hostname, $new_hostname);
00023 our ($usage, $debug, $show_version, $show_version_script, $dbh);
00024 our ($d_mysql_client, $d_db_name, $d_uncompress);
00025 our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
00026 # This script does not accept a database password on the command-line.
00027 # Any packager who enables the functionality should modify the --help output.
00028 # our ($db_password);
00029 our ($backup_directory, $backup_filename);
00030 our ($verbose_level_always, $verbose_level_debug, $verbose_level_error);
00031
00032 our %mysql_conf = ('db_host' => '',
00033 'db_port' => -1,
00034 'db_user' => '',
00035 'db_pass' => '',
00036 'db_name' => '',
00037 'db_schemaver' => ''
00038 );
00039 our %backup_conf = ('directory' => '',
00040 'filename' => ''
00041 );
00042
00043 # Debug levels
00044 $verbose_level_always = 0;
00045 $verbose_level_debug = 1;
00046 $verbose_level_error = 255;
00047
00048 # Defaults
00049 $d_db_name = 'mythconverg';
00050 $d_mysql_client = 'mysql';
00051 $d_uncompress = 'gzip -d';
00052
00053 # Provide default values for GetOptions
00054 $mysql_client = $d_mysql_client;
00055 $uncompress = $d_uncompress;
00056 $debug = 0;
00057 $new_hostname = '';
00058 $old_hostname = '';
00059
00060 # Load the cli options
00061 GetOptions('hostname|DBHostName=s' => \$db_hostname,
00062 'port|DBPort=i' => \$db_port,
00063 'username|DBUserName=s' => \$db_username,
00064 # This script does not accept a database password on the command-line.
00065 # 'password|DBPassword=s' => \$db_password,
00066 'name|DBName=s' => \$db_name,
00067 'schemaver|DBSchemaVer=s' => \$db_schema_version,
00068 'directory|DBBackupDirectory=s' => \$backup_directory,
00069 'filename|DBBackupFilename=s' => \$backup_filename,
00070 'partial_restore|new_hardware|'.
00071 'partial-restore|new-hardware' => \$partial_restore,
00072 'with_plugin_data|plugin_data|'.
00073 'with-plugin-data|plugin-data' => \$with_plugin_data,
00074 'restore_xmltvids|'.
00075 'restore-xmltvids|xmltvids' => \$restore_xmltvids,
00076 'mysql_client|mysql-client|client=s' => \$mysql_client,
00077 'uncompress=s' => \$uncompress,
00078 'drop_database|drop_db|'.
00079 'drop-database|drop-db' => \$drop_database,
00080 'create_database|create_db|mc_sql|'.
00081 'create-database|create-db|mc-sql' => \$create_database,
00082 'change_hostname|change-hostname' => \$change_hostname,
00083 'new_hostname|new-hostname=s' => \$new_hostname,
00084 'old_hostname|old-hostname=s' => \$old_hostname,
00085 'usage|help|h+' => \$usage,
00086 'version' => \$show_version,
00087 'script_version|script-version|v' => \$show_version_script,
00088 'verbose|debug|d+' => \$debug
00089 );
00090
00091 $partial_restore ||= $restore_xmltvids;
00092
00093 # Print version information
00094 sub print_version_information
00095 {
00096 my $script_name = substr $0, rindex($0, '/') + 1;
00097 print "$NAME\n$script_name\nversion: $VERSION\n";
00098 }
00099
00100 if ($show_version_script)
00101 {
00102 print "$NAME,$VERSION,,\n";
00103 exit;
00104 }
00105 elsif ($show_version)
00106 {
00107 print_version_information;
00108 exit;
00109 }
00110
00111 # Print usage
00112 if ($usage)
00113 {
00114 print_version_information;
00115 print <<EOF;
00116
00117 Usage:
00118 $0 [options|database_information_file]
00119
00120 Restores a backup of the MythTV database.
00121
00122 QUICK START:
00123
00124 Create a file ~/.mythtv/backuprc with a single line,
00125 "DBBackupDirectory=/home/mythtv" (no quotes). For example:
00126
00127 # echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
00128
00129 To do a full restore:
00130 Ensure you have an empty database. If you are replacing an existing database,
00131 you must first drop the old database. You may do this using the mysql client
00132 executable by issuing the command:
00133
00134 # mysql -umythtv -p mythconverg -e "DROP DATABASE IF EXISTS mythconverg;"
00135
00136 (fix the database name and username, as required). Then, execute the mc.sql
00137 script as described in the MythTV HOWTO ( http:
00138 prepare a new (empty) database. Alternatively, you may specify the
00139 --drop_database and/or --create_database arguments to automatically drop and/or
00140 create the database for you (see the command-line argument descriptions in the
00141 detailed help for more information).
00142
00143 Then, run this script to restore the most-recent backup in the directory
00144 specified in ~/.mythtv/backuprc . Use the --verbose argument to see what is
00145 happening:
00146
00147 # $0 --verbose
00148
00149 or specify a backup file with:
00150
00151 # $0 --directory=/path/to/backups/ --filename=backup_file.sql.gz --verbose
00152
00153 (You may leave out the --directory argument if you've specified the directory
00154 in the ~/.mythtv/backuprc .)
00155
00156 Once the restore completes successfully, you may start mythtv-setup or
00157 mythbackend. If you restored a backup from an older version of MythTV,
00158 mythtv-setup will upgrade the database for you.
00159
00160 To change the hostname of a MythTV frontend or backend:
00161
00162 Ensure that the database exists (restore an old database, as above, if
00163 necessary) and execute the following command, replacing "XXXX" and "YYYY"
00164 with appropriate values for the old and new hostnames, respectively:
00165
00166 # $0 --change_hostname --old_hostname="XXXX" --new_hostname="YYYY"
00167
00168 To restore xmltvids:
00169
00170 Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
00171 script with the --restore_xmltvids argument.
00172
00173 # $0 --restore_xmltvids
00174
00175 EOF
00176
00177 if ($usage > 1)
00178 {
00179 print <<EOF;
00180 DETAILED DESCRIPTION:
00181
00182 This script is used to restore a backup of the MythTV database (as created by
00183 the mythconverg_backup.pl script). It can be called with a single command-line
00184 argument specifying the name of a "database information file" (see DATABASE
00185 INFORMATION FILE, below), which contains sufficient information about the
00186 database and the backup to allow the script to restore a backup without needing
00187 any additional configuration files. In this mode, all other MythTV
00188 configuration files (including config.xml, mysql.txt) are ignored, but the
00189 backup resource file (see RESOURCE FILE, below) and the MySQL option files
00190 (i.e. /etc/my.cnf or ~/.my.cnf) will be honored.
00191
00192 The script can also be called using command-line arguments to specify the
00193 required information. If no database information file is specified, the script
00194 will attempt to determine the appropriate configuration by using the MythTV
00195 configuration file(s) (prefering config.xml, but falling back to mysql.txt if
00196 no config.xml exists). Once the MythTV configuration file has been parsed, the
00197 backup resource file (see RESOURCE FILE, below) will be parsed, then
00198 command-line arguments will be applied (thus overriding any values determined
00199 from the configuration files).
00200
00201 The only information required by the script is the directory in which the
00202 backup exists (the script will attempt to find the most current backup file,
00203 based on the filename). Therefore, when using a database information file, the
00204 DBBackupDirectory should be specified, or if running manually, the --directory
00205 command-line argument should be specified. The DBBackupDirectory may be
00206 specified in a backup resource file (see RESOURCE FILE, below). If the
00207 specified directory is not readable, the script will terminate. Likewise, if
00208 the backup file cannot be read, the script will terminate.
00209
00210 If the database name is not specified, the script will attempt to use the
00211 MythTV default database name, $d_db_name. Note that the same is not true for
00212 the database username and database password. These must be explicitly
00213 specified. The password must be specified in a database information file, a
00214 backup resource file, or a MySQL options file. The username may be specified
00215 the same way or may be specified using a command-line argument if not using a
00216 database information file.
00217
00218 If attempting to perform a full restore, the database must be empty (no
00219 tables). To automatically drop any existing database and create an empty
00220 database, specify the --drop_database and the --create_database arguments.
00221
00222 If you have a corrupt database, you may be able to recover some information
00223 using a partial restore. To do a partial restore, you must have a
00224 fully-populated database schema (but without the data you wish to import) from
00225 the version of MythTV used to create the backup. You may create and populate
00226 the database by running the mc.sql script (see the description of the
00227 --create_database argument) to create the database. Then, start and exit
00228 mythtv-setup to populate the database. And, finally, do the partial restore
00229 with:
00230
00231 # $0 --partial_restore
00232
00233 Include the --with_plugin_data argument if you would like to keep the data used
00234 by MythTV plugins. Note that this approach cannot be used to "merge"
00235 databases from different MythTV databases nor to import recordings from other
00236 MythTV databases.
00237
00238 If you would like to do a partial/new-hardware restore and have upgraded
00239 MythTV, you must first do a full restore, then start and exit mythtv-setup (to
00240 upgrade the database), then create a backup, then drop the database, then
00241 follow the instructions for doing a partial restore with the new (upgraded)
00242 backup file.
00243
00244 DATABASE INFORMATION FILE
00245
00246 The database information file contains information about the database and the
00247 backup. The information within the file is specified as name=value pairs using
00248 the same names as used by the MythTV config.xml and mysql.txt configuration
00249 files. The following variables are recognized:
00250
00251 DBHostName - The hostname (or IP address) which should be used to find the
00252 MySQL server.
00253 DBPort - The TCP/IP port number to use for the connection. This may have a
00254 value of 0, i.e. if the hostname is localhost or if the server is
00255 using the default MySQL port or the port specified in a MySQL
00256 options file.
00257 DBUserName - The database username to use when connecting to the server.
00258 DBPassword - The password to use when connecting to the server.
00259 DBName - The name of the database that contains the MythTV data.
00260 DBSchemaVer - The MythTV schema version of the database. This value will be
00261 used to create the backup filename, but only if the filename
00262 has not been specified using DBBackupFilename or the --filename
00263 argument.
00264 DBBackupDirectory - The directory in which the backup file should be
00265 created. This directory may have been specially
00266 configured by the user as the "DB Backups" storage
00267 group. It is recommended that this directory be
00268 used--especially in "common-use" scripts such as those
00269 provided by distributions.
00270 DBBackupFilename - The name of the file in which the backup should be
00271 created. Additional extensions may be added by this
00272 script as required (i.e. adding an appropriate suffix,
00273 such as ".gz", to the file if it is compressed). If the
00274 filename recommended by mythbackend is used, it will be
00275 displayed in the GUI messages provided for the user. If
00276 the recommended filename is not used, the user will not be
00277 told where to find the backup file. If no value is
00278 provided, a filename using the default filename format
00279 will be chosen.
00280 partial_restore - Do a partial restore (as would be required when setting
00281 up MythTV on new hardware) of only the MythTV recordings
00282 and recording rules.
00283 with_plugin_data - When doing a partial restore, include plugin data.
00284 Ignored, unless the --partial_restore argument is given.i
00285 Note that you will still need to configure all plugins
00286 after the restore completes.
00287 mysql_client - The path (including filename) of the mysql client
00288 executable.
00289 uncompress - The command (including path, if necessary) to use to
00290 uncompress the backup. If you specify an uncompress
00291 program, the backup file will be assumed to be compressed,
00292 so the command will be run on the file regardless.
00293 If no value is specified for uncompress or if the value
00294 '$d_uncompress' or 'gunzip' is specified, the script will
00295 check to see if the file is actually a gzip-compressed
00296 file, and if so, will first attempt to use the
00297 IO::Uncompress::Gunzip module to uncompress the backup
00298 file, but, if not available, will run the command
00299 specified. Therefore, if IO::Uncompress::Gunzip is
00300 installed and functional, specifying a value for
00301 uncompress is unnecessary.
00302
00303 RESOURCE FILE
00304
00305 The backup resource file specifies values using the same format as described
00306 for the database information file, above, but is intended as a "permanent,"
00307 user-created configuration file. The database information file is intended as a
00308 "single-use" configuration file, often created automatically (i.e. by a
00309 program, such as a script). The backup resource file should be placed at
00310 "~/.mythtv/backuprc" and given appropriate permissions. To be usable by the
00311 script, it must be readable. However, it should be protected as required--i.e.
00312 if the DBPassword is specified, it should be made readable only by the owner.
00313
00314 When specifying a database information file, the resource file is parsed before
00315 the database information file to prevent the resource file from overriding the
00316 information in the database information file. When no database information
00317 file is specified, the resource file is parsed after the MythTV configuration
00318 files, but before the command-line arguments to allow the resource file to
00319 override values in the configuration files and to allow command-line arguments
00320 to override resource file defaults.
00321
00322 options:
00323
00324 --hostname [database hostname]
00325
00326 The hostname (or IP address) which should be used to find the MySQL server.
00327 See DBHostName, above.
00328
00329 --port [database port]
00330
00331 The TCP/IP port number to use for connection to the MySQL server. See
00332 DBPort, above.
00333
00334 --username [database username]
00335
00336 The MySQL username to use for connection to the MySQL server. See
00337 DBUserName, above.
00338
00339 --name [database name]
00340
00341 The name of the database containing the MythTV data. See DBName, above.
00342
00343 Default: $d_db_name
00344
00345 --schemaver [MythTV database schema version]
00346
00347 The MythTV schema version. See DBSchemaVer, above.
00348
00349 --directory [directory]
00350
00351 The directory in which the backup file should be stored. See
00352 DBBackupDirectory, above.
00353
00354 --filename [database backup filename]
00355
00356 The name to use for the database backup file. If not provided, a filename
00357 using a default format will be chosen. See DBBackupFilename, above.
00358
00359 --partial_restore
00360
00361 Do a partial restore (as would be required when setting up MythTV on new
00362 hardware) of only the MythTV recordings and recording rules.
00363
00364 --with_plugin_data
00365
00366 When doing a partial restore, include plugin data. Ignored, unless the
00367 --partial_restore argument is given. Note that you will still need to
00368 configure all plugins after the restore completes.
00369
00370 --restore_xmltvids
00371
00372 Restore channel xmltvids from a backup created with
00373 mythconverg_backup.pl --backup_xmltvids
00374
00375 --mysql_client [path]
00376
00377 The path (including filename) of the mysql client executable. See
00378 mysql_client in the DATABASE INFORMATION FILE description, above.
00379
00380 Default: $d_mysql_client
00381
00382 --uncompress [path]
00383
00384 The command (including path, if necessary) to use to uncompress the
00385 backup. See uncompress in the DATABASE INFORMATION FILE description, above.
00386
00387 Default: $d_uncompress
00388
00389 --drop_database
00390
00391 If specified, and if the database already exists, the script will attempt
00392 to drop the database. This argument may only be used when the
00393 --create_database argument is also specified (see below).
00394
00395 --create_database
00396
00397 If specified, and if the database does not exist or the --drop_database
00398 argument is specified, the script will attempt to create the initial
00399 database. Note that database creation requires a properly configured MySQL
00400 user and permissions. See, also, the MythTV HOWTO (
00401 http://www.mythtv.org/docs/ ) for details on "Setting up the initial
00402 database."
00403
00404 --change_hostname
00405
00406 Specifies that the script should change the hostname of a MythTV frontend
00407 or backend in the database rather than restore a database backup. It is
00408 critical that no MythTV frontends or backends are running when a hostname
00409 is changed.
00410
00411 --new_hostname
00412
00413 Specifies the new hostname. The new_hostname is only used when the
00414 --change_hostname argument is specified.
00415
00416 --old_hostname
00417
00418 Specifies the old hostname. The old_hostname is only used when the
00419 --change_hostname argument is specified.
00420
00421 --help
00422
00423 Show this help text.
00424
00425 --version
00426
00427 Show version information.
00428
00429 --verbose
00430
00431 Show what is happening.
00432
00433 --script_version | -v
00434
00435 Show script version information. This is primarily useful for scripts
00436 or programs needing to parse the version information.
00437
00438 EOF
00439 }
00440 else
00441 {
00442 print "For detailed help:\n\n# $0 --help --help\n\n";
00443 }
00444 exit;
00445 }
00446
00447 sub verbose
00448 {
00449 my $level = shift;
00450 my $error = 0;
00451 if ($level == $verbose_level_error)
00452 {
00453 $error = 1;
00454 }
00455 else
00456 {
00457 return unless ($debug >= $level);
00458 }
00459 print { $error ? STDERR : STDOUT } join("\n", @_), "\n";
00460 }
00461
00462 sub print_configuration
00463 {
00464 verbose($verbose_level_debug,
00465 '',
00466 'Database Information:',
00467 " DBHostName: $mysql_conf{'db_host'}",
00468 " DBPort: $mysql_conf{'db_port'}",
00469 " DBUserName: $mysql_conf{'db_user'}",
00470 ' DBPassword: ' .
00471 ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
00472 # "$mysql_conf{'db_pass'}",
00473 " DBName: $mysql_conf{'db_name'}",
00474 " DBSchemaVer: $mysql_conf{'db_schemaver'}",
00475 " DBBackupDirectory: $backup_conf{'directory'}",
00476 " DBBackupFilename: $backup_conf{'filename'}",
00477 ' drop_database: '.($drop_database ? 'yes' : 'no'),
00478 ' create_database: '.($create_database ? 'yes' : 'no'));
00479 verbose($verbose_level_debug,
00480 '',
00481 'Executables:',
00482 " mysql_client: $mysql_client",
00483 " uncompress: $uncompress");
00484 verbose($verbose_level_debug,
00485 '',
00486 'Miscellaneous:',
00487 ' partial_restore: '.($partial_restore ? 'yes' : 'no'));
00488 if ($partial_restore)
00489 {
00490 verbose($verbose_level_debug,
00491 ' with_plugin_data: '.($with_plugin_data ?
00492 'yes' : 'no'));
00493 }
00494 verbose($verbose_level_debug,
00495 ' restore_xmltvids: '.($restore_xmltvids ? 'yes' : 'no'),
00496 ' change_hostname: '.($change_hostname ? 'yes' : 'no'));
00497 if ($change_hostname)
00498 {
00499 verbose($verbose_level_debug,
00500 ' - old_hostname: '.$old_hostname,
00501 ' - new_hostname: '.$new_hostname);
00502 }
00503 }
00504
00505 sub configure_environment
00506 {
00507 verbose($verbose_level_debug,
00508 '', 'Configuring environment:');
00509
00510 # Get the user's login and home directory, so we can look for config files
00511 ($username, $homedir) = (getpwuid $>)[0,7];
00512 $username = $ENV{'USER'} if ($ENV{'USER'});
00513 $homedir = $ENV{'HOME'} if ($ENV{'HOME'});
00514 if ($username && !$homedir)
00515 {
00516 $homedir = "/home/$username";
00517 if (!-e $homedir && -e "/Users/$username")
00518 {
00519 $homedir = "/Users/$username";
00520 }
00521 }
00522 verbose($verbose_level_debug,
00523 " - username: $username",
00524 " - HOME: $homedir");
00525
00526 # Find the config directory
00527 $mythconfdir = $ENV{'MYTHCONFDIR'}
00528 ? $ENV{'MYTHCONFDIR'}
00529 : "$homedir/.mythtv"
00530 ;
00531
00532 verbose($verbose_level_debug,
00533 " - MYTHCONFDIR: $mythconfdir");
00534 }
00535
00536 # Though much of the configuration file parsing could be done by the MythTV
00537 # Perl bindings, using them to retrieve database information is not appropriate
00538 # for a backup script. The Perl bindings require the backend to be running and
00539 # use UPnP for autodiscovery. Also, parsing the files "locally" allows
00540 # supporting even the old MythTV database configuration file, mysql.txt.
00541 sub parse_database_information
00542 {
00543 my $file = shift;
00544 verbose($verbose_level_debug,
00545 " - checking: $file");
00546 return 0 unless ($file && -e $file);
00547 verbose($verbose_level_debug,
00548 " parsing: $file");
00549 open(CONF, $file) or die("\nERROR: Unable to read $file: $!".
00550 ', stopped');
00551 while (my $line = <CONF>)
00552 {
00553 # Cleanup
00554 next if ($line =~ m/^\s*#/);
00555 $line =~ s/^str
00556 chomp($line);
00557 $line =~ s/^\s+
00558 $line =~ s/\s+$
00559 # Split off the var=val pairs
00560 my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
00561 # Also look for <var>val</var> from config.xml
00562 if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
00563 {
00564 $var = $1; $val = $2;
00565 }
00566 next unless ($var && $var =~ m/\w/);
00567 if (($var eq 'Host') || ($var eq 'DBHostName'))
00568 {
00569 $mysql_conf{'db_host'} = $val;
00570 }
00571 elsif (($var eq 'Port') || ($var eq 'DBPort'))
00572 {
00573 $mysql_conf{'db_port'} = $val;
00574 }
00575 elsif (($var eq 'UserName') || ($var eq 'DBUserName'))
00576 {
00577 $mysql_conf{'db_user'} = $val;
00578 }
00579 elsif (($var eq 'Password') || ($var eq 'DBPassword'))
00580 {
00581 $mysql_conf{'db_pass'} = $val;
00582 $mysql_conf{'db_pass'} =~ s/&/&/sg;
00583 $mysql_conf{'db_pass'} =~ s/>/>/sg;
00584 $mysql_conf{'db_pass'} =~ s/</</sg;
00585 }
00586 elsif (($var eq 'DatabaseName') || ($var eq 'DBName'))
00587 {
00588 $mysql_conf{'db_name'} = $val;
00589 }
00590 elsif ($var eq 'DBSchemaVer')
00591 {
00592 $mysql_conf{'db_schemaver'} = $val;
00593 }
00594 elsif ($var eq 'DBBackupDirectory')
00595 {
00596 $backup_conf{'directory'} = $val;
00597 }
00598 elsif ($var eq 'DBBackupFilename')
00599 {
00600 $backup_conf{'filename'} = $val;
00601 }
00602 elsif ($var eq 'partial_restore')
00603 {
00604 $partial_restore = $val;
00605 }
00606 elsif ($var eq 'with_plugin_data')
00607 {
00608 $with_plugin_data = $val;
00609 }
00610 elsif ($var eq 'mysql_client')
00611 {
00612 $mysql_client = $val;
00613 }
00614 elsif ($var eq 'uncompress')
00615 {
00616 $uncompress = $val;
00617 }
00618 }
00619 close CONF;
00620 return 1;
00621 }
00622
00623 sub read_mysql_txt
00624 {
00625 # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
00626 # couple places, so try the usual suspects in the same order that mythtv
00627 # does in libs/libmyth/mythcontext.cpp
00628 my $found = 0;
00629 my $result = 0;
00630 my @mysql = ('/usr/local/share/mythtv/mysql.txt',
00631 '/usr/share/mythtv/mysql.txt',
00632 '/usr/local/etc/mythtv/mysql.txt',
00633 '/etc/mythtv/mysql.txt',
00634 $homedir ? "$homedir/.mythtv/mysql.txt" : '',
00635 'mysql.txt',
00636 $mythconfdir ? "$mythconfdir/mysql.txt" : '',
00637 );
00638 foreach my $file (@mysql)
00639 {
00640 $found = parse_database_information($file);
00641 $result = $result + $found;
00642 }
00643 return $result;
00644 }
00645
00646 sub read_resource_file
00647 {
00648 parse_database_information("$mythconfdir/backuprc");
00649 }
00650
00651 sub apply_arguments
00652 {
00653 verbose($verbose_level_debug,
00654 '', 'Applying command-line arguments.');
00655 if ($db_hostname)
00656 {
00657 $mysql_conf{'db_host'} = $db_hostname;
00658 }
00659 if ($db_port)
00660 {
00661 $mysql_conf{'db_port'} = $db_port;
00662 }
00663 if ($db_username)
00664 {
00665 $mysql_conf{'db_user'} = $db_username;
00666 }
00667 # This script does not accept a database password on the command-line.
00668 # if ($db_password)
00669 # {
00670 # $mysql_conf{'db_pass'} = $db_password;
00671 # }
00672 if ($db_name)
00673 {
00674 $mysql_conf{'db_name'} = $db_name;
00675 }
00676 if ($db_schema_version)
00677 {
00678 $mysql_conf{'db_schemaver'} = $db_schema_version;
00679 }
00680 if ($backup_directory)
00681 {
00682 $backup_conf{'directory'} = $backup_directory;
00683 }
00684 if ($backup_filename)
00685 {
00686 $backup_conf{'filename'} = $backup_filename;
00687 }
00688 }
00689
00690 sub read_config
00691 {
00692 my $result = 0;
00693 # If specified, use only the database information file
00694 if ($database_information_file)
00695 {
00696 verbose($verbose_level_debug,
00697 '', 'Database Information File specified. Ignoring all'.
00698 ' command-line arguments');
00699 verbose($verbose_level_debug,
00700 '', 'Database Information File: '.
00701 $database_information_file);
00702 unless (-T "$database_information_file")
00703 {
00704 verbose($verbose_level_always,
00705 '', 'The argument you supplied for the database'.
00706 ' information file is invalid.',
00707 'If you were trying to specify a backup filename,'.
00708 ' please use the --directory ',
00709 'and --filename arguments.');
00710 die("\nERROR: Invalid database information file, stopped");
00711 }
00712 # When using a database information file, parse the resource file first
00713 # so it cannot override database information file settings
00714 read_resource_file;
00715 $result = parse_database_information($database_information_file);
00716 return $result;
00717 }
00718
00719 # No database information file, so try the MythTV configuration files.
00720 verbose($verbose_level_debug,
00721 '', 'Parsing configuration files:');
00722 # Prefer the config.xml file
00723 my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
00724 $result = parse_database_information($file);
00725 if (!$result)
00726 {
00727 # Use the "legacy" mysql.txt file as a fallback
00728 $result = read_mysql_txt;
00729 }
00730 # Read the resource file next to override the config file information, but
00731 # to allow command-line arguments to override resource file "defaults"
00732 read_resource_file;
00733 # Apply the command-line arguments to override the information provided
00734 # by the config file(s).
00735 apply_arguments;
00736 return $result;
00737 }
00738
00739 sub create_defaults_extra_file
00740 {
00741 return '' if (!$mysql_conf{'db_pass'});
00742 verbose($verbose_level_debug,
00743 '', "Attempting to use supplied password for $mysql_client".
00744 ' command-line client.',
00745 'Any [client] or [mysql] password specified in the MySQL'.
00746 ' options file will',
00747 'take precedence.');
00748 # Let tempfile handle unlinking on exit so we don't have to verify that the
00749 # file with $filename is the file we created
00750 my ($fh, $filename) = tempfile(UNLINK => 1);
00751 # Quote the password if it contains # or whitespace or quotes.
00752 # Quoting of values in MySQL options files is only supported on MySQL
00753 # 4.0.16 and above, so only quote if required.
00754 my $quote = '';
00755 my $safe_password = $mysql_conf{'db_pass'};
00756 if ($safe_password =~ /[#'"\s]/)
00757 {
00758 $quote = "'";
00759 $safe_password =~ s/'/\\'/g;
00760 }
00761 print $fh "[client]\npassword=${quote}${safe_password}${quote}\n".
00762 "[mysqldump]\npassword=${quote}${safe_password}${quote}\n";
00763 return $filename;
00764 }
00765
00766 sub check_file_config
00767 {
00768 if (!$backup_conf{'directory'})
00769 {
00770 if (!$backup_conf{'filename'} || (!-r "/$backup_conf{'filename'}"))
00771 {
00772 print_configuration;
00773 die("\nERROR: DBBackupDirectory not specified, stopped");
00774 }
00775 # The user must have specified an absolute path for the
00776 # DBBackupFilename. Though this is not how the script is meant to be
00777 # used, allow it.
00778 $backup_conf{'directory'} = '';
00779 }
00780 elsif (!-d $backup_conf{'directory'})
00781 {
00782 print_configuration;
00783 verbose($verbose_level_error,
00784 '', 'ERROR: DBBackupDirectory is not a directory. Please'.
00785 ' specify a directory in',
00786 ' your database information file using'.
00787 ' DBBackupDirectory.',
00788 ' If not using a database information file,' .
00789 ' please specify the ',
00790 ' --directory command-line option.');
00791 die("\nInvalid backup directory, stopped");
00792 }
00793 if (!$backup_conf{'filename'})
00794 {
00795 # Look for most current backup file
00796 verbose($verbose_level_debug,
00797 '', 'No filename specified. Attempting to find the newest'.
00798 ' database backup.');
00799 if ($restore_xmltvids)
00800 {
00801 $backup_conf{'filename'} = 'mythtv_xmltvid_backup';
00802 }
00803 else
00804 {
00805 $backup_conf{'filename'} = $mysql_conf{'db_name'};
00806 if (!$backup_conf{'filename'})
00807 {
00808 $backup_conf{'filename'} = $d_db_name;
00809 }
00810 }
00811 my @files = <$backup_conf{'directory'}/$backup_conf{'filename'}*>;
00812 @files = grep(!/.*mythconverg_(backup|restore).*\.pl$/, @files);
00813 my $num_files = @files;
00814 if ($num_files < 1)
00815 {
00816 verbose($verbose_level_error,
00817 'ERROR: Unable to find any backup files in'.
00818 ' DBBackupDir and none specified.');
00819 }
00820 else
00821 {
00822 my @sorted_files = sort { lc($b) cmp lc($a) } @files;
00823 $backup_conf{'filename'} = $sorted_files[0];
00824 $backup_conf{'filename'} =~ s#^$backup_conf{'directory'}/?##;
00825 verbose($verbose_level_debug,
00826 'Using database backup file:',
00827 "$backup_conf{'directory'}/$backup_conf{'filename'}");
00828 }
00829 }
00830 if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
00831 {
00832 my $temp_filename = $backup_conf{'filename'};
00833 # Perhaps the user specified some unnecessary path information in the
00834 # filename (i.e. using the shell's filename completion)
00835 $temp_filename =~ s#^.*/##;
00836 if (-e "$backup_conf{'directory'}/$temp_filename")
00837 {
00838 $backup_conf{'filename'} = $temp_filename;
00839 }
00840 else
00841 {
00842 verbose($verbose_level_error,
00843 '', 'ERROR: The specified backup file does not exist.',
00844 "$backup_conf{'directory'}/$backup_conf{'filename'}");
00845 die("\nInvalid backup filename, stopped");
00846 }
00847 }
00848 if ((-d "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
00849 (-p "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
00850 (-S "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
00851 (-b "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
00852 (-c "$backup_conf{'directory'}/$backup_conf{'filename'}") ||
00853 (!-s "$backup_conf{'directory'}/$backup_conf{'filename'}"))
00854 {
00855 verbose($verbose_level_error,
00856 '', 'ERROR: The specified backup file is empty or is'.
00857 ' not a file.',
00858 "$backup_conf{'directory'}/$backup_conf{'filename'}");
00859 die("\nInvalid backup filename, stopped");
00860 }
00861 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
00862 {
00863 verbose($verbose_level_error,
00864 '', 'ERROR: The specified backup file cannot be read.');
00865 die("\nInvalid backup filename, stopped");
00866 }
00867 if (!$mysql_conf{'db_name'})
00868 {
00869 verbose($verbose_level_debug,
00870 '', "WARNING: DBName not specified. Using $d_db_name");
00871 $mysql_conf{'db_name'} = $d_db_name;
00872 }
00873 }
00874
00875 sub check_config
00876 {
00877 verbose($verbose_level_debug,
00878 '', 'Checking configuration.');
00879
00880 if (!defined($change_hostname))
00881 {
00882 # Check directory/filename
00883 check_file_config;
00884 }
00885 # Though the script will attempt a restore even if no other database
00886 # information is provided (i.e. using "defaults" from the MySQL options
00887 # file, warning the user that some "normally-necessary" information is not
00888 # provided may be nice.
00889 return if (!$debug);
00890 if (!$mysql_conf{'db_host'})
00891 {
00892 verbose($verbose_level_debug,
00893 '', 'WARNING: DBHostName not specified.',
00894 ' Assuming it is specified in the MySQL'.
00895 ' options file.');
00896 }
00897 if (!$mysql_conf{'db_user'})
00898 {
00899 verbose($verbose_level_debug,
00900 '', 'WARNING: DBUserName not specified.',
00901 ' Assuming it is specified in the MySQL'.
00902 ' options file.');
00903 }
00904 if (!$mysql_conf{'db_pass'})
00905 {
00906 verbose($verbose_level_debug,
00907 '', 'WARNING: DBPassword not specified.',
00908 ' Assuming it is specified in the MySQL'.
00909 ' options file.');
00910 }
00911 }
00912
00913 sub connect_to_database
00914 {
00915 my $use_db = shift;
00916 my $show_errors = shift;
00917 my $result = 1;
00918 my $connect_string = 'dbi:mysql:database=';
00919 if ($use_db)
00920 {
00921 $connect_string .= $mysql_conf{'db_name'};
00922 }
00923 $connect_string .= ":host=$mysql_conf{'db_host'}";
00924 $dbh->disconnect if (defined($dbh));
00925 $dbh = DBI->connect($connect_string,
00926 "$mysql_conf{'db_user'}",
00927 "$mysql_conf{'db_pass'}",
00928 { PrintError => 0 });
00929 $result = 0 if (!defined($dbh));
00930 if ($show_errors && !defined($dbh))
00931 {
00932 verbose($verbose_level_always,
00933 '', 'Unable to connect to database.',
00934 " database: $mysql_conf{'db_name'}",
00935 " host: $mysql_conf{'db_host'}",
00936 " username: $mysql_conf{'db_user'}"
00937 );
00938 if ($debug < $verbose_level_debug)
00939 {
00940 verbose($verbose_level_always,
00941 'To see the password used, please re-run the script '.
00942 'with the --verbose',
00943 'argument.');
00944 }
00945 # Connection issues will only occur with improper user configuration
00946 # Because they should be rare, output the password with --verbose
00947 verbose($verbose_level_debug,
00948 " password: $mysql_conf{'db_pass'}");
00949 verbose($verbose_level_always,
00950 '', 'Please check your configuration files to verify the'.
00951 ' database connection',
00952 'information is correct. The files that are used to'.
00953 ' retrieve connection',
00954 'information are prefixed with "parsing" in the "Parsing'.
00955 ' configuration files"',
00956 'section of the --verbose output.');
00957 verbose($verbose_level_always,
00958 '', 'Also note that any [client] or [mysql] password'.
00959 ' specified in the MySQL options',
00960 'file (/etc/my.cnf or /etc/mysql/my.cnf or ~/.my.cnf)'.
00961 ' will take precedence over',
00962 'the password specified in the MythTV configuration'.
00963 ' files.');
00964 }
00965 return $result;
00966 }
00967
00968 sub is_database_empty
00969 {
00970 my $result = 1;
00971 connect_to_database(1, 1);
00972 if (!defined($dbh))
00973 {
00974 verbose($verbose_level_error,
00975 '', 'ERROR: Unable to connect to database.');
00976 return -1;
00977 }
00978
00979 if (defined($dbh))
00980 {
00981 my $sth = $dbh->table_info('', '', '', 'TABLE');
00982 my $num_tables = keys %{$sth->fetchall_hashref('TABLE_NAME')};
00983 verbose($verbose_level_debug,
00984 '', "Found $num_tables tables in the database.");
00985 if ($num_tables > 0)
00986 {
00987 if (!defined($change_hostname) && !defined($partial_restore))
00988 {
00989 verbose($verbose_level_debug,
00990 'WARNING: Database not empty.');
00991 }
00992 $result = 0;
00993 }
00994 }
00995 return $result;
00996 }
00997
00998 sub create_initial_database
00999 {
01000 return 0 if (!$create_database && !$drop_database);
01001
01002 my $database_exists = (connect_to_database(1, 0) && defined($dbh));
01003 if ($database_exists)
01004 {
01005 if ($drop_database && !$create_database)
01006 {
01007 verbose($verbose_level_error,
01008 '', 'ERROR: Refusing to drop the database without'.
01009 ' the --create_database argument.',
01010 'If you really want to drop the database, please '.
01011 're-run the script and specify',
01012 'the --create_database argument, too.');
01013 return 2;
01014 }
01015 }
01016 else
01017 {
01018 if (!$create_database)
01019 {
01020 verbose($verbose_level_error,
01021 '', 'ERROR: The database does not exist.');
01022 return 1;
01023 }
01024 }
01025
01026 verbose($verbose_level_debug,
01027 '', 'Preparing initial database.');
01028
01029 my ($query, $sth);
01030
01031 if ($database_exists && $drop_database)
01032 {
01033 verbose($verbose_level_debug, 'Dropping database.');
01034 connect_to_database(0, 1);
01035 if (!defined($dbh))
01036 {
01037 verbose($verbose_level_error,
01038 '', 'ERROR: Unable to connect to database.');
01039 return -1;
01040 }
01041
01042 $query = qq{DROP DATABASE $mysql_conf{'db_name'};};
01043 $sth = $dbh->prepare($query);
01044 if (! $sth->execute())
01045 {
01046 verbose($verbose_level_error,
01047 '', 'ERROR: Unable to drop database.',
01048 $sth->errstr);
01049 return -2;
01050 }
01051 }
01052
01053 connect_to_database(0, 1) if (!defined($dbh));
01054
01055 if (!defined($dbh))
01056 {
01057 verbose($verbose_level_error,
01058 '', 'ERROR: Unable to connect to database.');
01059 return -1;
01060 }
01061
01062 verbose($verbose_level_debug, 'Creating database.');
01063 $query = qq{CREATE DATABASE $mysql_conf{'db_name'};};
01064 $sth = $dbh->prepare($query);
01065 if (! $sth->execute())
01066 {
01067 verbose($verbose_level_error,
01068 '', 'ERROR: Unable to create database.',
01069 $sth->errstr);
01070 return -4;
01071 }
01072
01073 verbose($verbose_level_debug, 'Setting database character set.');
01074 $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
01075 DEFAULT CHARACTER SET latin1
01076 COLLATE latin1_swedish_ci;};
01077 $sth = $dbh->prepare($query);
01078 if (! $sth->execute())
01079 {
01080 verbose($verbose_level_error,
01081 '', 'ERROR: Unable to create database.',
01082 $sth->errstr);
01083 return -8;
01084 }
01085
01086 return 0;
01087 }
01088
01089 sub check_database_libs
01090 {
01091 # Try to load the DBI library if available (but don't require it)
01092 BEGIN
01093 {
01094 our $has_dbi = 1;
01095 eval 'use DBI;';
01096 if ($@)
01097 {
01098 $has_dbi = 0;
01099 }
01100 }
01101 verbose($verbose_level_debug,
01102 '', 'DBI is not installed.') if (!$has_dbi);
01103 # Try to load the DBD::mysql library if available (but don't # require it)
01104 BEGIN
01105 {
01106 our $has_dbd = 1;
01107 eval 'use DBD::mysql;';
01108 if ($@)
01109 {
01110 $has_dbd = 0;
01111 }
01112 }
01113 verbose($verbose_level_debug,
01114 '', 'DBD::mysql is not installed.') if (!$has_dbd);
01115 return ($has_dbi + $has_dbd);
01116 }
01117
01118 sub check_database
01119 {
01120 my $have_database_libs = check_database_libs;
01121 if ($have_database_libs < 2)
01122 {
01123 if ($create_database || $drop_database)
01124 {
01125 verbose($verbose_level_error,
01126 '', 'ERROR: Unable to drop or create the initial '.
01127 'database without Perl database',
01128 ' libraries.',
01129 'Please ensure the Perl DBI and DBD::mysql modules'.
01130 ' are installed.');
01131 die("\nPerl database libraries missing, stopped");
01132 }
01133 if ($change_hostname)
01134 {
01135 verbose($verbose_level_error,
01136 '', 'ERROR: Unable to change hostname without Perl'.
01137 ' database libraries.',
01138 'Please ensure the Perl DBI and DBD::mysql modules'.
01139 ' are installed.');
01140 die("\nPerl database libraries missing, stopped");
01141 }
01142 else
01143 {
01144 verbose($verbose_level_debug,
01145 'Blindly assuming your database is prepared for a'.
01146 ' restore. For better checking,',
01147 'please ensure the Perl DBI and DBD::mysql modules'.
01148 ' are installed.');
01149 return 1;
01150 }
01151 }
01152 # DBI/DBD::mysql are available; check the DB status
01153 verbose($verbose_level_debug,
01154 '', 'Checking database.');
01155 my $initial_database = create_initial_database;
01156 if ($initial_database)
01157 {
01158 return 0;
01159 }
01160 my $database_empty = is_database_empty;
01161 if ($database_empty == -1)
01162 {
01163 # Unable to connect to database
01164 return 0;
01165 }
01166 if ($change_hostname)
01167 {
01168 if ($database_empty)
01169 {
01170 verbose($verbose_level_error,
01171 '', 'ERROR: Unable to change hostname. The database'.
01172 ' is empty.',
01173 ' Please restore a backup, first, then re-run'.
01174 ' this script.');
01175 return 0;
01176 }
01177 }
01178 elsif ($partial_restore)
01179 {
01180 if ($database_empty)
01181 {
01182 verbose($verbose_level_error,
01183 '', 'ERROR: Unable to do a partial restore. The'.
01184 ' database is empty.',
01185 ' Please run mythtv-setup, first, then re-run'.
01186 ' this script.');
01187 return 0;
01188 }
01189 }
01190 else
01191 {
01192 if (!$database_empty)
01193 {
01194 verbose($verbose_level_error,
01195 '', 'ERROR: Unable to do a full restore. The'.
01196 ' database contains data.');
01197 return 0;
01198 }
01199 }
01200 return 1;
01201 }
01202
01203 sub is_gzipped
01204 {
01205 # Simple magic number verification.
01206 # This naive approach works without requiring File::MMagic or any other
01207 # modules.
01208 my $result = 0;
01209 my $magic_number;
01210 my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
01211 open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
01212 or return $result;
01213 binmode(BACKUPFILE);
01214 read(BACKUPFILE, $magic_number, 2);
01215 close(BACKUPFILE);
01216 return ($gzip_magic_number eq $magic_number);
01217 }
01218
01219 # Though it's possible to uncompress the file without writing the uncompressed
01220 # data to a file, doing so is complicated by supporting the use of
01221 # IO::Uncompress::Gunzip /and/ external uncompress programs. Also,
01222 # uncompressing the file separately allows for easier and more precise error
01223 # reporting.
01224 sub uncompress_backup_file
01225 {
01226 if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
01227 {
01228 if (!is_gzipped)
01229 {
01230 verbose($verbose_level_debug,
01231 '', 'Backup file is uncompressed.');
01232 return 0;
01233 }
01234 verbose($verbose_level_debug,
01235 '', 'Backup file is compressed.');
01236 # Try to load the IO::Uncompress::Gunzip library if available (but
01237 # don't require it)
01238 BEGIN
01239 {
01240 our $has_uncompress_gunzip = 1;
01241 # Though this does nothing, it prevents an invalid "only used
01242 # once" warning that occurs for users without IO::Uncompress
01243 # installed.
01244 undef $GunzipError;
01245 eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
01246 if ($@)
01247 {
01248 $has_uncompress_gunzip = 0;
01249 }
01250 }
01251 if (!$has_uncompress_gunzip)
01252 {
01253 verbose($verbose_level_debug,
01254 ' - IO::Uncompress::Gunzip is not installed.');
01255 }
01256 else
01257 {
01258 verbose($verbose_level_debug,
01259 ' - Uncompressing backup file with'.
01260 ' IO::Uncompress::Gunzip.');
01261 my ($bfh, $temp_backup_filename) = tempfile(UNLINK => 1);
01262 my $result = gunzip(
01263 "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
01264 $bfh);
01265 if ((defined($result)) &&
01266 (-f "$temp_backup_filename") &&
01267 (-r "$temp_backup_filename") &&
01268 (-s "$temp_backup_filename"))
01269 {
01270 $backup_conf{'directory'} = '';
01271 $backup_conf{'filename'} = "$temp_backup_filename";
01272 return 0;
01273 }
01274 verbose($verbose_level_error,
01275 " ERROR: $GunzipError");
01276 }
01277 }
01278 else
01279 {
01280 verbose($verbose_level_debug,
01281 '', 'Unrecognized uncompress program.'.
01282 ' Assuming backup file is compressed.',
01283 ' - If the file is not compressed, please do not specify'.
01284 ' a custom uncompress',
01285 ' program name.');
01286 }
01287 # Try to uncompress the file with the uncompress binary.
01288 # With the approach, the original backup file will be uncompressed and
01289 # left uncompressed.
01290 my $safe_uncompress = $uncompress;
01291 $safe_uncompress =~ s/'/'\\''/sg;
01292 verbose($verbose_level_debug,
01293 " - Uncompressing backup file with $uncompress.",
01294 ' The original backup file will be left uncompressed.'.
01295 ' Please recompress,',
01296 ' if desired.');
01297 my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
01298 $backup_path =~ s/'/'\\''/sg;
01299 my $output = `'$safe_uncompress' '$backup_path' 2>&1`;
01300 my $exit = $? >> 8;
01301 verbose($verbose_level_debug,
01302 '', "$uncompress exited with status: $exit");
01303 if ($exit)
01304 {
01305 verbose($verbose_level_debug,
01306 "$uncompress output:", $output);
01307 }
01308 else
01309 {
01310 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
01311 {
01312 # Assume the final extension was removed by uncompressing.
01313 $backup_conf{'filename'} =~ s/\.\w+$
01314 if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
01315 {
01316 verbose($verbose_level_error,
01317 '',
01318 'ERROR: Unable to find uncompressed backup file.');
01319 die("\nInvalid backup filename, stopped");
01320 }
01321 }
01322 }
01323 return $exit;
01324 }
01325
01326 sub do_hostname_change
01327 {
01328 my $exit = 0;
01329 if (!$new_hostname)
01330 {
01331 $exit++;
01332 verbose($verbose_level_error,
01333 '', 'ERROR: Cannot change hostname without --new_hostname'.
01334 ' value.');
01335 }
01336 if (!$old_hostname)
01337 {
01338 $exit++;
01339 verbose($verbose_level_error,
01340 '', 'ERROR: Cannot change hostname without --old_hostname'.
01341 ' value.');
01342 }
01343 if ($exit > 0)
01344 {
01345 die("\nInvalid --old/--new_hostname value(s) for".
01346 ' --change_hostname, stopped');
01347 }
01348 # Get a list of all tables in the DB.
01349 if (defined($dbh))
01350 {
01351 my $num_tables = 0;
01352 my $sth_tables;
01353 my $table_cat;
01354 my $table_schema;
01355 my $table_name;
01356 my $sth_columns;
01357 my $column_name;
01358 my $query;
01359 my $sth_update;
01360 my $result;
01361
01362 $sth_tables = $dbh->table_info('', '', '', 'TABLE');
01363 while (my $table = $sth_tables->fetchrow_hashref)
01364 {
01365 # Loop over all tables in the DB, checking for a hostname column.
01366 $num_tables++;
01367 $table_cat = $table->{'TABLE_CAT'};
01368 $table_schema = $table->{'TABLE_SCHEM'};
01369 $table_name = $table->{'TABLE_NAME'};
01370 $sth_columns = $dbh->column_info($table_cat, $table_schema,
01371 $table_name, '%');
01372 while (my $column = $sth_columns->fetchrow_hashref)
01373 {
01374 # If a hostname column exists, change its value.
01375 $column_name = $column->{'COLUMN_NAME'};
01376 if (($column_name eq 'hostname') ||
01377 ($column_name eq 'host'))
01378 {
01379 verbose($verbose_level_debug,
01380 "Found '$column_name' column in $table_name.");
01381 $query = "UPDATE $table_name SET $column_name = ?".
01382 " WHERE $column_name = ?";
01383 $sth_update = $dbh->prepare($query);
01384 $sth_update->bind_param(1, $new_hostname);
01385 $sth_update->bind_param(2, $old_hostname);
01386 $result = $sth_update->execute;
01387 if (!defined($result))
01388 {
01389 verbose($verbose_level_always,
01390 "Unable to update $column_name in table: ".
01391 $table_name,
01392 $sth_update->errstr);
01393 $exit++;
01394 }
01395 else
01396 {
01397 verbose($verbose_level_debug,
01398 'Updated '.
01399 (($result == 0E0) ? '0' : $result)
01400 ." rows in table: $table_name");
01401 }
01402 last;
01403 }
01404 }
01405 }
01406 if ($num_tables == 0)
01407 {
01408 verbose($verbose_level_always,
01409 'Database is empty. Cannot change hostname.');
01410 return 1;
01411 }
01412 # delete (orphaned) rows with hostname coded into chainid in tvchain
01413 # live-<hostname>-2008-06-26T18:43:18
01414 $table_name = 'tvchain';
01415 $query = "DELETE FROM $table_name WHERE chainid LIKE ?";
01416 $sth_update = $dbh->prepare($query);
01417 $sth_update->bind_param(1, '%'.$old_hostname.'%');
01418 $result = $sth_update->execute;
01419 if (!defined($result))
01420 {
01421 verbose($verbose_level_debug,
01422 "Unable to remove orphaned $table_name rows.",
01423 $sth_update->errstr);
01424 }
01425 else
01426 {
01427 verbose($verbose_level_debug,
01428 'Removed '.
01429 (($result == 0E0) ? '0' : $result)
01430 ." orphaned entries in table: $table_name");
01431 }
01432 # hostname coded into SGweightPerDir setting in settings (modify)
01433 # SGweightPerDir:<hostname>:<directory>
01434 $table_name = 'settings';
01435 $query = "UPDATE $table_name SET value = REPLACE(value, ?, ?)".
01436 " WHERE value LIKE ?";
01437 $sth_update = $dbh->prepare($query);
01438 $sth_update->bind_param(1, 'SGweightPerDir:'.$old_hostname.':');
01439 $sth_update->bind_param(2, 'SGweightPerDir:'.$new_hostname.':');
01440 $sth_update->bind_param(3, 'SGweightPerDir:'.$old_hostname.':%');
01441 $result = $sth_update->execute;
01442 if (!defined($result))
01443 {
01444 verbose($verbose_level_always,
01445 'Unable to update SGweightPerDir setting for host.',
01446 $sth_update->errstr);
01447 }
01448 else
01449 {
01450 verbose($verbose_level_debug,
01451 'Updated '.
01452 (($result == 0E0) ? '0' : $result)
01453 .' SGweightPerDir settings.');
01454 }
01455 }
01456 return $exit;
01457 }
01458
01459 sub get_db_schema_ver
01460 {
01461 connect_to_database(1, 1) if (!defined($dbh));
01462 if (!defined($dbh))
01463 {
01464 verbose($verbose_level_error,
01465 '', 'ERROR: Unable to connect to database.');
01466 return -1;
01467 }
01468 my $query = 'SELECT data FROM settings WHERE value = ?';
01469 if (defined($dbh))
01470 {
01471 my $sth = $dbh->prepare($query);
01472 if ($sth->execute('DBSchemaVer'))
01473 {
01474 while (my @data = $sth->fetchrow_array)
01475 {
01476 $mysql_conf{'db_schemaver'} = $data[0];
01477 verbose($verbose_level_debug,
01478 '', 'Found DBSchemaVer:'.
01479 " $mysql_conf{'db_schemaver'}.");
01480 }
01481 }
01482 else
01483 {
01484 verbose($verbose_level_debug,
01485 "Unable to retrieve DBSchemaVer from".
01486 " database.");
01487 }
01488 }
01489
01490 return 0;
01491 }
01492
01493 sub set_database_charset
01494 {
01495 return 0 if (!$create_database && !$drop_database);
01496
01497 if (get_db_schema_ver && ! $mysql_conf{'db_schemaver'})
01498 {
01499 verbose($verbose_level_error,
01500 "Unknown database schema version. Assuming current.");
01501 $mysql_conf{'db_schemaver'} = '1216';
01502 }
01503
01504 if ($mysql_conf{'db_schemaver'} > 1215)
01505 {
01506 connect_to_database(0, 1) if (!defined($dbh));
01507 if (!defined($dbh))
01508 {
01509 verbose($verbose_level_error,
01510 '', 'ERROR: Unable to connect to database.');
01511 return -1;
01512 }
01513
01514 verbose($verbose_level_debug, 'Setting database character set.');
01515
01516 my ($query, $sth);
01517 $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
01518 DEFAULT CHARACTER SET utf8
01519 COLLATE utf8_general_ci;};
01520 $sth = $dbh->prepare($query);
01521 if (! $sth->execute())
01522 {
01523 verbose($verbose_level_error,
01524 '', 'ERROR: Unable to set database character set.',
01525 $sth->errstr);
01526 return -16;
01527 }
01528 }
01529
01530 return 0;
01531 }
01532
01533 sub restore_backup
01534 {
01535 my $exit = 0;
01536 my $defaults_extra_file = create_defaults_extra_file;
01537 my $host_arg = '';
01538 my $port_arg = '';
01539 my $user_arg = '';
01540 my $filter = '';
01541 if ($defaults_extra_file)
01542 {
01543 $defaults_arg = " --defaults-extra-file='$defaults_extra_file'";
01544 }
01545 else
01546 {
01547 $defaults_arg = '';
01548 }
01549 my $safe_mysql_client = $mysql_client;
01550 $safe_mysql_client =~ s/'/'\\''/g;
01551 # Create the args for host, port, and user, shell-escaping values, as
01552 # necessary.
01553 my $safe_string;
01554 if ($mysql_conf{'db_host'})
01555 {
01556 $safe_string = $mysql_conf{'db_host'};
01557 $safe_string =~ s/'/'\\''/g;
01558 $host_arg = " --host='$safe_string'";
01559 }
01560 if ($mysql_conf{'db_port'} > 0)
01561 {
01562 $safe_string = $mysql_conf{'db_port'};
01563 $safe_string =~ s/'/'\\''/g;
01564 $port_arg = " --port='$safe_string'";
01565 }
01566 if ($mysql_conf{'db_user'})
01567 {
01568 $safe_string = $mysql_conf{'db_user'};
01569 $safe_string =~ s/'/'\\''/g;
01570 $user_arg = " --user='$safe_string'";
01571 }
01572 # Configure a filter for a partial/new-host restore
01573 if ($partial_restore)
01574 {
01575 my @partial_restore_tables;
01576 if (defined($with_plugin_data))
01577 {
01578 # Blacklist the MythTV tables we don't want to keep
01579 # This may result in keeping old tables that were dropped in
01580 # previous DB schema updates if the user is running a restore
01581 # script from an older version of MythTV, but the extra data will
01582 # only take a bit of hard drive space.
01583 @partial_restore_tables = ('callsignnetworkmap',
01584 'capturecard',
01585 'cardinput',
01586 'channel',
01587 'channelgroup',
01588 'channelgroupnames',
01589 'channelscan',
01590 'channelscan_channel',
01591 'channelscan_dtv_multiplex',
01592 'codecparams',
01593 'conflictresolutionany', # historic
01594 'conflictresolutionoverride', # hst
01595 'conflictresolutionsingle', # hst
01596 'credits',
01597 'customexample',
01598 'diseqc_config',
01599 'diseqc_tree',
01600 'displayprofilegroups',
01601 'displayprofiles',
01602 'dtv_multiplex',
01603 'dtv_privatetypes',
01604 'dvb_channel', # historic
01605 'dvb_pids', # historic
01606 'dvb_sat', # historic
01607 'dvb_signal_quality', # historic
01608 'dvb_transport', # historic
01609 'eit_cache',
01610 'favorites',
01611 'filemarkup',
01612 'housekeeping',
01613 'inputgroup',
01614 'inuseprograms',
01615 'jobqueue',
01616 'jumppoints',
01617 'keybindings',
01618 'keyword',
01619 'mythlog',
01620 'networkiconmap',
01621 'oldfind',
01622 'oldprogram',
01623 'people',
01624 'pidcache',
01625 'playgroup',
01626 'powerpriority',
01627 'profilegroups',
01628 'program',
01629 'programgenres',
01630 'programrating',
01631 'recgrouppassword',
01632 'recordedcredits',
01633 'recordedfile',
01634 'recordedprogram',
01635 'recordingprofiles',
01636 'recordmatch',
01637 'recordoverride', # historic
01638 'record_tmp',
01639 'schemalock',
01640 'settings',
01641 'storagegroup',
01642 'transcoding', # historic
01643 'tvchain',
01644 'tvosdmenu',
01645 'upnpmedia',
01646 'videobookmarks', # historic
01647 'videosource',
01648 'xvmc_buffer_settings' # historic
01649 );
01650 }
01651 else
01652 {
01653 # Whitelist the tables we want to keep
01654 @partial_restore_tables = ('oldrecorded',
01655 'record',
01656 'recorded',
01657 'recordedmarkup',
01658 'recordedprogram',
01659 'recordedrating',
01660 'recordedseek');
01661 }
01662 if (!defined($restore_xmltvids))
01663 {
01664 $filter = '^INSERT INTO \`?(' .
01665 join('|', @partial_restore_tables).')\`? ';
01666 # If doing a whitelist restore, ensure we keep the character
01667 # set info to prevent data corruption
01668 if (!defined($with_plugin_data))
01669 {
01670 $filter = '(40101 SET NAMES |'.$filter.')';
01671 }
01672 verbose($verbose_level_debug,
01673 '', 'Restoring partial backup with filter:', $filter);
01674 }
01675 }
01676 my $safe_db_name = $mysql_conf{'db_name'};
01677 $safe_db_name =~ s/'/'\\''/g;
01678 my $command = "'${safe_mysql_client}'${defaults_arg}${host_arg}".
01679 "${port_arg}${user_arg} '$safe_db_name'";
01680 verbose($verbose_level_debug,
01681 '', 'Executing command:', $command);
01682 my $read_status = open(BACKUP,
01683 "<$backup_conf{'directory'}/$backup_conf{'filename'}");
01684 if (!defined($read_status))
01685 {
01686 verbose($verbose_level_error,
01687 '', 'ERROR: Unable to read backup file.');
01688 return 255;
01689 }
01690 my $write_status = open(COMMAND, "| $command");
01691 if (!defined($write_status))
01692 {
01693 verbose($verbose_level_error,
01694 '', "ERROR: Unable to execute $mysql_client.");
01695 return 254;
01696 }
01697 my $lines_total = 0;
01698 my $lines_restored = 0;
01699 while (<BACKUP>)
01700 {
01701 $lines_total++;
01702 if ($partial_restore)
01703 {
01704 if ($restore_xmltvids)
01705 {
01706 # Send all lines through
01707 }
01708 if ($with_plugin_data)
01709 {
01710 # Skip tables in the blacklist
01711 next if /$filter/;
01712 }
01713 else
01714 {
01715 # Skip tables not in the whitelist
01716 next if !/$filter/;
01717 }
01718 }
01719 $lines_restored++;
01720 print COMMAND or die("\nERROR: Cannot write to ".
01721 "$mysql_client, stopped");
01722 }
01723 close(COMMAND);
01724 close(BACKUP);
01725 $exit = $?;
01726 verbose($verbose_level_debug,
01727 '', "$mysql_client exited with status: $exit",
01728 '', "Restored $lines_restored of $lines_total lines.");
01729 return $exit;
01730 }
01731
01732 ##############################################################################
01733 # Main functionality
01734 ##############################################################################
01735
01736 # The first argument after option parsing, if it exists, should be a database
01737 # information file.
01738 $database_information_file = shift;
01739
01740 configure_environment;
01741 read_config;
01742 check_config;
01743
01744 print_configuration;
01745
01746 my $status = 1;
01747 if (check_database)
01748 {
01749 if ($change_hostname)
01750 {
01751 $status = do_hostname_change;
01752 verbose($verbose_level_always,
01753 '', 'Successfully changed hostname.') if (!$status);
01754 }
01755 elsif (!uncompress_backup_file)
01756 {
01757 $status = restore_backup;
01758 if (!$status)
01759 {
01760 verbose($verbose_level_always,
01761 '', 'Successfully restored backup.');
01762 $status = set_database_charset;
01763 }
01764 }
01765 }
01766
01767 $dbh->disconnect if (defined($dbh));
01768
01769 exit $status;
01770