diff --git a/autoconf/configure.in b/autoconf/configure.in index 0c198f7ae8e..e460084cf2b 100644 --- a/autoconf/configure.in +++ b/autoconf/configure.in @@ -3385,6 +3385,67 @@ if test x$support_scsi_crypto = xyes; then fi AC_SUBST(CAM_LIBS) +dnl +dnl Check for GlusterFS via gfapi. +dnl +GLUSTER_LIBS="-lgfapi" +GLUSTER_INC="" +have_glusterfs=no +AC_ARG_WITH(glusterfs, + AC_HELP_STRING([--with-glusterfs@<:@=DIR@:>@], [Directory holding GLUSTERFS includes/libs]), + with_glusterfs_directory=$withval +) + +if test "x${with_glusterfs_directory}" != "xyes" && test x"${with_glusterfs_directory}" != "x"; then + # + # Make sure the $with_glusterfs_directory also makes sense + # + if test -d "${with_glusterfs_directory}/lib" -a -d "${with_glusterfs_directory}/include"; then + GLUSTER_LIBS="-L${with_glusterfs_directory}/lib ${GLUSTER_LIBS}" + GLUSTER_INC="-I${with_glusterfs_directory}/include ${GLUSTER_INC}" + fi +else + GLUSTER_INC="-I/usr/include/glusterfs" +fi + +saved_LIBS="${LIBS}" +saved_CFLAGS="${CFLAGS}" +saved_CPPFLAGS="${CPPFLAGS}" +LIBS="${saved_LIBS} ${GLUSTER_LIBS}" +CFLAGS="${saved_CFLAGS} ${GLUSTER_INC}" +CPPFLAGS="${saved_CPPFLAGS} ${GLUSTER_INC}" + +AC_CHECK_HEADER(api/glfs.h) + +AC_MSG_CHECKING(for glfs_init in gfapi library) +AC_TRY_LINK( + [ + #include + ], [ + glfs_new("volumename"); + ], [ + AC_MSG_RESULT(yes) + have_glusterfs="yes" + ], [ + AC_MSG_RESULT(no) + have_glusterfs="no" + ] +) + +LIBS="${saved_LIBS}" +CFLAGS="${saved_CFLAGS}" +CPPFLAGS="${saved_CPPFLAGS}" + +if test "x${have_glusterfs}" = "xyes"; then + AC_DEFINE(HAVE_GFAPI, 1, [Define to 1 if you have gfapi lib]) +else + GLUSTER_LIBS="" + GLUSTER_INC="" +fi + +AC_SUBST(GLUSTER_INC) +AC_SUBST(GLUSTER_LIBS) + dnl dnl Check for pthread libraries dnl @@ -3980,6 +4041,7 @@ Configuration on `date`: ACL support: ${have_acl} XATTR support: ${have_xattr} SCSI Crypto support: ${have_scsi_crypto} + GLUSTERFS support: ${have_glusterfs} Python support: ${support_python} ${PYTHON_LIBS} systemd support: ${support_systemd} ${SYSTEMD_UNITDIR} Batch insert enabled: ${batch_insert_db_backends} diff --git a/src/stored/Makefile.in b/src/stored/Makefile.in index 183297e3c4d..0b2abcffb18 100644 --- a/src/stored/Makefile.in +++ b/src/stored/Makefile.in @@ -27,7 +27,8 @@ GNUTLS_LIBS_NONSHARED = @GNUTLS_LIBS_NONSHARED@ first_rule: all dummy: -DEVICE_API_SRCS = unix_file_device.c \ +DEVICE_API_SRCS = gfapi_device.c \ + unix_file_device.c \ unix_tape_device.c \ vtape.c @@ -80,6 +81,8 @@ BEXTRACT_LIBS += @ZLIB_LIBS_NONSHARED@ BEXTRACT_LIBS += @LZO_LIBS_NONSHARED@ BEXTRACT_LIBS += @FASTLZ_LIBS_NONSHARED@ +GLUSTER_INC = @GLUSTER_INC@ +LIBS += @GLUSTER_LIBS@ INCLUDES += -I$(srcdir) -I$(basedir) -I$(basedir)/include DEBUG = @DEBUG@ @@ -99,6 +102,14 @@ NDMP_LIBS = @NDMP_LIBS@ $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< #------------------------------------------------------------------------- +dev.o: dev.c + @echo "Compiling $<" + $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(GLUSTER_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< + +gfapi_device.o: gfapi_device.c + @echo "Compiling $<" + $(NO_ECHO)$(CXX) $(DEFS) $(DEBUG) $(GLUSTER_INC) -c $(WCFLAGS) $(CPPFLAGS) $(INCLUDES) $(DINCLUDE) $(CXXFLAGS) $< + all: Makefile bareos-sd @STATIC_SD@ bls bextract bscan btape bcopy @echo "===== Make of stored is good ====" @echo " " diff --git a/src/stored/backends/gfapi_device.c b/src/stored/backends/gfapi_device.c new file mode 100644 index 00000000000..fba2af3fcc2 --- /dev/null +++ b/src/stored/backends/gfapi_device.c @@ -0,0 +1,565 @@ +/* + BAREOSĀ® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2014-2014 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + 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 Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +/* + * Gluster Filesystem API device abstraction. + * + * Marco van Wieringen, February 2014 + */ + +#include "bareos.h" + +#ifdef HAVE_GFAPI +#include "stored.h" +#include "backends/gfapi_device.h" + +/* + * Parse a gluster definition into something we can use for setting + * up the right connection to a gluster management server and get access + * to a gluster volume. + * + * Syntax: + * + * gluster[+transport]://[server[:port]]/volname[/dir][?socket=...] + * + * 'gluster' is the protocol. + * + * 'transport' specifies the transport type used to connect to gluster + * management daemon (glusterd). Valid transport types are tcp, unix + * and rdma. If a transport type isn't specified, then tcp type is assumed. + * + * 'server' specifies the server where the volume file specification for + * the given volume resides. This can be either hostname, ipv4 address + * or ipv6 address. ipv6 address needs to be within square brackets [ ]. + * If transport type is 'unix', then 'server' field should not be specifed. + * The 'socket' field needs to be populated with the path to unix domain + * socket. + * + * 'port' is the port number on which glusterd is listening. This is optional + * and if not specified, QEMU will send 0 which will make gluster to use the + * default port. If the transport type is unix, then 'port' should not be + * specified. + * + * 'volname' is the name of the gluster volume which contains the backup images. + * + * 'dir' is an optional directory on the 'volname' + * + * Examples: + * + * gluster://1.2.3.4/testvol[/dir] + * gluster+tcp://1.2.3.4/testvol[/dir] + * gluster+tcp://1.2.3.4:24007/testvol[/dir] + * gluster+tcp://[1:2:3:4:5:6:7:8]/testvol[/dir] + * gluster+tcp://[1:2:3:4:5:6:7:8]:24007/testvol[/dir] + * gluster+tcp://server.domain.com:24007/testvol[/dir] + * gluster+unix:///testvol[/dir]?socket=/tmp/glusterd.socket + * gluster+rdma://1.2.3.4:24007/testvol[/dir] + */ +static inline bool parse_gfapi_devicename(char *devicename, + char **transport, + char **servername, + char **volumename, + char **dir, + int *serverport) +{ + char *bp; + + /* + * Make sure its a URI that starts with gluster. + */ + if (!bstrncasecmp(devicename, "gluster", 7)) { + return false; + } + + /* + * Parse any explicit protocol. + */ + bp = strchr(devicename, '+'); + if (bp) { + *transport = ++bp; + bp = strchr(bp, ':'); + if (bp) { + *bp++ = '\0'; + } else { + goto bail_out; + } + } else { + *transport = NULL; + bp = strchr(devicename, ':'); + if (!bp) { + goto bail_out; + } + } + + /* + * When protocol is not UNIX parse servername and portnr. + */ + if (!*transport || !bstrcasecmp(*transport, "unix")) { + /* + * Parse servername of gluster management server. + */ + bp = strchr(bp, '/'); + + /* + * Validate URI. + */ + if (!bp || !*bp == '/') { + goto bail_out; + } + + /* + * Skip the two // + */ + *bp++ = '\0'; + bp++; + *servername = bp; + + /* + * Parse any explicit server portnr. + * We search reverse in the string for a : what indicates + * a port specification but in that string there may not contain a ']' + * because then we searching in a IPv6 string. + */ + bp = strrchr(bp, ':'); + if (bp && !strchr(bp, ']')) { + char *port; + + *bp++ = '\0'; + port = bp; + bp = strchr(bp, '/'); + if (!bp) { + goto bail_out; + } + *bp++ = '\0'; + *serverport = str_to_int64(port); + *volumename = bp; + + /* + * See if there is a dir specified. + */ + bp = strchr(bp, '/'); + if (bp) { + *bp++ = '\0'; + *dir = bp; + } + } else { + *serverport = 0; + bp = *servername; + + /* + * Parse the volume name. + */ + bp = strchr(bp, '/'); + if (!bp) { + goto bail_out; + } + *bp++ = '\0'; + *volumename = bp; + + /* + * See if there is a dir specified. + */ + bp = strchr(bp, '/'); + if (bp) { + *bp++ = '\0'; + *dir = bp; + } + } + } else { + /* + * For UNIX serverport is zero. + */ + *serverport = 0; + + /* + * Validate URI. + */ + if (*bp != '/' || *(bp + 1) != '/') { + goto bail_out; + } + + /* + * Skip the two // + */ + *bp++ = '\0'; + bp++; + + /* + * For UNIX URIs the server part of the URI needs to be empty. + */ + if (*bp++ != '/') { + goto bail_out; + } + *volumename = bp; + + /* + * See if there is a dir specified. + */ + bp = strchr(bp, '/'); + if (bp) { + *bp++ = '\0'; + *dir = bp; + } + + /* + * Parse any socket parameters. + */ + bp = strchr(bp, '?'); + if (bp) { + if (bstrncasecmp(bp + 1, "socket=", 7)) { + *bp = '\0'; + *servername = bp + 8; + } + } + } + + return true; + +bail_out: + return false; +} + +/* + * Create a parent directory using the gfapi. + */ +static inline bool gfapi_makedir(glfs_t *glfs, const char *directory) +{ + char *bp; + struct stat st; + bool retval = false; + POOL_MEM new_directory(PM_FNAME); + + pm_strcpy(new_directory, directory); + + /* + * See if the parent exists. + */ + bp = strrchr(new_directory.c_str(), '/'); + if (bp) { + /* + * See if we reached the root. + */ + if (bp == new_directory.c_str()) { + /* + * Create the directory. + */ + if (glfs_mkdir(glfs, directory, 0750) == 0) { + retval = true; + } + } else { + *bp = '\0'; + + if (glfs_stat(glfs, new_directory.c_str(), &st) != 0) { + switch (errno) { + case ENOENT: + /* + * Make sure our parent exists. + */ + retval = gfapi_makedir(glfs, new_directory.c_str()); + if (!retval) { + return false; + } + + /* + * Create the directory. + */ + if (glfs_mkdir(glfs, directory, 0750) == 0) { + retval = true; + } + break; + default: + break; + } + } else { + retval = true; + } + } + } + + return retval; +} + +/* + * Open a volume using gfapi. + */ +int gfapi_device::d_open(const char *pathname, int flags, int mode) +{ + int status; + POOL_MEM virtual_filename(PM_FNAME); + + /* + * Parse the gluster URI. + */ + if (!m_gfapi_volume) { + m_gfapi_volume = bstrdup(dev_name); + if (!parse_gfapi_devicename(m_gfapi_volume, + &m_transport, + &m_servername, + &m_volumename, + &m_basedir, + &m_serverport)) { + Mmsg1(errmsg, _("Unable to parse device URI %s.\n"), dev_name); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + } + + /* + * See if we need to setup a Gluster context. + */ + if (!m_glfs) { + m_glfs = glfs_new(m_volumename); + if (!m_glfs) { + Mmsg1(errmsg, _("Unable to create new Gluster context for volumename %s.\n"), m_volumename); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + + status = glfs_set_volfile_server(m_glfs, (m_transport) ? m_transport : "tcp", m_servername, m_serverport); + if (status < 0) { + Mmsg3(errmsg, _("Unable to initialize Gluster management server for transport %s, servername %s, serverport %d\n"), + (m_transport) ? m_transport : "tcp", m_servername, m_serverport); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + + status = glfs_init(m_glfs); + if (status < 0) { + Mmsg1(errmsg, _("Unable to initialize Gluster for volumename %s.\n"), m_volumename); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + } + + /* + * See if we don't have a file open already. + */ + if (m_gfd) { + glfs_close(m_gfd); + m_gfd = NULL; + } + + /* + * See if we store in an explicit directory. + */ + if (m_basedir) { + struct stat st; + + /* + * Make sure the dir exists if one is defined. + */ + Mmsg(virtual_filename, "/%s", m_basedir); + if (glfs_stat(m_glfs, virtual_filename.c_str(), &st) != 0) { + switch (errno) { + case ENOENT: + if (!gfapi_makedir(m_glfs, virtual_filename.c_str())) { + Mmsg1(errmsg, _("Specified glusterfs direcory %s cannot be created.\n"), virtual_filename.c_str()); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + break; + default: + goto bail_out; + } + } else { + if (!S_ISDIR(st.st_mode)) { + Mmsg1(errmsg, _("Specified glusterfs direcory %s is not a directory.\n"), virtual_filename.c_str()); + Emsg0(M_FATAL, 0, errmsg); + goto bail_out; + } + } + + Mmsg(virtual_filename, "/%s/%s", m_basedir, getVolCatName()); + } else { + Mmsg(virtual_filename, "%s", getVolCatName()); + } + + /* + * See if the O_CREAT flag is set as glfs_open doesn't support that flag and you have to call glfs_creat then. + */ + if (flags & O_CREAT) { + m_gfd = glfs_creat(m_glfs, virtual_filename.c_str(), flags, mode); + } else { + m_gfd = glfs_open(m_glfs, virtual_filename.c_str(), flags); + } + + if (!m_gfd) { + goto bail_out; + } + + return 0; + +bail_out: + /* + * Cleanup the Gluster context. + */ + if (m_glfs) { + glfs_fini(m_glfs); + m_glfs = NULL; + } + + return -1; +} + +/* + * Read data from a volume using gfapi. + */ +ssize_t gfapi_device::d_read(int fd, void *buffer, size_t count) +{ + if (m_gfd) { + return glfs_read(m_gfd, buffer, count, 0); + } else { + errno = EBADF; + return -1; + } +} + +/* + * Write data to a volume using gfapi. + */ +ssize_t gfapi_device::d_write(int fd, const void *buffer, size_t count) +{ + if (m_gfd) { + return glfs_write(m_gfd, buffer, count, 0); + } else { + errno = EBADF; + return -1; + } +} + +int gfapi_device::d_close(int fd) +{ + if (m_gfd) { + int status; + + status = glfs_close(m_gfd); + m_gfd = NULL; + return status; + } else { + errno = EBADF; + return -1; + } +} + +int gfapi_device::d_ioctl(int fd, ioctl_req_t request, char *op) +{ + return -1; +} + +boffset_t gfapi_device::d_lseek(DCR *dcr, boffset_t offset, int whence) +{ + if (m_gfd) { + return glfs_lseek(m_gfd, offset, whence); + } else { + errno = EBADF; + return -1; + } +} + +bool gfapi_device::d_truncate(DCR *dcr) +{ + struct stat st; + + if (m_gfd) { + if (glfs_ftruncate(m_gfd, 0) != 0) { + berrno be; + + Mmsg2(errmsg, _("Unable to truncate device %s. ERR=%s\n"), + print_name(), be.bstrerror()); + Emsg0(M_FATAL, 0, errmsg); + return false; + } + + /* + * Check for a successful glfs_truncate() and issue work-around when truncation doesn't work. + * + * 1. close file + * 2. delete file + * 3. open new file with same mode + * 4. change ownership to original + */ + if (glfs_fstat(m_gfd, &st) != 0) { + berrno be; + + Mmsg2(errmsg, _("Unable to stat device %s. ERR=%s\n"), print_name(), be.bstrerror()); + return false; + } + + if (st.st_size != 0) { /* glfs_truncate() didn't work */ + glfs_close(m_gfd); + glfs_unlink(m_glfs, getVolCatName()); + + set_mode(CREATE_READ_WRITE); + + /* + * Recreate the file -- of course, empty + */ + m_gfd = glfs_creat(m_glfs, getVolCatName(), oflags, st.st_mode); + if (!m_gfd) { + berrno be; + + dev_errno = errno; + Mmsg2(errmsg, _("Could not reopen: %s, ERR=%s\n"), getVolCatName(), be.bstrerror()); + Emsg0(M_FATAL, 0, errmsg); + + return false; + } + + /* + * Reset proper owner + */ + glfs_chown(m_glfs, getVolCatName(), st.st_uid, st.st_gid); + } + } + + return true; +} + +gfapi_device::~gfapi_device() +{ + if (m_gfd) { + glfs_close(m_gfd); + m_gfd = NULL; + } + + if (!m_glfs) { + glfs_fini(m_glfs); + m_glfs = NULL; + } + + if (m_gfapi_volume) { + free(m_gfapi_volume); + m_gfapi_volume = NULL; + } +} + +gfapi_device::gfapi_device() +{ + m_fd = -1; + m_gfapi_volume = NULL; + m_transport = NULL; + m_servername = NULL; + m_volumename = NULL; + m_basedir = NULL; + m_serverport = 0; + m_glfs = NULL; + m_gfd = NULL; +} +#endif diff --git a/src/stored/backends/gfapi_device.h b/src/stored/backends/gfapi_device.h new file mode 100644 index 00000000000..19943538ffa --- /dev/null +++ b/src/stored/backends/gfapi_device.h @@ -0,0 +1,59 @@ +/* + BAREOSĀ® - Backup Archiving REcovery Open Sourced + + Copyright (C) 2014-2014 Bareos GmbH & Co. KG + + This program is Free Software; you can redistribute it and/or + modify it under the terms of version three of the GNU Affero General Public + License as published by the Free Software Foundation, which is + listed in the file LICENSE. + + 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 + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. +*/ +/* + * Gluster Filesystem API device abstraction. + * + * Marco van Wieringen, February 2014 + */ + +#ifndef GFAPI_DEVICE_H +#define GFAPI_DEVICE_H + +#include + +class gfapi_device: public DEVICE { +private: + char *m_gfapi_volume; + char *m_transport; + char *m_servername; + char *m_volumename; + char *m_basedir; + int m_serverport; + glfs_t *m_glfs; + glfs_fd_t *m_gfd; + +public: + gfapi_device(); + ~gfapi_device(); + + /* + * Interface from DEVICE + */ + int d_close(int); + int d_open(const char *pathname, int flags, int mode); + int d_ioctl(int fd, ioctl_req_t request, char *mt = NULL); + boffset_t d_lseek(DCR *dcr, boffset_t offset, int whence); + ssize_t d_read(int fd, void *buffer, size_t count); + ssize_t d_write(int fd, const void *buffer, size_t count); + bool d_truncate(DCR *dcr); +}; + +#endif /* GFAPI_DEVICE_H */ diff --git a/src/stored/dev.c b/src/stored/dev.c index 36fc21f2e72..68be2895e7a 100644 --- a/src/stored/dev.c +++ b/src/stored/dev.c @@ -75,6 +75,9 @@ #ifdef USE_VTAPE #include "backends/vtape.h" #endif +#ifdef HAVE_GFAPI +#include "backends/gfapi_device.h" +#endif #ifdef HAVE_WIN32 #include "backends/win32_tape_device.h" #include "backends/win32_file_device.h" @@ -161,6 +164,11 @@ m_init_dev(JCR *jcr, DEVRES *device, bool new_init) dev = New(vtape); break; #endif +#ifdef HAVE_GFAPI + case B_GFAPI_DEV: + dev = New(gfapi_device); + break; +#endif #ifdef HAVE_WIN32 case B_TAPE_DEV: dev = New(win32_tape_device); @@ -1954,6 +1962,8 @@ bool DEVICE::mount(DCR *dcr, int timeout) retval = do_file_mount(dcr, 1, timeout); } break; + case B_GFAPI_DEV: + break; default: break; } @@ -2021,6 +2031,8 @@ bool DEVICE::unmount(DCR *dcr, int timeout) retval = do_file_mount(dcr, 0, timeout); } break; + case B_GFAPI_DEV: + break; default: break; } diff --git a/src/stored/dev.h b/src/stored/dev.h index a6dc964f687..53bb730573e 100644 --- a/src/stored/dev.h +++ b/src/stored/dev.h @@ -86,7 +86,8 @@ enum { B_TAPE_DEV, B_FIFO_DEV, B_VTAPE_DEV, - B_VTL_DEV + B_VTL_DEV, + B_GFAPI_DEV }; /* IO directions */ @@ -310,7 +311,8 @@ class DEVICE: public SMARTALLOC { int is_removable() const { return capabilities & CAP_REM; } int is_tape() const { return (dev_type == B_TAPE_DEV || dev_type == B_VTAPE_DEV); } - int is_file() const { return (dev_type == B_FILE_DEV); } + int is_file() const { return (dev_type == B_FILE_DEV || + dev_type == B_GFAPI_DEV); } int is_fifo() const { return dev_type == B_FIFO_DEV; } int is_vtl() const { return dev_type == B_VTL_DEV; } int is_vtape() const { return dev_type == B_VTAPE_DEV; } diff --git a/src/stored/stored_conf.c b/src/stored/stored_conf.c index 91436bfcc61..764f239e6c1 100644 --- a/src/stored/stored_conf.c +++ b/src/stored/stored_conf.c @@ -277,6 +277,7 @@ static s_kw dev_types[] = { { "fifo", B_FIFO_DEV }, { "vtl", B_VTL_DEV }, { "vtape", B_VTAPE_DEV }, + { "gfapi", B_GFAPI_DEV }, { NULL, 0 } };