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.19';
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  if ($use_db)
929  {
930  $connect_string .= ":database=$mysql_conf{'db_name'}";
931  }
932  $dbh->disconnect if (defined($dbh));
933  $dbh = DBI->connect($connect_string,
934  "$mysql_conf{'db_user'}",
935  "$mysql_conf{'db_pass'}",
936  { PrintError => 1 });
937  $result = 0 if (!defined($dbh));
938  if ($show_errors && !defined($dbh))
939  {
940  verbose($verbose_level_always,
941  '', 'Unable to connect to database.',
942  " database: $mysql_conf{'db_name'}",
943  " host: $mysql_conf{'db_host'}",
944  " username: $mysql_conf{'db_user'}"
945  );
946  if ($debug < $verbose_level_debug)
947  {
948  verbose($verbose_level_always,
949  'To see the password used, please re-run the script '.
950  'with the --verbose',
951  'argument.');
952  }
953  # Connection issues will only occur with improper user configuration
954  # Because they should be rare, output the password with --verbose
955  verbose($verbose_level_debug,
956  " password: $mysql_conf{'db_pass'}");
957  verbose($verbose_level_always,
958  '', 'Please check your configuration files to verify the'.
959  ' database connection',
960  'information is correct. The files that are used to'.
961  ' retrieve connection',
962  'information are prefixed with "parsing" in the "Parsing'.
963  ' configuration files"',
964  'section of the --verbose output.');
965  verbose($verbose_level_always,
966  '', 'Also note that any [client] or [mysql] password'.
967  ' specified in the MySQL options',
968  'file (/etc/my.cnf or /etc/mysql/my.cnf or ~/.my.cnf)'.
969  ' will take precedence over',
970  'the password specified in the MythTV configuration'.
971  ' files.');
972  }
973  return $result;
974  }
975 
976  sub is_database_empty
977  {
978  my $result = 1;
979  connect_to_database(1, 1);
980  if (!defined($dbh))
981  {
982  verbose($verbose_level_error,
983  '', 'ERROR: Unable to connect to database.');
984  return -1;
985  }
986 
987  if (defined($dbh))
988  {
989  my $sth = $dbh->table_info('', '', '', 'TABLE');
990  my $num_tables = keys %{$sth->fetchall_hashref('TABLE_NAME')};
991  verbose($verbose_level_debug,
992  '', "Found $num_tables tables in the database.");
993  if ($num_tables > 0)
994  {
995  if (!defined($change_hostname) && !defined($partial_restore))
996  {
997  verbose($verbose_level_debug,
998  'WARNING: Database not empty.');
999  }
1000  $result = 0;
1001  }
1002  }
1003  return $result;
1004  }
1005 
1006  sub create_initial_database
1007  {
1008  return 0 if (!$create_database && !$drop_database);
1009 
1010  my $database_exists = (connect_to_database(1, 0) && defined($dbh));
1011  if ($database_exists)
1012  {
1013  if ($drop_database && !$create_database)
1014  {
1015  verbose($verbose_level_error,
1016  '', 'ERROR: Refusing to drop the database without'.
1017  ' the --create_database argument.',
1018  'If you really want to drop the database, please '.
1019  're-run the script and specify',
1020  'the --create_database argument, too.');
1021  return 2;
1022  }
1023  }
1024  else
1025  {
1026  if (!$create_database)
1027  {
1028  verbose($verbose_level_error,
1029  '', 'ERROR: The database does not exist.');
1030  return 1;
1031  }
1032  }
1033 
1034  verbose($verbose_level_debug,
1035  '', 'Preparing initial database.');
1036 
1037  my ($query, $sth);
1038 
1039  if ($database_exists && $drop_database)
1040  {
1041  verbose($verbose_level_debug, 'Dropping database.');
1042  connect_to_database(0, 1);
1043  if (!defined($dbh))
1044  {
1045  verbose($verbose_level_error,
1046  '', 'ERROR: Unable to connect to database.');
1047  return -1;
1048  }
1049 
1050  $query = qq{DROP DATABASE $mysql_conf{'db_name'};};
1051  $sth = $dbh->prepare($query);
1052  if (! $sth->execute())
1053  {
1054  verbose($verbose_level_error,
1055  '', 'ERROR: Unable to drop database.',
1056  $sth->errstr);
1057  return -2;
1058  }
1059  }
1060 
1061  connect_to_database(0, 1) if (!defined($dbh));
1062 
1063  if (!defined($dbh))
1064  {
1065  verbose($verbose_level_error,
1066  '', 'ERROR: Unable to connect to database.');
1067  return -1;
1068  }
1069 
1070  verbose($verbose_level_debug, 'Creating database.');
1071  $query = qq{CREATE DATABASE $mysql_conf{'db_name'};};
1072  $sth = $dbh->prepare($query);
1073  if (! $sth->execute())
1074  {
1075  verbose($verbose_level_error,
1076  '', 'ERROR: Unable to create database.',
1077  $sth->errstr);
1078  return -4;
1079  }
1080 
1081  verbose($verbose_level_debug, 'Setting database character set.');
1082  $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
1083  DEFAULT CHARACTER SET latin1
1084  COLLATE latin1_swedish_ci;};
1085  $sth = $dbh->prepare($query);
1086  if (! $sth->execute())
1087  {
1088  verbose($verbose_level_error,
1089  '', 'ERROR: Unable to create database.',
1090  $sth->errstr);
1091  return -8;
1092  }
1093 
1094  return 0;
1095  }
1096 
1097  sub check_database_libs
1098  {
1099  # Try to load the DBI library if available (but don't require it)
1100  BEGIN
1101  {
1102  our $has_dbi = 1;
1103  eval 'use DBI;';
1104  if ($@)
1105  {
1106  $has_dbi = 0;
1107  }
1108  }
1109  verbose($verbose_level_debug,
1110  '', 'DBI is not installed.') if (!$has_dbi);
1111  # Try to load the DBD::mysql library if available (but don't # require it)
1112  BEGIN
1113  {
1114  our $has_dbd = 1;
1115  eval 'use DBD::mysql;';
1116  if ($@)
1117  {
1118  $has_dbd = 0;
1119  }
1120  }
1121  verbose($verbose_level_debug,
1122  '', 'DBD::mysql is not installed.') if (!$has_dbd);
1123  return ($has_dbi + $has_dbd);
1124  }
1125 
1126  sub check_database
1127  {
1128  my $have_database_libs = check_database_libs;
1129  if ($have_database_libs < 2)
1130  {
1131  if ($create_database || $drop_database)
1132  {
1133  verbose($verbose_level_error,
1134  '', 'ERROR: Unable to drop or create the initial '.
1135  'database without Perl database',
1136  ' libraries.',
1137  'Please ensure the Perl DBI and DBD::mysql modules'.
1138  ' are installed.');
1139  die("\nPerl database libraries missing, stopped");
1140  }
1141  if ($change_hostname)
1142  {
1143  verbose($verbose_level_error,
1144  '', 'ERROR: Unable to change hostname without Perl'.
1145  ' database libraries.',
1146  'Please ensure the Perl DBI and DBD::mysql modules'.
1147  ' are installed.');
1148  die("\nPerl database libraries missing, stopped");
1149  }
1150  else
1151  {
1152  verbose($verbose_level_debug,
1153  'Blindly assuming your database is prepared for a'.
1154  ' restore. For better checking,',
1155  'please ensure the Perl DBI and DBD::mysql modules'.
1156  ' are installed.');
1157  return 1;
1158  }
1159  }
1160  # DBI/DBD::mysql are available; check the DB status
1161  verbose($verbose_level_debug,
1162  '', 'Checking database.');
1163  my $initial_database = create_initial_database;
1164  if ($initial_database)
1165  {
1166  return 0;
1167  }
1168  my $database_empty = is_database_empty;
1169  if ($database_empty == -1)
1170  {
1171  # Unable to connect to database
1172  return 0;
1173  }
1174  if ($change_hostname)
1175  {
1176  if ($database_empty)
1177  {
1178  verbose($verbose_level_error,
1179  '', 'ERROR: Unable to change hostname. The database'.
1180  ' is empty.',
1181  ' Please restore a backup, first, then re-run'.
1182  ' this script.');
1183  return 0;
1184  }
1185  }
1186  elsif ($partial_restore)
1187  {
1188  if ($database_empty)
1189  {
1190  verbose($verbose_level_error,
1191  '', 'ERROR: Unable to do a partial restore. The'.
1192  ' database is empty.',
1193  ' Please run mythtv-setup, first, then re-run'.
1194  ' this script.');
1195  return 0;
1196  }
1197  }
1198  else
1199  {
1200  if (!$database_empty)
1201  {
1202  verbose($verbose_level_error,
1203  '', 'ERROR: Unable to do a full restore. The'.
1204  ' database contains data.');
1205  return 0;
1206  }
1207  }
1208  return 1;
1209  }
1210 
1211  sub is_gzipped
1212  {
1213  # Simple magic number verification.
1214  # This naive approach works without requiring File::MMagic or any other
1215  # modules.
1216  my $result = 0;
1217  my $magic_number;
1218  my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
1219  open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
1220  or return $result;
1221  binmode(BACKUPFILE);
1222  read(BACKUPFILE, $magic_number, 2);
1223  close(BACKUPFILE);
1224  return ($gzip_magic_number eq $magic_number);
1225  }
1226 
1227 # Though it's possible to uncompress the file without writing the uncompressed
1228 # data to a file, doing so is complicated by supporting the use of
1229 # IO::Uncompress::Gunzip /and/ external uncompress programs. Also,
1230 # uncompressing the file separately allows for easier and more precise error
1231 # reporting.
1232  sub uncompress_backup_file
1233  {
1234  if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
1235  {
1236  if (!is_gzipped)
1237  {
1238  verbose($verbose_level_debug,
1239  '', 'Backup file is uncompressed.');
1240  return 0;
1241  }
1242  verbose($verbose_level_debug,
1243  '', 'Backup file is compressed.');
1244  # Try to load the IO::Uncompress::Gunzip library if available (but
1245  # don't require it)
1246  BEGIN
1247  {
1248  our $has_uncompress_gunzip = 1;
1249  # Though this does nothing, it prevents an invalid "only used
1250  # once" warning that occurs for users without IO::Uncompress
1251  # installed.
1252  undef $GunzipError;
1253  eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
1254  if ($@)
1255  {
1256  $has_uncompress_gunzip = 0;
1257  }
1258  }
1259  if (!$has_uncompress_gunzip)
1260  {
1261  verbose($verbose_level_debug,
1262  ' - IO::Uncompress::Gunzip is not installed.');
1263  }
1264  else
1265  {
1266  verbose($verbose_level_debug,
1267  ' - Uncompressing backup file with'.
1268  ' IO::Uncompress::Gunzip.');
1269  my ($bfh, $temp_backup_filename) = tempfile(UNLINK => 1);
1270  my $result = gunzip(
1271  "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
1272  $bfh);
1273  if ((defined($result)) &&
1274  (-f "$temp_backup_filename") &&
1275  (-r "$temp_backup_filename") &&
1276  (-s "$temp_backup_filename"))
1277  {
1278  $backup_conf{'directory'} = '';
1279  $backup_conf{'filename'} = "$temp_backup_filename";
1280  return 0;
1281  }
1282  verbose($verbose_level_error,
1283  " ERROR: $GunzipError");
1284  }
1285  }
1286  else
1287  {
1288  verbose($verbose_level_debug,
1289  '', 'Unrecognized uncompress program.'.
1290  ' Assuming backup file is compressed.',
1291  ' - If the file is not compressed, please do not specify'.
1292  ' a custom uncompress',
1293  ' program name.');
1294  }
1295  # Try to uncompress the file with the uncompress binary.
1296  # With the approach, the original backup file will be uncompressed and
1297  # left uncompressed.
1298  my $safe_uncompress = $uncompress;
1299  $safe_uncompress =~ s/'/'\\''/sg;
1300  verbose($verbose_level_debug,
1301  " - Uncompressing backup file with $uncompress.",
1302  ' The original backup file will be left uncompressed.'.
1303  ' Please recompress,',
1304  ' if desired.');
1305  my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
1306  $backup_path =~ s/'/'\\''/sg;
1307  my $output = `'$safe_uncompress' '$backup_path' 2>&1`;
1308  my $exit = $? >> 8;
1309  verbose($verbose_level_debug,
1310  '', "$uncompress exited with status: $exit");
1311  if ($exit)
1312  {
1313  verbose($verbose_level_debug,
1314  "$uncompress output:", $output);
1315  }
1316  else
1317  {
1318  if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1319  {
1320  # Assume the final extension was removed by uncompressing.
1321  $backup_conf{'filename'} =~ s/\.\w+$//;
1322  if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
1323  {
1324  verbose($verbose_level_error,
1325  '',
1326  'ERROR: Unable to find uncompressed backup file.');
1327  die("\nInvalid backup filename, stopped");
1328  }
1329  }
1330  }
1331  return $exit;
1332  }
1333 
1334  sub do_hostname_change
1335  {
1336  my $exit = 0;
1337  if (!$new_hostname)
1338  {
1339  $exit++;
1340  verbose($verbose_level_error,
1341  '', 'ERROR: Cannot change hostname without --new_hostname'.
1342  ' value.');
1343  }
1344  if (!$old_hostname)
1345  {
1346  $exit++;
1347  verbose($verbose_level_error,
1348  '', 'ERROR: Cannot change hostname without --old_hostname'.
1349  ' value.');
1350  }
1351  if ($exit > 0)
1352  {
1353  die("\nInvalid --old/--new_hostname value(s) for".
1354  ' --change_hostname, stopped');
1355  }
1356  # Get a list of all tables in the DB.
1357  if (defined($dbh))
1358  {
1359  my $num_tables = 0;
1360  my $sth_tables;
1361  my $table_cat;
1362  my $table_schema;
1363  my $table_name;
1364  my $sth_columns;
1365  my $column_name;
1366  my $query;
1367  my $sth_update;
1368  my $result;
1369 
1370  $sth_tables = $dbh->table_info('', '', '', 'TABLE');
1371  while (my $table = $sth_tables->fetchrow_hashref)
1372  {
1373  # Loop over all tables in the DB, checking for a hostname column.
1374  $num_tables++;
1375  $table_cat = $table->{'TABLE_CAT'};
1376  $table_schema = $table->{'TABLE_SCHEM'};
1377  $table_name = $table->{'TABLE_NAME'};
1378  $sth_columns = $dbh->column_info($table_cat, $table_schema,
1379  $table_name, '%');
1380  while (my $column = $sth_columns->fetchrow_hashref)
1381  {
1382  # If a hostname column exists, change its value.
1383  $column_name = $column->{'COLUMN_NAME'};
1384  if (($column_name eq 'hostname') ||
1385  ($column_name eq 'host'))
1386  {
1387  verbose($verbose_level_debug,
1388  "Found '$column_name' column in $table_name.");
1389  $query = "UPDATE $table_name SET $column_name = ?".
1390  " WHERE $column_name = ?";
1391  $sth_update = $dbh->prepare($query);
1392  $sth_update->bind_param(1, $new_hostname);
1393  $sth_update->bind_param(2, $old_hostname);
1394  $result = $sth_update->execute;
1395  if (!defined($result))
1396  {
1397  verbose($verbose_level_always,
1398  "Unable to update $column_name in table: ".
1399  $table_name,
1400  $sth_update->errstr);
1401  $exit++;
1402  }
1403  else
1404  {
1405  verbose($verbose_level_debug,
1406  'Updated '.
1407  (($result == 0E0) ? '0' : $result)
1408  ." rows in table: $table_name");
1409  }
1410  last;
1411  }
1412  }
1413  }
1414  if ($num_tables == 0)
1415  {
1416  verbose($verbose_level_always,
1417  'Database is empty. Cannot change hostname.');
1418  return 1;
1419  }
1420  # delete (orphaned) rows with hostname coded into chainid in tvchain
1421  # live-<hostname>-2008-06-26T18:43:18
1422  $table_name = 'tvchain';
1423  $query = "DELETE FROM $table_name WHERE chainid LIKE ?";
1424  $sth_update = $dbh->prepare($query);
1425  $sth_update->bind_param(1, '%'.$old_hostname.'%');
1426  $result = $sth_update->execute;
1427  if (!defined($result))
1428  {
1429  verbose($verbose_level_debug,
1430  "Unable to remove orphaned $table_name rows.",
1431  $sth_update->errstr);
1432  }
1433  else
1434  {
1435  verbose($verbose_level_debug,
1436  'Removed '.
1437  (($result == 0E0) ? '0' : $result)
1438  ." orphaned entries in table: $table_name");
1439  }
1440  # hostname coded into SGweightPerDir setting in settings (modify)
1441  # SGweightPerDir:<hostname>:<directory>
1442  $table_name = 'settings';
1443  $query = "UPDATE $table_name SET value = REPLACE(value, ?, ?)".
1444  " WHERE value LIKE ?";
1445  $sth_update = $dbh->prepare($query);
1446  $sth_update->bind_param(1, 'SGweightPerDir:'.$old_hostname.':');
1447  $sth_update->bind_param(2, 'SGweightPerDir:'.$new_hostname.':');
1448  $sth_update->bind_param(3, 'SGweightPerDir:'.$old_hostname.':%');
1449  $result = $sth_update->execute;
1450  if (!defined($result))
1451  {
1452  verbose($verbose_level_always,
1453  'Unable to update SGweightPerDir setting for host.',
1454  $sth_update->errstr);
1455  }
1456  else
1457  {
1458  verbose($verbose_level_debug,
1459  'Updated '.
1460  (($result == 0E0) ? '0' : $result)
1461  .' SGweightPerDir settings.');
1462  }
1463  }
1464  return $exit;
1465  }
1466 
1467  sub get_db_schema_ver
1468  {
1469  connect_to_database(1, 1) if (!defined($dbh));
1470  if (!defined($dbh))
1471  {
1472  verbose($verbose_level_error,
1473  '', 'ERROR: Unable to connect to database.');
1474  return -1;
1475  }
1476  my $query = 'SELECT data FROM settings WHERE value = ?';
1477  if (defined($dbh))
1478  {
1479  my $sth = $dbh->prepare($query);
1480  if ($sth->execute('DBSchemaVer'))
1481  {
1482  while (my @data = $sth->fetchrow_array)
1483  {
1484  $mysql_conf{'db_schemaver'} = $data[0];
1485  verbose($verbose_level_debug,
1486  '', 'Found DBSchemaVer:'.
1487  " $mysql_conf{'db_schemaver'}.");
1488  }
1489  }
1490  else
1491  {
1492  verbose($verbose_level_debug,
1493  "Unable to retrieve DBSchemaVer from".
1494  " database.");
1495  }
1496  }
1497 
1498  return 0;
1499  }
1500 
1501  sub set_database_charset
1502  {
1503  return 0 if (!$create_database && !$drop_database);
1504 
1505  if (get_db_schema_ver && ! $mysql_conf{'db_schemaver'})
1506  {
1507  verbose($verbose_level_error,
1508  "Unknown database schema version. Assuming current.");
1509  $mysql_conf{'db_schemaver'} = '1216';
1510  }
1511 
1512  if ($mysql_conf{'db_schemaver'} > 1215)
1513  {
1514  connect_to_database(0, 1) if (!defined($dbh));
1515  if (!defined($dbh))
1516  {
1517  verbose($verbose_level_error,
1518  '', 'ERROR: Unable to connect to database.');
1519  return -1;
1520  }
1521 
1522  verbose($verbose_level_debug, 'Setting database character set.');
1523 
1524  my ($query, $sth);
1525  $query = qq{ALTER DATABASE $mysql_conf{'db_name'}
1526  DEFAULT CHARACTER SET utf8
1527  COLLATE utf8_general_ci;};
1528  $sth = $dbh->prepare($query);
1529  if (! $sth->execute())
1530  {
1531  verbose($verbose_level_error,
1532  '', 'ERROR: Unable to set database character set.',
1533  $sth->errstr);
1534  return -16;
1535  }
1536  }
1537 
1538  return 0;
1539  }
1540 
1541  sub restore_backup
1542  {
1543  my $exit = 0;
1544  my $defaults_extra_file = create_defaults_extra_file;
1545  my $host_arg = '';
1546  my $port_arg = '';
1547  my $user_arg = '';
1548  my $filter = '';
1549  if ($defaults_extra_file)
1550  {
1551  $defaults_arg = " --defaults-extra-file='$defaults_extra_file'";
1552  }
1553  else
1554  {
1555  $defaults_arg = '';
1556  }
1557  my $safe_mysql_client = $mysql_client;
1558  $safe_mysql_client =~ s/'/'\\''/g;
1559  # Create the args for host, port, and user, shell-escaping values, as
1560  # necessary.
1561  my $safe_string;
1562  if ($mysql_conf{'db_host'})
1563  {
1564  $safe_string = $mysql_conf{'db_host'};
1565  $safe_string =~ s/'/'\\''/g;
1566  $host_arg = " --host='$safe_string'";
1567  }
1568  if ($mysql_conf{'db_port'} > 0)
1569  {
1570  $safe_string = $mysql_conf{'db_port'};
1571  $safe_string =~ s/'/'\\''/g;
1572  $port_arg = " --port='$safe_string'";
1573  }
1574  if ($mysql_conf{'db_user'})
1575  {
1576  $safe_string = $mysql_conf{'db_user'};
1577  $safe_string =~ s/'/'\\''/g;
1578  $user_arg = " --user='$safe_string'";
1579  }
1580  # Configure a filter for a partial/new-host restore
1581  if ($partial_restore)
1582  {
1583  my @partial_restore_tables;
1584  if (defined($with_plugin_data))
1585  {
1586  # Blacklist the MythTV tables we don't want to keep
1587  # This may result in keeping old tables that were dropped in
1588  # previous DB schema updates if the user is running a restore
1589  # script from an older version of MythTV, but the extra data will
1590  # only take a bit of hard drive space.
1591  @partial_restore_tables = ('callsignnetworkmap', # historic
1592  'capturecard',
1593  'cardinput',
1594  'channel',
1595  'channelgroup',
1596  'channelgroupnames',
1597  'channelscan',
1598  'channelscan_channel',
1599  'channelscan_dtv_multiplex',
1600  'codecparams',
1601  'conflictresolutionany', # historic
1602  'conflictresolutionoverride', # hst
1603  'conflictresolutionsingle', # hst
1604  'credits',
1605  'customexample',
1606  'diseqc_config',
1607  'diseqc_tree',
1608  'displayprofilegroups',
1609  'displayprofiles',
1610  'dtv_multiplex',
1611  'dtv_privatetypes',
1612  'dvb_channel', # historic
1613  'dvb_pids', # historic
1614  'dvb_sat', # historic
1615  'dvb_signal_quality', # historic
1616  'dvb_transport', # historic
1617  'eit_cache',
1618  'favorites',
1619  'filemarkup',
1620  'housekeeping',
1621  'inputgroup',
1622  'inuseprograms',
1623  'jobqueue',
1624  'jumppoints',
1625  'keybindings',
1626  'keyword',
1627  'mythlog',
1628  'networkiconmap', # historic
1629  'oldfind',
1630  'oldprogram',
1631  'people',
1632  'pidcache',
1633  'playgroup',
1634  'powerpriority',
1635  'profilegroups',
1636  'program',
1637  'programgenres',
1638  'programrating',
1639  'recgroups',
1640  'recordedcredits',
1641  'recordedfile',
1642  'recordedprogram',
1643  'recordingprofiles',
1644  'recordmatch',
1645  'recordoverride', # historic
1646  'record_tmp',
1647  'schemalock',
1648  'settings',
1649  'storagegroup',
1650  'transcoding', # historic
1651  'tvchain',
1652  'tvosdmenu',
1653  'upnpmedia',
1654  'videobookmarks', # historic
1655  'videosource',
1656  'xvmc_buffer_settings' # historic
1657  );
1658  }
1659  else
1660  {
1661  # Whitelist the tables we want to keep
1662  @partial_restore_tables = ('oldrecorded',
1663  'record',
1664  'recorded',
1665  'recordedmarkup',
1666  'recordedprogram',
1667  'recordedrating',
1668  'recordedseek');
1669  }
1670  if (!defined($restore_xmltvids))
1671  {
1672  $filter = '^INSERT INTO \`?(' .
1673  join('|', @partial_restore_tables).')\`? ';
1674  # If doing a whitelist restore, ensure we keep the character
1675  # set info to prevent data corruption
1676  if (!defined($with_plugin_data))
1677  {
1678  $filter = '(40101 SET NAMES |'.$filter.')';
1679  }
1680  verbose($verbose_level_debug,
1681  '', 'Restoring partial backup with filter:', $filter);
1682  }
1683  }
1684  my $safe_db_name = $mysql_conf{'db_name'};
1685  $safe_db_name =~ s/'/'\\''/g;
1686  my $command = "'${safe_mysql_client}'${defaults_arg}${host_arg}".
1687  "${port_arg}${user_arg} '$safe_db_name'";
1688  verbose($verbose_level_debug,
1689  '', 'Executing command:', $command);
1690  my $read_status = open(BACKUP,
1691  "<$backup_conf{'directory'}/$backup_conf{'filename'}");
1692  if (!defined($read_status))
1693  {
1694  verbose($verbose_level_error,
1695  '', 'ERROR: Unable to read backup file.');
1696  return 255;
1697  }
1698  my $write_status = open(COMMAND, "| $command");
1699  if (!defined($write_status))
1700  {
1701  verbose($verbose_level_error,
1702  '', "ERROR: Unable to execute $mysql_client.");
1703  return 254;
1704  }
1705  my $lines_total = 0;
1706  my $lines_restored = 0;
1707  while (<BACKUP>)
1708  {
1709  $lines_total++;
1710  if ($partial_restore)
1711  {
1712  if ($restore_xmltvids)
1713  {
1714  # Send all lines through
1715  }
1716  if ($with_plugin_data)
1717  {
1718  # Skip tables in the blacklist
1719  next if /$filter/;
1720  }
1721  else
1722  {
1723  # Skip tables not in the whitelist
1724  next if !/$filter/;
1725  }
1726  }
1727  $lines_restored++;
1728  print COMMAND or die("\nERROR: Cannot write to ".
1729  "$mysql_client, stopped");
1730  }
1731  close(COMMAND);
1732  close(BACKUP);
1733  $exit = $?;
1734  verbose($verbose_level_debug,
1735  '', "$mysql_client exited with status: $exit",
1736  '', "Restored $lines_restored of $lines_total lines.");
1737  return $exit;
1738  }
1739 
1740 ##############################################################################
1741 # Main functionality
1742 ##############################################################################
1743 
1744 # The first argument after option parsing, if it exists, should be a database
1745 # information file.
1746  $database_information_file = shift;
1747 
1748  configure_environment;
1749  read_config;
1750  check_config;
1751 
1752  print_configuration;
1753 
1754  my $status = 1;
1755  if (check_database)
1756  {
1757  if ($change_hostname)
1758  {
1759  $status = do_hostname_change;
1760  verbose($verbose_level_always,
1761  '', 'Successfully changed hostname.') if (!$status);
1762  }
1763  elsif (!uncompress_backup_file)
1764  {
1765  $status = restore_backup;
1766  if (!$status)
1767  {
1768  verbose($verbose_level_always,
1769  '', 'Successfully restored backup.');
1770  $status = set_database_charset;
1771  }
1772  }
1773  }
1774 
1775  $dbh->disconnect if (defined($dbh));
1776 
1777  exit $status;
1778