From d7a0e39e91bcefde2d2362c9fcfa28808b96ec33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wanderlei=20H=C3=BCttel?= Date: Wed, 27 May 2020 10:23:25 -0300 Subject: [PATCH] Add vchanger 1.0.3 code --- AUTHORS | 3 + COPYING | 2 +- ChangeLog | 21 + ReleaseNotes | 34 +- config.h.in | 11 +- config.h.in~ | 316 +++ configure | 216 +- configure.ac | 27 +- contrib/vchanger.logrotate | 5 + doc/example-vchanger-udev.rules | 3 +- doc/vchanger-example.conf | 25 +- doc/vchanger.8 | 14 +- doc/vchanger.8.asciidoc | 31 +- doc/vchanger.conf.5 | 8 +- doc/vchanger.conf.5.asciidoc | 4 +- doc/vchangerHowto.html | 3555 +++++++++++++++-------------- rpm/vchanger.el6.spec | 11 +- rpm/vchanger.el7.spec | 14 +- rpm/vchanger.f29.spec | 106 + scripts/vchanger-genudevrules | 2 +- scripts/vchanger-launch-mount.sh | 16 +- scripts/vchanger-launch-umount.sh | 10 +- scripts/vchanger-mount-uuid.sh | 2 +- scripts/vchanger-umount-uuid.sh | 2 +- src/Makefile.am | 5 +- src/Makefile.in | 44 +- src/bconsole.cpp | 137 +- src/bconsole.h | 4 +- src/changerstate.cpp | 62 +- src/compat/semaphore.c | 197 ++ src/compat/semaphore.h | 54 + src/diskchanger.cpp | 234 +- src/diskchanger.h | 10 +- src/loghandler.cpp | 29 +- src/loghandler.h | 15 +- src/mymutex.cpp | 131 ++ src/mymutex.h | 32 + src/mypopen.cpp | 40 +- src/uuidlookup.c | 68 +- src/vchanger.cpp | 171 +- src/vconf.cpp | 49 +- src/vconf.h | 3 +- win32/installer.nsi | 32 +- win32build | 2 +- 44 files changed, 3448 insertions(+), 2309 deletions(-) create mode 100644 config.h.in~ create mode 100644 contrib/vchanger.logrotate create mode 100644 rpm/vchanger.f29.spec create mode 100644 src/compat/semaphore.c create mode 100644 src/compat/semaphore.h create mode 100644 src/mymutex.cpp create mode 100644 src/mymutex.h diff --git a/AUTHORS b/AUTHORS index fd51a0c..4e949b1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,3 +7,6 @@ Principle Authors: Josh Fisher Contributors: + Bill Arlofski + Wanderlei Hüttel + Steven A. Falco \ No newline at end of file diff --git a/COPYING b/COPYING index cb1251f..7756028 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ COPYRIGHTS: -Vchanger is Copyright (C) 2008-2015 Josh Fisher. +Vchanger is Copyright (C) 2008-2020 Josh Fisher. LICENSE: Vchanger is licensed under the GNU GPL version 2, the full text diff --git a/ChangeLog b/ChangeLog index 6111246..34fe510 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,26 @@ vchanger ChangeLog +1.0.3 (2020-05-06) + - Redesign locking mechanism for multiple instances using POSIX + semaphores. + - Use at, rather than nohup, in scripts invoked by udev rules to fix + nohup not working as expected on some platforms. + (Patch from Steven A. Falco) + - Add spec file for Fedora 29 (Provided by Steven A. Falco) + - Correct number of slots reported by SIZE command (Fixes bug 17) + - Rename logging variables that conflict with cmath's log function (Fixes bug 18) +1.0.2 (2018-06-14) + - Use named mutex to prevent instances of vchanger invoked by Bacula during + a bconsole call from initiating further bconsole calls. Prevents a race + condition caused by the need for Bacula to invoke additional instances of + vchanger when vchanger invokes bconsole to issue 'update slots' and + 'label barcodes' commands. (Fixes bug 15) + - Prevent load command from loading the same virtual slot into more than + one virtual drive. (Fixes bug 13) + - Fix bconsole update slots command needs drive to be specified. [Patch + from Bill Arlofski] (Fixes bug 14) + - Improve generated volume label format. [Patch from Wanderlei Huttel] + - Additional logging to help debug udev/UUID assignment problems 1.0.1 (2015-06-09) - When looking up the mountpoint of a magazine by UUID with libudev, also look for mountpoint of device alias names in DEVLINKS in addition diff --git a/ReleaseNotes b/ReleaseNotes index c9678c3..ea5a578 100644 --- a/ReleaseNotes +++ b/ReleaseNotes @@ -1,4 +1,34 @@ - Release Notes for vchanger 1.0.1 2015-06-09 + Release Notes for vchanger 1.0.3 2020-05-07 + + Release 1.0.3 + + This is mostly a bug fix release, correcting the number of slots + reported by SIZE/LIST commands, a compilation error on FreeBSD, + and failure of the launch scripts invoked by udev on some platforms. + + The locking mechanism to allow multiple instances and automatically + issuing 'update slots' and other commands to bconsole has been + redesigned to use POSIX semaphores. + + Bugs Fixed: + 17 SIZE/​LIST commands return wrong number of slots + 18 Compilation fails on FreeBSD 13 (head) + + ================================================================== + + Release 1.0.2 + + This is a bug fix release, fixing three issues found in version 1.0.1 + and improving volume label formatting for CREATEVOLS command and logging + of udev/UUID mountpoint discovery. + + Bugs Fixed: + + 13 LOAD command allows loading slot into multiple drives + 14 Hang when bconsole called to update slots + 15 Race condition in bconsole call may hang vchanger + + ================================================================== Release 1.0.1 @@ -6,7 +36,7 @@ Additionally, the Windows installer was fixed to correctly install on 64-bit Windows and create Start Menu items. -Bugs Fixed: + Bugs Fixed: 9 Vchanger may hang when issuing commands to Bacula 10 uuidlookup.c compilation failure on FreeBSD 9.3 diff --git a/config.h.in b/config.h.in index f650f97..1f2c69f 100644 --- a/config.h.in +++ b/config.h.in @@ -15,6 +15,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CTYPE_H +/* have header direct.h */ +#undef HAVE_DIRECT_H + /* Define to 1 if you have the header file. */ #undef HAVE_DIRENT_H @@ -69,7 +72,7 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LOCALE_H -/* Define to 1 if you have the `localtime_r' function. */ +/* have function localtime_r */ #undef HAVE_LOCALTIME_R /* Define to 1 if you have the header file. */ @@ -96,6 +99,9 @@ /* Define to 1 if you have the `readlink' function. */ #undef HAVE_READLINK +/* Define to 1 if you have the header file. */ +#undef HAVE_SEMAPHORE_H + /* Define to 1 if you have the `setlocale' function. */ #undef HAVE_SETLOCALE @@ -148,6 +154,9 @@ */ #undef HAVE_SYS_DIR_H +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_MMAN_H + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_MOUNT_H diff --git a/config.h.in~ b/config.h.in~ new file mode 100644 index 0000000..b370bfd --- /dev/null +++ b/config.h.in~ @@ -0,0 +1,316 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Copyright notice */ +#undef COPYRIGHT_NOTICE + +/* Define to 1 if you have the header file. */ +#undef HAVE_ALLOCA_H + +/* Have blkid_evaluate_tag function */ +#undef HAVE_BLKID_EVALUATE_TAG + +/* Have blkid_get_devname function */ +#undef HAVE_BLKID_GET_DEVNAME + +/* Define to 1 if you have the header file. */ +#undef HAVE_CTYPE_H + +/* have header direct.h */ +#undef HAVE_DIRECT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_DIRENT_H + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +#undef HAVE_DOPRNT + +/* Define to 1 if you have the header file. */ +#undef HAVE_ERRNO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_FCNTL_H + +/* Define to 1 if you have the `getfsstat' function. */ +#undef HAVE_GETFSSTAT + +/* Define to 1 if you have the `getline' function. */ +#undef HAVE_GETLINE + +/* Define to 1 if you have the `getmntent' function. */ +#undef HAVE_GETMNTENT + +/* Define to 1 if you have the `getmntent_r' function. */ +#undef HAVE_GETMNTENT_R + +/* Define to 1 if you have the header file. */ +#undef HAVE_GETOPT_H + +/* Define to 1 if you have the `gettimeofday' function. */ +#undef HAVE_GETTIMEOFDAY + +/* Define to 1 if you have the `getuid' function. */ +#undef HAVE_GETUID + +/* Define to 1 if you have the header file. */ +#undef HAVE_GRP_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_IO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIBGEN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIBUDEV_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LIMITS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LOCALE_H + +/* have function localtime_r */ +#undef HAVE_LOCALTIME_R + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MNTENT_H + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +#undef HAVE_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_OPTARG_H + +/* Define to 1 if you have the `pipe' function. */ +#undef HAVE_PIPE + +/* Define to 1 if you have the header file. */ +#undef HAVE_PTHREAD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_PWD_H + +/* Define to 1 if you have the `readlink' function. */ +#undef HAVE_READLINK + +/* Define to 1 if you have the `setlocale' function. */ +#undef HAVE_SETLOCALE + +/* have header shlobj.h */ +#undef HAVE_SHLOBJ_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SIGNAL_H + +/* Define to 1 if you have the `sleep' function. */ +#undef HAVE_SLEEP + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDARG_H + +/* Define to 1 if stdbool.h conforms to C99. */ +#undef HAVE_STDBOOL_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDDEF_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDIO_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the `symlink' function. */ +#undef HAVE_SYMLINK + +/* Define to 1 if you have the `syslog' function. */ +#undef HAVE_SYSLOG + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYSLOG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_BITYPES_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_DIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_MMAN_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_MOUNT_H + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#undef HAVE_SYS_NDIR_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_PARAM_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_SELECT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIMESPEC_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_UCRED_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_WAIT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_TIME_H + +/* Define to 1 if typeof works with your compiler. */ +#undef HAVE_TYPEOF + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_UTIME_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_VARARGS_H + +/* Define to 1 if you have the `vprintf' function. */ +#undef HAVE_VPRINTF + +/* have header windows.h */ +#undef HAVE_WINDOWS_H + +/* have header winioctl.h */ +#undef HAVE_WINIOCTL_H + +/* have header winreg.h */ +#undef HAVE_WINREG_H + +/* have header winsock.h */ +#undef HAVE_WINSOCK_H + +/* Define to 1 if the system has the type `_Bool'. */ +#undef HAVE__BOOL + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Version number of package */ +#undef VERSION + +/* Enable large inode numbers on Mac OS X 10.5. */ +#ifndef _DARWIN_USE_64_BIT_INODE +# define _DARWIN_USE_64_BIT_INODE 1 +#endif + +/* Number of bits in a file offset, on hosts where this is settable. */ +#undef _FILE_OFFSET_BITS + +/* Define for large files, on AIX-style hosts. */ +#undef _LARGE_FILES + +/* Define for Solaris 2.5.1 so the uint32_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT32_T + +/* Define for Solaris 2.5.1 so the uint64_t typedef from , + , or is not used. If the typedef were allowed, the + #define below would cause a syntax error. */ +#undef _UINT64_T + +/* Define to empty if `const' does not conform to ANSI C. */ +#undef const + +/* Define to `int' if doesn't define. */ +#undef gid_t + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +#undef inline +#endif + +/* Define to the type of a signed integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef int32_t + +/* Define to the type of a signed integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#undef int64_t + +/* Define to `int' if does not define. */ +#undef mode_t + +/* Define to `long int' if does not define. */ +#undef off_t + +/* Define to `int' if does not define. */ +#undef pid_t + +/* Define to `unsigned int' if does not define. */ +#undef size_t + +/* Define to `int' if does not define. */ +#undef ssize_t + +/* Define to __typeof__ if your compiler spells it that way. */ +#undef typeof + +/* Define to `int' if doesn't define. */ +#undef uid_t + +/* Define to the type of an unsigned integer type of width exactly 32 bits if + such a type exists and the standard includes do not define it. */ +#undef uint32_t + +/* Define to the type of an unsigned integer type of width exactly 64 bits if + such a type exists and the standard includes do not define it. */ +#undef uint64_t diff --git a/configure b/configure index c12d3e8..9cae160 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for vchanger 1.0.1. +# Generated by GNU Autoconf 2.69 for vchanger 1.0.3. # # Report bugs to . # @@ -580,8 +580,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='vchanger' PACKAGE_TARNAME='vchanger' -PACKAGE_VERSION='1.0.1' -PACKAGE_STRING='vchanger 1.0.1' +PACKAGE_VERSION='1.0.3' +PACKAGE_STRING='vchanger 1.0.3' PACKAGE_BUGREPORT='jfisher@jaybus.com' PACKAGE_URL='' @@ -1279,7 +1279,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 vchanger 1.0.1 to adapt to many kinds of systems. +\`configure' configures vchanger 1.0.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1345,7 +1345,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of vchanger 1.0.1:";; + short | recursive ) echo "Configuration of vchanger 1.0.3:";; esac cat <<\_ACEOF @@ -1439,7 +1439,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -vchanger configure 1.0.1 +vchanger configure 1.0.3 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2026,11 +2026,57 @@ $as_echo "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_c_check_func + +# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES +# --------------------------------------------- +# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR +# accordingly. +ac_fn_c_check_decl () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + as_decl_name=`echo $2|sed 's/ *(.*//'` + as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'` + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5 +$as_echo_n "checking whether $as_decl_name is declared... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +int +main () +{ +#ifndef $as_decl_name +#ifdef __cplusplus + (void) $as_decl_use; +#else + (void) $as_decl_name; +#endif +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_decl 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 vchanger $as_me 1.0.1, which was +It was created by vchanger $as_me 1.0.3, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2342,12 +2388,14 @@ as_fn_append ac_header_list " grp.h" as_fn_append ac_header_list " pwd.h" as_fn_append ac_header_list " dirent.h" as_fn_append ac_header_list " fcntl.h" +as_fn_append ac_header_list " sys/mman.h" as_fn_append ac_header_list " sys/select.h" as_fn_append ac_header_list " optarg.h" as_fn_append ac_header_list " pthread.h" as_fn_append ac_header_list " libgen.h" as_fn_append ac_header_list " io.h" as_fn_append ac_header_list " signal.h" +as_fn_append ac_header_list " semaphore.h" # Check that the precious variables saved in the cache have kept the same # value. ac_cache_corrupted=false @@ -2417,7 +2465,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -$as_echo "#define COPYRIGHT_NOTICE \"vchanger Copyright (c) 2006-2015 Josh Fisher\"" >>confdefs.h +$as_echo "#define COPYRIGHT_NOTICE \"vchanger Copyright (c) 2006-2020 Josh Fisher\"" >>confdefs.h @@ -2938,7 +2986,7 @@ fi # Define the identity of the package. PACKAGE='vchanger' - VERSION='1.0.1' + VERSION='1.0.3' cat >>confdefs.h <<_ACEOF @@ -5514,6 +5562,70 @@ else as_fn_error $? "\"could not find libpthread\"" "$LINENO" 5 fi +ac_fn_c_check_header_mongrel "$LINENO" "sys/mman.h" "ac_cv_header_sys_mman_h" "$ac_includes_default" +if test "x$ac_cv_header_sys_mman_h" = xyes; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing shm_open" >&5 +$as_echo_n "checking for library containing shm_open... " >&6; } +if ${ac_cv_search_shm_open+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shm_open (); +int +main () +{ +return shm_open (); + ; + return 0; +} +_ACEOF +for ac_lib in '' rt; do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_c_try_link "$LINENO"; then : + ac_cv_search_shm_open=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext + if ${ac_cv_search_shm_open+:} false; then : + break +fi +done +if ${ac_cv_search_shm_open+:} false; then : + +else + ac_cv_search_shm_open=no +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_shm_open" >&5 +$as_echo "$ac_cv_search_shm_open" >&6; } +ac_res=$ac_cv_search_shm_open +if test "$ac_res" != no; then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else + as_fn_error $? "shm_open() not found" "$LINENO" 5 +fi + + +fi + + # Support using libudev or libblkid for UUID lookup { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing udev_new" >&5 $as_echo_n "checking for library containing udev_new... " >&6; } @@ -5960,6 +6072,10 @@ done + + + + @@ -6011,6 +6127,15 @@ $as_echo "#define HAVE_SHLOBJ_H /**/" >>confdefs.h fi +ac_fn_c_check_header_compile "$LINENO" "direct.h" "ac_cv_header_direct_h" "#include +" +if test "x$ac_cv_header_direct_h" = xyes; then : + +$as_echo "#define HAVE_DIRECT_H /**/" >>confdefs.h + +fi + + # Checks for functions. for ac_func in vprintf do : @@ -6031,32 +6156,18 @@ fi done -for ac_func in setlocale getmntent getmntent_r getfsstat -do : - as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` -ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" -if eval test \"x\$"$as_ac_var"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 -_ACEOF + +ac_fn_c_check_decl "$LINENO" "localtime_r" "ac_cv_have_decl_localtime_r" "$ac_includes_default + #include + #include +" +if test "x$ac_cv_have_decl_localtime_r" = xyes; then : + +$as_echo "#define HAVE_LOCALTIME_R /**/" >>confdefs.h fi -done -ac_fn_c_check_func "$LINENO" "getline" "ac_cv_func_getline" -if test "x$ac_cv_func_getline" = xyes; then : - $as_echo "#define HAVE_GETLINE 1" >>confdefs.h - -else - case " $LIBOBJS " in - *" getline.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS getline.$ac_objext" - ;; -esac - -fi - ac_fn_c_check_func "$LINENO" "gettimeofday" "ac_cv_func_gettimeofday" if test "x$ac_cv_func_gettimeofday" = xyes; then : $as_echo "#define HAVE_GETTIMEOFDAY 1" >>confdefs.h @@ -6070,6 +6181,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "getline" "ac_cv_func_getline" +if test "x$ac_cv_func_getline" = xyes; then : + $as_echo "#define HAVE_GETLINE 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" getline.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS getline.$ac_objext" + ;; +esac + +fi + ac_fn_c_check_func "$LINENO" "getuid" "ac_cv_func_getuid" if test "x$ac_cv_func_getuid" = xyes; then : $as_echo "#define HAVE_GETUID 1" >>confdefs.h @@ -6083,19 +6207,6 @@ esac fi -ac_fn_c_check_func "$LINENO" "localtime_r" "ac_cv_func_localtime_r" -if test "x$ac_cv_func_localtime_r" = xyes; then : - $as_echo "#define HAVE_LOCALTIME_R 1" >>confdefs.h - -else - case " $LIBOBJS " in - *" localtime_r.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS localtime_r.$ac_objext" - ;; -esac - -fi - ac_fn_c_check_func "$LINENO" "pipe" "ac_cv_func_pipe" if test "x$ac_cv_func_pipe" = xyes; then : $as_echo "#define HAVE_PIPE 1" >>confdefs.h @@ -6163,6 +6274,19 @@ fi +for ac_func in setlocale getmntent getmntent_r getfsstat +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + ac_config_files="$ac_config_files Makefile src/Makefile doc/Makefile scripts/Makefile" @@ -6700,7 +6824,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 vchanger $as_me 1.0.1, which was +This file was extended by vchanger $as_me 1.0.3, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -6766,7 +6890,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="\\ -vchanger config.status 1.0.1 +vchanger config.status 1.0.3 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 9825144..f848085 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,9 @@ # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. -AC_PREREQ([2.63]) -AC_INIT([vchanger], [1.0.1], [jfisher@jaybus.com]) -AC_DEFINE([COPYRIGHT_NOTICE],["AC_PACKAGE_NAME Copyright (c) 2006-2015 Josh Fisher"],[Copyright notice]) +AC_PREREQ([2.69]) +AC_INIT([vchanger], [1.0.3], [jfisher@jaybus.com]) +AC_DEFINE([COPYRIGHT_NOTICE],["AC_PACKAGE_NAME Copyright (c) 2006-2020 Josh Fisher"],[Copyright notice]) AC_CONFIG_SRCDIR([src]) AC_CONFIG_LIBOBJ_DIR([src/compat]) AC_CONFIG_HEADERS([config.h]) @@ -34,6 +34,9 @@ AC_SYS_LARGEFILE # Use multithreading AC_SEARCH_LIBS([pthread_create], [pthread], [], [AC_MSG_ERROR(["could not find libpthread"])]) +AC_CHECK_HEADER([sys/mman.h], + [ AC_SEARCH_LIBS([shm_open],[rt],[],[AC_MSG_ERROR([shm_open() not found])]) + ]) # Support using libudev or libblkid for UUID lookup AC_SEARCH_LIBS([udev_new],[udev],[AC_CHECK_HEADERS([libudev.h])]) AC_CHECK_HEADER([blkid/blkid.h], @@ -50,21 +53,27 @@ AC_CHECK_HEADERS_ONCE([stdio.h stdlib.h string.h stdarg.h stddef.h]) AC_CHECK_HEADERS_ONCE([time.h sys/time.h sys/timespec.h sys/wait.h limits.h locale.h syslog.h]) AC_CHECK_HEADERS_ONCE([sys/types.h strings.h alloca.h sys/bitypes.h getopt.h utime.h sys/stat.h]) AC_CHECK_HEADERS_ONCE([inttypes.h ctype.h errno.h unistd.h varargs.h mntent.h]) -AC_CHECK_HEADERS_ONCE([sys/param.h sys/mount.h sys/ucred.h grp.h pwd.h dirent.h fcntl.h]) -AC_CHECK_HEADERS_ONCE([sys/select.h optarg.h pthread.h libgen.h io.h signal.h]) -AC_CHECK_HEADER([windows.h], - [AC_DEFINE([HAVE_WINDOWS_H],,[have header windows.h]) +AC_CHECK_HEADERS_ONCE([sys/param.h sys/mount.h sys/ucred.h grp.h pwd.h dirent.h fcntl.h sys/mman.h]) +AC_CHECK_HEADERS_ONCE([sys/select.h optarg.h pthread.h libgen.h io.h signal.h semaphore.h]) +AC_CHECK_HEADER([windows.h], [AC_DEFINE([HAVE_WINDOWS_H],,[have header windows.h]) WINLDADD=-static]) AC_SUBST(WINLDADD) AC_CHECK_HEADER([winioctl.h], [AC_DEFINE([HAVE_WINIOCTL_H],,[have header winioctl.h])], [], [#include ]) AC_CHECK_HEADER([winsock.h], [AC_DEFINE([HAVE_WINSOCK_H],,[have header winsock.h])], [], [#include ]) AC_CHECK_HEADER([winreg.h], [AC_DEFINE([HAVE_WINREG_H],,[have header winreg.h])], [], [#include ]) AC_CHECK_HEADER([shlobj.h], [AC_DEFINE([HAVE_SHLOBJ_H],,[have header shlobj.h])], [], [#include ]) +AC_CHECK_HEADER([direct.h], [AC_DEFINE([HAVE_DIRECT_H],,[have header direct.h])], [], [#include ]) # Checks for functions. AC_FUNC_VPRINTF -AC_CHECK_FUNCS([setlocale getmntent getmntent_r getfsstat]) -AC_REPLACE_FUNCS([getline gettimeofday getuid localtime_r pipe readlink sleep symlink syslog]) +AC_CHECK_DECL([localtime_r], [AC_DEFINE([HAVE_LOCALTIME_R],,[have function localtime_r])], [], + [AC_INCLUDES_DEFAULT + #include + #include ]) + +AC_REPLACE_FUNCS([gettimeofday getline getuid pipe readlink sleep symlink syslog]) + +AC_CHECK_FUNCS([setlocale getmntent getmntent_r getfsstat]) AC_CONFIG_FILES([Makefile src/Makefile doc/Makefile scripts/Makefile]) diff --git a/contrib/vchanger.logrotate b/contrib/vchanger.logrotate new file mode 100644 index 0000000..432262f --- /dev/null +++ b/contrib/vchanger.logrotate @@ -0,0 +1,5 @@ +/var/log/vchanger/*.log { + missingok + notifempty + sharedscripts +} diff --git a/doc/example-vchanger-udev.rules b/doc/example-vchanger-udev.rules index b9c62d3..86ad737 100644 --- a/doc/example-vchanger-udev.rules +++ b/doc/example-vchanger-udev.rules @@ -1,4 +1,5 @@ -# This file contains udev rules for automounting drives assigned to vchanger +# This file contains example udev rules for automounting filesystems assigned to vchanger +# See vchangerHowto.html for details on generating udev rules for vchanger magazines. # # Rules for magazine 0 (uuid:7b4526c4-d8e9-48ba-b227-f67f855a0dc7) ACTION=="add",SUBSYSTEM=="block", ENV{ID_FS_UUID}=="7b4526c4-d8e9-48ba-b227-f67f855a0dc7", RUN+="/usr/libexec/vchanger/vchanger-launch-mount.sh 7b4526c4-d8e9-48ba-b227-f67f855a0dc7" diff --git a/doc/vchanger-example.conf b/doc/vchanger-example.conf index 38ecfa5..3ca31bd 100644 --- a/doc/vchanger-example.conf +++ b/doc/vchanger-example.conf @@ -1,4 +1,4 @@ -# Example vchanger 1.0.0 config file +# Example vchanger 1.0.2 config file # # Storage Resource Name of the Storage resource defined in bacula-dir.conf @@ -10,27 +10,27 @@ # User User to run as when invoked by root. This must be the # owner of the volume files controlled by this changer, and # so will need to be the same user that bacula-sd runs as. -# [Default: none ] -User = bacula +# [Default: bacula ] +#User = bacula # # Group Group to run as when invoked by root. This should be the # owner group of the volume files controlled by this changer, -# and should also be the default group pf the user that +# and should also be the default group of the user that # bacula-sd runs as. -# [Default: none ] -group = tape +# [Default: tape ] +#group = tape # # Work Dir Directory where virtual drive and magazine state information # and symlinks for this changer are stored. -# [Default: /var/spool/vchanger/[StorageResource] ] +# [Default: /var/spool/vchanger/{StorageResource} ] #work dir = "/var/spool/vchanger/vcahnger" # # Logfile Path to log file for this changer. -# [Default: /var/log/vchanger/[StorageResource].log ] -#logfile = "/var/log/vchanger/vchanger.log" +# [Default: {WorkDir}/{StorageResource}.log ] +#logfile = "/var/spool/vchanger/vchanger.log" # # Log Level Sets the level of detail being logged. An integer value from @@ -50,7 +50,8 @@ group = tape # # bconsole config Path to the config file bconsole will use when vchanger # invokes bconsole. By default, bconsole will be invoked -# without the -c flag. +# without the -c flag. The file must be readable by the user +# vchanger is run as. # [Default: none ] #bconsole config = /etc/bacula/bconsole.conf @@ -69,7 +70,9 @@ group = tape # by prefixing the string "UUID:" to the filesystem's UUID. # For magazines specified by UUID, the mountpoint of the # filesystem will be queried from the system. Note that vchanger -# does not attempt to mount the filesystem. +# does not attempt to mount the magazines that are specified +# by filesystem UUID. Vchanger utilizes all magazines that are +# already mounted at the time it is invoked. # [Default: none ] #magazine = "uuid:4fcb1422-f15c-4d7a-8a32-a4dcc0af5e00" #Magazine = "/mnt/backup2" diff --git a/doc/vchanger.8 b/doc/vchanger.8 index 5109406..b1161cd 100644 --- a/doc/vchanger.8 +++ b/doc/vchanger.8 @@ -2,12 +2,12 @@ .\" Title: vchanger .\" Author: Josh Fisher .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 06/03/2015 +.\" Date: 05/11/2020 .\" Manual: vchanger Manual -.\" Source: vchanger 1.0.1 +.\" Source: vchanger 1.0.3 .\" Language: English .\" -.TH "VCHANGER" "8" "06/03/2015" "vchanger 1\&.0\&.1" "vchanger Manual" +.TH "VCHANGER" "8" "05/11/2020" "vchanger 1\&.0\&.3" "vchanger Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -40,7 +40,7 @@ vchanger \- Virtual disk\-based autochanger for Bacula network backup system \fBvchanger\fR [\fIOptions\fR] config REFRESH .SH "DESCRIPTION" .sp -The \fBvchanger(8)\fR utility is used to emulate and control a virtual autochanger within the Bacula network backup system environment\&. Backup volumes stored on multiple disk filesystems are mapped to a single set of virtual slots, allowing an unlimited number of virtual drives for concurrent backup jobs and easy, unlimited scaling to any size by simply adding additional disks/filesystems, +The \fBvchanger(8)\fR utility is used to emulate and control a virtual autochanger within the Bacula network backup system environment\&. Backup volumes stored on multiple disk filesystems are mapped to a single set of virtual slots\&. This allows an unlimited number of virtual drives and an unlimited number of virtual slots spread across an unlimited number of physical disk drives to be assigned to a single autochanger\&. This allows unlimited scaling of the cirtual autochanger simply by adding additional disk drives\&. .sp Vchanger is primarily deigned for use with removable disk drives\&. Its ability to interact with Bacula and determine removable drive mount points through udev allow for plug\-n\-play operation when attaching and detaching removable disk drives\&. .sp @@ -142,9 +142,9 @@ command to Bacula if required\&. .sp \fBBacula Interaction\fR .sp -By default, vcahgner will invoke bconsole and issue commands to Bacula when certain operator actions are needed\&. When anything happens that changes the current set of volume files being used, vchanger will invoke bconsole and issue an \fIupdate slots\fR command\&. For example, when the operator attaches a removable drive defined as one of the changer\(cqs magazines, the volume files on the removable drive must be mapped to virtual slots\&. Since the volume\-to\-slot mapping will have changed, Bacula will need to be informed of the change via the \fIupdate slots\fR command\&. The \fBREFRESH\fR command can be invoked to force vchanger to update state info and trigger \fIupdate slots\fR if needed\&. +By default, vcahgner will invoke bconsole and issue commands to Bacula when certain operator actions are needed\&. When anything happens that changes the current set of volume files being used, (the virtual slot to volume file mapping), vchanger will invoke bconsole and issue an \fIupdate slots\fR command\&. For example, when the operator attaches a removable drive defined as one of the changer\(cqs magazines, the volume files on the removable drive must be mapped to virtual slots\&. Since the slot\-to\-volume mapping will have changed, Bacula will need to be informed of the change via the \fIupdate slots\fR command\&. The \fBREFRESH\fR command can be invoked to force vchanger to update state info and trigger \fIupdate slots\fR if needed\&. .sp -Additionally, when new volumes are created with the \fBCREATEVOLS\fR command, vchanger will invoke bconsole and issue a \fIlabel barcodes\fR command to write volume labels on the newly created volume files\&. +Additionally, when new volumes are created with the \fBCREATEVOLS\fR command, vchanger will invoke bconsole and issue a \fIlabel barcodes\fR command to allow Bacula to write volume labels on the newly created volume files\&. .SH "COMMAND LINE OPTIONS" .PP \fB\-u, \-\-user\fR=\fIuid\fR @@ -195,7 +195,7 @@ See the vchangerHowto\&.html file included in the doc directory of the source di \fBvchanger\&.conf(5)\fR .SH "COPYRIGHT" .sp -Copyright 2006\-2015 Josh Fisher +Copyright 2006\-2020 Josh Fisher .sp This is free software; See the source for copying conditions\&. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. .SH "AUTHOR" diff --git a/doc/vchanger.8.asciidoc b/doc/vchanger.8.asciidoc index f3473ef..368d5f8 100644 --- a/doc/vchanger.8.asciidoc +++ b/doc/vchanger.8.asciidoc @@ -3,7 +3,7 @@ VCHANGER(8) Josh Fisher :doctype: manpage :man source: vchanger -:man version: 1.0.1 +:man version: 1.0.3 :man manual: vchanger Manual NAME @@ -27,9 +27,11 @@ DESCRIPTION The *vchanger(8)* utility is used to emulate and control a virtual autochanger within the Bacula network backup system environment. Backup volumes stored on multiple disk filesystems are mapped to a -single set of virtual slots, allowing an unlimited number of virtual -drives for concurrent backup jobs and easy, unlimited scaling to any -size by simply adding additional disks/filesystems, +single set of virtual slots. This allows an unlimited number of +virtual drives and an unlimited number of virtual slots spread +across an unlimited number of physical disk drives to be assigned +to a single autochanger. This allows unlimited scaling of the cirtual +autochanger simply by adding additional disk drives. Vchanger is primarily deigned for use with removable disk drives. Its ability to interact with Bacula and determine removable drive mount @@ -115,18 +117,19 @@ Additionally, the following extended commands are supported. By default, vcahgner will invoke bconsole and issue commands to Bacula when certain operator actions are needed. When anything happens that -changes the current set of volume files being used, vchanger will -invoke bconsole and issue an 'update slots' command. For example, -when the operator attaches a removable drive defined as one of the -changer's magazines, the volume files on the removable drive must be -mapped to virtual slots. Since the volume-to-slot mapping will have -changed, Bacula will need to be informed of the change via the -'update slots' command. The *REFRESH* command can be invoked to force -vchanger to update state info and trigger 'update slots' if needed. +changes the current set of volume files being used, (the virtual slot +to volume file mapping), vchanger will invoke bconsole and issue an +'update slots' command. For example, when the operator attaches a +removable drive defined as one of the changer's magazines, the volume +files on the removable drive must be mapped to virtual slots. Since +the slot-to-volume mapping will have changed, Bacula will need to be +informed of the change via the 'update slots' command. The *REFRESH* +command can be invoked to force vchanger to update state info and +trigger 'update slots' if needed. Additionally, when new volumes are created with the *CREATEVOLS* command, vchanger will invoke bconsole and issue a 'label barcodes' command to -write volume labels on the newly created volume files. +allow Bacula to write volume labels on the newly created volume files. COMMAND LINE OPTIONS -------------------- @@ -172,7 +175,7 @@ SEE ALSO COPYRIGHT --------- -Copyright 2006-2015 Josh Fisher +Copyright 2006-2020 Josh Fisher This is free software; See the source for copying conditions. diff --git a/doc/vchanger.conf.5 b/doc/vchanger.conf.5 index 3586934..e286cfe 100644 --- a/doc/vchanger.conf.5 +++ b/doc/vchanger.conf.5 @@ -2,12 +2,12 @@ .\" Title: vchanger.conf .\" Author: Josh Fisher .\" Generator: DocBook XSL Stylesheets v1.78.1 -.\" Date: 06/03/2015 +.\" Date: 05/11/2020 .\" Manual: vchanger Manual -.\" Source: vchanger.conf 1.0.1 +.\" Source: vchanger.conf 1.0.3 .\" Language: English .\" -.TH "VCHANGER\&.CONF" "5" "06/03/2015" "vchanger\&.conf 1\&.0\&.1" "vchanger Manual" +.TH "VCHANGER\&.CONF" "5" "05/11/2020" "vchanger\&.conf 1\&.0\&.3" "vchanger Manual" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -134,7 +134,7 @@ See the vchangerHowto\&.html file included in the doc directory of the source di \fBvchanger(8)\fR .SH "COPYRIGHT" .sp -Copyright 2006\-2015 Josh Fisher +Copyright 2006\-2020 Josh Fisher .sp This is free software; See the source for copying conditions\&. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\&. .SH "AUTHOR" diff --git a/doc/vchanger.conf.5.asciidoc b/doc/vchanger.conf.5.asciidoc index 378fb41..54d3425 100644 --- a/doc/vchanger.conf.5.asciidoc +++ b/doc/vchanger.conf.5.asciidoc @@ -3,7 +3,7 @@ VCHANGER.CONF(5) Josh Fisher :doctype: manpage :man source: vchanger.conf -:man version: 1.0.1 +:man version: 1.0.3 :man manual: vchanger Manual NAME @@ -112,7 +112,7 @@ SEE ALSO COPYRIGHT --------- -Copyright 2006-2015 Josh Fisher +Copyright 2006-2020 Josh Fisher This is free software; See the source for copying conditions. diff --git a/doc/vchangerHowto.html b/doc/vchangerHowto.html index cb86ccf..9fe2321 100644 --- a/doc/vchangerHowto.html +++ b/doc/vchangerHowto.html @@ -1,1845 +1,1898 @@ - - - - - - - - - - - - -

Vchanger Howto

-

Josh Fisher

-

<jfisher at jaybus dot com>

-

Revision History

-

Revision 1.0.1 2015-06-09

-

Documented changes related to version 1.0.1 of the - software. This is a minor bug fix release.

-

Revision 1.0.0 2015-04-09

-

Documented changes related to version 1.0.0 of the - software. This major version change includes significant changes including - a dynamic and unlimited number of virtual drives and virtual slots, - removal of meta-information files from magazine partitions, interaction - with Bacula via bconsole, udev support, and more.

-

Revision 0.8.6 2010-05-14

-

Documented changes related to version 0.8.6 of the - software.

-

Revision 0.8.5 2010-02-05

-

Documented changes related to version 0.8.5 of the - software.

-

Revision 0.8.4 2009-12-02

-

Documented changes related to version 0.8.4 of the - software.

-

Revision 0.8.3 2009-10-26

-

Documented changes related to version 0.8.3 of the - software, which includes specifying magazine partitions by UUID and the - addition of the LISTMAGS command.

-

Revision 0.8.2 2009-04-14

-

Fixed some config file typos in the howto examples.

-

Revision 0.8.1 2009-01-27

-

Documented addition of command line flags for specifying - uid and gid vchanger should run as when invoked by root. Fixed examples to - coincide with changes to the by-label-fix udev rules for multi-magazine - autochangers.

-

Revision 0.8.0 2008-10-01

-

Initial beta release

-

Table of Contents

- +

1. Introduction

+

This document describes how to utilize disk storage, particularly + removable disk storage devices, as backup media for a virtual autochanger + to implement a backup solution using Bacula, + an open source network backup solution. Bacula can use many types of + backup devices, one of them being robotic tape libraries, also known as + autochangers. Bacula utilizes these devices through an interface known as + the Bacula Autochanger - Interface. Because autochangers are controlled in various - proprietary ways, the Bacula Autochanger Interface provides a generalized - method of interfacing with these devices by issuing commands, (such as - LOAD a tape, UNLOAD a tape, etc.), to an external script or program that - in turn implements the device-specific method of carrying out the desired - action. Vchanger is such a program, implementing the Bacula Autochanger - Interface to act as a virtual autochanger, using files on disk storage in - place of tapes.

-

1.1 Copyright And License

-

This document, VchangerHOWTO, is Copyright (c) 2008-2015 by Josh Fisher. Permission is - granted to copy, distribute and/or modify this document under the terms of - the GNU Free Documentation License, Version 1.1 or any later version - published by the Free Software Foundation; with no Invariant Sections, - with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the - license is available at http://www.gnu.org/copyleft/fdl.html. -

-

1.2 Disclaimer

-

No liability for the contents of this document can be accepted. Use the - concepts, examples and information at your own risk. There may be errors - and inaccuracies which could damage to your system. Though this is highly - unlikely, proceed with caution. The author(s) do not accept responsibility - for your actions.

-

All copyrights are held by their respective owners, unless specifically - noted otherwise. Use of a term in this document should not be regarded as - affecting the validity of any trademark or service mark. Naming of - particular products or brands should not be seen as endorsements.

-

1.3 Credits / Contributors

+ Interface. Because autochangers are controlled in various + proprietary ways, the Bacula Autochanger Interface provides a generalized + method of interfacing with these devices by issuing commands, (such as + LOAD a tape, UNLOAD a tape, etc.), to an external script or program that + in turn implements the device-specific method of carrying out the desired + action. Vchanger is such a program, implementing the Bacula Autochanger + Interface to act as a virtual autochanger, using files on disk storage in + place of tapes.

+

1.1 Copyright And License

+

This document, VchangerHOWTO, is Copyright (c) 2008-2020 by Josh Fisher. Permission is + granted to copy, distribute and/or modify this document under the terms of + the GNU Free Documentation License, Version 1.1 or any later version + published by the Free Software Foundation; with no Invariant Sections, + with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the + license is available at http://www.gnu.org/copyleft/fdl.html. +

+

1.2 Disclaimer

+

No liability for the contents of this document can be accepted. Use the + concepts, examples and information at your own risk. There may be errors + and inaccuracies which could damage to your system. Though this is highly + unlikely, proceed with caution. The author(s) do not accept responsibility + for your actions.

+

All copyrights are held by their respective owners, unless specifically + noted otherwise. Use of a term in this document should not be regarded as + affecting the validity of any trademark or service mark. Naming of + particular products or brands should not be seen as endorsements.

+

1.3 Credits / Contributors

Many thanks to those who have participated on the Vchanger Users - e-mail list with bug reports, testing, and suggestions.

+ e-mail list with bug reports, testing, and suggestions.

Thanks, also, to all those who frequent the Bacula - User's e-mail list, and of course to Kern Sibbald and the other Bacula developers.

-

Bacula® is a registered trademark of Kern Sibbald.

-

Windows® is a registered trademark of Microsoft Corporation in - the United States and other countries.

-

1.4 Feedback

+ User's e-mail list, and of course to Kern Sibbald and the other Bacula developers.

+

Bacula® is a registered trademark of Kern Sibbald.

+

Windows® is a registered trademark of Microsoft + Corporation in the United States and other countries.

+

1.4 Feedback

Vchanger Users - e-mail list hosted by Sourceforge should be used for feedback. - Otherwise, contact information for the author is available in the source - tarball.

-

2. Definitions

-

Bacula is an open source network backup solution. Much more - information about Bacula is available at the Bacula website http://www.bacula.org.

-

An autochanger is a backup storage device consisting of one or - more tape drives, a mechanical tape library where a number of tape - cartridges are stored, and an individually addressable robotic device - capable of physically moving tape cartridges between the tape library and - the tape drives. The tape library contains mechanical slots, each of which - can hold one tape cartridge. In most cases, the slots are located in one - or more mechanical magazines, (tape caddies), that can be inserted into - and removed from one or more or the tape library's magazine bays.

-

A virtual autochanger emulates a tape autochanger using disk - storage, rather than tapes. Note that in the strictest sense, vchanger is - not a tape library emulator, in that it does not implement a device driver - with SCSI command interface. Rather it utilizes an API implemented by - Bacula to manipulate autochangers.

-

A slot is a physical location in a tape library that may - contain one volume (ie. tape cartridge).

-

A magazine is a mechanical tape storage device having a number of - slots into which tape cartridges may be inserted. The tape library of many - autochangers consists of one or more removable magazines, allowing several - tapes at a time to be swapped into or out of the tape library.

-

A bay, or magazine bay, is a physical location in a - tape library into which a magazine (tape caddy) can be inserted.

-

A removable disk drive is a random access disk storage device - that is external to, or easily removable from, a computer system. Examples - are external drive enclosures that connect via USB, eSATA, and hot - swappable SCSI/SAS/SATA drive bays.

-

The term hot swappable refers to hardware devices that may be - attached to and removed from a running computer system.

-

A volume is a sequential stream of data written to a tape or - file that begins with a Bacula volume label that identifies the data - stream.

-

The term drive, unless otherwise stated, refers to one of an - autochanger's drives, a device capable of reading and writing data from/to - a single tape or volume file.

-

3. Overview

-

The Bacula Storage Daemon controls an autochanger by invoking a script or - program, passing it command line arguments defined by the Bacula + e-mail list hosted by Sourceforge should be used for feedback. + Otherwise, contact information for the author is available in the source + tarball.

+

2. Definitions

+

Bacula is an open source network backup solution. Much more + information about Bacula is available at the Bacula website http://www.bacula.org.

+

An autochanger is a backup storage device consisting of one or + more tape drives, a mechanical tape library where a number of tape + cartridges are stored in slots, and an individually addressable robotic + device capable of physically moving tape cartridges between the tape + library slots and the tape drives. The tape library contains mechanical + slots, each of which can hold one tape cartridge. In most cases, the slots + are located in one or more mechanical magazines, (tape caddies), that can + be inserted into and removed from one or more or the tape library's + magazine bays.

+

A vchanger autochanger emulates a tape autochanger using files on + disk storage in place of tapes. Note that in the strictest sense, vchanger + is not a tape library emulator, in that it does not implement a device + driver with SCSI command interface. Rather it utilizes an API implemented + by Bacula to manipulate autochangers.

+

A slot is a physical location in a tape library that may + contain one volume (ie. tape cartridge).

+

A magazine is a mechanical tape storage device having a number of + slots into which tape cartridges may be inserted. The tape library of many + autochangers consists of one or more removable magazines, allowing several + tapes at a time to be swapped into or out of the tape library.

+

A bay, or magazine bay, is a physical location in a + tape library into which a magazine (tape caddy) can be inserted.

+

A removable disk drive is a random access disk storage device + that is external to, or easily removable from, a computer system. Examples + are external drive enclosures that connect via USB, eSATA, and hot + swappable SCSI/SAS/SATA drive bays.

+

The term hot swappable refers to hardware devices that may be + attached to and removed from a running computer system.

+

A volume is a sequential stream of data written to a tape or + file that begins with a Bacula volume label that identifies the data + stream.

+

The term drive, unless otherwise stated, refers to one of an + autochanger's drives, a device capable of reading and writing data from/to + a single volume at a time.

+

3. Overview

+

The Bacula Storage Daemon controls an autochanger by invoking a script or + program, passing it command line arguments defined by the Bacula Autochanger - Interface. The interface defines a set of commands and associated - arguments to load and unload the autochanger's drives, determine which - volumes are available in the autochanger's slots, and to determine from - which slot a drive was loaded or if it is empty. The interface is an - abstraction, allowing Bacula to communicate with various different - autochanger hardware devices through the use of a common set of commands. - It is the script or program's responsibility to interpret these Bacula - Autochanger Interface commands and cause the underlying hardware to - perform the requested action. Vchanger is such a program, accepting Bacula - Autochanger Interface commands and treating a group of disk file systems - as a virtual autochanger. Rather than loading tapes from library slots - into tape drives, as the mtx-changer script does for tape autochanger - hardware, vchanger "loads" disk file volumes from virtual slots into - virtual drives by creating symlinks pointing to volume files located on - one or more filesystems or directories assigned to the virtual - autochanger.

-

Using autochanger interface commands, Bacula interacts with a vchanger - autochanger in exactly the same way as it interacts with a tape - autochanger. Therefore to understand how Bacula interacts with a vchanger - autochanger, it is necessary to first understand how Bacula interacts with - a tape autochanger. An autochanger is defined in the Bacula Storage Daemon - configuration file as an Autochanger - resource. The Autochanger + Interface. The interface defines a set of commands and associated + arguments to load and unload the autochanger's drives, determine which + volumes are available in the autochanger's slots, and to determine from + which slot a drive was loaded or if it is empty. The interface is an + abstraction, allowing Bacula to communicate with various different + autochanger hardware devices through the use of a common set of commands. + It is the script or program's responsibility to interpret these Bacula + Autochanger Interface commands and cause the underlying hardware to + perform the requested action. Vchanger is such a program, accepting Bacula + Autochanger Interface commands and treating a group of disk file systems + as a virtual autochanger. Rather than loading tapes from library slots + into tape drives, as the mtx-changer script does for tape autochanger + hardware, vchanger "loads" disk file volumes from virtual slots into + virtual drives by creating symlinks pointing to volume files located on + one or more virtual magazines, where a virtual magazine is a disk file + system or a directory.

+

Using autochanger interface commands, Bacula interacts with a vchanger + autochanger in exactly the same way as it interacts with a tape + autochanger. Therefore to understand how Bacula interacts with a vchanger + autochanger, it is necessary to first understand how Bacula interacts with + a tape autochanger. An autochanger is defined in the Bacula Storage Daemon + configuration file as an Autochanger + resource. The Autochanger resource defines the Changer - Device to be the system file name (device node) of the tape + Device to be the system file name (device node) of the tape library's robot and the Changer - Command to be the command that Bacula will - invoke to perform Bacula Autochanger Interface commands. Normally this - command will be the mtx-changer script that is shipped with Bacula, of - course customized and/or configured to operate with the particular + Command to be the command that Bacula will + invoke to perform Bacula Autochanger Interface commands. Normally this + command will be the mtx-changer script that is shipped with Bacula, of + course customized and/or configured to operate with the particular autochanger hardware. Additionally, the Autochanger resource defines a list of one or more Device - resources, (also defined in the Storage Daemon configuration), - that belong to the autochanger. Each of an autochanger's tape drives is - defined as a separate Device + resources, (also defined in the Storage Daemon configuration), + that belong to the autochanger. Each of an autochanger's tape drives is + defined as a separate Device resource. Each tape drive's Device resource defines its Archive - Device to be the device node of one of the autochanger's tape - drives and its Drive - Index that defines which of the autochanger's tape drives that - particular device node pertains to. An example configuration for a two - drive autochanger is given below.

-
# /etc/bacula/bacula-sd.conf
Autochanger {
Name = tapechgr
Changer Device = /dev/sg3
Changer Command = "/etc/bacula/mtx-changer %c %o %S %a %d"
Device = tapechgr-0, tapechgr-1
}
Device {
Name = tapechgr-0
Drive Index = 0
Autochanger = yes
Archive Device = /dev/nst0
}
Device {
Name = tapechgr-1
Drive Index = 1
Autochanger = yes
Archive Device = /dev/nst1
}
-

When Bacula needs to load a tape from one of the library's slots into one + Device to be the device node of one of the autochanger's tape + drives and its Drive + Index that defines which of the autochanger's tape drives that + particular device node pertains to. An example configuration for a two + drive autochanger is given below.

+
# /etc/bacula/bacula-sd.conf
Autochanger {
Name = tapechgr
Changer Device = /dev/sg3
Changer Command = "/etc/bacula/mtx-changer %c %o %S %a %d"
Device = tapechgr-0, tapechgr-1
}
Device {
Name = tapechgr-0
Drive Index = 0
Autochanger = yes
Archive Device = /dev/nst0
}
Device {
Name = tapechgr-1
Drive Index = 1
Autochanger = yes
Archive Device = /dev/nst1
}
+

When Bacula needs to load a tape from one of the library's slots into one of its tape drives, it will invoke the Changer - Command, (the mtx-changer script), with positional parameters, + Command, (the mtx-changer script), with positional parameters, where parameter 1 is the Changer - Device, parameter 2 is the command to be performed ("LOAD" in - this case), parameter 3 is the library slot containing the tape to be + Device, parameter 2 is the command to be performed ("LOAD" in + this case), parameter 3 is the library slot containing the tape to be loaded, parameter 4 is the Archive - Device of the Device + Device of the Device resource (tape drive) to be loaded, and parameter 5 is the Drive - Index of the Device - resource to be loaded. Once the needed tape is loaded into a drive, Bacula + Index of the Device + resource to be loaded. Once the needed tape is loaded into a drive, Bacula will open the device node specified by the Archive - Device (the device node of the tape drive) to read or write - volume data from or to the tape.

-

Since a vchanger autochanger is treated like a tape autochanger, the - configuration for a vchanger autochanger looks very much the same,

-
# /etc/bacula/bacula-sd.conf
Autochanger {
Name = vchgr
Changer Device = /etc/vchanger/vchgr.conf
Changer Command = "/usr/bin/vchanger %c %o %S %a %d"
Device = vchgr-0, vchgr-1
}
Device {
Name = vchgr-0
Drive Index = 0
Autochanger = yes
Archive Device = /var/spool/vchanger/vchgr/0
}
Device {
Name = vchgr-1
Drive Index = 1
Autochanger = yes
Archive Device = /var/spool/vchanger/vchgr/1
} -
-

One obvious difference is that vchanger is invoked as the Changer - Command in place of the mtx-changer script, and with the Changer - Device passed as parameter 1 being the path to a vchanger - configuration file, rather than the device node of a tape library robot. - The only other difference is the Archive - Device setting in each of the Device - resources. Rather than the device node of a physical tape drive, - the Archive Device - of a vchanger autochanger's Device - resource specifies the path to a symlink that will act as a - virtual drive. When vchanger is invoked with the LOAD command, it creates - this symlink pointing to the volume file associated with the requested - slot number. This slot number is an index into a - virtual-slot-to-volume-file map maintained by vchanger. Just like the tape - autochanger, once the needed volume is "loaded", (ie. the symlink has been + Device (the device node of the tape drive) to read or write + volume data from or to the tape.

+

Since a vchanger autochanger is treated like a tape autochanger, the + configuration for a vchanger autochanger looks very much the same,

+
# /etc/bacula/bacula-sd.conf
Autochanger {
Name = vchgr
Changer Device = /etc/vchanger/vchgr.conf
Changer Command = "/usr/bin/vchanger %c %o %S %a %d"
Device = vchgr-0, vchgr-1
}
Device {
Name = vchgr-0
Drive Index = 0
Autochanger = yes
Archive Device = /var/spool/vchanger/vchgr/0
}
Device {
Name = vchgr-1
Drive Index = 1
Autochanger = yes
Archive Device = /var/spool/vchanger/vchgr/1
} +
+

One obvious difference is that Changer + Command defines the path to the vchanger binary, rather than the + mtx-changer script, with + parameter 1 being the path to a vchanger configuration file, rather than + the device node of a tape library robot. The only other difference is the + Archive Device + setting in each of the Device + resources associated with the Autochanger resource. + Rather than the device node of a physical tape drive, the Archive + Device of a vchanger autochanger's Device + resource specifies the path to a symlink that will act as a + virtual drive. When vchanger is invoked with the LOAD command, it creates + this symlink pointing to the volume file associated with the requested + slot number. This slot number is an index into a + virtual-slot-to-volume-file map maintained by vchanger. Just like the tape + autochanger, once the needed volume is "loaded", (ie. the symlink has been created), Bacula will open the device specified by the Archive - Device to read or write volume data, in this case a regular file - instead of a tape drive device node. In the Unix way of treating - everything as a file, that is really only a minor difference. Details of + Device to read or write volume data, in this case a regular file + instead of a tape drive device node. In the Unix way of treating + everything as a file, that is really only a minor difference. Details of the vchanger autochanger implementation are given in section - 4 below.

-

3.1. What About Bacula's Native Virtual - Autochanger Support?

+ 4 below.

+

3.1. What About Bacula's Native Virtual + Autochanger Support?

A Device resource's Archive Device directive - determines the i/o device that will be used to read or write volume data. + determines the i/o device that will be used to read or write volume data. Bacula handles the opening of the path specified as the Archive - Device differently depending upon whether or not that path + Device differently depending upon whether or not that path specifies a filesystem directory. If a Device resource's Archive Device specifies a directory, then when that Device - is opened Bacula will open one of the files in that directory for + is opened Bacula will open one of the files in that directory for reading or writing. Otherwise, if the Archive - Device is not a directory, then Bacula will directly open the + Device is not a directory, then Bacula will directly open the path specified by the Archive - Device for reading or writing.

-

For a Device - where the Archive - Device is not a directory, there can only possibly be one volume - available for reading or writing at any one time. For example, a tape - drive can only have one tape loaded at a time. For a + Device for reading or writing.

+

For a Device + where the Archive + Device is not a directory, there can only possibly be one volume + available for reading or writing at any one time. For example, a tape + drive can only have one tape loaded at a time. For a Device resource where the Archive - Device is a directory there, can be many volumes available. - Though a filesystem directory may contain multiple volume files, and - unlike a tape drive, could allow multiple volumes to be opened + Device is a directory there, can be many volumes available. + Though a filesystem directory may contain multiple volume files, and + unlike a tape drive, could allow multiple volumes to be opened simultaneously, Bacula purposefully limits a Device - resource to reading or writing only one volume at a time. This - abstraction is a good thing! It allows the remainder of Bacula's complex, + resource to reading or writing only one volume at a time. This + abstraction is a good thing! It allows the remainder of Bacula's complex, multi-threaded logic to treat all Device resources in the same, consistent way. One Device - may read or write only one volume at a time.

-

Because this single volume restriction is not technically necessary for a + may read or write only one volume at a time.

+

Because this single volume restriction is not technically necessary for a disk filesystem directory, one could define several Device - resources, all specifying the same directory path as their Archive Device. - Each of these Device - resources could have one volume file opened at a time. This will - allow multiple volume files in that directory to be opened simultaneously, - but will also force the operator to manually allocate backup jobs across - the available Devices. + resources, all specifying the same directory path as their Archive Device. + Each of these Device + resources could have one volume file opened at a time. This will + allow multiple volume files in that directory to be opened simultaneously, + but will also force the operator to manually allocate backup jobs across + the available Devices. The Autochanger resource provides a means to group multiple Devices - together into a single addressable resource. Jobs can then be + together into a single addressable resource. Jobs can then be configured to write to the single Autochanger - resource and Bacula will automatically handle the selection of an + resource and Bacula will automatically handle the selection of an available Device from - the group of Devices - assigned to that Autochanger.

+ the group of Devices + assigned to that Autochanger.

An Autochanger resource also defines a Changer Command and Changer Device. Ordinarily, for a tape autochanger, the Changer - Device defines the device node of a robotic tape library device - that moves tapes between library slots and one or more tape drives, and - the Changer Command - specifies a script or program that Bacula invokes to command the robot to - move tapes between library slots and tape drives. - When the Changer + Device defines the device node of a robotic tape library device + that moves tapes between library slots and one or more tape drives, and + the Changer Command + specifies a script or program that Bacula invokes to command the robot to + move tapes between library slots and tape drives. + When the Changer Command  and Changer - Device are both null, Bacula has special handling that allows an - Autochanger + Device are both null, Bacula has special handling that allows an + Autochanger resource to be used with multiple disk storage Device - resources as a disk-based virtual autochanger.

-

For example, the following placed in the Bacula Storage Daemon - configuration defines a "native" disk virtual autochanger named FileChgr1 + resources as a disk-based virtual autochanger.

+

For example, the following placed in the Bacula Storage Daemon + configuration defines a native disk virtual autochanger named FileChgr1 with two virtual drives. The two Device - resources (virtual drives) allow two volume files to be open at + resources (virtual drives) allow two volume files to be open at the same time, and of course more than two Device - resources could be configured to allow as many virtual drives as - needed.

-
Autochanger {
-    Name = FileChgr1
-    Device = FileChgr1-Dev1, FileChgr1-Dev2
-    Changer Command = /dev/null
-    Changer Device = /dev/null
-}
-
-Device {
-    Name = FileChgr1-Dev1
-    Media Type = File1
-    Archive Device = /backups
-    LabelMedia = yes          # lets Bacula label unlabeled media
-    Random Access = Yes
-    AutomaticMount = yes
-    RemovableMedia = yes
-    AlwaysOpen = no
-    Maximum Concurrent Jobs = 5
-}
-
-Device {
-    Name = FileChgr1-Dev2
-    Media Type = File1
-    Archive Device = /backups
-    LabelMedia = yes          # lets Bacula label unlabeled media
-    Random Access = Yes
-    AutomaticMount = yes
-    RemovableMedia = yes
-    AlwaysOpen = no
-    Maximum Concurrent Jobs = 5
-}
+ resources could be configured to allow as many virtual drives as + needed.

+
Autochanger {
+    Name = FileChgr1
+    Device = FileChgr1-Dev1, FileChgr1-Dev2
+    Changer Command = /dev/null
+    Changer Device = /dev/null
+}
+
+Device {
+    Name = FileChgr1-Dev1
+    Media Type = File1
+    Archive Device = /backups
+    LabelMedia = yes          # lets Bacula label unlabeled media
+    Random Access = Yes
+    AutomaticMount = yes
+    RemovableMedia = yes
+    AlwaysOpen = no
+    Maximum Concurrent Jobs = 5
+}
+
+Device {
+    Name = FileChgr1-Dev2
+    Media Type = File1
+    Archive Device = /backups
+    LabelMedia = yes          # lets Bacula label unlabeled media
+    Random Access = Yes
+    AutomaticMount = yes
+    RemovableMedia = yes
+    AlwaysOpen = no
+    Maximum Concurrent Jobs = 5
+}

Notice that both Device resources specify the same Archive Device and Media - Type. This is because the Storage - resource in the Director configuration may only specify a single - Media Type. A - volume is given the Media - Type of the Device - it is written with. Since a location may have several tape drives - of the same type, Bacula uses the Media - Type to determine which volumes may be loaded into which Devices. - This, for example, allows Bacula to load a LTO-5 tape into any available - LTO-5 tape drive, while preventing Bacula from attempting to load the - LTO-5 tape into a LTO-2 drive. The same applies to the native virtual - autochanger. A volume file in one Device's - directory would not be found in another Device's + Type. This is because the corresponding Autochanger + resource in the Director's bacula-dir.conf configuration may only + specify a single Media + Type. A volume is given the Media + Type of the Device + it is written with. Since a location may have several tape drives + of the same type, Bacula uses the Media + Type to determine which volumes may be loaded into which Devices. + This, for example, allows Bacula to load a LTO-5 tape into any available + LTO-5 tape drive, while preventing Bacula from attempting to load the + LTO-5 tape into a LTO-2 drive. The same applies to the native virtual + autochanger. A volume file in one Device's + directory would not be found in another Device's directory, and so with each Device having a different Archive Device directory, it would also be necessary for the Devices to have different Media - Types to prevent Bacula from attempting to load a Device + Types to prevent Bacula from attempting to load a Device with a volume file that does not exist on its Archive - Device. That in turn means that all of the autochanger's Devices - could not be associated to a single Storage - resource in the - Director configuration, and so defeats the purpose.

-

3.2. Why vchanger?

+ Device. That in turn means that all of the autochanger's Devices + could not be associated to a single Autochanger + resource in the + Director configuration.

+

3.2. Why vchanger?

Because Bacula currently requires a Storage - resource to specify a single Media - Type, the native virtual autochanger is, for practical purposes, - limited to utilizing a single directory as volume file storage. If volume - files are contained in multiple filesystems, such as removable drives, - then those filesystems can only be mounted at that directory one at a - time. With vchanger, an unlimited number of filesystems may be - simultaneously mounted at different mount point directories with all - volume files on all filesystems having the same Media - Type. This allows an unlimited number of simultaneously available - volume files and an unlimited number of Device - resources (virtual drives), where any volume file on any + and Autochanger resources to specify a single Media + Type, the native virtual autochanger is, for practical purposes, + limited to utilizing a single directory as volume file storage for a + particular Media Type. If volume files are contained in + multiple filesystems, such as removable drives, then those filesystems can + only be mounted at that directory one at a time. With vchanger, an + unlimited number of filesystems may be simultaneously mounted at different + mount point directories with all volume files on all filesystems having + the same Media Type. + This allows an unlimited number of simultaneously available volume files + and an unlimited number of Device + resources (virtual drives), where any volume file on any filesystem can be "loaded" into any of the autochanger's Device - resources using only a single Storage - resource in the Bacula Director. This gives vchanger the - following advantages:

-
    -
  • Available volume storage space can be easily scaled by adding - additional filesystems or directories
  • -
  • Scaling available volume storage space requires no change to the - Bacula configuration and no restart or reload of any Bacula daemons
  • -
  • All volumes have the same Media Type and so can be moved between - filesystems without any need to update volume information in the Bacula - catalog.
  • -
  • Filesystems can be specified by UUID and vchanger will query the OS - for the mount point when needed. This allows any type of automounting to - be used and the filesystems to be mounted at arbitrary paths.
  • -
  • It is very restore friendly. When volumes needed for a restore are - located on different filesystems, for example on multiple removable disk - drives, all filesystems containing the volumes needed for a restore can - be mounted simultaneously, eliminating the need for operator - interventions to swap removable drives.
  • -
-

By default, vchanger supports integration with Bacula through issuing - commands via bconsole. Whenever a change is detected in the volume file to - virtual slot mapping, for example when a removable drive is attached or - detached, vchanger will issue an update + resources using only a single Autochanger + resource in the Director's bacula-dir.conf file. This gives + vchanger the following advantages:

+
    +
  • Available volume storage space can be easily scaled by adding + additional filesystems or directories
  • +
  • Scaling available volume storage space requires no change to the + Bacula configuration and no restart or reload of any Bacula daemons
  • +
  • All volumes have the same Media Type and so can be + moved between filesystems without any need to update volume information + in the Bacula catalog (other than an update slots command in + bconsole to update the volume's slot number).
  • +
  • Filesystems can be specified by UUID and vchanger will query the OS + for the mount point when needed. This allows any type of automounting to + be used and the filesystems to be mounted at arbitrary paths.
  • +
  • It is very restore friendly. When volumes needed for a restore are + located on different filesystems, for example on multiple removable disk + drives, all filesystems containing the volumes needed for a restore can + be mounted simultaneously, eliminating the need for operator + interventions to swap removable drives.
  • +
+

By default, vchanger supports integration with Bacula through issuing + commands via bconsole. Whenever a change is detected in the + volume-file-to-virtual-slot mapping, for example when a removable drive is + attached or detached, vchanger will issue an update slots command to synchronize Bacula's view of the autochanger's - available volumes. Also, when vchanger is used to add volume files to an - autochanger, a label barcodes - command is automatically issued to label the new volumes.

-

Vchanger also automates the generation of udev rules and scripts that may - be used to automatically mount and unmount the filesystems defined in - autochanger configuration files. When a removable drive is attached or - detached, a script may be launched by udev to mount or unmount the drive's - partition and issue an update slots - command via bconsole. These udev rules and scripts allow the attaching and - detaching of  removable drives used by vchanger to be a true - plug-n-play operation.
-

-

4. Virtual Autochanger Implementation

-

Vchanger implements the following Bacula - Autochanger Interface commands:

- - - - - - - - - - - - - - - - - - - - - - - - -
-

LIST

-
-

Lists the slots and barcodes of currently inserted volumes

-
-

LOAD

-
-

Loads a volume from a slot into a drive

-
-

LOADED

-
-

Returns the one-based slot number where the volume currently - loaded into a drive came from, or zero if the drive is unloaded.

-
-

SLOTS

-
-

Returns the number of slots in the autochanger

-
-

UNLOAD

-
-

Unloads a volume from a drive and moves it back into a slot

-
-

Vchanger also implements the following - undocumented command defined in mtx-changer since Bacula version 5.1.0:

- - - - - - - - -
-

LISTALL

-
-

Lists status information for all drives and slots

-
-

Vchanger also implements three vchanger-specific - extended commands that are not part of the documented Bacula Autochanger - Interface:

- - - - - - - - - - - - - - - - -
-

LISTMAGS

-
-

List the status of configured magazines

-
-

CREATEVOLS

-
-

Creates empty volume files on a magazine.

-
-

REFRESH

-
-

Cause vchanger to refresh virtual drive and magazine state and - communicate any changes to Bacula.

-
-

The Bacula - Storage Daemon calls vchanger with 5 positional parameters defined as:

- - - - - - + + + + + + + + + + + + + + + + + + +
-

Param 1

-
-

Path given by the + available volumes. Also, when vchanger is used to add volume files to an + autochanger, a label barcodes + command is automatically issued to label the newly created volumes.

+

Vchanger also provides scripts that may be used to automate the + generation of udev rules and automatically mount and unmount the + filesystems defined in autochanger configuration files. When a removable + drive is attached or detached, a script may be launched by udev to mount + or unmount the drive's partition and issue an update + slots command via bconsole. These udev rules and scripts allow + the attaching and detaching of removable drives used by vchanger to be a + true plug-n-play operation.
+

+

4. Virtual Autochanger Implementation

+

Vchanger implements the following Bacula + Autochanger Interface commands:

+ + + + + + + + + + + + + + + + + + + + + + + + +
+

LIST

+
+

Lists the slots and barcodes of currently inserted volumes

+
+

LOAD

+
+

Loads a volume from a slot into a drive

+
+

LOADED

+
+

Returns the one-based slot number where the volume currently + loaded into a drive came from, or zero if the drive is unloaded.

+
+

SLOTS

+
+

Returns the number of slots in the autochanger

+
+

UNLOAD

+
+

Unloads a volume from a drive and moves it back into a slot

+
+

Vchanger also implements the following + undocumented command defined in mtx-changer since Bacula version 5.1.0:

+ + + + + + + + +
+

LISTALL

+
+

Lists status information for all drives and slots

+
+

Vchanger also implements three vchanger-specific + extended commands that are not part of the documented Bacula Autochanger + Interface:

+ + + + + + + + + + + + + + + + +
+

LISTMAGS

+
+

List the status of configured magazines

+
+

CREATEVOLS

+
+

Creates empty volume files on a magazine.

+
+

REFRESH

+
+

Cause vchanger to refresh virtual drive and magazine state and + communicate any changes to Bacula.

+
+

The Bacula + Storage Daemon calls vchanger with 5 positional parameters defined as:

+ + + + + + - - - - - - - - - - - - - - - - - - -
+

Param 1

+
+

Path given by the Changer Device directive - in the Autochanger - resource. For - vchanger, this should define the path to the - autochanger's  vchanger - configuration file.

-
-

Param 2

-
-

Bacula Autochanger Interface command to execute

-
-

Param 3

-
-

The one-based slot number that the command is to act upon

-
-

Param 4

-
-

Path given by the Archive Device - directive of the Device - resource corresponding to the drive number given in parameter 5 - (ignored by vchanger)

-
-

Param 5

-
-

The zero-based drive number that the command is to act upon

-
-

Each vchanger autochanger requires a unique - configuration file and work directory. The configuration file specifies - the location of its work directory, defines the directories and/or - filesystems that are to be used for volume file storage, and specifies the + in the Autochanger + resource. For + vchanger, this should define the path to the + autochanger's  vchanger + configuration file.

+
+

Param 2

+
+

Bacula Autochanger Interface command to execute

+
+

Param 3

+
+

The one-based slot number that the command is to act upon

+
+

Param 4

+
+

Path given by the Archive Device + directive of the Device + resource corresponding to the drive number given in parameter 5 + (ignored by vchanger)

+
+

Param 5

+
+

The zero-based drive number that the command is to act upon

+
+

Each vchanger autochanger requires a unique + configuration file and work directory. The configuration file specifies + the location of its work directory, defines the directories and/or + filesystems that are to be used for volume file storage, and specifies the Storage resource name, (as defined by the Name directive of the Storage - resource in bacula-dir.conf), that this autochanger is associated - with. An autochanger's work directory is where vchanger will keep virtual - drive and magazine state information and where the symlinks to volume - files will be created for loaded virtual drives.

-

4.1. Virtual Magazines

-

Virtual autochangers using vchanger treat the directories and filesystems - assigned to them as "virtual magazines", each able to contain a variable - number of volume files. This is analogous to the mechanical tape - magazines, (tape caddies), that many tape autochangers use. These tape - autochangers have one or more bays into which removable tape magazines can - be inserted. Each tape magazine contains a fixed number of slots into - which individual tape cartridges may be inserted. The principle difference - is that tape magazines have a fixed number of physical slots, whereas - vchanger virtual magazines have a variable and unlimited number of virtual - slots.

-

Virtual magazines are filesystems or directories that are assigned to an - autochanger by including Magazine directive lines in the vchanger + resource in bacula-dir.conf), that this autochanger is associated + with. An autochanger's work directory is where vchanger will keep virtual + drive and magazine state information and where the symlinks to volume + files will be created for loaded virtual drives.

+

4.1. Virtual Magazines

+

Vchanger autochangers treat the directories and filesystems assigned to + them as "virtual magazines", each able to contain a variable number of + volume files. This is analogous to the mechanical tape magazines, (tape + caddies), that many tape autochangers use. These tape autochangers have + one or more bays into which removable tape magazines can be inserted. Each + tape magazine contains a fixed number of slots into which individual tape + cartridges may be inserted. The principle difference is that tape + magazines have a fixed number of physical slots, whereas vchanger virtual + magazines have a variable and unlimited number of virtual slots.

+

Virtual magazines are filesystems or directories that are assigned to an + autochanger by including Magazine directive lines in the vchanger configuration file. The order in which the Magazine - directive lines appear in the configuration file  determines - which "virtual bay" the magazine is "inserted" into. The first occurrence - of the Magazine - directive in the vchanger configuration file specifies the magazine in bay - 0, the second occurrence specifies the magazine in bay 1, and so on. The - virtual bay is simply a zero-based index into the array of + directive lines appear in the configuration file  determines + which "virtual bay" the magazine is "inserted" into. The first occurrence + of the Magazine + directive in the vchanger configuration file specifies the magazine in bay + 0, the second occurrence specifies the magazine in bay 1, and so on. The + virtual bay is simply a zero-based index into the array of filesystems/directories defined by Magazine - directives in the vchanger configuration file, and will be - referred to as a "magazine index".

-

The magazine index, or virtual bay, for a magazine is static and + directives in the vchanger configuration file, and will be + referred to as a "magazine index".

+

The magazine index, or virtual bay, for a magazine is static and determined solely by the order in which the Magazine - definitions appear in the vchanger configuration file. This - allows assigning integer numbers to the magazines to ease tracking of - multiple magazines. For example, consider an autochanger associated with a - Storage resource - in bacula-dir.conf named "changer1" for which we are using 10 RDX - cartridges as its virtual magazines. The default volume naming scheme used - by the CREATEVOLS command will name the first volume file created on the + definitions appear in the vchanger configuration file. This + allows assigning integer numbers to the magazines to ease tracking of + multiple magazines. For example, consider an autochanger associated with + an Autochanger resource + in bacula-dir.conf named "changer1" for which we are using 10 RDX + cartridges as its virtual magazines. The default volume naming scheme used + by the CREATEVOLS command will name the first volume file created on the first Magazine as - "changer1_0_0", the 23rd volume created on the second Magazine - as "changer1_1_22", and so on. When Bacula later requests a - volume that is on a RDX cartridge that is not currently mounted, this - volume file naming scheme will make it very easy to determine which RDX - cartridge needs to be attached in order to make the requested volume - available.

-

On most systems, filesystems to be used as magazines may be specified by - filesystem UUID, rather than mountpoint directory. This is useful on - systems where udev, Gnome Nautilus, other dbus-enabled automounters, and - etc. are used to automatically mount removable drives whenever they are - attached. Since vchanger is not normally running as root, it makes no - attempt to mount those filesystems. It only queries the system to - determine the current mount point of an already mounted filesystem.

-

Note that there is nothing special about the directories and filesystems - being used as magazines. A directory containing volume files created by - Bacula's native disk storage handling can be used as a vchanger magazine. - Likewise, a directory containing volumes created using a vchanger - autochanger can later be used with Bacula's native disk storage. Also, - individual volume files can be moved between the two. The only stipulation + "changer1_0000_0000", the 23rd volume created on the second Magazine + as "changer1_0001_0022", and so on. When Bacula later requests a + volume that is on a RDX cartridge that is not currently mounted, this + volume file naming scheme will make it very easy to determine which RDX + cartridge needs to be attached in order to make the requested volume + available.

+

The default volume naming scheme used by the CREATEVOLS command is not + required. The CREATEVOLS command accepts a pattern string that may be used + to name the volume files it creates. In fact, the CREATEVOLS command is + simply a convenience. A volume file may be created by creating an empty + file on the magazine using touch, etc., and then issuing a label + barcodes command in bconsole. Volume files may have any + arbitrary name. Existing volume files may be moved from one magazine to + another, requiring only an update slots command in bconsole + to inform Bacula of the change in slot assignments.

+

On most systems, filesystems to be used as magazines may be specified by + filesystem UUID, rather than mountpoint directory. This is useful on + systems where udev, Gnome Nautilus, other dbus-enabled automounters, and + etc. are used to automatically mount removable drives whenever they are + attached. Since vchanger is not normally running as root, it makes no + attempt to mount those filesystems. It only queries the system to + determine the current mount points of its assigned magazine filesystems.

+

Note that there is nothing special about the directories and filesystems + being used as magazines. A directory containing volume files created by + Bacula's native disk storage handling can be used as a vchanger magazine. + Likewise, a directory containing volumes created using a vchanger + autochanger can later be used with Bacula's native disk storage. Also, + individual volume files can be moved between the two. The only stipulation is that the volume must have the same Media - Type as the bacula-dir.conf Storage - resource that it will be used with. If existing volumes are being moved to - a Storage - resource that has - a different Media + Type as the bacula-dir.conf Storage + or Autochanger resource that it will be used with. If existing + volumes are being moved to a Storage + or Autochanger resource that + has a different Media Type, then it will be necessary to manually update the Media - Type for those volumes.

-

4.2. Volume Files

-

A volume file is simply a regular file on one of the defined magazine - directories or file systems. A file cannot be used by Bacula until a - Bacula volume label has been written to it. Bacula supports bulk labeling - of volumes using barcodes. Many tape autochangers have barcode reader - hardware that can read the name of a tape cartridge from a barcode printed - on an adhesive paper label attached to the tape cartridge. The tape - autochanger can be commanded to read the barcodes and list the names of + Type for those volumes.

+

4.2. Volume Files

+

A volume file is simply a regular file on one of the defined magazine + directories or filesystems. A file cannot be used by Bacula until a Bacula + volume label has been written to it. Bacula supports bulk labeling of + volumes using barcodes. Many tape autochangers have barcode reader + hardware that can read the name of a tape cartridge from a barcode printed + on an adhesive paper label attached to the tape cartridge. The tape + autochanger can be commanded to read the barcodes and list the names of the tapes it finds in each of its library slots. Bacula's label - barcodes command acquires this information from the tape - autochanger and writes a Bacula volume label to each unlabeled tape, - naming it according to the tape's barcode. Vchanger emulates a barcode - reader by using the volume file's filename as the barcode for that volume.

-

While it is recommended to use the default volume naming scheme - implemented by the CREATEVOLS command, it is not required and volume files - can have any filename. Just keep in mind that vchanger uses the volume - file's filename as its barcode label. This means that the filename must - match the volume label that was written to the file when the volume was - labeled. Volumes cannot be renamed by simply renaming the file to a - different filename. They would also have to have a new Bacula volume label - written to them.

-

4.3. Virtual Slots

-

Each time vchanger is invoked it maps the volume files on all attached - magazines to a virtual slot number. State information kept in the - autochanger's work directory is used to keep volume-to-slot mapping as - consistent as possible when removable disk drives are attached and - detached from the system. Drive state files contain information about the - volume last loaded into a virtual drive and are named 'drive_state-N', - where N is the drive number. Magazine state files contain information - about the magazines that were attached when vchanger was last invoked and - are named 'bay_state-N', where N is the magazine index.

-

Whenever anything happens to change the volume-to-slot mapping, Bacula - must be informed of the change. This is because Bacula tracks the contents - of autochanger slots in its catalog, as it must know which volumes are - available and where they are located in order to decide which volumes - should be assigned to which jobs and which volumes should be loaded into - which drives. Vchanger notifies Bacula when something changes by invoking + barcodes command acquires this information from the tape + autochanger and writes a Bacula volume label to each unlabeled tape, + naming it according to the tape's barcode. Vchanger emulates a barcode + reader by using the volume file's filename as the barcode for that volume. +

+

While it is recommended to use the default volume naming scheme + implemented by the CREATEVOLS command, it is not required and volume files + can have any filename. Just keep in mind that vchanger uses the volume + file's filename as its barcode label. This means that the filename must + match the volume label that was written to the file when the volume was + labeled. Volumes cannot be renamed by simply renaming the file to a + different filename. if renamed, they should also have a new Bacula volume + label written to them.

+

4.3. Virtual Slots

+

Each time vchanger is invoked it maps the volume files on all currently + mounted magazines to a virtual slot number. State information kept in the + autochanger's work directory is used to keep volume-to-slot mapping as + consistent as possible as removable disk drives are attached and detached + from the system. Drive state files contain information about the volume + last loaded into a virtual drive and are named 'drive_state-N', where N is + the drive number. Magazine state files contain information about the + magazines that were attached when vchanger was last invoked and are named + 'bay_state-N', where N is the magazine index.

+

Whenever anything happens to change the volume-to-slot mapping, Bacula + must be informed of the change. This is because Bacula tracks the contents + of autochanger slots in its catalog, as it must know which volumes are + available and where they are located in order to decide which volumes + should be assigned to which jobs and which volumes should be loaded into + which drives. Vchanger notifies Bacula when something changes by invoking bconsole and issuing an update - slots command to cause Bacula to update its catalog info for the - affected autochanger.

-

Note that vchanger is not dependent on the state information kept in an - autochanger's work directory. Vchanger recalculates the current state of - virtual drives, bays, and slots whenever invoked. However, the state + slots command to cause Bacula to update its catalog info for the + affected autochanger.

+

Note that vchanger is not dependent on the state information kept in an + autochanger's work directory. Vchanger recalculates the current state of + virtual drives, bays, and slots whenever invoked. However, the state information allows vchanger to only issue the update - slots command when a change is detected that requires it and - allows keeping the volume-to-slot mapping as consistent as possible.

-

4.4. Virtual Drives

-

Bacula treats volumes on a vchanger autochanger in the same way that it - treats tapes in a tape autochanger, by loading the volume it is going to - access into one of the autochanger's drives. Vchanger "loads" a virtual - drive by creating a symlink at a known location in the autochanger's work - directory that points to the volume file being loaded. A virtual drive is - unloaded by deleting that symlink. Vchanger supports a - dynamic and unlimited number of virtual drives. The number of - virtual drives actually used by Bacula will be determined in the Bacula + slots command when a change is detected that requires it and + allows keeping the volume-to-slot mapping as consistent as possible.

+

4.4. Virtual Drives

+

Bacula treats volumes on a vchanger autochanger in the same way that it + treats tapes in a tape autochanger, by loading the volume it is going to + access into one of the autochanger's drives. Vchanger "loads" a virtual + drive by creating a symlink at a known location in the autochanger's work + directory that points to the volume file being loaded. A virtual drive is + unloaded by deleting that symlink. Vchanger supports a + dynamic and unlimited number of virtual drives. The number of + virtual drives actually used by Bacula will be determined in the Bacula Storage Director's configuration file by the number of Device resources assigned to the Autochanger - resource associated with the vchanger autochanger. Additional + resource associated with the vchanger autochanger. Additional virtual drives can be added by adding additional Device resources to the Autochanger - resource in bacula-sd.conf and requires no further changes to the - vchanger configuration.

-

4.5. Vchanger Commands

-

The LIST command is performed by simply listing to stdout the calculated - slot-to-volume mapping, one line per virtual slot, in the format

-
slot:barcode
-

where slot is the virtual slot - number and barcode is the - filename of the volume mapped to that virtual slot. Because slot-to-volume - mapping is kept as consistent as possible, there may be virtual slots that - are empty, (ie. not currently mapped to a volume file). Empty slots will - be listed without a barcode string following the ':'.

-

The LISTALL command is similar to the LIST command except that it lists - drive status in addition to slot status to stdout. The output is in the - format

-
type:num:status:barcode
-

where type is 'D' for a drive - or 'S' for a slot, num is an + resource in bacula-sd.conf and requires no further changes to the + vchanger configuration.

+

4.5. Vchanger Commands

+

The LIST command is performed by simply listing to stdout the calculated + slot-to-volume mapping, one line per virtual slot, in the format

+
slot:barcode
+

where slot is the virtual slot + number and barcode is the + filename of the volume mapped to that virtual slot. Because slot-to-volume + mapping is kept as consistent as possible, there may be virtual slots that + are empty, (ie. not currently mapped to a volume file). Empty slots will + be listed without a barcode string following the ':'.

+

The LISTALL command is similar to the LIST command except that it lists + drive status in addition to slot status to stdout. The output is in the + format

+
type:num:status:barcode
+

where type is 'D' for a drive + or 'S' for a slot, num is an integer drive number for drives or slot number for slots, status is 'F' for full or 'E' for empty, and barcode - is the filename of the associated volume file.

-

The LOAD command is performed by creating a symlink in the autochanger's - work directory that points to the volume file mapped to the requested - slot. The symlink is named as the integer drive number of the virtual - drive being loaded. For example, if the autochanger's work directory is - /var/spool/vchanger/changer1 and volume file /mnt/mag/volume1 is mapped to - virtual slot 10, then a LOAD command requesting that slot 10 be loaded - into drive 0 would result in the creation of a symlink named  - /var/spool/vchanger/changer1/0 that points to the file /mnt/mag/volume1.

-

The UNLOAD command is performed by deleting the symlink that was created - for a virtual drive by a previous LOAD command.

-

The LOADED command is performed by printing to stdout the slot number of - the virtual slot currently loaded into the requested drive, or zero if the - drive is not currently loaded.

-

The SLOTS command is performed by printing the current number of virtual - slots to stdout. For each autochanger, vchanger tracks the maximum number - of slots that have ever been simultaneously available on mounted magazines - in the dynamic.conf file in the autochanger's work directory. This is the - number reported by the SLOTS command. The number of slots defined for an - autochanger will increase as needed when multiple magazines are - simultaneously attached, but it will never decrease.

-

The LISTMAGS command is performed by printing one line of status - information for each magazine defined in the vchanger configuration file. - The format of a status line is:

-
bay:slots:start:mountpoint
-

where bay is the zero-based + is the filename of the associated volume file.

+

The LOAD command is performed by creating a symlink in the autochanger's + work directory that points to the volume file mapped to the requested + slot. The symlink is named as the integer drive number of the virtual + drive being loaded. For example, if the autochanger's work directory is + /var/spool/vchanger/changer1 and volume file /mnt/mag/volume1 is mapped to + virtual slot 10, then a LOAD command requesting that slot 10 be loaded + into drive 0 would result in the creation of a symlink named  + /var/spool/vchanger/changer1/0 that points to the file /mnt/mag/volume1.

+

The UNLOAD command is performed by deleting the symlink that was created + for a virtual drive by a previous LOAD command.

+

The LOADED command is performed by printing to stdout the slot number of + the virtual slot currently loaded into the requested drive, or zero if the + drive is not currently loaded.

+

The SLOTS command is performed by printing the current number of virtual + slots to stdout. For each autochanger, vchanger tracks the maximum number + of slots that have ever been simultaneously available on mounted magazines + in the dynamic.conf file in the autochanger's work directory. This is the + number reported by the SLOTS command. The number of slots defined for an + autochanger will increase as needed when multiple magazines are + simultaneously attached, but it will never decrease.

+

The LISTMAGS command is performed by printing one line of status + information for each magazine defined in the vchanger configuration file. + The format of a status line is:

+
bay:slots:start:mountpoint
+

where bay is the zero-based index of the Magazine line in the vchanger configuration file, slots - is the number of volume files currently existing on the magazine, - start is the starting slot - number of the range of virtual slots mapped to the magazine's volume - files, and mountpoint is the - magazine's directory or current filesystem mount point.

-

The CREATEVOLS command is used to add new volume files to a magazine and - cause Bacula to write volume labels to them.

-

The REFRESH command causes vchanger to refresh the current state of + is the number of volume files currently existing on the magazine, + start is the starting slot + number of the range of virtual slots mapped to the magazine's volume + files, and mountpoint is the + magazine's directory or current filesystem mount point.

+

The CREATEVOLS command is used to add new volume files to a magazine and, + optionally, cause Bacula to write volume labels to them.

+

The REFRESH command causes vchanger to refresh the current state of magazines and virtual drives and issue a update - slots command via bconsole if needed. This command is intended to - be invoked when a magazine filesystem is mounted or unmounted, for example - by an automount script invoked by udev or by the operator after manually - mounting a magazine filesystem.

-

5. Installing vchanger

-

5.1. Installing from Source

-

On most POSIX systems, vchanger can be compiled and installed from source - as follows.

-

Unpack the gzip compressed tar archive.

-
[]$ tar -xzf vcahnger-1.0.0.tar.gz
-

Configure the build system and compile - vchanger

-
[]$ cd vchanger
-[]$ ./configure
-[]$ make
-

As root, install the executable and - documentation

-
[]$ su root
-[]# make install-strip
-

This will install the vchanger executable in - /usr/local/bin, udev automount scripts in /usr/local/libexec/vchanger, and - the documentation in /usr/local/share/doc/vchanger.

-

5.2. Installing from an RPM Package

-

x86_64 RPM packages for RHEL/Centos 6.x and RHEL/Centos 7.x, along with - the GPG signing key, are available for downloading from the vchanger - project page on SourceForge. An RPM install will create the /etc/vchanger - directory to hold autochanger configuration files, the /var/spool/vchanger - directory under which autochanger work directories will be created, and - the /var/log/vchanger directory under which autochanger log files will be - written.

-

The spec files used to create the RPM packages are included in the rpm - directory of the source distribution for those who would like to build an - RPM for other distributions or architectures.

-

5.3. Installing the Windows Version of - vchanger

-

On Windows, vchanger requires at least Windows Server 2008 or Vista and - will not run on earlier versions of Windows. A Windows installer is - available for downloading from SourceForge that will install a x86 or - x86_64 build of vchanger.

-

5.3.1. Installing the Windows Version from - Source

-

The Windows version was developed under Centos - 7.0 using the MinGW-w64 - (gcc-4.9.1) cross-compiler system. On a Linux system with MinGW-w64 - development tools installed, the Windows version can be built as follows:

-

Unpack the gzip compressed tar archive in your home directory.

-
[]$ cd ~
-[]$ tar -xzf /path/to/vcahnger-1.0.1.tar.gz
-

Configure the build system and cross-compile - vchanger

-
[]$ cd vchanger
-[]$ ./configure --host=x86_64-w64-mingw32 --build=`./config.guess` --prefix=./win32
-[]$ make
-[]$ make install-strip
-

The win32 executable, vchanger.exe, will be in - ~/vchanger/win32/bin and the Windows version documentation in - ~/vchanger/win32/share/doc/vchanger.

-

Building the Windows installer for vchanger requires the makensis - ustility from the NSIS project in addition to MinGW-w64. A shell script - named win32build in the source distribution will configure and build both - 32-bit and 64-bit Windows versions and then run makensis to create a - Windows installer that will install the 32-bit version on 32-bit versions - of Windows or the 64-bit version on 64-bit versions of Windows.

-

6. Preparing Removable Drives

-

6.1. Preparing Drives

-

Preparing removable drives for use by vchanger is fairly straight - forward. All that is needed is a filesystem partition on the removable - drive. Most external USB drives come with a NTFS filesystem installed and - using a GPT partition table. It is recommended that new removable drives - be partitioned and formatted to include a single partition and filesystem - encompassing the entire disk. Any read/write filesystem supported by the - OS may be used.

-

6.1.1. Setting Permissions for the - Magazine Filesystem

-

The Bacula Storage Daemon does not usually run as root. Since vchanger - will be invoked by the Storage Daemon, and so will run as the same user - that it does, permissions for magazine filesystems must be set to allow - read/write access to that user. This can be done by mounting the new - partition somewhere, then setting the appropriate permissions to give - read/write access to the user that the Storage Daemon runs as. It may also - be necessary to configure SELinux or AppArmour to allow the user that the - Storage Daemon runs as to read/write the magazine filesystems.

-

6.2. Determining the Magazine's - Filesystem UUID

-

On most POSIX systems, the blkid utility can be used to view the UUID of - a partition's filesystem.

-

On Windows, a filesystem UUID is known as a Volume Serial Number, (not to - be confused with the manufacturer's hardware serial number). Volume Serial - Numbers, are 8 hex digits with a '-' between the first 4 and last 4 hex - digits. This format differs from the UUIDs used by most POSIX systems, and - is due to Windows storing the Volume Serial Number internally as a 32-bit - binary number. This is not a problem, however, as Linux (and probably most - other POSIX systems) will happily use the Windows Volume Serial Number as - the filesystem UUID, though it is shorter in length. The simplest way to - get the Volume Serial Number is to open a Command Prompt window and issue - a DIR command on the removable drive's drive letter.

-

7. Mounting Removable Disk Drives

-

On Linux and other modern POSIX systems, removable drives are assigned a - device node when they are plugged in, but the OS does not by default mount - the drive partitions. Automounting is often handled by, for example, Gnome - Nautilus, - but this may not suffice for use with daemons, such as the Bacula Storage - Daemon that will normally invoke vchanger. The block storage device - containing the magazine's filesystem partition must, by some means, be - mounted before it can be used by vchanger. Since vchanger will be invoked - by the Bacula Storage Daemon, and will not ordinarily run with superuser - privileges, it does not itself attempt to mount filesystems.

-

7.1. udev and Hot-swappable Drives

-

Udev - is the subsystem that dynamically assigns device nodes to hot swappable - hardware devices when they are attached to a running system and frees - those device nodes when they are detached. In general, there is no - guarantee that a particular piece of hardware will be assigned a known - device node when it is plugged in. The device node that udev assigns will - depend on how many other devices are already attached and could change - every time the hot-swappable device is plugged in.

-

Fortunately, udev provides a way to create aliases to the device nodes it - assigns so that the device can be accessed using the fixed alias, - regardless of the actual device node assigned. With most Linux - distributions, the aliases are symlinks created dynamically by udev in - subdirectories of /dev/disk, and these symlinks point to the actual device - node assigned to the block storage device. In particular, symlinks are - created under /dev/disk/by-uuid such that the symlink name is the UUID of - the partition's filesystem. Consider, for example, a USB removable drive - with a partition containing a ext3 filesystem with UUID - 9667f83c-6150-44c7-b0d4-57564f174b35. When this USB drive is attached, - udev will assign a device node to it's partition and create the symlink - /dev/disk/by-uuid/9667f83c-6150-44c7-b0d4-57564f174b35 pointing to the - actual device node assigned. The device can then be accessed using the - known 'by-uuid' alias without having to discover the actual device node - assigned.

-

7.2. Manually Mounting Removable Drive - Partitions

-

One way to handle mounting and unmounting of removable drive partitions - is for the operator to manually mount the partitions when the removable - drive is attached and manually unmount the partitions before they are - detached. The mounting of magazine filesystems can be simplified by adding - the mounting information for the magazine filesystems to /etc/fstab, - identifying the partition by its filesystem UUID, and assigning the - filesystems in the vchanger configuration file by UUID. For example:

-
#/etc/fstab
...
# USB disks for vchanger -UUID=7b4526c4-d8e9-48ba-b227-f67f855a0dc7 /mnt/vchanger/mag0 ext4 defaults,noauto 1 2 -UUID=a4902c05-e47d-40e0-9db7-b3d03d08c266 /mnt/vchanger/mag1 ext4 defaults,noauto 1 2 -...
#eof

#/etc/vchanger/vchanger.conf
Storage Resource = vchanger
...
Magazine = UUID:7b4526c4-d8e9-48ba-b227-f67f855a0dc7
Magazine = UUID:a4902c05-e47d-40e0-9db7-b3d03d08c266 -#eof
-

The removable drive being used as magazine 0 could them be mounted and - made available with:

-
[]# mount /mnt/vchanger/mag0
[]# vchanger /etc/vchanger/vchanger.conf refresh
-

After use, the removable drive can then be removed using:

-
[]# umount /mnt/vchanger/mag0
[]# vchanger /etc/vchanger/vchanger.conf refresh
-

It is also possible, when manually mounting magazine partitions, to use - the mountpoint path in the Magazine - directive, rather than the UUID. When a magazine is specified by - path (rather than by UUID), vchanger assumes the magazine is mounted if - that path exists. However, if that directory path exists while the - magazine's partition is not mounted, then that directory will be part of - the underlying filesystem rather than the unmounted magazine partition's - filesystem. For this reason, specifying directory paths in Magazine - directives is only recommended for permanently mounted filesystems. - Filesystems that are not permanently mounted should be specified by UUID. -

-

7.3. Using udev To Mount Removable Drive - Partitions

-

Udev - responds to kernel messages to enable naming and handling of devices as - they are attached and detached from the system using a rule-based - approach. Rule files stored in /etc/udev/rules.d and /usr/lib/udev/rules.d - enable tailoring the naming and handling of hardware devices. Udev rules - make it possible to run short scripts in response to device events. With - the appropriate rules and the helper scripts provided with vchanger, the - attachment and detachment of removable disk drives used as vchanger - magazines can be made into a plug-n-play operation.

-

The vchanger-genudevrules - script will scan all vchanger configuration files in /etc/vchanger and - output to stdout the udev rules needed for each magazine that is specified - by filesystem UUID. These rules configure udev to execute scripts whenever - the partition with that filesystem UUID is attached or detached. The - scripts are included in the scripts directory of the vchanger source and - are usually installed into the /usr/libexec/vchanger directory.

-

The rule that matches when the device is attached invokes /usr/libexec/vchanger/vchanger-launch-mount.sh. - This is a very simple script with the sole purpose of lanching the - /usr/libexec/vchanger/vchanger-mount-uuid.sh script as a separate, - independent process. This is because udev does not invoke scripts in a - true shell and the scripts it invokes must execute very quickly and - without error The vchanger-mount-uuid.sh - script is supplied with a single argument, the UUID of the partition being - attached. If there is an fstab entry for the filesystem UUID, then the - partition is mounted using the mount point and mount options specified in - /etc/fstab. Otherwise, the partition is mounted at MOUNT_DIR/{uuid}, where - MOUNT_DIR is defined in a default parameter file named - /etc/default/vchanger (or /etc/sysconfig/vchanger). The - /etc/default/vchanger file may also define MOUNT_OPTIONS to be the mount - options that should be passed to the mount command via the -o flag. Note - that the same MOUNT_OPTIONS will be passed to the mount command for every - magazine file system. If there are magazine filesystems that require - different mount options, then they will have to be defined in /etc/fstab. - Finally, the script ends by invoking vchanger with the REFRESH command to - update state and issue a "update slots" command to Bacula via bconsole.

-

After adding or changing Magazine directives in a vchanger configuration - file, configure automounting through udev by:

-
[]# vchanger-genudevrules >/etc/udev/rules.d/96-vchanger.rules
[]# udevadm control --reload-rules
-

7.4. Using autofs To Mount Removable Drive - Partitions

-

The autofs daemon provides a way to - mount and unmount the magazine partitions on-the-fly as they are accessed. - This eliminates the need for manual mounting, but in a different way than - when using udev rules. autofs works by watching for accesses of a - particular path and mounting the device at that path on an as-needed - basis, then unmounting after an idle period. When using autofs to mount + slots command via bconsole if needed. This command is intended to + be invoked when a magazine filesystem is mounted or unmounted, for example + by an automount script invoked by udev or by the operator after manually + mounting a magazine filesystem.

+

5. Installing vchanger

+

5.1. Installing from Source

+

On most POSIX systems, vchanger can be compiled and installed from source + as follows.

+

Unpack the gzip compressed tar archive.

+
[]$ tar -xzf vcahnger-1.0.0.tar.gz
+

Configure the build system and compile + vchanger

+
[]$ cd vchanger
+[]$ ./configure
+[]$ make
+

As root, install the executable and + documentation

+
[]$ su root
+[]# make install-strip
+

This will install the vchanger executable in + /usr/local/bin, udev automount scripts in /usr/local/libexec/vchanger, and + the documentation in /usr/local/share/doc/vchanger.

+

5.2. Installing from an RPM Package

+

x86_64 RPM packages for RHEL/Centos 6.x and RHEL/Centos 7.x, along with + the GPG signing key, are available for downloading from the vchanger + project page on SourceForge. An RPM install will create the /etc/vchanger + directory to hold autochanger configuration files, the /var/spool/vchanger + directory under which autochanger work directories will be created, and + the /var/log/vchanger directory under which autochanger log files will be + written.

+

The spec files used to create the RPM packages are included in the rpm + directory of the source distribution for those who would like to build an + RPM for other distributions or architectures.

+

5.3. Installing the Windows Version of + vchanger

+

On Windows, vchanger requires at least Windows Server 2008 or Vista and + will not run on earlier versions of Windows. A Windows installer is + available for downloading from SourceForge that will install a x86 or + x86_64 build of vchanger.

+

5.3.1. Installing the Windows Version from + Source

+

The Windows version was developed under Centos + 7.8 using the MinGW-w64 + (gcc-4.9.3) cross-compiler system. On a Linux system with MinGW-w64 + development tools installed, the Windows version can be built as follows:

+

Unpack the gzip compressed tar archive in your home directory.

+
[]$ cd ~
+[]$ tar -xzf /path/to/vcahnger-1.0.1.tar.gz
+

Configure the build system and cross-compile + vchanger

+
[]$ cd vchanger
+[]$ ./configure --host=x86_64-w64-mingw32 --build=`./config.guess` --prefix=./win32
+[]$ make
+

The win32 executable, vchanger.exe, will be in + ~/vchanger/win32/bin and the Windows version documentation in + ~/vchanger/win32/share/doc/vchanger.

+

5.3.2. Building the Windows + installer

+

Building the Windows installer for vchanger requires the makensis + ustility from the NSIS project in addition to MinGW-w64. A shell script + named win32build in the source distribution will configure and build both + 32-bit and 64-bit Windows versions and then run makensis to create a + Windows installer that will install the 32-bit version on 32-bit versions + of Windows or the 64-bit version on 64-bit versions of Windows.

+

6. Preparing Removable Drives

+

6.1. Preparing Drives

+

Preparing removable drives for use by vchanger is fairly straight + forward. All that is needed is a filesystem partition on the removable + drive. Most external USB drives come with a NTFS filesystem installed and + using a GPT partition table. It is recommended that new removable drives + be partitioned and formatted to include a single partition and filesystem + encompassing the entire disk. Any read/write filesystem supported by the + OS may be used.

+

6.1.1. Setting Permissions for the + Magazine Filesystem

+

The Bacula Storage Daemon does not usually run as root. Since vchanger + will be invoked by the Storage Daemon, and so will run as the same user + that it does, permissions for magazine filesystems must be set to allow + read/write access to the user:group that the bacula-sd Storage Daemon runs + as. This can be done by mounting the new partition somewhere, then setting + the appropriate permissions to give read/write access to the user that the + Storage Daemon runs as. It may also be necessary to configure SELinux or + AppArmour to allow the user that the Storage Daemon runs as to have + read/write the magazine filesystems.

+

6.2. Determining the Magazine's + Filesystem UUID

+

On most POSIX systems, the blkid utility can be used to view the UUID of + a partition's filesystem.

+

On Windows, a filesystem UUID is known as a Volume Serial Number, (not to + be confused with the manufacturer's hardware serial number). Volume Serial + Numbers, are 8 hex digits with a '-' between the first 4 and last 4 hex + digits. This format differs from the UUIDs used by most POSIX systems, and + is due to Windows storing the Volume Serial Number internally as a 32-bit + binary number. This is not a problem, however, as Linux (and probably most + other POSIX systems) will happily use the Windows Volume Serial Number as + the filesystem UUID, though it is shorter in length. The simplest way to + get the Volume Serial Number is to open a Command Prompt window and issue + a DIR command on the removable drive's drive letter.

+

7. Mounting Removable Disk Drives

+

On Linux and other modern POSIX systems, removable drives are assigned a + device node when they are plugged in, but the OS does not by default mount + the drive partitions. Automounting is often handled by, for example, Gnome + Nautilus, + but this may not suffice for use with daemons, such as the Bacula Storage + Daemon that will normally invoke vchanger. The block storage device + containing the magazine's filesystem partition must, by some means, be + mounted before it can be used by vchanger or Bacula. Since vchanger will + be invoked by the Bacula Storage Daemon, and will not ordinarily run with + superuser privileges, it does not itself attempt to mount filesystems.

+

7.1. udev and Hot-swappable Drives

+

Udev + is the subsystem that dynamically assigns device nodes to hot swappable + hardware devices when they are attached to a running system and frees + those device nodes when they are detached. In general, there is no + guarantee that a particular piece of hardware will be assigned a known + device node when it is plugged in. The device node that udev assigns will + depend on how many other devices are already attached and could change + every time the hot-swappable device is plugged in.

+

Fortunately, udev provides a way to create aliases to the device nodes it + assigns so that the device can be accessed using the fixed alias, + regardless of the actual device node assigned. With most Linux + distributions, the aliases are symlinks created dynamically by udev in + subdirectories of /dev/disk, and these symlinks point to the actual device + node assigned to the block storage device. In particular, symlinks are + created under /dev/disk/by-uuid such that the symlink name is the UUID of + the partition's filesystem. Consider, for example, a USB removable drive + with a partition containing a ext3 filesystem with UUID + 9667f83c-6150-44c7-b0d4-57564f174b35. When this USB drive is attached, + udev will assign a device node to it's partition and create the symlink + /dev/disk/by-uuid/9667f83c-6150-44c7-b0d4-57564f174b35 pointing to the + actual device node assigned. The device can then be accessed using the + known 'by-uuid' alias without having to discover the actual device node + assigned.

+

7.2. Manually Mounting Removable Drive + Partitions

+

One way to handle mounting and unmounting of removable drive partitions + is for the operator to manually mount the partitions when the removable + drive is attached and manually unmount the partitions before they are + detached. The mounting of magazine filesystems can be simplified by adding + the mounting information for the magazine filesystems to /etc/fstab, + identifying the partition by its filesystem UUID, and assigning the + filesystems in the vchanger configuration file by UUID. For example:

+
#/etc/fstab
...
# USB disks for vchanger +UUID=7b4526c4-d8e9-48ba-b227-f67f855a0dc7 /mnt/vchanger/mag0 ext4 defaults,noauto 1 2 +UUID=a4902c05-e47d-40e0-9db7-b3d03d08c266 /mnt/vchanger/mag1 ext4 defaults,noauto 1 2 +...
#eof

#/etc/vchanger/vchanger.conf
Storage Resource = vchanger
...
Magazine = UUID:7b4526c4-d8e9-48ba-b227-f67f855a0dc7
Magazine = UUID:a4902c05-e47d-40e0-9db7-b3d03d08c266 +#eof
+

The removable drive being used as magazine 0 could them be mounted and + made available with:

+
[]# mount /mnt/vchanger/mag0
[]# vchanger /etc/vchanger/vchanger.conf refresh
+

After use, the removable drive can then be removed using:

+
[]# umount /mnt/vchanger/mag0
[]# vchanger /etc/vchanger/vchanger.conf refresh
+

It is also possible, when manually mounting magazine partitions, to use a + mountpoint path in the Magazine + directive, rather than a UUID. When a magazine is specified by + path (rather than by UUID), vchanger assumes the magazine is mounted if + that path exists. However, if that directory path exists while the + magazine's partition is not mounted, then that directory will be part of + the underlying filesystem rather than the unmounted magazine partition's + filesystem. For this reason, specifying directory paths in Magazine + directives is only recommended for permanently mounted filesystems. For + filesystems that are not permanently mounted, specifying magazines by UUID + is more flexible, allowing for multiple simultaneously mounted disks and + udev mount/unmount scripts that automate the bconsole calls needed to keep + Bacula in sync.

+

When specifying a magazine by directory path, rather than filesystem + UUID, vchanger will use any files in that directory as volume files. For + example, one could specify a single magazine= line in the vchanger + configuration file, then mount removable disks there one at a time. At + each invocation, vchanger will assign whatever files exist in that + directory to its virtual slots. However, this would in turn require + manually running 'update slots' in bconsole and would prevent mounting + more than one disk at a time, so will require more operator intervention. +

+

7.3. Using udev To Mount Removable Drive + Partitions

+

Udev + responds to kernel messages to enable naming and handling of devices as + they are attached and detached from the system using a rule-based + approach. Rule files stored in /etc/udev/rules.d and /usr/lib/udev/rules.d + enable tailoring the naming and handling of hardware devices. Udev rules + make it possible to run short scripts in response to device events. With + the appropriate rules and the helper scripts provided with vchanger, the + attachment and detachment of removable disk drives used as vchanger + magazines can be made into a plug-n-play operation.

+

The vchanger-genudevrules + script will scan all vchanger configuration files in /etc/vchanger and + output to stdout the udev rules needed for each magazine that is specified + by filesystem UUID. These rules configure udev to execute scripts whenever + the partition with that filesystem UUID is attached or detached. The + scripts are included in the scripts directory of the vchanger source and + are usually installed into the /usr/libexec/vchanger directory.

+

The rule that matches when the device is attached invokes /usr/libexec/vchanger/vchanger-launch-mount.sh. + This is a very simple script with the sole purpose of lanching the + /usr/libexec/vchanger/vchanger-mount-uuid.sh script as a separate, + independent process. This is because udev does not invoke scripts in a + true shell and the scripts it invokes must execute very quickly and + without error The vchanger-mount-uuid.sh + script is supplied with a single argument, the UUID of the partition being + attached. If there is an fstab entry for the filesystem UUID, then the + partition is mounted using the mount point and mount options specified in + /etc/fstab. Otherwise, the partition is mounted at MOUNT_DIR/{uuid}, where + MOUNT_DIR is defined in a default parameter file named + /etc/default/vchanger (or /etc/sysconfig/vchanger). The + /etc/default/vchanger file may also define MOUNT_OPTIONS to be the mount + options that should be passed to the mount command via the -o flag. Note + that the same MOUNT_OPTIONS will be passed to the mount command for every + magazine file system. If there are magazine filesystems that require + different mount options, then they will have to be defined in /etc/fstab. + Finally, the script ends by invoking vchanger with the REFRESH command to + update state and issue a "update slots" command to Bacula via bconsole.

+

After adding or changing Magazine directives in a vchanger configuration + file, configure automounting through udev by:

+
[]# vchanger-genudevrules >/etc/udev/rules.d/96-vchanger.rules
[]# udevadm control --reload-rules
+

Note that it may be necessary to detach and then reattach currently + attached magazine drives following a change in udev rules in order to + ensure that the new rules are applied.

+

+

7.4. Using autofs To Mount Removable Drive + Partitions

+

The autofs daemon provides a way to + mount and unmount the magazine partitions on-the-fly as they are accessed. + This eliminates the need for manual mounting, but in a different way than + when using udev rules. autofs works by watching for accesses of a + particular path and mounting the device at that path on an as-needed + basis, then unmounting after an idle period. When using autofs to mount the magazine partitions, the Magazine - directives in the vchanger configuration file will specify the - path to the mountpoint, rather than the filesystem UUID. Specifying paths + directives in the vchanger configuration file will specify the + path to the mountpoint, rather than the filesystem UUID. Specifying paths for Magazine directives - does not present a problem in this case, since autofs creates and deletes - the path dynamically as it mounts and unmounts the filesystem. For all - practical purpose, a mountpoint path controlled by autofs can be treated - as if it were permanently mounted.

-

An automount map file for use with vchanger is given below.

-
# /etc/auto.vchanger
-*          -fstype=auto,rw,sync       :/dev/disk/by-uuid/&
-# eof
-

Notice that 'sync' is specified in the flags that autofs will pass to the - mount command. This will turn off write caching and force all writes to - the magazine's filesystem to be written immediately to disk. Though it - reduces write performance, it helps to reduce the chance of data loss when - a removable drive is detached following a backup. Much better write - performance can be had by not specifying the sync option and being careful - not to unplug removable drives until after autofs unmounts them.

-

A line is then added to the /etc/auto.master file to tell autofs to pull - in the new auto.vchanger configuration:

-
# /etc/auto.master
-# ...
-/mnt/vchanger      /etc/auto.vchanger        --timeout=30
-# eof
-

After restarting the autofs daemon, whenever a file or directory with a - full path beginning with '/mnt/vchanger' is accessed, autofs will search - the map defined in the /etc/auto.vchanger file for a key matching the path - being accessed. In this case, the only key in /etc/auto.vchanger is the - wildcard '*', meaning any path name beginning with '/mnt/vchanger' will - match. Autofs then automatically mounts the device mapped to the wildcard - key at the path being accessed. The /etc/auto.vchanger map file specifies - the device to be mounted as /dev/disk/by-uuid/&. The '&' is - substituted for the key value. For example, when a program attempts to + does not present a problem in this case, since autofs creates and deletes + the path dynamically as it mounts and unmounts the filesystem. For all + practical purpose, a mountpoint path controlled by autofs can be treated + as if it were permanently mounted.

+

An automount map file for use with vchanger is given below.

+
# /etc/auto.vchanger
+*          -fstype=auto,rw,sync       :/dev/disk/by-uuid/&
+# eof
+

Notice that 'sync' is specified in the flags that autofs will pass to the + mount command. This will turn off write caching and force all writes to + the magazine's filesystem to be written immediately to disk. Though it + reduces write performance, it helps to reduce the chance of data loss when + a removable drive is detached following a backup. Much better write + performance can be had by not specifying the sync option and being careful + not to unplug removable drives until after autofs unmounts them.

+

A line is then added to the /etc/auto.master file to tell autofs to pull + in the new auto.vchanger configuration:

+
# /etc/auto.master
+# ...
+/mnt/vchanger      /etc/auto.vchanger        --timeout=30
+# eof
+

After restarting the autofs daemon, whenever a file or directory with a + full path beginning with '/mnt/vchanger' is accessed, autofs will search + the map defined in the /etc/auto.vchanger file for a key matching the path + being accessed. In this case, the only key in /etc/auto.vchanger is the + wildcard '*', meaning any path name beginning with '/mnt/vchanger' will + match. Autofs then automatically mounts the device mapped to the wildcard + key at the path being accessed. The /etc/auto.vchanger map file specifies + the device to be mounted as /dev/disk/by-uuid/&. The '&' is + substituted for the key value. For example, when a program attempts to access /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35 - (or any files or directories below it), the autofs daemon will look at - the auto.vchanger map for the key 9667f83c-6150-44c7-b0d4-57564f174b35 - and discover that it should mount - /dev/disk/by-uuid/9667f83c-6150-44c7-b0d4-57564f174b35 at - /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35 with mount options - '-fstype=auto,rw,sync'. After a period of 30 seconds of no activity, - autofs will automatically unmount the device.

-

Always make sure autofs is working properly - before continuing with setting up vchanger. If using the above autofs - config files, when you plug in a USB drive with filesystem UUID of, say, - 9667f83c-6150-44c7-b0d4-57564f174b35, you should be able to list its - contents with the command

-
[]# ls /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-

The Magazine directive in the vchanger - configuration file for the above example partition would be:

-
Magazine = /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-

7.5. Mounting Removable Drives On Windows

-

Microsoft Windows operating systems normally automount removable drive - partitions with recognized file systems. By default, the mountpoint for a - partition will be a drive letter (ie. E:). Unlike POSIX operating systems, - where '/' is the root of the single directory tree, Windows operating - systems use a multi-root directory structure where drive letters (and - network UNC base names) represent the roots of multiple, distinct - directory trees. When a removable drive is plugged in for the first time, - assuming the drive has a single partition, its partition will be mounted - at the next available drive letter. Windows remembers the drive letter - assignment for a partition so that it will be mounted at the same drive - letter when it is later re-attached, unless the drive letter is already - being used for another partition, in which case it will be mounted at the - next available drive letter. Note that it is not possible to assign the - same drive letter to two different removable drives simultaneously.

-

It is also possible, however, to mount a volume at a (empty) subdirectory - in another directory tree, as long as the volume mounted at the root of - that other directory tree is a NTFS volume. In fact, only the volume from - which Windows boots and volumes containing a virtual memory storage file - even require a drive letter at all. For removable drives, the drive letter - assignment can be deleted altogether and the partition always auto-mounted - at a directory in an NTFS directory tree. It should be noted that only the - mount point directory must be on an NTFS volume. The partition being - mounted may have a FAT32 file system or any other file system for which - there is a file system driver installed. See “Assign - a mount point folder path to a drive” for instructions on assigning - mountpoints for removable drive partitions. Like with drive letters, it is - not possible to assign the same mountpoint to more than one drive.

-

On Windows, magazine partitions should always be specified by UUID - (Volume Serial Number). This ensures that vchanger will be able to find - the partition's mountpoint without prior knowledge of which drive letter - or mountpoint Windows has currently assigned to the drive.

-

8. Configuring vchanger

-

Each vchanger autochanger is defined by a configuration file. Multiple - autochangers may be defined as long as each is given a unique name, its - own unique work directory, and its own unique configuration file. By - default, vchanger configuration files are placed in the /etc/vchanger - directory. The configuration files must be given permissions that - allow the user that the Bacula Storage Daemon runs as to have read access.

-

A vchanger configuration file consists of - keyword = value pairs. Comments are defined by a '#' character, and cause - text from the '#' until the next newline character to be ignored. Values - including whitespace or special characters must be enclosed in single or - double quotes. Special characters, including the quote character, - appearing inside of the quoted value must be escaped by prepending with a - '\' character. The following keywords are defined for an autochanger - configuration file:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Storage Resource

-
-

[Required] The name of the Storage resource, defined in the - Bacula Director configuration file, that is associated with this - autochanger.
- Default: none

-
-

User

-
-

User to run as when vchanger is invoked by root. This user must - have read/write access to the work directory, magazine - directories, and volume files and should be the same user that the - Bacula Storage Daemon runs as.
- Default: bacula

-
-

Group

-
-

Group to run as when vchanger is invoked by root. This group must - have access to the work directory, magazine directories, and - volume files and should be the same group that the Bacula Storage - Daemon runs as.
- Default: tape

-
-

Work Dir

-
-

The directory where this autochanger will store virtual drive and - magazine state information and create symlinks to its volume - files.
- Default: /var/spool/vchanger/{Storage Resource}

-
-

Logfile

-
-

Path to the logfile for this autochanger.
- Default: /var/log/vchanger/{Storage Resource}.log

-
-

Log Level

-
-

The level of detail being logged. An integer value from 0-7 is - expected, where higher values increase logging. The levels - correspond to 'LOG_EMERG' through 'LOG_DEBUG' as defined for the - syslog() system call. See man 2 syslog for possible values.
- Default: 3

-
-

bconsole

-
-

The path to the bconsole binary that vchanger will invoke to - issue 'update slots' and 'label barcodes' commands to Bacula. To - disable interaction with Bacula, set to the empty string.
- Default: /usr/sbin/bconsole

-
-

bconsole config

-
-

The path to the configuration file to use when invoking bconsole.
- Default: /etc/bacula/bconsole.conf

-
-

Default Pool

-
-

The default pool in which to add new volumes created by the - CREATEVOLS command.
- Default: Scratch

-
-

Magazine

-
-

[Required] Defines either the path to a directory or the UUID of - a filesystem partition (prepended by the string “UUID:”) that is - to be used as a magazine containing volume files . The magazine - directive assigns a directory or partition to this autochanger. - This directive may appear multiple times to assign multiple - magazines.
- Default: none

-
-

9. Initializing New - Magazines

-

After storage for a new magazine has been prepared, - it must be defined as an autochanger magazine. This is accomplished by - adding a Magazine - directive to the vchanger configuration file. For removable storage, the

+

Always make sure autofs is working properly + before continuing with setting up vchanger. If using the above autofs + config files, when you plug in a USB drive with filesystem UUID of, say, + 9667f83c-6150-44c7-b0d4-57564f174b35, you should be able to list its + contents with the command

+
[]# ls /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+

The Magazine directive in the vchanger + configuration file for the above example partition would be:

+
Magazine = /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+

7.5. Mounting Removable Drives On Windows

+

Microsoft Windows operating systems normally automount removable drive + partitions with recognized file systems. By default, the mountpoint for a + partition will be a drive letter (ie. E:). Unlike POSIX operating systems, + where '/' is the root of the single directory tree, Windows operating + systems use a multi-root directory structure where drive letters (and + network UNC base names) represent the roots of multiple, distinct + directory trees. When a removable drive is plugged in for the first time, + assuming the drive has a single partition, its partition will be mounted + at the next available drive letter. Windows remembers the drive letter + assignment for a partition so that it will be mounted at the same drive + letter when it is later re-attached, unless the drive letter is already + being used for another partition, in which case it will be mounted at the + next available drive letter. Note that it is not possible to assign the + same drive letter to two different removable drives simultaneously.

+

It is also possible, however, to mount a volume at a (empty) subdirectory + in another directory tree, as long as the volume mounted at the root of + that other directory tree is a NTFS volume. In fact, only the volume from + which Windows boots and volumes containing a virtual memory storage file + even require a drive letter at all. For removable drives, the drive letter + assignment can be deleted altogether and the partition always auto-mounted + at a directory in an NTFS directory tree. It should be noted that only the + mount point directory must be on an NTFS volume. The partition being + mounted may have a FAT32 file system or any other file system for which + there is a file system driver installed. See “Assign + a mount point folder path to a drive” for instructions on + assigning mountpoints for removable drive partitions. Like with drive + letters, it is not possible to assign the same mountpoint to more than one + drive.

+

On Windows, magazine partitions should always be specified by UUID + (Volume Serial Number). This ensures that vchanger will be able to find + the partition's mountpoint without prior knowledge of which drive letter + or mountpoint Windows has currently assigned to the drive.

+

8. Configuring vchanger

+

Each vchanger autochanger is defined by a configuration file. Multiple + autochangers may be defined as long as each is given a unique Storage + Resource name, its own unique work directory, and its own + unique configuration file. By default, vchanger configuration files are + placed in the /etc/vchanger directory. The configuration files must be + given permissions that allow the user that the Bacula Storage Daemon + runs as to have read access.

+

A vchanger configuration file consists of + keyword = value pairs. Comments are defined by a '#' character, and cause + text from the '#' until the next newline character to be ignored. Values + including whitespace or special characters must be enclosed in single or + double quotes. Special characters, including the quote character, + appearing inside of the quoted value must be escaped by prepending with a + '\' character. The following keywords are defined for an autochanger + configuration file:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Storage Resource

+
+

[Required] The name of the Storage resource, defined in the + Bacula Director configuration file, that is associated with this + autochanger. This also names the vchanger autochanger and is used + in constructing the default volume naming prefix used by the + CREATEVOLS command.
+ Default: none

+
+

User

+
+

User to run as when vchanger is invoked by root. This user must + have read/write access to the work directory, magazine + directories, and volume files and should be the same user that the + Bacula Storage Daemon runs as.
+ Default: bacula

+
+

Group

+
+

Group to run as when vchanger is invoked by root. This group must + have access to the work directory, magazine directories, and + volume files and should be the same group that the Bacula Storage + Daemon runs as.
+ Default: tape

+
+

Work Dir

+
+

The directory where this autochanger will store virtual drive and + magazine state information and create symlinks to its volume + files.
+ Default: /var/spool/vchanger/{Storage Resource}

+
+

Logfile

+
+

Path to the logfile for this autochanger.
+ Default: /var/log/vchanger/{Storage Resource}.log

+
+

Log Level

+
+

The level of detail being logged. An integer value from 0-7 is + expected, where higher values increase logging. The levels + correspond to 'LOG_EMERG' through 'LOG_DEBUG' as defined for the + syslog() system call. See man 2 syslog for possible values.
+ Default: 3

+
+

bconsole

+
+

The path to the bconsole binary that vchanger will invoke to + issue 'update slots' and 'label barcodes' commands to Bacula. To + disable interaction with Bacula via bconsole, set to the empty + string. When disabled, 'update slots' and 'label barcodes' + commands will need to be issued manually from within bconsole.
+ Default: /usr/sbin/bconsole

+
+

bconsole config

+
+

The path to the configuration file to use when invoking bconsole. The user that vchanger is + invoked by, usually the user that the Bacula Storage Daemon runs + as, must have permissions to read the bconsole.conf file.
+ Default: /etc/bacula/bconsole.conf

+
+

Default Pool

+
+

The default pool in which to add new volumes created by the + CREATEVOLS command.
+ Default: Scratch

+
+

Magazine

+
+

[Required] Defines either the path to a directory or the UUID of + a filesystem partition (prepended by the string + “UUID:”) that is to be used as a magazine containing + volume files. The Magazine directive assigns a directory or + partition to this autochanger. This directive may appear multiple + times to assign multiple magazines.
+ Default: none

+
+

9. Initializing New + Magazines

+

After storage for a new magazine has been prepared, + it must be defined as an autochanger magazine. This is accomplished by + adding a Magazine + directive to the vchanger configuration file. For removable storage, the Magazine directive - should specify the filesystem UUID (or the mountpoint path if using - autofs). If a directory on permanently mounted fixed-disk storage is being - used as a magazine, then the Magazine - directive should specify the directory path. In either case, the - filesystem or directory must by writable by the user that Bacula Storage - Daemon runs as. The LISTMAGS vchanger command can be used to determine - that the magazine has been defined.

-

Once defined as an autochanger magazine, it is necessary to create volume - files on the magazine. While this can be accomplished manually by creating - empty files on the magazine and then labeling them from within bconsole - using the label - barcodes command, to make - adding volume files to a magazine easier and to facilitate a consistent - naming scheme, vchanger implements the CREATEVOLS command as:

-
vchanger config_file CREATEVOLS mag_bay count [start] [--pool=pool_name] [--label=prefix_string]
-

CREATEVOLS provides an easy way to add new volume files to a magazine and - label them into an appropriate pool. Volume files can be added to a - magazine at any time. Appendix A covers details of the CREATEVOLS - command.

-

10. Testing vchanger

-

Vchanger may be tested by running it from the command line. You must - either invoke vchanger as root or else as the user:group specified in the - vchanger configuration file, (which must be the same user:group that the - Bacula Storage Daemon runs as). If invoked as root, vchanger will switch - users to run as the user:group specified in the vchanger configuration - file given in the first parameter.

-

For example purposes, an autochanger using vchanger with USB external - drives will be designed step by step. Initially, two USB drives will be - initialized. For each of the two drives, the output from the dmesg command - is used to determine the assigned block device, and the blkid command used - to determine the UUID of its ext4 filesystem. A vchanger configuration - file is defined with Magazine directives assigning the empty USB drive - partitions to an autochanger named vchanger-1.

-
# /etc/vchanger/vchanger-1.conf
-Storage Resource = vchanger-1
User = bacula
Group = tape
Magazine = UUID:9667f83c-6150-44c7-b0d4-57564f174b35 -Magazine = UUID:5039284a-4312-57d1-92c4-354710032c79 -#eof
-

This defines an autochanger associated with a bacula-dir.conf Storage - resource named vchanger-1, with the Bacula Storage Daemon running as user - bacula:tape, and using two filesystems on removable USB drives as its - magazines. The filesystems will be mounted using udev, and the automount - scripts in the scripts directory of the vchanger source have been - installed to /usr/libexec/vchanger and the vchanger-genudevrules script - installed to /usr/bin. The udev rules needed to automount the magazine - filesystems can be generated and added to the udev rules with:

-
[]# vchanger-genudevrules >/etc/udev/rules.d/96-vchanger.rules
[]# udevadm control --reload-rules
-

A directory is created under which udev will mount the filesystems. The - filesystems will be mounted at subdirectories of this directory using the - filesystem UUID string as the subdirectory name.

-
[]# mkdir /mnt/vchanger
[]# chown bacula:tape /mnt/vchanger
[]# chmod 750 /mnt/vchanger
[]# echo "MOUNT_DIR=/mnt/vchanger" >/etc/sysconfig/vchanger
[]# echo "MOUNT_OPTIONS= >>/etc/sysconfig/vchanger
-

Now, attaching the USB drives should cause udev to invoke the - vchanger-mount-uuid.sh script to mount the filesystems as subdirectories - of the directory defined by the MOUNT_DIR variable specified in - /etc/sysconfig/vchanger (or /etc/default/vchanger).

-
[]# ls -1 /mnt/vchanger
-9667f83c-6150-44c7-b0d4-57564f174b35
-5039284a-4312-57d1-92c4-354710032c79
-
-

The owner and permissions must be set for the new filesystems in order to - give read/write access to the user that the Bacula Storage Daemon runs as:

-
[]# chown bacula:tape /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-[]# chmod 750 /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-][# chown bacula:tape /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
-][# chmod 750 /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
-
-

The Storage resource that this autochanger is associated with is defined - in bacula-dir.conf as:

-
# /etc/bacula/bacula-dir.conf
...
# vchanger autochanger vchanger-1 -Storage { - Name = vchanger-1 - Address = 192.168.1.9 - SDPort = 9103 - Password = "password" - Device = usb-vchanger-1 - Media Type = File - Autochanger = yes; - Maximum Concurrent Jobs = 20 -} -... -#eof
-

The Device resource pointed to by the Storage resource is defined in - bacula-sd.conf as:

-
# /etc/bacula/bacula-sd.conf
...
# vchanger autochanger vchanger-1 -Autochanger { - Name = usb-vchanger-1 - Device = usb-vchanger-1-drive-0,usb-vchanger-1-drive-1 - Changer Command = "vchanger %c %o %S %a %d" - Changer Device = "/etc/vchanger/vchanger-1.conf" -} - -Device { - Name = usb-vchanger-1-drive-0 - Drive Index = 0 - Autochanger = yes; - Device Type = File - Media Type = File - Removable Media = no; - Random Access = yes; - Maximum Concurrent Jobs = 1 - Archive Device = "/var/spool/vchanger/vchanger-1/0" -} - -Device { - Name = usb-vchanger-1-drive-1 - Drive Index = 1 - Autochanger = yes; - Device Type = File - Media Type = File - Removable Media = no; - Random Access = yes; - Maximum Concurrent Jobs = 1 - Archive Device = "/var/spool/vchanger/vchanger-1/1" -} -... -#eof
-

After detaching both of the USB drives, list the status of the magazine - partitions currently in use by invoking (as root):

-
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
-0:::
1:::
-

The output from the LISTMAGS - command shows that - neither of the magazine partitions is mounted, which is correct, since - neither of the two USB drives are attached. After attaching the USB drive - having the partition we defined in the first - Magazine directive, the output is:

-
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
-0:::/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-1:::
+ should specify the filesystem UUID (or the mountpoint path if using + autofs or manual mounting). If a directory on permanently mounted + fixed-disk storage is being used as a magazine, then the Magazine + directive should specify the directory path. In either case, the + filesystem or directory must by writable by the user that Bacula Storage + Daemon runs as. The LISTMAGS vchanger command can be used to determine + that the magazine has been defined.

+

Once defined as an autochanger magazine, it is necessary to create volume + files on the magazine. While this can be accomplished manually by creating + empty files on the magazine and then labeling them from within bconsole + using the label + barcodes command, to make + adding volume files to a magazine easier and to facilitate a consistent + naming scheme, vchanger implements the CREATEVOLS command as:

+
vchanger config_file CREATEVOLS mag_bay count [start] [--pool=pool_name] [--label=prefix_string]
+

CREATEVOLS provides an easy way to add new volume files to a magazine and + label them into an appropriate pool. Volume files can be added to a + magazine at any time. Appendix A covers details of the CREATEVOLS + command.

+

10. Testing vchanger

+

Vchanger may be tested by running it from the command line. You must + either invoke vchanger as root or else as the user:group specified in the + vchanger configuration file, (which must be the same user:group that the + Bacula Storage Daemon runs as). If invoked as root, vchanger will switch + users to run as the user:group specified in the vchanger configuration + file given in the first parameter.

+

For example purposes, an autochanger using vchanger with USB external + drives will be designed step by step. Initially, two USB drives will be + initialized. For each of the two drives, the output from the dmesg command + is used to determine the assigned block device, and the blkid command used + to determine the UUID of its ext4 filesystem. A vchanger configuration + file is defined with Magazine directives assigning the empty USB drive + partitions to an autochanger named vchanger-1.

+
# /etc/vchanger/vchanger-1.conf
+Storage Resource = vchanger-1
User = bacula
Group = tape
Magazine = UUID:9667f83c-6150-44c7-b0d4-57564f174b35 +Magazine = UUID:5039284a-4312-57d1-92c4-354710032c79 +#eof
+

This defines an autochanger associated with a bacula-dir.conf Storage + resource named vchanger-1, with the Bacula Storage Daemon running as user + bacula:tape, and using two filesystems on removable USB drives as its + magazines. The filesystems will be mounted using udev, and the automount + scripts in the scripts directory of the vchanger source have been + installed to /usr/libexec/vchanger and the vchanger-genudevrules script + installed to /usr/bin. The udev rules needed to automount the magazine + filesystems can be generated and added to the udev rules with:

+
[]# vchanger-genudevrules >/etc/udev/rules.d/96-vchanger.rules
[]# udevadm control --reload-rules
+

A directory is created under which udev will mount the filesystems. The + filesystems will be mounted at subdirectories of this directory using the + filesystem UUID string as the subdirectory name.

+
[]# mkdir /mnt/vchanger
[]# chown bacula:tape /mnt/vchanger
[]# chmod 750 /mnt/vchanger
[]# echo "MOUNT_DIR=/mnt/vchanger" >/etc/sysconfig/vchanger
[]# echo "MOUNT_OPTIONS= >>/etc/sysconfig/vchanger
+

Now, attaching the USB drives should cause udev to invoke the + vchanger-mount-uuid.sh script to mount the filesystems as subdirectories + of the directory defined by the MOUNT_DIR variable specified in + /etc/sysconfig/vchanger (or /etc/default/vchanger).

+
[]# ls -1 /mnt/vchanger
+9667f83c-6150-44c7-b0d4-57564f174b35
+5039284a-4312-57d1-92c4-354710032c79
+
+

The owner and permissions must be set for the new filesystems in order to + give read/write access to the user that the Bacula Storage Daemon runs as:

+
[]# chown bacula:tape /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+[]# chmod 750 /mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+][# chown bacula:tape /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
+][# chmod 750 /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
+
+

The Autochanger resource that this autochanger is associated with is + defined in bacula-dir.conf as:

+
# /etc/bacula/bacula-dir.conf
...
# vchanger autochanger vchanger-1 +Autochanger { + Name = vchanger-1 + Address = 192.168.1.9 + SDPort = 9103 + Password = "password" + Device = usb-vchanger-1 + Media Type = File + Maximum Concurrent Jobs = 20 +} +... +#eof
+

The Device resource pointed to by the Storage resource is defined in + bacula-sd.conf as:

+
# /etc/bacula/bacula-sd.conf
...
# vchanger autochanger vchanger-1 +Autochanger { + Name = usb-vchanger-1 + Device = usb-vchanger-1-drive-0,usb-vchanger-1-drive-1 + Changer Command = "vchanger %c %o %S %a %d" + Changer Device = "/etc/vchanger/vchanger-1.conf" +} + +Device { + Name = usb-vchanger-1-drive-0 + Drive Index = 0 + Autochanger = yes; + Device Type = File + Media Type = File + Removable Media = no; + Random Access = yes; + Maximum Concurrent Jobs = 1 + Archive Device = "/var/spool/vchanger/vchanger-1/0" +} + +Device { + Name = usb-vchanger-1-drive-1 + Drive Index = 1 + Autochanger = yes; + Device Type = File + Media Type = File + Removable Media = no; + Random Access = yes; + Maximum Concurrent Jobs = 1 + Archive Device = "/var/spool/vchanger/vchanger-1/1" +} +... +#eof
+

After detaching both of the USB drives, list the status of the magazine + partitions currently in use by invoking (as root):

+
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
+0:::
1:::
+

The output from the LISTMAGS + command shows that + neither of the magazine partitions is mounted, which is correct, since + neither of the two USB drives are attached. After attaching the USB drive + having the partition we defined in the first + Magazine directive, the output is:

+
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
+0:::/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+1:::

The output from the LISTMAGS command - now shows that the first defined magazine is mounted, but it has no - volumes. We can now add some - volumes with:

-
[]# vchanger /etc/vchanger/vchanger-1.conf createvols 0 2
-creating label 'vchanger-1_0_0'
-creating label 'vchanger-1_0_1'
-Created 2 volume files on magazine 0
-
-

Now the output from a LISTMAGS - command:

-
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
-0:2:1:/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
-1:::
-

will show that the first defined magazine is mounted and has 2 volumes - mapped to virtual slots 1-2. We can issue the LIST command to see what - volumes the vchanger-1 autochanger has in its virtual slots.

-
[]# vchanger /etc/vchanger/vchanger-1.conf list
-1:vchanger-1_0_0
2:vchanger-1_0_1
-

This shows that two volume files are in slots 1 and 2. We can use - bconsole to see what Bacula thinks the autochanger status is.

-
[]# echo "status slots storage=vchanger-1 drive=0" | bconsole
-Connecting to Director 127.0.0.1:9101
-1000 OK: bacula-dir Version: 5.2.13 (19 February 2013)
-Enter a period to cancel a command.
-status slots storage=vchanger-1 drive=0
-Automatically selected Catalog: MyCatalog
-Using Catalog "MyCatalog"
-Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
-3306 Issuing autochanger "slots" command.
-Device "vchanger-1" has 2 slots.
-Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
-3306 Issuing autochanger "list" command.
- Slot |   Volume Name    |   Status  |     Media Type       |      Pool          |
-------+------------------+-----------+----------------------+--------------------|
-    1 | vchanger-1_0_0   |    Append |                 File |            Scratch |
-    2 | vchanger-1_0_1   |    Append |                 File |            Scratch | 
-

This shows that the CREATEVOLS - command has created two volume files on the first magazine and has - successfully labeled them using the label barcodes command via - bconsole.

-

Now attach the second USB drive and add some volumes to it using:

-
[]# vchanger /etc/vchanger/vchanger-1.conf createvols 1 3
-creating label 'vchanger-1_1_0'
-creating label 'vchanger-1_1_1'
creating label 'vchanger-1_1_2'
Created 3 volume files on magazine 1 -
-

and confirm that the volumes were added as expected with:

-
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
0:2:1:/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35 -1:3:3:/mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79

[]# vchanger /etc/vchanger/vchanger-1.conf list -1:vchanger-1_0_0
2:vchanger-1_0_1
3:vchanger-1_1_0
4:vchanger-1_1_1
5:vchanger-1_1_2

[]# echo "status slots storage=vchanger-1 drive=0" | bconsole -Connecting to Director 127.0.0.1:9101 -1000 OK: bacula-dir Version: 5.2.13 (19 February 2013) -Enter a period to cancel a command. -status slots storage=vchanger-1 drive=0 -Automatically selected Catalog: MyCatalog -Using Catalog "MyCatalog" -Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ... -3306 Issuing autochanger "slots" command. -Device "vchanger-1" has 2 slots. -Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ... -3306 Issuing autochanger "list" command. - Slot | Volume Name | Status | Media Type | Pool | -------+------------------+-----------+----------------------+--------------------| - 1 | vchanger-1_0_0 | Append | File | Scratch |
2 | vchanger-1_0_1 | Append | File | Scratch | - 3 | vchanger-1_1_0 | Append | File | Scratch | - 4 | vchanger-1_1_1 | Append | File | Scratch | - 5 | vchanger-1_1_2 | Append | File | Scratch | -
-

If the first USB drive is now unplugged, the script invoked by udev + now shows that the first defined magazine is mounted, but it has no + volumes. We can now add some + volumes with:

+
[]# vchanger /etc/vchanger/vchanger-1.conf createvols 0 2
+creating label 'vchanger-1_0000_0000'
+creating label 'vchanger-1_0000_0001'
+Created 2 volume files on magazine 0
+
+

Now the output from a LISTMAGS + command:

+
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
+0:2:1:/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35
+1:::
+

will show that the first defined magazine is mounted and has 2 volumes + mapped to virtual slots 1-2. We can issue the LIST command to see what + volumes the vchanger-1 autochanger has in its virtual slots.

+
[]# vchanger /etc/vchanger/vchanger-1.conf list
+1:vchanger-1_0000_0000
2:vchanger-1_0000_0001
+

This shows that two volume files are in slots 1 and 2. We can use + bconsole to see what Bacula thinks the autochanger status is.

+
[]# echo "status slots storage=vchanger-1 drive=0" | bconsole
+Connecting to Director 127.0.0.1:9101
+1000 OK: bacula-dir Version: 5.2.13 (19 February 2013)
+Enter a period to cancel a command.
+status slots storage=vchanger-1 drive=0
+Automatically selected Catalog: MyCatalog
+Using Catalog "MyCatalog"
+Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
+3306 Issuing autochanger "slots" command.
+Device "vchanger-1" has 2 slots.
+Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
+3306 Issuing autochanger "list" command.
+ Slot |   Volume Name          |   Status  |     Media Type       |      Pool          |
+------+------------------------+-----------+----------------------+--------------------|
+    1 | vchanger-1_0000_0000   |    Append |                 File |            Scratch |
+    2 | vchanger-1_0000_0001   |    Append |                 File |            Scratch | 
+

This shows that the CREATEVOLS + command has created two volume files on the first magazine and has + successfully labeled them using the label barcodes command via + bconsole.

+

Now attach the second USB drive and add some volumes to it using:

+
[]# vchanger /etc/vchanger/vchanger-1.conf createvols 1 3
+creating label 'vchanger-1_0001_0000'
+creating label 'vchanger-1_0001_0001'
creating label 'vchanger-1_0001_0002'
Created 3 volume files on magazine 1 +
+

and confirm that the volumes were added as expected with:

+
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
0:2:1:/mnt/vchanger/9667f83c-6150-44c7-b0d4-57564f174b35 +1:3:3:/mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79

[]# vchanger /etc/vchanger/vchanger-1.conf list +1:vchanger-1_0000_0000
2:vchanger-1_0000_0001
3:vchanger-1_0001_0000
4:vchanger-1_0001_0001
5:vchanger-1_0001_0002

[]# echo "status slots storage=vchanger-1 drive=0" | bconsole +Connecting to Director 127.0.0.1:9101 +1000 OK: bacula-dir Version: 5.2.13 (19 February 2013) +Enter a period to cancel a command. +status slots storage=vchanger-1 drive=0 +Automatically selected Catalog: MyCatalog +Using Catalog "MyCatalog" +Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ... +3306 Issuing autochanger "slots" command. +Device "vchanger-1" has 2 slots. +Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ... +3306 Issuing autochanger "list" command. + Slot | Volume Name | Status | Media Type | Pool | +------+------------------------+-----------+----------------------+--------------------| + 1 | vchanger-1_0000_0000 | Append | File | Scratch |
2 | vchanger-1_0000_0001 | Append | File | Scratch | + 3 | vchanger-1_0001_0000 | Append | File | Scratch | + 4 | vchanger-1_0001_0001 | Append | File | Scratch | + 5 | vchanger-1_0001_0002 | Append | File | Scratch | +
+

If the first USB drive is now unplugged, the script invoked by udev should unmount the first magazine partition and cause an update - slots command to be issued. This can be confirmed with:

-
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
-0:::
-1:3:3:/mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
-
-[]# vchanger /etc/vchanger/vchanger-1.conf list
-1:
-2:
-3:vchanger-1_1_0
-4:vchanger-1_1_1
-5:vchanger-1_1_2
-
-[]# echo "status slots storage=vchanger-1 drive=0" | bconsole
-Connecting to Director 127.0.0.1:9101
-1000 OK: bacula-dir Version: 5.2.13 (19 February 2013)
-Enter a period to cancel a command.
-status slots storage=vchanger-1 drive=0
-Automatically selected Catalog: MyCatalog
-Using Catalog "MyCatalog"
-Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
-3306 Issuing autochanger "slots" command.
-Device "vchanger-1" has 2 slots.
-Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
-3306 Issuing autochanger "list" command.
- Slot |   Volume Name    |   Status  |     Media Type       |      Pool          |
-------+------------------+-----------+----------------------+--------------------|
1*| | ? | ? | ? |
2*| | ? | ? | ? |
  3 | vchanger-1_1_0 | Append | File | Scratch | - 4 | vchanger-1_1_1 | Append | File | Scratch | - 5 | vchanger-1_1_2 | Append | File | Scratch | -
-

Let's now examine what happens when a volume file is "loaded" into a - virtual drive.

-
[]# vchanger /etc/vchanger/vchanger-1.conf load 3 /dev/null 0
-[]# ls -l /var/spool/vchanger/vchanger-1
-lrwxrwxrwx 1 bacula tape 29 Mar  1 16:46 0 -> /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79/vchanger-1_1_0
--rw-r----- 1 bacula tape 21 Mar  1 16:46 bay_state-1
--rw-r----- 1 bacula tape 30 Mar  1 16:46 drive_state-0
-
-

The LOAD command created a symlink named '0' in the autochanger's work - directory that points to the file 'vchanger-1_1_0' in the filesystem - mounted at /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79, the volume - file that is mapped to virtual slot 3. The symlink named '0' is this - autochanger's drive 0. Notice that the path to this symlink was defined in + slots command to be issued. This can be confirmed with:

+
[]# vchanger /etc/vchanger/vchanger-1.conf listmags
+0:::
+1:3:3:/mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79
+
+[]# vchanger /etc/vchanger/vchanger-1.conf list
+1:
+2:
+3:vchanger-1_0001_0000
+4:vchanger-1_0001_0001
+5:vchanger-1_0001_0002
+
+[]# echo "status slots storage=vchanger-1 drive=0" | bconsole
+Connecting to Director 127.0.0.1:9101
+1000 OK: bacula-dir Version: 5.2.13 (19 February 2013)
+Enter a period to cancel a command.
+status slots storage=vchanger-1 drive=0
+Automatically selected Catalog: MyCatalog
+Using Catalog "MyCatalog"
+Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
+3306 Issuing autochanger "slots" command.
+Device "vchanger-1" has 2 slots.
+Connecting to Storage daemon vchanger-1 at 192.168.1.9:9103 ...
+3306 Issuing autochanger "list" command.
+ Slot |   Volume Name          |   Status  |     Media Type       |      Pool          |
+------+------------------------+-----------+----------------------+--------------------|
1*| | ? | ? | ? |
2*| | ? | ? | ? |
  3 | vchanger-1_0001_0000 | Append | File | Scratch | + 4 | vchanger-1_0001_0001 | Append | File | Scratch | + 5 | vchanger-1_0001_0002 | Append | File | Scratch | +
+

Let's now examine what happens when a volume file is "loaded" into a + virtual drive.

+
[]# vchanger /etc/vchanger/vchanger-1.conf load 3 /dev/null 0
+[]# ls -l /var/spool/vchanger/vchanger-1
+lrwxrwxrwx 1 bacula tape 29 Mar  1 16:46 0 -> /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79/vchanger-1_0001_0000
+-rw-r----- 1 bacula tape 21 Mar  1 16:46 bay_state-1
+-rw-r----- 1 bacula tape 30 Mar  1 16:46 drive_state-0
+
+

The LOAD command created a symlink named '0' in the autochanger's work + directory that points to the file 'vchanger-1_0001_0000' in the filesystem + mounted at /mnt/vchanger/5039284a-4312-57d1-92c4-354710032c79, the volume + file that is mapped to virtual slot 3. The symlink named '0' is this + autochanger's drive 0. Notice that the path to this symlink was defined in bacula-sd.conf as the Archive - Device for this autochanger's drive 0. When Bacula opens th the - symlink for reading or writing, it will actually be opening the volume - file that the symlink points to. 

-

The LOADED command can be used to check the status of a drive.

-
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 0
3
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 1
0
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 13
0 -
-

The first command shows that drive 0 is loaded from slot 3. The zero - result of the second command shows that drive 1 is empty. The last line - shows that drive 13 is also empty. Although only drive 0 and drive 1 were - defined for this autochanger in bacula-sd.conf, vchanger commands will - operate on an unlimited number of virtual drives without requiring any - change in the vchanger configuration file. However, Bacula will only make + Device for this autochanger's drive 0. When Bacula opens the + symlink for reading or writing, it will actually be opening the volume + file that the symlink points to. 

+

The LOADED command can be used to check the status of a drive.

+
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 0
3
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 1
0
[]# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 13
0 +
+

The first command shows that drive 0 is loaded from slot 3. The zero + result of the second command shows that drive 1 is empty. The last line + shows that drive 13 is also empty. Although only drive 0 and drive 1 were + defined for this autochanger in bacula-sd.conf, vchanger commands will + operate on an unlimited number of virtual drives without requiring any + change in the vchanger configuration file. However, Bacula will only make use of virtual drives defined by a Device - resource in bacula-sd.conf.

-

Finally, a virtual drive is unloaded using the UNLOAD command.

-
[]# vchanger /etc/vchanger/vchanger-1.conf unload 3 /dev/null 0
[]# ls -l /var/spool/vchanger/vchanger-1 --rw-r----- 1 bacula tape 21 Mar 1 16:46 bay_state-1
{}# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 0
0
-

The virtual drive is unloaded by removing its corresponding symlink.

-

It is also possible to define mountpoints and mount options for the - magazine filesystems, (by UUID), in /etc/fstab. When the - vchanger-mount-uuid.sh script is invoked by udev, it will check for the - existence of an fstab entry for the magazine filesystem's UUID, and if it - exists will mount using the /etc/fstab entry rather than using the UUID as - the name of a subdirectory of the MOUNT_DIR directory defined in - /etc/sysconfig/vchanger.

-

11. Configuring Bacula To Use The - Autochanger

-

The virtual autochanger must be defined in Bacula by adding Autochanger and Device resources to Bacula's configuration - files.

-

11.1. Configuring the Bacula Storage Daemon

-

To configure the Bacula storage daemon (bacula-sd), add an Autochanger - resource and associated Device resource(es) to bacula-sd.conf. An - example of an autochanger with 2 virtual drives is:

-
# /etc/bacula/bacula-sd.conf
-# ...
-#----  local virtual autochanger with USB drive "magazines"
-Autochanger {
-  Name = usb-changer
-  Device = usb-changer-drive-0
-  Device = usb-changer-drive-1
-  Changer Command = "vchanger %c %o %S %a %d"
-  Changer Device = "/etc/vchanger/vc1.conf"
-}
-#---  drive 0 of the usb-changer autochanger
-Device {
-  Name = usb-changer-drive-0
-  DriveIndex = 0
-  Autochanger = yes;
-  DeviceType = File
-  MediaType = File
-  ArchiveDevice = /var/spool/vchanger/vc1/0
-  RemovableMedia = no;
-  RandomAccess = yes;
-}
-#---  drive 1 of the usb-changer autochanger
-Device {
-  Name = usb-changer-drive-1
-  DriveIndex = 1
-  Autochanger = yes;
-  DeviceType = File
-  MediaType = File
-  ArchiveDevice = /var/spool/vchanger/vc1/1
-  RemovableMedia = no;
-  RandomAccess = yes;
-}# ...
-# eof
-

In the Autochanger - resource, the Changer Device - value is the path to the vchanger configuration file for this autochanger. - The Changer Command value is the - command that Bacula will execute when it needs the autochanger to perform - some function, (like loading a volume). Here, we have installed vchanger + resource in bacula-sd.conf.

+

Finally, a virtual drive is unloaded using the UNLOAD command.

+
[]# vchanger /etc/vchanger/vchanger-1.conf unload 3 /dev/null 0
[]# ls -l /var/spool/vchanger/vchanger-1 +-rw-r----- 1 bacula tape 21 Mar 1 16:46 bay_state-1
{}# vchanger /etc/vchanger/vchanger-1.conf loaded 0 /dev/null 0
0
+

The virtual drive is unloaded by removing its corresponding symlink.

+

It is also possible to define mountpoints and mount options for the + magazine filesystems, (by UUID), in /etc/fstab. When the + vchanger-mount-uuid.sh script is invoked by udev, it will check for the + existence of an fstab entry for the magazine filesystem's UUID, and if it + exists will mount using the /etc/fstab entry rather than using the UUID as + the name of a subdirectory of the MOUNT_DIR directory defined in + /etc/sysconfig/vchanger.

+

11. Configuring Bacula To Use The + Autochanger

+

The virtual autochanger must be defined in Bacula by adding Autochanger and Device resources to Bacula's configuration + files.

+

11.1. Configuring the Bacula Storage Daemon

+

To configure the Bacula storage daemon (bacula-sd), add an Autochanger + resource and associated Device resource(es) to bacula-sd.conf. An + example of an autochanger with 2 virtual drives is:

+
# /etc/bacula/bacula-sd.conf
+# ...
+#----  local virtual autochanger with USB drive "magazines"
+Autochanger {
+  Name = usb-changer
+  Device = usb-changer-drive-0
+  Device = usb-changer-drive-1
+  Changer Command = "vchanger %c %o %S %a %d"
+  Changer Device = "/etc/vchanger/vc1.conf"
+}
+#---  drive 0 of the usb-changer autochanger
+Device {
+  Name = usb-changer-drive-0
+  DriveIndex = 0
+  Autochanger = yes;
+  DeviceType = File
+  MediaType = File
+  ArchiveDevice = /var/spool/vchanger/vc1/0
+  RemovableMedia = no;
+  RandomAccess = yes;
+}
+#---  drive 1 of the usb-changer autochanger
+Device {
+  Name = usb-changer-drive-1
+  DriveIndex = 1
+  Autochanger = yes;
+  DeviceType = File
+  MediaType = File
+  ArchiveDevice = /var/spool/vchanger/vc1/1
+  RemovableMedia = no;
+  RandomAccess = yes;
+}# ...
+# eof
+

In the Autochanger + resource, the Changer Device + value is the path to the vchanger configuration file for this autochanger. + The Changer Command value is the + command that Bacula will execute when it needs the autochanger to perform + some function, (like loading a volume). Here, we have installed vchanger in /usr/bin, and Bacula is going to pass the Changer - Device value (the path to the vchanger configuration file) in - parameter 1, the API command (load, unload, etc) in parameter 2, the slot + Device value (the path to the vchanger configuration file) in + parameter 1, the API command (load, unload, etc) in parameter 2, the slot number for the command in parameter 3, the Archive - Device value from the Device - resource that has a Drive Index value - equal to the drive index for the command in parameter 4, and the drive - index for the command in parameter 5.

-

11.2. Configuring the Bacula Director

-

The Bacula Director is configured to use the virtual autochanger in - exactly the same way as it would be configured for a tape autochanger.

-
# /etc/bacula/bacula-dir.conf
-# ...
-# Local USB drive virtual autochanger
-Storage {
-  Name = vchanger-1   # same as 'Storage Resource' in the vchanger config file
-  Address = sd-server-address
-  Password = "secret_password"
-  Device = usb-changer  # name of the Autochanger resource in bacula-sd.conf
-  Media Type = File
-  Autochanger = yes;
-}
-# eof
-

Appendix A. vchanger Commands

-

A.1. LIST Command

-

Bacula - issues this command to an autochanger to list to stdout the “barcode - labels” of volumes in the autochanger's slots. Many tape autochanger - robots have barcode readers such that tapes can be affixed with an - adhesive barcode label that identifies the tape. This allows Bacula to - automate the process of creating volume labels by utilizing the - autochanger's barcode reader. Vchanger emulates barcodes for the volumes - in a virtual autochanger's slots by listing the names of volume files - mapped to each virtual slot. The empty string is listed for each slot - corresponding to an empty slot (a slot that is not currently mapped to a - volume file).

-

A.2. LISTALL Command

-

It is not clear that Bacula currently uses this command internally, and - it is not specified in the Bacula - Autochanger Interface documentation. Nevertheless, it is implemented - in Bacula's mtx-changer script since version 5.1.0 and is used by some - web-based admin utilities, so has been implemented in vchanger. This - command is similar to the LIST command except that it also lists current - drive status in addition to slot status.

-

A.3. LOAD Command

-

The load command is used to “load” a volume - file from a virtual slot into a virtual drive. A tape autochanger does - this by physically moving the tape located in the requested library slot - into a tape drive. Bacula reads and writes volume data from/to the tape - drive's device file. With vchanger, a filesystem symlink at a known path - is used as a virtual drive in place of a tape drive's device file. A - volume file is then loaded by making the target of that symlink point to - the volume file mapped to the requested slot.

-

Parameter 3 gives the autochanger slot number - of the volume to load. Parameter 4 gives the path to the device that - Bacula will read/write and is ignored by vchanger. Parameter 5 gives the - drive number of the virtual drive that the volume is to be loaded into.

-

A.4. LOADED Command

-

This command is issued to determine which - slot, if any, is loaded into a drive. If a drive is loaded, then the - virtual slot number corresponding to the loaded volume file is written to - stdout. If the drive is not loaded, the string “0” is written to stdout to - inform Bacula that the drive is not loaded.

-

A.5. SLOTS Command

-

This - command simply prints to stdout the maximum number of volume files that - the autochanger has ever had simultaneously available. For example, if 6 - USB drive filesystems are assigned as the autochanger's magazines, each - with 10 volume files, and the maximum number of USB drives that were ever - simultaneously attached is 3, then the SLOTS command will return 30. If - more storage is needed at some point and 4 of the USB drives are - simultaneously attached, then a SLOTS command will return 40. Bacula - issues this command to determine how many slots an autochanger has.

-

A.6. UNLOAD Command

-

Bacula issues this command to unload a volume - from a drive and move it back into a library slot. Vchanger accomplished - this by deleting the virtual drive's symlink.

-

A.7. CREATEVOLS Command

-

This is an extended command that is not - part of the Bacula + Device value from the Device + resource that has a Drive Index value + equal to the drive index for the command in parameter 4, and the drive + index for the command in parameter 5.

+

11.2. Configuring the Bacula Director

+

The Bacula Director is configured to use the virtual autochanger in + exactly the same way as it would be configured for a tape autochanger.

+
# /etc/bacula/bacula-dir.conf
+# ...
+# Local USB drive virtual autochanger
+Autochanger {
+  Name = vchanger-1   # same as 'Storage Resource' in the vchanger config file
+  Address = sd-server-address
+  Password = "secret_password"
+  Device = usb-changer  # name of the Autochanger resource in bacula-sd.conf
+  Media Type = File
+}
+# eof
+

Appendix A. vchanger Commands

+

A.1. LIST Command

+

Bacula + issues this command to an autochanger to list to stdout the “barcode + labels” of volumes in the autochanger's slots. Many tape autochanger + robots have barcode readers such that tapes can be affixed with an + adhesive barcode label that identifies the tape. This allows Bacula to + automate the process of creating volume labels by utilizing the + autochanger's barcode reader. Vchanger emulates barcodes for the volumes + in a virtual autochanger's slots by listing the names of volume files + mapped to each virtual slot. The empty string is listed for each slot + corresponding to an empty slot (a slot that is not currently mapped to a + volume file).

+

A.2. LISTALL Command

+

It is not clear that Bacula currently uses this command internally, and + it is not specified in the Bacula + Autochanger Interface documentation. Nevertheless, it is implemented + in Bacula's mtx-changer script since version 5.1.0 and is used by some + web-based admin utilities, so has been implemented in vchanger. This + command is similar to the LIST command except that it also lists current + drive status in addition to slot status.

+

A.3. LOAD Command

+

The load command is used to “load” + a volume file from a virtual slot into a virtual drive. A tape autochanger + does this by physically moving the tape located in the requested library + slot into a tape drive. Bacula reads and writes volume data from/to the + tape drive's device file. With vchanger, a filesystem symlink at a known + path is used as a virtual drive in place of a tape drive's device file. A + volume file is then loaded by making the target of that symlink point to + the volume file mapped to the requested slot.

+

Parameter 3 gives the autochanger slot number + of the volume to load. Parameter 4 gives the path to the device that + Bacula will read/write and is ignored by vchanger. Parameter 5 gives the + drive number of the virtual drive that the volume is to be loaded into.

+

A.4. LOADED Command

+

This command is issued to determine which + slot, if any, is loaded into a drive. If a drive is loaded, then the + virtual slot number corresponding to the loaded volume file is written to + stdout. If the drive is not loaded, the string “0” is written + to stdout to inform Bacula that the drive is not loaded.

+

A.5. SLOTS Command

+

This + command simply prints to stdout the maximum number of volume files that + the autochanger has ever had simultaneously available. For example, if 6 + USB drive filesystems are assigned as the autochanger's magazines, each + with 10 volume files, and the maximum number of USB drives that were ever + simultaneously attached is 3, then the SLOTS command will return 30. If + more storage is needed at some point and 4 of the USB drives are + simultaneously attached, then a SLOTS command will return 40. Bacula + issues this command to determine how many slots an autochanger has.

+

A.6. UNLOAD Command

+

Bacula issues this command to unload a volume + from a drive and move it back into a library slot. Vchanger accomplished + this by deleting the virtual drive's symlink.

+

A.7. CREATEVOLS Command

+

This is an extended command that is not + part of the Bacula Autochanger - Interface API, and is - used to add volume files to a magazine filesystem and cause them to be - labeled with a Bacula volume label. The format of this command is

-
vchanger config_file createcols mag_ndx count [start_num] [--label=prefix] [--pool=pool_name]
-

where:

- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

config_file
-

-
-

Path to the autochanger's configuration file.
-

-
-

mag_ndx
-

-
-

The zero-based index of the magazine where volume files are to be - created. The index refers to a Magazine directive in the - configuration file specified by config_file, where 0 is the first - Magazine directive, 1, is the second Magazine directive, etc.
-

-
-

count

-
-

The number of volume files to create.

-
-

start_num

-
-

The lowest integer to append to the prefix string when forming - unique volume file names. The default is -1. A negative value - causes  the uniqueness number appended to the prefix to be - greater than the currently highest number used for file names - beginning with the given prefix.

-
-

prefix

-
-

An optional prefix string used in naming the created volume - files. The default prefix string is the autochanger's name - concatenated with '_' plus the magazine index number.

-
-

pool_name

-
-

The name of the pool into which created volumes should be - labeled. The default is "Scratch".

-
+ Interface API, and is + used to add volume files to a magazine filesystem and cause them to be + labeled with a Bacula volume label. The format of this command is

+
vchanger config_file createcols mag_ndx count [start_num] [--label=prefix] [--pool=pool_name]
+

where:

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

config_file
+

+
+

Path to the autochanger's configuration file.
+

+
+

mag_ndx
+

+
+

The zero-based index of the magazine where volume files are to be + created. The index refers to a Magazine directive in the + configuration file specified by config_file, where 0 is the first + Magazine directive, 1, is the second Magazine directive, etc.
+

+
+

count

+
+

The number of volume files to create.

+
+

start_num

+
+

The lowest integer to append to the prefix string when forming + unique volume file names. The default is -1. A negative value + causes  the uniqueness number appended to the prefix to be + greater than the currently highest number used for file names + beginning with the given prefix.

+
+

prefix

+
+

An optional prefix string used in naming the created volume + files.  The filenames created will be the prefix string + concatenated with the integer uniqueness number. The default + prefix string is the autochanger's name concatenated with '_', + followed by the magazine index number, followed by another '_'.

+
+

pool_name

+
+

The name of the pool into which created volumes should be + labeled. The default is "Scratch".

+

When new empty volume files are added, vchanger will issue a label - barcodes command to Bacula via bconsole to cause Bacula to write - volume labels on the new volume files. Adding volumes to a magazine will - change the virtual slot to volume mapping and may increase the - autochanger's slot count (the value returned by the SLOTS command). For + barcodes command to Bacula via bconsole to cause Bacula to write + volume labels on the new volume files. Adding volumes to a magazine will + change the virtual slot to volume mapping and may increase the + autochanger's slot count (the value returned by the SLOTS command). For this reason, vchanger will also issue the update - slots command to Bacula whenever new volume files are added.

-

A.8. LISTMAGS Command

-

This is an extended command that is not part of the Bacula + slots command to Bacula whenever new volume files are added.

+

A.8. LISTMAGS Command

+

This is an extended command that is not part of the Bacula Autochanger - Interface API, and is used to list status information about the - autochanger's assigned magazines. The format of this command is

-
vchanger config_file listmags
-

where:

- - - - - - - - -
-

config_file

-
-

Path to the autochanger configuration file

-
-

The contents of each magazine bay are written - to stdout, one line per bay. The format of an output line is:

-
mag_ndx:count:start_slot:mountpoint
-

where:

- - - - - - - - - - - - - - - - - - - - -
-

mag_ndx

-
-

The zero-based index of the magazine

-
-

count

-
-

The number of volume files on the magazine

-
-

start_slot

-
-

The beginning of the ranges of contiguous virtual slots mapped to - the magazine's volume files.

-
-

mountpoint

-
-

The mountpoint/directory of a mounted magazine, else blank if not - mounted.

-
-

A.9. REFRESH Command

-

This is an extended command that is not part of the Bacula Autochanger - Interface API, and is used to instruct vchanger to scan attached magazines - and recalculate the virtual-slot-to-volume-file mapping, sending an update slots - command to Bacula if any changes are detected. In general, this command is - designed to be invoked from a shell script launched by a udev event or - other automount mechanism.

- - + Interface API, and is used to list status information about the + autochanger's assigned magazines. The format of this command is

+
vchanger config_file listmags
+

where:

+ + + + + + + + +
+

config_file

+
+

Path to the autochanger configuration file

+
+

The contents of each magazine bay are written + to stdout, one line per bay. The format of an output line is:

+
mag_ndx:count:start_slot:mountpoint
+

where:

+ + + + + + + + + + + + + + + + + + + + +
+

mag_ndx

+
+

The zero-based index of the magazine

+
+

count

+
+

The number of volume files on the magazine

+
+

start_slot

+
+

The beginning of the ranges of contiguous virtual slots mapped to + the magazine's volume files.

+
+

mountpoint

+
+

The mountpoint/directory of a mounted magazine, else blank if not + mounted.

+
+

A.9. REFRESH Command

+

This is an extended command that is not part of the Bacula Autochanger + Interface API, and is used to instruct vchanger to scan attached magazines + and recalculate the virtual-slot-to-volume-file mapping, sending an update slots + command to Bacula if any changes are detected. In general, this command is + designed to be invoked from a shell script launched by a udev event or + other automount mechanism.

+ + diff --git a/rpm/vchanger.el6.spec b/rpm/vchanger.el6.spec index 53daa50..5099297 100644 --- a/rpm/vchanger.el6.spec +++ b/rpm/vchanger.el6.spec @@ -3,8 +3,8 @@ # Summary: A virtual autochanger for Bacula Name: vchanger -Version: 1.0.1 -Release: 1.el6 +Version: 1.0.3 +Release: 1%{dist} License: GPLv2 Group: System Environment/Daemons Source: http://sourceforge.net/projects/vchanger/files/vchanger/%{version}/vchanger-%{version}.tar.gz @@ -35,6 +35,7 @@ make rm -rf $RPM_BUILD_ROOT mkdir -p ${RPM_BUILD_ROOT}%{_bindir} mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name} mkdir -p ${RPM_BUILD_ROOT}%{_libexecdir}/%{name} mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man5 @@ -44,6 +45,7 @@ mkdir -m 0770 -p ${RPM_BUILD_ROOT}%{_localstatedir}/spool/%{name} mkdir -m 0755 -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name} make DESTDIR=${RPM_BUILD_ROOT} install-strip install -m 0644 scripts/vchanger.default ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/vchanger +install -m 0644 contrib/vchanger.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/vchanger %clean rm -rf $RPM_BUILD_ROOT @@ -52,6 +54,7 @@ rm -rf $RPM_BUILD_ROOT %defattr(-,root,root,-) %{_bindir}/* %{_libexecdir}/%{name}/* +%{_sysconfdir}/logrotate.d/* %doc %{_docdir}/%{name}-%{version}/AUTHORS %doc %{_docdir}/%{name}-%{version}/ChangeLog %doc %{_docdir}/%{name}-%{version}/COPYING @@ -85,6 +88,10 @@ if [ $1 -eq 1 ] ; then fi %changelog +* Thu May 6 2020 Josh Fisher +- Updated to release 1.0.2 +* Thu Jun 14 2018 Josh Fisher +- Updated to release 1.0.2 * Wed Jun 3 2015 Josh Fisher - Updated to release 1.0.1 * Fri Apr 3 2015 Josh Fisher diff --git a/rpm/vchanger.el7.spec b/rpm/vchanger.el7.spec index 4c2caf0..c1f2225 100644 --- a/rpm/vchanger.el7.spec +++ b/rpm/vchanger.el7.spec @@ -3,8 +3,8 @@ # Summary: A virtual autochanger for Bacula Name: vchanger -Version: 1.0.1 -Release: 1.el7 +Version: 1.0.3 +Release: 1%{dist} License: GPLv2 Group: System Environment/Daemons Source: http://sourceforge.net/projects/vchanger/files/vchanger/%{version}/vchanger-%{version}.tar.gz @@ -14,6 +14,9 @@ Packager: Josh Fisher BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires(post): /bin/mkdir, /bin/chown, /usr/bin/getent, /usr/sbin/useradd, /usr/sbin/groupadd +# For libudev support +BuildRequires: systemd-devel + %description Vchanger implements a virtual autochanger for use with the Bacula open source network backup system. Vchanger emulates a magazine-based tape autoloader @@ -35,6 +38,7 @@ make rm -rf $RPM_BUILD_ROOT mkdir -p ${RPM_BUILD_ROOT}%{_bindir} mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name} mkdir -p ${RPM_BUILD_ROOT}%{_libexecdir}/%{name} mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man5 @@ -44,6 +48,7 @@ mkdir -m 0770 -p ${RPM_BUILD_ROOT}%{_localstatedir}/spool/%{name} mkdir -m 0755 -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name} make DESTDIR=${RPM_BUILD_ROOT} install-strip install -m 0644 scripts/vchanger.default ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/vchanger +install -m 0644 contrib/vchanger.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/vchanger %clean rm -rf $RPM_BUILD_ROOT @@ -52,6 +57,7 @@ rm -rf $RPM_BUILD_ROOT %defattr(-,root,root,-) %{_bindir}/* %{_libexecdir}/%{name}/* +%{_sysconfdir}/logrotate.d/* %doc %{_docdir}/%{name}-%{version}/AUTHORS %doc %{_docdir}/%{name}-%{version}/ChangeLog %doc %{_docdir}/%{name}-%{version}/COPYING @@ -85,6 +91,10 @@ if [ $1 -eq 1 ] ; then fi %changelog +* Thu May 6 2020 Josh Fisher +- Updated to release 1.0.2 +* Thu Jun 14 2018 Josh Fisher +- Updated to release 1.0.2 * Wed Jun 3 2015 Josh Fisher - Updated to release 1.0.1 * Fri Apr 3 2015 Josh Fisher diff --git a/rpm/vchanger.f29.spec b/rpm/vchanger.f29.spec new file mode 100644 index 0000000..b1a9ddc --- /dev/null +++ b/rpm/vchanger.f29.spec @@ -0,0 +1,106 @@ +# +# Spec file for vchanger - Fedora 29 +# + +%global debug_package %{nil} + +Summary: A virtual autochanger for Bacula +Name: vchanger +Version: 1.0.3 +Release: 1%{dist} +License: GPLv2 +Group: System Environment/Daemons +Source: http://sourceforge.net/projects/vchanger/files/vchanger/%{version}/vchanger-%{version}.tar.gz +URL: http://vchanger.sourceforge.net +Vendor: Josh Fisher. +Packager: Josh Fisher +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +Requires(post): /usr/bin/mkdir, /usr/bin/chown, /usr/bin/getent, /usr/sbin/useradd, /usr/sbin/groupadd + +# For libudev support +BuildRequires: systemd-devel + +%description +Vchanger implements a virtual autochanger for use with the Bacula open source +network backup system. Vchanger emulates a magazine-based tape autoloader +using disk file system mountpoints as virtual magazines and the files in +each virtual magazine as virtual tape volumes. Vchanger is primarily +designed to use an unlimited number of removable disk drives as an easily +scalable virtual autochanger and allow seamless removal of backups for +offsite storage. + +%prep +%setup -q -n %{name} + +%build +CFLAGS="%{optflags} -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE" \ +%configure +make + +%install +rm -rf $RPM_BUILD_ROOT +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name} +mkdir -p ${RPM_BUILD_ROOT}%{_libexecdir}/%{name} +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man5 +mkdir -p ${RPM_BUILD_ROOT}%{_mandir}/man8 +mkdir -p ${RPM_BUILD_ROOT}%{_docdir}/%{name}-%{version} +mkdir -m 0770 -p ${RPM_BUILD_ROOT}%{_localstatedir}/spool/%{name} +mkdir -m 0755 -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name} +make DESTDIR=${RPM_BUILD_ROOT} install-strip +install -m 0644 scripts/vchanger.default ${RPM_BUILD_ROOT}%{_sysconfdir}/sysconfig/vchanger +install -m 0644 contrib/vchanger.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/vchanger + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%{_bindir}/* +%{_libexecdir}/%{name}/* +%{_sysconfdir}/logrotate.d/* +%doc %{_docdir}/%{name}-%{version}/AUTHORS +%doc %{_docdir}/%{name}-%{version}/ChangeLog +%doc %{_docdir}/%{name}-%{version}/COPYING +%doc %{_docdir}/%{name}-%{version}/INSTALL +%doc %{_docdir}/%{name}-%{version}/NEWS +%doc %{_docdir}/%{name}-%{version}/README +%doc %{_docdir}/%{name}-%{version}/ReleaseNotes +%doc %{_docdir}/%{name}-%{version}/vchanger-example.conf +%doc %{_docdir}/%{name}-%{version}/example-vchanger-udev.rules +%doc %{_docdir}/%{name}-%{version}/vchangerHowto.html +%{_mandir}/man5/* +%{_mandir}/man8/* +%config(noreplace) %{_sysconfdir}/sysconfig/vchanger + +%post +if [ $1 -eq 1 ] ; then + /usr/bin/getent group tape &>/dev/null || /usr/sbin/groupadd -r tape + /usr/bin/getent group bacula &>/dev/null || /usr/sbin/groupadd -r bacula + /usr/bin/getent passwd bacula &>/dev/null || /usr/sbin/useradd -r -g bacula -d %{_localstatedir}/spool/bacula -s /bin/bash bacula + if [ ! -d %{_localstatedir}/spool/vchanger ] ; then + /bin/mkdir -p -m 0770 %{_localstatedir}/spool/vchanger + /bin/chown bacula:tape %{_localstatedir}/spool/vchanger + fi + if [ ! -d %{_localstatedir}/log/vchanger ] ; then + /bin/mkdir -p -m 0755 %{_localstatedir}/log/vchanger + /bin/chown bacula:tape %{_localstatedir}/log/vchanger + fi + if [ ! -d %{_sysconfdir}/vchanger ] ; then + /bin/mkdir -p -m 0755 %{_sysconfdir}/vchanger + fi +fi + +%changelog +* Thu May 6 2020 Josh Fisher +- Updated to release 1.0.3 +* Mon Dec 24 2018 Steven A. Falco +- Various changes for Fedora 29 +* Thu Jun 14 2018 Josh Fisher +- Updated to release 1.0.2 +* Wed Jun 3 2015 Josh Fisher +- Updated to release 1.0.1 +* Fri Apr 3 2015 Josh Fisher +- Initial spec file diff --git a/scripts/vchanger-genudevrules b/scripts/vchanger-genudevrules index 9eb52b6..ca7cc70 100644 --- a/scripts/vchanger-genudevrules +++ b/scripts/vchanger-genudevrules @@ -1,6 +1,6 @@ #!/bin/bash # -# vchanger-genudevrules ( vchanger v.1.0.1 ) 2015-06-03 +# vchanger-genudevrules ( vchanger v.1.0.3 ) 2020-05-06 # # Search all autochanger configuration files for magazines being defined # by filesystem UUID and print to stdout the udev rules for launching diff --git a/scripts/vchanger-launch-mount.sh b/scripts/vchanger-launch-mount.sh index 552662f..e21eada 100644 --- a/scripts/vchanger-launch-mount.sh +++ b/scripts/vchanger-launch-mount.sh @@ -1,12 +1,14 @@ #!/bin/sh # -# vchanger-launch-mount.sh ( vchanger v.1.0.1 ) 2015-06-03 +# vchanger-launch-mount.sh ( vchanger v.1.0.3 ) 2020-05-06 # -# This script is used to run the vchanger-mount-uuid.sh script in -# another [background] process launched by the at command in order -# to prevent delays when invoked by a udev rule. +# This script is used to run the vchanger-mount-uuid.sh script as +# a detached process and immediately exit. This is to prevent delays +# when invoked by a udev rule. # VCHANGER_MOUNT=/usr/libexec/vchanger/vchanger-mount-uuid.sh -{ -$VCHANGER_MOUNT $1 -} & + +# For some reason, nohup doesn't work, but "at now" does. This may have to +# do with cgroups. +#nohup $VCHANGER_MOUNT $1 /dev/null 2>&1 & +echo "$VCHANGER_MOUNT $1 /dev/null 2>&1" | at now diff --git a/scripts/vchanger-launch-umount.sh b/scripts/vchanger-launch-umount.sh index e4726bb..ae175b3 100644 --- a/scripts/vchanger-launch-umount.sh +++ b/scripts/vchanger-launch-umount.sh @@ -1,12 +1,14 @@ #!/bin/sh # -# vchanger-launch-umount.sh ( vchanger v.1.0.1 ) 2015-06-03 +# vchanger-launch-umount.sh ( vchanger v.1.0.3 ) 2020-05-06 # # This script is used to run the vchanger-umount-uuid.sh script in # another [background] process launched by the at command in order # to prevent delays when invoked by a udev rule. # VCHANGER_UMOUNT=/usr/libexec/vchanger/vchanger-umount-uuid.sh -{ -$VCHANGER_UMOUNT $1 -} & + +# For some reason, nohup doesn't work, but "at now" does. This may have to +# do with cgroups. +#nohup $VCHANGER_UMOUNT $1 /dev/null 2>&1 & +echo "$VCHANGER_UMOUNT $1 /dev/null 2>&1" | at now diff --git a/scripts/vchanger-mount-uuid.sh b/scripts/vchanger-mount-uuid.sh index ad01fac..52b05ca 100644 --- a/scripts/vchanger-mount-uuid.sh +++ b/scripts/vchanger-mount-uuid.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# vchanger-mount-uuid.sh ( vchanger v.1.0.1 ) 2015-06-03 +# vchanger-mount-uuid.sh ( vchanger v.1.0.3 ) 2020-05-06 # # This script is used to mount the filesystem having the # UUID specified in parameter 1 at a fixed path. The path diff --git a/scripts/vchanger-umount-uuid.sh b/scripts/vchanger-umount-uuid.sh index d26712b..bf6a599 100644 --- a/scripts/vchanger-umount-uuid.sh +++ b/scripts/vchanger-umount-uuid.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# vchanger-umount-uuid.sh ( vchanger v.1.0.1 ) 2015-06-03 +# vchanger-umount-uuid.sh ( vchanger v.1.0.3 ) 2020-05-06 # # This script is used to unmount the filesystem having the # UUID specified in parameter 1. The mountpoint path diff --git a/src/Makefile.am b/src/Makefile.am index d1805b1..7f86b37 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,11 +4,10 @@ AM_CXXFLAGS = -DLOCALSTATEDIR='"${localstatedir}"' AM_LDFLAGS = @WINLDADD@ bin_PROGRAMS = vchanger vchanger_SOURCES = compat/getline.c compat/gettimeofday.c \ - compat/localtime_r.c \ - compat/readlink.c \ + compat/readlink.c compat/semaphore.c \ compat/symlink.c compat/sleep.c compat/syslog.c \ win32_util.c uuidlookup.c bconsole.cpp \ - tstring.cpp inifile.cpp mypopen.cpp \ + tstring.cpp inifile.cpp mymutex.cpp mypopen.cpp \ vconf.cpp loghandler.cpp errhandler.cpp \ util.cpp changerstate.cpp diskchanger.cpp \ vchanger.cpp diff --git a/src/Makefile.in b/src/Makefile.in index 4d70f0c..c329eba 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -91,12 +91,12 @@ CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am_vchanger_OBJECTS = getline.$(OBJEXT) gettimeofday.$(OBJEXT) \ - localtime_r.$(OBJEXT) readlink.$(OBJEXT) symlink.$(OBJEXT) \ + readlink.$(OBJEXT) semaphore.$(OBJEXT) symlink.$(OBJEXT) \ sleep.$(OBJEXT) syslog.$(OBJEXT) win32_util.$(OBJEXT) \ uuidlookup.$(OBJEXT) bconsole.$(OBJEXT) tstring.$(OBJEXT) \ - inifile.$(OBJEXT) mypopen.$(OBJEXT) vconf.$(OBJEXT) \ - loghandler.$(OBJEXT) errhandler.$(OBJEXT) util.$(OBJEXT) \ - changerstate.$(OBJEXT) diskchanger.$(OBJEXT) \ + inifile.$(OBJEXT) mymutex.$(OBJEXT) mypopen.$(OBJEXT) \ + vconf.$(OBJEXT) loghandler.$(OBJEXT) errhandler.$(OBJEXT) \ + util.$(OBJEXT) changerstate.$(OBJEXT) diskchanger.$(OBJEXT) \ vchanger.$(OBJEXT) vchanger_OBJECTS = $(am_vchanger_OBJECTS) vchanger_LDADD = $(LDADD) @@ -269,11 +269,10 @@ AM_CFLAGS = -DLOCALSTATEDIR='"${localstatedir}"' AM_CXXFLAGS = -DLOCALSTATEDIR='"${localstatedir}"' AM_LDFLAGS = @WINLDADD@ vchanger_SOURCES = compat/getline.c compat/gettimeofday.c \ - compat/localtime_r.c \ - compat/readlink.c \ + compat/readlink.c compat/semaphore.c \ compat/symlink.c compat/sleep.c compat/syslog.c \ win32_util.c uuidlookup.c bconsole.cpp \ - tstring.cpp inifile.cpp mypopen.cpp \ + tstring.cpp inifile.cpp mymutex.cpp mypopen.cpp \ vconf.cpp loghandler.cpp errhandler.cpp \ util.cpp changerstate.cpp diskchanger.cpp \ vchanger.cpp @@ -372,10 +371,11 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getline.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gettimeofday.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/inifile.Po@am__quote@ -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/localtime_r.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/loghandler.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mymutex.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mypopen.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/readlink.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/semaphore.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sleep.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/symlink.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/syslog.Po@am__quote@ @@ -428,20 +428,6 @@ gettimeofday.obj: compat/gettimeofday.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o gettimeofday.obj `if test -f 'compat/gettimeofday.c'; then $(CYGPATH_W) 'compat/gettimeofday.c'; else $(CYGPATH_W) '$(srcdir)/compat/gettimeofday.c'; fi` -localtime_r.o: compat/localtime_r.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT localtime_r.o -MD -MP -MF $(DEPDIR)/localtime_r.Tpo -c -o localtime_r.o `test -f 'compat/localtime_r.c' || echo '$(srcdir)/'`compat/localtime_r.c -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/localtime_r.Tpo $(DEPDIR)/localtime_r.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compat/localtime_r.c' object='localtime_r.o' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o localtime_r.o `test -f 'compat/localtime_r.c' || echo '$(srcdir)/'`compat/localtime_r.c - -localtime_r.obj: compat/localtime_r.c -@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT localtime_r.obj -MD -MP -MF $(DEPDIR)/localtime_r.Tpo -c -o localtime_r.obj `if test -f 'compat/localtime_r.c'; then $(CYGPATH_W) 'compat/localtime_r.c'; else $(CYGPATH_W) '$(srcdir)/compat/localtime_r.c'; fi` -@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/localtime_r.Tpo $(DEPDIR)/localtime_r.Po -@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compat/localtime_r.c' object='localtime_r.obj' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ -@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o localtime_r.obj `if test -f 'compat/localtime_r.c'; then $(CYGPATH_W) 'compat/localtime_r.c'; else $(CYGPATH_W) '$(srcdir)/compat/localtime_r.c'; fi` - readlink.o: compat/readlink.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT readlink.o -MD -MP -MF $(DEPDIR)/readlink.Tpo -c -o readlink.o `test -f 'compat/readlink.c' || echo '$(srcdir)/'`compat/readlink.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/readlink.Tpo $(DEPDIR)/readlink.Po @@ -456,6 +442,20 @@ readlink.obj: compat/readlink.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o readlink.obj `if test -f 'compat/readlink.c'; then $(CYGPATH_W) 'compat/readlink.c'; else $(CYGPATH_W) '$(srcdir)/compat/readlink.c'; fi` +semaphore.o: compat/semaphore.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT semaphore.o -MD -MP -MF $(DEPDIR)/semaphore.Tpo -c -o semaphore.o `test -f 'compat/semaphore.c' || echo '$(srcdir)/'`compat/semaphore.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/semaphore.Tpo $(DEPDIR)/semaphore.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compat/semaphore.c' object='semaphore.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o semaphore.o `test -f 'compat/semaphore.c' || echo '$(srcdir)/'`compat/semaphore.c + +semaphore.obj: compat/semaphore.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT semaphore.obj -MD -MP -MF $(DEPDIR)/semaphore.Tpo -c -o semaphore.obj `if test -f 'compat/semaphore.c'; then $(CYGPATH_W) 'compat/semaphore.c'; else $(CYGPATH_W) '$(srcdir)/compat/semaphore.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/semaphore.Tpo $(DEPDIR)/semaphore.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='compat/semaphore.c' object='semaphore.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o semaphore.obj `if test -f 'compat/semaphore.c'; then $(CYGPATH_W) 'compat/semaphore.c'; else $(CYGPATH_W) '$(srcdir)/compat/semaphore.c'; fi` + symlink.o: compat/symlink.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT symlink.o -MD -MP -MF $(DEPDIR)/symlink.Tpo -c -o symlink.o `test -f 'compat/symlink.c' || echo '$(srcdir)/'`compat/symlink.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/symlink.Tpo $(DEPDIR)/symlink.Po diff --git a/src/bconsole.cpp b/src/bconsole.cpp index c3a80a0..dbd494e 100644 --- a/src/bconsole.cpp +++ b/src/bconsole.cpp @@ -1,6 +1,6 @@ /* bconsole.cpp * - * Copyright (C) 2008-2014 Josh Fisher + * Copyright (C) 2008-2018 Josh Fisher * * This program is free software. You may redistribute it and/or modify * it under the terms of the GNU General Public License, as published by @@ -47,18 +47,22 @@ #include #endif +#include "compat_defs.h" +#include "util.h" #include "loghandler.h" +#include "mymutex.h" #include "mypopen.h" #include "vconf.h" #include "bconsole.h" +#ifndef HAVE_WINDOWS_H /* * Function to issue command in Bacula console. * Returns zero on success, or errno if there was an error running the command * or a timeout occurred. */ -int issue_bconsole_command(const char *bcmd) +static int issue_bconsole_command(const char *bcmd) { int pid, rc, n, len, fno_in = -1, fno_out = -1; struct timeval tv; @@ -66,7 +70,6 @@ int issue_bconsole_command(const char *bcmd) tString cmd, tmp; char buf[4096]; -#ifndef HAVE_WINDOWS_H /* Build command line */ cmd = conf.bconsole; if (cmd.empty()) return 0; @@ -76,11 +79,11 @@ int issue_bconsole_command(const char *bcmd) } cmd += " -n -u 30"; /* Start bconsole process */ - log.Debug("bconsole: running '%s'", bcmd); + vlog.Debug("running '%s'", cmd.c_str()); pid = mypopen_raw(cmd.c_str(), &fno_in, &fno_out, NULL); if (pid < 0) { rc = errno; - log.Error("bconsole: run failed errno=%d", rc); + vlog.Error("bconsole run failed errno=%d", rc); errno = rc; return rc; } @@ -91,7 +94,7 @@ int issue_bconsole_command(const char *bcmd) FD_SET(fno_in, &rfd); rc = select(fno_in + 1, NULL, &rfd, NULL, &tv); if (rc == 0) { - log.Error("bconsole: timed out waiting to send command"); + vlog.Error("timeout waiting to send command to bconsole"); close(fno_in); close(fno_out); errno = ETIMEDOUT; @@ -99,20 +102,21 @@ int issue_bconsole_command(const char *bcmd) } if (rc < 0) { rc = errno; - log.Error("bconsole: errno=%d waiting to send command", rc); + vlog.Error("errno=%d waiting to send command to bconsole", rc); close(fno_in); close(fno_out); errno = rc; return rc; } /* Send command to bconsole's stdin */ + vlog.Debug("sending bconsole command '%s'", bcmd); len = strlen(bcmd); n = 0; while (n < len) { rc = write(fno_in, bcmd + n, len - n); if (rc < 0) { rc = errno; - log.Error("bconsole: send to bconsole's stdin failed errno=%d", rc); + vlog.Error("send to bconsole's stdin failed errno=%d", rc); close(fno_in); close(fno_out); errno = rc; @@ -120,57 +124,90 @@ int issue_bconsole_command(const char *bcmd) } n += rc; } - close(fno_in); - - /* Read stdout from bconsole */ - memset(buf, 0, sizeof(buf)); - tmp.clear(); - while (true) { - tv.tv_sec = 30; - tv.tv_usec = 0; - FD_ZERO(&rfd); - FD_SET(fno_out, &rfd); - rc = select(fno_out + 1, &rfd, NULL, NULL, &tv); - if (rc == 0) { - log.Error("bconsole: timed out waiting for bconsole output"); - close(fno_out); - errno = ETIMEDOUT; - return ETIMEDOUT; - } - if (rc < 0) { - rc = errno; - log.Error("bconsole: errno=%d waiting for bconsole output", rc); - close(fno_out); - errno = rc; - return rc; - } - rc = read(fno_out, buf, 4095); - if (rc < 0) { - rc = errno; - log.Error("bconsole: errno=%d reading bconsole stdout", rc); - close(fno_out); - errno = rc; - return rc; - } else if (rc > 0) { - buf[rc] = 0; - tmp += buf; - } else break; + if (write(fno_in, "\n", 1) != 1) { + rc = errno; + vlog.Error("send to bconsole's stdin failed errno=%d", rc); + close(fno_in); + close(fno_out); + errno = rc; + return rc; } - close(fno_out); - log.Debug("bconsole: output:\n%s", tmp.c_str()); /* Wait for bconsole process to finish */ + close(fno_in); pid = waitpid(pid, &rc, 0); if (!WIFEXITED(rc)) { - log.Error("bconsole: abnormal exit of bconsole process"); + vlog.Error("abnormal exit of bconsole process"); + close(fno_out); return EPIPE; } if (WEXITSTATUS(rc)) { - log.Error("bconsole: exited with rc=%d", WEXITSTATUS(rc)); + vlog.Error("bconsole: exited with rc=%d", WEXITSTATUS(rc)); + close(fno_out); return WEXITSTATUS(rc); } + + /* Read stdout from bconsole */ + vlog.Debug("bconsole: bconsole terminated normally"); + memset(buf, 0, sizeof(buf)); + tmp.clear(); + rc = read(fno_out, buf, 4095); + while (rc > 0) { + buf[rc] = 0; + tmp += buf; + rc = read(fno_out, buf, 4095); + } + if (rc < 0) { + rc = errno; + vlog.Error("errno=%d reading bconsole stdout", rc); + close(fno_out); + errno = rc; + return rc; + } + close(fno_out); + vlog.Debug("bconsole output:\n%s", tmp.c_str()); + return 0; -#else - return EINVAL; -#endif } + + +/* + * Function to fork a new process and issue commands in Bacula console to + * perform update slots and/or label new volumes using barcodes. + */ +void IssueBconsoleCommands(bool update_slots, bool label_barcodes) +{ + tString cmd; + + /* Check if update needed */ + if (!update_slots && !label_barcodes) return; /* Nothing to do */ + + /* Perform update slots command in bconsole */ + if (update_slots) { + tFormat(cmd, "update slots storage=\"%s\" drive=\"0\"", conf.storage_name.c_str()); + if(issue_bconsole_command(cmd.c_str())) { + vlog.Error("WARNING! 'update slots' needed in bconsole"); + } + vlog.Info("bconsole update slots command success"); + } + + /* Perform label barcodes command in bconsole */ + if (label_barcodes) { + tFormat(cmd, "label storage=\"%s\" pool=\"%s\" barcodes\nyes\nyes\n", conf.storage_name.c_str(), + conf.def_pool.c_str()); + if (issue_bconsole_command(cmd.c_str())) { + vlog.Error("WARNING! 'label barcodes' needed in bconsole"); + } + vlog.Info("bconsole label barcodes command success"); + } +} + +#else +/* + * Bconsole interaction is not currently supported on Windows + */ +void IssueBconsoleCommands(bool update_slots, bool label_barcodes) +{ + return; +} +#endif diff --git a/src/bconsole.h b/src/bconsole.h index adba63c..938bd8f 100644 --- a/src/bconsole.h +++ b/src/bconsole.h @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2014 Josh Fisher + * vchanger copyright (C) 2008-2017 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -24,6 +24,6 @@ #ifndef BCONSOLE_H_ #define BCONSOLE_H_ -int issue_bconsole_command(const char *bcmd); +void IssueBconsoleCommands(bool update_slots, bool label_barcodes); #endif /* BCONSOLE_H_ */ diff --git a/src/changerstate.cpp b/src/changerstate.cpp index 9f6d923..641d7a2 100644 --- a/src/changerstate.cpp +++ b/src/changerstate.cpp @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2015 Josh Fisher + * vchanger copyright (C) 2008-2018 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -173,7 +173,7 @@ int MagazineState::save() if (mag_bay < 0) { verr.SetErrorWithErrno(EINVAL, "cannot save state of invalid magazine %d", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EINVAL; } /* Build path to state file */ @@ -191,7 +191,7 @@ int MagazineState::save() rc = errno; umask(old_mask); verr.SetErrorWithErrno(rc, "cannot open magazine %d state file for writing", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } /* Save magazine device (directory or UUID), number of volumes, and start of @@ -203,12 +203,12 @@ int MagazineState::save() unlink(sname); umask(old_mask); verr.SetErrorWithErrno(rc, "cannot write to magazine %d state file", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } fclose(FS); umask(old_mask); - log.Notice("saved state of magazine %d", mag_bay); + vlog.Notice("saved state of magazine %d", mag_bay); return 0; } @@ -230,7 +230,7 @@ int MagazineState::restore() if (mag_bay < 0) { verr.SetErrorWithErrno(EINVAL, "cannot restore state of invalid magazine %d", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EINVAL; } clear(); @@ -250,7 +250,7 @@ int MagazineState::restore() /* No read permission? */ rc = errno; verr.SetErrorWithErrno(rc, "cannot open magazine %d state file for reading", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } if (tGetLine(line, FS) == NULL) { @@ -259,7 +259,7 @@ int MagazineState::restore() /* error reading bay state file */ fclose(FS); verr.SetErrorWithErrno(rc, "error reading magazine %d state file", mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } } @@ -270,7 +270,7 @@ int MagazineState::restore() p = 0; if (tParseCSV(word, line, p) <= 0) { /* bay state file should not be empty, assume it didn't exist */ - log.Warning("WARNING! magazine %d state file was empty, deleting it", mag_bay); + vlog.Warning("WARNING! magazine %d state file was empty, deleting it", mag_bay); unlink(sname); return 0; } @@ -285,7 +285,7 @@ int MagazineState::restore() /* Bay state file is corrupt. * Treat as if it was not mounted at last invocation */ clear(); - log.Warning("WARNING! magazine %d state file corrupt, deleting it", mag_bay); + vlog.Warning("WARNING! magazine %d state file corrupt, deleting it", mag_bay); unlink(sname); return 0; } @@ -293,7 +293,7 @@ int MagazineState::restore() /* Corrupt bay state file, assume it doesn't exist */ clear(); unlink(sname); - log.Warning("WARNING! magazine %d state file has invalid number of slots field, deleting it", mag_bay); + vlog.Warning("WARNING! magazine %d state file has invalid number of slots field, deleting it", mag_bay); return 0; } prev_num_slots = (int)strtol(word.c_str(), NULL, 10); @@ -302,7 +302,7 @@ int MagazineState::restore() clear(); prev_num_slots = 0; unlink(sname); - log.Warning("WARNING! magazine %d state file has invalid number of slots field, deleting it", mag_bay); + vlog.Warning("WARNING! magazine %d state file has invalid number of slots field, deleting it", mag_bay); return 0; } @@ -312,7 +312,7 @@ int MagazineState::restore() * Treat as if it was not mounted at last invocation */ clear(); prev_num_slots = 0; - log.Warning("WARNING! magazine %d state file corrupt, deleting it", mag_bay); + vlog.Warning("WARNING! magazine %d state file corrupt, deleting it", mag_bay); unlink(sname); return 0; } @@ -321,7 +321,7 @@ int MagazineState::restore() clear(); prev_num_slots = 0; unlink(sname); - log.Warning("WARNING! magazine %d state file has invalid virtual slot assignment field, deleting it", + vlog.Warning("WARNING! magazine %d state file has invalid virtual slot assignment field, deleting it", mag_bay); return 0; } @@ -332,11 +332,11 @@ int MagazineState::restore() prev_num_slots = 0; prev_start_slot = 0; unlink(sname); - log.Warning("WARNING! magazine %d state file has invalid virtual slot assignment field, deleting it", + vlog.Warning("WARNING! magazine %d state file has invalid virtual slot assignment field, deleting it", mag_bay); return 0; } - log.Notice("restored state of magazine %d", mag_bay); + vlog.Notice("restored state of magazine %d", mag_bay); return 0; } @@ -389,7 +389,7 @@ int MagazineState::UpdateMagazineFormat() fs = fopen(lname.c_str(), "r"); if (fs == NULL) { verr.SetErrorWithErrno(errno, "failed to find loaded%d file when updating magazine %d", drv, mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); de = readdir(dir); continue; } @@ -397,7 +397,7 @@ int MagazineState::UpdateMagazineFormat() fclose(fs); if (str.empty()) { verr.SetError(-1, "loaded%d file empty when updating magazine %d", drv, mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); de = readdir(dir); continue; } @@ -406,7 +406,7 @@ int MagazineState::UpdateMagazineFormat() if (rename(fname.c_str(), vname.c_str())) { verr.SetError(EINVAL, "unable to rename 'drive%d' on magazine %d", drv, mag_bay); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); } } de = readdir(dir); @@ -445,7 +445,7 @@ int MagazineState::UpdateMagazineFormat() tFormat(fname, "%s%sindex", mountpoint.c_str(), DIR_DELIM); unlink(fname.c_str()); - log.Warning("magaine %d updated from old format", mag_bay); + vlog.Warning("magaine %d updated from old format", mag_bay); return 0; } @@ -494,7 +494,7 @@ int MagazineState::Mount() } if (rc) { verr.SetError(rc, "system error determining mountpoint from UUID"); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); mountpoint.clear(); return -1; } @@ -510,7 +510,7 @@ int MagazineState::Mount() /* Ensure access to magazine mountpoint */ if (access(mountpoint.c_str(), W_OK) != 0) { verr.SetError(rc, "no write access to directory %s", mountpoint.c_str()); - log.Error("%s", verr.GetErrorMsg()); + vlog.Error("%s", verr.GetErrorMsg()); mountpoint.clear(); return -5; } @@ -529,7 +529,7 @@ int MagazineState::Mount() /* could not open mountpoint dir */ rc = errno; verr.SetErrorWithErrno(rc, "cannot open directory '%s'", mountpoint.c_str()); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); mountpoint.clear(); if (rc == ENOTDIR || rc == ENOENT) return -3; if (rc == EACCES) return -5; @@ -659,7 +659,7 @@ int MagazineState::CreateVolume(const char *vol_label_in) } if (rc != ENOENT) { verr.SetErrorWithErrno(rc, "error %d accessing volumes on magazine %d", rc, mag_bay); - log.Error("MagazineState::CreateVolume: %s", verr.GetErrorMsg()); + vlog.Error("MagazineState::CreateVolume: %s", verr.GetErrorMsg()); return -1; } /* Create new volume file on magazine */ @@ -667,7 +667,7 @@ int MagazineState::CreateVolume(const char *vol_label_in) if (!fs) { rc = errno; verr.SetErrorWithErrno(rc, "error %d creating volume on magazine %d", rc, mag_bay); - log.Error("MagazineState::CreateVolume: %s", verr.GetErrorMsg()); + vlog.Error("MagazineState::CreateVolume: %s", verr.GetErrorMsg()); return -1; } fclose(fs); @@ -676,7 +676,7 @@ int MagazineState::CreateVolume(const char *vol_label_in) new_mslot.label = label; mslot.push_back(new_mslot); ++num_slots; - log.Notice("created volume '%s' on magazine %d (%s)", label.c_str(), mag_bay, mag_dev.c_str()); + vlog.Notice("created volume '%s' on magazine %d (%s)", label.c_str(), mag_bay, mag_dev.c_str()); return 0; } @@ -784,7 +784,7 @@ void DynamicConfig::save() /* Unable to open dynamic.conf file for writing */ rc = errno; umask(old_mask); - log.Error("ERROR! cannot open dynamic.conf file for writing (errno=%d)", rc); + vlog.Error("ERROR! cannot open dynamic.conf file for writing (errno=%d)", rc); return; } /* Save max slot number in use to dynamic configuration */ @@ -794,12 +794,12 @@ void DynamicConfig::save() fclose(FS); unlink(sname); umask(old_mask); - log.Error("ERROR! i/o error writing dynamic.conf file (errno=%d)", rc); + vlog.Error("ERROR! i/o error writing dynamic.conf file (errno=%d)", rc); return; } fclose(FS); umask(old_mask); - log.Notice("saved dynamic configuration (max used slot: %d)", max_slot); + vlog.Notice("saved dynamic configuration (max used slot: %d)", max_slot); } @@ -827,7 +827,7 @@ void DynamicConfig::restore() if (!FS) { /* No read permission? */ rc = errno; - log.Error("ERROR! cannot open dynamic.conf file for restore (errno=%d)", rc); + vlog.Error("ERROR! cannot open dynamic.conf file for restore (errno=%d)", rc); return; } if (tGetLine(line, FS) == NULL) { @@ -835,7 +835,7 @@ void DynamicConfig::restore() if (!feof(FS)) { /* error reading bay state file */ fclose(FS); - log.Error("ERROR! i/o error reading dynamic.conf file (errno=%d)", rc); + vlog.Error("ERROR! i/o error reading dynamic.conf file (errno=%d)", rc); return; } } diff --git a/src/compat/semaphore.c b/src/compat/semaphore.c new file mode 100644 index 0000000..4c40c47 --- /dev/null +++ b/src/compat/semaphore.c @@ -0,0 +1,197 @@ +/* semaphore.c + * + * This file is part of vchanger by Josh Fisher. + * + * vchanger copyright (C) 2020 Josh Fisher + * + * vchanger is free software. + * You may redistribute it and/or modify it under the terms of the + * GNU General Public License version 2, as published by the Free + * Software Foundation. + * + * vchanger is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with vchanger. See the file "COPYING". If not, + * write to: The Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#ifndef HAVE_SEMAPHORE_H + +#ifdef HAVE_WINDOWS_H +#include "targetver.h" +#include +#include +#include "win32_util.h" +#endif +/* +#ifdef HAVE_ERRNO_H +#include +#endif +*/ +#include "compat/semaphore.h" + +#ifdef HAVE_WINDOWS_H + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_open() function using win32/win64 CreateSemaphoreW() function. + * On success address of semaphore, else NULL on error. + *-------------------------------------------------*/ +sem_t* sem_open(const char *name, int oflag, ...) +{ + DWORD rc; + long mode, value; + wchar_t *wname = NULL; + size_t wname_sz = 0; + sem_t *fd; + va_list vl; + + va_start(vl, oflag); + mode = (long)va_arg(vl, unsigned int); + value = (long)va_arg(vl, unsigned int); + va_end(vl); + + /* Convert path strings to UTF16 encoding */ + if (!AnsiToUTF16(name, &wname, &wname_sz)) { + rc = ERROR_BAD_PATHNAME; + errno = w32errno(rc); + return NULL; + } + fd = (sem_t*)CreateSemaphoreW(NULL, value, LONG_MAX, wname); + free(wname); + if (fd == NULL) { + errno = w32errno(GetLastError()); + } + return fd; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_close() function using win32/win64 CloseHandle() function. + * On success returns zero, else negative on error. + *-------------------------------------------------*/ +int sem_close(sem_t *sem) +{ + if (sem == NULL) { + errno = EINVAL; + return -1; + } + CloseHandle((HANDLE)sem); + return 0; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_unnlink() function by doing nothing. Windows will + * automatically unlink the object when the last open handle is closed. + * Returns zero. + *-------------------------------------------------*/ +int sem_unlink(const char *name) +{ + return 0; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_post() function using win32/win64 ReleaseSemaphore() function. + * On success returns zero, else negative on error. + *-------------------------------------------------*/ +int sem_post(sem_t *sem) +{ + if (sem == NULL) { + errno = EINVAL; + return -1; + } + ReleaseSemaphore((HANDLE)sem, 1, NULL); + return 0; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_wait() function using win32/win64 WaitForSingleObjectEx() function. + * On success returns zero, else negative on error. + *-------------------------------------------------*/ +int sem_wait(sem_t *sem) +{ + DWORD reason; + if (sem == NULL) { + errno = EINVAL; + return -1; + } + reason = WaitForSingleObjectEx((HANDLE)sem, INFINITE, FALSE); + if (reason == 0) return 0; + switch (reason) { + case WAIT_IO_COMPLETION: + errno = EINTR; + break; + case WAIT_TIMEOUT: + errno = ETIMEDOUT; + break; + default: + errno = EINVAL; + break; + } + return -1; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_timedwait() function using win32/win64 WaitForSingleObjectEx() + * function. + * On success returns zero, else negative on error. + *-------------------------------------------------*/ +int sem_timedwait(sem_t *sem, const struct timespec *timeout) +{ + DWORD reason, mt; + if (sem == NULL) { + errno = EINVAL; + return -1; + } + /* semaphore.h functions use absolute time and Windows needs a time interval */ + timeout->tv_sec -= time(NULL); + mt = timeout->tv_sec * 1000 + (timeout->tv_nsec / 1000000); + reason = WaitForSingleObjectEx((HANDLE)sem, mt, FALSE); + if (reason == WAIT_OBJECT_0) return 0; + switch (reason) { + case WAIT_IO_COMPLETION: + errno = EINTR; + break; + case WAIT_TIMEOUT: + errno = ETIMEDOUT; + break; + default: + errno = EINVAL; + break; + } + return -1; +} + + +/*------------------------------------------------- + * Emulate POSIX.1-2001 sem_trywait() function using win32/win64 WaitForSingleObjectEx() + * function. + * On success returns zero, else negative on error. + *-------------------------------------------------*/ +int sem_trywait(sem_t *sem) +{ + DWORD reason; + if (sem == NULL) { + errno = EINVAL; + return -1; + } + reason = WaitForSingleObjectEx((HANDLE)sem, 0, FALSE); + if (reason == 0) return 0; + errno = EAGAIN; + return -1; +} + +#endif + +#endif diff --git a/src/compat/semaphore.h b/src/compat/semaphore.h new file mode 100644 index 0000000..8710424 --- /dev/null +++ b/src/compat/semaphore.h @@ -0,0 +1,54 @@ +/* semaphore.h + * + * This file is part of vchanger by Josh Fisher. + * + * vchanger copyright (C) 2020 Josh Fisher + * + * vchanger is free software. + * You may redistribute it and/or modify it under the terms of the + * GNU General Public License version 2, as published by the Free + * Software Foundation. + * + * vchanger is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with vchanger. See the file "COPYING". If not, + * write to: The Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. +*/ + +#ifndef _SEMAPHORE_H +#define _SEMAPHORE_H + +#ifndef HAVE_SEMAPHORE_H +/* For systems without symlink() function, use internal version */ + +typedef union +{ + char __size[32]; + long int __align; +} sem_t; + +#ifdef __cplusplus +extern "C" { +#endif + +sem_t* sem_open(const char *name, int oflag, ...); +int sem_post(sem_t *sem); +int sem_wait(sem_t *sem); +int sem_trywait(sem_t *sem); +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +int sem_close(sem_t *sem); +int sem_unlink(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif /* _SEMAPHORE_H */ diff --git a/src/diskchanger.cpp b/src/diskchanger.cpp index a4da259..078fe96 100644 --- a/src/diskchanger.cpp +++ b/src/diskchanger.cpp @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2015 Josh Fisher + * vchanger copyright (C) 2008-2020 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -65,20 +65,13 @@ #include "loghandler.h" #include "bconsole.h" #include "diskchanger.h" +#include "vconf.h" /*================================================= * Class DiskChanger *=================================================*/ -/*------------------------------------------------- - * destructor - *-------------------------------------------------*/ -DiskChanger::~DiskChanger() -{ - Unlock(); -} - /*------------------------------------------------- * Protected method to read previous state of magazine bays. @@ -148,6 +141,7 @@ void DiskChanger::InitializeVirtSlots() /* Create all known slots as initially empty */ vslot.clear(); + vs.clear(); for (s = 0; s <= dconf.max_slot; s++) { vs.vs = s; vslot.push_back(vs); @@ -155,7 +149,7 @@ void DiskChanger::InitializeVirtSlots() /* Re-create virtual slots that existed previously if possible */ for (m = 0; m < (int)magazine.size(); m++) { /* Create slots if needed to match max slot used by previous magazines */ - last = magazine[m].prev_start_slot + magazine[m].prev_num_slots; + last = magazine[m].prev_start_slot + magazine[m].prev_num_slots - 1; if (last >= (int)vslot.size()) { vs.clear(); while ((int)vslot.size() <= last) { @@ -165,11 +159,11 @@ void DiskChanger::InitializeVirtSlots() } /* Check this magazine's slots */ if (magazine[m].empty()) { - log.Info("magazine %d is not mounted", m); + vlog.Info("magazine %d is not mounted", m); /* magazine is not currently mounted, so will have no slots assigned */ if (magazine[m].prev_start_slot) { /* Since it was previously mounted, an 'update slots' is needed */ - log.Warning("update slots needed. magazine %d no longer mounted; previous: %d volumes in slots %d-%d", m, + vlog.Warning("update slots needed. magazine %d no longer mounted; previous: %d volumes in slots %d-%d", m, magazine[m].prev_num_slots, magazine[m].prev_start_slot, magazine[m].prev_start_slot + magazine[m].prev_num_slots - 1); needs_update = true; @@ -177,12 +171,12 @@ void DiskChanger::InitializeVirtSlots() continue; } /* Magazine is currently mounted, so check for change in slot assignment */ - log.Info("magazine %d has %d volumes on %s", m, magazine[m].num_slots, + vlog.Info("magazine %d has %d volumes on %s", m, magazine[m].num_slots, magazine[m].mountpoint.c_str()); if (magazine[m].num_slots != magazine[m].prev_num_slots) { /* Number of volumes has changed or magazine was not previously mounted, so * needs new slot assignment and also 'update slots' will be needed */ - log.Warning("update slots needed. magazine %d has %d volumes, previously had %d", m, + vlog.Warning("update slots needed. magazine %d has %d volumes, previously had %d", m, magazine[m].num_slots, magazine[m].prev_num_slots); needs_update = true; continue; @@ -194,7 +188,7 @@ void DiskChanger::InitializeVirtSlots() /* Magazine is mounted, was previously mounted, and has the same volume count, * so attempt to assign to the same slots previously assigned */ found = false; - for (v = magazine[m].prev_start_slot; v < (int)vslot.size(); v++) { + for (v = magazine[m].prev_start_slot; v < magazine[m].prev_start_slot + magazine[m].prev_num_slots; v++) { if (!vslot[v].empty()) { found = true; break; @@ -204,7 +198,7 @@ void DiskChanger::InitializeVirtSlots() /* Slot used previously has already been assigned to another magazine. * Magazine will need to be assigned a new slot range, so an * 'update slots' will also be needed. */ - log.Warning("update slots needed. magazine %d previous slots %d-%d are not available", m, + vlog.Warning("update slots needed. magazine %d previous slots %d-%d are not available", m, magazine[m].prev_start_slot, magazine[m].prev_start_slot + magazine[m].prev_num_slots - 1); needs_update = true; continue; @@ -216,7 +210,7 @@ void DiskChanger::InitializeVirtSlots() vslot[v].mag_bay = m; vslot[v].mag_slot = s; } - log.Notice("%d volumes on magazine %d assigned slots %d-%d", magazine[m].num_slots, m, + vlog.Notice("%d volumes on magazine %d assigned slots %d-%d", magazine[m].num_slots, m, magazine[m].start_slot, magazine[m].start_slot + magazine[m].num_slots - 1); } @@ -230,7 +224,7 @@ void DiskChanger::InitializeVirtSlots() vslot[v].mag_bay = m; vslot[v].mag_slot = s; } - log.Notice("%d volumes on magazine %d assigned slots %d-%d", magazine[m].num_slots, m, + vlog.Notice("%d volumes on magazine %d assigned slots %d-%d", magazine[m].num_slots, m, magazine[m].start_slot, magazine[m].start_slot + magazine[m].num_slots - 1); } @@ -263,7 +257,7 @@ int DiskChanger::InitializeDrives() if (!d) { rc = errno; verr.SetErrorWithErrno(rc, "error %d accessing work directory", rc); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } de = readdir(d); @@ -297,7 +291,7 @@ int DiskChanger::InitializeDrives() drive.push_back(ds); /* Attempt to restore drive's last state */ if (RestoreDriveState(n)) { - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); } } return 0; @@ -351,7 +345,7 @@ int DiskChanger::CreateDriveSymlink(int drv) lname[rc] = 0; if (fname == lname) { /* symlink already exists */ - log.Info("found symlink for drive %d -> %s", drv, fname.c_str()); + vlog.Info("found symlink for drive %d -> %s", drv, fname.c_str()); return 0; } /* Symlink points to wrong mountpoint, so delete and re-create */ @@ -362,7 +356,7 @@ int DiskChanger::CreateDriveSymlink(int drv) verr.SetErrorWithErrno(rc, "error %d creating symlink for drive %d", rc, drv); return rc; } - log.Notice("created symlink for drive %d -> %s", drv, fname.c_str()); + vlog.Notice("created symlink for drive %d -> %s", drv, fname.c_str()); return 0; } @@ -389,7 +383,7 @@ int DiskChanger::RemoveDriveSymlink(int drv) verr.SetErrorWithErrno(errno, "error %d deleting symlink for drive %d: ", rc, drv); return rc; } - log.Notice("deleted symlink for drive %d", drv); + vlog.Notice("deleted symlink for drive %d", drv); return 0; } @@ -416,7 +410,7 @@ int DiskChanger::SaveDriveState(int drv) tFormat(sname, "%s%sdrive_state-%d", conf.work_dir.c_str(), DIR_DELIM, drv); if (drive[drv].empty()) { if (access(sname.c_str(), F_OK) == 0) { - log.Notice("deleted state file for drive %d", drv); + vlog.Notice("deleted state file for drive %d", drv); } unlink(sname.c_str()); return 0; @@ -443,7 +437,7 @@ int DiskChanger::SaveDriveState(int drv) } fclose(FS); umask(old_mask); - log.Notice("wrote state file for drive %d", drv); + vlog.Notice("wrote state file for drive %d", drv); return 0; } @@ -477,7 +471,7 @@ int DiskChanger::RestoreDriveState(int drv) if (stat(sname.c_str(), &st)) { /* drive state file not found, so drive is not loaded */ RemoveDriveSymlink(drv); - log.Info("drive %d previously unloaded", drv); + vlog.Info("drive %d previously unloaded", drv); return 0; } /* Read loaded volume info from state file */ @@ -526,7 +520,7 @@ int DiskChanger::RestoreDriveState(int drv) } if (v >= (int)vslot.size()) { /* Volume last loaded is no longer available. Change state to unloaded. */ - log.Notice("volume %s no longer available, unloading drive %d", + vlog.Notice("volume %s no longer available, unloading drive %d", labl.c_str(), drv); unlink(sname.c_str()); RemoveDriveSymlink(drv); @@ -538,7 +532,7 @@ int DiskChanger::RestoreDriveState(int drv) if ((rc = CreateDriveSymlink(drv)) != 0) { /* Unable to create symlink */ drive[drv].vs = -1; - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } @@ -546,7 +540,7 @@ int DiskChanger::RestoreDriveState(int drv) vslot[v].drv = drv; m = vslot[v].mag_bay; ms = vslot[v].mag_slot; - log.Notice("drive %d previously loaded from slot %d (%s)", drv, v, magazine[m].GetVolumeLabel(ms)); + vlog.Notice("drive %d previously loaded from slot %d (%s)", drv, v, magazine[m].GetVolumeLabel(ms)); return 0; } @@ -562,7 +556,6 @@ int DiskChanger::RestoreDriveState(int drv) int DiskChanger::Initialize() { /* Make sure we have a lock on this changer */ - if (Lock()) return verr.GetError(); magazine.clear(); vslot.clear(); drive.clear(); @@ -591,38 +584,38 @@ int DiskChanger::LoadDrive(int drv, int slot) { int rc, m, ms; - if (!changer_lock) { - verr.SetError(EINVAL, "changer not initialized"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return EINVAL; - } if (drv < 0) { verr.SetError(EINVAL, "invalid drive number %d", drv); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EINVAL; } SetMaxDrive(drv); if (slot < 1 || slot >= (int)vslot.size()) { verr.SetError(EINVAL, "cannot load drive %d from invalid slot %d", drv, slot); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EINVAL; } if (!drive[drv].empty()) { if (drive[drv].vs == slot) return 0; /* already loaded from this slot */ verr.SetError(EBUSY, "drive %d already loaded from slot %d", drv, slot); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EBUSY; } + if (vslot[slot].drv >= 0) { + verr.SetError(EINVAL, "requested slot %d already loaded in drive %d", slot, drv); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); + return ENOENT; + } if (vslot[slot].empty()) { verr.SetError(EINVAL, "cannot load drive %d from empty slot %d", drv, slot); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return ENOENT; } /* Create symlink for drive pointing to volume file */ drive[drv].vs = slot; if ((rc = CreateDriveSymlink(drv))) { drive[drv].vs = -1; - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } /* Save state of newly loaded drive */ @@ -630,14 +623,14 @@ int DiskChanger::LoadDrive(int drv, int slot) /* Error writing drive state file */ RemoveDriveSymlink(drv); drive[drv].vs = -1; - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } /* Assign virtual slot to drive */ vslot[slot].drv = drv; m = vslot[slot].mag_bay; ms = vslot[slot].mag_slot; - log.Notice("loaded drive %d from slot %d (%s)", drv, slot, magazine[m].GetVolumeLabel(ms)); + vlog.Notice("loaded drive %d from slot %d (%s)", drv, slot, magazine[m].GetVolumeLabel(ms)); return 0; } @@ -652,14 +645,9 @@ int DiskChanger::UnloadDrive(int drv) { int rc; - if (!changer_lock) { - verr.SetError(EINVAL, "changer not initialized"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return EINVAL; - } if (drv < 0) { verr.SetError(EINVAL, "invalid drive number %d", drv); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return EINVAL; } SetMaxDrive(drv); @@ -669,7 +657,7 @@ int DiskChanger::UnloadDrive(int drv) } /* Remove drive's symlink */ if ((rc = RemoveDriveSymlink(drv)) != 0) { - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } /* Remove virtual slot assignment */ @@ -677,10 +665,10 @@ int DiskChanger::UnloadDrive(int drv) drive[drv].vs = -1; /* Update drive state file (will delete state file due to negative slot number) */ if ((rc = SaveDriveState(drv)) != 0) { - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return rc; } - log.Notice("unloaded drive %d", drv); + vlog.Notice("unloaded drive %d", drv); return 0; } @@ -699,26 +687,21 @@ int DiskChanger::CreateVolumes(int bay, int count, int start, const char *label_ tString label, label_prefix(label_prefix_in); int i; - if (!changer_lock) { - verr.SetError(EINVAL, "changer not initialized"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return -1; - } if (bay < 0 || bay >= (int)magazine.size()) { verr.SetError(EINVAL, "invalid magazine"); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return -1; } if (count < 1) count = 1; tStrip(tRemoveEOL(label_prefix)); if (label_prefix.empty()) { /* Default prefix is storage-name_magazine-number */ - tFormat(label_prefix, "%s_%d_", conf.storage_name.c_str(), bay); + tFormat(label_prefix, "%s_%04d_", conf.storage_name.c_str(), bay); } if (start < 0) { /* Find highest uniqueness number for this filename prefix */ for (i = magazine[bay].num_slots * 5; i > 0; i--) { - tFormat(label, "%s_%d", label_prefix.c_str(), i); + tFormat(label, "%s%04d", label_prefix.c_str(), i); if (magazine[bay].GetVolumeSlot(label) >= 0) break; } start = i; @@ -733,6 +716,7 @@ int DiskChanger::CreateVolumes(int bay, int count, int start, const char *label_ } fprintf(stdout, "creating label '%s'\n", label.c_str()); if (magazine[bay].CreateVolume(label)) { + /* On failure, update magazine state if any were created */ if (i) magazine[bay].save(); return -1; } @@ -743,61 +727,7 @@ int DiskChanger::CreateVolumes(int bay, int count, int start, const char *label_ /* New mag state will require 'update slots' and 'label barcodes' in Bacula */ needs_update = true; needs_label = true; - log.Notice("update slots needed. %d volumes added to magazine %d",count , bay); - return 0; -} - -/*------------------------------------------------- - * Method to cause Bacula to update its catalog to reflect - * changes in the available volumes - *-------------------------------------------------*/ -int DiskChanger::UpdateBacula() -{ - int rc; - FILE *update_lock; - tString cmd; - char lockfile[4096]; - - /* Check if update needed */ - if (!needs_update && !needs_label) return 0; /* Nothing to do */ - /* Create update lock lockfile */ - snprintf(lockfile, sizeof(lockfile), "%s%s%s.updatelock", conf.work_dir.c_str(), DIR_DELIM, - conf.storage_name.c_str()); - rc = exclusive_fopen(lockfile, &update_lock); - if (rc == EEXIST) { - /* Update already in progress in another process, so skip */ - return 0; - } - if (rc) { - /* error creating lockfile, so skip */ - log.Error("bconsole: errno=%d creating update lockfile", rc); - if (needs_update) - log.Error("WARNING! 'update slots' needed in bconsole"); - if (needs_label) - log.Error("WARNING! 'label barcodes' needed in bconsole"); - return 0; - } - log.Debug("created update lockfile for pid %d", getpid()); - /* Perform update slots command in bconsole */ - if (needs_update) { - /* Issue update slots command in bconsole */ - tFormat(cmd, "update slots storage=\"%s\"", conf.storage_name.c_str()); - if(issue_bconsole_command(cmd.c_str())) { - log.Error("WARNING! 'update slots' needed in bconsole"); - } - } - /* Perform label barcodes command in bconsole */ - if (needs_label) { - tFormat(cmd, "label storage=\"%s\" pool=\"%s\" barcodes\nyes\nyes\n", conf.storage_name.c_str(), - conf.def_pool.c_str()); - if (issue_bconsole_command(cmd.c_str())) { - log.Error("WARNING! 'label barcodes' needed in bconsole"); - } - } - /* Obtain changer lock before removing update lock */ - fclose(update_lock); - unlink(lockfile); - log.Debug("removed update lockfile for pid %d", getpid()); + vlog.Notice("%d volumes added to magazine %d",count , bay); return 0; } @@ -809,7 +739,7 @@ const char* DiskChanger::GetVolumeLabel(int slot) { if (slot <= 0 || slot >= (int)vslot.size()) { verr.SetError(-1, "volume label request from invalid slot %d", slot); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return NULL; } if (vslot[slot].empty()) return ""; @@ -825,7 +755,7 @@ const char* DiskChanger::GetVolumePath(tString &path, int slot) path.clear(); if (slot <= 0 || slot >= (int)vslot.size()) { verr.SetError(-1, "volume path request from invalid slot %d", slot); - log.Error("ERROR! %s", verr.GetErrorMsg()); + vlog.Error("ERROR! %s", verr.GetErrorMsg()); return NULL; } if (vslot[slot].empty()) return path.c_str(); @@ -833,78 +763,6 @@ const char* DiskChanger::GetVolumePath(tString &path, int slot) } -/*------------------------------------------------- - * Protected method to lock changer device using a lock file such that - * only one process at a time may execute changer commands on the - * same autochanger. If another process has the lock, then this process - * will sleep 1 second before trying again. This try/wait loop will continue - * until the lock is obtained or 'timeout' seconds have expired. If - * timeout = 0 then only tries to obtain lock once. If timeout < 0 - * then doesn't return until the lock is obtained. - * On success, returns true. Otherwise on error or timeout, sets - * lasterr negative and returns false. - *------------------------------------------------*/ -int DiskChanger::Lock(long timeout_seconds) -{ - int rc; - time_t timeout = 0; - char lockfile[4096]; - - if (changer_lock) return 0; - - if (timeout_seconds < 0) { - timeout_seconds = 3600 * 24 * 365; - } - if (timeout_seconds > 0) { - timeout = time(NULL) + timeout_seconds; - } - snprintf(lockfile, sizeof(lockfile), "%s%s%s.lock", conf.work_dir.c_str(), DIR_DELIM, - conf.storage_name.c_str()); - rc = exclusive_fopen(lockfile, &changer_lock); - if (rc == EEXIST && timeout == 0) { - /* timeout=0 means do not wait */ - verr.SetErrorWithErrno(rc, "cannot open lockfile"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return -1; - } - while (rc == EEXIST) { - /* sleep before trying again */ - sleep(1); - if (time(NULL) > timeout) { - verr.SetError(EBUSY, "timeout waiting for lockfile"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return EACCES; - } - rc = exclusive_fopen(lockfile, &changer_lock); - } - if (rc) { - verr.SetErrorWithErrno(rc, "cannot open lockfile"); - log.Error("ERROR! %s", verr.GetErrorMsg()); - return -1; - } - /* Write PID to lockfile and leave open for exclusive R/W */ - fprintf(changer_lock, "%d", getpid()); - fflush(changer_lock); - log.Debug("created lockfile for pid %d", getpid()); - return 0; -} - -/*------------------------------------------------- - * Protected method to unlock changer device - *------------------------------------------------*/ -void DiskChanger::Unlock() -{ - char lockfile[4096]; - if (!changer_lock) return; - fclose(changer_lock); - changer_lock = NULL; - snprintf(lockfile, sizeof(lockfile), "%s%s%s.lock", conf.work_dir.c_str(), DIR_DELIM, - conf.storage_name.c_str()); - log.Debug("removing lockfile for pid %d", getpid()); - unlink(lockfile); -} - - /*------------------------------------------------- * Method returns true if magazine is not mounted, * else returns false. diff --git a/src/diskchanger.h b/src/diskchanger.h index ef5a01a..03a34ba 100644 --- a/src/diskchanger.h +++ b/src/diskchanger.h @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2015 Josh Fisher + * vchanger copyright (C) 2008-2020 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -30,13 +30,12 @@ class DiskChanger { public: - DiskChanger() : changer_lock(NULL), needs_update(false), needs_label(false) {} - virtual ~DiskChanger(); + DiskChanger() : needs_update(false), needs_label(false) {} + virtual ~DiskChanger() {}; int Initialize(); int LoadDrive(int drv, int slot); int UnloadDrive(int drv); int CreateVolumes(int bay, int count, int start = -1, const char *label_prefix = ""); - int UpdateBacula(); const char* GetVolumeLabel(int slot); const char* GetVolumePath(tString &fname, int slot); bool MagazineEmpty(int bay) const; @@ -54,8 +53,6 @@ public: inline const char* GetErrorMsg() const { return verr.GetErrorMsg(); } inline bool NeedsUpdate() const { return needs_update; } inline bool NeedsLabel() const { return needs_label; } - int Lock(long timeout = 30); - void Unlock(); protected: void InitializeMagazines(); int FindEmptySlotRange(int count); @@ -67,7 +64,6 @@ protected: int SaveDriveState(int drv); int RestoreDriveState(int drv); protected: - FILE *changer_lock; bool needs_update; bool needs_label; ErrorHandler verr; diff --git a/src/loghandler.cpp b/src/loghandler.cpp index 7e963d9..44bf7c2 100644 --- a/src/loghandler.cpp +++ b/src/loghandler.cpp @@ -1,6 +1,6 @@ /* loghandler.cpp * - * Copyright (C) 2013-2014 Josh Fisher + * Copyright (C) 2013-2018 Josh Fisher * * This program is free software. You may redistribute it and/or modify * it under the terms of the GNU General Public License, as published by @@ -22,6 +22,9 @@ #ifdef HAVE_STDIO_H #include #endif +#ifdef HAVE_UNISTD_H +#include +#endif #ifdef HAVE_TIME_H #include #endif @@ -34,13 +37,11 @@ #ifdef HAVE_STDARG_H #include #endif -#ifndef HAVE_LOCALTIME_R #include "compat/localtime_r.h" -#endif #define LOGHANDLER_SOURCE 1 #include "loghandler.h" -LogHandler log; +LogHandler vlog; LogHandler::LogHandler() : use_syslog(false), max_debug_level(LOG_WARNING), errfs(stderr) { @@ -179,7 +180,7 @@ void LogHandler::WriteLog(int priority, const char *fmt, va_list vl) size_t n; struct tm bt; time_t t; - char buf[1024]; + char ftim[128], buf[4096]; Lock(); if (priority > max_debug_level || priority < LOG_EMERG || !fmt) { Unlock(); @@ -187,8 +188,8 @@ void LogHandler::WriteLog(int priority, const char *fmt, va_list vl) } t = time(NULL); localtime_r(&t, &bt); - strftime(buf, 100, "%b %d %T: ", &bt); - strncpy(buf + strlen(buf), fmt, sizeof(buf) - strlen(buf)); + strftime(ftim, 100, "%b %d %T: ", &bt); + snprintf(buf, sizeof(buf), "%s [%d]: %s", ftim, getpid(), fmt); if (use_syslog) vsyslog(priority, buf, vl); else { n = strlen(buf); @@ -204,3 +205,17 @@ void LogHandler::WriteLog(int priority, const char *fmt, va_list vl) } Unlock(); } + +/*************************************************************************************** + * C wrapper to write to LogHandler object + ***************************************************************************************/ + +extern "C" void LogHandler_write(int level, const char *fmt, ...) +{ + va_list vl; + va_start(vl, fmt); + vlog.WriteLog(level, fmt, vl); + va_end(vl); + +} + diff --git a/src/loghandler.h b/src/loghandler.h index e558150..feaf8fe 100644 --- a/src/loghandler.h +++ b/src/loghandler.h @@ -1,6 +1,6 @@ /* loghandler.h * - * Copyright (C) 2013-2014 Josh Fisher + * Copyright (C) 2013-2018 Josh Fisher * * This program is free software. You may redistribute it and/or modify * it under the terms of the GNU General Public License, as published by @@ -32,6 +32,15 @@ #include #endif +#ifdef __cplusplus +extern "C" { +#endif +void LogHandler_write(int level, const char *format, ...); +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus class LogHandler { public: @@ -50,6 +59,7 @@ public: void Debug(const char *fmt, ... ); void MajorDebug(const char *fmt, ... ); inline bool UsingSyslog() { return use_syslog; } + friend void LogHandler_write(int level, const char *format, ...); protected: void Lock(); void Unlock(); @@ -64,7 +74,8 @@ protected: }; #ifndef LOGHANDLER_SOURCE -extern LogHandler log; +extern LogHandler vlog; +#endif #endif #endif /* _LOGHANDLER_H_ */ diff --git a/src/mymutex.cpp b/src/mymutex.cpp new file mode 100644 index 0000000..c39e749 --- /dev/null +++ b/src/mymutex.cpp @@ -0,0 +1,131 @@ +/* mymutex.cpp + * + * Copyright (C) 2017-2020 Josh Fisher + * + * This program is free software. You may redistribute it and/or modify + * it under the terms of the GNU General Public License, as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. See the file "COPYING". If not, + * see . +*/ + +#include "config.h" +#ifdef HAVE_WINDOWS_H +#include "targetver.h" +#include +#include "win32_util.h" +#endif +#include +#include +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_SEMAPHORE_H +#include +#endif + +#include "compat/semaphore.h" +#include "loghandler.h" +#include "mypopen.h" + + +/* + * Function to create a mutex owned by the caller. Waits up to max_wait seconds + * for the mutex to be created. If max_wait is negative, waits indefinitely. If + * max_wait is zero, tries once to create mutex and does not block. + * On success, returns the handle of a named mutex. On error, returns zero and + * sets errno appropriately. + */ +void* mymutex_create(const char *storage_name) +{ + char lockname[4096]; + + if (!storage_name || !storage_name[0]) { + /* Only create named mutex */ + errno = EINVAL; + return 0; + } +#ifdef HAVE_WINDOWS_H + snprintf(lockname, sizeof(lockname), "vchanger-%s", storage_name); +#else + snprintf(lockname, sizeof(lockname), "/vchanger-%s", storage_name); +#endif + return (void*)sem_open(lockname, O_CREAT, 0770, 1); +} + + +/* + * Function to lock an opened mutex given by fd. + * On success, returns zero. On error, returns -1 and + * sets errno appropriately. + */ +int mymutex_lock(void *fd, time_t wait_sec) +{ + struct timespec ts; + if (wait_sec == 0) return sem_trywait((sem_t*)fd); + ts.tv_sec = time(NULL) + wait_sec; /* semaphore.h functions use absolute time */ + ts.tv_nsec = 0; + return sem_timedwait((sem_t*)fd, &ts); +} + + +/* + * Function to unlock an opened mutex given by fd. + * On success, returns zero. On error, returns -1 and + * sets errno appropriately. + */ +int mymutex_unlock(void *fd) +{ + return sem_post((sem_t*)fd); +} + + +/* + * Function to destroy a mutex owned by the caller. + * On success, returns zero. On error, returns -1 and + * sets errno appropriately. + */ +void mymutex_destroy(const char *name, void *fd) +{ + if (fd) { + sem_post((sem_t*)fd); + sem_close((sem_t*)fd); + } + if (name && name[0]) sem_unlink(name); +} + diff --git a/src/mymutex.h b/src/mymutex.h new file mode 100644 index 0000000..55672ab --- /dev/null +++ b/src/mymutex.h @@ -0,0 +1,32 @@ +/* mymutex.h + * + * Copyright (C) 2017-2020 Josh Fisher + * + * This program is free software. You may redistribute it and/or modify + * it under the terms of the GNU General Public License, as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. See the file "COPYING". If not, + * see . +*/ + +#ifndef _MYMUTEX_H_ +#define _MYMUTEX_H_ 1 + +#ifndef HAVE_TIME_H +#include +#endif + +void* mymutex_create(const char *storage_name); +int mymutex_lock(void* fd, time_t wait_sec); +int mymutex_unlock(void* fd); +int mymutex_destroy(const char *storage_name, void* fd); + +#endif /* _MYPOPEN_H_ */ diff --git a/src/mypopen.cpp b/src/mypopen.cpp index 9bf425d..77a9caf 100644 --- a/src/mypopen.cpp +++ b/src/mypopen.cpp @@ -179,7 +179,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in /* Build argv array from command line string */ if (mypopen_args(cline, argv, argc, argbuf, sizeof(argbuf))) { /* Invalid args, so terminate child */ - log.Debug("popen: invalid cmdline args for child"); + vlog.Debug("popen: invalid cmdline args for child"); errno = EINVAL; return -1; } @@ -193,7 +193,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in /* error creating pipe */ return -1; } - log.Debug("popen: child stdin uses pipe (%d -> %d)", pipe_in[0], pipe_in[1]); + vlog.Debug("popen: child stdin uses pipe (%d -> %d)", pipe_in[0], pipe_in[1]); } else if (*fno_stdin == STDIN_FILENO) { /* Caller specified stdin so just let child inherit it */ fno_stdin = NULL; @@ -212,7 +212,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in errno = rc; return -1; } - log.Debug("popen: child stdout uses pipe (%d -> %d)", pipe_out[0], pipe_out[1]); + vlog.Debug("popen: child stdout uses pipe (%d -> %d)", pipe_out[0], pipe_out[1]); } else { if (*fno_stdout == STDOUT_FILENO) fno_stdout = NULL; else fsync(*fno_stdout); @@ -231,7 +231,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in errno = rc; return -1; } - log.Debug("popen: child stderr uses pipe (%d -> %d)", pipe_err[0], pipe_err[1]); + vlog.Debug("popen: child stderr uses pipe (%d -> %d)", pipe_err[0], pipe_err[1]); } else { if (*fno_stderr == STDERR_FILENO) fno_stderr = NULL; else fsync(*fno_stderr); @@ -239,7 +239,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in } /* fork a child process to run the command in */ - log.Debug("popen: forking now"); + vlog.Debug("popen: forking now"); pid = fork(); switch (pid) { @@ -251,7 +251,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in case 0: /* child is running */ /* close pipe ends always used by parent */ - log.Debug("popen: child closing pipe ends %d,%d,%d used by parent", pipe_in[1], pipe_out[0], pipe_err[0]); + vlog.Debug("popen: child closing pipe ends %d,%d,%d used by parent", pipe_in[1], pipe_out[0], pipe_err[0]); if (pipe_in[1] >= 0) close(pipe_in[1]); if (pipe_out[0] >= 0) close(pipe_out[0]); if (pipe_err[0] >= 0) close(pipe_err[0]); @@ -259,7 +259,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in if (fno_stdin) { if (*fno_stdin < 0) { /* Read end of pipe will be child's stdin */ - log.Debug("popen: child will read stdin from %d", pipe_in[0]); + vlog.Debug("popen: child will read stdin from %d", pipe_in[0]); dup2(pipe_in[0], STDIN_FILENO); close(pipe_in[0]); } else { @@ -272,7 +272,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in if (fno_stdout) { if (*fno_stdout < 0) { /* Write end of pipe will be child's stdout */ - log.Debug("popen: child will write stdout to %d", pipe_out[1]); + vlog.Debug("popen: child will write stdout to %d", pipe_out[1]); dup2(pipe_out[1], STDOUT_FILENO); close(pipe_out[1]); } else { @@ -285,7 +285,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in if (fno_stderr) { if (*fno_stderr < 0) { /* Write end of pipe will be child's stderr */ - log.Debug("popen: child will write stderr to %d", pipe_err[1]); + vlog.Debug("popen: child will write stderr to %d", pipe_err[1]); dup2(pipe_err[1], STDERR_FILENO); close(pipe_err[1]); } else { @@ -295,7 +295,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in } } /* now run the command */ - log.Debug("popen: child executing '%s'", argv[0]); + vlog.Debug("popen: child executing '%s'", argv[0]); execvp(argv[0], argv); /* only gets here if execvp fails */ return -1; @@ -304,7 +304,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in /* parent is running this */ /* close pipe ends always used by child */ - log.Debug("popen: parent closing pipe ends %d,%d,%d used by child", pipe_in[0], pipe_out[1], pipe_err[1]); + vlog.Debug("popen: parent closing pipe ends %d,%d,%d used by child", pipe_in[0], pipe_out[1], pipe_err[1]); if (pipe_in[0] >= 0) close(pipe_in[0]); if (pipe_out[1] >= 0) close(pipe_out[1]); if (pipe_err[1] >= 0) close(pipe_err[1]); @@ -314,25 +314,25 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in if (*fno_stdin < 0) { /* Caller will be writing to child's stdin through pipe */ *fno_stdin = pipe_in[1]; - log.Debug("popen: parent writes child's stdin to %d", pipe_in[1]); + vlog.Debug("popen: parent writes child's stdin to %d", pipe_in[1]); } } if (fno_stdout) { if (*fno_stdout < 0) { /* Caller will be reading from child's stdout through pipe */ *fno_stdout = pipe_out[0]; - log.Debug("popen: parent reads child's stdout from %d", pipe_out[0]); + vlog.Debug("popen: parent reads child's stdout from %d", pipe_out[0]); } } if (fno_stderr) { if (*fno_stderr < 0) { /* Caller will be reading from child's stderr through pipe */ *fno_stderr = pipe_err[0]; - log.Debug("popen: parent reads child's stderr from %d", pipe_err[0]); + vlog.Debug("popen: parent reads child's stderr from %d", pipe_err[0]); } } //sleep(2); - log.Debug("popen: parent returning pid=%d of child", pid); + vlog.Debug("popen: parent returning pid=%d of child", pid); return pid; } @@ -360,7 +360,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in /* Build argv array from command line string */ if (mypopen_args(cline, argv, argc, argbuf, sizeof(argbuf))) { /* Invalid args, so terminate child */ - log.Debug("popen: invalid cmdline args for child"); + vlog.Debug("popen: invalid cmdline args for child"); errno = EINVAL; return -1; } @@ -375,7 +375,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in return -1; } child_in = pipe_in[0]; - log.Debug("popen: child stdin uses pipe (%d -> %d)", pipe_in[0], pipe_in[1]); + vlog.Debug("popen: child stdin uses pipe (%d -> %d)", pipe_in[0], pipe_in[1]); } else { if (*fno_stdin != STDIN_FILENO) { /* Caller supplied an open file to use as child's stdin */ @@ -397,7 +397,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in return -1; } child_out = pipe_out[1]; - log.Debug("popen: child stdout uses pipe (%d -> %d)", pipe_out[0], pipe_out[1]); + vlog.Debug("popen: child stdout uses pipe (%d -> %d)", pipe_out[0], pipe_out[1]); } else { if (*fno_stdout != STDOUT_FILENO) { /* Caller supplied open file to use as child's stdout */ @@ -419,7 +419,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in return -1; } child_err = pipe_err[1]; - log.Debug("popen: child stderr uses pipe (%d -> %d)", pipe_err[0], pipe_err[1]); + vlog.Debug("popen: child stderr uses pipe (%d -> %d)", pipe_err[0], pipe_err[1]); } else { if (*fno_stderr != STDERR_FILENO) { /* Caller supplied an open file to use as child's stderr */ @@ -547,7 +547,7 @@ static int do_mypopen_raw(const char *cline, int *fno_stdin, int *fno_stdout, in if (pipe_out[0] >= 0) *fno_stdout = pipe_out[0]; if (pipe_err[0] >= 0) *fno_stderr = pipe_err[0]; - log.Debug("popen: parent returning pid=%d of child", pid); + vlog.Debug("popen: parent returning pid=%d of child", pid); return pid; } diff --git a/src/uuidlookup.c b/src/uuidlookup.c index 3e79f67..f1d4dee 100644 --- a/src/uuidlookup.c +++ b/src/uuidlookup.c @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2013 Josh Fisher + * vchanger copyright (C) 2008-2018 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -41,6 +41,7 @@ #endif #include "uuidlookup.h" +#include "loghandler.h" #ifdef HAVE_WINDOWS_H @@ -230,6 +231,7 @@ static int GetDevMountpoint(char *mountp, size_t mountp_sz, const char *devname) */ static int GetDevMountpoint(char *mountp, size_t mountp_sz, const char *devname) { + LogHandler_write(LOG_ERROR, "build does not support getmntent() or getfsstat() calls"); return -1; } @@ -256,8 +258,8 @@ int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str) struct udev_list_entry *devices, *dev_list_entry; struct udev_device *dev; int rc = -3; - const char *dev_name, *path, *uuid; - size_t n, pos, dev_name_len; + const char *dev_name, *dev_links, *path, *uuid; + size_t n, pos, dev_links_len; char devlink[4096]; if (!mountp || !mountp_sz) return -2; @@ -280,29 +282,50 @@ int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str) dev_name = udev_device_get_property_value(dev, "DEVNAME"); if (dev_name == NULL) { /* Failed to get kernel device node */ + LogHandler_write(LOG_DEBUG, "filesystem %s has no udev assigned device node", + uuid_str); break; } + LogHandler_write(LOG_DEBUG, "filesystem %s has udev assigned device %s", + uuid_str, dev_name); /* Lookup mountpoint of the kernel device node */ rc = GetDevMountpoint(mountp, mountp_sz, dev_name); - if (rc == 0) break; - /* If not mounted as the DEVNAME, also check if mounted as - * a device alias name from DEVLINKS */ - dev_name = udev_device_get_property_value(dev, "DEVLINKS"); - if (dev_name == NULL) { - /* Failed to get device alias links */ + if (rc == 0) { + /* Found mountpoint */ + LogHandler_write(LOG_DEBUG, "filesystem %s (device %s) mounted at %s", uuid_str, dev_name, mountp); break; } - dev_name_len = strlen(dev_name); - pos = 0; - while (rc == -4 && pos < dev_name_len) { - for (n = pos; n < dev_name_len && !isblank(dev_name[n]); n++) ; - n -= pos; - memmove(devlink, dev_name + pos, n); - devlink[n] = 0; - rc = GetDevMountpoint(mountp, mountp_sz, devlink); - pos += n; - while (pos < dev_name_len && isblank(dev_name[pos])) ++pos; + if (rc == -4) { + /* If not mounted as the DEVNAME, also check if mounted as + * a device alias name from DEVLINKS */ + dev_links = udev_device_get_property_value(dev, "DEVLINKS"); + if (dev_links == NULL) { + /* No device alias links found */ + break; + } + LogHandler_write(LOG_DEBUG, "device %s not found in system mounts, searching all udev device aliases", + dev_name); + /* For each device alias, look for a mountpoint */ + dev_links_len = strlen(dev_links); + pos = 0; + while (rc == -4 && pos < dev_links_len) { + for (n = pos; n < dev_links_len && !isblank(dev_links[n]); n++) ; + n -= pos; + memmove(devlink, dev_links + pos, n); + devlink[n] = 0; + rc = GetDevMountpoint(mountp, mountp_sz, devlink); + if (rc == 0) { + /* Device alias is mounted */ + LogHandler_write(LOG_DEBUG, "filesystem %s (device %s) mounted at %s", uuid_str, devlink, + mountp); + break; + } + rc = -4; /* Ignore other errors from attempt to get alias's mountpoint */ + pos += n; + while (pos < dev_links_len && isblank(dev_links[pos])) ++pos; + } } + if (rc == -4) LogHandler_write(LOG_DEBUG, "filesystem %s (device %s) not mounted", uuid_str, dev_name); break; } } @@ -340,7 +363,11 @@ int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str) #else dev_name = blkid_get_devname(NULL, "UUID", uuid_str); #endif - if (!dev_name) return -3; /* no device with UUID found */ + if (!dev_name) { + LogHandler_write(LOG_DEBUG, "filesystem %s not found", uuid_str); + return -3; + } + LogHandler_write(LOG_DEBUG, "libblkid found filesystem %s at device %s", uuid_str, dev_name); /* find mount point for device */ rc = GetDevMountpoint(mountp, mountp_sz, dev_name); @@ -355,6 +382,7 @@ int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str) */ int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str) { + LogHandler_write(LOG_DEBUG, "GetMountpointFromUUID: UUID lookups not supported by this build"); return -1; } diff --git a/src/vchanger.cpp b/src/vchanger.cpp index 2d2859a..40dda4c 100644 --- a/src/vchanger.cpp +++ b/src/vchanger.cpp @@ -2,7 +2,7 @@ * * This file is part of the vchanger package * - * vchanger copyright (C) 2008-2015 Josh Fisher + * vchanger copyright (C) 2008-2020 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -31,6 +31,9 @@ #ifdef HAVE_STDINT_H #include #endif +#ifdef HAVE_ERRNO_H +#include +#endif #ifdef HAVE_GETOPT_H #include #endif @@ -53,7 +56,10 @@ #include "util.h" #include "compat_defs.h" #include "loghandler.h" +#include "errhandler.h" #include "diskchanger.h" +#include "mymutex.h" +#include "bconsole.h" DiskChanger changer; @@ -80,6 +86,7 @@ typedef struct _cmdparams_s { bool print_version; bool print_help; + bool force; int command; int slot; int drive; @@ -115,12 +122,15 @@ static void print_help(void) " changer defined by vchanger configuration file\n" " 'config_file' using 'slot', 'device', and 'drive'\n" " vchanger [options] config_file LISTMAGS\n" - " vchanger extension to list info on all defined magazines.\n" + " API extension to list info on all defined magazines.\n" " vchanger [options] config_file CREATEVOLS mag_ndx count [start] [CREATEVOLS options]\n" - " vchanger extension to create 'count' empty volume files on the magazine at\n" - " index 'mag_ndx'. If specified, 'start' is the lowest integer to use when\n" + " API extension to create 'count' empty volume files on the magazine at\n" + " index 'mag_ndx'. If specified, 'start' is the lowest integer to use in\n" " appending integers to the label prefix when generating volume names.\n" " vchanger [options] config_file REFRESH\n" + " API extension to issue an Update Slots command in bconsole if a change\n" + " in the virtual slot to volume file mapping is detected. The --force flag\n" + " forces the bconsole call regardless detected changes.\n" " vchanger --version\n" " print version info\n" " vchanger --help\n" @@ -131,13 +141,13 @@ static void print_help(void) "\nCREATEVOLS command options:\n" " -l, --label=string string to use as a prefix for determining the\n" " barcode label of the volume files created. Labels\n" - " will be of the form 'string'_N, where N is an\n" - " integer. By default the prefix will be generated\n" - " using the changer name and the position of the\n" - " magazine's declaration in the configuration file.\n" - " --pool=string Overrides the default pool, defined in the vchanger\n" - " config file, that new volumes should be placed into\n" - " when labeling newly created volumes.\n" + " will be of the form 'string'N, where N is a\n" + " 4 digit integer with leading zeros. The magazine\n" + " name is used as the prefix string by default.\n" + " --pool=string Overrides the default pool that new volumes should\n" + " be placed into when labeling newly created volumes.\n" + "\nREFRESH command options:\n" + " --force Force a bconsole update slots command to be invoked\n" "\nReport bugs to %s.\n", PACKAGE_BUGREPORT); } @@ -147,6 +157,7 @@ static void print_help(void) #define LONGONLYOPT_VERSION 0 #define LONGONLYOPT_HELP 1 #define LONGONLYOPT_POOL 2 +#define LONGONLYOPT_FORCE 3 static int parse_cmdline(int argc, char *argv[]) { @@ -158,10 +169,12 @@ static int parse_cmdline(int argc, char *argv[]) { "group", 1, 0, 'g' }, { "label", 1, 0, 'l' }, { "pool", 1, 0, LONGONLYOPT_POOL }, + { "force", 0, 0, LONGONLYOPT_FORCE }, { 0, 0, 0, 0 } }; cmdl.print_version = false; cmdl.print_help = false; + cmdl.force = false; cmdl.command = 0; cmdl.slot = 0; cmdl.drive = 0; @@ -198,6 +211,9 @@ static int parse_cmdline(int argc, char *argv[]) case LONGONLYOPT_POOL: cmdl.pool = optarg; break; + case LONGONLYOPT_FORCE: + cmdl.force = true; + break; default: fprintf(stderr, "unknown option %s\n", optarg); return -1; @@ -237,6 +253,11 @@ static int parse_cmdline(int argc, char *argv[]) fprintf(stderr, "flag --pool not valid for this command\n"); return -1; } + /* Make sure only REFRESH command has --force flag */ + if (cmdl.force && cmdl.command != CMD_REFRESH) { + fprintf(stderr, "flag --force not valid for this command\n"); + return -1; + } /* Check param 3 exists */ ++ndx; if (ndx >= argc) { @@ -378,7 +399,7 @@ static int do_list_cmd() fprintf(stdout, "%d:%s\n", slot, changer.GetVolumeLabel(slot)); } } - log.Info(" SUCCESS sent list to stdout"); + vlog.Info(" SUCCESS sent list to stdout"); return 0; } @@ -390,7 +411,7 @@ static int do_list_cmd() static int do_slots_cmd() { fprintf(stdout, "%d\n", changer.NumSlots()); - log.Info(" SUCCESS reporting %d slots", changer.NumSlots()); + vlog.Info(" SUCCESS reporting %d slots", changer.NumSlots()); return 0; } @@ -403,10 +424,10 @@ static int do_load_cmd() { if (changer.LoadDrive(cmdl.drive, cmdl.slot)) { fprintf(stderr, "%s\n", changer.GetErrorMsg()); - log.Error(" ERROR loading slot %d into drive %d", cmdl.slot, cmdl.drive); + vlog.Error(" ERROR loading slot %d into drive %d", cmdl.slot, cmdl.drive); return 1; } - log.Info(" SUCCESS loading slot %d into drive %d", cmdl.slot, cmdl.drive); + vlog.Info(" SUCCESS loading slot %d into drive %d", cmdl.slot, cmdl.drive); return 0; } @@ -419,10 +440,10 @@ static int do_unload_cmd() { if (changer.UnloadDrive(cmdl.drive)) { fprintf(stderr, "%s\n", changer.GetErrorMsg()); - log.Error(" ERROR unloading slot %d from drive %d", cmdl.slot, cmdl.drive); + vlog.Error(" ERROR unloading slot %d from drive %d", cmdl.slot, cmdl.drive); return 1; } - log.Info(" SUCCESS unloading slot %d from drive %d", cmdl.slot, cmdl.drive); + vlog.Info(" SUCCESS unloading slot %d from drive %d", cmdl.slot, cmdl.drive); return 0; } @@ -437,10 +458,11 @@ static int do_loaded_cmd() int slot = changer.GetDriveSlot(cmdl.drive); if (slot < 0) slot = 0; fprintf(stdout, "%d\n", slot); - log.Info(" SUCCESS reporting drive %d loaded from slot %d", cmdl.drive, slot); + vlog.Info(" SUCCESS reporting drive %d loaded from slot %d", cmdl.drive, slot); return 0; } + /*------------------------------------------------- * LISTALL Command * Prints state of drives (loaded or empty), followed by state @@ -471,7 +493,7 @@ static int do_list_all() fprintf(stdout, "S:%d:E\n", n); } } - log.Info(" SUCCESS sent listall to stdout"); + vlog.Info(" SUCCESS sent listall to stdout"); return 0; } @@ -487,7 +509,7 @@ static int do_list_magazines() if (changer.NumMagazines() == 0) { fprintf(stdout, "No magazines are defined\n"); - log.Info(" SUCCESS no magazines are defined"); + vlog.Info(" SUCCESS no magazines are defined"); return 0; } for (n = 0; n < changer.NumMagazines(); n++) { @@ -498,10 +520,11 @@ static int do_list_magazines() changer.GetMagazineStartSlot(n), changer.GetMagazineMountpoint(n)); } } - log.Info(" SUCCESS listing magazine info to stdout"); + vlog.Info(" SUCCESS listing magazine info to stdout"); return 0; } + /*------------------------------------------------- * CREATEVOLS (Create Volumes) Command * Creates volume files on the specified magazine @@ -511,12 +534,12 @@ static int do_create_vols() /* Create new volume files on magazine */ if (changer.CreateVolumes(cmdl.mag_bay, cmdl.count, cmdl.slot, cmdl.label_prefix.c_str())) { fprintf(stderr, "%s\n", changer.GetErrorMsg()); - log.Error(" ERROR"); + vlog.Error(" ERROR: %s", changer.GetErrorMsg()); return -1; } fprintf(stdout, "Created %d volume files on magazine %d\n", cmdl.count, cmdl.mag_bay); - log.Info(" SUCCESS"); + vlog.Info(" SUCCESS"); return 0; } @@ -529,13 +552,14 @@ int main(int argc, char *argv[]) int rc; FILE *fs = NULL; int32_t error_code; + void *command_mux = NULL, *bconsole_mux = NULL; #ifdef HAVE_LOCALE_H setlocale(LC_ALL, ""); #endif /* Log initially to stderr */ - log.OpenLog(stderr, LOG_ERR); + vlog.OpenLog(stderr, LOG_ERR); /* parse the command line */ if ((error_code = parse_cmdline(argc, argv)) != 0) { print_help(); @@ -551,6 +575,7 @@ int main(int argc, char *argv[]) print_help(); return 0; } + /* Read vchanger config file */ if (!conf.Read(cmdl.config_file)) { return 1; @@ -573,89 +598,137 @@ int main(int argc, char *argv[]) fprintf(stderr, "Error opening opening log file\n"); return 1; } - log.OpenLog(fs, conf.log_level); + vlog.OpenLog(fs, conf.log_level); } /* Validate and commit configuration parameters */ if (!conf.Validate()) { + fprintf(stderr, "ERROR! configuration file error\n"); return 1; } #ifndef HAVE_WINDOWS_H /* Ignore SIGPIPE signals */ signal(SIGPIPE, SIG_IGN); #endif - /* Initialize changer. A lock file is created to serialize access + + /* Open/create named mutex */ + command_mux = mymutex_create("vchanger-command"); + if (command_mux == 0) { + vlog.Error("ERROR! failed to create named mutex errno=%d", errno); + fprintf(stderr, "ERROR! failed to create named mutex errno=%d\n", errno); + return 1; + } + /* Lock mutex to perform command */ + if (mymutex_lock(command_mux, 300)) { + vlog.Error("ERROR! failed to lock named mutex errno=%d", errno); + fprintf(stderr, "ERROR! failed to lock named mutex errno=%d\n", errno); + mymutex_destroy("vchanger-command", command_mux); + return 1; + } + + /* Initialize changer. A named mutex is created to serialize access * to the changer. As a result, changer initialization may block * for up to 30 seconds, and may fail if a timeout is reached */ if (changer.Initialize()) { + vlog.Error("%s", changer.GetErrorMsg()); fprintf(stderr, "%s\n", changer.GetErrorMsg()); + mymutex_destroy("vchanger-command", command_mux); return 1; } /* Perform command */ switch (cmdl.command) { case CMD_LIST: - log.Debug("==== preforming LIST command pid=%d", getpid()); + vlog.Debug("==== preforming LIST command"); error_code = do_list_cmd(); break; case CMD_SLOTS: - log.Debug("==== preforming SLOTS command pid=%d", getpid()); + vlog.Debug("==== preforming SLOTS command"); error_code = do_slots_cmd(); break; case CMD_LOAD: - log.Debug("==== preforming LOAD command pid=%d", getpid()); + vlog.Debug("==== preforming LOAD command"); error_code = do_load_cmd(); break; case CMD_UNLOAD: - log.Debug("==== preforming UNLOAD command pid=%d", getpid()); + vlog.Debug("==== preforming UNLOAD command"); error_code = do_unload_cmd(); break; case CMD_LOADED: - log.Debug("==== preforming LOADED command pid=%d", getpid()); + vlog.Debug("==== preforming LOADED command"); error_code = do_loaded_cmd(); break; case CMD_LISTALL: - log.Debug("==== preforming LISTALL command pid=%d", getpid()); + vlog.Debug("==== preforming LISTALL command"); error_code = do_list_all(); break; case CMD_LISTMAGS: - log.Debug("==== preforming LISTMAGS command pid=%d", getpid()); + vlog.Debug("==== preforming LISTMAGS command"); error_code = do_list_magazines(); break; case CMD_CREATEVOLS: - log.Debug("==== preforming CREATEVOLS command pid=%d", getpid()); + vlog.Debug("==== preforming CREATEVOLS command"); error_code = do_create_vols(); break; case CMD_REFRESH: - log.Debug("==== preforming REFRESH command pid=%d", getpid()); + vlog.Debug("==== preforming REFRESH command"); error_code = 0; - log.Info(" SUCCESS pid=%d", getpid()); break; } - changer.Unlock(); /* If there was an error, then exit */ - if (error_code) return error_code; + if (error_code) { + mymutex_destroy("vchanger-command", command_mux); + return error_code; + } /* If not updating Bacula, then exit */ +#ifdef HAVE_WINDOWS_H + conf.bconsole = ""; /* Issuing bconsole commands not implemented on Windows */ +#endif if (conf.bconsole.empty()) { /* Bacula interaction via bconsole is disabled, so log warnings */ if (changer.NeedsUpdate()) - log.Error("WARNING! 'update slots' needed in bconsole pid=%d", getpid()); + vlog.Error("WARNING! 'update slots' needed in bconsole pid=%d", getpid()); if (changer.NeedsLabel()) - log.Error("WARNING! 'label barcodes' needed in bconsole pid=%d", getpid()); + vlog.Error("WARNING! 'label barcodes' needed in bconsole pid=%d", getpid()); + mymutex_destroy("vchanger-command", command_mux); return 0; } /* Update Bacula via bconsole */ -#ifndef HAVE_WINDOWS_H - changer.UpdateBacula(); -#else - /* Auto-update of bacula not working for Windows */ - if (changer.NeedsUpdate()) - log.Error("WARNING! 'update slots' needed in bconsole"); - if (changer.NeedsLabel()) - log.Error("WARNING! 'label barcodes' needed in bconsole"); -#endif + /* Create named mutex to prevent further bconsole commands when bconsole + * commands have already been initiated */ + bconsole_mux = mymutex_create("vchanger-bconsole"); + if (bconsole_mux == 0) { + vlog.Error("ERROR! failed to create named mutex errno=%d", errno); + fprintf(stderr, "ERROR! failed to create named mutex errno=%d\n", errno); + mymutex_destroy("vchanger-command", command_mux); + return 1; + } + /* Lock mutex to perform command */ + if (mymutex_lock(bconsole_mux, 0)) { + /* If bconsole mutex is locked because another instance has previously invoked + * bconsole, then this instance is the result of bconsole itself invoking + * additional vchanger processes to handle the previous instance's bconsole + * command. So tto prevent a race condition, this instance must not invoke + * further bconsole processes. */ + vlog.Info("invoked from bconsole - skipping further bconsole commands", errno); + mymutex_destroy("vchanger-bconsole", bconsole_mux); + mymutex_destroy("vchanger-command", command_mux); + return 0; + } + + /* Unlock the command mutex long enough to issue bconsole commands. + * Note that the bconsole mutex is left locked to prevent a race condition + * should the invoked bconsole process need to invoke additional + * instances of vchanger. */ + mymutex_unlock(command_mux); + IssueBconsoleCommands(changer.NeedsUpdate() | cmdl.force, changer.NeedsLabel()); + mymutex_lock(command_mux, 300); + + /* Cleanup */ + mymutex_destroy("vchanger-bconsole", command_mux); + mymutex_destroy("vchanger-command", bconsole_mux); return 0; } diff --git a/src/vconf.cpp b/src/vconf.cpp index 167cf63..f388040 100644 --- a/src/vconf.cpp +++ b/src/vconf.cpp @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2014 Josh Fisher + * vchanger copyright (C) 2008-2018 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -45,26 +45,21 @@ #include #endif #ifdef HAVE_WINDOWS_H -#include "targetver.h" #include +#endif +#ifdef HAVE_SHLOBJ_H #include +#endif +#ifdef HAVE_DIRECT_H #include -#define DIR_DELIM "\\" -#define DIR_DELIM_C '\\' -#define MAG_VOLUME_MASK 0 -#else -#define DIR_DELIM "/" -#define DIR_DELIM_C '/' -#define MAG_VOLUME_MASK S_IWGRP|S_IRWXO #endif +#include "compat_defs.h" #include "loghandler.h" #include "util.h" #define __VCONF_SOURCE 1 #include "vconf.h" -/* Global configuration object */ -VchangerConfig conf; /*------------------------------------------- * Config file keywords and defaults @@ -145,19 +140,19 @@ bool VchangerConfig::Read(const char *cfile) tmp_ini.ClearKeywordValues(); if (!cfile || !cfile[0]) { - log.Error("config file not specified"); + vlog.Error("config file not specified"); return false; } /* Does config file exist */ if (access(cfile, R_OK)) { - log.Error("could not access config file %s", cfile); + vlog.Error("could not access config file %s", cfile); return false; } /* Read config file values */ rc = tmp_ini.Read(cfile); if (rc) { - if (rc > 0) log.Error("Parse error in %s at line %d", cfile, rc); - else log.Error("could not open config file %s", cfile); + if (rc > 0) vlog.Error("Parse error in %s at line %d", cfile, rc); + else vlog.Error("could not open config file %s", cfile); return false; } /* Update keyword values */ @@ -168,7 +163,7 @@ bool VchangerConfig::Read(const char *cfile) storage_name = (const char*)keyword[VK_STORAGE_NAME]; tStrip(storage_name); if (storage_name.empty()) { - log.Error("config file keyword '%s' must specify a non-empty string", VK_STORAGE_NAME); + vlog.Error("config file keyword '%s' must specify a non-empty string", VK_STORAGE_NAME); return false; } /* Update defaults for this changer name */ @@ -181,7 +176,7 @@ bool VchangerConfig::Read(const char *cfile) work_dir = (const char*)keyword[VK_WORK_DIR]; tStrip(work_dir); if (work_dir.empty()) { - log.Error("config file keyword '%s' must specify a non-empty string", VK_WORK_DIR); + vlog.Error("config file keyword '%s' must specify a non-empty string", VK_WORK_DIR); return false; } } @@ -191,7 +186,7 @@ bool VchangerConfig::Read(const char *cfile) logfile = (const char*)keyword[VK_LOGFILE]; tStrip(logfile); if (logfile.empty()) { - log.Error("config file keyword '%s' must specify a non-empty string", VK_LOGFILE); + vlog.Error("config file keyword '%s' must specify a non-empty string", VK_LOGFILE); return false; } } @@ -200,7 +195,7 @@ bool VchangerConfig::Read(const char *cfile) if (keyword[VK_LOG_LEVEL].IsSet()) { log_level = (int)keyword[VK_LOG_LEVEL]; if (log_level < 0 || log_level > 7) { - log.Error("config file keyword '%s' must specify a value between 0 and 7 inclusive", VK_LOG_LEVEL); + vlog.Error("config file keyword '%s' must specify a value between 0 and 7 inclusive", VK_LOG_LEVEL); return false; } } @@ -210,7 +205,7 @@ bool VchangerConfig::Read(const char *cfile) user = (const char*)keyword[VK_USER]; tStrip(user); if (user.empty()) { - log.Error("keyword '%s' value cannot be empty", VK_USER); + vlog.Error("keyword '%s' value cannot be empty", VK_USER); return false; } } @@ -220,7 +215,7 @@ bool VchangerConfig::Read(const char *cfile) group = (const char*)keyword[VK_GROUP]; tStrip(group); if (group.empty()) { - log.Error("keyword '%s' value cannot be empty", VK_GROUP); + vlog.Error("keyword '%s' value cannot be empty", VK_GROUP); return false; } } @@ -242,7 +237,7 @@ bool VchangerConfig::Read(const char *cfile) def_pool = (const char*)keyword[VK_DEF_POOL]; tStrip(def_pool); if (def_pool.empty()) { - log.Error("keyword '%s' value cannot be empty", VK_DEF_POOL); + vlog.Error("keyword '%s' value cannot be empty", VK_DEF_POOL); return false; } } @@ -252,13 +247,13 @@ bool VchangerConfig::Read(const char *cfile) magazine = keyword[VK_MAGAZINE]; } if (magazine.empty()) { - log.Error("config file keyword '%s' must appear at least once", VK_MAGAZINE); + vlog.Error("config file keyword '%s' must appear at least once", VK_MAGAZINE); return false; } for (n = 0; n < (int)magazine.size(); n++) { tStrip(magazine[n]); if (magazine[n].empty()) { - log.Error("config file keyword '%s' cannot be set to the empty string", VK_MAGAZINE); + vlog.Error("config file keyword '%s' cannot be set to the empty string", VK_MAGAZINE); return false; } } @@ -283,13 +278,13 @@ bool VchangerConfig::Validate() #else if (_mkdir(work_dir.c_str())) { #endif - log.Error("could not create work directory '%s'", work_dir.c_str()); + vlog.Error("could not create work directory '%s'", work_dir.c_str()); umask(old_mask); return false; } umask(old_mask); } else { - log.Error("could not access work directory '%s'", work_dir.c_str()); + vlog.Error("could not access work directory '%s'", work_dir.c_str()); return false; } } @@ -299,7 +294,7 @@ bool VchangerConfig::Validate() if (!bconsole_config.empty()) { if (access(bconsole_config.c_str(), R_OK)) { /* If bconsole config doesn't exist or is not readable, disable use of bconsole */ - log.Warning("cannot read bconsole config file. Disabling Bacula interaction."); + vlog.Warning("cannot read bconsole config file. Disabling Bacula interaction."); bconsole.clear(); bconsole_config.clear(); } diff --git a/src/vconf.h b/src/vconf.h index f36df28..f0ee816 100644 --- a/src/vconf.h +++ b/src/vconf.h @@ -2,7 +2,7 @@ * * This file is part of vchanger by Josh Fisher. * - * vchanger copyright (C) 2008-2014 Josh Fisher + * vchanger copyright (C) 2008-2018 Josh Fisher * * vchanger is free software. * You may redistribute it and/or modify it under the terms of the @@ -65,6 +65,7 @@ extern char DEFAULT_STATEDIR[4096]; #else char DEFAULT_LOGDIR[4096]; char DEFAULT_STATEDIR[4096]; +VchangerConfig conf; #endif #endif /* _VCONF_H_ */ diff --git a/win32/installer.nsi b/win32/installer.nsi index 85e8363..053ee06 100644 --- a/win32/installer.nsi +++ b/win32/installer.nsi @@ -1,5 +1,5 @@ #................- -#. Date of creation: 2015-06-01 +#. Date of creation: 2020-05-06 #. Name: installer.nsi #................- #..- Package parameters ... @@ -9,7 +9,15 @@ SetCompressor lzma !include MUI2.nsh !include WinMessages.nsh !include x64.nsh -!insertmacro MUI_LANGUAGE English +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_LICENSE "license.txt" +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_LANGUAGE "English" + #... Installation Windows Parameters .. !define APPNAME "vchanger" !define COMPANYNAME "Josh Fisher" @@ -17,20 +25,20 @@ SetCompressor lzma # These three must be integers !define VERSIONMAJOR 1 !define VERSIONMINOR 0 -!define VERSIONBUILD 1 +!define VERSIONBUILD 3 # These will be displayed by the "Click here for support information" link in "Add/Remove Programs" !define HELPURL "http://sourceforge.net/projects/vchanger/" # "Support Information" link -Name "vchanger 1.0.1" -VIProductVersion "1.0.1.0" +Name "vchanger 1.0.3" +VIProductVersion "1.0.3.0" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "vchanger Installer" -VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "Copyright (c) Josh Fisher 2008-2015" +VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "Copyright (c) Josh Fisher 2008-2020" VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "vchanger Windows Installer" -VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "1.0.1" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "1.0.3" ShowInstDetails nevershow SilentInstall normal RequestExecutionLevel admin AutoCloseWindow True -OutFile "vchanger-1.0.1.exe" +OutFile "vchanger-1.0.3.exe" PageEx license LicenseData "license.txt" @@ -121,13 +129,13 @@ Function .onInit SetShellVarContext all #Determine the bitness of the OS and enable the correct section ${If} ${RunningX64} - SectionSetFlags SEC0001 ${SECTION_OFF} - SectionSetFlags SEC0002 ${SF_SELECTED} + SectionSetFlags ${SEC0001} ${SECTION_OFF} + SectionSetFlags ${SEC0002} ${SF_SELECTED} StrCpy $INSTDIR "$PROGRAMFILES64\vchanger" SetRegView 64 ${Else} - SectionSetFlags SEC0002 ${SECTION_OFF} - SectionSetFlags SEC0001 ${SF_SELECTED} + SectionSetFlags ${SEC0002} ${SECTION_OFF} + SectionSetFlags ${SEC0001} ${SF_SELECTED} StrCpy $INSTDIR "$PROGRAMFILES32\vchanger" SetRegView 32 ${EndIf} diff --git a/win32build b/win32build index 157c50a..84a7a28 100755 --- a/win32build +++ b/win32build @@ -2,7 +2,7 @@ # # Build for 32-bit and 64-bit Windows binary and create NSIS installer # -VERS=1.0.1 +VERS=1.0.3 rm -f ./win32/vchanger-$VERS.exe rm -f ./win32/vchanger.exe rm -f ./win32/vchanger64.exe