From: Radosław Korzeniewski Date: Sat, 14 Dec 2019 14:05:12 +0000 (+0100) Subject: Add Docker Plugin for FileDaemon. X-Git-Tag: Release-9.6.0~117 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91b5df1f20a16466fac21b5e44f709463129b933;p=thirdparty%2Fbacula.git Add Docker Plugin for FileDaemon. This is a File Daemon Docker Plugin which backup and restore Docker objects including images, containers and persistent volumes data with dedicated docker image. The plugin requires a docker utility for compile and build. The default is to automatically check for docker binary and if found then build the plugin. Additionally refactor some configuration and build procedures. --- diff --git a/bacula/.gitignore b/bacula/.gitignore index a74eb12ba..ffa46b4e9 100644 --- a/bacula/.gitignore +++ b/bacula/.gitignore @@ -54,6 +54,7 @@ kernssunproductionconfig kernswinconfig kernswinproductionconfig Makefile +nbproject newdb newtape run_clean diff --git a/bacula/autoconf/configure.in b/bacula/autoconf/configure.in index 3246b2e7e..1ea09cf3e 100644 --- a/bacula/autoconf/configure.in +++ b/bacula/autoconf/configure.in @@ -232,7 +232,6 @@ AC_SUBST(LIBTOOL_INSTALL_TARGET) AC_SUBST(LIBTOOL_UNINSTALL_TARGET) AC_SUBST(LIBTOOL_CLEAN_TARGET) AC_SUBST(QMAKE_LIBTOOL) -AC_SUBST(FD_PLUGIN_DIR) dnl -------------------------------------------------- dnl Include file handling @@ -590,11 +589,11 @@ dnl ------------------------------------------- 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 @@ -611,9 +610,9 @@ dnl ------------------------------------------- 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 ] ) @@ -3022,6 +3021,40 @@ AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIB="-lpthread", ] ) +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 @@ -3421,111 +3454,112 @@ if test "x${subsysdir}" = "x${sbindir}" ; then 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 @@ -3628,72 +3662,74 @@ ${MAKE:-make} clean 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 diff --git a/bacula/configure b/bacula/configure index e14e4f45a..e3028c021 100755 --- a/bacula/configure +++ b/bacula/configure @@ -1,6 +1,6 @@ #! /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. @@ -587,8 +587,8 @@ MAKEFLAGS= # 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='' @@ -648,6 +648,8 @@ DINCLUDE DEBUG FDLIBS CAP_LIBS +FD_PLUGIN_DIR +docker_bin_path XATTROBJS ACLOBJS LZO_LIBS @@ -678,6 +680,9 @@ MYSQL_LIBS POSTGRESQL_BINDIR POSTGRESQL_INCLUDE POSTGRESQL_LIBS +CYTHON_INC +CYTHON_LIBS +CYTHON SBINPERM fd_group fd_user @@ -796,7 +801,6 @@ HAVE_SUN_OS_FALSE HAVE_SUN_OS_TRUE INCLUDE_UNINSTALL_TARGET INCLUDE_INSTALL_TARGET -FD_PLUGIN_DIR QMAKE_LIBTOOL LIBTOOL_CLEAN_TARGET LIBTOOL_UNINSTALL_TARGET @@ -1008,6 +1012,7 @@ with_sd_group with_fd_user with_fd_group with_sbin_perm +with_cython enable_batch_insert with_postgresql with_mysql @@ -1022,6 +1027,7 @@ enable_lzo with_lzo enable_acl enable_xattr +enable_docker_plugin with_systemd ' ac_precious_vars='build_alias @@ -1578,7 +1584,7 @@ if test "$ac_init_help" = "long"; then # 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]... @@ -1647,7 +1653,7 @@ fi 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 @@ -1685,6 +1691,7 @@ Optional Features: --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] @@ -1745,6 +1752,7 @@ Optional Packages: --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 @@ -1846,7 +1854,7 @@ fi 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. @@ -2781,7 +2789,7 @@ cat >config.log <<_ACEOF 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 $@ @@ -16754,7 +16762,6 @@ fi - # Check whether --enable-includes was given. if test "${enable_includes+set}" = set; then : enableval=$enable_includes; @@ -22001,11 +22008,11 @@ fi # 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 @@ -22023,9 +22030,9 @@ 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 @@ -23850,6 +23857,40 @@ 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 : @@ -30812,6 +30853,79 @@ fi 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` @@ -31286,10 +31400,7 @@ if test "x${subsysdir}" = "x${sbindir}" ; then 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 @@ -31798,7 +31909,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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 @@ -31864,7 +31975,7 @@ _ACEOF 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\\" @@ -32477,8 +32588,8 @@ do "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 @@ -33982,8 +34093,6 @@ _LT_EOF ;; esac done ;; - "default":C) - ;; esac done # for ac_tag @@ -34115,72 +34224,74 @@ ${MAKE:-make} clean 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 diff --git a/bacula/src/plugins/fd/docker/Makefile.in b/bacula/src/plugins/fd/docker/Makefile.in new file mode 100644 index 000000000..6774e7d9d --- /dev/null +++ b/bacula/src/plugins/fd/docker/Makefile.in @@ -0,0 +1,89 @@ +# +# 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: diff --git a/bacula/src/plugins/fd/docker/dkcommctx.c b/bacula/src/plugins/fd/docker/dkcommctx.c new file mode 100644 index 000000000..286de1a6d --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkcommctx.c @@ -0,0 +1,2409 @@ +/* + 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: */ + 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 + * - 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 + * - 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 + * - 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: -> + * + * 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; +}; diff --git a/bacula/src/plugins/fd/docker/dkcommctx.h b/bacula/src/plugins/fd/docker/dkcommctx.h new file mode 100644 index 000000000..13475d9a2 --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkcommctx.h @@ -0,0 +1,210 @@ +/* + 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 diff --git a/bacula/src/plugins/fd/docker/dkid.c b/bacula/src/plugins/fd/docker/dkid.c new file mode 100644 index 000000000..7813d9548 --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkid.c @@ -0,0 +1,353 @@ +/* + 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 diff --git a/bacula/src/plugins/fd/docker/dkid.h b/bacula/src/plugins/fd/docker/dkid.h new file mode 100644 index 000000000..cca496f5b --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkid.h @@ -0,0 +1,70 @@ +/* + 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 diff --git a/bacula/src/plugins/fd/docker/dkinfo.c b/bacula/src/plugins/fd/docker/dkinfo.c new file mode 100644 index 000000000..0f6740ce9 --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkinfo.c @@ -0,0 +1,382 @@ +/* + 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; +}; diff --git a/bacula/src/plugins/fd/docker/dkinfo.h b/bacula/src/plugins/fd/docker/dkinfo.h new file mode 100644 index 000000000..8803e8488 --- /dev/null +++ b/bacula/src/plugins/fd/docker/dkinfo.h @@ -0,0 +1,193 @@ +/* + 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 diff --git a/bacula/src/plugins/fd/docker/docker-fd.c b/bacula/src/plugins/fd/docker/docker-fd.c new file mode 100644 index 000000000..7082001d4 --- /dev/null +++ b/bacula/src/plugins/fd/docker/docker-fd.c @@ -0,0 +1,2046 @@ +/* + 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 +#include +#include +#include + +/* + * 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 = :[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 + * - 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 = :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 = :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 = :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 = :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); +} diff --git a/bacula/src/plugins/fd/docker/docker-fd.h b/bacula/src/plugins/fd/docker/docker-fd.h new file mode 100644 index 000000000..e2685cf77 --- /dev/null +++ b/bacula/src/plugins/fd/docker/docker-fd.h @@ -0,0 +1,161 @@ +/* + 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_ */ diff --git a/bacula/src/version.h b/bacula/src/version.h index 15a21ad2b..2f6a7f3d5 100644 --- a/bacula/src/version.h +++ b/bacula/src/version.h @@ -19,6 +19,7 @@ */ #define DEPKGS_QT_VERSION "01Jan13" #define DEPKGS_VERSION "10Oct18" +#define DOCKER_TAR_IMAGE "19Aug19" /* Bacula(R) - The Network Backup Solution