From 694d5ab55e0f5c5b61e6f868a4484f388713329e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wanderlei=20H=C3=BCttel?= Date: Mon, 6 Nov 2023 19:38:13 -0300 Subject: [PATCH] Fix line break CRLF to LF --- doc/vchanger-example.conf | 156 ++-- src/targetver.h | 48 +- src/util.cpp | 384 +++++----- src/uuidlookup.h | 74 +- src/vchanger.cpp | 1468 ++++++++++++++++++------------------- 5 files changed, 1065 insertions(+), 1065 deletions(-) diff --git a/doc/vchanger-example.conf b/doc/vchanger-example.conf index 3ca31bd..d560fe6 100644 --- a/doc/vchanger-example.conf +++ b/doc/vchanger-example.conf @@ -1,78 +1,78 @@ -# Example vchanger 1.0.2 config file - -# -# Storage Resource Name of the Storage resource defined in bacula-dir.conf -# that is associated with this changer. -# [Default: 'vchanger' ] -#Storage Resource = "vchanger" - -# -# 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: 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 of the user that -# bacula-sd runs as. -# [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} ] -#work dir = "/var/spool/vchanger/vcahnger" - -# -# Logfile Path to log file for this changer. -# [Default: {WorkDir}/{StorageResource}.log ] -#logfile = "/var/spool/vchanger/vchanger.log" - -# -# Log Level Sets the level of detail being logged. An integer value from -# 0-7 is expected, where 7 logs the most detail. The levels -# correspond to LOG_EMERG - LOG_DEBUG. (See man 3 syslog) -# [Default: 3 (LOG_ERR) ] -#log_level = 3 - -# -# bconsole Sets the path to the bconsole binary that vchanger will run -# in order to send 'update slots' and 'label barcodes' commands -# to Bacula. To disable sending commands to Bacula, set -# bconsole="". -# [Default: "/usr/sbin/bconsole" ] -#bconsole = "/usr/sbin/bconsole" - -# -# bconsole config Path to the config file bconsole will use when vchanger -# invokes bconsole. By default, bconsole will be invoked -# without the -c flag. The file must be readable by the user -# vchanger is run as. -# [Default: none ] -#bconsole config = /etc/bacula/bconsole.conf - -# -# Default Pool Name of the pool that new volumes created by vcahnger -# should be placed into when using bconsole to send a -# 'label barcodes' command. -# [Default: "Scratch" ] -#default pool = "Scratch" - -# -# Magazine [Required] Gives the list of magazines known to this changer. -# One or more magazine directives must be specified. A magazine -# may be specified as either a directory path or as the UUID -# of a filesystem partition. A magazine is specified by UUID -# 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 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" +# Example vchanger 1.0.2 config file + +# +# Storage Resource Name of the Storage resource defined in bacula-dir.conf +# that is associated with this changer. +# [Default: 'vchanger' ] +#Storage Resource = "vchanger" + +# +# 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: 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 of the user that +# bacula-sd runs as. +# [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} ] +#work dir = "/var/spool/vchanger/vcahnger" + +# +# Logfile Path to log file for this changer. +# [Default: {WorkDir}/{StorageResource}.log ] +#logfile = "/var/spool/vchanger/vchanger.log" + +# +# Log Level Sets the level of detail being logged. An integer value from +# 0-7 is expected, where 7 logs the most detail. The levels +# correspond to LOG_EMERG - LOG_DEBUG. (See man 3 syslog) +# [Default: 3 (LOG_ERR) ] +#log_level = 3 + +# +# bconsole Sets the path to the bconsole binary that vchanger will run +# in order to send 'update slots' and 'label barcodes' commands +# to Bacula. To disable sending commands to Bacula, set +# bconsole="". +# [Default: "/usr/sbin/bconsole" ] +#bconsole = "/usr/sbin/bconsole" + +# +# bconsole config Path to the config file bconsole will use when vchanger +# invokes bconsole. By default, bconsole will be invoked +# without the -c flag. The file must be readable by the user +# vchanger is run as. +# [Default: none ] +#bconsole config = /etc/bacula/bconsole.conf + +# +# Default Pool Name of the pool that new volumes created by vcahnger +# should be placed into when using bconsole to send a +# 'label barcodes' command. +# [Default: "Scratch" ] +#default pool = "Scratch" + +# +# Magazine [Required] Gives the list of magazines known to this changer. +# One or more magazine directives must be specified. A magazine +# may be specified as either a directory path or as the UUID +# of a filesystem partition. A magazine is specified by UUID +# 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 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/src/targetver.h b/src/targetver.h index c9d5562..ad5d385 100644 --- a/src/targetver.h +++ b/src/targetver.h @@ -1,24 +1,24 @@ - -/* The following macros define the minimum required platform. The minimum required platform - * is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run - * your application. The macros work by enabling all features available on platform versions up to and - * including the version specified. - * - * Modify the following defines if you have to target a platform prior to the ones specified below. - * Refer to MSDN for the latest info on corresponding values for different platforms. - */ -#ifndef _TARGETVER_H_ -#define _TARGETVER_H_ 1 -#ifdef HAVE_WINDOWS_H -#ifndef WINVER -#define WINVER 0x0600 -#endif -#ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x0600 -#endif -#ifndef _WIN32_IE -#define _WIN32_IE 0x0700 -#endif -#define _POSIX 1 -#endif -#endif + +/* The following macros define the minimum required platform. The minimum required platform + * is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run + * your application. The macros work by enabling all features available on platform versions up to and + * including the version specified. + * + * Modify the following defines if you have to target a platform prior to the ones specified below. + * Refer to MSDN for the latest info on corresponding values for different platforms. + */ +#ifndef _TARGETVER_H_ +#define _TARGETVER_H_ 1 +#ifdef HAVE_WINDOWS_H +#ifndef WINVER +#define WINVER 0x0600 +#endif +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0600 +#endif +#ifndef _WIN32_IE +#define _WIN32_IE 0x0700 +#endif +#define _POSIX 1 +#endif +#endif diff --git a/src/util.cpp b/src/util.cpp index e345106..354a7ba 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,192 +1,192 @@ -/* util.cpp - * - * This file is part of vchanger by Josh Fisher. - * - * vchanger copyright (C) 2008-2014 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. - * - * This file simply provides some utility functions - */ - -#include "config.h" -#include -#ifdef HAVE_STDLIB_H -#include -#endif -#ifdef HAVE_STRING_H -#include -#endif -#ifdef HAVE_CTYPE_H -#include -#endif -#ifdef HAVE_ERRNO_H -#include -#endif -#ifdef HAVE_UNISTD_H -#include -#endif -#ifdef HAVE_STDARG_H -#include -#endif -#ifdef HAVE_SYS_TYPES_H -#include -#endif -#ifdef HAVE_SYS_STAT_H -#include -#endif -#ifdef HAVE_FCNTL_H -#include -#endif -#ifdef HAVE_GRP_H -#include -#endif -#ifdef HAVE_PWD_H -#include -#endif -#ifdef HAVE_TIME_H -#include -#endif -#ifdef HAVE_SYS_TIME_H -#include -#endif - -#include "util.h" - -/*------------------------------------------------- - * Function to return elapsed time between two struct timeval values - * in microseconds. - *------------------------------------------------*/ -long timeval_et(struct timeval *tv1, struct timeval *tv2) -{ - if (!tv1 || !tv2) return 0; - return ((tv2->tv_sec - tv1->tv_sec) * 1000000) + tv2->tv_usec - tv1->tv_usec; -} - - -/*------------------------------------------------- - * Function to open file 'fname' for write in exclusive access mode. - * On success, returns the opened stream. Otherwise, on error returns - * NULL. - *------------------------------------------------*/ -int exclusive_fopen(const char *fname, FILE **fs) -{ - int fd, result = 0; - *fs = NULL; - fd = open(fname, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); - if (fd > 0) { - *fs = fdopen(fd, "w"); - if (*fs == NULL) { - result = errno; - close(fd); - unlink(fname); - } - } else { - result = errno; - } - return result; -} - - -/*------------------------------------------------- - * Function to copy file 'from_path' to new file 'to_path'. - * On success returns zero, else returns errno - *------------------------------------------------*/ -int file_copy(const char *to_path, const char *from_path) -{ - int rc; - size_t n; - FILE *to, *from; - char *buf; - size_t buf_sz = 1024 * 1024; - - if (access(to_path, F_OK) == 0) return EEXIST; - from = fopen(from_path, "r"); - if (!from) return errno; - to = fopen(to_path, "w"); - if (!to) { - rc = errno; - fclose(from); - return rc; - } - buf = (char*)malloc(buf_sz); - while ((n = fread(buf, 1, buf_sz, from)) > 0) { - if (fwrite(buf, 1 , n, to) != n) { - rc = ferror(to); - fclose(to); - unlink(to_path); - fclose(from); - free(buf); - return rc; - } - } - free(buf); - fclose(to); - if (!feof(from)) { - rc = ferror(from); - unlink(to_path); - fclose(from); - return rc; - } - fclose(from); - return 0; -} - -/*------------------------------------------------- - * Function to drop root privileges and change persona to uid:gid - * of the given user name and group name. - * On success returns zero, else on error returns errno. - *------------------------------------------------*/ -int drop_privs(const char *uname, const char *gname) -{ -#ifdef HAVE_WINDOWS_H - return 0; /* For windows ignore user switching */ -#else - gid_t new_gid; - struct passwd *pw; - struct group *gr; - if (!uname || !uname[0] || getuid()) return 0; /* Nothing to do */ - if ((pw = getpwnam(uname)) == NULL) return errno; - if (pw->pw_uid == 0) return 0; /* already running as root */ - new_gid = pw->pw_gid; - if (gname && gname[0]) { - /* find given group */ - if ((gr = getgrnam(gname)) == NULL) return errno; /* no such group */ - new_gid = gr->gr_gid; - } - /* Set supplemental groups */ - if (initgroups(uname, new_gid)) return errno; - /* Start running as given group */ - if (setgid(new_gid)) return errno; - /* Drop root and run as given user */ - if (setuid(pw->pw_uid)) return errno; - return 0; -#endif -} - - -/*------------------------------------------------- - * Function returns zero if current user not superuser - * else non-zero. - *------------------------------------------------*/ -int is_root_user() -{ -#ifndef HAVE_WINDOWS_H - return getuid() == 0 ? 1 : 0; -#endif - return 0; -} +/* util.cpp + * + * This file is part of vchanger by Josh Fisher. + * + * vchanger copyright (C) 2008-2014 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. + * + * This file simply provides some utility functions + */ + +#include "config.h" +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_STDARG_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#ifdef HAVE_PWD_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#include "util.h" + +/*------------------------------------------------- + * Function to return elapsed time between two struct timeval values + * in microseconds. + *------------------------------------------------*/ +long timeval_et(struct timeval *tv1, struct timeval *tv2) +{ + if (!tv1 || !tv2) return 0; + return ((tv2->tv_sec - tv1->tv_sec) * 1000000) + tv2->tv_usec - tv1->tv_usec; +} + + +/*------------------------------------------------- + * Function to open file 'fname' for write in exclusive access mode. + * On success, returns the opened stream. Otherwise, on error returns + * NULL. + *------------------------------------------------*/ +int exclusive_fopen(const char *fname, FILE **fs) +{ + int fd, result = 0; + *fs = NULL; + fd = open(fname, O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR); + if (fd > 0) { + *fs = fdopen(fd, "w"); + if (*fs == NULL) { + result = errno; + close(fd); + unlink(fname); + } + } else { + result = errno; + } + return result; +} + + +/*------------------------------------------------- + * Function to copy file 'from_path' to new file 'to_path'. + * On success returns zero, else returns errno + *------------------------------------------------*/ +int file_copy(const char *to_path, const char *from_path) +{ + int rc; + size_t n; + FILE *to, *from; + char *buf; + size_t buf_sz = 1024 * 1024; + + if (access(to_path, F_OK) == 0) return EEXIST; + from = fopen(from_path, "r"); + if (!from) return errno; + to = fopen(to_path, "w"); + if (!to) { + rc = errno; + fclose(from); + return rc; + } + buf = (char*)malloc(buf_sz); + while ((n = fread(buf, 1, buf_sz, from)) > 0) { + if (fwrite(buf, 1 , n, to) != n) { + rc = ferror(to); + fclose(to); + unlink(to_path); + fclose(from); + free(buf); + return rc; + } + } + free(buf); + fclose(to); + if (!feof(from)) { + rc = ferror(from); + unlink(to_path); + fclose(from); + return rc; + } + fclose(from); + return 0; +} + +/*------------------------------------------------- + * Function to drop root privileges and change persona to uid:gid + * of the given user name and group name. + * On success returns zero, else on error returns errno. + *------------------------------------------------*/ +int drop_privs(const char *uname, const char *gname) +{ +#ifdef HAVE_WINDOWS_H + return 0; /* For windows ignore user switching */ +#else + gid_t new_gid; + struct passwd *pw; + struct group *gr; + if (!uname || !uname[0] || getuid()) return 0; /* Nothing to do */ + if ((pw = getpwnam(uname)) == NULL) return errno; + if (pw->pw_uid == 0) return 0; /* already running as root */ + new_gid = pw->pw_gid; + if (gname && gname[0]) { + /* find given group */ + if ((gr = getgrnam(gname)) == NULL) return errno; /* no such group */ + new_gid = gr->gr_gid; + } + /* Set supplemental groups */ + if (initgroups(uname, new_gid)) return errno; + /* Start running as given group */ + if (setgid(new_gid)) return errno; + /* Drop root and run as given user */ + if (setuid(pw->pw_uid)) return errno; + return 0; +#endif +} + + +/*------------------------------------------------- + * Function returns zero if current user not superuser + * else non-zero. + *------------------------------------------------*/ +int is_root_user() +{ +#ifndef HAVE_WINDOWS_H + return getuid() == 0 ? 1 : 0; +#endif + return 0; +} diff --git a/src/uuidlookup.h b/src/uuidlookup.h index c6da73e..be6d994 100644 --- a/src/uuidlookup.h +++ b/src/uuidlookup.h @@ -1,37 +1,37 @@ -/* uuidlookup.h - * - * This file is part of vchanger by Josh Fisher. - * - * vchanger copyright (C) 2008-2013 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 __UUIDLOOKUP_H_ -#define __UUIDLOOKUP_H_ - -#ifdef __cplusplus -extern "C" { -#endif - -int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str); - -#ifdef __cplusplus -} -#endif - -#endif /* __UUIDLOOKUP_H_ */ +/* uuidlookup.h + * + * This file is part of vchanger by Josh Fisher. + * + * vchanger copyright (C) 2008-2013 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 __UUIDLOOKUP_H_ +#define __UUIDLOOKUP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int GetMountpointFromUUID(char *mountp, size_t mountp_sz, const char *uuid_str); + +#ifdef __cplusplus +} +#endif + +#endif /* __UUIDLOOKUP_H_ */ diff --git a/src/vchanger.cpp b/src/vchanger.cpp index a4b21f8..bd3071e 100644 --- a/src/vchanger.cpp +++ b/src/vchanger.cpp @@ -1,734 +1,734 @@ -/* vchanger.cpp - * - * This file is part of the vchanger package - * - * vchanger copyright (C) 2008-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" -#ifdef HAVE_STDIO_H -#include -#endif -#ifdef HAVE_STDLIB_H -#include -#endif -#ifdef HAVE_STDINT_H -#include -#endif -#ifdef HAVE_ERRNO_H -#include -#endif -#ifdef HAVE_GETOPT_H -#include -#endif -#ifdef HAVE_UNISTD_H -#include -#endif -#ifdef HAVE_CTYPE_H -#include -#endif -#ifdef HAVE_LOCALE_H -#include -#endif -#ifdef HAVE_STRING_H -#include -#endif -#ifdef HAVE_SIGNAL_H -#include -#endif - -#include "util.h" -#include "compat_defs.h" -#include "loghandler.h" -#include "errhandler.h" -#include "diskchanger.h" -#include "mymutex.h" -#include "bconsole.h" - -DiskChanger changer; - -/*------------------------------------------------- - * Commands - * ------------------------------------------------*/ -#define NUM_AUTOCHANGER_COMMANDS 9 -static char autochanger_command[NUM_AUTOCHANGER_COMMANDS][32] = { "list", "slots", "load", - "unload", "loaded", "listall", "listmags", "createvols", "refresh" }; -#define CMD_LIST 0 -#define CMD_SLOTS 1 -#define CMD_LOAD 2 -#define CMD_UNLOAD 3 -#define CMD_LOADED 4 -#define CMD_LISTALL 5 -#define CMD_LISTMAGS 6 -#define CMD_CREATEVOLS 7 -#define CMD_REFRESH 8 - -/*------------------------------------------------- - * Command line parameters - * ------------------------------------------------*/ -typedef struct _cmdparams_s -{ - bool print_version; - bool print_help; - bool force; - int command; - int slot; - int drive; - int mag_bay; - int count; - tString label_prefix; - tString pool; - tString runas_user; - tString runas_group; - tString config_file; - tString archive_device; -} CMDPARAMS; -CMDPARAMS cmdl; - -/*------------------------------------------------- - * Function to print version info to stdout - *------------------------------------------------*/ -static void print_version(void) -{ - fprintf(stdout, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); - fprintf(stdout, "\n%s.\n", COPYRIGHT_NOTICE); -} - -/*------------------------------------------------- - * Function to print command help to stdout - *------------------------------------------------*/ -static void print_help(void) -{ - fprintf(stdout, "vchanger version %s\n\n", PACKAGE_VERSION); - fprintf(stdout, "USAGE:\n\n" - " vchanger [options] config_file command slot device drive\n" - " Perform Bacula Autochanger API command for virtual\n" - " changer defined by vchanger configuration file\n" - " 'config_file' using 'slot', 'device', and 'drive'\n" - " vchanger [options] config_file LISTMAGS\n" - " API extension to list info on all defined magazines.\n" - " vchanger [options] config_file CREATEVOLS mag_ndx count [start] [CREATEVOLS options]\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" - " print help\n" - "\nGeneral options:\n" - " -u, --user=uid user to run as (when invoked by root)\n" - " -g, --group=gid group to run as (when invoked by root)\n" - "\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 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); -} - -/*------------------------------------------------- - * Function to parse command line parameters - *------------------------------------------------*/ -#define LONGONLYOPT_VERSION 0 -#define LONGONLYOPT_HELP 1 -#define LONGONLYOPT_POOL 2 -#define LONGONLYOPT_FORCE 3 - -static int parse_cmdline(int argc, char *argv[]) -{ - int c, ndx = 0; - tString tmp; - struct option options[] = { { "version", 0, 0, LONGONLYOPT_VERSION }, - { "help", 0, 0, LONGONLYOPT_HELP }, - { "user", 1, 0, 'u' }, - { "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; - cmdl.mag_bay = 0; - cmdl.count = 0; - cmdl.label_prefix.clear(); - cmdl.pool.clear(); - cmdl.runas_user.clear(); - cmdl.runas_group.clear(); - cmdl.config_file.clear(); - cmdl.archive_device.clear(); - /* process the command line */ - for (;;) { - c = getopt_long(argc ,argv, "u:g:l:", options, NULL); - if (c == -1) break; - switch (c) { - case LONGONLYOPT_VERSION: - cmdl.print_version = true; - cmdl.print_help = false; - return 0; - case LONGONLYOPT_HELP: - cmdl.print_version = false; - cmdl.print_help = true; - return 0; - case 'u': - cmdl.runas_user = optarg; - break; - case 'g': - cmdl.runas_group = optarg; - break; - case 'l': - cmdl.label_prefix = optarg; - break; - case LONGONLYOPT_POOL: - cmdl.pool = optarg; - break; - case LONGONLYOPT_FORCE: - cmdl.force = true; - break; - default: - fprintf(stderr, "unknown option %s\n", optarg); - return -1; - } - } - - /* process positional params */ - ndx = optind; - /* First parameter is the vchanger config file path */ - if (ndx >= argc) { - fprintf(stderr, "missing parameter 1 (config_file)\n"); - return -1; - } - cmdl.config_file = argv[ndx]; - /* Second parameter is the command */ - ++ndx; - if (ndx >= argc) { - fprintf(stderr, "missing parameter 2 (command)\n"); - return -1; - } - tmp = argv[ndx]; - tToLower(tStrip(tmp)); - for (cmdl.command = 0; cmdl.command < NUM_AUTOCHANGER_COMMANDS; cmdl.command++) { - if (tmp == autochanger_command[cmdl.command]) break; - } - if (cmdl.command >= NUM_AUTOCHANGER_COMMANDS) { - fprintf(stderr, "'%s' is not a recognized command", argv[ndx]); - return -1; - } - /* Make sure only CREATEVOLS command has -l flag */ - if (!cmdl.label_prefix.empty() && cmdl.command != CMD_CREATEVOLS) { - fprintf(stderr, "flag -l not valid for this command\n"); - return -1; - } - /* Make sure only CREATEVOLS command has --pool flag */ - if (!cmdl.pool.empty() && cmdl.command != CMD_CREATEVOLS) { - 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) { - /* Only 2 parameters given */ - switch (cmdl.command) { - case CMD_LIST: - case CMD_LISTALL: - case CMD_SLOTS: - case CMD_LISTMAGS: - case CMD_REFRESH: - return 0; /* OK, because these commands only need 2 parameters */ - case CMD_CREATEVOLS: - fprintf(stderr, "missing parameter 3 (magazine index)\n"); - break; - default: - fprintf(stderr, "missing parameter 3 (slot number)\n"); - break; - } - return -1; - } - /* Process parameter 3 */ - switch (cmdl.command) { - case CMD_LIST: - case CMD_LISTALL: - case CMD_SLOTS: - case CMD_LISTMAGS: - case CMD_REFRESH: - return 0; /* These commands only need 2 params, so ignore extraneous */ - case CMD_CREATEVOLS: - /* Param 3 for CREATEVOLS command is magazine index */ - cmdl.mag_bay = (int)strtol(argv[ndx], NULL, 10); - if (cmdl.mag_bay < 0) { - fprintf(stderr, "invalid magazine index in parameter 3\n"); - return -1; - } - break; - case CMD_LOADED: - /* slot is ignored for LOADED command, so just set to 1 */ - cmdl.slot = 1; - break; - default: - /* Param 3 for all other commands is the slot number */ - cmdl.slot = (int)strtol(argv[ndx], NULL, 10); - if (cmdl.slot < 1) { - fprintf(stderr, "invalid slot number in parameter 3\n"); - return -1; - } - break; - } - /* Check param 4 exists */ - ++ndx; - if (ndx >= argc) { - /* Only 3 parameters given */ - switch (cmdl.command) { - case CMD_CREATEVOLS: - fprintf(stderr, "missing parameter 4 (count)\n"); - break; - default: - fprintf(stderr, "missing parameter 4 (archive device)\n"); - break; - } - return -1; - } - /* Process param 4 */ - switch (cmdl.command) { - case CMD_CREATEVOLS: - /* Param 4 for CREATEVOLS command is volume count */ - cmdl.count = (int)strtol(argv[ndx], NULL, 10); - if (cmdl.count <= 0 ) { - fprintf(stderr, "invalid count in parameter 4\n"); - return -1; - } - break; - default: - /* Param 4 for all other commands is the archive device path */ - cmdl.archive_device = argv[ndx]; - break; - } - /* Check param 5 exists */ - ++ndx; - if (ndx >= argc) { - /* Only 4 parameters given */ - switch (cmdl.command) { - case CMD_CREATEVOLS: - cmdl.slot = -1; - return 0; /* OK, because parameter 5 optional */ - default: - fprintf(stderr, "missing parameter 5 (drive index)\n"); - break; - } - return -1; - } - switch (cmdl.command) { - case CMD_CREATEVOLS: - cmdl.slot = (int)strtol(argv[ndx], NULL, 10); - if (cmdl.slot < 0) cmdl.slot = -1; - break; - default: - /* Param 5 for all other commands is drive index number */ - if (!isdigit(argv[ndx][0])) { - fprintf(stderr, "invalid drive index in parameter 5\n"); - return -1; - } - cmdl.drive = (int)strtol(argv[ndx], NULL, 10); - if (cmdl.drive < 0) { - fprintf(stderr, "invalid drive index in parameter 5\n"); - return -1; - } - break; - } - - /* note that any extraneous parameters are simply ignored */ - return 0; -} - - -/*------------------------------------------------- - * LIST Command - * Prints a line on stdout for each autochanger slot that contains a - * volume file, even if that volume is currently loaded in a drive. - * Output is of the form: - * s:barcode - * where 's' is the one-based virtual slot number and 'barcode' is the barcode - * label of the volume in the slot. The volume in the slot is a file on one - * of the changer's magazines. A magazine is a directory, which is usually the - * mountpoint of a filesystem partition. The changer has one or more - * magazines, each of which may or may not be attached. Each volume file on - * each magazine is mapped to a virtual slot. The barcode is the volume filename. - *------------------------------------------------*/ -static int do_list_cmd() -{ - int slot, num_slots = changer.NumSlots(); - - /* Print all slot numbers, adding volume labels for non-empty slots */ - for (slot = 1; slot <= num_slots; slot++) { - if (changer.SlotEmpty(slot)) { - fprintf(stdout, "%d:\n", slot); - } else { - fprintf(stdout, "%d:%s\n", slot, changer.GetVolumeLabel(slot)); - } - } - vlog.Info(" SUCCESS sent list to stdout"); - return 0; -} - - -/*------------------------------------------------- - * SLOTS Command - * Prints the number of virtual slots the changer has - *------------------------------------------------*/ -static int do_slots_cmd() -{ - fprintf(stdout, "%d\n", changer.NumSlots()); - vlog.Info(" SUCCESS reporting %d slots", changer.NumSlots()); - return 0; -} - - -/*------------------------------------------------- - * LOAD Command - * Loads the volume file mapped to a virtual slot into a virtual drive - *------------------------------------------------*/ -static int do_load_cmd() -{ - if (changer.LoadDrive(cmdl.drive, cmdl.slot)) { - fprintf(stderr, "%s\n", changer.GetErrorMsg()); - vlog.Error(" ERROR loading slot %d into drive %d", cmdl.slot, cmdl.drive); - return 1; - } - vlog.Info(" SUCCESS loading slot %d into drive %d", cmdl.slot, cmdl.drive); - return 0; -} - - -/*------------------------------------------------- - * UNLOAD Command - * Unloads the volume in a virtual drive - *------------------------------------------------*/ -static int do_unload_cmd() -{ - if (changer.UnloadDrive(cmdl.drive)) { - fprintf(stderr, "%s\n", changer.GetErrorMsg()); - vlog.Error(" ERROR unloading slot %d from drive %d", cmdl.slot, cmdl.drive); - return 1; - } - vlog.Info(" SUCCESS unloading slot %d from drive %d", cmdl.slot, cmdl.drive); - return 0; -} - - -/*------------------------------------------------- - * LOADED Command - * Prints the virtual slot number of the volume file currently loaded - * into a virtual drive, or zero if the drive is unloaded. - *------------------------------------------------*/ -static int do_loaded_cmd() -{ - int slot = changer.GetDriveSlot(cmdl.drive); - if (slot < 0) slot = 0; - fprintf(stdout, "%d\n", 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 - * of virtual slots (full or empty). - *------------------------------------------------*/ -static int do_list_all() -{ - int n, s, num_slots = changer.NumSlots(); - - /* Print drive state info */ - for (n = 0; n < changer.NumDrives(); n++) { - if (changer.DriveEmpty(n)) { - fprintf(stdout, "D:%d:E\n", n); - } else { - s = changer.GetDriveSlot(n); - fprintf(stdout, "D:%d:F:%d:%s\n", n, s, - changer.GetVolumeLabel(s)); - } - } - /* Print slot state info */ - for (n = 1; n <= num_slots; n++) { - if (changer.SlotEmpty(n)) { - fprintf(stdout, "S:%d:E\n", n); - } else { - if (changer.GetSlotDrive(n) < 0) - fprintf(stdout, "S:%d:F:%s\n", n, changer.GetVolumeLabel(n)); - else - fprintf(stdout, "S:%d:E\n", n); - } - } - vlog.Info(" SUCCESS sent listall to stdout"); - return 0; -} - - -/*------------------------------------------------- - * LISTMAGS (List Magazines) Command - * Prints a listing of all magazine bays and info on the magazine - * (if any) each bay contains. - *------------------------------------------------*/ -static int do_list_magazines() -{ - int n; - - if (changer.NumMagazines() == 0) { - fprintf(stdout, "No magazines are defined\n"); - vlog.Info(" SUCCESS no magazines are defined"); - return 0; - } - for (n = 0; n < changer.NumMagazines(); n++) { - if (changer.MagazineEmpty(n)) { - fprintf(stdout, "%d:::\n", n); - } else { - fprintf(stdout, "%d:%d:%d:%s\n", n, changer.GetMagazineSlots(n), - changer.GetMagazineStartSlot(n), changer.GetMagazineMountpoint(n)); - } - } - vlog.Info(" SUCCESS listing magazine info to stdout"); - return 0; -} - - -/*------------------------------------------------- - * CREATEVOLS (Create Volumes) Command - * Creates volume files on the specified magazine - *------------------------------------------------*/ -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()); - vlog.Error(" ERROR: %s", changer.GetErrorMsg()); - return -1; - } - fprintf(stdout, "Created %d volume files on magazine %d\n", - cmdl.count, cmdl.mag_bay); - vlog.Info(" SUCCESS"); - return 0; -} - - - -/* ------------- Main -------------------------*/ - -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 */ - vlog.OpenLog(stderr, LOG_ERR); - /* parse the command line */ - if ((error_code = parse_cmdline(argc, argv)) != 0) { - print_help(); - return 1; - } - /* Check for --version flag */ - if (cmdl.print_version) { - print_version(); - return 0; - } - /* Check for --help flag */ - if (cmdl.print_help) { - print_help(); - return 0; - } - - /* Read vchanger config file */ - if (!conf.Read(cmdl.config_file)) { - return 1; - } - /* User:group from cmdline overrides config file values */ - if (cmdl.runas_user.size()) conf.user = cmdl.runas_user; - if (cmdl.runas_group.size()) conf.group = cmdl.runas_group; - /* Pool from cmdline overrides config file */ - if (!cmdl.pool.empty()) conf.def_pool = cmdl.pool; - /* If root, try to run as configured user:group */ - rc = drop_privs(conf.user.c_str(), conf.group.c_str()); - if (rc) { - fprintf(stderr, "Error %d attempting to run as user '%s'", rc, conf.user.c_str()); - return 1; - } - /* Start logging to log file specified in configuration file */ - if (!conf.logfile.empty()) { - fs = fopen(conf.logfile.c_str(), "a"); - if (fs == NULL) { - fprintf(stderr, "Error opening opening log file\n"); - return 1; - } - 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 - - /* 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: - vlog.Debug("==== performing LIST command"); - error_code = do_list_cmd(); - break; - case CMD_SLOTS: - vlog.Debug("==== performing SLOTS command"); - error_code = do_slots_cmd(); - break; - case CMD_LOAD: - vlog.Debug("==== performing LOAD command"); - error_code = do_load_cmd(); - break; - case CMD_UNLOAD: - vlog.Debug("==== performing UNLOAD command"); - error_code = do_unload_cmd(); - break; - case CMD_LOADED: - vlog.Debug("==== performing LOADED command"); - error_code = do_loaded_cmd(); - break; - case CMD_LISTALL: - vlog.Debug("==== performing LISTALL command"); - error_code = do_list_all(); - break; - case CMD_LISTMAGS: - vlog.Debug("==== performing LISTMAGS command"); - error_code = do_list_magazines(); - break; - case CMD_CREATEVOLS: - vlog.Debug("==== performing CREATEVOLS command"); - error_code = do_create_vols(); - break; - case CMD_REFRESH: - vlog.Debug("==== performing REFRESH command"); - error_code = 0; - break; - } - - /* If there was an error, then exit */ - 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()) - vlog.Error("WARNING! 'update slots' needed in bconsole pid=%d", getpid()); - if (changer.NeedsLabel()) - vlog.Error("WARNING! 'label barcodes' needed in bconsole pid=%d", getpid()); - mymutex_destroy("vchanger-command", command_mux); - return 0; - } - - /* Update Bacula via bconsole */ - - /* 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; -} +/* vchanger.cpp + * + * This file is part of the vchanger package + * + * vchanger copyright (C) 2008-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" +#ifdef HAVE_STDIO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STDINT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_GETOPT_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_CTYPE_H +#include +#endif +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_SIGNAL_H +#include +#endif + +#include "util.h" +#include "compat_defs.h" +#include "loghandler.h" +#include "errhandler.h" +#include "diskchanger.h" +#include "mymutex.h" +#include "bconsole.h" + +DiskChanger changer; + +/*------------------------------------------------- + * Commands + * ------------------------------------------------*/ +#define NUM_AUTOCHANGER_COMMANDS 9 +static char autochanger_command[NUM_AUTOCHANGER_COMMANDS][32] = { "list", "slots", "load", + "unload", "loaded", "listall", "listmags", "createvols", "refresh" }; +#define CMD_LIST 0 +#define CMD_SLOTS 1 +#define CMD_LOAD 2 +#define CMD_UNLOAD 3 +#define CMD_LOADED 4 +#define CMD_LISTALL 5 +#define CMD_LISTMAGS 6 +#define CMD_CREATEVOLS 7 +#define CMD_REFRESH 8 + +/*------------------------------------------------- + * Command line parameters + * ------------------------------------------------*/ +typedef struct _cmdparams_s +{ + bool print_version; + bool print_help; + bool force; + int command; + int slot; + int drive; + int mag_bay; + int count; + tString label_prefix; + tString pool; + tString runas_user; + tString runas_group; + tString config_file; + tString archive_device; +} CMDPARAMS; +CMDPARAMS cmdl; + +/*------------------------------------------------- + * Function to print version info to stdout + *------------------------------------------------*/ +static void print_version(void) +{ + fprintf(stdout, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION); + fprintf(stdout, "\n%s.\n", COPYRIGHT_NOTICE); +} + +/*------------------------------------------------- + * Function to print command help to stdout + *------------------------------------------------*/ +static void print_help(void) +{ + fprintf(stdout, "vchanger version %s\n\n", PACKAGE_VERSION); + fprintf(stdout, "USAGE:\n\n" + " vchanger [options] config_file command slot device drive\n" + " Perform Bacula Autochanger API command for virtual\n" + " changer defined by vchanger configuration file\n" + " 'config_file' using 'slot', 'device', and 'drive'\n" + " vchanger [options] config_file LISTMAGS\n" + " API extension to list info on all defined magazines.\n" + " vchanger [options] config_file CREATEVOLS mag_ndx count [start] [CREATEVOLS options]\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" + " print help\n" + "\nGeneral options:\n" + " -u, --user=uid user to run as (when invoked by root)\n" + " -g, --group=gid group to run as (when invoked by root)\n" + "\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 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); +} + +/*------------------------------------------------- + * Function to parse command line parameters + *------------------------------------------------*/ +#define LONGONLYOPT_VERSION 0 +#define LONGONLYOPT_HELP 1 +#define LONGONLYOPT_POOL 2 +#define LONGONLYOPT_FORCE 3 + +static int parse_cmdline(int argc, char *argv[]) +{ + int c, ndx = 0; + tString tmp; + struct option options[] = { { "version", 0, 0, LONGONLYOPT_VERSION }, + { "help", 0, 0, LONGONLYOPT_HELP }, + { "user", 1, 0, 'u' }, + { "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; + cmdl.mag_bay = 0; + cmdl.count = 0; + cmdl.label_prefix.clear(); + cmdl.pool.clear(); + cmdl.runas_user.clear(); + cmdl.runas_group.clear(); + cmdl.config_file.clear(); + cmdl.archive_device.clear(); + /* process the command line */ + for (;;) { + c = getopt_long(argc ,argv, "u:g:l:", options, NULL); + if (c == -1) break; + switch (c) { + case LONGONLYOPT_VERSION: + cmdl.print_version = true; + cmdl.print_help = false; + return 0; + case LONGONLYOPT_HELP: + cmdl.print_version = false; + cmdl.print_help = true; + return 0; + case 'u': + cmdl.runas_user = optarg; + break; + case 'g': + cmdl.runas_group = optarg; + break; + case 'l': + cmdl.label_prefix = optarg; + break; + case LONGONLYOPT_POOL: + cmdl.pool = optarg; + break; + case LONGONLYOPT_FORCE: + cmdl.force = true; + break; + default: + fprintf(stderr, "unknown option %s\n", optarg); + return -1; + } + } + + /* process positional params */ + ndx = optind; + /* First parameter is the vchanger config file path */ + if (ndx >= argc) { + fprintf(stderr, "missing parameter 1 (config_file)\n"); + return -1; + } + cmdl.config_file = argv[ndx]; + /* Second parameter is the command */ + ++ndx; + if (ndx >= argc) { + fprintf(stderr, "missing parameter 2 (command)\n"); + return -1; + } + tmp = argv[ndx]; + tToLower(tStrip(tmp)); + for (cmdl.command = 0; cmdl.command < NUM_AUTOCHANGER_COMMANDS; cmdl.command++) { + if (tmp == autochanger_command[cmdl.command]) break; + } + if (cmdl.command >= NUM_AUTOCHANGER_COMMANDS) { + fprintf(stderr, "'%s' is not a recognized command", argv[ndx]); + return -1; + } + /* Make sure only CREATEVOLS command has -l flag */ + if (!cmdl.label_prefix.empty() && cmdl.command != CMD_CREATEVOLS) { + fprintf(stderr, "flag -l not valid for this command\n"); + return -1; + } + /* Make sure only CREATEVOLS command has --pool flag */ + if (!cmdl.pool.empty() && cmdl.command != CMD_CREATEVOLS) { + 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) { + /* Only 2 parameters given */ + switch (cmdl.command) { + case CMD_LIST: + case CMD_LISTALL: + case CMD_SLOTS: + case CMD_LISTMAGS: + case CMD_REFRESH: + return 0; /* OK, because these commands only need 2 parameters */ + case CMD_CREATEVOLS: + fprintf(stderr, "missing parameter 3 (magazine index)\n"); + break; + default: + fprintf(stderr, "missing parameter 3 (slot number)\n"); + break; + } + return -1; + } + /* Process parameter 3 */ + switch (cmdl.command) { + case CMD_LIST: + case CMD_LISTALL: + case CMD_SLOTS: + case CMD_LISTMAGS: + case CMD_REFRESH: + return 0; /* These commands only need 2 params, so ignore extraneous */ + case CMD_CREATEVOLS: + /* Param 3 for CREATEVOLS command is magazine index */ + cmdl.mag_bay = (int)strtol(argv[ndx], NULL, 10); + if (cmdl.mag_bay < 0) { + fprintf(stderr, "invalid magazine index in parameter 3\n"); + return -1; + } + break; + case CMD_LOADED: + /* slot is ignored for LOADED command, so just set to 1 */ + cmdl.slot = 1; + break; + default: + /* Param 3 for all other commands is the slot number */ + cmdl.slot = (int)strtol(argv[ndx], NULL, 10); + if (cmdl.slot < 1) { + fprintf(stderr, "invalid slot number in parameter 3\n"); + return -1; + } + break; + } + /* Check param 4 exists */ + ++ndx; + if (ndx >= argc) { + /* Only 3 parameters given */ + switch (cmdl.command) { + case CMD_CREATEVOLS: + fprintf(stderr, "missing parameter 4 (count)\n"); + break; + default: + fprintf(stderr, "missing parameter 4 (archive device)\n"); + break; + } + return -1; + } + /* Process param 4 */ + switch (cmdl.command) { + case CMD_CREATEVOLS: + /* Param 4 for CREATEVOLS command is volume count */ + cmdl.count = (int)strtol(argv[ndx], NULL, 10); + if (cmdl.count <= 0 ) { + fprintf(stderr, "invalid count in parameter 4\n"); + return -1; + } + break; + default: + /* Param 4 for all other commands is the archive device path */ + cmdl.archive_device = argv[ndx]; + break; + } + /* Check param 5 exists */ + ++ndx; + if (ndx >= argc) { + /* Only 4 parameters given */ + switch (cmdl.command) { + case CMD_CREATEVOLS: + cmdl.slot = -1; + return 0; /* OK, because parameter 5 optional */ + default: + fprintf(stderr, "missing parameter 5 (drive index)\n"); + break; + } + return -1; + } + switch (cmdl.command) { + case CMD_CREATEVOLS: + cmdl.slot = (int)strtol(argv[ndx], NULL, 10); + if (cmdl.slot < 0) cmdl.slot = -1; + break; + default: + /* Param 5 for all other commands is drive index number */ + if (!isdigit(argv[ndx][0])) { + fprintf(stderr, "invalid drive index in parameter 5\n"); + return -1; + } + cmdl.drive = (int)strtol(argv[ndx], NULL, 10); + if (cmdl.drive < 0) { + fprintf(stderr, "invalid drive index in parameter 5\n"); + return -1; + } + break; + } + + /* note that any extraneous parameters are simply ignored */ + return 0; +} + + +/*------------------------------------------------- + * LIST Command + * Prints a line on stdout for each autochanger slot that contains a + * volume file, even if that volume is currently loaded in a drive. + * Output is of the form: + * s:barcode + * where 's' is the one-based virtual slot number and 'barcode' is the barcode + * label of the volume in the slot. The volume in the slot is a file on one + * of the changer's magazines. A magazine is a directory, which is usually the + * mountpoint of a filesystem partition. The changer has one or more + * magazines, each of which may or may not be attached. Each volume file on + * each magazine is mapped to a virtual slot. The barcode is the volume filename. + *------------------------------------------------*/ +static int do_list_cmd() +{ + int slot, num_slots = changer.NumSlots(); + + /* Print all slot numbers, adding volume labels for non-empty slots */ + for (slot = 1; slot <= num_slots; slot++) { + if (changer.SlotEmpty(slot)) { + fprintf(stdout, "%d:\n", slot); + } else { + fprintf(stdout, "%d:%s\n", slot, changer.GetVolumeLabel(slot)); + } + } + vlog.Info(" SUCCESS sent list to stdout"); + return 0; +} + + +/*------------------------------------------------- + * SLOTS Command + * Prints the number of virtual slots the changer has + *------------------------------------------------*/ +static int do_slots_cmd() +{ + fprintf(stdout, "%d\n", changer.NumSlots()); + vlog.Info(" SUCCESS reporting %d slots", changer.NumSlots()); + return 0; +} + + +/*------------------------------------------------- + * LOAD Command + * Loads the volume file mapped to a virtual slot into a virtual drive + *------------------------------------------------*/ +static int do_load_cmd() +{ + if (changer.LoadDrive(cmdl.drive, cmdl.slot)) { + fprintf(stderr, "%s\n", changer.GetErrorMsg()); + vlog.Error(" ERROR loading slot %d into drive %d", cmdl.slot, cmdl.drive); + return 1; + } + vlog.Info(" SUCCESS loading slot %d into drive %d", cmdl.slot, cmdl.drive); + return 0; +} + + +/*------------------------------------------------- + * UNLOAD Command + * Unloads the volume in a virtual drive + *------------------------------------------------*/ +static int do_unload_cmd() +{ + if (changer.UnloadDrive(cmdl.drive)) { + fprintf(stderr, "%s\n", changer.GetErrorMsg()); + vlog.Error(" ERROR unloading slot %d from drive %d", cmdl.slot, cmdl.drive); + return 1; + } + vlog.Info(" SUCCESS unloading slot %d from drive %d", cmdl.slot, cmdl.drive); + return 0; +} + + +/*------------------------------------------------- + * LOADED Command + * Prints the virtual slot number of the volume file currently loaded + * into a virtual drive, or zero if the drive is unloaded. + *------------------------------------------------*/ +static int do_loaded_cmd() +{ + int slot = changer.GetDriveSlot(cmdl.drive); + if (slot < 0) slot = 0; + fprintf(stdout, "%d\n", 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 + * of virtual slots (full or empty). + *------------------------------------------------*/ +static int do_list_all() +{ + int n, s, num_slots = changer.NumSlots(); + + /* Print drive state info */ + for (n = 0; n < changer.NumDrives(); n++) { + if (changer.DriveEmpty(n)) { + fprintf(stdout, "D:%d:E\n", n); + } else { + s = changer.GetDriveSlot(n); + fprintf(stdout, "D:%d:F:%d:%s\n", n, s, + changer.GetVolumeLabel(s)); + } + } + /* Print slot state info */ + for (n = 1; n <= num_slots; n++) { + if (changer.SlotEmpty(n)) { + fprintf(stdout, "S:%d:E\n", n); + } else { + if (changer.GetSlotDrive(n) < 0) + fprintf(stdout, "S:%d:F:%s\n", n, changer.GetVolumeLabel(n)); + else + fprintf(stdout, "S:%d:E\n", n); + } + } + vlog.Info(" SUCCESS sent listall to stdout"); + return 0; +} + + +/*------------------------------------------------- + * LISTMAGS (List Magazines) Command + * Prints a listing of all magazine bays and info on the magazine + * (if any) each bay contains. + *------------------------------------------------*/ +static int do_list_magazines() +{ + int n; + + if (changer.NumMagazines() == 0) { + fprintf(stdout, "No magazines are defined\n"); + vlog.Info(" SUCCESS no magazines are defined"); + return 0; + } + for (n = 0; n < changer.NumMagazines(); n++) { + if (changer.MagazineEmpty(n)) { + fprintf(stdout, "%d:::\n", n); + } else { + fprintf(stdout, "%d:%d:%d:%s\n", n, changer.GetMagazineSlots(n), + changer.GetMagazineStartSlot(n), changer.GetMagazineMountpoint(n)); + } + } + vlog.Info(" SUCCESS listing magazine info to stdout"); + return 0; +} + + +/*------------------------------------------------- + * CREATEVOLS (Create Volumes) Command + * Creates volume files on the specified magazine + *------------------------------------------------*/ +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()); + vlog.Error(" ERROR: %s", changer.GetErrorMsg()); + return -1; + } + fprintf(stdout, "Created %d volume files on magazine %d\n", + cmdl.count, cmdl.mag_bay); + vlog.Info(" SUCCESS"); + return 0; +} + + + +/* ------------- Main -------------------------*/ + +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 */ + vlog.OpenLog(stderr, LOG_ERR); + /* parse the command line */ + if ((error_code = parse_cmdline(argc, argv)) != 0) { + print_help(); + return 1; + } + /* Check for --version flag */ + if (cmdl.print_version) { + print_version(); + return 0; + } + /* Check for --help flag */ + if (cmdl.print_help) { + print_help(); + return 0; + } + + /* Read vchanger config file */ + if (!conf.Read(cmdl.config_file)) { + return 1; + } + /* User:group from cmdline overrides config file values */ + if (cmdl.runas_user.size()) conf.user = cmdl.runas_user; + if (cmdl.runas_group.size()) conf.group = cmdl.runas_group; + /* Pool from cmdline overrides config file */ + if (!cmdl.pool.empty()) conf.def_pool = cmdl.pool; + /* If root, try to run as configured user:group */ + rc = drop_privs(conf.user.c_str(), conf.group.c_str()); + if (rc) { + fprintf(stderr, "Error %d attempting to run as user '%s'", rc, conf.user.c_str()); + return 1; + } + /* Start logging to log file specified in configuration file */ + if (!conf.logfile.empty()) { + fs = fopen(conf.logfile.c_str(), "a"); + if (fs == NULL) { + fprintf(stderr, "Error opening opening log file\n"); + return 1; + } + 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 + + /* 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: + vlog.Debug("==== performing LIST command"); + error_code = do_list_cmd(); + break; + case CMD_SLOTS: + vlog.Debug("==== performing SLOTS command"); + error_code = do_slots_cmd(); + break; + case CMD_LOAD: + vlog.Debug("==== performing LOAD command"); + error_code = do_load_cmd(); + break; + case CMD_UNLOAD: + vlog.Debug("==== performing UNLOAD command"); + error_code = do_unload_cmd(); + break; + case CMD_LOADED: + vlog.Debug("==== performing LOADED command"); + error_code = do_loaded_cmd(); + break; + case CMD_LISTALL: + vlog.Debug("==== performing LISTALL command"); + error_code = do_list_all(); + break; + case CMD_LISTMAGS: + vlog.Debug("==== performing LISTMAGS command"); + error_code = do_list_magazines(); + break; + case CMD_CREATEVOLS: + vlog.Debug("==== performing CREATEVOLS command"); + error_code = do_create_vols(); + break; + case CMD_REFRESH: + vlog.Debug("==== performing REFRESH command"); + error_code = 0; + break; + } + + /* If there was an error, then exit */ + 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()) + vlog.Error("WARNING! 'update slots' needed in bconsole pid=%d", getpid()); + if (changer.NeedsLabel()) + vlog.Error("WARNING! 'label barcodes' needed in bconsole pid=%d", getpid()); + mymutex_destroy("vchanger-command", command_mux); + return 0; + } + + /* Update Bacula via bconsole */ + + /* 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; +}