kernswinconfig
kernswinproductionconfig
Makefile
+nbproject
newdb
newtape
run_clean
AC_SUBST(LIBTOOL_UNINSTALL_TARGET)
AC_SUBST(LIBTOOL_CLEAN_TARGET)
AC_SUBST(QMAKE_LIBTOOL)
-AC_SUBST(FD_PLUGIN_DIR)
dnl --------------------------------------------------
dnl Include file handling
AC_ARG_ENABLE(client-only,
AC_HELP_STRING([--enable-client-only], [build client (File daemon) only @<:@default=no@:>@]),
[
- if test x$enableval = xyes; then
- build_client_only=yes
- db_backends="None"
- DB_BACKENDS="none"
- fi
+ if test x$enableval = xyes; then
+ build_client_only=yes
+ db_backends="None"
+ DB_BACKENDS="none"
+ fi
]
)
if test x$build_client_only = xno; then
AC_ARG_ENABLE(build-dird,
AC_HELP_STRING([--enable-build-dird], [enable building of dird (Director) @<:@default=yes@:>@]),
[
- if test x$enableval = xno; then
- build_dird=no
- fi
+ if test x$enableval = xno; then
+ build_dird=no
+ fi
]
)
]
)
+PLUGCONFILES=""
+
+dnl
+dnl Docker Plugin enable/disable
+dnl
+support_docker=auto
+AC_ARG_ENABLE(docker-plugin,
+ AC_HELP_STRING([--disable-docker-plugin], [disable Docker plugin support @<:@default=auto@:>@]),
+ [
+ if test x$enableval = xyes; then
+ support_docker=yes
+ elif test x$enableval = xno; then
+ support_docker=disabled
+ fi
+ ]
+)
+if test x$support_docker = xyes -o x$support_docker = xauto; then
+ AC_PATH_PROG(docker_bin_path, "docker", "no")
+ if test x$docker_bin_path != xno; then
+ FD_PLUGIN_DIR="$FD_PLUGIN_DIR src/plugins/fd/docker"
+ PLUGCONFILES="${PLUGCONFILES} src/plugins/fd/docker/Makefile"
+ else
+ if test x$support_docker = xyes; then
+ echo " "
+ echo "You enabled a Docker Plugin build but 'docker' command not found."
+ echo " "
+ exit 1
+ fi
+ support_docker=no
+ fi
+fi
+
+AC_SUBST(FD_PLUGIN_DIR)
+
dnl
dnl Check for headers, functions and libraries required to support
dnl keeping readall capabilities
exit 1
fi
-
-AC_OUTPUT([autoconf/Make.common \
- Makefile \
- manpages/Makefile \
- scripts/btraceback \
- scripts/bconsole \
- scripts/baculabackupreport \
- scripts/bacula \
- scripts/bacula-ctl-dir \
- scripts/bacula-ctl-fd \
- scripts/bacula-ctl-sd \
- scripts/devel_bacula \
- scripts/Makefile \
- scripts/logrotate \
- scripts/mtx-changer \
- scripts/disk-changer \
- scripts/logwatch/Makefile \
- scripts/logwatch/logfile.bacula.conf \
- scripts/bat.desktop \
- scripts/bat.desktop.xsu \
- scripts/bat.desktop.consolehelper \
- scripts/bat.console_apps \
- scripts/bacula-tray-monitor.desktop \
- src/Makefile \
- src/host.h \
- src/console/Makefile \
- src/console/bconsole.conf \
- src/qt-console/bat.conf \
- src/qt-console/bat.pro \
- src/qt-console/bat.pro.mingw32 \
- src/qt-console/bat.pro.mingw64 \
- src/qt-console/install_conf_file \
- src/qt-console/tray-monitor/tray-monitor.conf \
- src/qt-console/tray-monitor/bacula-tray-monitor.conf \
- src/qt-console/tray-monitor/tray-monitor.pro \
- src/qt-console/tray-monitor/tray-monitor.pro.mingw32 \
- src/qt-console/tray-monitor/tray-monitor.pro.mingw64 \
- src/dird/Makefile \
- src/dird/bacula-dir.conf \
- src/lib/Makefile \
- src/stored/Makefile \
- src/stored/bacula-sd.conf \
- src/filed/Makefile \
- src/filed/bacula-fd.conf \
- src/cats/Makefile \
- src/cats/make_catalog_backup.pl \
- src/cats/make_catalog_backup \
- src/cats/delete_catalog_backup \
- src/cats/create_postgresql_database \
- src/cats/update_postgresql_tables \
- src/cats/make_postgresql_tables \
- src/cats/grant_postgresql_privileges \
- src/cats/drop_postgresql_tables \
- src/cats/drop_postgresql_database \
- src/cats/create_mysql_database \
- src/cats/update_mysql_tables \
- src/cats/make_mysql_tables \
- src/cats/grant_mysql_privileges \
- src/cats/drop_mysql_tables \
- src/cats/drop_mysql_database \
- src/cats/create_sqlite3_database \
- src/cats/update_sqlite3_tables \
- src/cats/make_sqlite3_tables \
- src/cats/grant_sqlite3_privileges \
- src/cats/drop_sqlite3_tables \
- src/cats/drop_sqlite3_database \
- src/cats/sqlite \
- src/cats/mysql \
- src/cats/create_bacula_database \
- src/cats/update_bacula_tables \
- src/cats/grant_bacula_privileges \
- src/cats/make_bacula_tables \
- src/cats/drop_bacula_tables \
- src/cats/drop_bacula_database \
- src/cats/install-default-backend \
- src/findlib/Makefile \
- src/tools/Makefile \
- src/plugins/fd/Makefile \
- src/plugins/sd/Makefile \
- src/plugins/dir/Makefile \
- po/Makefile.in \
- updatedb/update_mysql_tables \
- updatedb/update_sqlite3_tables \
- updatedb/update_postgresql_tables \
- updatedb/update_mysql_tables_9_to_10 \
- updatedb/update_sqlite3_tables_9_to_10 \
- updatedb/update_postgresql_tables_9_to_10 \
- updatedb/update_mysql_tables_10_to_11 \
- updatedb/update_sqlite3_tables_10_to_11 \
- updatedb/update_postgresql_tables_10_to_11 \
- updatedb/update_mysql_tables_11_to_12 \
- updatedb/update_sqlite3_tables_11_to_12 \
- updatedb/update_postgresql_tables_11_to_12 \
- examples/nagios/check_bacula/Makefile \
- platforms/rpms/redhat/bacula.spec \
- platforms/rpms/redhat/bacula-bat.spec \
- platforms/rpms/redhat/bacula-docs.spec \
- platforms/rpms/redhat/bacula-mtx.spec \
- platforms/rpms/suse/bacula.spec \
- platforms/rpms/suse/bacula-bat.spec \
- platforms/rpms/suse/bacula-docs.spec \
- platforms/rpms/suse/bacula-mtx.spec \
- $PFILES ],
- [ ]
-)
+AC_CONFIG_FILES([
+ autoconf/Make.common \
+ Makefile \
+ manpages/Makefile \
+ scripts/btraceback \
+ scripts/bconsole \
+ scripts/baculabackupreport \
+ scripts/bacula \
+ scripts/bacula-ctl-dir \
+ scripts/bacula-ctl-fd \
+ scripts/bacula-ctl-sd \
+ scripts/devel_bacula \
+ scripts/Makefile \
+ scripts/logrotate \
+ scripts/mtx-changer \
+ scripts/disk-changer \
+ scripts/logwatch/Makefile \
+ scripts/logwatch/logfile.bacula.conf \
+ scripts/bat.desktop \
+ scripts/bat.desktop.xsu \
+ scripts/bat.desktop.consolehelper \
+ scripts/bat.console_apps \
+ scripts/bacula-tray-monitor.desktop \
+ src/Makefile \
+ src/host.h \
+ src/console/Makefile \
+ src/console/bconsole.conf \
+ src/qt-console/bat.conf \
+ src/qt-console/bat.pro \
+ src/qt-console/bat.pro.mingw32 \
+ src/qt-console/bat.pro.mingw64 \
+ src/qt-console/install_conf_file \
+ src/qt-console/tray-monitor/tray-monitor.conf \
+ src/qt-console/tray-monitor/bacula-tray-monitor.conf \
+ src/qt-console/tray-monitor/tray-monitor.pro \
+ src/qt-console/tray-monitor/tray-monitor.pro.mingw32 \
+ src/qt-console/tray-monitor/tray-monitor.pro.mingw64 \
+ src/dird/Makefile \
+ src/dird/bacula-dir.conf \
+ src/lib/Makefile \
+ src/stored/Makefile \
+ src/stored/bacula-sd.conf \
+ src/filed/Makefile \
+ src/filed/bacula-fd.conf \
+ src/cats/Makefile \
+ src/cats/make_catalog_backup.pl \
+ src/cats/make_catalog_backup \
+ src/cats/delete_catalog_backup \
+ src/cats/create_postgresql_database \
+ src/cats/update_postgresql_tables \
+ src/cats/make_postgresql_tables \
+ src/cats/grant_postgresql_privileges \
+ src/cats/drop_postgresql_tables \
+ src/cats/drop_postgresql_database \
+ src/cats/create_mysql_database \
+ src/cats/update_mysql_tables \
+ src/cats/make_mysql_tables \
+ src/cats/grant_mysql_privileges \
+ src/cats/drop_mysql_tables \
+ src/cats/drop_mysql_database \
+ src/cats/create_sqlite3_database \
+ src/cats/update_sqlite3_tables \
+ src/cats/make_sqlite3_tables \
+ src/cats/grant_sqlite3_privileges \
+ src/cats/drop_sqlite3_tables \
+ src/cats/drop_sqlite3_database \
+ src/cats/sqlite \
+ src/cats/mysql \
+ src/cats/create_bacula_database \
+ src/cats/update_bacula_tables \
+ src/cats/grant_bacula_privileges \
+ src/cats/make_bacula_tables \
+ src/cats/drop_bacula_tables \
+ src/cats/drop_bacula_database \
+ src/cats/install-default-backend \
+ src/findlib/Makefile \
+ src/tools/Makefile \
+ src/plugins/fd/Makefile \
+ src/plugins/sd/Makefile \
+ src/plugins/dir/Makefile \
+ po/Makefile.in \
+ updatedb/update_mysql_tables \
+ updatedb/update_sqlite3_tables \
+ updatedb/update_postgresql_tables \
+ updatedb/update_mysql_tables_9_to_10 \
+ updatedb/update_sqlite3_tables_9_to_10 \
+ updatedb/update_postgresql_tables_9_to_10 \
+ updatedb/update_mysql_tables_10_to_11 \
+ updatedb/update_sqlite3_tables_10_to_11 \
+ updatedb/update_postgresql_tables_10_to_11 \
+ updatedb/update_mysql_tables_11_to_12 \
+ updatedb/update_sqlite3_tables_11_to_12 \
+ updatedb/update_postgresql_tables_11_to_12 \
+ examples/nagios/check_bacula/Makefile \
+ platforms/rpms/redhat/bacula.spec \
+ platforms/rpms/redhat/bacula-bat.spec \
+ platforms/rpms/redhat/bacula-docs.spec \
+ platforms/rpms/redhat/bacula-mtx.spec \
+ platforms/rpms/suse/bacula.spec \
+ platforms/rpms/suse/bacula-bat.spec \
+ platforms/rpms/suse/bacula-docs.spec \
+ platforms/rpms/suse/bacula-mtx.spec \
+ $PLUGCONFILES \
+ $PFILES
+])
+AC_OUTPUT
if test "${support_bat}" = "yes" ; then
if test "x$QMAKE" = "xnone"; then
echo "
Configuration on `date`:
- Host: ${host}${post_host} -- ${DISTNAME} ${DISTVER}
- Bacula version: ${BACULA} ${VERSION} (${DATE})
- Source code location: ${srcdir}
- Install binaries: ${sbindir}
- Install libraries: ${libdir}
- Install config files: ${sysconfdir}
- Scripts directory: ${scriptdir}
- Archive directory: ${archivedir}
- Working directory: ${working_dir}
- PID directory: ${piddir}
- Subsys directory: ${subsysdir}
- Man directory: ${mandir}
- Data directory: ${datarootdir}
- Plugin directory: ${plugindir}
- C Compiler: ${CC} ${CCVERSION}
- C++ Compiler: ${CXX} ${CXXVERSION}
- Compiler flags: ${WCFLAGS} ${CFLAGS}
- Linker flags: ${WLDFLAGS} ${LDFLAGS}
- Libraries: ${LIBS}
- Statically Linked Tools: ${support_static_tools}
- Statically Linked FD: ${support_static_fd}
- Statically Linked SD: ${support_static_sd}
- Statically Linked DIR: ${support_static_dir}
- Statically Linked CONS: ${support_static_cons}
- Database backends: ${db_backends}
- Database port: ${db_port}
- Database name: ${db_name}
- Database user: ${db_user}
- Database SSL options: ${db_ssl_options}
-
- Job Output Email: ${job_email}
- Traceback Email: ${dump_email}
- SMTP Host Address: ${smtp_host}
-
- Director Port: ${dir_port}
- File daemon Port: ${fd_port}
- Storage daemon Port: ${sd_port}
-
- Director User: ${dir_user}
- Director Group: ${dir_group}
- Storage Daemon User: ${sd_user}
- Storage DaemonGroup: ${sd_group}
- File Daemon User: ${fd_user}
- File Daemon Group: ${fd_group}
-
- Large file support: $largefile_support
- Bacula conio support: ${got_conio} ${CONS_LIBS}
- readline support: ${got_readline} ${PRTREADLINE_SRC}
- TCP Wrappers support: ${TCPW_MSG} ${WRAPLIBS}
- TLS support: ${support_tls}
- Encryption support: ${support_crypto}
- ZLIB support: ${have_zlib}
- LZO support: ${have_lzo}
- enable-smartalloc: ${support_smartalloc}
- enable-lockmgr: ${support_lockmgr}
- bat support: ${support_bat}
- client-only: ${build_client_only}
- build-dird: ${build_dird}
- build-stored: ${build_stored}
- Plugin support: ${have_plugins}
- AFS support: ${have_afs}
- ACL support: ${have_acl}
- XATTR support: ${have_xattr}
- systemd support: ${support_systemd} ${SYSTEMD_UNITDIR}
- Batch insert enabled: ${batch_insert_db_backends}
-
+ Host: ${host}${post_host} -- ${DISTNAME} ${DISTVER}
+ Bacula version: ${BACULA} ${VERSION} (${DATE})
+ Source code location: ${srcdir}
+ Install binaries: ${sbindir}
+ Install libraries: ${libdir}
+ Install config files: ${sysconfdir}
+ Scripts directory: ${scriptdir}
+ Archive directory: ${archivedir}
+ Working directory: ${working_dir}
+ PID directory: ${piddir}
+ Subsys directory: ${subsysdir}
+ Man directory: ${mandir}
+ Data directory: ${datarootdir}
+ Plugin directory: ${plugindir}
+ C Compiler: ${CC} ${CCVERSION}
+ C++ Compiler: ${CXX} ${CXXVERSION}
+ Compiler flags: ${WCFLAGS} ${CFLAGS}
+ Linker flags: ${WLDFLAGS} ${LDFLAGS}
+ Libraries: ${LIBS}
+ Statically Linked Tools: ${support_static_tools}
+ Statically Linked FD: ${support_static_fd}
+ Statically Linked SD: ${support_static_sd}
+ Statically Linked DIR: ${support_static_dir}
+ Statically Linked CONS: ${support_static_cons}
+ Database backends: ${db_backends}
+ Database port: ${db_port}
+ Database name: ${db_name}
+ Database user: ${db_user}
+ Database SSL options: ${db_ssl_options}
+
+ Job Output Email: ${job_email}
+ Traceback Email: ${dump_email}
+ SMTP Host Address: ${smtp_host}
+
+ Director Port: ${dir_port}
+ File daemon Port: ${fd_port}
+ Storage daemon Port: ${sd_port}
+
+ Director User: ${dir_user}
+ Director Group: ${dir_group}
+ Storage Daemon User: ${sd_user}
+ Storage DaemonGroup: ${sd_group}
+ File Daemon User: ${fd_user}
+ File Daemon Group: ${fd_group}
+
+ Large file support: $largefile_support
+ Bacula conio support: ${got_conio} ${CONS_LIBS}
+ readline support: ${got_readline} ${PRTREADLINE_SRC}
+ TCP Wrappers support: ${TCPW_MSG} ${WRAPLIBS}
+ TLS support: ${support_tls}
+ Encryption support: ${support_crypto}
+ ZLIB support: ${have_zlib}
+ LZO support: ${have_lzo}
+ enable-smartalloc: ${support_smartalloc}
+ enable-lockmgr: ${support_lockmgr}
+ bat support: ${support_bat}
+ client-only: ${build_client_only}
+ build-dird: ${build_dird}
+ build-stored: ${build_stored}
+ Plugin support: ${have_plugins}
+ AFS support: ${have_afs}
+ ACL support: ${have_acl}
+ XATTR support: ${have_xattr}
+ systemd support: ${support_systemd} ${SYSTEMD_UNITDIR}
+ Batch insert enabled: ${batch_insert_db_backends}
+
+ Plugins:
+ - Docker: ${support_docker}
" > config.out
# create a small shell script useful for support with
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for bacula 9.4.3.
+# Generated by GNU Autoconf 2.69 for bacula 9.6.0.
#
#
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
# Identity of this package.
PACKAGE_NAME='bacula'
PACKAGE_TARNAME='bacula'
-PACKAGE_VERSION='9.4.3'
-PACKAGE_STRING='bacula 9.4.3'
+PACKAGE_VERSION='9.6.0'
+PACKAGE_STRING='bacula 9.6.0'
PACKAGE_BUGREPORT=''
PACKAGE_URL=''
DEBUG
FDLIBS
CAP_LIBS
+FD_PLUGIN_DIR
+docker_bin_path
XATTROBJS
ACLOBJS
LZO_LIBS
POSTGRESQL_BINDIR
POSTGRESQL_INCLUDE
POSTGRESQL_LIBS
+CYTHON_INC
+CYTHON_LIBS
+CYTHON
SBINPERM
fd_group
fd_user
HAVE_SUN_OS_TRUE
INCLUDE_UNINSTALL_TARGET
INCLUDE_INSTALL_TARGET
-FD_PLUGIN_DIR
QMAKE_LIBTOOL
LIBTOOL_CLEAN_TARGET
LIBTOOL_UNINSTALL_TARGET
with_fd_user
with_fd_group
with_sbin_perm
+with_cython
enable_batch_insert
with_postgresql
with_mysql
with_lzo
enable_acl
enable_xattr
+enable_docker_plugin
with_systemd
'
ac_precious_vars='build_alias
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures bacula 9.4.3 to adapt to many kinds of systems.
+\`configure' configures bacula 9.6.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of bacula 9.4.3:";;
+ short | recursive ) echo "Configuration of bacula 9.6.0:";;
esac
cat <<\_ACEOF
--disable-lzo disable lzo support [default=yes]
--disable-acl disable acl support [default=auto]
--disable-xattr disable xattr support [default=auto]
+ --disable-docker-plugin disable Docker plugin support [default=auto]
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--with-fd-user=USER specify user for File daemon
--with-fd-group=GROUP specify group for File daemon
--with-sbin-perm=MODE specify permissions for sbin binaries [default=0750]
+ --with-cython=cython Cython path
--with-postgresql[=DIR] Include PostgreSQL support. DIR is the PostgreSQL
base install directory, [default=/usr/local/pgsql]
--with-mysql[=DIR] Include MySQL support. DIR is the MySQL base install
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-bacula configure 9.4.3
+bacula configure 9.6.0
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by bacula $as_me 9.4.3, which was
+It was created by bacula $as_me 9.6.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
-
# Check whether --enable-includes was given.
if test "${enable_includes+set}" = set; then :
enableval=$enable_includes;
# Check whether --enable-client-only was given.
if test "${enable_client_only+set}" = set; then :
enableval=$enable_client_only;
- if test x$enableval = xyes; then
- build_client_only=yes
- db_backends="None"
- DB_BACKENDS="none"
- fi
+ if test x$enableval = xyes; then
+ build_client_only=yes
+ db_backends="None"
+ DB_BACKENDS="none"
+ fi
fi
# Check whether --enable-build-dird was given.
if test "${enable_build_dird+set}" = set; then :
enableval=$enable_build_dird;
- if test x$enableval = xno; then
- build_dird=no
- fi
+ if test x$enableval = xno; then
+ build_dird=no
+ fi
fi
+CYTHON=cython
+CYTHON_LIBS=
+CYTHON_INC=
+
+# Check whether --with-cython was given.
+if test "${with_cython+set}" = set; then :
+ withval=$with_cython;
+ if test "x$withval" != "xno" ; then
+ CYTHON=$withval
+ fi
+
+
+fi
+
+
+$CYTHON -h > /dev/null 2> /dev/null
+if test $? = 0; then
+ if echo $CYTHON | grep cython3 > /dev/null; then
+ PYTHON=python3
+ CYTHON="$CYTHON -3"
+ else
+ PYTHON=python
+ CYTHON="$CYTHON -2"
+ fi
+ CYTHON_LIBS=`${PYTHON}-config --libs 2>/dev/null`
+ CYTHON_INC=`${PYTHON}-config --includes 2>/dev/null`
+else
+ CYTHON=
+fi
+
+
+
+
+
support_batch_insert=yes
# Check whether --enable-batch-insert was given.
if test "${enable_batch_insert+set}" = set; then :
fi
+PLUGCONFILES=""
+
+support_docker=auto
+# Check whether --enable-docker-plugin was given.
+if test "${enable_docker_plugin+set}" = set; then :
+ enableval=$enable_docker_plugin;
+ if test x$enableval = xyes; then
+ support_docker=yes
+ elif test x$enableval = xno; then
+ support_docker=disabled
+ fi
+
+
+fi
+
+if test x$support_docker = xyes -o x$support_docker = xauto; then
+ # Extract the first word of ""docker"", so it can be a program name with args.
+set dummy "docker"; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_path_docker_bin_path+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ case $docker_bin_path in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_docker_bin_path="$docker_bin_path" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_path_docker_bin_path="$as_dir/$ac_word$ac_exec_ext"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+ test -z "$ac_cv_path_docker_bin_path" && ac_cv_path_docker_bin_path=""no""
+ ;;
+esac
+fi
+docker_bin_path=$ac_cv_path_docker_bin_path
+if test -n "$docker_bin_path"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $docker_bin_path" >&5
+$as_echo "$docker_bin_path" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ if test x$docker_bin_path != xno; then
+ FD_PLUGIN_DIR="$FD_PLUGIN_DIR src/plugins/fd/docker"
+ PLUGCONFILES="${PLUGCONFILES} src/plugins/fd/docker/Makefile"
+ else
+ if test x$support_docker = xyes; then
+ echo " "
+ echo "You enabled a Docker Plugin build but 'docker' command not found."
+ echo " "
+ exit 1
+ fi
+ support_docker=no
+ fi
+fi
+
+
+
for ac_header in sys/prctl.h sys/capability.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
exit 1
fi
-
-ac_config_files="$ac_config_files autoconf/Make.common Makefile manpages/Makefile scripts/btraceback scripts/bconsole scripts/baculabackupreport scripts/bacula scripts/bacula-ctl-dir scripts/bacula-ctl-fd scripts/bacula-ctl-sd scripts/devel_bacula scripts/Makefile scripts/logrotate scripts/mtx-changer scripts/disk-changer scripts/logwatch/Makefile scripts/logwatch/logfile.bacula.conf scripts/bat.desktop scripts/bat.desktop.xsu scripts/bat.desktop.consolehelper scripts/bat.console_apps scripts/bacula-tray-monitor.desktop src/Makefile src/host.h src/console/Makefile src/console/bconsole.conf src/qt-console/bat.conf src/qt-console/bat.pro src/qt-console/bat.pro.mingw32 src/qt-console/bat.pro.mingw64 src/qt-console/install_conf_file src/qt-console/tray-monitor/tray-monitor.conf src/qt-console/tray-monitor/bacula-tray-monitor.conf src/qt-console/tray-monitor/tray-monitor.pro src/qt-console/tray-monitor/tray-monitor.pro.mingw32 src/qt-console/tray-monitor/tray-monitor.pro.mingw64 src/dird/Makefile src/dird/bacula-dir.conf src/lib/Makefile src/stored/Makefile src/stored/bacula-sd.conf src/filed/Makefile src/filed/bacula-fd.conf src/cats/Makefile src/cats/make_catalog_backup.pl src/cats/make_catalog_backup src/cats/delete_catalog_backup src/cats/create_postgresql_database src/cats/update_postgresql_tables src/cats/make_postgresql_tables src/cats/grant_postgresql_privileges src/cats/drop_postgresql_tables src/cats/drop_postgresql_database src/cats/create_mysql_database src/cats/update_mysql_tables src/cats/make_mysql_tables src/cats/grant_mysql_privileges src/cats/drop_mysql_tables src/cats/drop_mysql_database src/cats/create_sqlite3_database src/cats/update_sqlite3_tables src/cats/make_sqlite3_tables src/cats/grant_sqlite3_privileges src/cats/drop_sqlite3_tables src/cats/drop_sqlite3_database src/cats/sqlite src/cats/mysql src/cats/create_bacula_database src/cats/update_bacula_tables src/cats/grant_bacula_privileges src/cats/make_bacula_tables src/cats/drop_bacula_tables src/cats/drop_bacula_database src/cats/install-default-backend src/findlib/Makefile src/tools/Makefile src/plugins/fd/Makefile src/plugins/sd/Makefile src/plugins/dir/Makefile po/Makefile.in updatedb/update_mysql_tables updatedb/update_sqlite3_tables updatedb/update_postgresql_tables updatedb/update_mysql_tables_9_to_10 updatedb/update_sqlite3_tables_9_to_10 updatedb/update_postgresql_tables_9_to_10 updatedb/update_mysql_tables_10_to_11 updatedb/update_sqlite3_tables_10_to_11 updatedb/update_postgresql_tables_10_to_11 updatedb/update_mysql_tables_11_to_12 updatedb/update_sqlite3_tables_11_to_12 updatedb/update_postgresql_tables_11_to_12 examples/nagios/check_bacula/Makefile platforms/rpms/redhat/bacula.spec platforms/rpms/redhat/bacula-bat.spec platforms/rpms/redhat/bacula-docs.spec platforms/rpms/redhat/bacula-mtx.spec platforms/rpms/suse/bacula.spec platforms/rpms/suse/bacula-bat.spec platforms/rpms/suse/bacula-docs.spec platforms/rpms/suse/bacula-mtx.spec $PFILES"
-
-ac_config_commands="$ac_config_commands default"
+ac_config_files="$ac_config_files autoconf/Make.common Makefile manpages/Makefile scripts/btraceback scripts/bconsole scripts/baculabackupreport scripts/bacula scripts/bacula-ctl-dir scripts/bacula-ctl-fd scripts/bacula-ctl-sd scripts/devel_bacula scripts/Makefile scripts/logrotate scripts/mtx-changer scripts/disk-changer scripts/logwatch/Makefile scripts/logwatch/logfile.bacula.conf scripts/bat.desktop scripts/bat.desktop.xsu scripts/bat.desktop.consolehelper scripts/bat.console_apps scripts/bacula-tray-monitor.desktop src/Makefile src/host.h src/console/Makefile src/console/bconsole.conf src/qt-console/bat.conf src/qt-console/bat.pro src/qt-console/bat.pro.mingw32 src/qt-console/bat.pro.mingw64 src/qt-console/install_conf_file src/qt-console/tray-monitor/tray-monitor.conf src/qt-console/tray-monitor/bacula-tray-monitor.conf src/qt-console/tray-monitor/tray-monitor.pro src/qt-console/tray-monitor/tray-monitor.pro.mingw32 src/qt-console/tray-monitor/tray-monitor.pro.mingw64 src/dird/Makefile src/dird/bacula-dir.conf src/lib/Makefile src/stored/Makefile src/stored/bacula-sd.conf src/filed/Makefile src/filed/bacula-fd.conf src/cats/Makefile src/cats/make_catalog_backup.pl src/cats/make_catalog_backup src/cats/delete_catalog_backup src/cats/create_postgresql_database src/cats/update_postgresql_tables src/cats/make_postgresql_tables src/cats/grant_postgresql_privileges src/cats/drop_postgresql_tables src/cats/drop_postgresql_database src/cats/create_mysql_database src/cats/update_mysql_tables src/cats/make_mysql_tables src/cats/grant_mysql_privileges src/cats/drop_mysql_tables src/cats/drop_mysql_database src/cats/create_sqlite3_database src/cats/update_sqlite3_tables src/cats/make_sqlite3_tables src/cats/grant_sqlite3_privileges src/cats/drop_sqlite3_tables src/cats/drop_sqlite3_database src/cats/sqlite src/cats/mysql src/cats/create_bacula_database src/cats/update_bacula_tables src/cats/grant_bacula_privileges src/cats/make_bacula_tables src/cats/drop_bacula_tables src/cats/drop_bacula_database src/cats/install-default-backend src/findlib/Makefile src/tools/Makefile src/plugins/fd/Makefile src/plugins/sd/Makefile src/plugins/dir/Makefile po/Makefile.in updatedb/update_mysql_tables updatedb/update_sqlite3_tables updatedb/update_postgresql_tables updatedb/update_mysql_tables_9_to_10 updatedb/update_sqlite3_tables_9_to_10 updatedb/update_postgresql_tables_9_to_10 updatedb/update_mysql_tables_10_to_11 updatedb/update_sqlite3_tables_10_to_11 updatedb/update_postgresql_tables_10_to_11 updatedb/update_mysql_tables_11_to_12 updatedb/update_sqlite3_tables_11_to_12 updatedb/update_postgresql_tables_11_to_12 examples/nagios/check_bacula/Makefile platforms/rpms/redhat/bacula.spec platforms/rpms/redhat/bacula-bat.spec platforms/rpms/redhat/bacula-docs.spec platforms/rpms/redhat/bacula-mtx.spec platforms/rpms/suse/bacula.spec platforms/rpms/suse/bacula-bat.spec platforms/rpms/suse/bacula-docs.spec platforms/rpms/suse/bacula-mtx.spec $PLUGCONFILES $PFILES"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by bacula $as_me 9.4.3, which was
+This file was extended by bacula $as_me 9.6.0, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-bacula config.status 9.4.3
+bacula config.status 9.6.0
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
"platforms/rpms/suse/bacula-bat.spec") CONFIG_FILES="$CONFIG_FILES platforms/rpms/suse/bacula-bat.spec" ;;
"platforms/rpms/suse/bacula-docs.spec") CONFIG_FILES="$CONFIG_FILES platforms/rpms/suse/bacula-docs.spec" ;;
"platforms/rpms/suse/bacula-mtx.spec") CONFIG_FILES="$CONFIG_FILES platforms/rpms/suse/bacula-mtx.spec" ;;
+ "$PLUGCONFILES") CONFIG_FILES="$CONFIG_FILES $PLUGCONFILES" ;;
"$PFILES") CONFIG_FILES="$CONFIG_FILES $PFILES" ;;
- "default") CONFIG_COMMANDS="$CONFIG_COMMANDS default" ;;
*) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
esac
;;
esac
done ;;
- "default":C)
- ;;
esac
done # for ac_tag
echo "
Configuration on `date`:
- Host: ${host}${post_host} -- ${DISTNAME} ${DISTVER}
- Bacula version: ${BACULA} ${VERSION} (${DATE})
- Source code location: ${srcdir}
- Install binaries: ${sbindir}
- Install libraries: ${libdir}
- Install config files: ${sysconfdir}
- Scripts directory: ${scriptdir}
- Archive directory: ${archivedir}
- Working directory: ${working_dir}
- PID directory: ${piddir}
- Subsys directory: ${subsysdir}
- Man directory: ${mandir}
- Data directory: ${datarootdir}
- Plugin directory: ${plugindir}
- C Compiler: ${CC} ${CCVERSION}
- C++ Compiler: ${CXX} ${CXXVERSION}
- Compiler flags: ${WCFLAGS} ${CFLAGS}
- Linker flags: ${WLDFLAGS} ${LDFLAGS}
- Libraries: ${LIBS}
- Statically Linked Tools: ${support_static_tools}
- Statically Linked FD: ${support_static_fd}
- Statically Linked SD: ${support_static_sd}
- Statically Linked DIR: ${support_static_dir}
- Statically Linked CONS: ${support_static_cons}
- Database backends: ${db_backends}
- Database port: ${db_port}
- Database name: ${db_name}
- Database user: ${db_user}
- Database SSL options: ${db_ssl_options}
-
- Job Output Email: ${job_email}
- Traceback Email: ${dump_email}
- SMTP Host Address: ${smtp_host}
-
- Director Port: ${dir_port}
- File daemon Port: ${fd_port}
- Storage daemon Port: ${sd_port}
-
- Director User: ${dir_user}
- Director Group: ${dir_group}
- Storage Daemon User: ${sd_user}
- Storage DaemonGroup: ${sd_group}
- File Daemon User: ${fd_user}
- File Daemon Group: ${fd_group}
-
- Large file support: $largefile_support
- Bacula conio support: ${got_conio} ${CONS_LIBS}
- readline support: ${got_readline} ${PRTREADLINE_SRC}
- TCP Wrappers support: ${TCPW_MSG} ${WRAPLIBS}
- TLS support: ${support_tls}
- Encryption support: ${support_crypto}
- ZLIB support: ${have_zlib}
- LZO support: ${have_lzo}
- enable-smartalloc: ${support_smartalloc}
- enable-lockmgr: ${support_lockmgr}
- bat support: ${support_bat}
- client-only: ${build_client_only}
- build-dird: ${build_dird}
- build-stored: ${build_stored}
- Plugin support: ${have_plugins}
- AFS support: ${have_afs}
- ACL support: ${have_acl}
- XATTR support: ${have_xattr}
- systemd support: ${support_systemd} ${SYSTEMD_UNITDIR}
- Batch insert enabled: ${batch_insert_db_backends}
-
+ Host: ${host}${post_host} -- ${DISTNAME} ${DISTVER}
+ Bacula version: ${BACULA} ${VERSION} (${DATE})
+ Source code location: ${srcdir}
+ Install binaries: ${sbindir}
+ Install libraries: ${libdir}
+ Install config files: ${sysconfdir}
+ Scripts directory: ${scriptdir}
+ Archive directory: ${archivedir}
+ Working directory: ${working_dir}
+ PID directory: ${piddir}
+ Subsys directory: ${subsysdir}
+ Man directory: ${mandir}
+ Data directory: ${datarootdir}
+ Plugin directory: ${plugindir}
+ C Compiler: ${CC} ${CCVERSION}
+ C++ Compiler: ${CXX} ${CXXVERSION}
+ Compiler flags: ${WCFLAGS} ${CFLAGS}
+ Linker flags: ${WLDFLAGS} ${LDFLAGS}
+ Libraries: ${LIBS}
+ Statically Linked Tools: ${support_static_tools}
+ Statically Linked FD: ${support_static_fd}
+ Statically Linked SD: ${support_static_sd}
+ Statically Linked DIR: ${support_static_dir}
+ Statically Linked CONS: ${support_static_cons}
+ Database backends: ${db_backends}
+ Database port: ${db_port}
+ Database name: ${db_name}
+ Database user: ${db_user}
+ Database SSL options: ${db_ssl_options}
+
+ Job Output Email: ${job_email}
+ Traceback Email: ${dump_email}
+ SMTP Host Address: ${smtp_host}
+
+ Director Port: ${dir_port}
+ File daemon Port: ${fd_port}
+ Storage daemon Port: ${sd_port}
+
+ Director User: ${dir_user}
+ Director Group: ${dir_group}
+ Storage Daemon User: ${sd_user}
+ Storage DaemonGroup: ${sd_group}
+ File Daemon User: ${fd_user}
+ File Daemon Group: ${fd_group}
+
+ Large file support: $largefile_support
+ Bacula conio support: ${got_conio} ${CONS_LIBS}
+ readline support: ${got_readline} ${PRTREADLINE_SRC}
+ TCP Wrappers support: ${TCPW_MSG} ${WRAPLIBS}
+ TLS support: ${support_tls}
+ Encryption support: ${support_crypto}
+ ZLIB support: ${have_zlib}
+ LZO support: ${have_lzo}
+ enable-smartalloc: ${support_smartalloc}
+ enable-lockmgr: ${support_lockmgr}
+ bat support: ${support_bat}
+ client-only: ${build_client_only}
+ build-dird: ${build_dird}
+ build-stored: ${build_stored}
+ Plugin support: ${have_plugins}
+ AFS support: ${have_afs}
+ ACL support: ${have_acl}
+ XATTR support: ${have_xattr}
+ systemd support: ${support_systemd} ${SYSTEMD_UNITDIR}
+ Batch insert enabled: ${batch_insert_db_backends}
+
+ Plugins:
+ - Docker: ${support_docker}
" > config.out
# create a small shell script useful for support with
--- /dev/null
+#
+# Simple Makefile for building test FD plugins for Bacula
+#
+# Copyright (C) 2000-2015 by Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+#
+@MCOMMON@
+
+# No optimization for now for easy debugging
+
+FDDIR=../../../filed
+SRCDIR=../../..
+LIBDIR=../../../lib
+FDPLUGDIR=..
+
+topdir = @BUILD_DIR@
+working_dir=@working_dir@
+thisdir = src/plugins/fd/docker
+
+DOCKERSRC = dkid.c dkinfo.c dkcommctx.c docker-fd.c
+DOCKERSRCH = dkid.h dkinfo.h dkcommctx.h docker-fd.h
+DOCKEROBJ = $(DOCKERSRC:.c=.lo)
+
+.SUFFIXES: .c .lo
+
+all: docker-fd.la
+
+.c.lo:
+ @echo "Compiling $< ..."
+ $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) -I${SRCDIR} -I${FDDIR} -I${FDPLUGDIR} -DWORKDIR=\"$(DESTDIR)$(working_dir)\" -c $<
+
+%.lo: %.c %.h Makefile
+ @echo "Compiling $< ..."
+ $(NO_ECHO)$(LIBTOOL_COMPILE) $(CXX) $(DEFS) $(DEBUG) $(CPPFLAGS) $(CFLAGS) -I${SRCDIR} -I${FDDIR} -I${FDPLUGDIR} -DWORKDIR=\"$(DESTDIR)$(working_dir)\" -c $(@:.lo=.c)
+
+$(FDPLUGDIR)/pluglib.lo:
+ $(MAKE) -C $(FDPLUGDIR) pluglib.lo
+
+docker-fd.la: Makefile $(DOCKEROBJ) $(FDPLUGDIR)/pluglib.lo $(DOCKERSRCH)
+ @echo "Linking $(@:.la=.so) ..."
+ $(NO_ECHO)$(LIBTOOL_LINK) --silent $(CXX) $(LDFLAGS) -shared $^ -o $@ -rpath $(plugindir) -module -export-dynamic -avoid-version
+
+$(LIBDIR)/unittests.o: $(LIBDIR)/unittests.c
+ $(MAKE) -C $(LIBDIR) unittests.o
+
+test_dkid: Makefile dkid.c $(LIBDIR)/unittests.o
+ $(RMF) dkid.o
+ $(CXX) -DTEST_PROGRAM $(DEFS) $(DEBUG) -c $(CPPFLAGS) -I$(SRCDIR) -I$(LIBDIR) $(DINCLUDE) $(CFLAGS) dkid.c
+ $(LIBTOOL_LINK) $(CXX) $(LDFLAGS) -L$(LIBDIR) -o $@ dkid.o $(LIBDIR)/unittests.o $(DLIB) -lbac -lm $(LIBS) $(OPENSSL_LIBS)
+ $(RMF) dkid.o
+
+install: all
+ $(MKDIR) $(DESTDIR)$(plugindir)
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) docker-fd.la $(DESTDIR)$(plugindir)
+ $(NO_ECHO)$(RMF) $(DESTDIR)$(plugindir)/docker-fd.la
+
+install-test-plugin: all
+ $(MKDIR) $(DESTDIR)$(plugindir)
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) docker-fd.la $(DESTDIR)$(plugindir)
+ $(NO_ECHO)$(RMF) $(DESTDIR)$(plugindir)/docker-fd.la
+ $(LIBTOOL_INSTALL) $(INSTALL_PROGRAM) test_docker $(DESTDIR)$(sbindir)
+
+Makefile: Makefile.in $(topdir)/config.status
+ cd $(topdir) \
+ && CONFIG_FILES=$(thisdir)/$@ CONFIG_HEADERS= $(SHELL) ./config.status
+
+libtool-clean:
+ @find . -name '*.lo' -print | xargs $(LIBTOOL_CLEAN) $(RMF)
+ @$(RMF) *.la
+ @$(RMF) -r .libs _libs
+
+test_docker-clean:
+ @$(RMF) test_docker
+
+clean: libtool-clean test_docker-clean
+ @rm -f main *.so *.o
+
+distclean: clean
+ @rm -f Makefile *.la *.lo
+ @rm -rf .libs
+
+libtool-uninstall:
+ $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/docker-fd.so
+ $(LIBTOOL_UNINSTALL) $(RMF) $(DESTDIR)$(plugindir)/test_docker
+
+uninstall: @LIBTOOL_UNINSTALL_TARGET@
+
+depend:
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#include "dkcommctx.h"
+
+#define PLUGINPREFIX "dkcommctx:"
+
+/*
+ * Constructor, does simple initialization.
+ */
+DKCOMMCTX::DKCOMMCTX(const char *cmd) :
+ bpipe(NULL),
+ param_include_container(NULL),
+ param_include_image(NULL),
+ param_exclude_container(NULL),
+ param_exclude_image(NULL),
+ param_container(NULL),
+ param_image(NULL),
+ param_volume(NULL),
+ param_mode(DKPAUSE), /* default backup mode */
+ param_container_create(true),
+ param_container_run(false),
+ param_container_imageid(false),
+ param_container_defaultnames(false),
+ param_docker_host(NULL),
+ param_timeout(0),
+
+ abort_on_error(false),
+ all_containers(NULL),
+ all_images(NULL),
+ all_volumes(NULL),
+ objs_to_backup(NULL),
+ all_to_backup(false),
+ all_vols_to_backup(false),
+ f_eod(false),
+ f_error(false),
+ f_fatal(false),
+ ini(NULL),
+ workingvolume(NULL),
+ workingdir(NULL)
+{
+ /* setup initial plugin command here */
+ command = bstrdup(cmd);
+ /* prepare backup list */
+ objs_to_backup = New(alist(32, not_owned_by_alist));
+ param_timeout = 30; // this is a default you can overwrite
+};
+
+/*
+ * Destructor, releases all memory allocated.
+ */
+DKCOMMCTX::~DKCOMMCTX()
+{
+ if (command){
+ free(command);
+ }
+ if (ini){
+ delete ini;
+ }
+
+ release_all_dkinfo_list(&all_containers);
+ release_all_dkinfo_list(&all_images);
+ release_all_dkinfo_list(&all_volumes);
+ if (objs_to_backup){
+ delete objs_to_backup;
+ }
+ release_all_pm_list(¶m_include_container);
+ release_all_pm_list(¶m_exclude_container);
+ release_all_pm_list(¶m_include_image);
+ release_all_pm_list(¶m_exclude_image);
+ release_all_pm_list(¶m_container);
+ release_all_pm_list(¶m_image);
+ release_all_pm_list(¶m_volume);
+ free_and_null_pool_memory(param_docker_host);
+ free_and_null_pool_memory(workingvolume);
+ free_and_null_pool_memory(workingdir);
+};
+
+/*
+ * sets runtime workingdir variable used in working volume creation.
+ *
+ * in:
+ * workdir - the file daemon working directory parameter
+ * out:
+ * none
+ */
+void DKCOMMCTX::setworkingdir(char* workdir)
+{
+ if (workingdir == NULL){
+ /* not allocated yet */
+ workingdir = get_pool_memory(PM_FNAME);
+ }
+ pm_strcpy(&workingdir, workdir);
+ DMSG1(NULL, DVDEBUG, "workingdir: %s\n", workingdir);
+};
+
+/*
+ * Releases the memory allocated by all_* list.
+ *
+ * in:
+ * alist - a list to release
+ * out:
+ * none
+ */
+void DKCOMMCTX::release_all_dkinfo_list(alist **list)
+{
+ DKINFO *dkinfo;
+
+ if (*list){
+ foreach_alist(dkinfo, *list){
+ if (dkinfo){
+ delete dkinfo;
+ }
+ }
+ delete *list;
+ }
+ *list = NULL;
+}
+
+/*
+ * Releases the memory allocated by param_* list.
+ *
+ * in:
+ * alist - a list to release
+ * out:
+ * none
+ */
+void DKCOMMCTX::release_all_pm_list(alist **list)
+{
+ POOLMEM *pm;
+
+ if (*list){
+ foreach_alist(pm, *list){
+ free_and_null_pool_memory(pm);
+ }
+ delete *list;
+ }
+ *list = NULL;
+}
+
+/*
+ * Prepare a docker volume directory for container execution.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * jobid - for volume directory distinguish and jobid tracking
+ * out:
+ * bRC_OK - on success
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::prepare_working_volume(bpContext *ctx, int jobid)
+{
+ char *dir;
+ struct stat statp;
+ pid_t pid = getpid();
+
+ DMSG0(ctx, DINFO, "prepare_working_volume called\n");
+ if (workingvolume == NULL){
+ workingvolume = get_pool_memory(PM_FNAME);
+ /* create dir template for mkdtemp function */
+ Mmsg(workingvolume, "%s/docker-%d-%d-XXXXXX",
+ workingdir != NULL ? workingdir : WORKDIR, jobid, pid);
+ dir = mkdtemp(workingvolume);
+ if (dir == NULL){
+ /* failback to standard method */
+ Mmsg(workingvolume, "%s/docker-%d-%d",
+ workingdir != NULL ? workingdir : WORKDIR, jobid, pid);
+ if (stat(workingvolume, &statp) != 0){
+ berrno be;
+ /* if the path does not exist then create one */
+ if (be.code() != ENOENT || mkdir(workingvolume, 0700) != 0){
+ /* creation or other error, set new errno and proceed to inform user */
+ be.set_errno(errno);
+ DMSG2(ctx, DERROR, "working volume path (%s) creation Err=%s\n", workingvolume, be.bstrerror());
+ JMSG2(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "Working volume path (%s) creation Err=%s!\n", workingvolume, be.bstrerror());
+ return bRC_Error;
+ }
+ } else
+ if (!S_ISDIR(statp.st_mode)){
+ /* the expected working dir/volume is already available and it is not a directory, strange */
+ DMSG2(ctx, DERROR, "working volume path (%s) is not directory Mode=%o\n", workingvolume, statp.st_mode);
+ JMSG2(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "Working volume path (%s) is not directory Mode=%o\n", workingvolume, statp.st_mode);
+ return bRC_Error;
+ }
+ }
+ }
+ DMSG1(ctx, DINFO, "prepare_working_volume finish: %s\n", workingvolume);
+ return bRC_OK;
+};
+
+/*
+ * It removes files and a volume directory after a successful backup/restore
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * out
+ * none
+ */
+void DKCOMMCTX::clean_working_volume(bpContext* ctx)
+{
+ POOL_MEM fname(PM_FNAME);
+ int status;
+ int a;
+ bool ferr = false;
+ const char *ftab[] = {
+ BACULACONTAINERERRLOG,
+ BACULACONTAINERARCHLOG,
+ BACULACONTAINERFIN,
+ BACULACONTAINERFOUT,
+ NULL,
+ };
+
+ DMSG0(ctx, DDEBUG, "clean_working_volume called\n");
+ for (a = 0; ftab[a] != NULL; a++) {
+ render_working_volume_filename(fname, ftab[a]);
+ status = unlink(fname.c_str());
+ if (status < 0){
+ /* unlink error - report to user */
+ berrno be;
+ if (be.code() == ENOENT){
+ continue;
+ }
+ ferr = true;
+ DMSG2(ctx, DERROR, "unlink error: %s Err=%s\n", fname.c_str(), be.bstrerror());
+ JMSG2(ctx, M_ERROR, "Cannot unlink a file: %s Err=%s\n", fname.c_str(), be.bstrerror());
+ }
+ DMSG1(ctx, DDEBUG, "removing: %s\n", fname.c_str())
+ }
+ if (!ferr){
+ status = rmdir(workingvolume);
+ if (status < 0){
+ /* unlink error - report to user */
+ berrno be;
+ DMSG2(ctx, DERROR, "rmdir error: %s Err=%s\n", workingvolume, be.bstrerror());
+ JMSG2(ctx, M_ERROR, "Cannot remove directory: %s Err=%s\n", workingvolume, be.bstrerror());
+ }
+ }
+ free_and_null_pool_memory(workingvolume);
+ DMSG0(ctx, DDEBUG, "clean_working_volume finish.\n");
+};
+
+/*
+ * Terminate the connection represented by BPIPE object.
+ * it shows a debug and job messages when connection close
+ * is unsuccessful and when ctx is available only.
+ *
+ * in:
+ * bpContext - Bacula Plugin context required for debug/job
+ * messages to show, it could be NULL in this
+ * case no messages will be shown.
+ * out:
+ * none
+ */
+void DKCOMMCTX::terminate(bpContext *ctx)
+{
+ int status;
+
+ if (is_closed()){
+ return;
+ }
+
+ DMSG(ctx, DDEBUG, "Terminating PID=%d\n", bpipe->worker_pid);
+ status = close_bpipe(bpipe);
+ if (status){
+ /* error during close */
+ berrno be;
+ f_error = true;
+ DMSG(ctx, DERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "Error closing backend. Err=%s\n", be.bstrerror(status));
+ }
+ if (bpipe->worker_pid){
+ /* terminate the backend */
+ kill(bpipe->worker_pid, SIGTERM);
+ }
+ bpipe = NULL;
+};
+
+/*
+ * Run the command using *_CMD compile variable and prepared
+ * parameters.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * cmd - the command type to execute
+ * args - the command arguments
+ * out:
+ * True - when command execute successfully
+ * False - when execution return error
+ */
+bool DKCOMMCTX::execute_command(bpContext *ctx, POOL_MEM &args)
+{
+ return execute_command(ctx, args.c_str());
+}
+
+/*
+ * Run the command using *_CMD compile variable and prepared
+ * parameters.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * cmd - the command type to execute
+ * args - the command arguments
+ * out:
+ * True - when command execute successfully
+ * False - when execution return error
+ */
+bool DKCOMMCTX::execute_command(bpContext *ctx, const char *args)
+{
+ return execute_command(ctx, (char*)args);
+}
+
+/*
+ * Run the command using *_CMD compile variable and prepared
+ * parameters.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * cmd - the command type to execute
+ * args - the command arguments
+ * out:
+ * True - when command execute successfully
+ * False - when execution return error
+ */
+bool DKCOMMCTX::execute_command(bpContext *ctx, POOLMEM *args)
+{
+ POOL_MEM exe_cmd(PM_FNAME);
+ POOL_MEM DH(PM_NAME);
+ const char *command = DOCKER_CMD;
+ char *envp[3];
+ int a = 0;
+
+ if (args == NULL){
+ /* cannot execute command with args NULL */
+ DMSG0(ctx, DERROR, "Logic error: Cannot execute empty command tool!\n");
+ JMSG0(ctx, M_FATAL, "Logic error: Cannot execute empty command tool!\n");
+ return false;
+ }
+ /* check if command is still available to Bacula */
+ if (access(command, X_OK) < 0){
+ berrno be;
+ DMSG2(ctx, DERROR, "Unable to access %s command. Err=%s\n", command, be.bstrerror());
+ JMSG2(ctx, M_FATAL, "Unable to access %s command. Err=%s\n", command, be.bstrerror());
+ return false;
+ }
+ /* yes, we still have access to it.
+ * the format of a command line to execute is: <cmd> <params> */
+ Mmsg(exe_cmd, "%s %s", command, args);
+ DMSG(ctx, DINFO, "Executing: %s\n", exe_cmd.c_str());
+ /* preparing envinroment variables */
+ envp[a++] = bstrdup("LANG=C");
+ if (param_docker_host != NULL){
+ Mmsg(DH, "DOCKER_HOST=%s", param_docker_host);
+ envp[a++] = bstrdup(DH.c_str());
+ }
+ envp[a] = NULL;
+ bpipe = open_bpipe(exe_cmd.c_str(), 0, "rw", envp);
+ a = 0;
+ while (envp[a] != NULL){
+ free(envp[a++]);
+ }
+ if (bpipe == NULL){
+ berrno be;
+ DMSG(ctx, DERROR, "Unable to execute command. Err=%s\n", be.bstrerror());
+ JMSG(ctx, M_FATAL, "Unable to execute command. Err=%s\n", be.bstrerror());
+ return false;
+ }
+ DMSG(ctx, DINFO, "Command executed at PID=%d\n", get_backend_pid());
+ return true;
+}
+
+/*
+ * Read all output from command tool - until eod and save it
+ * in the out buffer.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out - the POOL_MEM buffer we will read data
+ * out:
+ * -1 - when we've got any error; the function will report
+ * it to Bacula when ctx is not NULL
+ * 0 - when no more data to read - EOD
+ * <n> - the size of received message
+ */
+int32_t DKCOMMCTX::read_output(bpContext *ctx, POOL_MEM &out)
+{
+ int status;
+ int rbytes;
+ bool ndone;
+
+ if (is_closed()){
+ f_error = true;
+ DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot get data.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot get data.\n");
+ return -1;
+ }
+
+ /* set variables */
+ rbytes = 0;
+ ndone = true;
+ /* wait a bit for a command to execute */
+ bmicrosleep(0, 1000); // sleep 1mS
+ /* read all output data */
+ while (ndone){
+ status = read_data(ctx, out.c_str() + rbytes, out.size() - rbytes);
+ if (status < 0){
+ /* error */
+ return -1;
+ }
+ rbytes += status;
+ if (is_eod()){
+ /* we read all data available */
+ ndone = false;
+ continue;
+ }
+ /* it seems out buffer is too small for all data */
+ out.realloc_pm(rbytes + 1024);
+ }
+ return rbytes;
+}
+
+/*
+ * Reads a single data block from command tool.
+ * It reads as more data as is available on the other size and will fit into
+ * a memory buffer - buf. When EOD encountered during reading it will set
+ * f_eod flag, so checking this flag is mandatory!
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * buf - a memory buffer for data
+ * len - the length of the memory buffer - buf
+ * out:
+ * -1 - when we've got any error; the function reports it to Bacula when
+ * ctx is not NULL
+ * when no more data to read - EOD
+ * <n> - the size of received data
+ */
+int32_t DKCOMMCTX::read_data(bpContext *ctx, POOLMEM *buf, int32_t len)
+{
+ int status;
+ int nbytes;
+ int rbytes;
+ int timeout;
+
+ if (buf == NULL || len < 1){
+ /* we have no space to read data */
+ f_error = true;
+ DMSG0(ctx, DERROR, "No space to read data from command tool.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "No space to read data from command tool.\n");
+ return -1;
+ }
+
+ if (is_closed()){
+ f_error = true;
+ DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot get data.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot get data.\n");
+ return -1;
+ }
+
+ /* we will read no more then len bytes available in the buf */
+ nbytes = len;
+ rbytes = 0;
+ /* clear flags */
+ f_eod = f_error = f_fatal = false;
+ timeout = 200; // timeout of 200ms
+ while (nbytes){
+ status = fread(buf + rbytes, 1, nbytes, bpipe->rfd);
+ if (status == 0){
+ berrno be;
+ if (ferror(bpipe->rfd) != 0){
+ f_error = true;
+ DMSG(ctx, DERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read error: ERR=%s\n", be.bstrerror());
+ return -1;
+ }
+ if (feof(bpipe->rfd) != 0){
+ f_eod = true;
+ return rbytes;
+ }
+ bmicrosleep(0, 1000); // sleep 1mS
+ if (!timeout--){
+ /* reach timeout*/
+ f_error = true;
+ DMSG0(ctx, DERROR, "BPIPE read timeout.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE read timeout.\n");
+ return -1;
+ }
+ } else {
+ timeout = 200; // reset timeout
+ }
+ nbytes -= status;
+ rbytes += status;
+ }
+ return rbytes;
+}
+
+/*
+ * Sends a raw data block to command tool.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * buf - a message buffer contains data to send
+ * len - the length of the data to send
+ * out:
+ * -1 - when encountered any error
+ * <n> - the number of bytes sent, success
+ */
+int32_t DKCOMMCTX::write_data(bpContext *ctx, POOLMEM *buf, int32_t len)
+{
+ int status;
+ int nbytes;
+ int wbytes;
+ int timeout;
+
+ if (buf == NULL){
+ /* we have no data to write */
+ f_error = true;
+ DMSG0(ctx, DERROR, "No data to send to command tool.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "No data to send to command tool.\n");
+ return -1;
+ }
+
+ if (is_closed()){
+ f_error = true;
+ DMSG0(ctx, DERROR, "BPIPE to command tool is closed, cannot send data.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE to command tool is closed, cannot send data.\n");
+ return -1;
+ }
+
+ /* we will write len bytes available in the buf */
+ nbytes = len;
+ wbytes = 0;
+ /* clear flags */
+ f_eod = f_error = f_fatal = false;
+ timeout = 200; // timeout of 200ms
+ while (nbytes){
+ status = fwrite(buf + wbytes, 1, nbytes, bpipe->wfd);
+ if (status == 0){
+ berrno be;
+ if (ferror(bpipe->wfd) != 0){
+ f_error = true;
+ DMSG(ctx, DERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write error: ERR=%s\n", be.bstrerror());
+ return -1;
+ }
+ bmicrosleep(0, 1000); // sleep 1mS
+ if (!timeout--){
+ /* reached timeout*/
+ f_error = true;
+ DMSG0(ctx, DERROR, "BPIPE write timeout.\n");
+ JMSG0(ctx, is_fatal() ? M_FATAL : M_ERROR, "BPIPE write timeout.\n");
+ return -1;
+ }
+ } else {
+ timeout = 200; // reset timeout
+ }
+ nbytes -= status;
+ wbytes += status;
+ }
+ return wbytes;
+}
+
+/*
+ * Render a command tool parameter for string value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * fmt - a low-level parameter name
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ Mmsg(*param, " -%s '%s' ", fmt, value);
+ DMSG(ctx, DDEBUG, "render param:%s\n", *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render a command tool parameter for integer value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * fmt - a low-level parameter name
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, int value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ Mmsg(*param, " -%s %d ", value);
+ DMSG(ctx, DDEBUG, "render param:%s\n", *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render a command tool parameter for string value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ Mmsg(*param, "%s", value);
+ DMSG(ctx, DDEBUG, "render param:%s\n", *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup DKCOMMCTX parameter for boolean value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::render_param(bpContext *ctx, bool *param, const char *pname, const char *name, bool value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (param){
+ *param = value;
+ DMSG2(ctx, DDEBUG, "render param: %s=%s\n", pname, *param ? "True" : "False");
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup DKCOMMCTX parameter for int32_t value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::render_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, int32_t value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (param){
+ *param = value;
+ DMSG2(ctx, DDEBUG, "render param: %s=%d\n", pname, *param);
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup DKCOMMCTX parameter for boolean from string value.
+ * The parameter value will be false if value start with '0' character and
+ * will be true in any other case. So, when a plugin will have a following:
+ * param
+ * param=...
+ * param=1
+ * then a param will be set to true.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::parse_param(bpContext *ctx, bool *param, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (value && *value == '0'){
+ *param = false;
+ } else {
+ *param = true;
+ }
+ DMSG2(ctx, DINFO, "%s parameter: %s\n", name, *param ? "True" : "False");
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup DKCOMMCTX parameter for integer from string value.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::parse_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, char *value)
+{
+ if (value && bstrcasecmp(name, pname)){
+ /* convert str to integer */
+ *param = atoi(value);
+ if (*param == 0){
+ /* error in conversion */
+ f_error = true;
+ DMSG2(ctx, DERROR, "Invalid %s parameter: %s\n", name, value);
+ JMSG2(ctx, M_ERROR, "Invalid %s parameter: %s\n", name, value);
+ return false;
+ }
+ DMSG2(ctx, DINFO, "%s parameter: %d\n", name, *param);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Setup DKCOMMCTX parameter for DOCKER_BACKUP_MODE_T from string value.
+ * supported values are: pause, nopause
+ * any other will be ignored.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * param - a pointer to the param variable where we will render a parameter
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::parse_param(bpContext *ctx, DOCKER_BACKUP_MODE_T *param, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (value){
+ if (strcasecmp(value, "pause") == 0){
+ *param = DKPAUSE;
+ } else
+ if (strcasecmp(value, "nopause") == 0){
+ *param = DKNOPAUSE;
+ }
+ }
+ switch (*param){
+ case DKPAUSE:
+ DMSG(ctx, DINFO, "%s parameter: DKPAUSE\n", name);
+ break;
+ case DKNOPAUSE:
+ DMSG(ctx, DINFO, "%s parameter: DKNOPAUSE\n", name);
+ break;
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render and add a parameter for string value to alist.
+ * When alist is NULL (uninitialized) then it creates a new list to use.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * list - pointer to alist class to use
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::add_param_str(bpContext *ctx, alist **list, const char *pname, const char *name, char *value)
+{
+ POOLMEM *param;
+
+ if (bstrcasecmp(name, pname)){
+ if (!*list){
+ *list = New(alist(8, not_owned_by_alist));
+ }
+ param = get_pool_memory(PM_NAME);
+ Mmsg(param, "%s", value);
+ (*list)->append(param);
+ DMSG2(ctx, DDEBUG, "add param: %s=%s\n", name, value);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Render and set a parameter for string value.
+ * When param is NULL (uninitialized) then it allocates a new string.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * list - pointer to alist class to use
+ * pname - a name of the parameter to compare
+ * name - a name of the parameter from parameter list
+ * value - a value to render
+ * out:
+ * True if parameter was rendered
+ * False if it was not the parameter required
+ */
+bool DKCOMMCTX::parse_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value)
+{
+ if (bstrcasecmp(name, pname)){
+ if (!*param){
+ *param = get_pool_memory(PM_NAME);
+ }
+ pm_strcpy(param, value);
+ DMSG2(ctx, DDEBUG, "add param: %s=%s\n", name, value);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Parse a restore plugin parameters for DKCOMMCTX class (single at a time).
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * item - restore plugin parameter to parse
+ * out:
+ * if parameter found it will be rendered in class variable
+ */
+void DKCOMMCTX::parse_parameters(bpContext *ctx, ini_items &item)
+{
+ /* container_create variable */
+ if (render_param(ctx, ¶m_container_create, "container_create", item.name, item.val.boolval)){
+ return;
+ }
+ /* container_create variable */
+ if (render_param(ctx, ¶m_container_run, "container_run", item.name, item.val.boolval)){
+ return;
+ }
+ /* container_create variable */
+ if (render_param(ctx, ¶m_container_imageid, "container_imageid", item.name, item.val.boolval)){
+ return;
+ }
+ /* container_create variable */
+ if (render_param(ctx, ¶m_container_defaultnames, "container_defaultnames", item.name, item.val.boolval)){
+ return;
+ }
+ /* docker_host variable */
+ if (render_param(ctx, ¶m_docker_host, "docker_host", item.name, item.val.strval)){
+ return;
+ }
+ /* timeout variable */
+ if (render_param(ctx, ¶m_timeout, "timeout", item.name, item.val.int32val)){
+ return;
+ }
+ f_error = true;
+ DMSG(ctx, DERROR, "INI: Unknown parameter: %s\n", item.name);
+ JMSG(ctx, M_ERROR, "INI: Unknown parameter: %s\n", item.name);
+}
+/*
+ * Check and render DKCOMMCTX required parameters (single at a time).
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * argk - the parameter name
+ * argv - the parameter value (it could be null)
+ * out:
+ * bRC_OK - when parameter found and rendered successfully
+ * bRC_Error - on any error
+ * bRC_Max - when parameter not found and should be checked elsewhere
+ */
+bRC DKCOMMCTX::parse_parameters(bpContext *ctx, char *argk, char *argv)
+{
+ /* check abort_on_error parameter */
+ if (parse_param(ctx, &abort_on_error, "abort_on_error", argk, argv)){
+ return bRC_OK;
+ }
+ /* check allvolumes parameter */
+ if (parse_param(ctx, &all_vols_to_backup, "allvolumes", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle container list */
+ if (add_param_str(ctx, ¶m_container, "container", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle include_container list */
+ if (add_param_str(ctx, ¶m_include_container, "include_container", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle exclude_container list */
+ if (add_param_str(ctx, ¶m_exclude_container, "exclude_container", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle image list */
+ if (add_param_str(ctx, ¶m_image, "image", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle include_image list */
+ if (add_param_str(ctx, ¶m_include_image, "include_image", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle exclude_image list */
+ if (add_param_str(ctx, ¶m_exclude_image, "exclude_image", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle volume list */
+ if (add_param_str(ctx, ¶m_volume, "volume", argk, argv)){
+ return bRC_OK;
+ }
+ /* check and handle timeout parameter */
+ if (parse_param(ctx, ¶m_timeout, "timeout", argk, argv)){
+ return bRC_OK;
+ }
+ /* check mode parameter */
+ if (parse_param(ctx, ¶m_mode, "mode", argk, argv)){
+ return bRC_OK;
+ }
+ /* check docker_host parameter */
+ if (parse_param(ctx, ¶m_docker_host, "docker_host", argk, argv)){
+ return bRC_OK;
+ }
+
+ /* parameter unknown */
+ return bRC_Max;
+}
+
+/*
+ * Used for dumping current restore object contents for debugging.
+ */
+void DKCOMMCTX::dump_robjdebug(bpContext* ctx, restore_object_pkt* rop)
+{
+ POOL_MEM out(PM_FNAME);
+
+ if (rop){
+ out.check_size(rop->object_len + 1);
+ pm_memcpy(out, rop->object, rop->object_len);
+ DMSG1(ctx, DERROR, "failed restore object:\n%s\n", out.c_str());
+ }
+}
+
+/*
+ * Parse a Restore Object saved during backup and modified by user during restore.
+ * Every RO received will allocate a dedicated command context which is used
+ * by bEventRestoreCommand to handle default parameters for restore.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * rop - a restore object structure to parse
+ * out:
+ * bRC_OK - on success
+ * bRC_Error - on error
+ */
+bRC DKCOMMCTX::parse_restoreobj(bpContext *ctx, restore_object_pkt *rop)
+{
+ int i;
+
+ DMSG(ctx, DINFO, "INIcmd: %s\n", command);
+ if (!ini){
+ ini = new ConfigFile();
+ }
+ if (!ini->dump_string(rop->object, rop->object_len)){
+ DMSG0(ctx, DERROR, "ini->dump_string failed.\n");
+ dump_robjdebug(ctx, rop);
+ return bRC_OK;
+ }
+ ini->register_items(plugin_items_dump, sizeof(struct ini_items));
+ if (!ini->parse(ini->out_fname)){
+ DMSG0(ctx, DERROR, "ini->parse failed.\n");
+ dump_robjdebug(ctx, rop);
+ return bRC_OK;
+ }
+ for (i=0; ini->items[i].name; i++){
+ if (ini->items[i].found){
+ parse_parameters(ctx, ini->items[i]);
+ }
+ }
+ return bRC_OK;
+}
+
+/*
+ * Sets all to backup variables.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * none, internal variables set
+ */
+void DKCOMMCTX::set_all_to_backup(bpContext* ctx)
+{
+ set_all_containers_to_backup(ctx);
+ set_all_images_to_backup(ctx);
+ set_all_volumes_to_backup(ctx);
+ all_to_backup = true;
+}
+
+/*
+ * Sets objs_to_backup list for all containers backup.
+ */
+void DKCOMMCTX::set_all_containers_to_backup(bpContext *ctx)
+{
+ DKINFO *container;
+
+ if (all_containers){
+ foreach_alist(container, all_containers){
+ objs_to_backup->append(container);
+ };
+ }
+ all_to_backup = true;
+};
+
+/*
+ * Sets objs_to_backup list for all images backup.
+ */
+void DKCOMMCTX::set_all_images_to_backup(bpContext *ctx)
+{
+ DKINFO *image;
+
+ if (all_images){
+ foreach_alist(image, all_images){
+ objs_to_backup->append(image);
+ };
+ }
+ all_to_backup = true;
+};
+
+/*
+ * Sets objs_to_backup list for all volumes backup.
+ */
+void DKCOMMCTX::set_all_volumes_to_backup(bpContext *ctx)
+{
+ DKINFO *volume;
+
+ if (all_volumes){
+ foreach_alist(volume, all_volumes){
+ objs_to_backup->append(volume);
+ };
+ }
+ all_to_backup = true;
+};
+
+/*
+ * Sets objs_to_backup list for all containers or images which match the
+ * container/image id or container/image names parameters from plugin
+ * command parameters.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * params - a list of parameters to compare
+ * dklist - a list of containers/images available
+ * estimate - set to true when doing estimate
+ * out:
+ * this->objs_to_backup updated if required
+ */
+void DKCOMMCTX::filter_param_to_backup(bpContext *ctx, alist *params, alist *dklist, bool estimate)
+{
+ DKID dkid;
+ DKINFO *dkinfo;
+ POOLMEM *pobj;
+ bool found;
+
+ if (params){
+ /* container parameter configured */
+ foreach_alist(pobj, params){
+ found = false;
+ foreach_alist(dkinfo, dklist){
+ DMSG3(ctx, DDEBUG, "compare: %s/%s vs %s\n",
+ (char*)dkinfo->id(), dkinfo->name(), pobj);
+ /* we have to check container id or container names */
+ dkid = pobj;
+ if (bstrcmp(pobj, dkinfo->name()) || dkid == *(dkinfo->id())
+ || bstrcmp(pobj, dkinfo->get_image_repository())){
+ /* container or image to backup found */
+ objs_to_backup->append(dkinfo);
+ found = true;
+ DMSG3(ctx, DINFO, "adding %s to backup (1): %s (%s)\n",
+ dkinfo->type_str(),
+ dkinfo->name(), (char*)dkinfo->id());
+ break;
+ };
+ };
+ if (!found){
+ /* docker object not found */
+ f_error = true;
+ if (!estimate){
+ DMSG1(ctx, DERROR, "Not found to backup: %s!\n", pobj);
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Not found to backup: %s!\n", pobj);
+ } else {
+ DMSG1(ctx, DERROR, "Not found to estimate: %s!\n", pobj);
+ JMSG1(ctx, is_fatal() ? M_FATAL : M_ERROR, "Not found to estimate: %s!\n", pobj);
+ };
+ };
+ };
+ };
+};
+
+/*
+ * It is called when 'allvolumes' parameter is set for backup and add
+ * a volumes mounted in containers selected to backup by reading
+ * a Mounts parameter list from docker container.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * out:
+ * none
+ */
+void DKCOMMCTX::add_container_volumes_to_backup(bpContext* ctx)
+{
+ DKINFO *container;
+ DKINFO *volume;
+ DKINFO *obj;
+ char *p, *q;
+ POOL_MEM buf(PM_MESSAGE);
+ int len;
+ bool found;
+ alist containerlist(16, not_owned_by_alist);
+
+ DMSG0(ctx, DDEBUG, "add_container_volumes_to_backup called\n");
+ /* prepare containers to backup list */
+ foreach_alist(container, objs_to_backup){
+ if (container->type() == DOCKER_CONTAINER){
+ containerlist.append(container);
+ }
+ }
+ /* proceed if any container to backup */
+ if (!containerlist.empty()){
+ foreach_alist(container, &containerlist){
+ DMSG1(ctx, DDEBUG, "processing container: %s\n", container->get_container_id());
+ p = container->get_container_mounts();
+ if (p != NULL && *p != 0){
+ /* the container has mounts, so iterate on them and check volumes to backup */
+ len = strlen(p);
+ pm_strcpy(buf, p);
+ p = buf.c_str();
+ while (*p != 0){
+ if ((q = strchr(p, ',')) != NULL){
+ *q = 0; // terminate comma as a string separator
+ } else {
+ q = buf.c_str() + len - 1;
+ }
+ DMSG1(ctx, DDEBUG, "volmount: %s\n", p);
+ /* at 'p' we have mounted docker volume name as string
+ * check if volume already selected for backup */
+ found = false;
+ foreach_alist(obj, objs_to_backup){
+ if (obj->type() == DOCKER_VOLUME && bstrcmp(obj->get_volume_name(), p)){
+ found = true;
+ DMSG0(ctx, DDEBUG, "volume found in objs_to_backup, good!\n");
+ break;
+ }
+ };
+ /* not? simple check in volume list and add it to backup */
+ if (!found){
+ foreach_alist(volume, all_volumes){
+ if (bstrcmp(volume->get_volume_name(), p)){
+ /* this volume we should add for backup */
+ objs_to_backup->append(volume);
+ DMSG0(ctx, DDEBUG, "adding volume to backup!\n");
+ break;
+ }
+ };
+ }
+ /* next in list */
+ p = q + 1;
+ }
+ }
+ }
+ }
+
+ DMSG0(ctx, DDEBUG, "add_container_volumes_to_backup finish.\n");
+};
+
+/*
+ * It creates a list of volumes to backup for a particular container
+ * which are selected manually to backup and should be reflected in
+ * catalog database as a volumes links. It is called after 'allvolumes'
+ * parameter verification.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * out:
+ * none
+ */
+void DKCOMMCTX::select_container_vols(bpContext* ctx)
+{
+ DKINFO *container;
+ DKINFO *volume;
+ DKVOLS *vols;
+ char *p, *q;
+ alist vollist(16, not_owned_by_alist);
+ int len;
+ POOL_MEM buf(PM_MESSAGE);
+
+ DMSG0(ctx, DDEBUG, "select_container_vols called\n");
+ /* prepare volume to backup list */
+ foreach_alist(volume, objs_to_backup){
+ if (volume->type() == DOCKER_VOLUME){
+ vollist.append(volume);
+ }
+ }
+ /* proceed if any volume to backup */
+ if (!vollist.empty()){
+ foreach_alist(container, objs_to_backup){
+ if (container->type() == DOCKER_CONTAINER){
+ DMSG1(ctx, DDEBUG, "processing container: %s\n", container->get_container_id());
+ p = container->get_container_mounts();
+ if (p != NULL && *p != 0){
+ /* the container has mounts, so iterate on them and check volumes to backup */
+ len = strlen(p);
+ pm_strcpy(buf, p);
+ p = buf.c_str();
+ while (*p != 0){
+ if ((q = strchr(p, ',')) != NULL){
+ *q = 0; // terminate comma as a string separator
+ } else {
+ q = buf.c_str() + len - 1;
+ }
+ DMSG1(ctx, DDEBUG, "volmount: %s\n", p);
+ if (*p != '/'){
+ foreach_alist(volume, &vollist){
+ if (bstrcmp(volume->get_volume_name(), p)){
+ volume->inc_volume_linknr();
+ vols = New(DKVOLS(volume));
+ update_vols_mounts(ctx, container, vols);
+ container->container_append_vols(vols);
+ DMSG0(ctx, DDEBUG, "adding to vols\n");
+ break;
+ }
+ };
+ }
+ /* next param */
+ p = q + 1;
+ }
+ }
+ }
+ };
+ }
+ DMSG0(ctx, DDEBUG, "select_container_vols finish.\n");
+};
+
+/*
+ * Checks for common Docker error strings.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * buf - the string to scan
+ * out:
+ * true - when a common error found
+ * false - no common errors found
+ */
+bool DKCOMMCTX::check_for_docker_errors(bpContext* ctx, char* buf)
+{
+ const char *err1 = "Cannot connect to the Docker daemon";
+ const char *err2 = "Unable to find image '" BACULATARIMAGE "' locally";
+ int len;
+
+ /* no docker running error */
+ len = strlen(err1);
+ if (strncmp(buf, err1, len) == 0){
+ DMSG1(ctx, DERROR, "no docker running error! Err=%s\n", buf);
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Cannot continue!\n");
+ return true;
+ }
+
+ /* cannot find baculatar image */
+ len = strlen(err2);
+ if (strncmp(buf, err2, len) == 0){
+ DMSG1(ctx, DERROR, "cannot find baculatar image! Err=%s\n", buf);
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "Docker is unable to find required Bacula container backup image. Cannot continue!\n");
+ return true;
+ }
+
+ return false;
+};
+
+/*
+ * Sets objs_to_backup list for all containers or images which match the
+ * container/image names based on include_* / exclude_* regex parameters from plugin
+ * command parameters.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * params_include - a list of include regex parameters to match
+ * params_exclude - a list of exclude regex parameters to not match
+ * dklist - a list of containers/images available
+ * out:
+ * this->objs_to_backup updated if required
+ */
+void DKCOMMCTX::filter_incex_to_backup(bpContext* ctx, alist* params_include, alist *params_exclude, alist* dklist)
+{
+ alist inex_list(16, not_owned_by_alist);
+ POOLMEM *expr;
+ bool found;
+ int options = REG_EXTENDED | REG_ICASE;
+ int rc, indx;
+ char prbuf[500];
+ DKINFO *dkinfo;
+
+ /* prepare a list of objects from include regex */
+ if (params_include){
+ foreach_alist(expr, params_include){
+ DMSG(ctx, DDEBUG, "processing include: %s\n", expr);
+ rc = regcomp(&preg, expr, options);
+ if (rc != 0) {
+ f_error = true;
+ regerror(rc, &preg, prbuf, sizeof(prbuf));
+ DMSG(ctx, DERROR, "include regex compilation error: %s\n", prbuf);
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "include_container regex compilation error: %s\n", prbuf);
+ continue;
+ }
+ /* include regex compiled, so iterate through all_containers */
+ foreach_alist(dkinfo, dklist){
+ rc = regexec(&preg, dkinfo->name(), 0, NULL, 0);
+ if (rc == 0){
+ /* found */
+ inex_list.append(dkinfo);
+ DMSG2(ctx, DDEBUG, "include %s found: %s\n", dkinfo->type_str(), dkinfo->name());
+ }
+ };
+ regfree(&preg);
+ };
+ }
+
+ /* exclude objects from include list using exclude regex */
+ if (params_exclude){
+ foreach_alist(expr, params_exclude){
+ DMSG(ctx, DDEBUG, "processing exclude: %s\n", expr);
+ rc = regcomp(&preg, expr, options);
+ if (rc != 0) {
+ f_error = true;
+ regerror(rc, &preg, prbuf, sizeof(prbuf));
+ DMSG(ctx, DERROR, "exclude regex compilation error: %s\n", prbuf);
+ JMSG(ctx, is_fatal() ? M_FATAL : M_ERROR, "exclude regex compilation error: %s\n", prbuf);
+ continue;
+ }
+ /* iterate through objects list found used params_include */
+ found = true;
+ while (found){
+ foreach_alist(dkinfo, &inex_list){
+ DMSG2(ctx, DDEBUG, "exclude processing %s: %s\n", dkinfo->type_str(), dkinfo->name());
+ rc = regexec(&preg, dkinfo->name(), 0, NULL, 0);
+ if (rc == 0){
+ /* found */
+ indx = inex_list.current() - 1;
+ DMSG(ctx, DVDEBUG, "inex_list_indx: %d\n", indx);
+ inex_list.remove(indx);
+ /* we have to start again as inex_list->cur_item points to the wrong position */
+ DMSG2(ctx, DDEBUG, "exclude %s found: %s\n", dkinfo->type_str(), dkinfo->name());
+ break;
+ }
+ };
+ if (!dkinfo){
+ DMSG0(ctx, DDEBUG, "exclude no more objects to check\n");
+ found = false;
+ }
+ }
+ regfree(&preg);
+ };
+ }
+ if (inex_list.size()){
+ /* move dkinfos to objs_to_backup list */
+ foreach_alist(dkinfo, &inex_list){
+ objs_to_backup->append(dkinfo);
+ DMSG3(ctx, DINFO, "adding %s to backup (2): %s (%s)\n",
+ dkinfo->type_str(),
+ dkinfo->name(), (char*)dkinfo->id());
+ };
+ }
+};
+
+/*
+ * Prepares a DKCOMMCTX class for a single Plugin parameters for backup and estimate jobs.
+ * The main purpose is to set a objs_to_backup list for a list of vms to backup.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * estimate - if the preparation for estimate (true) or backup (false) job
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::prepare_bejob(bpContext *ctx, bool estimate)
+{
+ /* get list of all objects */
+ if (!get_all_containers(ctx) || !get_all_images(ctx)){
+ return bRC_Error;
+ }
+ /* when docker_host defined then skip all volumes */
+ if (param_docker_host == NULL && !get_all_volumes(ctx)){
+ return bRC_Error;
+ }
+
+ /* when no volume/container/image/include/exclude parameters found that all objects should be saved */
+ if (!param_container && !param_image && !param_include_container && !param_exclude_container
+ && !param_include_image && !param_exclude_image && !param_volume){
+ set_all_to_backup(ctx);
+ } else {
+ all_to_backup = false;
+ /* find all objects on param_* lists */
+ filter_param_to_backup(ctx, param_container, all_containers, estimate);
+ filter_param_to_backup(ctx, param_image, all_images, estimate);
+ if (param_volume && param_docker_host == NULL){
+ filter_param_to_backup(ctx, param_volume, all_volumes, estimate);
+ }
+
+ /* handle include/exclude regex for containers and images only */
+ filter_incex_to_backup(ctx, param_include_container, param_exclude_container, all_containers);
+ filter_incex_to_backup(ctx, param_include_image, param_exclude_image, all_images);
+
+ /* handle allvolumes for containers backup */
+ if (all_vols_to_backup && param_docker_host == NULL){
+ add_container_volumes_to_backup(ctx);
+ }
+
+ /* generate a warning message if required */
+ if ((param_volume || all_vols_to_backup) && param_docker_host){
+ DMSG0(ctx, DINFO, "Docker Volume backup with docker_host is unsupported!\n");
+ JMSG0(ctx, M_WARNING, "Docker Volume backup with docker_host is unsupported!\n");
+ }
+ }
+
+ select_container_vols(ctx);
+
+ return bRC_OK;
+}
+
+/*
+ * Prepares a DKCOMMCTX class for a single Plugin parameters for restore job.
+ * The main purpose is to handle storage_res restore parameter.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error (unimplemented)
+ */
+bRC DKCOMMCTX::prepare_restore(bpContext* ctx)
+{
+ DMSG0(ctx, DDEBUG, "prepare_restore called\n");
+ return bRC_OK;
+}
+
+/*
+ * Setup DKINFO class values based on object type and string parameters from
+ * docker command output.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * type - a docker object type
+ * paramtab - a table of docker command output values
+ * dkinfo - a class to setup
+ * out:
+ * dkinfo updated
+ */
+void DKCOMMCTX::setup_dkinfo(bpContext* ctx, DKINFO_OBJ_t type, char *paramtab[], DKINFO *dkinfo)
+{
+ switch (type){
+ case DOCKER_CONTAINER:
+ setup_container_dkinfo(ctx, paramtab, dkinfo);
+ break;
+ case DOCKER_IMAGE:
+ setup_image_dkinfo(ctx, paramtab, dkinfo);
+ break;
+ case DOCKER_VOLUME:
+ setup_volume_dkinfo(ctx, paramtab, dkinfo);
+ break;
+ }
+};
+
+/*
+ * Setup DKINFO container class values based on string parameters from docker command output.
+ * It is required to setup a following parameters in paramtab array
+ * [0] - container id
+ * [1] - container name
+ * [2] - container size
+ * other parameters will be ignored.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * paramtab - a table of docker command output values
+ * dkinfo - a class to setup
+ * out:
+ * dkinfo updated
+ */
+void DKCOMMCTX::setup_container_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
+{
+ dkinfo->set_container_id(paramtab[0]);
+ dkinfo->set_container_names(paramtab[1]);
+ dkinfo->scan_container_size(paramtab[2]);
+ dkinfo->set_container_mounts(paramtab[3]);
+ DMSG3(ctx, DINFO, "setup_container_dkinfo: %s %s %d\n",
+ (char*)dkinfo->get_container_id(), dkinfo->get_container_names(), dkinfo->get_container_size());
+ DMSG1(ctx, DINFO, "setup_container_dkinfo: %s\n", dkinfo->get_container_mounts());
+};
+
+/*
+ * Setup DKINFO image class values based on string parameters from docker command output.
+ * It is required to setup a following parameters in paramtab array
+ * [0] - image id
+ * [1] - image repository
+ * [2] - image tag
+ * [3] - image size
+ * [4] - image creation date
+ * other parameters will be ignored.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * paramtab - a table of docker command output values
+ * dkinfo - a class to setup
+ * out:
+ * dkinfo updated
+ */
+void DKCOMMCTX::setup_image_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
+{
+ dkinfo->set_image_id(paramtab[0]);
+ dkinfo->set_image_repository(paramtab[1]);
+ dkinfo->set_image_tag(paramtab[2]);
+ dkinfo->scan_image_size(paramtab[3]);
+ dkinfo->set_image_created(str_to_utime(paramtab[4]));
+ DMSG3(ctx, DINFO, "setup_image_dkinfo: %s %s : %s\n",
+ (char*)dkinfo->get_image_id(), dkinfo->get_image_repository(), dkinfo->get_image_tag());
+ DMSG2(ctx, DINFO, "setup_image_dkinfo: %d %ld\n", dkinfo->get_image_size(), dkinfo->get_image_created());
+};
+
+/*
+ * Setup DKINFO volume class values based on string parameters from docker command output.
+ * It is required to setup a following parameters in paramtab array
+ * [0] - volume name
+ * [1] - volume size
+ * other parameters will be ignored.
+ *
+ * To get a volume created time you have to inspect details of the particular volume:
+ * $ docker volume inspect --format "{{.CreatedAt}}" 0a5f9bf16602f2de6c9e701e6fb6d4ce1292ee336348c7c2624f4a08aaacebc4
+ * 2019-06-21T17:11:33+02:00
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * paramtab - a table of docker command output values
+ * dkinfo - a class to setup
+ * out:
+ * dkinfo updated
+ */
+void DKCOMMCTX::setup_volume_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo)
+{
+ dkinfo->set_volume_name(paramtab[0]);
+ dkinfo->scan_volume_size(paramtab[1]);
+ DMSG2(ctx, DINFO, "setup_volume_dkinfo: %s %ld\n",
+ dkinfo->get_volume_name(), dkinfo->get_volume_size());
+};
+
+/*
+ * Setup a list of objects based on docker command execution.
+ * The docker command should format its output into a number of columns separated by a tab character - '/t'
+ * The function support no more then 10 columns to scan. In current implementation we are using no more then 6.
+ * It will setup a list of all docker objects at 'dklist' list based on 'type' of docker objects.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * cmd - a docker command to execute
+ * cols - a number of columns to scan
+ * dklist - a pointer to dklist variable to update
+ * type - a docker object type to setup
+ * out:
+ * dklist - a new list allocated and populated
+ */
+alist *DKCOMMCTX::get_all_list_from_docker(bpContext* ctx, const char *cmd, int cols, alist **dklist, DKINFO_OBJ_t type)
+{
+ POOL_MEM out(PM_MESSAGE);
+ int a;
+ char *paramtab[10];
+ int status;
+ DKINFO *dkinfo;
+ char *p, *q, *t;
+
+ if (cols > 10){
+ DMSG1(ctx, DERROR, "BUG! unsupported number of parameter columns: %d\n", cols);
+ JMSG1(ctx, M_FATAL, "Unsupported number of parameter columns: %d You should call a support!\n", cols);
+ return NULL;
+ }
+ /* invalid pointer to list*/
+ if (!dklist){
+ DMSG0(ctx, DERROR, "BUG! invalid pointer to dklist\n");
+ return NULL;
+ }
+ if (!*dklist){
+ DMSG0(ctx, DINFO, "get_all_list_from_docker called\n");
+ /* first get all containers list */
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "get_all_list_from_docker execution error\n");
+ return NULL;
+ }
+ /* allocate a new list */
+ *dklist = New(alist(32, not_owned_by_alist));
+ memset(out.c_str(), 0, out.size());
+ if ((status = read_output(ctx, out)) > 0){
+ /* we read a string, so terminate it with nul char */
+ p = out.c_str();
+ p[status] = 0;
+ while (*p != 0 && (q = strchr(p, '\n')) != NULL){
+ /* p is the start of the string and q is the end of line */
+ *q = 0; // q+1 will be the next line
+ DMSG(ctx, DVDEBUG, "get_all_list_from_docker scanning: %s\n", p);
+ if (check_for_docker_errors(ctx, p)){
+ goto bailout; // TODO: remove this goto
+ }
+ t = p;
+ /* expect 5 tabs-separators for 6 parameters handled in-place */
+ for (a = 0; a < cols; a++){
+ paramtab[a] = t;
+ t = strchr(t, '\t');
+ if (t != NULL){
+ *t = 0;
+ t++; // next param
+ } else {
+ break; // finish scanning
+ }
+ }
+ for (a = 0; a < cols; a++){
+ DMSG2(ctx, DDEBUG, "get_all_list_from_docker paramtab[%d]: %s\n", a, paramtab[a]);
+ }
+ /* so, the single line is between ( p ; q ) and consist of 6 nul terminated string parameters */
+ dkinfo = New(DKINFO(type));
+ setup_dkinfo(ctx, type, paramtab, dkinfo);
+ (*dklist)->append(dkinfo);
+ if (dkinfo->type() != DOCKER_VOLUME){
+ DMSG3(ctx, DDEBUG, "found %s: %s -> %s\n", dkinfo->type_str(), (char*)dkinfo->id(), dkinfo->name());
+ } else {
+ DMSG2(ctx, DDEBUG, "found %s: %s\n", dkinfo->type_str(), dkinfo->name());
+ }
+ /* next line */
+ DMSG0(ctx, DVDEBUG, "get_all_list_from_docker next line\n");
+ p = q + 1;
+ }
+ } else {
+ DMSG0(ctx, DINFO, "get_all_list_from_docker no container found.\n");
+ }
+ terminate(ctx);
+ } else {
+ DMSG1(ctx, DINFO, "get_all_list_from_docker used cached data: %p\n", *dklist);
+ }
+
+bailout:
+ DMSG0(ctx, DINFO, "get_all_list_from_docker finish.\n");
+ return *dklist;
+}
+
+/*
+ * Updates a docker volume mount point in docker list of mounted vols
+ * for proper volume support file (link) name rendering as: <volname> -> </mount/dir>
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * dkinfo - the container to scan
+ * dkvols - the volume mount information to update
+ * out:
+ * none
+ */
+void DKCOMMCTX::update_vols_mounts(bpContext* ctx, DKINFO *container, DKVOLS *volume)
+{
+ POOL_MEM out(PM_MESSAGE);
+ POOL_MEM cmd(PM_MESSAGE);
+ int status;
+ char *p, *q, *t;
+
+ DMSG0(ctx, DINFO, "update_volume_mounts called\n");
+ if (container && volume){
+ /* get details about container mounts */
+ Mmsg(cmd, "container inspect --format '{{range .Mounts}}{{.Name}}{{print \"\\t\"}}{{println .Destination}}{{end}}' %s", container->get_container_id());
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "update_volume_mounts execution error\n");
+ return;
+ }
+ /* process data:
+ * aa9d3074f8c65a5afafddc6eaaf9827e99bb51f676aafaacc05cfca0188e65bf /var/log\n
+ * 9194f415cafcf8d234673478f3358728d43e0203e58d0338b4ee18a4dca6646b /etc/logrotate.d\n
+ * (...)
+ */
+ if ((status = read_output(ctx, out)) > 0){
+ /* we read a string, so terminate it with nul char */
+ p = out.c_str();
+ p[status] = 0;
+ while (*p != 0 && (q = strchr(p, '\n')) != NULL){
+ /* p is the start of the string and q is the end of line */
+ *q = 0; // q+1 will be the next line
+ DMSG(ctx, DVDEBUG, "update_volume_mounts scanning: %s\n", p);
+ if (check_for_docker_errors(ctx, p)){
+ return;
+ }
+ /* expect 1 tab-separator for 2 parameters handled in-place */
+ t = strchr(p, '\t');
+ if (t != NULL){
+ *t++ = 0;
+ } else {
+ /* scan error */
+ return;
+ }
+ DMSG2(ctx, DDEBUG, "update_volume_mounts volname: %s dest: %s\n", p, t);
+ if (bstrcmp(volume->vol->get_volume_name(), p)){
+ /* this is the volume we are looking for */
+ pm_strcpy(volume->destination, t);
+ return;
+ }
+ /* next line */
+ DMSG0(ctx, DVDEBUG, "get_all_list_from_docker next line\n");
+ p = q + 1;
+ }
+ } else {
+ DMSG0(ctx, DINFO, "get_all_list_from_docker no container found.\n");
+ }
+ terminate(ctx);
+ } else {
+ DMSG2(ctx, DERROR, "invalid parameters: c:%p v:%p\n", container, volume);
+ return;
+ }
+ DMSG0(ctx, DINFO, "update_volume_mounts finish.\n");
+}
+
+/*
+ * Return a list of all containers available on Docker.
+ *
+ * the container list is described as the following text block:
+# docker ps -a --no-trunc=true --format "{{.ID}}\t{{.Names}}\t{{.Size}}\t{{.Mounts}}\t{{.Labels}}\t{{.Image}}"
+66f45d8601bae26a6b2ffeb46922318534d3b3905377b3a224693bd78601cb3b brave_edison 0B (virtual 228MB) c0a478d317195ba27dda1370b73e5cb94a7773f2a611142d7dff690abdcfdcbf postgres\n
+
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out:
+ * NULL - on any error
+ * empty alist - when no docker comntainers found
+ * alist - a list of DKINFO class which describe a single docker container
+ */
+alist *DKCOMMCTX::get_all_containers(bpContext* ctx)
+{
+ return get_all_list_from_docker(ctx,
+ "ps -a --no-trunc=true --format \"{{.ID}}\\t{{.Names}}\\t{{.Size}}\\t{{.Mounts}}\\t{{.Labels}}\\t{{.Image}}\"",
+ 6, &all_containers, DOCKER_CONTAINER);
+};
+
+/*
+ * Return a list of all images available on Docker.
+ *
+ * the images list is described as the following text block:
+# # docker image ls --no-trunc=true --format "{{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}"
+restore/a6ba1cb597d5 latest sha256:44c34a8a510dc08b7b0a8f6961257e89120152739e61611f564353e8feb95e68 319MB\n
+
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out:
+ * NULL - on any error
+ * empty alist - when no vms found
+ * alist - a list of DKINFO class which describe a single VM
+ */
+alist *DKCOMMCTX::get_all_images(bpContext* ctx)
+{
+ return get_all_list_from_docker(ctx,
+ "image ls --no-trunc=true --format \"{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}\\t{{.CreatedAt}}\"",
+ 5, &all_images, DOCKER_IMAGE);
+}
+
+/*
+ * Return a list of all volumes available on Docker.
+ *
+ * the volumes list is described as the following text block:
+# # docker volume ls --format "{{.Name}}\t{{.Size}}"
+0a5f9bf16602f2de6c9e701e6fb6d4ce1292ee336348c7c2624f4a08aaacebc4 N/A\n
+
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out:
+ * NULL - on any error
+ * empty alist - when no vms found
+ * alist - a list of DKINFO class which describe a single VM
+ */
+alist *DKCOMMCTX::get_all_volumes(bpContext* ctx)
+{
+ return get_all_list_from_docker(ctx,
+ "volume ls --format \"{{.Name}}\\t{{.Size}}\"", 2, &all_volumes, DOCKER_VOLUME);
+}
+
+/*
+ * Rename/set the docker image tag on selected image.
+ */
+bRC DKCOMMCTX::docker_tag(bpContext* ctx, DKID &dkid, POOLMEM *tag)
+{
+ bRC rc = bRC_OK;
+ POOL_MEM cmd(PM_FNAME);
+ POOL_MEM out(PM_BSOCK);
+ int status;
+
+ DMSG0(ctx, DINFO, "docker_tag called.\n");
+ if (!tag){
+ DMSG0(ctx, DERROR, "docker_tag tag is NULL!\n");
+ return bRC_Error;
+ }
+ Mmsg(cmd, "image tag %s %s", (char*)dkid, tag);
+ DMSG1(ctx, DDEBUG, "%s\n", cmd.c_str());
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "docker_tag execution error\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "docker_tag execution error\n");
+ return bRC_Error;
+ }
+
+ memset(out.c_str(), 0, out.size());
+ status = read_output(ctx, out);
+ if (status < 0){
+ /* error reading data from docker command */
+ DMSG0(ctx, DERROR, "docker_tag error reading data from docker command\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "docker_tag error reading data from docker command\n");
+ rc = bRC_Error;
+ } else
+ if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
+ rc = bRC_Error;
+ }
+
+ /* we do not expect any output here */
+ terminate(ctx);
+ DMSG0(ctx, DINFO, "docker_tag finish.\n");
+ return rc;
+};
+
+/*
+ * Waits for restore commands to finish.
+ * Closes a BPIPE write descriptor which means EOF to command tool. Then try
+ * to read from tools output and checks if restore was successful or not.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out:
+ * bRC_OK - restore was successful and vmuuid is filled with VM UUID restored.
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::wait_for_restore(bpContext *ctx, DKID &dkid)
+{
+ POOL_MEM out(PM_BSOCK);
+ POOL_MEM buf(PM_BSOCK);
+ int status;
+ char *p;
+ bRC rc = bRC_OK;
+
+ DMSG0(ctx, DINFO, "wait_for_restore called.\n");
+ /* first flush any outstanding restore data and close write descriptor */
+ close_wpipe(bpipe);
+ /* now read the status from command */
+ while ((status = read_output(ctx, out)) != 0){
+ if (status < 0){
+ /* error reading data from command tool */
+ DMSG0(ctx, DERROR, "error reading data from command tool\n");
+ rc = bRC_Error;
+ goto bailout;
+ }
+ pm_strcat(buf, out);
+ p = buf.c_str();
+ p[status] = 0;
+ }
+
+ /* check for errors */
+ DMSG1(ctx, DVDEBUG, "bufout: %s\n", buf.c_str());
+ p = buf.c_str();
+ if (strstr(p, "Loaded image ID: ") == NULL){
+ /* error, missing confirmation*/
+ DMSG0(ctx, DERROR, "wait_for_restore confirmation error!\n");
+ JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "Image restore commit error: %s\n", p);
+ rc = bRC_Error;
+ } else {
+ dkid = (char*)(p+17);
+ DMSG1(ctx, DDEBUG, "scanned dkid: %s\n", (char*)dkid);
+ }
+
+bailout:
+ terminate(ctx);
+ DMSG0(ctx, DINFO, "wait_for_restore finish.\n");
+ return rc;
+}
+
+/*
+ * Executes docker command tool to make a container image commit.
+ * Setup dkinfo->data.container.imagesave with image id of newly created image
+ * which should be used at image save method.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * dkinfo - container for commit
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::container_commit(bpContext* ctx, DKINFO *dkinfo, int jobid)
+{
+ POOL_MEM cmd(PM_FNAME);
+ POOL_MEM imagename(PM_FNAME);
+ POOL_MEM out(PM_MESSAGE);
+ const char *mode = "";
+ const char *PAUSE = "-p";
+ DKID imagesave;
+ bRC rc = bRC_OK;
+ int status;
+ char *p;
+
+ DMSG0(ctx, DINFO, "container_commit called.\n");
+ if (dkinfo->type() != DOCKER_CONTAINER){
+ /* commit works on containers only */
+ return bRC_Error;
+ }
+ if (param_mode == DKPAUSE){
+ mode = PAUSE;
+ }
+ // commit -p 66f45d8601bae26a6b2ffeb46922318534d3b3905377b3a224693bd78601cb3b mcache1/66f45d8601ba:backup
+ render_imagesave_name(imagename, dkinfo, jobid);
+ Mmsg(cmd, "commit %s %s %s", mode, (char*)dkinfo->get_container_id(), imagename.c_str());
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "container_commit execution error\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit execution error\n");
+ return bRC_Error;
+ }
+
+ memset(out.c_str(), 0, out.size());
+ status = read_output(ctx, out);
+ if (status < 0){
+ /* error reading data from docker command */
+ DMSG0(ctx, DERROR, "container_commit error reading data from docker command\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit error reading data from docker command\n");
+ rc = bRC_Error;
+ } else {
+ /* terminate committed image id string */
+ p = out.c_str();
+ p[status] = 0;
+ strip_trailing_junk(out.c_str());
+
+ /* check a known output error */
+ if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
+ rc = bRC_Error;
+ } else {
+ // should return a string: sha256:290835d692069c376072061362cb11b1e99efd555f6fb83b7be3e524ba6067fc
+ imagesave = p;
+ if (imagesave.id() < 0){
+ /* error scanning image id */
+ DMSG1(ctx, DERROR, "container_commit cannot scan commit image id. Err=%s\n", p);
+ JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "container_commit cannot scan commit image id. Err=%s\n", p);
+ rc = bRC_Error;
+ } else {
+ dkinfo->set_container_imagesave(imagesave);
+ dkinfo->set_container_imagesave_tag(imagename);
+ DMSG(ctx, DINFO, "Commit created: %s\n", dkinfo->get_container_imagesave_tag());
+ JMSG(ctx, M_INFO, "Commit created: %s\n", dkinfo->get_container_imagesave_tag());
+ }
+ }
+ }
+
+ terminate(ctx);
+ DMSG0(ctx, DINFO, "container_commit finish.\n");
+ return rc;
+}
+
+/*
+ * Executes docker command tool to make a container image commit.
+ * Setup dkinfo->data.container.imagesave with image id of newly created image
+ * which should be used at image save method.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * dkinfo - container for commit
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::delete_container_commit(bpContext* ctx, DKINFO *dkinfo, int jobid)
+{
+ POOL_MEM cmd(PM_FNAME);
+ POOL_MEM imagename(PM_FNAME);
+ POOL_MEM out(PM_MESSAGE);
+ DKID imagesave;
+ bRC rc = bRC_OK;
+ int status;
+ char *p, *q;
+ int noerror = 0;
+
+ DMSG0(ctx, DINFO, "delete_container_commit called.\n");
+ if (dkinfo->type() != DOCKER_CONTAINER){
+ /* commit works on containers only, silently ignore images */
+ return bRC_OK;
+ }
+
+ if (dkinfo->get_container_imagesave()->id() > 0){
+ /* container has commit image */
+ /*
+ # docker rmi e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69
+ Untagged: mcache1/66f45d8601ba/123:backup
+ Deleted: sha256:e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69
+ */
+
+ Mmsg(cmd, "rmi %s", (char*)dkinfo->get_container_imagesave());
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "delete_container_commit execution error\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "delete_container_commit execution error\n");
+ return bRC_Error;
+ }
+
+ memset(out.c_str(), 0, out.size());
+ status = read_output(ctx, out);
+ if (status < 0){
+ /* error reading data from docker command */
+ DMSG0(ctx, DERROR, "delete_container_commit error reading data from docker command\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "delete_container_commit error reading data from docker command\n");
+ rc = bRC_Error;
+ goto bailout;
+ }
+
+ /* terminate output string */
+ p = out.c_str();
+ p[status] = 0;
+
+ /* check a known output error */
+ if (status > 0 && (strncmp(out.c_str(), "Cannot connect to the Docker daemon", 35) == 0)){
+ DMSG1(ctx, DERROR, "No Docker is running. Cannot continue! Err=%s\n", out.c_str());
+ JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Err=%s\n", out.c_str());
+ rc = bRC_Error;
+ goto bailout;
+ }
+
+ render_imagesave_name(imagename, dkinfo, jobid);
+
+ /* it should return the following string:
+ Untagged: mcache1/66f45d8601ba:backup\n
+ Deleted: sha256:e7cd2a7f1c52a1fa8d88ab812abdcd814064e4884a12bd1f9acde16133023a69\n
+ */
+ while (*p != 0 && (q = strchr(p, '\n')) != NULL){
+ /* p is the start of the string and q is the end of line */
+ *q = 0;
+ DMSG(ctx, DVDEBUG, "delete_container_commit scanning: %s\n", p);
+ if (strstr(p, "Untagged: ") == p && strstr(p, imagename.c_str()) != NULL){
+ /* above message means 1/3 of success */
+ noerror++;
+ }
+ if (strstr(p, "Deleted: ") == p){
+ /* check if it deleted what we requested for */
+ noerror++;
+ imagesave = (char*)(p + 9);
+ if (imagesave == *dkinfo->get_container_imagesave()){
+ /* yes it deleted exactly what we are requesting for */
+ noerror++;
+ }
+ }
+ /* next line */
+ DMSG0(ctx, DVDEBUG, "delete_snapshot next line\n");
+ p = q + 1;
+ }
+
+ if (noerror < 3){
+ /* error deleting snapshot */
+ strip_trailing_junk(out.c_str());
+ DMSG(ctx, DERROR, "Error deleting commit image. Err=%s\n", out.c_str());
+ JMSG(ctx, abort_on_error ? M_FATAL : M_ERROR, "Error deleting commit image. Err=%s\n", out.c_str());
+ rc = bRC_Error;
+ goto bailout;
+ }
+
+ DMSG(ctx, DINFO, "Commit removed: %s\n", dkinfo->get_container_imagesave_tag());
+ JMSG(ctx, M_INFO, "Commit removed: %s\n", dkinfo->get_container_imagesave_tag());
+
+bailout:
+ terminate(ctx);
+ } else {
+ DMSG0(ctx, DINFO, "container_commit no imagesave available.\n");
+ }
+
+ DMSG0(ctx, DINFO, "container_commit finish.\n");
+ return rc;
+}
+
+/*
+ * Executes docker command tool to save docker image.
+ * The data to backup is generated on docker stdout channel and will be saved
+ * on pluginIO calls.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * dkid - docker image to save information
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::image_save(bpContext* ctx, DKID *dkid)
+{
+ POOL_MEM cmd(PM_FNAME);
+
+ DMSG0(ctx, DINFO, "image_save called.\n");
+ Mmsg(cmd, "save %s", (char*)dkid);
+ if (!execute_command(ctx, cmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "image_save execution error\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "image_save execution error\n");
+ return bRC_Error;
+ }
+ DMSG0(ctx, DINFO, "image_save finish, now we can read all the data.\n");
+
+ return bRC_OK;
+}
+
+/*
+ * It runs a Bacula Archive container for Docker Volume files data backup.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * cmd - the command for Bacula Archive container execution ('backup' and 'restore' are supported)
+ * volname - a volume name to backup from or restore to
+ * jobid - required for proper support volume creation
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::run_container_volume_cmd(bpContext* ctx, const char *cmd, POOLMEM *volname, int jobid)
+{
+ POOL_MEM bactarcmd(PM_FNAME);
+ POOL_MEM out(PM_MESSAGE);
+ int status;
+ char *p;
+
+ DMSG1(ctx, DINFO, "run_container_volume_cmd called: %s.\n", cmd);
+ if (workingvolume == NULL && prepare_working_volume(ctx, jobid) != bRC_OK){
+ return bRC_Error;
+ }
+ /* Here we will run archive container for volume backup */
+ Mmsg(bactarcmd, "run -d --rm -v %s:/%s -v %s:/logs %s %s", volname, cmd, workingvolume, BACULATARIMAGE, cmd);
+ if (!execute_command(ctx, bactarcmd)){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "run_container_volume_cmd execution error\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "run_container_volume_cmd execution error\n");
+ return bRC_Error;
+ }
+
+ /* setup output buffer */
+ memset(out.c_str(), 0, out.size());
+ status = read_output(ctx, out);
+ if (status < 0){
+ /* error reading data from docker command */
+ DMSG0(ctx, DERROR, "run_container_volume_cmd error reading data from docker command\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR, "run_container_volume_cmd error reading data from docker command\n");
+ return bRC_Error;
+ }
+
+ /* terminate container id string */
+ p = out.c_str();
+ p[status] = 0;
+ strip_trailing_junk(out.c_str());
+
+ /* check a known output error */
+ if (status > 0 && check_for_docker_errors(ctx, out.c_str())){
+ return bRC_Error;
+ }
+
+ DMSG2(ctx, DINFO, "run_container_volume_cmd finish - acc: %s, now we can %s all the data.\n", out.c_str(), cmd);
+
+ return bRC_OK;
+}
+
+/*
+ * Execute a Bacula Archive Container for volume backup.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * volname - a volume name to backup from
+ * jobid - required for proper support volume creation
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::run_container_volume_save(bpContext* ctx, POOLMEM* volname, int jobid)
+{
+ return run_container_volume_cmd(ctx, "backup", volname, jobid);
+};
+
+/*
+ * Execute a Bacula Archive Container for volume restore.
+ *
+ * in:
+ * bpContext - required for debug/job messages
+ * volname - a volume name to restore to
+ * jobid - required for proper support volume creation
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::run_container_volume_load(bpContext* ctx, POOLMEM* volname, int jobid)
+{
+ return run_container_volume_cmd(ctx, "restore", volname, jobid);
+};
+
+/*
+ * Execute Docker commands to perform backup procedure.
+ * Commit container then save committed image for container backup
+ * or simply save docker image for image backup.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * dkinfo - the docker object to backup
+ * jobid - bacula jobid number
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::backup_docker(bpContext *ctx, DKINFO *dkinfo, int jobid)
+{
+ DMSG0(ctx, DINFO, "backup_docker called.\n");
+ switch (dkinfo->type()){
+ case DOCKER_CONTAINER:
+ /* create container commit */
+ if (container_commit(ctx, dkinfo, jobid) == bRC_OK){
+ if (dkinfo->get_container_imagesave()->id() > 0){
+ return image_save(ctx, dkinfo->get_container_imagesave());
+ }
+ }
+ break;
+ case DOCKER_IMAGE:
+ return image_save(ctx, dkinfo->get_image_id());
+ case DOCKER_VOLUME:
+ return run_container_volume_save(ctx, dkinfo->get_volume_name(), jobid);
+ default:
+ break;
+ }
+ DMSG0(ctx, DINFO, "backup_docker finish with error.\n");
+ return bRC_Error;
+};
+
+/*
+ * Executes Docker commands to perform restore.
+ * The data to restore is gathered on command stdin channel and will be sent
+ * on pluginIO calls.
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * out:
+ * bRC_OK - when command execution was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::restore_docker(bpContext *ctx, DKINFO *dkinfo, int jobid)
+{
+ DMSG0(ctx, DINFO, "restore_docker called.\n");
+ if (dkinfo != NULL && dkinfo->type() == DOCKER_VOLUME){
+ return run_container_volume_load(ctx, dkinfo->get_volume_name(), jobid);
+ } else {
+ if (!execute_command(ctx, "load")){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "restore_docker execution error\n");
+ return bRC_Error;
+ }
+ }
+ DMSG0(ctx, DINFO, "restore_docker finish, now we can write the data.\n");
+ return bRC_OK;
+};
+
+/*
+ * Create or run docker container based on restored image.
+ *
+ * # docker container create --name mcache1_59 mcache1/b97d4dd88063/59:restore
+ *
+ * in:
+ * bpContext - for Bacula debug jobinfo messages
+ * dkinfo - restored image for container creation or run
+ * out:
+ * bRC_OK - when creation/run was successful
+ * bRC_Error - on any error
+ */
+bRC DKCOMMCTX::docker_create_run_container(bpContext* ctx, DKINFO *dkinfo)
+{
+ POOL_MEM cmd(PM_FNAME);
+ POOL_MEM out(PM_BSOCK);
+ bRC rc = bRC_OK;
+ int status;
+ char *p;
+ char *imagelabel;
+ const char *namepar;
+ const char *nameval;
+ DKID containerid;
+
+ if (!param_container_create && !param_container_run){
+ DMSG0(ctx, DINFO, "docker_create_container skipped on request.\n");
+ return bRC_OK;
+ }
+ DMSG0(ctx, DINFO, "docker_create_container called.\n");
+ if (dkinfo){
+ imagelabel = param_container_imageid ? (char*)dkinfo->get_container_imagesave() : dkinfo->get_container_imagesave_tag();
+ namepar = param_container_defaultnames ? "" : "--name ";
+ nameval = param_container_defaultnames ? "" : dkinfo->get_container_names();
+ if (param_container_run){
+ // create and run the container
+ Mmsg(cmd, "run -d %s%s %s", namepar, nameval, imagelabel);
+ } else {
+ // create only
+ Mmsg(cmd, "container create %s%s %s", namepar, nameval, imagelabel);
+ }
+ if (!execute_command(ctx, cmd.c_str())){
+ /* some error executing command */
+ DMSG0(ctx, DERROR, "docker_create_container execution error\n");
+ return bRC_Error;
+ }
+
+ memset(out.c_str(), 0, out.size());
+ status = read_output(ctx, out);
+ if (status < 0){
+ /* error reading data from docker command */
+ DMSG0(ctx, DERROR, "docker_create_container error reading data from docker command\n");
+ JMSG0(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "docker_create_container error reading data from docker command\n");
+ rc = bRC_Error;
+ goto bailout;
+ }
+
+ /* terminate committed image id string */
+ p = out.c_str();
+ p[status] = 0;
+ strip_trailing_junk(out.c_str());
+
+ /* check a known output error */
+ if (status > 0 && (strncmp(out.c_str(), "Cannot connect to the Docker daemon", 35) == 0)){
+ DMSG1(ctx, DERROR, "No Docker is running. Cannot continue! Err=%s\n", out.c_str());
+ JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR, "No Docker is running. Err=%s\n", out.c_str());
+ rc = bRC_Error;
+ goto bailout;
+ }
+
+ // should return a string like: 5dd2e72fd9981184ddb8b04aaea06003617fd3f09ad0764921694e20be680c54
+ containerid = p;
+ if (containerid.id() < 0){
+ /* error scanning container id */
+ DMSG1(ctx, DERROR, "docker_create_container cannot scan commit image id. Err=%s\n", p);
+ JMSG1(ctx, abort_on_error ? M_FATAL : M_ERROR,
+ "docker_create_container cannot scan commit image id. Err=%s\n", p);
+ rc = bRC_Error;
+ goto bailout;
+ } else {
+ dkinfo->set_container_id(containerid);
+ if (param_container_run){
+ DMSG1(ctx, DINFO, "docker_create_container successfully run container as: %s\n", (char*)containerid);
+ JMSG1(ctx, M_INFO, "Successfully run container as: (%s)\n", containerid.digest_short());
+ }
+ }
+ }
+
+bailout:
+ terminate(ctx);
+ DMSG0(ctx, DINFO, "docker_create_container finish.\n");
+ return rc;
+};
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef _DKCOMMCTX_H_
+#define _DKCOMMCTX_H_
+
+#include "pluglib.h"
+#include "lib/ini.h"
+#include "lib/bregex.h"
+
+#define USE_CMD_PARSER
+#include "fd_common.h"
+#include "dkinfo.h"
+
+/* Plugin compile time variables */
+#ifndef DOCKER_CMD
+#ifndef HAVE_WIN32
+#define DOCKER_CMD "/usr/bin/docker"
+#else
+#define DOCKER_CMD "C:/Program Files/Docker/Docker/resources/bin/docker.exe"
+#endif
+#endif
+
+#ifndef WORKDIR
+#define WORKDIR "/opt/bacula/working"
+#endif
+
+#define BACULATARIMAGE "baculatar:" DOCKER_TAR_IMAGE
+
+#define BACULACONTAINERFOUT "fout"
+#define BACULACONTAINERFIN "fin"
+#define BACULACONTAINERERRLOG "docker.err"
+#define BACULACONTAINERARCHLOG "docker.log"
+
+/*
+ * Supported backup modes
+ */
+typedef enum {
+ DKPAUSE,
+ DKNOPAUSE,
+} DOCKER_BACKUP_MODE_T;
+
+/*
+ * The list of restore options saved to the RestoreObject.
+ */
+static struct ini_items plugin_items_dump[] = {
+// name handler comment required default
+ {"container_create", ini_store_bool, "Create container on restore", 0, "*Yes*"},
+ {"container_run", ini_store_bool, "Run container on restore", 0, "*No*"},
+ {"container_imageid", ini_store_bool, "Use Image Id for container creation/start", 0, "*No*"},
+ {"container_defaultnames", ini_store_bool, "Use default docker Names on container creation", 0, "*No*"},
+ {"docker_host", ini_store_str, "Use defined docker host to restore", 0, "*local*"},
+ {"timeout", ini_store_int32, "Timeout connecting to volume container", 0, "*30*"},
+ {NULL, NULL, NULL, 0, NULL}
+};
+
+/*
+ * This is a low-level communication class which handles command tools execution.
+ */
+class DKCOMMCTX: public SMARTALLOC {
+ public:
+ char *command;
+
+ alist *get_all_containers(bpContext *ctx);
+ alist *get_all_images(bpContext *ctx);
+ alist *get_all_volumes(bpContext *ctx);
+ void release_all_dkinfo_list(alist **list);
+ void release_all_pm_list(alist **list);
+ void set_all_to_backup(bpContext *ctx);
+ void set_all_containers_to_backup(bpContext *ctx);
+ void set_all_images_to_backup(bpContext *ctx);
+ void set_all_volumes_to_backup(bpContext *ctx);
+
+ inline DKINFO *get_first_to_backup(bpContext *ctx) { return (DKINFO*)objs_to_backup->first(); };
+ inline DKINFO *get_next_to_backup(bpContext *ctx) { return (DKINFO*)objs_to_backup->next(); };
+ inline void finish_backup_list(bpContext *ctx) { objs_to_backup->last(); };
+
+ bRC container_commit(bpContext *ctx, DKINFO *dkinfo, int jobid);
+ bRC delete_container_commit(bpContext *ctx, DKINFO *dkinfo, int jobid);
+ bRC image_save(bpContext *ctx, DKID *dkid);
+ bRC backup_docker(bpContext *ctx, DKINFO *dkinfo, int jobid);
+ bRC restore_docker(bpContext *ctx, DKINFO *dkinfo, int jobid);
+ bRC docker_tag(bpContext* ctx, DKID &dkid, POOLMEM *tag);
+ bRC docker_create_run_container(bpContext* ctx, DKINFO *dkinfo);
+ bRC wait_for_restore(bpContext *ctx, DKID &dkid);
+ void update_vols_mounts(bpContext* ctx, DKINFO *container, DKVOLS *volume);
+
+ int32_t read_data(bpContext *ctx, POOLMEM *buf, int32_t len);
+ int32_t read_output(bpContext *ctx, POOL_MEM &out);
+ int32_t write_data(bpContext *ctx, POOLMEM *buf, int32_t len);
+ void terminate(bpContext *ctx);
+ inline int get_backend_pid() { if (bpipe){ return bpipe->worker_pid; } return -1;};
+
+ bRC parse_parameters(bpContext *ctx, char *argk, char *argv);
+ bRC parse_restoreobj(bpContext *ctx, restore_object_pkt *rop);
+ bRC prepare_bejob(bpContext *ctx, bool estimate);
+ bRC prepare_restore(bpContext *ctx);
+ bRC prepare_working_volume(bpContext* ctx, int jobid);
+ void clean_working_volume(bpContext* ctx);
+ inline void render_working_volume_filename(POOL_MEM &buf, const char *fname)
+ { Mmsg(buf, "%s/%s", workingvolume, fname); };
+ void setworkingdir(char *workdir);
+
+ inline bool is_open() { return bpipe != NULL; };
+ inline bool is_closed() { return bpipe == NULL; };
+ inline bool is_error() { return f_error || f_fatal; };
+ inline void set_error() { f_error = true; };
+ inline bool is_fatal() { return f_fatal || (f_error && abort_on_error); };
+ inline bool is_eod() { return f_eod; };
+ inline void clear_eod() { f_eod = false; };
+ inline void set_eod() { f_eod = true; };
+ inline void set_abort_on_error() { abort_on_error = true; };
+ inline void clear_abort_on_error() { abort_on_error = false; };
+ inline bool is_abort_on_error() { return abort_on_error; };
+ inline bool is_all_vols_to_backup() { return all_vols_to_backup; };
+ inline bool is_remote_docker() { return param_docker_host != NULL; };
+ inline int32_t timeout() { return param_timeout; };
+
+ DKCOMMCTX(const char *cmd);
+ ~DKCOMMCTX();
+
+ private:
+ BPIPE *bpipe; /* this is our bpipe to communicate with command tools */
+ alist *param_include_container; /* the include parameter list which filter what container name to backup as regex */
+ alist *param_include_image; /* the include parameter list which filter what image name to backup as regex */
+ alist *param_exclude_container; /* the exclude parameter list which filter what container name to exclude from backup */
+ alist *param_exclude_image; /* the exclude parameter list which filter what image name to exclude from backup */
+ alist *param_container; /* the container parameter list which filter what container name or id to backup */
+ alist *param_image; /* the image parameter list which filter what image name or id to backup */
+ alist *param_volume; /* the volume parameter list which filter what volume name to backup */
+ DOCKER_BACKUP_MODE_T param_mode; /* the mode parameter which is used with docker commit, default is pause */
+ bool param_container_create; /* the restore parameter for container creation */
+ bool param_container_run; /* the restore parameter for container creation and execution */
+ bool param_container_imageid; /* the restore parameter for setting imageid during container creation/run */
+ bool param_container_defaultnames; /* the restore parameter for setting default docker names on container creation */
+ POOLMEM *param_docker_host; /* use defined docker host to docker operations */
+ int32_t param_timeout; /* a timeout opening container communication pipe, the default is 30 */
+ regex_t preg; /* this is a regex context for include/exclude */
+ bool abort_on_error; /* abort on error flag */
+ alist *all_containers; /* the list of all containers defined on Docker */
+ alist *all_images; /* the list of all docker images defined on Docker */
+ alist *all_volumes; /* the list of all docker volumes defined on Docker */
+ alist *objs_to_backup; /* the list of all docker objects selected to backup or filtered */
+ bool all_to_backup; /* if true use all_containers list to backup or containers_to_backup list when false */
+ bool all_vols_to_backup; /* if true use all volumes for container to backup */
+ bool f_eod; /* the command tool signaled EOD */
+ bool f_error; /* the plugin signaled an error */
+ bool f_fatal; /* the plugin signaled a fatal error */
+ ConfigFile *ini; /* restore object config parser */
+ POOLMEM *workingvolume; /* */
+ POOLMEM *workingdir; /* runtime working directory from file daemon */
+
+ bool execute_command(bpContext *ctx, POOLMEM *args);
+ bool execute_command(bpContext *ctx, const char *args);
+ bool execute_command(bpContext *ctx, POOL_MEM &args);
+ void parse_parameters(bpContext *ctx, ini_items &item);
+ bool render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, char *value);
+ bool render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *fmt, const char *name, int value);
+ bool render_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value);
+ bool render_param(bpContext *ctx, bool *param, const char *pname, const char *name, bool value);
+ bool render_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, int32_t value);
+ bool add_param_str(bpContext *ctx, alist **list, const char *pname, const char *name, char *value);
+ bool parse_param(bpContext *ctx, POOLMEM **param, const char *pname, const char *name, char *value);
+ bool parse_param(bpContext *ctx, bool *param, const char *pname, const char *name, char *value);
+ bool parse_param(bpContext *ctx, int32_t *param, const char *pname, const char *name, char *value);
+ bool parse_param(bpContext *ctx, DOCKER_BACKUP_MODE_T *param, const char *pname, const char *name, char *value);
+
+ void filter_param_to_backup(bpContext *ctx, alist *params, alist *dklist, bool estimate);
+ void filter_incex_to_backup(bpContext *ctx, alist *params_include, alist *params_exclude, alist *dklist);
+ void add_container_volumes_to_backup(bpContext *ctx);
+ void select_container_vols(bpContext *ctx);
+ alist *get_all_list_from_docker(bpContext* ctx, const char *cmd, int cols, alist **dklist, DKINFO_OBJ_t type);
+ void setup_dkinfo(bpContext* ctx, DKINFO_OBJ_t type, char *paramtab[], DKINFO *dkinfo);
+ void setup_container_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo);
+ void setup_image_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo);
+ void setup_volume_dkinfo(bpContext* ctx, char *paramtab[], DKINFO *dkinfo);
+ bRC run_container_volume_cmd(bpContext* ctx, const char *cmd, POOLMEM *volname, int jobid);
+ bRC run_container_volume_save(bpContext* ctx, POOLMEM *volname, int jobid);
+ bRC run_container_volume_load(bpContext* ctx, POOLMEM *volname, int jobid);
+ bool check_for_docker_errors(bpContext* ctx, char *buf);
+ inline void render_imagesave_name(POOL_MEM &out, DKINFO *dkinfo, int jobid)
+ { Mmsg(out, "%s/%s/%d:backup", dkinfo->get_container_names(),
+ dkinfo->get_container_id()->digest_short(), jobid); };
+ void dump_robjdebug(bpContext *ctx, restore_object_pkt *rop);
+};
+
+#endif /* _DKCOMMCTX_H_ */
\ No newline at end of file
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#include "dkid.h"
+
+/*
+ * DKID class constructor, does default initialization.
+ */
+DKID::DKID()
+{
+ bmemzero(Digest, DKIDDIGESTSIZE + 1);
+ ShortD = DKIDInvalid;
+ shortonly = false;
+};
+
+/*
+ * DKID class constructor, does parm initialization.
+ */
+DKID::DKID(const char* data)
+{
+ init(data);
+};
+
+/*
+ * DKID class constructor, does parm initialization.
+ */
+DKID::DKID(POOL_MEM& data)
+{
+ init(data.c_str());
+};
+
+/*
+ * DKID initialization from string.
+ * as the usable area of short sha256 version used in Docker is 6bytes/48bits
+ * and we are using a 64bit (signed) integer then we have a plenty of space to mark
+ * invalid sha256 conversion with a negative ShortD value.
+ */
+void DKID::init(const char* data)
+{
+ int len;
+ int a;
+ unsigned char c;
+ bool valid = true;
+ char *dig = (char*)data;
+
+ if (dig != NULL){
+ /* check for sha256: prefix*/
+ if (strstr(dig, "sha256:") == dig){
+ dig += 7;
+ }
+ len = strlen(dig);
+ /* check for invalid input data */
+ for (a = 0; a < (len > DKIDDIGESTShortSIZE ? DKIDDIGESTShortSIZE : len); a++){
+ // we are checking for ASCII codes, a subset of UTF-8 for short digest only
+ c = (unsigned char)dig[a];
+ if (c > 'f' || (c > '9' && c < 'A') || (c > 'F' && c < 'a')){
+ valid = false;
+ break;
+ }
+ }
+ if (valid){
+ if (len > DKIDDIGESTShortSIZE){
+ /* initialize from full data */
+ memcpy(Digest, dig, DKIDDIGESTSIZE);
+ Digest[DKIDDIGESTSIZE] = 0;
+ shortonly = false;
+ } else {
+ /* handle short data */
+ memcpy(Digest, dig, len);
+ memcpy(Digest + len, "(...)\0", 6);
+ shortonly = true;
+ }
+ memcpy(DigestShort, dig, DKIDDIGESTShortSIZE);
+ DigestShort[DKIDDIGESTShortSIZE] = 0;
+ ShortD = strtol(DigestShort, NULL, 16);
+ } else {
+ ShortD = DKIDInvalid;
+ shortonly = false;
+ }
+ }
+};
+
+/*
+ * Basic assignment operator overloading for string.
+ *
+ * in:
+ * data - the null terminated string where up to 64 chars will be used
+ * out:
+ * reinitialized DKID class
+ */
+DKID& DKID::operator= (char* data)
+{
+ init(data);
+ return *this;
+};
+
+/*
+ * Basic assignment operator overloading for POOL_MEM class.
+ *
+ * in:
+ * data - a reference to POOL_MEM class instance which is used as a source
+ * of null terminated string for initialization
+ * out:
+ * reinitialized DKID class
+ */
+DKID& DKID::operator =(POOL_MEM &data)
+{
+ init(data.c_str());
+ return *this;
+}
+
+/*
+ * Basic assignment operator overloading for DKID class.
+ *
+ * in:
+ * other - a reference to another DKID class instance which will be used for
+ * assignment
+ * out:
+ * reinitialized DKID class
+ */
+DKID& DKID::operator =(DKID &other)
+{
+ memcpy(Digest, other.Digest, DKIDDIGESTSIZE);
+ memcpy(DigestShort, other.DigestShort, DKIDDIGESTShortSIZE);
+ Digest[DKIDDIGESTSIZE] = 0;
+ DigestShort[DKIDDIGESTShortSIZE] = 0;
+ ShortD = other.ShortD;
+ shortonly = other.shortonly;
+ return *this;
+}
+
+/*
+ * Equal to operator overloading for DKID class.
+ *
+ * in:
+ * other - a reference to another DKID class instance which will be used for
+ * comparison
+ * out:
+ * true - if both ShortD are the same
+ * false - if ShortD variables differ or any DKID is invalid
+ */
+bool DKID::operator ==(DKID &other)
+{
+ if (ShortD >= 0 && other.ShortD >= 0 && ShortD == other.ShortD &&
+ (shortonly || other.shortonly || bstrcmp(Digest, other.Digest))){
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Not-Equal to operator overloading for DKID class.
+ *
+ * in:
+ * other - a reference to another DKID class instance which will be used for
+ * comparison
+ * out:
+ * true - if ShortD variables differ and none of them are invalid
+ * false - if both ShortD are the same or any DKID is invalid
+ */
+bool DKID::operator !=(DKID &other)
+{
+ if (ShortD >= 0 && other.ShortD >= 0 && ShortD != other.ShortD){
+ return true;
+ }
+ if (!shortonly && !other.shortonly && !bstrcmp(Digest, other.Digest)){
+ return true;
+ }
+ return false;
+}
+
+#ifndef TEST_PROGRAM
+#define TEST_PROGRAM_A
+#endif
+
+#ifdef TEST_PROGRAM
+#include "unittests.h"
+
+void DKID::dump()
+{
+ printf ("%p::ShortD: %ld\n", this, ShortD);
+ printf ("%p::Digest: %s\n", this, Digest);
+ printf ("%p::shortonly: %s\n", this, shortonly?"true":"false");
+ printf ("%p::DigestShort: %s\n", this, DigestShort);
+};
+
+const char *dig1 = "66f45d8601bae26a6b2ffeb46922318534d3b3905377b3a224693bd78601cb3b";
+const char *sdig1 = "66f45d8601ba";
+const int64_t vdig1 = 0x66f45d8601ba;
+const char *dig2 = "B546087C43F75A2C1484B4AEE0737499AA69A09067B04237907FCCD4BDE938C7";
+const char *sdig2 = "B546087C43F7";
+const int64_t vdig2 = 0xb546087c43f7;
+const char *sdig3 = "0f601bcb1ef5";
+const int64_t vdig3 = 0x0f601bcb1ef5;
+const char *sdig4 = "00571da76d";
+const int64_t vdig4 = 0x00571da76d;
+const char *dig5 = "sha256:daabf4372f900cb1ad0db17d26abf3acce55224275d1850f02459180e4dacf1d";
+const char *tdig5 = "daabf4372f900cb1ad0db17d26abf3acce55224275d1850f02459180e4dacf1d";
+const char *sdig5 = "daabf4372f90";
+const int64_t vdig5 = 0xdaabf4372f90;
+const char *sinv1 = "Invalid initialization string";
+const char *sinv2 = "brave_edison";
+const char *sinv3 = "0xDEADBEEF";
+const char *sinv4 = "c0a478d317195b…";
+const char *sinv5 = "a478d317195b…";
+const char *sinv6 = "78d317195b…";
+
+int main()
+{
+ Unittests dkid_test("dkid_test");
+ DKID *id;
+ DKID id2(dig2);
+ char *p;
+ int64_t v;
+ POOL_MEM m(PM_FNAME);
+
+ Pmsg0(0, "Initialize tests ...\n");
+
+ id = New(DKID);
+ ok(id && id->id() == DKIDInvalid, "Check default initialization short");
+ ok(id && strlen(id->digest()) == 0, "Check default initialization full");
+ ok(id && strlen(id->digest_short()) == 0, "Check short default initialization full");
+ delete(id);
+
+ id = New(DKID(dig1));
+ ok(id && id->id() == vdig1, "Check param initialization short");
+ ok(id && bstrcmp(id->digest(), dig1), "Check param initialization full");
+ ok(id && bstrcmp(id->digest_short(), sdig1), "Check short param initialization");
+ delete(id);
+
+ id = New(DKID(dig2));
+ ok(id && id->id() == vdig2, "Check param initialization short upper");
+ ok(id && bstrcmp(id->digest(), dig2), "Check param initialization full upper");
+ ok(id && bstrcmp(id->digest_short(), sdig2), "Check short param initialization full upper");
+ delete(id);
+
+ Mmsg(m, "%s", dig1);
+ id = New(DKID(m));
+ ok(id && id->id() == vdig1, "Check pool_mem initialization short");
+ ok(id && bstrcmp(id->digest(), dig1), "Check pool_mem initialization full");
+ ok(id && bstrcmp(id->digest_short(), sdig1), "Check short pool_mem initialization full");
+ delete(id);
+
+ id = New(DKID(sdig3));
+ ok(id && id->id() == vdig3, "Check short digest initialization");
+ Mmsg(m, "%s(...)", sdig3);
+ ok(id && bstrcmp(id->digest(), m.c_str()), "Check short digest initialization full str");
+ ok(id && bstrcmp(id->digest_short(), sdig3), "Check short for short digest initialization");
+ delete(id);
+
+ id = New(DKID(sdig4));
+ ok(id && id->id() == vdig4, "Check shorter digest initialization");
+ Mmsg(m, "%s(...)", sdig4);
+ ok(id && bstrcmp(id->digest(), m.c_str()), "Check shorter digest initialization full str");
+ ok(id && bstrcmp(id->digest_short(), sdig4), "Check short for shorter digest initialization");
+ delete(id);
+
+ id = New(DKID(dig5));
+ ok(id && id->id() == vdig5, "Check param initialization with sha256: prefix");
+ ok(id && bstrcmp(id->digest(), tdig5), "Check param initialization full with sha256: prefix");
+ ok(id && bstrcmp(id->digest_short(), sdig5), "Check short param initialization with sha256: prefix");
+ delete(id);
+
+ Pmsg0(0, "Invalid initialization tests ...\n");
+
+ id = New(DKID(sinv1));
+ ok(id && id->id() < 0, "Checking invalid digest string long");
+ delete(id);
+
+ id = New(DKID(sinv2));
+ ok(id && id->id() < 0, "Checking invalid digest string short");
+ delete(id);
+
+ id = New(DKID(sinv3));
+ ok(id && id->id() < 0, "Checking invalid digest string hex");
+ delete(id);
+
+ id = New(DKID(sinv4));
+ ok(id && id->id() >= 0, "Checking digest string with ellipsis");
+ delete(id);
+
+ id = New(DKID(sinv5));
+ ok(id && id->id() >= 0, "Checking digest string with ellipsis short");
+ delete(id);
+
+ id = New(DKID(sinv6));
+ ok(id && id->id() < 0, "Checking invalid digest string with ellipsis short");
+ delete(id);
+
+ Pmsg0(0, "Operators tests ...\n");
+
+ id = New(DKID(dig1));
+ p = (char*)id;
+ ok(bstrcmp(p, dig1), "Checking operator char* ()");
+ v = *id;
+ ok(v == vdig1, "Checking operator int64_t ()");
+
+ id2 = *id;
+ ok(id2.id() == vdig1, "Checking operator= (DKID&)");
+ ok(id2 == *id, "Checking operator== on the same");
+ nok(id2 != *id, "Checking operator!= on the same");
+
+ *id = (char*)dig2;
+ ok(id->id() == vdig2, "Checking operator= (char*)");
+ nok(id2 == *id, "Checking operator== on different");
+ ok(id2 != *id, "Checking operator!= on different");
+
+ *id = m;
+ ok(id2.id() == vdig1, "Checking operator= (POOL_MEM&)");
+
+ id2 = (char*)dig2;
+ ok(id2.id() == vdig2, "Checking operator= (char*)");
+ delete(id);
+
+ id = New(DKID(sinv1));
+ id2 = *id;
+ nok (id2 == *id, "Checking operator== on invalid digest");
+ nok (id2 != *id, "Checking operator!= on invalid digest");
+ delete(id);
+
+ id = New(DKID(sdig1));
+ id2 = (char*)dig1;
+ ok (id2 == *id, "Checking operator== on full and short digest");
+ nok (id2 != *id, "Checking operator!= on full and short digest");
+ delete(id);
+
+ return report();
+};
+
+#endif /* TEST_PROGRAM */
\ No newline at end of file
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef _DKID_H_
+#define _DKID_H_
+
+#include "bacula.h"
+
+#define DKIDDIGESTSIZE 64 // the size of string array for hex chars, without trailing nul
+#define DKIDDIGESTShortSIZE 12 // the number of hex characters in short digest, without trailing nul
+#define DKIDInvalid -256 // the non-trivial negative value :)
+
+/*
+ * This is a simple storage class to handle Docker Container IDs
+ */
+class DKID: public SMARTALLOC {
+ public:
+ DKID();
+ DKID(const char *data);
+ DKID(POOL_MEM &data);
+ ~DKID() {};
+
+ inline int64_t id() { return ShortD; };
+ inline char *digest() { return Digest; };
+ inline char *digest_short() { return DigestShort; };
+ inline operator int64_t () { return ShortD; };
+ inline operator char* () { return Digest; };
+ DKID& operator= (char *data);
+ DKID& operator= (DKID &other);
+ DKID& operator= (POOL_MEM &data);
+ bool operator== (DKID &other);
+ bool operator!= (DKID &other);
+#ifdef TEST_PROGRAM
+ void dump();
+#endif
+
+ private:
+ char Digest[DKIDDIGESTSIZE + 1];
+ char DigestShort[DKIDDIGESTShortSIZE + 1];
+ int64_t ShortD; // default short digest on Docker is 48bits/6bytes/12hex chars
+ // https://github.com/docker/cli/blob/master/vendor/github.com/docker/docker/pkg/stringid/stringid.go
+ bool shortonly;
+
+ void init(const char* d);
+};
+
+#endif /* _DKID_H_ */
\ No newline at end of file
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ *
+ * TODO: add unittests
+ */
+
+#include "dkinfo.h"
+
+/*
+ * libbac uses its own sscanf implementation which is not compatible with
+ * libc implementation, unfortunately.
+ * use bsscanf for Bacula sscanf flavor
+ */
+#ifdef sscanf
+#undef sscanf
+#endif
+
+/*
+ * The base constructor
+ */
+DKVOLS::DKVOLS(DKINFO *dk)
+{
+ vol = dk;
+ destination = get_pool_memory(PM_FNAME);
+};
+
+/*
+ * Default class destructor
+ */
+DKVOLS::~DKVOLS()
+{
+ // WARINIG: The this->destination is freed outside the class
+ // TODO: It should be verified why and where the destination variable is freed!
+ // free_and_null_pool_memory(destination);
+};
+
+/*
+ * DKINFO class constructor, does initialization for unknown.
+ */
+DKINFO::DKINFO(DKINFO_OBJ_t t)
+{
+ init(t);
+};
+
+/*
+ * DKINFO base copy constructor by simple copy variables by value
+ */
+DKINFO::DKINFO(const DKINFO& dkinfo)
+{
+ init(dkinfo.Type);
+ switch (Type){
+ case DOCKER_CONTAINER:
+ set_container_id(*dkinfo.data.container.containerid);
+ set_container_names(dkinfo.data.container.names);
+ set_container_size(dkinfo.data.container.size);
+ set_container_mounts(dkinfo.data.container.mounts);
+ set_container_status(dkinfo.data.container.status);
+ set_container_imagesave(*dkinfo.data.container.imagesave);
+ set_container_imagesave_tag(dkinfo.data.container.imagesave_tag);
+ break;
+ case DOCKER_IMAGE:
+ set_image_id(*dkinfo.data.image.imageid);
+ set_image_repository(dkinfo.data.image.repository);
+ set_image_tag(dkinfo.data.image.tag);
+ set_image_size(dkinfo.data.image.size);
+ set_image_created(dkinfo.data.image.created);
+ break;
+ case DOCKER_VOLUME:
+ set_volume_name(dkinfo.data.volume.name);
+ set_volume_created(dkinfo.data.volume.created);
+ set_volume_size(dkinfo.data.volume.size);
+ set_volume_linknr(dkinfo.data.volume.linknr);
+ break;
+ }
+};
+
+/*
+ * DKINFO destructor, releases all memory allocated.
+ */
+DKINFO::~DKINFO()
+{
+ DKVOLS *v;
+
+ switch(Type){
+ case DOCKER_CONTAINER:
+ if (data.container.containerid){
+ delete data.container.containerid;
+ }
+ if (data.container.imagesave){
+ delete data.container.imagesave;
+ }
+ if (data.container.vols){
+ foreach_alist(v, data.container.vols){
+ delete v;
+ }
+ delete data.container.vols;
+ }
+ free_and_null_pool_memory(data.container.names);
+ free_and_null_pool_memory(data.container.mounts);
+ free_and_null_pool_memory(data.container.imagesave_tag);
+ break;
+ case DOCKER_IMAGE:
+ if (data.image.imageid){
+ delete data.image.imageid;
+ }
+ free_and_null_pool_memory(data.image.repository);
+ free_and_null_pool_memory(data.image.tag);
+ free_and_null_pool_memory(data.image.repository_tag);
+ break;
+ case DOCKER_VOLUME:
+ free_and_null_pool_memory(data.volume.name);
+ break;
+ default:
+ break;
+ }
+};
+
+/*
+ * initialization of the DKINFO class.
+ */
+void DKINFO::init(DKINFO_OBJ_t t)
+{
+ Type = t;
+ switch(Type){
+ case DOCKER_CONTAINER:
+ data.container.containerid = New(DKID);
+ data.container.names = get_pool_memory(PM_NAME);
+ data.container.size = 0;
+ data.container.mounts = get_pool_memory(PM_MESSAGE);
+ data.container.status = DKUNKNOWN;
+ data.container.imagesave = New(DKID);
+ data.container.imagesave_tag = get_pool_memory(PM_NAME);
+ data.container.vols = New(alist(10, not_owned_by_alist));
+ break;
+ case DOCKER_IMAGE:
+ data.image.imageid = New(DKID);
+ data.image.repository = get_pool_memory(PM_NAME);
+ data.image.size = 0;
+ data.image.tag = get_pool_memory(PM_NAME);
+ data.image.repository_tag = get_pool_memory(PM_NAME);
+ data.image.created = 0;
+ break;
+ case DOCKER_VOLUME:
+ data.volume.name = get_pool_memory(PM_NAME);
+ data.volume.created = 0;
+ data.volume.linknr = 1;
+ break;
+ default:
+ bmemzero(&data, sizeof(data));
+ }
+};
+
+/*
+ * Sets the container size based on the docker size string:
+ * "123B (virtual 319MB)"
+ *
+ * in:
+ * s - the string which represents the Docker container size
+ * out:
+ * none
+ */
+void DKINFO::scan_container_size(POOLMEM* str)
+{
+ int status;
+ float srw;
+ char suff[2];
+ float sv;
+ uint64_t srwsize, svsize;
+
+ if (Type == DOCKER_CONTAINER && str){
+ status = sscanf(str, "%f%c%*c%*s%f%c", &srw, &suff[0], &sv, &suff[1]);
+ if (status == 4){
+ /* scan successful */
+ srwsize = pluglib_size_suffix(srw, suff[0]);
+ svsize = pluglib_size_suffix(sv, suff[1]);
+ data.container.size = srwsize + svsize;
+ }
+ }
+};
+
+/*
+ * Sets the image size based on the docker size string:
+ * "319MB"
+ *
+ * in:
+ * s - the string which represents the Docker image size
+ * out:
+ * none
+ */
+void DKINFO::scan_image_size(POOLMEM* str)
+{
+ int status;
+ float fsize;
+ char suff;
+
+ if (Type == DOCKER_IMAGE && str){
+ status = sscanf(str, "%f%c", &fsize, &suff);
+ if (status == 2){
+ /* scan successful */
+ data.image.size = pluglib_size_suffix(fsize, suff);
+ }
+ }
+};
+
+/*
+ * Sets the volume size based on the docker volume size string:
+ * "319MB"
+ *
+ * in:
+ * s - the string which represents the Docker volume size
+ * out:
+ * none
+ */
+void DKINFO::scan_volume_size(POOLMEM* str)
+{
+ int status;
+ float fsize;
+ char suff;
+
+ if (Type == DOCKER_VOLUME && str){
+ if (bstrcmp(str, "N/A")){
+ data.volume.size = 0;
+ } else {
+ status = sscanf(str, "%f%c", &fsize, &suff);
+ if (status == 2){
+ /* scan successful */
+ data.volume.size = pluglib_size_suffix(fsize, suff);
+ }
+ }
+ }
+};
+
+/*
+ * Setup an image repository/tag variables from a single image repository:tag string.
+ * The class uses three variables to store repository:tag data.
+ * - data.image.repository_tag is used for full info string
+ * - data.image.repository is used to store repository part
+ * - data.image.tag is used to store a tag part
+ * TODO: optimize repository_tag storage
+ *
+ * in:
+ * rt - a repository:tag string
+ * out:
+ * none
+ */
+void DKINFO::scan_image_repository_tag(POOL_MEM& rt)
+{
+ char *colon;
+
+ if (Type == DOCKER_IMAGE){
+ pm_strcpy(data.image.repository_tag, rt);
+ colon = strchr(data.image.repository_tag, ':');
+ if (colon){
+ /* have a colon in string, so split it */
+ pm_strcpy(data.image.tag, colon);
+ *colon = 0; // temporary usage
+ pm_strcpy(data.image.repository, data.image.repository_tag);
+ *colon = ':'; // restore
+ } else {
+ pm_strcpy(data.image.repository, rt);
+ pm_strcpy(data.image.tag, NULL);
+ }
+ }
+};
+
+/*
+ * Sets the container status based on the status string.
+ *
+ * in:
+ * s - the string which represents the status
+ * out:
+ * none
+ */
+void DKINFO::set_container_status(POOL_MEM &s)
+{
+ if (Type == DOCKER_CONTAINER){
+ /* scan a container state and save it */
+ if (bstrcmp(s.c_str(), "exited")){
+ /* container exited */
+ data.container.status = DKEXITED;
+ } else
+ if (bstrcmp(s.c_str(), "running")){
+ /* vm is running */
+ data.container.status = DKRUNNING;
+ } else
+ if (bstrcmp(s.c_str(), "paused")){
+ /* container paused */
+ data.container.status = DKPAUSED;
+ } else {
+ data.container.status = DKUNKNOWN;
+ }
+ }
+}
+
+/* fake dkid for volumes */
+static DKID volfakeid;
+
+/*
+ * Return object ID based on object type.
+ */
+DKID *DKINFO::id()
+{
+ switch(Type){
+ case DOCKER_CONTAINER:
+ return data.container.containerid;
+ case DOCKER_IMAGE:
+ return data.image.imageid;
+ case DOCKER_VOLUME:
+ return &volfakeid;
+ }
+ return NULL;
+};
+
+/*
+ * Return object name based on object type.
+ */
+POOLMEM *DKINFO::name()
+{
+ switch(Type){
+ case DOCKER_CONTAINER:
+ return data.container.names;
+ case DOCKER_IMAGE:
+ return data.image.repository_tag;
+ case DOCKER_VOLUME:
+ return data.volume.name;
+ }
+ return NULL;
+};
+
+/*
+ * Return object type string constant.
+ */
+const char *DKINFO::type_str()
+{
+ switch(Type){
+ case DOCKER_CONTAINER:
+ return "Docker Container";
+ case DOCKER_IMAGE:
+ return "Docker Image";
+ case DOCKER_VOLUME:
+ return "Docker Volume";
+ }
+ return "Unknown";
+};
+
+/*
+ * Return object size info.
+ */
+uint64_t DKINFO::size()
+{
+ switch(Type){
+ case DOCKER_CONTAINER:
+ return data.container.size;
+ case DOCKER_IMAGE:
+ return data.image.size;
+ default:
+ break;
+ }
+ return 0;
+};
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef _DKINFO_H_
+#define _DKINFO_H_
+
+#include "dkid.h"
+#include "pluglib.h"
+
+/* The Docker container Power states */
+typedef enum {
+ DKUNKNOWN = 0,
+ DKCREATED,
+ DKEXITED,
+ DKRUNNING,
+ DKPAUSED,
+} DOCKER_POWER_T;
+
+/* Docker object types */
+typedef enum {
+ DOCKER_CONTAINER,
+ DOCKER_IMAGE,
+ DOCKER_VOLUME,
+} DKINFO_OBJ_t;
+
+/*
+ * Docker objects data variables.
+ */
+typedef union {
+ struct {
+ DKID *containerid;
+ POOLMEM *names;
+ uint64_t size;
+ DOCKER_POWER_T status;
+ DKID *imagesave;
+ POOLMEM *imagesave_tag;
+ POOLMEM *mounts;
+ alist *vols;
+ } container;
+ struct {
+ DKID *imageid;
+ POOLMEM *repository;
+ uint64_t size;
+ POOLMEM *tag;
+ POOLMEM *repository_tag;
+ utime_t created;
+ } image;
+ struct {
+ POOLMEM *name;
+ utime_t created;
+ uint64_t size;
+ int linknr;
+ } volume;
+} DOCKER_OBJ;
+
+/* forward reference */
+class DKINFO;
+
+/*
+ * This is a simple special struct for handling docker mounts destination mappings
+ */
+class DKVOLS : public SMARTALLOC {
+public:
+ DKVOLS(DKINFO *dk);
+ ~DKVOLS();
+ DKINFO *vol;
+ POOLMEM *destination;
+};
+
+/*
+ * The class which handles DKINFO operations
+ */
+class DKINFO : public SMARTALLOC {
+ public:
+ DKINFO(DKINFO_OBJ_t t);
+ DKINFO(const DKINFO &dkinfo);
+ ~DKINFO();
+
+ /* set methods dedicated to container */
+ inline void set_container_id(DKID &id) { if (Type == DOCKER_CONTAINER){ *data.container.containerid = id; }};
+ inline void set_container_id(POOL_MEM &id) { if (Type == DOCKER_CONTAINER){ *data.container.containerid = id; }};
+ inline void set_container_id(POOLMEM *id) { if (Type == DOCKER_CONTAINER){ *data.container.containerid = (char*)id; }};
+ inline void set_container_names(POOLMEM *n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.names, n); }};
+ inline void set_container_names(POOL_MEM &n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.names, n); }};
+ inline void set_container_size(uint64_t s) { if (Type == DOCKER_CONTAINER){ data.container.size = s; }};
+ inline void set_container_imagesave(DKID &id) { if (Type == DOCKER_CONTAINER){ *data.container.imagesave = id; }};
+ inline void set_container_imagesave(POOL_MEM &id) { if (Type == DOCKER_CONTAINER){ *data.container.imagesave = id; }};
+ inline void set_container_imagesave(POOLMEM *id) { if (Type == DOCKER_CONTAINER){ *data.container.imagesave = (char*)id; }};
+ inline void set_container_imagesave_tag(POOL_MEM &n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.imagesave_tag, n); }};
+ inline void set_container_imagesave_tag(POOLMEM *n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.imagesave_tag, n); }};
+ inline void set_container_status(DOCKER_POWER_T s) { if (Type == DOCKER_CONTAINER){ data.container.status = s; }};
+ void set_container_status(POOL_MEM &s);
+ inline void set_container_mounts(POOLMEM *n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.mounts, n); }};
+ inline void set_container_mounts(POOL_MEM &n) { if (Type == DOCKER_CONTAINER){ pm_strcpy(data.container.mounts, n); }};
+
+ /* special vols handling */
+ inline void container_append_vols(DKVOLS *dk) { if (Type == DOCKER_CONTAINER){ data.container.vols->append(dk); }};
+ inline bool container_has_vols() { return Type == DOCKER_CONTAINER ? !data.container.vols->empty() : false; };
+ inline DKVOLS *container_first_vols() { return Type == DOCKER_CONTAINER ? (DKVOLS*)data.container.vols->first() : NULL; };
+ inline DKVOLS *container_next_vols() { return Type == DOCKER_CONTAINER ? (DKVOLS*)data.container.vols->next() : NULL; };
+
+ /* set methods dedicated to image */
+ inline void set_image_id(DKID &id) { if (Type == DOCKER_IMAGE){ *data.image.imageid = id; }};
+ inline void set_image_id(POOLMEM *id) { if (Type == DOCKER_IMAGE){ *data.image.imageid = (char*)id; }};
+ inline void set_image_id(POOL_MEM &id) { if (Type == DOCKER_IMAGE){ *data.image.imageid = id; }};
+ inline void set_image_repository(POOLMEM *n) { if (Type == DOCKER_IMAGE){ pm_strcpy(data.image.repository, n); render_image_repository_tag(); }};
+ inline void set_image_repository(POOL_MEM &n) { if (Type == DOCKER_IMAGE){ pm_strcpy(data.image.repository, n); render_image_repository_tag(); }};
+ inline void set_image_tag(POOLMEM *n) { if (Type == DOCKER_IMAGE){ pm_strcpy(data.image.tag, n); render_image_repository_tag(); }};
+ inline void set_image_tag(POOL_MEM &n) { if (Type == DOCKER_IMAGE){ pm_strcpy(data.image.tag, n); render_image_repository_tag(); }};
+ inline void set_image_size(uint64_t s) { if (Type == DOCKER_IMAGE){ data.image.size = s; }};
+ inline void set_image_created(utime_t t) { if (Type == DOCKER_IMAGE){ data.image.created = t; }};
+
+ /* set methods dedicated to volume */
+ inline void set_volume_name(POOLMEM *n) { if (Type == DOCKER_VOLUME){ pm_strcpy(data.volume.name, n); }};
+ inline void set_volume_name(POOL_MEM &n) { if (Type == DOCKER_VOLUME){ pm_strcpy(data.volume.name, n); }};
+ inline void set_volume_created(utime_t t) { if (Type == DOCKER_VOLUME){ data.volume.created = t; }};
+ inline void set_volume_size(uint64_t s) { if (Type == DOCKER_VOLUME){ data.volume.size = s; }};
+ inline void set_volume_linknr(int l) { if (Type == DOCKER_VOLUME){ data.volume.linknr = l; }};
+ inline int inc_volume_linknr() { return Type == DOCKER_VOLUME ? ++data.volume.linknr : 0; };
+
+ /* scanning methods */
+ void scan_container_size(POOLMEM *str);
+ void scan_image_size(POOLMEM *str);
+ void scan_image_repository_tag(POOL_MEM &rt);
+ void scan_volume_size(POOLMEM *str);
+
+ /* get methods dedicated to container */
+ inline DKID *get_container_id() { return Type == DOCKER_CONTAINER ? data.container.containerid : NULL; };
+ inline DKID *get_container_imagesave() { return Type == DOCKER_CONTAINER ? data.container.imagesave : NULL; };
+ inline POOLMEM *get_container_names() { return Type == DOCKER_CONTAINER ? data.container.names : NULL; };
+ inline DOCKER_POWER_T get_container_status() { return Type == DOCKER_CONTAINER ? data.container.status : DKUNKNOWN; };
+ inline uint64_t get_container_size() { return Type == DOCKER_CONTAINER ? data.container.size : 0; };
+ inline POOLMEM *get_container_imagesave_tag() { return Type == DOCKER_CONTAINER ? data.container.imagesave_tag : NULL; };
+ inline POOLMEM *get_container_mounts() { return Type == DOCKER_CONTAINER ? data.container.mounts : NULL; };
+
+ /* get methods dedicated to image */
+ inline DKID *get_image_id() { return Type == DOCKER_IMAGE ? data.image.imageid : NULL; };
+ inline POOLMEM *get_image_repository() { return Type == DOCKER_IMAGE ? data.image.repository : NULL; };
+ inline POOLMEM *get_image_tag() { return Type == DOCKER_IMAGE ? data.image.tag : NULL; };
+ inline POOLMEM *get_image_repository_tag() { return Type == DOCKER_IMAGE ? data.image.repository_tag : NULL; };
+ inline uint64_t get_image_size() { return Type == DOCKER_IMAGE ? data.image.size : 0; };
+ inline utime_t get_image_created() { return Type == DOCKER_IMAGE ? data.image.created : 0; };
+
+ /* get methods dedicated to volume */
+ inline POOLMEM *get_volume_name() { return Type == DOCKER_VOLUME ? data.volume.name : NULL; };
+ inline utime_t get_volume_created() { return Type == DOCKER_VOLUME ? data.volume.created : 0; };
+ inline uint64_t get_volume_size() { return Type == DOCKER_VOLUME ? data.volume.size : 0; };
+ inline int get_volume_linknr() { return Type == DOCKER_VOLUME ? data.volume.linknr : 0; };
+
+ /* generic get methods which check dkinfo type */
+ DKID *id();
+ POOLMEM *name();
+ uint64_t size();
+ inline DKINFO_OBJ_t type() { return Type; };
+ const char *type_str();
+
+ private:
+ DKINFO_OBJ_t Type;
+ DOCKER_OBJ data;
+
+ void init(DKINFO_OBJ_t t);
+ inline void render_image_repository_tag()
+ {
+ pm_strcpy(data.image.repository_tag, data.image.repository);
+ pm_strcat(data.image.repository_tag, ":");
+ pm_strcat(data.image.repository_tag, data.image.tag);
+ };
+};
+
+#endif /* _DKINFO_H_ */
\ No newline at end of file
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#include "docker-fd.h"
+#include <sys/stat.h>
+#include <signal.h>
+#include <time.h>
+#include <libgen.h>
+
+/*
+ * libbac uses its own sscanf implementation which is not compatible with
+ * libc implementation, unfortunately.
+ * use bsscanf for Bacula sscanf flavor
+ */
+#ifdef sscanf
+#undef sscanf
+#endif
+
+extern DLL_IMP_EXP int64_t debug_level;
+
+/* Forward referenced functions */
+static bRC newPlugin(bpContext *ctx);
+static bRC freePlugin(bpContext *ctx);
+static bRC getPluginValue(bpContext *ctx, pVariable var, void *value);
+static bRC setPluginValue(bpContext *ctx, pVariable var, void *value);
+static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value);
+static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp);
+static bRC endBackupFile(bpContext *ctx);
+static bRC pluginIO(bpContext *ctx, struct io_pkt *io);
+static bRC startRestoreFile(bpContext *ctx, const char *cmd);
+static bRC endRestoreFile(bpContext *ctx);
+static bRC createFile(bpContext *ctx, struct restore_pkt *rp);
+static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp);
+static bRC checkFile(bpContext *ctx, char *fname);
+static bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl);
+
+/* Pointers to Bacula functions */
+bFuncs *bfuncs = NULL;
+bInfo *binfo = NULL;
+
+static pFuncs pluginFuncs = {
+ sizeof(pluginFuncs),
+ FD_PLUGIN_INTERFACE_VERSION,
+
+ /* Entry points into plugin */
+ newPlugin,
+ freePlugin,
+ getPluginValue,
+ setPluginValue,
+ handlePluginEvent,
+ startBackupFile,
+ endBackupFile,
+ startRestoreFile,
+ endRestoreFile,
+ pluginIO,
+ createFile,
+ setFileAttributes,
+ checkFile,
+ handleXACLdata
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Plugin Information structure */
+static pInfo pluginInfo = {
+ sizeof(pluginInfo),
+ FD_PLUGIN_INTERFACE_VERSION,
+ FD_PLUGIN_MAGIC,
+ DOCKER_LICENSE,
+ DOCKER_AUTHOR,
+ DOCKER_DATE,
+ DOCKER_VERSION,
+ DOCKER_DESCRIPTION,
+};
+
+/*
+ * Plugin called here when it is first loaded.
+ */
+bRC DLL_IMP_EXP loadPlugin(bInfo *lbinfo, bFuncs *lbfuncs, pInfo ** pinfo, pFuncs ** pfuncs)
+{
+ bfuncs = lbfuncs; /* set Bacula function pointers */
+ binfo = lbinfo;
+
+ Dmsg2(DINFO, PLUGINNAME " Plugin version %s %s (c) 2019 by Inteos\n",
+ DOCKER_VERSION, DOCKER_DATE);
+
+ *pinfo = &pluginInfo; /* return pointer to our info */
+ *pfuncs = &pluginFuncs; /* return pointer to our functions */
+
+ if (access(DOCKER_CMD, X_OK) < 0){
+ berrno be;
+ bfuncs->DebugMessage(NULL, __FILE__, __LINE__, DERROR,
+ PLUGINPREFIX " Unable to use command tool: %s Err=%s\n", DOCKER_CMD,
+ be.bstrerror());
+ return bRC_Error;
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Plugin called here when it is unloaded, normally when Bacula is going to exit.
+ */
+bRC DLL_IMP_EXP unloadPlugin()
+{
+ return bRC_OK;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+/*
+ * Main DOCKER Plugin class constructor.
+ * Initializes all variables required.
+ */
+DOCKER::DOCKER(bpContext *bpctx) :
+ mode(DOCKER_NONE),
+ JobId(0),
+ JobName(NULL),
+ since(0),
+ where(NULL),
+ regexwhere(NULL),
+ replace(0),
+ robjsent(false),
+ estimate(false),
+ accurate_warning(false),
+ local_restore(false),
+ backup_finish(false),
+ unsupportedfeature(false),
+ param_notrunc(false),
+ errortar(false),
+ volumewarning(false),
+ dockerworkclear(0),
+ dkcommctx(NULL),
+ commandlist(NULL),
+ fname(NULL),
+ lname(NULL),
+ dkfd(0),
+ robjbuf(NULL),
+ currdkinfo(NULL),
+ restoredkinfo(NULL),
+ listing_mode(DOCKER_LISTING_NONE),
+ listing_objnr(0),
+ parser(NULL),
+ workingdir(NULL)
+{
+ /* TODO: we have a ctx variable stored internally, decide if we use it
+ * for every method or rip it off as not required in our code */
+ ctx = bpctx;
+}
+
+/*
+ * Main DOCKER Plugin class destructor, handles variable release on delete.
+ *
+ * in: none
+ * out: freed internal variables and class allocated during job execution
+ */
+DOCKER::~DOCKER()
+{
+ /* free standard variables */
+ free_and_null_pool_memory(fname);
+ free_and_null_pool_memory(lname);
+ free_and_null_pool_memory(robjbuf);
+ free_and_null_pool_memory(workingdir);
+ /* free backend contexts */
+ if (commandlist){
+ /* free all backend contexts */
+ foreach_alist(dkcommctx, commandlist){
+ delete dkcommctx;
+ }
+ delete commandlist;
+ }
+ if (parser){
+ delete parser;
+ }
+ if (restoredkinfo){
+ delete restoredkinfo;
+ }
+}
+
+/*
+ * sets runtime workingdir variable used in working volume creation.
+ *
+ * in:
+ * workdir - the file daemon working directory parameter
+ * out:
+ * none
+ */
+void DOCKER::setworkingdir(char* workdir)
+{
+ if (workingdir == NULL){
+ /* not allocated yet */
+ workingdir = get_pool_memory(PM_FNAME);
+ }
+ pm_strcpy(&workingdir, workdir);
+ DMSG1(NULL, DVDEBUG, "workingdir: %s\n", workingdir);
+};
+
+/*
+ * Parse a Restore Object saved during backup and modified by user during restore.
+ * Every RO received will allocate a dedicated command context which is used
+ * by bEventRestoreCommand to handle default parameters for restore.
+ *
+ * in:
+ * bpContext - Bacula Plugin context structure
+ * rop - a restore object structure to parse
+ * out:
+ * bRC_OK - on success
+ * bRC_Error - on error
+ */
+bRC DOCKER::parse_plugin_restoreobj(bpContext *ctx, restore_object_pkt *rop)
+{
+ if (!rop){
+ return bRC_OK; /* end of rop list */
+ }
+
+ if (bstrcmp(rop->object_name, INI_RESTORE_OBJECT_NAME)){
+ /* we have a single RO for every command */
+ switch_commandctx(ctx, rop->plugin_name);
+ /* all restore parameters are DKCOMMCTX specific, so forward parsing to it */
+ return dkcommctx->parse_restoreobj(ctx, rop);
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Parsing a plugin command.
+ * Plugin command e.g. plugin = <plugin-name>:[parameters [parameters]...]
+ *
+ * in:
+ * bpContext - Bacula Plugin context structure
+ * command - plugin command string to parse
+ * out:
+ * bRC_OK - on success
+ * bRC_Error - on error
+ */
+bRC DOCKER::parse_plugin_command(bpContext *ctx, const char *command)
+{
+ int i, a;
+ bRC status;
+
+ DMSG(ctx, DINFO, "Parse command: %s\n", command);
+ /* allocate a new parser if required */
+ if (parser == NULL){
+ parser = new cmd_parser();
+ }
+
+ /* and parse command */
+ if (parser->parse_cmd(command) != bRC_OK) {
+ DMSG0(ctx, DERROR, "Unable to parse Plugin command line.\n");
+ JMSG0(ctx, M_FATAL, "Unable to parse Plugin command line.\n");
+ return bRC_Error;
+ }
+
+ /* switch dkcommctx to the required context or allocate a new context */
+ switch_commandctx(ctx, command);
+
+ /* the first (zero) parameter is a plugin name, we should skip it */
+ for (i = 1; i < parser->argc; i++) {
+ /* loop over all parsed parameters */
+ if (estimate && bstrcmp(parser->argk[i], "listing")){
+ /* we have a listing parameter which for estimate means .ls command */
+ listing_objnr = 1;
+ listing_mode = DOCKER_LISTING_TOP;
+ a = 0;
+ while (docker_objects[a].name){
+ if (bstrcmp(parser->argv[i], docker_objects[a].name) ||
+ (*parser->argv[i] == '/' && bstrcmp(parser->argv[i]+1, docker_objects[a].name))){
+ listing_mode = docker_objects[a].mode;
+ break;
+ }
+ a++;
+ }
+ continue;
+ }
+ if (estimate && bstrcmp(parser->argk[i], "notrunc")){
+ /* we are doing estimate and user requested notrunc in display */
+ param_notrunc = true;
+ continue;
+ }
+ /* handle it with dkcommctx */
+ status = dkcommctx->parse_parameters(ctx, parser->argk[i], parser->argv[i]);
+ switch (status){
+ case bRC_OK:
+ /* the parameter was handled by dkcommctx, proceed to the next */
+ continue;
+ case bRC_Error:
+ /* parsing returned error, raise it up */
+ return bRC_Error;
+ default:
+ break;
+ }
+ DMSG(ctx, DERROR, "Unknown parameter: %s\n", parser->argk[i]);
+ JMSG(ctx, M_ERROR, "Unknown parameter: %s\n", parser->argk[i]);
+ }
+ return bRC_OK;
+}
+
+/*
+ * Allocate and initialize new command context list at commandlist.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command for a job as a first backend context
+ * out:
+ * New backend contexts list at commandlist allocated and initialized.
+ * The only error we can get here is out of memory error, handled internally
+ * by Bacula itself.
+ */
+void DOCKER::new_commandctx(bpContext *ctx, const char *command)
+{
+ /* our new command context */
+ dkcommctx = New(DKCOMMCTX(command));
+ /* add command context to our list */
+ commandlist->append(dkcommctx);
+ DMSG(ctx, DINFO, "Command context allocated for: %s\n", command);
+ /* setup runtime workingdir */
+ dkcommctx->setworkingdir(workingdir);
+}
+
+/*
+ * The function manages the command contexts list.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command for a job as a first backend context
+ * out:
+ * this.dkcommctx - the DKCOMMCTX allocated/switched for command
+ */
+void DOCKER::switch_commandctx(bpContext *ctx, const char *command)
+{
+ DKCOMMCTX *dkctx;
+
+ if (commandlist == NULL){
+ /* new command list required, we assumed 8 command contexts at start, should be sufficient */
+ commandlist = New(alist(8, not_owned_by_alist));
+ /* our first command context */
+ new_commandctx(ctx, command);
+ } else {
+ /* command list available, so search for already allocated context */
+ foreach_alist(dkctx, commandlist){
+ if (bstrcmp(dkctx->command, command)){
+ /* found, set dkcommctx to it and return */
+ dkcommctx = dkctx;
+ DMSG(ctx, DINFO, "Command context switched to: %s\n", command);
+ return;
+ }
+ }
+ /* well, command context not found, so allocate a new one */
+ new_commandctx(ctx, command);
+ }
+}
+
+/*
+ * Prepares a single Plugin command for backup or estimate job.
+ * Make a preparation by parse a plugin command and check the
+ * backup/estimate/listing mode and make a proper dkcommctx initialization.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command to prepare
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::prepare_bejob(bpContext* ctx, char *command)
+{
+ /* check if it is our Plugin command */
+ if (isourplugincommand(PLUGINPREFIX, command)){
+ /* first, parse backup command */
+ if (parse_plugin_command(ctx, command) != bRC_OK){
+ return bRC_Error;
+ }
+
+ switch (listing_mode){
+ case DOCKER_LISTING_NONE:
+ /* other will prepare backup job in dkcommctx context */
+ return dkcommctx->prepare_bejob(ctx, estimate);
+ case DOCKER_LISTING_CONTAINER:
+ /* listing require all */
+ if (!dkcommctx->get_all_containers(ctx)){
+ return bRC_Error;
+ }
+ dkcommctx->set_all_containers_to_backup(ctx);
+ break;
+ case DOCKER_LISTING_IMAGE:
+ if (!dkcommctx->get_all_images(ctx)){
+ return bRC_Error;
+ }
+ dkcommctx->set_all_images_to_backup(ctx);
+ break;
+ case DOCKER_LISTING_VOLUME:
+ if (!dkcommctx->get_all_volumes(ctx)){
+ return bRC_Error;
+ }
+ dkcommctx->set_all_volumes_to_backup(ctx);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Prepares a single Plugin command for backup.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command to prepare
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::prepare_backup(bpContext* ctx, char *command)
+{
+ estimate = false;
+ if (prepare_bejob(ctx, command) != bRC_OK){
+ return bRC_Error;
+ }
+ return bRC_OK;
+}
+
+/*
+ * Prepares a single Plugin command for estimate/listing.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command to prepare
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::prepare_estimate(bpContext* ctx, char *command)
+{
+ estimate = true;
+ if (prepare_bejob(ctx, command) != bRC_OK){
+ return bRC_Error;
+ }
+ dkcommctx->clear_abort_on_error();
+ return bRC_OK;
+}
+
+/*
+ * Prepares a single Plugin command for restore.
+ * Make a preparation by parse a plugin command and make a proper dkcommctx
+ * initialization.
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * command - a Plugin command to prepare
+ * out:
+ * bRC_OK - when preparation was successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::prepare_restore(bpContext* ctx, char *command)
+{
+ /* check if it is our Plugin command */
+ if (isourplugincommand(PLUGINPREFIX, command)){
+ /* first, parse backup command */
+ if (parse_plugin_command(ctx, command) != bRC_OK){
+ return bRC_Error;
+ }
+
+ /* prepare restore */
+ return dkcommctx->prepare_restore(ctx);
+ }
+ return bRC_OK;
+}
+
+/*
+ * This is the main method for handling events generated by Bacula.
+ * The behavior of the method depends on event type generated, but there are
+ * some events which does nothing, just return with bRC_OK. Every event is
+ * tracked in debug trace file to verify the event flow during development.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * event - a Bacula event structure
+ * value - optional event value
+ * out:
+ * bRC_OK - in most cases signal success/no error
+ * bRC_Error - in most cases signal error
+ * <other> - depend on Bacula Plugin API if applied
+ */
+bRC DOCKER::handlePluginEvent(bpContext *ctx, bEvent *event, void *value)
+{
+ switch (event->eventType) {
+ case bEventJobStart:
+ DMSG_EVENT_STR(event, value);
+ getBaculaVar(bVarJobId, (void *)&JobId);
+ getBaculaVar(bVarJobName, (void *)&JobName);
+ break;
+
+ case bEventJobEnd:
+ DMSG_EVENT_STR(event, value);
+ if (dockerworkclear == 1){
+ dkcommctx->clean_working_volume(ctx);
+ dockerworkclear = 0;
+ }
+ break;
+
+ case bEventLevel:
+ char lvl;
+ lvl = (char)((intptr_t) value & 0xff);
+ DMSG_EVENT_CHAR(event, lvl);
+ /* the plugin support a FULL backup only as Docker does not support other levels */
+ mode = DOCKER_BACKUP_FULL;
+ if (lvl != 'F'){
+ unsupportedfeature = true;
+ }
+ break;
+
+ case bEventSince:
+ since = (time_t) value;
+ DMSG_EVENT_LONG(event, since);
+ break;
+
+ case bEventStartBackupJob:
+ DMSG_EVENT_STR(event, value);
+ break;
+
+ case bEventEndBackupJob:
+ DMSG_EVENT_STR(event, value);
+ break;
+
+ case bEventStartRestoreJob:
+ DMSG_EVENT_STR(event, value);
+ getBaculaVar(bVarWhere, &where);
+ DMSG(ctx, DINFO, "Where=%s\n", NPRT(where));
+ getBaculaVar(bVarReplace, &replace);
+ DMSG(ctx, DINFO, "Replace=%c\n", replace);
+ mode = DOCKER_RESTORE;
+ break;
+
+ case bEventEndRestoreJob:
+ DMSG_EVENT_STR(event, value);
+ break;
+
+ /* Plugin command e.g. plugin = <plugin-name>:parameters */
+ case bEventEstimateCommand:
+ DMSG_EVENT_STR(event, value);
+ estimate = true;
+ free_and_null_pool_memory(fname);
+ return prepare_estimate(ctx, (char*) value);
+
+ /* Plugin command e.g. plugin = <plugin-name>:parameters */
+ case bEventBackupCommand:
+ DMSG_EVENT_STR(event, value);
+ robjsent = false;
+ free_and_null_pool_memory(fname);
+ return prepare_backup(ctx, (char*)value);
+
+ /* Plugin command e.g. plugin = <plugin-name>:parameters */
+ case bEventRestoreCommand:
+ DMSG_EVENT_STR(event, value);
+ getBaculaVar(bVarRegexWhere, ®exwhere);
+ DMSG(ctx, DINFO, "RegexWhere=%s\n", NPRT(regexwhere));
+ if (regexwhere){
+ /* the plugin cannot support regexwhere, so raise the error */
+ DMSG0(ctx, DERROR, "Cannot support RegexWhere restore parameter. Aborting Job.\n");
+ JMSG0(ctx, M_FATAL, "Cannot support RegexWhere restore parameter. Aborting Job.\n");
+ return bRC_Error;
+ }
+ return prepare_restore(ctx, (char*)value);
+
+ /* Plugin command e.g. plugin = <plugin-name>:parameters */
+ case bEventPluginCommand:
+ // Check supported level
+ if (isourplugincommand(PLUGINPREFIX, (char*)value) && unsupportedfeature){
+ DMSG0(ctx, DERROR, "Unsupported backup level. Doing FULL backup.\n");
+ JMSG0(ctx, M_ERROR, "Unsupported backup level. Doing FULL backup.\n");
+ /* single error message is enough */
+ unsupportedfeature = false;
+ }
+ DMSG_EVENT_STR(event, value);
+ break;
+
+ case bEventOptionPlugin:
+ case bEventHandleBackupFile:
+ if (isourplugincommand(PLUGINPREFIX, (char*)value)){
+ DMSG0(ctx, DERROR, "Invalid handle Option Plugin called!\n");
+ JMSG0(ctx, M_FATAL,
+ "The " PLUGINNAME " plugin doesn't support the Option Plugin configuration.\n"
+ "Please review your FileSet and move the Plugin=" PLUGINPREFIX
+ "... command into the Include {} block.\n");
+ return bRC_Error;
+ }
+ return bRC_OK;
+
+ case bEventEndFileSet:
+ DMSG_EVENT_STR(event, value);
+ break;
+
+ case bEventRestoreObject:
+ /* Restore Object handle - a plugin configuration for restore and user supplied parameters */
+ if (!value){
+ DMSG0(ctx, DINFO, "End restore objects.\n");
+ break;
+ }
+ DMSG_EVENT_PTR(event, value);
+ return parse_plugin_restoreobj(ctx, (restore_object_pkt *) value);
+
+ default:
+ // enabled only for Debug
+ DMSG2(ctx, D2, "Unknown event: %s (%d) \n", eventtype2str(event), event->eventType);
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Reads a data from command tool on backup.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::perform_read_data(bpContext *ctx, struct io_pkt *io)
+{
+ int rc;
+
+ if (dkcommctx->is_eod()){
+ /* TODO: we signal EOD as rc=0, so no need to explicity check for EOD, right? */
+ io->status = 0;
+ } else {
+ rc = dkcommctx->read_data(ctx, io->buf, io->count);
+ io->status = rc;
+ if (rc < 0){
+ io->io_errno = EIO;
+ return bRC_Error;
+ }
+ }
+ return bRC_OK;
+}
+
+/*
+ * Reads a data from command tool on backup.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::perform_read_volume_data(bpContext *ctx, struct io_pkt *io)
+{
+ io->status = read(dkfd, io->buf, io->count);
+ if (io->status < 0){
+ io->io_errno = errno;
+ return bRC_Error;
+ }
+ return bRC_OK;
+}
+
+/*
+ * Writes data to command tool on restore.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::perform_write_data(bpContext *ctx, struct io_pkt *io)
+{
+ int rc = 0;
+
+ if (dkfd){
+ rc = write(dkfd, io->buf, io->count);
+ } else {
+ rc = dkcommctx->write_data(ctx, io->buf, io->count);
+ }
+ io->status = rc;
+ if (rc < 0){
+ io->io_errno = EIO;
+ return bRC_Error;
+ }
+ return bRC_OK;
+}
+
+/*
+ * Execute a backup command.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ * io->status, io->io_errno - set to error on any error
+ */
+bRC DOCKER::perform_backup_open(bpContext *ctx, struct io_pkt *io)
+{
+ POOL_MEM wname(PM_FNAME);
+ struct stat statp;
+ btimer_t *timer;
+
+ DMSG1(ctx, DDEBUG, "perform_backup_open called: %s\n", io->fname);
+ /* prepare backup for DOCKER_VOLUME */
+ if (currdkinfo->type() == DOCKER_VOLUME){
+ if (dkcommctx->prepare_working_volume(ctx, JobId) != bRC_OK){
+ io->status = -1;
+ io->io_errno = EIO;
+ return bRC_Error;
+ }
+ dkcommctx->render_working_volume_filename(wname, BACULACONTAINERFOUT);
+ if (stat(wname.c_str(), &statp) != 0){
+ berrno be;
+ /* if the path does not exist then create one */
+ if (be.code() != ENOENT || mkfifo(wname.c_str(), 0600) != 0){
+ /* error creating named pipe */
+ berrno be;
+ io->status = -1;
+ io->io_errno = be.code();
+ dkcommctx->set_error();
+ DMSG2(ctx, DERROR, "cannot create file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Cannot create file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ return bRC_Error;
+ }
+ } else {
+ /* check if it is a proper file */
+ if (!S_ISFIFO(statp.st_mode)){
+ /* not fifo, not good */
+ DMSG2(ctx, DERROR, "file is not fifo: %s [%o]\n", wname.c_str(), statp.st_mode);
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Improper file type: %s [%o]\n", wname.c_str(), statp.st_mode);
+ return bRC_Error;
+ }
+ }
+ }
+
+ /* execute backup docker */
+ if (dkcommctx->backup_docker(ctx, currdkinfo, JobId) != bRC_OK){
+ io->status = -1;
+ io->io_errno = EIO;
+ if (dkcommctx->is_abort_on_error()){
+ /* abort_on_error set, so terminate other backup for other container */
+ dkcommctx->finish_backup_list(ctx);
+ }
+ return bRC_Error;
+ }
+
+ /* finish preparation for DOCKER_VOLUME */
+ if (currdkinfo->type() == DOCKER_VOLUME){
+ timer = start_thread_timer(NULL, pthread_self(), dkcommctx->timeout());
+ dkfd = open(wname.c_str(), O_RDONLY);
+ stop_thread_timer(timer);
+ if (dkfd < 0){
+ /* error opening file to read */
+ berrno be;
+ io->status = -1;
+ io->io_errno = be.code();
+ dkcommctx->set_error();
+ DMSG2(ctx, DERROR, "cannot open archive file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Cannot open archive file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ return bRC_Error;
+ }
+ mode = DOCKER_BACKUP_VOLUME_FULL;
+ }
+
+ dkcommctx->clear_eod();
+
+ return bRC_OK;
+}
+
+/*
+ * Perform a restore file creation and open when restore to local server or
+ * restore command execution when restore to Docker.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ * io->status, io->io_errno - set to error on any error
+ */
+bRC DOCKER::perform_restore_open(bpContext* ctx, io_pkt* io)
+{
+ POOL_MEM wname(PM_FNAME);
+ int status;
+ btimer_t *timer;
+
+ /* first local restore as a simpler case */
+ if (local_restore){
+ /* restore local */
+ dkfd = open(fname, O_CREAT|O_WRONLY, 0640);
+ if (dkfd < 0){
+ /* error opening file to write */
+ io->status = -1;
+ io->io_errno = errno;
+ return bRC_Error;
+ }
+ } else {
+ /* prepare restore for DOCKER_VOLUME */
+ if (restoredkinfo->type() == DOCKER_VOLUME){
+ if (dkcommctx->prepare_working_volume(ctx, JobId) != bRC_OK){
+ io->status = -1;
+ io->io_errno = EIO;
+ return bRC_Error;
+ }
+ dkcommctx->render_working_volume_filename(wname, BACULACONTAINERFIN);
+ status = mkfifo(wname.c_str(), 0600);
+ if (status < 0){
+ /* error creating named pipe */
+ berrno be;
+ io->status = -1;
+ io->io_errno = be.code();
+ dkcommctx->set_error();
+ DMSG2(ctx, DERROR, "cannot create file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Cannot create file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ return bRC_Error;
+ }
+ }
+
+ /* execute backup docker */
+ if (dkcommctx->restore_docker(ctx, restoredkinfo, JobId) != bRC_OK){
+ io->status = -1;
+ io->io_errno = EIO;
+ return bRC_Error;
+ }
+
+ /* finish preparation for DOCKER_VOLUME */
+ if (restoredkinfo->type() == DOCKER_VOLUME){
+ timer = start_thread_timer(NULL, pthread_self(), dkcommctx->timeout());
+ dkfd = open(wname.c_str(), O_WRONLY);
+ stop_thread_timer(timer);
+ if (dkfd < 0){
+ /* error opening file to write */
+ berrno be;
+ io->status = -1;
+ io->io_errno = be.code();
+ dkcommctx->set_error();
+ DMSG2(ctx, DERROR, "cannot open archive file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Cannot open archive file: %s Err=%s\n", wname.c_str(), be.bstrerror());
+ return bRC_Error;
+ }
+ mode = DOCKER_RESTORE_VOLUME;
+ }
+
+ dkcommctx->clear_eod();
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Perform command tool termination when backup finish.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::perform_backup_close(bpContext *ctx, struct io_pkt *io)
+{
+ bRC status = bRC_OK;
+
+ dkcommctx->terminate(ctx);
+ if (currdkinfo->type() == DOCKER_VOLUME){
+ if (close(dkfd) < 0){
+ io->status = -1;
+ io->io_errno = errno;
+ status = bRC_Error;
+ }
+ mode = DOCKER_BACKUP_FULL;
+ errortar = check_container_tar_error(ctx, currdkinfo->get_volume_name());
+ }
+ return status;
+}
+
+/*
+ * Perform a restore file close when restore to local server or wait for restore
+ * command finish execution when restore to Docker.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ */
+bRC DOCKER::perform_restore_close(bpContext *ctx, struct io_pkt *io)
+{
+ bRC status = bRC_OK;
+ DKID dkid;
+ POOL_MEM buf(PM_NAME);
+ POOL_MEM names(PM_NAME);
+
+ /* both local_restore and volume restore uses dkfd */
+ if (dkfd > 0){
+ if (close(dkfd) < 0){
+ io->status = -1;
+ io->io_errno = errno;
+ status = bRC_Error;
+ }
+ dkfd = 0;
+ if (mode == DOCKER_RESTORE_VOLUME && restoredkinfo && restoredkinfo->type() == DOCKER_VOLUME){
+ mode = DOCKER_RESTORE;
+ errortar = check_container_tar_error(ctx, restoredkinfo->get_volume_name());
+ }
+ } else {
+ status = dkcommctx->wait_for_restore(ctx, dkid);
+ if (status != bRC_OK){
+ io->status = -1;
+ io->io_errno = EIO;
+ } else {
+ switch (restoredkinfo->type()){
+ case DOCKER_IMAGE:
+ /* when restore image then rename it only */
+ status = dkcommctx->docker_tag(ctx, dkid, restoredkinfo->get_image_repository_tag());
+ break;
+ case DOCKER_CONTAINER:
+ /* on container image we need to create a container itself, first tag the restored image */
+ Mmsg(buf, "%s/%s/%d:restore", restoredkinfo->name(), restoredkinfo->id()->digest_short(), JobId);
+ status = dkcommctx->docker_tag(ctx, dkid, buf.c_str());
+ if (status != bRC_OK){
+ DMSG1(ctx, DERROR, "perform_restore_close cannot tag restored image: %s\n", buf.c_str());
+ JMSG1(ctx, M_ERROR, "perform_restore_close cannot tag restored image: %s\n", buf.c_str());
+ break;
+ }
+ /* update image information on restoring container */
+ restoredkinfo->set_container_imagesave(dkid);
+ restoredkinfo->set_container_imagesave_tag(buf);
+ /* update a container name */
+ pm_strcpy(names, restoredkinfo->get_container_names());
+ Mmsg(buf, "%s_%d", names.c_str(), JobId);
+ restoredkinfo->set_container_names(buf);
+ status = dkcommctx->docker_create_run_container(ctx, restoredkinfo);
+ if (status != bRC_OK){
+ DMSG1(ctx, DERROR, "perform_restore_close cannot create container: %s\n",
+ restoredkinfo->get_container_names());
+ JMSG1(ctx, M_ERROR, "perform_restore_close cannot create container: %s\n",
+ restoredkinfo->get_container_names());
+ break;
+ }
+ break;
+ case DOCKER_VOLUME:
+ /* XXX */
+ break;
+ }
+ }
+ }
+ return status;
+}
+
+/*
+ * This is to check how Bacula archive container finish its job.
+ * We are doing this by examining docker.err file contents.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * out:
+ * false - when no errors found
+ * true - errors found and reported to user
+ */
+bool DOCKER::check_container_tar_error(bpContext* ctx, char *volname)
+{
+ struct stat statp;
+ POOL_MEM flog(PM_FNAME);
+ int rc;
+
+ if (dockerworkclear == 0){
+ dockerworkclear = 1;
+ }
+ dkcommctx->render_working_volume_filename(flog, BACULACONTAINERERRLOG);
+ if (stat(flog.c_str(), &statp) == 0){
+ if (statp.st_size > 0){
+ /* the error file has some content, so archive command was unsuccessful, report it */
+ POOL_MEM errlog(PM_MESSAGE);
+ int fd;
+ char *p;
+
+ fd = open(flog.c_str(), O_RDONLY);
+ if (fd < 0){
+ /* error opening errorlog, strange */
+ berrno be;
+ DMSG2(ctx, DERROR, "error opening archive errorlog file: %s Err=%s\n",
+ flog.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Error opening archive errorlog file: %s Err=%s\n", flog.c_str(), be.bstrerror());
+ return true;
+ }
+ rc = read(fd, errlog.c_str(), errlog.size() - 1);
+ close(fd);
+ if (rc < 0){
+ /* we should read some data, right? */
+ berrno be;
+ DMSG2(ctx, DERROR, "error reading archive errorlog file: %s Err=%s\n",
+ flog.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Error reading archive errorlog file: %s Err=%s\n", flog.c_str(), be.bstrerror());
+ return true;
+ }
+ /* clear last newline */
+ p = errlog.c_str();
+ if (p[rc-1] == '\n')
+ p[rc-1] = 0;
+ /* display error to user */
+ DMSG1(ctx, DERROR, "errorlog: %s\n", errlog.c_str());
+ JMSG1(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Archive error: %s\n", errlog.c_str());
+ /* rename log files for future use */
+ if (debug_level > 200){
+ POOL_MEM nflog(PM_FNAME);
+ dockerworkclear = 2;
+ Mmsg(nflog, "%s.%s", flog.c_str(), volname);
+ rc = rename(flog.c_str(), nflog.c_str());
+ if (rc < 0){
+ /* error renaming, report */
+ berrno be;
+ DMSG2(ctx, DERROR, "error renaming archive errorlog to: %s Err=%s\n",
+ nflog.c_str(), be.bstrerror());
+ JMSG2(ctx, M_ERROR,
+ "Error renaming archive errorlog file to: %s Err=%s\n", nflog.c_str(), be.bstrerror());
+ }
+ dkcommctx->render_working_volume_filename(flog, BACULACONTAINERARCHLOG);
+ Mmsg(nflog, "%s.%s", flog.c_str(), volname);
+ rc = rename(flog.c_str(), nflog.c_str());
+ if (rc < 0){
+ /* error renaming, report */
+ berrno be;
+ DMSG2(ctx, DERROR, "error renaming archive log to: %s Err=%s\n",
+ nflog.c_str(), be.bstrerror());
+ JMSG2(ctx, M_ERROR,
+ "Error renaming archive log file to: %s Err=%s\n", nflog.c_str(), be.bstrerror());
+ }
+ }
+ return true;
+ }
+ } else {
+ /* error access to BACULACONTAINERERRLOG, strange, report it */
+ berrno be;
+ DMSG2(ctx, DERROR, "error access archive errorlog file: %s Err=%s\n", flog.c_str(), be.bstrerror());
+ JMSG2(ctx, M_ERROR, "Error access archive errorlog file: %s Err=%s\n", flog.c_str(), be.bstrerror());
+ }
+
+ return false;
+};
+
+/*
+ * Handle Bacula Plugin I/O API for backend
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * io - Bacula Plugin API I/O structure for I/O operations
+ * out:
+ * bRC_OK - when successful
+ * bRC_Error - on any error
+ * io->status, io->io_errno - correspond to a plugin io operation status
+ */
+bRC DOCKER::pluginIO(bpContext *ctx, struct io_pkt *io)
+{
+ static int rw = 0; // this variable handles single debug message
+
+ /* assume no error from the very beginning */
+ io->status = 0;
+ io->io_errno = 0;
+ switch (io->func) {
+ case IO_OPEN:
+ DMSG(ctx, D2, "IO_OPEN: (%s)\n", io->fname);
+ switch (mode){
+ case DOCKER_BACKUP_FULL:
+ case DOCKER_BACKUP_INCR:
+ case DOCKER_BACKUP_DIFF:
+ case DOCKER_BACKUP_VOLUME_FULL:
+ return perform_backup_open(ctx, io);
+ case DOCKER_RESTORE:
+ case DOCKER_RESTORE_VOLUME:
+ return perform_restore_open(ctx, io);
+ default:
+ return bRC_Error;
+ }
+ break;
+ case IO_READ:
+ if (!rw) {
+ rw = 1;
+ DMSG2(ctx, D2, "IO_READ buf=%p len=%d\n", io->buf, io->count);
+ }
+ switch (mode){
+ case DOCKER_BACKUP_FULL:
+ case DOCKER_BACKUP_INCR:
+ case DOCKER_BACKUP_DIFF:
+ return perform_read_data(ctx, io);
+ case DOCKER_BACKUP_VOLUME_FULL:
+ return perform_read_volume_data(ctx, io);
+ default:
+ return bRC_Error;
+ }
+ break;
+ case IO_WRITE:
+ if (!rw) {
+ rw = 1;
+ DMSG2(ctx, D2, "IO_WRITE buf=%p len=%d\n", io->buf, io->count);
+ }
+ switch (mode){
+ case DOCKER_RESTORE:
+ case DOCKER_RESTORE_VOLUME:
+ return perform_write_data(ctx, io);
+ default:
+ return bRC_Error;
+ }
+ break;
+ case IO_CLOSE:
+ DMSG0(ctx, D2, "IO_CLOSE\n");
+ rw = 0;
+ switch (mode){
+ case DOCKER_RESTORE:
+ case DOCKER_RESTORE_VOLUME:
+ return perform_restore_close(ctx, io);
+ case DOCKER_BACKUP_FULL:
+ case DOCKER_BACKUP_VOLUME_FULL:
+ case DOCKER_BACKUP_INCR:
+ case DOCKER_BACKUP_DIFF:
+ return perform_backup_close(ctx, io);
+ default:
+ return bRC_Error;
+ }
+ break;
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Unimplemented, always return bRC_OK.
+ */
+bRC DOCKER::getPluginValue(bpContext *ctx, pVariable var, void *value)
+{
+ return bRC_OK;
+}
+
+/*
+ * Unimplemented, always return bRC_OK.
+ */
+bRC DOCKER::setPluginValue(bpContext *ctx, pVariable var, void *value)
+{
+ return bRC_OK;
+}
+
+/*
+ * Get all required information from Docker to populate save_pkt for Bacula.
+ * It handles a Restore Object (FT_PLUGIN_CONFIG) for every backup and
+ * new Plugin Backup Command if setup in FileSet. It handles
+ * backup/estimate/listing modes of operation.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * save_pkt - Bacula Plugin API save packet structure
+ * out:
+ * bRC_OK - when save_pkt prepared successfully and we have file to backup
+ * bRC_Max - when no more files to backup
+ * bRC_Error - in any error
+ */
+bRC DOCKER::startBackupFile(bpContext *ctx, struct save_pkt *sp)
+{
+ /* handle listing mode if requested */
+ if (estimate && listing_mode == DOCKER_LISTING_TOP){
+ sp->fname = (char*)docker_objects[listing_objnr++].name;
+ sp->type = FT_DIREND;
+ sp->statp.st_size = 0;
+ sp->statp.st_nlink = 1;
+ sp->statp.st_uid = 0;
+ sp->statp.st_gid = 0;
+ sp->statp.st_mode = 040750;
+ sp->statp.st_blksize = 4096;
+ sp->statp.st_blocks = 1;
+ sp->statp.st_atime = sp->statp.st_mtime = sp->statp.st_ctime = time(NULL);
+ return bRC_OK;
+ }
+
+ /* The first file in Full backup, is the RestoreObject */
+ if (!estimate && mode == DOCKER_BACKUP_FULL && robjsent == false) {
+ ConfigFile ini;
+
+ /* robj for the first time, allocate the buffer */
+ if (!robjbuf){
+ robjbuf = get_pool_memory(PM_FNAME);
+ }
+
+ ini.register_items(plugin_items_dump, sizeof(struct ini_items));
+ sp->object_name = (char *)INI_RESTORE_OBJECT_NAME;
+ sp->object_len = ini.serialize(&robjbuf);
+ sp->object = robjbuf;
+ sp->type = FT_PLUGIN_CONFIG;
+ DMSG0(ctx, DINFO, "Prepared RestoreObject sent.\n");
+ return bRC_OK;
+ }
+
+ /* check for forced backup finish */
+ if (backup_finish){
+ DMSG0(ctx, DINFO, "forced backup finish!\n");
+ backup_finish = false;
+ return bRC_Max;
+ }
+
+ /* check if this is the first container to backup/estimate/listing */
+ if (currdkinfo == NULL){
+ /* set all_to_backup list at first element */
+ currdkinfo = dkcommctx->get_first_to_backup(ctx);
+ if (!currdkinfo){
+ /* no docker objects to backup at all */
+ DMSG0(ctx, DDEBUG, "No Docker containers or objects to backup found.\n");
+ JMSG0(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "No Docker containers or objects to backup found.\n");
+ return bRC_Max;
+ }
+ }
+
+ /* in currdkinfo we have all info about docker object to backup */
+ if (!estimate && mode != DOCKER_BACKUP_CONTAINER_VOLLIST){
+ if (currdkinfo->type() != DOCKER_VOLUME){
+ DMSG3(ctx, DINFO, "Start Backup %s: %s (%s)\n",
+ currdkinfo->type_str(), currdkinfo->name(), currdkinfo->id()->digest_short());
+ JMSG3(ctx, M_INFO, "Start Backup %s: %s (%s)\n",
+ currdkinfo->type_str(), currdkinfo->name(), currdkinfo->id()->digest_short());
+ } else {
+ DMSG2(ctx, DINFO, "Start Backup %s: %s\n",
+ currdkinfo->type_str(), currdkinfo->name());
+ JMSG2(ctx, M_INFO, "Start Backup %s: %s\n",
+ currdkinfo->type_str(), currdkinfo->name());
+ }
+ }
+
+ /* generate the filename in backup/estimate */
+ if (!fname){
+ fname = get_pool_memory(PM_FNAME);
+ }
+ if (!lname){
+ lname = get_pool_memory(PM_FNAME);
+ }
+
+ /* populate common statp */
+ sp->statp.st_nlink = 1;
+ sp->statp.st_uid = 0;
+ sp->statp.st_gid = 0;
+ sp->portable = true;
+ sp->statp.st_blksize = 4096;
+ // TODO: use created time of image and volume objects
+ sp->statp.st_atime = sp->statp.st_mtime = sp->statp.st_ctime = time(NULL);
+ sp->statp.st_mode = S_IFREG | 0640; // standard file with '-rw-r----' permissions
+
+ if (mode == DOCKER_BACKUP_CONTAINER_VOLLIST && currvols){
+ sp->statp.st_size = currvols->vol->size();
+ sp->statp.st_blocks = sp->statp.st_size / 4096 + 1;
+ sp->type = FT_LNK;
+ if (!estimate){
+ Mmsg(fname, "%s%s/%s/volume: %s -> %s", PLUGINNAMESPACE, CONTAINERNAMESPACE,
+ currdkinfo->name(), currvols->vol->get_volume_name(), currvols->destination);
+ *lname = 0;
+ } else {
+ Mmsg(fname, "%s%s/%s/volume: %s", PLUGINNAMESPACE, CONTAINERNAMESPACE,
+ currdkinfo->name(), currvols->vol->get_volume_name());
+ lname = currvols->destination;
+ }
+ sp->link = lname;
+ sp->statp.st_mode = S_IFLNK | 0640;
+ } else {
+ sp->statp.st_size = currdkinfo->size();
+ sp->statp.st_blocks = sp->statp.st_size / 4096 + 1;
+ sp->type = FT_REG; // exported archive is a standard file
+
+ switch(listing_mode){
+ case DOCKER_LISTING_NONE:
+ /* generate a backup/estimate filename */
+ switch (currdkinfo->type()){
+ case DOCKER_CONTAINER:
+ Mmsg(fname, "%s%s/%s/%s.tar", PLUGINNAMESPACE, CONTAINERNAMESPACE,
+ currdkinfo->name(), (char*)currdkinfo->id());
+ break;
+ case DOCKER_IMAGE:
+ Mmsg(fname, "%s%s/%s/%s.tar", PLUGINNAMESPACE, IMAGENAMESPACE,
+ currdkinfo->name(), (char*)currdkinfo->id());
+ break;
+ case DOCKER_VOLUME:
+ Mmsg(fname, "%s%s/%s.tar", PLUGINNAMESPACE, VOLUMENAMESPACE,
+ currdkinfo->name());
+ break;
+ default:
+ DMSG1(ctx, DERROR, "unknown object type to backup: %s\n", currdkinfo->type_str());
+ JMSG1(ctx, M_ERROR, "Unknown object type to backup: %s\n", currdkinfo->type_str());
+ return bRC_Error;
+ }
+ break;
+ case DOCKER_LISTING_VOLUME:
+ sp->statp.st_mode = S_IFBLK | 0640; // standard block device with 'brw-r----' permissions
+ Mmsg(fname, "%s", currdkinfo->name());
+ break;
+ case DOCKER_LISTING_IMAGE:
+ sp->statp.st_mode = S_IFBLK | 0640; // standard block device with 'brw-r----' permissions
+ case DOCKER_LISTING_CONTAINER:
+ Mmsg(lname, "%s", param_notrunc?(char*)currdkinfo->id():currdkinfo->id()->digest_short());
+ Mmsg(fname, "%s", currdkinfo->name());
+ sp->link = lname;
+ sp->type = FT_LNK;
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+
+ /* populate rest of statp */
+ sp->fname = fname;
+
+ return bRC_OK;
+}
+
+/*
+ * Finish the Docker backup and clean temporary objects.
+ * For estimate/listing modes it handles next object to display.
+ *
+ * in:
+ * bpContext - for Bacula debug and jobinfo messages
+ * save_pkt - Bacula Plugin API save packet structure
+ * out:
+ * bRC_OK - when no more files to backup
+ * bRC_More - when Bacula should expect a next file
+ * bRC_Error - in any error
+ */
+bRC DOCKER::endBackupFile(bpContext *ctx)
+{
+ if (!estimate && mode != DOCKER_BACKUP_CONTAINER_VOLLIST){
+ /* If the current file was the restore object, so just ask for the next file */
+ if (mode == DOCKER_BACKUP_FULL && robjsent == false) {
+ robjsent = true;
+ return bRC_More;
+ }
+ switch (currdkinfo->type()){
+ case DOCKER_CONTAINER:
+ /* delete backup commit image */
+ if (dkcommctx->delete_container_commit(ctx, currdkinfo, JobId) != bRC_OK){
+ /* TODO: report problem to the user but not abort backup */
+ return bRC_Error;
+ }
+ case DOCKER_IMAGE:
+ DMSG4(ctx, DINFO, "Backup of %s: %s (%s) %s.\n", currdkinfo->type_str(), currdkinfo->name(),
+ currdkinfo->id()->digest_short(), dkcommctx->is_error() ? "Failed" : "OK");
+ JMSG4(ctx, M_INFO, "Backup of %s: %s (%s) %s.\n", currdkinfo->type_str(), currdkinfo->name(),
+ currdkinfo->id()->digest_short(), dkcommctx->is_error() ? "Failed" : "OK");
+ break;
+ case DOCKER_VOLUME:
+ /* check */
+ DMSG3(ctx, DINFO, "Backup of %s: %s %s.\n", currdkinfo->type_str(), currdkinfo->name(),
+ dkcommctx->is_error() || errortar ? "Failed" : "OK");
+ JMSG3(ctx, M_INFO, "Backup of %s: %s %s.\n", currdkinfo->type_str(), currdkinfo->name(),
+ dkcommctx->is_error() || errortar ? "Failed" : "OK");
+ break;
+ };
+ }
+
+ /* handle listing and next file to backup */
+ if (listing_mode == DOCKER_LISTING_TOP){
+ /* handle top-level listing mode */
+ if (docker_objects[listing_objnr].name){
+ /* next object available */
+ return bRC_More;
+ }
+ } else {
+ /* check if container we just backup has any vols mounted */
+ if (currdkinfo->type() == DOCKER_CONTAINER && !currvols && currdkinfo->container_has_vols() &&
+ mode != DOCKER_BACKUP_CONTAINER_VOLLIST){
+ /* yes, so prepare the flow for symbolic link backup */
+ currvols = currdkinfo->container_first_vols();
+ mode = DOCKER_BACKUP_CONTAINER_VOLLIST;
+ DMSG0(ctx, DDEBUG, "docker vols to backup found\n");
+ return bRC_More;
+ }
+ /* check if we already in symbolic link backup mode */
+ if (mode == DOCKER_BACKUP_CONTAINER_VOLLIST && currvols){
+ /* yes, so check for next symbolic link to backup */
+ currvols = currdkinfo->container_next_vols();
+ if (currvols){
+ DMSG0(ctx, DDEBUG, "docker next vols to backup found\n");
+ return bRC_More;
+ } else {
+ /* it was the last symbolic link, so finish this mode */
+ mode = DOCKER_BACKUP_FULL;
+ currvols = NULL;
+ }
+ }
+ /* check if next object to backup/estimate/listing */
+ currdkinfo = dkcommctx->get_next_to_backup(ctx);
+ if (currdkinfo){
+ DMSG0(ctx, DDEBUG, "next docker object to backup found\n");
+ return bRC_More;
+ }
+ }
+
+ return bRC_OK;
+}
+
+/*
+ * Start Restore File.
+ */
+bRC DOCKER::startRestoreFile(bpContext *ctx, const char *cmd)
+{
+ return bRC_OK;
+}
+
+/*
+ * End Restore File.
+ * Handles the next vm state.
+ */
+bRC DOCKER::endRestoreFile(bpContext *ctx)
+{
+ /* release restore dkinfo */
+ if (restoredkinfo){
+ delete restoredkinfo;
+ restoredkinfo = NULL;
+ }
+ return bRC_OK;
+}
+
+/*
+ * Search in Docker all available images if image we are restoring already exist.
+ *
+ * in:
+ * bpContext - bacula plugin context
+ * this->restoredkinfo - current image to restore
+ * out:
+ * *DKINFO from Docker all_images if image to restore found
+ * NULL when not found
+ */
+DKINFO *DOCKER::search_docker_image(bpContext *ctx)
+{
+ alist *allimages;
+ DKINFO *image = NULL;
+
+ allimages = dkcommctx->get_all_images(ctx);
+ if (allimages){
+ DMSG1(ctx, DDEBUG, "search allimages for: %s\n", (char*)restoredkinfo->get_image_id());
+ /* check if image which we are restoring exist on Docker already */
+ foreach_alist(image, allimages){
+ DMSG1(ctx, DDEBUG, "compare: %s\n", (char*)image->get_image_id());
+ if (image && *image->get_image_id() == *restoredkinfo->get_image_id()){
+ DMSG0(ctx, DINFO, "image to restore found available\n");
+ break;
+ }
+ };
+ }
+ return image;
+};
+
+/*
+ * Search in Docker all available volumes if volume we are restoring already exist.
+ *
+ * in:
+ * bpContext - bacula plugin context
+ * this->restoredkinfo - current volume to restore
+ * out:
+ * *DKINFO from Docker all_volumes if volume to restore found
+ * NULL when not found
+ */
+DKINFO *DOCKER::search_docker_volume(bpContext *ctx)
+{
+ alist *allvolumes;
+ DKINFO *volume = NULL;
+
+ allvolumes = dkcommctx->get_all_volumes(ctx);
+ if (allvolumes){
+ DMSG1(ctx, DDEBUG, "search allvolumes for: %s\n", restoredkinfo->get_volume_name());
+ /* check if image which we are restoring exist on Docker already */
+ foreach_alist(volume, allvolumes){
+ DMSG1(ctx, DDEBUG, "compare: %s\n", volume->get_volume_name());
+ if (volume && bstrcmp(volume->get_volume_name(), restoredkinfo->get_volume_name())){
+ DMSG0(ctx, DINFO, "volume to restore found available\n");
+ break;
+ }
+ };
+ }
+ return volume;
+};
+
+/*
+ * When restore to local server then handle restored file creation else
+ * inform user about starting a restore.
+ *
+ * in:
+ * bpContext - bacula plugin context
+ * restore_pkt - Bacula Plugin API restore packet structure
+ * out:
+ * bRC_OK - when success reported from backend
+ * rp->create_status = CF_EXTRACT - the backend will restore the file
+ * with pleasure
+ * rp->create_status = CF_SKIP - the backend wants to skip restoration, i.e.
+ * the file already exist and Replace=n was set
+ * bRC_Error, rp->create_status = CF_ERROR - in any error
+ */
+bRC DOCKER::createFile(bpContext *ctx, struct restore_pkt *rp)
+{
+ POOL_MEM fmt(PM_FNAME);
+ POOL_MEM fmt2(PM_FNAME);
+ POOL_MEM imageid(PM_FNAME);
+ POOL_MEM label(PM_FNAME);
+ struct stat statp;
+ char *dir, *p;
+ int len;
+ int status;
+ DKINFO *image;
+
+ /* skip a support volume file link */
+ if (rp->type == FT_LNK && S_ISLNK(rp->statp.st_mode)){
+ DMSG1(ctx, DDEBUG, "skipping support file: %s\n", rp->ofname);
+ rp->create_status = CF_SKIP;
+ } else {
+ /* it seems something to restore */
+ if (!fname){
+ fname = get_pool_memory(PM_FNAME);
+ }
+ /* where=/ then we'll restore to Docker else we'll restore local */
+ if (where && strlen(where) > 1 && *where == PathSeparator){
+ local_restore = true;
+ len = strlen(where);
+ DMSG(ctx, DINFO, "local restore to: %s\n", where);
+ pm_strcpy(fmt, rp->ofname);
+ dir = strrchr(fmt.c_str(), '.');
+ if (dir && bstrcmp(dir, ".tar")){
+ *dir = 0;
+ JMSG(ctx, M_INFO, "Docker local restore: %s\n", fmt.c_str() + len + strlen(PLUGINNAMESPACE) + 1);
+ }
+ /* compose a destination fname */
+ pm_strcpy(fname, where);
+ pm_strcat(fname, rp->ofname + len + strlen(PLUGINNAMESPACE));
+ DMSG(ctx, DDEBUG, "composed fname: %s\n", fname);
+ /* prepare a destination directory */
+ pm_strcpy(fmt, fname);
+ dir = dirname(fmt.c_str());
+ if (!dir){
+ berrno be;
+ DMSG2(ctx, DERROR, "dirname error for %s Err=%s\n", fmt.c_str(), be.bstrerror());
+ JMSG2(ctx, dkcommctx->is_fatal() ? M_FATAL : M_ERROR, "dirname error for %s Err=%s\n", fmt.c_str(), be.bstrerror());
+ rp->create_status = CF_ERROR;
+ return bRC_Error;
+ }
+ DMSG(ctx, DDEBUG, "dirname: %s\n", fmt.c_str());
+ if (pluglib_mkpath(ctx, dir, dkcommctx->is_fatal()) != bRC_OK){
+ rp->create_status = CF_ERROR;
+ return bRC_Error;
+ }
+ switch (replace){
+ case REPLACE_ALWAYS:
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_NEVER:
+ /* check if file exist locally */
+ if (stat(fname, &statp) == 0){
+ /* exist, so skip restore */
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_IFNEWER:
+ if (stat(fname, &statp) == 0){
+ /* exist, so check if newer */
+ if (statp.st_mtime < rp->statp.st_mtime){
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_IFOLDER:
+ if (stat(fname, &statp) == 0){
+ /* exist, so check if newer */
+ if (statp.st_mtime > rp->statp.st_mtime){
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ }
+ } else {
+ /* TODO: report docker restore start */
+ local_restore = false;
+ pm_strcpy(fname, rp->ofname + strlen(PLUGINNAMESPACE));
+ DMSG(ctx, DINFO, "scanning fname to restore: %s\n", fname);
+
+ /*
+ * first scan for Container backup file
+ * the dirtmp variable has a sscanf format to scan which is dynamically generated
+ * based on the size of label and imageid variables. this limits the size of the scan
+ * and prevents any memory overflow. the destination scan format is something like this:
+ * "/container/%256[^/]/%256[^.]", so it will scan two string variables up to
+ * 256 characters long
+ */
+ Mmsg(fmt, "%s/%%%d[^/]/%%%d[^.]", CONTAINERNAMESPACE,
+ label.size(), imageid.size());
+ // DMSG(ctx, DVDEBUG, "container scan str: %s\n", dirtmp.c_str());
+ status = sscanf(fname, fmt.c_str(), label.c_str(), imageid.c_str());
+ if (status == 2){
+ /* insanity check for memleak */
+ if (restoredkinfo != NULL){
+ delete restoredkinfo;
+ }
+ restoredkinfo = New(DKINFO(DOCKER_CONTAINER));
+ restoredkinfo->set_container_id(imageid);
+ restoredkinfo->set_container_names(label);
+ pm_strcpy(fmt, label.c_str()); // Well there is no a pm_strcpy(POOL_MEM&, POOL_MEM&), strange
+ Mmsg(label, "%s/%s", fmt.c_str(), restoredkinfo->get_container_id()->digest_short());
+ DMSG2(ctx, DINFO, "scanned: %s %s\n", restoredkinfo->get_container_names(),
+ (char*)restoredkinfo->get_container_id());
+ /* we replace container always? */
+ rp->create_status = CF_EXTRACT;
+ } else {
+ /*
+ * scan for Volume backup file
+ * the dirtmp variable has a sscanf format to scan which is dynamically generated
+ * based on the size of label variable. this limits the size of the scan
+ * and prevents any memory overflow. the destination scan format is something like this:
+ * "/volume/%256s", so it will scan a single string variable up to
+ * 256 characters long
+ */
+ Mmsg(fmt, "%s/%%%ds", VOLUMENAMESPACE, label.size());
+ // DMSG(ctx, DVDEBUG, "volume scan str: %s\n", dirtmp.c_str());
+ status = sscanf(fname, fmt.c_str(), label.c_str());
+ if (status == 1){
+ /* terminate volume name, so fname without '.tar. */
+ p = strstr(label.c_str(), ".tar");
+ *p = 0;
+ /* insanity check for memleak */
+ if (restoredkinfo != NULL){
+ delete restoredkinfo;
+ }
+ restoredkinfo = New(DKINFO(DOCKER_VOLUME));
+ restoredkinfo->set_volume_name(label);
+ DMSG1(ctx, DINFO, "scanned: %s\n", restoredkinfo->get_volume_name());
+
+ /* check for remote docker operations as this is not supported currently */
+ if (dkcommctx->is_remote_docker()){
+ DMSG1(ctx, DINFO, "volume %s restore with docker_host skipped.\n", restoredkinfo->get_volume_name());
+ if (!volumewarning){
+ JMSG0(ctx, M_WARNING, "Docker Volume restore with docker_host is unsupported! All volumes restore skipped.\n");
+ volumewarning = true;
+ }
+ rp->create_status = CF_SKIP;
+ return bRC_OK;
+ }
+
+ switch (replace){
+ case REPLACE_ALWAYS:
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_NEVER:
+ case REPLACE_IFNEWER:
+ case REPLACE_IFOLDER:
+ default:
+ /*
+ * check if volume exist on docker,
+ * as we cannot check if the volume was modified
+ * then we will treat it the same as REPLACE_NEVER flag
+ */
+ if ((image = search_docker_volume(ctx)) != NULL){
+ /* exist, so skip restore */
+ DMSG1(ctx, DINFO, "volume exist, skipping restore of: %s\n",
+ restoredkinfo->get_volume_name());
+ JMSG1(ctx, M_INFO, "Volume exist, skipping restore of: %s\n",
+ restoredkinfo->get_volume_name());
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ }
+ } else {
+ /* now scan for Image backup */
+ p = strrchr(fname, '/');
+ if (p){
+ /* found the last (first in reverse) path_separator,
+ * so before $p we have a path and after $p we have digest to scan */
+ *p++ = 0;
+ }
+ /*
+ * scan path to separate image repository:tag data from filename
+ * the dirtmp and tmp2 variables have a sscanf format to scan which is dynamically
+ * generated based on the size of label and imageid variables. this limits the size
+ * of the scan and prevents any memory overflow. the destination scan format is
+ * something like this:
+ * "/image/%256s", for image repository:tag encoded in filename and
+ * "%256[^.]", for imageid part of the encoded filename, so it will scan
+ * two string variables in two sscanf up to 256 characters long each
+ */
+ Mmsg(fmt, "%s/%%%ds", IMAGENAMESPACE, label.size());
+ Mmsg(fmt2, "%%%d[^.]", imageid.size());
+ // DMSG(ctx, DVDEBUG, "image scan str: %s\n", dirtmp.c_str());
+ if (sscanf(fname, fmt.c_str(), label.c_str()) == 1 &&
+ sscanf(p, fmt2.c_str(), imageid.c_str()) == 1){
+ /* insanity check for memleak */
+ if (restoredkinfo != NULL){
+ delete restoredkinfo;
+ }
+ /* we will restore the Docker Image */
+ restoredkinfo = New(DKINFO(DOCKER_IMAGE));
+ restoredkinfo->set_image_id(imageid);
+ restoredkinfo->scan_image_repository_tag(label);
+ DMSG2(ctx, DINFO, "scanned: %s %s\n", restoredkinfo->get_image_repository_tag(),
+ (char*)restoredkinfo->get_image_id());
+ switch (replace){
+ case REPLACE_ALWAYS:
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_NEVER:
+ /* check if image exist on docker */
+ if ((image = search_docker_image(ctx)) != NULL){
+ /* exist, so skip restore */
+ DMSG1(ctx, DINFO, "image exist, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ JMSG1(ctx, M_INFO, "Image exist, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_IFNEWER:
+ if ((image = search_docker_image(ctx)) != NULL){
+ /* exist, so check if newer */
+ if (image->get_image_created() < rp->statp.st_mtime){
+ DMSG1(ctx, DINFO, "image exist and is newer, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ JMSG1(ctx, M_INFO, "Image exist and is newer, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ rp->create_status = CF_SKIP;
+ break;
+ }
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ case REPLACE_IFOLDER:
+ if ((image = search_docker_image(ctx)) != NULL){
+ /* exist, so check if newer */
+ if (image->get_image_created() > rp->statp.st_mtime){
+ rp->create_status = CF_SKIP;
+ DMSG1(ctx, DINFO, "image exist and is older, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ JMSG1(ctx, M_INFO, "Image exist and is older, skipping restore of: %s\n",
+ restoredkinfo->get_image_repository_tag());
+ break;
+ }
+ }
+ rp->create_status = CF_EXTRACT;
+ break;
+ }
+ } else {
+ // fname scanning error
+ DMSG1(ctx, DERROR, "Filename scan error on: %s\n", fmt.c_str());
+ JMSG1(ctx, dkcommctx->is_abort_on_error() ? M_FATAL : M_ERROR,
+ "Filename scan error on: %s\n", fmt.c_str());
+ rp->create_status = CF_ERROR;
+ return bRC_Error;
+ }
+ }
+ }
+ if (rp->create_status == CF_EXTRACT){
+ /* display info about a restore to the user */
+ DMSG2(ctx, DINFO, "%s restore: %s\n", restoredkinfo-> type_str(), label.c_str());
+ JMSG2(ctx, M_INFO, "%s restore: %s\n", restoredkinfo->type_str(), label.c_str());
+ }
+ }
+ }
+ return bRC_OK;
+}
+
+/*
+ * Unimplemented, always return bRC_OK.
+ */
+bRC DOCKER::setFileAttributes(bpContext *ctx, struct restore_pkt *rp)
+{
+ return bRC_OK;
+}
+
+/*
+ * Unimplemented, always return bRC_Seen.
+ */
+bRC DOCKER::checkFile(bpContext *ctx, char *fname)
+{
+ if (!accurate_warning){
+ accurate_warning = true;
+ JMSG0(ctx, M_WARNING, "Accurate mode is not supported. Please disable Accurate mode for this job.\n");
+ }
+ return bRC_Seen;
+}
+
+/*
+ * We will not generate any acl/xattr data, always return bRC_OK.
+ */
+bRC DOCKER::handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl)
+{
+ return bRC_OK;
+}
+
+/*
+ * Called here to make a new instance of the plugin -- i.e. when
+ * a new Job is started. There can be multiple instances of
+ * each plugin that are running at the same time. Your
+ * plugin instance must be thread safe and keep its own
+ * local data.
+ */
+static bRC newPlugin(bpContext *ctx)
+{
+ int JobId;
+ DOCKER *self = New(DOCKER(ctx));
+ char *workdir;
+
+ if (!self){
+ return bRC_Error;
+ }
+ ctx->pContext = (void*) self;
+
+ getBaculaVar(bVarJobId, (void *)&JobId);
+ DMSG(ctx, DINFO, "newPlugin JobId=%d\n", JobId);
+
+ /* we are very paranoid here to double check it */
+ if (access(DOCKER_CMD, X_OK) < 0){
+ berrno be;
+ DMSG2(ctx, DERROR, "Unable to use command tool: %s Err=%s\n", DOCKER_CMD, be.bstrerror());
+ JMSG2(ctx, M_FATAL, "Unable to use command tool: %s Err=%s\n", DOCKER_CMD, be.bstrerror());
+ return bRC_Error;
+ }
+
+ /* get dynamic working directory from file daemon */
+ getBaculaVar(bVarWorkingDir, (void *)&workdir);
+ self->setworkingdir(workdir);
+
+ return bRC_OK;
+}
+
+/*
+ * Release everything concerning a particular instance of
+ * a plugin. Normally called when the Job terminates.
+ */
+static bRC freePlugin(bpContext *ctx)
+{
+ if (!ctx){
+ return bRC_Error;
+ }
+ DOCKER *self = pluginclass(ctx);
+ DMSG(ctx, D1, "freePlugin this=%p\n", self);
+ if (!self){
+ return bRC_Error;
+ }
+ delete self;
+ return bRC_OK;
+}
+
+/*
+ * Called by core code to get a variable from the plugin.
+ * Not currently used.
+ */
+static bRC getPluginValue(bpContext *ctx, pVariable var, void *value)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D3, "getPluginValue called.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->getPluginValue(ctx,var, value);
+}
+
+/*
+ * Called by core code to set a plugin variable.
+ * Not currently used.
+ */
+static bRC setPluginValue(bpContext *ctx, pVariable var, void *value)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D3, "setPluginValue called.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->setPluginValue(ctx, var, value);
+}
+
+/*
+ * Called by Bacula when there are certain events that the
+ * plugin might want to know. The value depends on the
+ * event.
+ */
+static bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value)
+{
+ ASSERT_CTX;
+
+ DMSG(ctx, D1, "handlePluginEvent (%i)\n", event->eventType);
+ DOCKER *self = pluginclass(ctx);
+ return self->handlePluginEvent(ctx, event, value);
+}
+
+/*
+ * Called when starting to backup a file. Here the plugin must
+ * return the "stat" packet for the directory/file and provide
+ * certain information so that Bacula knows what the file is.
+ * The plugin can create "Virtual" files by giving them
+ * a name that is not normally found on the file system.
+ */
+static bRC startBackupFile(bpContext *ctx, struct save_pkt *sp)
+{
+ ASSERT_CTX;
+ if (!sp){
+ return bRC_Error;
+ }
+ DMSG0(ctx, D1, "startBackupFile.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->startBackupFile(ctx, sp);
+}
+
+/*
+ * Done backing up a file.
+ */
+static bRC endBackupFile(bpContext *ctx)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D1, "endBackupFile.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->endBackupFile(ctx);
+}
+
+/*
+ * Called when starting restore the file, right after a createFile().
+ */
+static bRC startRestoreFile(bpContext *ctx, const char *cmd)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D1, "startRestoreFile.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->startRestoreFile(ctx, cmd);
+}
+
+/*
+ * Done restore the file.
+ */
+static bRC endRestoreFile(bpContext *ctx)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D1, "endRestoreFile.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->endRestoreFile(ctx);
+}
+
+/*
+ * Do actual I/O. Bacula calls this after startBackupFile
+ * or after startRestoreFile to do the actual file
+ * input or output.
+ */
+static bRC pluginIO(bpContext *ctx, struct io_pkt *io)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, DVDEBUG, "pluginIO.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->pluginIO(ctx, io);
+}
+
+/*
+ * Called here to give the plugin the information needed to
+ * re-create the file on a restore. It basically gets the
+ * stat packet that was created during the backup phase.
+ * This data is what is needed to create the file, but does
+ * not contain actual file data.
+ */
+static bRC createFile(bpContext *ctx, struct restore_pkt *rp)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D1, "createFile.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->createFile(ctx, rp);
+}
+
+/*
+ * checkFile used for accurate mode backup
+ */
+static bRC checkFile(bpContext *ctx, char *fname)
+{
+ ASSERT_CTX;
+
+ DMSG(ctx, D1, "checkFile: %s\n", fname);
+ DOCKER *self = pluginclass(ctx);
+ return self->checkFile(ctx, fname);
+}
+
+/*
+ * Called after the file has been restored. This can be used to
+ * set directory permissions, ...
+ */
+static bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp)
+{
+ ASSERT_CTX;
+
+ DMSG0(ctx, D1, "setFileAttributes.\n");
+ DOCKER *self = pluginclass(ctx);
+ return self->setFileAttributes(ctx, rp);
+}
+
+/*
+ * handleXACLdata used for ACL/XATTR backup and restore
+ */
+static bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl)
+{
+ ASSERT_CTX;
+
+ DMSG(ctx, D1, "handleXACLdata: %i\n", xacl->func);
+ DOCKER *self = pluginclass(ctx);
+ return self->handleXACLdata(ctx, xacl);
+}
--- /dev/null
+/*
+ Bacula(R) - The Network Backup Solution
+
+ Copyright (C) 2000-2019 Kern Sibbald
+
+ The original author of Bacula is Kern Sibbald, with contributions
+ from many others, a complete list can be found in the file AUTHORS.
+
+ You may use this file and others of this release according to the
+ license defined in the LICENSE file, which includes the Affero General
+ Public License, v3.0 ("AGPLv3") and some additional permissions and
+ terms pursuant to its AGPLv3 Section 7.
+
+ This notice must be preserved when any source code is
+ conveyed and/or propagated.
+
+ Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+/*
+ * This is a Bacula plugin for backup/restore Docker using native tools.
+ *
+ * Author: Radosław Korzeniewski, MMXIX
+ * radoslaw@korzeniewski.net, radekk@inteos.pl
+ * Inteos Sp. z o.o. http://www.inteos.pl/
+ */
+
+#ifndef _DOCKER_FD_H_
+#define _DOCKER_FD_H_
+
+#include "dkcommctx.h"
+
+/* Plugin Info definitions */
+#define DOCKER_LICENSE "Bacula AGPLv3"
+#define DOCKER_AUTHOR "Radoslaw Korzeniewski"
+#define DOCKER_DATE "Oct 2019"
+#define DOCKER_VERSION "1.2.0"
+#define DOCKER_DESCRIPTION "Bacula Docker Plugin"
+
+/* Plugin compile time variables */
+#define PLUGINPREFIX "docker:"
+#define PLUGINNAME "Docker"
+#define PLUGINNAMESPACE "/@docker"
+#define CONTAINERNAMESPACE "/container"
+#define IMAGENAMESPACE "/image"
+#define VOLUMENAMESPACE "/volume"
+
+/* types used by Plugin */
+typedef enum {
+ DOCKER_NONE = 0,
+ DOCKER_BACKUP_FULL,
+ DOCKER_BACKUP_INCR,
+ DOCKER_BACKUP_DIFF,
+ DOCKER_BACKUP_VOLUME_FULL,
+ DOCKER_BACKUP_CONTAINER_VOLLIST,
+ DOCKER_RESTORE,
+ DOCKER_RESTORE_VOLUME,
+} DOCKER_MODE_T;
+
+#define pluginclass(ctx) (DOCKER*)ctx->pContext;
+
+/* listing mode */
+typedef enum {
+ DOCKER_LISTING_NONE = 0,
+ DOCKER_LISTING_TOP,
+ DOCKER_LISTING_IMAGE,
+ DOCKER_LISTING_CONTAINER,
+ DOCKER_LISTING_VOLUME,
+} DOCKER_LISTING_MODE;
+
+/* listing objects for plugin */
+typedef struct {
+ const char *name;
+ DOCKER_LISTING_MODE mode;
+} DOCKER_LISTING_T;
+
+static DOCKER_LISTING_T docker_objects[] = {
+ {"/", DOCKER_LISTING_TOP},
+ {"image", DOCKER_LISTING_IMAGE},
+ {"container", DOCKER_LISTING_CONTAINER},
+ {"volume", DOCKER_LISTING_VOLUME},
+ {NULL, DOCKER_LISTING_NONE},
+};
+
+/*
+ * This is a main plugin API class. It manages a plugin context.
+ * All the public methods correspond to a public Bacula API calls, even if
+ * a callback is not implemented.
+ */
+class DOCKER: public SMARTALLOC {
+ public:
+ bRC getPluginValue(bpContext *ctx, pVariable var, void *value);
+ bRC setPluginValue(bpContext *ctx, pVariable var, void *value);
+ bRC handlePluginEvent(bpContext *ctx, bEvent *event, void *value);
+ bRC startBackupFile(bpContext *ctx, struct save_pkt *sp);
+ bRC endBackupFile(bpContext *ctx);
+ bRC startRestoreFile(bpContext *ctx, const char *cmd);
+ bRC endRestoreFile(bpContext *ctx);
+ bRC pluginIO(bpContext *ctx, struct io_pkt *io);
+ bRC createFile(bpContext *ctx, struct restore_pkt *rp);
+ bRC setFileAttributes(bpContext *ctx, struct restore_pkt *rp);
+ bRC checkFile(bpContext *ctx, char *fname);
+ bRC handleXACLdata(bpContext *ctx, struct xacl_pkt *xacl);
+ void setworkingdir(char *workdir);
+ DOCKER(bpContext *bpctx);
+ ~DOCKER();
+
+ private:
+ bpContext *ctx; /* Bacula Plugin Context */
+ DOCKER_MODE_T mode; /* Plugin mode of operation */
+ int JobId; /* Job ID */
+ char *JobName; /* Job name */
+ time_t since; /* Job since parameter */
+ char *where; /* the Where variable for restore job if set by user */
+ char *regexwhere; /* the RegexWhere variable for restore job if set by user */
+ char replace; /* the replace variable for restore job */
+ bool robjsent; /* set when RestoreObject was sent during Full backup */
+ bool estimate; /* used when mode is DOCKER_BACKUP_* but we are doing estimate only */
+ bool accurate_warning; /* for sending accurate mode warning once */
+ bool local_restore; /* if where parameter is set to local path then make a local restore */
+ bool backup_finish; /* the hack to force finish backup list */
+ bool unsupportedfeature; /* this flag show if plugin should report unsupported feature like unsupported backup level*/
+ bool param_notrunc; /* when "notrunc" option specified, used in listing mode only */
+ bool errortar; /* show if container tar for volume archive had errors */
+ bool volumewarning; /* when set then a warning about remote docker volume restore was sent to user */
+ int dockerworkclear; /* set to 1 when docker working dir should be cleaned */
+ /* TODO: define a variable which will signal job cancel */
+ DKCOMMCTX *dkcommctx; /* the current command tool execution context */
+ alist *commandlist; /* the command context list for multiple config execution for a single job */
+ POOLMEM *fname; /* current file name to backup or restore */
+ POOLMEM *lname; /* current link name to estimate or listing */
+ int dkfd; /* the file descriptor for local restore and volume backup */
+ POOLMEM *robjbuf; /* the buffer for restore object data */
+ DKINFO *currdkinfo; /* current docker object - container or image to backup */
+ DKINFO *restoredkinfo; /* the current docker object - container or image to restore */
+ DKVOLS *currvols; /* current docker volume object for backup or restore */
+ DOCKER_LISTING_MODE listing_mode; /* the listing mode */
+ int listing_objnr; /* when at listing top mode iterate through docker_objects */
+ cmd_parser *parser; /* the plugin params parser */
+ POOLMEM *workingdir; /* runtime working directory from file daemon */
+
+ bRC parse_plugin_command(bpContext *ctx, const char *command);
+ bRC parse_plugin_restoreobj(bpContext *ctx, restore_object_pkt *rop);
+ bRC prepare_bejob(bpContext* ctx, char *command);
+ bRC prepare_estimate(bpContext *ctx, char *command);
+ bRC prepare_backup(bpContext *ctx, char *command);
+ bRC prepare_restore(bpContext *ctx, char *command);
+ bRC perform_backup_open(bpContext *ctx, struct io_pkt *io);
+ bRC perform_restore_open(bpContext *ctx, struct io_pkt *io);
+ bRC perform_read_data(bpContext *ctx, struct io_pkt *io);
+ bRC perform_read_volume_data(bpContext *ctx, struct io_pkt *io);
+ bRC perform_write_data(bpContext *ctx, struct io_pkt *io);
+ bRC perform_restore_close(bpContext *ctx, struct io_pkt *io);
+ bRC perform_backup_close(bpContext *ctx, struct io_pkt *io);
+ void new_commandctx(bpContext *ctx, const char *command);
+ void switch_commandctx(bpContext *ctx, const char *command);
+ DKINFO *search_docker_image(bpContext *ctx);
+ DKINFO *search_docker_volume(bpContext *ctx);
+ bool check_container_tar_error(bpContext *ctx, char *volname);
+};
+
+#endif /* _DOCKER_FD_H_ */
*/
#define DEPKGS_QT_VERSION "01Jan13"
#define DEPKGS_VERSION "10Oct18"
+#define DOCKER_TAR_IMAGE "19Aug19"
/*
Bacula(R) - The Network Backup Solution