MythTV  master
mythconverg_restore.pl
Go to the documentation of this file.
1 #!/usr/bin/perl -w
2 #
3 # mythconverg_restore.pl
4 #
5 # Restores a backup of the MythTV database.
6 #
7 # For details, see:
8 # mythconverg_restore.pl --help
9 
10 # Includes
11  use Getopt::Long;
12  use File::Temp qw/ tempfile /;
13 
14 # Script info
15  $NAME = 'MythTV Database Restore Script';
16  $VERSION = '1.0.20';
17 
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.
28 # our ($db_password);
29  our ($backup_directory, $backup_filename);
30  our ($verbose_level_always, $verbose_level_debug, $verbose_level_error);
31 
32  our %mysql_conf = ('db_host' => '',
33  'db_port' => -1,
34  'db_user' => '',
35  'db_pass' => '',
36  'db_name' => '',
37  'db_schemaver' => ''
38  );
39  our %backup_conf = ('directory' => '',
40  'filename' => ''
41  );
42 
43 # Debug levels
44  $verbose_level_always = 0;
45  $verbose_level_debug = 1;
46  $verbose_level_error = 255;
47 
48 # Defaults
49  $d_db_name = 'mythconverg';
50  $d_mysql_client = 'mysql';
51  $d_uncompress = 'gzip -d';
52 
53 # Provide default values for GetOptions
54  $mysql_client = $d_mysql_client;
55  $uncompress = $d_uncompress;
56  $debug = 0;
57  $new_hostname = '';
58  $old_hostname = '';
59 
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,
74  'restore_xmltvids|'.
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
89  );
90 
91  $partial_restore ||= $restore_xmltvids;
92 
93 # Print version information
94  sub print_version_information
95  {
96  my $script_name = substr $0, rindex($0, '/') + 1;
97  print "$NAME\n$script_name\nversion: $VERSION\n";
98  }
99 
100  if ($show_version_script)
101  {
102  print "$NAME,$VERSION,,\n";
103  exit;
104  }
105  elsif ($show_version)
106  {
107  print_version_information;
108  exit;
109  }
110 
111 # Print usage
112  if ($usage)
113  {
114  print_version_information;
115  print <<EOF;
116 
117 Usage:
118  $0 [options|database_information_file]
119 
120 Restores a backup of the MythTV database.
121 
122 QUICK START:
123 
124 Create a file ~/.mythtv/backuprc with a single line,
125 "DBBackupDirectory=/home/mythtv" (no quotes). For example:
126 
127 # echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
128 
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:
133 
134 # mysql -umythtv -p mythconverg -e "DROP DATABASE IF EXISTS mythconverg;"
135 
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).
142 
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
145 happening:
146 
147 # $0 --verbose
148 
149 or specify a backup file with:
150 
151 # $0 --directory=/path/to/backups/ --filename=backup_file.sql.gz --verbose
152 
153 (You may leave out the --directory argument if you've specified the directory
154 in the ~/.mythtv/backuprc .)
155 
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.
159 
160 To change the hostname of a MythTV frontend or backend:
161 
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:
165 
166 # $0 --change_hostname --old_hostname="XXXX" --new_hostname="YYYY"
167 
168 To restore xmltvids:
169 
170 Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
171 script with the --restore_xmltvids argument.
172 
173 # $0 --restore_xmltvids
174 
175 EOF
176 
177  if ($usage > 1)
178  {
179  print <<EOF;
180 DETAILED DESCRIPTION:
181 
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.
191 
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).
200 
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.
209 
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.
217 
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.
221 
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
229 with:
230 
231 # $0 --partial_restore
232 
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
236 MythTV databases.
237 
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)
242 backup file.
243 
244 DATABASE INFORMATION FILE
245 
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:
250 
251  DBHostName - The hostname (or IP address) which should be used to find the
252  MySQL server.
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
256  options file.
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
263  argument.
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
279  will be chosen.
280  partial_restore - Do a partial restore (as would be required when setting
281  up MythTV on new hardware) of only the MythTV recordings
282  and recording rules.
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
288  executable.
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.
302 
303 RESOURCE FILE
304 
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.
313 
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.
321 
322 options:
323 
324 --hostname [database hostname]
325 
326  The hostname (or IP address) which should be used to find the MySQL server.
327  See DBHostName, above.
328 
329 --port [database port]
330 
331  The TCP/IP port number to use for connection to the MySQL server. See
332  DBPort, above.
333 
334 --username [database username]
335 
336  The MySQL username to use for connection to the MySQL server. See
337  DBUserName, above.
338 
339 --name [database name]
340 
341  The name of the database containing the MythTV data. See DBName, above.
342 
343  Default: $d_db_name
344 
345 --schemaver [MythTV database schema version]
346 
347  The MythTV schema version. See DBSchemaVer, above.
348 
349 --directory [directory]
350 
351  The directory in which the backup file should be stored. See
352  DBBackupDirectory, above.
353 
354 --filename [database backup filename]
355 
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.
358 
359 --partial_restore
360 
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.
363 
364 --with_plugin_data
365 
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.
369 
370 --restore_xmltvids
371 
372  Restore channel xmltvids from a backup created with
373  mythconverg_backup.pl --backup_xmltvids
374 
375 --mysql_client [path]
376 
377  The path (including filename) of the mysql client executable. See
378  mysql_client in the DATABASE INFORMATION FILE description, above.
379 
380  Default: $d_mysql_client
381 
382 --uncompress [path]
383 
384  The command (including path, if necessary) to use to uncompress the
385  backup. See uncompress in the DATABASE INFORMATION FILE description, above.
386 
387  Default: $d_uncompress
388 
389 --drop_database
390 
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).
394 
395 --create_database
396 
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
402  database."
403 
404 --change_hostname
405 
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
409  is changed.
410 
411 --new_hostname
412 
413  Specifies the new hostname. The new_hostname is only used when the
414  --change_hostname argument is specified.
415 
416 --old_hostname
417 
418  Specifies the old hostname. The old_hostname is only used when the
419  --change_hostname argument is specified.
420 
421 --help
422 
423  Show this help text.
424 
425 --version
426 
427  Show version information.
428 
429 --verbose
430 
431  Show what is happening.
432 
433 --script_version | -v
434 
435  Show script version information. This is primarily useful for scripts
436  or programs needing to parse the version information.
437 
438 EOF
439  }
440  else
441  {
442  print "For detailed help:\n\n# $0 --help --help\n\n";
443  }
444  exit;
445  }
446 
447  sub verbose
448  {
449  my $level = shift;
450  my $error = 0;
451  if ($level == $verbose_level_error)
452  {
453  $error = 1;
454  }
455  else
456  {
457  return unless ($debug >= $level);
458  }
459  print { $error ? STDERR : STDOUT } join("\n", @_), "\n";
460  }
461 
462  sub print_configuration
463  {
464  verbose($verbose_level_debug,
465  '',
466  'Database Information:',
467  " DBHostName: $mysql_conf{'db_host'}",
468  " DBPort: $mysql_conf{'db_port'}",
469  " DBUserName: $mysql_conf{'db_user'}",
470  ' DBPassword: ' .
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,
480  '',
481  'Executables:',
482  " mysql_client: $mysql_client",
483  " uncompress: $uncompress");
484  verbose($verbose_level_debug,
485  '',
486  'Miscellaneous:',
487  ' partial_restore: '.($partial_restore ? 'yes' : 'no'));
488  if ($partial_restore)
489  {
490  verbose($verbose_level_debug,
491  ' with_plugin_data: '.($with_plugin_data ?
492  'yes' : 'no'));
493  }
494  verbose($verbose_level_debug,
495  ' restore_xmltvids: '.($restore_xmltvids ? 'yes' : 'no'),
496  ' change_hostname: '.($change_hostname ? 'yes' : 'no'));
497  if ($change_hostname)
498  {
499  verbose($verbose_level_debug,
500  ' - old_hostname: '.$old_hostname,
501  ' - new_hostname: '.$new_hostname);
502  }
503  }
504 
505  sub configure_environment
506  {
507  verbose($verbose_level_debug,
508  '', 'Configuring environment:');
509 
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)
515  {
516  $homedir = "/home/$username";
517  if (!-e $homedir && -e "/Users/$username")
518  {
519  $homedir = "/Users/$username";
520  }
521  }
522  verbose($verbose_level_debug,
523  " - username: $username",
524  " - HOME: $homedir");
525 
526  # Find the config directory
527  $mythconfdir = $ENV{'MYTHCONFDIR'}
528  ? $ENV{'MYTHCONFDIR'}
529  : "$homedir/.mythtv"
530  ;
531 
532  verbose($verbose_level_debug,
533  " - MYTHCONFDIR: $mythconfdir");
534  }
535 
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
542  {
543  my $file = shift;
544  verbose($verbose_level_debug,
545  " - checking: $file");
546  return 0 unless ($file && -e $file);
547  verbose($verbose_level_debug,
548  " parsing: $file");
549  open(CONF, $file) or die("\nERROR: Unable to read $file: $!".
550  ', stopped');
551  while (my $line = <CONF>)
552  {
553  # Cleanup
554  next if ($line =~ m/^\s*#/);
555  $line =~ s/^str //;
556  chomp($line);
557  $line =~ s/^\s+//;
558  $line =~ s/\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)
563  {
564  $var = $1; $val = $2;
565  }
566  next unless ($var && $var =~ m/\w/);
567  if (($var eq 'Host') || ($var eq 'DBHostName'))
568  {
569  $mysql_conf{'db_host'} = $val;
570  }
571  elsif (($var eq 'Port') || ($var eq 'DBPort'))
572  {
573  $mysql_conf{'db_port'} = $val;
574  }
575  elsif (($var eq 'UserName') || ($var eq 'DBUserName'))
576  {
577  $mysql_conf{'db_user'} = $val;
578  }
579  elsif (($var eq 'Password') || ($var eq 'DBPassword'))
580  {
581  $mysql_conf{'db_pass'} = $val;
582  $mysql_conf{'db_pass'} =~ s/&amp;/&/sg;
583  $mysql_conf{'db_pass'} =~ s/&gt;/>/sg;
584  $mysql_conf{'db_pass'} =~ s/&lt;/</sg;
585  }
586  elsif (($var eq 'DatabaseName') || ($var eq 'DBName'))
587  {
588  $mysql_conf{'db_name'} = $val;
589  }
590  elsif ($var eq 'DBSchemaVer')
591  {
592  $mysql_conf{'db_schemaver'} = $val;
593  }
594  elsif ($var eq 'DBBackupDirectory')
595  {
596  $backup_conf{'directory'} = $val;
597  }
598  elsif ($var eq 'DBBackupFilename')
599  {
600  $backup_conf{'filename'} = $val;
601  }
602  elsif ($var eq 'partial_restore')
603  {
604  $partial_restore = $val;
605  }
606  elsif ($var eq 'with_plugin_data')
607  {
608  $with_plugin_data = $val;
609  }
610  elsif ($var eq 'mysql_client')
611  {
612  $mysql_client = $val;
613  }
614  elsif ($var eq 'uncompress')
615  {
616  $uncompress = $val;
617  }
618  }
619  close CONF;
620  return 1;
621  }
622 
623  sub read_mysql_txt
624  {
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
628  my $found = 0;
629  my $result = 0;
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" : '',
635  'mysql.txt',
636  $mythconfdir ? "$mythconfdir/mysql.txt" : '',
637  );
638  foreach my $file (@mysql)
639  {
640  $found = parse_database_information($file);
641  $result = $result + $found;
642  }
643  return $result;
644  }
645 
646  sub read_resource_file
647  {
648  parse_database_information("$mythconfdir/backuprc");
649  }
650 
651  sub apply_arguments
652  {
653  verbose($verbose_level_debug,
654  '', 'Applying command-line arguments.');
655  if ($db_hostname)
656  {
657  $mysql_conf{'db_host'} = $db_hostname;
658  }
659  if ($db_port)
660  {
661  $mysql_conf{'db_port'} = $db_port;
662  }
663  if ($db_username)
664  {
665  $mysql_conf{'db_user'} = $db_username;
666  }
667  # This script does not accept a database password on the command-line.
668 # if ($db_password)
669 # {
670 # $mysql_conf{'db_pass'} = $db_password;
671 # }
672  if ($db_name)
673  {
674  $mysql_conf{'db_name'} = $db_name;
675  }
676  if ($db_schema_version)
677  {
678  $mysql_conf{'db_schemaver'} = $db_schema_version;
679  }
680  if ($backup_directory)
681  {
682  $backup_conf{'directory'} = $backup_directory;
683  }
684  if ($backup_filename)
685  {
686  $backup_conf{'filename'} = $backup_filename;
687  }
688  }
689 
690  sub read_config
691  {
692  my $result = 0;
693  # If specified, use only the database information file
694  if ($database_information_file)
695  {
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")
703  {
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");
711  }
712  # When using a database information file, parse the resource file first
713  # so it cannot override database information file settings
714  read_resource_file;
715  $result = parse_database_information($database_information_file);
716  return $result;
717  }
718 
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);
725  if (!$result)
726  {
727  # Use the "legacy" mysql.txt file as a fallback
728  $result = read_mysql_txt;
729  }
730  # Read the resource file next to override the config file information, but
731  # to allow command-line arguments to override resource file "defaults"
732  read_resource_file;
733  # Apply the command-line arguments to override the information provided
734  # by the config file(s).
735  apply_arguments;
736  return $result;
737  }
738 
739  sub create_defaults_extra_file
740  {
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',
747  'take precedence.');
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.
754  my $quote = '';
755  my $safe_password = $mysql_conf{'db_pass'};
756  if ($safe_password =~ /[#'"\s]/)
757  {
758  $quote = "'";
759  $safe_password =~ s/'/\\'/g;
760  }
761  print $fh "[client]\npassword=${quote}${safe_password}${quote}\n".
762  "[mysqldump]\npassword=${quote}${safe_password}${quote}\n";
763  return $filename;
764  }
765 
766  sub check_file_config
767  {
768  if (!$backup_conf{'directory'})
769  {
770  if (!$backup_conf{'filename'} || (!-r "/$backup_conf{'filename'}"))
771  {
772  print_configuration;
773  die("\nERROR: DBBackupDirectory not specified, stopped");
774  }
775  # The user must have specified an absolute path for the
776  # DBBackupFilename. Though this is not how the script is meant to be
777  # used, allow it.
778  $backup_conf{'directory'} = '';
779  }
780  elsif (!-d $backup_conf{'directory'})
781  {
782  print_configuration;
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");
792  }
793  if (!$backup_conf{'filename'})
794  {
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)
800  {
801  $backup_conf{'filename'} = 'mythtv_xmltvid_backup';
802  }
803  else
804  {
805  $backup_conf{'filename'} = $mysql_conf{'db_name'};
806  if (!$backup_conf{'filename'})
807  {
808  $backup_conf{'filename'} = $d_db_name;
809  }
810  }
811  my @files = <$backup_conf{'directory'}/$backup_conf{'filename'}*>;
812  @files = grep(!/.*mythconverg_(backup|restore).*\.pl$/, @files);
813  my $num_files = @files;
814  if ($num_files < 1)
815  {
816  verbose($verbose_level_error,
817  'ERROR: Unable to find any backup files in'.
818  ' DBBackupDir and none specified.');
819  }
820  else
821  {
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'}");
828  }
829  }
830  if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
831  {
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")
837  {
838  $backup_conf{'filename'} = $temp_filename;
839  }
840  else
841  {
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");
846  }
847  }
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'}"))
854  {
855  verbose($verbose_level_error,
856  '', 'ERROR: The specified backup file is empty or is'.
857  ' not a file.',
858  "$backup_conf{'directory'}/$backup_conf{'filename'}");
859  die("\nInvalid backup filename, stopped");
860  }
861  if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
862  {
863  verbose($verbose_level_error,
864  '', 'ERROR: The specified backup file cannot be read.');
865  die("\nInvalid backup filename, stopped");
866  }
867  if (!$mysql_conf{'db_name'})
868  {
869  verbose($verbose_level_debug,
870  '', "WARNING: DBName not specified. Using $d_db_name");
871  $mysql_conf{'db_name'} = $d_db_name;
872  }
873  }
874 
875  sub check_config
876  {
877  verbose($verbose_level_debug,
878  '', 'Checking configuration.');
879 
880  if (!defined($change_hostname))
881  {
882  # Check directory/filename
883  check_file_config;
884  }
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.
889  return if (!$debug);
890  if (!$mysql_conf{'db_host'})
891  {
892  verbose($verbose_level_debug,
893  '', 'WARNING: DBHostName not specified.',
894  ' Assuming it is specified in the MySQL'.
895  ' options file.');
896  }
897  if (!$mysql_conf{'db_user'})
898  {
899  verbose($verbose_level_debug,
900  '', 'WARNING: DBUserName not specified.',
901  ' Assuming it is specified in the MySQL'.
902  ' options file.');
903  }
904  if (!$mysql_conf{'db_pass'})
905  {
906  verbose($verbose_level_debug,
907  '', 'WARNING: DBPassword not specified.',
908  ' Assuming it is specified in the MySQL'.
909  ' options file.');
910  }
911  }
912 
913  sub connect_to_database
914  {
915  my $use_db = shift;
916  my $show_errors = shift;
917  my $result = 1;
918  my $connect_string = 'dbi:mysql';
919  my $temp_host = $mysql_conf{'db_host'};
920  if ($temp_host =~ /:/)
921  {
922  if ($temp_host =~ /^(?!\[).*(?!\])$/)
923  {
924  $temp_host = "[$temp_host]";
925  }
926  }
927  $connect_string .= ":host=$temp_host";
928  $connect_string .= ":port=$mysql_conf{'db_port'}";
929  if ($use_db)
930  {
931  $connect_string .= ":database=$mysql_conf{'db_name'}";
932  }
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))
940  {
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'}"
946  );
947  if ($debug < $verbose_level_debug)
948  {
949  verbose($verbose_level_always,
950  'To see the password used, please re-run the script '.
951  'with the --verbose',
952  'argument.');
953  }
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'.
972  ' files.');
973  }
974  return $result;
975  }
976 
977  sub is_database_empty
978  {
979  my $result = 1;
980  connect_to_database(1, 1);
981  if (!defined($dbh))
982  {
983  verbose($verbose_level_error,
984  '', 'ERROR: Unable to connect to database.');
985  return -1;
986  }
987 
988  if (defined($dbh))
989  {
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.");
994  if ($num_tables > 0)
995  {
996  if (!defined($change_hostname) && !defined($partial_restore))
997  {
998  verbose($verbose_level_debug,
999  'WARNING: Database not empty.');
1000  }
1001  $result = 0;
1002  }
1003  }
1004  return $result;
1005  }
1006 
1007  sub create_initial_database
1008  {
1009  return 0 if (!$create_database && !$drop_database);
1010 
1011  my $database_exists = (connect_to_database(1, 0) && defined($dbh));
1012  if ($database_exists)
1013  {
1014  if ($drop_database && !$create_database)
1015  {
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.');
1022  return 2;
1023  }
1024  }
1025  else
1026  {
1027  if (!$create_database)
1028  {
1029  verbose($verbose_level_error,
1030  '', 'ERROR: The database does not exist.');
1031  return 1;
1032  }
1033  }
1034 
1035  verbose($verbose_level_debug,
1036  '', 'Preparing initial database.');
1037 
1038  my ($query, $sth);
1039 
1040  if ($database_exists && $drop_database)
1041  {
1042  verbose($verbose_level_debug, 'Dropping database.');
1043  connect_to_database(0, 1);
1044  if (!defined($dbh))
1045  {
1046  verbose($verbose_level_error,
1047  '', 'ERROR: Unable to connect to database.');
1048  return -1;
1049  }
1050 
1051  $query = qq{DROP DATABASE $mysql_conf{'db_name'};};
1052  $sth = $dbh->prepare($query);
1053  if (! $sth->execute())
1054  {
1055  verbose($verbose_level_error,
1056  '', 'ERROR: Unable to drop database.',
1057  $sth->errstr);
1058  return -2;
1059  }
1060  }
1061 
1062  connect_to_database(0, 1) if (!defined($dbh));
1063 
1064  if (!defined($dbh))
1065  {
1066  verbose($verbose_level_error,
1067  '', 'ERROR: Unable to connect to database.');
1068  return -1;
1069  }
1070 
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())
1075  {
1076  verbose($verbose_level_error,
1077  '', 'ERROR: Unable to create database.',
1078  $sth->errstr);
1079  return -4;
1080  }
1081 
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())
1088  {
1089  verbose($verbose_level_error,
1090  '', 'ERROR: Unable to create database.',
1091  $sth->errstr);
1092  return -8;
1093  }
1094 
1095  return 0;
1096  }
1097 
1098  sub check_database_libs
1099  {
1100  # Try to load the DBI library if available (but don't require it)
1101  BEGIN
1102  {
1103  our $has_dbi = 1;
1104  eval 'use DBI;';
1105  if ($@)
1106  {
1107  $has_dbi = 0;
1108  }
1109  }
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)
1113  BEGIN
1114  {
1115  our $has_dbd = 1;
1116  eval 'use DBD::mysql;';
1117  if ($@)
1118  {
1119  $has_dbd = 0;
1120  }
1121  }
1122  verbose($verbose_level_debug,
1123  '', 'DBD::mysql is not installed.') if (!$has_dbd);
1124  return ($has_dbi + $has_dbd);
1125  }
1126 
1127  sub check_database
1128  {
1129  my $have_database_libs = check_database_libs;
1130  if ($have_database_libs < 2)
1131  {
1132  if ($create_database || $drop_database)
1133  {
1134  verbose($verbose_level_error,
1135  '', 'ERROR: Unable to drop or create the initial '.
1136  'database without Perl database',
1137  ' libraries.',
1138  'Please ensure the Perl DBI and DBD::mysql modules'.
1139  ' are installed.');
1140  die("\nPerl database libraries missing, stopped");
1141  }
1142  if ($change_hostname)
1143  {
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'.
1148  ' are installed.');
1149  die("\nPerl database libraries missing, stopped");
1150  }
1151  else
1152  {
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'.
1157  ' are installed.');
1158  return 1;
1159  }
1160  }
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)
1166  {
1167  return 0;
1168  }
1169  my $database_empty = is_database_empty;
1170  if ($database_empty == -1)
1171  {
1172  # Unable to connect to database
1173  return 0;
1174  }
1175  if ($change_hostname)
1176  {
1177  if ($database_empty)
1178  {
1179  verbose($verbose_level_error,
1180  '', 'ERROR: Unable to change hostname. The database'.
1181  ' is empty.',
1182  ' Please restore a backup, first, then re-run'.
1183  ' this script.');
1184  return 0;
1185  }
1186  }
1187  elsif ($partial_restore)
1188  {
1189  if ($database_empty)
1190  {
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'.
1195  ' this script.');
1196  return 0;
1197  }
1198  }
1199  else
1200  {
1201  if (!$database_empty)
1202  {
1203  verbose($verbose_level_error,
1204  '', 'ERROR: Unable to do a full restore. The'.
1205  ' database contains data.');
1206  return 0;
1207  }
1208  }
1209  return 1;
1210  }
1211 
1212  sub is_gzipped
1213  {
1214  # Simple magic number verification.
1215  # This naive approach works without requiring File::MMagic or any other
1216  # modules.
1217  my $result = 0;
1218  my $magic_number;
1219  my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
1220  open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
1221  or return $result;
1222  binmode(BACKUPFILE);
1223  read(BACKUPFILE, $magic_number, 2);
1224  close(BACKUPFILE);
1225  return ($gzip_magic_number eq $magic_number);
1226  }
1227 
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
1232 # reporting.
1233  sub uncompress_backup_file
1234  {
1235  if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
1236  {
1237  if (!is_gzipped)
1238  {
1239  verbose($verbose_level_debug,
1240  '', 'Backup file is uncompressed.');
1241  return 0;
1242  }
1243  verbose($verbose_level_debug,
1244  '', 'Backup file is compressed.');
1245  # Try to load the IO::Uncompress::Gunzip library if available (but
1246  # don't require it)
1247  BEGIN
1248  {
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
1252  # installed.
1253  undef $GunzipError;
1254  eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
1255  if ($@)
1256  {
1257  $has_uncompress_gunzip = 0;
1258  }
1259  }
1260  if (!$has_uncompress_gunzip)
1261  {
1262  verbose($verbose_level_debug,
1263  ' - IO::Uncompress::Gunzip is not installed.');
1264  }
1265  else
1266  {
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'}" =>
1273  $bfh);
1274  if ((defined($result)) &&
1275  (-f "$temp_backup_filename") &&
1276  (-r "$temp_backup_filename") &&
1277  (-s "$temp_backup_filename"))
1278  {
1279  $backup_conf{'directory'} = '';
1280  $backup_conf{'filename'} = "$temp_backup_filename";
1281  return 0;
1282  }
1283  verbose($verbose_level_error,
1284  " ERROR: $GunzipError");
1285  }
1286  }
1287  else
1288  {
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',
1294  ' program name.');
1295  }
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,',
1305  ' if desired.');
1306  my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
1307  $backup_path =~ s/'/'\\''/sg;
1308  my $output = `'$safe_uncompress' '$backup_path' 2>&1`;
1309  my $exit = $? >> 8;
1310  verbose($verbose_level_debug,
1311  '', "$uncompress exited with status: $exit");
1312  if ($exit)
1313  {
1314  verbose($verbose_level_debug,
1315  "$uncompress output:", $output);
1316  }
1317  else
1318  {
1319  if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1320  {
1321  # Assume the final extension was removed by uncompressing.
1322  $backup_conf{'filename'} =~ s/\.\w+$//;
1323  if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1324  {
1325  verbose($verbose_level_error,
1326  '',
1327  'ERROR: Unable to find uncompressed backup file.');
1328  die("\nInvalid backup filename, stopped");
1329  }
1330  }
1331  }
1332  return $exit;
1333  }
1334 
1335  sub do_hostname_change
1336  {
1337  my $exit = 0;
1338  if (!$new_hostname)
1339  {
1340  $exit++;
1341  verbose($verbose_level_error,
1342  '', 'ERROR: Cannot change hostname without --new_hostname'.
1343  ' value.');
1344  }
1345  if (!$old_hostname)
1346  {
1347  $exit++;
1348  verbose($verbose_level_error,
1349  '', 'ERROR: Cannot change hostname without --old_hostname'.
1350  ' value.');
1351  }
1352  if ($exit > 0)
1353  {
1354  die("\nInvalid --old/--new_hostname value(s) for".
1355  ' --change_hostname, stopped');
1356  }
1357  # Get a list of all tables in the DB.
1358  if (defined($dbh))
1359  {
1360  my $num_tables = 0;
1361  my $sth_tables;
1362  my $table_cat;
1363  my $table_schema;
1364  my $table_name;
1365  my $sth_columns;
1366  my $column_name;
1367  my $query;
1368  my $sth_update;
1369  my $result;
1370 
1371  $sth_tables = $dbh->table_info('', '', '', 'TABLE');
1372  while (my $table = $sth_tables->fetchrow_hashref)
1373  {
1374  # Loop over all tables in the DB, checking for a hostname column.
1375  $num_tables++;
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,
1380  $table_name, '%');
1381  while (my $column = $sth_columns->fetchrow_hashref)
1382  {
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'))
1387  {
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))
1397  {
1398  verbose($verbose_level_always,
1399  "Unable to update $column_name in table: ".
1400  $table_name,
1401  $sth_update->errstr);
1402  $exit++;
1403  }
1404  else
1405  {
1406  verbose($verbose_level_debug,
1407  'Updated '.
1408  (($result == 0E0) ? '0' : $result)
1409  ." rows in table: $table_name");
1410  }
1411  last;
1412  }
1413  }
1414  }
1415  if ($num_tables == 0)
1416  {
1417  verbose($verbose_level_always,
1418  'Database is empty. Cannot change hostname.');
1419  return 1;
1420  }
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))
1429  {
1430  verbose($verbose_level_debug,
1431  "Unable to remove orphaned $table_name rows.",
1432  $sth_update->errstr);
1433  }
1434  else
1435  {
1436  verbose($verbose_level_debug,
1437  'Removed '.
1438  (($result == 0E0) ? '0' : $result)
1439  ." orphaned entries in table: $table_name");
1440  }
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))
1452  {
1453  verbose($verbose_level_always,
1454  'Unable to update SGweightPerDir setting for host.',
1455  $sth_update->errstr);
1456  }
1457  else
1458  {
1459  verbose($verbose_level_debug,
1460  'Updated '.
1461  (($result == 0E0) ? '0' : $result)
1462  .' SGweightPerDir settings.');
1463  }
1464  }
1465  return $exit;
1466  }
1467 
1468  sub get_db_schema_ver
1469  {
1470  connect_to_database(1, 1) if (!defined($dbh));
1471  if (!defined($dbh))
1472  {
1473  verbose($verbose_level_error,
1474  '', 'ERROR: Unable to connect to database.');
1475  return -1;
1476  }
1477  my $query = 'SELECT data FROM settings WHERE value = ?';
1478  if (defined($dbh))
1479  {
1480  my $sth = $dbh->prepare($query);
1481  if ($sth->execute('DBSchemaVer'))
1482  {
1483  while (my @data = $sth->fetchrow_array)
1484  {
1485  $mysql_conf{'db_schemaver'} = $data[0];
1486  verbose($verbose_level_debug,
1487  '', 'Found DBSchemaVer:'.
1488  " $mysql_conf{'db_schemaver'}.");
1489  }
1490  }
1491  else
1492  {
1493  verbose($verbose_level_debug,
1494  "Unable to retrieve DBSchemaVer from".
1495  " database.");
1496  }
1497  }
1498 
1499  return 0;
1500  }
1501 
1502  sub set_database_charset
1503  {
1504  return 0 if (!$create_database && !$drop_database);
1505 
1506  if (get_db_schema_ver && ! $mysql_conf{'db_schemaver'})
1507  {
1508  verbose($verbose_level_error,
1509  "Unknown database schema version. Assuming current.");
1510  $mysql_conf{'db_schemaver'} = '1216';
1511  }
1512 
1513  if ($mysql_conf{'db_schemaver'} > 1215)
1514  {
1515  connect_to_database(0, 1) if (!defined($dbh));
1516  if (!defined($dbh))
1517  {
1518  verbose($verbose_level_error,
1519  '', 'ERROR: Unable to connect to database.');
1520  return -1;
1521  }
1522 
1523  verbose($verbose_level_debug, 'Setting database character set.');
1524 
1525  my ($query, $sth);
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())
1531  {
1532  verbose($verbose_level_error,
1533  '', 'ERROR: Unable to set database character set.',
1534  $sth->errstr);
1535  return -16;
1536  }
1537  }
1538 
1539  return 0;
1540  }
1541 
1542  sub restore_backup
1543  {
1544  my $exit = 0;
1545  my $defaults_extra_file = create_defaults_extra_file;
1546  my $host_arg = '';
1547  my $port_arg = '';
1548  my $user_arg = '';
1549  my $filter = '';
1550  if ($defaults_extra_file)
1551  {
1552  $defaults_arg = " --defaults-extra-file='$defaults_extra_file'";
1553  }
1554  else
1555  {
1556  $defaults_arg = '';
1557  }
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
1561  # necessary.
1562  my $safe_string;
1563  if ($mysql_conf{'db_host'})
1564  {
1565  $safe_string = $mysql_conf{'db_host'};
1566  $safe_string =~ s/'/'\\''/g;
1567  $host_arg = " --host='$safe_string'";
1568  }
1569  if ($mysql_conf{'db_port'} > 0)
1570  {
1571  $safe_string = $mysql_conf{'db_port'};
1572  $safe_string =~ s/'/'\\''/g;
1573  $port_arg = " --port='$safe_string'";
1574  }
1575  if ($mysql_conf{'db_user'})
1576  {
1577  $safe_string = $mysql_conf{'db_user'};
1578  $safe_string =~ s/'/'\\''/g;
1579  $user_arg = " --user='$safe_string'";
1580  }
1581  # Configure a filter for a partial/new-host restore
1582  if ($partial_restore)
1583  {
1584  my @partial_restore_tables;
1585  if (defined($with_plugin_data))
1586  {
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
1593  'capturecard',
1594  'cardinput',
1595  'channel',
1596  'channelgroup',
1597  'channelgroupnames',
1598  'channelscan',
1599  'channelscan_channel',
1600  'channelscan_dtv_multiplex',
1601  'codecparams',
1602  'conflictresolutionany', # historic
1603  'conflictresolutionoverride', # hst
1604  'conflictresolutionsingle', # hst
1605  'credits',
1606  'customexample',
1607  'diseqc_config',
1608  'diseqc_tree',
1609  'displayprofilegroups',
1610  'displayprofiles',
1611  'dtv_multiplex',
1612  'dtv_privatetypes',
1613  'dvb_channel', # historic
1614  'dvb_pids', # historic
1615  'dvb_sat', # historic
1616  'dvb_signal_quality', # historic
1617  'dvb_transport', # historic
1618  'eit_cache',
1619  'favorites',
1620  'filemarkup',
1621  'housekeeping',
1622  'inputgroup',
1623  'inuseprograms',
1624  'jobqueue',
1625  'jumppoints',
1626  'keybindings',
1627  'keyword',
1628  'mythlog',
1629  'networkiconmap', # historic
1630  'oldfind',
1631  'oldprogram',
1632  'people',
1633  'pidcache',
1634  'playgroup',
1635  'powerpriority',
1636  'profilegroups',
1637  'program',
1638  'programgenres',
1639  'programrating',
1640  'recgroups',
1641  'recordedcredits',
1642  'recordedfile',
1643  'recordedprogram',
1644  'recordingprofiles',
1645  'recordmatch',
1646  'recordoverride', # historic
1647  'record_tmp',
1648  'schemalock',
1649  'settings',
1650  'storagegroup',
1651  'transcoding', # historic
1652  'tvchain',
1653  'tvosdmenu',
1654  'upnpmedia',
1655  'videobookmarks', # historic
1656  'videosource',
1657  'xvmc_buffer_settings' # historic
1658  );
1659  }
1660  else
1661  {
1662  # Whitelist the tables we want to keep
1663  @partial_restore_tables = ('oldrecorded',
1664  'record',
1665  'recorded',
1666  'recordedmarkup',
1667  'recordedprogram',
1668  'recordedrating',
1669  'recordedseek');
1670  }
1671  if (!defined($restore_xmltvids))
1672  {
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))
1678  {
1679  $filter = '(40101 SET NAMES |'.$filter.')';
1680  }
1681  verbose($verbose_level_debug,
1682  '', 'Restoring partial backup with filter:', $filter);
1683  }
1684  }
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))
1694  {
1695  verbose($verbose_level_error,
1696  '', 'ERROR: Unable to read backup file.');
1697  return 255;
1698  }
1699  my $write_status = open(COMMAND, "| $command");
1700  if (!defined($write_status))
1701  {
1702  verbose($verbose_level_error,
1703  '', "ERROR: Unable to execute $mysql_client.");
1704  return 254;
1705  }
1706  my $lines_total = 0;
1707  my $lines_restored = 0;
1708  while (<BACKUP>)
1709  {
1710  $lines_total++;
1711  if ($partial_restore)
1712  {
1713  if ($restore_xmltvids)
1714  {
1715  # Send all lines through
1716  }
1717  if ($with_plugin_data)
1718  {
1719  # Skip tables in the blacklist
1720  next if /$filter/;
1721  }
1722  else
1723  {
1724  # Skip tables not in the whitelist
1725  next if !/$filter/;
1726  }
1727  }
1728  $lines_restored++;
1729  print COMMAND or die("\nERROR: Cannot write to ".
1730  "$mysql_client, stopped");
1731  }
1732  close(COMMAND);
1733  close(BACKUP);
1734  $exit = $?;
1735  verbose($verbose_level_debug,
1736  '', "$mysql_client exited with status: $exit",
1737  '', "Restored $lines_restored of $lines_total lines.");
1738  return $exit;
1739  }
1740 
1741 ##############################################################################
1742 # Main functionality
1743 ##############################################################################
1744 
1745 # The first argument after option parsing, if it exists, should be a database
1746 # information file.
1747  $database_information_file = shift;
1748 
1749  configure_environment;
1750  read_config;
1751  check_config;
1752 
1753  print_configuration;
1754 
1755  my $status = 1;
1756  if (check_database)
1757  {
1758  if ($change_hostname)
1759  {
1760  $status = do_hostname_change;
1761  verbose($verbose_level_always,
1762  '', 'Successfully changed hostname.') if (!$status);
1763  }
1764  elsif (!uncompress_backup_file)
1765  {
1766  $status = restore_backup;
1767  if (!$status)
1768  {
1769  verbose($verbose_level_always,
1770  '', 'Successfully restored backup.');
1771  $status = set_database_charset;
1772  }
1773  }
1774  }
1775 
1776  $dbh->disconnect if (defined($dbh));
1777 
1778  exit $status;
1779