From bfe781cd36fff56b8f54747aabac47776554c34d Mon Sep 17 00:00:00 2001 From: Eric Cano <Eric.Cano@cern.ch> Date: Thu, 11 Feb 2016 18:54:37 +0100 Subject: [PATCH] Created the embryo of the new cta-taped daemon, importing many daemon utilities from castor in the exercise. --- CMakeLists.txt | 10 + common/CMakeLists.txt | 38 +- common/Configuration.cpp | 330 ++++++++++++ common/Configuration.hpp | 270 ++++++++++ common/ProcessCap.cpp | 135 +++++ common/exception/Errnum.cpp | 2 +- common/exception/Errnum.hpp | 2 +- common/exception/Exception.hpp | 210 ++++---- common/exception/Serrnum.cpp | 68 --- common/exception/Serrnum.hpp | 44 -- common/log/CMakeLists.txt | 23 + common/log/DummyLogger.cpp | 95 ++++ common/log/DummyLogger.hpp | 159 ++++++ common/log/LogContext.cpp | 114 ++++ common/log/LogContext.hpp | 164 ++++++ common/log/LogContextTest.cpp | 72 +++ common/log/Logger.cpp | 44 ++ common/log/Logger.hpp | 293 +++++++++++ common/log/Message.cpp | 26 + common/log/Message.hpp | 47 ++ common/log/Param.cpp | 40 ++ common/log/Param.hpp | 108 ++++ common/log/ParamTest.cpp | 59 +++ common/log/StringLogger.cpp | 324 ++++++++++++ common/log/StringLogger.hpp | 385 ++++++++++++++ common/log/StringLoggerTest.cpp | 37 ++ common/log/SyslogLogger.cpp | 493 ++++++++++++++++++ common/log/SyslogLogger.hpp | 474 +++++++++++++++++ common/log/SyslogLoggerTest.cpp | 149 ++++++ common/log/TestingSyslogLogger.hpp | 46 ++ common/processCap/ProcessCap.cpp | 138 +++++ common/processCap/ProcessCap.hpp | 98 ++++ common/processCap/ProcessCapDummy.cpp | 45 ++ common/processCap/ProcessCapDummy.hpp | 73 +++ common/processCap/SmartCap.cpp | 97 ++++ common/processCap/SmartCap.hpp | 119 +++++ common/processCap/SmartCapTest.cpp | 88 ++++ common/threading/Daemon.cpp | 202 +++++++ common/threading/Daemon.hpp | 140 +++++ common/threading/DaemonTest.cpp | 101 ++++ common/threading/MutexLocker.hpp | 64 +++ common/threading/System.cpp | 193 +++++++ common/threading/System.hpp | 64 +++ common/{ => utils}/Utils.cpp | 86 ++- common/{ => utils}/Utils.hpp | 38 ++ common/{ => utils}/UtilsTest.cpp | 134 ++++- common/{ => utils}/strerror_r_wrapper.cpp | 2 +- common/{ => utils}/strerror_r_wrapper.hpp | 0 nameserver/mockNS/MockNameServer.cpp | 2 +- .../mockNS/makeMockNameServerBasePath.cpp | 2 +- objectstore/BackendFactory.cpp | 2 +- objectstore/BackendVFS.cpp | 2 +- remotens/EosNS.cpp | 2 +- remotens/MockRemoteNS.cpp | 2 +- scheduler/Scheduler.cpp | 3 +- scheduler/_old_prototype_DummyScheduler.cpp | 2 +- tapeserver/CMakeLists.txt | 8 + tapeserver/castor/exception/Errnum.cpp | 2 +- tapeserver/castor/io/IoTest.cpp | 2 +- tapeserver/castor/io/io.cpp | 2 +- {common => tapeserver/castor/io}/marshall.h | 0 tapeserver/castor/messages/messages.cpp | 2 +- tapeserver/castor/server/ProcessCap.cpp | 12 +- tapeserver/castor/server/ProcessCap.hpp | 25 +- .../tape/tapeserver/daemon/CMakeLists.txt | 4 +- .../daemon/DataTransferSessionTest.cpp | 2 +- .../tape/tapeserver/daemon/ProcessForker.cpp | 2 +- tapeserver/castor/utils/utils.cpp | 4 +- tapeserver/cta-taped.cpp | 197 +++++++ .../tapeserverd.init => cta-taped.init} | 0 ...r-server.logrotate => cta-taped.logrotate} | 0 .../daemon/tapeserverd.man => cta-taped.man} | 0 ...eserverd.sysconfig => cta-taped.sysconfig} | 0 tapeserver/daemon/CMakeLists.txt | 7 + tapeserver/daemon/DriveConfiguration.hpp | 96 ++++ tapeserver/daemon/GlobalConfiguration.cpp | 36 ++ tapeserver/daemon/GlobalConfiguration.hpp | 47 ++ tapeserver/daemon/TapeDaemon.cpp | 172 ++++++ tapeserver/daemon/TapeDaemon.hpp | 266 ++++++++++ tapeserver/daemon/TpconfigLine.cpp | 33 ++ tapeserver/daemon/TpconfigLine.hpp | 69 +++ tapeserver/daemon/TpconfigLines.cpp | 167 ++++++ tapeserver/daemon/TpconfigLines.hpp | 44 ++ version.hpp.in | 22 + 84 files changed, 6905 insertions(+), 276 deletions(-) create mode 100644 common/Configuration.cpp create mode 100644 common/Configuration.hpp create mode 100644 common/ProcessCap.cpp delete mode 100644 common/exception/Serrnum.cpp delete mode 100644 common/exception/Serrnum.hpp create mode 100644 common/log/CMakeLists.txt create mode 100644 common/log/DummyLogger.cpp create mode 100644 common/log/DummyLogger.hpp create mode 100644 common/log/LogContext.cpp create mode 100644 common/log/LogContext.hpp create mode 100644 common/log/LogContextTest.cpp create mode 100644 common/log/Logger.cpp create mode 100644 common/log/Logger.hpp create mode 100644 common/log/Message.cpp create mode 100644 common/log/Message.hpp create mode 100644 common/log/Param.cpp create mode 100644 common/log/Param.hpp create mode 100644 common/log/ParamTest.cpp create mode 100644 common/log/StringLogger.cpp create mode 100644 common/log/StringLogger.hpp create mode 100644 common/log/StringLoggerTest.cpp create mode 100644 common/log/SyslogLogger.cpp create mode 100644 common/log/SyslogLogger.hpp create mode 100644 common/log/SyslogLoggerTest.cpp create mode 100644 common/log/TestingSyslogLogger.hpp create mode 100644 common/processCap/ProcessCap.cpp create mode 100644 common/processCap/ProcessCap.hpp create mode 100644 common/processCap/ProcessCapDummy.cpp create mode 100644 common/processCap/ProcessCapDummy.hpp create mode 100644 common/processCap/SmartCap.cpp create mode 100644 common/processCap/SmartCap.hpp create mode 100644 common/processCap/SmartCapTest.cpp create mode 100644 common/threading/Daemon.cpp create mode 100644 common/threading/Daemon.hpp create mode 100644 common/threading/DaemonTest.cpp create mode 100644 common/threading/MutexLocker.hpp create mode 100644 common/threading/System.cpp create mode 100644 common/threading/System.hpp rename common/{ => utils}/Utils.cpp (86%) rename common/{ => utils}/Utils.hpp (82%) rename common/{ => utils}/UtilsTest.cpp (73%) rename common/{ => utils}/strerror_r_wrapper.cpp (96%) rename common/{ => utils}/strerror_r_wrapper.hpp (100%) rename {common => tapeserver/castor/io}/marshall.h (100%) create mode 100644 tapeserver/cta-taped.cpp rename tapeserver/{castor/tape/tapeserver/daemon/tapeserverd.init => cta-taped.init} (100%) rename tapeserver/{castor/tape/tapeserver/daemon/castor-tapeserver-server.logrotate => cta-taped.logrotate} (100%) rename tapeserver/{castor/tape/tapeserver/daemon/tapeserverd.man => cta-taped.man} (100%) rename tapeserver/{castor/tape/tapeserver/daemon/tapeserverd.sysconfig => cta-taped.sysconfig} (100%) create mode 100644 tapeserver/daemon/CMakeLists.txt create mode 100644 tapeserver/daemon/DriveConfiguration.hpp create mode 100644 tapeserver/daemon/GlobalConfiguration.cpp create mode 100644 tapeserver/daemon/GlobalConfiguration.hpp create mode 100644 tapeserver/daemon/TapeDaemon.cpp create mode 100644 tapeserver/daemon/TapeDaemon.hpp create mode 100644 tapeserver/daemon/TpconfigLine.cpp create mode 100644 tapeserver/daemon/TpconfigLine.hpp create mode 100644 tapeserver/daemon/TpconfigLines.cpp create mode 100644 tapeserver/daemon/TpconfigLines.hpp create mode 100644 version.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index de23a79950..fa45613cd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,12 @@ set(CMAKE_C_FLAGS "-fPIC -pedantic -Wall -Wextra -Werror -Wno-unused-parameter") # # ClientSimulator.hpp:65: error: ISO C++ prohibits anonymous structs set(CMAKE_CXX_FLAGS "-fPIC -pedantic -Wall -Wextra -Werror -Wno-unused-parameter -Wno-long-long -std=c++0x -fms-extensions -fstack-protector-all") +# +# A maximalist error checking parameter combo has been suggested by S. Ponce and D. Come: +# to be tested once we have a stable compilation on CC7: +#-Wno-unused-parameter -Wlogical-op -Wfloat-equal -Wdeclaration-after- statement -Wundef -Wno-endif-labels -Wshadow -Wunsafe-loop- optimizations -Wpointer-arith -Wbad-function-cast -Wcast-align -Wwrite- strings -Wconversion -Wmissing-field-initializers -Wredundant-decls -Wnested-externs -Wunreachable-code -Winline -Wvariadic-macros -Wtraditional -Wmissing-prototypes -Wmissing-declarations -Wold-style- definition -Wc++-compat -Wstrict-prototypes -Wpadded -Wcast-qual -Wnon-virtual-dtor -Wlogical-op -Wmissing-declarations -Wsign-conversion -Wredundant-decls -Wold-style-cast -Wshadow + + # Explicitly setting the C and C++ compiler flags for the RelWithDebInfo build @@ -77,6 +83,10 @@ ELSE(DEFINED PackageOnly) add_subdirectory(tapeserver) add_subdirectory(tests) add_subdirectory(xroot_plugins) + + #Generate version information + configure_file(${PROJECT_SOURCE_DIR}/version.hpp.in + ${CMAKE_BINARY_DIR}/version.h) ENDIF(DEFINED PackageOnly) ################################################################################ diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e51c291224..ff3fc504d0 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -45,30 +45,41 @@ set (COMMON_LIB_SRC_FILES archiveNS/StorageClass.cpp archiveNS/Tape.cpp archiveNS/TapeFileLocation.cpp - ArchiveRequest.cpp - CreationLog.cpp checksum/Checksum.cpp exception/Backtrace.cpp exception/DiskException.hpp exception/Errnum.cpp exception/Exception.cpp - exception/Serrnum.cpp exception/TapeException.cpp + log/DummyLogger.cpp + log/LogContext.cpp + log/Logger.cpp + log/Message.cpp + log/Param.cpp + log/StringLogger.cpp + log/SyslogLogger.cpp priorities/DriveQuota.cpp priorities/MountCriteria.cpp priorities/UserGroup.cpp + processCap/ProcessCap.cpp + processCap/SmartCap.cpp remoteFS/RemoteFileStatus.cpp remoteFS/RemotePath.cpp remoteFS/RemotePathAndStatus.cpp - SecurityIdentity.cpp - strerror_r_wrapper.cpp - TapePool.cpp - Timer.cpp threading/ChildProcess.cpp + threading/Daemon.cpp threading/Mutex.cpp + threading/System.cpp threading/Threading.cpp + utils/Utils.cpp + utils/strerror_r_wrapper.cpp + ArchiveRequest.cpp + CreationLog.cpp + Configuration.cpp + SecurityIdentity.cpp + TapePool.cpp + Timer.cpp UserIdentity.cpp - Utils.cpp VO.cpp) add_library (ctacommon SHARED @@ -81,13 +92,18 @@ target_link_libraries (ctacommon ${SQLITE3_LIBRARY_RELEASE} uuid z - Utils) + Utils + cap) set (COMMON_UNIT_TESTS_LIB_SRC_FILES checksum/ChecksumTest.cpp + log/LogContextTest.cpp + log/ParamTest.cpp + log/SyslogLoggerTest.cpp remoteFS/RemotePathTest.cpp - UserIdentityTest.cpp - UtilsTest.cpp) + threading/DaemonTest.cpp + utils/UtilsTest.cpp + UserIdentityTest.cpp) add_library (ctacommonunittests SHARED ${COMMON_UNIT_TESTS_LIB_SRC_FILES}) diff --git a/common/Configuration.cpp b/common/Configuration.cpp new file mode 100644 index 0000000000..4e3dc12fa3 --- /dev/null +++ b/common/Configuration.cpp @@ -0,0 +1,330 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "Configuration.hpp" +#include "common/exception/Errnum.hpp" + +#include <algorithm> +#include <fstream> +#include <errno.h> + + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::common::Configuration::Configuration(std::string fileName) + : m_fileName(fileName), + m_lastUpdateTime(0) { + // create internal r/w lock + int rc = pthread_rwlock_init(&m_lock, NULL); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "CastorConfiguration constructor Failed" + ": Failed to create internal r/w lock"; + throw e; + } +} + +//------------------------------------------------------------------------------ +// copy constructor +//------------------------------------------------------------------------------ +cta::common::Configuration::Configuration( + const Configuration & other) : + m_fileName(other.m_fileName), m_lastUpdateTime(other.m_lastUpdateTime), + m_config(other.m_config) { + // create a new internal r/w lock + int rc = pthread_rwlock_init(&m_lock, NULL); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "CastorConfiguration copy constructor failed" + ": Failed to create a new internal r/w lock"; + throw e; + } +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::common::Configuration::~Configuration() { + // relase read write lock + pthread_rwlock_destroy(&m_lock); +} + +//------------------------------------------------------------------------------ +// assignment operator +//------------------------------------------------------------------------------ +cta::common::Configuration & + cta::common::Configuration::operator=( + const cta::common::Configuration & other) + { + m_fileName = other.m_fileName; + m_lastUpdateTime = other.m_lastUpdateTime; + m_config = other.m_config; + // create a new internal r/w lock + int rc = pthread_rwlock_init(&m_lock, NULL); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "Assignment operator of CastorConfiguration object failed" + ": Failed to create a new internal r/w lock"; + throw e; + } + return *this; +} + +//------------------------------------------------------------------------------ +// getConfEntString +//------------------------------------------------------------------------------ +const std::string& cta::common::Configuration::getConfEntString( + const std::string &category, const std::string &key, + const std::string &defaultValue, log::Logger *const log) { + try { + if (isStale()) { + tryToRenewConfig(); + } + // get read lock + int rc = pthread_rwlock_rdlock(&m_lock); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "Failed to get configuration entry " << category << ":" + << key << ": Failed to get read lock"; + throw e; + } + // get the entry + std::map<std::string, ConfCategory>::const_iterator catIt = m_config.find(category); + if (m_config.end() != catIt) { + // get the entry + ConfCategory::const_iterator entIt = catIt->second.find(key); + if (catIt->second.end() != entIt) { + // release the lock + pthread_rwlock_unlock(&m_lock); + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", entIt->second), + log::Param("source", m_fileName)}; + (*log)(LOG_INFO, "Configuration entry", params); + } + return entIt->second; + } + } + // no entry found + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", defaultValue), + log::Param("source", "DEFAULT")}; + (*log)(LOG_INFO, "Configuration entry", params); + } + // Unlock and return default + pthread_rwlock_unlock(&m_lock); + } catch (cta::exception::Exception ex) { + // exception caught : Unlock and return default + pthread_rwlock_unlock(&m_lock); + // log the exception + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", defaultValue), + log::Param("source", "DEFAULT")}; + (*log)(LOG_INFO, "Configuration entry", params); + } + } catch (...) { + // release the lock + pthread_rwlock_unlock(&m_lock); + throw; + } + return defaultValue; +} + +//------------------------------------------------------------------------------ +// getConfEntString +//------------------------------------------------------------------------------ +const std::string& cta::common::Configuration::getConfEntString( + const std::string &category, const std::string &key, log::Logger *const log) { + // check whether we need to reload the configuration + if (isStale()) { + tryToRenewConfig(); + } + // get read lock + int rc = pthread_rwlock_rdlock(&m_lock); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "Failed to get configuration entry " << category << ":" + << key << ": Failed to get read lock"; + throw e; + } + // get the entry + try { + std::map<std::string, ConfCategory>::const_iterator catIt = + m_config.find(category); + if (m_config.end() == catIt) { + NoEntry e; + e.getMessage() << "Failed to get configuration entry " << category << ":" + << key << ": Failed to find " << category << " category"; + throw e; + } + // get the entry + ConfCategory::const_iterator entIt = catIt->second.find(key); + if (catIt->second.end() == entIt) { + NoEntry e; + e.getMessage() << "Failed to get configuration entry " << category << ":" + << key << ": Failed to find " << key << " key"; + throw e; + } + + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", entIt->second), + log::Param("source", m_fileName)}; + (*log)(LOG_INFO, "Configuration entry", params); + } + + // release the lock + pthread_rwlock_unlock(&m_lock); + return entIt->second; + } catch (...) { + // release the lock + pthread_rwlock_unlock(&m_lock); + throw; + } +} + +//------------------------------------------------------------------------------ +// isStale +//------------------------------------------------------------------------------ +bool cta::common::Configuration::isStale() + { + // get read lock + int rc = pthread_rwlock_rdlock(&m_lock); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "Failed to determine if CASTOR configuration is stale" + ": Failed to get read lock"; + throw e; + } + try { + // get the timeout + int timeout = getTimeoutNolock(); + // release the lock + pthread_rwlock_unlock(&m_lock); + // return whether we should renew + return time(0) > m_lastUpdateTime + timeout; + } catch (...) { + // release the lock + pthread_rwlock_unlock(&m_lock); + throw; + } +} + +//------------------------------------------------------------------------------ +// tryToRenewConfig +//------------------------------------------------------------------------------ +void cta::common::Configuration::tryToRenewConfig() + { + // we should probably renew. First take the write lock. + int rc = pthread_rwlock_wrlock(&m_lock); + if (0 != rc) { + cta::exception::Errnum e(rc); + e.getMessage() << "Failed to renew configuration cache" + ": Failed to take write lock"; + throw e; + } + // now check that we should really renew, because someone may have done it + // while we waited for the lock + try { + if (time(0) > m_lastUpdateTime + getTimeoutNolock()) { + // now we should really renew + renewConfigNolock(); + } + } catch (...) { + // release the lock + pthread_rwlock_unlock(&m_lock); + throw; + } + // release the lock + pthread_rwlock_unlock(&m_lock); + return; +} + +//------------------------------------------------------------------------------ +// getTimeoutNolock +//------------------------------------------------------------------------------ +int cta::common::Configuration::getTimeoutNolock() + { + // start with the default (300s = 5mn) + int timeout = 300; + // get value from config + std::map<std::string, ConfCategory>::const_iterator catIt = + m_config.find("Config"); + if (m_config.end() != catIt) { + ConfCategory::const_iterator entIt = catIt->second.find("ExpirationDelay"); + if (catIt->second.end() != entIt) { + // parse the timeout into an integer + timeout = atoi(entIt->second.c_str()); + } + } + // return timeout + return timeout; +} + +//------------------------------------------------------------------------------ +// renewConfigNolock +//------------------------------------------------------------------------------ +void cta::common::Configuration::renewConfigNolock() + { + // reset the config + m_config.clear(); + + // try to open the configuration file, throwing an exception if there is a + // failure + std::ifstream file(m_fileName.c_str()); + if(file.fail()) { + cta::exception::Errnum ex(EIO); + ex.getMessage() << __FUNCTION__ << " failed" + ": Failed to open file" + ": m_fileName=" << m_fileName; + throw ex; + } + + std::string line; + while(std::getline(file, line)) { + // get rid of potential tabs + std::replace(line.begin(),line.end(),'\t',' '); + // get the category + std::istringstream sline(line); + std::string category; + if (!(sline >> category)) continue; // empty line + if (category[0] == '#') continue; // comment + // get the key + std::string key; + if (!(sline >> key)) continue; // no key on line + if (key[0] == '#') continue; // key commented + // get and store value + while (sline.get() == ' '){}; sline.unget(); // skip spaces + std::string value; + std::getline(sline, value, '#'); + value.erase(value.find_last_not_of(" \n\r\t")+1); // right trim + m_config[category][key] = value; + } + m_lastUpdateTime = time(0); +} diff --git a/common/Configuration.hpp b/common/Configuration.hpp new file mode 100644 index 0000000000..c69ac2f180 --- /dev/null +++ b/common/Configuration.hpp @@ -0,0 +1,270 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/exception/Exception.hpp" +#include "common/log/Logger.hpp" +#include "common/utils/Utils.hpp" + +#include <string> +#include <map> + +namespace cta { namespace common { + + /** + * represents a category from the CTA configuration file + */ + typedef std::map<std::string, std::string> ConfCategory; + + /** + * a class representing the configuration of castor. + * This configurations is obtained from the local file given in the + * constructor and will be updated regularly. The time between two + * updates is taken from the Config/ExpirationDelay entry of the + * configuration itself and defaults to 5mn if no such entry is found + */ + class Configuration { + + public: + + /** + * Private exceptions for this + */ + CTA_GENERATE_EXCEPTION_CLASS(InvalidConfigEntry); + CTA_GENERATE_EXCEPTION_CLASS(NoEntry); + + public: + + /** + * constructor + * @param fileName the file that should be used to build the configuration + */ + Configuration(std::string fileName = "/etc/cta/cta.conf"); + + /** + * copy constructor + * @param other instance of CastorConfiguration class + */ + Configuration(const Configuration & other); + + /** + * destructor + */ + virtual ~Configuration(); + + /** + * assignment operator + * @param other instance of CastorConfiguration class + */ + Configuration & operator=(const Configuration & other); + + /** + * Retrieves a configuration entry. + * + * If this method is passed a logger object then it will log the value + * of the configuration entry together with an indication of whether the + * value was found in the castor configuration file or whether the + * specified default value was used instead. + * + * @param category the category of the entry + * @param key the key of the entry + * @param defaultValue the value to be returned if the configuration entry + * is not in the configuration file + * @param log pointer to NULL or an optional logger object + */ + const std::string& getConfEntString(const std::string &category, + const std::string &key, const std::string &defaultValue, + log::Logger *const log = NULL); + + /** + * Retrieves a configuration entry. + * + * Besides other possible exceptions, this method throws a + * castor::exception::NoEntry exception if the specified configuration + * entry is not in the configuration file. + * + * If this method is passed a logger object then this method will log the + * the value of the configuration entry. + * + * @param category the category of the entry + * @param key the key of the entry + * @param log pointer to NULL or an optional logger object + */ + const std::string& getConfEntString(const std::string &category, + const std::string &key, log::Logger *const log = NULL); + + /** + * Retrieves a configuration entry as an integer. + * + * If this method is passed a logger object then it will log the value + * of the configuration entry together with an indication of whether the + * value was found in the castor configuration file or whether the + * specified default value was used instead. + * + * @param category category of the configuration parameter + * @param name category of the configuration parameter + * @param defaultValue the value to be returned if the configuration entry + * is not in the configuration file + * @param log pointer to NULL or an optional logger object + * @return the integer value + */ + template<typename T> T getConfEntInt(const std::string &category, + const std::string &key, const T defaultValue, + log::Logger *const log = NULL) { + std::string strValue; + try { + strValue = getConfEntString(category, key); + } catch(cta::exception::Exception &ex) { + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", defaultValue), + log::Param("source", "DEFAULT")}; + (*log)(LOG_INFO, "Configuration entry", params); + } + return defaultValue; + } + + if (!Utils::isValidUInt(strValue.c_str())) { + InvalidConfigEntry ex(category.c_str(), + key.c_str(), strValue.c_str()); + ex.getMessage() << "Failed to get configuration entry " << category << + ":" << key << ": Value is not a valid unsigned integer: value=" << + strValue; + throw ex; + } + + T value; + std::stringstream ss; + ss << strValue.c_str(); + ss >> value; + + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", value), + log::Param("source", m_fileName)}; + (*log)(LOG_INFO, "Configuration entry", params); + } + + return value; + } + + /** + * Retrieves a configuration entry as an integer. + * + * Besides other possible exceptions, this method throws a + * castor::exception::NoEntry exception if the specified configuration + * entry is not in the configuration file. + * + * @param category category of the configuration parameter + * @param name category of the configuration parameter + * @param log pointer to NULL or an optional logger object + * @return the integer value + */ + template<typename T> T getConfEntInt(const std::string &category, + const std::string &key, log::Logger *const log = NULL) { + const std::string strValue = getConfEntString(category, key); + + if (!Utils::isValidUInt(strValue.c_str())) { + InvalidConfigEntry ex(category.c_str(), + key.c_str(), strValue.c_str()); + ex.getMessage() << "Failed to get configuration entry " << category << + ":" << key << ": Value is not a valid unsigned integer: value=" << + strValue; + throw ex; + } + + T value; + std::stringstream ss; + ss << strValue.c_str(); + ss >> value; + + if(NULL != log) { + log::Param params[] = { + log::Param("category", category), + log::Param("key", key), + log::Param("value", value), + log::Param("source", m_fileName)}; + (*log)(LOG_INFO, "Configuration entry", params); + } + + return value; + } + + private: + + /** + * check whether the configuration should be renewed + */ + bool isStale() ; + + /** + * tries to renew the configuration. + * That is : take the write lock to do it, check whether it's needed + * and do it only if needed before releasing the lock + */ + void tryToRenewConfig() ; + + /** + * gets current timeout value (in seconds) + * this function does not take any lock while reading the + * configuration. So it should never be called without holding + * a read or a write lock + */ + int getTimeoutNolock() ; + + /** + * renews the configuration + * this function does not take any lock while renewing the + * configuration. So it should never be called without holding + * the write lock + */ + void renewConfigNolock() ; + + private: + + /** + * fileName to be used when updating the configuration + */ + std::string m_fileName; + + /** + * last time we've updated the configuration + */ + time_t m_lastUpdateTime; + + /** + * the dictionnary of configuration items + * actually a dictionnary of ConfCategories, which are dictionnaries of entries + */ + std::map<std::string, ConfCategory> m_config; + + /** + * lock to garantee safe access to the configuration, lastUpdateTime and timeout + */ + pthread_rwlock_t m_lock; + + }; + + } // namespace common +} // namespace castor + diff --git a/common/ProcessCap.cpp b/common/ProcessCap.cpp new file mode 100644 index 0000000000..5b5d49c710 --- /dev/null +++ b/common/ProcessCap.cpp @@ -0,0 +1,135 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common/exception/Exception.hpp" +#include "common/ProcessCap.hpp" +#include "castor/server/SmartCap.hpp" +#include "common/Utils.hpp" + +#include <errno.h> + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +castor::server::ProcessCap::~ProcessCap() + throw() { +} + +//------------------------------------------------------------------------------ +// getProcText +//------------------------------------------------------------------------------ +std::string castor::server::ProcessCap::getProcText() { + try { + SmartCap cap(getProc()); + return toText((cap_t)cap.get()); + } catch(castor::exception::Exception &ne) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to get text representation of the capabilities of the process: " + << ne.getMessage().str(); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// getProc +//------------------------------------------------------------------------------ +cap_t castor::server::ProcessCap::getProc() { + cap_t cap = cap_get_proc(); + if(NULL == cap) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to get the capabilities of the process: " + << cta::Utils::errnoToString(errno); + throw ex; + } + return cap; +} + +//------------------------------------------------------------------------------ +// toText +//------------------------------------------------------------------------------ +std::string castor::server::ProcessCap::toText( + const cap_t cap) { + // Create a C++ string with the result of calling cap_to_text() + char *const text = cap_to_text(cap, NULL); + if(NULL == text) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to create string representation of capability state: " + << cta::Utils::errnoToString(errno); + throw ex; + } + std::string result(text); + + // Free the memory allocated by cap_to_text() + if(cap_free(text)) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to free string representation of capability state: " + << cta::Utils::errnoToString(errno); + throw ex; + } + + // Return the C++ string + return result; +} + +//------------------------------------------------------------------------------ +// setProcText +//------------------------------------------------------------------------------ +void castor::server::ProcessCap::setProcText(const std::string &text) { + try { + SmartCap cap(fromText(text)); + setProc(cap.get()); + } catch(castor::exception::Exception &ne) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to set capabilities of process: " << ne.getMessage().str(); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// fromText +//------------------------------------------------------------------------------ +cap_t castor::server::ProcessCap::fromText(const std::string &text) { + const cap_t cap = cap_from_text(text.c_str()); + if(NULL == cap) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to create capability state from string representation" + ": text='" << text << "': " << cta::Utils::errnoToString(errno); + throw ex; + } + + return cap; +} + +//------------------------------------------------------------------------------ +// setProc +//------------------------------------------------------------------------------ +void castor::server::ProcessCap::setProc(const cap_t cap) { + if(cap_set_proc(cap)) { + castor::exception::Exception ex; + ex.getMessage() << + "Failed to set the capabilities of the process: " + << cta::Utils::errnoToString(errno); + throw ex; + } +} diff --git a/common/exception/Errnum.cpp b/common/exception/Errnum.cpp index 254ebf47b7..5f47876384 100644 --- a/common/exception/Errnum.cpp +++ b/common/exception/Errnum.cpp @@ -17,7 +17,7 @@ */ #include "common/exception/Errnum.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include <errno.h> #include <string.h> diff --git a/common/exception/Errnum.hpp b/common/exception/Errnum.hpp index 080d67a074..926acbfef5 100644 --- a/common/exception/Errnum.hpp +++ b/common/exception/Errnum.hpp @@ -25,7 +25,7 @@ namespace exception { class Errnum: public cta::exception::Exception { public: Errnum(std::string what = ""); - Errnum (int err, std::string what = ""); + Errnum (int err, std::string what = ""); virtual ~Errnum() throw() {}; int errorNumber() const { return m_errnum; } std::string strError() const { return m_strerror; } diff --git a/common/exception/Exception.hpp b/common/exception/Exception.hpp index 5063156463..0bf77aa29e 100644 --- a/common/exception/Exception.hpp +++ b/common/exception/Exception.hpp @@ -23,112 +23,106 @@ #include <exception> #include <sstream> -namespace cta { - - namespace exception { - - /** - * class Exception - * A simple exception used for error handling in castor - */ - class Exception: public std::exception { - - public: - - /** - * Constructor. - * - * @param context optional context string added to the message - * at initialisation time. - * @param embedBacktrace whether to embed a backtrace of where the - * exception was throw in the message - */ - Exception(const std::string &context="", const bool embedBacktrace=true); - - /** - * Copy Constructor - */ - Exception(const Exception& rhs); - - /** - * Assignment operator - */ - Exception& operator=(const Exception &rhs); - - /** - * Empty Destructor, explicitely non-throwing (needed for std::exception - * inheritance) - */ - virtual ~Exception() throw (); - - /** - * Get the value of m_message - * A message explaining why this exception was raised - * @return the value of m_message - */ - std::ostringstream& getMessage() { - return m_message; - } - - /** - * Get the value of m_message - * A message explaining why this exception was raised - * @return the value of m_message - */ - const std::ostringstream& getMessage() const { - return m_message; - } - - /** - * Get the value of m_message as a sting, for const-c orrectness - * @return the value as a string. - */ - std::string getMessageValue() const { - return m_message.str(); - } - - /** - * Get the backtrace's contents - * @return backtrace in a standard string. - */ - std::string const backtrace() const { - return (std::string)m_backtrace; - } - - /** - * Updates the m_what member with a concatenation of the message and - * the stack trace. - * @return pointer to m_what's contents - */ - virtual const char * what() const throw (); - - private: - /// A message explaining why this exception was raised - std::ostringstream m_message; - - /** - * Placeholder for the what result. It has to be a member - * of the object, and not on the stack of the "what" function. - */ - mutable std::string m_what; - - protected: - void setWhat(const std::string &w); - - /** - * Backtrace object. Its constructor does the heavy lifting of - * generating the backtrace. - */ - Backtrace m_backtrace; - - }; - - } // end of exception namespace - -} // end of castor namespace - -#define CTA_GENERATE_EXCEPTION_CLASS(A) \ -class A: public cta::exception::Exception { \ -public: \ - A(const std::string & w): cta::exception::Exception(w) {} \ +namespace cta { namespace exception { +/** + * class Exception + * A simple exception used for error handling in castor + */ +class Exception : public std::exception { +public: + + /** + * Constructor. + * + * @param context optional context string added to the message + * at initialisation time. + * @param embedBacktrace whether to embed a backtrace of where the + * exception was throw in the message + */ + Exception(const std::string &context = "", const bool embedBacktrace = true); + + /** + * Copy Constructor + */ + Exception(const Exception& rhs); + + /** + * Assignment operator + */ + Exception& operator=(const Exception &rhs); + + /** + * Empty Destructor, explicitely non-throwing (needed for std::exception + * inheritance) + */ + virtual ~Exception() throw (); + + /** + * Get the value of m_message + * A message explaining why this exception was raised + * @return the value of m_message + */ + std::ostringstream& getMessage() { + return m_message; + } + + /** + * Get the value of m_message + * A message explaining why this exception was raised + * @return the value of m_message + */ + + const std::ostringstream& getMessage() const { + return m_message; + } + /** + * Get the value of m_message as a sting, for const-c orrectness + * @return the value as a string. + */ + + std::string getMessageValue() const { + return m_message.str(); + } + /** + * Get the backtrace's contents + * @return backtrace in a standard string. + */ + std::string const backtrace() const { + return (std::string)m_backtrace; + } + + /** + * Updates the m_what member with a concatenation of the message and + * the stack trace. + * @return pointer to m_what's contents + */ + virtual const char * what() const throw (); + +private: + /// A message explaining why this exception was raised + std::ostringstream m_message; + + /** + * Placeholder for the what result. It has to be a member + * of the object, and not on the stack of the "what" function. + */ + mutable std::string m_what; + +protected: + void setWhat(const std::string &w); + + /** + * Backtrace object. Its constructor does the heavy lifting of + * generating the backtrace. + */ + Backtrace m_backtrace; + +} ; + +}} // namespace cta::exception + +#define CTA_GENERATE_EXCEPTION_CLASS(A) \ +class A: public cta::exception::Exception { \ +public: \ + A(const std::string & w = ""): cta::exception::Exception(w) {} \ } diff --git a/common/exception/Serrnum.cpp b/common/exception/Serrnum.cpp deleted file mode 100644 index 333ebaf3be..0000000000 --- a/common/exception/Serrnum.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * The CERN Tape Archive (CTA) project - * Copyright (C) 2015 CERN - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "common/exception/Serrnum.hpp" -#include "common/Utils.hpp" - -#include <errno.h> -#include <string.h> - -using namespace cta::exception; - -Serrnum::Serrnum(std::string what):Exception("") { - m_serrnum = errno; - SerrnumConstructorBottomHalf(what); -} - -Serrnum::Serrnum(int err, std::string what):Exception("") { - m_serrnum = err; - SerrnumConstructorBottomHalf(what); -} - -void Serrnum::SerrnumConstructorBottomHalf(const std::string & what) { - m_strerror = Utils::errnoToString(m_serrnum); - std::stringstream w2; - if (what.size()) - w2 << what << " "; - w2 << "Errno=" << m_serrnum << ": " << m_strerror; - getMessage().str(w2.str()); -} - -void Serrnum::throwOnReturnedErrno (const int err, const std::string &context) { - if (err) throw Serrnum(err, context); -} - -void Serrnum::throwOnNonZero(const int status, const std::string &context) { - if (status) throw Serrnum(context); -} - -void Serrnum::throwOnZero(const int status, const std::string &context) { - if (!status) throw Serrnum(context); -} - -void Serrnum::throwOnNull(const void *const f, const std::string &context) { - if (NULL == f) throw Serrnum(context); -} - -void Serrnum::throwOnNegative(const int ret, const std::string &context) { - if (ret < 0) throw Serrnum(context); -} - -void Serrnum::throwOnMinusOne(const int ret, const std::string &context) { - if (-1 == ret) throw Serrnum(context); -} diff --git a/common/exception/Serrnum.hpp b/common/exception/Serrnum.hpp deleted file mode 100644 index 1dd46b01f5..0000000000 --- a/common/exception/Serrnum.hpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * The CERN Tape Archive (CTA) project - * Copyright (C) 2015 CERN - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. - */ - -#pragma once - -#include "Exception.hpp" - -namespace cta { -namespace exception { - class Serrnum: public cta::exception::Exception { - public: - Serrnum(std::string what = ""); - Serrnum (int err, std::string what = ""); - virtual ~Serrnum() throw() {}; - int serrorNumber() const { return m_serrnum; } - std::string strError() const { return m_strerror; } - static void throwOnReturnedErrno(const int err, const std::string &context = ""); - static void throwOnNonZero(const int status, const std::string &context = ""); - static void throwOnZero(const int status, const std::string &context = ""); - static void throwOnNull(const void *const f, const std::string &context = ""); - static void throwOnNegative(const int ret, const std::string &context = ""); - static void throwOnMinusOne(const int ret, const std::string &context = ""); - protected: - void SerrnumConstructorBottomHalf(const std::string & what); - int m_serrnum; - std::string m_strerror; - }; -} -} diff --git a/common/log/CMakeLists.txt b/common/log/CMakeLists.txt new file mode 100644 index 0000000000..3fcad0947e --- /dev/null +++ b/common/log/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required (VERSION 2.6) + +include_directories(${PROJECT_SOURCE_DIR}/tapeserver) +include_directories(${PROJECT_SOURCE_DIR}/tapeserver/h) + +add_library (ctalogutils SHARED + DummyLogger.cpp) + +set (LOG_LIB_SRC_FILES + DummyLogger.cpp + Logger.cpp + StringLogger.cpp + SyslogLogger.cpp + LogContext.cpp + Message.cpp + Param.cpp) +add_library (ctalog ${LOG_LIB_SRC_FILES}) + +add_library (ctalogunittests SHARED + ParamTest.cpp + LogContextTest.cpp + StringLoggerTest.cpp + SyslogLoggerTest.cpp) diff --git a/common/log/DummyLogger.cpp b/common/log/DummyLogger.cpp new file mode 100644 index 0000000000..7156eff7d2 --- /dev/null +++ b/common/log/DummyLogger.cpp @@ -0,0 +1,95 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common/log/DummyLogger.hpp" + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::log::DummyLogger::DummyLogger(const std::string &programName) : +Logger(programName) { } + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::log::DummyLogger::~DummyLogger() { } + +//------------------------------------------------------------------------------ +// prepareForFork +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::prepareForFork() {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator()( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[], + const struct timeval &timeStamp) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[]) {} + +//------------------------------------------------------------------------------ +// operator() +//------------------------------------------------------------------------------ +void cta::log::DummyLogger::operator() ( + const int priority, + const std::string &msg) {} diff --git a/common/log/DummyLogger.hpp b/common/log/DummyLogger.hpp new file mode 100644 index 0000000000..52eee7d85c --- /dev/null +++ b/common/log/DummyLogger.hpp @@ -0,0 +1,159 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/log/Logger.hpp" + +#include <map> +#include <pthread.h> +#include <syslog.h> +#include <sys/time.h> + +namespace cta { namespace log { + +/** + * A dummy logger class whose implementation of the API of the CASTOR logging + * system does nothing. + * + * The primary purpose of this class is to facilitate the unit testing of + * classes that require a logger object. Using an instance of this class + * during unit testing means that no logs will actually be written to a log + * file. + */ +class DummyLogger: public Logger { +public: + + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + DummyLogger(const std::string &programName); + + /** + * Destructor. + */ + ~DummyLogger(); + + /** + * Prepares the logger object for a call to fork(). + * + * No further calls to operator() should be made after calling this + * method until the call to fork() has completed. + */ + void prepareForFork() ; + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[], + const struct timeval &timeStamp); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[]); + + /** + * Dummy operator() method that does nothing. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + void operator() ( + const int priority, + const std::string &msg); + +}; // class DummyLogger + +} // namespace log +} // namespace castor + diff --git a/common/log/LogContext.cpp b/common/log/LogContext.cpp new file mode 100644 index 0000000000..c1cf6deafa --- /dev/null +++ b/common/log/LogContext.cpp @@ -0,0 +1,114 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Interface to the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/LogContext.hpp" +#include "common/log/Param.hpp" +#include "common/log/Logger.hpp" + +#include <list> +#include <algorithm> +#include <bfd.h> + +cta::log::LogContext::LogContext(cta::log::Logger& logger): +m_log(logger) {} + +void cta::log::LogContext::pushOrReplace(const Param& param) { + ParamNameMatcher match(param.getName()); + std::list<Param>::iterator i = + std::find_if(m_params.begin(), m_params.end(), match); + if (i != m_params.end()) { + i->setValue(param.getValue()); + } else { + m_params.push_back(param); + } +} + +void cta::log::LogContext::erase(const std::string& paramName) { + ParamNameMatcher match(paramName); + m_params.erase(std::remove_if(m_params.begin(), m_params.end(), match), m_params.end()); +} + +void cta::log::LogContext::log(const int priority, const std::string& msg) { + m_log(priority, msg, m_params); +} + +void cta::log::LogContext::log(const int priority, const std::string& msg, + const timeval& timeStamp) { + m_log(priority, msg, m_params, timeStamp); +} + +void cta::log::LogContext::logBacktrace(const int priority, + const std::string& backtrace) { + // Sanity check to prevent substr from throwing exceptions + if (!backtrace.size()) + return; + size_t position = 0; + int lineNumber = 0; + bool stillGoing = true; + while(stillGoing) { + size_t next = backtrace.find_first_of("\n", position); + std::string line; + if(next != std::string::npos) { + line = backtrace.substr(position, next - position); + // If our position is out of range, substr would throw an exception + // so we check here if we would get out of range. + position = next + 1; + if (position >= backtrace.size()) + stillGoing = false; + } else { + stillGoing=false; + line = backtrace.substr(position); + } + if (line.size()) { + ScopedParam sp1 (*this, cta::log::Param("traceFrameNumber", lineNumber++)); + ScopedParam sp2 (*this, cta::log::Param("traceFrame", line)); + log(priority, "Stack trace"); + } + } +} + +cta::log::LogContext::ScopedParam::ScopedParam( + LogContext& context, + const Param& param) : + m_context(context), m_name(param.getName()) { + m_context.pushOrReplace(param); +} + +cta::log::LogContext::ScopedParam::~ScopedParam() { + m_context.erase(m_name); +} + +std::ostream & cta::log::operator << (std::ostream & os, + const cta::log::LogContext & lc) { + bool first=true; + for (std::list<Param>::const_iterator p = lc.m_params.begin(); + p != lc.m_params.end(); ++p) { + if (!first) { + os << " "; + } else { + first = false; + } + os << p->getName() << "=" << p->getValue(); + } + return os; +} diff --git a/common/log/LogContext.hpp b/common/log/LogContext.hpp new file mode 100644 index 0000000000..a30525a7ce --- /dev/null +++ b/common/log/LogContext.hpp @@ -0,0 +1,164 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <ostream> +#include "common/log/Logger.hpp" + +namespace cta { namespace log { + +/** + * Container for a set of parameters to be used repetitively in logs. The + * container is ordered , by order of inclusion. There can be only one + * parameter value per parameter name. + */ +class LogContext { + friend std::ostream & operator << (std::ostream & os , const LogContext & lc); +public: + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + LogContext(cta::log::Logger &logger); + + /** + * Destructor. + */ + virtual ~LogContext() throw() {}; + + /** + * Access to the logger object. + * @return reference to this context's logger + */ + cta::log::Logger & logger() throw() { return m_log; } + + /** + * Add a parameter to the container. Replaces any parameter going by the same + * name. Does not throw exceptions (fails silently). + * @param param + */ + void pushOrReplace(const Param & param); + + /** + * Removes a parameter from the list. + * @param paramName value of param.getName(); + */ + void erase(const std::string & paramName); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of logMsg() implicitly uses the current time as + * the time stamp of the message. + * + * All the parameters present in the context will be added to the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + virtual void log( + const int priority, + const std::string &msg); + + /** + * Logs a multiline backtrace as multiple entries in the logs, without + * the context + * @param priority the logging priority + * @param backtrace the multi-line (\n separated) stack trace + */ + virtual void logBacktrace( + const int priority, + const std::string &backtrace); + + /** + * Small introspection function to help in tests + * @return size + */ + size_t size() const { return m_params.size(); } + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * All the parameters present in the context will be added to the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + virtual void log( + const int priority, + const std::string &msg, + const struct timeval &timeStamp); + /** + * Helper class to find parameters by name. + */ + class ParamNameMatcher { + public: + ParamNameMatcher(const std::string & name) throw(): m_name(name) {} + bool operator() (const Param & p) throw() { return m_name == p.getName(); } + private: + std::string m_name; + }; + + /** + * Scoped parameter addition to the context. Constructor adds the parameter, + * destructor erases it. + */ + class ScopedParam { + public: + ScopedParam(LogContext & context, const Param ¶m); + ~ScopedParam(); + private: + LogContext & m_context; + std::string m_name; + }; +private: + Logger & m_log; + std::list<Param> m_params; +}; // class LogContext + +class ScopedParamContainer{ + public: + ScopedParamContainer(LogContext & context):m_context(context) {} + ~ScopedParamContainer() { + for(std::vector<std::string>::const_iterator it=m_names.begin();it!=m_names.end();++it) + { m_context.erase(*it); + } + } + + template <class T> ScopedParamContainer& add(const std::string& s,const T& t){ + m_context.pushOrReplace(Param(s,t)); + m_names.push_back(s); + return *this; + } + private: + + LogContext & m_context; + std::vector<std::string> m_names; +}; + +std::ostream & operator << (std::ostream & os , const LogContext & lc); + +} // namespace log +} // namespace castor diff --git a/common/log/LogContextTest.cpp b/common/log/LogContextTest.cpp new file mode 100644 index 0000000000..570ca17a52 --- /dev/null +++ b/common/log/LogContextTest.cpp @@ -0,0 +1,72 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common/log/DummyLogger.hpp" +#include "common/log/StringLogger.hpp" +#include "common/log/LogContext.hpp" + +#include <gtest/gtest.h> + +using namespace cta::log; + +namespace unitTests { + TEST(cta_log_LogContextTest, additionScopedRemove) { + DummyLogger dl("castor_log_LogContextTest"); + LogContext lc(dl); + lc.pushOrReplace(Param("MigrationRequestId", 123)); + ASSERT_EQ(1U, lc.size()); + { + // Create an anonymous variable (for its scope) + LogContext::ScopedParam sp(lc, Param("NSFILEID", 12345)); + ASSERT_EQ(2U, lc.size()); + lc.log(LOG_DEBUG, "Two params message"); + { + // Test that we do not allow duplicate params + LogContext::ScopedParam sp(lc, Param("NSFILEID", 123456)); + ASSERT_EQ(2U, lc.size()); + LogContext::ScopedParam sp2(lc, Param("TPVID", "T1234")); + ASSERT_EQ(3U, lc.size()); + } + } + ASSERT_EQ(1U, lc.size()); + lc.log(LOG_DEBUG, "One param message"); + lc.erase("MigrationRequestId"); + ASSERT_EQ(0U, lc.size()); + } + + TEST(cta_log_LogContextTest, paramsFound) { + StringLogger sl ("castor_log_LogContextTest"); + LogContext lc(sl); + lc.pushOrReplace(Param("MigrationRequestId", 123)); + lc.log(LOG_INFO, "First log"); + std::string first = sl.getLog(); + ASSERT_NE(std::string::npos, first.find("MigrationRequestId")); + { + LogContext::ScopedParam sp(lc, Param("NSFILEID", 12345)); + lc.log(LOG_INFO, "Second log"); + } + std::string second = sl.getLog(); + ASSERT_NE(std::string::npos, second.find("NSFILEID")); + // We expect the NSFILEID parameter to show up only once (i.e, not after + // offset, which marks the end of its first occurrence). + lc.log(LOG_INFO, "Third log"); + std::string third = sl.getLog(); + size_t offset = third.find("NSFILEID") + strlen("NSFILEID"); + ASSERT_EQ(std::string::npos, third.find("NSFILEID", offset)); + } +} diff --git a/common/log/Logger.cpp b/common/log/Logger.cpp new file mode 100644 index 0000000000..3221f44fdc --- /dev/null +++ b/common/log/Logger.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Interface to the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/Logger.hpp" + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::log::Logger::Logger(const std::string &programName): + m_programName(programName) { +} + +//------------------------------------------------------------------------------ +// getProgramName +//------------------------------------------------------------------------------ +const std::string &cta::log::Logger::getProgramName() const { + return m_programName; +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::log::Logger::~Logger() { +} diff --git a/common/log/Logger.hpp b/common/log/Logger.hpp new file mode 100644 index 0000000000..0cc2ef7ddc --- /dev/null +++ b/common/log/Logger.hpp @@ -0,0 +1,293 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +// Include Files +#include "common/log/Param.hpp" +#include "common/exception/Exception.hpp" + +#include <list> +#include <map> +#include <pthread.h> +#include <syslog.h> +#include <sys/time.h> +#include <vector> + +/** + * It is a convention of CTA to use syslog level of LOG_NOTICE to label + * user errors. This macro helps enforce that convention and document it in + * the code. + */ +#define LOG_USERERR LOG_NOTICE + +namespace cta { namespace log { + +/** + * Abstract class representing the API of the CASTOR logging system. + * + * The intended way to use the CASTOR logging API is as follows: + * + * 1. Keep a reference to a Logger object, for example: + * \code{.cpp} + * + * class MyClassThatWillLog { + * protected: + * Logger & m_log; + * + * public: + * MyClassThatWillLog(Logger &log): m_log(log) { + * .... + * } + * } + * + * \endcode + * + * 2. To log a message, use the reference to the Logger object like a function. + * In other words the Logger object implements operator() and therefore + * behaves like a functor: + * \code{.cpp} + * + * void MyClassThatWillLog::aMethodThatWillLog() { + * .... + * m_log(LOG_INFO, "My log message"); + * .... + * } + * + * \endcode + * + * The Logger object implements operator() in order to avoid the following long + * winded syntax (which does not work by the way, so please do NOT copy and + * paste the following example): + * \code{.cpp} + * + * m_log.logMsg(LOG_INFO, "My log message"); + * + * \endcode + */ +class Logger { +public: + + CTA_GENERATE_EXCEPTION_CLASS(InvalidArgument); + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + Logger(const std::string &programName); + + /** + * Destructor. + */ + virtual ~Logger() = 0; + + /** + * Prepares the logger object for a call to fork(). + * + * No further calls to operator() should be made after calling this + * method until the call to fork() has completed. + */ + virtual void prepareForFork() = 0; + + /** + * Returns the name of the program that is to be prepended to every log + * message. + */ + const std::string &getProgramName() const; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[], + const struct timeval &timeStamp) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + */ + virtual void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[]) = 0; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + virtual void operator() ( + const int priority, + const std::string &msg) = 0; + + /** + * A template function that wraps operator() in order to get the compiler + * to automatically determine the size of the params parameter, therefore + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog + * API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + template<int numParams> void operator() ( + const int priority, + const std::string &msg, + const log::Param(¶ms)[numParams], + const struct timeval &timeStamp) throw() { + operator() (priority, msg, numParams, params, timeStamp); + } + + /** + * A template function that wraps operator() in order to get the compiler + * to automatically determine the size of the params parameter, therefore + * removing the need for the devloper to provide it explicity. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog + * API. + * @param msg the message. + * @param params the parameters of the message. + */ + template<int numParams> void operator() ( + const int priority, + const std::string &msg, + const log::Param(¶ms)[numParams]) throw() { + operator() (priority, msg, numParams, params); + } + +protected: + + /** + * The name of the program to be prepended to every log message. + */ + const std::string m_programName; + +}; // class Logger + +} // namespace log +} // namespace castor + diff --git a/common/log/Message.cpp b/common/log/Message.cpp new file mode 100644 index 0000000000..476466c7e0 --- /dev/null +++ b/common/log/Message.cpp @@ -0,0 +1,26 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Container for a CASTOR log message + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +// Include Files +#include "common/log/Message.hpp" diff --git a/common/log/Message.hpp b/common/log/Message.hpp new file mode 100644 index 0000000000..7c571cdec9 --- /dev/null +++ b/common/log/Message.hpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Container for a CASTOR log message + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +// Include Files +#include <string> + +namespace castor { + + namespace log { + + /** + * Container for a CASTOR log message + */ + struct Message { + /// Message number + int number; + /// Message text + std::string text; + }; + + } // end of namespace log + +} // end of namespace castor + diff --git a/common/log/Param.cpp b/common/log/Param.cpp new file mode 100644 index 0000000000..84b34c412a --- /dev/null +++ b/common/log/Param.cpp @@ -0,0 +1,40 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * A parameter for the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/Param.hpp" +#include <cstdio> + +//------------------------------------------------------------------------------ +// getName +//------------------------------------------------------------------------------ +const std::string &cta::log::Param::getName() const throw() { + return m_name; +} + +//------------------------------------------------------------------------------ +// getValue +//------------------------------------------------------------------------------ +const std::string &cta::log::Param::getValue() const throw() { + return m_value; +} + diff --git a/common/log/Param.hpp b/common/log/Param.hpp new file mode 100644 index 0000000000..bd6ff65b37 --- /dev/null +++ b/common/log/Param.hpp @@ -0,0 +1,108 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <sstream> +#include <string.h> +#include <cstdio> + +namespace cta { namespace log { + +/** + * A name/value parameter for the CASTOR logging system. + */ +class Param { + +public: + + /** + * Constructor. + * + * @param name The name of the parameter. + * @param value The value of the parameter that will be converted to a string + * using std::ostringstream. + */ + template <typename T> Param(const std::string &name, const T &value) throw(): + m_name(name) { + std::ostringstream oss; + oss << value; + m_value = oss.str(); + } + + /** + * Constructor. + * + * @param name The name of the parameter. + * @param value The value of the parameter that will be converted to a string + * using snprintf for doubles + */ + Param (const std::string &name, const double value) throw(): + m_name(name) { + char buf[100]; + std::snprintf(buf, sizeof(buf), "%f", value); + // Just in case we overflow + buf[sizeof(buf)-1]='\0'; + m_value = buf; + } + + /** + * Value changer. Useful for log contexts. + * @param value + */ + template <typename T> + void setValue (const T &value) throw() { + std::stringstream oss; + oss << value; + m_value = oss.str(); + } + + /** + * Returns a const reference to the name of the parameter. + */ + const std::string &getName() const throw(); + + /** + * Returns a const reference to the value of the parameter. + */ + const std::string &getValue() const throw(); + +protected: + + /** + * Name of the parameter + */ + std::string m_name; + + /** + * The value of the parameter. + */ + std::string m_value; + +}; // class Param + +/** + * An helper class allowing the construction of a Param class with sprintf + * formatting for a double. + */ +class ParamDoubleSnprintf: public Param { +public: + ParamDoubleSnprintf(const std::string &name, const double value); +}; // class ParamDoubleSnprintf + +}} //namespace cta::log diff --git a/common/log/ParamTest.cpp b/common/log/ParamTest.cpp new file mode 100644 index 0000000000..549a7fe228 --- /dev/null +++ b/common/log/ParamTest.cpp @@ -0,0 +1,59 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/Param.hpp" + +#include <gtest/gtest.h> +#include <memory> + +namespace unitTests { + +class cta_log_ParamTest: public ::testing::Test { +protected: + + void SetUp() { + } + + void TearDown() { + } +}; // cta_log_ParamTest + +TEST_F(cta_log_ParamTest, testConstructorWithAString) { + using namespace cta::log; + std::unique_ptr<Param> param; + + ASSERT_NO_THROW(param.reset(new Param("Name", "Value"))); + ASSERT_EQ(std::string("Name"), param->getName()); + ASSERT_EQ(std::string("Value"), param->getValue()); +} + +TEST_F(cta_log_ParamTest, testConstructorWithAnInt) { + using namespace cta::log; + std::unique_ptr<Param> param; + + ASSERT_NO_THROW(param.reset(new Param("Name", 1234))); + ASSERT_EQ(std::string("Name"), param->getName()); + ASSERT_EQ(std::string("1234"), param->getValue()); +} + +} // namespace unitTests diff --git a/common/log/StringLogger.cpp b/common/log/StringLogger.cpp new file mode 100644 index 0000000000..10726b061c --- /dev/null +++ b/common/log/StringLogger.cpp @@ -0,0 +1,324 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Interface to the CASTOR logging system, with string output. + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/StringLogger.hpp" +#include "common/utils/Utils.hpp" + +#include <errno.h> +#include <sstream> +#include <stdlib.h> +#include <string.h> +#include <sys/un.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::log::StringLogger::StringLogger( + const std::string &programName): + Logger(programName), + m_maxMsgLen(determineMaxMsgLen()), + m_priorityToText(generatePriorityToTextMap()) { + initMutex(); +} + +//------------------------------------------------------------------------------ +// determineMaxMsgLen +//------------------------------------------------------------------------------ +size_t cta::log::StringLogger::determineMaxMsgLen() const { + return DEFAULT_SYSLOG_MSGLEN; +} + +//------------------------------------------------------------------------------ +// generatePriorityToTextMap +//------------------------------------------------------------------------------ +std::map<int, std::string> + cta::log::StringLogger::generatePriorityToTextMap() const + { + std::map<int, std::string> m; + + try { + m[LOG_EMERG] = "Emerg"; + m[LOG_ALERT] = "Alert"; + m[LOG_CRIT] = "Crit"; + m[LOG_ERR] = "Error"; + m[LOG_WARNING] = "Warn"; + m[LOG_NOTICE] = "Notice"; + m[LOG_INFO] = "Info"; + m[LOG_DEBUG] = "Debug"; + } catch(std::exception &se) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to generate priority to text mapping: " << + se.what(); + throw ex; + } + + return m; +} + +//------------------------------------------------------------------------------ +// initMutex +//------------------------------------------------------------------------------ +void cta::log::StringLogger::initMutex() { + pthread_mutexattr_t attr; + int rc = pthread_mutexattr_init(&attr); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to initialize mutex attribute for m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to set mutex type of m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutex_init(&m_mutex, NULL); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to initialize m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutexattr_destroy(&attr); + if(0 != rc) { + pthread_mutex_destroy(&m_mutex); + cta::exception::Exception ex; + ex.getMessage() << "Failed to destroy mutex attribute of m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::log::StringLogger::~StringLogger() { +} + +//------------------------------------------------------------------------------ +// prepareForFork +//------------------------------------------------------------------------------ +void cta::log::StringLogger::prepareForFork() { +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp) { + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp) { + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[], + const struct timeval &timeStamp) { + operator() (priority, msg, params, params+numParams, timeStamp); +} + +//----------------------------------------------------------------------------- +// buildSyslogHeader +//----------------------------------------------------------------------------- +std::string cta::log::StringLogger::buildSyslogHeader( + const int priority, + const struct timeval &timeStamp, + const int pid) const throw() { + char buf[80]; + int bufLen = sizeof(buf); + int len = 0; + std::ostringstream oss; + + oss << "<" << priority << ">"; + + struct tm localTime; + localtime_r(&(timeStamp.tv_sec), &localTime); + len += strftime(buf, bufLen, "%Y-%m-%dT%T", &localTime); + len += snprintf(buf + len, bufLen - len, ".%06ld", + (unsigned long)timeStamp.tv_usec); + len += strftime(buf + len, bufLen - len, "%z: ", &localTime); + // dirty trick to have the proper timezone format (':' between hh and mm) + buf[len-2] = buf[len-3]; + buf[len-3] = buf[len-4]; + buf[len-4] = ':'; + buf[sizeof(buf) - 1] = '\0'; + oss << buf << m_programName.c_str() << "[" << pid << "]: "; + return oss.str(); +} + +//----------------------------------------------------------------------------- +// cleanString +//----------------------------------------------------------------------------- +std::string cta::log::StringLogger::cleanString(const std::string &s, + const bool replaceSpaces) { + + //find first non white char + const std::string& spaces="\t\n\v\f\r "; + size_t beginpos = s.find_first_not_of(spaces); + std::string::const_iterator it1; + if (std::string::npos != beginpos) + it1 = beginpos + s.begin(); + else + it1 = s.begin(); + + //find last non white char + std::string::const_iterator it2; + size_t endpos = s.find_last_not_of(spaces); + if (std::string::npos != endpos) + it2 = endpos + 1 + s.begin(); + else + it2 = s.end(); + + std::string result(it1, it2); + +// if (s.begin() == it1 && it2 == s.end()) +// result=""; + + for (std::string::iterator it = result.begin(); it != result.end(); ++it) { + + // Replace newline and tab with a space + if (replaceSpaces) { + if ('\t' == *it) + *it = ' '; + + if ('\n' == *it) + *it = ' '; + + // Replace spaces with underscore + if (' ' == *it) + *it = '_'; + } + // Replace double quotes with single quotes + if ('"' == *it) + *it = '\''; + } + return result; + +} + +//----------------------------------------------------------------------------- +// reducedSyslog +//----------------------------------------------------------------------------- +void cta::log::StringLogger::reducedSyslog(std::string msg) { + // Truncate the log message if it exceeds the permitted maximum + if(msg.length() > m_maxMsgLen) { + msg.resize(m_maxMsgLen); + msg[msg.length() - 1] = '\n'; + } + + // enter critical section + const int mutex_lock_rc = pthread_mutex_lock(&m_mutex); + // Do nothing if we failed to enter the critical section + if(0 != mutex_lock_rc) { + return; + } + + // Append the message to the log + m_log << msg << std::endl; + + // Temporary hack: also print them out: + printf (msg.c_str()); + + // Leave critical section. + pthread_mutex_unlock(&m_mutex); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[]) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, numParams, params, timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::StringLogger::operator() ( + const int priority, + const std::string &msg) { + + Param *emptyParams = NULL; + operator() (priority, msg, 0, emptyParams); +} diff --git a/common/log/StringLogger.hpp b/common/log/StringLogger.hpp new file mode 100644 index 0000000000..bb58ffb706 --- /dev/null +++ b/common/log/StringLogger.hpp @@ -0,0 +1,385 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/log/Logger.hpp" + +#include <map> +#include <pthread.h> +#include <syslog.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#ifdef __APPLE__ +#include <mach/mach.h> +#endif + +namespace cta { namespace log { + +/** + * Class implementing the API of the CASTOR logging system. + */ +class StringLogger: public Logger { +public: + + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + StringLogger(const std::string &programName); + + /** + * Destructor. + */ + ~StringLogger(); + + /** + * Prepares the logger object for a call to fork(). + * + * No further calls to operator() should be made after calling this + * method until the call to fork() has completed. + */ + void prepareForFork() ; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[], + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[]); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + void operator() ( + const int priority, + const std::string &msg); + + /** + * Extractor for the resulting logs. + */ + + std::string getLog() { return m_log.str(); } + +private: + + /** + * Default size of a syslog message. + */ + static const size_t DEFAULT_SYSLOG_MSGLEN = 1024; + + /** + * Default size of a rsyslog message. + */ + static const size_t DEFAULT_RSYSLOG_MSGLEN = 2000; + + /** + * Maximum length of a parameter name. + */ + static const size_t LOG_MAX_PARAMNAMELEN = 20; + + /** + * Maximum length of a string value. + */ + static const size_t LOG_MAX_PARAMSTRLEN = 1024; + + /** + * Maximum length of a log message. + */ + static const size_t LOG_MAX_LINELEN = 8192; + + /** + * The maximum message length that the client syslog server can handle. + */ + const size_t m_maxMsgLen; + + /** + * Mutex used to protect the critical section of the StringLogger + * object. + */ + pthread_mutex_t m_mutex; + + /** + * The file descriptor of the socket used to send messages to syslog. + */ + std::stringstream m_log; + + /** + * Map from syslog integer priority to textual representation. + */ + const std::map<int, std::string> m_priorityToText; + + /** + * Determines the maximum message length that the client syslog server can + * handle. + * + * @return The maximum message length that the client syslog server can + * handle. + */ + size_t determineMaxMsgLen() const; + + /** + * Generates and returns the mapping between syslog priorities and their + * textual representations. + */ + std::map<int, std::string> generatePriorityToTextMap() const + ; + + /** + * Initializes the mutex used to protect the critical section of the + * StringLogger object. + */ + void initMutex() ; + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param paramsBegin the first parameters of the message. + * @param paramsEnd one past the end of the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + template<typename ParamIterator> void operator() ( + const int priority, + const std::string &msg, + ParamIterator paramsBegin, + ParamIterator paramsEnd, + const struct timeval &timeStamp) throw() { + //------------------------------------------------------------------------- + // Note that we do here part of the work of the real syslog call, by + // building the message ourselves. We then only call a reduced version of + // syslog (namely reducedSyslog). The reason behind it is to be able to set + // the message timestamp ourselves, in case we log messages asynchronously, + // as we do when retrieving logs from the DB + //------------------------------------------------------------------------- + + // Try to find the textual representation of the syslog priority + std::map<int, std::string>::const_iterator priorityTextPair = + m_priorityToText.find(priority); + + // Do nothing if the log priority is not valid + if(m_priorityToText.end() == priorityTextPair) { + return; + } + + // Safe to get a reference to the textual representation of the priority + const std::string &priorityText = priorityTextPair->second; + + std::ostringstream logMsg; + + // Start message with priority, time, program and PID (syslog standard + // format) + logMsg << buildSyslogHeader(priority | LOG_LOCAL3, timeStamp, getpid()); + + // Determine the thread id +#ifdef __APPLE__ + const int tid = mach_thread_self(); +#else + const int tid = syscall(__NR_gettid); +#endif + + // Append the log level, the thread id and the message text + logMsg << "LVL=" << priorityText << " TID=" << tid << " MSG=\"" << msg << + "\" "; + + // Process parameters + for(ParamIterator itor = paramsBegin; itor != paramsEnd; itor++) { + const Param ¶m = *itor; + + // Check the parameter name, if it's an empty string set the value to + // "Undefined". + const std::string name = param.getName() == "" ? "Undefined" : + cleanString(param.getName(), true); + + // Process the parameter value + const std::string value = cleanString(param.getValue(), false); + + // Write the name and value to the buffer + logMsg << name << "=\"" << value << "\" "; + } + + // Terminate the string + logMsg << "\n"; + + reducedSyslog(logMsg.str()); + } + + /** + * Build the header of a syslog message. + * + * @param priority The priority of the message. + * @param timeStamp The time stamp of the message. + * @param pid The process ID of the process logging the message. + * @return The header of the syslog message. + */ + std::string buildSyslogHeader( + const int priority, + const struct timeval &timeStamp, + const int pid) const throw(); + + /** + * Creates a clean version of the specified string ready for use with syslog. + * + * @param s The string to be cleaned. + * @param replaceSpaces Set to true if spaces should be replaced by + * underscores. + * @return A cleaned version of the string. + */ + std::string cleanString(const std::string &s, const bool replaceSpaces); + + /** + * A reduced version of syslog. This method is able to set the message + * timestamp. This is necessary when logging messages asynchronously of there + * creation, such as when retrieving logs from the DB. + * + * @param msg The message to be logged. + */ + void reducedSyslog(std::string msg); + +}; // class StringLogger + +} // namespace log +} // namespace castor + + + + diff --git a/common/log/StringLoggerTest.cpp b/common/log/StringLoggerTest.cpp new file mode 100644 index 0000000000..34adf5e70d --- /dev/null +++ b/common/log/StringLoggerTest.cpp @@ -0,0 +1,37 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Interface to the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "StringLogger.hpp" + +#include <gtest/gtest.h> + +using namespace castor::log; + +namespace unitTests { + TEST(castor_log_StringLogger, basicTest) { + std::string jat = "Just a test"; + StringLogger sl("castor_log_StringLogger"); + sl(LOG_INFO, jat); + ASSERT_NE(std::string::npos, sl.getLog().find(jat)); + } +} diff --git a/common/log/SyslogLogger.cpp b/common/log/SyslogLogger.cpp new file mode 100644 index 0000000000..99c23c20fe --- /dev/null +++ b/common/log/SyslogLogger.cpp @@ -0,0 +1,493 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Interface to the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/SyslogLogger.hpp" +#include "common/utils/Utils.hpp" +#include "common/Configuration.hpp" + +#include <errno.h> +#include <sstream> +#include <stdlib.h> +#include <string.h> +#include <sys/un.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/syscall.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::log::SyslogLogger::SyslogLogger( + const std::string &programName): + Logger(programName), + m_maxMsgLen(determineMaxMsgLen()), + m_logFile(-1), + m_priorityToText(generatePriorityToTextMap()), + m_configTextToPriority(generateConfigTextToPriorityMap()) { + initMutex(); +} + +//------------------------------------------------------------------------------ +// determineMaxMsgLen +//------------------------------------------------------------------------------ +size_t cta::log::SyslogLogger::determineMaxMsgLen() const{ + size_t msgSize = 0; + // Determine the size automatically, this is not guaranteed to work! + FILE *const fp = fopen("/etc/rsyslog.conf", "r"); + if(fp) { + char buffer[1024]; + + // The /etc/rsyslog.conf file exists so we assume the default message + // size of 2K. + msgSize = DEFAULT_RSYSLOG_MSGLEN; + + // In rsyslog versions >= 3.21.4, the maximum size of a message became + // configurable through the $MaxMessageSize global config directive. + // Here we attempt to find out if the user has increased the size! + while(fgets(buffer, sizeof(buffer), fp) != NULL) { + if(strncasecmp(buffer, "$MaxMessageSize", 15)) { + continue; // Option not of interest + } + msgSize = atol(&buffer[15]); + } + fclose(fp); + } + // If the /etc/rsyslog.conf file is missing which implies that we are + // running on a stock syslogd system, therefore the message size is + // governed by the syslog RFC: http://www.faqs.org/rfcs/rfc3164.html + + // Check that the size of messages falls within acceptable limits + if((msgSize >= DEFAULT_SYSLOG_MSGLEN) && (msgSize <= LOG_MAX_LINELEN)) { + return msgSize; + } else { + return DEFAULT_SYSLOG_MSGLEN; + } +} + +//------------------------------------------------------------------------------ +// generatePriorityToTextMap +//------------------------------------------------------------------------------ +std::map<int, std::string> + cta::log::SyslogLogger::generatePriorityToTextMap() const { + std::map<int, std::string> m; + + try { + m[LOG_EMERG] = "Emerg"; + m[LOG_ALERT] = "Alert"; + m[LOG_CRIT] = "Crit"; + m[LOG_ERR] = "Error"; + m[LOG_WARNING] = "Warn"; + m[LOG_NOTICE] = "Notice"; + m[LOG_INFO] = "Info"; + m[LOG_DEBUG] = "Debug"; + } catch(std::exception &se) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to generate priority to text mapping: " << + se.what(); + throw ex; + } + + return m; +} + +//------------------------------------------------------------------------------ +// generateConfigTextToPriorityMap +//------------------------------------------------------------------------------ +std::map<std::string, int> + cta::log::SyslogLogger::generateConfigTextToPriorityMap() const { + std::map<std::string, int> m; + + try { + m["LOG_EMERG"] = LOG_EMERG; + m["LOG_ALERT"] = LOG_ALERT; + m["LOG_CRIT"] = LOG_CRIT; + m["LOG_ERR"] = LOG_ERR; + m["LOG_WARNING"] = LOG_WARNING; + m["LOG_NOTICE"] = LOG_NOTICE; + m["LOG_INFO"] = LOG_INFO; + m["LOG_DEBUG"] = LOG_DEBUG; + } catch(std::exception &se) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to generate configuration text to priority mapping: " << + se.what(); + throw ex; + } + + return m; +} + +//------------------------------------------------------------------------------ +// initMutex +//------------------------------------------------------------------------------ +void cta::log::SyslogLogger::initMutex() { + pthread_mutexattr_t attr; + int rc = pthread_mutexattr_init(&attr); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to initialize mutex attribute for m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to set mutex type of m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutex_init(&m_mutex, NULL); + if(0 != rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to initialize m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } + rc = pthread_mutexattr_destroy(&attr); + if(0 != rc) { + pthread_mutex_destroy(&m_mutex); + cta::exception::Exception ex; + ex.getMessage() << "Failed to destroy mutex attribute of m_mutex: " << + Utils::errnoToString(rc); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::log::SyslogLogger::~SyslogLogger() { + closeLog(); +} + +//------------------------------------------------------------------------------ +// prepareForFork +//------------------------------------------------------------------------------ +void cta::log::SyslogLogger::prepareForFork() { + // Enter critical section + { + const int mutex_lock_rc = pthread_mutex_lock(&m_mutex); + if(0 != mutex_lock_rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to lock mutex of logger's critcial section: " + << Utils::errnoToString(mutex_lock_rc); + throw ex; + } + } + + closeLog(); + + // Leave critical section. + { + const int mutex_unlock_rc = pthread_mutex_unlock(&m_mutex); + if(0 != mutex_unlock_rc) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to unlock mutex of logger's critcial section: " + << Utils::errnoToString(mutex_unlock_rc); + throw ex; + } + } +} + +//------------------------------------------------------------------------------ +// openLog +//------------------------------------------------------------------------------ +void cta::log::SyslogLogger::openLog() { + if(-1 != m_logFile) { + return; + } + + { + struct sockaddr_un syslogAddr; + syslogAddr.sun_family = AF_UNIX; + strncpy(syslogAddr.sun_path, _PATH_LOG, sizeof(syslogAddr.sun_path)); + m_logFile = socket(AF_UNIX, SOCK_DGRAM, 0); + } + if(-1 == m_logFile) { + return; + } + + if(-1 == fcntl(m_logFile, F_SETFD, FD_CLOEXEC)) { + close(m_logFile); + m_logFile = -1; + return; + } + + { + struct sockaddr_un syslogAddr; + syslogAddr.sun_family = AF_UNIX; + strncpy(syslogAddr.sun_path, _PATH_LOG, sizeof(syslogAddr.sun_path)); + if(-1 == connect(m_logFile, (struct sockaddr *)&syslogAddr, + sizeof(syslogAddr))) { + close(m_logFile); + m_logFile = -1; + return; + } + } + +#ifdef __APPLE__ + { + // MAC has has no MSG_NOSIGNAL + // but >= 10.2 comes with SO_NOSIGPIPE + int set = 1; + if(0 != setsockopt(m_logFile, SOL_SOCKET, SO_NOSIGPIPE, &set, + sizeof(int))) { + close(m_logFile); + m_logFile = -1; + return; + } + } +#endif +} + +//------------------------------------------------------------------------------ +// closeLog +//------------------------------------------------------------------------------ +void cta::log::SyslogLogger::closeLog() { + if(-1 == m_logFile) { + return; + } + close(m_logFile); + m_logFile = -1; +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp) { + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp) { + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[], + const struct timeval &timeStamp) { + operator() (priority, msg, params, params+numParams, timeStamp); +} + +//----------------------------------------------------------------------------- +// buildSyslogHeader +//----------------------------------------------------------------------------- +std::string cta::log::SyslogLogger::buildSyslogHeader( + const int priority, + const struct timeval &timeStamp, + const int pid) const { + char buf[80]; + int bufLen = sizeof(buf); + int len = 0; + std::ostringstream oss; + + oss << "<" << priority << ">"; + + struct tm localTime; + localtime_r(&(timeStamp.tv_sec), &localTime); + len += strftime(buf, bufLen, "%Y-%m-%dT%T", &localTime); + len += snprintf(buf + len, bufLen - len, ".%06ld", + (unsigned long)timeStamp.tv_usec); + len += strftime(buf + len, bufLen - len, "%z: ", &localTime); + // dirty trick to have the proper timezone format (':' between hh and mm) + buf[len-2] = buf[len-3]; + buf[len-3] = buf[len-4]; + buf[len-4] = ':'; + buf[sizeof(buf) - 1] = '\0'; + oss << buf << m_programName.c_str() << "[" << pid << "]: "; + return oss.str(); +} + +//----------------------------------------------------------------------------- +// cleanString +//----------------------------------------------------------------------------- +std::string cta::log::SyslogLogger::cleanString(const std::string &s, + const bool replaceUnderscores) const { + // Trim both left and right white-space + std::string result = Utils::trimString(s); + + for (std::string::iterator it = result.begin(); it != result.end(); ++it) { + + // Replace double quote with single quote + if ('"' == *it) { + *it = '\''; + } + + // Replace newline and tab with a space + if ('\t' == *it || '\n' == *it) { + *it = ' '; + } + + // If requested, replace spaces with underscores + if(replaceUnderscores && ' ' == *it) { + *it = '_'; + } + } + + return result; +} + +//----------------------------------------------------------------------------- +// reducedSyslog +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::reducedSyslog(std::string msg) { + // Truncate the log message if it exceeds the permitted maximum + if(msg.length() > m_maxMsgLen) { + msg.resize(m_maxMsgLen); + msg[msg.length() - 1] = '\n'; + } + + int send_flags = 0; +#ifndef __APPLE__ + // MAC has has no MSG_NOSIGNAL + // but >= 10.2 comes with SO_NOSIGPIPE + send_flags = MSG_NOSIGNAL; +#endif + // enter critical section + const int mutex_lock_rc = pthread_mutex_lock(&m_mutex); + // Do nothing if we failed to enter the critical section + if(0 != mutex_lock_rc) { + return; + } + + // Try to connect if not already connected + openLog(); + + // If connected + if(-1 != m_logFile) { + // If sending the log message fails then try to reopen the syslog + // connection and try again + if(0 > send(m_logFile, msg.c_str(), msg.length(), send_flags)) { + closeLog(); + openLog(); + if (-1 != m_logFile) { + // If the second attempt to send the log message fails then give up and + // attempt re-open next time + if(0 > send(m_logFile, msg.c_str(), msg.length(), send_flags)) { + closeLog(); + } + } + } + } + + // Leave critical section. + pthread_mutex_unlock(&m_mutex); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, params.begin(), params.end(), timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg, + const int numParams, + const log::Param params[]) { + + struct timeval timeStamp; + gettimeofday(&timeStamp, NULL); + + operator() (priority, msg, numParams, params, timeStamp); +} + +//----------------------------------------------------------------------------- +// operator() +//----------------------------------------------------------------------------- +void cta::log::SyslogLogger::operator() ( + const int priority, + const std::string &msg) { + + Param *emptyParams = NULL; + operator() (priority, msg, 0, emptyParams); +} + +//------------------------------------------------------------------------------ +// logMask +//------------------------------------------------------------------------------ +int cta::log::SyslogLogger::logMask() const { + cta::common::Configuration conf; + const std::string confEnt = conf.getConfEntString("LogMask", m_programName, 0); + + // If the configuration file defines the log mask to use + if (!confEnt.empty()) { + // Try to find the corresponding integer priority value + std::map<std::string, int>::const_iterator itor = + m_configTextToPriority.find(confEnt); + + // Return the priority if it was found else return the default INFO level + if(m_configTextToPriority.end() != itor) { + return itor->second; + } else { + return LOG_INFO; + } + } + + // If the priority wasn't found, default is INFO level + return LOG_INFO; +} diff --git a/common/log/SyslogLogger.hpp b/common/log/SyslogLogger.hpp new file mode 100644 index 0000000000..76f58afe69 --- /dev/null +++ b/common/log/SyslogLogger.hpp @@ -0,0 +1,474 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * Interface to the CASTOR logging system + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +#include "common/log/Logger.hpp" + +#include <map> +#include <pthread.h> +#include <syslog.h> +#include <sys/syscall.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#ifdef __APPLE__ +#include <mach/mach.h> +#endif + +namespace cta { namespace log { + +/** + * Class implementaing the API of the CASTOR logging system. + */ +class SyslogLogger: public Logger { +public: + + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + SyslogLogger(const std::string &programName); + + /** + * Destructor. + */ + ~SyslogLogger(); + + /** + * Prepares the logger object for a call to fork(). + * + * No further calls to operator() should be made after calling this + * method until the call to fork() has completed. + */ + void prepareForFork(); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms, + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[], + const struct timeval &timeStamp); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::vector<Param> ¶ms); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const std::list<Param> ¶ms); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param numParams the number of parameters in the message. + * @param params the parameters of the message. + */ + void operator() ( + const int priority, + const std::string &msg, + const int numParams, + const Param params[]); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + */ + void operator() ( + const int priority, + const std::string &msg); + + /** + * A template function that wraps operator() in order to get the compiler + * to automatically determine the size of the params parameter, therefore + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog + * API. + * @param msg the message. + * @param params the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + template<int numParams> void operator() ( + const int priority, + const std::string &msg, + cta::log::Param(¶ms)[numParams], + const struct timeval &timeStamp) { + operator() (priority, msg, numParams, params, timeStamp); + } + + /** + * A template function that wraps operator() in order to get the compiler + * to automatically determine the size of the params parameter, therefore + * removing the need for the devloper to provide it explicity. + * + * Note that this version of operator() implicitly uses the current time as + * the time stamp of the message. + * + * @param priority the priority of the message as defined by the syslog + * API. + * @param msg the message. + * @param params the parameters of the message. + */ + template<int numParams> void operator() ( + const int priority, + const std::string &msg, + cta::log::Param(¶ms)[numParams]) { + operator() (priority, msg, numParams, params); + } + +protected: + + /** + * Default size of a syslog message. + */ + static const size_t DEFAULT_SYSLOG_MSGLEN = 1024; + + /** + * Default size of a rsyslog message. + */ + static const size_t DEFAULT_RSYSLOG_MSGLEN = 2000; + + /** + * Maximum length of a parameter name. + */ + static const size_t LOG_MAX_PARAMNAMELEN = 20; + + /** + * Maximum length of a string value. + */ + static const size_t LOG_MAX_PARAMSTRLEN = 1024; + + /** + * Maximum length of a log message. + */ + static const size_t LOG_MAX_LINELEN = 8192; + + /** + * The maximum message length that the client syslog server can handle. + */ + const size_t m_maxMsgLen; + + /** + * Mutex used to protect the critical section of the SyslogLogger + * object. + */ + pthread_mutex_t m_mutex; + + /** + * The file descriptor of the socket used to send messages to syslog. + */ + int m_logFile; + + /** + * Map from syslog integer priority to textual representation. + */ + const std::map<int, std::string> m_priorityToText; + + /** + * Map from the possible string values of the LogMask parameters of + * /etc/castor.conf and their equivalent syslog priorities. + */ + const std::map<std::string, int> m_configTextToPriority; + + /** + * Returns the log mask for the program. + */ + int logMask() const ; + + /** + * Determines the maximum message length that the client syslog server can + * handle. + * + * @return The maximum message length that the client syslog server can + * handle. + */ + size_t determineMaxMsgLen() const; + + /** + * Generates and returns the mapping between syslog priorities and their + * textual representations. + */ + std::map<int, std::string> generatePriorityToTextMap() const + ; + + /** + * Generates and returns the mapping between the possible string values + * of the LogMask parameters of /etc/castor.conf and their equivalent + * syslog priorities. + */ + std::map<std::string, int> generateConfigTextToPriorityMap() const + ; + + /** + * Initializes the mutex used to protect the critical section of the + * SyslogLogger object. + */ + void initMutex() ; + + /** + * Connects to syslog. + * + * Please note that this method must be called from within the critical + * section of the SyslogLogger object. + * + * If the connection with syslog is already open then this method does + * nothing. + * + * This method does not throw an exception if the connection cannot be made + * to syslog. In this case the internal state of the SyslogLogger + * object reflects the fact that no connection was made. + */ + void openLog(); + + /** + * Writes a message into the CASTOR logging system. Note that no exception + * will ever be thrown in case of failure. Failures will actually be + * silently ignored in order to not impact the processing. + * + * Note that this version of operator() allows the caller to specify the + * time stamp of the log message. + * + * @param priority the priority of the message as defined by the syslog API. + * @param msg the message. + * @param paramsBegin the first parameters of the message. + * @param paramsEnd one past the end of the parameters of the message. + * @param timeStamp the time stamp of the log message. + */ + template<typename ParamIterator> void operator() ( + const int priority, + const std::string &msg, + ParamIterator paramsBegin, + ParamIterator paramsEnd, + const struct timeval &timeStamp) { + //------------------------------------------------------------------------- + // Note that we do here part of the work of the real syslog call, by + // building the message ourselves. We then only call a reduced version of + // syslog (namely reducedSyslog). The reason behind it is to be able to set + // the message timestamp ourselves, in case we log messages asynchronously, + // as we do when retrieving logs from the DB + //------------------------------------------------------------------------- + + // Ignore messages whose priority is not of interest + if(priority > logMask()) { + return; + } + + // Try to find the textual representation of the syslog priority + std::map<int, std::string>::const_iterator priorityTextPair = + m_priorityToText.find(priority); + + // Do nothing if the log priority is not valid + if(m_priorityToText.end() == priorityTextPair) { + return; + } + + // Safe to get a reference to the textual representation of the priority + const std::string &priorityText = priorityTextPair->second; + + std::ostringstream logMsg; + + // Start message with priority, time, program and PID (syslog standard + // format) + logMsg << buildSyslogHeader(priority | LOG_LOCAL3, timeStamp, getpid()); + + // Determine the thread id +#ifdef __APPLE__ + const int tid = mach_thread_self(); +#else + const int tid = syscall(__NR_gettid); +#endif + + // Append the log level, the thread id and the message text + logMsg << "LVL=" << priorityText << " TID=" << tid << " MSG=\"" << msg << + "\" "; + + // Process parameters + for(ParamIterator itor = paramsBegin; itor != paramsEnd; itor++) { + const Param ¶m = *itor; + + // Check the parameter name, if it's an empty string set the value to + // "Undefined". + const std::string name = (param.getName() == "" ? "Undefined" : + cleanString(param.getName(), true)); + + // Process the parameter value + const std::string value = cleanString(param.getValue(), false); + + // Write the name and value to the buffer + logMsg << name << "=\"" << value << "\" "; + } + + // Terminate the string + logMsg << "\n"; + + reducedSyslog(logMsg.str()); + } + + /** + * Build the header of a syslog message. + * + * @param priority The priority of the message. + * @param timeStamp The time stamp of the message. + * @param pid The process ID of the process logging the message. + * @return The header of the syslog message. + */ + std::string buildSyslogHeader( + const int priority, + const struct timeval &timeStamp, + const int pid) const ; + + /** + * Creates a clean version of the specified string ready for use with syslog. + * + * @param s The string to be cleaned. + * @param replaceUnderscores Set to true if spaces should be replaced by + * underscores. + * @return A cleaned version of the string. + */ + std::string cleanString(const std::string &s, const bool replaceUnderscores) + const ; + + /** + * A reduced version of syslog. This method is able to set the message + * timestamp. This is necessary when logging messages asynchronously of there + * creation, such as when retrieving logs from the DB. + * + * @param msg The message to be logged. + */ + void reducedSyslog(std::string msg); + + /** + * Closes the connection to syslog. + * + * Please note that this method must be called from within the critical + * section of the SyslogLogger object. + * + * If the connection to syslog is already closed then this method does + * nothing. + */ + void closeLog(); + +}; // class SyslogLogger + +} // namespace log +} // namespace castor + diff --git a/common/log/SyslogLoggerTest.cpp b/common/log/SyslogLoggerTest.cpp new file mode 100644 index 0000000000..62a55502c3 --- /dev/null +++ b/common/log/SyslogLoggerTest.cpp @@ -0,0 +1,149 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/SyslogLogger.hpp" +#include "common/log/TestingSyslogLogger.hpp" + +#include <gtest/gtest.h> +#include <memory> +#include <sys/time.h> + +namespace unitTests { + +class cta_log__SyslogLoggerTest: public ::testing::Test { +public: + cta_log__SyslogLoggerTest(): m_log("unitttests") { + } +protected: + + virtual void SetUp() { + } + + virtual void TearDown() { + } + + cta::log::TestingSyslogLogger m_log; +}; // class SyslogLoggerTest + +TEST_F(cta_log__SyslogLoggerTest, logMsgParamsVectorAndTimeStamp) { + using namespace cta::log; + std::vector<Param> params; + params.push_back(Param("testParam", "value of test param")); + struct timeval timeStamp; + + ASSERT_EQ(0, gettimeofday(&timeStamp, NULL)); + + ASSERT_NO_THROW(m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgParamsVectorAndTimeStamp", + params, + timeStamp)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsgParamsListAndTimeStamp) { + using namespace cta::log; + std::list<Param> params; + params.push_back(Param("testParam", "value of test param")); + struct timeval timeStamp; + + ASSERT_EQ(0, gettimeofday(&timeStamp, NULL)); + + ASSERT_NO_THROW(m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgParamsListAndTimeStamp", + params, + timeStamp)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsgParamsArrayAndTimeStamp) { + using namespace cta::log; + const int numParams = 1; + const Param params[1] = {Param("testParam", "value of test param")}; + struct timeval timeStamp; + + ASSERT_EQ(0, gettimeofday(&timeStamp, NULL)); + + ASSERT_NO_THROW(m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgParamsArrayAndTimeStamp", + numParams, + params, + timeStamp)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsgAndParamsVector) { + using namespace cta::log; + std::vector<Param> params; + params.push_back(Param("testParam", "value of test param")); + + ASSERT_NO_THROW(m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgAndParamsVector", + params)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsgAndParamsList) { + using namespace cta::log; + std::list<Param> params; + params.push_back(Param("testParam", "value of test param")); + + ASSERT_NO_THROW( + m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgAndParamsList", + params)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsgAndParamsArray) { + using namespace cta::log; + const int numParams = 1; + const Param params[1] = {Param("testParam", "value of test param")}; + + ASSERT_NO_THROW( + m_log( + LOG_INFO, + "cta_log__SyslogLoggerTest logMsgAndParamsArray", + numParams, + params)); +} + +TEST_F(cta_log__SyslogLoggerTest, logMsg) { + using namespace cta::log; + + ASSERT_NO_THROW( + m_log(LOG_INFO, "Calling logger without parameters or time stamp")); +} + +TEST_F(cta_log__SyslogLoggerTest, cleanStringWithoutReplacingUnderscores) { + const std::string s(" \t\t\n\n\"Hello there\tWorld\" \t\t\n\n"); + const std::string cleaned = m_log.cleanString(s, false); + ASSERT_EQ(std::string("'Hello there World'"), cleaned); +} + +TEST_F(cta_log__SyslogLoggerTest, cleanStringReplacingUnderscores) { + const std::string s(" \t\t\n\n\"Hello there\tWorld\" \t\t\n\n"); + const std::string cleaned = m_log.cleanString(s, true); + ASSERT_EQ(std::string("'Hello_there_World'"), cleaned); +} + +} // namespace unitTests diff --git a/common/log/TestingSyslogLogger.hpp b/common/log/TestingSyslogLogger.hpp new file mode 100644 index 0000000000..8fe0d1340a --- /dev/null +++ b/common/log/TestingSyslogLogger.hpp @@ -0,0 +1,46 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/log/SyslogLogger.hpp" + +namespace cta { namespace log { + +/** + * Class used to facilitate unit testing by making public one or more of the + * protected members of its super class. + */ +class TestingSyslogLogger: public SyslogLogger { +public: + + /** + * Constructor + * + * @param programName The name of the program to be prepended to every log + * message. + */ + TestingSyslogLogger(const std::string &programName): SyslogLogger(programName) {} + + using SyslogLogger::cleanString; + +}; // class TestingSyslogLogger + +}} // namespace cta::log + + diff --git a/common/processCap/ProcessCap.cpp b/common/processCap/ProcessCap.cpp new file mode 100644 index 0000000000..719fe028cc --- /dev/null +++ b/common/processCap/ProcessCap.cpp @@ -0,0 +1,138 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/exception/Exception.hpp" +#include "common/processCap/ProcessCap.hpp" +#include "common/processCap/SmartCap.hpp" +#include "common/utils/Utils.hpp" + +#include <errno.h> + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::server::ProcessCap::~ProcessCap() {} + +//------------------------------------------------------------------------------ +// getProcText +//------------------------------------------------------------------------------ +std::string cta::server::ProcessCap::getProcText() { + try { + SmartCap cap(getProc()); + return toText((cap_t)cap.get()); + } catch(cta::exception::Exception &ne) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to get text representation of the capabilities of the process: " + << ne.getMessage().str(); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// getProc +//------------------------------------------------------------------------------ +cap_t cta::server::ProcessCap::getProc() { + cap_t cap = cap_get_proc(); + if(NULL == cap) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to get the capabilities of the process: " + << cta::Utils::errnoToString(errno); + throw ex; + } + return cap; +} + +//------------------------------------------------------------------------------ +// toText +//------------------------------------------------------------------------------ +std::string cta::server::ProcessCap::toText( + const cap_t cap) { + // Create a C++ string with the result of calling cap_to_text() + char *const text = cap_to_text(cap, NULL); + if(NULL == text) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to create string representation of capability state: " + << cta::Utils::errnoToString(errno); + throw ex; + } + std::string result(text); + + // Free the memory allocated by cap_to_text() + if(cap_free(text)) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to free string representation of capability state: " + << cta::Utils::errnoToString(errno); + throw ex; + } + + // Return the C++ string + return result; +} + +//------------------------------------------------------------------------------ +// setProcText +//------------------------------------------------------------------------------ +void cta::server::ProcessCap::setProcText(const std::string &text) { + try { + SmartCap cap(fromText(text)); + setProc(cap.get()); + } catch(cta::exception::Exception &ne) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to set capabilities of process: " << ne.getMessage().str(); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// fromText +//------------------------------------------------------------------------------ +cap_t cta::server::ProcessCap::fromText(const std::string &text) { + const cap_t cap = cap_from_text(text.c_str()); + if(NULL == cap) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to create capability state from string representation" + ": text='" << text << "': " << cta::Utils::errnoToString(errno); + throw ex; + } + + return cap; +} + +//------------------------------------------------------------------------------ +// setProc +//------------------------------------------------------------------------------ +void cta::server::ProcessCap::setProc(const cap_t cap) { + if(cap_set_proc(cap)) { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to set the capabilities of the process: " + << cta::Utils::errnoToString(errno); + throw ex; + } +} diff --git a/common/processCap/ProcessCap.hpp b/common/processCap/ProcessCap.hpp new file mode 100644 index 0000000000..b04d920161 --- /dev/null +++ b/common/processCap/ProcessCap.hpp @@ -0,0 +1,98 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +#include <string> +#include <sys/capability.h> + +namespace cta { namespace server { + +/** + * Class providing support for UNIX capabilities. + * + * This class is used to provide support for UNIX capbilities, so that + * subclasses can be created that override its virtual member functions. + * Unit testing is the primary use-case where you may want a dummy capabilities + * object that does nothing. + * + * Please note that process capabilities are not supported on Mac OS X. + */ +class ProcessCap { +public: + + /** + * Destructor. + */ + virtual ~ProcessCap(); + + /** + * C++ wrapper around the C functions cap_get_proc() and cap_to_text(). + * + * @return The string representation the capabilities of the current + * process. + */ + virtual std::string getProcText(); + + /** + * C++ wrapper around the C functions cap_from_text() and cap_set_proc(). + * + * @text The string representation the capabilities that the current + * process should have. + */ + virtual void setProcText(const std::string &text); + +private: + + /** + * C++ wrapper around the C function cap_get_proc(). + * + * @return The capability state. + */ + cap_t getProc(); + + /** + * C++ wrapper around the C function cap_to_text(). + * + * @param cap The capability state. + */ + std::string toText(const cap_t cap); + + /** + * C++ wrapper around the C function cap_from_text(). + * + * @return The capability state. + */ + cap_t fromText(const std::string &text); + + /** + * C++ wrapper around the C function cap_set_proc(). + * + * @param cap The capability state. + */ + void setProc(const cap_t cap); + +}; // class ProcessCap + +} // namespace server +} // namespace castor diff --git a/common/processCap/ProcessCapDummy.cpp b/common/processCap/ProcessCapDummy.cpp new file mode 100644 index 0000000000..ae1e6a27bc --- /dev/null +++ b/common/processCap/ProcessCapDummy.cpp @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "castor/server/ProcessCapDummy.hpp" + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +castor::server::ProcessCapDummy::~ProcessCapDummy() + throw() { +} + +//------------------------------------------------------------------------------ +// getProcText +//------------------------------------------------------------------------------ +std::string castor::server::ProcessCapDummy::getProcText() { + return m_text; +} + +//------------------------------------------------------------------------------ +// setProcText +//------------------------------------------------------------------------------ +void castor::server::ProcessCapDummy::setProcText(const std::string &text) { + m_text = text; +} diff --git a/common/processCap/ProcessCapDummy.hpp b/common/processCap/ProcessCapDummy.hpp new file mode 100644 index 0000000000..6da07c2e97 --- /dev/null +++ b/common/processCap/ProcessCapDummy.hpp @@ -0,0 +1,73 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +#include "castor/server/ProcessCap.hpp" + +#include <string> +#include <sys/capability.h> + +namespace castor { +namespace server { + +/** + * A dummy class that pretends to provide support for UNIX capabilities. + * + * This primary goal of this class is to facilitate unit testing. + */ +class ProcessCapDummy: public ProcessCap { +public: + + /** + * Destructor. + */ + ~ProcessCapDummy() throw(); + + /** + * C++ wrapper around the C functions cap_get_proc() and cap_to_text(). + * + * @return The string representation the capabilities of the current + * process. + */ + std::string getProcText(); + + /** + * C++ wrapper around the C functions cap_from_text() and cap_set_proc(). + * + * @text The string representation the capabilities that the current + * process should have. + */ + void setProcText(const std::string &text); + +private: + + /** + * The string representation of the current capability state. + */ + std::string m_text; + +}; // class ProcessCapDummy + +} // namespace server +} // namespace castor diff --git a/common/processCap/SmartCap.cpp b/common/processCap/SmartCap.cpp new file mode 100644 index 0000000000..a6f23dd425 --- /dev/null +++ b/common/processCap/SmartCap.cpp @@ -0,0 +1,97 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/exception/Exception.hpp" +#include "common/processCap/SmartCap.hpp" + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::server::SmartCap::SmartCap() throw(): + m_cap(NULL) { +} + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::server::SmartCap::SmartCap(cap_t cap) throw(): + m_cap(cap) { +} + +//------------------------------------------------------------------------------ +// reset +//------------------------------------------------------------------------------ +void cta::server::SmartCap::reset(cap_t cap) throw() { + // If the new capability state is not the one already owned + if(cap != m_cap) { + + // If this smart pointer still owns a capability state then free it using + // cap_free() + if(NULL != m_cap) { + cap_free(m_cap); + } + + // Take ownership of the new capability state + m_cap = cap; + } +} + +//------------------------------------------------------------------------------ +// operator= +//------------------------------------------------------------------------------ +cta::server::SmartCap &cta::server::SmartCap::operator=(SmartCap& obj) { + reset(obj.release()); + return *this; +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::server::SmartCap::~SmartCap() throw() { + reset(); +} + +//------------------------------------------------------------------------------ +// get +//------------------------------------------------------------------------------ +cap_t cta::server::SmartCap::get() const throw() { + return m_cap; +} + +//------------------------------------------------------------------------------ +// release +//------------------------------------------------------------------------------ +cap_t cta::server::SmartCap::release() { + // If this smart pointer does not own a capbility state + if(NULL == m_cap) { + cta::exception::Exception ex; + ex.getMessage() << "Smart pointer does not own a capbility state"; + throw(ex); + } + + // Assigning NULL to m_cap indicates this smart pointer does not own a + // capability state + cap_t tmpCap = m_cap; + m_cap = NULL; + return tmpCap; +} diff --git a/common/processCap/SmartCap.hpp b/common/processCap/SmartCap.hpp new file mode 100644 index 0000000000..d0500d3d2e --- /dev/null +++ b/common/processCap/SmartCap.hpp @@ -0,0 +1,119 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +#include <stddef.h> +#include <sys/capability.h> + +namespace cta { namespace server { + +/** + * A smart pointer that owns a capability state. + * + * This smart pointer will call cap_free() if it needs to free the owned + * resource. + * + * Please note that process capabilities are not supported on Mac OS X. + */ +class SmartCap { +public: + + /** + * Constructor. + */ + SmartCap() throw(); + + /** + * Constructor. + * + * @param cap The capability state to be owned. + */ + SmartCap(cap_t cap) throw(); + + /** + * Takes ownership of the specified capability state. If this smart pointer + * already owns a capbility state that is not the same as the one specified + * then it will be freed using cap_free(). + * + * @param cap The capability state to be owned. If a capabibility state is + * not specified then the default value of NULL will be used. In this default + * case the smart pointer will not own a capbility state after the reset() + * method returns. + */ + void reset(cap_t cap = NULL) throw(); + + /** + * SmartCap assignment operator. + * + * This function does the following: + * <ul> + * <li> Calls release on the previous owner (obj); + * <li> Resets this smart pointer to the released pointer of the previous + * owner (obj). + * </ul> + */ + SmartCap &operator=(SmartCap& obj); + + /** + * Destructor. + * + * Resets this smart pointer with the default value of NULL. + */ + ~SmartCap() throw(); + + /** + * Returns the owned capbility state or NULL if this smart pointer does not + * own one. + * + * @return The owned capbility state or NULL if this smart pointer does not + * own one. + */ + cap_t get() const throw(); + + /** + * Releases the owned capability state. + * + * @return The released capability state. + */ + cap_t release(); + +private: + + /** + * The owned capbility state or NULL if this smart pointer does not own one. + */ + cap_t m_cap; + + /** + * Private copy-constructor to prevent users from trying to create a new + * copy of an object of this class. + * + * Not implemented so that it cannot be called. + */ + SmartCap(const SmartCap &obj) throw(); + +}; // class SmartCap + +}} // namespace cta::server + diff --git a/common/processCap/SmartCapTest.cpp b/common/processCap/SmartCapTest.cpp new file mode 100644 index 0000000000..5094bd4f96 --- /dev/null +++ b/common/processCap/SmartCapTest.cpp @@ -0,0 +1,88 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "castor/exception/Exception.hpp" +#include "castor/server/SmartCap.hpp" + +#include <gtest/gtest.h> + +namespace unitTests { + +class castor_server_SmartCapTest : public ::testing::Test { +protected: + + virtual void SetUp() { + } + + virtual void TearDown() { + } +}; + +TEST_F(castor_server_SmartCapTest, default_constructor) { + castor::server::SmartCap smartPtr; + ASSERT_EQ((cap_t)NULL, smartPtr.get()); +} + +TEST_F(castor_server_SmartCapTest, constructor) { + cap_t cap = cap_get_proc(); + ASSERT_NE((cap_t)NULL, cap); + + castor::server::SmartCap smartPtr(cap); + ASSERT_EQ(cap, smartPtr.get()); +} + +TEST_F(castor_server_SmartCapTest, reset) { + castor::server::SmartCap smartPtr; + ASSERT_EQ((cap_t)NULL, smartPtr.get()); + + cap_t cap = cap_get_proc(); + ASSERT_NE((cap_t)NULL, cap); + + smartPtr.reset(cap); + ASSERT_EQ(cap, smartPtr.get()); +} + +TEST_F(castor_server_SmartCapTest, assignment) { + cap_t cap = cap_get_proc(); + ASSERT_NE((cap_t)NULL, cap); + + castor::server::SmartCap smartPtr1; + castor::server::SmartCap smartPtr2; + + ASSERT_EQ((cap_t)NULL, smartPtr1.get()); + ASSERT_EQ((cap_t)NULL, smartPtr2.get()); + + smartPtr1.reset(cap); + ASSERT_EQ(cap, smartPtr1.get()); + + smartPtr2 = smartPtr1; + ASSERT_EQ((cap_t)NULL, smartPtr1.get()); + ASSERT_EQ(cap, smartPtr2.get()); +} + +TEST_F(castor_server_SmartCapTest, releaseNull) { + castor::server::SmartCap smartPtr; + ASSERT_THROW(smartPtr.release(), castor::exception::Exception); +} + +} // namespace unitTests diff --git a/common/threading/Daemon.cpp b/common/threading/Daemon.cpp new file mode 100644 index 0000000000..358746784b --- /dev/null +++ b/common/threading/Daemon.cpp @@ -0,0 +1,202 @@ +/******************************************************************************* + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * @author Castor Dev team, castor-dev@cern.ch + ******************************************************************************/ + +#include "common/exception/Errnum.hpp" +#include "common/threading/Daemon.hpp" +#include "common/threading/System.hpp" +#include <getopt.h> + +#include <signal.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> + +//------------------------------------------------------------------------------ +// constructor +//------------------------------------------------------------------------------ +cta::server::Daemon::Daemon(std::ostream &stdOut, std::ostream &stdErr, + log::Logger &log) throw(): + m_stdOut(stdOut), + m_stdErr(stdErr), + m_log(log), + m_foreground(false), + m_commandLineHasBeenParsed(false) { +} + +//------------------------------------------------------------------------------ +// destructor +//------------------------------------------------------------------------------ +cta::server::Daemon::~Daemon() { +} + +//------------------------------------------------------------------------------ +// parseCommandLine +//------------------------------------------------------------------------------ +void cta::server::Daemon::parseCommandLine(int argc, + char *argv[]) { + struct ::option longopts[4]; + + longopts[0].name = "foreground"; + longopts[0].has_arg = no_argument; + longopts[0].flag = NULL; + longopts[0].val = 'f'; + + longopts[1].name = "config"; + longopts[1].has_arg = required_argument; + longopts[1].flag = NULL; + longopts[1].val = 'c'; + + longopts[2].name = "help"; + longopts[2].has_arg = no_argument; + longopts[2].flag = NULL; + longopts[2].val = 'h'; + + longopts[3].name = 0; + + char c; + while ((c = getopt_long(argc, argv, "fc:h", longopts, NULL)) != -1) { + switch (c) { + case 'f': + m_foreground = true; + break; + case 'c': + setenv("PATH_CONFIG", optarg, 1); + m_stdOut << "Using configuration file " << optarg << std::endl; + break; + case 'h': + help(argv[0]); + exit(0); + break; + default: + break; + } + } + + m_commandLineHasBeenParsed = true; +} + +//------------------------------------------------------------------------------ +// help +//------------------------------------------------------------------------------ +void cta::server::Daemon::help(const std::string &programName) + throw() { + m_stdOut << "Usage: " << programName << " [options]\n" + "\n" + "where options can be:\n" + "\n" + "\t--foreground or -f \tRemain in the Foreground\n" + "\t--config <config-file> or -c \tConfiguration file\n" + "\t--metrics or -m \tEnable metrics collection\n" + "\t--help or -h \tPrint this help and exit\n" + "\n" + "Comments to: Castor.Support@cern.ch\n"; +} + +//------------------------------------------------------------------------------ +// getServerName +//------------------------------------------------------------------------------ +const std::string &cta::server::Daemon::getServerName() const throw() { + return m_log.getProgramName(); +} + +//------------------------------------------------------------------------------ +// getForeground +//------------------------------------------------------------------------------ +bool cta::server::Daemon::getForeground() const + { + if(!m_commandLineHasBeenParsed) { + CommandLineNotParsed ex; + ex.getMessage() << + "Failed to determine whether or not the daemon should run in the" + " foreground because the command-line has not yet been parsed"; + throw ex; + } + + return m_foreground; +} + +//----------------------------------------------------------------------------- +// setCommandLineParsed +//----------------------------------------------------------------------------- +void cta::server::Daemon::setCommandLineHasBeenParsed(const bool foreground) + throw() { + m_foreground = foreground; + m_commandLineHasBeenParsed = true; +} + +//------------------------------------------------------------------------------ +// daemonizeIfNotRunInForeground +//------------------------------------------------------------------------------ +void cta::server::Daemon::daemonizeIfNotRunInForeground( + const bool runAsStagerSuperuser) { + // Do nothing if already a daemon + if (1 == getppid()) { + return; + } + + // If the daemon is to be run in the background + if (!m_foreground) { + m_log.prepareForFork(); + + { + pid_t pid = 0; + cta::exception::Errnum::throwOnNegative(pid = fork(), + "Failed to daemonize: Failed to fork"); + // If we got a good PID, then we can exit the parent process + if (0 < pid) { + exit(EXIT_SUCCESS); + } + } + + // We could set our working directory to '/' here with a call to chdir(2). + // For the time being we don't and leave it to the initd script to change + // to a suitable directory for us! + + // Change the file mode mask + umask(0); + + // Run the daemon in a new session + cta::exception::Errnum::throwOnNegative(setsid(), + "Failed to daemonize: Failed to run daemon is a new session"); + + // Redirect standard files to /dev/null + cta::exception::Errnum::throwOnNull( + freopen("/dev/null", "r", stdin), + "Failed to daemonize: Falied to freopen stdin"); + cta::exception::Errnum::throwOnNull( + freopen("/dev/null", "w", stdout), + "Failed to daemonize: Failed to freopen stdout"); + cta::exception::Errnum::throwOnNull( + freopen("/dev/null", "w", stderr), + "Failed to daemonize: Failed to freopen stderr"); + } // if (!m_foreground) + + // Change the user of the daemon process to the Castor superuser if requested + if (runAsStagerSuperuser) { + cta::System::switchToCtaSuperuser(); + } + + // Ignore SIGPIPE (connection lost with client) + // and SIGXFSZ (a file is too big) + signal(SIGPIPE, SIG_IGN); + signal(SIGXFSZ, SIG_IGN); +} + diff --git a/common/threading/Daemon.hpp b/common/threading/Daemon.hpp new file mode 100644 index 0000000000..542dca8a3b --- /dev/null +++ b/common/threading/Daemon.hpp @@ -0,0 +1,140 @@ +/******************************************************************************* + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * @author Castor Dev team, castor-dev@cern.ch + ******************************************************************************/ + +#pragma once + +#include "common/exception/Exception.hpp" +#include "common/log/Logger.hpp" + +#include <ostream> + +namespace cta { namespace server { + +/** + * This class contains the code common to all daemon classes. + * + * The code common to all daemon classes includes daemonization and logging. + */ +class Daemon { + +public: + + CTA_GENERATE_EXCEPTION_CLASS(CommandLineNotParsed); + + /** + * Constructor + * + * @param stdOut Stream representing standard out. + * @param stdErr Stream representing standard error. + * @param log Object representing the API of the CASTOR logging system. + */ + Daemon(std::ostream &stdOut, std::ostream &stdErr, log::Logger &log) + throw(); + + /** + * Destructor. + */ + virtual ~Daemon(); + + /** + * Parses a command line to set the server options. + * + * @param argc The size of the command-line vector. + * @param argv The command-line vector. + */ + virtual void parseCommandLine(int argc, char *argv[]) + ; + + /** + * Prints out the online help + */ + virtual void help(const std::string &programName) throw(); + + /** + * Returns this server's name as used by the CASTOR logging system. + */ + const std::string &getServerName() const throw(); + + /** + * Returns true if the daemon is configured to run in the foreground. + */ + bool getForeground() const ; + +protected: + + /** + * Tells the daemon object that the command-line has been parsed. This + * method allows subclasses to implement their own command-line parsing logic, + * whilst enforcing the fact that they must provide values for the options and + * arguments this class requires. + * + * @param foreground Set to true if the daemon should run in the foreground. + */ + void setCommandLineHasBeenParsed(const bool foreground) throw(); + + /** + * Daemonizes the daemon if it has not been configured to run in the + * foreground. + * + * Please make sure that the setForeground() method has been called as + * appropriate before this method is called. + * + * This method takes into account whether or not the dameon should run in + * foregreound or background mode (m_foreground). + * + * @param runAsStagerSuperuser Set to true if the user ID and group ID of the + * daemon should be set to those of the stager superuser. + */ + void daemonizeIfNotRunInForeground(const bool runAsStagerSuperuser); + + /** + * Stream representing standard out. + */ + std::ostream &m_stdOut; + + /** + * Stream representing standard in. + */ + std::ostream &m_stdErr; + + /** + * Object representing the API of the CASTOR logging system. + */ + log::Logger &m_log; + +private: + + /** + * Flag indicating whether the server should run in foreground or background + * mode. + */ + bool m_foreground; + + /** + * True if the command-line has been parsed. + */ + bool m_commandLineHasBeenParsed; + +}; // class Daemon + +} // namespace server +} // namespace castor + diff --git a/common/threading/DaemonTest.cpp b/common/threading/DaemonTest.cpp new file mode 100644 index 0000000000..c68782bbf2 --- /dev/null +++ b/common/threading/DaemonTest.cpp @@ -0,0 +1,101 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "common/log/DummyLogger.hpp" +#include "common/threading/Daemon.hpp" + +#include <gtest/gtest.h> +#include <sstream> +#include <stdio.h> +#include <string.h> + +namespace unitTests { + +class cta_threading_DaemonTest : public ::testing::Test { +protected: + + const std::string m_programName; + int m_argc; + char **m_argv; + + cta_threading_DaemonTest() : + m_programName("testdaemon"), + m_argc(0), + m_argv(NULL) { + } + + virtual void SetUp() { + m_argc = 0; + m_argv = NULL; + } + + virtual void TearDown() { + for(int i = 0; i < m_argc; i++) { + free(m_argv[i]); + } + + delete[] m_argv; + } +}; + +TEST_F(cta_threading_DaemonTest, getForegroundBeforeParseCommandLine) { + std::ostringstream dummyStdOut; + std::ostringstream dummyStdErr; + cta::log::DummyLogger log(m_programName); + cta::server::Daemon daemon(dummyStdOut, dummyStdErr, log); + + ASSERT_THROW(daemon.getForeground(), cta::server::Daemon::CommandLineNotParsed); +} + +TEST_F(cta_threading_DaemonTest, parseEmptyCmdLine) { + m_argv = new char *[2]; + m_argv[0] = strdup(m_programName.c_str()); + m_argv[1] = NULL; + m_argc = 1; + + std::ostringstream dummyStdOut; + std::ostringstream dummyStdErr; + cta::log::DummyLogger log(m_programName); + cta::server::Daemon daemon(dummyStdOut, dummyStdErr, log); + + ASSERT_NO_THROW(daemon.parseCommandLine(m_argc, m_argv)); + ASSERT_EQ(false, daemon.getForeground()); +} + +TEST_F(cta_threading_DaemonTest, parseFOnCmdLine) { + m_argv = new char *[3]; + m_argv[0] = strdup(m_programName.c_str()); + m_argv[1] = strdup("-f"); + m_argv[2] = NULL; + m_argc = 2; + + std::ostringstream dummyStdOut; + std::ostringstream dummyStdErr; + cta::log::DummyLogger log(m_programName); + cta::server::Daemon daemon(dummyStdOut, dummyStdErr, log); + + ASSERT_NO_THROW(daemon.parseCommandLine(m_argc, m_argv)); + ASSERT_EQ(true, daemon.getForeground()); +} + +} // namespace unitTests diff --git a/common/threading/MutexLocker.hpp b/common/threading/MutexLocker.hpp new file mode 100644 index 0000000000..2d799d31c5 --- /dev/null +++ b/common/threading/MutexLocker.hpp @@ -0,0 +1,64 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include "Mutex.hpp" + +#include <pthread.h> +#include <semaphore.h> + +namespace cta { namespace threading { + +/** + * A simple scoped locker for mutexes. Highly recommended as + * the mutex will be released in all cases (exception, mid-code return, etc...) + * To use, simply instantiate and forget. + */ +class MutexLocker { +public: + + /** + * Constructor. + * + * @param m pointer to Mutex to be owned. + */ + MutexLocker(Mutex & m): m_mutex(m) { + m.lock(); + } + + /** + * Destructor. + */ + ~MutexLocker() { + try { + m_mutex.unlock(); + } catch (...) { + // Ignore any exceptions + } + } + +private: + + /** + * The mutex owened by this MutexLocker. + */ + Mutex & m_mutex; + +}; // class MutexLocker + +}} // namespace cta::threading diff --git a/common/threading/System.cpp b/common/threading/System.cpp new file mode 100644 index 0000000000..3f890890cb --- /dev/null +++ b/common/threading/System.cpp @@ -0,0 +1,193 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * @(#)BaseClient.cpp,v 1.37 $Release$ 2006/02/16 15:56:58 sponcec3 + * + * A class with static methods for system level utilities. + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +// Include Files +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <arpa/inet.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> + +// Local includes +#include "System.hpp" +#include "common/exception/Errnum.hpp" + +#define STAGERSUPERGROUP "st" +#define STAGERSUPERUSER "stage" + + +//------------------------------------------------------------------------------ +// getHostName +//------------------------------------------------------------------------------ +std::string cta::System::getHostName() +{ + // All this to get the hostname, thanks to C ! + int len = 64; + char* hostname; + hostname = (char*) calloc(len, 1); + if (0 == hostname) { + OutOfMemory ex; + ex.getMessage() << "Could not allocate hostname with length " << len; + throw ex; + } + if (gethostname(hostname, len) < 0) { + // Test whether error is due to a name too long + // The errno depends on the glibc version + if (EINVAL != errno && + ENAMETOOLONG != errno) { + free(hostname); + cta::exception::Errnum e(errno); + e.getMessage() << "gethostname error"; + throw e; + } + // So the name was too long + while (hostname[len - 1] != 0) { + len *= 2; + char *hostnameLonger = (char*) realloc(hostname, len); + if (0 == hostnameLonger) { + free(hostname); + cta::exception::Errnum e(ENOMEM); + e.getMessage() << "Could not allocate memory for hostname"; + throw e; + + } + hostname = hostnameLonger; + memset(hostname, 0, len); + if (gethostname(hostname, len) < 0) { + // Test whether error is due to a name too long + // The errno depends on the glibc version + if (EINVAL != errno && + ENAMETOOLONG != errno) { + free(hostname); + cta::exception::Errnum e(errno); + e.getMessage() << "Could not get hostname" + << strerror(errno); + throw e; + } + } + } + } + std::string res(hostname); // copy the string + free(hostname); + return res; +} + +//------------------------------------------------------------------------------ +// porttoi +//------------------------------------------------------------------------------ +int cta::System::porttoi(char* str) + { + char* dp = str; + errno = 0; + int iport = strtoul(str, &dp, 0); + if (*dp != 0) { + cta::exception::Errnum e(errno); + e.getMessage() << "Bad port value." << std::endl; + throw e; + } + if ((iport > 65535) || (iport < 0)) { + cta::exception::Errnum e(errno); + e.getMessage() + << "Invalid port value : " << iport + << ". Must be < 65535 and > 0." << std::endl; + throw e; + } + return iport; +} + +//------------------------------------------------------------------------------ +// switchToCastorSuperuser +//------------------------------------------------------------------------------ +void cta::System::switchToCtaSuperuser() + { + struct passwd *stage_passwd; // password structure pointer + struct group *stage_group; // group structure pointer + + uid_t ruid, euid; // Original uid/euid + gid_t rgid, egid; // Original gid/egid + + // Save original values + ruid = getuid(); + euid = geteuid(); + rgid = getgid(); + egid = getegid(); + + // Get information on generic stage account from password file + if ((stage_passwd = getpwnam(STAGERSUPERUSER)) == NULL) { + cta::exception::Exception e; + e.getMessage() << "CTA super user " << STAGERSUPERUSER + << " not found in password file"; + throw e; + } + // verify existence of its primary group id + if (getgrgid(stage_passwd->pw_gid) == NULL) { + cta::exception::Exception e; + e.getMessage() << "CTA super user group does not exist"; + throw e; + } + // Get information on generic stage account from group file + if ((stage_group = getgrnam(STAGERSUPERGROUP)) == NULL) { + cta::exception::Exception e; + e.getMessage() << "CTA super user group " << STAGERSUPERGROUP + << " not found in group file"; + throw e; + } + // Verify consistency + if (stage_group->gr_gid != stage_passwd->pw_gid) { + cta::exception::Exception e; + e.getMessage() << "Inconsistent password file. The group of the " + << "castor superuser " << STAGERSUPERUSER + << " should be " << stage_group->gr_gid + << "(" << STAGERSUPERGROUP << "), but is " + << stage_passwd->pw_gid; + throw e; + } + // Undo group privilege + if (setregid (egid, rgid) < 0) { + cta::exception::Exception e; + e.getMessage() << "Unable to undo group privilege"; + throw e; + } + // Undo user privilege + if (setreuid (euid, ruid) < 0) { + cta::exception::Exception e; + e.getMessage() << "Unable to undo user privilege"; + throw e; + } + // set the effective privileges to superuser + if (setegid(stage_passwd->pw_gid) < 0) { + cta::exception::Exception e; + e.getMessage() << "Unable to set group privileges of CTA Superuser. " + << "You may want to check that the suid bit is set properly"; + throw e; + } + if (seteuid(stage_passwd->pw_uid) < 0) { + cta::exception::Exception e; + e.getMessage() << "Unable to set privileges of CTA Superuser."; + throw e; + } +} diff --git a/common/threading/System.hpp b/common/threading/System.hpp new file mode 100644 index 0000000000..42af73af1e --- /dev/null +++ b/common/threading/System.hpp @@ -0,0 +1,64 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * A class with static methods for system level utilities. + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#pragma once + +#include "common/exception/Exception.hpp" +#include <string> + +namespace cta { + + class System { + + public: + + CTA_GENERATE_EXCEPTION_CLASS(OutOfMemory); + + /** + * Gets the host name. Handles all errors that may occur with + * the gethostname() API. + * @exception in case of an error + */ + static std::string getHostName() ; + + /** + * Converts a string into a port number, checking + * that the value is in range [0-65535] + * @param str the string giving the port number + * @return the port as an int + * @exception in case of invalid value + */ + static int porttoi(char* str) ; + + /** + * Switches the current process to use the CTA superuser + * (typically stage:st). + * @exception in case of an error + */ + static void switchToCtaSuperuser() ; + + }; + +} // end of namespace castor + diff --git a/common/Utils.cpp b/common/utils/Utils.cpp similarity index 86% rename from common/Utils.cpp rename to common/utils/Utils.cpp index 4d1cecd807..f3aa1f96b9 100644 --- a/common/Utils.cpp +++ b/common/utils/Utils.cpp @@ -18,8 +18,8 @@ #include "common/exception/Exception.hpp" #include "common/exception/Errnum.hpp" -#include "common/strerror_r_wrapper.hpp" -#include "common/Utils.hpp" +#include "common/utils/strerror_r_wrapper.hpp" +#include "common/utils/Utils.hpp" #include <attr/xattr.h> #include <limits> @@ -32,6 +32,7 @@ #include <uuid/uuid.h> #include <zlib.h> #include <sys/utsname.h> +#include <sys/prctl.h> using cta::exception::Exception; @@ -226,6 +227,33 @@ void cta::Utils::splitString(const std::string &str, const char separator, } } +//----------------------------------------------------------------------------- +// trimString +//----------------------------------------------------------------------------- +std::string cta::Utils::trimString(const std::string &s) throw() { + const std::string& spaces="\t\n\v\f\r "; + + // Find first non white character + size_t beginpos = s.find_first_not_of(spaces); + std::string::const_iterator it1; + if (std::string::npos != beginpos) { + it1 = beginpos + s.begin(); + } else { + it1 = s.begin(); + } + + // Find last non white chararacter + std::string::const_iterator it2; + size_t endpos = s.find_last_not_of(spaces); + if (std::string::npos != endpos) { + it2 = endpos + 1 + s.begin(); + } else { + it2 = s.end(); + } + + return std::string(it1, it2); +} + //----------------------------------------------------------------------------- // generateUuid //----------------------------------------------------------------------------- @@ -482,4 +510,58 @@ std::string cta::Utils::getShortHostname() { return snn.at(0); } +//------------------------------------------------------------------------------ +// getDumpableProcessAttribute +//------------------------------------------------------------------------------ +bool cta::Utils::getDumpableProcessAttribute() { + const int rc = prctl(PR_GET_DUMPABLE); + switch(rc) { + case -1: + { + const std::string errStr = errnoToString(errno); + cta::exception::Exception ex; + ex.getMessage() << + "Failed to get the dumpable attribute of the process: " << errStr; + throw ex; + } + case 0: return false; + case 1: return true; + case 2: return true; + default: + { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to get the dumpable attribute of the process" + ": Unknown value returned by prctl(): rc=" << rc; + throw ex; + } + } +} + +//------------------------------------------------------------------------------ +// setDumpableProcessAttribute +//------------------------------------------------------------------------------ + void cta::Utils::setDumpableProcessAttribute(const bool dumpable) { + const int rc = prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0); + switch(rc) { + case -1: + { + const std::string errStr = errnoToString(errno); + cta::exception::Exception ex; + ex.getMessage() << + "Failed to set the dumpable attribute of the process: " << errStr; + throw ex; + } + case 0: return; + default: + { + cta::exception::Exception ex; + ex.getMessage() << + "Failed to set the dumpable attribute of the process" + ": Unknown value returned by prctl(): rc=" << rc; + throw ex; + } + } +} + diff --git a/common/Utils.hpp b/common/utils/Utils.hpp similarity index 82% rename from common/Utils.hpp rename to common/utils/Utils.hpp index 1a919c1c01..d61bde7afb 100644 --- a/common/Utils.hpp +++ b/common/utils/Utils.hpp @@ -106,6 +106,26 @@ public: */ static void splitString(const std::string &str, const char separator, std::vector<std::string> &result); + + + /** + * Returns the result of trimming both left and right white-space from the + * specified string. + * + * @param s The string to be trimmed. + * @return The result of trimming the string. + */ + static std::string trimString(const std::string &s) throw(); + + /** + * Creates and returns an std::string which is the result of replacing each + * occurance of whitespace (a collection of on or more space and tab + * characters) with a single space character. + * + * @param str The original string. + * @return The newly created string with single spaces. + */ + static std::string singleSpaceString(const std::string &str) throw(); /** * Returns uuid in the form of a string. @@ -212,6 +232,24 @@ public: */ static uint32_t getAdler32(const uint8_t *buf, const uint32_t len) throw(); + /** + * Returns true if the attributes of the current process indicate that it will + * produce a core dump if it receives a signal whose behaviour is to produce a + * core dump. + * + * This method is implemented using prctl(). + * + * @return true if the current program is dumpable. + */ + static bool getDumpableProcessAttribute(); + + /** + * Sets the attributes of the current process to indicate hat it will produce a + * core dump if it receives a signal whose behaviour is to produce a core dump. + * + * @param dumpable true if the current program should be dumpable. + */ + static void setDumpableProcessAttribute(const bool dumpable); }; // class Utils } // namespace cta diff --git a/common/UtilsTest.cpp b/common/utils/UtilsTest.cpp similarity index 73% rename from common/UtilsTest.cpp rename to common/utils/UtilsTest.cpp index 490cac80c1..5c9377c20a 100644 --- a/common/UtilsTest.cpp +++ b/common/utils/UtilsTest.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include <gtest/gtest.h> @@ -467,4 +467,136 @@ TEST_F(cta_UtilsTest, adler32_buf_of_character_1) { } +/** + * Tests the good day senario of passing a multi-column string to the + * splitString() method. + */ +TEST_F(cta_UtilsTest, testGoodDaySplitString) { + using cta::Utils; + const std::string line("col0 col1 col2 col3 col4 col5 col6 col7"); + std::vector<std::string> columns; + + ASSERT_NO_THROW(Utils::splitString(line, ' ', columns)); + ASSERT_EQ((std::vector<std::string>::size_type)8, columns.size()); + ASSERT_EQ(std::string("col0"), columns[0]); + ASSERT_EQ(std::string("col1"), columns[1]); + ASSERT_EQ(std::string("col2"), columns[2]); + ASSERT_EQ(std::string("col3"), columns[3]); + ASSERT_EQ(std::string("col4"), columns[4]); + ASSERT_EQ(std::string("col5"), columns[5]); + ASSERT_EQ(std::string("col6"), columns[6]); + ASSERT_EQ(std::string("col7"), columns[7]); +} + +/** + * Test the case of an empty string being passed to the splitString() method. + */ +TEST_F(cta_UtilsTest, testSplitStringWithEmptyString) { + using cta::Utils; + const std::string emptyString; + std::vector<std::string> columns; + + ASSERT_NO_THROW(Utils::splitString(emptyString, ' ', columns)); + ASSERT_EQ((std::vector<std::string>::size_type)0, columns.size()); +} + +/** + * Test the case of a non-empty string containing no separator character + * passed to the splitString() method. + */ +TEST_F(cta_UtilsTest, testSplitStringWithNoSeparatorInString) { + using cta::Utils; + const std::string stringContainingNoSeparator = + "stringContainingNoSeparator"; + std::vector<std::string> columns; + + ASSERT_NO_THROW(Utils::splitString(stringContainingNoSeparator, ' ', columns)); + ASSERT_EQ((std::vector<std::string>::size_type)1, columns.size()); + ASSERT_EQ(stringContainingNoSeparator, columns[0]); +} + +TEST_F(cta_UtilsTest, testTrimStringWithEmptyString) { + using cta::Utils; + const std::string s; + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(s, trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingNoSpaces) { + using cta::Utils; + const std::string s("NO_SPACES"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(s, trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftSpace) { + using cta::Utils; + const std::string s(" VALUE"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingRightSpace) { + using cta::Utils; + const std::string s("VALUE "); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftAndRightSpace) { + using cta::Utils; + const std::string s(" VALUE "); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftTab) { + using cta::Utils; + const std::string s("\tVALUE"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingRightTab) { + using cta::Utils; + const std::string s("VALUE\t"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftAndRightTab) { + using cta::Utils; + const std::string s("\tVALUE\t"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftNewLine) { + using cta::Utils; + const std::string s("\nVALUE"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingRightNewLine) { + using cta::Utils; + const std::string s("VALUE\n"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftAndRightNewLine) { + using cta::Utils; + const std::string s("\nVALUE\n"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + +TEST_F(cta_UtilsTest, testTrimStringContainingLeftAndRightWhiteSpace) { + using cta::Utils; + const std::string s(" \t\t\n\nVALUE \t\t\n\n"); + const std::string trimmedString = Utils::trimString(s); + ASSERT_EQ(std::string("VALUE"), trimmedString); +} + } // namespace unitTests diff --git a/common/strerror_r_wrapper.cpp b/common/utils/strerror_r_wrapper.cpp similarity index 96% rename from common/strerror_r_wrapper.cpp rename to common/utils/strerror_r_wrapper.cpp index 538464ff00..0f27181516 100644 --- a/common/strerror_r_wrapper.cpp +++ b/common/utils/strerror_r_wrapper.cpp @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include "common/strerror_r_wrapper.hpp" +#include "common/utils/strerror_r_wrapper.hpp" /* * Undefine _GNU_SOURCE and define _XOPEN_SOURCE as being 600 so that the diff --git a/common/strerror_r_wrapper.hpp b/common/utils/strerror_r_wrapper.hpp similarity index 100% rename from common/strerror_r_wrapper.hpp rename to common/utils/strerror_r_wrapper.hpp diff --git a/nameserver/mockNS/MockNameServer.cpp b/nameserver/mockNS/MockNameServer.cpp index 7c36ef081d..58f1c83830 100644 --- a/nameserver/mockNS/MockNameServer.cpp +++ b/nameserver/mockNS/MockNameServer.cpp @@ -30,7 +30,7 @@ #include "common/exception/Exception.hpp" #include "common/exception/Errnum.hpp" #include "nameserver/mockNS/SmartFd.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "nameserver/mockNS/MockNameServer.hpp" //------------------------------------------------------------------------------ diff --git a/nameserver/mockNS/makeMockNameServerBasePath.cpp b/nameserver/mockNS/makeMockNameServerBasePath.cpp index 0910d5afa1..f8be00e5e3 100644 --- a/nameserver/mockNS/makeMockNameServerBasePath.cpp +++ b/nameserver/mockNS/makeMockNameServerBasePath.cpp @@ -29,7 +29,7 @@ #include "common/exception/Errnum.hpp" #include "common/exception/Exception.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" namespace { diff --git a/objectstore/BackendFactory.cpp b/objectstore/BackendFactory.cpp index 687f694fda..d88a9824ef 100644 --- a/objectstore/BackendFactory.cpp +++ b/objectstore/BackendFactory.cpp @@ -19,7 +19,7 @@ #include "BackendFactory.hpp" #include "BackendRados.hpp" #include "BackendVFS.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "tapeserver/castor/tape/tapeserver/utils/Regex.hpp" auto cta::objectstore::BackendFactory::createBackend(const std::string& URL) diff --git a/objectstore/BackendVFS.cpp b/objectstore/BackendVFS.cpp index 1964f81048..4f0a1105c7 100644 --- a/objectstore/BackendVFS.cpp +++ b/objectstore/BackendVFS.cpp @@ -18,7 +18,7 @@ #include "BackendVFS.hpp" #include "common/exception/Errnum.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "tapeserver/castor/tape/tapeserver/utils/Regex.hpp" #include <fstream> diff --git a/remotens/EosNS.cpp b/remotens/EosNS.cpp index fae40c2049..266ba16f7d 100644 --- a/remotens/EosNS.cpp +++ b/remotens/EosNS.cpp @@ -25,7 +25,7 @@ #include "common/exception/Exception.hpp" #include "common/remoteFS/RemotePath.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "remotens/EosNS.hpp" #include "XrdCl/XrdClFileSystem.hh" diff --git a/remotens/MockRemoteNS.cpp b/remotens/MockRemoteNS.cpp index d0d5b8e8dd..8f944f8d88 100644 --- a/remotens/MockRemoteNS.cpp +++ b/remotens/MockRemoteNS.cpp @@ -24,7 +24,7 @@ #include "common/exception/Exception.hpp" #include "common/remoteFS/RemotePath.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "remotens/MockRemoteNS.hpp" diff --git a/scheduler/Scheduler.cpp b/scheduler/Scheduler.cpp index 39872f730a..3f97092fd7 100644 --- a/scheduler/Scheduler.cpp +++ b/scheduler/Scheduler.cpp @@ -18,6 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + #include "scheduler/Scheduler.hpp" #include "catalogue/MockCatalogue.hpp" @@ -726,4 +727,4 @@ std::unique_ptr<cta::common::dataStructures::TapeMount> cta::Scheduler::getNextM std::unique_ptr<cta::TapeMount> cta::Scheduler::_old_getNextMount(const std::string &logicalLibraryName, const std::string & driveName) { throw cta::exception::Exception(std::string(__FUNCTION__)+" Error: not implemented!"); return std::unique_ptr<TapeMount>(); -} \ No newline at end of file +} diff --git a/scheduler/_old_prototype_DummyScheduler.cpp b/scheduler/_old_prototype_DummyScheduler.cpp index 46b60a0af1..40f897d2b5 100644 --- a/scheduler/_old_prototype_DummyScheduler.cpp +++ b/scheduler/_old_prototype_DummyScheduler.cpp @@ -24,7 +24,7 @@ #include "common/exception/Exception.hpp" #include "common/remoteFS/RemotePathAndStatus.hpp" #include "common/UserIdentity.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "common/SecurityIdentity.hpp" #include "common/TapePool.hpp" #include "nameserver/NameServer.hpp" diff --git a/tapeserver/CMakeLists.txt b/tapeserver/CMakeLists.txt index 02f4382f42..8984496ca8 100644 --- a/tapeserver/CMakeLists.txt +++ b/tapeserver/CMakeLists.txt @@ -1,4 +1,12 @@ cmake_minimum_required (VERSION 2.6) +# Old CASTOR's tapeserverd daemon code add_subdirectory (castor) add_subdirectory (test) + +# CTA's cta-taped code +add_subdirectory (daemon) + +add_executable (cta-taped cta-taped.cpp) +target_link_libraries(cta-taped + ctatapedaemon ctacommon protobuf) diff --git a/tapeserver/castor/exception/Errnum.cpp b/tapeserver/castor/exception/Errnum.cpp index e1de0c9353..e2d786d235 100644 --- a/tapeserver/castor/exception/Errnum.cpp +++ b/tapeserver/castor/exception/Errnum.cpp @@ -22,7 +22,7 @@ * @author Castor Dev team, castor-dev@cern.ch *****************************************************************************/ #include "castor/exception/Errnum.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include <errno.h> #include <string.h> diff --git a/tapeserver/castor/io/IoTest.cpp b/tapeserver/castor/io/IoTest.cpp index 5cd64c6175..8dc6b7b692 100644 --- a/tapeserver/castor/io/IoTest.cpp +++ b/tapeserver/castor/io/IoTest.cpp @@ -23,7 +23,7 @@ #include "castor/io/io.hpp" #include "castor/utils/SmartFd.hpp" -#include "common/marshall.h" +#include "castor/io/marshall.h" #include <fcntl.h> #include <gtest/gtest.h> diff --git a/tapeserver/castor/io/io.cpp b/tapeserver/castor/io/io.cpp index 1f407b1067..3eece19e94 100644 --- a/tapeserver/castor/io/io.cpp +++ b/tapeserver/castor/io/io.cpp @@ -27,7 +27,7 @@ #include "castor/io/io.hpp" #include "castor/utils/SmartFd.hpp" #include "castor/utils/utils.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "common/Timer.hpp" #include "common/exception/Errnum.hpp" diff --git a/common/marshall.h b/tapeserver/castor/io/marshall.h similarity index 100% rename from common/marshall.h rename to tapeserver/castor/io/marshall.h diff --git a/tapeserver/castor/messages/messages.cpp b/tapeserver/castor/messages/messages.cpp index 14c7f69302..b5bc5a966f 100644 --- a/tapeserver/castor/messages/messages.cpp +++ b/tapeserver/castor/messages/messages.cpp @@ -23,7 +23,7 @@ #include "castor/messages/messages.hpp" #include "castor/utils/utils.hpp" -#include "common/strerror_r_wrapper.hpp" +#include "common/utils/strerror_r_wrapper.hpp" #include "castor/legacymsg/TapeConstants.h" #include <string.h> diff --git a/tapeserver/castor/server/ProcessCap.cpp b/tapeserver/castor/server/ProcessCap.cpp index 34189aa3dd..368929f216 100644 --- a/tapeserver/castor/server/ProcessCap.cpp +++ b/tapeserver/castor/server/ProcessCap.cpp @@ -24,7 +24,7 @@ #include "castor/exception/Exception.hpp" #include "castor/server/ProcessCap.hpp" #include "castor/server/SmartCap.hpp" -#include "common/Utils.hpp" +#include "castor/utils/utils.hpp" #include <errno.h> @@ -60,7 +60,7 @@ cap_t castor::server::ProcessCap::getProc() { castor::exception::Exception ex; ex.getMessage() << "Failed to get the capabilities of the process: " - << cta::Utils::errnoToString(errno); + << castor::utils::errnoToString(errno); throw ex; } return cap; @@ -77,7 +77,7 @@ std::string castor::server::ProcessCap::toText( castor::exception::Exception ex; ex.getMessage() << "Failed to create string representation of capability state: " - << cta::Utils::errnoToString(errno); + << castor::utils::errnoToString(errno); throw ex; } std::string result(text); @@ -87,7 +87,7 @@ std::string castor::server::ProcessCap::toText( castor::exception::Exception ex; ex.getMessage() << "Failed to free string representation of capability state: " - << cta::Utils::errnoToString(errno); + << castor::utils::errnoToString(errno); throw ex; } @@ -119,7 +119,7 @@ cap_t castor::server::ProcessCap::fromText(const std::string &text) { castor::exception::Exception ex; ex.getMessage() << "Failed to create capability state from string representation" - ": text='" << text << "': " << cta::Utils::errnoToString(errno); + ": text='" << text << "': " << castor::utils::errnoToString(errno); throw ex; } @@ -134,7 +134,7 @@ void castor::server::ProcessCap::setProc(const cap_t cap) { castor::exception::Exception ex; ex.getMessage() << "Failed to set the capabilities of the process: " - << cta::Utils::errnoToString(errno); + << castor::utils::errnoToString(errno); throw ex; } } diff --git a/tapeserver/castor/server/ProcessCap.hpp b/tapeserver/castor/server/ProcessCap.hpp index 2740a58f2b..3ff9b80dd7 100644 --- a/tapeserver/castor/server/ProcessCap.hpp +++ b/tapeserver/castor/server/ProcessCap.hpp @@ -1,25 +1,20 @@ -/****************************************************************************** +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN * - * This file is part of the Castor project. - * See http://castor.web.cern.ch/castor + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * - * Copyright (C) 2003 CERN - * This program is free software; you can 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 - * of the License, 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; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * * - * @author Castor Dev team, castor-dev@cern.ch - *****************************************************************************/ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ #pragma once diff --git a/tapeserver/castor/tape/tapeserver/daemon/CMakeLists.txt b/tapeserver/castor/tape/tapeserver/daemon/CMakeLists.txt index cd008703a6..0d467286ba 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/CMakeLists.txt +++ b/tapeserver/castor/tape/tapeserver/daemon/CMakeLists.txt @@ -70,13 +70,13 @@ add_library(ctaTapeServerDaemon TpconfigLine.cpp TpconfigLines.cpp) -target_link_libraries(ctaTapeServerDaemon ctamessages ctatapereactor ctacommon ctanameserver ctaremotens protobuf ctascheduler ctalegacymsg ctacatalogue) +target_link_libraries(ctaTapeServerDaemon ctamessages ctatapereactor ctacommon ctanameserver ctaremotens protobuf ctascheduler ctalegacymsg ctaserverutils ctacatalogue) add_dependencies(ctaTapeServerDaemon ctamessagesprotobuf) add_executable(cta-tapeserverd TapeDaemon.cpp) target_link_libraries(cta-tapeserverd ctaTapeServerDaemon SCSI System Utils File TapeDrive ctacommon ctatapereactor ${LIBCAP_LIB} ${ZLIB_LIBRARIES} - ctamessages zmq ctaio ctautils) + ctamessages zmq ctaio ctautils ctaserverutils) install (TARGETS cta-tapeserverd DESTINATION usr/bin) add_library(ctatapeserverdaemonutils SHARED diff --git a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp index 1693d28cf0..732f177aa5 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/DataTransferSessionTest.cpp @@ -41,7 +41,7 @@ #include "castor/tape/tapeserver/file/File.hpp" #include "castor/tape/tapeserver/drive/FakeDrive.hpp" #include "common/exception/Exception.hpp" -#include "common/Utils.hpp" +#include "common/utils/Utils.hpp" #include "scheduler/Scheduler.hpp" #include "smc_struct.h" #include "nameserver/mockNS/MockNameServer.hpp" diff --git a/tapeserver/castor/tape/tapeserver/daemon/ProcessForker.cpp b/tapeserver/castor/tape/tapeserver/daemon/ProcessForker.cpp index fe2ee3c81c..c185ee2650 100644 --- a/tapeserver/castor/tape/tapeserver/daemon/ProcessForker.cpp +++ b/tapeserver/castor/tape/tapeserver/daemon/ProcessForker.cpp @@ -470,7 +470,7 @@ castor::tape::tapeserver::daemon::Session::EndOfSessionAction castor::tape::tapeserver::daemon::ProcessForker::runCleanerSession( const messages::ForkCleaner &rqst) { try { - server::ProcessCap capUtils; + castor::server::ProcessCap capUtils; const DriveConfig driveConfig = getDriveConfig(rqst); std::list<log::Param> params; diff --git a/tapeserver/castor/utils/utils.cpp b/tapeserver/castor/utils/utils.cpp index 72ef342ca7..c5c76eeb9b 100644 --- a/tapeserver/castor/utils/utils.cpp +++ b/tapeserver/castor/utils/utils.cpp @@ -20,7 +20,7 @@ *****************************************************************************/ #include "castor/utils/utils.hpp" -#include "common/strerror_r_wrapper.hpp" +#include "common/utils/strerror_r_wrapper.hpp" #include "h/Castor_limits.h" #include <algorithm> @@ -391,7 +391,7 @@ bool castor::utils::getDumpableProcessAttribute() { //------------------------------------------------------------------------------ // setDumpableProcessAttribute //------------------------------------------------------------------------------ -void castor::utils::setDumpableProcessAttribute(const bool dumpable) { + void castor::utils::setDumpableProcessAttribute(const bool dumpable) { const int rc = prctl(PR_SET_DUMPABLE, dumpable ? 1 : 0); switch(rc) { case -1: diff --git a/tapeserver/cta-taped.cpp b/tapeserver/cta-taped.cpp new file mode 100644 index 0000000000..760727bab6 --- /dev/null +++ b/tapeserver/cta-taped.cpp @@ -0,0 +1,197 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "common/Configuration.hpp" +#include "common/log/SyslogLogger.hpp" +#include "common/processCap/ProcessCap.hpp" +#include "tapeserver/daemon/GlobalConfiguration.hpp" +#include "tapeserver/daemon/TpconfigLines.hpp" +#include "tapeserver/daemon/TapeDaemon.hpp" + +#include "version.h" + +#include <google/protobuf/stubs/common.h> +#include <memory> +#include <sstream> +#include <string> +#include <iostream> + +//------------------------------------------------------------------------------ +// exceptionThrowingMain +// +// The main() function delegates the bulk of its implementation to this +// exception throwing version. +// +// @param argc The number of command-line arguments. +// @param argv The command-line arguments. +// @param log The logging system. +//------------------------------------------------------------------------------ +static int exceptionThrowingMain(const int argc, char **const argv, + cta::log::Logger &log); + +//------------------------------------------------------------------------------ +// main +//------------------------------------------------------------------------------ +int main(const int argc, char **const argv) { + // Try to instantiate the logging system API + std::unique_ptr<cta::log::SyslogLogger> logPtr; + try { + logPtr.reset(new cta::log::SyslogLogger("tapeserverd")); + } catch(cta::exception::Exception &ex) { + std::cerr << + "Failed to instantiate object representing CTA logging system: " << + ex.getMessage().str() << std::endl; + return 1; + } + cta::log::Logger &log = *logPtr; + + int programRc = 1; // Be pessimistic + try { + programRc = exceptionThrowingMain(argc, argv, log); + } catch(cta::exception::Exception &ex) { + cta::log::Param params[] = { + cta::log::Param("message", ex.getMessage().str())}; + log(LOG_ERR, "Caught an unexpected CASTOR exception", params); + } catch(std::exception &se) { + cta::log::Param params[] = {cta::log::Param("what", se.what())}; + log(LOG_ERR, "Caught an unexpected standard exception", params); + } catch(...) { + log(LOG_ERR, "Caught an unexpected and unknown exception"); + } + + google::protobuf::ShutdownProtobufLibrary(); + return programRc; +} + +//------------------------------------------------------------------------------ +// Logs the start of the daemon. +//------------------------------------------------------------------------------ +static void logStartOfDaemon(cta::log::Logger &log, const int argc, + const char *const *const argv); + +//------------------------------------------------------------------------------ +// Creates a string that contains the specified command-line arguments +// separated by single spaces. +// +// @param argc The number of command-line arguments. +// @param argv The array of command-line arguments. +//------------------------------------------------------------------------------ +static std::string argvToString(const int argc, const char *const *const argv); + +////------------------------------------------------------------------------------ +//// Writes the specified TPCONFIG lines to the specified logging system. +//// +//// @param log The logging system. +//// @param lines The lines parsed from /etc/castor/TPCONFIG. +////------------------------------------------------------------------------------ +//static void logTpconfigLines(cta::log::Logger &log, +// const cta::tape::daemon::TpconfigLines &lines); +// +////------------------------------------------------------------------------------ +//// Writes the specified TPCONFIG lines to the logging system. +//// +//// @param log The logging system. +//// @param line The line parsed from /etc/castor/TPCONFIG. +////------------------------------------------------------------------------------ +//static void logTpconfigLine(cta::log::Logger &log, +// const cta::tape::daemon::TpconfigLine &line); + +//------------------------------------------------------------------------------ +// exceptionThrowingMain +//------------------------------------------------------------------------------ +static int exceptionThrowingMain(const int argc, char **const argv, + cta::log::Logger &log) { + using namespace cta::tape::daemon; + + logStartOfDaemon(log, argc, argv); + + // Parse /etc/cta/cta.conf and /etc/cta/TPCONFIG for global parameters + const GlobalConfiguration globalConfig = + GlobalConfiguration::createFromCtaConf(log); + + // Create the object providing utilities for working with UNIX capabilities + cta::server::ProcessCap capUtils; + + // Create the main tapeserverd object + cta::tape::daemon::TapeDaemon daemon( + argc, + argv, + std::cout, + std::cerr, + log, + globalConfig, + capUtils); + + // Run the tapeserverd daemon + return daemon.main(); +} + +//------------------------------------------------------------------------------ +// logStartOfDaemon +//------------------------------------------------------------------------------ +static void logStartOfDaemon(cta::log::Logger &log, const int argc, + const char *const *const argv) { + + const std::string concatenatedArgs = argvToString(argc, argv); + cta::log::Param params[] = { + cta::log::Param("version", CTA_VERSION), + cta::log::Param("argv", concatenatedArgs)}; + log(LOG_INFO, "tapeserverd started", params); +} + +//------------------------------------------------------------------------------ +// argvToString +//------------------------------------------------------------------------------ +static std::string argvToString(const int argc, const char *const *const argv) { + std::string str; + + for(int i=0; i < argc; i++) { + if(i != 0) { + str += " "; + } + + str += argv[i]; + } + return str; +} + +////------------------------------------------------------------------------------ +//// logTpconfigLines +////------------------------------------------------------------------------------ +//static void logTpconfigLines(cta::log::Logger &log, +// const cta::tape::daemon::TpconfigLines &lines) { +// using namespace cta::tape::daemon; +// +// for(TpconfigLines::const_iterator itor = lines.begin(); +// itor != lines.end(); itor++) { +// logTpconfigLine(log, *itor); +// } +//} +// +////------------------------------------------------------------------------------ +//// logTpconfigLine +////------------------------------------------------------------------------------ +//static void logTpconfigLine(cta::log::Logger &log, +// const cta::tape::daemon::TpconfigLine &line) { +// cta::log::Param params[] = { +// cta::log::Param("unitName", line.unitName), +// cta::log::Param("logicalLibrary", line.logicalLibrary), +// cta::log::Param("devFilename", line.devFilename), +// cta::log::Param("librarySlot", line.librarySlot)}; +// log(LOG_INFO, "TPCONFIG line", params); +//} diff --git a/tapeserver/castor/tape/tapeserver/daemon/tapeserverd.init b/tapeserver/cta-taped.init similarity index 100% rename from tapeserver/castor/tape/tapeserver/daemon/tapeserverd.init rename to tapeserver/cta-taped.init diff --git a/tapeserver/castor/tape/tapeserver/daemon/castor-tapeserver-server.logrotate b/tapeserver/cta-taped.logrotate similarity index 100% rename from tapeserver/castor/tape/tapeserver/daemon/castor-tapeserver-server.logrotate rename to tapeserver/cta-taped.logrotate diff --git a/tapeserver/castor/tape/tapeserver/daemon/tapeserverd.man b/tapeserver/cta-taped.man similarity index 100% rename from tapeserver/castor/tape/tapeserver/daemon/tapeserverd.man rename to tapeserver/cta-taped.man diff --git a/tapeserver/castor/tape/tapeserver/daemon/tapeserverd.sysconfig b/tapeserver/cta-taped.sysconfig similarity index 100% rename from tapeserver/castor/tape/tapeserver/daemon/tapeserverd.sysconfig rename to tapeserver/cta-taped.sysconfig diff --git a/tapeserver/daemon/CMakeLists.txt b/tapeserver/daemon/CMakeLists.txt new file mode 100644 index 0000000000..cd4e6782e1 --- /dev/null +++ b/tapeserver/daemon/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required (VERSION 2.6) + +add_library(ctatapedaemon + GlobalConfiguration.cpp + TapeDaemon.cpp + TpconfigLine.cpp + TpconfigLines.cpp) \ No newline at end of file diff --git a/tapeserver/daemon/DriveConfiguration.hpp b/tapeserver/daemon/DriveConfiguration.hpp new file mode 100644 index 0000000000..a93713cced --- /dev/null +++ b/tapeserver/daemon/DriveConfiguration.hpp @@ -0,0 +1,96 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <string> +#include <cstdint> +#include "common/log/DummyLogger.hpp" + +namespace cta { namespace tape { namespace daemon { +/** + * Class containing all the parameters needed by the transfer process + * to manage its drive + */ +struct DriveConfiguration { + /** The name of the drive */ + std::string unitName; + + /** The logical library to which the drive belongs */ + std::string logicalLibrary; + + /** The filename of the device file of the tape drive. */ + std::string devFilename; + + /** The slot in the tape library that contains the tape drive. */ + std::string librarySlot; + + /** The size in bytes of a data-transfer buffer. */ + uint32_t bufferSize; + + /** The total number of data-transfer buffers to be instantiated. */ + uint32_t bufferCount; + + /** The structure representing the maximum number of bytes and files + cta-taped will fetch or report in one access to the object store*/ + struct fetchReportOrFLushLimits { + uint64_t maxBytes; + uint64_t maxFiles; + fetchReportOrFLushLimits(): maxBytes(0), maxFiles(0) {} + }; + + /** Archive jobs fetch/report limits */ + fetchReportOrFLushLimits bulkArchive; + + /** Retrieve jobs fetch/report limits */ + fetchReportOrFLushLimits bulkRetrieve; + + /** Amount of data/files after which we will flush data to tape (archive only) */ + fetchReportOrFLushLimits flushData; + + /** The number of disk I/O threads. */ + uint32_t nbDiskThreads; + + /** Constructor that sets all integer member-variables to 0 and all string + * member-variables to the empty string. */ + DriveConfiguration(): bufferSize(0), bufferCount(0), nbDiskThreads(0) {}; + + /** Returns a configuration structure based on the contents of + * /etc/cta/cta.conf, /etc/cta/TPCONFIG and compile-time constants. + * + * @param log pointer to NULL or an optional logger object. + * @return The configuration structure. */ + static DriveConfiguration createFromCtaConf( + cta::log::Logger & log = gDummyLogger); + + /** Returns a configuration structure based on the contents of + * /etc/cta/cta.conf, /etc/cta/TPCONFIG and compile-time constants. + * + * @param log pointer to NULL or an optional logger object. + * @return The configuration structure. */ + static DriveConfiguration createFromCtaConf( + const std::string & generalConfigPath, + const std::string & tapeConfigFile, + cta::log::Logger & log = gDummyLogger); + +private: + /** A private dummy logger which will simplify the implementaion of the + * functions (just unconditionally log things). */ + static cta::log::DummyLogger gDummyLogger; +}; // DriveConfiguration + +}}} // namespace cta::tape::daemon \ No newline at end of file diff --git a/tapeserver/daemon/GlobalConfiguration.cpp b/tapeserver/daemon/GlobalConfiguration.cpp new file mode 100644 index 0000000000..6d14b9f24e --- /dev/null +++ b/tapeserver/daemon/GlobalConfiguration.cpp @@ -0,0 +1,36 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "GlobalConfiguration.hpp" + +namespace cta { namespace tape { namespace daemon { + +GlobalConfiguration GlobalConfiguration::createFromCtaConf(cta::log::Logger& log) { + return createFromCtaConf("/etc/cta/cta.conf", "/etc/cta/TPCONFIG", log); +} + +GlobalConfiguration GlobalConfiguration::createFromCtaConf( + const std::string& generalConfigPath, + const std::string& tapeConfigFile, cta::log::Logger& log) { + GlobalConfiguration ret; + return ret; +} + +cta::log::DummyLogger GlobalConfiguration::gDummyLogger(""); + +}}} // namespace cta::tape::daemon diff --git a/tapeserver/daemon/GlobalConfiguration.hpp b/tapeserver/daemon/GlobalConfiguration.hpp new file mode 100644 index 0000000000..d136c0cffc --- /dev/null +++ b/tapeserver/daemon/GlobalConfiguration.hpp @@ -0,0 +1,47 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <string> +#include <map> +#include "DriveConfiguration.hpp" +#include "common/log/DummyLogger.hpp" + +namespace cta { +namespace tape { +namespace daemon { +/** + * Class containing all the parameters needed by the watchdog process + * to spawn a transfer session per drive. + */ +struct GlobalConfiguration { + static GlobalConfiguration createFromCtaConf( + cta::log::Logger &log = gDummyLogger); + static GlobalConfiguration createFromCtaConf( + const std::string & generalConfigPath, + const std::string & tapeConfigFile, + cta::log::Logger & log = gDummyLogger); + std::map<std::string, DriveConfiguration> driveConfigs; +private: + /** A private dummy logger which will simplify the implementaion of the + * functions (just unconditionally log things). */ + static cta::log::DummyLogger gDummyLogger; +} ; +} +} +} // namespace cta::tape::daemon \ No newline at end of file diff --git a/tapeserver/daemon/TapeDaemon.cpp b/tapeserver/daemon/TapeDaemon.cpp new file mode 100644 index 0000000000..b943333917 --- /dev/null +++ b/tapeserver/daemon/TapeDaemon.cpp @@ -0,0 +1,172 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TapeDaemon.hpp" +#include "common/exception/Errnum.hpp" +#include "common/utils/Utils.hpp" +#include <google/protobuf/service.h> + +namespace cta { namespace tape { namespace daemon { + +TapeDaemon::TapeDaemon(const int argc, char* * const argv, + std::ostream& stdOut, std::ostream& stdErr, + log::Logger& log, + const GlobalConfiguration& globalConfig, + cta::server::ProcessCap& capUtils): + cta::server::Daemon(stdOut, stdErr, log), + m_argc(argc), m_argv(argv), + m_globalConfiguration(globalConfig), m_capUtils(capUtils), + m_programName("cta-taped"), m_hostName(getHostName()) { } + +TapeDaemon::~TapeDaemon() { + google::protobuf::ShutdownProtobufLibrary(); +} + +//------------------------------------------------------------------------------ +// main +//------------------------------------------------------------------------------ +int TapeDaemon::main() { + try { + + exceptionThrowingMain(m_argc, m_argv); + + } catch (cta::exception::Exception &ex) { + // Write the error to standard error + m_stdErr << std::endl << "Aborting: " << ex.getMessage().str() << std::endl + << std::endl; + + // Log the error + log::Param params[] = { + log::Param("Message", ex.getMessage().str())}; + m_log(LOG_ERR, "Aborting", params); + + return 1; + } + + return 0; +} + +//------------------------------------------------------------------------------ +// getHostName +//------------------------------------------------------------------------------ +std::string cta::tape::daemon::TapeDaemon::getHostName() const { + char nameBuf[81]; + if(gethostname(nameBuf, sizeof(nameBuf))) + throw cta::exception::Errnum("Failed to get host name"); + return nameBuf; +} + +//------------------------------------------------------------------------------ +// exceptionThrowingMain +//------------------------------------------------------------------------------ +void cta::tape::daemon::TapeDaemon::exceptionThrowingMain( + const int argc, char **const argv) { + parseCommandLine(argc, argv); + + if(m_globalConfiguration.driveConfigs.empty()) + throw cta::exception::Exception("/etc/cta/TPCONFIG is empty"); + + // Process must be able to change user now and should be permitted to perform + // raw IO in the future + setProcessCapabilities("cap_setgid,cap_setuid+ep cap_sys_rawio+p"); + + const bool runAsStagerSuperuser = true; + daemonizeIfNotRunInForeground(runAsStagerSuperuser); + setDumpable(); + + // There is no longer any need for the process to be able to change user, + // however the process should still be permitted to perform raw IO in the + // future + setProcessCapabilities("cap_sys_rawio+p"); + + blockSignals(); + mainEventLoop(); +} + +//------------------------------------------------------------------------------ +// mainEventLoop +//------------------------------------------------------------------------------ +void cta::tape::daemon::TapeDaemon::mainEventLoop() { + throw cta::exception::Exception("cta::tape::daemon::TapeDaemon::mainEventLoop: not implemented"); +// while (handleIOEvents() && handleTick() && handlePendingSignals()) { +// } +} + +//------------------------------------------------------------------------------ +// setDumpable +//------------------------------------------------------------------------------ +void cta::tape::daemon::TapeDaemon::setDumpable() { + cta::Utils::setDumpableProcessAttribute(true); + const bool dumpable = cta::Utils::getDumpableProcessAttribute(); + log::Param params[] = { + log::Param("dumpable", dumpable ? "true" : "false")}; + m_log(LOG_INFO, "Got dumpable attribute of process", params); + if(!dumpable) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to set dumpable attribute of process to true"; + throw ex; + } +} + +//------------------------------------------------------------------------------ +// setProcessCapabilities +//------------------------------------------------------------------------------ +void cta::tape::daemon::TapeDaemon::setProcessCapabilities( + const std::string &text) { + try { + m_capUtils.setProcText(text); + log::Param params[] = + {log::Param("capabilities", m_capUtils.getProcText())}; + m_log(LOG_INFO, "Set process capabilities", params); + } catch(cta::exception::Exception &ne) { + cta::exception::Exception ex; + ex.getMessage() << "Failed to set process capabilities to '" << text << + "': " << ne.getMessage().str(); + throw ex; + } +} + +//------------------------------------------------------------------------------ +// blockSignals +//------------------------------------------------------------------------------ +void cta::tape::daemon::TapeDaemon::blockSignals() const { + sigset_t sigs; + sigemptyset(&sigs); + // The signals that should not asynchronously disturb the daemon + sigaddset(&sigs, SIGHUP); + sigaddset(&sigs, SIGINT); + sigaddset(&sigs, SIGQUIT); + sigaddset(&sigs, SIGPIPE); + sigaddset(&sigs, SIGTERM); + sigaddset(&sigs, SIGUSR1); + sigaddset(&sigs, SIGUSR2); + sigaddset(&sigs, SIGCHLD); + sigaddset(&sigs, SIGTSTP); + sigaddset(&sigs, SIGTTIN); + sigaddset(&sigs, SIGTTOU); + sigaddset(&sigs, SIGPOLL); + sigaddset(&sigs, SIGURG); + sigaddset(&sigs, SIGVTALRM); + cta::exception::Errnum::throwOnNonZero( + sigprocmask(SIG_BLOCK, &sigs, NULL), + "Failed to block signals: sigprocmask() failed"); +} + + + +}}} // namespace cta::tape::daemon \ No newline at end of file diff --git a/tapeserver/daemon/TapeDaemon.hpp b/tapeserver/daemon/TapeDaemon.hpp new file mode 100644 index 0000000000..dea03694a3 --- /dev/null +++ b/tapeserver/daemon/TapeDaemon.hpp @@ -0,0 +1,266 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/threading/Daemon.hpp" +#include "tapeserver/daemon/GlobalConfiguration.hpp" +#include "common/processCap/ProcessCap.hpp" +#include <signal.h> + + +namespace cta { namespace tape { namespace daemon { + +/** Daemon responsible for reading and writing data from and to one or more tape + * drives drives connected to a tape server. */ + +class TapeDaemon : public cta::server::Daemon { + +public: + + /** Constructor. + * @param argc The argc of main(). + * @param argv The argv of main(). + * @param stdOut Stream representing standard out. + * @param stdErr Stream representing standard error. + * @param log The object representing the API of the CTA logging system. + * @param globalConfig The configuration of the tape server. + * @param capUtils Object providing utilities for working UNIX capabilities. */ + TapeDaemon( + const int argc, + char **const argv, + std::ostream &stdOut, + std::ostream &stdErr, + log::Logger &log, + const GlobalConfiguration &globalConfig, + cta::server::ProcessCap &capUtils); + + virtual ~TapeDaemon(); + + /** The main entry function of the daemon. + * @return The return code of the process. */ + int main(); + +protected: + + /** Enumeration of the possible tape-daemon states. */ + enum State { + TAPEDAEMON_STATE_RUNNING, + TAPEDAEMON_STATE_SHUTTINGDOWN}; + + /** Return the string representation of the specified tape-daemon state. + * @param The tape-daemon state. + * @return The string representation. */ + static const char *stateToStr(const State state) throw(); + + /** The current state of the tape-server daemon. */ + State m_state; + + /** The absolute time at which the shutdown sequence was started. */ + time_t m_startOfShutdown; + + /** Returns the name of the host on which the daemon is running. */ + std::string getHostName() const; + + /** Exception throwing main() function. + * @param argc The number of command-line arguments. + * @param argv The array of command-line arguments. */ + void exceptionThrowingMain(const int argc, char **const argv); + + /** Sets the dumpable attribute of the current process to true. */ + void setDumpable(); + + /** Sets the capabilities of the current process. + * + * @text The string representation the capabilities that the current + * process should have. */ + void setProcessCapabilities(const std::string &text); + + /** Socket pair used to send commands to the DriveProcess. */ + struct DriveSocketPair { + /** Bi-directional socket used by the TapeDaemon parent process to send + * commands to the process forker and receive replies in return. */ + int tapeDaemon; + + /** Bi-directional socket used by the ProcessForker to receive commands + * from the TapeDaemon parent process and send back replies. */ + int driveProcess; + + /** Constructor. + * Sets members to -1 which represents an invalid file descriptor. */ + DriveSocketPair(): tapeDaemon(-1), driveProcess(-1) { + } + /** Close utility. Closes both sockets */ + void closeBoth(); + /** Close utility. Closes drive's socket */ + void closeDriveEnd(); + /** Close utility. Closes daemon's socket */ + void closeDaemonEnd(); + }; // struct DriveSocketPair + + /** Creates the socket pair to be used to control the ProcessForker. + * @return The socket pair. */ + DriveSocketPair createDriveSocketPair(); + + /** + * Forks a drive process. + * + * PLEASE NOTE: No sockets should be registered with m_reactor before this + * method is called. This method will NOT call m_reactor.clear() in the + * client process. This is because it is possible to put ZMQ sockets into the + * reactor and one should not manipulate such sockets in two difefrent threads + * or processes. Specifically do not call setUpReactor() until + * forkProcessForker() has been called. + * + * @param cmdPair Socket pair used to send commands to the ProcessForker. + * @param reaperPair Socket pair used by the ProcessForker to notify the + * TapeDaemon parent process of the termination of ProcessForker child + * processes. + * by the ProcessForker. + * @return The process identifier of the ProcessForker. + */ + pid_t forkDriveProcess(const DriveSocketPair &drivePair); + + /** Runs the driveProcess after fork + * + * @param heartbeatSocket The socket used to send heartbeat to. + * @return the exit code to be used for the process running the DriveProcess. */ + int runDriveProcess(const int heartbeatSocket); + + /** Blocks the signals that should not asynchronously disturb the daemon. */ + void blockSignals() const; + + /** + * Creates the handler to handle the incoming connection from the + * ProcessForker. + * + * @param reaperSocket The TapeDaemon side of the socket pair used by the + * ProcessForker to report the termination of its child processes. + */ + void createAndRegisterProcessForkerConnectionHandler(const int reaperSocket); + + /** + * Creates the handler to handle messages from forked sessions. + */ + void createAndRegisterTapeMessageHandler(); + + /** + * The main event loop of the daemon. + */ + void mainEventLoop(); + + /** + * Handles any pending IO events. + * + * @return True if the main event loop should continue, else false. + */ + bool handleIOEvents() throw(); + + /** + * Handles a tick in time. Time driven actions such as alarms should be + * implemented here. + * + * This method does not have to be called at any time precise interval, + * though it should be called at least twice as fast as the quickest reaction + * time imposed on the catalogue. + * + * @return True if the main event loop should continue, else false. + */ + bool handleTick() throw(); + + /** + * Handles any pending signals. + * + * @return True if the main event loop should continue, else false. + */ + bool handlePendingSignals() throw(); + + /** + * Handles the specified signals. + * + * @param sig The number of the signal. + * @param sigInfo Information about the signal. + * @return True if the main event loop should continue, else false. + */ + bool handleSignal(const int sig, const siginfo_t &sigInfo); + + /** + * Handles a SIGINT signal. + * + * @param sigInfo Information about the signal. + * @return True if the main event loop should continue, else false. + */ + bool handleSIGINT(const siginfo_t &sigInfo); + + /** + * Handles a SIGTERM signal. + * + * @param sigInfo Information about the signal. + * @return True if the main event loop should continue, else false. + */ + bool handleSIGTERM(const siginfo_t &sigInfo); + + /** + * Handles a SIGCHLD signal. + * + * @param sigInfo Information about the signal. + * @return True if the main event loop should continue, else false. + */ + bool handleSIGCHLD(const siginfo_t &sigInfo); + + /** + * Logs the fact that the specified child process has terminated. + * + * @param pid The process ID of the child process. + * @param waitpidStat The status information given by a call to waitpid(). + */ + void logChildProcessTerminated(const pid_t pid, const int waitpidStat) + throw(); + + /** + * The argc of main(). + */ + const int m_argc; + + /** + * The argv of main(). + */ + char **const m_argv; + + /** The tape server's configuration */ + const GlobalConfiguration& m_globalConfiguration; + + /** + * Object providing utilities for working UNIX capabilities. + */ + cta::server::ProcessCap &m_capUtils; + + /** + * The program name of the daemon. + */ + const std::string m_programName; + + /** + * The name of the host on which the daemon is running. This name is + * needed to fill in messages to be sent to the vdqmd daemon. + */ + const std::string m_hostName; + +}; // class TapeDaemon + +}}} // namespace cta::tape::daemon diff --git a/tapeserver/daemon/TpconfigLine.cpp b/tapeserver/daemon/TpconfigLine.cpp new file mode 100644 index 0000000000..9f30af84c4 --- /dev/null +++ b/tapeserver/daemon/TpconfigLine.cpp @@ -0,0 +1,33 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "tapeserver/daemon/TpconfigLine.hpp" + +//------------------------------------------------------------------------------ +// Constructor. +//------------------------------------------------------------------------------ +cta::tape::daemon::TpconfigLine::TpconfigLine( + const std::string &unitName, + const std::string &logicalLibrary, + const std::string &devFilename, + const std::string &librarySlot) throw(): + unitName(unitName), + logicalLibrary(logicalLibrary), + devFilename(devFilename), + librarySlot(librarySlot) { +} diff --git a/tapeserver/daemon/TpconfigLine.hpp b/tapeserver/daemon/TpconfigLine.hpp new file mode 100644 index 0000000000..a481da6777 --- /dev/null +++ b/tapeserver/daemon/TpconfigLine.hpp @@ -0,0 +1,69 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <string> + +namespace cta { namespace tape { namespace daemon { + +/** + * The data stored in a data-line (as opposed to a comment-line) from a + * TPCONFIG file (/etc/castor/TPCONFIG). + */ +struct TpconfigLine { + /** + * The unit name of the tape drive. + */ + std::string unitName; + + /** + * The logical library of the tape drive. + */ + std::string logicalLibrary; + + /** + * The filename of the device file of the tape drive. + */ + std::string devFilename; + + /** + * The slot in the tape library that contains the tape drive. + */ + std::string librarySlot; + + /** + * Constructor. + * + * @param unitName The unit name of the tape drive. + * @param dgn The Device Group Name (DGN) of the tape drive. + * @param devFilename The filename of the device file of the tape drive. + * @param librarySlot The slot in the tape library that contains the tape + * drive. + */ + TpconfigLine( + const std::string &unitName, + const std::string &logicalLibrary, + const std::string &devFilename, + const std::string &librarySlot) throw(); + + static const size_t maxUnitNameLen; + static const size_t maxLogicalLibraryNameLen; +}; // struct TpconfigLine + +}}} // namespace cta::tape::daemon diff --git a/tapeserver/daemon/TpconfigLines.cpp b/tapeserver/daemon/TpconfigLines.cpp new file mode 100644 index 0000000000..1364226913 --- /dev/null +++ b/tapeserver/daemon/TpconfigLines.cpp @@ -0,0 +1,167 @@ +/****************************************************************************** + * + * This file is part of the Castor project. + * See http://castor.web.cern.ch/castor + * + * Copyright (C) 2003 CERN + * This program is free software; you can 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 + * of the License, 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; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * + * + * + * @author Castor Dev team, castor-dev@cern.ch + *****************************************************************************/ + +#include "tapeserver/daemon/TpconfigLines.hpp" +#include "common/utils/Utils.hpp" +#include "common/exception/Errnum.hpp" + +#include <errno.h> +#include <memory> + +//------------------------------------------------------------------------------ +// parseTpconfigFile +//------------------------------------------------------------------------------ +cta::tape::daemon::TpconfigLines +cta::tape::daemon::TpconfigLines::parseFile(const std::string &filename) { + TpconfigLines lines; + + // Open the TPCONFIG file for reading + std::unique_ptr<FILE, decltype(&::fclose)> file(fopen(filename.c_str(), "r"), &::fclose); + { + const int savedErrno = errno; + + // Throw an exception if the file could not be opened + if(file.get() == NULL) { + cta::exception::Errnum ex(savedErrno); + + ex.getMessage() << + "Failed to parse TPCONFIG file" + ": Failed to open file" + ": filename='" << filename << "'" + ": " << cta::Utils::errnoToString(savedErrno); + + throw ex; + } + } + + // Line buffer + char lineBuf[1024]; + + // The error number recorded immediately after fgets is called + int fgetsErrno = 0; + + // For each line was read + for(int lineNb = 1; fgets(lineBuf, sizeof(lineBuf), file.get()); lineNb++) { + fgetsErrno = errno; + + // Create a std::string version of the line + std::string line(lineBuf); + + // Remove the newline character if there is one + { + const std::string::size_type newlinePos = line.find("\n"); + if(newlinePos != std::string::npos) { + line = line.substr(0, newlinePos); + } + } + + // If there is a comment, then remove it from the line + { + const std::string::size_type startOfComment = line.find("#"); + if(startOfComment != std::string::npos) { + line = line.substr(0, startOfComment); + } + } + + // Left and right trim the line of whitespace + line = cta::Utils::trimString(std::string(line)); + + // If the line is not empty + if(line != "") { + + // Replace each occurance of whitespace with a single space + line = cta::Utils::singleSpaceString(line); + + // Split the line into its component data-columns + std::vector<std::string> columns; + cta::Utils::splitString(line, ' ', columns); + + // The expected number of data-columns in a TPCONFIG data-line is 4: + // unitName dgn devFilename librarySlot + const unsigned int expectedNbOfColumns = 4; + + // Throw an exception if the number of data-columns is invalid + if(columns.size() != expectedNbOfColumns) { + InvalidArgument ex; + ex.getMessage() << + "Failed to parse TPCONFIG file" + ": Invalid number of data columns in TPCONFIG line" + ": filename='" << filename << "'" + " lineNb=" << lineNb << + " expectedNbColumns=" << expectedNbOfColumns << + " actualNbColumns=" << columns.size() << + " expectedFormat='unitName dgn devFilename librarySlot'"; + throw ex; + } + + const TpconfigLine configLine( + columns[0], // unitName + columns[1], // logicalLibrary + columns[2], // devFilename + columns[3] // librarySlot + ); + + if(TpconfigLine::maxUnitNameLen < configLine.unitName.length()) { + InvalidArgument ex; + ex.getMessage() << + "Failed to parse TPCONFIG file" + ": Tape-drive unit-name is too long" + ": filename='" << filename << "'" + " lineNb=" << lineNb << + " unitName=" << configLine.unitName << + " maxUnitNameLen=" << TpconfigLine::maxUnitNameLen << + " actualUnitNameLen=" << configLine.unitName.length(); + throw ex; + } + + if(TpconfigLine::maxLogicalLibraryNameLen < configLine.logicalLibrary.length()) { + InvalidArgument ex; + ex.getMessage() << + "Failed to parse TPCONFIG file" + ": logical library is too long" + ": filename='" << filename << "'" + " lineNb=" << lineNb << + " logicalLibrary=" << configLine.logicalLibrary << + " maxLogicalLibraryLen=" << TpconfigLine::maxLogicalLibraryNameLen << + " actualLogicalLibraryLen=" << configLine.logicalLibrary.length(); + throw ex; + } + + // Store the value of the data-columns in the output list parameter + lines.push_back(TpconfigLine(configLine)); + } + } + + // Throw an exception if there was error whilst reading the file + if(ferror(file.get())) { + std::stringstream err; + err <<"Failed to parse TPCONFIG file" + ": Failed to read file" + ": filename='" << filename << "'"; + cta::exception::Errnum ex(fgetsErrno, err.str()); + throw ex; + } + + return lines; +} diff --git a/tapeserver/daemon/TpconfigLines.hpp b/tapeserver/daemon/TpconfigLines.hpp new file mode 100644 index 0000000000..0810f48a01 --- /dev/null +++ b/tapeserver/daemon/TpconfigLines.hpp @@ -0,0 +1,44 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "tapeserver/daemon/TpconfigLine.hpp" +#include "common/exception/Exception.hpp" + +#include <list> + +namespace cta { namespace tape { namespace daemon { + +/** + * A list of lines parsed from a TPCONFIG file. + */ +class TpconfigLines: public std::list<TpconfigLine> { +public: + + CTA_GENERATE_EXCEPTION_CLASS(InvalidArgument); + /** + * Parses the specified TPCONFIG file. + * + * @param filename The filename of the TPCONFIG file. + * @return The result of parsing the TPCONFIG file. + */ + static TpconfigLines parseFile(const std::string &filename); +}; // class TpconfigLines + +}}} // namespace cta::tape::daemon diff --git a/version.hpp.in b/version.hpp.in new file mode 100644 index 0000000000..d9e67f98f4 --- /dev/null +++ b/version.hpp.in @@ -0,0 +1,22 @@ +/* + * The CERN Tape Archive (CTA) project + * Copyright (C) 2015 CERN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#define CTA_VERSION "@CTA_VERSION@-@CTA_REPLEASE@" + -- GitLab