Ticket #4760: mythtv-4760-database_backup_scripts-20080627.patch

File mythtv-4760-database_backup_scripts-20080627.patch, 107.7 KB (added by sphery <mtdean@…>, 16 years ago)

Update patch (adds new features, see comment, below)

  • programs/programs.pro

     
    1111}
    1212
    1313using_backend {
    14     SUBDIRS += mythbackend mythfilldatabase mythtv-setup
     14    SUBDIRS += mythbackend mythfilldatabase mythtv-setup scripts
    1515}
    1616
    1717using_frontend:using_backend {
  • programs/scripts/database/database_mythconverg_restore.pl

     
     1#!/usr/bin/perl -w
     2#
     3# database_mythconverg_restore.pl
     4#
     5# Restores a backup of the MythTV database.
     6#
     7# For details, see:
     8#   database_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.1';
     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, $mc_sql);
     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'       => \$partial_restore,
     71               'with_plugin_data|plugin_data'       => \$with_plugin_data,
     72               'restore_xmltvids|xmltvids'          => \$restore_xmltvids,
     73               'mysql_client|client=s'              => \$mysql_client,
     74               'uncompress=s'                       => \$uncompress,
     75               'create_database|create_db|mc_sql=s' => \$mc_sql,
     76               'change_hostname'                    => \$change_hostname,
     77               'new_hostname=s'                     => \$new_hostname,
     78               'old_hostname=s'                     => \$old_hostname,
     79               'usage|help|h'                       => \$usage,
     80               'version'                            => \$show_version,
     81               'script_version|v'                   => \$show_version_script,
     82               'verbose|debug|d+'                   => \$debug
     83              );
     84
     85    $partial_restore ||= $restore_xmltvids;
     86
     87# Print version information
     88    sub print_version_information
     89    {
     90        my $script_name = substr $0, rindex($0, '/') + 1;
     91        print "$NAME\n$script_name\nversion: $VERSION\n";
     92    }
     93
     94    if ($show_version_script)
     95    {
     96        print "$NAME,$VERSION,,\n";
     97        exit;
     98    }
     99    elsif ($show_version)
     100    {
     101        print_version_information;
     102        exit;
     103    }
     104
     105# Print usage
     106    if ($usage)
     107    {
     108        print_version_information;
     109        print <<EOF;
     110
     111Usage:
     112  $0 [options|database_information_file]
     113
     114Restores a backup of the MythTV database.
     115
     116DETAILED DESCRIPTION:
     117
     118This script is used to restore a backup of the MythTV database (as created by
     119the database_mythconverg_backup.pl script). It can be called with a single
     120command-line argument specifying the name of a "database information file" (see
     121DATABASE INFORMATION FILE, below), which contains sufficient information about
     122the database and the backup to allow the script to restore a backup without
     123needing any additional configuration files. In this mode, all other MythTV
     124configuration files (including config.xml, mysql.txt) are ignored, but the
     125backup resource file (see RESOURCE FILE, below) and the MySQL option files
     126(i.e. /etc/my.cnf or ~/.my.cnf) will be honored.
     127
     128The script can also be called  using command-line arguments to specify the
     129required information. If no database information file is specified, the script
     130will attempt to determine the appropriate configuration by using the MythTV
     131configuration file(s) (prefering config.xml, but falling back to mysql.txt if
     132no config.xml exists). Once the MythTV configuration file has been parsed, the
     133backup resource file (see RESOURCE FILE, below) will be parsed, then
     134command-line arguments will be applied (thus overriding any values determined
     135from the configuration files).
     136
     137The only information required by the script is the directory in which the
     138backup exists (the script will attempt to find the most current backup file,
     139based on the filename). Therefore, when using a database information file, the
     140DBBackupDirectory should be specified, or if running manually, the --directory
     141command-line argument should be specified. The DBBackupDirectory may be
     142specified in a backup resource file (see RESOURCE FILE, below). If the
     143specified directory is not readable, the script will terminate. Likewise, if
     144the backup file cannot be read, the script will terminate.
     145
     146If the database name is not specified, the script will attempt to use the
     147MythTV default database name, $d_db_name. Note that the same is not true for
     148the database username and database password. These must be explicitly
     149specified. The password must be specified in a database information file, a
     150backup resource file, or a MySQL options file. The username may be specified
     151the same way or may be specified using a command-line argument if not using a
     152database information file.
     153
     154If no database exists, the script can attempt to create the initial database.
     155To allow this, specify the --create_database argument and specify the location
     156of the mc.sql script (full directory/filename). On most MySQL configurations
     157this will fail unless connecting to the database using the MySQL root user--
     158even if the user specified has CREATE DATABASE privilege--as RELOAD privilege
     159is also required. Though you may specify '--username=root' on the command line,
     160the script does not allow specifying a database password on the command line.
     161Therefore, you will need to specify 'DBPassword=<MySQL root user password>' in
     162the backup resource file.
     163
     164If attempting to perform a full restore, the database must be empty (no
     165tables), or--if the script is allowed to create the initial database, as
     166explained above--must not exist. If attempting to do a partial or "new
     167hardware" restore, the database must exist and must have tables. See QUICK
     168START, below, for more information.
     169
     170DATABASE INFORMATION FILE
     171
     172The database information file contains information about the database and the
     173backup. The information within the file is specified as name=value pairs using
     174the same names as used by the MythTV config.xml and mysql.txt configuration
     175files. The following variables are recognized:
     176
     177  DBHostName - The hostname (or IP address) which should be used to find the
     178               MySQL server.
     179  DBPort - The TCP/IP port number to use for the connection. This may have a
     180           value of 0, i.e. if the hostname is localhost or if the server is
     181           using the default MySQL port or the port specified in a MySQL
     182           options file.
     183  DBUserName - The database username to use when connecting to the server.
     184  DBPassword - The password to use when connecting to the server.
     185  DBName - The name of the database that contains the MythTV data.
     186  DBSchemaVer - The MythTV schema version of the database. This value will be
     187                used to create the backup filename, but only if the filename
     188                has not been specified using DBBackupFilename or the --filename
     189                argument.
     190  DBBackupDirectory - The directory in which the backup file should be
     191                      created. This directory may have been specially
     192                      configured by the user as the "DB Backups" storage
     193                      group. It is recommended that this directory be
     194                      used--especially in "common-use" scripts such as those
     195                      provided by distributions.
     196  DBBackupFilename - The name of the file in which the backup should be
     197                     created. Additional extensions may be added by this
     198                     script as required (i.e. adding an appropriate suffix,
     199                     such as ".gz", to the file if it is compressed). If the
     200                     filename recommended by mythbackend is used, it will be
     201                     displayed in the GUI messages provided for the user. If
     202                     the recommended filename is not used, the user will not be
     203                     told where to find the backup file. If no value is
     204                     provided, a filename using the default filename format
     205                     will be chosen.
     206  partial_restore  - Do a partial restore (as would be required when setting
     207                     up MythTV on new hardware) of only the MythTV recordings
     208                     and recording rules.
     209  with_plugin_data - When doing a partial restore, include plugin data.
     210                     Ignored, unless the --partial_restore argument is given.i
     211                     Note that you will still need to configure all plugins
     212                     after the restore completes.
     213  mysql_client     - The path (including filename) of the mysql client
     214                     executable.
     215  uncompress       - The command (including path, if necessary)  to use to
     216                     uncompress the backup. If you specify an uncompress
     217                     program, the backup file will be assumed to be compressed,
     218                     so the command will be run on the file regardless.
     219                     If no value is specified for uncompress or if the value
     220                     '$d_uncompress' or 'gunzip' is specified, the script will
     221                     check to see if the file is actually a gzip-compressed
     222                     file, and if so, will first attempt to use the
     223                     IO::Uncompress::Gunzip module to uncompress the backup
     224                     file, but, if not available, will run the command
     225                     specified. Therefore, if IO::Uncompress::Gunzip is
     226                     installed and functional, specifying a value for
     227                     uncompress is unnecessary.
     228  create_database  - The location of the mc.sql script (full directory and
     229                     filename). If specified, and if the database does not
     230                     exist, the script will attempt to create the initial
     231                     database by running the specified mc.sql script commands.
     232                     This probably requires running the restore script with
     233                     the DBUserName set to root. See, also, the MythTV HOWTO
     234                     ( http://www.mythtv.org/docs/ )for details on "Setting up
     235                     the initial database."
     236
     237RESOURCE FILE
     238
     239The backup resource file specifies values using the same format as described
     240for the database information file, above, but is intended as a "permanent,"
     241user-created configuration file. The database information file is intended as a
     242"single-use" configuration file, often created automatically (i.e. by a
     243program, such as a script). The backup resource file should be placed at
     244"~/.mythtv/backuprc" and given appropriate permissions. To be usable by the
     245script, it must be readable. However, it should be protected as required--i.e.
     246if the DBPassword is specified, it should be made readable only by the owner.
     247
     248When specifying a database information file, the resource file is parsed before
     249the database information file to prevent the resource file from overriding the
     250information in the database information file. When no database information
     251file is specified, the resource file is parsed after the MythTV configuration
     252files, but before the command-line arguments to allow the resource file to
     253override values in the configuration files and to allow command-line arguments
     254to override resource file defaults.
     255
     256options:
     257
     258--hostname [database hostname]
     259
     260    The hostname (or IP address) which should be used to find the MySQL server.
     261    See DBHostName, above.
     262
     263--port [database port]
     264
     265    The TCP/IP port number to use for connection to the MySQL server. See
     266    DBPort, above.
     267
     268--username [database username]
     269
     270    The MySQL username to use for connection to the MySQL server. See
     271    DBUserName, above.
     272
     273--name [database name]
     274
     275    The name of the database containing the MythTV data. See DBName, above.
     276
     277    Default:  $d_db_name
     278
     279--schemaver [MythTV database schema version]
     280
     281    The MythTV schema version. See DBSchemaVer, above.
     282
     283--directory [directory]
     284
     285    The directory in which the backup file should be stored. See
     286    DBBackupDirectory, above.
     287
     288--filename [database backup filename]
     289
     290    The name to use for the database backup file. If not provided, a filename
     291    using a default format will be chosen. See DBBackupFilename, above.
     292
     293--partial_restore
     294
     295    Do a partial restore (as would be required when setting up MythTV on new
     296    hardware) of only the MythTV recordings and recording rules.
     297
     298--with_plugin_data
     299
     300    When doing a partial restore, include plugin data. Ignored, unless the
     301    --partial_restore argument is given. Note that you will still need to
     302    configure all plugins after the restore completes.
     303
     304--restore_xmltvids
     305
     306    Restore channel xmltvids from a backup create with
     307    database_mythconverg_backup.pl --backup_xmltvids
     308
     309--mysql_client [path]
     310
     311    The path (including filename) of the mysql client executable. See
     312    mysql_client in the DATABASE INFORMATION FILE description, above.
     313
     314    Default:  $d_mysql_client
     315
     316--uncompress [path]
     317
     318    The command (including path, if necessary) to use to uncompress the
     319    backup. See uncompress in the DATABASE INFORMATION FILE description, above.
     320
     321    Default:  $d_uncompress
     322
     323--create_database [path]
     324
     325    The location of the mc.sql script (full directory and filename). See
     326    create_database in the DATABASE INFORMATION FILE description, above.
     327
     328--change_hostname
     329
     330    Specifies that the script should change the hostname of a MythTV frontend
     331    or backend in the database rather than restore a database backup. It is
     332    critical that no MythTV frontends or backends are running when a hostname
     333    is changed.
     334
     335--new_hostname
     336
     337    Specifies the new hostname. The new_hostname is only used when the
     338    --change_hostname argument is specified.
     339
     340--old_hostname
     341
     342    Specifies the old hostname. The old_hostname is only used when the
     343    --change_hostname argument is specified.
     344
     345--help
     346
     347    Show this help text.
     348
     349--version
     350
     351    Show version information.
     352
     353--verbose
     354
     355    Show what is happening.
     356
     357--script_version | -v
     358
     359    Show script version information. This is primarily useful for scripts
     360    or programs needing to parse the version information.
     361
     362QUICK START:
     363
     364Create a file ~/.mythtv/backuprc with a single line,
     365"DBBackupDirectory=/home/mythtv" (no quotes). For example:
     366
     367# echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
     368
     369To do a full restore:
     370Ensure you have an empty database. If you a replacing a corrupt database, you
     371must first drop the existing database. You may do this using the mysql client
     372executable by issuing the statement, "DROP DATABASE mythconverg;" (no quotes;
     373fix the database name, as required). Then, execute the mc.sql script as
     374described in the MythTV HOWTO ( http://www.mythtv.org/docs/ ) to prepare a new
     375(empty) database (or see the above description of the --create_database
     376argument).
     377
     378Run this script to restore the backup. Use the --verbose argument to see what
     379is happening.
     380
     381# $0
     382
     383At this point, you may start mythtv-setup or mythbackend. If you restored a
     384backup from an older version of MythTV, mythtv-setup will upgrade the database
     385for you.
     386
     387To do a partial restore:
     388
     389You must have a fully-populated database schema (but without the data you wish
     390to import) from the version of MythTV used to create the backup. You may
     391create and populate the database by running the mc.sql script (see the above
     392description of the --create_database argument) to create the database. Then,
     393start and exit mythtv-setup to populate the database. And, finally, do the
     394partial restore with:
     395
     396# $0 --partial_restore
     397
     398Include the --with_plugin_data argument if you would like to keep the data
     399used by MythTV plugins.
     400
     401If you would like to do a partial/new-hardware restore and have upgraded
     402MythTV, you must first do a full restore, then start and exit mythtv-setup (to
     403upgrade the database), then create a backup, then drop the database, then
     404follow the instructions for doing a partial restore with the new (upgraded)
     405backup file.
     406
     407To change the hostname of a MythTV frontend or backend:
     408
     409Ensure that the database exists (restore an old database, as above, if
     410necessary) and execute the following command, replacing "XXXX" and "YYYY"
     411with appropriate values for the old and new hostnames, respectively:
     412
     413# $0 --change_hostname --old_hostname="XXXX" --new_hostname="YYYY"
     414
     415To restore xmltvids:
     416
     417Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
     418script with the --restore_xmltvids argument.
     419
     420# $0 --restore_xmltvids
     421
     422EOF
     423        exit;
     424    }
     425
     426    sub verbose
     427    {
     428        my $level = shift;
     429        my $error = 0;
     430        if ($level == $verbose_level_error)
     431        {
     432            $error = 1;
     433        }
     434        else
     435        {
     436            return unless ($debug >= $level);
     437        }
     438        print { $error ? STDOUT : STDERR } join("\n", @_), "\n";
     439    }
     440
     441    sub print_configuration
     442    {
     443        verbose($verbose_level_debug,
     444                "\nDatabase Information:",
     445                "         DBHostName:  $mysql_conf{'db_host'}",
     446                "             DBPort:  $mysql_conf{'db_port'}",
     447                "         DBUserName:  $mysql_conf{'db_user'}",
     448                "         DBPassword:  " .
     449                    ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
     450                  #  "$mysql_conf{'db_pass'}",
     451                "             DBName:  $mysql_conf{'db_name'}",
     452                "        DBSchemaVer:  $mysql_conf{'db_schemaver'}",
     453                "  DBBackupDirectory:  $backup_conf{'directory'}",
     454                "   DBBackupFilename:  $backup_conf{'filename'}",
     455                "    create_database:  " . ($mc_sql ? $mc_sql : ''));
     456        verbose($verbose_level_debug,
     457                "\nExecutables:",
     458                "       mysql_client:  $mysql_client",
     459                "         uncompress:  $uncompress");
     460        verbose($verbose_level_debug,
     461                "\nMiscellaneous:",
     462                "    partial_restore:  " . ($partial_restore ? 'yes' : 'no'));
     463        if ($partial_restore)
     464        {
     465            verbose($verbose_level_debug,
     466                    "   with_plugin_data:  " . ($with_plugin_data ?
     467                                                'yes' : 'no'));
     468        }
     469        verbose($verbose_level_debug,
     470                "   restore_xmltvids:  " . ($restore_xmltvids ? 'yes' : 'no'),
     471                "    change_hostname:  " . ($change_hostname ? 'yes' : 'no'));
     472        if ($change_hostname)
     473        {
     474            verbose($verbose_level_debug,
     475                    "     - old_hostname:  " . $old_hostname,
     476                    "     - new_hostname:  " . $new_hostname);
     477        }
     478    }
     479
     480    sub configure_environment
     481    {
     482        verbose($verbose_level_debug,
     483                "\nConfiguring environment:");
     484
     485    # Get the user's login and home directory, so we can look for config files
     486        ($username, $homedir) = (getpwuid $>)[0,7];
     487        $username = $ENV{'USER'} if ($ENV{'USER'});
     488        $homedir  = $ENV{'HOME'} if ($ENV{'HOME'});
     489        if ($username && !$homedir)
     490        {
     491            $homedir = "/home/$username";
     492            if (!-e $homedir && -e "/Users/$username")
     493            {
     494                $homedir = "/Users/$username";
     495            }
     496        }
     497        verbose($verbose_level_debug,
     498                "  -    username:  $username",
     499                "  -        HOME:  $homedir");
     500
     501    # Find the config directory
     502        $mythconfdir = $ENV{'MYTHCONFDIR'}
     503            ? $ENV{'MYTHCONFDIR'}
     504            : "$homedir/.mythtv"
     505            ;
     506
     507        verbose($verbose_level_debug,
     508                "  - MYTHCONFDIR:  $mythconfdir");
     509    }
     510
     511# Though much of the configuration file parsing could be done by the MythTV
     512# Perl bindings, using them to retrieve database information is not appropriate
     513# for a backup script. The Perl bindings require the backend to be running and
     514# use UPnP for autodiscovery. Also, parsing the files "locally" allows
     515# supporting even the old MythTV database configuration file, mysql.txt.
     516    sub parse_database_information
     517    {
     518        my $file = shift;
     519        verbose($verbose_level_debug,
     520                "  - checking:  $file");
     521        return 0 unless ($file && -e $file);
     522        verbose($verbose_level_debug,
     523                "     parsing:  $file");
     524        open(CONF, $file) or die("\nERROR:  Unable to read $file:  $!".
     525                                 ", stopped");
     526        while (my $line = <CONF>)
     527        {
     528        # Cleanup
     529            next if ($line =~ m/^\s*#/);
     530            $line =~ s/^str //;
     531            chomp($line);
     532            $line =~ s/^\s+//;
     533            $line =~ s/\s+$//;
     534        # Split off the var=val pairs
     535            my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
     536        # Also look for <var>val</var> from config.xml
     537            if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
     538            {
     539                $var = $1; $val = $2;
     540            }
     541            next unless ($var && $var =~ m/\w/);
     542            if ($var eq 'DBHostName')
     543            {
     544                $mysql_conf{'db_host'} = $val;
     545            }
     546            elsif ($var eq 'DBPort')
     547            {
     548                $mysql_conf{'db_port'} = $val;
     549            }
     550            elsif ($var eq 'DBUserName')
     551            {
     552                $mysql_conf{'db_user'} = $val;
     553            }
     554            elsif ($var eq 'DBPassword')
     555            {
     556                $mysql_conf{'db_pass'} = $val;
     557            }
     558            elsif ($var eq 'DBName')
     559            {
     560                $mysql_conf{'db_name'} = $val;
     561            }
     562            elsif ($var eq 'DBSchemaVer')
     563            {
     564                $mysql_conf{'db_schemaver'} = $val;
     565            }
     566            elsif ($var eq 'DBBackupDirectory')
     567            {
     568                $backup_conf{'directory'} = $val;
     569            }
     570            elsif ($var eq 'DBBackupFilename')
     571            {
     572                $backup_conf{'filename'} = $val;
     573            }
     574            elsif ($var eq 'partial_restore')
     575            {
     576                $partial_restore = $val;
     577            }
     578            elsif ($var eq 'with_plugin_data')
     579            {
     580                $with_plugin_data = $val;
     581            }
     582            elsif ($var eq 'mysql_client')
     583            {
     584                $mysql_client = $val;
     585            }
     586            elsif ($var eq 'uncompress')
     587            {
     588                $uncompress = $val;
     589            }
     590            elsif ($var eq 'create_database')
     591            {
     592                $mc_sql = $val;
     593            }
     594        }
     595        close CONF;
     596        return 1;
     597    }
     598
     599    sub read_mysql_txt
     600    {
     601    # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
     602    # couple places, so try the usual suspects in the same order that mythtv
     603    # does in libs/libmyth/mythcontext.cpp
     604        my $found = 0;
     605        my $result = 0;
     606        my @mysql = ('/usr/local/share/mythtv/mysql.txt',
     607                     '/usr/share/mythtv/mysql.txt',
     608                     '/usr/local/etc/mythtv/mysql.txt',
     609                     '/etc/mythtv/mysql.txt',
     610                     $homedir ? "$homedir/.mythtv/mysql.txt"    : '',
     611                     'mysql.txt',
     612                     $mythconfdir ? "$mythconfdir/mysql.txt"    : '',
     613                    );
     614        foreach my $file (@mysql)
     615        {
     616            $found = parse_database_information($file);
     617            $result = $result + $found;
     618        }
     619        return $result;
     620    }
     621
     622    sub read_resource_file
     623    {
     624        parse_database_information("$mythconfdir/backuprc");
     625    }
     626
     627    sub apply_arguments
     628    {
     629        verbose($verbose_level_debug,
     630                "\nApplying command-line arguments.");
     631        if ($db_hostname)
     632        {
     633            $mysql_conf{'db_host'} = $db_hostname;
     634        }
     635        if ($db_port)
     636        {
     637            $mysql_conf{'db_port'} = $db_port;
     638        }
     639        if ($db_username)
     640        {
     641            $mysql_conf{'db_user'} = $db_username;
     642        }
     643    # This script does not accept a database password on the command-line.
     644#        if ($db_password)
     645#        {
     646#            $mysql_conf{'db_pass'} = $db_password;
     647#        }
     648        if ($db_name)
     649        {
     650            $mysql_conf{'db_name'} = $db_name;
     651        }
     652        if ($db_schema_version)
     653        {
     654            $mysql_conf{'db_schemaver'} = $db_schema_version;
     655        }
     656        if ($backup_directory)
     657        {
     658            $backup_conf{'directory'} = $backup_directory;
     659        }
     660        if ($backup_filename)
     661        {
     662            $backup_conf{'filename'} = $backup_filename;
     663        }
     664    }
     665
     666    sub read_config
     667    {
     668        my $result = 0;
     669    # If specified, use only the database information file
     670        if ($database_information_file)
     671        {
     672            verbose($verbose_level_debug,
     673                    "\nDatabase Information File specified. Ignoring all".
     674                    " command-line arguments");
     675            verbose($verbose_level_debug,
     676                    "\nDatabase Information File:".
     677                    " $database_information_file");
     678            unless (-T "$database_information_file")
     679            {
     680                die("\nERROR:  Invalid database information file, stopped");
     681            }
     682        # When using a database information file, parse the resource file first
     683        # so it cannot override database information file settings
     684            read_resource_file;
     685            $result = parse_database_information($database_information_file);
     686            return $result;
     687        }
     688
     689    # No database information file, so try the MythTV configuration files.
     690        verbose($verbose_level_debug,
     691                "\nParsing configuration files:");
     692    # Prefer the config.xml file
     693        my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
     694        $result = parse_database_information($file);
     695        if (!$result)
     696        {
     697        # Use the "legacy" mysql.txt file as a fallback
     698            $result = read_mysql_txt;
     699        }
     700    # Read the resource file next to override the config file information, but
     701    # to allow command-line arguments to override resource file "defaults"
     702        read_resource_file;
     703    # Apply the command-line arguments to override the information provided
     704    # by the config file(s).
     705        apply_arguments;
     706        return $result;
     707    }
     708
     709    sub create_defaults_extra_file
     710    {
     711        return '' if (!$mysql_conf{'db_pass'});
     712        verbose($verbose_level_debug,
     713                "\nAttempting to use supplied password for $mysql_client".
     714                " command-line client.",
     715                "Any [client] or [mysql] password specified in the MySQL".
     716                " options file will",
     717                "take precedence.");
     718    # Let tempfile handle unlinking on exit so we don't have to verify that the
     719    # file with $filename is the file we created
     720        my ($fh, $filename) = tempfile(UNLINK => 1);
     721        print $fh  "[client]\npassword=$mysql_conf{'db_pass'}\n".
     722                   "[mysql]\npassword=$mysql_conf{'db_pass'}\n";
     723        return $filename;
     724    }
     725
     726    sub check_file_config
     727    {
     728        if (!$backup_conf{'directory'})
     729        {
     730            if (!-r "/$backup_conf{'filename'}")
     731            {
     732                print_configuration;
     733                die("\nERROR:  DBBackupDirectory not specified, stopped");
     734            }
     735        # The user must have specified an absolute path for the
     736        # DBBackupFilename. Though this is not how the script is meant to be
     737        # used, allow it.
     738            $backup_conf{'directory'} = '';
     739        }
     740        elsif (!-d $backup_conf{'directory'})
     741        {
     742            print_configuration;
     743            verbose($verbose_level_error,
     744                    "\nERROR:  DBBackupDirectory is not a directory. Please".
     745                    " specify a directory in",
     746                    "        your database information file using".
     747                    " DBBackupDirectory.",
     748                    "        If not using a database information file," .
     749                    " please specify the ",
     750                    "        --directory command-line option.");
     751            die("\nInvalid backup directory, stopped");
     752        }
     753        if (!$backup_conf{'filename'})
     754        {
     755        # Look for most current backup file
     756            verbose($verbose_level_debug,
     757                    "\nNo filename specified. Attempting to find the newest".
     758                    " database backup.");
     759            if ($restore_xmltvids)
     760            {
     761                $backup_conf{'filename'} = 'mythtv_xmltvid_backup';
     762            }
     763            else
     764            {
     765                $backup_conf{'filename'} = $mysql_conf{'db_name'};
     766                if (!$backup_conf{'filename'})
     767                {
     768                    $backup_conf{'filename'} = $d_db_name;
     769                }
     770            }
     771            my @files = <$backup_conf{'directory'}/$backup_conf{'filename'}*>;
     772            my $num_files = @files;
     773            if ($num_files < 1)
     774            {
     775                verbose($verbose_level_error,
     776                        "ERROR:  Unable to find any backup files in".
     777                        " DBBackupDir and none specified.");
     778            }
     779            else
     780            {
     781                my @sorted_files = sort { lc($b) cmp lc($0) } @files;
     782                $backup_conf{'filename'} = $sorted_files[0];
     783                $backup_conf{'filename'} =~ s#^$backup_conf{'directory'}/?##;
     784                verbose($verbose_level_debug,
     785                        "Using database backup file:",
     786                        "$backup_conf{'directory'}/$backup_conf{'filename'}");
     787            }
     788        }
     789        if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     790        {
     791            my $temp_filename = $backup_conf{'filename'};
     792        # Perhaps the user specified some unnecessary path information in the
     793        # filename (i.e. using the shell's filename completion)
     794            $temp_filename =~ s#^.*/##;
     795            if (-e "$backup_conf{'directory'}/$temp_filename")
     796            {
     797                $backup_conf{'filename'} = $temp_filename;
     798            }
     799            else
     800            {
     801                verbose($verbose_level_error,
     802                        "\nERROR:  The specified backup file does not exist.",
     803                        "$backup_conf{'directory'}/$backup_conf{'filename'}");
     804                die("\nInvalid backup filename, stopped");
     805            }
     806        }
     807        if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     808        {
     809            verbose($verbose_level_error,
     810                    "\nERROR:  The specified backup file cannot be read.");
     811            die("\nInvalid backup filename, stopped");
     812        }
     813        if (!$mysql_conf{'db_name'})
     814        {
     815            verbose($verbose_level_debug,
     816                    "\nWARNING:  DBName not specified. Using $d_db_name");
     817            $mysql_conf{'db_name'} = $d_db_name;
     818        }
     819    }
     820
     821    sub check_config
     822    {
     823        verbose($verbose_level_debug,
     824                "\nChecking configuration.");
     825
     826        if (!defined($change_hostname))
     827        {
     828        # Check directory/filename
     829            check_file_config;
     830        }
     831    # Though the script will attempt a restore even if no other database
     832    # information is provided (i.e. using "defaults" from the MySQL options
     833    # file, warning the user that some "normally-necessary" information is not
     834    # provided may be nice.
     835        return if (!$debug);
     836        if (!$mysql_conf{'db_host'})
     837        {
     838            verbose($verbose_level_debug,
     839                    "\nWARNING:  DBHostName not specified.",
     840                    "         Assuming it's specified in the MySQL".
     841                    " options file.");
     842        }
     843        if (!$mysql_conf{'db_user'})
     844        {
     845            verbose($verbose_level_debug,
     846                    "\nWARNING:  DBUserName not specified.",
     847                    "         Assuming it's specified in the MySQL".
     848                    " options file.");
     849        }
     850        if (!$mysql_conf{'db_pass'})
     851        {
     852            verbose($verbose_level_debug,
     853                    "\nWARNING:  DBPassword not specified.",
     854                    "         Assuming it's specified in the MySQL".
     855                    " options file.");
     856        }
     857    }
     858
     859    sub database_exists
     860    {
     861        $result = 1;
     862        $dbh = DBI->connect("dbi:mysql:".
     863                            "database=$mysql_conf{'db_name'}:".
     864                            "host=$mysql_conf{'db_host'}",
     865                            "$mysql_conf{'db_user'}",
     866                            "$mysql_conf{'db_pass'}",
     867                            { PrintError => 0 });
     868        if (!defined($dbh))
     869        {
     870            verbose($verbose_level_debug,
     871                    "Unable to connect to database.");
     872            $result = 0;
     873        }
     874        return $result;
     875    }
     876
     877    sub create_initial_database
     878    {
     879        return 1 if (!$mc_sql);
     880        return 1 if (!-r "$mc_sql");
     881
     882        my $defaults_extra_file = create_defaults_extra_file;
     883        my $host_arg = '';
     884        my $port_arg = '';
     885        my $user_arg = '';
     886        if ($defaults_extra_file)
     887        {
     888            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     889        }
     890        else
     891        {
     892            $defaults_arg='';
     893        }
     894    # Create the args for host, port, and user, shell-escaping values, as
     895    # necessary.
     896        if ($mysql_conf{'db_host'})
     897        {
     898            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     899            $host_arg=" --host='$mysql_conf{'db_host'}'";
     900        }
     901        if ($mysql_conf{'db_port'} > 0)
     902        {
     903            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     904            $port_arg=" --port='$mysql_conf{'db_port'}'";
     905        }
     906        if ($mysql_conf{'db_user'})
     907        {
     908            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     909            $user_arg=" --user='$mysql_conf{'db_user'}'";
     910        }
     911        verbose($verbose_level_debug,
     912                "\nAttempting to create initial database.");
     913    # Use redirects to capture stdout and stderr (for debug)
     914        my $command = "${mysql_client}${defaults_arg}${host_arg}${port_arg}".
     915                      "${user_arg}  2>&1 < $mc_sql";
     916        verbose($verbose_level_debug,
     917                "\nExecuting command:", $command);
     918        my $result = `$command`;
     919        my $exit = $? >> 8;
     920        verbose($verbose_level_debug,
     921                "\n$mysql_client exited with status:  $exit");
     922        verbose($verbose_level_error,
     923                "$mysql_client output:", $result) if ($exit);
     924        die("\nUnable to create initial database, stopped");
     925        return $exit;
     926    }
     927
     928    sub is_database_empty
     929    {
     930        $result = 1;
     931        if (defined($dbh))
     932        {
     933            my $sth = $dbh->table_info('', '', '', 'TABLE');
     934            my $num_tables = keys %{$sth->fetchall_hashref('TABLE_NAME')};
     935            verbose($verbose_level_debug,
     936                    "\nFound $num_tables tables in the database.");
     937            if ($num_tables > 0)
     938            {
     939                if (!defined($change_hostname) && !defined($partial_restore))
     940                {
     941                    verbose($verbose_level_debug,
     942                            "WARNING:  Database not empty.");
     943                }
     944                $result = 0;
     945            }
     946        }
     947        return $result;
     948    }
     949
     950    sub check_database_libs
     951    {
     952    # Try to load the DBI library if available (but don't require it)
     953        BEGIN
     954        {
     955            our $has_dbi = 1;
     956            eval 'use DBI;';
     957            if ($@)
     958            {
     959                $has_dbi = 0;
     960            }
     961        }
     962        verbose($verbose_level_debug,
     963                "\nDBI is not installed.") if (!$has_dbi);
     964    # Try to load the DBD::mysql library if available (but don't # require it)
     965        BEGIN
     966        {
     967            our $has_dbd = 1;
     968            eval 'use DBD::mysql;';
     969            if ($@)
     970            {
     971                $has_dbd = 0;
     972            }
     973        }
     974        verbose($verbose_level_debug,
     975                "\nDBD::mysql is not installed.") if (!$has_dbd);
     976        return ($has_dbi + $has_dbd);
     977    }
     978
     979    sub check_database
     980    {
     981        my $have_database_libs = check_database_libs;
     982        if ($have_database_libs < 2)
     983        {
     984            if ($mc_sql)
     985            {
     986                verbose($verbose_level_error,
     987                        "\nERROR:  Unable to create initial database without".
     988                        " Perl database libraries.",
     989                        "        Please ensure the Perl DBI and DBD::mysql".
     990                        " modules are installed.");
     991                die("\nPerl database libraries missing, stopped");
     992            }
     993            if ($change_hostname)
     994            {
     995                verbose($verbose_level_error,
     996                        "\nERROR:  Unable to change hostname without Perl".
     997                        " database libraries.",
     998                        "        Please ensure the Perl DBI and DBD::mysql".
     999                        " modules are installed.");
     1000                die("\nPerl database libraries missing, stopped");
     1001            }
     1002            else
     1003            {
     1004                verbose($verbose_level_debug,
     1005                        "Blindly assuming your database is prepared for a".
     1006                        " restore. For better checking,",
     1007                        "please ensure the Perl DBI and DBD::mysql modules".
     1008                        " are installed.");
     1009                return 1;
     1010            }
     1011        }
     1012    # DBI/DBD::mysql are available; check the DB status
     1013        verbose($verbose_level_debug,
     1014                "\nChecking database.");
     1015        if (!database_exists)
     1016        {
     1017            if (create_initial_database)
     1018            {
     1019                verbose($verbose_level_always,
     1020                        "\nERROR:  The database does not exist.");
     1021                return 0;
     1022            }
     1023        }
     1024        my $database_empty = is_database_empty;
     1025        if ($change_hostname)
     1026        {
     1027            if ($database_empty)
     1028            {
     1029                verbose($verbose_level_always,
     1030                        "\nERROR:  Unable to change hostname. The database".
     1031                        " is empty.",
     1032                        "        Please restore a backup, first, then re-run".
     1033                        " this script.");
     1034                return 0;
     1035            }
     1036        }
     1037        elsif ($partial_restore)
     1038        {
     1039            if ($database_empty)
     1040            {
     1041                verbose($verbose_level_always,
     1042                        "\nERROR:  Unable to do a partial restore. The".
     1043                        " database is empty.",
     1044                        "        Please run mythtv-setup, first, then re-run".
     1045                        " this script.");
     1046                return 0;
     1047            }
     1048        }
     1049        else
     1050        {
     1051            if (!$database_empty)
     1052            {
     1053                verbose($verbose_level_always,
     1054                        "\nERROR:  Unable to do a full restore. The".
     1055                        " database contains data.");
     1056                return 0;
     1057            }
     1058        }
     1059        return 1;
     1060    }
     1061
     1062    sub is_gzipped
     1063    {
     1064    # Simple magic number verification.
     1065    # This naive approach works without requiring File::MMagic or any other
     1066    # modules.
     1067        my $result = 0;
     1068        my $magic_number;
     1069        my $gzip_magic_number = pack("C*", 0x1f, 0x8b);
     1070        open(BACKUPFILE, "$backup_conf{'directory'}/$backup_conf{'filename'}")
     1071          or return $result;
     1072        binmode(BACKUPFILE);
     1073        read(BACKUPFILE, $magic_number, 2);
     1074        close(BACKUPFILE);
     1075        return ($gzip_magic_number eq $magic_number);
     1076    }
     1077
     1078# Though it's possible to uncompress the file without writing the uncompressed
     1079# data to a file, doing so is complicated by supporting the use of
     1080# IO::Uncompress::Gunzip /and/ external uncompress programs. Also,
     1081# uncompressing the file separately allows for easier and more precise error
     1082# reporting.
     1083    sub uncompress_backup_file
     1084    {
     1085        if (($d_uncompress eq $uncompress) || ('gunzip' eq $uncompress))
     1086        {
     1087            if (!is_gzipped)
     1088            {
     1089                verbose($verbose_level_debug,
     1090                        "\nBackup file is uncompressed.");
     1091                return 0;
     1092            }
     1093            verbose($verbose_level_debug,
     1094                    "\nBackup file is compressed.");
     1095        # Try to load the IO::Uncompress::Gunzip library if available (but
     1096        # don't require it)
     1097            BEGIN
     1098            {
     1099                our $has_uncompress_gunzip = 1;
     1100                # Though this does nothing, it prevents an invalid "only used
     1101                # once" warning that occurs for users without IO::Uncompress
     1102                # installed.
     1103                undef $GunzipError;
     1104                eval 'use IO::Uncompress::Gunzip qw(gunzip $GunzipError);';
     1105                if ($@)
     1106                {
     1107                    $has_uncompress_gunzip = 0;
     1108                }
     1109            }
     1110            if (!$has_uncompress_gunzip)
     1111            {
     1112                verbose($verbose_level_debug,
     1113                        " - IO::Uncompress::Gunzip is not installed.");
     1114            }
     1115            else
     1116            {
     1117                verbose($verbose_level_debug,
     1118                        " - Uncompressing backup file with".
     1119                        " IO::Uncompress::Gunzip.");
     1120                my ($bfh, $temp_backup_filename) = tempfile(UNLINK => 1);
     1121                my $result = gunzip(
     1122                    "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
     1123                    $bfh);
     1124                if ((defined($result)) &&
     1125                    (-f "$temp_backup_filename") &&
     1126                    (-r "$temp_backup_filename") &&
     1127                    (-s "$temp_backup_filename"))
     1128                {
     1129                    $backup_conf{'directory'} = '';
     1130                    $backup_conf{'filename'} = "$temp_backup_filename";
     1131                    return 0;
     1132                }
     1133                verbose($verbose_level_always,
     1134                        "   Error:  $GunzipError");
     1135            }
     1136        }
     1137        else
     1138        {
     1139            verbose($verbose_level_debug,
     1140                    "\nUnrecognized uncompress program.".
     1141                    " Assuming backup file is compressed.",
     1142                    " - If the file is not compressed, please do not specify".
     1143                    " a custom uncompress",
     1144                    "   program name.");
     1145        }
     1146    # Try to uncompress the file with the uncompress binary.
     1147    # With the approach, the original backup file will be uncompressed and
     1148    # left uncompressed.
     1149        verbose($verbose_level_debug,
     1150                " - Uncompressing backup file with $uncompress.",
     1151                "   The original backup file will be left uncompressed.".
     1152                " Please recompress,",
     1153                "   if desired.");
     1154        my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
     1155        my $output = `$uncompress '$backup_path' 2>&1`;
     1156        my $exit = $? >> 8;
     1157        verbose($verbose_level_debug,
     1158                "\n$uncompress exited with status:  $exit");
     1159        if ($exit)
     1160        {
     1161            verbose($verbose_level_debug,
     1162                    "$uncompress output:", $output);
     1163        }
     1164        else
     1165        {
     1166            if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     1167            {
     1168        # Assume the final extension was removed by uncompressing.
     1169                $backup_conf{'filename'} =~ s/\.\w+$//;
     1170                if (!-r "$backup_conf{'directory'}/$backup_conf{'filename'}")
     1171                {
     1172                    verbose($verbose_level_error,
     1173                            "\nERROR:  Unable to find uncompressed backup".
     1174                            " file.");
     1175                    die("\nInvalid backup filename, stopped");
     1176                }
     1177            }
     1178        }
     1179        return $exit;
     1180    }
     1181
     1182    sub do_hostname_change
     1183    {
     1184        my $exit = 0;
     1185        if (!$new_hostname)
     1186        {
     1187            $exit++;
     1188            verbose($verbose_level_always,
     1189                    "\nERROR:  Cannot change hostname without --new_hostname".
     1190                    " value.");
     1191        }
     1192        if (!$old_hostname)
     1193        {
     1194            $exit++;
     1195            verbose($verbose_level_always,
     1196                    "\nERROR:  Cannot change hostname without --old_hostname".
     1197                    " value.");
     1198        }
     1199        if ($exit > 0)
     1200        {
     1201            die("\nInvalid --old/--new_hostname value(s) for".
     1202                " --change_hostname, stopped");
     1203        }
     1204    # Get a list of all tables in the DB.
     1205        if (defined($dbh))
     1206        {
     1207            my $num_tables = 0;
     1208            my $sth_tables;
     1209            my $table_cat;
     1210            my $table_schema;
     1211            my $table_name;
     1212            my $sth_columns;
     1213            my $column_name;
     1214            my $query;
     1215            my $sth_update;
     1216            my $result;
     1217
     1218            $sth_tables = $dbh->table_info('', '', '', 'TABLE');
     1219            while (my $table = $sth_tables->fetchrow_hashref)
     1220            {
     1221            # Loop over all tables in the DB, checking for a hostname column.
     1222                $num_tables++;
     1223                $table_cat = $table->{'TABLE_CAT'};
     1224                $table_schema = $table->{'TABLE_SCHEM'};
     1225                $table_name = $table->{'TABLE_NAME'};
     1226                $sth_columns = $dbh->column_info($table_cat, $table_schema,
     1227                                                 $table_name, '%');
     1228                while (my $column = $sth_columns->fetchrow_hashref)
     1229                {
     1230                # If a hostname column exists, change its value.
     1231                    $column_name = $column->{'COLUMN_NAME'};
     1232                    if ($column_name eq 'hostname')
     1233                    {
     1234                        verbose($verbose_level_debug,
     1235                                "Found 'hostname' column in $table_name.");
     1236                        $query = "UPDATE $table_name SET hostname = ?".
     1237                                 " WHERE hostname = ?";
     1238                        $sth_update = $dbh->prepare($query);
     1239                        $sth_update->bind_param(1, $new_hostname);
     1240                        $sth_update->bind_param(2, $old_hostname);
     1241                        $result = $sth_update->execute;
     1242                        if (!defined($result))
     1243                        {
     1244                            verbose($verbose_level_always,
     1245                                    "Unable to update hostname in table: ".
     1246                                    $table_name,
     1247                                    $sth_update->errstr);
     1248                            $exit++;
     1249                        }
     1250                        else
     1251                        {
     1252                            verbose($verbose_level_debug,
     1253                                    "Updated ".
     1254                                    (($result == 0E0) ? '0' : $result)
     1255                                    ." rows in table: $table_name");
     1256                        }
     1257                        last;
     1258                    }
     1259                }
     1260            }
     1261            if ($num_tables == 0)
     1262            {
     1263                verbose($verbose_level_always,
     1264                        "Database is empty. Cannot change hostname.");
     1265                return 1;
     1266            }
     1267        # delete (orphaned) rows with hostname coded into chainid in tvchain
     1268        # live-<hostname>-2008-06-26T18:43:18
     1269            $table_name = 'tvchain';
     1270            $query = "DELETE FROM $table_name WHERE chainid LIKE ?";
     1271            $sth_update = $dbh->prepare($query);
     1272            $sth_update->bind_param(1, '%'.$old_hostname.'%');
     1273            $result = $sth_update->execute;
     1274            if (!defined($result))
     1275            {
     1276                verbose($verbose_level_debug,
     1277                        "Unable to remove orphaned $table_name rows.",
     1278                        $sth_update->errstr);
     1279            }
     1280            else
     1281            {
     1282                verbose($verbose_level_debug,
     1283                        "Removed ".
     1284                        (($result == 0E0) ? '0' : $result)
     1285                        ." orphaned entries in table: $table_name");
     1286            }
     1287        # hostname coded into SGweightPerDir setting in settings (modify)
     1288        # SGweightPerDir:<hostname>:<directory>
     1289            $table_name = 'settings';
     1290            $query = "UPDATE $table_name SET value = REPLACE(value, ?, ?)".
     1291                     " WHERE value LIKE ?";
     1292            $sth_update = $dbh->prepare($query);
     1293            $sth_update->bind_param(1, $old_hostname);
     1294            $sth_update->bind_param(2, $new_hostname);
     1295            $sth_update->bind_param(3, '%'.$old_hostname.'%');
     1296            $result = $sth_update->execute;
     1297            if (!defined($result))
     1298            {
     1299                verbose($verbose_level_always,
     1300                        "Unable to update SGweightPerDir setting for host.",
     1301                        $sth_update->errstr);
     1302            }
     1303            else
     1304            {
     1305                verbose($verbose_level_debug,
     1306                        "Updated ".
     1307                        (($result == 0E0) ? '0' : $result)
     1308                        ." SGweightPerDir settings.");
     1309            }
     1310        }
     1311        return $exit;
     1312    }
     1313
     1314    sub restore_backup
     1315    {
     1316        my $exit = 0;
     1317        my $defaults_extra_file = create_defaults_extra_file;
     1318        my $host_arg = '';
     1319        my $port_arg = '';
     1320        my $user_arg = '';
     1321        my $filter = '';
     1322        if ($defaults_extra_file)
     1323        {
     1324            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     1325        }
     1326        else
     1327        {
     1328            $defaults_arg='';
     1329        }
     1330    # Create the args for host, port, and user, shell-escaping values, as
     1331    # necessary.
     1332        if ($mysql_conf{'db_host'})
     1333        {
     1334            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     1335            $host_arg=" --host='$mysql_conf{'db_host'}'";
     1336        }
     1337        if ($mysql_conf{'db_port'} > 0)
     1338        {
     1339            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     1340            $port_arg=" --port='$mysql_conf{'db_port'}'";
     1341        }
     1342        if ($mysql_conf{'db_user'})
     1343        {
     1344            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     1345            $user_arg=" --user='$mysql_conf{'db_user'}'";
     1346        }
     1347    # Configure a filter for a partial/new-host restore
     1348        if ($partial_restore)
     1349        {
     1350            my @partial_restore_tables;
     1351            if (defined($with_plugin_data))
     1352            {
     1353            # Blacklist the MythTV tables we don't want to keep
     1354            # This may result in keeping old tables that were dropped in
     1355            # previous DB schema updates if the user is running a restore
     1356            # script from an older version of MythTV, but the extra data will
     1357            # only take a bit of hard drive space.
     1358                @partial_restore_tables = ('callsignnetworkmap',
     1359                                           'capturecard',
     1360                                           'cardinput',
     1361                                           'channel',
     1362                                           'channelscan',
     1363                                           'channelscan_channel',
     1364                                           'channelscan_dtv_multiplex',
     1365                                           'codecparams',
     1366                                           'conflictresolutionany', # historic
     1367                                           'conflictresolutionoverride', # hst
     1368                                           'conflictresolutionsingle',   # hst
     1369                                           'credits',
     1370                                           'customexample',
     1371                                           'diseqc_config',
     1372                                           'diseqc_tree',
     1373                                           'displayprofilegroups',
     1374                                           'displayprofiles',
     1375                                           'dtv_multiplex',
     1376                                           'dtv_privatetypes',
     1377                                           'dvb_channel',           # historic
     1378                                           'dvb_pids',              # historic
     1379                                           'dvb_sat',               # historic
     1380                                           'dvb_signal_quality',    # historic
     1381                                           'dvb_transport',         # historic
     1382                                           'eit_cache',
     1383                                           'favorites',
     1384                                           'filemarkup',
     1385                                           'housekeeping',
     1386                                           'inputgroup',
     1387                                           'inuseprograms',
     1388                                           'jobqueue',
     1389                                           'jumppoints',
     1390                                           'keybindings',
     1391                                           'keyword',
     1392                                           'mythlog',
     1393                                           'networkiconmap',
     1394                                           'oldfind',
     1395                                           'oldprogram',
     1396                                           'people',
     1397                                           'pidcache',
     1398                                           'playgroup',
     1399                                           'powerpriority',
     1400                                           'profilegroups',
     1401                                           'program',
     1402                                           'programgenres',
     1403                                           'programrating',
     1404                                           'recgrouppassword',
     1405                                           'recordedcredits',
     1406                                           'recordedfile',
     1407                                           'recordedprogram',
     1408                                           'recordingprofiles',
     1409                                           'recordmatch',
     1410                                           'recordoverride',        # historic
     1411                                           'record_tmp',
     1412                                           'schemalock',
     1413                                           'settings',
     1414                                           'storagegroup',
     1415                                           'transcoding',           # historic
     1416                                           'tvchain',
     1417                                           'upnpmedia',
     1418                                           'videobookmarks',        # historic
     1419                                           'videosource',
     1420                                           'xvmc_buffer_settings'   # historic
     1421                                          );
     1422            }
     1423            else
     1424            {
     1425            # Whitelist the tables we want to keep
     1426                @partial_restore_tables = ('oldrecorded',
     1427                                           'record',
     1428                                           'recorded',
     1429                                           'recordedmarkup',
     1430                                           'recordedprogram',
     1431                                           'recordedrating',
     1432                                           'recordedseek');
     1433            }
     1434            if (!defined($restore_xmltvids))
     1435            {
     1436                $filter = '^INSERT INTO \`(' .
     1437                          join('|', @partial_restore_tables) . ')\` ';
     1438                verbose($verbose_level_debug,
     1439                        "\nRestoring partial backup with filter:", $filter);
     1440            }
     1441        }
     1442        my $command = "${mysql_client}${defaults_arg}${host_arg}${port_arg}".
     1443                      "${user_arg} $mysql_conf{'db_name'}";
     1444        verbose($verbose_level_debug,
     1445                "\nExecuting command:", $command);
     1446        my $read_status = open(BACKUP,
     1447            "<$backup_conf{'directory'}/$backup_conf{'filename'}");
     1448        if (!defined($read_status))
     1449        {
     1450            verbose($verbose_level_always,
     1451                    "\nERROR: Unable to read backup file.");
     1452            return 255;
     1453        }
     1454        my $write_status = open(COMMAND, "| $command");
     1455        if (!defined($write_status))
     1456        {
     1457            verbose($verbose_level_always,
     1458                    "\nERROR: Unable to execute $mysql_client.");
     1459            return 254;
     1460        }
     1461        my $lines_total = 0;
     1462        my $lines_restored = 0;
     1463        while (<BACKUP>)
     1464        {
     1465            $lines_total++;
     1466            if ($partial_restore)
     1467            {
     1468                if ($restore_xmltvids)
     1469                {
     1470                # Send all lines through
     1471                }
     1472                if ($with_plugin_data)
     1473                {
     1474                # Skip tables in the blacklist
     1475                    next if /$filter/;
     1476                }
     1477                else
     1478                {
     1479                # Skip tables not in the whitelist
     1480                    next if !/$filter/;
     1481                }
     1482            }
     1483            $lines_restored++;
     1484            print COMMAND or die("\nERROR:  Cannot write to ".
     1485                                 "$mysql_client, stopped");
     1486        }
     1487        close(COMMAND);
     1488        close(BACKUP);
     1489        $exit = $?;
     1490        verbose($verbose_level_debug,
     1491                "\n$mysql_client exited with status:  $exit",
     1492                "\nRestored $lines_restored of $lines_total lines.");
     1493        return $exit;
     1494    }
     1495
     1496##############################################################################
     1497# Main functionality
     1498##############################################################################
     1499
     1500# The first argument after option parsing, if it exists, should be a database
     1501# information file.
     1502    $database_information_file = shift;
     1503
     1504    configure_environment;
     1505    read_config;
     1506    check_config;
     1507
     1508    print_configuration;
     1509
     1510    my $status = 1;
     1511    if (check_database)
     1512    {
     1513        if ($change_hostname)
     1514        {
     1515            $status = do_hostname_change;
     1516            print("\nSuccessfully changed hostname.\n") if (!$status);
     1517        }
     1518        elsif (!uncompress_backup_file)
     1519        {
     1520            $status = restore_backup;
     1521            print("\nSuccessfully restored backup.\n") if (!$status);
     1522        }
     1523    }
     1524
     1525    $dbh->disconnect if (defined($dbh));
     1526
     1527    exit $status;
     1528
  • programs/scripts/database/database_mythconverg_backup.pl

     
     1#!/usr/bin/perl -w
     2#
     3# database_mythconverg_backup.pl
     4#
     5# Creates a backup of the MythTV database.
     6#
     7# For details, see:
     8#   database_mythconverg_backup.pl --help
     9
     10# Includes
     11    use Getopt::Long;
     12    use File::Temp qw/ tempfile /;
     13
     14# Script info
     15    $NAME           = 'MythTV Database Backup Script';
     16    $VERSION        = '1.0.1';
     17
     18# Some variables we'll use here
     19    our ($username, $homedir, $mythconfdir, $database_information_file);
     20    our ($mysqldump, $compress, $rotate, $rotateglob, $backup_xmltvids);
     21    our ($usage, $debug, $show_version, $show_version_script, $dbh);
     22    our ($d_db_name, $d_mysqldump, $d_compress, $d_rotate, $d_rotateglob);
     23# This script does not accept a database password on the command-line.
     24# Any packager who enables the functionality should modify the --help output.
     25#    our ($db_password);
     26    our ($db_hostname, $db_port, $db_username, $db_name, $db_schema_version);
     27    our ($backup_directory, $backup_filename);
     28    our ($verbose_level_always, $verbose_level_debug, $verbose_level_error);
     29
     30    our %mysql_conf  = ('db_host'       => '',
     31                        'db_port'       => -1,
     32                        'db_user'       => '',
     33                        'db_pass'       => '',
     34                        'db_name'       => '',
     35                        'db_schemaver'  => ''
     36                        );
     37    our %backup_conf = ('directory'     => '',
     38                        'filename'      => ''
     39                       );
     40
     41# Debug levels
     42    $verbose_level_always = 0;
     43    $verbose_level_debug = 1;
     44    $verbose_level_error = 255;
     45
     46# Defaults
     47    $d_db_name       = 'mythconverg';
     48    $d_mysqldump     = 'mysqldump';
     49    $d_compress      = 'gzip';
     50    $d_rotate        = 5;
     51    $d_rotateglob    = $d_db_name . '-????-??????????????.sql*';
     52
     53# Provide default values for GetOptions
     54    $mysqldump       = $d_mysqldump;
     55    $compress        = $d_compress;
     56    $rotate          = $d_rotate;
     57    $rotateglob      = $d_rotateglob;
     58    $debug           = 0;
     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               'mysqldump=s'                        => \$mysqldump,
     71               'compress=s'                         => \$compress,
     72               'rotate=i'                           => \$rotate,
     73               'rotateglob|glob=s'                  => \$rotateglob,
     74               'backup_xmltvids|xmltvids'           => \$backup_xmltvids,
     75               'usage|help|h'                       => \$usage,
     76               'version'                            => \$show_version,
     77               'script_version|v'                   => \$show_version_script,
     78               'verbose|debug|d+'                   => \$debug
     79              );
     80
     81# Print version information
     82    sub print_version_information
     83    {
     84        my $script_name = substr $0, rindex($0, '/') + 1;
     85        print "$NAME\n$script_name\nversion: $VERSION\n";
     86    }
     87
     88    if ($show_version_script)
     89    {
     90        print "$NAME,$VERSION,,\n";
     91        exit;
     92    }
     93    elsif ($show_version)
     94    {
     95        print_version_information;
     96        exit;
     97    }
     98
     99
     100# Print usage
     101    if ($usage)
     102    {
     103        print_version_information;
     104        print <<EOF;
     105
     106Usage:
     107  $0 [options|database_information_file]
     108
     109Creates a backup of the MythTV database.
     110
     111DETAILED DESCRIPTION:
     112
     113This script can be called by MythTV for creating automatic database backups.
     114In this mode, it is always exected with a single command-line argument
     115specifying the name of a "database information file" (see DATABASE INFORMATION
     116FILE, below), which contains sufficient information about the database and the
     117backup to allow the script to create a backup without needing any additional
     118configuration files. In this mode, all other MythTV configuration files
     119(including config.xml, mysql.txt) are ignored, but the backup resource file
     120(see RESOURCE FILE, below) and the MySQL option files (i.e. /etc/my.cnf or
     121~/.my.cnf) will be honored.
     122
     123The script can also be called interactively (i.e. "manually") by the user to
     124create a database backup on demand. Required information may be passed into
     125the script using command-line arguments or with a database information file.
     126If a database information file is specified, all command-line arguments will be
     127ignored. If no database information file is specified, the script will attempt
     128to determine the appropriate configuration by using the MythTV configuration
     129file(s) (prefering config.xml, but falling back to mysql.txt if no config.xml
     130exists). Once the MythTV configuration file has been parsed, the backup
     131resource file (see RESOURCE FILE, below) will be parsed, then command-line
     132arguments will be applied (thus overriding any values determined from the
     133configuration files).
     134
     135The only information required by the script is the directory in which the
     136backup should be created. Therefore, when using a database information file,
     137the DBBackupDirectory should be specified, or if running manually, the
     138--directory command-line argument should be specified. The DBBackupDirectory
     139may be specified in a backup resource file (see RESOURCE FILE, below). Doing
     140so is especially useful for manual backups. If the specified directory is not
     141writable, the script will terminate. Likewise, if a file whose name matches
     142the name to be used for the backup file already exists, the script will
     143terminate.
     144
     145If the database name is not specified, the script will attempt to use the
     146MythTV default database name, $d_db_name. Note that the same is not true for
     147the database username and database password. These must be explicitly
     148specified. The password must be specified in a database information file, a
     149backup resource file, or a MySQL options file. The username may be specified
     150the same way or may be specified using a command-line argument if not using a
     151database information file.
     152
     153While this script may be called while MythTV is running, there is a possibility
     154of creating a backup with data integrity errors (i.e. if MythTV updates data in
     155multiple tables between the time the script backs up the first and subsequent
     156tables). Also, depending on your system configuration, performing a backup
     157(which may result in locking a table while it is being backed up) while
     158recording may cause corruption of the recording or inability to properly write
     159recording data (such as the recording seek table) to the database.
     160Therefore, if configuring this script to run in a cron job, try to ensure it
     161runs at a time when recordings are least likely to occur. Alternatively, by
     162choosing to run the script in a system start/shutdown script (i.e. an init
     163script), you may call the script before starting mythbackend or after stopping
     164mythbackend. Note, however, that checking whether to perform the backup is the
     165responsibility of the init script (not this script)--i.e. in a system with
     166multiple frontends/backends, the init script should ensure the backup is
     167created only on the master backend.
     168
     169DATABASE INFORMATION FILE
     170
     171The database information file contains information about the database and the
     172backup. The information within the file is specified as name=value pairs using
     173the same names as used by the MythTV config.xml and mysql.txt configuration
     174files. The following variables are recognized:
     175
     176  DBHostName - The hostname (or IP address) which should be used to find the
     177               MySQL server.
     178  DBPort - The TCP/IP port number to use for the connection. This may have a
     179           value of 0, i.e. if the hostname is localhost or if the server is
     180           using the default MySQL port or the port specified in a MySQL
     181           options file.
     182  DBUserName - The database username to use when connecting to the server.
     183  DBPassword - The password to use when connecting to the server.
     184  DBName - The name of the database that contains the MythTV data.
     185  DBSchemaVer - The MythTV schema version of the database. This value will be
     186                used to create the backup filename, but only if the filename
     187                has not been specified using DBBackupFilename or the --filename
     188                argument.
     189  DBBackupDirectory - The directory in which the backup file should be
     190                      created. This directory may have been specially
     191                      configured by the user as the "DB Backups" storage
     192                      group. It is recommended that this directory be
     193                      used--especially in "common-use" scripts such as those
     194                      provided by distributions.
     195  DBBackupFilename - The name of the file in which the backup should be
     196                     created. Additional extensions may be added by this
     197                     script as required (i.e. adding an appropriate suffix,
     198                     such as ".gz", to the file if it is compressed). If the
     199                     filename recommended by mythbackend is used, it will be
     200                     displayed in the GUI messages provided for the user. If
     201                     the recommended filename is not used, the user will not be
     202                     told where to find the backup file. If no value is
     203                     provided, a filename using the default filename format
     204                     will be chosen.
     205  mysqldump        - The path (including filename) of the mysqldump executable.
     206  compress         - The command (including path, if necessary) to use to
     207                     compress the backup. Using gzip is significantly less
     208                     resource intensive on an SQL backup file than using bzip2,
     209                     at the cost of a slightly (about 33%) larger compressed
     210                     filesize, a difference which should be irrelevant at the
     211                     filesizes involved (especially when compared to the size
     212                     of recording files). If you decide to use another
     213                     compression algorithm, please ensure you test it
     214                     appropriately to verify it does not negatively affect
     215                     operation of your system. If no value is specified for
     216                     compress or if the value '$d_compress' is specified, the
     217                     script will first attempt to use the IO::Compress::Gzip
     218                     module to compress the backup file, but, if not available,
     219                     will run the command specified. Therefore, if
     220                     IO::Compress::Gzip is installed and functional, specifying
     221                     a value for compress is unnecessary. If neither approach
     222                     works, the backup file will be left uncompressed.
     223  rotate           - The number of backups to keep when rotating. To disable
     224                     rotation, specify -1. Backup rotation is performed by
     225                     identifying all files in DBBackupDirectory whose names
     226                     match the glob specified by rotateglob. It is critical
     227                     that the chosen backup filenames can be sorted properly
     228                     using an alphabetical sort. If using the default filename
     229                     format--which contains the DBSchemaVer--and you downgrade
     230                     MythTV and restore a backup from an older DBSchemaVer,
     231                     make sure you move the backups from the newer DBSchemaVer
     232                     out of the DBBackupDirectory or they may cause your new
     233                     backups to be deleted.
     234  rotateglob       - The sh-like glob used to identify files within
     235                     DBBackupDirectory to be considered for rotation. Be
     236                     very careful with the value--especially if using a
     237                     DBBackupDirectory that contains any files other than
     238                     backups.
     239
     240RESOURCE FILE
     241
     242The backup resource file specifies values using the same format as described
     243for the database information file, above, but is intended as a "permanent,"
     244user-created configuration file. The database information file is intended as
     245a "single-use" configuration file, often created automatically (i.e. by a
     246program, such as mythbackend, or a script). The backup resource file should be
     247placed at "~/.mythtv/backuprc" and given appropriate permissions. To be usable
     248by the script, it must be readable. However, it should be protected as
     249required--i.e. if the DBPassword is specified, it should be made readable only
     250by the owner.
     251
     252When specifying a database information file, the resource file is parsed before
     253the database information file to prevent the resource file from overriding the
     254information in the database information file. When no database information
     255file is specified, the resource file is parsed after the MythTV configuration
     256files, but before the command-line arguments to allow the resource file to
     257override values in the configuration files and to allow command-line arguments
     258to override resource file defaults.
     259
     260options:
     261
     262--hostname [database hostname]
     263
     264    The hostname (or IP address) which should be used to find the MySQL server.
     265    See DBHostName, above.
     266
     267--port [database port]
     268
     269    The TCP/IP port number to use for connection to the MySQL server. See
     270    DBPort, above.
     271
     272--username [database username]
     273
     274    The MySQL username to use for connection to the MySQL server. See
     275    DBUserName, above.
     276
     277--name [database name]
     278
     279    The name of the database containing the MythTV data. See DBName, above.
     280
     281    Default:  $d_db_name
     282
     283--schemaver [MythTV database schema version]
     284
     285    The MythTV schema version. See DBSchemaVer, above.
     286
     287--directory [directory]
     288
     289    The directory in which the backup file should be stored. See
     290    DBBackupDirectory, above.
     291
     292--filename [database backup filename]
     293
     294    The name to use for the database backup file. If not provided, a filename
     295    using a default format will be chosen. See DBBackupFilename, above.
     296
     297--mysqldump [path]
     298
     299    The path (including filename) of the mysqldump executable. See mysqldump
     300    in the DATABASE INFORMATION FILE description, above.
     301
     302    Default:  $d_mysqldump
     303
     304--compress [path]
     305
     306    The command (including path, if necessary) to use to compress the backup.
     307    See compress in the DATABASE INFORMATION FILE description, above.
     308
     309    Default:  $d_compress
     310
     311--rotate [number]
     312    The number of backups to keep when rotating. To disable rotation, specify
     313    -1. See rotate in the DATABASE INFORMATION FILE description, above.
     314
     315    Default:  $d_rotate
     316
     317--rotateglob [glob]
     318    The sh-like glob used to identify files within DBBackupDirectory to be
     319    considered for rotation. See rotateglob in the DATABASE INFORMATION FILE
     320    description, above.
     321
     322    Default:  $d_rotateglob
     323
     324--backup_xmltvids
     325    Rather than creating a backup of the entire database, create a backup of
     326    xmltvid's. This is useful when doing a full channel scan. The resulting
     327    backup is a series of SQL UPDATE statements that can be executed to set
     328    the xmltvid for channels whose callsign is the same before and after
     329    the scan. Note that the backup file will contain comments with additional
     330    channel information, which you can use to identify channels in case the
     331    callsign changes.
     332
     333--help
     334
     335    Show this help text.
     336
     337--version
     338
     339    Show version information.
     340
     341--verbose
     342
     343    Show what is happening.
     344
     345--script_version | -v
     346
     347    Show script version information. This is primarily useful for scripts
     348    or programs needing to parse the version information.
     349
     350QUICK START:
     351
     352Create a file ~/.mythtv/backuprc with a single line,
     353"DBBackupDirectory=/home/mythtv" (no quotes), and run this script to create a
     354database backup. Use the --verbose argument to see what is happening.
     355
     356# echo "DBBackupDirectory=/home/mythtv" > ~/.mythtv/backuprc
     357# $0 --verbose
     358
     359Make sure you keep the backuprc file for next time. Once you have successfully
     360created a backup, the script may be run without the --verbose argument.
     361
     362To backup xmltvids:
     363
     364Ensure you have a ~/.mythtv/backuprc file, as described above, and execute this
     365script with the --backup_xmltvids argument.
     366
     367# $0 --backup_xmltvids
     368
     369EOF
     370        exit;
     371    }
     372
     373    sub verbose
     374    {
     375        my $level = shift;
     376        my $error = 0;
     377        if ($level == $verbose_level_error)
     378        {
     379            $error = 1;
     380        }
     381        else
     382        {
     383            return unless ($debug >= $level);
     384        }
     385        print { $error ? STDOUT : STDERR } join("\n", @_), "\n";
     386    }
     387
     388    sub print_configuration
     389    {
     390        verbose($verbose_level_debug,
     391                "\nDatabase Information:",
     392                "         DBHostName:  $mysql_conf{'db_host'}",
     393                "             DBPort:  $mysql_conf{'db_port'}",
     394                "         DBUserName:  $mysql_conf{'db_user'}",
     395                "         DBPassword:  " .
     396                    ( $mysql_conf{'db_pass'} ? 'XXX' : '' ),
     397                  #  "$mysql_conf{'db_pass'}",
     398                "             DBName:  $mysql_conf{'db_name'}",
     399                "        DBSchemaVer:  $mysql_conf{'db_schemaver'}",
     400                "  DBBackupDirectory:  $backup_conf{'directory'}",
     401                "   DBBackupFilename:  $backup_conf{'filename'}");
     402        verbose($verbose_level_debug,
     403                "\nExecutables:",
     404                "          mysqldump:  $mysqldump",
     405                "           compress:  $compress");
     406    }
     407
     408    sub configure_environment
     409    {
     410        verbose($verbose_level_debug,
     411                "\nConfiguring environment:");
     412
     413    # Get the user's login and home directory, so we can look for config files
     414        ($username, $homedir) = (getpwuid $>)[0,7];
     415        $username = $ENV{'USER'} if ($ENV{'USER'});
     416        $homedir  = $ENV{'HOME'} if ($ENV{'HOME'});
     417        if ($username && !$homedir)
     418        {
     419            $homedir = "/home/$username";
     420            if (!-e $homedir && -e "/Users/$username")
     421            {
     422                $homedir = "/Users/$username";
     423            }
     424        }
     425        verbose($verbose_level_debug,
     426                "  -    username:  $username",
     427                "  -        HOME:  $homedir");
     428
     429    # Find the config directory
     430        $mythconfdir = $ENV{'MYTHCONFDIR'}
     431            ? $ENV{'MYTHCONFDIR'}
     432            : "$homedir/.mythtv"
     433            ;
     434
     435        verbose($verbose_level_debug,
     436                "  - MYTHCONFDIR:  $mythconfdir");
     437    }
     438
     439# Though much of the configuration file parsing could be done by the MythTV
     440# Perl bindings, using them to retrieve database information is not appropriate
     441# for a backup script. The Perl bindings require the backend to be running and
     442# use UPnP for autodiscovery. Also, parsing the files "locally" allows
     443# supporting even the old MythTV database configuration file, mysql.txt.
     444    sub parse_database_information
     445    {
     446        my $file = shift;
     447        verbose($verbose_level_debug,
     448                "  - checking:  $file");
     449        return 0 unless ($file && -e $file);
     450        verbose($verbose_level_debug,
     451                "     parsing:  $file");
     452        open(CONF, $file) or die("\nERROR:  Unable to read $file:  $!".
     453                                 ", stopped");
     454        while (my $line = <CONF>)
     455        {
     456        # Cleanup
     457            next if ($line =~ m/^\s*#/);
     458            $line =~ s/^str //;
     459            chomp($line);
     460            $line =~ s/^\s+//;
     461            $line =~ s/\s+$//;
     462        # Split off the var=val pairs
     463            my ($var, $val) = split(/ *[\=\: ] */, $line, 2);
     464        # Also look for <var>val</var> from config.xml
     465            if ($line =~ m/<(\w+)>(.+)<\/(\w+)>$/ && $1 eq $3)
     466            {
     467                $var = $1; $val = $2;
     468            }
     469            next unless ($var && $var =~ m/\w/);
     470            if ($var eq 'DBHostName')
     471            {
     472                $mysql_conf{'db_host'} = $val;
     473            }
     474            elsif ($var eq 'DBPort')
     475            {
     476                $mysql_conf{'db_port'} = $val;
     477            }
     478            elsif ($var eq 'DBUserName')
     479            {
     480                $mysql_conf{'db_user'} = $val;
     481            }
     482            elsif ($var eq 'DBPassword')
     483            {
     484                $mysql_conf{'db_pass'} = $val;
     485            }
     486            elsif ($var eq 'DBName')
     487            {
     488                $mysql_conf{'db_name'} = $val;
     489            }
     490            elsif ($var eq 'DBSchemaVer')
     491            {
     492                $mysql_conf{'db_schemaver'} = $val;
     493            }
     494            elsif ($var eq 'DBBackupDirectory')
     495            {
     496                $backup_conf{'directory'} = $val;
     497            }
     498            elsif ($var eq 'DBBackupFilename')
     499            {
     500                $backup_conf{'filename'} = $val;
     501            }
     502            elsif ($var eq 'mysqldump')
     503            {
     504                $mysqldump = $val;
     505            }
     506            elsif ($var eq 'compress')
     507            {
     508                $compress = $val;
     509            }
     510            elsif ($var eq 'rotate')
     511            {
     512                $rotate = $val;
     513            }
     514            elsif ($var eq 'rotateglob')
     515            {
     516                $rotateglob = $val;
     517            }
     518        }
     519        close CONF;
     520        return 1;
     521    }
     522
     523    sub read_mysql_txt
     524    {
     525    # Read the "legacy" mysql.txt file in use by MythTV. It could be in a
     526    # couple places, so try the usual suspects in the same order that mythtv
     527    # does in libs/libmyth/mythcontext.cpp
     528        my $found = 0;
     529        my $result = 0;
     530        my @mysql = ('/usr/local/share/mythtv/mysql.txt',
     531                     '/usr/share/mythtv/mysql.txt',
     532                     '/usr/local/etc/mythtv/mysql.txt',
     533                     '/etc/mythtv/mysql.txt',
     534                     $homedir ? "$homedir/.mythtv/mysql.txt"    : '',
     535                     'mysql.txt',
     536                     $mythconfdir ? "$mythconfdir/mysql.txt"    : '',
     537                    );
     538        foreach my $file (@mysql)
     539        {
     540            $found = parse_database_information($file);
     541            $result = $result + $found;
     542        }
     543        return $result;
     544    }
     545
     546    sub read_resource_file
     547    {
     548        parse_database_information("$mythconfdir/backuprc");
     549    }
     550
     551    sub apply_arguments
     552    {
     553        verbose($verbose_level_debug,
     554                "\nApplying command-line arguments.");
     555        if ($db_hostname)
     556        {
     557            $mysql_conf{'db_host'} = $db_hostname;
     558        }
     559        if ($db_port)
     560        {
     561            $mysql_conf{'db_port'} = $db_port;
     562        }
     563        if ($db_username)
     564        {
     565            $mysql_conf{'db_user'} = $db_username;
     566        }
     567    # This script does not accept a database password on the command-line.
     568#        if ($db_password)
     569#        {
     570#            $mysql_conf{'db_pass'} = $db_password;
     571#        }
     572        if ($db_name)
     573        {
     574            $mysql_conf{'db_name'} = $db_name;
     575        }
     576        if ($db_schema_version)
     577        {
     578            $mysql_conf{'db_schemaver'} = $db_schema_version;
     579        }
     580        if ($backup_directory)
     581        {
     582            $backup_conf{'directory'} = $backup_directory;
     583        }
     584        if ($backup_filename)
     585        {
     586            $backup_conf{'filename'} = $backup_filename;
     587        }
     588    }
     589
     590    sub read_config
     591    {
     592        my $result = 0;
     593    # If specified, use only the database information file
     594        if ($database_information_file)
     595        {
     596            verbose($verbose_level_debug,
     597                    "\nDatabase Information File specified. Ignoring all".
     598                    " command-line arguments");
     599            verbose($verbose_level_debug,
     600                    "\nDatabase Information File:".
     601                    " $database_information_file");
     602            unless (-T "$database_information_file")
     603            {
     604                die("\nERROR:  Invalid database information file, stopped");
     605            }
     606        # When using a database information file, parse the resource file first
     607        # so it cannot override database information file settings
     608            read_resource_file;
     609            $result = parse_database_information($database_information_file);
     610            return $result;
     611        }
     612
     613    # No database information file, so try the MythTV configuration files.
     614        verbose($verbose_level_debug,
     615                "\nParsing configuration files:");
     616    # Prefer the config.xml file
     617        my $file = $mythconfdir ? "$mythconfdir/config.xml" : '';
     618        $result = parse_database_information($file);
     619        if (!$result)
     620        {
     621        # Use the "legacy" mysql.txt file as a fallback
     622            $result = read_mysql_txt;
     623        }
     624    # Read the resource file next to override the config file information, but
     625    # to allow command-line arguments to override resource file "defaults"
     626        read_resource_file;
     627    # Apply the command-line arguments to override the information provided
     628    # by the config file(s).
     629        apply_arguments;
     630        return $result;
     631    }
     632
     633    sub check_database_libs
     634    {
     635    # Try to load the DBI library if available (but don't require it)
     636        BEGIN
     637        {
     638            our $has_dbi = 1;
     639            eval 'use DBI;';
     640            if ($@)
     641            {
     642                $has_dbi = 0;
     643            }
     644        }
     645        verbose($verbose_level_debug,
     646                "\nDBI is not installed.") if (!$has_dbi);
     647    # Try to load the DBD::mysql library if available (but don't
     648    # require it)
     649        BEGIN
     650        {
     651            our $has_dbd = 1;
     652            eval 'use DBD::mysql;';
     653            if ($@)
     654            {
     655                $has_dbd = 0;
     656            }
     657        }
     658        verbose($verbose_level_debug,
     659                "\nDBD::mysql is not installed.") if (!$has_dbd);
     660        return ($has_dbi + $has_dbd);
     661    }
     662
     663    sub check_database
     664    {
     665        if (!defined($dbh))
     666        {
     667            my $have_database_libs = check_database_libs;
     668            return 0 if ($have_database_libs < 2);
     669            $dbh = DBI->connect("dbi:mysql:".
     670                                "database=$mysql_conf{'db_name'}:".
     671                                "host=$mysql_conf{'db_host'}",
     672                                "$mysql_conf{'db_user'}",
     673                                "$mysql_conf{'db_pass'}",
     674                                { PrintError => 0 });
     675        }
     676        return 1;
     677    }
     678
     679    sub create_backup_filename
     680    {
     681    # Create a default backup filename
     682        $backup_conf{'filename'} = $mysql_conf{'db_name'};
     683        if (!$backup_conf{'filename'})
     684        {
     685            $backup_conf{'filename'} = $d_db_name;
     686        }
     687        if ((!$mysql_conf{'db_schemaver'}) &&
     688            ($mysql_conf{'db_host'}) && ($mysql_conf{'db_name'}) &&
     689            ($mysql_conf{'db_user'}) && ($mysql_conf{'db_pass'}))
     690        {
     691        # If DBI is available, query the DB for the schema version
     692            if (check_database)
     693            {
     694                verbose($verbose_level_debug,
     695                        "\nNo DBSchemaVer specified, querying database.");
     696                my $query = 'SELECT data FROM settings WHERE value = ?';
     697                if (defined($dbh))
     698                {
     699                    my $sth = $dbh->prepare($query);
     700                    if ($sth->execute('DBSchemaVer'))
     701                    {
     702                        while (my @data = $sth->fetchrow_array)
     703                        {
     704                            $mysql_conf{'db_schemaver'} = $data[0];
     705                            verbose($verbose_level_debug,
     706                                    "Found DBSchemaVer:".
     707                                    " $mysql_conf{'db_schemaver'}.");
     708                        }
     709                    }
     710                    else
     711                    {
     712                        verbose($verbose_level_debug,
     713                                "Unable to retrieve DBSchemaVer from".
     714                                " database. Filename will not contain ",
     715                                "DBSchemaVer.");
     716                    }
     717                }
     718            }
     719            else
     720            {
     721                verbose($verbose_level_debug,
     722                        "\nNo DBSchemaVer specified.",
     723                        "DBI and/or DBD:mysql is not available. Unable".
     724                        " to query database to determine ",
     725                        "DBSchemaVer. DBSchemaVer will not be included".
     726                        " in backup filename.",
     727                        "Please ensure DBI and DBD::mysql are".
     728                        " installed.");
     729            }
     730        }
     731        if ($mysql_conf{'db_schemaver'})
     732        {
     733            $backup_conf{'filename'} .= '-'.$mysql_conf{'db_schemaver'};
     734        }
     735    # Format the time using localtime data so we don't have to bring in
     736    # another dependency.
     737        my @timeData = localtime(time);
     738        $backup_conf{'filename'} .= sprintf('-%04d%02d%02d%02d%02d%02d.sql',
     739                                            ($timeData[5] + 1900),
     740                                            ($timeData[4] + 1),
     741                                            $timeData[3], $timeData[2],
     742                                            $timeData[1], $timeData[0]);
     743    }
     744
     745    sub check_config
     746    {
     747        verbose($verbose_level_debug,
     748                "\nChecking configuration.");
     749    # Check directory/filename
     750        if (!$backup_conf{'directory'})
     751        {
     752            print_configuration;
     753            die("\nERROR:  DBBackupDirectory not specified, stopped");
     754        }
     755        if ((!-d $backup_conf{'directory'}) ||
     756            (!-w $backup_conf{'directory'}))
     757        {
     758            print_configuration;
     759            verbose($verbose_level_error,
     760                    "\nERROR:  DBBackupDirectory is not a directory or is not".
     761                    " writable. Please specify",
     762                    "        a directory in your database information file" .
     763                    " using DBBackupDirectory.",
     764                    "        If not using a database information file," .
     765                    " please specify the ",
     766                    "        --directory command-line option.");
     767            die("\nInvalid backup directory, stopped");
     768        }
     769        if (!$backup_conf{'filename'})
     770        {
     771            if ($backup_xmltvids)
     772            {
     773                my $file = 'mythtv_xmltvid_backup';
     774            # Format the time using localtime data so we don't have to bring in
     775            # another dependency.
     776                my @timeData = localtime(time);
     777                $file .= sprintf('-%04d%02d%02d%02d%02d%02d.sql',
     778                                 ($timeData[5] + 1900),
     779                                 ($timeData[4] + 1),
     780                                 $timeData[3], $timeData[2],
     781                                 $timeData[1], $timeData[0]);
     782                $backup_conf{'filename'} = $file;
     783            }
     784            else
     785            {
     786                create_backup_filename;
     787            }
     788        }
     789        if ( -e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     790        {
     791            verbose($verbose_level_error,
     792                    "\nERROR:  The specified file already exists.");
     793            die("\nInvalid backup filename, stopped");
     794        }
     795        if (!$mysql_conf{'db_name'})
     796        {
     797            verbose($verbose_level_debug,
     798                    "\nWARNING:  DBName not specified. Using $d_db_name");
     799            $mysql_conf{'db_name'} = $d_db_name;
     800        }
     801    # Though the script will attempt a backup even if no other database
     802    # information is provided (i.e. using "defaults" from the MySQL options
     803    # file, warning the user that some "normally-necessary" information is not
     804    # provided may be nice.
     805        return if (!$debug);
     806        if (!$mysql_conf{'db_host'})
     807        {
     808            verbose($verbose_level_debug,
     809                    "\nWARNING:  DBHostName not specified.",
     810                    "         Assuming it's specified in the MySQL".
     811                    " options file.");
     812        }
     813        if (!$mysql_conf{'db_user'})
     814        {
     815            verbose($verbose_level_debug,
     816                    "\nWARNING:  DBUserName not specified.",
     817                    "         Assuming it's specified in the MySQL".
     818                    " options file.");
     819        }
     820        if (!$mysql_conf{'db_pass'})
     821        {
     822            verbose($verbose_level_debug,
     823                    "\nWARNING:  DBPassword not specified.",
     824                    "         Assuming it's specified in the MySQL".
     825                    " options file.");
     826        }
     827    }
     828
     829    sub create_defaults_extra_file
     830    {
     831        return '' if (!$mysql_conf{'db_pass'});
     832        verbose($verbose_level_debug,
     833                "\nAttempting to use supplied password for $mysqldump.",
     834                "Any [client] or [mysqldump] password specified in the MySQL".
     835                " options file will",
     836                "take precedence.");
     837    # Let tempfile handle unlinking on exit so we don't have to verify that the
     838    # file with $filename is the file we created
     839        my ($fh, $filename) = tempfile(UNLINK => 1);
     840        print $fh  "[client]\npassword=$mysql_conf{'db_pass'}\n".
     841                   "[mysqldump]\npassword=$mysql_conf{'db_pass'}\n";
     842        return $filename;
     843    }
     844
     845    sub do_xmltvid_backup
     846    {
     847        my $exit = 1;
     848        if (check_database)
     849        {
     850            my ($chanid, $channum, $callsign, $name, $xmltvid);
     851            my $query = "  SELECT chanid, channum, callsign, name, xmltvid".
     852                        "    FROM channel ".
     853                        "ORDER BY CAST(channum AS SIGNED),".
     854                        "         CAST(SUBSTRING(channum".
     855                        "                        FROM (1 +".
     856                        "                              LOCATE('_', channum) +".
     857                        "                              LOCATE('-', channum) +".
     858                        "                              LOCATE('#', channum) +".
     859                        "                              LOCATE('.', channum)))".
     860                        "              AS SIGNED)";
     861            my $sth = $dbh->prepare($query);
     862            verbose($verbose_level_debug,
     863                    "\nQuerying database for xmltvid information.");
     864            my $file = "$backup_conf{'directory'}/$backup_conf{'filename'}";
     865            open BACKUP, '>', $file or die("\nERROR:  Unable to open".
     866                                           " $file:  $!, stopped");
     867            for ($section = 0; $section < 2; $section++)
     868            {
     869                if ($sth->execute)
     870                {
     871                    while (my @data = $sth->fetchrow_array)
     872                    {
     873                        $chanid = $data[0];
     874                        $channum = $data[1];
     875                        $callsign = $data[2];
     876                        $name = $data[3];
     877                        $xmltvid = $data[4];
     878                        verbose($verbose_level_debug,
     879                                "Found channel: $chanid, $channum, $callsign,".
     880                                " $name, $xmltvid.") if ($section == 0);
     881                        if ($xmltvid && $callsign)
     882                        {
     883                            if ($section == 0)
     884                            {
     885                                print BACKUP "-- Start Channel Data\n".
     886                                             "-- ID: '$chanid'\n".
     887                                             "-- Number: '$channum'\n".
     888                                             "-- Callsign: '$callsign'\n".
     889                                             "-- Name: '$name'\n".
     890                                             "-- XMLTVID: '$xmltvid'\n".
     891                                             "-- End Channel Data\n";
     892                                print BACKUP "UPDATE channel".
     893                                             " SET xmltvid = '$xmltvid'".
     894                                             " WHERE callsign = '$callsign'".
     895                                             ";\n";
     896                            }
     897                            else
     898                            {
     899                                print BACKUP "UPDATE channel".
     900                                             " SET xmltvid = '$xmltvid'".
     901                                             " WHERE channum = '$channum'".
     902                                             " AND name = '$name';\n";
     903                            }
     904                        }
     905                    }
     906                    if ($section == 0)
     907                    {
     908                        verbose($verbose_level_debug,
     909                                "\nSuccessfully backed up xmltvid".
     910                                " information.".
     911                                "\n\nCreating alternate format backup.");
     912                        print BACKUP "\n\n/* Alternate format */\n".
     913                                     "/*\n";
     914                    }
     915                    else
     916                    {
     917                        print BACKUP "*/\n";
     918                        verbose($verbose_level_debug,
     919                                "Successfully created alternate format".
     920                                " backup.");
     921                    }
     922                    $exit = 0;
     923                }
     924                else
     925                {
     926                    verbose($verbose_level_error,
     927                            "\nERROR: Unable to retrieve xmltvid information".
     928                            " from database.");
     929                    die("\nError retrieving xmltvid information, stopped");
     930                }
     931            }
     932            close BACKUP;
     933        }
     934        else
     935        {
     936            verbose($verbose_level_error,
     937                    "\nERROR:  Unable to backup xmltvids without Perl".
     938                    " database libraries.",
     939                    "        Please ensure the Perl DBI and DBD::mysql".
     940                    " modules are installed.");
     941            die("\nPerl database libraries missing, stopped");
     942        }
     943        return $exit;
     944    }
     945
     946    sub do_backup
     947    {
     948        my $defaults_extra_file = create_defaults_extra_file;
     949        my $host_arg = '';
     950        my $port_arg = '';
     951        my $user_arg = '';
     952        if ($defaults_extra_file)
     953        {
     954            $defaults_arg=" --defaults-extra-file='$defaults_extra_file'";
     955        }
     956        else
     957        {
     958            $defaults_arg='';
     959        }
     960    # Create the args for host, port, and user, shell-escaping values, as
     961    # necessary.
     962        if ($mysql_conf{'db_host'})
     963        {
     964            $mysql_conf{'db_host'} =~ s/'/'\\''/g;
     965            $host_arg=" --host='$mysql_conf{'db_host'}'";
     966        }
     967        if ($mysql_conf{'db_port'} > 0)
     968        {
     969            $mysql_conf{'db_port'} =~ s/'/'\\''/g;
     970            $port_arg=" --port='$mysql_conf{'db_port'}'";
     971        }
     972        if ($mysql_conf{'db_user'})
     973        {
     974            $mysql_conf{'db_user'} =~ s/'/'\\''/g;
     975            $user_arg=" --user='$mysql_conf{'db_user'}'";
     976        }
     977    # Use redirects to capture stderr (for debug) and send stdout (the backup)
     978    # to a file
     979        my $command = "${mysqldump}${defaults_arg}${host_arg}${port_arg}".
     980                      "${user_arg} --add-drop-table --add-locks ".
     981                      "--allow-keywords --complete-insert --extended-insert ".
     982                      "--lock-tables --no-create-db --quick ".
     983                      "$mysql_conf{'db_name'} 2>&1 ".
     984                      "1>$backup_conf{'directory'}/$backup_conf{'filename'}";
     985        verbose($verbose_level_debug,
     986                "\nExecuting command:", $command);
     987        my $result = `$command`;
     988        my $exit = $? >> 8;
     989        verbose($verbose_level_debug,
     990                "\n$mysqldump exited with status:  $exit");
     991        verbose($verbose_level_debug,
     992                "$mysqldump output:", $result) if ($exit);
     993        return $exit;
     994    }
     995
     996    sub compress_backup
     997    {
     998        if (!-e "$backup_conf{'directory'}/$backup_conf{'filename'}")
     999        {
     1000            verbose($verbose_level_debug,
     1001                    "\nUnable to find backup file to compress");
     1002            return 1;
     1003        }
     1004        my $result = 0;
     1005        verbose($verbose_level_debug,
     1006                "\nAttempting to compress backup file.");
     1007        if ($d_compress eq $compress)
     1008        {
     1009        # Try to load the IO::Compress::Gzip library if available (but don't
     1010        # require it)
     1011            BEGIN
     1012            {
     1013                our $has_compress_gzip = 1;
     1014                # Though this does nothing, it prevents an invalid "only used
     1015                # once" warning that occurs for users without IO::Compress
     1016                # installed.
     1017                undef $GzipError;
     1018                eval 'use IO::Compress::Gzip qw(gzip $GzipError);';
     1019                if ($@)
     1020                {
     1021                    $has_compress_gzip = 0;
     1022                }
     1023            }
     1024            if (!$has_compress_gzip)
     1025            {
     1026                verbose($verbose_level_debug,
     1027                        " - IO::Compress::Gzip is not installed.");
     1028            }
     1029            else
     1030            {
     1031                if (-e "$backup_conf{'directory'}/$backup_conf{'filename'}.gz")
     1032                {
     1033                    verbose($verbose_level_debug,
     1034                            "\nA file whose name is the backup filename with".
     1035                            " the '.gz' extension already",
     1036                            "exists. Leaving backup uncompressed.");
     1037                    return 1;
     1038                }
     1039                verbose($verbose_level_debug,
     1040                        " - Compressing backup file with IO::Compress::Gzip.");
     1041                $result = gzip(
     1042                   "$backup_conf{'directory'}/$backup_conf{'filename'}" =>
     1043                   "$backup_conf{'directory'}/$backup_conf{'filename'}.gz");
     1044                if ((defined($result)) &&
     1045                    (-e "$backup_conf{'directory'}/".
     1046                        "$backup_conf{'filename'}.gz"))
     1047                {
     1048                    unlink "$backup_conf{'directory'}/".
     1049                           "$backup_conf{'filename'}";
     1050                    $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
     1051                    verbose($verbose_level_debug,
     1052                            "\nSuccessfully compressed backup to file:",
     1053                            "$backup_conf{'directory'}/".
     1054                            "$backup_conf{'filename'}");
     1055                    return 0;
     1056                }
     1057                verbose($verbose_level_debug,
     1058                        "   Error:  $GzipError");
     1059            }
     1060        }
     1061    # Try to compress the file with the compress binary.
     1062        verbose($verbose_level_debug,
     1063                " - Compressing backup file with $compress.");
     1064        my $backup_path = "$backup_conf{'directory'}/$backup_conf{'filename'}";
     1065        my $output = `$compress '$backup_path' 2>&1`;
     1066        my $exit = $? >> 8;
     1067        verbose($verbose_level_debug,
     1068                "\n$compress exited with status:  $exit");
     1069        if ($exit)
     1070        {
     1071            verbose($verbose_level_debug,
     1072                    "$compress output:", $output);
     1073        }
     1074        else
     1075        {
     1076            $backup_conf{'filename'} = "$backup_conf{'filename'}.gz";
     1077        }
     1078        return $exit;
     1079    }
     1080
     1081    sub rotate_backups
     1082    {
     1083        if (($rotate < 1) || (!defined($rotateglob)) || (!$rotateglob))
     1084        {
     1085            verbose($verbose_level_debug,
     1086                    "\nBackup file rotation disabled.");
     1087            return 0;
     1088        }
     1089        verbose($verbose_level_debug,
     1090                "\nRotating backups.");
     1091        verbose($verbose_level_debug,
     1092                "\nSearching for files matching pattern:",
     1093                "$backup_conf{'directory'}/$rotateglob");
     1094        my @files = <$backup_conf{'directory'}/$rotateglob>;
     1095        my @sorted_files = sort { lc($a) cmp lc($b) } @files;
     1096        my $num_files = @sorted_files;
     1097        verbose($verbose_level_debug,
     1098                " - Found $num_files matching files.");
     1099        $num_files = $num_files - $rotate;
     1100        $num_files = 0 if ($num_files < 0);
     1101        verbose($verbose_level_debug,
     1102                "\nDeleting $num_files and keeping (up to) $rotate backup".
     1103                " files.");
     1104        my $index = 0;
     1105        foreach my $file (@sorted_files)
     1106        {
     1107            if ($index++ < $num_files)
     1108            {
     1109                if ($file eq
     1110                    "$backup_conf{'directory'}/$backup_conf{'filename'}")
     1111                {
     1112                # This is the just-created backup. Warn the user that older
     1113                # backups with newer schema versions may cause rotation to
     1114                # fail.
     1115                    verbose($verbose_level_debug,
     1116                            "\nWARNING:  You seem to have reverted to an".
     1117                            " older database schema version.",
     1118                            "You should move all backups from newer schema".
     1119                            " versions to another directory or",
     1120                            "delete them to prevent your new backups from".
     1121                            " being deleted on rotation.\n");
     1122                    verbose($verbose_level_debug,
     1123                            " - Keeping backup file:  $file");
     1124
     1125                }
     1126                else
     1127                {
     1128                    verbose($verbose_level_debug,
     1129                            " - Deleting old backup file:  $file");
     1130                    unlink "$file";
     1131                }
     1132            }
     1133            else
     1134            {
     1135                verbose($verbose_level_debug,
     1136                        " - Keeping backup file:  $file");
     1137            }
     1138        }
     1139        return 1;
     1140    }
     1141
     1142##############################################################################
     1143# Main functionality
     1144##############################################################################
     1145
     1146# The first argument after option parsing, if it exists, should be a database
     1147# information file.
     1148    $database_information_file = shift;
     1149
     1150    configure_environment;
     1151    read_config;
     1152    check_config;
     1153
     1154    print_configuration;
     1155
     1156    my $status = 1;
     1157    if ($backup_xmltvids)
     1158    {
     1159        $status = do_xmltvid_backup;
     1160    }
     1161    else
     1162    {
     1163        $status = do_backup;
     1164        if (!$status)
     1165        {
     1166            compress_backup;
     1167            rotate_backups;
     1168        }
     1169    }
     1170
     1171    $dbh->disconnect if (defined($dbh));
     1172
     1173    exit $status;
     1174
  • programs/scripts/scripts.pro

     
     1include ( ../../config.mak )
     2include ( ../../settings.pro )
     3
     4QMAKE_STRIP = echo
     5
     6TEMPLATE = app
     7CONFIG -= moc qt
     8
     9installscripts.path = $${PREFIX}/share/mythtv
     10installscripts.files = database/*
     11
     12INSTALLS += installscripts
     13
     14SOURCES += dummy.c
  • programs/scripts/dummy.c

     
     1int main(void)
     2{
     3    return 0;
     4}