1#!/bin/sh
2# vim: tabstop=4 shiftwidth=4 expandtab
3#
4# MySQL backup tool.
5#
6# Copyright 2010-2014, Alexey Degtyarev <alexey@renatasystems.org>.
7# All rights reserved.
8#
9# $Id: mysqlbackup 396 2014-05-24 09:30:53Z degtyarev.alexey@gmail.com $
10
11REVISION="$Revision: 396 $"
12VERSION="2.8"
13
14# Some default values.
15DEFAULT_ARCHIVE_DAYS=5                # How long to keep backups.
16DEFAULT_ARCHIVE_DIR="/var/backups"    # According to hier(7).
17DEFAULT_MYSQLDUMP_OPTIONS="--opt --skip-lock-tables --quote-names"
18DEFAULT_MYSQLCHECK_OPTIONS=\
19"--auto-repair --check-only-changed --extended --silent"
20DEFAULT_MYSQLOPTIMIZE_OPTIONS="--optimize --silent"
21DEFAULT_DIR_MODE=0700
22DEFAULT_FILE_MODE=0600
23DEFAULT_SAVE_MYCNF="yes"              # Either save my.cnf or not
24DEFAULT_PATH_MYCNF="%%DATADIR%%my.cnf"    # Macros will be replaced
25DEFAULT_CHECK_TABLES="yes"            # yes | no
26DEFAULT_OPTIMIZE_TABLES="yes"         # yes | no
27DEFAULT_LOCKFILE="/var/tmp/mysqlbackup.%%UID%%.lock"    # Path to lockfile.
28DEFAULT_LOCKFILE_EXPIRE=90000         # Seconds to expire lockfile.
29DEFAULT_SLAVE_STATUS_FILE="slave-status"    # Filename for slave status.
30
31# CHECK TABLE only works for MyISAM, InnoDB, and (as of MySQL 5.0.16+) ARCHIVE
32# tables.  All the `check-ready' engines should be indicated here separated by
33# space character.
34CHECK_READY_ENGINES="MyISAM InnoDB Archive"
35
36# For InnoDB tables, OPTIMIZE TABLE is mapped to ALTER TABLE, which rebuilds
37# the table to update index statistics and free unused space in the clustered
38# index. Beginning with MySQL 5.1.27, this is displayed in the output of
39# OPTIMIZE TABLE.
40OPTIMIZE_READY_ENGINES="MyISAM"
41
42# If a complete line of input is not read while reading the password within
43# given seconds - give up and continue with no password.
44ASK_PASSWORD_TIMEOUT=60
45
46# Configurable options ends here.
47
48OPTIONS="au:h:p:P:x:o:l:d:z:ZF:D:m:C:O:L:t:SvVHI"
49
50# These keys will be used while invoking mysql(1) program.
51: ${MYSQL_KEYS="--batch --silent"}
52
53# Outputs program usage instructions.
54usage()
55{
56    me=`basename $0`
57    cat << EOF
58${me} meant to create MySQL databases backup on a periodic basis.
59
60Usage: ${me} [OPTIONS] [database [database [ ... ] ]]
61
62Options:
63   -a                Dump all available databases except "information_schema"
64                     and "performance_schema" databases.
65   -u user           The MySQL user name to use when connecting to the server.
66   -h host           Connect to host.
67   -p password       Password to use when connecting to server.  You should
68                     note that specifying a password on the command line should
69                     be considered insecure.  See the section named "SECURITY".
70   -P filename|ask   Read the clear password from the file.  The file must
71                     normally not be readable by "others" and must contain
72                     exactly one line.  Password will be prompted from the
73                     command line if the special keyword "ask" specified here.
74   -x login-path     MySQL login-path
75   -o option|no      Additional mysqldump option.  To specify multiple options
76                     you should repeat this key for each mysqldump-option.  The
77                     default options are: ${DEFAULT_MYSQLDUMP_OPTIONS}.  To not
78                     use the default options force "no" option.
79   -l days           Keep created backups for the specified number of the days.
80                     The default is ${DEFAULT_ARCHIVE_DAYS} days.
81   -d directory      Target directory to archive backups.
82                     The default is ${DEFAULT_ARCHIVE_DIR} (will be created if
83                     need).
84   -z xz|pbzip2|bzip2|gzip|7z|no
85                     Compress dumps with specified program.  Unless explicitly
86                     set or "no" keyword used, the compressor is selected in
87                     the next order: if xz(1) compressor found in \$PATH, it
88                     will be used.  If it not found, bzip2(1), gzip(1) and
89                     7z(1) programs will be searched and used if found.  If
90                     none found, plain dumps will be created. 
91   -Z                Pipeline mysqldump to compressor program.  By default,
92                     mysqlbackup create plain SQL dump for whole database and
93                     call compressor program afterwards.  This help to make
94                     MySQL locktime as small as possible.  If long locktime for
95                     huge databases is not a problem but filesystem space usage
96                     is - use this key to save disk space.
97   -F mode           Create files with given mode access permissions.
98                     The default mode is ${DEFAULT_FILE_MODE}.
99   -D mode           Create directories with given mode access permissions.
100                     The default mode is ${DEFAULT_DIR_MODE}.
101   -m path|yes|no    Save my.cnf config or specify it alternate path.
102                     Default is: ${DEFAULT_SAVE_MYCNF}, ${DEFAULT_PATH_MYCNF}.
103   -C yes|no|keys    Check tables before doing backup or use specified keys
104                     for mysqlcheck(1) program while perfoming check.
105                     Default: ${DEFAULT_CHECK_TABLES},
106                     keys: ${DEFAULT_MYSQLCHECK_OPTIONS}.
107   -O yes|no|keys    Optimize tables before doing backup or use specified keys
108                     for mysqlcheck(1) program while perfoming optimization.
109                     Note that not all table engines supports table
110                     optimization.  Please refer to "OPTIMIZE TABLE Syntax"
111                     paragraph of MySQL documentation.
112                     Default: ${DEFAULT_OPTIMIZE_TABLES},
113                     keys: ${DEFAULT_MYSQLOPTIMIZE_OPTIONS}.
114   -L lockfile       Alternate default path to lockfile (${DEFAULT_LOCKFILE}).
115   -t seconds        Timeout in seconds to expire existing lockfile.
116                     By default lockfile expires after ${DEFAULT_LOCKFILE_EXPIRE}
117                     seconds.
118   -S                Slave mode.  Under this mode mysqlbackup assumes it is
119                     running on MySQL slave.  Then, prior to his work,
120                     mysqlbackup stops the slave and saves "SHOW SLAVE STATUS"
121                     output.  After work is done, the slave is started up. The
122                     output is saved to "${DEFAULT_SLAVE_STATUS_FILE}" file.
123   -I                Ignore errors while dumping database.  mysqlbackup will
124                     not stop if mysqldump(1) running on any database will
125                     return an error.  Excludes -Z because there is no way to
126                     detect which program has failed.
127   -v                Be verbose.
128   -V                Print version and exit.
129   -H                Print this help and exit.
130
131Examples:
132
133   mysqlbackup               Do nothing, print help.
134   mysqlbackup -av           Verbose backup all the accessible databases on the
135                             local MySQL server.
136   mysqlbackup -z no mysql   Backup MySQL system database without output dump
137                             being compressed.
138   mysqlbackup -a -P ask     You are prompted for password to backup all the
139                             databases available under current user.
140   mysqlbackup -aS           Operate in slave mode.  Save SLAVE STATUS for
141                             further replication restore.
142
143Report bugs to <alexey@renatasystems.org>
144EOF
145
146    # Do not debug after usage() exit while cleanup().
147    DEBUG="no"
148
149    if [ $# -ne 0 ]; then
150        exit $1
151    fi
152    exit 0
153}
154
155# Outputs program version and exit.
156version()
157{
158    cat <<- EOF
159$(basename $0) ${VERSION}
160
161Written by Alexey Degtyarev
162EOF
163
164    # Do not debug cleanup() trap.
165    DEBUG="no"
166
167    exit 0
168}
169
170# Helper function to determine given argument either `yes' or `no'.
171# Return exit codes:
172#  0 - given argument in $1 is a `positive' answer
173#  1 - argument is a `negative' answer
174#  2 - neither `positive' nor `negative' argument given
175#  3 - wrong number of arguments given
176check_yesno()
177{
178    # Accepts exactly one argument.
179    [ $# -eq 1 ] || return 3
180
181    case $1 in
182        [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1) return 0;;
183        [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0) return 1;;
184        *) return 2;
185    esac
186
187    # Should not be here ever.
188    return 0
189}
190
191# Check for locking: if lockfile found and it has not expired yet - give up
192# with error. Expire lockfile in other case.
193check_lockfile()
194{
195    [ -f "${LOCKFILE}" ] || return 0
196    [ -r "${LOCKFILE}" ] || return 1
197
198    file_stat ${LOCKFILE}
199
200    # Check that lockfile mtime is older than LOCKFILE_EXPIRE seconds. If
201    # it does - expire it and set the new one. If lockfile is not expired -
202    # this could mean that another process still running, so we give up.
203    if [ ${st_mtime} -lt $((`date +%s`-${LOCKFILE_EXPIRE})) ]; then
204        if ! touch_lockfile; then
205            echo "Can not expire lockfile (${LOCKFILE})."
206            return 2
207        else
208            echo "Lockfile expired (unclean shutdown previous time?)"
209            return 0
210        fi
211    # Check that process id saved in LOCKFILE is a real process.
212    elif check_process ${LOCKFILE}; then
213        _pid=`cat ${LOCKFILE}`
214        echo "Lockfile found. Another process is still running?"
215        echo "Found mysqlbackup program running with pid ${_pid}."
216        return 1
217    # Process running with stored pid is not me.
218    elif ! touch_lockfile; then
219        echo "Can not release lockfile ($?)"
220        return 1
221    # Releasing lockfile.
222    else
223        echo "Lockfile released (unclead shutdown previous time?)"
224        return 0
225    fi
226
227    return 0
228}
229
230check_process()
231{
232    [ $# -eq 1 ] || return 1
233    [ ! -z $1 ] || return 1
234    [ -f $1 -a -r $1 ] || return 1
235
236    # Take the process name and arguments from process tree.
237    case ${UNAME_s} in
238        Linux)
239            _pid="`cat $1`"
240            _procname="`pgrep -l -f mysqlbackup |grep ^${_pid}`"
241            ;;
242        FreeBSD)
243            _procname="`pgrep -j none -F $1 -l -f`"
244            ;;
245        *)
246            return 1
247            ;;
248    esac
249
250    if [ $? -ne 0 ]; then
251        return 3
252    fi
253
254    # 14659 /bin/sh /usr/local/bin/mysqlbackup -a
255    set -- ${_procname}
256
257    # Take the basename of interpreter running puppetd instance we got from
258    # process list.
259    _running_interpreter=${2##*/}
260
261    # And take the interpreter from the executable script.
262    read _interpreter < $3
263    case "${_interpreter}" in
264        # strip #!
265        \#!*) _interpreter=${_interpreter#\#!}
266        set -- ${_interpreter}
267        _interpreter=${_interpreter##*/}
268        ;;
269        *) _interpreter="/nonexistent"
270        ;;
271    esac
272
273    # Compare two interpretators from ps list and from executable script.
274    if [ ${_running_interpreter} != ${_interpreter} ]; then
275        return 4
276    fi
277
278    return 0
279}
280
281# Write down to lockfile current process id.
282touch_lockfile()
283{
284    echo $$ >${LOCKFILE} || return $?
285    chmod 600 ${LOCKFILE}
286    return 0
287}
288
289# Release lockfile while normal program exit.
290release_lockfile()
291{
292    debug "Releasing lockfile"
293
294    if [ ! -f ${LOCKFILE} ]; then
295        debug "Lockfile not found (${LOCKFILE})"
296        return 1
297    fi
298    if ! rm ${LOCKFILE}; then
299        _rc=$?
300        debug "Can't unlink lockfile (${LOCKFILE}): ${_rc}"
301        return ${_rc}
302    fi
303
304    return 0
305}
306
307# Set all the variables to their default values.
308set_defaults()
309{
310    MYSQL_USER=
311    MYSQL_HOST=
312    MYSQL_PASSWORD=
313    MYSQL_PASSWORD_FILE=
314    MYSQL_LOGIN_PATH=
315    MYSQLDUMP_OPTIONS=${DEFAULT_MYSQLDUMP_OPTIONS}
316    MYSQLCHECK_OPTIONS=${DEFAULT_MYSQLCHECK_OPTIONS}
317    MYSQLOPTIMIZE_OPTIONS=${DEFAULT_MYSQLOPTIMIZE_OPTIONS}
318    MYSQL_AUTH_KEYS=
319    ARCHIVE_DAYS=${DEFAULT_ARCHIVE_DAYS}
320    ARCHIVE_DIR=${DEFAULT_ARCHIVE_DIR}
321    DUMP_ALL="no"
322    DEBUG="no"
323    DUMP_DATABASE=
324    FILE_MODE=${DEFAULT_FILE_MODE}
325    DIR_MODE=${DEFAULT_DIR_MODE}
326    SAVE_MYCNF=${DEFAULT_SAVE_MYCNF}
327    PATH_MYCNF=${DEFAULT_PATH_MYCNF}
328    CHECK_TABLES=${DEFAULT_CHECK_TABLES}
329    OPTIMIZE_TABLES=${DEFAULT_OPTIMIZE_TABLES}
330    ONLY_PRINT_VERSION="no"
331    ONLY_PRINT_HELP="no"
332    CLEANUP_BACKUP_DIR="yes"
333    CLEANUP_LOCKFILE="no"
334    SLAVE_MODE="no"
335    SLAVE_STATUS_FILE=${DEFAULT_SLAVE_STATUS_FILE}
336    SLAVE_STOPPED="no"      # Did we stopped slave?
337    SLAVE_STOP="no"         # Do we need to stop slave?
338    IGNORE_ERRORS="no"      # Don't ignore errors, but rather exit on error.
339    PIPELINE_COMPRESSOR="no"    # Pipeline mysqldump to compressor?
340
341    : ${DEBUG_IDENT="yes"}
342
343    COMPRESSOR=
344    local c e
345    for c in xz pbzip2 bzip2 gzip 7z; do
346        e=`which $c`
347        if [ -x "$e" ]; then
348            COMPRESSOR=${e##*/}
349            break
350        fi
351    done
352
353    # Set MySQL installation prefix.
354    if [ -z "${MYSQL_PREFIX}" ]; then
355        e=`which mysql`
356        if [ -x "$e" ]; then
357            MYSQL_PREFIX=${e%/bin/mysql}
358        fi
359    fi
360
361    # Shortcut.
362    UNAME_s=`uname -s`
363
364    # Configure MySQL stuff.
365    MYSQL="${MYSQL_PREFIX}/bin/mysql"
366    MYSQLDUMP="${MYSQL_PREFIX}/bin/mysqldump"
367    MYSQLCHECK="${MYSQL_PREFIX}/bin/mysqlcheck"
368    MYSQLOPTIMIZE="${MYSQL_PREFIX}/bin/mysqlcheck"
369
370    _id=`id -u || echo 65534`
371    LOCKFILE=`echo -n ${DEFAULT_LOCKFILE} |sed -E "s#%%UID%%#${_id}#g"`
372    LOCKFILE_EXPIRE=${DEFAULT_LOCKFILE_EXPIRE}
373
374    return 0
375}
376
377# Wrap stat(1) for different OSes.
378file_stat()
379{
380    if [ $# -ne 1 ]; then
381        return 1
382    fi
383
384    [ ! -z "${UNAME_s}" ] || UNAME_s=`uname -s`
385
386    case ${UNAME_s} in
387        Linux)
388             eval `stat --printf st_mode=%a $1`
389             eval `stat --printf st_mtime=%Y $1`
390        ;;
391        FreeBSD)
392             eval `stat -s $1`
393        ;;
394        *)
395            debug "file_stat(): operating system not supported: ${UNAME_s}";
396            exit 1
397        ;;
398    esac
399
400    # Determine shell we are running on: Linux Ubuntu has /bin/sh linked to
401    # dash, other Linuxes link it to bash. Try to avoid chaos.
402    SHELL=/bin/sh
403    if [ -h /bin/sh ]; then
404        local t
405        t=`readlink /bin/sh`
406        SHELL=${t##*/}
407    fi
408
409    return 0
410}
411
412# Parse given options using builtin getopts function.
413parse_options()
414{
415    while getopts ${OPTIONS} option $@; do
416        case ${option} in
417            u) MYSQL_USER=${OPTARG};;
418            h) MYSQL_HOST=${OPTARG};;
419            p) MYSQL_PASSWORD=${OPTARG};;
420            P) MYSQL_PASSWORD_FILE=${OPTARG};;
421            x) MYSQL_LOGIN_PATH=${OPTARG};;
422            l) ARCHIVE_DAYS=${OPTARG};;
423            d) ARCHIVE_DIR=${OPTARG};;
424            a) DUMP_ALL="yes";;
425            F) FILE_MODE=${OPTARG};;
426            D) DIR_MODE=${OPTARG};;
427            C) check_yesno "${OPTARG}";
428            case $? in
429                0) CHECK_TABLES="yes";;
430                1) CHECK_TABLES="no";;
431                *) MYSQLCHECK_OPTIONS=${OPTARG};;
432            esac;;
433            O) check_yesno "${OPTARG}";
434            case $? in
435                0) OPTIMIZE_TABLES="yes";;
436                1) OPTIMIZE_TABLES="no";;
437                *) MYSQLOPTIMIZE_OPTIONS=${OPTARG};;
438            esac;;
439            m) check_yesno "${OPTARG}";
440            case $? in
441                0) SAVE_MYCNF="yes";;
442                1) SAVE_MYCNF="no";;
443                *) PATH_MYCNF=${OPTARG};;
444            esac;;
445            z) case ${OPTARG} in
446                xz|7z|gzip|bzip2|pbzip2)
447                    if ! which ${OPTARG} >/dev/null 2>/dev/null; then
448                        echo "${OPTARG} unavailable on this system"
449                        return 1
450                    fi
451                    COMPRESSOR=${OPTARG}
452                ;;
453                [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
454                COMPRESSOR=no;;
455                *) echo "Wrong arg for -z option"; return 1;;
456            esac;;
457            Z) PIPELINE_COMPRESSOR="yes";;
458            o) case ${OPTARG} in
459                [Nn][Oo]|[Ff][Aa][Ll][Ss][Ee]|[Oo][Ff][Ff]|0)
460                MYSQLDUMP_OPTIONS=;;
461                -*)
462                MYSQLDUMP_OPTIONS="${MYSQLDUMP_OPTIONS} ${OPTARG}";;
463                *) echo "Wrong arg for -o option: ${OPTARG}";
464                return 1;;
465            esac;;
466            L) LOCKFILE=${OPTARG};;
467            t) LOCKFILE_EXPIRE=${OPTARG};;
468            S) SLAVE_MODE="yes";;
469            v) DEBUG="yes";;
470            V) ONLY_PRINT_VERSION="yes";;
471            H) ONLY_PRINT_HELP="yes";;
472            I) IGNORE_ERRORS="yes";;
473            ?) exit 1;;
474        esac
475    done
476
477    shift $(($OPTIND-1))
478
479    # Take the database name(s) if given in command line.
480    DUMP_DATABASE=$@
481
482    return 0
483}
484
485# This will check that any options set are not conflicts with each others and
486# all the necessary options are set.
487check_options()
488{
489    # Was -v or -h keys specified?
490    check_yesno ${ONLY_PRINT_VERSION} && version
491    check_yesno ${ONLY_PRINT_HELP} && usage
492
493    # Either -a OR explicit database(s) name(s) must be specified, not
494    # both together.
495    if check_yesno "${DUMP_ALL}"; then
496        if [ ! -z "${DUMP_DATABASE}" ]; then
497            _str="Flag '-a' conflicts with explicit database"
498            _str="${_str} name given."
499            echo ${_str}
500            return 1
501        fi
502    else
503        if [ -z "${DUMP_DATABASE}" ]; then
504            _str="Specify database name to dump or set '-a'"
505            _str="${_str} to dump all the available databases."
506            echo ${_str}
507            return 1
508        fi
509    fi
510
511    # Check if all binaries exists and are executables.
512    binaries="${MYSQL} ${MYSQLDUMP} ${MYSQLCHECK} ${MYSQLOPTIMIZE}"
513    for binary in ${binaries}; do
514        if [ ! -x "${binary}" ]; then
515            _str="Binary \`${binary}' not found, "
516            _str="${_str} check your MYSQL_PREFIX path"
517            echo ${_str}
518            return 1
519        fi
520    done
521
522    # Can't specify both login path and password/password file/user/host
523    if [ ! -z "${MYSQL_LOGIN_PATH}" ]; then
524        if [ ! -z "${MYSQL_PASSWORD}" -o \
525             ! -z "${MYSQL_PASSWORD_FILE}" -o \
526             ! -z "${MYSQL_HOST}" -o \
527             ! -z "${MYSQL_USER}" ]; then
528            echo "The -x and -P -p -u -h options are mutually exclusive"
529            return 1
530        fi
531    fi
532
533
534    # Can't specify both password and password file.
535    if [ ! -z "${MYSQL_PASSWORD}" -a \
536        ! -z "${MYSQL_PASSWORD_FILE}" ]; then
537        echo "The -p and -P options are mutually exclusive"
538        return 1
539    fi
540
541    # Check for "ask" keyword and prompt password if keyword given.
542    if echo ${MYSQL_PASSWORD_FILE} |egrep -q "[Aa][Ss][Kk]"; then
543        # Preserve all the current settings for the terminal.
544        _stty=$(stty -g)
545
546        # Do not echo back every character typed while reading the
547        # password.
548        stty -echo
549
550        # Dash sheel doesn't support -t key for 'read'.
551        local k
552        case ${SHELL} in
553            bash|sh)
554                k=-t ${ASK_PASSWORD_TIMEOUT}
555                ;;
556            *) k=
557                ;;
558        esac
559
560        # Read the password.
561        read -p "Password: " $k MYSQL_PASSWORD && \
562            echo || \
563            echo "time out!"
564
565        # Restore terminal settings.
566        stty ${_stty}
567
568        # Flush password file variable.
569        MYSQL_PASSWORD_FILE=
570    fi
571
572    # Login path given, use it
573    if [ ! -z "${MYSQL_LOGIN_PATH}" ]; then
574        MYSQL_AUTH_KEYS="--login-path=${MYSQL_LOGIN_PATH}"
575    fi
576
577    # Password file given, let's check if it is ok.
578    if [ ! -z "${MYSQL_PASSWORD_FILE}" ]; then
579
580        # Check if item is file and is accessable.
581        if [ ! -f "${MYSQL_PASSWORD_FILE}" -o \
582            ! -r "${MYSQL_PASSWORD_FILE}" ]
583        then
584            echo "Can't read password file: ${MYSQL_PASSWORD_FILE}"
585            return 1
586        fi
587
588        # Check permissions: must not be world readable.
589        # First take the file permissions mode as a shell variable.
590        file_stat ${MYSQL_PASSWORD_FILE}
591
592        # Yeld the last digit and check if `r' bit is set.
593        if [ $((${st_mode##${st_mode%%?}} & 4)) -ne 0 ]; then
594            echo "Password file is readable by others"
595            return 1
596        fi
597
598        # Password file must contain exactly one line (password).
599        _line_count=`wc -l ${MYSQL_PASSWORD_FILE} |awk '{print $1}' |\
600            tr -d ' '`
601        if [ ${_line_count} -ne 1 ]; then
602            echo "Password file MUST contain exactly one line"
603            return 1
604        fi
605
606        # Seems that the password file is ok, so read it.
607        MYSQL_PASSWORD=`cat ${MYSQL_PASSWORD_FILE}`
608    fi
609
610    # Keep entered password in secure: use MySQL option
611    # --defaults-extra-file to read MySQL client configuration.
612    if [ ! -z "${MYSQL_PASSWORD}" ]; then
613
614        # This will create temporary file only accessable by creator.
615        MYSQL_EXTRA_FILE=`mktemp -t mysqlbackup.XXXXXX` || \
616        { echo "Cannot create temporary file"; return 1; }
617
618        # Write the password. Handling carefully with Ubuntu's Linux
619        # /bin/sh (actually, dash) - it doesn't have -e option.
620        cat - >>${MYSQL_EXTRA_FILE} <<- EOF
621        [client]
622        password=${MYSQL_PASSWORD}
623EOF
624
625        MYSQL_AUTH_KEYS="${MYSQL_AUTH_KEYS} \
626            --defaults-extra-file=${MYSQL_EXTRA_FILE}"
627    fi
628
629    # Check the lockfile timeout: should be greater than zero.
630    [ "${LOCKFILE_EXPIRE}" -gt 0 ] >/dev/null 2>&1 ||\
631        { echo "Timeout value must be numeric number of seconds" &&
632        return 1; }
633
634    # "-z no -Z" — conflicting combination.
635    if [ "${COMPRESSOR}" = "no" ] && \
636        check_yesno "${PIPELINE_COMPRESSOR}" ; then
637        echo "You didn't set compressor — nowhere to pipeline"
638        return 1
639    fi
640
641    # There is no way to detect which command force error.
642    if check_yesno "${PIPELINE_COMPRESSOR}" && \
643        check_yesno "${IGNORE_ERRORS}" ; then
644        echo "Meaningless options: -I in conjunction with -Z."
645        return 1
646    fi
647
648    # Seems that all is ok.
649    return 0
650}
651
652# Output $@ if debug flag is enabled. When DEBUG_IDENT is "yes", append
653# identification information: timestamp, selfname, pid.
654debug()
655{
656    check_yesno ${DEBUG} || return 0
657
658    msgprefix=
659    if check_yesno ${DEBUG_IDENT}; then
660        msgprefix="`date +%c` `basename $0` [$$]: "
661    fi
662
663    echo "${msgprefix}$@"
664
665    return 0
666}
667
668# This will raise error end exit with custom error code if given as $1
669# argument. When DEBUG_IDENT is "yes", append identification information:
670# timestamp, selfname, pid.
671error()
672{
673    check_yesno ${DEBUG} || debug $@
674
675    msgprefix=
676    if check_yesno ${DEBUG_IDENT}; then
677        msgprefix="`date +%c` `basename $0` [$$]: "
678    fi
679
680    echo "${msgprefix}$@"
681
682    [ ! -z "$1" ] && code=1 || code=$?
683
684    exit ${code}
685}
686
687# Clean up before exit: release the lockfile, remove backup directory if
688# CLEANUP_BACKUP_DIR set, clean up extra defaults options if exists. Used by
689# traps while emergency or normal exit.
690cleanup()
691{
692    debug "Cleaning up"
693
694    # Start slave if it was stopped by us.
695    if check_yesno ${SLAVE_STOPPED}; then
696        ${MYSQL} ${MYSQL_AUTH_KEYS} -e "START SLAVE;"
697        if [ $? -ne 0 ]; then
698            error "Can't start slave"
699        fi
700        debug "Slave started"
701        SLAVE_STOPPED="no"
702    fi
703
704    # Remove extra defaults options.
705    if [ -n "${MYSQL_EXTRA_FILE}" -a -e "${MYSQL_EXTRA_FILE}" ]; then
706        debug "Removing extra options (${MYSQL_EXTRA_FILE})"
707        rm -f ${MYSQL_EXTRA_FILE} || \
708            debug "Problem removing extra options"
709    fi
710
711    # Remove backup directory if it was NOT completely created.
712    if check_yesno ${CLEANUP_BACKUP_DIR} ; then
713        if [ -d ${BACKUP_DIR} ]; then
714            debug "Removing backup directory"
715            rm -r ${BACKUP_DIR} 2>/dev/null
716        fi
717    fi
718
719    # Release lockfile only if it was created by THIS process.
720    if check_yesno ${CLEANUP_LOCKFILE}; then
721        [ -f ${LOCKFILE} ] && ( release_lockfile || \
722            debug "Problem removing lockfile" )
723    fi
724
725    # Release `exit' trap to avoid cleanup loop.
726    trap - EXIT
727
728    debug "Exit"
729    exit 0
730}
731
732# Check that MySQL server is available with given credentials.
733check_mysql_connect()
734{
735    local _mysql_out
736
737    # Check if we can reach MySQL server.
738    debug "Pinging MySQL..."
739    if ! ${MYSQL} ${MYSQL_AUTH_KEYS} -e "QUIT"; then
740        error "MySQL connect error"
741    fi
742    debug "MySQL ok"
743
744    # Are we run on slave?
745    if check_yesno ${SLAVE_MODE}; then
746        debug "Slave mode requested, checking slave"
747
748        _mysql_out=`${MYSQL} ${MYSQL_AUTH_KEYS} \
749            --batch -e "SHOW SLAVE STATUS\G" 2>&1 |\
750            grep "Slave_IO_Running:" |\
751            awk '{print $2}'`
752        if [ $? -ne 0 ]; then
753            error "MySQL error: ${_mysql_out}"
754        fi
755
756        # Empty output means no slave was configured.
757        if [ -z "${_mysql_out}" ]; then
758            debug "Slave not configured"
759            SLAVE_MODE="no"
760        elif [ "${_mysql_out}" != "Yes" ]; then
761            debug "Slave IO Running: No"
762        else
763            # Slave IO running, request to stop slave.
764            SLAVE_STOP="yes"
765            debug "Slave IO Running: Yes"
766        fi
767    fi
768
769    return 0
770}
771
772# This function checks either do backup my.cnf configuration file or not.
773check_mycnf()
774{
775    debug "Checking for my.cnf backup"
776
777    # Normalaize variable value.
778    check_yesno ${SAVE_MYCNF}
779    case $? in
780        0) SAVE_MYCNF="yes";;
781        1) SAVE_MYCNF="no";;
782        *) SAVE_MYCNF="no";;
783    esac
784
785    if check_yesno ${SAVE_MYCNF}; then
786
787        # Retrieve the data directory configuration value.
788        datadir=`${MYSQL} ${MYSQL_AUTH_KEYS} ${MYSQL_KEYS} \
789                    -e "SHOW VARIABLES LIKE '%datadir%';" |\
790                awk '{print $2}'`
791
792        # Replace macros if given in pathname.
793        PATH_MYCNF=`echo ${PATH_MYCNF} |\
794                    sed -e "s#%%DATADIR%%#${datadir}#"`
795
796        # The last check - if file is readable, will backup it.
797        if [ -f ${PATH_MYCNF} ]; then
798            debug "Will backup my.cnf from: ${PATH_MYCNF}"
799        else
800            debug "Will not backup my.cnf (no access)"
801            SAVE_MYCNF="no"
802        fi
803    else
804        debug "Will not backup my.cnf"
805    fi
806
807    return 0
808}
809
810# Set and create directory where to save backups based on date or backup
811# number.
812create_target_dir()
813{
814    debug "Reading datadir from MySQL"
815    datadir=`${MYSQL} ${MYSQL_AUTH_KEYS} ${MYSQL_KEYS} \
816                -e "SHOW VARIABLES LIKE '%datadir%';" |\
817            awk '{print $2}'`
818    debug "MySQL says: ${datadir}"
819    datadir=`basename ${datadir}`
820
821    # We will preserve previous backups untouched.
822    date_suffix=`date +%Y%m%d`
823    max_oneday_backups=20
824
825    # Today's backup already done, take the next directory name.
826    if [ -d "${ARCHIVE_DIR}/${datadir}/${date_suffix}" ]; then
827        i=1
828        while [ $i -le ${max_oneday_backups} ]; do
829            _d="${ARCHIVE_DIR}/${datadir}/${date_suffix}.$i"
830
831            # Take the first unused name.
832            if [ ! -d ${_d} ]; then
833                date_suffix="${date_suffix}.$i"
834                break
835            fi
836            i=$(($i+1))
837        done
838
839        # Preserve any error with directory naming.
840        [ $i -ge ${max_oneday_backups} ] && ( \
841            _str="Too much similar backups found," && \
842            _str="${_str} consider remove previous backups." && \
843            error ${_str} )
844    fi
845
846    local _dirs="${ARCHIVE_DIR} \
847            ${ARCHIVE_DIR}/${datadir} \
848            ${ARCHIVE_DIR}/${datadir}/${date_suffix}"
849
850    # Create directories with given directory mode permissions.
851    for dir in ${_dirs}; do
852        [ -d ${dir} ] && continue
853        debug "Creating directory: ${dir}"
854        if ! mkdir -m ${DIR_MODE} ${dir}; then
855            error "Can't create directory: ${dir}"
856            return 1
857        fi
858    done
859
860    BACKUP_DIR="${ARCHIVE_DIR}/${datadir}/${date_suffix}"
861
862    return 0
863}
864
865# Remove older backups.
866remove_old_backups()
867{
868    debug "Removing old backups"
869
870    local d=$((${ARCHIVE_DAYS} + 1))
871
872    # Different OS'es handle dates differently.
873    local _date_flags
874    local _uname_s=`uname -s`
875    case ${_uname_s} in
876        Linux)
877             _date_flags="--date=\$d day ago"
878        ;;
879        FreeBSD)
880             _date_flags="-v-\${d}d"
881        ;;
882        *)
883            debug "Not yet supported for $_uname_s";
884            return 1
885        ;;
886    esac
887
888    while [ ${d} -lt $((${ARCHIVE_DAYS} + 30)) ]; do
889        _date_flags_e=`eval "echo ${_date_flags}"`
890        _purge_date=`date "${_date_flags_e}" +%Y%m%d`
891        find ${BACKUP_DIR}/../ \
892            -maxdepth 1 \
893            -mindepth 1 \
894            -name "${_purge_date}*" \
895            -type d \
896            -exec rm -r {} \;
897        d=$((${d} + 1))
898    done
899
900    return 0
901}
902
903# Stop slave and save it's status if "slave mode" requested.
904handle_slave_mode()
905{
906    if ! check_yesno ${SLAVE_MODE}; then
907        return 0
908    fi
909
910    if [ ! -e "${BACKUP_DIR}" ]; then
911        debug "Backup dir does not exist?"
912        return 1
913    fi
914
915    if ! check_yesno ${SLAVE_STOP}; then
916        debug "Don't need to stop slave"
917    else
918        ${MYSQL} ${MYSQL_AUTH_KEYS} -e "STOP SLAVE;"
919        if [ $? -ne 0 ]; then
920            error "Can't stop slave"
921        fi
922        debug "Slave stopped"
923        SLAVE_STOPPED="yes"
924    fi
925
926    ${MYSQL} ${MYSQL_AUTH_KEYS} --batch -e "SHOW SLAVE STATUS\G" \
927        > ${BACKUP_DIR}/${SLAVE_STATUS_FILE}
928    if [ $? -ne 0 ]; then
929        debug "Can't get slave status"
930    fi
931    debug "Slave status saved: ${BACKUP_DIR}/${SLAVE_STATUS_FILE}"
932
933    return 0
934}
935
936# Create databases-to-backup list.
937get_databases()
938{
939    BACKUP_DATABASES=
940
941    # -a key given?
942    if check_yesno ${DUMP_ALL}; then
943        debug "Reading available databases"
944        BACKUP_DATABASES=`${MYSQL} ${MYSQL_AUTH_KEYS} ${MYSQL_KEYS} \
945                    -e "SHOW DATABASES;" |\
946                    egrep -v "^(information_schema|performance_schema)$"`
947        [ $? -eq 0 ] || error "Can't get available databases"
948
949    else
950        # Select databases by matching pattern.
951        for database in ${DUMP_DATABASE}; do
952            db=`${MYSQL} ${MYSQL_AUTH_KEYS} ${MYSQL_KEYS} \
953                -e "SHOW DATABASES LIKE '${database}';"`
954            [ $? -eq 0 ] || error "Can't use database: ${database}"
955            BACKUP_DATABASES="${BACKUP_DATABASES} ${db}"
956        done
957    fi
958
959    count=`echo ${BACKUP_DATABASES} |wc -w |tr -d ' '`
960    [ -z "${count}" ] && count=0
961    debug "Got ${count} database(s)"
962
963    if [ ${count} -le 0 ]; then
964        debug "Nothing to do, exiting"
965        exit $?
966    fi
967
968    return 0
969}
970
971# Set ${OUTPUT_SUFFIX} to suffix according to selected comressor program.
972# Arguments:
973#   None.
974# Return:
975#   0: OK
976#  >0: NOK
977set_output_suffix()
978{
979    # Select correct compressor program and suffix.
980    case ${COMPRESSOR} in
981        xz)
982            SUFFIX="sql.xz"
983            ;;
984        gzip)
985            SUFFIX="sql.gz"
986            ;;
987        bzip2|pbzip2)
988            SUFFIX="sql.bz2"
989            ;;
990        7z)
991            SUFFIX="sql.7z"
992            ;;
993        no)
994            SUFFIX="sql"
995            ;;
996        *)
997            return 1
998    esac
999
1000    return 0
1001}
1002
1003# Dump database and compress it inline by pipelining compressor.
1004# Arguments:
1005#   $1: database to dump
1006#   $2: outputfile NOT including compressor suffix
1007# Return:
1008#   0: OK
1009#  >0: NOK
1010dump_pipe_compress()
1011{
1012    [ $# -eq 2 ] || return 1
1013
1014    _db=$1
1015    _out_file=$2
1016
1017    set_output_suffix || return 1
1018
1019    # If compressor is not set to valid compressor program, abort to do
1020    # pipeline.
1021    if [ "${SUFFIX}" = "sql" ]; then
1022        error "Nowere to pipeline"
1023    fi
1024
1025    debug "${_db}: creating compressed SQL-dump"
1026
1027    case ${COMPRESSOR} in
1028        xz|pbzip2|bzip2|gzip)
1029            ${MYSQLDUMP}                                \
1030                ${MYSQL_AUTH_KEYS}                      \
1031                ${MYSQLDUMP_OPTIONS}                    \
1032                ${_db}                                  \
1033            | ${COMPRESSOR} > ${_out_file}.${SUFFIX}    \
1034            || return 1
1035        ;;
1036        7z)
1037            ${MYSQLDUMP}                    \
1038                ${MYSQL_AUTH_KEYS}          \
1039                ${MYSQLDUMP_OPTIONS}        \
1040                ${_db}                      \
1041            | ${COMPRESSOR} -bd a -si       \
1042                ${_out_file}.${SUFFIX}      \
1043                >/dev/null                  \
1044            || return 1
1045        ;;
1046    esac
1047
1048    chmod ${FILE_MODE} ${_out_file}.${SUFFIX} || return 1
1049
1050    debug "${_db}: dumped and compressed"
1051
1052    return 0
1053}
1054
1055# Arguments:
1056#   $1: database to dump
1057#   $2: filename where to dump
1058dump_database()
1059{
1060    [ $# -eq 2 ] || return 1
1061
1062    _db=$1
1063    _out_file=$2
1064
1065    # Invoke mysqldump(1) for database
1066    debug "${db}: dumping"
1067    ${MYSQLDUMP} \
1068        ${MYSQL_AUTH_KEYS} \
1069        ${MYSQLDUMP_OPTIONS} \
1070        ${db} > ${_out_file}
1071    _rc=$?
1072
1073    if [ ${_rc} -ne 0 ]; then
1074        # Are we forced to ignore any errors?
1075        if ! check_yesno ${IGNORE_ERRORS} ; then
1076            debug "${db}: error occurred (rc=${_rc})"
1077            return ${_rc}
1078        fi
1079        debug "${db}: error occurred (rc=${_rc}), ignored"
1080    else
1081        debug "${db}: dumped"
1082    fi
1083
1084    return 0
1085}
1086
1087# Compress SQL dump using given compressor.
1088# Arguments:
1089#   $1: filename to compress
1090# Return:
1091#       0: ok
1092#   not 0: error occurred
1093compress_dump()
1094{
1095    [ $# -eq 1 ] || return 1
1096
1097    _out_file=$1
1098
1099    set_output_suffix || return 1
1100
1101    # Nothing to do?
1102    if [ "${SUFFIX}" = "sql" ]; then
1103        mv ${_out_file} ${_out_file}.sql
1104        chmod ${FILE_MODE} ${_out_file}.sql
1105        return 0
1106    fi
1107
1108    debug "${db}: compressing"
1109    case ${COMPRESSOR} in
1110        xz|pbzip2|bzip2|gzip)
1111            ${COMPRESSOR} --stdout ${_out_file}     \
1112                > ${_out_file}.${SUFFIX} ||         \
1113                return $?
1114        ;;
1115        7z)
1116            # If verbose mode is inactive - volume
1117            # down 7z compressor. I can't find any
1118            # tunes to disable all the output from
1119            # this compressor.
1120            if ! check_yesno ${DEBUG} ; then
1121                ${COMPRESSOR} -bd a         \
1122                ${_out_file}.${SUFFIX}      \
1123                ${_out_file} 1>/dev/null || \
1124                return $?
1125            else
1126                ${COMPRESSOR} -bd a     \
1127                ${_out_file}.${SUFFIX}  \
1128                ${_out_file} ||         \
1129                return $?
1130            fi
1131        ;;
1132    esac
1133    debug "${db}: compressed"
1134
1135    rm ${_out_file}
1136
1137    chmod ${FILE_MODE} ${_out_file}.${SUFFIX} || return 1
1138
1139    return 0
1140}
1141
1142# Backup databases.
1143do_backup()
1144{
1145    [ ! -z "${BACKUP_DATABASES}" ] || return 0
1146
1147    if check_yesno "${PIPELINE_COMPRESSOR}"; then
1148        debug "Compression will use pipeline to ${COMPRESSOR}"
1149    elif [ "${COMPRESSOR}" != "no" ]; then
1150        debug "Using ${COMPRESSOR} as compressor"
1151    else
1152        debug "Not using compresion"
1153    fi
1154
1155    _check_tables="no"
1156    if check_yesno ${CHECK_TABLES}; then
1157        _check_tables="yes"
1158    fi
1159    _optimize_tables="no"
1160    if check_yesno ${OPTIMIZE_TABLES}; then
1161        _optimize_tables="yes"
1162    fi
1163
1164    db_count=0
1165    db_total=`echo ${BACKUP_DATABASES} |wc -w`
1166    for db in ${BACKUP_DATABASES}; do
1167        db_count=$((${db_count}+1))
1168        db_left=$((${db_total}-${db_count}))
1169        debug "${db}: doing database backup (${db_left} left)"
1170
1171        # Get tables in database. This is need to check if table engine
1172        # allows checking or optimizing.
1173        tables_total=`${MYSQL} \
1174                ${MYSQL_AUTH_KEYS} \
1175                ${MYSQL_KEYS} \
1176                ${db} \
1177                -e "SHOW TABLES;"`
1178
1179        # No tables found, nothing to do?
1180        tables_total_c=`echo ${tables_total} |wc -w |tr -d ' '`
1181        if [ ${tables_total_c} -le 0 ]; then
1182            debug "${db}: no tables found, skipping"
1183            continue
1184        fi
1185        debug "${db}: ${tables_total_c} table(s) found in total"
1186
1187        # Check tables.
1188        if [ ${_check_tables} = "yes" ]; then
1189
1190            # Not all tables are ready to check or optimize.  We
1191            # should trim off such tables from the all tables list.
1192            local tables_checkready=""
1193
1194            for engine in ${CHECK_READY_ENGINES}; do
1195
1196                # Get tables with one of checkready-engine
1197                _tables=`${MYSQL} \
1198                    ${MYSQL_AUTH_KEYS} \
1199                    ${MYSQL_KEYS} \
1200                    ${db} \
1201                    -e "SHOW TABLE STATUS WHERE Engine LIKE '${engine}';" |\
1202                    awk '{print $1}'`
1203
1204                # Try another engine if no tables with such engine found.
1205                local _tables_c=`echo ${_tables} |wc -w |tr -d ' '`
1206                [ ${_tables_c} -eq 0 ] && continue;
1207                debug "${db}: check: ${_tables_c} ${engine}-engine table(s)"
1208
1209                tables_checkready="${tables_checkready} ${_tables}"
1210            done
1211
1212            tables_checkready_c=`echo ${tables_checkready} |wc -w |tr -d ' '`
1213            debug "${db}: check: will try to check ${tables_checkready_c} table(s)"
1214
1215            # Check tables filtered by engine.
1216            debug "${db}: check: checking tables"
1217            c=0
1218            for table in ${tables_checkready}; do
1219                if ! ${MYSQLCHECK} \
1220                    ${MYSQL_AUTH_KEYS} \
1221                    ${MYSQLCHECK_OPTIONS} \
1222                    ${db} ${table}; then
1223                    debug "${db}: check: ${table} check fail"
1224                    continue
1225                fi
1226                c=$((c+1))
1227                #debug "${db}: check: ${table} check ok"
1228            done
1229            debug "${db}: check: checked ${c} table(s)"
1230        fi
1231
1232        # Optimize tables.
1233        if [ ${_optimize_tables} = "yes" ]; then
1234
1235            # Collect OPTIMIZE-ready tables names into
1236            # tables_optimizeready array.
1237            local tables_optimizeready=""
1238
1239            # Foreach engines that has known compatibility with
1240            # OPTIMIZE.
1241            for engine in ${OPTIMIZE_READY_ENGINES}; do
1242
1243                # Get tables with one of the optimize-ready
1244                # engine.
1245                _tables=`${MYSQL} \
1246                    ${MYSQL_AUTH_KEYS} \
1247                    ${MYSQL_KEYS} \
1248                    ${db} \
1249                    -e "SHOW TABLE STATUS WHERE Engine LIKE '${engine}';" |\
1250                    awk '{print $1}'`
1251
1252                # Try another engine if no tables with such
1253                # engine found.
1254                _tables_c=`echo ${_tables} |wc -w |tr -d ' '`
1255                [ ${_tables_c} -eq 0 ] && continue;
1256                debug "${db}: optimize: ${_tables_c} ${engine}-engine table(s)"
1257
1258                # Catenate arrays with previous search results
1259                # (if was).
1260                tables_optimizeready="${tables_optimizeready} ${_tables}"
1261            done
1262
1263            tables_optimizeready_c=`echo ${tables_optimizeready} |wc -w |tr -d ' '`
1264            debug "${db}: optimize: will try to optimize ${tables_optimizeready_c} table(s)"
1265
1266            # Check tables filtered by engine.
1267            c=0
1268            for table in ${tables_optimizeready}; do
1269                if ! ${MYSQLOPTIMIZE} \
1270                    ${MYSQL_AUTH_KEYS} \
1271                    ${MYSQLOPTIMIZE_OPTIONS} \
1272                    ${db} ${table}; then
1273                    debug "${db}: optimize: table ${table} optimizing failed"
1274                    continue
1275                fi
1276                c=$((c+1))
1277                #debug "${db}: ${table} optimize ok"
1278            done
1279            debug "${db}: optimize: optimized ${c} table(s)"
1280        fi
1281
1282        local _out_file=${BACKUP_DIR}/${db}
1283
1284        if check_yesno ${PIPELINE_COMPRESSOR}; then
1285            dump_pipe_compress "${db}" "${_out_file}" || return 1
1286        else
1287            dump_database "${db}" "${_out_file}" || return 1
1288            compress_dump "${_out_file}"    || return 1
1289        fi
1290    done
1291
1292    # Backup my.cnf
1293    if check_yesno ${SAVE_MYCNF}; then
1294        mycnf=`basename ${PATH_MYCNF}`
1295        debug "Backup my.cnf: ${PATH_MYCNF} -> ${BACKUP_DIR}/${mycnf}"
1296        cp ${PATH_MYCNF} ${BACKUP_DIR}/${mycnf}
1297        chmod ${FILE_MODE} ${BACKUP_DIR}/${mycnf}
1298    fi
1299
1300    # Set flag "do not remove backups while doing cleanup()"
1301    CLEANUP_BACKUP_DIR="no"
1302
1303    return 0
1304}
1305
1306# Remember the time of successful completion.
1307set_done_flag()
1308{
1309    date +%s > ${BACKUP_DIR}/.done
1310
1311    return $?
1312}
1313
1314analyze_done_flag()
1315{
1316    # Retrieve the data directory configuration value.
1317    local datadir=`${MYSQL} ${MYSQL_AUTH_KEYS} ${MYSQL_KEYS} \
1318                -e "SHOW VARIABLES LIKE '%datadir%';" |\
1319            awk '{print $2}'`
1320
1321    DATADIR=
1322}
1323
1324# The main cycle.
1325main()
1326{
1327    debug "Ready (version ${VERSION}, rev${REVISION% $})"
1328    check_mysql_connect || return $?
1329    create_target_dir || return $?
1330    handle_slave_mode || return $?
1331    check_mycnf || return $?
1332    get_databases || return $?
1333    do_backup || return $?
1334    remove_old_backups || return $?
1335    set_done_flag || return $?
1336    debug "Done"
1337
1338    return 0
1339}
1340
1341# Make sure we find utilities from the base system
1342export PATH=${PATH}:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/local/sbin
1343
1344# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
1345export LC_ALL=C
1346
1347set_defaults
1348
1349[ $# -eq 0 ] && usage
1350
1351parse_options $@ || exit 255
1352
1353check_options || exit 255
1354
1355trap cleanup EXIT
1356
1357check_lockfile && touch_lockfile || exit
1358
1359# From here we assume that the lockfile either didn't exist before or was
1360# re-created due timeout, so we should remove it while program will cleanup at
1361# exit.
1362CLEANUP_LOCKFILE="yes"
1363
1364# Define authorization credentials if they are set.
1365[ ! -z "${MYSQL_USER}" ] && \
1366    MYSQL_AUTH_KEYS="${MYSQL_AUTH_KEYS} --user=${MYSQL_USER}"
1367
1368[ ! -z "${MYSQL_HOST}" ] && \
1369    MYSQL_AUTH_KEYS="${MYSQL_AUTH_KEYS} --host=${MYSQL_HOST}"
1370
1371# Use shortcuts for signals to deal with Linux's sh.
1372trap cleanup INT TERM EXIT
1373
1374main
1375