From 772c959b81dc118d0e1c2fe49931452945eb0e67 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 17 May 2018 16:55:58 +0200 Subject: [PATCH 001/105] intercept fopen --- ifs/include/preload/passthrough.hpp | 4 +-- ifs/src/preload/intcp_functions.cpp | 40 +++++++++++++++++++---------- ifs/src/preload/passthrough.cpp | 4 +-- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 60b032b2e..97d41b6c5 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -9,8 +9,8 @@ extern void* libc; extern void* libc_open; -extern void* libc_fopen; // XXX Does not work with streaming pointers. If used will block forever -extern void* libc_fopen64; // XXX Does not work with streaming pointers. If used will block forever +extern void* libc_fopen; +extern void* libc_fopen64; extern void* libc_mkdir; extern void* libc_mkdirat; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 55d27c8ee..3e97c91ad 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -52,19 +52,33 @@ int open64(const char* path, int flags, ...) { return open(path, flags | O_LARGEFILE, mode); } -//// TODO This function somehow always blocks forever if one puts anything between the paththru... -//FILE* fopen(const char* path, const char* mode) { -//// init_passthrough_if_needed(); -//// DAEMON_DEBUG(debug_fd, "fopen called with path %s\n", path); -// return (reinterpret_cast(libc_fopen))(path, mode); -//} - -//// TODO This function somehow always blocks forever if one puts anything between the paththru... -//FILE* fopen64(const char* path, const char* mode) { -//// init_passthrough_if_needed(); -//// DAEMON_DEBUG(debug_fd, "fopen64 called with path %s\n", path); -// return (reinterpret_cast(libc_fopen))(path, mode); -//} +FILE* fopen(const char* path, const char* mode) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, mode); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return nullptr; + } + } + return (reinterpret_cast(libc_fopen))(path, mode); +} + +FILE* fopen64(const char* path, const char* mode) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, mode); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return nullptr; + } + } + return (reinterpret_cast(libc_fopen64))(path, mode); +} #undef creat diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 7203939a7..e7e927609 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -76,8 +76,8 @@ void init_passthrough_() { } libc_open = dlsym(libc, "open"); -// libc_fopen = dlsym(libc, "fopen"); -// libc_fopen64 = dlsym(libc, "fopen64"); + libc_fopen = dlsym(libc, "fopen"); + libc_fopen64 = dlsym(libc, "fopen64"); libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From ee4ebce4d0af6a3e7eb70ba0c227e102049836e7 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 17 May 2018 18:40:49 +0200 Subject: [PATCH 002/105] Implement fopen --- ifs/src/preload/intcp_functions.cpp | 51 ++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 3e97c91ad..5edb7ab43 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -52,34 +52,61 @@ int open64(const char* path, int flags, ...) { return open(path, flags | O_LARGEFILE, mode); } -FILE* fopen(const char* path, const char* mode) { +/****** FILE OPS ******/ + +inline int file_to_fd(const FILE* f){ + assert(f != nullptr); + return *(reinterpret_cast(&f)); +} + +inline FILE* fd_to_file(const int fd){ + assert(fd >= 0); + return reinterpret_cast(fd); +} + +FILE* fopen(const char* path, const char* fmode) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, mode); + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); std::string rel_path(path); if (CTX->relativize_path(rel_path)) { - CTX->log()->error("{}() NOT SUPPORTED", __func__); - errno = ENOTSUP; - return nullptr; + int flags = 0; + std::string str_mode(fmode); + if(str_mode == "r") { + flags = O_RDONLY; + } else if(str_mode == "r+") { + flags = O_RDWR; + } else { + CTX->log()->error("{}() stream open flags NOT SUPPORTED: '{}'", __func__, str_mode); + errno = ENOTSUP; + return nullptr; + } + mode_t mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + auto fd = adafs_open(rel_path, mode, flags); + if(fd == -1){ + return nullptr; + } else { + return fd_to_file(fd); + } } } - return (reinterpret_cast(libc_fopen))(path, mode); + return (reinterpret_cast(libc_fopen))(path, fmode); } -FILE* fopen64(const char* path, const char* mode) { +FILE* fopen64(const char* path, const char* fmode) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, mode); + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); std::string rel_path(path); if (CTX->relativize_path(rel_path)) { - CTX->log()->error("{}() NOT SUPPORTED", __func__); - errno = ENOTSUP; - return nullptr; + return fopen(path, fmode); } } - return (reinterpret_cast(libc_fopen64))(path, mode); + return (reinterpret_cast(libc_fopen64))(path, fmode); } +/****** FILE OPS ******/ + #undef creat int creat(const char* path, mode_t mode) { -- GitLab From 2781e206aae9cbbfe0cc80cbf83475ec52d66c73 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 17 May 2018 19:55:02 +0200 Subject: [PATCH 003/105] implement file family functions --- ifs/include/preload/passthrough.hpp | 8 +++ ifs/src/preload/intcp_functions.cpp | 107 +++++++++++++++++++++++++++- ifs/src/preload/passthrough.cpp | 21 +++++- 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 97d41b6c5..085e4e167 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -9,8 +9,16 @@ extern void* libc; extern void* libc_open; + extern void* libc_fopen; extern void* libc_fopen64; +extern void* libc_fread; +extern void* libc_fwrite; +extern void* libc_fclose; +extern void* libc_clearerr; +extern void* libc_feof; +extern void* libc_ferror; +extern void* libc_fileno; extern void* libc_mkdir; extern void* libc_mkdirat; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 5edb7ab43..ae2e4b88a 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -76,6 +76,8 @@ FILE* fopen(const char* path, const char* fmode) { flags = O_RDONLY; } else if(str_mode == "r+") { flags = O_RDWR; + } else if(str_mode == "w") { + flags = (O_WRONLY | O_CREAT | O_TRUNC); } else { CTX->log()->error("{}() stream open flags NOT SUPPORTED: '{}'", __func__, str_mode); errno = ENOTSUP; @@ -102,7 +104,110 @@ FILE* fopen64(const char* path, const char* fmode) { return fopen(path, fmode); } } - return (reinterpret_cast(libc_fopen64))(path, fmode); + return (reinterpret_cast(libc_fopen64))(path, fmode); +} + +size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if (CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + auto adafs_fd = CTX->file_map()->get(fd); + auto pos = adafs_fd->pos(); //retrieve the current offset + auto ret = adafs_pread_ws(fd, ptr, size*nmemb, pos); + if (ret > 0) { + // Update offset in file descriptor in the file map + adafs_fd->pos(pos + ret); + return ret % size; + } + return ret; + } + } + return (reinterpret_cast(libc_fread))(ptr, size, nmemb, stream); +} + +size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if (CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + auto adafs_fd = CTX->file_map()->get(fd); + auto pos = adafs_fd->pos(); //retrieve the current offset + auto ret = adafs_pwrite_ws(fd, ptr, size*nmemb, pos); + if (ret > 0) { + // Update offset in file descriptor in the file map + adafs_fd->pos(pos + ret); + return ret % size; + } + return ret; + } + } + return (reinterpret_cast(libc_fwrite))(ptr, size, nmemb, stream); +} + +int fclose(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + // No call to the daemon is required + CTX->file_map()->remove(fd); + return 0; + } + } + return (reinterpret_cast(libc_fclose))(stream); +} + +int fileno(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + return fd; + } + } + return (reinterpret_cast(libc_fileno))(stream); +} + +void clearerr(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return; + } + } + return (reinterpret_cast(libc_clearerr))(stream); +} + +int feof(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return -1; + } + } + return (reinterpret_cast(libc_feof))(stream); +} + +int ferror(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return -1; + } + } + return (reinterpret_cast(libc_ferror))(stream); } /****** FILE OPS ******/ diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index e7e927609..cd3e8e0e5 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -13,8 +13,16 @@ static pthread_once_t init_lib_thread = PTHREAD_ONCE_INIT; void* libc; void* libc_open; -void* libc_fopen; // XXX Does not work with streaming pointers. If used will block forever -void* libc_fopen64; // XXX Does not work with streaming pointers. If used will block forever + +void* libc_fopen; +void* libc_fopen64; +void* libc_fread; +void* libc_fwrite; +void* libc_fclose; +void* libc_clearerr; +void* libc_feof; +void* libc_ferror; +void* libc_fileno; void* libc_mkdir; void* libc_mkdirat; @@ -76,8 +84,17 @@ void init_passthrough_() { } libc_open = dlsym(libc, "open"); + libc_fopen = dlsym(libc, "fopen"); libc_fopen64 = dlsym(libc, "fopen64"); + libc_fread = dlsym(libc, "fread"); + libc_fwrite = dlsym(libc, "fwrite"); + libc_fclose = dlsym(libc, "fclose"); + libc_clearerr = dlsym(libc, "clearerr"); + libc_feof = dlsym(libc, "feof"); + libc_ferror = dlsym(libc, "ferror"); + libc_fileno = dlsym(libc, "fileno"); + libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From 51c8768a35d37ded8264b8225eff161658db425f Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 17 May 2018 19:56:02 +0200 Subject: [PATCH 004/105] Add bad support for O_TRUNC --- ifs/src/preload/adafs_functions.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index a199797a5..9a4b8ec23 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -37,6 +37,19 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { #endif } + if( flags & O_TRUNC ){ + //TODO truncation leave chunks on the server side + if((flags & O_RDWR) || (flags & O_WRONLY)) { + long updated_size; + auto ret = rpc_send_update_metadentry_size(path.c_str(), 0, 0, false, updated_size); + if (ret != 0) { + CTX->log()->error("{}() update_metadentry_size failed with ret {}", __func__, ret); + errno = EIO; + return -1; // ERR + } + } + } + // TODO the open flags should not be in the map just set the pos accordingly return CTX->file_map()->add(std::make_shared(path, flags)); } -- GitLab From 6013547ef2c1e85a299445b6bd20f9d805648376 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 10:11:29 +0200 Subject: [PATCH 005/105] deps: use most recent cmake version available on centos the cmake 3 binary is called `cmake3`. In the compile script we try to use the most recent version of cmake that is available on the system. This will allow to prevent this mercury installation issue: https://github.com/mercury-hpc/mercury/issues/224 --- ifs/scripts/compile_dep.sh | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index f867acdfd..e413bb904 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -39,6 +39,16 @@ prepare_build_dir() { fi rm -rf $1/build/* } + +find_cmake() { + local CMAKE=`command -v cmake3 || command -v cmake` + if [ $? -ne 0 ]; then + >&2 echo "ERROR: could not find cmake" + exit 1 + fi + echo ${CMAKE} +} + PATCH_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PATCH_DIR="${PATCH_DIR}/patches" CLUSTER="" @@ -137,6 +147,8 @@ USE_BMI="-DNA_USE_BMI:BOOL=OFF" USE_CCI="-DNA_USE_CCI:BOOL=OFF" USE_OFI="-DNA_USE_OFI:BOOL=OFF" +CMAKE=`find_cmake` + echo "Source path = '$1'"; echo "Install path = '$2'"; @@ -168,7 +180,7 @@ if [[ ( "${CLUSTER}" == "mogon1" ) || ( "${CLUSTER}" == "fh2" ) || ( "${CLUSTER} CURR=${SOURCE}/gflags prepare_build_dir ${CURR} cd ${CURR}/build - cmake -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. + $CMAKE -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. make -j${CORES} make install # compile zstd @@ -176,7 +188,7 @@ if [[ ( "${CLUSTER}" == "mogon1" ) || ( "${CLUSTER}" == "fh2" ) || ( "${CLUSTER} CURR=${SOURCE}/zstd/build/cmake prepare_build_dir ${CURR} cd ${CURR}/build - cmake -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. + $CMAKE -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. make -j${CORES} make install echo "############################################################ Installing: lz4" @@ -188,7 +200,7 @@ if [[ ( "${CLUSTER}" == "mogon1" ) || ( "${CLUSTER}" == "fh2" ) || ( "${CLUSTER} CURR=${SOURCE}/snappy prepare_build_dir ${CURR} cd ${CURR}/build - cmake -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. + $CMAKE -DCMAKE_INSTALL_PREFIX=${INSTALL} -DCMAKE_BUILD_TYPE:STRING=Release .. make -j${CORES} make install fi @@ -271,7 +283,7 @@ if [ "$NA_LAYER" == "cci" ] || [ "$NA_LAYER" == "all" ]; then git apply ${PATCH_DIR}/mercury_cci_verbs_lookup.patch fi cd ${CURR}/build -cmake -DMERCURY_USE_SELF_FORWARD:BOOL=ON -DMERCURY_USE_CHECKSUMS:BOOL=OFF -DBUILD_TESTING:BOOL=ON \ +$CMAKE -DMERCURY_USE_SELF_FORWARD:BOOL=ON -DMERCURY_USE_CHECKSUMS:BOOL=OFF -DBUILD_TESTING:BOOL=ON \ -DMERCURY_USE_BOOST_PP:BOOL=ON -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_INSTALL_PREFIX=${INSTALL} \ -DCMAKE_BUILD_TYPE:STRING=Release -DMERCURY_USE_EAGER_BULK:BOOL=ON ${USE_BMI} ${USE_CCI} ${USE_OFI} ../ make -j${CORES} -- GitLab From b62b38d6d01a2c754b3c6974ef8c2cfe3f67ca16 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 10:15:42 +0200 Subject: [PATCH 006/105] Make ChunkStorage aware of chunksize The ChunkStorage class now store the chunksize in order to perform runtime checks upon chunks bundaries. --- ifs/include/daemon/backend/data/chunk_storage.hpp | 3 ++- ifs/src/daemon/adafs_daemon.cpp | 2 +- ifs/src/daemon/backend/data/chunk_storage.cpp | 9 ++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ifs/include/daemon/backend/data/chunk_storage.hpp b/ifs/include/daemon/backend/data/chunk_storage.hpp index 9a43d5f23..e222947e3 100644 --- a/ifs/include/daemon/backend/data/chunk_storage.hpp +++ b/ifs/include/daemon/backend/data/chunk_storage.hpp @@ -18,13 +18,14 @@ class ChunkStorage { std::shared_ptr log; std::string root_path; + size_t chunksize; inline std::string absolute(const std::string& internal_path) const; static inline std::string get_chunks_dir(const std::string& file_path); static inline std::string get_chunk_path(const std::string& file_path, unsigned int chunk_id); void init_chunk_space(const std::string& file_path) const; public: - ChunkStorage(const std::string& path); + ChunkStorage(const std::string& path, const size_t chunksize); void write_chunk(const std::string& file_path, unsigned int chunk_id, const char * buff, size_t size, off64_t offset, ABT_eventual& eventual) const; diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index 871a6e817..c94779f48 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -46,7 +46,7 @@ bool init_environment() { ADAFS_DATA->spdlogger()->debug("{}() Creating chunk storage directory: '{}'", __func__, chunk_storage_path); bfs::create_directories(chunk_storage_path); try { - ADAFS_DATA->storage(std::make_shared(chunk_storage_path)); + ADAFS_DATA->storage(std::make_shared(chunk_storage_path, CHUNKSIZE)); } catch (const std::exception & e) { ADAFS_DATA->spdlogger()->error("{}() unable to initialize storage backend: {}", __func__, e.what()); } diff --git a/ifs/src/daemon/backend/data/chunk_storage.cpp b/ifs/src/daemon/backend/data/chunk_storage.cpp index 0d2f0df7e..92983a003 100644 --- a/ifs/src/daemon/backend/data/chunk_storage.cpp +++ b/ifs/src/daemon/backend/data/chunk_storage.cpp @@ -12,8 +12,9 @@ std::string ChunkStorage::absolute(const std::string& internal_path) const { return root_path + '/' + internal_path; } -ChunkStorage::ChunkStorage(const std::string& path) : - root_path(path) +ChunkStorage::ChunkStorage(const std::string& path, const size_t chunksize) : + root_path(path), + chunksize(chunksize) { //TODO check path: absolute, exists, permission to write etc... assert(is_absolute_path(root_path)); @@ -57,6 +58,8 @@ void ChunkStorage::init_chunk_space(const std::string& file_path) const { void ChunkStorage::write_chunk(const std::string& file_path, unsigned int chunk_id, const char * buff, size_t size, off64_t offset, ABT_eventual& eventual) const { + assert((offset + size) <= chunksize); + init_chunk_space(file_path); auto chunk_path = absolute(get_chunk_path(file_path, chunk_id)); @@ -85,7 +88,7 @@ void ChunkStorage::write_chunk(const std::string& file_path, unsigned int chunk_ void ChunkStorage::read_chunk(const std::string& file_path, unsigned int chunk_id, char * buff, size_t size, off64_t offset, ABT_eventual& eventual) const { - + assert((offset + size) <= chunksize); auto chunk_path = absolute(get_chunk_path(file_path, chunk_id)); int fd = open(chunk_path.c_str(), O_RDONLY); if(fd < 0) { -- GitLab From 521c9bfd5f4f37e8e17a0758526f04a0c2e34bdd Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 10:17:19 +0200 Subject: [PATCH 007/105] Intercept realpath --- ifs/include/preload/passthrough.hpp | 2 ++ ifs/src/preload/intcp_functions.cpp | 23 +++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 085e4e167..f82e5d416 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -70,6 +70,8 @@ extern void* libc_closedir; extern void* libc_chdir; +extern void* libc_realpath; + void init_passthrough_if_needed(); #endif //IFS_PASSTHROUGH_HPP diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index ae2e4b88a..fe8e8c188 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -728,3 +728,26 @@ int chdir(const char* path){ } return (reinterpret_cast(libc_chdir))(path); } + +char *realpath(const char *path, char *resolved_path) { + init_passthrough_if_needed(); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + if(resolved_path != nullptr) { + CTX->log()->error("{}() use of user level buffer not supported", __func__); + errno = ENOTSUP; + return nullptr; + } + auto absolute_path = CTX->mountdir() + rel_path; + auto ret_ptr = static_cast(malloc(absolute_path.size() + 1)); + if(ret_ptr == nullptr){ + CTX->log()->error("{}() failed to allocate buffer for called with path {}", __func__, path); + errno = ENOMEM; + return nullptr; + } + strcpy(ret_ptr, absolute_path.c_str()); + return ret_ptr; + } + return (reinterpret_cast(libc_realpath))(path, resolved_path); +} diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index cd3e8e0e5..926567c9a 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -75,6 +75,8 @@ void* libc_closedir; void* libc_chdir; +void* libc_realpath; + void init_passthrough_() { libc = dlopen("libc.so.6", RTLD_LAZY); @@ -146,6 +148,8 @@ void init_passthrough_() { libc_chdir = dlsym(libc, "chdir"); + libc_realpath = dlsym(libc, "realpath"); + } void init_passthrough_if_needed() { -- GitLab From 71f1363b560bcf04bb70416828e8d6a9e7d04047 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 12:53:54 +0200 Subject: [PATCH 008/105] bugfix: handle partial read It is possible that we read less bytes then requested cause the actual file doesn't have so much. --- ifs/src/preload/intcp_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index fe8e8c188..17eddd5a8 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -530,7 +530,7 @@ ssize_t read(int fd, void* buf, size_t count) { auto ret = adafs_pread_ws(fd, buf, count, pos); // Update offset in file descriptor in the file map if (ret > 0) { - adafs_fd->pos(pos + count); + adafs_fd->pos(pos + ret); } return ret; } -- GitLab From c8400c9a507fa6007fc0b2c55c38eaa6af7ade2c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 12:55:17 +0200 Subject: [PATCH 009/105] bugfix: implement segmented read According to the read manpage a read could return less bytes then requested due to an interrupt or an error. We now try to repeat the read operation until all the existent data have been read correctly. --- ifs/src/daemon/backend/data/chunk_storage.cpp | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/ifs/src/daemon/backend/data/chunk_storage.cpp b/ifs/src/daemon/backend/data/chunk_storage.cpp index 92983a003..61544d1b8 100644 --- a/ifs/src/daemon/backend/data/chunk_storage.cpp +++ b/ifs/src/daemon/backend/data/chunk_storage.cpp @@ -95,15 +95,36 @@ void ChunkStorage::read_chunk(const std::string& file_path, unsigned int chunk_i log->error("Failed to open chunk file for read. File: {}, Error: {}", chunk_path, std::strerror(errno)); throw std::system_error(errno, std::system_category(), "Failed to open chunk file for read"); } - - auto read = pread64(fd, buff, size, offset); - if (read < 0) { - log->error("Failed to read chunk file. File: {}, size: {}, offset: {}, Error: {}", - chunk_path, size, offset, std::strerror(errno)); - throw std::system_error(errno, std::system_category(), "Failed to read chunk file"); - } - - ABT_eventual_set(eventual, &read, sizeof(size_t)); + size_t tot_read = 0; + ssize_t read = 0; + + do { + read = pread64(fd, + buff + tot_read, + size - tot_read, + offset + tot_read); + if(read == 0) { + break; + } + + if (read < 0) { + log->error("Failed to read chunk file. File: {}, size: {}, offset: {}, Error: {}", + chunk_path, size, offset, std::strerror(errno)); + throw std::system_error(errno, std::system_category(), "Failed to read chunk file"); + } + +#ifndef NDEBUG + if(tot_read + read < size) { + log->warn("Read less bytes than requested: {}/{}. Total read was {}", read, size - tot_read, size); + } +#endif + assert(read > 0); + tot_read += read; + + + } while (tot_read != size); + + ABT_eventual_set(eventual, &tot_read, sizeof(size_t)); auto err = close(fd); if (err < 0) { -- GitLab From 26adc3252da09678b6c8859693c847bfc9d4daea Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 22 May 2018 12:58:06 +0200 Subject: [PATCH 010/105] tests: read beyond end of file. --- ifs_test/wr_test.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ifs_test/wr_test.cpp b/ifs_test/wr_test.cpp index 073af8b0d..5d3dfc550 100644 --- a/ifs_test/wr_test.cpp +++ b/ifs_test/wr_test.cpp @@ -22,7 +22,7 @@ int main(int argc, char* argv[]) { string mountdir = "/tmp/mountdir"; string p = mountdir + "/file"; char buffIn[] = "oops."; - char *buffOut = new char[strlen(buffIn)]; + char *buffOut = new char[strlen(buffIn) + 1 + 20 ]; int fd; int ret; struct stat st; @@ -94,6 +94,12 @@ int main(int argc, char* argv[]) { return -1; } + nr = read(fd, buffOut, 1); + if(nr != 0){ + cerr << "Error reading at end of file" << endl; + return -1; + } + if(strncmp( buffIn, buffOut, strlen(buffIn)) != 0){ cerr << "File content mismatch" << endl; return -1; @@ -105,6 +111,27 @@ int main(int argc, char* argv[]) { return -1; }; + /* Read beyond end of file */ + + fd = open(p.c_str(), O_RDONLY); + if(fd < 0){ + cerr << "Error opening file (read)" << endl; + return -1; + } + + nr = read(fd, buffOut, strlen(buffIn) + 20 ); + if(nr != strlen(buffIn)){ + cerr << "Error reading file" << endl; + return -1; + } + + nr = read(fd, buffOut, 1 ); + if(nr != 0){ + cerr << "Error reading at end of file" << endl; + return -1; + } + + /* Remove test file */ ret = remove(p.c_str()); if(ret != 0){ cerr << "Error removing file: " << strerror(errno) << endl; -- GitLab From aa6955b41efd6d1e4efd662aee4aa901397cab43 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 23 May 2018 09:13:32 +0200 Subject: [PATCH 011/105] Fix blanks --- ifs/src/preload/adafs_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 9a4b8ec23..8201d6896 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -44,7 +44,7 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { auto ret = rpc_send_update_metadentry_size(path.c_str(), 0, 0, false, updated_size); if (ret != 0) { CTX->log()->error("{}() update_metadentry_size failed with ret {}", __func__, ret); - errno = EIO; + errno = EIO; return -1; // ERR } } -- GitLab From 6f18853f2d1563b92613cee2b4345f955b32722c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 23 May 2018 09:14:31 +0200 Subject: [PATCH 012/105] ChunkStorage: fix chunks path and folders permissions --- ifs/src/daemon/backend/data/chunk_storage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/src/daemon/backend/data/chunk_storage.cpp b/ifs/src/daemon/backend/data/chunk_storage.cpp index 61544d1b8..e949557a4 100644 --- a/ifs/src/daemon/backend/data/chunk_storage.cpp +++ b/ifs/src/daemon/backend/data/chunk_storage.cpp @@ -34,7 +34,7 @@ std::string ChunkStorage::get_chunks_dir(const std::string& file_path) { } std::string ChunkStorage::get_chunk_path(const std::string& file_path, unsigned int chunk_id) { - return get_chunks_dir(file_path) + std::to_string(chunk_id); + return get_chunks_dir(file_path) + '/' + std::to_string(chunk_id); } void ChunkStorage::destroy_chunk_space(const std::string& file_path) const { @@ -48,7 +48,7 @@ void ChunkStorage::destroy_chunk_space(const std::string& file_path) const { void ChunkStorage::init_chunk_space(const std::string& file_path) const { auto chunk_dir = absolute(get_chunks_dir(file_path)); - auto err = mkdir(chunk_dir.c_str(), 0640); + auto err = mkdir(chunk_dir.c_str(), 0750); if(err == -1 && errno != EEXIST){ log->error("Failed to create chunk dir. Path: {}, Error: {}", chunk_dir, std::strerror(errno)); throw std::system_error(errno, std::system_category(), "Failed to create chunk directory"); -- GitLab From 9dc4a75cdd93502720f243b219199af829f68f63 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 23 May 2018 09:18:43 +0200 Subject: [PATCH 013/105] Read beyond end of file In the daemon read handler manage read beyond end of file as normal case and not exceptional --- ifs/src/daemon/handler/h_data.cpp | 52 ++++++++++++++----------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/ifs/src/daemon/handler/h_data.cpp b/ifs/src/daemon/handler/h_data.cpp index 92a18fd38..d1a8c1f1f 100644 --- a/ifs/src/daemon/handler/h_data.cpp +++ b/ifs/src/daemon/handler/h_data.cpp @@ -431,33 +431,30 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { size_t* task_read_size; // wait causes the calling ult to go into BLOCKED state, implicitly yielding to the pool scheduler ABT_eventual_wait(task_eventuals[chnk_id_curr], (void**) &task_read_size); - if (task_read_size == nullptr || *task_read_size == 0) { - ADAFS_DATA->spdlogger()->error("{}() Reading file task for chunk {} failed and did return anything.", - __func__, chnk_id_curr); - /* - * XXX We have to talk about how chunk errors are handled? Should we try to read again? - * In any case we just ignore this for now and return the out.io_size with as much has been read - * After all, we can decide on the semantics. - */ - } else { - ret = margo_bulk_transfer(mid, HG_BULK_PUSH, hgi->addr, in.bulk_handle, origin_offsets[chnk_id_curr], - bulk_handle, local_offsets[chnk_id_curr], chnk_sizes[chnk_id_curr]); - if (ret != HG_SUCCESS) { - ADAFS_DATA->spdlogger()->error( - "{}() Failed push chnkid {} on path {} to client. origin offset {} local offset {} chunk size {}", - __func__, chnk_id_curr, in.path, origin_offsets[chnk_id_curr], local_offsets[chnk_id_curr], - chnk_sizes[chnk_id_curr]); - cancel_abt_io(&abt_tasks, &task_eventuals, in.chunk_n); - return rpc_cleanup_respond(&handle, &in, &out, &bulk_handle); - } - out.io_size += *task_read_size; // add task read size to output size + + assert(task_read_size != nullptr); + assert(*task_read_size >= 0); + + if(*task_read_size == 0){ + ADAFS_DATA->spdlogger()->warn("{}() Read task for chunk {} returned 0 bytes", __func__, chnk_id_curr); + continue; } - ABT_eventual_free(&task_eventuals[chnk_id_curr]); + + ret = margo_bulk_transfer(mid, HG_BULK_PUSH, hgi->addr, in.bulk_handle, origin_offsets[chnk_id_curr], + bulk_handle, local_offsets[chnk_id_curr], *task_read_size); + if (ret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error( + "{}() Failed push chnkid {} on path {} to client. origin offset {} local offset {} chunk size {}", + __func__, chnk_id_curr, in.path, origin_offsets[chnk_id_curr], local_offsets[chnk_id_curr], + chnk_sizes[chnk_id_curr]); + cancel_abt_io(&abt_tasks, &task_eventuals, in.chunk_n); + return rpc_cleanup_respond(&handle, &in, &out, &bulk_handle); + } + out.io_size += *task_read_size; // add task read size to output size } - // Sanity check to see if all data has been written - if (in.total_chunk_size != out.io_size) - ADAFS_DATA->spdlogger()->warn("{}() total chunk size {} and out.io_size {} mismatch!", __func__, - in.total_chunk_size, out.io_size); + + ADAFS_DATA->spdlogger()->debug("{}() total chunk size read {}/{}", __func__, out.io_size, in.total_chunk_size); + /* * 5. Respond and cleanup */ @@ -465,10 +462,7 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { ADAFS_DATA->spdlogger()->debug("{}() Sending output response {}", __func__, out.res); ret = rpc_cleanup_respond(&handle, &in, &out, &bulk_handle); // free tasks after responding - for (auto&& task : abt_tasks) { - ABT_task_join(task); - ABT_task_free(&task); - } + cancel_abt_io(&abt_tasks, &task_eventuals, in.chunk_n); return ret; } -- GitLab From 6a6670a6f17693ff4601e787830e944712cbad1c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 23 May 2018 09:21:12 +0200 Subject: [PATCH 014/105] Always return inode number in stats The `cp` command double check the inode number to detect file changes during transfers, we need to set it properly and keep it consistent. inode(file) = std::hash(file_path) --- ifs/src/preload/preload_util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index 66bd76b97..ce0788e20 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -65,9 +65,10 @@ bool is_fs_path(const char* path) { */ int db_val_to_stat(const std::string path, std::string db_val, struct stat& attr) { + attr.st_ino = std::hash{}(path); + auto pos = db_val.find(dentry_val_delim); if (pos == std::string::npos) { // no delimiter found => no metadata enabled. fill with dummy values - attr.st_ino = std::hash{}(path); attr.st_mode = static_cast(stoul(db_val)); attr.st_nlink = 1; attr.st_uid = CTX->fs_conf()->uid; -- GitLab From 0bcd87b82960713e92169cd9335f9e8f3904ad46 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 24 May 2018 14:58:06 +0200 Subject: [PATCH 015/105] trace log --- ifs/configure_public.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ifs/configure_public.hpp b/ifs/configure_public.hpp index 2fed817c8..45526e01b 100644 --- a/ifs/configure_public.hpp +++ b/ifs/configure_public.hpp @@ -7,15 +7,15 @@ #define FS_CONFIGURE_PUBLIC_H // To enabled logging for daemon -#define LOG_INFO +//#define LOG_INFO //#define LOG_DEBUG -//#define LOG_TRACE +#define LOG_TRACE #define LOG_DAEMON_PATH "/tmp/adafs_daemon.log" // Enable logging for preload -#define LOG_PRELOAD_INFO +//#define LOG_PRELOAD_INFO //#define LOG_PRELOAD_DEBUG -//#define LOG_PRELOAD_TRACE +#define LOG_PRELOAD_TRACE #define LOG_PRELOAD_PATH "/tmp/adafs_preload.log" // Set a hostname suffix when a connection is built. E.g., "-ib" to use Infiniband -- GitLab From e7e56b95f8c774560893e09caa913425e1cb261d Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 24 May 2018 14:58:40 +0200 Subject: [PATCH 016/105] unlocloed --- ifs/include/preload/intcp_functions.hpp | 10 ++++++++++ ifs/src/preload/intcp_functions.cpp | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ifs/include/preload/intcp_functions.hpp b/ifs/include/preload/intcp_functions.hpp index be24ef3e5..b241d5f2f 100644 --- a/ifs/include/preload/intcp_functions.hpp +++ b/ifs/include/preload/intcp_functions.hpp @@ -8,6 +8,9 @@ extern "C" { # define weak_alias(name, aliasname) \ extern __typeof (name) aliasname __attribute__ ((weak, alias (#name))); +# define strong_alias(name, aliasname) \ + extern __typeof (name) aliasname __attribute__ ((alias (#name))); + /** * In the glibc headers the following two functions (readdir & opendir) * marks the @dirp parameter with a non-null attribute. @@ -22,6 +25,13 @@ weak_alias(intcp_readdir, readdir) int intcp_closedir(DIR* dirp); weak_alias(intcp_closedir, closedir) +size_t intcp_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +strong_alias(intcp_fread, fread) +strong_alias(intcp_fread, fread_unlocked) +size_t intcp_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +strong_alias(intcp_fwrite, fwrite) +strong_alias(intcp_fwrite, fwrite_unlocked) + #endif // IFS_INTCP_FUNCTIONS_HPP } // extern C diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 17eddd5a8..30dca35d8 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -107,7 +107,7 @@ FILE* fopen64(const char* path, const char* fmode) { return (reinterpret_cast(libc_fopen64))(path, fmode); } -size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { +size_t intcp_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { init_passthrough_if_needed(); if(CTX->initialized() && (stream != nullptr)) { auto fd = file_to_fd(stream); @@ -127,7 +127,7 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { return (reinterpret_cast(libc_fread))(ptr, size, nmemb, stream); } -size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { +size_t intcp_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { init_passthrough_if_needed(); if(CTX->initialized() && (stream != nullptr)) { auto fd = file_to_fd(stream); -- GitLab From 4a90935e0405349ee03b20f695ee0d1ceb3ab5b9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 27 May 2018 14:14:37 +0200 Subject: [PATCH 017/105] intercept writev/readv --- ifs/include/preload/passthrough.hpp | 3 ++ ifs/src/preload/intcp_functions.cpp | 47 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 6 ++++ 3 files changed, 56 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index f82e5d416..84e4c10cf 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -48,9 +48,12 @@ extern void* libc_puts; extern void* libc_write; extern void* libc_pwrite; extern void* libc_pwrite64; +extern void* libc_writev; + extern void* libc_read; extern void* libc_pread; extern void* libc_pread64; +extern void* libc_readv; extern void* libc_lseek; extern void* libc_lseek64; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 30dca35d8..acf242128 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -520,6 +520,53 @@ ssize_t pwrite64(int fd, const void* buf, size_t count, __off64_t offset) { return (reinterpret_cast(libc_pwrite64))(fd, buf, count, offset); } +ssize_t writev(int fd, const struct iovec *iov, int iovcnt) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + if (CTX->file_map()->exist(fd)) { + auto adafs_fd = CTX->file_map()->get(fd); + auto pos = adafs_fd->pos(); // retrieve the current offset + ssize_t written = 0; + ssize_t ret; + for (int i = 0; i < iovcnt; ++i){ + auto buf = (iov+i)->iov_base; + auto count = (iov+i)->iov_len; + ret = adafs_pwrite_ws(fd, buf, count, pos); + if(ret == -1) { + break; + } + written += ret; + pos += ret; + + if(static_cast(ret) < count){ + break; + } + } + + if(written == 0){ + return -1; + } + adafs_fd->pos(pos); + return written; + } + } + return (reinterpret_cast(libc_writev))(fd, iov, iovcnt); +} + +ssize_t readv(int fd, const struct iovec *iov, int iovcnt) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + if (CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return -1; + } + } + return (reinterpret_cast(libc_readv))(fd, iov, iovcnt); +} + ssize_t read(int fd, void* buf, size_t count) { init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 926567c9a..a64d623fb 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -52,9 +52,12 @@ void* libc_puts; void* libc_write; void* libc_pwrite; void* libc_pwrite64; +void* libc_writev; + void* libc_read; void* libc_pread; void* libc_pread64; +void* libc_readv; void* libc_lseek; void* libc_lseek64; @@ -126,9 +129,12 @@ void init_passthrough_() { libc_write = dlsym(libc, "write"); libc_pwrite = dlsym(libc, "pwrite"); libc_pwrite64 = dlsym(libc, "pwrite64"); + libc_writev = dlsym(libc, "writev"); + libc_read = dlsym(libc, "read"); libc_pread = dlsym(libc, "pread"); libc_pread64 = dlsym(libc, "pread64"); + libc_readv = dlsym(libc, "readv"); libc_lseek = dlsym(libc, "lseek"); libc_lseek64 = dlsym(libc, "lseek64"); -- GitLab From bd9e26e228a68c10ee15f1a7de6946d8af2ef0a4 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 27 May 2018 14:15:10 +0200 Subject: [PATCH 018/105] intercept statat --- ifs/include/preload/passthrough.hpp | 2 ++ ifs/src/preload/intcp_functions.cpp | 27 +++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 4 ++++ 3 files changed, 33 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 84e4c10cf..8132ede4c 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -37,6 +37,8 @@ extern void* libc___xstat; extern void* libc___xstat64; extern void* libc___fxstat; extern void* libc___fxstat64; +extern void* libc___fxstatat; +extern void* libc___fxstatat64; extern void* libc___lxstat; extern void* libc___lxstat64; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index acf242128..6cf48a203 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -2,6 +2,8 @@ * All intercepted functions are defined here */ #include +#include +#include #include #include @@ -402,6 +404,31 @@ int __fxstat(int ver, int fd, struct stat* buf) __THROW { return (reinterpret_cast(libc___fxstat))(ver, fd, buf); } +int __fxstatat(int ver, int dirfd, const char * path, struct stat * buf, int flags) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path '{}'", __func__, path); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + return adafs_stat(rel_path, buf); + } + } + return (reinterpret_cast(libc___fxstatat))(ver, dirfd, path, buf, flags); +} + +int __fxstatat64(int ver, int dirfd, const char * path, struct stat64 * buf, int flags) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path '{}'", __func__, path); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + return adafs_stat64(rel_path, buf); + } + } + return (reinterpret_cast(libc___fxstatat64))(ver, dirfd, path, buf, flags); + +} + int __fxstat64(int ver, int fd, struct stat64* buf) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index a64d623fb..84fe1a05c 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -41,6 +41,8 @@ void* libc___xstat; void* libc___xstat64; void* libc___fxstat; void* libc___fxstat64; +void* libc___fxstatat; +void* libc___fxstatat64; void* libc___lxstat; void* libc___lxstat64; @@ -118,6 +120,8 @@ void init_passthrough_() { libc___xstat64 = dlsym(libc, "__xstat64"); libc___fxstat = dlsym(libc, "__fxstat"); libc___fxstat64 = dlsym(libc, "__fxstat64"); + libc___fxstatat = dlsym(libc, "__fxstatat"); + libc___fxstatat64 = dlsym(libc, "__fxstatat64"); libc___lxstat = dlsym(libc, "__lxstat"); libc___lxstat64 = dlsym(libc, "__lxstat64"); -- GitLab From 6da5e96dda9dc5c1e58a4f685e0abe8b8ed55d04 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 27 May 2018 16:27:42 +0200 Subject: [PATCH 019/105] O_PATH open flags are not supported --- ifs/src/preload/adafs_functions.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 8201d6896..f594f34a9 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -13,6 +13,12 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { init_ld_env_if_needed(); int err = 0; + if(flags & O_PATH){ + CTX->log()->error("{}() `O_PATH` flag is not supported", __func__); + errno = ENOTSUP; + return -1; + } + if (flags & O_CREAT){ // no access check required here. If one is using our FS they have the permissions. err = adafs_mk_node(path, mode | S_IFREG); -- GitLab From 7d290da55f3382e8dc707c9e6192a034641b87e9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 27 May 2018 16:34:03 +0200 Subject: [PATCH 020/105] Move O_DIRECTORY check inside adafs_open --- ifs/src/preload/adafs_functions.cpp | 6 ++++++ ifs/src/preload/intcp_functions.cpp | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index f594f34a9..fecf724d3 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -19,6 +19,12 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return -1; } + if(flags & O_DIRECTORY){ + CTX->log()->error("{}() `O_DIRECTORY` flag is not supported", __func__); + errno = ENOTSUP; + return -1; + } + if (flags & O_CREAT){ // no access check required here. If one is using our FS they have the permissions. err = adafs_mk_node(path, mode | S_IFREG); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 6cf48a203..a9c26c3ca 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -26,12 +26,6 @@ int open(const char* path, int flags, ...) { if(CTX->initialized()) { CTX->log()->trace("{}() called with path {}", __func__, path); - if(flags & O_DIRECTORY){ - CTX->log()->error("{}() called with `O_DIRECTORY` flag on {}", __func__, path); - errno = ENOTSUP; - return -1; - } - std::string rel_path(path); if (CTX->relativize_path(rel_path)) { return adafs_open(rel_path, mode, flags); -- GitLab From 676f1bb27600640100be60c4b5f11c3571b57dd5 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 12:19:51 +0200 Subject: [PATCH 021/105] intercept openat --- ifs/include/preload/passthrough.hpp | 1 + ifs/src/preload/intcp_functions.cpp | 42 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 2 ++ 3 files changed, 45 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 8132ede4c..9e42dabe3 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -9,6 +9,7 @@ extern void* libc; extern void* libc_open; +extern void* libc_openat; extern void* libc_fopen; extern void* libc_fopen64; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index a9c26c3ca..82512bc35 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -48,6 +48,48 @@ int open64(const char* path, int flags, ...) { return open(path, flags | O_LARGEFILE, mode); } +int openat(int dirfd, const char *path, int flags, ...) { + init_passthrough_if_needed(); + + mode_t mode = 0; + if (flags & O_CREAT) { + va_list vl; + va_start(vl, flags); + mode = static_cast(va_arg(vl, int)); + va_end(vl); + } + + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + return adafs_open(rel_path, mode, flags); + } + + if (CTX->file_map()->exist(dirfd)) { + CTX->log()->error("{}() called with relative path: NOT SUPPORTED", __func__); + errno = ENOTSUP; + return -1; + } + } + + return (reinterpret_cast(libc_openat))(dirfd, path, flags, mode); +} + +int openat64(int dirfd, const char *path, int flags, ...) { + init_passthrough_if_needed(); + + mode_t mode = 0; + if (flags & O_CREAT) { + va_list vl; + va_start(vl, flags); + mode = static_cast(va_arg(vl, int)); + va_end(vl); + } + + return openat(dirfd, path, flags | O_LARGEFILE, mode); +} + /****** FILE OPS ******/ inline int file_to_fd(const FILE* f){ diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 84fe1a05c..fa8dcdc0d 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -13,6 +13,7 @@ static pthread_once_t init_lib_thread = PTHREAD_ONCE_INIT; void* libc; void* libc_open; +void* libc_openat; void* libc_fopen; void* libc_fopen64; @@ -91,6 +92,7 @@ void init_passthrough_() { } libc_open = dlsym(libc, "open"); + libc_openat = dlsym(libc, "openat"); libc_fopen = dlsym(libc, "fopen"); libc_fopen64 = dlsym(libc, "fopen64"); -- GitLab From 11adfbecd01a30fdfd5b7d7da93d177077de304a Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 16:08:49 +0200 Subject: [PATCH 022/105] fix: opendir cannot open file opendir need to check that the given path is actually a directory before to open it. Moreover now open can be called with the O_DIRECTORY flag in order to open an existent directory --- ifs/src/preload/adafs_functions.cpp | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index fecf724d3..8536d7354 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -19,19 +19,23 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return -1; } - if(flags & O_DIRECTORY){ - CTX->log()->error("{}() `O_DIRECTORY` flag is not supported", __func__); - errno = ENOTSUP; - return -1; - } - if (flags & O_CREAT){ + if(flags & O_DIRECTORY){ + CTX->log()->error("{}() `O_CREAT` with `O_DIRECTORY` flag is not supported", __func__); + errno = ENOTSUP; + return -1; + } + // no access check required here. If one is using our FS they have the permissions. err = adafs_mk_node(path, mode | S_IFREG); if(err != 0) return -1; } else { + if(flags & O_DIRECTORY){ + return adafs_opendir(path); + } + auto mask = F_OK; // F_OK == 0 #if defined(CHECK_ACCESS_DURING_OPEN) if ((mode & S_IRUSR) || (mode & S_IRGRP) || (mode & S_IROTH)) @@ -244,12 +248,19 @@ ssize_t adafs_pread_ws(int fd, void* buf, size_t count, off64_t offset) { int adafs_opendir(const std::string& path) { init_ld_env_if_needed(); -#if defined(DO_LOOKUP) - auto err = rpc_send_access(path, F_OK); - if(err != 0){ + std::string attr; + auto err = rpc_send_stat(path, attr); + if (err != 0) { + CTX->log()->debug("{}() send stat failed", __func__); return err; } -#endif + struct stat st; + db_val_to_stat(path, attr, st); + if(!(st.st_mode & S_IFDIR)) { + CTX->log()->debug("{}() path is not a directory", __func__); + errno = ENOTDIR; + return -1; + } auto open_dir = std::make_shared(path); rpc_send_get_dirents(*open_dir); return CTX->file_map()->add(open_dir); -- GitLab From 4ef4a3e0a4320460e219097ce83524d5194ef963 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 16:12:09 +0200 Subject: [PATCH 023/105] intercept fcntl fcntl now is intercept and support the F_GETFD/F_SETFD command. --- ifs/include/preload/open_file_map.hpp | 1 + ifs/include/preload/passthrough.hpp | 2 ++ ifs/src/preload/intcp_functions.cpp | 41 +++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 4 +++ 4 files changed, 48 insertions(+) diff --git a/ifs/include/preload/open_file_map.hpp b/ifs/include/preload/open_file_map.hpp index a3cc7e2ba..3c0f930a8 100644 --- a/ifs/include/preload/open_file_map.hpp +++ b/ifs/include/preload/open_file_map.hpp @@ -13,6 +13,7 @@ enum class OpenFile_flags { rdonly, wronly, rdwr, + cloexec, flag_count // this is purely used as a size variable of this enum class }; diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 9e42dabe3..0e06ef7cd 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -66,6 +66,8 @@ extern void* libc_fdatasync; extern void* libc_truncate; extern void* libc_ftruncate; +extern void* libc_fcntl; + extern void* libc_dup; extern void* libc_dup2; extern void* libc_dup3; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 82512bc35..341032a56 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -9,6 +9,7 @@ #include #include #include +#include using namespace std; @@ -731,6 +732,46 @@ int ftruncate(int fd, off_t length) __THROW { return (reinterpret_cast(libc_ftruncate))(fd, length); } +int fcntl(int fd, int cmd, ...) { + init_passthrough_if_needed(); + va_list ap; + void *arg; + + va_start (ap, cmd); + arg = va_arg (ap, void *); + va_end (ap); + + if(CTX->initialized()) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + if (CTX->file_map()->exist(fd)) { + switch(cmd) { + case F_GETFD: + CTX->log()->trace("{}() F_GETFD on fd {}", __func__, fd); + if(CTX->file_map()->get(fd) + ->get_flag(OpenFile_flags::cloexec)) { + return FD_CLOEXEC; + } else { + return 0; + } + case F_SETFD: { + va_start (ap, cmd); + int flags = va_arg (ap, int); + va_end (ap); + CTX->log()->trace("{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}", __func__, fd, (flags & FD_CLOEXEC)); + CTX->file_map()->get(fd) + ->set_flag(OpenFile_flags::cloexec, (flags & FD_CLOEXEC)); + return 0; + } + default: + CTX->log()->error("{}() unrecognized command {} on fd {}", __func__, cmd, fd); + errno = ENOTSUP; + return -1; + } + } + } + return (reinterpret_cast(libc_fcntl))(fd, cmd, arg); +} + int dup(int oldfd) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index fa8dcdc0d..5fbc6daae 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -71,6 +71,8 @@ void* libc_fdatasync; void* libc_truncate; void* libc_ftruncate; +void* libc_fcntl; + void* libc_dup; void* libc_dup2; void* libc_dup3; @@ -150,6 +152,8 @@ void init_passthrough_() { libc_truncate = dlsym(libc, "truncate"); libc_ftruncate = dlsym(libc, "ftruncate"); + libc_fcntl = dlsym(libc, "fcntl"); + libc_dup = dlsym(libc, "dup"); libc_dup2 = dlsym(libc, "dup2"); libc_dup3 = dlsym(libc, "dup3"); -- GitLab From 3f54ae1494f195b7b6e49b9a115fff3d8578cf72 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 16:13:43 +0200 Subject: [PATCH 024/105] intercept fdopendir --- ifs/include/preload/passthrough.hpp | 1 + ifs/src/preload/intcp_functions.cpp | 18 ++++++++++++++++++ ifs/src/preload/passthrough.cpp | 2 ++ 3 files changed, 21 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 0e06ef7cd..6d710a540 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -73,6 +73,7 @@ extern void* libc_dup2; extern void* libc_dup3; extern void* libc_opendir; +extern void* libc_fdopendir; extern void* libc_readdir; extern void* libc_closedir; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 341032a56..19377aa12 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -836,6 +836,24 @@ DIR* opendir(const char* path){ return (reinterpret_cast(libc_opendir))(path); } +DIR* fdopendir(int fd){ + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + if (CTX->file_map()->exist(fd)) { + auto open_file = CTX->file_map()->get(fd); + auto open_dir = static_pointer_cast(open_file); + if(!open_dir){ + //Cast did not succeeded: open_file is a regular file + errno = EBADF; + return nullptr; + } + return fd_to_dirp(fd); + } + } + return (reinterpret_cast(libc_fdopendir))(fd); +} + struct dirent* intcp_readdir(DIR* dirp){ init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 5fbc6daae..5c829079e 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -78,6 +78,7 @@ void* libc_dup2; void* libc_dup3; void* libc_opendir; +void* libc_fdopendir; void* libc_readdir; void* libc_closedir; @@ -159,6 +160,7 @@ void init_passthrough_() { libc_dup3 = dlsym(libc, "dup3"); libc_opendir = dlsym(libc, "opendir"); + libc_fdopendir = dlsym(libc, "fdopendir"); libc_readdir = dlsym(libc, "readdir"); libc_closedir = dlsym(libc, "closedir"); -- GitLab From 7531e7c48aa239df5602ac28872ca53f162ca6b2 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 18:47:15 +0200 Subject: [PATCH 025/105] openat and statat support relative paths --- ifs/include/preload/open_file_map.hpp | 8 ++- ifs/src/preload/intcp_functions.cpp | 77 +++++++++++++++++++++------ ifs/src/preload/open_file_map.cpp | 10 ++++ 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/ifs/include/preload/open_file_map.hpp b/ifs/include/preload/open_file_map.hpp index 3c0f930a8..3d7f8e82f 100644 --- a/ifs/include/preload/open_file_map.hpp +++ b/ifs/include/preload/open_file_map.hpp @@ -6,6 +6,10 @@ #include #include +/* Forward declaration */ +class OpenDir; + + enum class OpenFile_flags { append = 0, creat, @@ -66,10 +70,12 @@ public: std::shared_ptr get(int fd); + std::shared_ptr get_dir(int dirfd); + bool exist(int fd); int add(std::shared_ptr); - + bool remove(int fd); int dup(int oldfd); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 19377aa12..e8d372563 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace std; @@ -49,7 +50,7 @@ int open64(const char* path, int flags, ...) { return open(path, flags | O_LARGEFILE, mode); } -int openat(int dirfd, const char *path, int flags, ...) { +int openat(int dirfd, const char *cpath, int flags, ...) { init_passthrough_if_needed(); mode_t mode = 0; @@ -61,20 +62,34 @@ int openat(int dirfd, const char *path, int flags, ...) { } if(CTX->initialized()) { + std::string path(cpath); CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_open(rel_path, mode, flags); - } + + if(is_relative_path(path)) { + if(!(CTX->file_map()->exist(dirfd))) { + goto passthrough; + } + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + if(has_trailing_slash(path)){ + path.pop_back(); + } + return adafs_open(dir->path() + '/' + path, mode, flags); + } else { + // Path is absolute + assert(is_absolute_path(path)); - if (CTX->file_map()->exist(dirfd)) { - CTX->log()->error("{}() called with relative path: NOT SUPPORTED", __func__); - errno = ENOTSUP; - return -1; + if (CTX->relativize_path(path)) { + return adafs_open(path, mode, flags); + } } } - - return (reinterpret_cast(libc_openat))(dirfd, path, flags, mode); +passthrough: + return (reinterpret_cast(libc_openat))(dirfd, cpath, flags, mode); } int openat64(int dirfd, const char *path, int flags, ...) { @@ -441,16 +456,44 @@ int __fxstat(int ver, int fd, struct stat* buf) __THROW { return (reinterpret_cast(libc___fxstat))(ver, fd, buf); } -int __fxstatat(int ver, int dirfd, const char * path, struct stat * buf, int flags) { +int __fxstatat(int ver, int dirfd, const char * cpath, struct stat * buf, int flags) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}'", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat(rel_path, buf); + std::string path(cpath); + CTX->log()->trace("{}() called with path '{}' and fd {}", __func__, path, dirfd); + if(flags & AT_EMPTY_PATH) { + CTX->log()->error("{}() AT_EMPTY_PATH flag not supported", __func__); + errno = ENOTSUP; + return -1; + } + + if(is_relative_path(path)) { + if((dirfd == AT_FDCWD) || !CTX->file_map()->exist(dirfd)) { + goto passthrough; + } + + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + if(has_trailing_slash(path)){ + path.pop_back(); + } + return adafs_stat(dir->path() + '/' + path, buf); + + } else { + // Path is absolute + assert(is_absolute_path(path)); + + if (CTX->relativize_path(path)) { + return adafs_stat(path, buf); + } } } - return (reinterpret_cast(libc___fxstatat))(ver, dirfd, path, buf, flags); +passthrough: + return (reinterpret_cast(libc___fxstatat))(ver, dirfd, cpath, buf, flags); } int __fxstatat64(int ver, int dirfd, const char * path, struct stat64 * buf, int flags) { diff --git a/ifs/src/preload/open_file_map.cpp b/ifs/src/preload/open_file_map.cpp index d9db8a4b5..902026204 100644 --- a/ifs/src/preload/open_file_map.cpp +++ b/ifs/src/preload/open_file_map.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -69,6 +70,15 @@ shared_ptr OpenFileMap::get(int fd) { } } +shared_ptr OpenFileMap::get_dir(int dirfd) { + auto f = get(dirfd); + if(f == nullptr){ + return nullptr; + } + auto open_dir = static_pointer_cast(f); + // If open_file is not an OpenDir we are returning nullptr + return open_dir; +} bool OpenFileMap::exist(const int fd) { lock_guard lock(files_mutex_); -- GitLab From a4a5dc13012baa31e57a658181e323c44e4facc1 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 18:48:38 +0200 Subject: [PATCH 026/105] fcntl supoort F_DUPFD(_CLOEXEC) commands --- ifs/src/preload/intcp_functions.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index e8d372563..1c958a4ea 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -788,6 +788,19 @@ int fcntl(int fd, int cmd, ...) { CTX->log()->trace("{}() called with fd {}", __func__, fd); if (CTX->file_map()->exist(fd)) { switch(cmd) { + case F_DUPFD: + CTX->log()->trace("{}() F_DUPFD on fd {}", __func__, fd); + return adafs_dup(fd); + case F_DUPFD_CLOEXEC: { + CTX->log()->trace("{}() F_DUPFD_CLOEXEC on fd {}", __func__, fd); + auto ret = adafs_dup(fd); + if(ret == -1) { + return ret; + } + CTX->file_map()->get(fd) + ->set_flag(OpenFile_flags::cloexec, true); + return ret; + } case F_GETFD: CTX->log()->trace("{}() F_GETFD on fd {}", __func__, fd); if(CTX->file_map()->get(fd) @@ -800,7 +813,7 @@ int fcntl(int fd, int cmd, ...) { va_start (ap, cmd); int flags = va_arg (ap, int); va_end (ap); - CTX->log()->trace("{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}", __func__, fd, (flags & FD_CLOEXEC)); + CTX->log()->trace("{}() [fd: {}, cmd: F_SETFD, FD_CLOEXEC: {}]", __func__, fd, (flags & FD_CLOEXEC)); CTX->file_map()->get(fd) ->set_flag(OpenFile_flags::cloexec, (flags & FD_CLOEXEC)); return 0; -- GitLab From 4f09f1e9fe5dee1a0a1760a1973a3b354c1d11af Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 28 May 2018 21:29:07 +0200 Subject: [PATCH 027/105] set device number in stat command like cp are relying on the dev_no and ino_num to monitor if the file have been changed during the copy operation. dev_no was not properly set, thus it was random. Now it is always 0/0 --- ifs/src/preload/preload_util.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index ce0788e20..724e803af 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -9,6 +9,7 @@ #include #include #include +#include using namespace std; @@ -66,7 +67,8 @@ bool is_fs_path(const char* path) { int db_val_to_stat(const std::string path, std::string db_val, struct stat& attr) { attr.st_ino = std::hash{}(path); - + attr.st_dev = makedev(0, 0); + auto pos = db_val.find(dentry_val_delim); if (pos == std::string::npos) { // no delimiter found => no metadata enabled. fill with dummy values attr.st_mode = static_cast(stoul(db_val)); -- GitLab From 950a5262d7f394ce7bd6ac0923587bf8e759a1ca Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 11:22:32 +0200 Subject: [PATCH 028/105] bugfix: initialize io byte count to 0 --- ifs/src/daemon/handler/h_data.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ifs/src/daemon/handler/h_data.cpp b/ifs/src/daemon/handler/h_data.cpp index d1a8c1f1f..1183049b3 100644 --- a/ifs/src/daemon/handler/h_data.cpp +++ b/ifs/src/daemon/handler/h_data.cpp @@ -40,8 +40,8 @@ void write_file_abt(void* _arg) { arg->buf, arg->size, arg->off, arg->eventual); } catch (const std::exception& e){ ADAFS_DATA->spdlogger()->error("{}() Error writing chunk {} of file {}", __func__, arg->chnk_id, path); - auto err = static_cast(EIO); - ABT_eventual_set(arg->eventual, &err, sizeof(size_t)); + auto wrote = 0; + ABT_eventual_set(arg->eventual, &wrote, sizeof(size_t)); } } @@ -77,8 +77,8 @@ void read_file_abt(void* _arg) { arg->buf, arg->size, arg->off, arg->eventual); } catch (const std::exception& e){ ADAFS_DATA->spdlogger()->error("{}() Error reading chunk {} of file {}", __func__, arg->chnk_id, path); - auto err = static_cast(EIO); - ABT_eventual_set(arg->eventual, &err, sizeof(size_t)); + size_t read = 0; + ABT_eventual_set(arg->eventual, &read, sizeof(size_t)); } } @@ -261,6 +261,7 @@ static hg_return_t rpc_srv_write_data(hg_handle_t handle) { /* * 4. Read task results and accumulate in out.io_size */ + out.io_size = 0; for (chnk_id_curr = 0; chnk_id_curr < in.chunk_n; chnk_id_curr++) { size_t* task_written_size; // wait causes the calling ult to go into BLOCKED state, implicitly yielding to the pool scheduler @@ -427,6 +428,7 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { /* * 4. Read task results and accumulate in out.io_size */ + out.io_size = 0; for (chnk_id_curr = 0; chnk_id_curr < in.chunk_n; chnk_id_curr++) { size_t* task_read_size; // wait causes the calling ult to go into BLOCKED state, implicitly yielding to the pool scheduler @@ -454,7 +456,7 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { } ADAFS_DATA->spdlogger()->debug("{}() total chunk size read {}/{}", __func__, out.io_size, in.total_chunk_size); - + /* * 5. Respond and cleanup */ -- GitLab From d017f52f39baec3adc5c60b679064e405c6ab3a4 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 13:57:23 +0200 Subject: [PATCH 029/105] bugfix: log error on mkdirat interception mkdirat is not yet supported at the moment. As usual we should log error and return ENOTSUP --- ifs/src/preload/intcp_functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 1c958a4ea..99ebcc3ad 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -305,8 +305,8 @@ int mkdirat(int dirfd, const char* path, mode_t mode) __THROW { std::string rel_path(path); if (CTX->relativize_path(rel_path)) { // not implemented - CTX->log()->trace("{}() not implemented.", __func__); - errno = EBUSY; + CTX->log()->error("{}() not implemented.", __func__); + errno = ENOTSUP; return -1; } } -- GitLab From 1854672a5b79da6dcaef7ac276d689b5079c0179 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 13:58:43 +0200 Subject: [PATCH 030/105] faccessat support relative paths --- ifs/src/preload/intcp_functions.cpp | 33 +++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 99ebcc3ad..dcbd92eaa 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -368,18 +368,37 @@ int access(const char* path, int mask) __THROW { return (reinterpret_cast(libc_access))(path, mask); } -int faccessat(int dirfd, const char* path, int mode, int flags) __THROW { +int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { + std::string path(cpath); CTX->log()->trace("{}() called path {} mode {} dirfd {} flags {}", __func__, path, mode, dirfd, flags); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - // not implemented - CTX->log()->trace("{}() not implemented.", __func__); - return -1; + + if(is_relative_path(path)) { + if(!(CTX->file_map()->exist(dirfd))) { + goto passthrough; + } + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + if(has_trailing_slash(path)){ + path.pop_back(); + } + return adafs_access(dir->path() + '/' + path, mode); + } else { + // Path is absolute + assert(is_absolute_path(path)); + + if (CTX->relativize_path(path)) { + return adafs_access(path, mode); + } } } - return (reinterpret_cast(libc_faccessat))(dirfd, path, mode, flags); +passthrough: + return (reinterpret_cast(libc_faccessat))(dirfd, cpath, mode, flags); } -- GitLab From a4ef4cff36cb3f2052d07eb1b1644d3538024e92 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 16:29:07 +0200 Subject: [PATCH 031/105] intercept unlinkat --- ifs/include/preload/passthrough.hpp | 1 + ifs/src/preload/intcp_functions.cpp | 40 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 2 ++ 3 files changed, 43 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 6d710a540..95cf8026c 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -24,6 +24,7 @@ extern void* libc_fileno; extern void* libc_mkdir; extern void* libc_mkdirat; extern void* libc_unlink; +extern void* libc_unlinkat; extern void* libc_rmdir; extern void* libc_close; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index dcbd92eaa..1ebf8d8ab 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -326,6 +327,45 @@ int unlink(const char* path) __THROW { return (reinterpret_cast(libc_unlink))(path); } +int unlinkat(int dirfd, const char *cpath, int flags) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + std::string path(cpath); + CTX->log()->trace("{}() called with path {}, dirfd {}, flags {}", __func__, path, dirfd, flags); + + if(flags & AT_REMOVEDIR) { + CTX->log()->error("{}() AT_REMOVEDIR flag is not supported", __func__); + errno = ENOTDIR; + return -1; + } + + if(is_relative_path(path)) { + if(!(CTX->file_map()->exist(dirfd))) { + goto passthrough; + } + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + if(has_trailing_slash(path)){ + path.pop_back(); + } + return adafs_rm_node(dir->path() + '/' + path); + } else { + // Path is absolute + assert(is_absolute_path(path)); + + if (CTX->relativize_path(path)) { + return adafs_rm_node(path); + } + } + } +passthrough: + return (reinterpret_cast(libc_unlinkat))(dirfd, cpath, flags); +} + int rmdir(const char* path) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 5c829079e..f0e8a055e 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -28,6 +28,7 @@ void* libc_fileno; void* libc_mkdir; void* libc_mkdirat; void* libc_unlink; +void* libc_unlinkat; void* libc_rmdir; void* libc_close; @@ -111,6 +112,7 @@ void init_passthrough_() { libc_mkdirat = dlsym(libc, "mkdirat"); libc_unlink = dlsym(libc, "unlink"); + libc_unlinkat = dlsym(libc, "unlinkat"); libc_rmdir = dlsym(libc, "rmdir"); libc_close = dlsym(libc, "close"); -- GitLab From fcfa2e666b7888a5988e6e853fac0e259ccb53d4 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 16:46:33 +0200 Subject: [PATCH 032/105] intercept fflush --- ifs/include/preload/passthrough.hpp | 1 + ifs/src/preload/intcp_functions.cpp | 13 +++++++++++++ ifs/src/preload/passthrough.cpp | 2 ++ 3 files changed, 16 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 95cf8026c..9d7b0c11e 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -20,6 +20,7 @@ extern void* libc_clearerr; extern void* libc_feof; extern void* libc_ferror; extern void* libc_fileno; +extern void* libc_fflush; extern void* libc_mkdir; extern void* libc_mkdirat; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 1ebf8d8ab..b8ac3491a 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -265,6 +265,19 @@ int ferror(FILE *stream) { return (reinterpret_cast(libc_ferror))(stream); } +int fflush(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); + return 0; + } + } + return (reinterpret_cast(libc_fflush))(stream); + +} + /****** FILE OPS ******/ #undef creat diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index f0e8a055e..f0e0e07c2 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -24,6 +24,7 @@ void* libc_clearerr; void* libc_feof; void* libc_ferror; void* libc_fileno; +void* libc_fflush; void* libc_mkdir; void* libc_mkdirat; @@ -107,6 +108,7 @@ void init_passthrough_() { libc_feof = dlsym(libc, "feof"); libc_ferror = dlsym(libc, "ferror"); libc_fileno = dlsym(libc, "fileno"); + libc_fflush = dlsym(libc, "fflush"); libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From 42619d3acb37c0f6db2dbbfefca3e7a6b101d137 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 29 May 2018 16:49:15 +0200 Subject: [PATCH 033/105] intercept (f)putc --- ifs/include/preload/passthrough.hpp | 3 +++ ifs/src/preload/intcp_functions.cpp | 27 ++++++++++++++++++++++++++- ifs/src/preload/passthrough.cpp | 6 ++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 9d7b0c11e..a1c5221d3 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -22,6 +22,9 @@ extern void* libc_ferror; extern void* libc_fileno; extern void* libc_fflush; +extern void* libc_putc; +extern void* libc_fputc; + extern void* libc_mkdir; extern void* libc_mkdirat; extern void* libc_unlink; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index b8ac3491a..a8e346955 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -65,7 +65,7 @@ int openat(int dirfd, const char *cpath, int flags, ...) { if(CTX->initialized()) { std::string path(cpath); CTX->log()->trace("{}() called with path {}", __func__, path); - + if(is_relative_path(path)) { if(!(CTX->file_map()->exist(dirfd))) { goto passthrough; @@ -275,7 +275,32 @@ int fflush(FILE *stream) { } } return (reinterpret_cast(libc_fflush))(stream); +} + +int putc(int c, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return (reinterpret_cast(libc_putc))(c, stream); +} +int fputc(int c, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return (reinterpret_cast(libc_fputc))(c, stream); } /****** FILE OPS ******/ diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index f0e0e07c2..8c6d76e89 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -26,6 +26,9 @@ void* libc_ferror; void* libc_fileno; void* libc_fflush; +void* libc_putc; +void* libc_fputc; + void* libc_mkdir; void* libc_mkdirat; void* libc_unlink; @@ -110,6 +113,9 @@ void init_passthrough_() { libc_fileno = dlsym(libc, "fileno"); libc_fflush = dlsym(libc, "fflush"); + libc_putc = dlsym(libc, "putc"); + libc_fputc = dlsym(libc, "fputc"); + libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From ce4c4b1e34d670ac753bcbe3f218ddcb905ec553 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 30 May 2018 13:17:28 +0200 Subject: [PATCH 034/105] Intercept file buffer related functions --- ifs/include/preload/passthrough.hpp | 7 +++ ifs/src/preload/intcp_functions.cpp | 72 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 14 ++++++ 3 files changed, 93 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index a1c5221d3..1517bd8ae 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -21,6 +21,13 @@ extern void* libc_feof; extern void* libc_ferror; extern void* libc_fileno; extern void* libc_fflush; +extern void* libc_fpurge; +extern void* libc___fpurge; + +extern void* libc_setbuf; +extern void* libc_setbuffer; +extern void* libc_setlinebuf; +extern void* libc_setvbuf; extern void* libc_putc; extern void* libc_fputc; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index a8e346955..f7d9dcefb 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -277,6 +277,78 @@ int fflush(FILE *stream) { return (reinterpret_cast(libc_fflush))(stream); } +int fpurge(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return 0; + } + } + return (reinterpret_cast(libc_fpurge))(stream); +} + +void __fpurge(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return; + } + } + return (reinterpret_cast(libc___fpurge))(stream); +} + +void setbuf(FILE *stream, char *buf) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return; + } + } + return (reinterpret_cast(libc_setbuf))(stream, buf); +} + +void setbuffer(FILE *stream, char *buf, size_t size) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return; + } + } + return (reinterpret_cast(libc_setbuffer))(stream, buf, size); +} + +void setlinebuf(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return; + } + } + return (reinterpret_cast(libc_setlinebuf))(stream); +} + +int setvbuf(FILE *stream, char *buf, int mode, size_t size) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called on fd {}", __func__, fd); + return 0; + } + } + return (reinterpret_cast(libc_setvbuf))(stream, buf, mode, size); +} + int putc(int c, FILE *stream) { init_passthrough_if_needed(); if(CTX->initialized() && (stream != nullptr)) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 8c6d76e89..ea3ef91fb 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -25,6 +25,13 @@ void* libc_feof; void* libc_ferror; void* libc_fileno; void* libc_fflush; +void* libc_fpurge; +void* libc___fpurge; + +void* libc_setbuf; +void* libc_setbuffer; +void* libc_setlinebuf; +void* libc_setvbuf; void* libc_putc; void* libc_fputc; @@ -112,6 +119,13 @@ void init_passthrough_() { libc_ferror = dlsym(libc, "ferror"); libc_fileno = dlsym(libc, "fileno"); libc_fflush = dlsym(libc, "fflush"); + libc_fpurge = dlsym(libc, "fpurge"); + libc___fpurge = dlsym(libc, "__fpurge"); + + libc_setbuf = dlsym(libc, "setbuf"); + libc_setbuffer = dlsym(libc, "setbuffer"); + libc_setlinebuf = dlsym(libc, "setlinebuf"); + libc_setvbuf = dlsym(libc, "setvbuf"); libc_putc = dlsym(libc, "putc"); libc_fputc = dlsym(libc, "fputc"); -- GitLab From af7a89960c0ed52015f685974b251719af70d8a7 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 31 May 2018 11:35:17 +0200 Subject: [PATCH 035/105] Check parent entity existance before making new node Ensure that the parent directory exists before to make a new node --- ifs/include/global/path_util.hpp | 1 + ifs/src/global/path_util.cpp | 13 +++++++++++++ ifs/src/preload/adafs_functions.cpp | 14 ++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/ifs/include/global/path_util.hpp b/ifs/include/global/path_util.hpp index 7ef09684f..2de59271d 100644 --- a/ifs/include/global/path_util.hpp +++ b/ifs/include/global/path_util.hpp @@ -8,5 +8,6 @@ bool is_absolute_path(const std::string& path); bool has_trailing_slash(const std::string& path); std::string path_to_relative(const std::string& root_path, const std::string& complete_path); +std::string dirname(const std::string& path); #endif //IFS_PATH_UTIL_HPP diff --git a/ifs/src/global/path_util.cpp b/ifs/src/global/path_util.cpp index 18fd3f528..0043c876b 100644 --- a/ifs/src/global/path_util.cpp +++ b/ifs/src/global/path_util.cpp @@ -56,3 +56,16 @@ std::string path_to_relative(const std::string& root_path, const std::string& ab return {rel_it_begin, rel_it_end}; } + +std::string dirname(const std::string& path) { + assert(path.size() > 1 || path.front() == PSP); + assert(path.size() == 1 || !has_trailing_slash(path)); + + auto parent_path_size = path.find_last_of(PSP); + assert(parent_path_size != std::string::npos); + if(parent_path_size == 0) { + // parent is '/' + parent_path_size = 1; + } + return path.substr(0, parent_path_size); +} diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 8536d7354..376f76515 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -78,6 +79,19 @@ int adafs_mk_node(const std::string& path, const mode_t mode) { //file type must be either regular file or directory assert(S_ISREG(mode) || S_ISDIR(mode)); + struct stat st; + auto p_comp = dirname(path); + auto ret = adafs_stat(p_comp, &st); + if(ret != 0) { + CTX->log()->debug("{}() parent component does not exists: '{}'", __func__, p_comp); + errno = ENOENT; + return -1; + } + if(!S_ISDIR(st.st_mode)) { + CTX->log()->debug("{}() parent component is not a direcotory: '{}'", __func__, p_comp); + errno = ENOTDIR; + return -1; + } return rpc_send_mk_node(path, mode); } -- GitLab From 6911c3e7416e80ecd2c04fe273660aede691a3f6 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 31 May 2018 11:37:38 +0200 Subject: [PATCH 036/105] Do not intercept puts puts has nothing to do with the FS. thus we leave libc handling it directly --- ifs/include/preload/passthrough.hpp | 2 -- ifs/src/preload/intcp_functions.cpp | 4 ---- ifs/src/preload/passthrough.cpp | 4 ---- 3 files changed, 10 deletions(-) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 1517bd8ae..15d67564f 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -58,8 +58,6 @@ extern void* libc___lxstat64; extern void* libc_statfs; extern void* libc_fstatfs; -extern void* libc_puts; - extern void* libc_write; extern void* libc_pwrite; extern void* libc_pwrite64; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index f7d9dcefb..9075aa5cb 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -750,10 +750,6 @@ int fstatfs(int fd, struct statfs* buf) { return (reinterpret_cast(libc_fstatfs))(fd, buf); } -int puts(const char* str) { - return (reinterpret_cast(libc_puts))(str); -} - ssize_t write(int fd, const void* buf, size_t count) { init_passthrough_if_needed(); if(CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index ea3ef91fb..b0f8a1f03 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -62,8 +62,6 @@ void* libc___lxstat64; void* libc_statfs; void* libc_fstatfs; -void* libc_puts; - void* libc_write; void* libc_pwrite; void* libc_pwrite64; @@ -157,8 +155,6 @@ void init_passthrough_() { libc_statfs = dlsym(libc, "statfs"); libc_fstatfs = dlsym(libc, "fstatfs"); - libc_puts = dlsym(libc, "puts"); - libc_write = dlsym(libc, "write"); libc_pwrite = dlsym(libc, "pwrite"); libc_pwrite64 = dlsym(libc, "pwrite64"); -- GitLab From 5c8813efb26fbf662531660d69ebccdb21fe06a9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 31 May 2018 16:07:41 +0200 Subject: [PATCH 037/105] bugfix: log error on rpc error instead of warning --- ifs/src/preload/rpc/ld_rpc_metadentry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp index cfe6074e6..f8995aec8 100644 --- a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp +++ b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp @@ -148,7 +148,7 @@ int rpc_send_stat(const std::string& path, string& attr) { // Get response if (ret != HG_SUCCESS) { errno = EBUSY; - CTX->log()->warn("{}() timed out"); + CTX->log()->error("{}() timed out"); margo_destroy(handle); return -1; } -- GitLab From decd7448414f9fc8e82b9420cb66ad21796cc0d3 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 31 May 2018 19:36:05 +0200 Subject: [PATCH 038/105] Implement (f)truncate Implemented all the logic to handle truncate operation. Test: added truncate test --- .../daemon/backend/data/chunk_storage.hpp | 5 ++ ifs/include/daemon/backend/metadata/db.hpp | 3 +- ifs/include/daemon/backend/metadata/merge.hpp | 14 ++- ifs/include/daemon/handler/rpc_defs.hpp | 4 + ifs/include/global/global_defs.hpp | 2 + ifs/include/global/rpc/rpc_types.hpp | 4 + ifs/include/preload/adafs_functions.hpp | 4 + ifs/include/preload/preload_util.hpp | 4 + ifs/include/preload/rpc/ld_rpc_data_ws.hpp | 2 + ifs/include/preload/rpc/ld_rpc_metadentry.hpp | 2 + ifs/src/daemon/adafs_daemon.cpp | 2 + ifs/src/daemon/adafs_ops/metadentry.cpp | 2 +- ifs/src/daemon/backend/data/chunk_storage.cpp | 42 +++++++++ ifs/src/daemon/backend/metadata/db.cpp | 10 ++- ifs/src/daemon/backend/metadata/merge.cpp | 24 ++++++ ifs/src/daemon/handler/h_data.cpp | 35 ++++++++ ifs/src/daemon/handler/h_metadentry.cpp | 35 ++++++++ ifs/src/preload/adafs_functions.cpp | 46 ++++++++++ ifs/src/preload/intcp_functions.cpp | 19 ++++- ifs/src/preload/preload.cpp | 12 +++ ifs/src/preload/rpc/ld_rpc_data_ws.cpp | 85 ++++++++++++++++++- ifs/src/preload/rpc/ld_rpc_metadentry.cpp | 52 ++++++++++++ ifs_test/CMakeLists.txt | 2 + ifs_test/truncate.cpp | 81 ++++++++++++++++++ 24 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 ifs_test/truncate.cpp diff --git a/ifs/include/daemon/backend/data/chunk_storage.hpp b/ifs/include/daemon/backend/data/chunk_storage.hpp index e222947e3..68ae8cd70 100644 --- a/ifs/include/daemon/backend/data/chunk_storage.hpp +++ b/ifs/include/daemon/backend/data/chunk_storage.hpp @@ -2,6 +2,7 @@ #define IFS_CHUNK_STORAGE_HPP #include +#include #include #include @@ -32,6 +33,10 @@ class ChunkStorage { void read_chunk(const std::string& file_path, unsigned int chunk_id, char * buff, size_t size, off64_t offset, ABT_eventual& eventual) const; + void trim_chunk_space(const std::string& file_path, unsigned int chunk_start, + unsigned int chunk_end = UINT_MAX); + void delete_chunk(const std::string& file_path, unsigned int chunk_id); + void truncate_chunk(const std::string& file_path, unsigned int chunk_id, off_t length); void destroy_chunk_space(const std::string& file_path) const; }; diff --git a/ifs/include/daemon/backend/metadata/db.hpp b/ifs/include/daemon/backend/metadata/db.hpp index 1b9081a2e..e7a8d0159 100644 --- a/ifs/include/daemon/backend/metadata/db.hpp +++ b/ifs/include/daemon/backend/metadata/db.hpp @@ -25,7 +25,8 @@ class MetadataDB { void remove(const std::string& key); bool exists(const std::string& key); void update(const std::string& old_key, const std::string& new_key, const std::string& val); - void update_size(const std::string& key, size_t size, bool append); + void increase_size(const std::string& key, size_t size, bool append); + void decrease_size(const std::string& key, size_t size); std::vector> get_dirents(const std::string& dir) const; void iterate_all(); }; diff --git a/ifs/include/daemon/backend/metadata/merge.hpp b/ifs/include/daemon/backend/metadata/merge.hpp index d4a794b65..164caeb95 100644 --- a/ifs/include/daemon/backend/metadata/merge.hpp +++ b/ifs/include/daemon/backend/metadata/merge.hpp @@ -8,7 +8,8 @@ namespace rdb = rocksdb; enum class OperandID: char { - increase_size = 's', + increase_size = 'i', + decrease_size = 'd', create = 'c' }; @@ -41,6 +42,17 @@ class IncreaseSizeOperand: public MergeOperand { std::string serialize_params() const override; }; +class DecreaseSizeOperand: public MergeOperand { + public: + size_t size; + + DecreaseSizeOperand(const size_t size); + DecreaseSizeOperand(const rdb::Slice& serialized_op); + + const OperandID id() const override; + std::string serialize_params() const override; +}; + class CreateOperand: public MergeOperand { public: std::string metadata; diff --git a/ifs/include/daemon/handler/rpc_defs.hpp b/ifs/include/daemon/handler/rpc_defs.hpp index 6fc154289..32e407721 100644 --- a/ifs/include/daemon/handler/rpc_defs.hpp +++ b/ifs/include/daemon/handler/rpc_defs.hpp @@ -19,6 +19,8 @@ DECLARE_MARGO_RPC_HANDLER(rpc_srv_access) DECLARE_MARGO_RPC_HANDLER(rpc_srv_stat) +DECLARE_MARGO_RPC_HANDLER(rpc_srv_decr_size) + DECLARE_MARGO_RPC_HANDLER(rpc_srv_rm_node) DECLARE_MARGO_RPC_HANDLER(rpc_srv_update_metadentry) @@ -34,4 +36,6 @@ DECLARE_MARGO_RPC_HANDLER(rpc_srv_read_data) DECLARE_MARGO_RPC_HANDLER(rpc_srv_write_data) +DECLARE_MARGO_RPC_HANDLER(rpc_srv_trunc_data) + #endif //LFS_RPC_DEFS_HPP diff --git a/ifs/include/global/global_defs.hpp b/ifs/include/global/global_defs.hpp index 347ec94af..908a7fee2 100644 --- a/ifs/include/global/global_defs.hpp +++ b/ifs/include/global/global_defs.hpp @@ -11,12 +11,14 @@ namespace hg_tag { constexpr auto access = "rpc_srv_access"; constexpr auto stat = "rpc_srv_stat"; constexpr auto remove = "rpc_srv_rm_node"; + constexpr auto decr_size = "rpc_srv_decr_size"; constexpr auto update_metadentry = "rpc_srv_update_metadentry"; constexpr auto get_metadentry_size = "rpc_srv_get_metadentry_size"; constexpr auto update_metadentry_size = "rpc_srv_update_metadentry_size"; constexpr auto get_dirents = "rpc_srv_get_dirents"; constexpr auto write_data = "rpc_srv_write_data"; constexpr auto read_data = "rpc_srv_read_data"; + constexpr auto trunc_data = "rpc_srv_trunc_data"; } // typedefs diff --git a/ifs/include/global/rpc/rpc_types.hpp b/ifs/include/global/rpc/rpc_types.hpp index 3745de09c..f78f95815 100644 --- a/ifs/include/global/rpc/rpc_types.hpp +++ b/ifs/include/global/rpc/rpc_types.hpp @@ -31,6 +31,10 @@ MERCURY_GEN_PROC(rpc_stat_out_t, ((hg_int32_t) (err)) MERCURY_GEN_PROC(rpc_rm_node_in_t, ((hg_const_string_t) (path))) +MERCURY_GEN_PROC(rpc_trunc_in_t, + ((hg_const_string_t) (path)) \ + ((hg_uint64_t) (length))) + MERCURY_GEN_PROC(rpc_update_metadentry_in_t, ((hg_const_string_t) (path))\ ((uint64_t) (nlink))\ diff --git a/ifs/include/preload/adafs_functions.hpp b/ifs/include/preload/adafs_functions.hpp index e651f272f..9ea55f586 100644 --- a/ifs/include/preload/adafs_functions.hpp +++ b/ifs/include/preload/adafs_functions.hpp @@ -44,6 +44,10 @@ off64_t adafs_lseek(int fd, off64_t offset, int whence); off64_t adafs_lseek(std::shared_ptr adafs_fd, off64_t offset, int whence); +int adafs_truncate(const std::string& path, off_t offset); + +int adafs_truncate(const std::string& path, off_t old_size, off_t new_size); + int adafs_dup(int oldfd); int adafs_dup2(int oldfd, int newfd); diff --git a/ifs/include/preload/preload_util.hpp b/ifs/include/preload/preload_util.hpp index d8d75c690..c5e43f95c 100644 --- a/ifs/include/preload/preload_util.hpp +++ b/ifs/include/preload/preload_util.hpp @@ -52,11 +52,13 @@ extern hg_id_t ipc_mk_node_id; extern hg_id_t ipc_access_id; extern hg_id_t ipc_stat_id; extern hg_id_t ipc_rm_node_id; +extern hg_id_t ipc_decr_size_id; extern hg_id_t ipc_update_metadentry_id; extern hg_id_t ipc_get_metadentry_size_id; extern hg_id_t ipc_update_metadentry_size_id; extern hg_id_t ipc_write_data_id; extern hg_id_t ipc_read_data_id; +extern hg_id_t ipc_trunc_data_id; extern hg_id_t ipc_get_dirents_id; // RPC IDs extern hg_id_t rpc_minimal_id; @@ -64,11 +66,13 @@ extern hg_id_t rpc_mk_node_id; extern hg_id_t rpc_stat_id; extern hg_id_t rpc_access_id; extern hg_id_t rpc_rm_node_id; +extern hg_id_t rpc_decr_size_id; extern hg_id_t rpc_update_metadentry_id; extern hg_id_t rpc_get_metadentry_size_id; extern hg_id_t rpc_update_metadentry_size_id; extern hg_id_t rpc_write_data_id; extern hg_id_t rpc_read_data_id; +extern hg_id_t rpc_trunc_data_id; extern hg_id_t rpc_get_dirents_id; // rpc addresses. Populated when environment is initialized. After that it is read-only accessed extern std::map rpc_addresses; diff --git a/ifs/include/preload/rpc/ld_rpc_data_ws.hpp b/ifs/include/preload/rpc/ld_rpc_data_ws.hpp index 9adf72953..241342b6e 100644 --- a/ifs/include/preload/rpc/ld_rpc_data_ws.hpp +++ b/ifs/include/preload/rpc/ld_rpc_data_ws.hpp @@ -20,4 +20,6 @@ ssize_t rpc_send_write(const std::string& path, const void* buf, const bool appe ssize_t rpc_send_read(const std::string& path, void* buf, const off64_t offset, const size_t read_size); +int rpc_send_trunc_data(const std::string& path, size_t current_size, size_t new_size); + #endif //IFS_PRELOAD_C_DATA_WS_HPP diff --git a/ifs/include/preload/rpc/ld_rpc_metadentry.hpp b/ifs/include/preload/rpc/ld_rpc_metadentry.hpp index f0e7f5f1d..e4a05af76 100644 --- a/ifs/include/preload/rpc/ld_rpc_metadentry.hpp +++ b/ifs/include/preload/rpc/ld_rpc_metadentry.hpp @@ -22,6 +22,8 @@ int rpc_send_stat(const std::string& path, std::string& attr); int rpc_send_rm_node(const std::string& path, const bool remove_metadentry_only); +int rpc_send_decr_size(const std::string& path, size_t length); + int rpc_send_update_metadentry(const std::string& path, const Metadentry& md, const MetadentryUpdateFlags& md_flags); int rpc_send_update_metadentry_size(const std::string& path, size_t size, off64_t offset, bool append_flag, diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index c94779f48..883de1cec 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -243,6 +243,7 @@ void register_server_rpcs(margo_instance_id mid) { MARGO_REGISTER(mid, hg_tag::create, rpc_mk_node_in_t, rpc_err_out_t, rpc_srv_mk_node); MARGO_REGISTER(mid, hg_tag::access, rpc_access_in_t, rpc_err_out_t, rpc_srv_access); MARGO_REGISTER(mid, hg_tag::stat, rpc_path_only_in_t, rpc_stat_out_t, rpc_srv_stat); + MARGO_REGISTER(mid, hg_tag::decr_size, rpc_trunc_in_t, rpc_err_out_t, rpc_srv_decr_size); MARGO_REGISTER(mid, hg_tag::remove, rpc_rm_node_in_t, rpc_err_out_t, rpc_srv_rm_node); MARGO_REGISTER(mid, hg_tag::update_metadentry, rpc_update_metadentry_in_t, rpc_err_out_t, rpc_srv_update_metadentry); @@ -254,6 +255,7 @@ void register_server_rpcs(margo_instance_id mid) { rpc_srv_get_dirents); MARGO_REGISTER(mid, hg_tag::write_data, rpc_write_data_in_t, rpc_data_out_t, rpc_srv_write_data); MARGO_REGISTER(mid, hg_tag::read_data, rpc_read_data_in_t, rpc_data_out_t, rpc_srv_read_data); + MARGO_REGISTER(mid, hg_tag::trunc_data, rpc_trunc_in_t, rpc_err_out_t, rpc_srv_trunc_data); } /** diff --git a/ifs/src/daemon/adafs_ops/metadentry.cpp b/ifs/src/daemon/adafs_ops/metadentry.cpp index 21df3f909..6af517f61 100644 --- a/ifs/src/daemon/adafs_ops/metadentry.cpp +++ b/ifs/src/daemon/adafs_ops/metadentry.cpp @@ -85,7 +85,7 @@ void update_metadentry_size(const string& path, size_t io_size, off64_t offset, #ifdef LOG_TRACE ADAFS_DATA->mdb()->iterate_all(); #endif - ADAFS_DATA->mdb()->update_size(path, io_size + offset, append); + ADAFS_DATA->mdb()->increase_size(path, io_size + offset, append); #ifdef LOG_TRACE ADAFS_DATA->mdb()->iterate_all(); #endif diff --git a/ifs/src/daemon/backend/data/chunk_storage.cpp b/ifs/src/daemon/backend/data/chunk_storage.cpp index e949557a4..ea0e098a0 100644 --- a/ifs/src/daemon/backend/data/chunk_storage.cpp +++ b/ifs/src/daemon/backend/data/chunk_storage.cpp @@ -55,6 +55,48 @@ void ChunkStorage::init_chunk_space(const std::string& file_path) const { } } +/* Delete all chunks stored on this node that falls in the gap [chunk_start, chunk_end] + * + * This is pretty slow method because it cycle over all the chunks sapce for this file. + */ +void ChunkStorage::trim_chunk_space(const std::string& file_path, + unsigned int chunk_start, unsigned int chunk_end) { + + auto chunk_dir = absolute(get_chunks_dir(file_path)); + const bfs::directory_iterator end; + + for (bfs::directory_iterator chunk_file(chunk_dir); chunk_file != end; ++chunk_file) { + auto chunk_path = chunk_file->path(); + auto chunk_id = std::stoul(chunk_path.filename().c_str()); + if(chunk_id >= chunk_start && chunk_id <= chunk_end) { + int ret = unlink(chunk_path.c_str()); + if(ret == -1) { + log->error("Failed to remove chunk file. File: {}, Error: {}", chunk_path.native(), std::strerror(errno)); + throw std::system_error(errno, std::system_category(), "Failed to remove chunk file"); + } + } + } +} + +void ChunkStorage::delete_chunk(const std::string& file_path, unsigned int chunk_id) { + auto chunk_path = absolute(get_chunk_path(file_path, chunk_id)); + int ret = unlink(chunk_path.c_str()); + if(ret == -1) { + log->error("Failed to remove chunk file. File: {}, Error: {}", chunk_path, std::strerror(errno)); + throw std::system_error(errno, std::system_category(), "Failed to remove chunk file"); + } +} + +void ChunkStorage::truncate_chunk(const std::string& file_path, unsigned int chunk_id, off_t length) { + auto chunk_path = absolute(get_chunk_path(file_path, chunk_id)); + assert(length > 0 && (unsigned int)length <= chunksize); + int ret = truncate(chunk_path.c_str(), length); + if(ret == -1) { + log->error("Failed to truncate chunk file. File: {}, Error: {}", chunk_path, std::strerror(errno)); + throw std::system_error(errno, std::system_category(), "Failed to truncate chunk file"); + } +} + void ChunkStorage::write_chunk(const std::string& file_path, unsigned int chunk_id, const char * buff, size_t size, off64_t offset, ABT_eventual& eventual) const { diff --git a/ifs/src/daemon/backend/metadata/db.cpp b/ifs/src/daemon/backend/metadata/db.cpp index f5a90daf6..d5608b7ac 100644 --- a/ifs/src/daemon/backend/metadata/db.cpp +++ b/ifs/src/daemon/backend/metadata/db.cpp @@ -93,7 +93,7 @@ void MetadataDB::update(const std::string& old_key, const std::string& new_key, } } -void MetadataDB::update_size(const std::string& key, size_t size, bool append){ +void MetadataDB::increase_size(const std::string& key, size_t size, bool append){ auto uop = IncreaseSizeOperand(size, append); auto s = db->Merge(write_opts, key, uop.serialize()); if(!s.ok()){ @@ -101,6 +101,14 @@ void MetadataDB::update_size(const std::string& key, size_t size, bool append){ } } +void MetadataDB::decrease_size(const std::string& key, size_t size) { + auto uop = DecreaseSizeOperand(size); + auto s = db->Merge(write_opts, key, uop.serialize()); + if(!s.ok()){ + MetadataDB::throw_rdb_status_excpt(s); + } +} + /** * Return all the first-level entries of the directory @dir * diff --git a/ifs/src/daemon/backend/metadata/merge.cpp b/ifs/src/daemon/backend/metadata/merge.cpp index 781e29fc3..c485e32f2 100644 --- a/ifs/src/daemon/backend/metadata/merge.cpp +++ b/ifs/src/daemon/backend/metadata/merge.cpp @@ -58,6 +58,26 @@ std::string IncreaseSizeOperand::serialize_params() const { } +DecreaseSizeOperand::DecreaseSizeOperand(const size_t size) : + size(size) {} + +DecreaseSizeOperand::DecreaseSizeOperand(const rdb::Slice& serialized_op){ + //Parse size + size_t read = 0; + size = std::stoul(serialized_op.data(), &read); + //check that we consumed all the input string + assert(read == serialized_op.size()); +} + +const OperandID DecreaseSizeOperand::id() const { + return OperandID::decrease_size; +} + +std::string DecreaseSizeOperand::serialize_params() const { + return std::to_string(size); +} + + CreateOperand::CreateOperand(const std::string& metadata): metadata(metadata) {} const OperandID CreateOperand::id() const{ @@ -108,6 +128,10 @@ bool MetadataMergeOperator::FullMergeV2( } else { fsize = std::max(op.size, fsize); } + } else if(operand_id == OperandID::decrease_size) { + auto op = DecreaseSizeOperand(parameters); + assert(op.size < fsize); // we assume no concurrency here + fsize = op.size; } else if(operand_id == OperandID::create){ continue; } else { diff --git a/ifs/src/daemon/handler/h_data.cpp b/ifs/src/daemon/handler/h_data.cpp index 1183049b3..c6a129180 100644 --- a/ifs/src/daemon/handler/h_data.cpp +++ b/ifs/src/daemon/handler/h_data.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -469,3 +470,37 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { } DEFINE_MARGO_RPC_HANDLER(rpc_srv_read_data) + +static hg_return_t rpc_srv_trunc_data(hg_handle_t handle) { + rpc_trunc_in_t in{}; + rpc_err_out_t out{}; + + auto ret = margo_get_input(handle, &in); + if (ret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Could not get RPC input data with err {}", __func__, ret); + throw runtime_error("Failed to get RPC input data"); + } + + unsigned int chunk_start = chnk_id_for_offset(in.length, CHUNKSIZE); + + // If we trunc in the the middle of a chunk, do not delete that chunk + auto left_pad = chnk_lpad(in.length, CHUNKSIZE); + if(left_pad != 0) { + ADAFS_DATA->storage()->truncate_chunk(in.path, chunk_start, left_pad); + ++chunk_start; + } + + ADAFS_DATA->storage()->trim_chunk_space(in.path, chunk_start); + + ADAFS_DATA->spdlogger()->debug("Sending output {}", out.err); + auto hret = margo_respond(handle, &out); + if (hret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to respond"); + } + // Destroy handle when finished + margo_free_input(handle, &in); + margo_destroy(handle); + return HG_SUCCESS; +} + +DEFINE_MARGO_RPC_HANDLER(rpc_srv_trunc_data) diff --git a/ifs/src/daemon/handler/h_metadentry.cpp b/ifs/src/daemon/handler/h_metadentry.cpp index 238cf78b1..a82609d06 100644 --- a/ifs/src/daemon/handler/h_metadentry.cpp +++ b/ifs/src/daemon/handler/h_metadentry.cpp @@ -129,6 +129,41 @@ static hg_return_t rpc_srv_stat(hg_handle_t handle) { DEFINE_MARGO_RPC_HANDLER(rpc_srv_stat) +static hg_return_t rpc_srv_decr_size(hg_handle_t handle) { + rpc_trunc_in_t in{}; + rpc_err_out_t out{}; + + auto ret = margo_get_input(handle, &in); + if (ret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to retrieve input from handle", __func__); + throw runtime_error("Failed to retrieve input from handle"); + } + + ADAFS_DATA->spdlogger()->debug("Serving decrement size RPC with path: '{}', length: {}", + in.path, in.length); + + try { + ADAFS_DATA->mdb()->decrease_size(in.path, in.length); + out.err = 0; + } catch (const std::exception& e) { + ADAFS_DATA->spdlogger()->error("{}() Failed to decrease size: {}", __func__, e.what()); + out.err = EIO; + } + + ADAFS_DATA->spdlogger()->debug("{}() Sending output {}", __func__, out.err); + auto hret = margo_respond(handle, &out); + if (hret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to respond"); + throw runtime_error("Failed to respond"); + } + // Destroy handle when finished + margo_free_input(handle, &in); + margo_destroy(handle); + return HG_SUCCESS; +} + +DEFINE_MARGO_RPC_HANDLER(rpc_srv_decr_size) + static hg_return_t rpc_srv_rm_node(hg_handle_t handle) { rpc_rm_node_in_t in{}; rpc_err_out_t out{}; diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 376f76515..b3afd9458 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -215,6 +215,52 @@ off64_t adafs_lseek(shared_ptr adafs_fd, off64_t offset, int whence) { return adafs_fd->pos(); } +int adafs_truncate(const std::string& path, off_t old_size, off_t new_size) { + assert(new_size >= 0); + assert(new_size < old_size); + + if (rpc_send_decr_size(path, new_size)) { + CTX->log()->debug("{}() failed to decrease size", __func__); + return -1; + } + + if(rpc_send_trunc_data(path, old_size, new_size)){ + CTX->log()->debug("{}() failed to truncate data", __func__); + return -1; + } + return 0; +} + +int adafs_truncate(const std::string& path, off_t length) { + /* TODO CONCURRENCY: + * At the moment we first ask the length to the metadata-server in order to + * know which data-server have data to be deleted. + * + * From the moment we issue the adafs_stat and the moment we issue the + * adafs_trunc_data, some more data could have been added to the file and the + * length increased. + */ + init_ld_env_if_needed(); + if(length < 0) { + CTX->log()->debug("{}() length is negative: {}", __func__, length); + errno = EINVAL; + return -1; + } + + struct stat st; + if(adafs_stat(path, &st)) { + return -1; + } + + if(length > st.st_size) { + CTX->log()->debug("{}() length is greater then file size: {} > {}", + __func__, length, st.st_size); + errno = EINVAL; + return -1; + } + return adafs_truncate(path, st.st_size, length); +} + int adafs_dup(const int oldfd) { return CTX->file_map()->dup(oldfd); } diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 9075aa5cb..4eef65805 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -930,13 +930,28 @@ int fdatasync(int fd) { return (reinterpret_cast(libc_fdatasync))(fd); } -int truncate(const char* path, off_t length) __THROW { +int truncate(const char* path, off_t length) { init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called with path: {}, offset: {}", __func__, + path, length); + std::string rel_path(path); + if (CTX->relativize_path(rel_path)) { + return adafs_truncate(rel_path, length); + } + } return (reinterpret_cast(libc_truncate))(path, length); } -int ftruncate(int fd, off_t length) __THROW { +int ftruncate(int fd, off_t length) { init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [fd: {}, offset: {}]", __func__, fd, length); + if (CTX->file_map()->exist(fd)) { + auto path = CTX->file_map()->get(fd)->path(); + return adafs_truncate(path, length); + } + } return (reinterpret_cast(libc_ftruncate))(fd, length); } diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index b0b673b2e..3cee92074 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -24,11 +24,13 @@ hg_id_t ipc_mk_node_id; hg_id_t ipc_access_id; hg_id_t ipc_stat_id; hg_id_t ipc_rm_node_id; +hg_id_t ipc_decr_size_id; hg_id_t ipc_update_metadentry_id; hg_id_t ipc_get_metadentry_size_id; hg_id_t ipc_update_metadentry_size_id; hg_id_t ipc_write_data_id; hg_id_t ipc_read_data_id; +hg_id_t ipc_trunc_data_id; hg_id_t ipc_get_dirents_id; // RPC IDs hg_id_t rpc_minimal_id; @@ -36,11 +38,13 @@ hg_id_t rpc_mk_node_id; hg_id_t rpc_access_id; hg_id_t rpc_stat_id; hg_id_t rpc_rm_node_id; +hg_id_t rpc_decr_size_id; hg_id_t rpc_update_metadentry_id; hg_id_t rpc_get_metadentry_size_id; hg_id_t rpc_update_metadentry_size_id; hg_id_t rpc_write_data_id; hg_id_t rpc_read_data_id; +hg_id_t rpc_trunc_data_id; hg_id_t rpc_get_dirents_id; // Margo instances margo_instance_id ld_margo_ipc_id; @@ -98,6 +102,8 @@ void register_client_rpcs(margo_instance_id mid, Margo_mode mode) { ipc_stat_id = MARGO_REGISTER(mid, hg_tag::stat, rpc_path_only_in_t, rpc_stat_out_t, NULL); ipc_rm_node_id = MARGO_REGISTER(mid, hg_tag::remove, rpc_rm_node_in_t, rpc_err_out_t, NULL); + ipc_decr_size_id = MARGO_REGISTER(mid, hg_tag::decr_size, rpc_trunc_in_t, rpc_err_out_t, + NULL); ipc_update_metadentry_id = MARGO_REGISTER(mid, hg_tag::update_metadentry, rpc_update_metadentry_in_t, rpc_err_out_t, NULL); ipc_get_metadentry_size_id = MARGO_REGISTER(mid, hg_tag::get_metadentry_size, rpc_path_only_in_t, @@ -110,6 +116,8 @@ void register_client_rpcs(margo_instance_id mid, Margo_mode mode) { NULL); ipc_read_data_id = MARGO_REGISTER(mid, hg_tag::read_data, rpc_read_data_in_t, rpc_data_out_t, NULL); + ipc_trunc_data_id = MARGO_REGISTER(mid, hg_tag::trunc_data, rpc_trunc_in_t, rpc_err_out_t, + NULL); ipc_get_dirents_id = MARGO_REGISTER(mid, hg_tag::get_dirents, rpc_get_dirents_in_t, rpc_get_dirents_out_t, NULL); } else { @@ -120,6 +128,8 @@ void register_client_rpcs(margo_instance_id mid, Margo_mode mode) { rpc_stat_id = MARGO_REGISTER(mid, hg_tag::stat, rpc_path_only_in_t, rpc_stat_out_t, NULL); rpc_rm_node_id = MARGO_REGISTER(mid, hg_tag::remove, rpc_rm_node_in_t, rpc_err_out_t, NULL); + rpc_decr_size_id = MARGO_REGISTER(mid, hg_tag::decr_size, rpc_trunc_in_t, rpc_err_out_t, + NULL); rpc_update_metadentry_id = MARGO_REGISTER(mid, hg_tag::update_metadentry, rpc_update_metadentry_in_t, rpc_err_out_t, NULL); rpc_get_metadentry_size_id = MARGO_REGISTER(mid, hg_tag::get_metadentry_size, rpc_path_only_in_t, @@ -132,6 +142,8 @@ void register_client_rpcs(margo_instance_id mid, Margo_mode mode) { NULL); rpc_read_data_id = MARGO_REGISTER(mid, hg_tag::read_data, rpc_read_data_in_t, rpc_data_out_t, NULL); + rpc_trunc_data_id = MARGO_REGISTER(mid, hg_tag::trunc_data, rpc_trunc_in_t, rpc_err_out_t, + NULL); rpc_get_dirents_id = MARGO_REGISTER(mid, hg_tag::get_dirents, rpc_get_dirents_in_t, rpc_get_dirents_out_t, NULL); } diff --git a/ifs/src/preload/rpc/ld_rpc_data_ws.cpp b/ifs/src/preload/rpc/ld_rpc_data_ws.cpp index bc4af5004..8d3fdf682 100644 --- a/ifs/src/preload/rpc/ld_rpc_data_ws.cpp +++ b/ifs/src/preload/rpc/ld_rpc_data_ws.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace std; // TODO If we decide to keep this functionality with one segment, the function can be merged mostly. @@ -246,4 +248,85 @@ ssize_t rpc_send_read(const string& path, void* buf, const off64_t offset, const margo_bulk_free(rpc_bulk_handle); margo_bulk_free(ipc_bulk_handle); return (err < 0) ? err : out_size; -} \ No newline at end of file +} + +int rpc_send_trunc_data(const std::string& path, size_t current_size, size_t new_size) { + assert(current_size > new_size); + hg_return_t ret; + rpc_trunc_in_t in; + in.path = path.c_str(); + in.length = new_size; + + bool error = false; + + // Find out which data server needs to delete chunks in order to contact only them + const unsigned int chunk_start = chnk_id_for_offset(new_size, CHUNKSIZE); + const unsigned int chunk_end = chnk_id_for_offset(current_size - new_size - 1, CHUNKSIZE); + std::unordered_set hosts; + for(unsigned int chunk_id = chunk_start; chunk_id <= chunk_end; ++chunk_id) { + hosts.insert(CTX->distributor()->locate_data(path, chunk_id)); + } + + std::vector rpc_handles(hosts.size()); + std::vector rpc_waiters(hosts.size()); + unsigned int req_num = 0; + for (const auto& host: hosts) { + ret = margo_create_wrap(ipc_trunc_data_id, rpc_trunc_data_id, host, rpc_handles[req_num], false); + if (ret != HG_SUCCESS) { + CTX->log()->error("{}() Unable to create Mercury handle for host: ", __func__, host); + break; + } + + // send async rpc + ret = margo_iforward(rpc_handles[req_num], &in, &rpc_waiters[req_num]); + if (ret != HG_SUCCESS) { + CTX->log()->error("{}() Failed to send request to host: {}", __func__, host); + break; + } + ++req_num; + } + + if(req_num < hosts.size()) { + // An error occurred. Cleanup and return + CTX->log()->error("{}() Error -> sent only some requests {}/{}. Cancelling request...", __func__, req_num, hosts.size()); + for(unsigned int i = 0; i < req_num; ++i) { + margo_destroy(rpc_handles[i]); + } + errno = EIO; + return -1; + } + + assert(req_num == hosts.size()); + // Wait for RPC responses and then get response + rpc_err_out_t out{}; + for (unsigned int i = 0; i < hosts.size(); ++i) { + ret = margo_wait(rpc_waiters[i]); + if (ret == HG_SUCCESS) { + ret = margo_get_output(rpc_handles[i], &out); + if (ret == HG_SUCCESS) { + if(out.err){ + CTX->log()->error("{}() received error response: {}", __func__, out.err); + error = true; + } + } else { + // Get output failed + CTX->log()->error("{}() while getting rpc output", __func__); + error = true; + } + } else { + // Wait failed + CTX->log()->error("{}() Failed while waiting for response", __func__); + error = true; + } + + /* clean up resources consumed by this rpc */ + margo_free_output(rpc_handles[i], &out); + margo_destroy(rpc_handles[i]); + } + + if(error) { + errno = EIO; + return -1; + } + return 0; +} diff --git a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp index f8995aec8..cb2902a1c 100644 --- a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp +++ b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp @@ -177,6 +177,58 @@ int rpc_send_stat(const std::string& path, string& attr) { return err; } +int rpc_send_decr_size(const std::string& path, size_t length) { + hg_handle_t handle; + rpc_trunc_in_t in{}; + int err = 0; + in.path = path.c_str(); + in.length = length; + + CTX->log()->debug("{}() Creating Mercury handle ...", __func__); + auto ret = margo_create_wrap(ipc_decr_size_id, rpc_decr_size_id, path, handle, false); + if (ret != HG_SUCCESS) { + errno = EBUSY; + return -1; + } + + // Send rpc +#if defined(MARGO_FORWARD_TIMER) + ret = margo_forward_timed_wrap_timer(handle, &in, __func__); +#else + ret = margo_forward_timed_wrap(handle, &in); +#endif + // Get response + if (ret != HG_SUCCESS) { + CTX->log()->error("{}() timed out", __func__); + margo_destroy(handle); + errno = EBUSY; + return -1; + } + + rpc_err_out_t out{}; + ret = margo_get_output(handle, &out); + if (ret != HG_SUCCESS) { + CTX->log()->error("{}() while getting rpc output", __func__); + margo_free_output(handle, &out); + margo_destroy(handle); + errno = EBUSY; + return -1; + } + + CTX->log()->debug("{}() Got response: {}", __func__, out.err); + + if(out.err != 0){ + //In case of error out.err contains the + //corresponding value of errno + errno = out.err; + err = -1; + } + + margo_free_output(handle, &out); + margo_destroy(handle); + return err; +} + int rpc_send_rm_node(const std::string& path, const bool remove_metadentry_only) { hg_return_t ret; int err = 0; // assume we succeed diff --git a/ifs_test/CMakeLists.txt b/ifs_test/CMakeLists.txt index 0c44dc783..c0a010dc3 100644 --- a/ifs_test/CMakeLists.txt +++ b/ifs_test/CMakeLists.txt @@ -20,6 +20,8 @@ add_executable(ifs_test_wr wr_test.cpp) add_executable(ifs_test_dir dir_test.cpp) +add_executable(ifs_test_truncate truncate.cpp) + find_package(MPI) if(${MPI_FOUND}) set(SOURCE_FILES_MPI main_MPI.cpp) diff --git a/ifs_test/truncate.cpp b/ifs_test/truncate.cpp new file mode 100644 index 000000000..2b40f9ea9 --- /dev/null +++ b/ifs_test/truncate.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +using namespace std; + +int main(int argc, char* argv[]) { + + string mountdir = "/tmp/mountdir"; + string f = mountdir + "/file"; + std::array buffIn {'i'}; + std::array buffOut {'\0'}; + unsigned int size_after_trunc = 2; + int fd; + int ret; + struct stat st; + + fd = open(f.c_str(), O_WRONLY | O_CREAT, 0777); + if(fd < 0){ + cerr << "Error opening file (write)" << endl; + return -1; + } + auto nw = write(fd, buffIn.data(), buffIn.size()); + if(nw != buffIn.size()){ + cerr << "Error writing file" << endl; + return -1; + } + + if(close(fd) != 0){ + cerr << "Error closing file" << endl; + return -1; + } + + ret = truncate(f.c_str(), size_after_trunc); + if(ret != 0){ + cerr << "Error truncating file: " << strerror(errno) << endl; + return -1; + }; + + /* Check file size */ + ret = stat(f.c_str(), &st); + if(ret != 0){ + cerr << "Error stating file: " << strerror(errno) << endl; + return -1; + }; + + if(st.st_size != size_after_trunc){ + cerr << "Wrong file size after truncation: " << st.st_size << endl; + return -1; + } + + + /* Read the file back */ + + fd = open(f.c_str(), O_RDONLY); + if(fd < 0){ + cerr << "Error opening file (read)" << endl; + return -1; + } + + auto nr = read(fd, buffOut.data(), buffOut.size()); + if(nr != size_after_trunc){ + cerr << "[Error] read more then file size: " << nr << endl; + return -1; + } + + ret = close(fd); + if(ret != 0){ + cerr << "Error closing file: " << strerror(errno) << endl; + return -1; + }; + + /* Remove test file */ + ret = remove(f.c_str()); + if(ret != 0){ + cerr << "Error removing file: " << strerror(errno) << endl; + return -1; + }; +} -- GitLab From 8194b35794343947385449ca216ed58e990f496a Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sat, 2 Jun 2018 19:30:47 +0200 Subject: [PATCH 039/105] refactor open function to support O_TRUNC --- ifs/src/preload/adafs_functions.cpp | 84 ++++++++++++++++++----------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index b3afd9458..770fc0b70 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -20,54 +20,78 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return -1; } - if (flags & O_CREAT){ + bool exists = true; + struct stat st; + err = adafs_stat(path, &st); + if(err) { + if(errno == ENOENT) { + exists = false; + } else { + CTX->log()->error("{}() error while retriving stat to file", __func__); + return -1; + } + } + + if (!exists) { + if (! (flags & O_CREAT)) { + // file doesn't exists and O_CREAT was not set + errno = ENOENT; + return -1; + } + + /*** CREATION ***/ + assert(flags & O_CREAT); + if(flags & O_DIRECTORY){ - CTX->log()->error("{}() `O_CREAT` with `O_DIRECTORY` flag is not supported", __func__); + CTX->log()->error("{}() O_DIRECTORY use with O_CREAT. NOT SUPPORTED", __func__); errno = ENOTSUP; return -1; } // no access check required here. If one is using our FS they have the permissions. - err = adafs_mk_node(path, mode | S_IFREG); - if(err != 0) + if(adafs_mk_node(path, mode | S_IFREG)) { + CTX->log()->error("{}() error creating non-existent file", __func__); return -1; - + } } else { - if(flags & O_DIRECTORY){ - return adafs_opendir(path); + /* File already exists */ + + if(flags & O_EXCL) { + // File exists and O_EXCL was set + errno = EEXIST; + return -1; } - auto mask = F_OK; // F_OK == 0 #if defined(CHECK_ACCESS_DURING_OPEN) + auto mask = F_OK; // F_OK == 0 if ((mode & S_IRUSR) || (mode & S_IRGRP) || (mode & S_IROTH)) mask = mask & R_OK; if ((mode & S_IWUSR) || (mode & S_IWGRP) || (mode & S_IWOTH)) mask = mask & W_OK; if ((mode & S_IXUSR) || (mode & S_IXGRP) || (mode & S_IXOTH)) mask = mask & X_OK; -#endif -#if defined(DO_LOOKUP) - // check if file exists - err = rpc_send_access(path, mask); - if(err != 0) + + if( ! ((mask & md.mode()) == mask)) { + errno = EACCES; return -1; + } #endif - } - if( flags & O_TRUNC ){ - //TODO truncation leave chunks on the server side - if((flags & O_RDWR) || (flags & O_WRONLY)) { - long updated_size; - auto ret = rpc_send_update_metadentry_size(path.c_str(), 0, 0, false, updated_size); - if (ret != 0) { - CTX->log()->error("{}() update_metadentry_size failed with ret {}", __func__, ret); - errno = EIO; - return -1; // ERR + if(S_ISDIR(st.st_mode)) { + return adafs_opendir(path); + } + + /*** Regular file exists ***/ + assert(S_ISREG(st.st_mode)); + + if( (flags & O_TRUNC) && ((flags & O_RDWR) || (flags & O_WRONLY)) ) { + if(adafs_truncate(path, st.st_size, 0)) { + CTX->log()->error("{}() error truncating file", __func__); + return -1; } } } - // TODO the open flags should not be in the map just set the pos accordingly return CTX->file_map()->add(std::make_shared(path, flags)); } @@ -308,19 +332,17 @@ ssize_t adafs_pread_ws(int fd, void* buf, size_t count, off64_t offset) { int adafs_opendir(const std::string& path) { init_ld_env_if_needed(); - std::string attr; - auto err = rpc_send_stat(path, attr); - if (err != 0) { - CTX->log()->debug("{}() send stat failed", __func__); - return err; - } + struct stat st; - db_val_to_stat(path, attr, st); + if(adafs_stat(path, &st)) { + return -1; + } if(!(st.st_mode & S_IFDIR)) { CTX->log()->debug("{}() path is not a directory", __func__); errno = ENOTDIR; return -1; } + auto open_dir = std::make_shared(path); rpc_send_get_dirents(*open_dir); return CTX->file_map()->add(open_dir); -- GitLab From 7741fff383af3f06418bd65c1be6b7c402fd0a86 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 5 Jun 2018 11:24:10 +0200 Subject: [PATCH 040/105] Improoved trace log on read/write --- ifs/src/preload/adafs_functions.cpp | 2 ++ ifs/src/preload/intcp_functions.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 770fc0b70..70ca5f6cb 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -298,6 +298,7 @@ ssize_t adafs_pwrite_ws(int fd, const void* buf, size_t count, off64_t offset) { init_ld_env_if_needed(); auto adafs_fd = CTX->file_map()->get(fd); auto path = make_shared(adafs_fd->path()); + CTX->log()->trace("{}() fd: {}, count: {}, offset: {}", __func__, fd, count, offset); auto append_flag = adafs_fd->get_flag(OpenFile_flags::append); ssize_t ret = 0; long updated_size = 0; @@ -318,6 +319,7 @@ ssize_t adafs_pread_ws(int fd, void* buf, size_t count, off64_t offset) { init_ld_env_if_needed(); auto adafs_fd = CTX->file_map()->get(fd); auto path = make_shared(adafs_fd->path()); + CTX->log()->trace("{}() fd: {}, count: {}, offset: {}", __func__, fd, count, offset); // Zeroing buffer before read is only relevant for sparse files. Otherwise sparse regions contain invalid data. #if defined(ZERO_BUFFER_BEFORE_READ) memset(buf, 0, sizeof(char)*count); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 4eef65805..24c0df983 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -842,7 +842,7 @@ ssize_t readv(int fd, const struct iovec *iov, int iovcnt) { ssize_t read(int fd, void* buf, size_t count) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with fd {}", __func__, fd); + CTX->log()->trace("{}() called with fd {}, count {}", __func__, fd, count); if (CTX->file_map()->exist(fd)) { auto adafs_fd = CTX->file_map()->get(fd); auto pos = adafs_fd->pos(); //retrieve the current offset @@ -851,6 +851,7 @@ ssize_t read(int fd, void* buf, size_t count) { if (ret > 0) { adafs_fd->pos(pos + ret); } + CTX->log()->trace("{}() returning {}", __func__, ret); return ret; } } -- GitLab From 443a690d9b67b847f7fb6c1e8c49fe23b973f218 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 5 Jun 2018 13:08:00 +0200 Subject: [PATCH 041/105] deprecated *stat64 functions family According to the glibc [1] the stat64 function family would be called only on 32-bit machine. To be sure we still intercept the function, but we now throw a NOTSUP error. In this way we can drop the adafs_stat64 function and implement just the logic to populate the `struct stat`. [1]: https://github.molgen.mpg.de/git-mirror/glibc/blob/20003c49884422da7ffbc459cdeee768a6fee07b/sysdeps/unix/sysv/linux/generic/xstat.c#L46 --- ifs/include/preload/preload_util.hpp | 3 -- ifs/src/preload/adafs_functions.cpp | 10 ---- ifs/src/preload/intcp_functions.cpp | 37 ++++++------- ifs/src/preload/preload_util.cpp | 81 ---------------------------- 4 files changed, 16 insertions(+), 115 deletions(-) diff --git a/ifs/include/preload/preload_util.hpp b/ifs/include/preload/preload_util.hpp index c5e43f95c..dafe9d394 100644 --- a/ifs/include/preload/preload_util.hpp +++ b/ifs/include/preload/preload_util.hpp @@ -87,11 +87,8 @@ int get_fd_idx(); bool is_fs_path(const char* path); -// TODO template these two suckers int db_val_to_stat(std::string path, std::string db_val, struct stat& attr); -int db_val_to_stat64(std::string path, std::string db_val, struct stat64& attr); - int get_daemon_pid(); bool read_system_hostfile(); diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 70ca5f6cb..21e4334f7 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -146,7 +146,6 @@ int adafs_access(const std::string& path, const int mask) { #endif } -// TODO combine adafs_stat and adafs_stat64 int adafs_stat(const string& path, struct stat* buf) { init_ld_env_if_needed(); string attr = ""s; @@ -156,15 +155,6 @@ int adafs_stat(const string& path, struct stat* buf) { return err; } -int adafs_stat64(const string& path, struct stat64* buf) { - init_ld_env_if_needed(); - string attr = ""s; - auto err = rpc_send_stat(path, attr); - if (err == 0) - db_val_to_stat64(path, attr, *buf); - return err; -} - int adafs_statfs(const string& path, struct statfs* adafs_buf, struct statfs& realfs_buf) { init_ld_env_if_needed(); // Check that file path exists diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 24c0df983..99b39b831 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -16,6 +16,10 @@ using namespace std; +void inline notsup_error_32_bit_func(const char* func = __builtin_FUNCTION()) { + CTX->log()->error("{}() is NOT SUPPORTED. According to glibc, this function should be called only on 32-bit machine", func); +} + int open(const char* path, int flags, ...) { init_passthrough_if_needed(); @@ -604,11 +608,9 @@ int __xstat(int ver, const char* path, struct stat* buf) __THROW { int __xstat64(int ver, const char* path, struct stat64* buf) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat64(rel_path, buf); - } + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } return (reinterpret_cast(libc___xstat64))(ver, path, buf); } @@ -668,24 +670,19 @@ passthrough: int __fxstatat64(int ver, int dirfd, const char * path, struct stat64 * buf, int flags) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}'", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat64(rel_path, buf); - } + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } return (reinterpret_cast(libc___fxstatat64))(ver, dirfd, path, buf, flags); - } int __fxstat64(int ver, int fd, struct stat64* buf) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with fd {}", __func__, fd); - if (CTX->file_map()->exist(fd)) { - auto path = CTX->file_map()->get(fd)->path(); - return adafs_stat64(path, buf); - } + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } return (reinterpret_cast(libc___fxstat64))(ver, fd, buf); } @@ -705,11 +702,9 @@ int __lxstat(int ver, const char* path, struct stat* buf) __THROW { int __lxstat64(int ver, const char* path, struct stat64* buf) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat64(rel_path, buf); - } + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } return (reinterpret_cast(libc___lxstat64))(ver, path, buf); } diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index 724e803af..3223a045c 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -142,87 +142,6 @@ int db_val_to_stat(const std::string path, std::string db_val, struct stat& attr return 0; } -/** - * Converts the dentry db value into a stat64 struct, which is needed by Linux - * @param path - * @param db_val - * @param attr - * @return - */ -int db_val_to_stat64(const std::string path, std::string db_val, struct stat64& attr) { - - auto pos = db_val.find(dentry_val_delim); - if (pos == std::string::npos) { // no delimiter found => no metadata enabled. fill with dummy values - attr.st_ino = std::hash{}(path); - attr.st_mode = static_cast(stoul(db_val)); - attr.st_nlink = 1; - attr.st_uid = CTX->fs_conf()->uid; - attr.st_gid = CTX->fs_conf()->gid; - attr.st_size = 0; - attr.st_blksize = BLOCKSIZE; - attr.st_blocks = 0; - attr.st_atim.tv_sec = 0; - attr.st_mtim.tv_sec = 0; - attr.st_ctim.tv_sec = 0; - return 0; - } - // some metadata is enabled: mode is always there - attr.st_mode = static_cast(stoul(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - // size is also there XXX - pos = db_val.find(dentry_val_delim); - if (pos != std::string::npos) { // delimiter found. more metadata is coming - attr.st_size = stol(db_val.substr(0, pos)); - db_val.erase(0, pos + 1); - } else { - attr.st_size = stol(db_val); - } - // The order is important. don't change. - if (CTX->fs_conf()->atime_state) { - pos = db_val.find(dentry_val_delim); - attr.st_atim.tv_sec = static_cast(stol(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } - if (CTX->fs_conf()->mtime_state) { - pos = db_val.find(dentry_val_delim); - attr.st_mtim.tv_sec = static_cast(stol(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } - if (CTX->fs_conf()->ctime_state) { - pos = db_val.find(dentry_val_delim); - attr.st_ctim.tv_sec = static_cast(stol(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } - if (CTX->fs_conf()->uid_state) { - pos = db_val.find(dentry_val_delim); - attr.st_uid = static_cast(stoul(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } else { - attr.st_uid = CTX->fs_conf()->gid; - } - if (CTX->fs_conf()->gid_state) { - pos = db_val.find(dentry_val_delim); - attr.st_gid = static_cast(stoul(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } else { - attr.st_gid = CTX->fs_conf()->gid; - } - if (CTX->fs_conf()->inode_no_state) { - pos = db_val.find(dentry_val_delim); - attr.st_ino = static_cast(stoul(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } - if (CTX->fs_conf()->link_cnt_state) { - pos = db_val.find(dentry_val_delim); - attr.st_nlink = static_cast(stoul(db_val.substr(0, pos))); - db_val.erase(0, pos + 1); - } - if (CTX->fs_conf()->blocks_state) { // last one will not encounter a delimiter anymore - attr.st_blocks = static_cast(stoul(db_val)); - } - return 0; -} - /** * @return daemon pid. If not running @return -1. * Loads set deamon mountdir set in daemon.pid file -- GitLab From f75dad97832a31a2e8ef8759b83339d75d2dfb4b Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 5 Jun 2018 15:36:51 +0200 Subject: [PATCH 042/105] missing ifndef for included header --- ifs/include/global/chunk_calc_util.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ifs/include/global/chunk_calc_util.hpp b/ifs/include/global/chunk_calc_util.hpp index 154f68482..13553d814 100644 --- a/ifs/include/global/chunk_calc_util.hpp +++ b/ifs/include/global/chunk_calc_util.hpp @@ -1,3 +1,8 @@ +#ifndef IFS_CHNK_CALC_UTIL_HPP +#define IFS_CHNK_CALC_UTIL_HPP + +#include + /** * Compute the base2 logarithm for 64 bit integers */ @@ -86,3 +91,5 @@ inline uint64_t chnk_count_for_offset(const off64_t offset, const size_t count, return static_cast((chnk_end >> log2(chnk_size)) - (chnk_start >> log2(chnk_size)) + 1); } + +#endif -- GitLab From 55c7558531179b3dfc645a267276d39fcd5c996e Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 5 Jun 2018 15:37:45 +0200 Subject: [PATCH 043/105] Prevent random data on stat output Some of the variables of stat struct are not initialized, thus they will remains will unpredictable junks data from memory. This random values could induct strange behaviours on user applications. Even if we don't support some of the fields in the struct stat, it is necessary to set them to some default value. --- ifs/include/preload/preload_util.hpp | 2 +- ifs/src/preload/preload_util.cpp | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ifs/include/preload/preload_util.hpp b/ifs/include/preload/preload_util.hpp index dafe9d394..e1887de52 100644 --- a/ifs/include/preload/preload_util.hpp +++ b/ifs/include/preload/preload_util.hpp @@ -87,7 +87,7 @@ int get_fd_idx(); bool is_fs_path(const char* path); -int db_val_to_stat(std::string path, std::string db_val, struct stat& attr); +int db_val_to_stat(const std::string& path, std::string db_val, struct stat& attr); int get_daemon_pid(); diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index 3223a045c..442d674eb 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -64,10 +64,21 @@ bool is_fs_path(const char* path) { * @param attr * @return */ -int db_val_to_stat(const std::string path, std::string db_val, struct stat& attr) { +int db_val_to_stat(const std::string& path, std::string db_val, struct stat& attr) { - attr.st_ino = std::hash{}(path); + /* Populate default values */ attr.st_dev = makedev(0, 0); + attr.st_ino = std::hash{}(path); + attr.st_nlink = 1; + attr.st_uid = CTX->fs_conf()->uid; + attr.st_gid = CTX->fs_conf()->gid; + attr.st_rdev = 0; + attr.st_blksize = BLOCKSIZE; + attr.st_blocks = 0; + + memset(&attr.st_atim, 0, sizeof(timespec)); + memset(&attr.st_mtim, 0, sizeof(timespec)); + memset(&attr.st_ctim, 0, sizeof(timespec)); auto pos = db_val.find(dentry_val_delim); if (pos == std::string::npos) { // no delimiter found => no metadata enabled. fill with dummy values @@ -114,18 +125,12 @@ int db_val_to_stat(const std::string path, std::string db_val, struct stat& attr pos = db_val.find(dentry_val_delim); attr.st_uid = static_cast(stoul(db_val.substr(0, pos))); db_val.erase(0, pos + 1); - } else { - attr.st_uid = CTX->fs_conf()->uid; } - if (CTX->fs_conf()->gid_state) { pos = db_val.find(dentry_val_delim); attr.st_gid = static_cast(stoul(db_val.substr(0, pos))); db_val.erase(0, pos + 1); - } else { - attr.st_gid = CTX->fs_conf()->gid; } - if (CTX->fs_conf()->inode_no_state) { pos = db_val.find(dentry_val_delim); attr.st_ino = static_cast(stoul(db_val.substr(0, pos))); -- GitLab From f8eaf857f4a03d333cd82b49ccbbbb6326cad5a7 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 17 Jun 2018 15:06:58 +0200 Subject: [PATCH 044/105] bugfix: wrong size parsing in DecrementSizeOperand The decrement size operand does not have a trailing slash, thus we need to add it before to use std::stoul. --- ifs/src/daemon/backend/metadata/merge.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ifs/src/daemon/backend/metadata/merge.cpp b/ifs/src/daemon/backend/metadata/merge.cpp index c485e32f2..ddff02aff 100644 --- a/ifs/src/daemon/backend/metadata/merge.cpp +++ b/ifs/src/daemon/backend/metadata/merge.cpp @@ -64,7 +64,9 @@ DecreaseSizeOperand::DecreaseSizeOperand(const size_t size) : DecreaseSizeOperand::DecreaseSizeOperand(const rdb::Slice& serialized_op){ //Parse size size_t read = 0; - size = std::stoul(serialized_op.data(), &read); + //we need to convert serialized_op to a string because it doesn't contain the + //leading slash needed by stoul + size = std::stoul(serialized_op.ToString(), &read); //check that we consumed all the input string assert(read == serialized_op.size()); } -- GitLab From 5033f9c6a2bfbe4aea7091887d7e2a556860d44f Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 17 Jun 2018 15:19:30 +0200 Subject: [PATCH 045/105] makes logs configurable at exec time loggers can be now configured at execution time, by using environment variables for both server and client. Server: - ADAFS_LOG_LEVEL - ADAFS_DAEMON_LOG_PATH Client: - ADAFS_LOG_LEVEL - ADAFS_PRELOAD_LOG_PATH --- ifs/configure_public.hpp | 12 ------- ifs/include/global/configure.hpp | 10 ++++++ ifs/include/global/log_util.hpp | 13 +++++++ ifs/src/daemon/CMakeLists.txt | 1 + ifs/src/daemon/adafs_daemon.cpp | 48 +++++++++++--------------- ifs/src/global/CMakeLists.txt | 10 ++++++ ifs/src/global/log_util.cpp | 58 ++++++++++++++++++++++++++++++++ ifs/src/preload/CMakeLists.txt | 1 + ifs/src/preload/preload.cpp | 41 ++++++++++++---------- 9 files changed, 136 insertions(+), 58 deletions(-) create mode 100644 ifs/include/global/log_util.hpp create mode 100644 ifs/src/global/log_util.cpp diff --git a/ifs/configure_public.hpp b/ifs/configure_public.hpp index 45526e01b..679e47d2b 100644 --- a/ifs/configure_public.hpp +++ b/ifs/configure_public.hpp @@ -6,18 +6,6 @@ #ifndef FS_CONFIGURE_PUBLIC_H #define FS_CONFIGURE_PUBLIC_H -// To enabled logging for daemon -//#define LOG_INFO -//#define LOG_DEBUG -#define LOG_TRACE -#define LOG_DAEMON_PATH "/tmp/adafs_daemon.log" - -// Enable logging for preload -//#define LOG_PRELOAD_INFO -//#define LOG_PRELOAD_DEBUG -#define LOG_PRELOAD_TRACE -#define LOG_PRELOAD_PATH "/tmp/adafs_preload.log" - // Set a hostname suffix when a connection is built. E.g., "-ib" to use Infiniband #define HOSTNAME_SUFFIX "" //#define MARGODIAG // enables diagnostics of margo (printed after shutting down diff --git a/ifs/include/global/configure.hpp b/ifs/include/global/configure.hpp index e39836a8b..87c65942e 100644 --- a/ifs/include/global/configure.hpp +++ b/ifs/include/global/configure.hpp @@ -74,4 +74,14 @@ // Debug configurations //#define RPC_TEST //unused +// environment prefixes +#define ENV_PREFIX "ADAFS_" + +// Log +#define DEFAULT_PRELOAD_LOG_PATH "/tmp/adafs_preload.log" +#define DEFAULT_DAEMON_LOG_PATH "/tmp/adafs_daemon.log" + +#define DEFAULT_PRELOAD_LOG_LEVEL 4 // info +#define DEFAULT_DAEMON_LOG_LEVEL 4 // info + #endif //FS_CONFIGURE_H diff --git a/ifs/include/global/log_util.hpp b/ifs/include/global/log_util.hpp new file mode 100644 index 000000000..d39c36299 --- /dev/null +++ b/ifs/include/global/log_util.hpp @@ -0,0 +1,13 @@ +#ifndef IFS_LOG_UITIL_HPP +#define IFS_LOG_UITIL_HPP + + +#include "extern/spdlog/spdlog.h" + +spdlog::level::level_enum get_spdlog_level(const std::string& level_str); +spdlog::level::level_enum get_spdlog_level(unsigned int level); +void setup_loggers(const std::vector& loggers, + spdlog::level::level_enum level, const std::string& path); + + +#endif diff --git a/ifs/src/daemon/CMakeLists.txt b/ifs/src/daemon/CMakeLists.txt index 36773850e..882e51cb1 100644 --- a/ifs/src/daemon/CMakeLists.txt +++ b/ifs/src/daemon/CMakeLists.txt @@ -36,6 +36,7 @@ target_link_libraries(adafs_daemon metadata_db storage distributor + log_util # margo libs ${ABT_LIBRARIES} ${ABT_SNOOZER_LIBRARIES} diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index 883de1cec..d3aec2e73 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -346,40 +347,31 @@ void shutdown_handler(int dummy) { } void initialize_loggers() { + std::string path = DEFAULT_DAEMON_LOG_PATH; + // Try to get log path from env variable + std::string env_path_key = ENV_PREFIX; + env_path_key += "DAEMON_LOG_PATH"; + char* env_path = getenv(env_path_key.c_str()); + if (env_path != nullptr) { + path = env_path; + } + + spdlog::level::level_enum level = get_spdlog_level(DEFAULT_DAEMON_LOG_LEVEL); + // Try to get log path from env variable + std::string env_level_key = ENV_PREFIX; + env_level_key += "LOG_LEVEL"; + char* env_level = getenv(env_level_key.c_str()); + if (env_level != nullptr) { + level = get_spdlog_level(env_level); + } + auto logger_names = std::vector{ "main", "MetadataDB", "ChunkStorage", }; - /* Create common sink */ - auto file_sink = std::make_shared(LOG_DAEMON_PATH); - - /* Create and configure loggers */ - auto loggers = std::list>(); - for(const auto& name: logger_names){ - auto logger = std::make_shared(name, file_sink); - logger->flush_on(spdlog::level::trace); - loggers.push_back(logger); - } - - /* register loggers */ - for(const auto& logger: loggers){ - spdlog::register_logger(logger); - } - - // set logger format - spdlog::set_pattern("[%C-%m-%d %H:%M:%S.%f] %P [%L][%n] %v"); - -#if defined(LOG_TRACE) - spdlog::set_level(spdlog::level::trace); -#elif defined(LOG_DEBUG) - spdlog::set_level(spdlog::level::debug); -#elif defined(LOG_INFO) - spdlog::set_level(spdlog::level::info); -#else - spdlog::set_level(spdlog::level::off); -#endif + setup_loggers(logger_names, level, path); } int main(int argc, const char* argv[]) { diff --git a/ifs/src/global/CMakeLists.txt b/ifs/src/global/CMakeLists.txt index e7f09152d..819d26a6b 100644 --- a/ifs/src/global/CMakeLists.txt +++ b/ifs/src/global/CMakeLists.txt @@ -6,3 +6,13 @@ target_sources(distributor PRIVATE ${CMAKE_CURRENT_LIST_DIR}/rpc/distributor.cpp ) + +add_library(log_util STATIC) +set_property(TARGET log_util PROPERTY POSITION_INDEPENDENT_CODE ON) +target_sources(log_util + PUBLIC + ${INCLUDE_DIR}/global/log_util.hpp + ${INCLUDE_DIR}/extern/spdlog/spdlog.h + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/log_util.cpp + ) diff --git a/ifs/src/global/log_util.cpp b/ifs/src/global/log_util.cpp new file mode 100644 index 000000000..65bb53699 --- /dev/null +++ b/ifs/src/global/log_util.cpp @@ -0,0 +1,58 @@ +#include "global/log_util.hpp" +#include +#include +#include + + +spdlog::level::level_enum get_spdlog_level(const std::string& level_str) { + char* parse_end; + unsigned long long uint_level = strtoul(level_str.c_str(), &parse_end, 10); + if( parse_end != (level_str.c_str() + level_str.size())) { + throw std::runtime_error("Error: log level has wrong format: '" + level_str + "'"); + } + return get_spdlog_level(uint_level); +} + +spdlog::level::level_enum get_spdlog_level(unsigned int level) { + switch(level) { + case 0: + return spdlog::level::off; + case 1: + return spdlog::level::critical; + case 2: + return spdlog::level::err; + case 3: + return spdlog::level::warn; + case 4: + return spdlog::level::info; + case 5: + return spdlog::level::debug; + default: + return spdlog::level::trace; + } +} + +void setup_loggers(const std::vector& loggers_name, + spdlog::level::level_enum level, const std::string& path) { + + /* Create common sink */ + auto file_sink = std::make_shared(path); + + /* Create and configure loggers */ + auto loggers = std::list>(); + for(const auto& name: loggers_name){ + auto logger = std::make_shared(name, file_sink); + logger->flush_on(spdlog::level::trace); + loggers.push_back(logger); + } + + /* register loggers */ + for(const auto& logger: loggers){ + spdlog::register_logger(logger); + } + + // set logger format + spdlog::set_pattern("[%C-%m-%d %H:%M:%S.%f] %P [%L][%n] %v"); + + spdlog::set_level(level); +} diff --git a/ifs/src/preload/CMakeLists.txt b/ifs/src/preload/CMakeLists.txt index 866636b14..d1c8df9ac 100644 --- a/ifs/src/preload/CMakeLists.txt +++ b/ifs/src/preload/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(adafs_preload_client SHARED ${PRELOAD_SRC} ${PRELOAD_HEADERS}) target_link_libraries(adafs_preload_client # internal distributor + log_util # external dl ${ABT_LIBRARIES} diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index 3cee92074..d1dae706b 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -1,4 +1,4 @@ - +#include #include #include #include @@ -264,24 +264,29 @@ void init_ld_env_if_needed() { } void init_logging() { - //set the spdlogger and initialize it with spdlog - auto ld_logger = spdlog::basic_logger_mt("basic_logger", LOG_PRELOAD_PATH); - // set logger format - spdlog::set_pattern("[%C-%m-%d %H:%M:%S.%f] %P [%L] %v"); - // flush log when info, warning, error messages are encountered - ld_logger->flush_on(spdlog::level::info); -#if defined(LOG_PRELOAD_TRACE) - spdlog::set_level(spdlog::level::trace); - ld_logger->flush_on(spdlog::level::trace); -#elif defined(LOG_PRELOAD_DEBUG) - spdlog::set_level(spdlog::level::debug); -#elif defined(LOG_PRELOAD_INFO) - spdlog::set_level(spdlog::level::info); -#else - spdlog::set_level(spdlog::level::off); -#endif + std::string path = DEFAULT_PRELOAD_LOG_PATH; + // Try to get log path from env variable + std::string env_key = ENV_PREFIX; + env_key += "PRELOAD_LOG_PATH"; + char* env_log_path = getenv(env_key.c_str()); + if (env_log_path != nullptr) { + path = env_log_path; + } + + spdlog::level::level_enum level = get_spdlog_level(DEFAULT_DAEMON_LOG_LEVEL); + // Try to get log path from env variable + std::string env_level_key = ENV_PREFIX; + env_level_key += "LOG_LEVEL"; + char* env_level = getenv(env_level_key.c_str()); + if (env_level != nullptr) { + level = get_spdlog_level(env_level); + } + + auto logger_names = std::vector {"main"}; + + setup_loggers(logger_names, level, path); - CTX->log(ld_logger); + CTX->log(spdlog::get(logger_names.at(0))); } /** -- GitLab From f6f80b9bc6848021080246933309c174260dfebe Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 17 Jun 2018 15:22:12 +0200 Subject: [PATCH 046/105] bugfix: writev handles 0 sized writes writev could reveive no-ops blocks characterized by a zero size length. We simply need to skip them. --- ifs/src/preload/intcp_functions.cpp | 5 ++++- ifs/src/preload/rpc/ld_rpc_data_ws.cpp | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 99b39b831..ff61c53ba 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -797,8 +797,11 @@ ssize_t writev(int fd, const struct iovec *iov, int iovcnt) { ssize_t written = 0; ssize_t ret; for (int i = 0; i < iovcnt; ++i){ - auto buf = (iov+i)->iov_base; auto count = (iov+i)->iov_len; + if(count == 0) { + continue; + } + auto buf = (iov+i)->iov_base; ret = adafs_pwrite_ws(fd, buf, count, pos); if(ret == -1) { break; diff --git a/ifs/src/preload/rpc/ld_rpc_data_ws.cpp b/ifs/src/preload/rpc/ld_rpc_data_ws.cpp index 8d3fdf682..afc3e24fd 100644 --- a/ifs/src/preload/rpc/ld_rpc_data_ws.cpp +++ b/ifs/src/preload/rpc/ld_rpc_data_ws.cpp @@ -17,6 +17,7 @@ using namespace std; */ ssize_t rpc_send_write(const string& path, const void* buf, const bool append_flag, const off64_t in_offset, const size_t write_size, const int64_t updated_metadentry_size) { + assert(write_size > 0); // Calculate chunkid boundaries and numbers so that daemons know in which interval to look for chunks off64_t offset = in_offset; if (append_flag) -- GitLab From 3410955d2ab3f6241b7343e94b94070f9c9245b9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 17 Jun 2018 15:23:44 +0200 Subject: [PATCH 047/105] performance: do not zero temporary buffer On C++14 make_unique function also zeroes the newly allocated buffer. It turns out that this operation is increadibly slow for such a big buffer. Moreover we don't need a zeroed buffer here. --- ifs/src/preload/rpc/ld_rpc_metadentry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp index cb2902a1c..492616cb3 100644 --- a/ifs/src/preload/rpc/ld_rpc_metadentry.cpp +++ b/ifs/src/preload/rpc/ld_rpc_metadentry.cpp @@ -469,7 +469,7 @@ void rpc_send_get_dirents(OpenDir& open_dir){ std::vector recv_buffers(host_size); // preallocate receiving buffer. The actual size is not known yet. - auto recv_buff = std::make_unique(RPC_DIRENTS_BUFF_SIZE); + auto recv_buff = std::unique_ptr(new char[RPC_DIRENTS_BUFF_SIZE]); const unsigned long int per_host_buff_size = RPC_DIRENTS_BUFF_SIZE / host_size; hg_return_t hg_ret; -- GitLab From 2018d22a3192a5adc7a916da72de98ed4276dcd7 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Sun, 17 Jun 2018 15:26:23 +0200 Subject: [PATCH 048/105] Add support for AT_REMOVEDIR flag on unlinkat --- ifs/src/preload/intcp_functions.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index ff61c53ba..1b36d7c0e 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -445,13 +445,7 @@ int unlinkat(int dirfd, const char *cpath, int flags) { init_passthrough_if_needed(); if(CTX->initialized()) { std::string path(cpath); - CTX->log()->trace("{}() called with path {}, dirfd {}, flags {}", __func__, path, dirfd, flags); - - if(flags & AT_REMOVEDIR) { - CTX->log()->error("{}() AT_REMOVEDIR flag is not supported", __func__); - errno = ENOTDIR; - return -1; - } + CTX->log()->trace("{}() called with path '{}' dirfd {}, flags {}", __func__, path, dirfd, flags); if(is_relative_path(path)) { if(!(CTX->file_map()->exist(dirfd))) { @@ -466,15 +460,21 @@ int unlinkat(int dirfd, const char *cpath, int flags) { if(has_trailing_slash(path)){ path.pop_back(); } - return adafs_rm_node(dir->path() + '/' + path); + path = dir->path() + '/' + path; } else { // Path is absolute assert(is_absolute_path(path)); - if (CTX->relativize_path(path)) { - return adafs_rm_node(path); + if (!CTX->relativize_path(path)) { + goto passthrough; } } + + if(flags & AT_REMOVEDIR) { + return adafs_rmdir(path); + } else { + return adafs_rm_node(path); + } } passthrough: return (reinterpret_cast(libc_unlinkat))(dirfd, cpath, flags); -- GitLab From b0485c4dc07ec5def08edf681fa8cf6e1d9eb1ee Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 18 Jun 2018 19:28:12 +0200 Subject: [PATCH 049/105] update deps, drop abt-snoozer margo, argobots and mercury have been updated. The abt-snoozer dep required by margo it is not needed anymore. RocksDB has been also updated to the lastest release --- ifs/CMake/FindAbt-Snoozer.cmake | 44 ---------------------- ifs/CMakeLists.txt | 1 - ifs/include/daemon/adafs_daemon.hpp | 1 - ifs/include/preload/rpc/ld_rpc_data_ws.hpp | 1 - ifs/scripts/compile_dep.sh | 28 +------------- ifs/scripts/dl_dep.sh | 12 ++---- ifs/src/daemon/CMakeLists.txt | 2 - ifs/src/daemon/adafs_daemon.cpp | 28 ++++++++++---- ifs/src/daemon/backend/data/CMakeLists.txt | 2 - ifs/src/preload/CMakeLists.txt | 2 - ifs/src/preload/preload.cpp | 33 ++++++++++------ 11 files changed, 47 insertions(+), 107 deletions(-) delete mode 100644 ifs/CMake/FindAbt-Snoozer.cmake diff --git a/ifs/CMake/FindAbt-Snoozer.cmake b/ifs/CMake/FindAbt-Snoozer.cmake deleted file mode 100644 index ec98fa477..000000000 --- a/ifs/CMake/FindAbt-Snoozer.cmake +++ /dev/null @@ -1,44 +0,0 @@ -find_path(ABT_SNOOZER_DIR - HINTS - /usr - /usr/local - /usr/local/adafs/ - ${ADAFS_DEPS_INSTALL} - ) - -find_path(ABT_SNOOZER_INCLUDE_DIR abt-snoozer.h - HINTS - ${ADAFS_DEPS_INSTALL} - ${ABT_SNOOZER_DIR} - /usr - /usr/local - /usr/local/adafs - /opt - PATH_SUFFIXES include - PATH_SUFFIXES include/abt-snoozer - ) - -find_library(ABT_SNOOZER_LIBRARY abt-snoozer - HINTS - ${ADAFS_DEPS_INSTALL} - ${ABT_SNOOZER_DIR} - /usr - /usr/local - /usr/local/adafs - /opt/ - PATH_SUFFIXES lib - PATH_SUFFIXES lib/argobots - ) - -set(ABT_SNOOZER_INCLUDE_DIRS ${ABT_SNOOZER_INCLUDE_DIR}) -set(ABT_SNOOZER_LIBRARIES ${ABT_SNOOZER_LIBRARY}) - - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Abt-Snoozer DEFAULT_MSG ABT_SNOOZER_LIBRARY ABT_SNOOZER_INCLUDE_DIR) - -mark_as_advanced( - ABT_SNOOZER_DIR - ABT_SNOOZER_LIBRARY - ABT_SNOOZER_INCLUDE_DIR -) \ No newline at end of file diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 8ecda8890..3aa479d1f 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -51,7 +51,6 @@ find_package(RocksDB REQUIRED) find_package(Libev REQUIRED) find_package(Mercury REQUIRED) find_package(Abt REQUIRED) -find_package(Abt-Snoozer REQUIRED) find_package(Margo REQUIRED) # boost dependencies, system is required for filesystem diff --git a/ifs/include/daemon/adafs_daemon.hpp b/ifs/include/daemon/adafs_daemon.hpp index 2b40d1224..23660a680 100644 --- a/ifs/include/daemon/adafs_daemon.hpp +++ b/ifs/include/daemon/adafs_daemon.hpp @@ -14,7 +14,6 @@ // margo extern "C" { #include -#include #include #include } diff --git a/ifs/include/preload/rpc/ld_rpc_data_ws.hpp b/ifs/include/preload/rpc/ld_rpc_data_ws.hpp index 241342b6e..7e33f8496 100644 --- a/ifs/include/preload/rpc/ld_rpc_data_ws.hpp +++ b/ifs/include/preload/rpc/ld_rpc_data_ws.hpp @@ -4,7 +4,6 @@ extern "C" { #include -#include #include #include #include diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index e413bb904..3393027ce 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -297,37 +297,11 @@ prepare_build_dir ${CURR} cd ${CURR} ./autogen.sh cd ${CURR}/build -../configure --prefix=${INSTALL} +../configure --prefix=${INSTALL} --enable-perf-opt --disable-checks make -j${CORES} make install [ "${PERFORM_TEST}" ] && make check -echo "############################################################ Installing: Abt-snoozer" -# Abt snoozer -CURR=${SOURCE}/abt-snoozer -prepare_build_dir ${CURR} -cd ${CURR} -./prepare.sh -cd ${CURR}/build -../configure --prefix=${INSTALL} PKG_CONFIG_PATH=${INSTALL}/lib/pkgconfig -make -j${CORES} -make install -[ "${PERFORM_TEST}" ] && make check - -#echo "############################################################ Installing: Abt-IO" -## Abt IO -#CURR=${SOURCE}/abt-io -#prepare_build_dir ${CURR} -#cd ${CURR} -#echo "########## ADA-FS injection: Applying abt-io c++ template clash patch" -#git apply ${PATCH_DIR}/abt_io_cplusplus_template_clash.patch -#./prepare.sh -#cd ${CURR}/build -#../configure --prefix=${INSTALL} PKG_CONFIG_PATH=${INSTALL}/lib/pkgconfig -#make -j${CORES} -#make install -#[ "${PERFORM_TEST}" ] && make check # The tests create so huge files that breaks memory :D - echo "############################################################ Installing: Margo" # Margo CURR=${SOURCE}/margo diff --git a/ifs/scripts/dl_dep.sh b/ifs/scripts/dl_dep.sh index 196042d71..fbb24200c 100755 --- a/ifs/scripts/dl_dep.sh +++ b/ifs/scripts/dl_dep.sh @@ -199,17 +199,13 @@ if [ "${NA_LAYER}" == "ofi" ] || [ "${NA_LAYER}" == "all" ]; then fi fi # get Mercury -clonedeps "mercury" "https://github.com/mercury-hpc/mercury" "8eb436a6cc42317050117ad9925c2b03c822e6b9" "--recurse-submodules" & +clonedeps "mercury" "https://github.com/mercury-hpc/mercury" "f7f6955f140426b2a7c9e26dc35f8c8e1654d86a" "--recurse-submodules" & # get Argobots -clonedeps "argobots" "https://github.com/carns/argobots.git" "78ceea28ed44faca12cf8ea7f5687b894c66a8c4" "-b dev-get-dev-basic" & -# get Argobots-snoozer -clonedeps "abt-snoozer" "https://xgitlab.cels.anl.gov/sds/abt-snoozer.git" "54c506103cf2d77bd76460db29a67a875f5c5a85" & -# get Argobots-IO -#clonedeps "abt-io" "https://xgitlab.cels.anl.gov/sds/abt-io.git" "35f16da88a1c579ed4726bfa77daa1884829fc0c" & +clonedeps "argobots" "https://github.com/carns/argobots.git" "4a84e66ed8544db215d6862f527775387418f7f6" "-b dev-fifo-wait" & # get Margo -clonedeps "margo" "https://xgitlab.cels.anl.gov/sds/margo.git" "56a2b6585ec8152d5e7d107a0cf33be84dbd5bed" & +clonedeps "margo" "https://xgitlab.cels.anl.gov/sds/margo.git" "cf673d430ce3d4b4f0a32f19f261e7898a863b81" & # get rocksdb -wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.12.4.tar.gz" & +wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.13.3.tar.gz" & # Wait for all download to be completed wait diff --git a/ifs/src/daemon/CMakeLists.txt b/ifs/src/daemon/CMakeLists.txt index 882e51cb1..d04bc9caa 100644 --- a/ifs/src/daemon/CMakeLists.txt +++ b/ifs/src/daemon/CMakeLists.txt @@ -39,7 +39,6 @@ target_link_libraries(adafs_daemon log_util # margo libs ${ABT_LIBRARIES} - ${ABT_SNOOZER_LIBRARIES} mercury ${MARGO_LIBRARIES} # others @@ -51,7 +50,6 @@ target_link_libraries(adafs_daemon target_include_directories(adafs_daemon PRIVATE ${ABT_INCLUDE_DIRS} - ${ABT_SNOOZER_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS} ${MARGO_INCLUDE_DIRS} ) diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index d3aec2e73..71b4a22b8 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -129,16 +129,30 @@ void destroy_enviroment() { } bool init_io_tasklet_pool() { - vector io_streams_tmp(DAEMON_IO_XSTREAMS); - ABT_pool io_pools_tmp; - auto ret = ABT_snoozer_xstream_create(DAEMON_IO_XSTREAMS, &io_pools_tmp, io_streams_tmp.data()); + unsigned int xstreams_num = DAEMON_IO_XSTREAMS; + assert(xstreams_num >= 0); + + //retrieve the pool of the just created scheduler + ABT_pool pool; + auto ret = ABT_pool_create_basic(ABT_POOL_FIFO_WAIT, ABT_POOL_ACCESS_MPMC, ABT_TRUE, &pool); if (ret != ABT_SUCCESS) { - ADAFS_DATA->spdlogger()->error( - "{}() ABT_snoozer_xstream_create() failed to initialize ABT_pool for I/O operations", __func__); + ADAFS_DATA->spdlogger()->error("{}() Failed to create I/O tasks pool", __func__); return false; } - RPC_DATA->io_streams(io_streams_tmp); - RPC_DATA->io_pool(io_pools_tmp); + + //create all subsequent xstream and the associated scheduler, all tapping into the same pool + vector xstreams(xstreams_num); + for (unsigned int i = 0; i < xstreams_num; ++i) { + ret = ABT_xstream_create_basic(ABT_SCHED_BASIC_WAIT, 1, &pool, + ABT_SCHED_CONFIG_NULL, &xstreams[i]); + if (ret != ABT_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to create task execution streams for I/O operations", __func__); + return false; + } + } + + RPC_DATA->io_streams(xstreams); + RPC_DATA->io_pool(pool); return true; } diff --git a/ifs/src/daemon/backend/data/CMakeLists.txt b/ifs/src/daemon/backend/data/CMakeLists.txt index be1c4b005..82a1ba2d5 100644 --- a/ifs/src/daemon/backend/data/CMakeLists.txt +++ b/ifs/src/daemon/backend/data/CMakeLists.txt @@ -13,11 +13,9 @@ target_link_libraries(storage PRIVATE Boost::filesystem ${ABT_LIBRARIES} - ${ABT_SNOOZER_LIBRARIES} ) target_include_directories(storage PRIVATE ${ABT_INCLUDE_DIRS} - ${ABT_SNOOZER_INCLUDE_DIRS} ) diff --git a/ifs/src/preload/CMakeLists.txt b/ifs/src/preload/CMakeLists.txt index d1c8df9ac..b640d00ec 100644 --- a/ifs/src/preload/CMakeLists.txt +++ b/ifs/src/preload/CMakeLists.txt @@ -46,7 +46,6 @@ target_link_libraries(adafs_preload_client # external dl ${ABT_LIBRARIES} - ${ABT_SNOOZER_LIBRARIES} mercury ${MARGO_LIBRARIES} Threads::Threads @@ -55,7 +54,6 @@ target_link_libraries(adafs_preload_client target_include_directories(adafs_preload_client PRIVATE ${ABT_INCLUDE_DIRS} - ${ABT_SNOOZER_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS} ${MARGO_INCLUDE_DIRS} ) diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index d1dae706b..a74209ba5 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -61,18 +61,6 @@ hg_addr_t daemon_svr_addr = HG_ADDR_NULL; bool init_ld_argobots() { CTX->log()->debug("{}() Initializing Argobots ...", __func__); - // We need no arguments to init - auto argo_err = ABT_init(0, nullptr); - if (argo_err != 0) { - CTX->log()->error("{}() ABT_init() Failed to init Argobots (client)", __func__); - return false; - } - // Set primary execution stream to idle without polling. Normally xstreams cannot sleep. This is what ABT_snoozer does - argo_err = ABT_snoozer_xstream_self_set(); - if (argo_err != 0) { - CTX->log()->error("{}() ABT_snoozer_xstream_self_set() (client)", __func__); - return false; - } /* * Single producer (progress function) and multiple consumers are causing an excess memory consumption * in some Argobots version. It does only show if an ES with a pool is created. @@ -81,6 +69,27 @@ bool init_ld_argobots() { * See for reference: https://xgitlab.cels.anl.gov/sds/margo/issues/40 */ putenv(const_cast("ABT_MEM_MAX_NUM_STACKS=8")); + + // We need no arguments to init + auto err = ABT_init(0, nullptr); + if (err != ABT_SUCCESS) { + CTX->log()->error("{}() failed to init Argobots environment", __func__); + return false; + } + + ABT_xstream self_xstream; + err = ABT_xstream_self(&self_xstream); + if (err != ABT_SUCCESS) { + CTX->log()->error("{}() failed to get argobots self xstream", __func__); + return false; + } + + err = ABT_xstream_set_main_sched_basic(self_xstream, ABT_SCHED_BASIC_WAIT, 1, nullptr); + if (err != ABT_SUCCESS) { + CTX->log()->error("{}() failed to set scheduler for main threads pool", __func__); + return false; + } + CTX->log()->debug("{}() Argobots initialization successful.", __func__); return true; } -- GitLab From c7c449e43c4f464061de72cbc754c72d4db84bc6 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 2 Jul 2018 20:11:55 +0200 Subject: [PATCH 050/105] tmp patch: fix mercury busy-waiting issue temporary patch to workaround this issue: https://github.com/mercury-hpc/mercury/issues/229 --- ifs/scripts/compile_dep.sh | 2 + .../patches/mercury_deregister_sock.patch | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 ifs/scripts/patches/mercury_deregister_sock.patch diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index 3393027ce..4a5797c37 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -282,6 +282,8 @@ if [ "$NA_LAYER" == "cci" ] || [ "$NA_LAYER" == "all" ]; then echo "########## Applying cci addr lookup error handling patch" git apply ${PATCH_DIR}/mercury_cci_verbs_lookup.patch fi +echo "########## Applying mercury deregister socket patch" +git apply ${PATCH_DIR}/mercury_deregister_sock.patch cd ${CURR}/build $CMAKE -DMERCURY_USE_SELF_FORWARD:BOOL=ON -DMERCURY_USE_CHECKSUMS:BOOL=OFF -DBUILD_TESTING:BOOL=ON \ -DMERCURY_USE_BOOST_PP:BOOL=ON -DBUILD_SHARED_LIBS:BOOL=ON -DCMAKE_INSTALL_PREFIX=${INSTALL} \ diff --git a/ifs/scripts/patches/mercury_deregister_sock.patch b/ifs/scripts/patches/mercury_deregister_sock.patch new file mode 100644 index 000000000..ca147aaab --- /dev/null +++ b/ifs/scripts/patches/mercury_deregister_sock.patch @@ -0,0 +1,42 @@ +From fd34de9e4a8b91202f0afb9aa222c06bc9761283 Mon Sep 17 00:00:00 2001 +From: Tommaso Tocci +Date: Mon, 2 Jul 2018 11:54:10 +0200 +Subject: [PATCH] sm: clear client disconnection event on socket + +--- + src/na/na_sm.c | 12 +++++++++++- + 1 file changed, 11 insertions(+), 1 deletion(-) + +diff --git a/src/na/na_sm.c b/src/na/na_sm.c +index d96f1b6..01e4110 100644 +--- a/src/na/na_sm.c ++++ b/src/na/na_sm.c +@@ -2134,6 +2134,16 @@ na_sm_progress_sock(na_class_t *na_class, struct na_sm_addr *poll_addr, + *progressed = NA_TRUE; + } + break; ++ case NA_SM_SOCK_DONE: { ++ *progressed = NA_FALSE; ++ ret = na_sm_poll_deregister(na_class, NA_SM_SOCK, poll_addr); ++ if (ret != NA_SUCCESS) { ++ NA_LOG_ERROR("Could not deregister socket from poll set"); ++ ret = NA_PROTOCOL_ERROR; ++ goto done; ++ } ++ } ++ break; + default: + /* TODO Silently ignore, no progress */ + *progressed = NA_FALSE; +@@ -2860,7 +2870,7 @@ na_sm_addr_free(na_class_t *na_class, na_addr_t addr) + ret = na_sm_poll_deregister(na_class, NA_SM_SOCK, na_sm_addr); + if (ret != NA_SUCCESS) { + NA_LOG_ERROR("Could not delete sock from poll set"); +- goto done; ++ //goto done; + } + + /* Remove addr from poll addr queue */ +-- +2.18.0 + -- GitLab From 71c2356306dcc366ff00467eb6b0901b5d8ab3ec Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 3 Jul 2018 10:27:14 +0200 Subject: [PATCH 051/105] deps: update RocksDB version 5.13.3 -> 5.13.4 --- ifs/scripts/dl_dep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/scripts/dl_dep.sh b/ifs/scripts/dl_dep.sh index fbb24200c..cdb9c0e07 100755 --- a/ifs/scripts/dl_dep.sh +++ b/ifs/scripts/dl_dep.sh @@ -205,7 +205,7 @@ clonedeps "argobots" "https://github.com/carns/argobots.git" "4a84e66ed8544db215 # get Margo clonedeps "margo" "https://xgitlab.cels.anl.gov/sds/margo.git" "cf673d430ce3d4b4f0a32f19f261e7898a863b81" & # get rocksdb -wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.13.3.tar.gz" & +wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.13.4.tar.gz" & # Wait for all download to be completed wait -- GitLab From afeb07fca7df4be89f0997db5f8f66fe962705b9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 3 Jul 2018 10:28:01 +0200 Subject: [PATCH 052/105] Enable log tracing of fileno() --- ifs/src/preload/intcp_functions.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 1b36d7c0e..013b608fc 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -224,6 +224,7 @@ int fileno(FILE *stream) { if(CTX->initialized() && (stream != nullptr)) { auto fd = file_to_fd(stream); if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called with fd {}", __func__, fd); return fd; } } -- GitLab From 965369af9c9935003fb232ff2f78c041943bf81d Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 3 Jul 2018 12:45:49 +0200 Subject: [PATCH 053/105] Move fd managment into open_file_map --- ifs/include/preload/open_file_map.hpp | 15 +++++++++++ ifs/include/preload/preload_util.hpp | 6 ----- ifs/src/preload/open_file_map.cpp | 36 ++++++++++++++++++++++++-- ifs/src/preload/preload_util.cpp | 37 --------------------------- 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/ifs/include/preload/open_file_map.hpp b/ifs/include/preload/open_file_map.hpp index 3d7f8e82f..7490845af 100644 --- a/ifs/include/preload/open_file_map.hpp +++ b/ifs/include/preload/open_file_map.hpp @@ -5,6 +5,7 @@ #include #include #include +#include /* Forward declaration */ class OpenDir; @@ -64,6 +65,18 @@ private: std::recursive_mutex files_mutex_; int safe_generate_fd_idx_(); + + /* + * TODO: Setting our file descriptor index to a specific value is dangerous because we might clash with the kernel. + * E.g., if we would passthrough and not intercept and the kernel assigns a file descriptor but we will later use + * the same fd value, we will intercept calls that were supposed to be going to the kernel. This works the other way around too. + * To mitigate this issue, we set the initial fd number to a high value. We "hope" that we do not clash but this is no permanent solution. + * Note: This solution will probably work well already for many cases because kernel fd values are reused, unlike to ours. + * The only case where we will clash with the kernel is, if one process has more than 100000 files open at the same time. + */ + int fd_idx; + std::mutex fd_idx_mutex; + std::atomic fd_validation_needed; public: OpenFileMap(); @@ -82,6 +95,8 @@ public: int dup2(int oldfd, int newfd); + int generate_fd_idx(); + int get_fd_idx(); }; diff --git a/ifs/include/preload/preload_util.hpp b/ifs/include/preload/preload_util.hpp index e1887de52..dd94263ee 100644 --- a/ifs/include/preload/preload_util.hpp +++ b/ifs/include/preload/preload_util.hpp @@ -76,15 +76,9 @@ extern hg_id_t rpc_trunc_data_id; extern hg_id_t rpc_get_dirents_id; // rpc addresses. Populated when environment is initialized. After that it is read-only accessed extern std::map rpc_addresses; -// file descriptor index validation flag -extern std::atomic fd_validation_needed; // function definitions -int generate_fd_idx(); - -int get_fd_idx(); - bool is_fs_path(const char* path); int db_val_to_stat(const std::string& path, std::string db_val, struct stat& attr); diff --git a/ifs/src/preload/open_file_map.cpp b/ifs/src/preload/open_file_map.cpp index 902026204..3e396f278 100644 --- a/ifs/src/preload/open_file_map.cpp +++ b/ifs/src/preload/open_file_map.cpp @@ -1,8 +1,9 @@ +#include + #include #include #include #include -#include using namespace std; @@ -24,7 +25,10 @@ OpenFile::OpenFile(const string& path, const int flags) : path_(path) { pos_ = 0; // If O_APPEND flag is used, it will be used before each write. } -OpenFileMap::OpenFileMap() {} +OpenFileMap::OpenFileMap(): + fd_idx(10000), + fd_validation_needed(false) + {} OpenFile::~OpenFile() { @@ -157,3 +161,31 @@ int OpenFileMap::dup2(const int oldfd, const int newfd) { files_.insert(make_pair(newfd, open_file)); return newfd; } + +/** + * Generate new file descriptor index to be used as an fd within one process in ADA-FS + * @return fd_idx + */ +int OpenFileMap::generate_fd_idx() { + // We need a mutex here for thread safety + std::lock_guard inode_lock(fd_idx_mutex); + if (fd_idx == std::numeric_limits::max()) { + CTX->log()->info("{}() File descriptor index exceeded ints max value. Setting it back to 100000", __func__); + /* + * Setting fd_idx back to 3 could have the effect that fd are given twice for different path. + * This must not happen. Instead a flag is set which tells can tell the OpenFileMap that it should check + * if this fd is really safe to use. + */ + fd_idx = 100000; + fd_validation_needed = true; + } + return fd_idx++; +} + +int OpenFileMap::get_fd_idx() { + std::lock_guard inode_lock(fd_idx_mutex); + return fd_idx; +} + + + diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index 442d674eb..0c0b0cb20 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -15,43 +15,6 @@ using namespace std; static const std::string dentry_val_delim = ","s; -/* - * TODO: Setting our file descriptor index to a specific value is dangerous because we might clash with the kernel. - * E.g., if we would passthrough and not intercept and the kernel assigns a file descriptor but we will later use - * the same fd value, we will intercept calls that were supposed to be going to the kernel. This works the other way around too. - * To mitigate this issue, we set the initial fd number to a high value. We "hope" that we do not clash but this is no permanent solution. - * Note: This solution will probably work well already for many cases because kernel fd values are reused, unlike to ours. - * The only case where we will clash with the kernel is, if one process has more than 100000 files open at the same time. - */ -static int fd_idx = 100000; -static mutex fd_idx_mutex; -std::atomic fd_validation_needed(false); - -/** - * Generate new file descriptor index to be used as an fd within one process in ADA-FS - * @return fd_idx - */ -int generate_fd_idx() { - // We need a mutex here for thread safety - std::lock_guard inode_lock(fd_idx_mutex); - if (fd_idx == std::numeric_limits::max()) { - CTX->log()->info("{}() File descriptor index exceeded ints max value. Setting it back to 100000", __func__); - /* - * Setting fd_idx back to 3 could have the effect that fd are given twice for different path. - * This must not happen. Instead a flag is set which tells can tell the OpenFileMap that it should check - * if this fd is really safe to use. - */ - fd_idx = 100000; - fd_validation_needed = true; - } - return fd_idx++; -} - -int get_fd_idx() { - std::lock_guard inode_lock(fd_idx_mutex); - return fd_idx; -} - bool is_fs_path(const char* path) { return strstr(path, CTX->mountdir().c_str()) == path; } -- GitLab From ceaf4ac5a0da74482189db02bff71f6f027d49dc Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Fri, 6 Jul 2018 16:34:55 +0200 Subject: [PATCH 054/105] deps: update rocksDB version 5.13.4 -> 5.14.2 changelog for the new version: https://github.com/facebook/rocksdb/releases/tag/v5.14.2 --- ifs/scripts/dl_dep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/scripts/dl_dep.sh b/ifs/scripts/dl_dep.sh index cdb9c0e07..419853607 100755 --- a/ifs/scripts/dl_dep.sh +++ b/ifs/scripts/dl_dep.sh @@ -205,7 +205,7 @@ clonedeps "argobots" "https://github.com/carns/argobots.git" "4a84e66ed8544db215 # get Margo clonedeps "margo" "https://xgitlab.cels.anl.gov/sds/margo.git" "cf673d430ce3d4b4f0a32f19f261e7898a863b81" & # get rocksdb -wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.13.4.tar.gz" & +wgetdeps "rocksdb" "https://github.com/facebook/rocksdb/archive/v5.14.2.tar.gz" & # Wait for all download to be completed wait -- GitLab From 06627fd734b12d42fa2aa2bc861a95acc9a05493 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 9 Jul 2018 17:55:20 +0200 Subject: [PATCH 055/105] Bugfix: missing throw in MetadataDB constructor --- ifs/src/daemon/adafs_daemon.cpp | 1 + ifs/src/daemon/backend/metadata/db.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index 71b4a22b8..e322b8d9a 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -40,6 +40,7 @@ bool init_environment() { ADAFS_DATA->mdb(std::make_shared(metadata_path)); } catch (const std::exception & e) { ADAFS_DATA->spdlogger()->error("{}() unable to initialize metadata DB: {}", __func__, e.what()); + return false; } // Initialize data backend diff --git a/ifs/src/daemon/backend/metadata/db.cpp b/ifs/src/daemon/backend/metadata/db.cpp index d5608b7ac..aa036d50c 100644 --- a/ifs/src/daemon/backend/metadata/db.cpp +++ b/ifs/src/daemon/backend/metadata/db.cpp @@ -20,7 +20,7 @@ MetadataDB::MetadataDB(const std::string& path): path(path) { rdb::DB * rdb_ptr; auto s = rocksdb::DB::Open(options, path, &rdb_ptr); if (!s.ok()) { - std::runtime_error("Failed to opend RocksDB: " + s.ToString()); + throw std::runtime_error("Failed to open RocksDB: " + s.ToString()); } this->db.reset(rdb_ptr); } -- GitLab From 27d1cc7fc6b5e4a9bda334f80abb6e44924ab94e Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 9 Jul 2018 17:30:25 +0200 Subject: [PATCH 056/105] cmake bugfix: include tokenizer header Tokenizer components of boost suite is an header-only library that must be explicitely imported through the Boost::boost imported target --- ifs/src/daemon/CMakeLists.txt | 1 + ifs/src/preload/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/ifs/src/daemon/CMakeLists.txt b/ifs/src/daemon/CMakeLists.txt index d04bc9caa..47070a7bc 100644 --- a/ifs/src/daemon/CMakeLists.txt +++ b/ifs/src/daemon/CMakeLists.txt @@ -42,6 +42,7 @@ target_link_libraries(adafs_daemon mercury ${MARGO_LIBRARIES} # others + Boost::boost # needed for tokenizer header Boost::program_options Boost::filesystem Threads::Threads diff --git a/ifs/src/preload/CMakeLists.txt b/ifs/src/preload/CMakeLists.txt index b640d00ec..8a6d16dad 100644 --- a/ifs/src/preload/CMakeLists.txt +++ b/ifs/src/preload/CMakeLists.txt @@ -48,6 +48,7 @@ target_link_libraries(adafs_preload_client ${ABT_LIBRARIES} mercury ${MARGO_LIBRARIES} + Boost::boost # needed for tokenizer header Threads::Threads ) -- GitLab From 9496766949379a88c9817e86b1d27184dfe10109 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 9 Jul 2018 20:03:44 +0200 Subject: [PATCH 057/105] cmake: Clean FindSnappy module --- ifs/CMake/FindSnappy.cmake | 21 ++++++++++++++++++ ifs/CMake/Findsnappy.cmake | 44 -------------------------------------- ifs/CMakeLists.txt | 4 ++-- 3 files changed, 23 insertions(+), 46 deletions(-) create mode 100644 ifs/CMake/FindSnappy.cmake delete mode 100644 ifs/CMake/Findsnappy.cmake diff --git a/ifs/CMake/FindSnappy.cmake b/ifs/CMake/FindSnappy.cmake new file mode 100644 index 000000000..025f0a5e4 --- /dev/null +++ b/ifs/CMake/FindSnappy.cmake @@ -0,0 +1,21 @@ +find_library(Snappy_LIBRARY + NAMES snappy + HINTS ${ADAFS_DEPS_INSTALL} +) + +find_path(Snappy_INCLUDE_DIR + NAMES snappy.h + HINTS ${ADAFS_DEPS_INSTALL} +) + +set(Snappy_LIBRARIES ${Snappy_LIBRARY}) +set(Snappy_INCLUDE_DIRS ${Snappy_INCLUDE_DIR}) + + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(snappy DEFAULT_MSG Snappy_LIBRARY Snappy_INCLUDE_DIR) + +mark_as_advanced( + Snappy_LIBRARY + Snappy_INCLUDE_DIR +) diff --git a/ifs/CMake/Findsnappy.cmake b/ifs/CMake/Findsnappy.cmake deleted file mode 100644 index aff30234e..000000000 --- a/ifs/CMake/Findsnappy.cmake +++ /dev/null @@ -1,44 +0,0 @@ -find_path(snappy_DIR - HINTS - /usr - /usr/local - /usr/local/adafs/ - ${ADAFS_DEPS_INSTALL} - ) - -find_path(snappy_INCLUDE_DIR snappy.h - HINTS - ${ADAFS_DEPS_INSTALL} - $ENV{HOME}/opt - /usr - /usr/local - /usr/local/adafs - /opt - PATH_SUFFIXES include - PATH_SUFFIXES include/snappy - ) - -find_library(snappy_LIBRARY snappy - HINTS - ${ADAFS_DEPS_INSTALL} - $ENV{HOME}/opt - /usr - /usr/local - /usr/local/adafs - /opt/ - PATH_SUFFIXES lib - PATH_SUFFIXES lib/snappy - ) - -set(snappy_INCLUDE_DIRS ${snappy_INCLUDE_DIR}) -set(snappy_LIBRARIES ${snappy_LIBRARY}) - - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(snappy DEFAULT_MSG snappy_LIBRARY snappy_INCLUDE_DIR) - -mark_as_advanced( - snappy_DIR - snappy_LIBRARY - snappy_INCLUDE_DIR -) diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 3aa479d1f..778916d4e 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -43,7 +43,7 @@ endif (NOT ADAFS_DEPS_INSTALL) find_package(LZ4 REQUIRED) find_package(ZLIB REQUIRED) find_package(BZip2 REQUIRED) -find_package(snappy REQUIRED) +find_package(Snappy REQUIRED) find_package(ZStd REQUIRED) find_package(JeMalloc) # required if rocksdb has been build with jemalloc find_package(RocksDB REQUIRED) @@ -119,7 +119,7 @@ target_link_libraries(RocksDB INTERFACE ${ROCKSDB_LIBRARIES} # rocksdb libs - ${snappy_LIBRARIES} + ${Snappy_LIBRARIES} ${ZLIB_LIBRARIES} ${BZIP2_LIBRARIES} ${ZSTD_LIBRARIES} -- GitLab From 852c07601fe4dc7c352e3bb166fb6080948cd236 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 10 Jul 2018 11:54:03 +0200 Subject: [PATCH 058/105] workaround rocksDB compilation error avoid compilation error reported here: https://github.com/facebook/rocksdb/issues/4017 --- ifs/scripts/compile_dep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index 4a5797c37..6b9f48159 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -321,7 +321,7 @@ echo "############################################################ Installing: CURR=${SOURCE}/rocksdb cd ${CURR} make clean -make USE_RTTI=1 -j${CORES} static_lib +make CXXFLAGS="-Wno-stringop-truncation" USE_RTTI=1 -j${CORES} static_lib make INSTALL_PATH=${INSTALL} install echo "Done" -- GitLab From d35803d9afa0c1c3cc08f900111bd32fedd36af4 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 10 Jul 2018 11:56:24 +0200 Subject: [PATCH 059/105] compile deps script: instruct compiler about custom installation dirs compiler need to be informed about dependencies not residing in canonical system folders --- ifs/scripts/compile_dep.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index 6b9f48159..feb15943b 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -157,6 +157,9 @@ mkdir -p ${SOURCE} ######### From now on exits on any error ######## set -e +export CPATH="${CPATH}:${INSTALL}/include" +export LIBRARY_PATH="${LIBRARY_PATH}:${INSTALL}/lib:${INSTALL}/lib64" + # Set cluster dependencies first if [[ ( "${CLUSTER}" == "mogon1" ) || ( "${CLUSTER}" == "fh2" ) || ( "${CLUSTER}" == "mogon2" ) ]]; then # get libtool -- GitLab From 7202dec318572318fb6a53513f243bf825a1d837 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 10 Jul 2018 16:49:35 +0200 Subject: [PATCH 060/105] bugfix: prepend env variables declarations in make command --- ifs/scripts/compile_dep.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index feb15943b..e8a7a4ff4 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -324,7 +324,7 @@ echo "############################################################ Installing: CURR=${SOURCE}/rocksdb cd ${CURR} make clean -make CXXFLAGS="-Wno-stringop-truncation" USE_RTTI=1 -j${CORES} static_lib -make INSTALL_PATH=${INSTALL} install +CXXFLAGS="-Wno-stringop-truncation" USE_RTTI=1 make -j${CORES} static_lib +INSTALL_PATH=${INSTALL} make install echo "Done" -- GitLab From a3e9d93507a32dd8b1fc127f6be8fea1b21c2990 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 19 Jul 2018 12:31:34 +0200 Subject: [PATCH 061/105] Clean log messages makes format of log messages more omogeneus --- ifs/src/daemon/handler/h_data.cpp | 3 ++- ifs/src/daemon/handler/h_metadentry.cpp | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ifs/src/daemon/handler/h_data.cpp b/ifs/src/daemon/handler/h_data.cpp index c6a129180..da661e9b8 100644 --- a/ifs/src/daemon/handler/h_data.cpp +++ b/ifs/src/daemon/handler/h_data.cpp @@ -480,6 +480,7 @@ static hg_return_t rpc_srv_trunc_data(hg_handle_t handle) { ADAFS_DATA->spdlogger()->error("{}() Could not get RPC input data with err {}", __func__, ret); throw runtime_error("Failed to get RPC input data"); } + ADAFS_DATA->spdlogger()->debug("{}() path: '{}', length: {}", __func__, in.path, in.length); unsigned int chunk_start = chnk_id_for_offset(in.length, CHUNKSIZE); @@ -492,7 +493,7 @@ static hg_return_t rpc_srv_trunc_data(hg_handle_t handle) { ADAFS_DATA->storage()->trim_chunk_space(in.path, chunk_start); - ADAFS_DATA->spdlogger()->debug("Sending output {}", out.err); + ADAFS_DATA->spdlogger()->debug("{}() Sending output {}", __func__, out.err); auto hret = margo_respond(handle, &out); if (hret != HG_SUCCESS) { ADAFS_DATA->spdlogger()->error("{}() Failed to respond"); diff --git a/ifs/src/daemon/handler/h_metadentry.cpp b/ifs/src/daemon/handler/h_metadentry.cpp index a82609d06..23ff183b1 100644 --- a/ifs/src/daemon/handler/h_metadentry.cpp +++ b/ifs/src/daemon/handler/h_metadentry.cpp @@ -99,7 +99,7 @@ static hg_return_t rpc_srv_stat(hg_handle_t handle) { if (ret != HG_SUCCESS) ADAFS_DATA->spdlogger()->error("{}() Failed to retrieve input from handle", __func__); assert(ret == HG_SUCCESS); - ADAFS_DATA->spdlogger()->debug("Got srv stat RPC for path {}", in.path); + ADAFS_DATA->spdlogger()->debug("{}() path: '{}'", __func__, in.path); std::string val; try { @@ -139,8 +139,7 @@ static hg_return_t rpc_srv_decr_size(hg_handle_t handle) { throw runtime_error("Failed to retrieve input from handle"); } - ADAFS_DATA->spdlogger()->debug("Serving decrement size RPC with path: '{}', length: {}", - in.path, in.length); + ADAFS_DATA->spdlogger()->debug("{}() path: '{}', length: {}", __func__, in.path, in.length); try { ADAFS_DATA->mdb()->decrease_size(in.path, in.length); @@ -270,7 +269,7 @@ static hg_return_t rpc_srv_update_metadentry_size(hg_handle_t handle) { if (ret != HG_SUCCESS) ADAFS_DATA->spdlogger()->error("{}() Failed to retrieve input from handle", __func__); assert(ret == HG_SUCCESS); - ADAFS_DATA->spdlogger()->debug("{}() Got update metadentry size RPC with path {}", __func__, in.path); + ADAFS_DATA->spdlogger()->debug("{}() path: {}, size: {}, offset: {}, append: {}", __func__, in.path, in.size, in.offset, in.append); try { update_metadentry_size(in.path, in.size, in.offset, (in.append == HG_TRUE)); -- GitLab From 3ccc43fdca472834107bed4262a50c8889e3330b Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 19 Jul 2018 12:32:23 +0200 Subject: [PATCH 062/105] bugfix: support truncation at zero length --- ifs/src/preload/adafs_functions.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 21e4334f7..8c783bea8 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -231,7 +231,11 @@ off64_t adafs_lseek(shared_ptr adafs_fd, off64_t offset, int whence) { int adafs_truncate(const std::string& path, off_t old_size, off_t new_size) { assert(new_size >= 0); - assert(new_size < old_size); + assert(new_size <= old_size); + + if(new_size == old_size) { + return 0; + } if (rpc_send_decr_size(path, new_size)) { CTX->log()->debug("{}() failed to decrease size", __func__); -- GitLab From ba7efef532e953e1a8d723c942e03600cca8e502 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 19 Jul 2018 12:33:10 +0200 Subject: [PATCH 063/105] bugfix: fix return value for fread/fwrite --- ifs/src/preload/intcp_functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 013b608fc..d5dcbd1c7 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -178,7 +178,7 @@ size_t intcp_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { if (ret > 0) { // Update offset in file descriptor in the file map adafs_fd->pos(pos + ret); - return ret % size; + return ret / size; } return ret; } @@ -198,7 +198,7 @@ size_t intcp_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (ret > 0) { // Update offset in file descriptor in the file map adafs_fd->pos(pos + ret); - return ret % size; + return ret / size; } return ret; } -- GitLab From 76a5cd0543427ce0db64f63fdb1eeb37bef1604b Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 23 Jul 2018 18:00:44 +0200 Subject: [PATCH 064/105] compile deps: do not clean RocksDB build dir --- ifs/scripts/compile_dep.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index e8a7a4ff4..113f6f9bf 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -323,7 +323,6 @@ echo "############################################################ Installing: # Rocksdb CURR=${SOURCE}/rocksdb cd ${CURR} -make clean CXXFLAGS="-Wno-stringop-truncation" USE_RTTI=1 make -j${CORES} static_lib INSTALL_PATH=${INSTALL} make install -- GitLab From c8ded6f42f427f4f7207e81f66b935295c09f802 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 23 Jul 2018 18:02:00 +0200 Subject: [PATCH 065/105] compile deps: Make use of CMAKE_PREFIX_PATH Make use of CMAKE_PREFIX_PATH if it is in the current environment --- ifs/scripts/compile_dep.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index 113f6f9bf..5513b1816 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -148,6 +148,7 @@ USE_CCI="-DNA_USE_CCI:BOOL=OFF" USE_OFI="-DNA_USE_OFI:BOOL=OFF" CMAKE=`find_cmake` +CMAKE="${CMAKE} -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" echo "Source path = '$1'"; echo "Install path = '$2'"; -- GitLab From 06c72fe63b17dae03431d9cb385ff281c2d7bf59 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 25 Jul 2018 10:34:31 +0200 Subject: [PATCH 066/105] Store CWD in preload context During interception library startup the CWD of intercepted process is stored in the PreloadContext object --- ifs/include/global/path_util.hpp | 3 +++ ifs/include/preload/preload_context.hpp | 4 ++++ ifs/src/global/path_util.cpp | 18 ++++++++++++++++++ ifs/src/preload/preload.cpp | 3 +++ ifs/src/preload/preload_context.cpp | 8 ++++++++ 5 files changed, 36 insertions(+) diff --git a/ifs/include/global/path_util.hpp b/ifs/include/global/path_util.hpp index 2de59271d..7e4cc7d9f 100644 --- a/ifs/include/global/path_util.hpp +++ b/ifs/include/global/path_util.hpp @@ -3,11 +3,14 @@ #include +#define PATH_MAX_LEN 4096 // 4k chars + bool is_relative_path(const std::string& path); bool is_absolute_path(const std::string& path); bool has_trailing_slash(const std::string& path); std::string path_to_relative(const std::string& root_path, const std::string& complete_path); std::string dirname(const std::string& path); +std::string get_current_working_dir(); #endif //IFS_PATH_UTIL_HPP diff --git a/ifs/include/preload/preload_context.hpp b/ifs/include/preload/preload_context.hpp index bc14b250f..a3a5b77b6 100644 --- a/ifs/include/preload/preload_context.hpp +++ b/ifs/include/preload/preload_context.hpp @@ -44,6 +44,7 @@ class PreloadContext { std::shared_ptr distributor_; std::shared_ptr fs_conf_; + std::string cwd_; std::string mountdir_; bool initialized_; @@ -62,6 +63,9 @@ class PreloadContext { void mountdir(const std::string& path); std::string mountdir() const; + void cwd(const std::string& path); + std::string cwd() const; + bool relativize_path(std::string& path) const; const std::shared_ptr& file_map() const; diff --git a/ifs/src/global/path_util.cpp b/ifs/src/global/path_util.cpp index 0043c876b..d12252e83 100644 --- a/ifs/src/global/path_util.cpp +++ b/ifs/src/global/path_util.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include @@ -69,3 +71,19 @@ std::string dirname(const std::string& path) { } return path.substr(0, parent_path_size); } + +std::string get_current_working_dir() { + char temp[PATH_MAX_LEN]; + if(getcwd(temp, PATH_MAX_LEN) == NULL) { + throw std::system_error(errno, + std::system_category(), + "Failed to retrieve current working directory"); + } + // getcwd could return "(unreachable)" in some cases + if(temp[0] != '/') { + throw std::runtime_error( + "Current working directory is unreachable"); + } + return {temp}; +} + diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index a74209ba5..086c6ee85 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -305,6 +306,8 @@ void init_preload() { init_passthrough_if_needed(); init_logging(); CTX->log()->debug("Initialized logging subsystem"); + CTX->cwd(get_current_working_dir()); + CTX->log()->debug("Current working directory: '{}'", CTX->cwd()); if (get_daemon_pid() == -1 || CTX->mountdir().empty()) { cerr << "ADA-FS daemon not running or mountdir could not be loaded. Check adafs_preload.log" << endl; CTX->log()->error("{}() Daemon not running or mountdir not set", __func__); diff --git a/ifs/src/preload/preload_context.cpp b/ifs/src/preload/preload_context.cpp index 4c605bb3d..ad8491305 100644 --- a/ifs/src/preload/preload_context.cpp +++ b/ifs/src/preload/preload_context.cpp @@ -28,6 +28,14 @@ std::string PreloadContext::mountdir() const { return mountdir_; } +void PreloadContext::cwd(const std::string& path) { + cwd_ = path; +} + +std::string PreloadContext::cwd() const { + return cwd_; +} + bool PreloadContext::relativize_path(std::string& path) const { // Relativize path should be called only after the library constructor has been executed assert(initialized_); -- GitLab From 3435e9dae6cf1921ca48d279038772db5678516e Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Wed, 25 Jul 2018 20:38:14 +0200 Subject: [PATCH 067/105] Store mountdir as splitted string components --- ifs/include/global/path_util.hpp | 2 ++ ifs/include/preload/preload_context.hpp | 7 +++++-- ifs/src/global/path_util.cpp | 15 +++++++++++++++ ifs/src/preload/preload_context.cpp | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/ifs/include/global/path_util.hpp b/ifs/include/global/path_util.hpp index 7e4cc7d9f..e8424d0fe 100644 --- a/ifs/include/global/path_util.hpp +++ b/ifs/include/global/path_util.hpp @@ -2,6 +2,7 @@ #define IFS_PATH_UTIL_HPP #include +#include #define PATH_MAX_LEN 4096 // 4k chars @@ -12,5 +13,6 @@ bool has_trailing_slash(const std::string& path); std::string path_to_relative(const std::string& root_path, const std::string& complete_path); std::string dirname(const std::string& path); std::string get_current_working_dir(); +std::vector split_path(const std::string& path); #endif //IFS_PATH_UTIL_HPP diff --git a/ifs/include/preload/preload_context.hpp b/ifs/include/preload/preload_context.hpp index a3a5b77b6..c02dcde2d 100644 --- a/ifs/include/preload/preload_context.hpp +++ b/ifs/include/preload/preload_context.hpp @@ -3,6 +3,7 @@ #include #include +#include #include /* Forward declarations */ @@ -45,6 +46,7 @@ class PreloadContext { std::shared_ptr fs_conf_; std::string cwd_; + std::vector mountdir_components_; std::string mountdir_; bool initialized_; @@ -61,10 +63,11 @@ class PreloadContext { std::shared_ptr log() const; void mountdir(const std::string& path); - std::string mountdir() const; + const std::string& mountdir() const; + const std::vector& mountdir_components() const; void cwd(const std::string& path); - std::string cwd() const; + const std::string& cwd() const; bool relativize_path(std::string& path) const; diff --git a/ifs/src/global/path_util.cpp b/ifs/src/global/path_util.cpp index d12252e83..cdbab13b1 100644 --- a/ifs/src/global/path_util.cpp +++ b/ifs/src/global/path_util.cpp @@ -20,6 +20,21 @@ bool has_trailing_slash(const std::string& path) { return path.back() == PSP; } +std::vector split_path(const std::string& path) { + std::vector tokens; + size_t start = std::string::npos; + size_t end = (path.front() != PSP)? 0 : 1; + while(end != std::string::npos && end < path.size()) { + start = end; + end = path.find(PSP, start); + tokens.push_back(path.substr(start, end - start)); + if(end != std::string::npos) { + ++end; + } + } + return tokens; +} + /* Make an absolute path relative to a root path * * Convert @absolute_path into a relative one with respect to the given @root_path. diff --git a/ifs/src/preload/preload_context.cpp b/ifs/src/preload/preload_context.cpp index ad8491305..63a1246c8 100644 --- a/ifs/src/preload/preload_context.cpp +++ b/ifs/src/preload/preload_context.cpp @@ -21,6 +21,7 @@ std::shared_ptr PreloadContext::log() const { void PreloadContext::mountdir(const std::string& path) { assert(is_absolute_path(path)); + mountdir_components_ = split_path(path); mountdir_ = path; } -- GitLab From 0e15fd56ce1d4af025d1657f179ce2d56d53ee0b Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 30 Jul 2018 16:38:51 +0200 Subject: [PATCH 068/105] add LIBC_FUNC macro helper --- ifs/include/preload/passthrough.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 15d67564f..824fd4e72 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -5,6 +5,10 @@ #ifndef IFS_PASSTHROUGH_HPP #define IFS_PASSTHROUGH_HPP + +#define LIBC_FUNC(FNAME, ...) \ + ((reinterpret_cast(libc_##FNAME))(__VA_ARGS__)) + // function pointer for preloading extern void* libc; -- GitLab From bbbac5435d47fddf2f588e66335c143108c09d4b Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 26 Jul 2018 14:16:04 +0200 Subject: [PATCH 069/105] path resolution add support for path resolution, this will be used to get canonical path. More info at `man path_resolution` --- ifs/include/global/path_util.hpp | 6 +- ifs/include/preload/passthrough.hpp | 4 + ifs/include/preload/preload_context.hpp | 2 +- ifs/include/preload/resolve.hpp | 5 ++ ifs/src/global/path_util.cpp | 39 ++++----- ifs/src/preload/CMakeLists.txt | 2 + ifs/src/preload/intcp_functions.cpp | 87 +++++++++++++++++-- ifs/src/preload/passthrough.cpp | 8 ++ ifs/src/preload/preload.cpp | 3 +- ifs/src/preload/preload_context.cpp | 31 +++++-- ifs/src/preload/resolve.cpp | 111 ++++++++++++++++++++++++ 11 files changed, 260 insertions(+), 38 deletions(-) create mode 100644 ifs/include/preload/resolve.hpp create mode 100644 ifs/src/preload/resolve.cpp diff --git a/ifs/include/global/path_util.hpp b/ifs/include/global/path_util.hpp index e8424d0fe..81ff1f619 100644 --- a/ifs/include/global/path_util.hpp +++ b/ifs/include/global/path_util.hpp @@ -6,13 +6,15 @@ #define PATH_MAX_LEN 4096 // 4k chars +const constexpr char PSP('/'); // PATH SEPARATOR + + bool is_relative_path(const std::string& path); bool is_absolute_path(const std::string& path); bool has_trailing_slash(const std::string& path); -std::string path_to_relative(const std::string& root_path, const std::string& complete_path); +std::string prepend_path(const std::string& path, const char * raw_path); std::string dirname(const std::string& path); -std::string get_current_working_dir(); std::vector split_path(const std::string& path); #endif //IFS_PATH_UTIL_HPP diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 824fd4e72..5321f6ad5 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -92,6 +92,10 @@ extern void* libc_readdir; extern void* libc_closedir; extern void* libc_chdir; +extern void* libc_fchdir; + +extern void* libc_getcwd; +extern void* libc_get_current_dir_name; extern void* libc_realpath; diff --git a/ifs/include/preload/preload_context.hpp b/ifs/include/preload/preload_context.hpp index c02dcde2d..49dc2ecaf 100644 --- a/ifs/include/preload/preload_context.hpp +++ b/ifs/include/preload/preload_context.hpp @@ -69,7 +69,7 @@ class PreloadContext { void cwd(const std::string& path); const std::string& cwd() const; - bool relativize_path(std::string& path) const; + bool relativize_path(const char * raw_path, std::string& relative_path) const; const std::shared_ptr& file_map() const; diff --git a/ifs/include/preload/resolve.hpp b/ifs/include/preload/resolve.hpp new file mode 100644 index 000000000..5c9edf972 --- /dev/null +++ b/ifs/include/preload/resolve.hpp @@ -0,0 +1,5 @@ +#include + +bool resolve_path (const std::string& path, std::string& resolved); + +std::string get_sys_cwd(); diff --git a/ifs/src/global/path_util.cpp b/ifs/src/global/path_util.cpp index cdbab13b1..a1f17fd28 100644 --- a/ifs/src/global/path_util.cpp +++ b/ifs/src/global/path_util.cpp @@ -1,11 +1,11 @@ #include #include #include +#include #include +#include -const constexpr char PSP('/'); // PATH SEPARATOR - bool is_relative_path(const std::string& path) { return (!path.empty()) && (path.front() != PSP); @@ -20,7 +20,18 @@ bool has_trailing_slash(const std::string& path) { return path.back() == PSP; } -std::vector split_path(const std::string& path) { +std::string prepend_path(const std::string& prefix_path, const char * raw_path) { + assert(!has_trailing_slash(prefix_path)); + std::size_t raw_len = std::strlen(raw_path); + std::string res; + res.reserve(prefix_path.size() + 1 + raw_len); + res.append(prefix_path); + res.push_back(PSP); + res.append(raw_path, raw_len); + return res; +} + +std::vector split_path(const std::string& path) { std::vector tokens; size_t start = std::string::npos; size_t end = (path.front() != PSP)? 0 : 1; @@ -31,10 +42,14 @@ std::vector split_path(const std::string& path) { if(end != std::string::npos) { ++end; } - } + } return tokens; } + + + + /* Make an absolute path relative to a root path * * Convert @absolute_path into a relative one with respect to the given @root_path. @@ -86,19 +101,3 @@ std::string dirname(const std::string& path) { } return path.substr(0, parent_path_size); } - -std::string get_current_working_dir() { - char temp[PATH_MAX_LEN]; - if(getcwd(temp, PATH_MAX_LEN) == NULL) { - throw std::system_error(errno, - std::system_category(), - "Failed to retrieve current working directory"); - } - // getcwd could return "(unreachable)" in some cases - if(temp[0] != '/') { - throw std::runtime_error( - "Current working directory is unreachable"); - } - return {temp}; -} - diff --git a/ifs/src/preload/CMakeLists.txt b/ifs/src/preload/CMakeLists.txt index 8a6d16dad..f81165e1c 100644 --- a/ifs/src/preload/CMakeLists.txt +++ b/ifs/src/preload/CMakeLists.txt @@ -7,6 +7,7 @@ set(PRELOAD_SRC open_dir.cpp passthrough.cpp preload.cpp + resolve.cpp preload_util.cpp rpc/ld_rpc_data_ws.cpp rpc/ld_rpc_metadentry.cpp @@ -32,6 +33,7 @@ set(PRELOAD_HEADERS ../../include/preload/open_dir.hpp ../../include/preload/passthrough.hpp ../../include/preload/preload.hpp + ../../include/preload/resolve.hpp ../../include/preload/preload_util.hpp ../../include/preload/rpc/ld_rpc_data_ws.hpp ../../include/preload/rpc/ld_rpc_metadentry.hpp diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index d5dcbd1c7..659285d69 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1124,14 +1124,91 @@ int intcp_closedir(DIR* dirp) { int chdir(const char* path){ init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(chdir, path); + } + CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - CTX->log()->error("Attempt to chdir into adafs namespace: NOT SUPPORTED", __func__, path); - errno = ENOTSUP; + std::string rel_path; + const char* new_path; + if (CTX->relativize_path(path, rel_path)) { + //path falls in our namespace + struct stat st; + if(adafs_stat(rel_path, &st) != 0) { + CTX->log()->error("{}() path does not exists"); + errno = ENOENT; + return -1; + } + if(!S_ISDIR(st.st_mode)) { + CTX->log()->error("{}() path is not a directory"); + errno = ENOTDIR; + return -1; + } + new_path = CTX->mountdir().c_str(); + } else { + new_path = rel_path.c_str(); + } + + if(LIBC_FUNC(chdir, new_path)) { + CTX->log()->error("{}() failed to change dir: {}", + __func__, std::strerror(errno)); return -1; } - return (reinterpret_cast(libc_chdir))(path); + + CTX->cwd(rel_path); + return 0; +} + +int fchdir(int fd) { + init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(fchdir, fd); + } + CTX->log()->trace("{}() called with fd {}", __func__, fd); + if (CTX->file_map()->exist(fd)) { + auto open_file = CTX->file_map()->get(fd); + auto open_dir = static_pointer_cast(open_file); + if(!open_dir){ + //Cast did not succeeded: open_file is a regular file + CTX->log()->error("{}() file descriptor refers to a normal file: '{}'", + __func__, open_dir->path()); + errno = EBADF; + return -1; + } + CTX->cwd(open_dir->path()); + } else { + if(LIBC_FUNC(fchdir, fd) != 0) { + CTX->log()->error("{}() failed to change dir: {}", + __func__, std::strerror(errno)); + } + CTX->cwd(get_sys_cwd()); + } + return 0; +} + +char *getcwd(char *buf, size_t size) { + init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(getcwd, buf, size); + } + CTX->log()->trace("{}() called with size {}", __func__, size); + if(CTX->cwd().size() + 1 > size) { + CTX->log()->error("{}() buffer too small to host current working dir", __func__); + errno = ERANGE; + return nullptr; + } + strcpy(buf, CTX->cwd().c_str()); + return buf; +} + +char *get_current_dir_name(void) { + init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(get_current_dir_name); + } + CTX->log()->error("{}() not implemented", __func__); + errno = ENOTSUP; + return nullptr; } char *realpath(const char *path, char *resolved_path) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index b0f8a1f03..8143e1dcd 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -93,6 +93,10 @@ void* libc_readdir; void* libc_closedir; void* libc_chdir; +void* libc_fchdir; + +void* libc_getcwd; +void* libc_get_current_dir_name; void* libc_realpath; @@ -185,6 +189,10 @@ void init_passthrough_() { libc_closedir = dlsym(libc, "closedir"); libc_chdir = dlsym(libc, "chdir"); + libc_fchdir = dlsym(libc, "fchdir"); + + libc_getcwd = dlsym(libc, "getcwd"); + libc_get_current_dir_name = dlsym(libc, "get_current_dir_name"); libc_realpath = dlsym(libc, "realpath"); diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index 086c6ee85..8025bd17b 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -306,7 +307,7 @@ void init_preload() { init_passthrough_if_needed(); init_logging(); CTX->log()->debug("Initialized logging subsystem"); - CTX->cwd(get_current_working_dir()); + CTX->cwd(get_sys_cwd()); CTX->log()->debug("Current working directory: '{}'", CTX->cwd()); if (get_daemon_pid() == -1 || CTX->mountdir().empty()) { cerr << "ADA-FS daemon not running or mountdir could not be loaded. Check adafs_preload.log" << endl; diff --git a/ifs/src/preload/preload_context.cpp b/ifs/src/preload/preload_context.cpp index 63a1246c8..30df05659 100644 --- a/ifs/src/preload/preload_context.cpp +++ b/ifs/src/preload/preload_context.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -21,37 +22,49 @@ std::shared_ptr PreloadContext::log() const { void PreloadContext::mountdir(const std::string& path) { assert(is_absolute_path(path)); + assert(!has_trailing_slash(path)); mountdir_components_ = split_path(path); mountdir_ = path; } -std::string PreloadContext::mountdir() const { +const std::string& PreloadContext::mountdir() const { return mountdir_; } +const std::vector& PreloadContext::mountdir_components() const { + return mountdir_components_; +} + void PreloadContext::cwd(const std::string& path) { + log_->debug("Setting CWD to '{}'", path); cwd_ = path; } -std::string PreloadContext::cwd() const { +const std::string& PreloadContext::cwd() const { return cwd_; } -bool PreloadContext::relativize_path(std::string& path) const { +bool PreloadContext::relativize_path(const char * raw_path, std::string& relative_path) const { // Relativize path should be called only after the library constructor has been executed assert(initialized_); // If we run the constructor we also already setup the mountdir assert(!mountdir_.empty()); - if(!is_absolute_path(path)) { - /* We don't support path resolution at the moment - * thus we don't know how to handle relative path - */ + if(raw_path == nullptr || raw_path[0] == '\0') { return false; } - path = path_to_relative(mountdir_, path); - return !path.empty(); + std::string path; + + if(raw_path[0] != PSP) { + /* Path is not absolute, we need to prepend CWD; + * First reserve enough space to minimize memory copy + */ + path = prepend_path(cwd_, raw_path); + } else { + path = raw_path; + } + return resolve_path(path, relative_path); } const std::shared_ptr& PreloadContext::file_map() const { diff --git a/ifs/src/preload/resolve.cpp b/ifs/src/preload/resolve.cpp new file mode 100644 index 000000000..5d731de48 --- /dev/null +++ b/ifs/src/preload/resolve.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include + +#include "global/path_util.hpp" +#include "preload/passthrough.hpp" +#include "preload/preload.hpp" + + + +bool resolve_path (const std::string& path, std::string& resolved) { + CTX->log()->debug("{}() path: '{}'", __func__, path, resolved); + + struct stat st; + const std::vector& mnt_components = CTX->mountdir_components(); + unsigned int mnt_matched = 0; // matched number of component in mountdir + std::string::size_type comp_size = 0; // size of current component + std::string::size_type start = 0; // start index of curr component + std::string::size_type end = 0; // end index of curr component (last processed PSP) + std::string::size_type last_slash_pos = 0; // index of last slash in resolved path + std::string component; + resolved.clear(); + resolved.reserve(path.size()); + + //Process first slash + assert(is_absolute_path(path)); + + while (++end < path.size()) { + start = end; + + /* Skip sequence of multiple path-separators. */ + while(path.at(start) == PSP) { + ++start; + } + + // Find next component + end = path.find(PSP, start); + if(end == std::string::npos) { + end = path.size(); + } + comp_size = end - start; + + if (comp_size == 0) { + // component is empty (this must be the last component) + break; + } + if (comp_size == 1 && path.at(start) == '.') { + // component is '.', we skip it + continue; + } + if (comp_size == 2 && path.at(start) == '.' && path.at(start+1) == '.') { + // component is '..' we need to rollback resolved path + if(resolved.size() > 0) { + resolved.erase(last_slash_pos); + } + if(mnt_matched > 0) { + --mnt_matched; + } + continue; + } + + // add `/` to the reresolved path + resolved.push_back(PSP); + last_slash_pos = resolved.size() - 1; + resolved.append(path, start, comp_size); + + if (mnt_matched < mnt_components.size()) { + // Outside GekkoFS + if (path.compare(start, comp_size, mnt_components.at(mnt_matched)) == 0) { + ++mnt_matched; + } + if (LIBC_FUNC(__xstat, _STAT_VER, resolved.c_str(), &st) < 0) { + resolved.append(path, end, std::string::npos); + return false; + } + if (S_ISLNK(st.st_mode)) { + CTX->log()->error("{}() encountered link: {}", __func__, resolved); + throw std::runtime_error("Readlink encoutered: '" + resolved + "'"); + } else if ((!S_ISDIR(st.st_mode)) && (end != path.size())) { + resolved.append(path, end, std::string::npos); + return false; + } + } else { + // Inside GekkoFS + ++mnt_matched; + } + } + if (mnt_matched >= mnt_components.size()) { + resolved.erase(1, CTX->mountdir().size()); + CTX->log()->debug("{}() internal: '{}'", __func__, resolved); + return true; + } + CTX->log()->debug("{}() external: '{}'", __func__, resolved); + return false; +} + +std::string get_sys_cwd() { + char temp[PATH_MAX_LEN]; + if(LIBC_FUNC(getcwd, temp, PATH_MAX_LEN) == NULL) { + throw std::system_error(errno, + std::system_category(), + "Failed to retrieve current working directory"); + } + // getcwd could return "(unreachable)" in some cases + if(temp[0] != PSP) { + throw std::runtime_error( + "Current working directory is unreachable"); + } + return {temp}; +} -- GitLab From a8ced1092e42a12db076a75bb9eb20378a2c4c3f Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 30 Jul 2018 17:24:28 +0200 Subject: [PATCH 070/105] Use new path resolution for intercepted functions --- ifs/src/preload/intcp_functions.cpp | 643 ++++++++++++++++------------ 1 file changed, 362 insertions(+), 281 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 659285d69..dcd1870a4 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -30,15 +31,16 @@ int open(const char* path, int flags, ...) { mode = static_cast(va_arg(vl, int)); va_end(vl); } + if (!CTX->initialized()) { + return LIBC_FUNC(open, path, flags, mode); + } - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_open(rel_path, mode, flags); - } + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(open, rel_path.c_str(), flags, mode); } - return (reinterpret_cast(libc_open))(path, flags, mode); + return adafs_open(rel_path, mode, flags); } #undef open64 @@ -66,35 +68,51 @@ int openat(int dirfd, const char *cpath, int flags, ...) { va_end(vl); } - if(CTX->initialized()) { - std::string path(cpath); - CTX->log()->trace("{}() called with path {}", __func__, path); + if(!CTX->initialized()) { + return LIBC_FUNC(openat, dirfd, cpath, flags, mode); + } - if(is_relative_path(path)) { - if(!(CTX->file_map()->exist(dirfd))) { - goto passthrough; - } - auto dir = CTX->file_map()->get_dir(dirfd); - if(dir == nullptr) { - CTX->log()->error("{}() dirfd is not a directory ", __func__); - errno = ENOTDIR; - return -1; - } - if(has_trailing_slash(path)){ - path.pop_back(); - } - return adafs_open(dir->path() + '/' + path, mode, flags); - } else { - // Path is absolute - assert(is_absolute_path(path)); + if(cpath == nullptr || cpath[0] == '\0') { + CTX->log()->error("{}() path is invalid", __func__); + errno = EINVAL; + return -1; + } - if (CTX->relativize_path(path)) { - return adafs_open(path, mode, flags); - } + CTX->log()->trace("{}() called with fd: {}, path: {}", __func__, cpath); + + std::string resolved; + + if(cpath[0] != PSP) { + // cpath is relative + + //TODO handle the case in which dirfd is AT_FDCWD + if(!(CTX->file_map()->exist(dirfd))) { + //TODO relative cpath could still lead to our FS + return LIBC_FUNC(openat, dirfd, cpath, flags, mode); + } + + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + + std::string path = CTX->mountdir(); + path.push_back(PSP); + path.append(dir->path()); + path.push_back(PSP); + path.append(cpath); + if(resolve_path(path, resolved)) { + return adafs_open(resolved, mode, flags); + } + } else { + // Path is absolute + if (CTX->relativize_path(cpath, resolved)) { + return adafs_open(resolved, mode, flags); } } -passthrough: - return (reinterpret_cast(libc_openat))(dirfd, cpath, flags, mode); + return LIBC_FUNC(openat, dirfd, resolved.c_str(), flags, mode); } int openat64(int dirfd, const char *path, int flags, ...) { @@ -125,45 +143,47 @@ inline FILE* fd_to_file(const int fd){ FILE* fopen(const char* path, const char* fmode) { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - int flags = 0; - std::string str_mode(fmode); - if(str_mode == "r") { - flags = O_RDONLY; - } else if(str_mode == "r+") { - flags = O_RDWR; - } else if(str_mode == "w") { - flags = (O_WRONLY | O_CREAT | O_TRUNC); - } else { - CTX->log()->error("{}() stream open flags NOT SUPPORTED: '{}'", __func__, str_mode); - errno = ENOTSUP; - return nullptr; - } - mode_t mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); - auto fd = adafs_open(rel_path, mode, flags); - if(fd == -1){ - return nullptr; - } else { - return fd_to_file(fd); - } - } + if(!CTX->initialized()) { + return LIBC_FUNC(fopen, path, fmode); + } + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(fopen, rel_path.c_str(), fmode); + } + int flags = 0; + std::string str_mode(fmode); + if(str_mode == "r") { + flags = O_RDONLY; + } else if(str_mode == "r+") { + flags = O_RDWR; + } else if(str_mode == "w") { + flags = (O_WRONLY | O_CREAT | O_TRUNC); + } else { + CTX->log()->error("{}() stream open flags NOT SUPPORTED: '{}'", __func__, str_mode); + errno = ENOTSUP; + return nullptr; + } + mode_t mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + auto fd = adafs_open(rel_path, mode, flags); + if(fd == -1){ + return nullptr; + } else { + return fd_to_file(fd); } - return (reinterpret_cast(libc_fopen))(path, fmode); } FILE* fopen64(const char* path, const char* fmode) { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return fopen(path, fmode); - } + if(!CTX->initialized()) { + return LIBC_FUNC(fopen64, path, fmode); } - return (reinterpret_cast(libc_fopen64))(path, fmode); + CTX->log()->trace("{}() called with path '{}' with mode '{}'", __func__, path, fmode); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(fopen64, rel_path.c_str(), fmode); + } + return fopen(path, fmode); } size_t intcp_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { @@ -404,93 +424,105 @@ int creat64(const char* path, mode_t mode) { int mkdir(const char* path, mode_t mode) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {}", __func__, path, mode); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_mk_node(rel_path, mode | S_IFDIR); - } + if(!CTX->initialized()) { + return LIBC_FUNC(mkdir, path, mode); + } + CTX->log()->trace("{}() called with path {} with mode {}", __func__, path, mode); + std::string rel_path; + if(!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(mkdir, rel_path.c_str(), mode); } - return (reinterpret_cast(libc_mkdir))(path, mode); + return adafs_mk_node(rel_path, mode | S_IFDIR); } int mkdirat(int dirfd, const char* path, mode_t mode) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { CTX->log()->trace("{}() called with path {} with mode {} with dirfd {}", __func__, path, mode, dirfd); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { + std::string rel_path; + if (CTX->relativize_path(path, rel_path)) { // not implemented CTX->log()->error("{}() not implemented.", __func__); errno = ENOTSUP; return -1; } } - return (reinterpret_cast(libc_mkdirat))(dirfd, path, mode); + return LIBC_FUNC(mkdirat, dirfd, path, mode); } int unlink(const char* path) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_rm_node(rel_path); - } + if(!CTX->initialized()) { + return LIBC_FUNC(unlink, path); } - return (reinterpret_cast(libc_unlink))(path); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(unlink, rel_path.c_str()); + } + return adafs_rm_node(rel_path); } int unlinkat(int dirfd, const char *cpath, int flags) { init_passthrough_if_needed(); - if(CTX->initialized()) { - std::string path(cpath); - CTX->log()->trace("{}() called with path '{}' dirfd {}, flags {}", __func__, path, dirfd, flags); + if(!CTX->initialized()) { + return LIBC_FUNC(unlinkat, dirfd, cpath, flags); + } - if(is_relative_path(path)) { - if(!(CTX->file_map()->exist(dirfd))) { - goto passthrough; - } - auto dir = CTX->file_map()->get_dir(dirfd); - if(dir == nullptr) { - CTX->log()->error("{}() dirfd is not a directory ", __func__); - errno = ENOTDIR; - return -1; - } - if(has_trailing_slash(path)){ - path.pop_back(); - } - path = dir->path() + '/' + path; - } else { - // Path is absolute - assert(is_absolute_path(path)); + if(cpath == nullptr || cpath[0] == '\0') { + CTX->log()->error("{}() path is invalid", __func__); + errno = EINVAL; + return -1; + } - if (!CTX->relativize_path(path)) { - goto passthrough; - } - } + CTX->log()->trace("{}() called with path '{}' dirfd {}, flags {}", __func__, cpath, dirfd, flags); + + std::string resolved; - if(flags & AT_REMOVEDIR) { - return adafs_rmdir(path); - } else { - return adafs_rm_node(path); + if(cpath[0] != PSP) { + if(!(CTX->file_map()->exist(dirfd))) { + //TODO relative cpath could still lead to our FS + return LIBC_FUNC(unlinkat, dirfd, cpath, flags); } + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + std::string path = CTX->mountdir(); + path.push_back(PSP); + path.append(dir->path()); + path.push_back(PSP); + path.append(cpath); + if(!resolve_path(path.c_str(), resolved)) { + return LIBC_FUNC(unlinkat, dirfd, resolved.c_str(), flags); + } + } else { + if (!CTX->relativize_path(cpath, resolved)) { + return LIBC_FUNC(unlinkat, dirfd, resolved.c_str(), flags); + } + } + + if(flags & AT_REMOVEDIR) { + return adafs_rmdir(resolved); + } else { + return adafs_rm_node(resolved); } -passthrough: - return (reinterpret_cast(libc_unlinkat))(dirfd, cpath, flags); } int rmdir(const char* path) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_rmdir(rel_path); - } + if(!CTX->initialized()) { + return LIBC_FUNC(rmdir, path); } - return (reinterpret_cast(libc_rmdir))(path); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(rmdir, rel_path.c_str()); + } + return adafs_rmdir(rel_path); } int close(int fd) { @@ -500,7 +532,7 @@ int close(int fd) { CTX->file_map()->remove(fd); return 0; } - return (reinterpret_cast(libc_close))(fd); + return LIBC_FUNC(close, fd); } int __close(int fd) { @@ -513,60 +545,74 @@ int remove(const char* path) { int access(const char* path, int mask) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called path {} mask {}", __func__, path, mask); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_access(rel_path, mask); - } + if(!CTX->initialized()) { + return LIBC_FUNC(access, path, mask); } - return (reinterpret_cast(libc_access))(path, mask); + CTX->log()->trace("{}() called path {} mask {}", __func__, path, mask); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(access, rel_path.c_str(), mask); + } + return adafs_access(rel_path, mask); } int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - std::string path(cpath); - CTX->log()->trace("{}() called path {} mode {} dirfd {} flags {}", __func__, path, mode, dirfd, flags); + if(!CTX->initialized()) { + return LIBC_FUNC(faccessat, dirfd, cpath, mode, flags); + } - if(is_relative_path(path)) { - if(!(CTX->file_map()->exist(dirfd))) { - goto passthrough; - } - auto dir = CTX->file_map()->get_dir(dirfd); - if(dir == nullptr) { - CTX->log()->error("{}() dirfd is not a directory ", __func__); - errno = ENOTDIR; - return -1; - } - if(has_trailing_slash(path)){ - path.pop_back(); - } - return adafs_access(dir->path() + '/' + path, mode); - } else { - // Path is absolute - assert(is_absolute_path(path)); + if(cpath == nullptr || cpath[0] == '\0') { + CTX->log()->error("{}() path is invalid", __func__); + errno = EINVAL; + return -1; + } - if (CTX->relativize_path(path)) { - return adafs_access(path, mode); - } + CTX->log()->trace("{}() called path {} mode {} dirfd {} flags {}", __func__, cpath, mode, dirfd, flags); + + std::string resolved; + + if(cpath[0] != PSP) { + if(!(CTX->file_map()->exist(dirfd))) { + //TODO relative cpath could still lead to our FS + return LIBC_FUNC(faccessat, dirfd, cpath, mode, flags); + } + + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + + std::string path = CTX->mountdir(); + path.push_back(PSP); + path.append(dir->path()); + path.push_back(PSP); + path.append(cpath); + if(resolve_path(path, resolved)) { + return adafs_access(resolved, mode); + } + } else { + if (CTX->relativize_path(cpath, resolved)) { + return adafs_access(resolved, mode); } } -passthrough: - return (reinterpret_cast(libc_faccessat))(dirfd, cpath, mode, flags); + return LIBC_FUNC(faccessat, dirfd, resolved.c_str(), mode, flags); } int stat(const char* path, struct stat* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat(rel_path, buf); - } + if(!CTX->initialized()) { + return LIBC_FUNC(stat, path, buf); + } + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(stat, rel_path.c_str(), buf); } - return (reinterpret_cast(libc_stat))(path, buf); + return adafs_stat(rel_path, buf); } int fstat(int fd, struct stat* buf) __THROW { @@ -578,42 +624,44 @@ int fstat(int fd, struct stat* buf) __THROW { return adafs_stat(path, buf); } } - return (reinterpret_cast(libc_fstat))(fd, buf); + return LIBC_FUNC(fstat, fd, buf); } int lstat(const char* path, struct stat* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - CTX->log()->warn("{}() No symlinks are supported. Stats will always target the given path", __func__); - return adafs_stat(rel_path, buf); - } + if(!CTX->initialized()) { + return LIBC_FUNC(lstat, path, buf); } - return (reinterpret_cast(libc_lstat))(path, buf); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(lstat, rel_path.c_str(), buf); + } + CTX->log()->warn("{}() No symlinks are supported. Stats will always target the given path", __func__); + return adafs_stat(rel_path, buf); } int __xstat(int ver, const char* path, struct stat* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat(rel_path, buf); - } + if(!CTX->initialized()) { + return LIBC_FUNC(__xstat, ver, path, buf); } - return (reinterpret_cast(libc___xstat))(ver, path, buf); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(__xstat, ver, rel_path.c_str(), buf); + } + return adafs_stat(rel_path, buf); } int __xstat64(int ver, const char* path, struct stat64* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - notsup_error_32_bit_func(); - errno = ENOTSUP; - return -1; + if(!CTX->initialized()) { + return LIBC_FUNC(__xstat64, ver, path, buf); } - return (reinterpret_cast(libc___xstat64))(ver, path, buf); + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } int __fxstat(int ver, int fd, struct stat* buf) __THROW { @@ -625,107 +673,129 @@ int __fxstat(int ver, int fd, struct stat* buf) __THROW { return adafs_stat(path, buf); } } - return (reinterpret_cast(libc___fxstat))(ver, fd, buf); + return LIBC_FUNC(__fxstat, ver, fd, buf); } int __fxstatat(int ver, int dirfd, const char * cpath, struct stat * buf, int flags) { init_passthrough_if_needed(); - if(CTX->initialized()) { - std::string path(cpath); - CTX->log()->trace("{}() called with path '{}' and fd {}", __func__, path, dirfd); - if(flags & AT_EMPTY_PATH) { - CTX->log()->error("{}() AT_EMPTY_PATH flag not supported", __func__); - errno = ENOTSUP; - return -1; - } + if(!CTX->initialized()) { + return LIBC_FUNC(__fxstatat, ver, dirfd, cpath, buf, flags); + } - if(is_relative_path(path)) { - if((dirfd == AT_FDCWD) || !CTX->file_map()->exist(dirfd)) { - goto passthrough; - } + if(cpath == nullptr || cpath[0] == '\0') { + CTX->log()->error("{}() path is invalid", __func__); + errno = EINVAL; + return -1; + } - auto dir = CTX->file_map()->get_dir(dirfd); - if(dir == nullptr) { - CTX->log()->error("{}() dirfd is not a directory ", __func__); - errno = ENOTDIR; - return -1; - } - if(has_trailing_slash(path)){ - path.pop_back(); - } - return adafs_stat(dir->path() + '/' + path, buf); + CTX->log()->trace("{}() called with path '{}' and fd {}", __func__, cpath, dirfd); + + if(flags & AT_EMPTY_PATH) { + CTX->log()->error("{}() AT_EMPTY_PATH flag not supported", __func__); + errno = ENOTSUP; + return -1; + } - } else { - // Path is absolute - assert(is_absolute_path(path)); + std::string resolved; - if (CTX->relativize_path(path)) { - return adafs_stat(path, buf); - } + if(cpath[0] != PSP) { + // cpath is relative + //TODO handle the case in which dirfd is AT_FDCWD + if(!(CTX->file_map()->exist(dirfd))) { + //TODO relative cpath could still lead to our FS + return LIBC_FUNC(__fxstatat, ver, dirfd, cpath, buf, flags); + } + + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + + std::string path = CTX->mountdir(); + path.push_back(PSP); + path.append(dir->path()); + path.push_back(PSP); + path.append(cpath); + if(resolve_path(path, resolved)) { + return adafs_stat(resolved, buf); + } + } else { + // Path is absolute + + if (CTX->relativize_path(cpath, resolved)) { + return adafs_stat(resolved, buf); } } -passthrough: - return (reinterpret_cast(libc___fxstatat))(ver, dirfd, cpath, buf, flags); + return LIBC_FUNC(__fxstatat, ver, dirfd, cpath, buf, flags); } int __fxstatat64(int ver, int dirfd, const char * path, struct stat64 * buf, int flags) { init_passthrough_if_needed(); - if(CTX->initialized()) { - notsup_error_32_bit_func(); - errno = ENOTSUP; - return -1; + if(!CTX->initialized()) { + return LIBC_FUNC(__fxstatat64, ver, dirfd, path, buf, flags); } - return (reinterpret_cast(libc___fxstatat64))(ver, dirfd, path, buf, flags); + + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } int __fxstat64(int ver, int fd, struct stat64* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - notsup_error_32_bit_func(); - errno = ENOTSUP; - return -1; + if(!CTX->initialized()) { + return LIBC_FUNC(__fxstat64, ver, fd, buf); } - return (reinterpret_cast(libc___fxstat64))(ver, fd, buf); + + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } int __lxstat(int ver, const char* path, struct stat* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_stat(rel_path, buf); - } + if(!CTX->initialized()) { + return LIBC_FUNC(__lxstat, ver, path, buf); + } + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(__lxstat, ver, path, buf); } - return (reinterpret_cast(libc___lxstat))(ver, path, buf); + return adafs_stat(rel_path, buf); } int __lxstat64(int ver, const char* path, struct stat64* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - notsup_error_32_bit_func(); - errno = ENOTSUP; - return -1; + if(!CTX->initialized()) { + return LIBC_FUNC(__lxstat64, ver, path, buf); } - return (reinterpret_cast(libc___lxstat64))(ver, path, buf); + + notsup_error_32_bit_func(); + errno = ENOTSUP; + return -1; } int statfs(const char* path, struct statfs* buf) __THROW { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - // get information of the underlying fs. - // Note, we explicitely call the real glibc statfs function to not intercept it again on the mountdir path - struct statfs realfs{}; - auto ret = (reinterpret_cast(libc_statfs))(CTX->mountdir().c_str(), &realfs); - if (ret != 0) - return ret; - return adafs_statfs(rel_path, buf, realfs); - } + if(!CTX->initialized()) { + return LIBC_FUNC(statfs, path, buf); } - return (reinterpret_cast(libc_statfs))(path, buf); + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(statfs, path, buf); + } + + // get information of the underlying fs. + // Note, we explicitely call the real glibc statfs function to not intercept it again on the mountdir path + struct statfs realfs{}; + auto ret = LIBC_FUNC(statfs, CTX->mountdir().c_str(), &realfs); + if (ret != 0) { + return ret; + } + return adafs_statfs(rel_path, buf, realfs); } int fstatfs(int fd, struct statfs* buf) { @@ -932,15 +1002,16 @@ int fdatasync(int fd) { int truncate(const char* path, off_t length) { init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path: {}, offset: {}", __func__, - path, length); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - return adafs_truncate(rel_path, length); - } + if(!CTX->initialized()) { + return LIBC_FUNC(truncate, path, length); + } + CTX->log()->trace("{}() called with path: {}, offset: {}", __func__, + path, length); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(truncate, rel_path.c_str(), length); } - return (reinterpret_cast(libc_truncate))(path, length); + return adafs_truncate(rel_path, length); } int ftruncate(int fd, off_t length) { @@ -1058,18 +1129,20 @@ inline DIR* fd_to_dirp(const int fd){ DIR* opendir(const char* path){ init_passthrough_if_needed(); - if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - auto fd = adafs_opendir(rel_path); - if(fd < 0){ - return nullptr; - } - return fd_to_dirp(fd); - } + if(!CTX->initialized()) { + return LIBC_FUNC(opendir, path); + } + CTX->log()->trace("{}() called with path {}", __func__, path); + std::string rel_path; + if(!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(opendir, rel_path.c_str()); + } + + auto fd = adafs_opendir(rel_path); + if(fd < 0){ + return nullptr; } - return (reinterpret_cast(libc_opendir))(path); + return fd_to_dirp(fd); } DIR* fdopendir(int fd){ @@ -1087,7 +1160,7 @@ DIR* fdopendir(int fd){ return fd_to_dirp(fd); } } - return (reinterpret_cast(libc_fdopendir))(fd); + return LIBC_FUNC(fdopendir, fd); } struct dirent* intcp_readdir(DIR* dirp){ @@ -1102,7 +1175,7 @@ struct dirent* intcp_readdir(DIR* dirp){ return adafs_readdir(fd); } } - return (reinterpret_cast(libc_readdir))(dirp); + return LIBC_FUNC(readdir, dirp); } int intcp_closedir(DIR* dirp) { @@ -1119,7 +1192,7 @@ int intcp_closedir(DIR* dirp) { return 0; } } - return (reinterpret_cast(libc_closedir))(dirp); + return LIBC_FUNC(closedir, dirp); } int chdir(const char* path){ @@ -1213,23 +1286,31 @@ char *get_current_dir_name(void) { char *realpath(const char *path, char *resolved_path) { init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(realpath, path, resolved_path); + } + CTX->log()->trace("{}() called with path {}", __func__, path); - std::string rel_path(path); - if (CTX->relativize_path(rel_path)) { - if(resolved_path != nullptr) { - CTX->log()->error("{}() use of user level buffer not supported", __func__); - errno = ENOTSUP; - return nullptr; - } - auto absolute_path = CTX->mountdir() + rel_path; - auto ret_ptr = static_cast(malloc(absolute_path.size() + 1)); - if(ret_ptr == nullptr){ + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(realpath, rel_path.c_str(), resolved_path); + } + + auto absolute_path = CTX->mountdir() + rel_path; + if(absolute_path.size() >= PATH_MAX) { + errno = ENAMETOOLONG; + return nullptr; + } + + if(resolved_path == nullptr) { + resolved_path = static_cast(malloc(absolute_path.size() + 1)); + if(resolved_path == nullptr){ CTX->log()->error("{}() failed to allocate buffer for called with path {}", __func__, path); errno = ENOMEM; return nullptr; } - strcpy(ret_ptr, absolute_path.c_str()); - return ret_ptr; } - return (reinterpret_cast(libc_realpath))(path, resolved_path); + + strcpy(resolved_path, absolute_path.c_str()); + return resolved_path; } -- GitLab From 611a73844b27b6c263ea4f824da4a3a8b108dcb2 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 4 Sep 2018 15:08:24 +0200 Subject: [PATCH 071/105] Add -Wextra flags in debug mode compilation --- ifs/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 778916d4e..2c39acbac 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -19,7 +19,7 @@ message("* Current build type is : ${CMAKE_BUILD_TYPE}") # Compiler flags for various cmake build types set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall --pedantic -g -O0") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra --pedantic -g -O0") set(CMAKE_CXX_FLAGS_MEMCHECK "-Wall --pedantic -g -O0 -fsanitize=address -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS_MAINTAINER "-Wall --pedantic -g -O0 -pg -no-pie") mark_as_advanced(CMAKE_CXX_FLAGS_MAINTAINER) -- GitLab From 308a22e52ea248265abeb2d77f63c6e69afb4828 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 4 Sep 2018 15:03:41 +0200 Subject: [PATCH 072/105] Fix log apices --- ifs/src/preload/intcp_functions.cpp | 48 +++++++++++++++-------------- ifs/src/preload/preload.cpp | 2 +- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index dcd1870a4..fe108adae 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -35,7 +35,7 @@ int open(const char* path, int flags, ...) { return LIBC_FUNC(open, path, flags, mode); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(open, rel_path.c_str(), flags, mode); @@ -407,7 +407,7 @@ int fputc(int c, FILE *stream) { int creat(const char* path, mode_t mode) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {}", __func__, path, mode); + CTX->log()->trace("{}() called with path '{}', mode '{}'", __func__, path, mode); } return open(path, O_CREAT | O_WRONLY | O_TRUNC, mode); } @@ -417,7 +417,7 @@ int creat(const char* path, mode_t mode) { int creat64(const char* path, mode_t mode) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {}", __func__, path, mode); + CTX->log()->trace("{}() called with path '{}', mode '{}'", __func__, path, mode); } return open(path, O_CREAT | O_WRONLY | O_TRUNC | O_LARGEFILE, mode); } @@ -427,18 +427,20 @@ int mkdir(const char* path, mode_t mode) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(mkdir, path, mode); } - CTX->log()->trace("{}() called with path {} with mode {}", __func__, path, mode); + CTX->log()->trace("{}() called with path '{}', mode '{}'", __func__, path, mode); std::string rel_path; if(!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(mkdir, rel_path.c_str(), mode); } - return adafs_mk_node(rel_path, mode | S_IFDIR); + auto ret = adafs_mk_node(rel_path, mode | S_IFDIR); + CTX->log()->trace("{}() ret {}", __func__, ret); + return ret; } int mkdirat(int dirfd, const char* path, mode_t mode) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {} with dirfd {}", __func__, path, mode, dirfd); + CTX->log()->trace("{}() called with path '{}', mode {}, dirfd {}", __func__, path, mode, dirfd); std::string rel_path; if (CTX->relativize_path(path, rel_path)) { // not implemented @@ -456,7 +458,7 @@ int unlink(const char* path) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(unlink, path); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(unlink, rel_path.c_str()); @@ -517,7 +519,7 @@ int rmdir(const char* path) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(rmdir, path); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(rmdir, rel_path.c_str()); @@ -548,7 +550,7 @@ int access(const char* path, int mask) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(access, path, mask); } - CTX->log()->trace("{}() called path {} mask {}", __func__, path, mask); + CTX->log()->trace("{}() called path '{}', mask {}", __func__, path, mask); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(access, rel_path.c_str(), mask); @@ -568,7 +570,7 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { return -1; } - CTX->log()->trace("{}() called path {} mode {} dirfd {} flags {}", __func__, cpath, mode, dirfd, flags); + CTX->log()->trace("{}() called path '{}', mode {}, dirfd {}, flags {}", __func__, cpath, mode, dirfd, flags); std::string resolved; @@ -607,7 +609,7 @@ int stat(const char* path, struct stat* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(stat, path, buf); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(stat, rel_path.c_str(), buf); @@ -632,7 +634,7 @@ int lstat(const char* path, struct stat* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(lstat, path, buf); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(lstat, rel_path.c_str(), buf); @@ -646,7 +648,7 @@ int __xstat(int ver, const char* path, struct stat* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(__xstat, ver, path, buf); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(__xstat, ver, rel_path.c_str(), buf); @@ -758,7 +760,7 @@ int __lxstat(int ver, const char* path, struct stat* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(__lxstat, ver, path, buf); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(__lxstat, ver, path, buf); @@ -782,7 +784,7 @@ int statfs(const char* path, struct statfs* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(statfs, path, buf); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(statfs, path, buf); @@ -952,7 +954,7 @@ ssize_t pread64(int fd, void* buf, size_t count, __off64_t offset) { off_t lseek(int fd, off_t offset, int whence) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {}", __func__, fd, offset, whence); + CTX->log()->trace("{}() called with path '{}', mode {}", __func__, fd, offset, whence); if (CTX->file_map()->exist(fd)) { auto off_ret = adafs_lseek(fd, static_cast(offset), whence); if (off_ret > std::numeric_limits::max()) { @@ -970,7 +972,7 @@ off_t lseek(int fd, off_t offset, int whence) __THROW { off64_t lseek64(int fd, off64_t offset, int whence) __THROW { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with path {} with mode {}", __func__, fd, offset, whence); + CTX->log()->trace("{}() called with path '{}', mode {}", __func__, fd, offset, whence); if (CTX->file_map()->exist(fd)) { return adafs_lseek(fd, offset, whence); } @@ -981,7 +983,7 @@ off64_t lseek64(int fd, off64_t offset, int whence) __THROW { int fsync(int fd) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with fd {} path {}", __func__, fd, CTX->file_map()->get(fd)->path()); + CTX->log()->trace("{}() called with fd {}, path '{}'", __func__, fd, CTX->file_map()->get(fd)->path()); if (CTX->file_map()->exist(fd)) { return 0; // This is a noop for us atm. fsync is called implicitly because each chunk is closed after access } @@ -992,7 +994,7 @@ int fsync(int fd) { int fdatasync(int fd) { init_passthrough_if_needed(); if(CTX->initialized()) { - CTX->log()->trace("{}() called with fd {} path {}", __func__, fd, CTX->file_map()->get(fd)->path()); + CTX->log()->trace("{}() called with fd {}, path '{}'", __func__, fd, CTX->file_map()->get(fd)->path()); if (CTX->file_map()->exist(fd)) { return 0; // This is a noop for us atm. fsync is called implicitly because each chunk is closed after access } @@ -1132,7 +1134,7 @@ DIR* opendir(const char* path){ if(!CTX->initialized()) { return LIBC_FUNC(opendir, path); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if(!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(opendir, rel_path.c_str()); @@ -1201,7 +1203,7 @@ int chdir(const char* path){ return LIBC_FUNC(chdir, path); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; const char* new_path; if (CTX->relativize_path(path, rel_path)) { @@ -1290,7 +1292,7 @@ char *realpath(const char *path, char *resolved_path) { return LIBC_FUNC(realpath, path, resolved_path); } - CTX->log()->trace("{}() called with path {}", __func__, path); + CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path)) { return LIBC_FUNC(realpath, rel_path.c_str(), resolved_path); @@ -1305,7 +1307,7 @@ char *realpath(const char *path, char *resolved_path) { if(resolved_path == nullptr) { resolved_path = static_cast(malloc(absolute_path.size() + 1)); if(resolved_path == nullptr){ - CTX->log()->error("{}() failed to allocate buffer for called with path {}", __func__, path); + CTX->log()->error("{}() failed to allocate buffer for called with path '{}'", __func__, path); errno = ENOMEM; return nullptr; } diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index 8025bd17b..918ab68a5 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -314,7 +314,7 @@ void init_preload() { CTX->log()->error("{}() Daemon not running or mountdir not set", __func__); exit(EXIT_FAILURE); } else { - CTX->log()->info("{}() mountdir \"{}\" loaded", __func__, CTX->mountdir()); + CTX->log()->info("{}() mountdir '{}' loaded", __func__, CTX->mountdir()); } CTX->initialized(true); CTX->log()->debug("{}() exit", __func__); -- GitLab From a166d03e86fd0371d1f4252ab0beebb5de7a2b13 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 4 Sep 2018 15:14:38 +0200 Subject: [PATCH 073/105] openat support AT_FDCWD flag --- ifs/src/preload/intcp_functions.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index fe108adae..63f90ad23 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -82,10 +82,13 @@ int openat(int dirfd, const char *cpath, int flags, ...) { std::string resolved; - if(cpath[0] != PSP) { + if((cpath[0] == PSP) || (dirfd == AT_FDCWD)) { + // cpath is absolute or relative to CWD + if (CTX->relativize_path(cpath, resolved)) { + return adafs_open(resolved, mode, flags); + } + } else { // cpath is relative - - //TODO handle the case in which dirfd is AT_FDCWD if(!(CTX->file_map()->exist(dirfd))) { //TODO relative cpath could still lead to our FS return LIBC_FUNC(openat, dirfd, cpath, flags, mode); @@ -106,11 +109,6 @@ int openat(int dirfd, const char *cpath, int flags, ...) { if(resolve_path(path, resolved)) { return adafs_open(resolved, mode, flags); } - } else { - // Path is absolute - if (CTX->relativize_path(cpath, resolved)) { - return adafs_open(resolved, mode, flags); - } } return LIBC_FUNC(openat, dirfd, resolved.c_str(), flags, mode); } -- GitLab From a2d5e9f43a3c02a8ef3b0b1ce757c964ecfff3fd Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 4 Sep 2018 15:15:01 +0200 Subject: [PATCH 074/105] More verbose error info on opening pid file --- ifs/src/preload/preload_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/src/preload/preload_util.cpp b/ifs/src/preload/preload_util.cpp index 0c0b0cb20..810cb6e1a 100644 --- a/ifs/src/preload/preload_util.cpp +++ b/ifs/src/preload/preload_util.cpp @@ -148,8 +148,8 @@ int get_daemon_pid() { cerr << "No permission to open pid file at " << daemon_pid_path() << " or ADA-FS daemon pid file not found. Daemon not running?" << endl; CTX->log()->error( - "{}() No permission to open pid file at {} or ADA-FS daemon pid file not found. Daemon not running?", - __func__, daemon_pid_path()); + "{}() Failed to open pid file '{}'. Error: {}", + __func__, daemon_pid_path(), std::strerror(errno)); } ifs.close(); -- GitLab From f4482c58fb737cb2faf89aa214f21e6582f09d22 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 11:39:21 +0200 Subject: [PATCH 075/105] fix openat log parameters --- ifs/src/preload/intcp_functions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 63f90ad23..374d5ce1b 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -78,7 +78,7 @@ int openat(int dirfd, const char *cpath, int flags, ...) { return -1; } - CTX->log()->trace("{}() called with fd: {}, path: {}", __func__, cpath); + CTX->log()->trace("{}() called with fd: {}, path: {}, flags: {}, mode: {}", __func__, dirfd, cpath, flags, mode); std::string resolved; -- GitLab From b7322b4e951603154b0f6a8d26904276693a15ec Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 11:42:02 +0200 Subject: [PATCH 076/105] fix additional slash on constructed path --- ifs/src/preload/intcp_functions.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 374d5ce1b..b047aa632 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -102,7 +102,6 @@ int openat(int dirfd, const char *cpath, int flags, ...) { } std::string path = CTX->mountdir(); - path.push_back(PSP); path.append(dir->path()); path.push_back(PSP); path.append(cpath); @@ -492,7 +491,6 @@ int unlinkat(int dirfd, const char *cpath, int flags) { return -1; } std::string path = CTX->mountdir(); - path.push_back(PSP); path.append(dir->path()); path.push_back(PSP); path.append(cpath); @@ -586,7 +584,6 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { } std::string path = CTX->mountdir(); - path.push_back(PSP); path.append(dir->path()); path.push_back(PSP); path.append(cpath); @@ -714,7 +711,6 @@ int __fxstatat(int ver, int dirfd, const char * cpath, struct stat * buf, int fl } std::string path = CTX->mountdir(); - path.push_back(PSP); path.append(dir->path()); path.push_back(PSP); path.append(cpath); -- GitLab From 5b3b7cf99d92ed7dca281565cebcc38aa2a47956 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 11:42:22 +0200 Subject: [PATCH 077/105] fix log parameters in resolve function+ --- ifs/src/preload/resolve.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/src/preload/resolve.cpp b/ifs/src/preload/resolve.cpp index 5d731de48..3cc5d3f1a 100644 --- a/ifs/src/preload/resolve.cpp +++ b/ifs/src/preload/resolve.cpp @@ -10,7 +10,7 @@ bool resolve_path (const std::string& path, std::string& resolved) { - CTX->log()->debug("{}() path: '{}'", __func__, path, resolved); + CTX->log()->debug("{}() path: '{}'", __func__, path); struct stat st; const std::vector& mnt_components = CTX->mountdir_components(); -- GitLab From 83cc0e40d786f97dc1ba3da86ea68d86ba39dd8c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 13:03:36 +0200 Subject: [PATCH 078/105] Intercept dirfd --- ifs/include/preload/passthrough.hpp | 1 + ifs/src/preload/intcp_functions.cpp | 15 +++++++++++++++ ifs/src/preload/passthrough.cpp | 2 ++ 3 files changed, 18 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 5321f6ad5..7bb3f89ce 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -86,6 +86,7 @@ extern void* libc_dup; extern void* libc_dup2; extern void* libc_dup3; +extern void* libc_dirfd; extern void* libc_opendir; extern void* libc_fdopendir; extern void* libc_readdir; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index b047aa632..9b998d51f 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1123,6 +1123,21 @@ inline DIR* fd_to_dirp(const int fd){ return reinterpret_cast(fd); } +int dirfd(DIR *dirp) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + if(dirp == nullptr){ + errno = EINVAL; + return -1; + } + auto fd = dirp_to_fd(dirp); + if(CTX->file_map()->exist(fd)) { + return fd; + } + } + return LIBC_FUNC(dirfd, dirp); +} + DIR* opendir(const char* path){ init_passthrough_if_needed(); if(!CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 8143e1dcd..1738d8a9c 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -87,6 +87,7 @@ void* libc_dup; void* libc_dup2; void* libc_dup3; +void* libc_dirfd; void* libc_opendir; void* libc_fdopendir; void* libc_readdir; @@ -183,6 +184,7 @@ void init_passthrough_() { libc_dup2 = dlsym(libc, "dup2"); libc_dup3 = dlsym(libc, "dup3"); + libc_dirfd = dlsym(libc, "dirfd"); libc_opendir = dlsym(libc, "opendir"); libc_fdopendir = dlsym(libc, "fdopendir"); libc_readdir = dlsym(libc, "readdir"); -- GitLab From 71606ea7400f0073f06a2db9be055779ef219fdc Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 13:15:11 +0200 Subject: [PATCH 079/105] Intercept __open{at,}{64,}_2 `tar` and other gnu coreutils are using __openat{64,}_2 variant. We need to intercept also that apart of the main `openat` function. StackOverflow related question [1] [1]: https://stackoverflow.com/questions/9161116/intercepting-the-openat-system-call-for-gnu-tar --- ifs/include/preload/intcp_functions.hpp | 13 +++++++++++++ ifs/src/preload/intcp_functions.cpp | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/ifs/include/preload/intcp_functions.hpp b/ifs/include/preload/intcp_functions.hpp index b241d5f2f..83364c68d 100644 --- a/ifs/include/preload/intcp_functions.hpp +++ b/ifs/include/preload/intcp_functions.hpp @@ -32,6 +32,19 @@ size_t intcp_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); strong_alias(intcp_fwrite, fwrite) strong_alias(intcp_fwrite, fwrite_unlocked) +int intcp_open(const char* path, int flags, ...); +strong_alias(intcp_open, open) +strong_alias(intcp_open, __open_2) +int intcp_open64(const char* path, int flags, ...); +strong_alias(intcp_open64, open64) +strong_alias(intcp_open64, __open64_2) +int intcp_openat(int dirfd, const char *cpath, int flags, ...); +strong_alias(intcp_openat, openat) +strong_alias(intcp_openat, __openat_2) +int intcp_openat64(int dirfd, const char *path, int flags, ...); +strong_alias(intcp_openat64, openat64) +strong_alias(intcp_openat64, __openat64_2) + #endif // IFS_INTCP_FUNCTIONS_HPP } // extern C diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 9b998d51f..cf8840501 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -21,7 +21,7 @@ void inline notsup_error_32_bit_func(const char* func = __builtin_FUNCTION()) { CTX->log()->error("{}() is NOT SUPPORTED. According to glibc, this function should be called only on 32-bit machine", func); } -int open(const char* path, int flags, ...) { +int intcp_open(const char* path, int flags, ...) { init_passthrough_if_needed(); mode_t mode = 0; @@ -45,7 +45,7 @@ int open(const char* path, int flags, ...) { #undef open64 -int open64(const char* path, int flags, ...) { +int intcp_open64(const char* path, int flags, ...) { init_passthrough_if_needed(); mode_t mode = 0; if (flags & O_CREAT) { @@ -54,10 +54,10 @@ int open64(const char* path, int flags, ...) { mode = va_arg(ap, mode_t); va_end(ap); } - return open(path, flags | O_LARGEFILE, mode); + return intcp_open(path, flags | O_LARGEFILE, mode); } -int openat(int dirfd, const char *cpath, int flags, ...) { +int intcp_openat(int dirfd, const char *cpath, int flags, ...) { init_passthrough_if_needed(); mode_t mode = 0; @@ -112,7 +112,7 @@ int openat(int dirfd, const char *cpath, int flags, ...) { return LIBC_FUNC(openat, dirfd, resolved.c_str(), flags, mode); } -int openat64(int dirfd, const char *path, int flags, ...) { +int intcp_openat64(int dirfd, const char *path, int flags, ...) { init_passthrough_if_needed(); mode_t mode = 0; @@ -123,7 +123,7 @@ int openat64(int dirfd, const char *path, int flags, ...) { va_end(vl); } - return openat(dirfd, path, flags | O_LARGEFILE, mode); + return intcp_openat(dirfd, path, flags | O_LARGEFILE, mode); } /****** FILE OPS ******/ -- GitLab From c00ccda9dddfa65bf136f37a3ad6017346f839bb Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 2 Aug 2018 14:16:49 +0200 Subject: [PATCH 080/105] Fix {f,}chdir Always store complete path as CWD instead of internal and relative ones --- ifs/src/preload/intcp_functions.cpp | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index cf8840501..4e39bedff 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1214,7 +1214,7 @@ int chdir(const char* path){ CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; - const char* new_path; + const char* fake_path; if (CTX->relativize_path(path, rel_path)) { //path falls in our namespace struct stat st; @@ -1228,12 +1228,19 @@ int chdir(const char* path){ errno = ENOTDIR; return -1; } - new_path = CTX->mountdir().c_str(); + fake_path = CTX->mountdir().c_str(); + //TODO get complete path from relativize_path instead of + // removing mountdir and then adding again here + rel_path.insert(0, CTX->mountdir()); + if (has_trailing_slash(rel_path)) { + // open_dir is '/' + rel_path.pop_back(); + } } else { - new_path = rel_path.c_str(); + fake_path = rel_path.c_str(); } - if(LIBC_FUNC(chdir, new_path)) { + if(LIBC_FUNC(chdir, fake_path)) { CTX->log()->error("{}() failed to change dir: {}", __func__, std::strerror(errno)); return -1; @@ -1259,7 +1266,19 @@ int fchdir(int fd) { errno = EBADF; return -1; } - CTX->cwd(open_dir->path()); + + if (LIBC_FUNC(chdir, CTX->mountdir().c_str())) { + CTX->log()->error("{}() failed to change dir: {}", + __func__, std::strerror(errno)); + return -1; + } + + std::string new_path = CTX->mountdir() + open_dir->path(); + if (has_trailing_slash(new_path)) { + // open_dir is '/' + new_path.pop_back(); + } + CTX->cwd(new_path); } else { if(LIBC_FUNC(fchdir, fd) != 0) { CTX->log()->error("{}() failed to change dir: {}", -- GitLab From 7fe603ce6710b5146fd9e12546e22cf56aec5e3e Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Fri, 3 Aug 2018 11:11:31 +0200 Subject: [PATCH 081/105] add -Wextra flags on compilation --- ifs/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 2c39acbac..59b02c39b 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -18,10 +18,11 @@ ENDIF (NOT CMAKE_BUILD_TYPE) message("* Current build type is : ${CMAKE_BUILD_TYPE}") # Compiler flags for various cmake build types +set(WARNINGS_FLAGS "-Wall -Wextra --pedantic -Wno-unused-parameter") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra --pedantic -g -O0") -set(CMAKE_CXX_FLAGS_MEMCHECK "-Wall --pedantic -g -O0 -fsanitize=address -fno-omit-frame-pointer") -set(CMAKE_CXX_FLAGS_MAINTAINER "-Wall --pedantic -g -O0 -pg -no-pie") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0") +set(CMAKE_CXX_FLAGS_MEMCHECK "${WARNINGS_FLAGS} -g -O0 -fsanitize=address -fno-omit-frame-pointer") +set(CMAKE_CXX_FLAGS_MAINTAINER "${WARNINGS_FLAGS} -g -O0 -pg -no-pie") mark_as_advanced(CMAKE_CXX_FLAGS_MAINTAINER) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake" ${CMAKE_MODULE_PATH}) -- GitLab From 765ac4a72fc1f38bcd1a6db2d2f720e3c4bfea1c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Fri, 3 Aug 2018 11:12:01 +0200 Subject: [PATCH 082/105] Avoid const on return by value --- ifs/include/daemon/backend/metadata/merge.hpp | 8 ++++---- ifs/include/preload/open_file_map.hpp | 2 +- ifs/src/daemon/backend/metadata/merge.cpp | 6 +++--- ifs/src/preload/open_file_map.cpp | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ifs/include/daemon/backend/metadata/merge.hpp b/ifs/include/daemon/backend/metadata/merge.hpp index 164caeb95..331ff9774 100644 --- a/ifs/include/daemon/backend/metadata/merge.hpp +++ b/ifs/include/daemon/backend/metadata/merge.hpp @@ -23,7 +23,7 @@ class MergeOperand { protected: std::string serialize_id() const; virtual std::string serialize_params() const = 0; - virtual const OperandID id() const = 0; + virtual OperandID id() const = 0; }; class IncreaseSizeOperand: public MergeOperand { @@ -38,7 +38,7 @@ class IncreaseSizeOperand: public MergeOperand { IncreaseSizeOperand(const size_t size, const bool append); IncreaseSizeOperand(const rdb::Slice& serialized_op); - const OperandID id() const override; + OperandID id() const override; std::string serialize_params() const override; }; @@ -49,7 +49,7 @@ class DecreaseSizeOperand: public MergeOperand { DecreaseSizeOperand(const size_t size); DecreaseSizeOperand(const rdb::Slice& serialized_op); - const OperandID id() const override; + OperandID id() const override; std::string serialize_params() const override; }; @@ -58,7 +58,7 @@ class CreateOperand: public MergeOperand { std::string metadata; CreateOperand(const std::string& metadata); - const OperandID id() const override; + OperandID id() const override; std::string serialize_params() const override; }; diff --git a/ifs/include/preload/open_file_map.hpp b/ifs/include/preload/open_file_map.hpp index 7490845af..1c68b7354 100644 --- a/ifs/include/preload/open_file_map.hpp +++ b/ifs/include/preload/open_file_map.hpp @@ -51,7 +51,7 @@ public: void pos(off64_t pos_); - const bool get_flag(OpenFile_flags flag); + bool get_flag(OpenFile_flags flag); void set_flag(OpenFile_flags flag, bool value); diff --git a/ifs/src/daemon/backend/metadata/merge.cpp b/ifs/src/daemon/backend/metadata/merge.cpp index ddff02aff..cbb9c98a5 100644 --- a/ifs/src/daemon/backend/metadata/merge.cpp +++ b/ifs/src/daemon/backend/metadata/merge.cpp @@ -44,7 +44,7 @@ IncreaseSizeOperand::IncreaseSizeOperand(const rdb::Slice& serialized_op){ assert(chrs_parsed + 1 == serialized_op.size()); } -const OperandID IncreaseSizeOperand::id() const { +OperandID IncreaseSizeOperand::id() const { return OperandID::increase_size; } @@ -71,7 +71,7 @@ DecreaseSizeOperand::DecreaseSizeOperand(const rdb::Slice& serialized_op){ assert(read == serialized_op.size()); } -const OperandID DecreaseSizeOperand::id() const { +OperandID DecreaseSizeOperand::id() const { return OperandID::decrease_size; } @@ -82,7 +82,7 @@ std::string DecreaseSizeOperand::serialize_params() const { CreateOperand::CreateOperand(const std::string& metadata): metadata(metadata) {} -const OperandID CreateOperand::id() const{ +OperandID CreateOperand::id() const{ return OperandID::create; } diff --git a/ifs/src/preload/open_file_map.cpp b/ifs/src/preload/open_file_map.cpp index 3e396f278..3cf4cf58b 100644 --- a/ifs/src/preload/open_file_map.cpp +++ b/ifs/src/preload/open_file_map.cpp @@ -52,7 +52,7 @@ void OpenFile::pos(off64_t pos_) { OpenFile::pos_ = pos_; } -const bool OpenFile::get_flag(OpenFile_flags flag) { +bool OpenFile::get_flag(OpenFile_flags flag) { lock_guard lock(pos_mutex_); return flags_[to_underlying(flag)]; } -- GitLab From b2785bfe64ad50c81864b87e9d0c04d59dc9b6d6 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Fri, 3 Aug 2018 11:15:29 +0200 Subject: [PATCH 083/105] avoid useless assert (unsigned >= 0) --- ifs/src/daemon/adafs_daemon.cpp | 2 +- ifs/src/daemon/handler/h_data.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index e322b8d9a..040b20848 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -130,8 +130,8 @@ void destroy_enviroment() { } bool init_io_tasklet_pool() { + assert(DAEMON_IO_XSTREAMS >= 0); unsigned int xstreams_num = DAEMON_IO_XSTREAMS; - assert(xstreams_num >= 0); //retrieve the pool of the just created scheduler ABT_pool pool; diff --git a/ifs/src/daemon/handler/h_data.cpp b/ifs/src/daemon/handler/h_data.cpp index da661e9b8..e48f78c68 100644 --- a/ifs/src/daemon/handler/h_data.cpp +++ b/ifs/src/daemon/handler/h_data.cpp @@ -436,7 +436,6 @@ static hg_return_t rpc_srv_read_data(hg_handle_t handle) { ABT_eventual_wait(task_eventuals[chnk_id_curr], (void**) &task_read_size); assert(task_read_size != nullptr); - assert(*task_read_size >= 0); if(*task_read_size == 0){ ADAFS_DATA->spdlogger()->warn("{}() Read task for chunk {} returned 0 bytes", __func__, chnk_id_curr); -- GitLab From 4ebf4d7702880a253c05e233097bbbbdd1e10cab Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 20 Aug 2018 15:53:25 +0200 Subject: [PATCH 084/105] intercept chmod et al --- ifs/include/preload/passthrough.hpp | 4 ++ ifs/src/preload/intcp_functions.cpp | 77 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 8 +++ 3 files changed, 89 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 7bb3f89ce..aae5bf611 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -92,6 +92,10 @@ extern void* libc_fdopendir; extern void* libc_readdir; extern void* libc_closedir; +extern void* libc_chmod; +extern void* libc_fchmod; +extern void* libc_fchmodat; + extern void* libc_chdir; extern void* libc_fchdir; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 4e39bedff..57753b1ad 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1206,6 +1206,83 @@ int intcp_closedir(DIR* dirp) { return LIBC_FUNC(closedir, dirp); } +int chmod(const char *path, mode_t mode) { + init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(chmod, path, mode); + } + + CTX->log()->trace("{}() called with path '{}'", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(chmod, rel_path.c_str(), mode); + } + CTX->log()->warn("{}() operation not supported", __func__); + errno = ENOTSUP; + return -1; +} + +int fchmod(int fd, mode_t mode) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [fd: {}, mode: {}]", __func__, fd, mode); + if (CTX->file_map()->exist(fd)) { + CTX->log()->warn("{}() operation not supported", __func__); + errno = ENOTSUP; + return -1; + } + } + return LIBC_FUNC(fchmod, fd, mode); +} + +int fchmodat(int dirfd, const char *cpath, mode_t mode, int flags) { + init_passthrough_if_needed(); + if(!CTX->initialized()) { + return LIBC_FUNC(fchmodat, dirfd, cpath, mode, flags); + } + + if(cpath == nullptr || cpath[0] == '\0') { + CTX->log()->error("{}() path is invalid", __func__); + errno = EINVAL; + return -1; + } + + CTX->log()->trace("{}() called path '{}', mode {}, dirfd {}, flags {}", __func__, cpath, mode, dirfd, flags); + + std::string resolved; + + if(cpath[0] != PSP) { + if(!(CTX->file_map()->exist(dirfd))) { + //TODO relative cpath could still lead to our FS + return LIBC_FUNC(fchmodat, dirfd, cpath, mode, flags); + } + + auto dir = CTX->file_map()->get_dir(dirfd); + if(dir == nullptr) { + CTX->log()->error("{}() dirfd is not a directory ", __func__); + errno = ENOTDIR; + return -1; + } + + std::string path = CTX->mountdir(); + path.append(dir->path()); + path.push_back(PSP); + path.append(cpath); + if(resolve_path(path, resolved)) { + CTX->log()->warn("{}() operation not supported", __func__); + errno = ENOTSUP; + return -1; + } + } else { + if (CTX->relativize_path(cpath, resolved)) { + CTX->log()->warn("{}() operation not supported", __func__); + errno = ENOTSUP; + return -1; + } + } + return LIBC_FUNC(fchmodat, dirfd, resolved.c_str(), mode, flags); +} + int chdir(const char* path){ init_passthrough_if_needed(); if(!CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 1738d8a9c..e5ef4fbef 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -93,6 +93,10 @@ void* libc_fdopendir; void* libc_readdir; void* libc_closedir; +void* libc_chmod; +void* libc_fchmod; +void* libc_fchmodat; + void* libc_chdir; void* libc_fchdir; @@ -190,6 +194,10 @@ void init_passthrough_() { libc_readdir = dlsym(libc, "readdir"); libc_closedir = dlsym(libc, "closedir"); + libc_chmod = dlsym(libc, "chmod"); + libc_fchmod = dlsym(libc, "fchmod"); + libc_fchmodat = dlsym(libc, "fchmodat"); + libc_chdir = dlsym(libc, "chdir"); libc_fchdir = dlsym(libc, "fchdir"); -- GitLab From 9e5325a53fa216589ed234072baddb66fb4ab85e Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 20 Aug 2018 15:54:26 +0200 Subject: [PATCH 085/105] support getcwd with 0 size when size is 0 the buffer to host the path of CWD must be allocated by us --- ifs/src/preload/intcp_functions.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 57753b1ad..bb79f3282 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1372,11 +1372,20 @@ char *getcwd(char *buf, size_t size) { return LIBC_FUNC(getcwd, buf, size); } CTX->log()->trace("{}() called with size {}", __func__, size); - if(CTX->cwd().size() + 1 > size) { + + if(size == 0) { + buf = static_cast(malloc(CTX->cwd().size() + 1)); + if(buf == nullptr){ + CTX->log()->error("{}() failed to allocate buffer of size {}", __func__, CTX->cwd().size()); + errno = ENOMEM; + return nullptr; + } + } else if(CTX->cwd().size() + 1 > size) { CTX->log()->error("{}() buffer too small to host current working dir", __func__); errno = ERANGE; return nullptr; } + strcpy(buf, CTX->cwd().c_str()); return buf; } -- GitLab From 83fbdf9a693895888a38a0e0d4a9738264d71ed7 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 20 Aug 2018 15:55:19 +0200 Subject: [PATCH 086/105] enable __xstat64 for external paths --- ifs/src/preload/intcp_functions.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index bb79f3282..3c8a2d5b7 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -656,6 +656,11 @@ int __xstat64(int ver, const char* path, struct stat64* buf) __THROW { if(!CTX->initialized()) { return LIBC_FUNC(__xstat64, ver, path, buf); } + CTX->log()->trace("{}() called with path '{}'", __func__, path); + std::string rel_path; + if (!CTX->relativize_path(path, rel_path)) { + return LIBC_FUNC(__xstat64, ver, rel_path.c_str(), buf); + } notsup_error_32_bit_func(); errno = ENOTSUP; return -1; -- GitLab From ffc27e889fe27e04136185ef3a22dc53f0cd46d2 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 20 Aug 2018 15:56:05 +0200 Subject: [PATCH 087/105] faccessat support AT_FDCWD flag --- ifs/src/preload/intcp_functions.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 3c8a2d5b7..c2262b72f 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -570,7 +570,13 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { std::string resolved; - if(cpath[0] != PSP) { + if((cpath[0] == PSP) || (dirfd == AT_FDCWD)) { + // cpath is absolute or relative to CWD + if (CTX->relativize_path(cpath, resolved)) { + return adafs_access(resolved, mode); + } + } else { + // cpath is relative if(!(CTX->file_map()->exist(dirfd))) { //TODO relative cpath could still lead to our FS return LIBC_FUNC(faccessat, dirfd, cpath, mode, flags); @@ -590,10 +596,6 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) __THROW { if(resolve_path(path, resolved)) { return adafs_access(resolved, mode); } - } else { - if (CTX->relativize_path(cpath, resolved)) { - return adafs_access(resolved, mode); - } } return LIBC_FUNC(faccessat, dirfd, resolved.c_str(), mode, flags); } -- GitLab From 176c04a7a8f42f8f518c08b15a62d92b0a3fe98a Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 21 Aug 2018 15:30:08 +0200 Subject: [PATCH 088/105] Store CWD in environment to surivive across exec --- ifs/include/preload/resolve.hpp | 7 ++++ ifs/src/preload/intcp_functions.cpp | 29 ++++++---------- ifs/src/preload/preload.cpp | 21 ++++++++++-- ifs/src/preload/resolve.cpp | 53 +++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 20 deletions(-) diff --git a/ifs/include/preload/resolve.hpp b/ifs/include/preload/resolve.hpp index 5c9edf972..da56b3023 100644 --- a/ifs/include/preload/resolve.hpp +++ b/ifs/include/preload/resolve.hpp @@ -3,3 +3,10 @@ bool resolve_path (const std::string& path, std::string& resolved); std::string get_sys_cwd(); +void set_sys_cwd(const std::string& path); + +void set_env_cwd(const std::string& path); +void unset_env_cwd(); + +void init_cwd(); +void set_cwd(const std::string& path, bool internal); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index c2262b72f..6c3cbb28f 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1298,8 +1298,8 @@ int chdir(const char* path){ CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; - const char* fake_path; - if (CTX->relativize_path(path, rel_path)) { + bool internal = CTX->relativize_path(path, rel_path); + if (internal) { //path falls in our namespace struct stat st; if(adafs_stat(rel_path, &st) != 0) { @@ -1312,7 +1312,6 @@ int chdir(const char* path){ errno = ENOTDIR; return -1; } - fake_path = CTX->mountdir().c_str(); //TODO get complete path from relativize_path instead of // removing mountdir and then adding again here rel_path.insert(0, CTX->mountdir()); @@ -1320,17 +1319,12 @@ int chdir(const char* path){ // open_dir is '/' rel_path.pop_back(); } - } else { - fake_path = rel_path.c_str(); } - - if(LIBC_FUNC(chdir, fake_path)) { - CTX->log()->error("{}() failed to change dir: {}", - __func__, std::strerror(errno)); + try { + set_cwd(rel_path, internal); + } catch (const std::system_error& se) { return -1; } - - CTX->cwd(rel_path); return 0; } @@ -1351,23 +1345,22 @@ int fchdir(int fd) { return -1; } - if (LIBC_FUNC(chdir, CTX->mountdir().c_str())) { - CTX->log()->error("{}() failed to change dir: {}", - __func__, std::strerror(errno)); - return -1; - } - std::string new_path = CTX->mountdir() + open_dir->path(); if (has_trailing_slash(new_path)) { // open_dir is '/' new_path.pop_back(); } - CTX->cwd(new_path); + try { + set_cwd(new_path, true); + } catch (const std::system_error& se) { + return -1; + } } else { if(LIBC_FUNC(fchdir, fd) != 0) { CTX->log()->error("{}() failed to change dir: {}", __func__, std::strerror(errno)); } + unset_env_cwd(); CTX->cwd(get_sys_cwd()); } return 0; diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index 918ab68a5..77f7b1f6d 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -11,6 +11,8 @@ #include #include +#include + enum class Margo_mode { RPC, IPC }; @@ -300,6 +302,20 @@ void init_logging() { CTX->log(spdlog::get(logger_names.at(0))); } +void log_prog_name() { + std::string line; + std::ifstream cmdline("/proc/self/cmdline"); + if (!cmdline.is_open()) { + CTX->log()->error("Unable to open cmdline file"); + throw runtime_error("Unable to open cmdline file"); + } + if(!getline(cmdline, line)) { + throw runtime_error("Unable to read cmdline file"); + } + CTX->log()->info("Command to itercept: '{}'", line); + cmdline.close(); +} + /** * Called initially ONCE when preload library is used with the LD_PRELOAD environment variable */ @@ -307,7 +323,8 @@ void init_preload() { init_passthrough_if_needed(); init_logging(); CTX->log()->debug("Initialized logging subsystem"); - CTX->cwd(get_sys_cwd()); + log_prog_name(); + init_cwd(); CTX->log()->debug("Current working directory: '{}'", CTX->cwd()); if (get_daemon_pid() == -1 || CTX->mountdir().empty()) { cerr << "ADA-FS daemon not running or mountdir could not be loaded. Check adafs_preload.log" << endl; @@ -366,4 +383,4 @@ void destroy_preload() { } else CTX->log()->debug("{}() No services in preload library used. Nothing to shut down.", __func__); -} \ No newline at end of file +} diff --git a/ifs/src/preload/resolve.cpp b/ifs/src/preload/resolve.cpp index 3cc5d3f1a..c0124ed88 100644 --- a/ifs/src/preload/resolve.cpp +++ b/ifs/src/preload/resolve.cpp @@ -109,3 +109,56 @@ std::string get_sys_cwd() { } return {temp}; } + +void set_sys_cwd(const std::string& path) { + CTX->log()->debug("{}() to '{}'", __func__, path); + if (LIBC_FUNC(chdir, path.c_str())) { + CTX->log()->error("{}() failed to set system current working directory: {}", + __func__, std::strerror(errno)); + throw std::system_error(errno, + std::system_category(), + "Failed to set system current working directory"); + } +} + +void set_env_cwd(const std::string& path) { + CTX->log()->debug("{}() to '{}'", __func__, path); + if(setenv("ADAFS_CWD", path.c_str(), 1)) { + CTX->log()->error("{}() failed to set environment current working directory: {}", + __func__, std::strerror(errno)); + throw std::system_error(errno, + std::system_category(), + "Failed to set environment current working directory"); + } +} + +void unset_env_cwd() { + CTX->log()->debug("{}()", __func__); + if(unsetenv("ADAFS_CWD")) { + CTX->log()->error("{}() failed to unset environment current working directory: {}", + __func__, std::strerror(errno)); + throw std::system_error(errno, + std::system_category(), + "Failed to unset environment current working directory"); + } +} + +void init_cwd() { + const char* env_cwd = std::getenv("ADAFS_CWD"); + if (env_cwd != nullptr) { + CTX->cwd(env_cwd); + } else { + CTX->cwd(get_sys_cwd()); + } +} + +void set_cwd(const std::string& path, bool internal) { + if(internal) { + set_sys_cwd(CTX->mountdir()); + set_env_cwd(path); + } else { + set_sys_cwd(path); + unset_env_cwd(); + } + CTX->cwd(path); +} -- GitLab From 95bd8b40cafdc905568d627134869be7bdef87f2 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:22:43 +0200 Subject: [PATCH 089/105] Update CMake find module for LZ4 It wasn't working and it was too complex --- ifs/CMake/FindLZ4.cmake | 70 ++++++++++------------------------------- ifs/CMakeLists.txt | 2 +- 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/ifs/CMake/FindLZ4.cmake b/ifs/CMake/FindLZ4.cmake index 93ae06b17..ef846c9ca 100644 --- a/ifs/CMake/FindLZ4.cmake +++ b/ifs/CMake/FindLZ4.cmake @@ -1,59 +1,23 @@ -# Finds liblz4. -# -# This module defines: -# LZ4_FOUND -# LZ4_INCLUDE_DIR -# LZ4_LIBRARY + +# - Find Lz4 +# Find the lz4 compression library and includes # +# LZ4_INCLUDE_DIR - where to find lz4.h, etc. +# LZ4_LIBRARIES - List of libraries when using lz4. +# LZ4_FOUND - True if lz4 found. -find_path(LZ4_INCLUDE_DIR lz4.h - HINTS - ${ADAFS_DEPS_INSTALL} - $ENV{HOME}/opt - /usr - /usr/local - /usr/local/adafs - /opt - PATH_SUFFIXES include - PATH_SUFFIXES include/lz4 - ) -find_library(LZ4_LIBRARY lz4 - HINTS - ${ADAFS_DEPS_INSTALL} - $ENV{HOME}/opt - /usr - /usr/local - /usr/local/adafs - /opt/ - PATH_SUFFIXES lib - PATH_SUFFIXES lib/lz4 - ) +find_path(LZ4_INCLUDE_DIR + NAMES lz4.h +) -# We require LZ4_compress_default() which was added in v1.7.0 -if (LZ4_LIBRARY) - include(CheckCSourceRuns) - set(CMAKE_REQUIRED_INCLUDES ${LZ4_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${LZ4_LIBRARY}) - check_c_source_runs(" -#include -int main() { - int good = (LZ4_VERSION_MAJOR > 1) || - ((LZ4_VERSION_MAJOR == 1) && (LZ4_VERSION_MINOR >= 7)); -return !good; -}" LZ4_GOOD_VERSION) - set(CMAKE_REQUIRED_INCLUDES) - set(CMAKE_REQUIRED_LIBRARIES) -endif () +find_library(LZ4_LIBRARIES + NAMES lz4 +) include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS( - LZ4 DEFAULT_MSG - LZ4_LIBRARY LZ4_INCLUDE_DIR LZ4_GOOD_VERSION) - -if (NOT LZ4_FOUND) - message(STATUS "Using third-party bundled LZ4") -else () - message(STATUS "Found LZ4: ${LZ4_LIBRARY}") -endif (NOT LZ4_FOUND) +find_package_handle_standard_args(lz4 DEFAULT_MSG LZ4_LIBRARIES LZ4_INCLUDE_DIR) -mark_as_advanced(LZ4_INCLUDE_DIR LZ4_LIBRARY) +mark_as_advanced( + LZ4_LIBRARIES + LZ4_INCLUDE_DIR +) diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 59b02c39b..84615a568 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -124,7 +124,7 @@ target_link_libraries(RocksDB ${ZLIB_LIBRARIES} ${BZIP2_LIBRARIES} ${ZSTD_LIBRARIES} - ${LZ4_LIBRARY} + ${LZ4_LIBRARIES} ) if(${JeMalloc_FOUND}) -- GitLab From 1f95ba1d8e8a28549fb6a86aadb1caed79c33838 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:25:33 +0200 Subject: [PATCH 090/105] Add internal adafs_read function Instead of using the more complex adafs_pread function now is possible to use adafs_read when it is necessary to read starting from the current file position --- ifs/include/preload/adafs_functions.hpp | 2 ++ ifs/src/preload/adafs_functions.cpp | 11 +++++++++++ ifs/src/preload/intcp_functions.cpp | 14 ++------------ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/ifs/include/preload/adafs_functions.hpp b/ifs/include/preload/adafs_functions.hpp index 9ea55f586..ac217b7ef 100644 --- a/ifs/include/preload/adafs_functions.hpp +++ b/ifs/include/preload/adafs_functions.hpp @@ -54,6 +54,8 @@ int adafs_dup2(int oldfd, int newfd); ssize_t adafs_pwrite_ws(int fd, const void* buf, size_t count, off64_t offset); +ssize_t adafs_read(int fd, void* buf, size_t count); + ssize_t adafs_pread_ws(int fd, void* buf, size_t count, off64_t offset); int adafs_opendir(const std::string& path); diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index 8c783bea8..baea550ad 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -309,6 +309,17 @@ ssize_t adafs_pwrite_ws(int fd, const void* buf, size_t count, off64_t offset) { return ret; // return written size or -1 as error } +ssize_t adafs_read(int fd, void* buf, size_t count) { + auto adafs_fd = CTX->file_map()->get(fd); + auto pos = adafs_fd->pos(); //retrieve the current offset + auto ret = adafs_pread_ws(fd, buf, count, pos); + // Update offset in file descriptor in the file map + if (ret > 0) { + adafs_fd->pos(pos + ret); + } + return ret; +} + ssize_t adafs_pread_ws(int fd, void* buf, size_t count, off64_t offset) { init_ld_env_if_needed(); auto adafs_fd = CTX->file_map()->get(fd); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 6c3cbb28f..d1dcf92ae 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -189,12 +189,8 @@ size_t intcp_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { auto fd = file_to_fd(stream); if (CTX->file_map()->exist(fd)) { CTX->log()->trace("{}() called with fd {}", __func__, fd); - auto adafs_fd = CTX->file_map()->get(fd); - auto pos = adafs_fd->pos(); //retrieve the current offset - auto ret = adafs_pread_ws(fd, ptr, size*nmemb, pos); + auto ret = adafs_read(fd, ptr, size*nmemb); if (ret > 0) { - // Update offset in file descriptor in the file map - adafs_fd->pos(pos + ret); return ret / size; } return ret; @@ -916,13 +912,7 @@ ssize_t read(int fd, void* buf, size_t count) { if(CTX->initialized()) { CTX->log()->trace("{}() called with fd {}, count {}", __func__, fd, count); if (CTX->file_map()->exist(fd)) { - auto adafs_fd = CTX->file_map()->get(fd); - auto pos = adafs_fd->pos(); //retrieve the current offset - auto ret = adafs_pread_ws(fd, buf, count, pos); - // Update offset in file descriptor in the file map - if (ret > 0) { - adafs_fd->pos(pos + ret); - } + auto ret = adafs_read(fd, buf, count); CTX->log()->trace("{}() returning {}", __func__, ret); return ret; } -- GitLab From f27ac62946fa7670a27b2fc089261c256d66f836 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:27:29 +0200 Subject: [PATCH 091/105] Intercept links function family --- ifs/include/preload/passthrough.hpp | 5 +++ ifs/src/preload/intcp_functions.cpp | 51 +++++++++++++++++++++++++++++ ifs/src/preload/passthrough.cpp | 10 ++++++ 3 files changed, 66 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index aae5bf611..0a0e7a80d 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -102,6 +102,11 @@ extern void* libc_fchdir; extern void* libc_getcwd; extern void* libc_get_current_dir_name; +extern void* libc_link; +extern void* libc_linkat; +extern void* libc_symlink; +extern void* libc_symlinkat; + extern void* libc_realpath; void init_passthrough_if_needed(); diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index d1dcf92ae..e66336d59 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1390,6 +1390,57 @@ char *get_current_dir_name(void) { return nullptr; } + +int link(const char *oldpath, const char *newpath) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [oldpath: '{}', newpath: '{}']", + __func__, oldpath, newpath); + CTX->log()->error("{}() not implemented", __func__); + errno = ENOTSUP; + return -1; + } + return LIBC_FUNC(link, oldpath, newpath); +} + +int linkat(int olddirfd, const char *oldpath, + int newdirfd, const char *newpath, int flags) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [olddirfd: '{}', oldpath: '{}',\ + newdirfd: '{}', newpath: '{}', flags: '{}']", + __func__, olddirfd, oldpath, newdirfd, newpath, flags); + CTX->log()->error("{}() not implemented", __func__); + errno = ENOTSUP; + return -1; + } + return LIBC_FUNC(linkat, olddirfd, oldpath, newdirfd, newpath, flags); +} + +int symlink(const char *oldpath, const char *newpath) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [oldpath: '{}', newpath: '{}']", + __func__, oldpath, newpath); + CTX->log()->error("{}() not implemented", __func__); + errno = ENOTSUP; + return -1; + } + return LIBC_FUNC(symlink, oldpath, newpath); +} + +int symlinkat(const char *oldpath, int fd, const char *newpath) { + init_passthrough_if_needed(); + if(CTX->initialized()) { + CTX->log()->trace("{}() called [oldpath: '{}', newpath: '{}']", + __func__, oldpath, newpath); + CTX->log()->error("{}() not implemented", __func__); + errno = ENOTSUP; + return -1; + } + return LIBC_FUNC(symlinkat, oldpath, fd, newpath); +} + char *realpath(const char *path, char *resolved_path) { init_passthrough_if_needed(); if(!CTX->initialized()) { diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index e5ef4fbef..3e5ad6186 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -103,6 +103,11 @@ void* libc_fchdir; void* libc_getcwd; void* libc_get_current_dir_name; +void* libc_link; +void* libc_linkat; +void* libc_symlink; +void* libc_symlinkat; + void* libc_realpath; @@ -204,6 +209,11 @@ void init_passthrough_() { libc_getcwd = dlsym(libc, "getcwd"); libc_get_current_dir_name = dlsym(libc, "get_current_dir_name"); + libc_link = dlsym(libc, "link"); + libc_linkat = dlsym(libc, "linkat"); + libc_symlink = dlsym(libc, "symlink"); + libc_symlinkat = dlsym(libc, "symlinkat"); + libc_realpath = dlsym(libc, "realpath"); } -- GitLab From 779da1d20c508651d923e2e0ba13539beda6a682 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:28:49 +0200 Subject: [PATCH 092/105] intercept fget* family --- ifs/include/preload/passthrough.hpp | 5 ++ ifs/src/preload/intcp_functions.cpp | 82 ++++++++++++++++++++++++++++- ifs/src/preload/passthrough.cpp | 10 ++++ 3 files changed, 95 insertions(+), 2 deletions(-) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 0a0e7a80d..748aa4bc6 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -35,6 +35,11 @@ extern void* libc_setvbuf; extern void* libc_putc; extern void* libc_fputc; +extern void* libc_fputs; +extern void* libc_getc; +extern void* libc_fgetc; +extern void* libc_fgets; +extern void* libc_ungetc; extern void* libc_mkdir; extern void* libc_mkdirat; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index e66336d59..8fa6439ef 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -377,7 +377,7 @@ int putc(int c, FILE *stream) { return EOF; } } - return (reinterpret_cast(libc_putc))(c, stream); + return LIBC_FUNC(putc, c, stream); } int fputc(int c, FILE *stream) { @@ -390,7 +390,85 @@ int fputc(int c, FILE *stream) { return EOF; } } - return (reinterpret_cast(libc_fputc))(c, stream); + return LIBC_FUNC(fputc, c, stream); +} + +int fputs(const char *s, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return LIBC_FUNC(fputs, s, stream); +} + +int getc(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return LIBC_FUNC(getc, stream); +} + +int fgetc(FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return LIBC_FUNC(fgetc, stream); +} + +char* fgets(char* s, int size, FILE* stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() [fd: {}, size: {}]", __func__, fd, size); + auto ret = adafs_read(fd, s, size - 1); + CTX->log()->debug("{}() read {} bytes", __func__, ret); + if(ret > 0) { + char* nl_ptr = static_cast(memchr(s, '\n', size - 1)); + assert((nl_ptr - s) < size); + if(nl_ptr != nullptr) { + CTX->log()->debug("{}() found new line char at {}", __func__, (nl_ptr - s)); + nl_ptr[1] = '\0'; + } else { + s[size - 1] = '\0'; + } + return s; + } else { + return nullptr; + } + } + } + return LIBC_FUNC(fgets, s, size, stream); +} + +int ungetc(int c, FILE *stream) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->error("{}() NOT SUPPORTED", __func__); + errno = ENOTSUP; + return EOF; + } + } + return LIBC_FUNC(ungetc, c, stream); } /****** FILE OPS ******/ diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 3e5ad6186..9f4ff2c08 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -35,6 +35,11 @@ void* libc_setvbuf; void* libc_putc; void* libc_fputc; +void* libc_fputs; +void* libc_getc; +void* libc_fgetc; +void* libc_fgets; +void* libc_ungetc; void* libc_mkdir; void* libc_mkdirat; @@ -141,6 +146,11 @@ void init_passthrough_() { libc_putc = dlsym(libc, "putc"); libc_fputc = dlsym(libc, "fputc"); + libc_fputs = dlsym(libc, "fputs"); + libc_getc = dlsym(libc, "getc"); + libc_fgetc = dlsym(libc, "fgetc"); + libc_fgets = dlsym(libc, "fgets"); + libc_ungetc = dlsym(libc, "ungetc"); libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From 79bd5371445007377b8a637df43c2795e2fc9ce6 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:29:25 +0200 Subject: [PATCH 093/105] fopen ignore 'b' flag --- ifs/src/preload/intcp_functions.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 8fa6439ef..019237fff 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -150,6 +150,8 @@ FILE* fopen(const char* path, const char* fmode) { } int flags = 0; std::string str_mode(fmode); + str_mode.erase(std::remove(str_mode.begin(), str_mode.end(), 'b'), str_mode.end()); + if(str_mode == "r") { flags = O_RDONLY; } else if(str_mode == "r+") { -- GitLab From 5b444afe9c82fa8d22e22299def44328f47b0bab Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 27 Aug 2018 17:29:45 +0200 Subject: [PATCH 094/105] fix chdir log: missing parameter --- ifs/src/preload/intcp_functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 019237fff..56ea8968b 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -1373,12 +1373,12 @@ int chdir(const char* path){ //path falls in our namespace struct stat st; if(adafs_stat(rel_path, &st) != 0) { - CTX->log()->error("{}() path does not exists"); + CTX->log()->error("{}() path does not exists", __func__); errno = ENOENT; return -1; } if(!S_ISDIR(st.st_mode)) { - CTX->log()->error("{}() path is not a directory"); + CTX->log()->error("{}() path is not a directory", __func__); errno = ENOTDIR; return -1; } -- GitLab From 06d4cda4af88347bb0a23ca5e419bedcfe0d043c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 28 Aug 2018 11:12:28 +0200 Subject: [PATCH 095/105] mark O_APPEND flags as not supported --- ifs/src/preload/adafs_functions.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ifs/src/preload/adafs_functions.cpp b/ifs/src/preload/adafs_functions.cpp index baea550ad..06b989d59 100644 --- a/ifs/src/preload/adafs_functions.cpp +++ b/ifs/src/preload/adafs_functions.cpp @@ -20,6 +20,12 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return -1; } + if(flags & O_APPEND){ + CTX->log()->error("{}() `O_APPEND` flag is not supported", __func__); + errno = ENOTSUP; + return -1; + } + bool exists = true; struct stat st; err = adafs_stat(path, &st); -- GitLab From 60b396822dbc80071b0f34ac448cfb02212e3b31 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 28 Aug 2018 11:17:39 +0200 Subject: [PATCH 096/105] extends fopen flags support --- ifs/src/preload/intcp_functions.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 56ea8968b..05865453b 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -158,6 +158,12 @@ FILE* fopen(const char* path, const char* fmode) { flags = O_RDWR; } else if(str_mode == "w") { flags = (O_WRONLY | O_CREAT | O_TRUNC); + } else if(str_mode == "w+") { + flags = (O_RDWR | O_CREAT | O_TRUNC); + } else if(str_mode == "a") { + flags = (O_WRONLY | O_CREAT | O_APPEND); + } else if(str_mode == "a+") { + flags = (O_RDWR | O_CREAT | O_APPEND); } else { CTX->log()->error("{}() stream open flags NOT SUPPORTED: '{}'", __func__, str_mode); errno = ENOTSUP; -- GitLab From 716f7705a3c30be41b499fb8a6501facec6a80e0 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Tue, 28 Aug 2018 11:18:41 +0200 Subject: [PATCH 097/105] intercept fseek --- ifs/include/preload/passthrough.hpp | 2 ++ ifs/src/preload/intcp_functions.cpp | 16 ++++++++++++++++ ifs/src/preload/passthrough.cpp | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/ifs/include/preload/passthrough.hpp b/ifs/include/preload/passthrough.hpp index 748aa4bc6..a4b8fa2af 100644 --- a/ifs/include/preload/passthrough.hpp +++ b/ifs/include/preload/passthrough.hpp @@ -41,6 +41,8 @@ extern void* libc_fgetc; extern void* libc_fgets; extern void* libc_ungetc; +extern void* libc_fseek; + extern void* libc_mkdir; extern void* libc_mkdirat; extern void* libc_unlink; diff --git a/ifs/src/preload/intcp_functions.cpp b/ifs/src/preload/intcp_functions.cpp index 05865453b..b66bae1f2 100644 --- a/ifs/src/preload/intcp_functions.cpp +++ b/ifs/src/preload/intcp_functions.cpp @@ -479,6 +479,22 @@ int ungetc(int c, FILE *stream) { return LIBC_FUNC(ungetc, c, stream); } +int fseek(FILE *stream, long offset, int whence) { + init_passthrough_if_needed(); + if(CTX->initialized() && (stream != nullptr)) { + auto fd = file_to_fd(stream); + if(CTX->file_map()->exist(fd)) { + CTX->log()->trace("{}() called [fd: {}, offset: {}, whence: {}"); + if(adafs_lseek(fd, offset, whence) == -1) { + return -1; + } else { + return 0; + } + } + } + return LIBC_FUNC(fseek, stream, offset, whence); +} + /****** FILE OPS ******/ #undef creat diff --git a/ifs/src/preload/passthrough.cpp b/ifs/src/preload/passthrough.cpp index 9f4ff2c08..3a4b017dd 100644 --- a/ifs/src/preload/passthrough.cpp +++ b/ifs/src/preload/passthrough.cpp @@ -41,6 +41,8 @@ void* libc_fgetc; void* libc_fgets; void* libc_ungetc; +void* libc_fseek; + void* libc_mkdir; void* libc_mkdirat; void* libc_unlink; @@ -152,6 +154,8 @@ void init_passthrough_() { libc_fgets = dlsym(libc, "fgets"); libc_ungetc = dlsym(libc, "ungetc"); + libc_fseek = dlsym(libc, "fseek"); + libc_mkdir = dlsym(libc, "mkdir"); libc_mkdirat = dlsym(libc, "mkdirat"); -- GitLab From 15ca10eeabde0042614d2dae91e9ef49e05bb9c9 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 10 Sep 2018 19:05:49 +0200 Subject: [PATCH 098/105] avoid "static initialization order problem" ensure rpc_addresses object have been initialized before we use it for the first time --- ifs/src/preload/preload.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ifs/src/preload/preload.cpp b/ifs/src/preload/preload.cpp index 77f7b1f6d..2df943de1 100644 --- a/ifs/src/preload/preload.cpp +++ b/ifs/src/preload/preload.cpp @@ -265,6 +265,8 @@ void init_ld_environment_() { CTX->log()->error("{}() Unable to read system hostfile /etc/hosts for address mapping.", __func__); exit(EXIT_FAILURE); } + //use rpc_addresses here to avoid "static initialization order problem" + rpc_addresses.clear(); if (!lookup_all_hosts()) { CTX->log()->error("{}() Unable to lookup all host RPC addresses.", __func__); exit(EXIT_FAILURE); -- GitLab From eb2cd0bc07c5702894c103c1187bd97452e291a0 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 13 Sep 2018 16:50:51 +0200 Subject: [PATCH 099/105] Compile deps script: suppress BMI compilation warnings --- ifs/scripts/compile_dep.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/scripts/compile_dep.sh b/ifs/scripts/compile_dep.sh index 5513b1816..7a9c1cb58 100755 --- a/ifs/scripts/compile_dep.sh +++ b/ifs/scripts/compile_dep.sh @@ -218,7 +218,7 @@ if [ "$NA_LAYER" == "bmi" ] || [ "$NA_LAYER" == "all" ]; then cd ${CURR} ./prepare cd ${CURR}/build - ../configure --prefix=${INSTALL} --enable-shared --disable-static --disable-karma --enable-bmi-only --enable-fast --disable-strict + CFLAGS=-w ../configure --prefix=${INSTALL} --enable-shared --disable-static --disable-karma --enable-bmi-only --enable-fast --disable-strict make -j${CORES} make install fi -- GitLab From c270659796c2886808993276f55adbda54289415 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Thu, 13 Sep 2018 16:51:41 +0200 Subject: [PATCH 100/105] Remove spaces in mercury patch --- ifs/scripts/patches/mercury_deregister_sock.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ifs/scripts/patches/mercury_deregister_sock.patch b/ifs/scripts/patches/mercury_deregister_sock.patch index ca147aaab..816cebfb4 100644 --- a/ifs/scripts/patches/mercury_deregister_sock.patch +++ b/ifs/scripts/patches/mercury_deregister_sock.patch @@ -17,7 +17,7 @@ index d96f1b6..01e4110 100644 break; + case NA_SM_SOCK_DONE: { + *progressed = NA_FALSE; -+ ret = na_sm_poll_deregister(na_class, NA_SM_SOCK, poll_addr); ++ ret = na_sm_poll_deregister(na_class, NA_SM_SOCK, poll_addr); + if (ret != NA_SUCCESS) { + NA_LOG_ERROR("Could not deregister socket from poll set"); + ret = NA_PROTOCOL_ERROR; -- GitLab From 24c2c194699819376260738d5cfdf74b3500de2c Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 17 Sep 2018 15:18:15 +0200 Subject: [PATCH 101/105] avoid usage of fmt library --- ifs/src/daemon/adafs_daemon.cpp | 2 +- ifs/src/daemon/adafs_ops/metadentry.cpp | 2 +- ifs/src/daemon/classes/metadata.cpp | 29 ++++++++++++++++--------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/ifs/src/daemon/adafs_daemon.cpp b/ifs/src/daemon/adafs_daemon.cpp index 040b20848..25c1a052d 100644 --- a/ifs/src/daemon/adafs_daemon.cpp +++ b/ifs/src/daemon/adafs_daemon.cpp @@ -492,7 +492,7 @@ int main(int argc, const char* argv[]) { } ADAFS_DATA->hosts(hostmap); ADAFS_DATA->host_size(hostmap.size()); - ADAFS_DATA->rpc_port(fmt::FormatInt(RPC_PORT).str()); + ADAFS_DATA->rpc_port(std::to_string(RPC_PORT)); ADAFS_DATA->hosts_raw(hosts_raw); ADAFS_DATA->spdlogger()->info("{}() Initializing environment. Hold on ...", __func__); diff --git a/ifs/src/daemon/adafs_ops/metadentry.cpp b/ifs/src/daemon/adafs_ops/metadentry.cpp index 6af517f61..a279c15cd 100644 --- a/ifs/src/daemon/adafs_ops/metadentry.cpp +++ b/ifs/src/daemon/adafs_ops/metadentry.cpp @@ -23,7 +23,7 @@ void create_metadentry(const std::string& path, mode_t mode) { if (ADAFS_DATA->atime_state() || ADAFS_DATA->mtime_state() || ADAFS_DATA->ctime_state()) { std::time_t time; std::time(&time); - auto time_s = fmt::FormatInt(time).str(); + auto time_s = std::to_string(time); if (ADAFS_DATA->atime_state()) md.atime(time); if (ADAFS_DATA->mtime_state()) diff --git a/ifs/src/daemon/classes/metadata.cpp b/ifs/src/daemon/classes/metadata.cpp index 4d563dee1..dfbb89cce 100644 --- a/ifs/src/daemon/classes/metadata.cpp +++ b/ifs/src/daemon/classes/metadata.cpp @@ -128,31 +128,40 @@ void Metadata::update_ACM_time(bool a, bool c, bool m) { std::string Metadata::serialize() const { std::string s; // The order is important. don't change. - s += fmt::FormatInt(mode_).c_str(); // add mandatory mode - s += dentry_val_delim + fmt::FormatInt(size_).c_str(); // add mandatory size + s += std::to_string(mode_); // add mandatory mode + s += dentry_val_delim; + s += std::to_string(size_); // add mandatory size if (ADAFS_DATA->atime_state()) { - s += dentry_val_delim + fmt::FormatInt(atime_).c_str(); + s += dentry_val_delim; + s += std::to_string(atime_); } if (ADAFS_DATA->mtime_state()) { - s += dentry_val_delim + fmt::FormatInt(mtime_).c_str(); + s += dentry_val_delim; + s += std::to_string(mtime_); } if (ADAFS_DATA->ctime_state()) { - s += dentry_val_delim + fmt::FormatInt(ctime_).c_str(); + s += dentry_val_delim; + s += std::to_string(ctime_); } if (ADAFS_DATA->uid_state()) { - s += dentry_val_delim + fmt::FormatInt(uid_).str(); + s += dentry_val_delim; + s += std::to_string(uid_); } if (ADAFS_DATA->gid_state()) { - s += dentry_val_delim + fmt::FormatInt(gid_).str(); + s += dentry_val_delim; + s += std::to_string(gid_); } if (ADAFS_DATA->inode_no_state()) { - s += dentry_val_delim + fmt::FormatInt(inode_no_).str(); + s += dentry_val_delim; + s += std::to_string(inode_no_); } if (ADAFS_DATA->link_cnt_state()) { - s += dentry_val_delim + fmt::FormatInt(link_count_).c_str(); + s += dentry_val_delim; + s += std::to_string(link_count_); } if (ADAFS_DATA->blocks_state()) { - s += dentry_val_delim + fmt::FormatInt(blocks_).c_str(); + s += dentry_val_delim; + s += std::to_string(blocks_); } return s; } -- GitLab From 4359a49fc2fee944d3ef01e15a526b90e27a4450 Mon Sep 17 00:00:00 2001 From: Tommaso Tocci Date: Mon, 17 Sep 2018 15:21:24 +0200 Subject: [PATCH 102/105] update spdlog library to version 1.1.0 --- ifs/CMakeLists.txt | 6 + ifs/include/daemon/adafs_daemon.hpp | 4 +- ifs/include/daemon/classes/fs_data.hpp | 1 + ifs/include/extern/spdlog/async.h | 87 + ifs/include/extern/spdlog/async_logger.h | 86 +- ifs/include/extern/spdlog/common.h | 140 +- .../extern/spdlog/details/async_log_helper.h | 378 - .../extern/spdlog/details/async_logger_impl.h | 119 +- .../extern/spdlog/details/circular_q.h | 72 + .../extern/spdlog/details/console_globals.h | 61 + .../extern/spdlog/details/file_helper.h | 111 +- .../extern/spdlog/details/fmt_helper.h | 130 + ifs/include/extern/spdlog/details/log_msg.h | 42 +- .../extern/spdlog/details/logger_impl.h | 311 +- .../extern/spdlog/details/mpmc_blocking_q.h | 121 + .../extern/spdlog/details/mpmc_bounded_q.h | 172 - .../extern/spdlog/details/null_mutex.h | 16 +- ifs/include/extern/spdlog/details/os.h | 262 +- .../extern/spdlog/details/pattern_formatter.h | 772 ++ .../spdlog/details/pattern_formatter_impl.h | 670 -- .../extern/spdlog/details/periodic_worker.h | 71 + ifs/include/extern/spdlog/details/registry.h | 247 +- .../extern/spdlog/details/spdlog_impl.h | 246 - .../extern/spdlog/details/thread_pool.h | 225 + .../extern/spdlog/fmt/bundled/LICENSE.rst | 23 + .../extern/spdlog/fmt/bundled/colors.h | 257 + ifs/include/extern/spdlog/fmt/bundled/core.h | 1578 ++++ .../extern/spdlog/fmt/bundled/format-inl.h | 578 ++ .../extern/spdlog/fmt/bundled/format.cc | 583 -- .../extern/spdlog/fmt/bundled/format.h | 6823 +++++++++-------- .../extern/spdlog/fmt/bundled/locale.h | 27 + .../extern/spdlog/fmt/bundled/ostream.cc | 37 - .../extern/spdlog/fmt/bundled/ostream.h | 203 +- ifs/include/extern/spdlog/fmt/bundled/posix.h | 484 ++ .../extern/spdlog/fmt/bundled/printf.h | 807 +- .../extern/spdlog/fmt/bundled/ranges.h | 344 + ifs/include/extern/spdlog/fmt/bundled/time.h | 199 + ifs/include/extern/spdlog/fmt/fmt.h | 14 +- ifs/include/extern/spdlog/fmt/ostr.h | 14 +- ifs/include/extern/spdlog/formatter.h | 39 +- ifs/include/extern/spdlog/logger.h | 198 +- .../extern/spdlog/sinks/android_sink.h | 92 +- .../extern/spdlog/sinks/ansicolor_sink.h | 216 +- ifs/include/extern/spdlog/sinks/base_sink.h | 70 +- .../extern/spdlog/sinks/basic_file_sink.h | 66 + .../extern/spdlog/sinks/daily_file_sink.h | 132 + ifs/include/extern/spdlog/sinks/dist_sink.h | 79 +- ifs/include/extern/spdlog/sinks/file_sinks.h | 239 - ifs/include/extern/spdlog/sinks/msvc_sink.h | 50 +- ifs/include/extern/spdlog/sinks/null_sink.h | 30 +- .../extern/spdlog/sinks/ostream_sink.h | 54 +- .../extern/spdlog/sinks/rotating_file_sink.h | 144 + ifs/include/extern/spdlog/sinks/sink.h | 66 +- .../extern/spdlog/sinks/stdout_color_sinks.h | 53 + .../extern/spdlog/sinks/stdout_sinks.h | 105 +- ifs/include/extern/spdlog/sinks/syslog_sink.h | 86 +- .../extern/spdlog/sinks/wincolor_sink.h | 147 +- ifs/include/extern/spdlog/spdlog.h | 227 +- ifs/include/extern/spdlog/tweakme.h | 82 +- ifs/include/extern/spdlog/version.h | 12 + ifs/include/global/log_util.hpp | 2 +- ifs/include/preload/preload_context.hpp | 3 +- ifs/src/daemon/CMakeLists.txt | 1 + ifs/src/daemon/backend/data/CMakeLists.txt | 1 + ifs/src/daemon/backend/data/chunk_storage.cpp | 2 +- .../daemon/backend/metadata/CMakeLists.txt | 1 + ifs/src/daemon/backend/metadata/db.cpp | 1 - ifs/src/daemon/classes/fs_data.cpp | 1 + ifs/src/global/CMakeLists.txt | 4 +- ifs/src/global/log_util.cpp | 4 +- ifs/src/global/rpc/rpc_utils.cpp | 1 - ifs/src/preload/preload_context.cpp | 1 - 72 files changed, 11153 insertions(+), 7377 deletions(-) create mode 100644 ifs/include/extern/spdlog/async.h delete mode 100644 ifs/include/extern/spdlog/details/async_log_helper.h create mode 100644 ifs/include/extern/spdlog/details/circular_q.h create mode 100644 ifs/include/extern/spdlog/details/console_globals.h create mode 100644 ifs/include/extern/spdlog/details/fmt_helper.h create mode 100644 ifs/include/extern/spdlog/details/mpmc_blocking_q.h delete mode 100644 ifs/include/extern/spdlog/details/mpmc_bounded_q.h create mode 100644 ifs/include/extern/spdlog/details/pattern_formatter.h delete mode 100644 ifs/include/extern/spdlog/details/pattern_formatter_impl.h create mode 100644 ifs/include/extern/spdlog/details/periodic_worker.h delete mode 100644 ifs/include/extern/spdlog/details/spdlog_impl.h create mode 100644 ifs/include/extern/spdlog/details/thread_pool.h create mode 100644 ifs/include/extern/spdlog/fmt/bundled/LICENSE.rst create mode 100644 ifs/include/extern/spdlog/fmt/bundled/colors.h create mode 100644 ifs/include/extern/spdlog/fmt/bundled/core.h create mode 100644 ifs/include/extern/spdlog/fmt/bundled/format-inl.h delete mode 100644 ifs/include/extern/spdlog/fmt/bundled/format.cc create mode 100644 ifs/include/extern/spdlog/fmt/bundled/locale.h delete mode 100644 ifs/include/extern/spdlog/fmt/bundled/ostream.cc create mode 100644 ifs/include/extern/spdlog/fmt/bundled/posix.h create mode 100644 ifs/include/extern/spdlog/fmt/bundled/ranges.h create mode 100644 ifs/include/extern/spdlog/fmt/bundled/time.h create mode 100644 ifs/include/extern/spdlog/sinks/basic_file_sink.h create mode 100644 ifs/include/extern/spdlog/sinks/daily_file_sink.h delete mode 100644 ifs/include/extern/spdlog/sinks/file_sinks.h create mode 100644 ifs/include/extern/spdlog/sinks/rotating_file_sink.h create mode 100644 ifs/include/extern/spdlog/sinks/stdout_color_sinks.h create mode 100644 ifs/include/extern/spdlog/version.h diff --git a/ifs/CMakeLists.txt b/ifs/CMakeLists.txt index 84615a568..0b083d322 100644 --- a/ifs/CMakeLists.txt +++ b/ifs/CMakeLists.txt @@ -139,6 +139,12 @@ set_target_properties(RocksDB INTERFACE_INCLUDE_DIRECTORIES ${ROCKSDB_INCLUDE_DIRS} ) +add_library(spdlog INTERFACE) +# we cannot use target_include_directories with CMake < 3.11 +set_target_properties(spdlog + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include/extern" +) set(INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include") diff --git a/ifs/include/daemon/adafs_daemon.hpp b/ifs/include/daemon/adafs_daemon.hpp index 23660a680..2e4333906 100644 --- a/ifs/include/daemon/adafs_daemon.hpp +++ b/ifs/include/daemon/adafs_daemon.hpp @@ -4,13 +4,11 @@ // std libs #include +#include // adafs config #include #include -// third party libs -#include -#include // margo extern "C" { #include diff --git a/ifs/include/daemon/classes/fs_data.hpp b/ifs/include/daemon/classes/fs_data.hpp index 071f98e6d..3ed9218f5 100644 --- a/ifs/include/daemon/classes/fs_data.hpp +++ b/ifs/include/daemon/classes/fs_data.hpp @@ -10,6 +10,7 @@ class ChunkStorage; class Distributor; #include +#include #include //std::hash class FsData { diff --git a/ifs/include/extern/spdlog/async.h b/ifs/include/extern/spdlog/async.h new file mode 100644 index 000000000..9c3e95512 --- /dev/null +++ b/ifs/include/extern/spdlog/async.h @@ -0,0 +1,87 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Async logging using global thread pool +// All loggers created here share same global thread pool. +// Each log message is pushed to a queue along withe a shared pointer to the +// logger. +// If a logger deleted while having pending messages in the queue, it's actual +// destruction will defer +// until all its messages are processed by the thread pool. +// This is because each message in the queue holds a shared_ptr to the +// originating logger. + +#include "spdlog/async_logger.h" +#include "spdlog/details/registry.h" +#include "spdlog/details/thread_pool.h" + +#include +#include + +namespace spdlog { + +namespace details { +static const size_t default_async_q_size = 8192; +} + +// async logger factory - creates async loggers backed with thread pool. +// if a global thread pool doesn't already exist, create it with default queue +// size of 8192 items and single thread. +template +struct async_factory_impl +{ + template + static std::shared_ptr create(const std::string &logger_name, SinkArgs &&... args) + { + auto ®istry_inst = details::registry::instance(); + + // create global thread pool if not already exists.. + std::lock_guard tp_lock(registry_inst.tp_mutex()); + auto tp = registry_inst.get_tp(); + if (tp == nullptr) + { + tp = std::make_shared(details::default_async_q_size, 1); + registry_inst.set_tp(tp); + } + + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(logger_name, std::move(sink), std::move(tp), OverflowPolicy); + registry_inst.register_and_init(new_logger); + return new_logger; + } +}; + +using async_factory = async_factory_impl; +using async_factory_nonblock = async_factory_impl; + +template +inline std::shared_ptr create_async(const std::string &logger_name, SinkArgs &&... sink_args) +{ + return async_factory::create(logger_name, std::forward(sink_args)...); +} + +template +inline std::shared_ptr create_async_nb(const std::string &logger_name, SinkArgs &&... sink_args) +{ + return async_factory_nonblock::create(logger_name, std::forward(sink_args)...); +} + +// set global thread pool. +inline void init_thread_pool(size_t q_size, size_t thread_count) +{ + auto tp = std::make_shared(q_size, thread_count); + details::registry::instance().set_tp(std::move(tp)); +} + +// get the global thread pool. +inline std::shared_ptr thread_pool() +{ + return details::registry::instance().get_tp(); +} +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/async_logger.h b/ifs/include/extern/spdlog/async_logger.h index 77a65e3b9..3250f4ad3 100644 --- a/ifs/include/extern/spdlog/async_logger.h +++ b/ifs/include/extern/spdlog/async_logger.h @@ -5,73 +5,67 @@ #pragma once -// Very fast asynchronous logger (millions of logs per second on an average desktop) -// Uses pre allocated lockfree queue for maximum throughput even under large number of threads. +// Very fast asynchronous logger (millions of logs per second on an average +// desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large +// number of threads. // Creates a single back thread to pop messages from the queue and log them. // // Upon each log write the logger: // 1. Checks if its log level is enough to log the message -// 2. Push a new copy of the message to a queue (or block the caller until space is available in the queue) +// 2. Push a new copy of the message to a queue (or block the caller until +// space is available in the queue) // 3. will throw spdlog_ex upon log exceptions -// Upon destruction, logs all remaining messages in the queue before destructing.. +// Upon destruction, logs all remaining messages in the queue before +// destructing.. -#include -#include +#include "spdlog/common.h" +#include "spdlog/logger.h" #include -#include -#include #include +#include -namespace spdlog -{ +namespace spdlog { -namespace details +// Async overflow policy - block by default. +enum class async_overflow_policy { -class async_log_helper; + block, // Block until message can be enqueued + overrun_oldest // Discard oldest message in the queue if full when trying to + // add new item. +}; + +namespace details { +class thread_pool; } -class async_logger :public logger +class async_logger SPDLOG_FINAL : public std::enable_shared_from_this, public logger { + friend class details::thread_pool; + public: - template - async_logger(const std::string& name, - const It& begin, - const It& end, - size_t queue_size, - const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, - const std::function& worker_warmup_cb = nullptr, - const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), - const std::function& worker_teardown_cb = nullptr); + template + async_logger(std::string logger_name, const It &begin, const It &end, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); - async_logger(const std::string& logger_name, - sinks_init_list sinks, - size_t queue_size, - const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, - const std::function& worker_warmup_cb = nullptr, - const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), - const std::function& worker_teardown_cb = nullptr); + async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); - async_logger(const std::string& logger_name, - sink_ptr single_sink, - size_t queue_size, - const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, - const std::function& worker_warmup_cb = nullptr, - const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), - const std::function& worker_teardown_cb = nullptr); + async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); - //Wait for the queue to be empty, and flush synchronously - //Warning: this can potentialy last forever as we wait it to complete - void flush() override; protected: - void _sink_it(details::log_msg& msg) override; - void _set_formatter(spdlog::formatter_ptr msg_formatter) override; - void _set_pattern(const std::string& pattern) override; + void sink_it_(details::log_msg &msg) override; + void flush_() override; + + void backend_log_(details::log_msg &incoming_log_msg); + void backend_flush_(); private: - std::unique_ptr _async_log_helper; + std::weak_ptr thread_pool_; + async_overflow_policy overflow_policy_; }; -} - +} // namespace spdlog -#include +#include "details/async_logger_impl.h" diff --git a/ifs/include/extern/spdlog/common.h b/ifs/include/extern/spdlog/common.h index 9b35ad0d3..1542fa920 100644 --- a/ifs/include/extern/spdlog/common.h +++ b/ifs/include/extern/spdlog/common.h @@ -5,22 +5,25 @@ #pragma once -#include -#include +#include "spdlog/tweakme.h" + +#include #include +#include +#include #include -#include -#include -#include +#include +#include +#include -#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) #include #include #endif -#include +#include "spdlog/details/null_mutex.h" -//visual studio upto 2013 does not support noexcept nor constexpr +// visual studio upto 2013 does not support noexcept nor constexpr #if defined(_MSC_VER) && (_MSC_VER < 1900) #define SPDLOG_NOEXCEPT throw() #define SPDLOG_CONSTEXPR @@ -29,7 +32,14 @@ #define SPDLOG_CONSTEXPR constexpr #endif -#if defined(__GNUC__) || defined(__clang__) +// final keyword support. On by default. See tweakme.h +#if defined(SPDLOG_NO_FINAL) +#define SPDLOG_FINAL +#else +#define SPDLOG_FINAL final +#endif + +#if defined(__GNUC__) || defined(__clang__) #define SPDLOG_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) #define SPDLOG_DEPRECATED __declspec(deprecated) @@ -37,35 +47,30 @@ #define SPDLOG_DEPRECATED #endif +#include "spdlog/fmt/fmt.h" -#include - -namespace spdlog -{ +namespace spdlog { class formatter; -namespace sinks -{ +namespace sinks { class sink; } using log_clock = std::chrono::system_clock; -using sink_ptr = std::shared_ptr < sinks::sink >; -using sinks_init_list = std::initializer_list < sink_ptr >; -using formatter_ptr = std::shared_ptr; +using sink_ptr = std::shared_ptr; +using sinks_init_list = std::initializer_list; +using log_err_handler = std::function; + #if defined(SPDLOG_NO_ATOMIC_LEVELS) using level_t = details::null_atomic_int; #else using level_t = std::atomic; #endif -using log_err_handler = std::function; - -//Log level enum -namespace level -{ -typedef enum +// Log level enum +namespace level { +enum level_enum { trace = 0, debug = 1, @@ -74,60 +79,81 @@ typedef enum err = 4, critical = 5, off = 6 -} level_enum; +}; -static const char* level_names[] { "trace", "debug", "info", "warning", "error", "critical", "off" }; +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES \ + { \ + "trace", "debug", "info", "warning", "error", "critical", "off" \ + } +#endif +static const char *level_names[] SPDLOG_LEVEL_NAMES; -static const char* short_level_names[] { "T", "D", "I", "W", "E", "C", "O" }; +static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"}; -inline const char* to_str(spdlog::level::level_enum l) +inline const char *to_c_str(spdlog::level::level_enum l) { return level_names[l]; } -inline const char* to_short_str(spdlog::level::level_enum l) +inline const char *to_short_c_str(spdlog::level::level_enum l) { return short_level_names[l]; } -} //level +inline spdlog::level::level_enum from_str(const std::string &name) +{ + static std::unordered_map name_to_level = // map string->level + {{level_names[0], level::trace}, // trace + {level_names[1], level::debug}, // debug + {level_names[2], level::info}, // info + {level_names[3], level::warn}, // warn + {level_names[4], level::err}, // err + {level_names[5], level::critical}, // critical + {level_names[6], level::off}}; // off + + auto lvl_it = name_to_level.find(name); + return lvl_it != name_to_level.end() ? lvl_it->second : level::off; +} + +using level_hasher = std::hash; +} // namespace level // -// Async overflow policy - block by default. +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default // -enum class async_overflow_policy +enum class pattern_time_type { - block_retry, // Block / yield / sleep until message can be enqueued - discard_log_msg // Discard the message it enqueue fails + local, // log localtime + utc // log utc }; - // // Log exception // -namespace details -{ -namespace os -{ -std::string errno_str(int err_num); -} -} -class spdlog_ex: public std::exception +class spdlog_ex : public std::exception { public: - spdlog_ex(const std::string& msg):_msg(msg) - {} - spdlog_ex(const std::string& msg, int last_errno) + explicit spdlog_ex(const std::string &msg) + : msg_(msg) { - _msg = msg + ": " + details::os::errno_str(last_errno); } - const char* what() const SPDLOG_NOEXCEPT override + + spdlog_ex(const std::string &msg, int last_errno) { - return _msg.c_str(); + fmt::memory_buffer outbuf; + fmt::format_system_error(outbuf, last_errno, msg); + msg_ = fmt::to_string(outbuf); } -private: - std::string _msg; + const char *what() const SPDLOG_NOEXCEPT override + { + return msg_.c_str(); + } + +private: + std::string msg_; }; // @@ -139,5 +165,13 @@ using filename_t = std::wstring; using filename_t = std::string; #endif - -} //spdlog +#define SPDLOG_CATCH_AND_HANDLE \ + catch (const std::exception &ex) \ + { \ + err_handler_(ex.what()); \ + } \ + catch (...) \ + { \ + err_handler_("Unknown exeption in logger"); \ + } +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/async_log_helper.h b/ifs/include/extern/spdlog/details/async_log_helper.h deleted file mode 100644 index 69ebf5c44..000000000 --- a/ifs/include/extern/spdlog/details/async_log_helper.h +++ /dev/null @@ -1,378 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -// async log helper : -// Process logs asynchronously using a back thread. -// -// If the internal queue of log messages reaches its max size, -// then the client call will block until there is more room. -// -// If the back thread throws during logging, a spdlog::spdlog_ex exception -// will be thrown in client's thread when tries to log the next message - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace spdlog -{ -namespace details -{ - -class async_log_helper -{ - // Async msg to move to/from the queue - // Movable only. should never be copied - enum class async_msg_type - { - log, - flush, - terminate - }; - struct async_msg - { - std::string logger_name; - level::level_enum level; - log_clock::time_point time; - size_t thread_id; - std::string txt; - async_msg_type msg_type; - - async_msg() = default; - ~async_msg() = default; - - -async_msg(async_msg&& other) SPDLOG_NOEXCEPT: - logger_name(std::move(other.logger_name)), - level(std::move(other.level)), - time(std::move(other.time)), - txt(std::move(other.txt)), - msg_type(std::move(other.msg_type)) - {} - - async_msg(async_msg_type m_type) :msg_type(m_type) - {} - - async_msg& operator=(async_msg&& other) SPDLOG_NOEXCEPT - { - logger_name = std::move(other.logger_name); - level = other.level; - time = std::move(other.time); - thread_id = other.thread_id; - txt = std::move(other.txt); - msg_type = other.msg_type; - return *this; - } - - // never copy or assign. should only be moved.. - async_msg(const async_msg&) = delete; - async_msg& operator=(const async_msg& other) = delete; - - // construct from log_msg - async_msg(const details::log_msg& m) : - level(m.level), - time(m.time), - thread_id(m.thread_id), - txt(m.raw.data(), m.raw.size()), - msg_type(async_msg_type::log) - { -#ifndef SPDLOG_NO_NAME - logger_name = *m.logger_name; -#endif - } - - - // copy into log_msg - void fill_log_msg(log_msg &msg) - { - msg.logger_name = &logger_name; - msg.level = level; - msg.time = time; - msg.thread_id = thread_id; - msg.raw << txt; - } - }; - -public: - - using item_type = async_msg; - using q_type = details::mpmc_bounded_queue; - - using clock = std::chrono::steady_clock; - - - async_log_helper(formatter_ptr formatter, - const std::vector& sinks, - size_t queue_size, - const log_err_handler err_handler, - const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, - const std::function& worker_warmup_cb = nullptr, - const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), - const std::function& worker_teardown_cb = nullptr); - - void log(const details::log_msg& msg); - - // stop logging and join the back thread - ~async_log_helper(); - - void set_formatter(formatter_ptr); - - void flush(bool wait_for_q); - - -private: - formatter_ptr _formatter; - std::vector> _sinks; - - // queue of messages to log - q_type _q; - - log_err_handler _err_handler; - - bool _flush_requested; - - bool _terminate_requested; - - - // overflow policy - const async_overflow_policy _overflow_policy; - - // worker thread warmup callback - one can set thread priority, affinity, etc - const std::function _worker_warmup_cb; - - // auto periodic sink flush parameter - const std::chrono::milliseconds _flush_interval_ms; - - // worker thread teardown callback - const std::function _worker_teardown_cb; - - // worker thread - std::thread _worker_thread; - - void push_msg(async_msg&& new_msg); - - // worker thread main loop - void worker_loop(); - - // pop next message from the queue and process it. will set the last_pop to the pop time - // return false if termination of the queue is required - bool process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush); - - void handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush); - - // sleep,yield or return immediatly using the time passed since last message as a hint - static void sleep_or_yield(const spdlog::log_clock::time_point& now, const log_clock::time_point& last_op_time); - - // wait until the queue is empty - void wait_empty_q(); - -}; -} -} - -/////////////////////////////////////////////////////////////////////////////// -// async_sink class implementation -/////////////////////////////////////////////////////////////////////////////// -inline spdlog::details::async_log_helper::async_log_helper( - formatter_ptr formatter, - const std::vector& sinks, - size_t queue_size, - log_err_handler err_handler, - const async_overflow_policy overflow_policy, - const std::function& worker_warmup_cb, - const std::chrono::milliseconds& flush_interval_ms, - const std::function& worker_teardown_cb): - _formatter(formatter), - _sinks(sinks), - _q(queue_size), - _err_handler(err_handler), - _flush_requested(false), - _terminate_requested(false), - _overflow_policy(overflow_policy), - _worker_warmup_cb(worker_warmup_cb), - _flush_interval_ms(flush_interval_ms), - _worker_teardown_cb(worker_teardown_cb), - _worker_thread(&async_log_helper::worker_loop, this) -{} - -// Send to the worker thread termination message(level=off) -// and wait for it to finish gracefully -inline spdlog::details::async_log_helper::~async_log_helper() -{ - try - { - push_msg(async_msg(async_msg_type::terminate)); - _worker_thread.join(); - } - catch (...) // don't crash in destructor - {} -} - - -//Try to push and block until succeeded (if the policy is not to discard when the queue is full) -inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) -{ - push_msg(async_msg(msg)); -} - -inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) -{ - if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg) - { - auto last_op_time = details::os::now(); - auto now = last_op_time; - do - { - now = details::os::now(); - sleep_or_yield(now, last_op_time); - } - while (!_q.enqueue(std::move(new_msg))); - } -} - -// optionally wait for the queue be empty and request flush from the sinks -inline void spdlog::details::async_log_helper::flush(bool wait_for_q) -{ - push_msg(async_msg(async_msg_type::flush)); - if(wait_for_q) - wait_empty_q(); //return only make after the above flush message was processed -} - -inline void spdlog::details::async_log_helper::worker_loop() -{ - try - { - if (_worker_warmup_cb) _worker_warmup_cb(); - auto last_pop = details::os::now(); - auto last_flush = last_pop; - while(process_next_msg(last_pop, last_flush)); - if (_worker_teardown_cb) _worker_teardown_cb(); - } - catch (const std::exception &ex) - { - _err_handler(ex.what()); - } - catch (...) - { - _err_handler("Unknown exception"); - } -} - -// process next message in the queue -// return true if this thread should still be active (while no terminate msg was received) -inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush) -{ - async_msg incoming_async_msg; - - if (_q.dequeue(incoming_async_msg)) - { - last_pop = details::os::now(); - switch (incoming_async_msg.msg_type) - { - case async_msg_type::flush: - _flush_requested = true; - break; - - case async_msg_type::terminate: - _flush_requested = true; - _terminate_requested = true; - break; - - default: - log_msg incoming_log_msg; - incoming_async_msg.fill_log_msg(incoming_log_msg); - _formatter->format(incoming_log_msg); - for (auto &s : _sinks) - { - if(s->should_log( incoming_log_msg.level)) - { - s->log(incoming_log_msg); - } - } - } - return true; - } - - // Handle empty queue.. - // This is the only place where the queue can terminate or flush to avoid losing messages already in the queue - else - { - auto now = details::os::now(); - handle_flush_interval(now, last_flush); - sleep_or_yield(now, last_pop); - return !_terminate_requested; - } -} - -// flush all sinks if _flush_interval_ms has expired -inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush) -{ - auto should_flush = _flush_requested || (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms); - if (should_flush) - { - for (auto &s : _sinks) - s->flush(); - now = last_flush = details::os::now(); - _flush_requested = false; - } -} - -inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter) -{ - _formatter = msg_formatter; -} - - -// spin, yield or sleep. use the time passed since last message as a hint -inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_clock::time_point& now, const spdlog::log_clock::time_point& last_op_time) -{ - using namespace std::this_thread; - using std::chrono::milliseconds; - using std::chrono::microseconds; - - auto time_since_op = now - last_op_time; - - // spin upto 50 micros - if (time_since_op <= microseconds(50)) - return; - - // yield upto 150 micros - if (time_since_op <= microseconds(100)) - return std::this_thread::yield(); - - // sleep for 20 ms upto 200 ms - if (time_since_op <= milliseconds(200)) - return sleep_for(milliseconds(20)); - - // sleep for 200 ms - return sleep_for(milliseconds(200)); -} - -// wait for the queue to be empty -inline void spdlog::details::async_log_helper::wait_empty_q() -{ - auto last_op = details::os::now(); - while (_q.approx_size() > 0) - { - sleep_or_yield(details::os::now(), last_op); - } -} - - - diff --git a/ifs/include/extern/spdlog/details/async_logger_impl.h b/ifs/include/extern/spdlog/details/async_logger_impl.h index bd701c3d9..47659ddc8 100644 --- a/ifs/include/extern/spdlog/details/async_logger_impl.h +++ b/ifs/include/extern/spdlog/details/async_logger_impl.h @@ -5,85 +5,96 @@ #pragma once -// Async Logger implementation -// Use an async_sink (queue per logger) to perform the logging in a worker thread +// async logger implementation +// uses a thread pool to perform the actual logging -#include -#include +#include "spdlog/details/thread_pool.h" -#include -#include #include #include +#include -template -inline spdlog::async_logger::async_logger(const std::string& logger_name, - const It& begin, - const It& end, - size_t queue_size, - const async_overflow_policy overflow_policy, - const std::function& worker_warmup_cb, - const std::chrono::milliseconds& flush_interval_ms, - const std::function& worker_teardown_cb) : - logger(logger_name, begin, end), - _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size, _err_handler, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb)) +template +inline spdlog::async_logger::async_logger( + std::string logger_name, const It &begin, const It &end, std::weak_ptr tp, async_overflow_policy overflow_policy) + : logger(std::move(logger_name), begin, end) + , thread_pool_(tp) + , overflow_policy_(overflow_policy) { } -inline spdlog::async_logger::async_logger(const std::string& logger_name, - sinks_init_list sinks_list, - size_t queue_size, - const async_overflow_policy overflow_policy, - const std::function& worker_warmup_cb, - const std::chrono::milliseconds& flush_interval_ms, - const std::function& worker_teardown_cb) : - async_logger(logger_name, sinks_list.begin(), sinks_list.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} - -inline spdlog::async_logger::async_logger(const std::string& logger_name, - sink_ptr single_sink, - size_t queue_size, - const async_overflow_policy overflow_policy, - const std::function& worker_warmup_cb, - const std::chrono::milliseconds& flush_interval_ms, - const std::function& worker_teardown_cb) : - async_logger(logger_name, +inline spdlog::async_logger::async_logger( + std::string logger_name, sinks_init_list sinks_list, std::weak_ptr tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), tp, overflow_policy) { - single_sink -}, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} - +} -inline void spdlog::async_logger::flush() +inline spdlog::async_logger::async_logger( + std::string logger_name, sink_ptr single_sink, std::weak_ptr tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), {single_sink}, tp, overflow_policy) { - _async_log_helper->flush(true); } -inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) +// send the log message to the thread pool +inline void spdlog::async_logger::sink_it_(details::log_msg &msg) { - _formatter = msg_formatter; - _async_log_helper->set_formatter(_formatter); +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_log(shared_from_this(), std::move(msg), overflow_policy_); + } + else + { + throw spdlog_ex("async log: thread pool doesn't exist anymore"); + } } -inline void spdlog::async_logger::_set_pattern(const std::string& pattern) +// send flush request to the thread pool +inline void spdlog::async_logger::flush_() { - _formatter = std::make_shared(pattern); - _async_log_helper->set_formatter(_formatter); + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_flush(shared_from_this(), overflow_policy_); + } + else + { + throw spdlog_ex("async flush: thread pool doesn't exist anymore"); + } } - -inline void spdlog::async_logger::_sink_it(details::log_msg& msg) +// +// backend functions - called from the thread pool to do the actual job +// +inline void spdlog::async_logger::backend_log_(details::log_msg &incoming_log_msg) { try { - _async_log_helper->log(msg); - if (_should_flush_on(msg)) - _async_log_helper->flush(false); // do async flush + for (auto &s : sinks_) + { + if (s->should_log(incoming_log_msg.level)) + { + s->log(incoming_log_msg); + } + } } - catch (const std::exception &ex) + SPDLOG_CATCH_AND_HANDLE + + if (should_flush_(incoming_log_msg)) { - _err_handler(ex.what()); + backend_flush_(); } - catch (...) +} + +inline void spdlog::async_logger::backend_flush_() +{ + try { - _err_handler("Unknown exception"); + for (auto &sink : sinks_) + { + sink->flush(); + } } + SPDLOG_CATCH_AND_HANDLE } diff --git a/ifs/include/extern/spdlog/details/circular_q.h b/ifs/include/extern/spdlog/details/circular_q.h new file mode 100644 index 000000000..b01325bb7 --- /dev/null +++ b/ifs/include/extern/spdlog/details/circular_q.h @@ -0,0 +1,72 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// cirucal q view of std::vector. +#pragma once + +#include + +namespace spdlog { +namespace details { +template +class circular_q +{ +public: + using item_type = T; + + explicit circular_q(size_t max_items) + : max_items_(max_items + 1) // one item is reserved as marker for full q + , v_(max_items_) + { + } + + // push back, overrun (oldest) item if no room left + void push_back(T &&item) + { + v_[tail_] = std::move(item); + tail_ = (tail_ + 1) % max_items_; + + if (tail_ == head_) // overrun last item if full + { + head_ = (head_ + 1) % max_items_; + ++overrun_counter_; + } + } + + // Pop item from front. + // If there are no elements in the container, the behavior is undefined. + void pop_front(T &popped_item) + { + popped_item = std::move(v_[head_]); + head_ = (head_ + 1) % max_items_; + } + + bool empty() + { + return tail_ == head_; + } + + bool full() + { + // head is ahead of the tail by 1 + return ((tail_ + 1) % max_items_) == head_; + } + + size_t overrun_counter() const + { + return overrun_counter_; + } + +private: + size_t max_items_; + typename std::vector::size_type head_ = 0; + typename std::vector::size_type tail_ = 0; + + std::vector v_; + + size_t overrun_counter_ = 0; +}; +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/console_globals.h b/ifs/include/extern/spdlog/details/console_globals.h new file mode 100644 index 000000000..4ac7f7437 --- /dev/null +++ b/ifs/include/extern/spdlog/details/console_globals.h @@ -0,0 +1,61 @@ +#pragma once +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "spdlog/details/null_mutex.h" +#include +#include + +namespace spdlog { +namespace details { +struct console_stdout +{ + static std::FILE *stream() + { + return stdout; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_OUTPUT_HANDLE); + } +#endif +}; + +struct console_stderr +{ + static std::FILE *stream() + { + return stderr; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_ERROR_HANDLE); + } +#endif +}; + +struct console_mutex +{ + using mutex_t = std::mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; + +struct console_nullmutex +{ + using mutex_t = null_mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/file_helper.h b/ifs/include/extern/spdlog/details/file_helper.h index e079cbb6f..65b3560fa 100644 --- a/ifs/include/extern/spdlog/details/file_helper.h +++ b/ifs/include/extern/spdlog/details/file_helper.h @@ -6,23 +6,22 @@ #pragma once // Helper class for file sink -// When failing to open a file, retry several times(5) with small delay between the tries(10 ms) -// Can be set to auto flush on every line +// When failing to open a file, retry several times(5) with small delay between +// the tries(10 ms) // Throw spdlog_ex exception on errors -#include -#include +#include "../details/log_msg.h" +#include "../details/os.h" +#include #include #include #include #include -#include +#include -namespace spdlog -{ -namespace details -{ +namespace spdlog { +namespace details { class file_helper { @@ -31,31 +30,29 @@ public: const int open_tries = 5; const int open_interval = 10; - explicit file_helper() : - _fd(nullptr) - {} + explicit file_helper() = default; - file_helper(const file_helper&) = delete; - file_helper& operator=(const file_helper&) = delete; + file_helper(const file_helper &) = delete; + file_helper &operator=(const file_helper &) = delete; ~file_helper() { close(); } - - void open(const filename_t& fname, bool truncate = false) + void open(const filename_t &fname, bool truncate = false) { - close(); auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); _filename = fname; for (int tries = 0; tries < open_tries; ++tries) { - if (!os::fopen_s(&_fd, fname, mode)) + if (!os::fopen_s(&fd_, fname, mode)) + { return; + } - std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); + details::os::sleep_for_millis(open_interval); } throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); @@ -64,55 +61,93 @@ public: void reopen(bool truncate) { if (_filename.empty()) + { throw spdlog_ex("Failed re opening file - was not opened before"); + } open(_filename, truncate); - } void flush() { - std::fflush(_fd); + std::fflush(fd_); } void close() { - if (_fd) + if (fd_ != nullptr) { - std::fclose(_fd); - _fd = nullptr; + std::fclose(fd_); + fd_ = nullptr; } } - void write(const log_msg& msg) + void write(const fmt::memory_buffer &buf) { - - size_t msg_size = msg.formatted.size(); - auto data = msg.formatted.data(); - if (std::fwrite(data, 1, msg_size, _fd) != msg_size) + size_t msg_size = buf.size(); + auto data = buf.data(); + if (std::fwrite(data, 1, msg_size, fd_) != msg_size) + { throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + } } - size_t size() + size_t size() const { - if (!_fd) + if (fd_ == nullptr) + { throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); - return os::filesize(_fd); + } + return os::filesize(fd_); } - const filename_t& filename() const + const filename_t &filename() const { return _filename; } - static bool file_exists(const filename_t& name) + static bool file_exists(const filename_t &fname) + { + return os::file_exists(fname); + } + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple split_by_extenstion(const spdlog::filename_t &fname) { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as + // extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } + + // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.rfind(details::os::folder_sep); + if (folder_index != filename_t::npos && folder_index >= ext_index - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } - return os::file_exists(name); + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); } private: - FILE* _fd; + FILE *fd_{nullptr}; filename_t _filename; }; -} -} +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/fmt_helper.h b/ifs/include/extern/spdlog/details/fmt_helper.h new file mode 100644 index 000000000..eabf93b5d --- /dev/null +++ b/ifs/include/extern/spdlog/details/fmt_helper.h @@ -0,0 +1,130 @@ +// +// Created by gabi on 6/15/18. +// + +#pragma once + +#include "chrono" +#include "spdlog/fmt/fmt.h" + +// Some fmt helpers to efficiently format and pad ints and strings +namespace spdlog { +namespace details { +namespace fmt_helper { + +template +inline void append_str(const std::string &str, fmt::basic_memory_buffer &dest) +{ + auto *str_ptr = str.data(); + dest.append(str_ptr, str_ptr + str.size()); +} + +template +inline void append_c_str(const char *c_str, fmt::basic_memory_buffer &dest) +{ + char ch; + while ((ch = *c_str) != '\0') + { + dest.push_back(ch); + ++c_str; + } +} + +template +inline void append_buf(const fmt::basic_memory_buffer &buf, fmt::basic_memory_buffer &dest) +{ + auto *buf_ptr = buf.data(); + dest.append(buf_ptr, buf_ptr + buf.size()); +} + +template +inline void append_int(T n, fmt::basic_memory_buffer &dest) +{ + fmt::format_int i(n); + dest.append(i.data(), i.data() + i.size()); +} + +template +inline void pad2(int n, fmt::basic_memory_buffer &dest) +{ + if (n > 99) + { + append_int(n, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back('0' + static_cast(n / 10)); + dest.push_back('0' + static_cast(n % 10)); + return; + } + if (n >= 0) // 0-9 + { + dest.push_back('0'); + dest.push_back('0' + static_cast(n)); + return; + } + // negatives (unlikely, but just in case, let fmt deal with it) + fmt::format_to(dest, "{:02}", n); +} + +template +inline void pad3(int n, fmt::basic_memory_buffer &dest) +{ + if (n > 999) + { + append_int(n, dest); + return; + } + + if (n > 99) // 100-999 + { + append_int(n / 100, dest); + pad2(n % 100, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back('0'); + dest.push_back('0' + static_cast(n / 10)); + dest.push_back('0' + static_cast(n % 10)); + return; + } + if (n >= 0) + { + dest.push_back('0'); + dest.push_back('0'); + dest.push_back('0' + static_cast(n)); + return; + } + // negatives (unlikely, but just in case let fmt deal with it) + fmt::format_to(dest, "{:03}", n); +} + +template +inline void pad6(size_t n, fmt::basic_memory_buffer &dest) +{ + if (n > 99999) + { + append_int(n, dest); + return; + } + pad3(static_cast(n / 1000), dest); + pad3(static_cast(n % 1000), dest); +} + +// return fraction of a second of the given time_point. +// e.g. +// fraction(tp) -> will return the millis part of the second +template +inline ToDuration time_fraction(const log_clock::time_point &tp) +{ + using namespace std::chrono; + auto duration = tp.time_since_epoch(); + auto secs = duration_cast(duration); + return duration_cast(duration) - duration_cast(secs); +} + +} // namespace fmt_helper +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/log_msg.h b/ifs/include/extern/spdlog/details/log_msg.h index c0c42f812..3272dd5d5 100644 --- a/ifs/include/extern/spdlog/details/log_msg.h +++ b/ifs/include/extern/spdlog/details/log_msg.h @@ -5,42 +5,44 @@ #pragma once -#include -#include - +#include "spdlog/common.h" +#include "spdlog/details/os.h" #include #include -namespace spdlog -{ -namespace details -{ +namespace spdlog { +namespace details { struct log_msg { log_msg() = default; - log_msg(const std::string *loggers_name, level::level_enum lvl) : logger_name(loggers_name), level(lvl) - { + + log_msg(const std::string *loggers_name, level::level_enum lvl) + : logger_name(loggers_name) + , level(lvl) #ifndef SPDLOG_NO_DATETIME - time = os::now(); + , time(os::now()) #endif #ifndef SPDLOG_NO_THREAD_ID - thread_id = os::thread_id(); + , thread_id(os::thread_id()) #endif + { } - log_msg(const log_msg& other) = delete; - log_msg& operator=(log_msg&& other) = delete; - log_msg(log_msg&& other) = delete; - + log_msg(const log_msg &other) = delete; + log_msg(log_msg &&other) = delete; + log_msg &operator=(log_msg &&other) = delete; - const std::string *logger_name; + const std::string *logger_name{nullptr}; level::level_enum level; log_clock::time_point time; size_t thread_id; - fmt::MemoryWriter raw; - fmt::MemoryWriter formatted; + fmt::memory_buffer raw; + size_t msg_id{0}; + // info about wrapping the formatted text with color + mutable size_t color_range_start{0}; + mutable size_t color_range_end{0}; }; -} -} +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/logger_impl.h b/ifs/include/extern/spdlog/details/logger_impl.h index c67c5dc56..0a7c5b6bf 100644 --- a/ifs/include/extern/spdlog/details/logger_impl.h +++ b/ifs/include/extern/spdlog/details/logger_impl.h @@ -5,294 +5,341 @@ #pragma once -#include -#include - #include #include - // create logger with given name, sinks and the default pattern formatter // all other ctors will call this one -template -inline spdlog::logger::logger(const std::string& logger_name, const It& begin, const It& end): - _name(logger_name), - _sinks(begin, end), - _formatter(std::make_shared("%+")) -{ - _level = level::info; - _flush_level = level::off; - _last_err_time = 0; - _err_handler = [this](const std::string &msg) - { - this->_default_err_handler(msg); - }; +template +inline spdlog::logger::logger(std::string logger_name, const It &begin, const It &end) + : name_(std::move(logger_name)) + , sinks_(begin, end) + , level_(level::info) + , flush_level_(level::off) + , last_err_time_(0) + , msg_counter_(1) // message counter will start from 1. 0-message id will be + // reserved for controll messages +{ + err_handler_ = [this](const std::string &msg) { this->default_err_handler_(msg); }; } // ctor with sinks as init list -inline spdlog::logger::logger(const std::string& logger_name, sinks_init_list sinks_list): - logger(logger_name, sinks_list.begin(), sinks_list.end()) -{} - +inline spdlog::logger::logger(std::string logger_name, sinks_init_list sinks_list) + : logger(std::move(logger_name), sinks_list.begin(), sinks_list.end()) +{ +} // ctor with single sink -inline spdlog::logger::logger(const std::string& logger_name, spdlog::sink_ptr single_sink): - logger(logger_name, +inline spdlog::logger::logger(std::string logger_name, spdlog::sink_ptr single_sink) + : logger(std::move(logger_name), {std::move(single_sink)}) { - single_sink -}) -{} - +} inline spdlog::logger::~logger() = default; - -inline void spdlog::logger::set_formatter(spdlog::formatter_ptr msg_formatter) +inline void spdlog::logger::set_formatter(std::unique_ptr f) { - _set_formatter(msg_formatter); + for (auto &sink : sinks_) + { + sink->set_formatter(f->clone()); + } } -inline void spdlog::logger::set_pattern(const std::string& pattern) +inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type) { - _set_pattern(pattern); + set_formatter(std::unique_ptr(new pattern_formatter(std::move(pattern), time_type))); } - -template -inline void spdlog::logger::log(level::level_enum lvl, const char* fmt, const Args&... args) +template +inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args) { - if (!should_log(lvl)) return; - - try - { - details::log_msg log_msg(&_name, lvl); - log_msg.raw.write(fmt, args...); - _sink_it(log_msg); - } - catch (const std::exception &ex) + if (!should_log(lvl)) { - _err_handler(ex.what()); + return; } - catch (...) + + try { - _err_handler("Unknown exception"); + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, fmt, args...); + sink_it_(log_msg); } + SPDLOG_CATCH_AND_HANDLE } -template -inline void spdlog::logger::log(level::level_enum lvl, const char* msg) +template +inline void spdlog::logger::log(level::level_enum lvl, const char *msg) { - if (!should_log(lvl)) return; - try - { - details::log_msg log_msg(&_name, lvl); - log_msg.raw << msg; - _sink_it(log_msg); - } - catch (const std::exception &ex) + if (!should_log(lvl)) { - _err_handler(ex.what()); + return; } - catch (...) + try { - _err_handler("Unknown exception"); + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, "{}", msg); + sink_it_(log_msg); } - + SPDLOG_CATCH_AND_HANDLE } template -inline void spdlog::logger::log(level::level_enum lvl, const T& msg) +inline void spdlog::logger::log(level::level_enum lvl, const T &msg) { - if (!should_log(lvl)) return; - try - { - details::log_msg log_msg(&_name, lvl); - log_msg.raw << msg; - _sink_it(log_msg); - } - catch (const std::exception &ex) + if (!should_log(lvl)) { - _err_handler(ex.what()); + return; } - catch (...) + try { - _err_handler("Unknown exception"); + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, "{}", msg); + sink_it_(log_msg); } + SPDLOG_CATCH_AND_HANDLE } - -template -inline void spdlog::logger::trace(const char* fmt, const Args&... args) +template +inline void spdlog::logger::trace(const char *fmt, const Args &... args) { log(level::trace, fmt, args...); } -template -inline void spdlog::logger::debug(const char* fmt, const Args&... args) +template +inline void spdlog::logger::debug(const char *fmt, const Args &... args) { log(level::debug, fmt, args...); } -template -inline void spdlog::logger::info(const char* fmt, const Args&... args) +template +inline void spdlog::logger::info(const char *fmt, const Args &... args) { log(level::info, fmt, args...); } - -template -inline void spdlog::logger::warn(const char* fmt, const Args&... args) +template +inline void spdlog::logger::warn(const char *fmt, const Args &... args) { log(level::warn, fmt, args...); } -template -inline void spdlog::logger::error(const char* fmt, const Args&... args) +template +inline void spdlog::logger::error(const char *fmt, const Args &... args) { log(level::err, fmt, args...); } -template -inline void spdlog::logger::critical(const char* fmt, const Args&... args) +template +inline void spdlog::logger::critical(const char *fmt, const Args &... args) { log(level::critical, fmt, args...); } - template -inline void spdlog::logger::trace(const T& msg) +inline void spdlog::logger::trace(const T &msg) { log(level::trace, msg); } template -inline void spdlog::logger::debug(const T& msg) +inline void spdlog::logger::debug(const T &msg) { log(level::debug, msg); } - template -inline void spdlog::logger::info(const T& msg) +inline void spdlog::logger::info(const T &msg) { log(level::info, msg); } - template -inline void spdlog::logger::warn(const T& msg) +inline void spdlog::logger::warn(const T &msg) { log(level::warn, msg); } template -inline void spdlog::logger::error(const T& msg) +inline void spdlog::logger::error(const T &msg) { log(level::err, msg); } template -inline void spdlog::logger::critical(const T& msg) +inline void spdlog::logger::critical(const T &msg) { log(level::critical, msg); } +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) +{ + if (!should_log(lvl)) + { + return; + } + decltype(wstring_converter_)::byte_string utf8_string; + try + { + { + std::lock_guard lock(wstring_converter_mutex_); + utf8_string = wstring_converter_.to_bytes(fmt); + } + log(lvl, utf8_string.c_str(), args...); + } + SPDLOG_CATCH_AND_HANDLE +} + +template +inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args) +{ + log(level::trace, fmt, args...); +} + +template +inline void spdlog::logger::debug(const wchar_t *fmt, const Args &... args) +{ + log(level::debug, fmt, args...); +} + +template +inline void spdlog::logger::info(const wchar_t *fmt, const Args &... args) +{ + log(level::info, fmt, args...); +} + +template +inline void spdlog::logger::warn(const wchar_t *fmt, const Args &... args) +{ + log(level::warn, fmt, args...); +} + +template +inline void spdlog::logger::error(const wchar_t *fmt, const Args &... args) +{ + log(level::err, fmt, args...); +} + +template +inline void spdlog::logger::critical(const wchar_t *fmt, const Args &... args) +{ + log(level::critical, fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT // // name and level // -inline const std::string& spdlog::logger::name() const +inline const std::string &spdlog::logger::name() const { - return _name; + return name_; } inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) { - _level.store(log_level); + level_.store(log_level); } inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) { - _err_handler = err_handler; + err_handler_ = std::move(err_handler); } inline spdlog::log_err_handler spdlog::logger::error_handler() { - return _err_handler; + return err_handler_; } +inline void spdlog::logger::flush() +{ + try + { + flush_(); + } + SPDLOG_CATCH_AND_HANDLE +} inline void spdlog::logger::flush_on(level::level_enum log_level) { - _flush_level.store(log_level); + flush_level_.store(log_level); +} + +inline bool spdlog::logger::should_flush_(const details::log_msg &msg) +{ + auto flush_level = flush_level_.load(std::memory_order_relaxed); + return (msg.level >= flush_level) && (msg.level != level::off); } inline spdlog::level::level_enum spdlog::logger::level() const { - return static_cast(_level.load(std::memory_order_relaxed)); + return static_cast(level_.load(std::memory_order_relaxed)); } inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const { - return msg_level >= _level.load(std::memory_order_relaxed); + return msg_level >= level_.load(std::memory_order_relaxed); } // -// protected virtual called at end of each user log call (if enabled) by the line_logger +// protected virtual called at end of each user log call (if enabled) by the +// line_logger // -inline void spdlog::logger::_sink_it(details::log_msg& msg) +inline void spdlog::logger::sink_it_(details::log_msg &msg) { - _formatter->format(msg); - for (auto &sink : _sinks) +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + for (auto &sink : sinks_) { - if( sink->should_log( msg.level)) + if (sink->should_log(msg.level)) { sink->log(msg); } } - if(_should_flush_on(msg)) + if (should_flush_(msg)) + { flush(); + } } -inline void spdlog::logger::_set_pattern(const std::string& pattern) -{ - _formatter = std::make_shared(pattern); -} -inline void spdlog::logger::_set_formatter(formatter_ptr msg_formatter) -{ - _formatter = msg_formatter; -} - -inline void spdlog::logger::flush() +inline void spdlog::logger::flush_() { - for (auto& sink : _sinks) + for (auto &sink : sinks_) + { sink->flush(); + } } -inline void spdlog::logger::_default_err_handler(const std::string &msg) +inline void spdlog::logger::default_err_handler_(const std::string &msg) { auto now = time(nullptr); - if (now - _last_err_time < 60) + if (now - last_err_time_ < 60) + { return; + } + last_err_time_ = now; auto tm_time = details::os::localtime(now); char date_buf[100]; std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); - details::log_msg err_msg; - err_msg.formatted.write("[*** LOG ERROR ***] [{}] [{}] [{}]{}", name(), msg, date_buf, details::os::eol); - sinks::stderr_sink_mt::instance()->log(err_msg); - _last_err_time = now; + fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg); } -inline bool spdlog::logger::_should_flush_on(const details::log_msg &msg) +inline void spdlog::logger::incr_msg_counter_(details::log_msg &msg) { - const auto flush_level = _flush_level.load(std::memory_order_relaxed); - return (msg.level >= flush_level) && (msg.level != level::off); + msg.msg_id = msg_counter_.fetch_add(1, std::memory_order_relaxed); +} + +inline const std::vector &spdlog::logger::sinks() const +{ + return sinks_; } -inline const std::vector& spdlog::logger::sinks() const +inline std::vector &spdlog::logger::sinks() { - return _sinks; + return sinks_; } diff --git a/ifs/include/extern/spdlog/details/mpmc_blocking_q.h b/ifs/include/extern/spdlog/details/mpmc_blocking_q.h new file mode 100644 index 000000000..ca789fc66 --- /dev/null +++ b/ifs/include/extern/spdlog/details/mpmc_blocking_q.h @@ -0,0 +1,121 @@ +#pragma once + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// multi producer-multi consumer blocking queue. +// enqueue(..) - will block until room found to put the new message. +// enqueue_nowait(..) - will return immediately with false if no room left in +// the queue. +// dequeue_for(..) - will block until the queue is not empty or timeout have +// passed. + +#include "spdlog/details/circular_q.h" + +#include +#include + +namespace spdlog { +namespace details { + +template +class mpmc_blocking_queue +{ +public: + using item_type = T; + explicit mpmc_blocking_queue(size_t max_items) + : q_(max_items) + { + } + +#ifndef __MINGW32__ + // try to enqueue and block if no room left + void enqueue(T &&item) + { + { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + } + pop_cv_.notify_one(); + return true; + } + +#else + // apparently mingw deadlocks if the mutex is released before cv.notify_one(), + // so release the mutex at the very end each function. + + // try to enqueue and block if no room left + void enqueue(T &&item) + { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + pop_cv_.notify_one(); + return true; + } + +#endif + + size_t overrun_counter() + { + std::unique_lock lock(queue_mutex_); + return q_.overrun_counter(); + } + +private: + std::mutex queue_mutex_; + std::condition_variable push_cv_; + std::condition_variable pop_cv_; + spdlog::details::circular_q q_; +}; +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/mpmc_bounded_q.h b/ifs/include/extern/spdlog/details/mpmc_bounded_q.h deleted file mode 100644 index a6e2df0b0..000000000 --- a/ifs/include/extern/spdlog/details/mpmc_bounded_q.h +++ /dev/null @@ -1,172 +0,0 @@ -/* -A modified version of Bounded MPMC queue by Dmitry Vyukov. - -Original code from: -http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue - -licensed by Dmitry Vyukov under the terms below: - -Simplified BSD license - -Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved. -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this list of -conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list -of conditions and the following disclaimer in the documentation and/or other materials -provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the authors and -should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov. -*/ - -/* -The code in its current form adds the license below: - -Copyright(c) 2015 Gabi Melman. -Distributed under the MIT License (http://opensource.org/licenses/MIT) - -*/ - -#pragma once - -#include - -#include -#include - -namespace spdlog -{ -namespace details -{ - -template -class mpmc_bounded_queue -{ -public: - - using item_type = T; - mpmc_bounded_queue(size_t buffer_size) - :max_size_(buffer_size), - buffer_(new cell_t [buffer_size]), - buffer_mask_(buffer_size - 1) - { - //queue size must be power of two - if(!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0))) - throw spdlog_ex("async logger queue size must be power of two"); - - for (size_t i = 0; i != buffer_size; i += 1) - buffer_[i].sequence_.store(i, std::memory_order_relaxed); - enqueue_pos_.store(0, std::memory_order_relaxed); - dequeue_pos_.store(0, std::memory_order_relaxed); - } - - ~mpmc_bounded_queue() - { - delete [] buffer_; - } - - - bool enqueue(T&& data) - { - cell_t* cell; - size_t pos = enqueue_pos_.load(std::memory_order_relaxed); - for (;;) - { - cell = &buffer_[pos & buffer_mask_]; - size_t seq = cell->sequence_.load(std::memory_order_acquire); - intptr_t dif = (intptr_t)seq - (intptr_t)pos; - if (dif == 0) - { - if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) - break; - } - else if (dif < 0) - { - return false; - } - else - { - pos = enqueue_pos_.load(std::memory_order_relaxed); - } - } - cell->data_ = std::move(data); - cell->sequence_.store(pos + 1, std::memory_order_release); - return true; - } - - bool dequeue(T& data) - { - cell_t* cell; - size_t pos = dequeue_pos_.load(std::memory_order_relaxed); - for (;;) - { - cell = &buffer_[pos & buffer_mask_]; - size_t seq = - cell->sequence_.load(std::memory_order_acquire); - intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1); - if (dif == 0) - { - if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) - break; - } - else if (dif < 0) - return false; - else - pos = dequeue_pos_.load(std::memory_order_relaxed); - } - data = std::move(cell->data_); - cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release); - return true; - } - - size_t approx_size() - { - size_t first_pos = dequeue_pos_.load(std::memory_order_relaxed); - size_t last_pos = enqueue_pos_.load(std::memory_order_relaxed); - if (last_pos <= first_pos) - return 0; - auto size = last_pos - first_pos; - return size < max_size_ ? size : max_size_; - } - -private: - struct cell_t - { - std::atomic sequence_; - T data_; - }; - - size_t const max_size_; - - static size_t const cacheline_size = 64; - typedef char cacheline_pad_t [cacheline_size]; - - cacheline_pad_t pad0_; - cell_t* const buffer_; - size_t const buffer_mask_; - cacheline_pad_t pad1_; - std::atomic enqueue_pos_; - cacheline_pad_t pad2_; - std::atomic dequeue_pos_; - cacheline_pad_t pad3_; - - mpmc_bounded_queue(mpmc_bounded_queue const&) = delete; - void operator= (mpmc_bounded_queue const&) = delete; -}; - -} // ns details -} // ns spdlog diff --git a/ifs/include/extern/spdlog/details/null_mutex.h b/ifs/include/extern/spdlog/details/null_mutex.h index 67b0aeee0..3f495bd98 100644 --- a/ifs/include/extern/spdlog/details/null_mutex.h +++ b/ifs/include/extern/spdlog/details/null_mutex.h @@ -8,10 +8,8 @@ #include // null, no cost dummy "mutex" and dummy "atomic" int -namespace spdlog -{ -namespace details -{ +namespace spdlog { +namespace details { struct null_mutex { void lock() {} @@ -27,8 +25,10 @@ struct null_atomic_int int value; null_atomic_int() = default; - null_atomic_int(int val):value(val) - {} + explicit null_atomic_int(int val) + : value(val) + { + } int load(std::memory_order) const { @@ -41,5 +41,5 @@ struct null_atomic_int } }; -} -} +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/os.h b/ifs/include/extern/spdlog/details/os.h index 69f08fa1b..d7f7430ff 100644 --- a/ifs/include/extern/spdlog/details/os.h +++ b/ifs/include/extern/spdlog/details/os.h @@ -4,31 +4,32 @@ // #pragma once -#include +#include "../common.h" +#include +#include #include +#include +#include #include #include #include -#include -#include -#include -#include #include #include +#include #ifdef _WIN32 #ifndef NOMINMAX -#define NOMINMAX //prevent windows redefining min/max +#define NOMINMAX // prevent windows redefining min/max #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif -#include +#include // _get_osfhandle and _isatty support #include // _get_pid support -#include // _get_osfhandle support +#include #ifdef __MINGW32__ #include @@ -36,8 +37,8 @@ #else // unix -#include #include +#include #ifdef __linux__ #include //Use gettid() syscall under linux to get thread id @@ -46,19 +47,15 @@ #include //Use thr_self() syscall under FreeBSD to get thread id #endif -#endif //unix +#endif // unix -#ifndef __has_feature // Clang - feature checking macros. -#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. #endif - -namespace spdlog -{ -namespace details -{ -namespace os -{ +namespace spdlog { +namespace details { +namespace os { inline spdlog::log_clock::time_point now() { @@ -67,14 +64,11 @@ inline spdlog::log_clock::time_point now() timespec ts; ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); return std::chrono::time_point( - std::chrono::duration_cast( - std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); - + std::chrono::duration_cast(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); #else return log_clock::now(); #endif - } inline std::tm localtime(const std::time_t &time_tt) { @@ -95,7 +89,6 @@ inline std::tm localtime() return localtime(now_t); } - inline std::tm gmtime(const std::time_t &time_tt) { @@ -114,24 +107,19 @@ inline std::tm gmtime() std::time_t now_t = time(nullptr); return gmtime(now_t); } -inline bool operator==(const std::tm& tm1, const std::tm& tm2) +inline bool operator==(const std::tm &tm1, const std::tm &tm2) { - return (tm1.tm_sec == tm2.tm_sec && - tm1.tm_min == tm2.tm_min && - tm1.tm_hour == tm2.tm_hour && - tm1.tm_mday == tm2.tm_mday && - tm1.tm_mon == tm2.tm_mon && - tm1.tm_year == tm2.tm_year && - tm1.tm_isdst == tm2.tm_isdst); + return (tm1.tm_sec == tm2.tm_sec && tm1.tm_min == tm2.tm_min && tm1.tm_hour == tm2.tm_hour && tm1.tm_mday == tm2.tm_mday && + tm1.tm_mon == tm2.tm_mon && tm1.tm_year == tm2.tm_year && tm1.tm_isdst == tm2.tm_isdst); } -inline bool operator!=(const std::tm& tm1, const std::tm& tm2) +inline bool operator!=(const std::tm &tm1, const std::tm &tm2) { return !(tm1 == tm2); } // eol definition -#if !defined (SPDLOG_EOL) +#if !defined(SPDLOG_EOL) #ifdef _WIN32 #define SPDLOG_EOL "\r\n" #else @@ -139,25 +127,35 @@ inline bool operator!=(const std::tm& tm1, const std::tm& tm2) #endif #endif -SPDLOG_CONSTEXPR static const char* eol = SPDLOG_EOL; -SPDLOG_CONSTEXPR static int eol_size = sizeof(SPDLOG_EOL) - 1; +SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif inline void prevent_child_fd(FILE *f) { + #ifdef _WIN32 +#if !defined(__cplusplus_winrt) auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) throw spdlog_ex("SetHandleInformation failed", errno); +#endif #else auto fd = fileno(f); - if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + { throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); + } #endif } - -//fopen_s on non windows for writing -inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode) +// fopen_s on non windows for writing +inline bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) { #ifdef _WIN32 #ifdef SPDLOG_WCHAR_FILENAMES @@ -165,18 +163,19 @@ inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode #else *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); #endif -#else //unix +#else // unix *fp = fopen((filename.c_str()), mode.c_str()); #endif #ifdef SPDLOG_PREVENT_CHILD_FD - if(*fp != nullptr) + if (*fp != nullptr) + { prevent_child_fd(*fp); + } #endif return *fp == nullptr; } - inline int remove(const filename_t &filename) { #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) @@ -186,7 +185,7 @@ inline int remove(const filename_t &filename) #endif } -inline int rename(const filename_t& filename1, const filename_t& filename2) +inline int rename(const filename_t &filename1, const filename_t &filename2) { #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) return _wrename(filename1.c_str(), filename2.c_str()); @@ -195,9 +194,8 @@ inline int rename(const filename_t& filename1, const filename_t& filename2) #endif } - -//Return if file exists -inline bool file_exists(const filename_t& filename) +// Return if file exists +inline bool file_exists(const filename_t &filename) { #ifdef _WIN32 #ifdef SPDLOG_WCHAR_FILENAMES @@ -206,54 +204,59 @@ inline bool file_exists(const filename_t& filename) auto attribs = GetFileAttributesA(filename.c_str()); #endif return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); -#else //common linux/unix all have the stat system call +#else // common linux/unix all have the stat system call struct stat buffer; - return (stat (filename.c_str(), &buffer) == 0); + return (stat(filename.c_str(), &buffer) == 0); #endif } - - - -//Return file size according to open FILE* object +// Return file size according to open FILE* object inline size_t filesize(FILE *f) { if (f == nullptr) + { throw spdlog_ex("Failed getting file size. fd is null"); -#ifdef _WIN32 + } +#if defined(_WIN32) && !defined(__CYGWIN__) int fd = _fileno(f); -#if _WIN64 //64 bits +#if _WIN64 // 64 bits struct _stat64 st; if (_fstat64(fd, &st) == 0) + { return st.st_size; + } -#else //windows 32 bits +#else // windows 32 bits long ret = _filelength(fd); if (ret >= 0) + { return static_cast(ret); + } #endif #else // unix int fd = fileno(f); - //64 bits(but not in osx, where fstat64 is deprecated) -#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) +// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) struct stat64 st; if (fstat64(fd, &st) == 0) + { return static_cast(st.st_size); -#else // unix 32 bits or osx + } +#else // unix 32 bits or cygwin struct stat st; + if (fstat(fd, &st) == 0) + { return static_cast(st.st_size); + } #endif #endif throw spdlog_ex("Failed getting file size from fd", errno); } - - - -//Return utc offset in minutes or throw spdlog_ex on failure -inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) +// Return utc offset in minutes or throw spdlog_ex on failure +inline int utc_minutes_offset(const std::tm &tm = details::os::localtime()) { #ifdef _WIN32 @@ -269,33 +272,36 @@ inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) int offset = -tzinfo.Bias; if (tm.tm_isdst) + { offset -= tzinfo.DaylightBias; + } else + { offset -= tzinfo.StandardBias; + } return offset; #else -#if defined(sun) || defined(__sun) +#if defined(sun) || defined(__sun) || defined(_AIX) // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris struct helper { - static long int calculate_gmt_offset(const std::tm & localtm = details::os::localtime(), const std::tm & gmtm = details::os::gmtime()) + static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) { int local_year = localtm.tm_year + (1900 - 1); int gmt_year = gmtm.tm_year + (1900 - 1); long int days = ( - // difference in day of year - localtm.tm_yday - gmtm.tm_yday + // difference in day of year + localtm.tm_yday - + gmtm.tm_yday - // + intervening leap days - + ((local_year >> 2) - (gmt_year >> 2)) - - (local_year / 100 - gmt_year / 100) - + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) + // + intervening leap days + + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) - // + difference in years * 365 */ - + (long int)(local_year - gmt_year) * 365 - ); + // + difference in years * 365 */ + + (long int)(local_year - gmt_year) * 365); long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); @@ -305,102 +311,122 @@ inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) } }; - long int offset_seconds = helper::calculate_gmt_offset(tm); + auto offset_seconds = helper::calculate_gmt_offset(tm); #else - long int offset_seconds = tm.tm_gmtoff; + auto offset_seconds = tm.tm_gmtoff; #endif return static_cast(offset_seconds / 60); #endif } -//Return current thread id as size_t -//It exists because the std::this_thread::get_id() is much slower(espcially under VS 2013) +// Return current thread id as size_t +// It exists because the std::this_thread::get_id() is much slower(especially +// under VS 2013) inline size_t _thread_id() { #ifdef _WIN32 - return static_cast(::GetCurrentThreadId()); + return static_cast(::GetCurrentThreadId()); #elif __linux__ -# if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) -# define SYS_gettid __NR_gettid -# endif - return static_cast(syscall(SYS_gettid)); +#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +#define SYS_gettid __NR_gettid +#endif + return static_cast(syscall(SYS_gettid)); #elif __FreeBSD__ long tid; thr_self(&tid); return static_cast(tid); -#else //Default to standard C++11 (OSX and other Unix) +#elif __APPLE__ + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast(tid); +#else // Default to standard C++11 (other Unix) return static_cast(std::hash()(std::this_thread::get_id())); #endif } -//Return current thread id as size_t (from thread local storage) +// Return current thread id as size_t (from thread local storage) inline size_t thread_id() { -#if defined(_MSC_VER) && (_MSC_VER < 1900) || defined(__clang__) && !__has_feature(cxx_thread_local) +#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) || \ + (defined(__clang__) && !__has_feature(cxx_thread_local)) return _thread_id(); -#else +#else // cache thread id in tls static thread_local const size_t tid = _thread_id(); return tid; #endif } - - +// This is avoid msvc issue in sleep_for that happens if the clock changes. +// See https://github.com/gabime/spdlog/issues/609 +inline void sleep_for_millis(int milliseconds) +{ +#if defined(_WIN32) + ::Sleep(milliseconds); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +#endif +} // wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) -#define SPDLOG_FILENAME_T(s) L ## s -inline std::string filename_to_str(const filename_t& filename) +#define SPDLOG_FILENAME_T(s) L##s +inline std::string filename_to_str(const filename_t &filename) { std::wstring_convert, wchar_t> c; return c.to_bytes(filename); } #else #define SPDLOG_FILENAME_T(s) s -inline std::string filename_to_str(const filename_t& filename) +inline std::string filename_to_str(const filename_t &filename) { return filename; } #endif - -// Return errno string (thread safe) -inline std::string errno_str(int err_num) +inline int pid() { - char buf[256]; - SPDLOG_CONSTEXPR auto buf_size = sizeof(buf); #ifdef _WIN32 - if(strerror_s(buf, buf_size, err_num) == 0) - return std::string(buf); - else - return "Unkown error"; + return static_cast(::GetCurrentProcessId()); +#else + return static_cast(::getpid()); +#endif +} -#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(ANDROID) || defined(__SUNPRO_CC) || \ - ((_POSIX_C_SOURCE >= 200112L) && ! defined(_GNU_SOURCE)) // posix version +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +inline bool is_color_terminal() +{ +#ifdef _WIN32 + return true; +#else + static constexpr const char *Terms[] = { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}; - if (strerror_r(err_num, buf, buf_size) == 0) - return std::string(buf); - else - return "Unkown error"; + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } -#else // gnu version (might not use the given buf, so its retval pointer must be used) - return std::string(strerror_r(err_num, buf, buf_size)); + static const bool result = + std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); + return result; #endif } -inline int pid() +// Detrmine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +inline bool in_terminal(FILE *file) { #ifdef _WIN32 - return ::_getpid(); + return _isatty(_fileno(file)) != 0; #else - return static_cast(::getpid()); + return isatty(fileno(file)) != 0; #endif - } - -} //os -} //details -} //spdlog +} // namespace os +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/pattern_formatter.h b/ifs/include/extern/spdlog/details/pattern_formatter.h new file mode 100644 index 000000000..dd352ae5b --- /dev/null +++ b/ifs/include/extern/spdlog/details/pattern_formatter.h @@ -0,0 +1,772 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/os.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/formatter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +class flag_formatter +{ +public: + virtual ~flag_formatter() = default; + virtual void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +class name_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(*msg.logger_name, dest); + } +}; + +// log level appender +class level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + } +}; + +// short log level appender +class short_level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_short_c_str(msg.level), dest); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char *ampm(const tm &t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm &t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +// Abbreviated weekday name +static const char *days[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +class a_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + } +}; + +// Full weekday name +static const char *full_days[]{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +class A_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_days[tm_time.tm_wday], dest); + } +}; + +// Abbreviated month +static const char *months[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}; +class b_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + } +}; + +// Full month name +static const char *full_months[]{ + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; +class B_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_months[tm_time.tm_mon], dest); + } +}; + +// Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{} {} {} ", days[tm_time.tm_wday], + // months[tm_time.tm_mon], tm_time.tm_mday); + // date + fmt_helper::append_str(days[tm_time.tm_wday], dest); + dest.push_back(' '); + fmt_helper::append_str(months[tm_time.tm_mon], dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_mday, dest); + dest.push_back(' '); + // time + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// year - 2 digit +class C_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_mday, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// year - 4 digit +class Y_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// month 1-12 +class m_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + } +}; + +// day of month 1-31 +class d_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mday, dest); + } +}; + +// hours in 24 format 0-23 +class H_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + } +}; + +// hours in 12 format 1-12 +class I_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + } +}; + +// minutes 0-59 +class M_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// seconds 0-59 +class S_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// milliseconds +class e_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto millis = fmt_helper::time_fraction(msg.time); + fmt_helper::pad3(static_cast(millis.count()), dest); + } +}; + +// microseconds +class f_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto micros = fmt_helper::time_fraction(msg.time); + fmt_helper::pad6(static_cast(micros.count()), dest); + } +}; + +// nanoseconds +class F_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto ns = fmt_helper::time_fraction(msg.time); + fmt::format_to(dest, "{:09}", ns.count()); + } +}; + +// seconds since epoch +class E_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + fmt_helper::append_int(seconds, dest); + } +}; + +// AM/PM +class p_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 12 hour clock 02:55:02 pm +class r_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{:02}:{:02}:{:02}", tm_time.tm_hour, + // tm_time.tm_min, tm_time.tm_sec); + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +class z_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter() = default; + z_formatter(const z_formatter &) = delete; + z_formatter &operator=(const z_formatter &) = delete; + + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + (void)(msg); + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + bool is_negative = total_minutes < 0; + if (is_negative) + { + total_minutes = -total_minutes; + dest.push_back('-'); + } + else + { + dest.push_back('+'); + } + + fmt_helper::pad2(total_minutes / 60, dest); // hours + dest.push_back(':'); + fmt_helper::pad2(total_minutes % 60, dest); // minutes + } + +private: + log_clock::time_point last_update_{std::chrono::seconds(0)}; +#ifdef _WIN32 + int offset_minutes_{0}; + + int get_cached_offset(const log_msg &msg, const std::tm &tm_time) + { + if (msg.time - last_update_ >= cache_refresh) + { + offset_minutes_ = os::utc_minutes_offset(tm_time); + last_update_ = msg.time; + } + return offset_minutes_; + } +#endif +}; + +// Thread id +class t_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.thread_id, dest); + } +}; + +// Current pid +class pid_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(details::os::pid(), dest); + } +}; + +// message counter formatter +class i_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.msg_id, dest); + } +}; + +class v_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_buf(msg.raw, dest); + } +}; + +class ch_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + explicit ch_formatter(char ch) + : ch_(ch) + { + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + dest.push_back(ch_); + } + +private: + char ch_; +}; + +// aggregate user chars to display as is +class aggregate_formatter SPDLOG_FINAL : public flag_formatter +{ +public: + aggregate_formatter() = default; + + void add_ch(char ch) + { + str_ += ch; + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(str_, dest); + } + +private: + std::string str_; +}; + +// mark the color range. expect it to be in the form of "%^colored text%$" +class color_start_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_start = dest.size(); + } +}; +class color_stop_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_end = dest.size(); + } +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v +class full_formatter SPDLOG_FINAL : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + using namespace std::chrono; +#ifndef SPDLOG_NO_DATETIME + + // cache the date/time part for the next second. + auto duration = msg.time.time_since_epoch(); + auto secs = duration_cast(duration); + + if (cache_timestamp_ != secs || cached_datetime_.size() == 0) + { + cached_datetime_.resize(0); + cached_datetime_.push_back('['); + fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mday, cached_datetime_); + cached_datetime_.push_back(' '); + + fmt_helper::pad2(tm_time.tm_hour, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_min, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_sec, cached_datetime_); + cached_datetime_.push_back('.'); + + cache_timestamp_ = secs; + } + fmt_helper::append_buf(cached_datetime_, dest); + + auto millis = fmt_helper::time_fraction(msg.time); + fmt_helper::pad3(static_cast(millis.count()), dest); + dest.push_back(']'); + dest.push_back(' '); + +#else // no datetime needed + (void)tm_time; +#endif + +#ifndef SPDLOG_NO_NAME + dest.push_back('['); + fmt_helper::append_str(*msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); +#endif + + dest.push_back('['); + // wrap the level name with color + msg.color_range_start = dest.size(); + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + msg.color_range_end = dest.size(); + dest.push_back(']'); + dest.push_back(' '); + fmt_helper::append_buf(msg.raw, dest); + } + +private: + std::chrono::seconds cache_timestamp_{0}; + fmt::basic_memory_buffer cached_datetime_; +}; + +} // namespace details + +class pattern_formatter SPDLOG_FINAL : public formatter +{ +public: + explicit pattern_formatter( + std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol) + : pattern_(std::move(pattern)) + , eol_(std::move(eol)) + , pattern_time_type_(time_type) + , last_log_secs_(0) + { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + compile_pattern_(pattern_); + } + + pattern_formatter(const pattern_formatter &other) = delete; + pattern_formatter &operator=(const pattern_formatter &other) = delete; + + std::unique_ptr clone() const override + { + return std::unique_ptr(new pattern_formatter(pattern_, pattern_time_type_, eol_)); + } + + void format(const details::log_msg &msg, fmt::memory_buffer &dest) override + { +#ifndef SPDLOG_NO_DATETIME + auto secs = std::chrono::duration_cast(msg.time.time_since_epoch()); + if (secs != last_log_secs_) + { + cached_tm_ = get_time_(msg); + last_log_secs_ = secs; + } +#endif + for (auto &f : formatters_) + { + f->format(msg, cached_tm_, dest); + } + // write eol + details::fmt_helper::append_str(eol_, dest); + } + +private: + std::string pattern_; + std::string eol_; + pattern_time_type pattern_time_type_; + std::tm cached_tm_; + std::chrono::seconds last_log_secs_; + + std::vector> formatters_; + + std::tm get_time_(const details::log_msg &msg) + { + if (pattern_time_type_ == pattern_time_type::local) + { + return details::os::localtime(log_clock::to_time_t(msg.time)); + } + return details::os::gmtime(log_clock::to_time_t(msg.time)); + } + + void handle_flag_(char flag) + { + using flag_formatter_ptr = std::unique_ptr; + switch (flag) + { + // logger name + case 'n': + formatters_.push_back(flag_formatter_ptr(new details::name_formatter())); + break; + + case 'l': + formatters_.push_back(flag_formatter_ptr(new details::level_formatter())); + break; + + case 'L': + formatters_.push_back(flag_formatter_ptr(new details::short_level_formatter())); + break; + + case ('t'): + formatters_.push_back(flag_formatter_ptr(new details::t_formatter())); + break; + + case ('v'): + formatters_.push_back(flag_formatter_ptr(new details::v_formatter())); + break; + + case ('a'): + formatters_.push_back(flag_formatter_ptr(new details::a_formatter())); + break; + + case ('A'): + formatters_.push_back(flag_formatter_ptr(new details::A_formatter())); + break; + + case ('b'): + case ('h'): + formatters_.push_back(flag_formatter_ptr(new details::b_formatter())); + break; + + case ('B'): + formatters_.push_back(flag_formatter_ptr(new details::B_formatter())); + break; + case ('c'): + formatters_.push_back(flag_formatter_ptr(new details::c_formatter())); + break; + + case ('C'): + formatters_.push_back(flag_formatter_ptr(new details::C_formatter())); + break; + + case ('Y'): + formatters_.push_back(flag_formatter_ptr(new details::Y_formatter())); + break; + + case ('D'): + case ('x'): + formatters_.push_back(flag_formatter_ptr(new details::D_formatter())); + break; + + case ('m'): + formatters_.push_back(flag_formatter_ptr(new details::m_formatter())); + break; + + case ('d'): + formatters_.push_back(flag_formatter_ptr(new details::d_formatter())); + break; + + case ('H'): + formatters_.push_back(flag_formatter_ptr(new details::H_formatter())); + break; + + case ('I'): + formatters_.push_back(flag_formatter_ptr(new details::I_formatter())); + break; + + case ('M'): + formatters_.push_back(flag_formatter_ptr(new details::M_formatter())); + break; + + case ('S'): + formatters_.push_back(flag_formatter_ptr(new details::S_formatter())); + break; + + case ('e'): + formatters_.push_back(flag_formatter_ptr(new details::e_formatter())); + break; + + case ('f'): + formatters_.push_back(flag_formatter_ptr(new details::f_formatter())); + break; + case ('F'): + formatters_.push_back(flag_formatter_ptr(new details::F_formatter())); + break; + + case ('E'): + formatters_.push_back(flag_formatter_ptr(new details::E_formatter())); + break; + + case ('p'): + formatters_.push_back(flag_formatter_ptr(new details::p_formatter())); + break; + + case ('r'): + formatters_.push_back(flag_formatter_ptr(new details::r_formatter())); + break; + + case ('R'): + formatters_.push_back(flag_formatter_ptr(new details::R_formatter())); + break; + + case ('T'): + case ('X'): + formatters_.push_back(flag_formatter_ptr(new details::T_formatter())); + break; + + case ('z'): + formatters_.push_back(flag_formatter_ptr(new details::z_formatter())); + break; + + case ('+'): + formatters_.push_back(flag_formatter_ptr(new details::full_formatter())); + break; + + case ('P'): + formatters_.push_back(flag_formatter_ptr(new details::pid_formatter())); + break; + + case ('i'): + formatters_.push_back(flag_formatter_ptr(new details::i_formatter())); + break; + + case ('^'): + formatters_.push_back(flag_formatter_ptr(new details::color_start_formatter())); + break; + + case ('$'): + formatters_.push_back(flag_formatter_ptr(new details::color_stop_formatter())); + break; + + default: // Unknown flag appears as is + formatters_.push_back(flag_formatter_ptr(new details::ch_formatter('%'))); + formatters_.push_back(flag_formatter_ptr(new details::ch_formatter(flag))); + break; + } + } + + void compile_pattern_(const std::string &pattern) + { + auto end = pattern.end(); + std::unique_ptr user_chars; + formatters_.clear(); + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) // append user chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + if (++it != end) + { + handle_flag_(*it); + } + else + { + break; + } + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + { + user_chars = std::unique_ptr(new details::aggregate_formatter()); + } + user_chars->add_ch(*it); + } + } + if (user_chars) // append raw chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + } +}; +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/pattern_formatter_impl.h b/ifs/include/extern/spdlog/details/pattern_formatter_impl.h deleted file mode 100644 index 95b6ef267..000000000 --- a/ifs/include/extern/spdlog/details/pattern_formatter_impl.h +++ /dev/null @@ -1,670 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace spdlog -{ -namespace details -{ -class flag_formatter -{ -public: - virtual ~flag_formatter() - {} - virtual void format(details::log_msg& msg, const std::tm& tm_time) = 0; -}; - -/////////////////////////////////////////////////////////////////////// -// name & level pattern appenders -/////////////////////////////////////////////////////////////////////// -namespace -{ -class name_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << *msg.logger_name; - } -}; -} - -// log level appender -class level_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << level::to_str(msg.level); - } -}; - -// short log level appender -class short_level_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << level::to_short_str(msg.level); - } -}; - -/////////////////////////////////////////////////////////////////////// -// Date time pattern appenders -/////////////////////////////////////////////////////////////////////// - -static const char* ampm(const tm& t) -{ - return t.tm_hour >= 12 ? "PM" : "AM"; -} - -static int to12h(const tm& t) -{ - return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; -} - -//Abbreviated weekday name -using days_array = std::array; -static const days_array& days() -{ - static const days_array arr{ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } }; - return arr; -} -class a_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << days()[tm_time.tm_wday]; - } -}; - -//Full weekday name -static const days_array& full_days() -{ - static const days_array arr{ { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } }; - return arr; -} -class A_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << full_days()[tm_time.tm_wday]; - } -}; - -//Abbreviated month -using months_array = std::array; -static const months_array& months() -{ - static const months_array arr{ { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" } }; - return arr; -} -class b_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << months()[tm_time.tm_mon]; - } -}; - -//Full month name -static const months_array& full_months() -{ - static const months_array arr{ { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } }; - return arr; -} -class B_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << full_months()[tm_time.tm_mon]; - } -}; - - -//write 2 ints seperated by sep with padding of 2 -static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, char sep) -{ - w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0'); - return w; -} - -//write 3 ints seperated by sep with padding of 2 -static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, int v3, char sep) -{ - w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0') << sep << fmt::pad(v3, 2, '0'); - return w; -} - - -//Date and time representation (Thu Aug 23 15:35:46 2014) -class c_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << days()[tm_time.tm_wday] << ' ' << months()[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; - pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900; - } -}; - - -// year - 2 digit -class C_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_year % 100, 2, '0'); - } -}; - - - -// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 -class D_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/'); - } -}; - - -// year - 4 digit -class Y_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << tm_time.tm_year + 1900; - } -}; - -// month 1-12 -class m_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_mon + 1, 2, '0'); - } -}; - -// day of month 1-31 -class d_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_mday, 2, '0'); - } -}; - -// hours in 24 format 0-23 -class H_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_hour, 2, '0'); - } -}; - -// hours in 12 format 1-12 -class I_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(to12h(tm_time), 2, '0'); - } -}; - -// minutes 0-59 -class M_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_min, 2, '0'); - } -}; - -// seconds 0-59 -class S_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << fmt::pad(tm_time.tm_sec, 2, '0'); - } -}; - -// milliseconds -class e_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - auto duration = msg.time.time_since_epoch(); - auto millis = std::chrono::duration_cast(duration).count() % 1000; - msg.formatted << fmt::pad(static_cast(millis), 3, '0'); - } -}; - -// microseconds -class f_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - auto duration = msg.time.time_since_epoch(); - auto micros = std::chrono::duration_cast(duration).count() % 1000000; - msg.formatted << fmt::pad(static_cast(micros), 6, '0'); - } -}; - -// nanoseconds -class F_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - auto duration = msg.time.time_since_epoch(); - auto ns = std::chrono::duration_cast(duration).count() % 1000000000; - msg.formatted << fmt::pad(static_cast(ns), 9, '0'); - } -}; - -// AM/PM -class p_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - msg.formatted << ampm(tm_time); - } -}; - - -// 12 hour clock 02:55:02 pm -class r_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time); - } -}; - -// 24-hour HH:MM time, equivalent to %H:%M -class R_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':'); - } -}; - -// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S -class T_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { - pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':'); - } -}; - - -// ISO 8601 offset from UTC in timezone (+-HH:MM) -class z_formatter:public flag_formatter -{ -public: - const std::chrono::seconds cache_refresh = std::chrono::seconds(5); - - z_formatter():_last_update(std::chrono::seconds(0)) - {} - z_formatter(const z_formatter&) = delete; - z_formatter& operator=(const z_formatter&) = delete; - - void format(details::log_msg& msg, const std::tm& tm_time) override - { -#ifdef _WIN32 - int total_minutes = get_cached_offset(msg, tm_time); -#else - // No need to chache under gcc, - // it is very fast (already stored in tm.tm_gmtoff) - int total_minutes = os::utc_minutes_offset(tm_time); -#endif - bool is_negative = total_minutes < 0; - char sign; - if (is_negative) - { - total_minutes = -total_minutes; - sign = '-'; - } - else - { - sign = '+'; - } - - int h = total_minutes / 60; - int m = total_minutes % 60; - msg.formatted << sign; - pad_n_join(msg.formatted, h, m, ':'); - } -private: - log_clock::time_point _last_update; - int _offset_minutes; - std::mutex _mutex; - - int get_cached_offset(const log_msg& msg, const std::tm& tm_time) - { - using namespace std::chrono; - std::lock_guard l(_mutex); - if (msg.time - _last_update >= cache_refresh) - { - _offset_minutes = os::utc_minutes_offset(tm_time); - _last_update = msg.time; - } - return _offset_minutes; - } -}; - - - -// Thread id -class t_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << msg.thread_id; - } -}; - -// Current pid -class pid_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << details::os::pid(); - } -}; - - -class v_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); - } -}; - -class ch_formatter:public flag_formatter -{ -public: - explicit ch_formatter(char ch): _ch(ch) - {} - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << _ch; - } -private: - char _ch; -}; - - -//aggregate user chars to display as is -class aggregate_formatter:public flag_formatter -{ -public: - aggregate_formatter() - {} - void add_ch(char ch) - { - _str += ch; - } - void format(details::log_msg& msg, const std::tm&) override - { - msg.formatted << _str; - } -private: - std::string _str; -}; - -// Full info formatter -// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v -class full_formatter:public flag_formatter -{ - void format(details::log_msg& msg, const std::tm& tm_time) override - { -#ifndef SPDLOG_NO_DATETIME - auto duration = msg.time.time_since_epoch(); - auto millis = std::chrono::duration_cast(duration).count() % 1000; - - /* Slower version(while still very fast - about 3.2 million lines/sec under 10 threads), - msg.formatted.write("[{:d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}] [{}] [{}] {} ", - tm_time.tm_year + 1900, - tm_time.tm_mon + 1, - tm_time.tm_mday, - tm_time.tm_hour, - tm_time.tm_min, - tm_time.tm_sec, - static_cast(millis), - msg.logger_name, - level::to_str(msg.level), - msg.raw.str());*/ - - - // Faster (albeit uglier) way to format the line (5.6 million lines/sec under 10 threads) - msg.formatted << '[' << static_cast(tm_time.tm_year + 1900) << '-' - << fmt::pad(static_cast(tm_time.tm_mon + 1), 2, '0') << '-' - << fmt::pad(static_cast(tm_time.tm_mday), 2, '0') << ' ' - << fmt::pad(static_cast(tm_time.tm_hour), 2, '0') << ':' - << fmt::pad(static_cast(tm_time.tm_min), 2, '0') << ':' - << fmt::pad(static_cast(tm_time.tm_sec), 2, '0') << '.' - << fmt::pad(static_cast(millis), 3, '0') << "] "; - - //no datetime needed -#else - (void)tm_time; -#endif - -#ifndef SPDLOG_NO_NAME - msg.formatted << '[' << *msg.logger_name << "] "; -#endif - - msg.formatted << '[' << level::to_str(msg.level) << "] "; - msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); - } -}; - - - -} -} -/////////////////////////////////////////////////////////////////////////////// -// pattern_formatter inline impl -/////////////////////////////////////////////////////////////////////////////// -inline spdlog::pattern_formatter::pattern_formatter(const std::string& pattern) -{ - compile_pattern(pattern); -} - -inline void spdlog::pattern_formatter::compile_pattern(const std::string& pattern) -{ - auto end = pattern.end(); - std::unique_ptr user_chars; - for (auto it = pattern.begin(); it != end; ++it) - { - if (*it == '%') - { - if (user_chars) //append user chars found so far - _formatters.push_back(std::move(user_chars)); - - if (++it != end) - handle_flag(*it); - else - break; - } - else // chars not following the % sign should be displayed as is - { - if (!user_chars) - user_chars = std::unique_ptr(new details::aggregate_formatter()); - user_chars->add_ch(*it); - } - } - if (user_chars) //append raw chars found so far - { - _formatters.push_back(std::move(user_chars)); - } - -} -inline void spdlog::pattern_formatter::handle_flag(char flag) -{ - switch (flag) - { - // logger name - case 'n': - _formatters.push_back(std::unique_ptr(new details::name_formatter())); - break; - - case 'l': - _formatters.push_back(std::unique_ptr(new details::level_formatter())); - break; - - case 'L': - _formatters.push_back(std::unique_ptr(new details::short_level_formatter())); - break; - - case('t'): - _formatters.push_back(std::unique_ptr(new details::t_formatter())); - break; - - case('v'): - _formatters.push_back(std::unique_ptr(new details::v_formatter())); - break; - - case('a'): - _formatters.push_back(std::unique_ptr(new details::a_formatter())); - break; - - case('A'): - _formatters.push_back(std::unique_ptr(new details::A_formatter())); - break; - - case('b'): - case('h'): - _formatters.push_back(std::unique_ptr(new details::b_formatter())); - break; - - case('B'): - _formatters.push_back(std::unique_ptr(new details::B_formatter())); - break; - case('c'): - _formatters.push_back(std::unique_ptr(new details::c_formatter())); - break; - - case('C'): - _formatters.push_back(std::unique_ptr(new details::C_formatter())); - break; - - case('Y'): - _formatters.push_back(std::unique_ptr(new details::Y_formatter())); - break; - - case('D'): - case('x'): - - _formatters.push_back(std::unique_ptr(new details::D_formatter())); - break; - - case('m'): - _formatters.push_back(std::unique_ptr(new details::m_formatter())); - break; - - case('d'): - _formatters.push_back(std::unique_ptr(new details::d_formatter())); - break; - - case('H'): - _formatters.push_back(std::unique_ptr(new details::H_formatter())); - break; - - case('I'): - _formatters.push_back(std::unique_ptr(new details::I_formatter())); - break; - - case('M'): - _formatters.push_back(std::unique_ptr(new details::M_formatter())); - break; - - case('S'): - _formatters.push_back(std::unique_ptr(new details::S_formatter())); - break; - - case('e'): - _formatters.push_back(std::unique_ptr(new details::e_formatter())); - break; - - case('f'): - _formatters.push_back(std::unique_ptr(new details::f_formatter())); - break; - case('F'): - _formatters.push_back(std::unique_ptr(new details::F_formatter())); - break; - - case('p'): - _formatters.push_back(std::unique_ptr(new details::p_formatter())); - break; - - case('r'): - _formatters.push_back(std::unique_ptr(new details::r_formatter())); - break; - - case('R'): - _formatters.push_back(std::unique_ptr(new details::R_formatter())); - break; - - case('T'): - case('X'): - _formatters.push_back(std::unique_ptr(new details::T_formatter())); - break; - - case('z'): - _formatters.push_back(std::unique_ptr(new details::z_formatter())); - break; - - case ('+'): - _formatters.push_back(std::unique_ptr(new details::full_formatter())); - break; - - case ('P'): - _formatters.push_back(std::unique_ptr(new details::pid_formatter())); - break; - - default: //Unkown flag appears as is - _formatters.push_back(std::unique_ptr(new details::ch_formatter('%'))); - _formatters.push_back(std::unique_ptr(new details::ch_formatter(flag))); - break; - } -} - - -inline void spdlog::pattern_formatter::format(details::log_msg& msg) -{ - -#ifndef SPDLOG_NO_DATETIME - auto tm_time = details::os::localtime(log_clock::to_time_t(msg.time)); -#else - std::tm tm_time; -#endif - for (auto &f : _formatters) - { - f->format(msg, tm_time); - } - //write eol - msg.formatted.write(details::os::eol, details::os::eol_size); -} diff --git a/ifs/include/extern/spdlog/details/periodic_worker.h b/ifs/include/extern/spdlog/details/periodic_worker.h new file mode 100644 index 000000000..57e5fa771 --- /dev/null +++ b/ifs/include/extern/spdlog/details/periodic_worker.h @@ -0,0 +1,71 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// periodic worker thread - periodically executes the given callback function. +// +// RAII over the owned thread: +// creates the thread on construction. +// stops and joins the thread on destruction. + +#include +#include +#include +#include +#include +namespace spdlog { +namespace details { + +class periodic_worker +{ +public: + periodic_worker(const std::function &callback_fun, std::chrono::seconds interval) + { + active_ = (interval > std::chrono::seconds::zero()); + if (!active_) + { + return; + } + + worker_thread_ = std::thread([this, callback_fun, interval]() { + for (;;) + { + std::unique_lock lock(this->mutex_); + if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) + { + return; // active_ == false, so exit this thread + } + callback_fun(); + } + }); + } + + periodic_worker(const periodic_worker &) = delete; + periodic_worker &operator=(const periodic_worker &) = delete; + + // stop the worker thread and join it + ~periodic_worker() + { + if (worker_thread_.joinable()) + { + { + std::lock_guard lock(mutex_); + active_ = false; + } + cv_.notify_one(); + worker_thread_.join(); + } + } + +private: + bool active_; + std::thread worker_thread_; + std::mutex mutex_; + std::condition_variable cv_; +}; +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/registry.h b/ifs/include/extern/spdlog/details/registry.h index a2cc7f681..3988552ab 100644 --- a/ifs/include/extern/spdlog/details/registry.h +++ b/ifs/include/extern/spdlog/details/registry.h @@ -10,176 +10,205 @@ // If user requests a non existing logger, nullptr will be returned // This class is thread safe -#include -#include -#include -#include +#include "spdlog/common.h" +#include "spdlog/details/periodic_worker.h" +#include "spdlog/logger.h" #include #include #include -#include #include #include -namespace spdlog -{ -namespace details -{ -template class registry_t +namespace spdlog { +namespace details { +class thread_pool; + +class registry { public: + registry(const registry &) = delete; + registry &operator=(const registry &) = delete; - void register_logger(std::shared_ptr logger) + void register_logger(std::shared_ptr new_logger) { - std::lock_guard lock(_mutex); - auto logger_name = logger->name(); - throw_if_exists(logger_name); - _loggers[logger_name] = logger; + std::lock_guard lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); + loggers_[logger_name] = std::move(new_logger); } - - std::shared_ptr get(const std::string& logger_name) + void register_and_init(std::shared_ptr new_logger) { - std::lock_guard lock(_mutex); - auto found = _loggers.find(logger_name); - return found == _loggers.end() ? nullptr : found->second; - } + std::lock_guard lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); - template - std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) - { - std::lock_guard lock(_mutex); - throw_if_exists(logger_name); - std::shared_ptr new_logger; - if (_async_mode) - new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb); - else - new_logger = std::make_shared(logger_name, sinks_begin, sinks_end); + // set the global formatter pattern + new_logger->set_formatter(formatter_->clone()); - if (_formatter) - new_logger->set_formatter(_formatter); + if (err_handler_) + { + new_logger->set_error_handler(err_handler_); + } - if (_err_handler) - new_logger->set_error_handler(_err_handler); + new_logger->set_level(level_); + new_logger->flush_on(flush_level_); - new_logger->set_level(_level); + // add to registry + loggers_[logger_name] = std::move(new_logger); + } + std::shared_ptr get(const std::string &logger_name) + { + std::lock_guard lock(logger_map_mutex_); + auto found = loggers_.find(logger_name); + return found == loggers_.end() ? nullptr : found->second; + } - //Add to registry - _loggers[logger_name] = new_logger; - return new_logger; + void set_tp(std::shared_ptr tp) + { + std::lock_guard lock(tp_mutex_); + tp_ = std::move(tp); } - void apply_all(std::function)> fun) + std::shared_ptr get_tp() { - std::lock_guard lock(_mutex); - for (auto &l : _loggers) - fun(l.second); + std::lock_guard lock(tp_mutex_); + return tp_; } - void drop(const std::string& logger_name) + // Set global formatter. Each sink in each logger will get a clone of this object + void set_formatter(std::unique_ptr formatter) { - std::lock_guard lock(_mutex); - _loggers.erase(logger_name); + std::lock_guard lock(logger_map_mutex_); + formatter_ = std::move(formatter); + for (auto &l : loggers_) + { + l.second->set_formatter(formatter_->clone()); + } } - void drop_all() + void set_level(level::level_enum log_level) { - std::lock_guard lock(_mutex); - _loggers.clear(); + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_level(log_level); + } + level_ = log_level; } - std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks) + + void flush_on(level::level_enum log_level) { - return create(logger_name, sinks.begin(), sinks.end()); + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush_on(log_level); + } + flush_level_ = log_level; } - std::shared_ptr create(const std::string& logger_name, sink_ptr sink) + void flush_every(std::chrono::seconds interval) { - return create(logger_name, { sink }); + std::lock_guard lock(flusher_mutex_); + std::function clbk = std::bind(®istry::flush_all, this); + periodic_flusher_.reset(new periodic_worker(clbk, interval)); } + void set_error_handler(log_err_handler handler) + { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_error_handler(handler); + } + err_handler_ = handler; + } - void formatter(formatter_ptr f) + void apply_all(const std::function)> &fun) { - std::lock_guard lock(_mutex); - _formatter = f; - for (auto& l : _loggers) - l.second->set_formatter(_formatter); + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + fun(l.second); + } } - void set_pattern(const std::string& pattern) + void flush_all() { - std::lock_guard lock(_mutex); - _formatter = std::make_shared(pattern); - for (auto& l : _loggers) - l.second->set_formatter(_formatter); + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush(); + } } - void set_level(level::level_enum log_level) + void drop(const std::string &logger_name) { - std::lock_guard lock(_mutex); - for (auto& l : _loggers) - l.second->set_level(log_level); - _level = log_level; + std::lock_guard lock(logger_map_mutex_); + loggers_.erase(logger_name); } - void set_error_handler(log_err_handler handler) + void drop_all() { - for (auto& l : _loggers) - l.second->set_error_handler(handler); - _err_handler = handler; + std::lock_guard lock(logger_map_mutex_); + loggers_.clear(); } - void set_async_mode(size_t q_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) + // clean all reasources and threads started by the registry + void shutdown() { - std::lock_guard lock(_mutex); - _async_mode = true; - _async_q_size = q_size; - _overflow_policy = overflow_policy; - _worker_warmup_cb = worker_warmup_cb; - _flush_interval_ms = flush_interval_ms; - _worker_teardown_cb = worker_teardown_cb; + { + std::lock_guard lock(flusher_mutex_); + periodic_flusher_.reset(); + } + + drop_all(); + + { + std::lock_guard lock(tp_mutex_); + tp_.reset(); + } } - void set_sync_mode() + std::recursive_mutex &tp_mutex() { - std::lock_guard lock(_mutex); - _async_mode = false; + return tp_mutex_; } - static registry_t& instance() + static registry &instance() { - static registry_t s_instance; + static registry s_instance; return s_instance; } private: - registry_t() {} - registry_t(const registry_t&) = delete; - registry_t& operator=(const registry_t&) = delete; + registry() + : formatter_(new pattern_formatter("%+")) + { + } + + ~registry() = default; - void throw_if_exists(const std::string &logger_name) + void throw_if_exists_(const std::string &logger_name) { - if (_loggers.find(logger_name) != _loggers.end()) + if (loggers_.find(logger_name) != loggers_.end()) + { throw spdlog_ex("logger with name '" + logger_name + "' already exists"); - } - Mutex _mutex; - std::unordered_map > _loggers; - formatter_ptr _formatter; - level::level_enum _level = level::info; - log_err_handler _err_handler; - bool _async_mode = false; - size_t _async_q_size = 0; - async_overflow_policy _overflow_policy = async_overflow_policy::block_retry; - std::function _worker_warmup_cb = nullptr; - std::chrono::milliseconds _flush_interval_ms; - std::function _worker_teardown_cb = nullptr; + } + } + + std::mutex logger_map_mutex_, flusher_mutex_; + std::recursive_mutex tp_mutex_; + std::unordered_map> loggers_; + std::unique_ptr formatter_; + level::level_enum level_ = level::info; + level::level_enum flush_level_ = level::off; + log_err_handler err_handler_; + std::shared_ptr tp_; + std::unique_ptr periodic_flusher_; }; -#ifdef SPDLOG_NO_REGISTRY_MUTEX -typedef registry_t registry; -#else -typedef registry_t registry; -#endif -} -} + +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/details/spdlog_impl.h b/ifs/include/extern/spdlog/details/spdlog_impl.h deleted file mode 100644 index ac7f47e18..000000000 --- a/ifs/include/extern/spdlog/details/spdlog_impl.h +++ /dev/null @@ -1,246 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// -// Global registry functions -// -#include -#include -#include -#include -#ifdef SPDLOG_ENABLE_SYSLOG -#include -#endif - -#ifdef _WIN32 -#include -#else - -#include -#endif - - -#ifdef __ANDROID__ -#include -#endif - -#include -#include -#include -#include - -inline void spdlog::register_logger(std::shared_ptr logger) -{ - return details::registry::instance().register_logger(logger); -} - -inline std::shared_ptr spdlog::get(const std::string& name) -{ - return details::registry::instance().get(name); -} - -inline void spdlog::drop(const std::string &name) -{ - details::registry::instance().drop(name); -} - -// Create multi/single threaded simple file logger -inline std::shared_ptr spdlog::basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate) -{ - return create(logger_name, filename, truncate); -} - -inline std::shared_ptr spdlog::basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate) -{ - return create(logger_name, filename, truncate); -} - -// Create multi/single threaded rotating file logger -inline std::shared_ptr spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) -{ - return create(logger_name, filename, max_file_size, max_files); -} - -inline std::shared_ptr spdlog::rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) -{ - return create(logger_name, filename, max_file_size, max_files); -} - -// Create file logger which creates new file at midnight): -inline std::shared_ptr spdlog::daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour, int minute) -{ - return create(logger_name, filename, hour, minute); -} - -inline std::shared_ptr spdlog::daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour, int minute) -{ - return create(logger_name, filename, hour, minute); -} - - -// -// stdout/stderr loggers -// -inline std::shared_ptr spdlog::stdout_logger_mt(const std::string& logger_name) -{ - return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_mt::instance()); -} - -inline std::shared_ptr spdlog::stdout_logger_st(const std::string& logger_name) -{ - return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_st::instance()); -} - -inline std::shared_ptr spdlog::stderr_logger_mt(const std::string& logger_name) -{ - return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_mt::instance()); -} - -inline std::shared_ptr spdlog::stderr_logger_st(const std::string& logger_name) -{ - return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_st::instance()); -} - -// -// stdout/stderr color loggers -// -#ifdef _WIN32 -inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) -{ - auto sink = std::make_shared(); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) -{ - auto sink = std::make_shared(); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) -{ - auto sink = std::make_shared(); - return spdlog::details::registry::instance().create(logger_name, sink); -} - - -inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) -{ - auto sink = std::make_shared(); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -#else //ansi terminal colors - -inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) -{ - auto sink = std::make_shared(spdlog::sinks::stdout_sink_mt::instance()); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) -{ - auto sink = std::make_shared(spdlog::sinks::stdout_sink_st::instance()); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) -{ - auto sink = std::make_shared(spdlog::sinks::stderr_sink_mt::instance()); - return spdlog::details::registry::instance().create(logger_name, sink); -} - -inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) -{ - auto sink = std::make_shared(spdlog::sinks::stderr_sink_st::instance()); - return spdlog::details::registry::instance().create(logger_name, sink); -} -#endif - -#ifdef SPDLOG_ENABLE_SYSLOG -// Create syslog logger -inline std::shared_ptr spdlog::syslog_logger(const std::string& logger_name, const std::string& syslog_ident, int syslog_option) -{ - return create(logger_name, syslog_ident, syslog_option); -} -#endif - -#ifdef __ANDROID__ -inline std::shared_ptr spdlog::android_logger(const std::string& logger_name, const std::string& tag) -{ - return create(logger_name, tag); -} -#endif - -// Create and register a logger a single sink -inline std::shared_ptr spdlog::create(const std::string& logger_name, const spdlog::sink_ptr& sink) -{ - return details::registry::instance().create(logger_name, sink); -} - -//Create logger with multiple sinks - -inline std::shared_ptr spdlog::create(const std::string& logger_name, spdlog::sinks_init_list sinks) -{ - return details::registry::instance().create(logger_name, sinks); -} - - -template -inline std::shared_ptr spdlog::create(const std::string& logger_name, Args... args) -{ - sink_ptr sink = std::make_shared(args...); - return details::registry::instance().create(logger_name, { sink }); -} - - -template -inline std::shared_ptr spdlog::create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) -{ - return details::registry::instance().create(logger_name, sinks_begin, sinks_end); -} - -inline void spdlog::set_formatter(spdlog::formatter_ptr f) -{ - details::registry::instance().formatter(f); -} - -inline void spdlog::set_pattern(const std::string& format_string) -{ - return details::registry::instance().set_pattern(format_string); -} - -inline void spdlog::set_level(level::level_enum log_level) -{ - return details::registry::instance().set_level(log_level); -} - -inline void spdlog::set_error_handler(log_err_handler handler) -{ - return details::registry::instance().set_error_handler(handler); -} - - -inline void spdlog::set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) -{ - details::registry::instance().set_async_mode(queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb); -} - -inline void spdlog::set_sync_mode() -{ - details::registry::instance().set_sync_mode(); -} - -inline void spdlog::apply_all(std::function)> fun) -{ - details::registry::instance().apply_all(fun); -} - -inline void spdlog::drop_all() -{ - details::registry::instance().drop_all(); -} diff --git a/ifs/include/extern/spdlog/details/thread_pool.h b/ifs/include/extern/spdlog/details/thread_pool.h new file mode 100644 index 000000000..282d67e8e --- /dev/null +++ b/ifs/include/extern/spdlog/details/thread_pool.h @@ -0,0 +1,225 @@ +#pragma once + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/mpmc_blocking_q.h" +#include "spdlog/details/os.h" + +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +using async_logger_ptr = std::shared_ptr; + +enum class async_msg_type +{ + log, + flush, + terminate +}; + +// Async msg to move to/from the queue +// Movable only. should never be copied +struct async_msg +{ + async_msg_type msg_type; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::basic_memory_buffer raw; + + size_t msg_id; + async_logger_ptr worker_ptr; + + async_msg() = default; + ~async_msg() = default; + + // should only be moved in or out of the queue.. + async_msg(const async_msg &) = delete; + +// support for vs2013 move +#if defined(_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) SPDLOG_NOEXCEPT : msg_type(other.msg_type), + level(other.level), + time(other.time), + thread_id(other.thread_id), + raw(move(other.raw)), + msg_id(other.msg_id), + worker_ptr(std::move(other.worker_ptr)) + { + } + + async_msg &operator=(async_msg &&other) SPDLOG_NOEXCEPT + { + msg_type = other.msg_type; + level = other.level; + time = other.time; + thread_id = other.thread_id; + raw = std::move(other.raw); + msg_id = other.msg_id; + worker_ptr = std::move(other.worker_ptr); + return *this; + } +#else // (_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) = default; + async_msg &operator=(async_msg &&other) = default; +#endif + + // construct from log_msg with given type + async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &&m) + : msg_type(the_type) + , level(m.level) + , time(m.time) + , thread_id(m.thread_id) + , msg_id(m.msg_id) + , worker_ptr(std::forward(worker)) + { + fmt_helper::append_buf(m.raw, raw); + } + + async_msg(async_logger_ptr &&worker, async_msg_type the_type) + : async_msg(std::forward(worker), the_type, details::log_msg()) + { + } + + explicit async_msg(async_msg_type the_type) + : async_msg(nullptr, the_type, details::log_msg()) + { + } + + // copy into log_msg + void to_log_msg(log_msg &msg) + { + msg.logger_name = &worker_ptr->name(); + msg.level = level; + msg.time = time; + msg.thread_id = thread_id; + fmt_helper::append_buf(raw, msg.raw); + msg.msg_id = msg_id; + msg.color_range_start = 0; + msg.color_range_end = 0; + } +}; + +class thread_pool +{ +public: + using item_type = async_msg; + using q_type = details::mpmc_blocking_queue; + + thread_pool(size_t q_max_items, size_t threads_n) + : q_(q_max_items) + { + // std::cout << "thread_pool() q_size_bytes: " << q_size_bytes << + // "\tthreads_n: " << threads_n << std::endl; + if (threads_n == 0 || threads_n > 1000) + { + throw spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " + "range is 1-1000)"); + } + for (size_t i = 0; i < threads_n; i++) + { + threads_.emplace_back(&thread_pool::worker_loop_, this); + } + } + + // message all threads to terminate gracefully join them + ~thread_pool() + { + try + { + for (size_t i = 0; i < threads_.size(); i++) + { + post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); + } + + for (auto &t : threads_) + { + t.join(); + } + } + catch (...) + { + } + } + + void post_log(async_logger_ptr &&worker_ptr, details::log_msg &&msg, async_overflow_policy overflow_policy) + { + async_msg async_m(std::forward(worker_ptr), async_msg_type::log, std::forward(msg)); + post_async_msg_(std::move(async_m), overflow_policy); + } + + void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) + { + post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); + } + + size_t overrun_counter() + { + return q_.overrun_counter(); + } + +private: + q_type q_; + + std::vector threads_; + + void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) + { + if (overflow_policy == async_overflow_policy::block) + { + q_.enqueue(std::move(new_msg)); + } + else + { + q_.enqueue_nowait(std::move(new_msg)); + } + } + + void worker_loop_() + { + while (process_next_msg_()) {}; + } + + // process next message in the queue + // return true if this thread should still be active (while no terminate msg + // was received) + bool process_next_msg_() + { + async_msg incoming_async_msg; + bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); + if (!dequeued) + { + return true; + } + + switch (incoming_async_msg.msg_type) + { + case async_msg_type::flush: + { + incoming_async_msg.worker_ptr->backend_flush_(); + return true; + } + + case async_msg_type::terminate: + { + return false; + } + + default: + { + log_msg msg; + incoming_async_msg.to_log_msg(msg); + incoming_async_msg.worker_ptr->backend_log_(msg); + return true; + } + } + return true; // should not be reached + } +}; + +} // namespace details +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/fmt/bundled/LICENSE.rst b/ifs/include/extern/spdlog/fmt/bundled/LICENSE.rst new file mode 100644 index 000000000..eb6be6503 --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/LICENSE.rst @@ -0,0 +1,23 @@ +Copyright (c) 2012 - 2016, Victor Zverovich + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ifs/include/extern/spdlog/fmt/bundled/colors.h b/ifs/include/extern/spdlog/fmt/bundled/colors.h new file mode 100644 index 000000000..003a6679e --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/colors.h @@ -0,0 +1,257 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for rgb color output. + +#ifndef FMT_COLORS_H_ +#define FMT_COLORS_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// rgb is a struct for red, green and blue colors. +// We use rgb as name because some editors will show it as color direct in the +// editor. +struct rgb +{ + FMT_CONSTEXPR_DECL rgb() + : r(0) + , g(0) + , b(0) + { + } + FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) + : r(r_) + , g(g_) + , b(b_) + { + } + FMT_CONSTEXPR_DECL rgb(uint32_t hex) + : r((hex >> 16) & 0xFF) + , g((hex >> 8) & 0xFF) + , b((hex)&0xFF) + { + } + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace internal { + +FMT_CONSTEXPR inline void to_esc(uint8_t c, char out[], int offset) +{ + out[offset + 0] = static_cast('0' + c / 100); + out[offset + 1] = static_cast('0' + c / 10 % 10); + out[offset + 2] = static_cast('0' + c % 10); +} + +} // namespace internal + +FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + std::fputs(escape_fd, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color + char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + internal::to_esc(bg.r, escape_bg, 7); + internal::to_esc(bg.g, escape_bg, 11); + internal::to_esc(bg.b, escape_bg, 15); + + std::fputs(escape_fd, stdout); + std::fputs(escape_bg, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +template +inline void print_rgb(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color +template +inline void print(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color and background color +template +inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, bg, format_str, make_format_args(args...)); +} + +enum class color : uint32_t +{ + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aqua_marine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32, // rgb(154,205,50) +}; // enum class colors + +FMT_END_NAMESPACE + +#endif // FMT_COLORS_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/core.h b/ifs/include/extern/spdlog/fmt/bundled/core.h new file mode 100644 index 000000000..19273a67a --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/core.h @@ -0,0 +1,1578 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CORE_H_ +#define FMT_CORE_H_ + +#include +#include +#include +#include +#include +#include + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 50100 + +#ifdef __has_feature +#define FMT_HAS_FEATURE(x) __has_feature(x) +#else +#define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_include +#define FMT_HAS_INCLUDE(x) __has_include(x) +#else +#define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +#define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +#define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +#define FMT_GCC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +#define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +#define FMT_HAS_GXX_CXX11 0 +#endif + +#ifdef _MSC_VER +#define FMT_MSC_VER _MSC_VER +#else +#define FMT_MSC_VER 0 +#endif + +// Check if relaxed c++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +#define FMT_USE_CONSTEXPR \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) +#endif +#if FMT_USE_CONSTEXPR +#define FMT_CONSTEXPR constexpr +#define FMT_CONSTEXPR_DECL constexpr +#else +#define FMT_CONSTEXPR inline +#define FMT_CONSTEXPR_DECL +#endif + +#ifndef FMT_OVERRIDE +#if FMT_HAS_FEATURE(cxx_override) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +#define FMT_OVERRIDE override +#else +#define FMT_OVERRIDE +#endif +#endif + +#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 +#define FMT_EXPLICIT explicit +#else +#define FMT_EXPLICIT +#endif + +#ifndef FMT_NULL +#if FMT_HAS_FEATURE(cxx_nullptr) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 +#define FMT_NULL nullptr +#define FMT_USE_NULLPTR 1 +#else +#define FMT_NULL NULL +#endif +#endif + +#ifndef FMT_USE_NULLPTR +#define FMT_USE_NULLPTR 0 +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) +#define FMT_NORETURN [[noreturn]] +#else +#define FMT_NORETURN +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +#define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VER && !_HAS_EXCEPTIONS +#define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +#define FMT_EXCEPTIONS 1 +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +#define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +#define FMT_DETECTED_NOEXCEPT noexcept +#else +#define FMT_DETECTED_NOEXCEPT throw() +#endif + +#ifndef FMT_NOEXCEPT +#if FMT_EXCEPTIONS +#define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +#define FMT_NOEXCEPT +#endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +#define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +#define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_BEGIN_NAMESPACE +#if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || FMT_MSC_VER >= 1900 +#define FMT_INLINE_NAMESPACE inline namespace +#define FMT_END_NAMESPACE \ + } \ + } +#else +#define FMT_INLINE_NAMESPACE namespace +#define FMT_END_NAMESPACE \ + } \ + using namespace v5; \ + } +#endif +#define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + FMT_INLINE_NAMESPACE v5 \ + { +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +#ifdef FMT_EXPORT +#define FMT_API __declspec(dllexport) +#elif defined(FMT_SHARED) +#define FMT_API __declspec(dllimport) +#endif +#endif +#ifndef FMT_API +#define FMT_API +#endif + +#ifndef FMT_ASSERT +#define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +#define FMT_DELETED = delete + +// A macro to disallow the copy construction and assignment. +#define FMT_DISALLOW_COPY_AND_ASSIGN(Type) \ + Type(const Type &) FMT_DELETED; \ + void operator=(const Type &) FMT_DELETED + +// libc++ supports string_view in pre-c++17. +#if (FMT_HAS_INCLUDE() && (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +#include +#define FMT_USE_STD_STRING_VIEW +#elif (FMT_HAS_INCLUDE() && __cplusplus >= 201402L) +#include +#define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +// std::result_of is defined in in gcc 4.4. +#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 +#include +#endif + +FMT_BEGIN_NAMESPACE + +namespace internal { + +// An implementation of declval for pre-C++11 compilers such as gcc 4. +template +typename std::add_rvalue_reference::type declval() FMT_NOEXCEPT; + +// Casts nonnegative integer to unsigned. +template +FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) +{ + FMT_ASSERT(value >= 0, "negative value"); + return static_cast::type>(value); +} + +} // namespace internal + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +template +class basic_string_view +{ +private: + const Char *data_; + size_t size_; + +public: + typedef Char char_type; + typedef const Char *iterator; + +// Standard basic_string_view type. +#if defined(FMT_USE_STD_STRING_VIEW) + typedef std::basic_string_view type; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) + typedef std::experimental::basic_string_view type; +#else + struct type + { + const char *data() const + { + return FMT_NULL; + } + size_t size() const + { + return 0; + } + }; +#endif + + FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT : data_(s), size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + \endrst + */ + basic_string_view(const Char *s) + : data_(s) + , size_(std::char_traits::length(s)) + { + } + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template + FMT_CONSTEXPR basic_string_view(const std::basic_string &s) FMT_NOEXCEPT : data_(s.c_str()), size_(s.size()) + { + } + + FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT : data_(s.data()), size_(s.size()) {} + + /** Returns a pointer to the string data. */ + const Char *data() const + { + return data_; + } + + /** Returns the string size. */ + FMT_CONSTEXPR size_t size() const + { + return size_; + } + + FMT_CONSTEXPR iterator begin() const + { + return data_; + } + FMT_CONSTEXPR iterator end() const + { + return data_ + size_; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) + { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + int compare(basic_string_view other) const + { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) != 0; + } + friend bool operator<(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(basic_string_view lhs, basic_string_view rhs) + { + return lhs.compare(rhs) >= 0; + } +}; + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; + +template +class basic_format_arg; + +template +class basic_format_args; + +// A formatter for objects of type T. +template +struct formatter; + +namespace internal { + +/** A contiguous memory buffer with an optional growing ability. */ +template +class basic_buffer +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(basic_buffer); + + T *ptr_; + std::size_t size_; + std::size_t capacity_; + +protected: + basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) FMT_NOEXCEPT : ptr_(p), size_(sz), capacity_(cap) {} + + /** Sets the buffer data and capacity. */ + void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT + { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual void grow(std::size_t capacity) = 0; + +public: + typedef T value_type; + typedef const T &const_reference; + + virtual ~basic_buffer() {} + + T *begin() FMT_NOEXCEPT + { + return ptr_; + } + T *end() FMT_NOEXCEPT + { + return ptr_ + size_; + } + + /** Returns the size of this buffer. */ + std::size_t size() const FMT_NOEXCEPT + { + return size_; + } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const FMT_NOEXCEPT + { + return capacity_; + } + + /** Returns a pointer to the buffer data. */ + T *data() FMT_NOEXCEPT + { + return ptr_; + } + + /** Returns a pointer to the buffer data. */ + const T *data() const FMT_NOEXCEPT + { + return ptr_; + } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) + { + reserve(new_size); + size_ = new_size; + } + + /** Reserves space to store at least *capacity* elements. */ + void reserve(std::size_t new_capacity) + { + if (new_capacity > capacity_) + grow(new_capacity); + } + + void push_back(const T &value) + { + reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) + { + return ptr_[index]; + } + const T &operator[](std::size_t index) const + { + return ptr_[index]; + } +}; + +typedef basic_buffer buffer; +typedef basic_buffer wbuffer; + +// A container-backed buffer. +template +class container_buffer : public basic_buffer +{ +private: + Container &container_; + +protected: + void grow(std::size_t capacity) FMT_OVERRIDE + { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + +public: + explicit container_buffer(Container &c) + : basic_buffer(&c[0], c.size(), c.size()) + , container_(c) + { + } +}; + +struct error_handler +{ + FMT_CONSTEXPR error_handler() {} + FMT_CONSTEXPR error_handler(const error_handler &) {} + + // This function is intentionally not constexpr to give a compile-time error. + FMT_API void on_error(const char *message); +}; + +// Formatting of wide characters and strings into a narrow output is disallowed: +// fmt::format("{}", L"test"); // error +// To fix this, use a wide format string: +// fmt::format(L"{}", L"test"); +template +inline void require_wchar() +{ + static_assert(std::is_same::value, "formatting of wide characters into a narrow output is disallowed"); +} + +template +struct named_arg_base; + +template +struct named_arg; + +template +struct is_named_arg : std::false_type +{ +}; + +template +struct is_named_arg> : std::true_type +{ +}; + +enum type +{ + none_type, + named_arg_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +FMT_CONSTEXPR bool is_integral(type t) +{ + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_integer_type; +} + +FMT_CONSTEXPR bool is_arithmetic(type t) +{ + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_numeric_type; +} + +template +struct convert_to_int +{ + enum + { + value = !std::is_arithmetic::value && std::is_convertible::value + }; +}; + +template +struct string_value +{ + const Char *value; + std::size_t size; +}; + +template +struct custom_value +{ + const void *value; + void (*format)(const void *arg, Context &ctx); +}; + +// A formatting argument value. +template +class value +{ +public: + typedef typename Context::char_type char_type; + + union + { + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + string_value string; + string_value sstring; + string_value ustring; + custom_value custom; + }; + + FMT_CONSTEXPR value(int val = 0) + : int_value(val) + { + } + value(unsigned val) + { + uint_value = val; + } + value(long long val) + { + long_long_value = val; + } + value(unsigned long long val) + { + ulong_long_value = val; + } + value(double val) + { + double_value = val; + } + value(long double val) + { + long_double_value = val; + } + value(const char_type *val) + { + string.value = val; + } + value(const signed char *val) + { + static_assert(std::is_same::value, "incompatible string types"); + sstring.value = val; + } + value(const unsigned char *val) + { + static_assert(std::is_same::value, "incompatible string types"); + ustring.value = val; + } + value(basic_string_view val) + { + string.value = val.data(); + string.size = val.size(); + } + value(const void *val) + { + pointer = val; + } + + template + explicit value(const T &val) + { + custom.value = &val; + custom.format = &format_custom_arg; + } + + const named_arg_base &as_named_arg() + { + return *static_cast *>(pointer); + } + +private: + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg(const void *arg, Context &ctx) + { + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + typename Context::template formatter_type::type f; + auto &&parse_ctx = ctx.parse_context(); + parse_ctx.advance_to(f.parse(parse_ctx)); + ctx.advance_to(f.format(*static_cast(arg), ctx)); + } +}; + +template +struct typed_value : value +{ + static const type type_tag = TYPE; + + template + FMT_CONSTEXPR typed_value(const T &val) + : value(val) + { + } +}; + +template +FMT_CONSTEXPR basic_format_arg make_arg(const T &value); + +#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ + template \ + FMT_CONSTEXPR typed_value make_value(ArgType val) \ + { \ + return static_cast(val); \ + } + +#define FMT_MAKE_VALUE_SAME(TAG, Type) \ + template \ + FMT_CONSTEXPR typed_value make_value(Type val) \ + { \ + return val; \ + } + +FMT_MAKE_VALUE(bool_type, bool, int) +FMT_MAKE_VALUE(int_type, short, int) +FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) +FMT_MAKE_VALUE_SAME(int_type, int) +FMT_MAKE_VALUE_SAME(uint_type, unsigned) + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +typedef std::conditional::type long_type; +FMT_MAKE_VALUE((sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) +typedef std::conditional::type ulong_type; +FMT_MAKE_VALUE((sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), unsigned long, ulong_type) + +FMT_MAKE_VALUE_SAME(long_long_type, long long) +FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) +FMT_MAKE_VALUE(int_type, signed char, int) +FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) +FMT_MAKE_VALUE(char_type, char, int) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +template +inline typed_value make_value(wchar_t val) +{ + require_wchar(); + return static_cast(val); +} +#endif + +FMT_MAKE_VALUE(double_type, float, double) +FMT_MAKE_VALUE_SAME(double_type, double) +FMT_MAKE_VALUE_SAME(long_double_type, long double) + +// Formatting of wide strings into a narrow buffer and multibyte strings +// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). +FMT_MAKE_VALUE(cstring_type, typename C::char_type *, const typename C::char_type *) +FMT_MAKE_VALUE(cstring_type, const typename C::char_type *, const typename C::char_type *) + +FMT_MAKE_VALUE(cstring_type, signed char *, const signed char *) +FMT_MAKE_VALUE_SAME(cstring_type, const signed char *) +FMT_MAKE_VALUE(cstring_type, unsigned char *, const unsigned char *) +FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char *) +FMT_MAKE_VALUE_SAME(string_type, basic_string_view) +FMT_MAKE_VALUE(string_type, typename basic_string_view::type, basic_string_view) +FMT_MAKE_VALUE(string_type, const std::basic_string &, basic_string_view) +FMT_MAKE_VALUE(pointer_type, void *, const void *) +FMT_MAKE_VALUE_SAME(pointer_type, const void *) + +#if FMT_USE_NULLPTR +FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void *) +#endif + +// Formatting of arbitrary pointers is disallowed. If you want to output a +// pointer cast it to "void *" or "const void *". In particular, this forbids +// formatting of "[const] volatile char *" which is printed as bool by +// iostreams. +template +typename std::enable_if::value>::type make_value(const T *) +{ + static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); +} + +template +inline typename std::enable_if::value && convert_to_int::value, typed_value>::type +make_value(const T &val) +{ + return static_cast(val); +} + +template +inline typename std::enable_if::value && !std::is_convertible>::value, + // Implicit conversion to std::string is not handled here because it's + // unsafe: https://github.com/fmtlib/fmt/issues/729 + typed_value>::type +make_value(const T &val) +{ + return val; +} + +template +typed_value make_value(const named_arg &val) +{ + basic_format_arg arg = make_arg(val.value); + std::memcpy(val.data, &arg, sizeof(arg)); + return static_cast(&val); +} + +// Maximum number of arguments with packed types. +enum +{ + max_packed_args = 15 +}; + +template +class arg_map; + +template +struct result_of; + +template +struct result_of +{ + // A workaround for gcc 4.4 that doesn't allow F to be a reference. + typedef typename std::result_of::type(Args...)>::type type; +}; +} // namespace internal + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template +class basic_format_arg +{ +private: + internal::value value_; + internal::type type_; + + template + friend FMT_CONSTEXPR basic_format_arg internal::make_arg(const T &value); + + template + friend FMT_CONSTEXPR typename internal::result_of::type visit(Visitor &&vis, basic_format_arg arg); + + friend class basic_format_args; + friend class internal::arg_map; + + typedef typename Context::char_type char_type; + +public: + class handle + { + public: + explicit handle(internal::custom_value custom) + : custom_(custom) + { + } + + void format(Context &ctx) const + { + custom_.format(custom_.value, ctx); + } + + private: + internal::custom_value custom_; + }; + + FMT_CONSTEXPR basic_format_arg() + : type_(internal::none_type) + { + } + + FMT_EXPLICIT operator bool() const FMT_NOEXCEPT + { + return type_ != internal::none_type; + } + + internal::type type() const + { + return type_; + } + + bool is_integral() const + { + return internal::is_integral(type_); + } + bool is_arithmetic() const + { + return internal::is_arithmetic(type_); + } +}; + +// Parsing context consisting of a format string range being parsed and an +// argument counter for automatic indexing. +template +class basic_parse_context : private ErrorHandler +{ +private: + basic_string_view format_str_; + int next_arg_id_; + +public: + typedef Char char_type; + typedef typename basic_string_view::iterator iterator; + + explicit FMT_CONSTEXPR basic_parse_context(basic_string_view format_str, ErrorHandler eh = ErrorHandler()) + : ErrorHandler(eh) + , format_str_(format_str) + , next_arg_id_(0) + { + } + + // Returns an iterator to the beginning of the format string range being + // parsed. + FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT + { + return format_str_.begin(); + } + + // Returns an iterator past the end of the format string range being parsed. + FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT + { + return format_str_.end(); + } + + // Advances the begin iterator to ``it``. + FMT_CONSTEXPR void advance_to(iterator it) + { + format_str_.remove_prefix(internal::to_unsigned(it - begin())); + } + + // Returns the next argument index. + FMT_CONSTEXPR unsigned next_arg_id(); + + FMT_CONSTEXPR bool check_arg_id(unsigned) + { + if (next_arg_id_ > 0) + { + on_error("cannot switch from automatic to manual argument indexing"); + return false; + } + next_arg_id_ = -1; + return true; + } + void check_arg_id(basic_string_view) {} + + FMT_CONSTEXPR void on_error(const char *message) + { + ErrorHandler::on_error(message); + } + + FMT_CONSTEXPR ErrorHandler error_handler() const + { + return *this; + } +}; + +typedef basic_parse_context parse_context; +typedef basic_parse_context wparse_context; + +namespace internal { +// A map from argument names to their values for named arguments. +template +class arg_map +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(arg_map); + + typedef typename Context::char_type char_type; + + struct entry + { + basic_string_view name; + basic_format_arg arg; + }; + + entry *map_; + unsigned size_; + + void push_back(value val) + { + const internal::named_arg_base &named = val.as_named_arg(); + map_[size_] = entry{named.name, named.template deserialize()}; + ++size_; + } + +public: + arg_map() + : map_(FMT_NULL) + , size_(0) + { + } + void init(const basic_format_args &args); + ~arg_map() + { + delete[] map_; + } + + basic_format_arg find(basic_string_view name) const + { + // The list is unsorted, so just return the first matching name. + for (entry *it = map_, *end = map_ + size_; it != end; ++it) + { + if (it->name == name) + return it->arg; + } + return basic_format_arg(); + } +}; + +template +class context_base +{ +public: + typedef OutputIt iterator; + +private: + basic_parse_context parse_context_; + iterator out_; + basic_format_args args_; + +protected: + typedef Char char_type; + typedef basic_format_arg format_arg; + + context_base(OutputIt out, basic_string_view format_str, basic_format_args ctx_args) + : parse_context_(format_str) + , out_(out) + , args_(ctx_args) + { + } + + // Returns the argument with specified index. + format_arg do_get_arg(unsigned arg_id) + { + format_arg arg = args_.get(arg_id); + if (!arg) + parse_context_.on_error("argument index out of range"); + return arg; + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + format_arg get_arg(unsigned arg_id) + { + return this->parse_context().check_arg_id(arg_id) ? this->do_get_arg(arg_id) : format_arg(); + } + +public: + basic_parse_context &parse_context() + { + return parse_context_; + } + + internal::error_handler error_handler() + { + return parse_context_.error_handler(); + } + + void on_error(const char *message) + { + parse_context_.on_error(message); + } + + // Returns an iterator to the beginning of the output range. + iterator out() + { + return out_; + } + iterator begin() + { + return out_; + } // deprecated + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) + { + out_ = it; + } + + basic_format_args args() const + { + return args_; + } +}; + +// Extracts a reference to the container from back_insert_iterator. +template +inline Container &get_container(std::back_insert_iterator it) +{ + typedef std::back_insert_iterator bi_iterator; + struct accessor : bi_iterator + { + accessor(bi_iterator iter) + : bi_iterator(iter) + { + } + using bi_iterator::container; + }; + return *accessor(it).container; +} +} // namespace internal + +// Formatting context. +template +class basic_format_context : public internal::context_base, Char> +{ +public: + /** The character type for the output. */ + typedef Char char_type; + + // using formatter_type = formatter; + template + struct formatter_type + { + typedef formatter type; + }; + +private: + internal::arg_map map_; + + FMT_DISALLOW_COPY_AND_ASSIGN(basic_format_context); + + typedef internal::context_base base; + typedef typename base::format_arg format_arg; + using base::get_arg; + +public: + using typename base::iterator; + + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + basic_format_context(OutputIt out, basic_string_view format_str, basic_format_args ctx_args) + : base(out, format_str, ctx_args) + { + } + + format_arg next_arg() + { + return this->do_get_arg(this->parse_context().next_arg_id()); + } + format_arg get_arg(unsigned arg_id) + { + return this->do_get_arg(arg_id); + } + + // Checks if manual indexing is used and returns the argument with the + // specified name. + format_arg get_arg(basic_string_view name); +}; + +template +struct buffer_context +{ + typedef basic_format_context>, Char> type; +}; +typedef buffer_context::type format_context; +typedef buffer_context::type wformat_context; + +namespace internal { +template +struct get_type +{ + typedef decltype(make_value(declval::type &>())) value_type; + static const type value = value_type::type_tag; +}; + +template +FMT_CONSTEXPR unsigned long long get_types() +{ + return 0; +} + +template +FMT_CONSTEXPR unsigned long long get_types() +{ + return get_type::value | (get_types() << 4); +} + +template +FMT_CONSTEXPR basic_format_arg make_arg(const T &value) +{ + basic_format_arg arg; + arg.type_ = get_type::value; + arg.value_ = make_value(value); + return arg; +} + +template +inline typename std::enable_if>::type make_arg(const T &value) +{ + return make_value(value); +} + +template +inline typename std::enable_if>::type make_arg(const T &value) +{ + return make_arg(value); +} +} // namespace internal + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template +class format_arg_store +{ +private: + static const size_t NUM_ARGS = sizeof...(Args); + + // Packed is a macro on MinGW so use IS_PACKED instead. + static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; + + typedef typename std::conditional, basic_format_arg>::type value_type; + + // If the arguments are not packed, add one more element to mark the end. + static const size_t DATA_SIZE = NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); + value_type data_[DATA_SIZE]; + + friend class basic_format_args; + + static FMT_CONSTEXPR long long get_types() + { + return IS_PACKED ? static_cast(internal::get_types()) : -static_cast(NUM_ARGS); + } + +public: +#if FMT_USE_CONSTEXPR + static constexpr long long TYPES = get_types(); +#else + static const long long TYPES; +#endif + +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || (FMT_MSC_VER && FMT_MSC_VER <= 1800) + // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. + format_arg_store(const Args &... args) + { + value_type init[DATA_SIZE] = {internal::make_arg(args)...}; + std::memcpy(data_, init, sizeof(init)); + } +#else + format_arg_store(const Args &... args) + : data_{internal::make_arg(args)...} + { + } +#endif +}; + +#if !FMT_USE_CONSTEXPR +template +const long long format_arg_store::TYPES = get_types(); +#endif + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can + be omitted in which case it defaults to `~fmt::context`. + \endrst + */ +template +inline format_arg_store make_format_args(const Args &... args) +{ + return format_arg_store(args...); +} + +template +inline format_arg_store make_format_args(const Args &... args) +{ + return format_arg_store(args...); +} + +/** Formatting arguments. */ +template +class basic_format_args +{ +public: + typedef unsigned size_type; + typedef basic_format_arg format_arg; + +private: + // To reduce compiled code size per formatting function call, types of first + // max_packed_args arguments are passed in the types_ field. + unsigned long long types_; + union + { + // If the number of arguments is less than max_packed_args, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::value *values_; + const format_arg *args_; + }; + + typename internal::type type(unsigned index) const + { + unsigned shift = index * 4; + unsigned long long mask = 0xf; + return static_cast((types_ & (mask << shift)) >> shift); + } + + friend class internal::arg_map; + + void set_data(const internal::value *values) + { + values_ = values; + } + void set_data(const format_arg *args) + { + args_ = args; + } + + format_arg do_get(size_type index) const + { + long long signed_types = static_cast(types_); + if (signed_types < 0) + { + unsigned long long num_args = static_cast(-signed_types); + return index < num_args ? args_[index] : format_arg(); + } + format_arg arg; + if (index > internal::max_packed_args) + return arg; + arg.type_ = type(index); + if (arg.type_ == internal::none_type) + return arg; + internal::value &val = arg.value_; + val = values_[index]; + return arg; + } + +public: + basic_format_args() + : types_(0) + { + } + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template + basic_format_args(const format_arg_store &store) + : types_(static_cast(store.TYPES)) + { + set_data(store.data_); + } + + /** Returns the argument at specified index. */ + format_arg get(size_type index) const + { + format_arg arg = do_get(index); + return arg.type_ == internal::named_arg_type ? arg.value_.as_named_arg().template deserialize() : arg; + } + + unsigned max_size() const + { + long long signed_types = static_cast(types_); + return static_cast(signed_types < 0 ? -signed_types : static_cast(internal::max_packed_args)); + } +}; + +/** An alias to ``basic_format_args``. */ +// It is a separate type rather than a typedef to make symbols readable. +struct format_args : basic_format_args +{ + template + format_args(Args &&... arg) + : basic_format_args(std::forward(arg)...) + { + } +}; +struct wformat_args : basic_format_args +{ + template + wformat_args(Args &&... arg) + : basic_format_args(std::forward(arg)...) + { + } +}; + +namespace internal { +template +struct named_arg_base +{ + basic_string_view name; + + // Serialized value. + mutable char data[sizeof(basic_format_arg)]; + + named_arg_base(basic_string_view nm) + : name(nm) + { + } + + template + basic_format_arg deserialize() const + { + basic_format_arg arg; + std::memcpy(&arg, data, sizeof(basic_format_arg)); + return arg; + } +}; + +template +struct named_arg : named_arg_base +{ + const T &value; + + named_arg(basic_string_view name, const T &val) + : named_arg_base(name) + , value(val) + { + } +}; +} // namespace internal + +/** + \rst + Returns a named argument to be used in a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline internal::named_arg arg(string_view name, const T &arg) +{ + return internal::named_arg(name, arg); +} + +template +inline internal::named_arg arg(wstring_view name, const T &arg) +{ + return internal::named_arg(name, arg); +} + +// This function template is deleted intentionally to disable nested named +// arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template +void arg(S, internal::named_arg) FMT_DELETED; + +#ifndef FMT_EXTENDED_COLORS +// color and (v)print_colored are deprecated. +enum color +{ + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white +}; +FMT_API void vprint_colored(color c, string_view format, format_args args); +FMT_API void vprint_colored(color c, wstring_view format, wformat_args args); +template +inline void print_colored(color c, string_view format_str, const Args &... args) +{ + vprint_colored(c, format_str, make_format_args(args...)); +} +template +inline void print_colored(color c, wstring_view format_str, const Args &... args) +{ + vprint_colored(c, format_str, make_format_args(args...)); +} +#endif + +format_context::iterator vformat_to(internal::buffer &buf, string_view format_str, format_args args); +wformat_context::iterator vformat_to(internal::wbuffer &buf, wstring_view format_str, wformat_args args); + +template +struct is_contiguous : std::false_type +{ +}; + +template +struct is_contiguous> : std::true_type +{ +}; + +template +struct is_contiguous> : std::true_type +{ +}; + +/** Formats a string and writes the output to ``out``. */ +template +typename std::enable_if::value, std::back_insert_iterator>::type vformat_to( + std::back_insert_iterator out, string_view format_str, format_args args) +{ + auto &container = internal::get_container(out); + internal::container_buffer buf(container); + vformat_to(buf, format_str, args); + return std::back_inserter(container); +} + +template +typename std::enable_if::value, std::back_insert_iterator>::type vformat_to( + std::back_insert_iterator out, wstring_view format_str, wformat_args args) +{ + auto &container = internal::get_container(out); + internal::container_buffer buf(container); + vformat_to(buf, format_str, args); + return std::back_inserter(container); +} + +std::string vformat(string_view format_str, format_args args); +std::wstring vformat(wstring_view format_str, wformat_args args); + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + #include + std::string message = fmt::format("The answer is {}", 42); + \endrst +*/ +template +inline std::string format(string_view format_str, const Args &... args) +{ + // This should be just + // return vformat(format_str, make_format_args(args...)); + // but gcc has trouble optimizing the latter, so break it down. + format_arg_store as{args...}; + return vformat(format_str, as); +} +template +inline std::wstring format(wstring_view format_str, const Args &... args) +{ + format_arg_store as{args...}; + return vformat(format_str, as); +} + +FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); +FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template +inline void print(std::FILE *f, string_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(f, format_str, as); +} +/** + Prints formatted data to the file *f* which should be in wide-oriented mode + set + via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. + */ +template +inline void print(std::FILE *f, wstring_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(f, format_str, as); +} + +FMT_API void vprint(string_view format_str, format_args args); +FMT_API void vprint(wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template +inline void print(string_view format_str, const Args &... args) +{ + format_arg_store as{args...}; + vprint(format_str, as); +} + +template +inline void print(wstring_view format_str, const Args &... args) +{ + format_arg_store as(args...); + vprint(format_str, as); +} +FMT_END_NAMESPACE + +#endif // FMT_CORE_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/format-inl.h b/ifs/include/extern/spdlog/fmt/bundled/format-inl.h new file mode 100644 index 000000000..1aba8c6e4 --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/format-inl.h @@ -0,0 +1,578 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#include "format.h" + +#include + +#include +#include +#include +#include +#include +#include // for std::ptrdiff_t +#include + +#if defined(_WIN32) && defined(__MINGW32__) +#include +#endif + +#if FMT_USE_WINDOWS_H +#if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +#define WIN32_LEAN_AND_MEAN +#endif +#if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +#include +#else +#define NOMINMAX +#include +#undef NOMINMAX +#endif +#endif + +#if FMT_EXCEPTIONS +#define FMT_TRY try +#define FMT_CATCH(x) catch (x) +#else +#define FMT_TRY if (true) +#define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +#pragma warning(disable : 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +inline fmt::internal::null<> strerror_r(int, char *, ...) +{ + return fmt::internal::null<>(); +} +inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) +{ + return fmt::internal::null<>(); +} + +FMT_BEGIN_NAMESPACE + +namespace { + +#ifndef _MSC_VER +#define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) +{ + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +#define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +#define FMT_SWPRINTF snwprintf +#else +#define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +typedef void (*FormatFunc)(internal::buffer &, int, string_view); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror(int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT +{ + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); + + class dispatcher + { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const dispatcher &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) + { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) + { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::null<>) + { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) + { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::null<>) + { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + dispatcher(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code) + , buffer_(buf) + , buffer_size_(buf_size) + { + } + + int run() + { + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return dispatcher(error_code, buffer, buffer_size).run(); +} + +void format_error_code(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::int_traits::main_type main_type; + main_type abs_value = static_cast(error_code); + if (internal::is_negative(error_code)) + { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + writer w(out); + if (message.size() <= inline_buffer_size - error_code_size) + { + w.write(message); + w.write(SEP); + } + w.write(ERROR_STR); + w.write(error_code); + assert(out.size() <= inline_buffer_size); +} + +void report_error(FormatFunc func, int error_code, string_view message) FMT_NOEXCEPT +{ + memory_buffer full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +class locale +{ +private: + std::locale locale_; + +public: + explicit locale(std::locale loc = std::locale()) + : locale_(loc) + { + } + std::locale get() + { + return locale_; + } +}; + +template +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) +{ + std::locale loc = lp ? lp->locale().get() : std::locale(); + return std::use_facet>(loc).thousands_sep(); +} + +FMT_FUNC void system_error::init(int err_code, string_view format_str, format_args args) +{ + error_code_ = err_code; + memory_buffer buffer; + format_system_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +namespace internal { +template +int char_traits::format_float(char *buffer, std::size_t size, const char *format, int precision, T value) +{ + return precision < 0 ? FMT_SNPRINTF(buffer, size, format, value) : FMT_SNPRINTF(buffer, size, format, precision, value); +} + +template +int char_traits::format_float(wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, T value) +{ + return precision < 0 ? FMT_SWPRINTF(buffer, size, format, value) : FMT_SWPRINTF(buffer, size, format, precision, value); +} + +template +const char basic_data::DIGITS[] = "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, factor * 100, factor * 1000, factor * 10000, factor * 100000, factor * 1000000, factor * 10000000, factor * 100000000, \ + factor * 1000000000 + +template +const uint32_t basic_data::POWERS_OF_10_32[] = {0, FMT_POWERS_OF_10(1)}; + +template +const uint64_t basic_data::POWERS_OF_10_64[] = {0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ull), 10000000000000000000ull}; + +// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. +// These are generated by support/compute-powers.py. +template +const uint64_t basic_data::POW10_SIGNIFICANDS[] = {0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, 0xcf42894a5dce35ea, + 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, 0x8dd01fad907ffc3c, + 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, 0xc21094364dfb5637, + 0x9096ea6f3848984f, 0xd77485cb25823ac7, 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, 0x84c8d4dfd2c63f3b, + 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, 0xb5b5ada8aaff80b8, + 0x87625f056c7c4a8b, 0xc9bcff6034c13053, 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, 0xf8a95fcf88747d94, + 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, 0xaa242499697392d3, + 0xfd87b5f28300ca0e, 0xbce5086492111aeb, 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, 0xe8d4a51000000000, + 0xad78ebc5ac620000, 0x813f3978f8940984, 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, 0x9f4f2726179a2245, + 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, 0xda01ee641a708dea, + 0xa26da3999aef774a, 0xf209787bb47d6b85, 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, 0x952ab45cfa97a0b3, + 0xde469fbd99a05fe3, 0xa59bc234db398c25, 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, 0xcc20ce9bd35c78a5, + 0x98165af37b2153df, 0xe2a0b5dc971f303a, 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, 0x8bab8eefb6409c1a, + 0xd01fef10a657842c, 0x9b10a4e5e9913129, 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, 0xbf21e44003acdd2d, + 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b}; + +// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding +// to significands above. +template +const int16_t basic_data::POW10_EXPONENTS[] = {-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, -927, -901, + -874, -847, -821, -794, -768, -741, -715, -688, -661, -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, -343, -316, + -289, -263, -236, -210, -183, -157, -130, -103, -77, -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, 242, 269, 295, 322, 348, 375, + 402, 428, 455, 481, 508, 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; + +template +const char basic_data::RESET_COLOR[] = "\x1b[0m"; +template +const wchar_t basic_data::WRESET_COLOR[] = L"\x1b[0m"; + +FMT_FUNC fp operator*(fp x, fp y) +{ + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = x.f >> 32, b = x.f & mask; + uint64_t c = y.f >> 32, d = y.f & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); +} + +FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) +{ + const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) + int index = static_cast(std::ceil((min_exponent + fp::significand_size - 1) * one_over_log2_10)); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between two consecutive decimal exponents in cached powers of + // 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); +} +} // namespace internal + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) +{ + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast(s.size()); + if (s_size == 0) + { + // MultiByteToWideChar does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return; + } + + int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) +{ + if (int error_code = convert(s)) + { + FMT_THROW(windows_error(error_code, "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) +{ + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + if (s_size == 0) + { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void windows_error::init(int err_code, string_view format_str, format_args args) +{ + error_code_ = err_code; + memory_buffer buffer; + internal::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +FMT_FUNC void internal::format_windows_error(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + FMT_TRY + { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) + { + wchar_t *system_message = &buf[0]; + int result = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, FMT_NULL, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message, static_cast(buf.size()), FMT_NULL); + if (result != 0) + { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) + { + writer w(out); + w.write(message); + w.write(": "); + w.write(utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error(internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT +{ + FMT_TRY + { + memory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) + { + char *system_message = &buf[0]; + int result = safe_strerror(error_code, system_message, buf.size()); + if (result == 0) + { + writer w(out); + w.write(message); + w.write(": "); + w.write(system_message); + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +template +void basic_fixed_buffer::grow(std::size_t) +{ + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC void internal::error_handler::on_error(const char *message) +{ + FMT_THROW(format_error(message)); +} + +FMT_FUNC void report_system_error(int error_code, fmt::string_view message) FMT_NOEXCEPT +{ + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error(int error_code, fmt::string_view message) FMT_NOEXCEPT +{ + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) +{ + memory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), 1, buffer.size(), f); +} + +FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) +{ + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); +} + +FMT_FUNC void vprint(string_view format_str, format_args args) +{ + vprint(stdout, format_str, args); +} + +FMT_FUNC void vprint(wstring_view format_str, wformat_args args) +{ + vprint(stdout, format_str, args); +} + +#ifndef FMT_EXTENDED_COLORS +FMT_FUNC void vprint_colored(color c, string_view format, format_args args) +{ + char escape[] = "\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputs(escape, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_colored(color c, wstring_view format, wformat_args args) +{ + wchar_t escape[] = L"\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputws(escape, stdout); + vprint(format, args); + std::fputws(internal::data::WRESET_COLOR, stdout); +} +#else +namespace internal { +FMT_CONSTEXPR void to_esc(uint8_t c, char out[], int offset) +{ + out[offset + 0] = static_cast('0' + c / 100); + out[offset + 1] = static_cast('0' + c / 10 % 10); + out[offset + 2] = static_cast('0' + c % 10); +} +} // namespace internal + +FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + std::fputs(escape_fd, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color + char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + internal::to_esc(bg.r, escape_bg, 7); + internal::to_esc(bg.g, escape_bg, 11); + internal::to_esc(bg.b, escape_bg, 15); + + std::fputs(escape_fd, stdout); + std::fputs(escape_bg, stdout); + vprint(format, args); + std::fputs(internal::data::RESET_COLOR, stdout); +} +#endif + +FMT_FUNC locale locale_provider::locale() +{ + return fmt::locale(); +} + +FMT_END_NAMESPACE + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // FMT_FORMAT_INL_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/format.cc b/ifs/include/extern/spdlog/fmt/bundled/format.cc deleted file mode 100644 index fd8855be4..000000000 --- a/ifs/include/extern/spdlog/fmt/bundled/format.cc +++ /dev/null @@ -1,583 +0,0 @@ -/* -Formatting library for C++ - -Copyright (c) 2012 - 2016, Victor Zverovich -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "format.h" - -#include - -#include -#include -#include -#include -#include -#include // for std::ptrdiff_t - -#if defined(_WIN32) && defined(__MINGW32__) -# include -#endif - -#if FMT_USE_WINDOWS_H -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include -# else -# define NOMINMAX -# include -# undef NOMINMAX -# endif -#endif - -using fmt::internal::Arg; - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4702) // unreachable code -// Disable deprecation warning for strerror. The latter is not called but -// MSVC fails to detect it. -# pragma warning(disable: 4996) -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -static inline fmt::internal::Null<> strerror_r(int, char *, ...) -{ - return fmt::internal::Null<>(); -} -static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) -{ - return fmt::internal::Null<>(); -} - -namespace fmt { - - FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT - {} - FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT - {} - FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT - {} - - namespace { - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER - inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) - { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; - } -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) -# define FMT_SWPRINTF snwprintf -#else -# define FMT_SWPRINTF swprintf -#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) - - const char RESET_COLOR[] = "\x1b[0m"; - - typedef void(*FormatFunc)(Writer &, int, StringRef); - - // Portable thread-safe version of strerror. - // Sets buffer to point to a string describing the error code. - // This can be either a pointer to a string stored in buffer, - // or a pointer to some static immutable string. - // Returns one of the following values: - // 0 - success - // ERANGE - buffer is not large enough to store the error message - // other - failure - // Buffer should be at least of size 1. - int safe_strerror( - int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT - { - FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); - - class StrError - { - private: - int error_code_; - char *&buffer_; - std::size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const StrError &) - {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) - { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - int handle(char *message) - { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - int handle(internal::Null<>) - { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - int fallback(int result) - { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? - ERANGE : result; - } - - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::Null<>) - { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } - - public: - StrError(int err_code, char *&buf, std::size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) - {} - - int run() - { - // Suppress a warning about unused strerror_r. - strerror_r(0, FMT_NULL, ""); - return handle(strerror_r(error_code_, buffer_, buffer_size_)); - } - }; - return StrError(error_code, buffer, buffer_size).run(); - } - - void format_error_code(Writer &out, int error_code, - StringRef message) FMT_NOEXCEPT - { - // Report error code making sure that the output fits into - // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential - // bad_alloc. - out.clear(); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::IntTraits::MainType MainType; - MainType abs_value = static_cast(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::count_digits(abs_value); - if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) - out << message << SEP; - out << ERROR_STR << error_code; - assert(out.size() <= internal::INLINE_BUFFER_SIZE); - } - - void report_error(FormatFunc func, int error_code, - StringRef message) FMT_NOEXCEPT - { - MemoryWriter full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); - } - } // namespace - - namespace internal { - - // This method is used to preserve binary compatibility with fmt 3.0. - // It can be removed in 4.0. - FMT_FUNC void format_system_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT - { - fmt::format_system_error(out, error_code, message); - } - } // namespace internal - - FMT_FUNC void SystemError::init( - int err_code, CStringRef format_str, ArgList args) - { - error_code_ = err_code; - MemoryWriter w; - format_system_error(w, err_code, format(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(w.str()); - } - - template - int internal::CharTraits::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, T value) - { - if (width == 0) { - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, value) : - FMT_SNPRINTF(buffer, size, format, precision, value); - } - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, width, value) : - FMT_SNPRINTF(buffer, size, format, width, precision, value); - } - - template - int internal::CharTraits::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, T value) - { - if (width == 0) { - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, value) : - FMT_SWPRINTF(buffer, size, format, precision, value); - } - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, width, value) : - FMT_SWPRINTF(buffer, size, format, width, precision, value); - } - - template - const char internal::BasicData::DIGITS[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, \ - factor * 100, \ - factor * 1000, \ - factor * 10000, \ - factor * 100000, \ - factor * 1000000, \ - factor * 10000000, \ - factor * 100000000, \ - factor * 1000000000 - - template - const uint32_t internal::BasicData::POWERS_OF_10_32[] = { - 0, FMT_POWERS_OF_10(1) - }; - - template - const uint64_t internal::BasicData::POWERS_OF_10_64[] = { - 0, - FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(ULongLong(1000000000)), - // Multiply several constants instead of using a single long long constant - // to avoid warnings about C++98 not supporting long long. - ULongLong(1000000000) * ULongLong(1000000000) * 10 - }; - - FMT_FUNC void internal::report_unknown_type(char code, const char *type) - { - (void)type; - if (std::isprint(static_cast(code))) { - FMT_THROW(FormatError( - format("unknown format code '{}' for {}", code, type))); - } - FMT_THROW(FormatError( - format("unknown format code '\\x{:02x}' for {}", - static_cast(code), type))); - } - -#if FMT_USE_WINDOWS_H - - FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) - { - static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; - if (s.size() > INT_MAX) - FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); - int s_size = static_cast(s.size()); - int length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); - if (length == 0) - FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); - buffer_.resize(length + 1); - length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); - if (length == 0) - FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); - buffer_[length] = 0; - } - - FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) - { - if (int error_code = convert(s)) { - FMT_THROW(WindowsError(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } - } - - FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) - { - if (s.size() > INT_MAX) - return ERROR_INVALID_PARAMETER; - int s_size = static_cast(s.size()); - int length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_[length] = 0; - return 0; - } - - FMT_FUNC void WindowsError::init( - int err_code, CStringRef format_str, ArgList args) - { - error_code_ = err_code; - MemoryWriter w; - internal::format_windows_error(w, err_code, format(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(w.str()); - } - - FMT_FUNC void internal::format_windows_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT - { - FMT_TRY{ - MemoryBuffer buffer; - buffer.resize(INLINE_BUFFER_SIZE); - for (;;) { - wchar_t *system_message = &buffer[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - system_message, static_cast(buffer.size()), FMT_NULL); - if (result != 0) { - UTF16ToUTF8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - out << message << ": " << utf8_message; - return; - } - break; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buffer.resize(buffer.size() * 2); - } - } FMT_CATCH(...) - {} - fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. - } - -#endif // FMT_USE_WINDOWS_H - - FMT_FUNC void format_system_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT - { - FMT_TRY{ - internal::MemoryBuffer buffer; - buffer.resize(internal::INLINE_BUFFER_SIZE); - for (;;) { - char *system_message = &buffer[0]; - int result = safe_strerror(error_code, system_message, buffer.size()); - if (result == 0) { - out << message << ": " << system_message; - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buffer.resize(buffer.size() * 2); - } - } FMT_CATCH(...) - {} - fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. - } - - template - void internal::ArgMap::init(const ArgList &args) - { - if (!map_.empty()) - return; - typedef internal::NamedArg NamedArg; - const NamedArg *named_arg = FMT_NULL; - bool use_values = - args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; - if (use_values) { - for (unsigned i = 0;/*nothing*/; ++i) { - internal::Arg::Type arg_type = args.type(i); - switch (arg_type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast(args.values_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - break; - default: - /*nothing*/; - } - } - return; - } - for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { - internal::Arg::Type arg_type = args.type(i); - if (arg_type == internal::Arg::NAMED_ARG) { - named_arg = static_cast(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - } - } - for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { - switch (args.args_[i].type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); - break; - default: - /*nothing*/; - } - } - } - - template - void internal::FixedBuffer::grow(std::size_t) - { - FMT_THROW(std::runtime_error("buffer overflow")); - } - - FMT_FUNC Arg internal::FormatterBase::do_get_arg( - unsigned arg_index, const char *&error) - { - Arg arg = args_[arg_index]; - switch (arg.type) { - case Arg::NONE: - error = "argument index out of range"; - break; - case Arg::NAMED_ARG: - arg = *static_cast(arg.pointer); - break; - default: - /*nothing*/; - } - return arg; - } - - FMT_FUNC void report_system_error( - int error_code, fmt::StringRef message) FMT_NOEXCEPT - { - // 'fmt::' is for bcc32. - report_error(format_system_error, error_code, message); - } - -#if FMT_USE_WINDOWS_H - FMT_FUNC void report_windows_error( - int error_code, fmt::StringRef message) FMT_NOEXCEPT - { - // 'fmt::' is for bcc32. - report_error(internal::format_windows_error, error_code, message); - } -#endif - - FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) - { - MemoryWriter w; - w.write(format_str, args); - std::fwrite(w.data(), 1, w.size(), f); - } - - FMT_FUNC void print(CStringRef format_str, ArgList args) - { - print(stdout, format_str, args); - } - - FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) - { - char escape[] = "\x1b[30m"; - escape[3] = static_cast('0' + c); - std::fputs(escape, stdout); - print(format, args); - std::fputs(RESET_COLOR, stdout); - } - -#ifndef FMT_HEADER_ONLY - - template struct internal::BasicData; - - // Explicit instantiations for char. - - template void internal::FixedBuffer::grow(std::size_t); - - template void internal::ArgMap::init(const ArgList &args); - - template int internal::CharTraits::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, double value); - - template int internal::CharTraits::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, long double value); - - // Explicit instantiations for wchar_t. - - template void internal::FixedBuffer::grow(std::size_t); - - template void internal::ArgMap::init(const ArgList &args); - - template int internal::CharTraits::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, double value); - - template int internal::CharTraits::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, long double value); - -#endif // FMT_HEADER_ONLY - -} // namespace fmt - -#ifdef _MSC_VER -# pragma warning(pop) -#endif diff --git a/ifs/include/extern/spdlog/fmt/bundled/format.h b/ifs/include/extern/spdlog/fmt/bundled/format.h index e5e2e2ef9..60001f363 100644 --- a/ifs/include/extern/spdlog/fmt/bundled/format.h +++ b/ifs/include/extern/spdlog/fmt/bundled/format.h @@ -1,351 +1,284 @@ /* -Formatting library for C++ - -Copyright (c) 2012 - 2016, Victor Zverovich -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ +#include #include -#include #include -#include #include #include #include #include -#include -#include -#include - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 30002 +#include -#ifdef _SECURE_SCL -# define FMT_SECURE_SCL _SECURE_SCL +#ifdef __clang__ +#define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else -# define FMT_SECURE_SCL 0 -#endif - -#if FMT_SECURE_SCL -# include +#define FMT_CLANG_VERSION 0 #endif -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER +#ifdef __INTEL_COMPILER +#define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +#define FMT_ICC_VERSION __ICL #else -# define FMT_MSC_VER 0 +#define FMT_ICC_VERSION 0 #endif -#if FMT_MSC_VER && FMT_MSC_VER <= 1500 -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; -typedef __int64 intmax_t; -#else -#include -#endif +#include "core.h" -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +#pragma GCC diagnostic push -#ifdef __GNUC__ -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# define FMT_GCC_EXTENSION __extension__ -# if FMT_GCC_VERSION >= 406 -# pragma GCC diagnostic push -// Disable the warning about "long long" which is sometimes reported even -// when using __extension__. -# pragma GCC diagnostic ignored "-Wlong-long" // Disable the warning about declaration shadowing because it affects too // many valid cases. -# pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wshadow" + // Disable the warning about implicit conversions that may change the sign of // an integer; silencing it otherwise would require many explicit casts. -# pragma GCC diagnostic ignored "-Wsign-conversion" -# endif -# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__ -# define FMT_HAS_GXX_CXX11 1 -# endif -#else -# define FMT_GCC_EXTENSION +#pragma GCC diagnostic ignored "-Wsign-conversion" #endif -#if defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL -#endif - -#if defined(__clang__) && !defined(FMT_ICC_VERSION) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -# pragma clang diagnostic ignored "-Wpadded" -#endif - -#ifdef __GNUC_LIBSTD__ -# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) -#endif - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) +#ifdef _SECURE_SCL +#define FMT_SECURE_SCL _SECURE_SCL #else -# define FMT_HAS_FEATURE(x) 0 +#define FMT_SECURE_SCL 0 #endif -#ifdef __has_builtin -# define FMT_HAS_BUILTIN(x) __has_builtin(x) -#else -# define FMT_HAS_BUILTIN(x) 0 +#if FMT_SECURE_SCL +#include #endif -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#ifdef __has_builtin +#define FMT_HAS_BUILTIN(x) __has_builtin(x) #else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#ifndef FMT_USE_VARIADIC_TEMPLATES -// Variadic templates are available in GCC since version 4.4 -// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ -// since version 2013. -# define FMT_USE_VARIADIC_TEMPLATES \ - (FMT_HAS_FEATURE(cxx_variadic_templates) || \ - (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800) -#endif - -#ifndef FMT_USE_RVALUE_REFERENCES -// Don't use rvalue references when compiling with clang and an old libstdc++ -// as the latter doesn't provide std::move. -# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 -# define FMT_USE_RVALUE_REFERENCES 0 -# else -# define FMT_USE_RVALUE_REFERENCES \ - (FMT_HAS_FEATURE(cxx_rvalue_references) || \ - (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600) -# endif -#endif - -#if FMT_USE_RVALUE_REFERENCES -# include // for std::move +#define FMT_HAS_BUILTIN(x) 0 #endif -// Check if exceptions are disabled. -#if defined(__GNUC__) && !defined(__EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -#endif -#if FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -#endif -#ifndef FMT_EXCEPTIONS -# define FMT_EXCEPTIONS 1 +#ifdef __GNUC_LIBSTD__ +#define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) #endif #ifndef FMT_THROW -# if FMT_EXCEPTIONS -# define FMT_THROW(x) throw x -# else -# define FMT_THROW(x) assert(false) -# endif -#endif - -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 +#if FMT_EXCEPTIONS +#if FMT_MSC_VER +FMT_BEGIN_NAMESPACE +namespace internal { +template +inline void do_throw(const Exception &x) +{ + // Silence unreachable code warnings in MSVC because these are nearly + // impossible to fix in a generic code. + volatile bool b = true; + if (b) + throw x; +} +} // namespace internal +FMT_END_NAMESPACE +#define FMT_THROW(x) fmt::internal::do_throw(x) +#else +#define FMT_THROW(x) throw x #endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept #else -# define FMT_DETECTED_NOEXCEPT throw() +#define FMT_THROW(x) \ + do \ + { \ + static_cast(sizeof(x)); \ + assert(false); \ + } while (false); #endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT -# else -# define FMT_NOEXCEPT -# endif #endif -// This is needed because GCC still uses throw() in its headers when exceptions -// are disabled. -#if FMT_GCC_VERSION -# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#ifndef FMT_USE_USER_DEFINED_LITERALS +// For Intel's compiler both it and the system gcc/msc must support UDLs. +#if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1500) +#define FMT_USE_USER_DEFINED_LITERALS 1 #else -# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#define FMT_USE_USER_DEFINED_LITERALS 0 #endif - -#ifndef FMT_OVERRIDE -# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif -#endif - -#ifndef FMT_NULL -# if FMT_HAS_FEATURE(cxx_nullptr) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1600 -# define FMT_NULL nullptr -# else -# define FMT_NULL NULL -# endif #endif -// A macro to disallow the copy constructor and operator= functions -// This should be used in the private: declarations for a class -#ifndef FMT_USE_DELETED_FUNCTIONS -# define FMT_USE_DELETED_FUNCTIONS 0 +#if FMT_USE_USER_DEFINED_LITERALS && !defined(FMT_ICC_VERSION) && \ + ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) +#define FMT_UDL_TEMPLATE 1 +#else +#define FMT_UDL_TEMPLATE 0 #endif -#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \ - (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 -# define FMT_DELETED_OR_UNDEFINED = delete -# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName& operator=(const TypeName&) = delete +#ifndef FMT_USE_EXTERN_TEMPLATES +#ifndef FMT_HEADER_ONLY +#define FMT_USE_EXTERN_TEMPLATES ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) #else -# define FMT_DELETED_OR_UNDEFINED -# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - TypeName& operator=(const TypeName&) +#define FMT_USE_EXTERN_TEMPLATES 0 #endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// All compilers which support UDLs also support variadic templates. This -// makes the fmt::literals implementation easier. However, an explicit check -// for variadic templates is added here just in case. -// For Intel's compiler both it and the system gcc/msc must support UDLs. -# define FMT_USE_USER_DEFINED_LITERALS \ - FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ - (FMT_HAS_FEATURE(cxx_user_literals) || \ - (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ - (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) #endif -#ifndef FMT_USE_EXTERN_TEMPLATES -// Clang doesn't have a feature check for extern templates so we check -// for variadic templates which were introduced in the same version. -// For GCC according to cppreference.com they were introduced in 3.3. -# define FMT_USE_EXTERN_TEMPLATES \ - ((__clang__ && FMT_USE_VARIADIC_TEMPLATES) || \ - (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || FMT_MSC_VER >= 1600 +#define FMT_USE_TRAILING_RETURN 1 +#else +#define FMT_USE_TRAILING_RETURN 0 #endif -#ifdef FMT_HEADER_ONLY -// If header only do not use extern templates. -# undef FMT_USE_EXTERN_TEMPLATES -# define FMT_USE_EXTERN_TEMPLATES 0 +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_rvalue_references) || FMT_MSC_VER >= 1600 +#define FMT_USE_RVALUE_REFERENCES 1 +#else +#define FMT_USE_RVALUE_REFERENCES 0 #endif -#ifndef FMT_ASSERT -# define FMT_ASSERT(condition, message) assert((condition) && message) +#ifndef FMT_USE_GRISU +#define FMT_USE_GRISU 0 #endif +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER #if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +#define FMT_BUILTIN_CLZ(n) __builtin_clz(n) #endif #if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +#define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +#endif #endif -// Some compilers masquerade as both MSVC and GCC-likes or -// otherwise support __builtin_clz and __builtin_clzll, so -// only define FMT_BUILTIN_CLZ using the MSVC intrinsics -// if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) -# include // _BitScanReverse, _BitScanReverse64 +// A workaround for gcc 4.4 that doesn't support union members with ctors. +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || (FMT_MSC_VER && FMT_MSC_VER <= 1800) +#define FMT_UNION struct +#else +#define FMT_UNION union +#endif -namespace fmt -{ -namespace internal -{ -# pragma intrinsic(_BitScanReverse) +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +#include // _BitScanReverse, _BitScanReverse64 + +FMT_BEGIN_NAMESPACE +namespace internal { +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +#ifndef __clang__ +#pragma intrinsic(_BitScanReverse) +#endif inline uint32_t clz(uint32_t x) { unsigned long r = 0; _BitScanReverse(&r, x); assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) +// Static analysis complains about using uninitialized data +// "r", but the only way that can happen is if "x" is 0, +// which the callers guarantee to not happen. +#pragma warning(suppress : 6102) return 31 - r; } -# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) +#define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) -# ifdef _WIN64 -# pragma intrinsic(_BitScanReverse64) -# endif +#if defined(_WIN64) && !defined(__clang__) +#pragma intrinsic(_BitScanReverse64) +#endif inline uint32_t clzll(uint64_t x) { unsigned long r = 0; -# ifdef _WIN64 +#ifdef _WIN64 _BitScanReverse64(&r, x); -# else +#else // Scan the high 32 bits. if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 - (r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); -# endif +#endif assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) +// Static analysis complains about using uninitialized data +// "r", but the only way that can happen is if "x" is 0, +// which the callers guarantee to not happen. +#pragma warning(suppress : 6102) return 63 - r; } -# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) -} -} +#define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} // namespace internal +FMT_END_NAMESPACE #endif -namespace fmt +FMT_BEGIN_NAMESPACE +namespace internal { + +// An equivalent of `*reinterpret_cast(&source)` that doesn't produce +// undefined behavior (e.g. due to type aliasing). +// Example: uint64_t d = bit_cast(2.718); +template +inline Dest bit_cast(const Source &source) +{ + static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); + Dest dest; + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// An implementation of begin and end for pre-C++11 compilers such as gcc 4. +template +FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) +{ + return c.begin(); +} +template +FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT +{ + return array; +} +template +FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) { -namespace internal + return c.end(); +} +template +FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT +{ + return array + N; +} + +// For std::result_of in gcc 4.4. +template +struct function { -struct DummyInt + template + struct result + { + typedef Result type; + }; +}; + +struct dummy_int { int data[2]; operator int() const @@ -353,81 +286,164 @@ struct DummyInt return 0; } }; -typedef std::numeric_limits FPUtil; +typedef std::numeric_limits fputil; // Dummy implementations of system functions such as signbit and ecvt called // if the latter are not available. -inline DummyInt signbit(...) +inline dummy_int signbit(...) +{ + return dummy_int(); +} +inline dummy_int _ecvt_s(...) +{ + return dummy_int(); +} +inline dummy_int isinf(...) { - return DummyInt(); + return dummy_int(); } -inline DummyInt _ecvt_s(...) +inline dummy_int _finite(...) { - return DummyInt(); + return dummy_int(); } -inline DummyInt isinf(...) +inline dummy_int isnan(...) { - return DummyInt(); + return dummy_int(); } -inline DummyInt _finite(...) +inline dummy_int _isnan(...) { - return DummyInt(); + return dummy_int(); } -inline DummyInt isnan(...) + +// A handmade floating-point number f * pow(2, e). +class fp +{ +private: + typedef uint64_t significand_type; + + // All sizes are in bits. + static FMT_CONSTEXPR_DECL const int char_size = std::numeric_limits::digits; + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + static FMT_CONSTEXPR_DECL const int double_significand_size = std::numeric_limits::digits - 1; + static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = 1ull << double_significand_size; + +public: + significand_type f; + int e; + + static FMT_CONSTEXPR_DECL const int significand_size = sizeof(significand_type) * char_size; + + fp(uint64_t f, int e) + : f(f) + , e(e) + { + } + + // Constructs fp from an IEEE754 double. It is a template to prevent compile + // errors on platforms where double is not IEEE754. + template + explicit fp(Double d) + { + // Assume double is in the format [sign][exponent][significand]. + typedef std::numeric_limits limits; + const int double_size = sizeof(Double) * char_size; + const int exponent_size = double_size - double_significand_size - 1; // -1 for sign + const uint64_t significand_mask = implicit_bit - 1; + const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + auto u = bit_cast(d); + auto biased_e = (u & exponent_mask) >> double_significand_size; + f = u & significand_mask; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = static_cast(biased_e - exponent_bias - double_significand_size); + } + + // Normalizes the value converted from double and multiplied by (1 << SHIFT). + template + void normalize() + { + // Handle subnormals. + auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((f & shifted_implicit_bit) == 0) + { + f <<= 1; + --e; + } + // Subtract 1 to account for hidden bit. + auto offset = significand_size - double_significand_size - SHIFT - 1; + f <<= offset; + e -= offset; + } +}; + +// Returns an fp number representing x - y. Result may not be normalized. +inline fp operator-(fp x, fp y) { - return DummyInt(); + FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); + return fp(x.f - y.f, x.e); } -inline DummyInt _isnan(...) + +// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest +// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be +// normalized. +fp operator*(fp x, fp y); + +// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its +// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. +fp get_cached_power(int min_exponent, int &pow10_exponent); + +template +typename Allocator::value_type *allocate(Allocator &alloc, std::size_t n) { - return DummyInt(); +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 + return std::allocator_traits::allocate(alloc, n); +#else + return alloc.allocate(n); +#endif } // A helper function to suppress bogus "conditional expression is constant" // warnings. -template +template inline T const_check(T value) { return value; } -} -} // namespace fmt +} // namespace internal +FMT_END_NAMESPACE -namespace std -{ +namespace std { // Standard permits specialization of std::numeric_limits. This specialization // is used to resolve ambiguity between isinf and std::isinf in glibc: // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 // and the same for isnan and signbit. -template <> -class numeric_limits: - public std::numeric_limits +template<> +class numeric_limits : public std::numeric_limits { public: // Portable version of isinf. - template + template static bool isinfinity(T x) { using namespace fmt::internal; // The resolution "priority" is: // isinf macro > std::isinf > ::isinf > fmt::internal::isinf - if (const_check(sizeof(isinf(x)) == sizeof(bool) || - sizeof(isinf(x)) == sizeof(int))) - { + if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) return isinf(x) != 0; - } return !_finite(static_cast(x)); } // Portable version of isnan. - template + template static bool isnotanumber(T x) { using namespace fmt::internal; - if (const_check(sizeof(isnan(x)) == sizeof(bool) || - sizeof(isnan(x)) == sizeof(int))) - { + if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) return isnan(x) != 0; - } return _isnan(static_cast(x)) != 0; } @@ -435,453 +451,246 @@ public: static bool isnegative(double x) { using namespace fmt::internal; - if (const_check(sizeof(signbit(x)) == sizeof(bool) || - sizeof(signbit(x)) == sizeof(int))) - { + if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) return signbit(x) != 0; - } - if (x < 0) return true; - if (!isnotanumber(x)) return false; + if (x < 0) + return true; + if (!isnotanumber(x)) + return false; int dec = 0, sign = 0; - char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); return sign != 0; } }; -} // namespace std - -namespace fmt -{ +} // namespace std -// Fix the warning about long long on older versions of GCC -// that don't support the diagnostic pragma. -FMT_GCC_EXTENSION typedef long long LongLong; -FMT_GCC_EXTENSION typedef unsigned long long ULongLong; +FMT_BEGIN_NAMESPACE +template +class basic_writer; -#if FMT_USE_RVALUE_REFERENCES -using std::move; -#endif - -template -class BasicWriter; - -typedef BasicWriter Writer; -typedef BasicWriter WWriter; - -template -class ArgFormatter; - -template -class BasicPrintfArgFormatter; - -template > -class BasicFormatter; - -/** -\rst -A string reference. It can be constructed from a C string or ``std::string``. - -You can use one of the following typedefs for common character types: - -+------------+-------------------------+ -| Type | Definition | -+============+=========================+ -| StringRef | BasicStringRef | -+------------+-------------------------+ -| WStringRef | BasicStringRef | -+------------+-------------------------+ - -This class is most useful as a parameter type to allow passing -different types of strings to a function, for example:: - -template -std::string format(StringRef format_str, const Args & ... args); - -format("{}", 42); -format(std::string("{}"), 42); -\endrst -*/ -template -class BasicStringRef +template +class output_range { private: - const Char *data_; - std::size_t size_; - -public: - /** Constructs a string reference object from a C string and a size. */ - BasicStringRef(const Char *s, std::size_t size): data_(s), size_(size) - {} + OutputIt it_; - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits::length``. - \endrst - */ - BasicStringRef(const Char *s) - : data_(s), size_(std::char_traits::length(s)) - {} + // Unused yet. + typedef void sentinel; + sentinel end() const; - /** - \rst - Constructs a string reference from an ``std::string`` object. - \endrst - */ - BasicStringRef(const std::basic_string &s) - : data_(s.c_str()), size_(s.size()) - {} +public: + typedef OutputIt iterator; + typedef T value_type; - /** - \rst - Converts a string reference to an ``std::string`` object. - \endrst - */ - std::basic_string to_string() const + explicit output_range(OutputIt it) + : it_(it) { - return std::basic_string(data_, size_); } - - /** Returns a pointer to the string data. */ - const Char *data() const + OutputIt begin() const { - return data_; + return it_; } +}; - /** Returns the string size. */ - std::size_t size() const - { - return size_; - } +// A range where begin() returns back_insert_iterator. +template +class back_insert_range : public output_range> +{ + typedef output_range> base; - // Lexicographically compare this string reference to other. - int compare(BasicStringRef other) const - { - std::size_t size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits::compare(data_, other.data_, size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } +public: + typedef typename Container::value_type value_type; - friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) - { - return lhs.compare(rhs) == 0; - } - friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) - { - return lhs.compare(rhs) != 0; - } - friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) - { - return lhs.compare(rhs) < 0; - } - friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) - { - return lhs.compare(rhs) <= 0; - } - friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) + back_insert_range(Container &c) + : base(std::back_inserter(c)) { - return lhs.compare(rhs) > 0; } - friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) + back_insert_range(typename base::iterator it) + : base(it) { - return lhs.compare(rhs) >= 0; } }; -typedef BasicStringRef StringRef; -typedef BasicStringRef WStringRef; - -/** -\rst -A reference to a null terminated string. It can be constructed from a C -string or ``std::string``. - -You can use one of the following typedefs for common character types: +typedef basic_writer> writer; +typedef basic_writer> wwriter; -+-------------+--------------------------+ -| Type | Definition | -+=============+==========================+ -| CStringRef | BasicCStringRef | -+-------------+--------------------------+ -| WCStringRef | BasicCStringRef | -+-------------+--------------------------+ - -This class is most useful as a parameter type to allow passing -different types of strings to a function, for example:: - -template -std::string format(CStringRef format_str, const Args & ... args); - -format("{}", 42); -format(std::string("{}"), 42); -\endrst -*/ -template -class BasicCStringRef +/** A formatting error such as invalid format string. */ +class format_error : public std::runtime_error { -private: - const Char *data_; - public: - /** Constructs a string reference object from a C string. */ - BasicCStringRef(const Char *s): data_(s) - {} - - /** - \rst - Constructs a string reference from an ``std::string`` object. - \endrst - */ - BasicCStringRef(const std::basic_string &s): data_(s.c_str()) - {} - - /** Returns the pointer to a C string. */ - const Char *c_str() const + explicit format_error(const char *message) + : std::runtime_error(message) { - return data_; } -}; -typedef BasicCStringRef CStringRef; -typedef BasicCStringRef WCStringRef; - -/** A formatting error such as invalid format string. */ -class FormatError: public std::runtime_error -{ -public: - explicit FormatError(CStringRef message) - : std::runtime_error(message.c_str()) - {} - FormatError(const FormatError &ferr): std::runtime_error(ferr) - {} - ~FormatError() FMT_DTOR_NOEXCEPT; + explicit format_error(const std::string &message) + : std::runtime_error(message) + { + } }; -namespace internal -{ +namespace internal { -// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. -template -struct MakeUnsigned +#if FMT_SECURE_SCL +template +struct checked { - typedef T Type; + typedef stdext::checked_array_iterator type; }; -#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ - template <> \ - struct MakeUnsigned { typedef U Type; } - -FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); -FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); -FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); -FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); -FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); -FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); - -// Casts nonnegative integer to unsigned. -template -inline typename MakeUnsigned::Type to_unsigned(Int value) +// Make a checked iterator to avoid warnings on MSVC. +template +inline stdext::checked_array_iterator make_checked(T *p, std::size_t size) { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast::Type>(value); + return {p, size}; } - -// The number of characters to store in the MemoryBuffer object itself -// to avoid dynamic memory allocation. -enum +#else +template +struct checked { - INLINE_BUFFER_SIZE = 500 + typedef T *type; }; - -#if FMT_SECURE_SCL -// Use checked iterator to avoid warnings on MSVC. -template -inline stdext::checked_array_iterator make_ptr(T *ptr, std::size_t size) -{ - return stdext::checked_array_iterator(ptr, size); -} -#else -template -inline T *make_ptr(T *ptr, std::size_t) +template +inline T *make_checked(T *p, std::size_t) { - return ptr; + return p; } #endif -} // namespace internal -/** -\rst -A buffer supporting a subset of ``std::vector``'s operations. -\endrst -*/ -template -class Buffer +template +template +void basic_buffer::append(const U *begin, const U *end) { -private: - FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); - -protected: - T *ptr_; - std::size_t size_; - std::size_t capacity_; - - Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0) - : ptr_(ptr), size_(0), capacity_(capacity) - {} + std::size_t new_size = size_ + internal::to_unsigned(end - begin); + reserve(new_size); + std::uninitialized_copy(begin, end, internal::make_checked(ptr_, capacity_) + size_); + size_ = new_size; +} +} // namespace internal - /** - \rst - Increases the buffer capacity to hold at least *size* elements updating - ``ptr_`` and ``capacity_``. - \endrst - */ - virtual void grow(std::size_t size) = 0; +// A wrapper around std::locale used to reduce compile times since +// is very heavy. +class locale; +class locale_provider +{ public: - virtual ~Buffer() - {} - - /** Returns the size of this buffer. */ - std::size_t size() const - { - return size_; - } + virtual ~locale_provider() {} + virtual fmt::locale locale(); +}; - /** Returns the capacity of this buffer. */ - std::size_t capacity() const - { - return capacity_; - } +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum +{ + inline_buffer_size = 500 +}; - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. - */ - void resize(std::size_t new_size) - { - if (new_size > capacity_) - grow(new_size); - size_ = new_size; - } +/** + \rst + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. - /** - \rst - Reserves space to store at least *capacity* elements. - \endrst - */ - void reserve(std::size_t capacity) - { - if (capacity > capacity_) - grow(capacity); - } + You can use one of the following typedefs for common character types: - void clear() FMT_NOEXCEPT - { - size_ = 0; - } + +----------------+------------------------------+ + | Type | Definition | + +================+==============================+ + | memory_buffer | basic_memory_buffer | + +----------------+------------------------------+ + | wmemory_buffer | basic_memory_buffer | + +----------------+------------------------------+ - void push_back(const T &value) - { - if (size_ == capacity_) - grow(size_ + 1); - ptr_[size_++] = value; - } + **Example**:: - /** Appends data to the end of the buffer. */ - template - void append(const U *begin, const U *end); + fmt::memory_buffer out; + format_to(out, "The answer is {}.", 42); - T &operator[](std::size_t index) - { - return ptr_[index]; - } - const T &operator[](std::size_t index) const - { - return ptr_[index]; - } -}; + This will write the following output to the ``out`` object: -template -template -void Buffer::append(const U *begin, const U *end) -{ - std::size_t new_size = size_ + internal::to_unsigned(end - begin); - if (new_size > capacity_) - grow(new_size); - std::uninitialized_copy(begin, end, - internal::make_ptr(ptr_, capacity_) + size_); - size_ = new_size; -} + .. code-block:: none -namespace internal -{ + The answer is 42. -// A memory buffer for trivially copyable/constructible types with the first -// SIZE elements stored in the object itself. -template > -class MemoryBuffer: private Allocator, public Buffer + The output can be converted to an ``std::string`` with ``to_string(out)``. + \endrst + */ +template> +class basic_memory_buffer : private Allocator, public internal::basic_buffer { private: - T data_[SIZE]; + T store_[SIZE]; // Deallocate memory allocated by the buffer. void deallocate() { - if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); + T *data = this->data(); + if (data != store_) + Allocator::deallocate(data, this->capacity()); } protected: void grow(std::size_t size) FMT_OVERRIDE; public: - explicit MemoryBuffer(const Allocator &alloc = Allocator()) - : Allocator(alloc), Buffer(data_, SIZE) - {} - ~MemoryBuffer() + explicit basic_memory_buffer(const Allocator &alloc = Allocator()) + : Allocator(alloc) { - deallocate(); + this->set(store_, SIZE); + } + ~basic_memory_buffer() + { + deallocate(); } -#if FMT_USE_RVALUE_REFERENCES private: // Move data from other to this buffer. - void move(MemoryBuffer &other) + void move(basic_memory_buffer &other) { Allocator &this_alloc = *this, &other_alloc = other; this_alloc = std::move(other_alloc); - this->size_ = other.size_; - this->capacity_ = other.capacity_; - if (other.ptr_ == other.data_) + T *data = other.data(); + std::size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { - this->ptr_ = data_; - std::uninitialized_copy(other.data_, other.data_ + this->size_, - make_ptr(data_, this->capacity_)); + this->set(store_, capacity); + std::uninitialized_copy(other.store_, other.store_ + size, internal::make_checked(store_, capacity)); } else { - this->ptr_ = other.ptr_; + this->set(data, capacity); // Set pointer to the inline array so that delete is not called // when deallocating. - other.ptr_ = other.data_; + other.set(other.store_, 0); } + this->resize(size); } public: - MemoryBuffer(MemoryBuffer &&other) + /** + \rst + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. + \endrst + */ + basic_memory_buffer(basic_memory_buffer &&other) { move(other); } - MemoryBuffer &operator=(MemoryBuffer &&other) + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + basic_memory_buffer &operator=(basic_memory_buffer &&other) { assert(this != &other); deallocate(); move(other); return *this; } -#endif // Returns a copy of the allocator associated with this buffer. Allocator get_allocator() const @@ -890,180 +699,381 @@ public: } }; -template -void MemoryBuffer::grow(std::size_t size) +template +void basic_memory_buffer::grow(std::size_t size) { - std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; + std::size_t old_capacity = this->capacity(); + std::size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; - T *new_ptr = this->allocate(new_capacity, FMT_NULL); + T *old_data = this->data(); + T *new_data = internal::allocate(*this, new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, - make_ptr(new_ptr, new_capacity)); - std::size_t old_capacity = this->capacity_; - T *old_ptr = this->ptr_; - this->capacity_ = new_capacity; - this->ptr_ = new_ptr; - // deallocate may throw (at least in principle), but it doesn't matter since - // the buffer already uses the new storage and will deallocate it in case - // of exception. - if (old_ptr != data_) - Allocator::deallocate(old_ptr, old_capacity); + std::uninitialized_copy(old_data, old_data + this->size(), internal::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) + Allocator::deallocate(old_data, old_capacity); } -// A fixed-size buffer. -template -class FixedBuffer: public fmt::Buffer +typedef basic_memory_buffer memory_buffer; +typedef basic_memory_buffer wmemory_buffer; + +/** + \rst + A fixed-size memory buffer. For a dynamically growing buffer use + :class:`fmt::basic_memory_buffer`. + + Trying to increase the buffer size past the initial capacity will throw + ``std::runtime_error``. + \endrst + */ +template +class basic_fixed_buffer : public internal::basic_buffer { public: - FixedBuffer(Char *array, std::size_t size): fmt::Buffer(array, size) - {} + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + given size. + \endrst + */ + basic_fixed_buffer(Char *array, std::size_t size) + { + this->set(array, size); + } + + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + size known at compile time. + \endrst + */ + template + explicit basic_fixed_buffer(Char (&array)[SIZE]) + { + this->set(array, SIZE); + } protected: FMT_API void grow(std::size_t size) FMT_OVERRIDE; }; -template -class BasicCharTraits +namespace internal { + +template +struct char_traits; + +template<> +struct char_traits { -public: -#if FMT_SECURE_SCL - typedef stdext::checked_array_iterator CharPtr; -#else - typedef Char *CharPtr; -#endif - static Char cast(int value) - { - return static_cast(value); - } + // Formats a floating-point number. + template + FMT_API static int format_float(char *buffer, std::size_t size, const char *format, int precision, T value); +}; + +template<> +struct char_traits +{ + template + FMT_API static int format_float(wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, T value); }; -template -class CharTraits; +#if FMT_USE_EXTERN_TEMPLATES +extern template int char_traits::format_float( + char *buffer, std::size_t size, const char *format, int precision, double value); +extern template int char_traits::format_float( + char *buffer, std::size_t size, const char *format, int precision, long double value); + +extern template int char_traits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, double value); +extern template int char_traits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, long double value); +#endif + +template +inline typename std::enable_if::value, typename checked::type>::type reserve( + std::back_insert_iterator &it, std::size_t n) +{ + Container &c = internal::get_container(it); + std::size_t size = c.size(); + c.resize(size + n); + return make_checked(&c[size], n); +} -template <> -class CharTraits: public BasicCharTraits +template +inline Iterator &reserve(Iterator &it, std::size_t) { -private: - // Conversion from wchar_t to char is not allowed. - static char convert(wchar_t); + return it; +} + +template +class null_terminating_iterator; +template +FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator it); + +// An iterator that produces a null terminator on *end. This simplifies parsing +// and allows comparing the performance of processing a null-terminated string +// vs string_view. +template +class null_terminating_iterator +{ public: - static char convert(char value) + typedef std::ptrdiff_t difference_type; + typedef Char value_type; + typedef const Char *pointer; + typedef const Char &reference; + typedef std::random_access_iterator_tag iterator_category; + + null_terminating_iterator() + : ptr_(0) + , end_(0) { - return value; } - // Formats a floating-point number. - template - FMT_API static int format_float(char *buffer, std::size_t size, - const char *format, unsigned width, int precision, T value); + FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr) + , end_(end) + { + } + + template + FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) + : ptr_(r.begin()) + , end_(r.end()) + { + } + + null_terminating_iterator &operator=(const Char *ptr) + { + assert(ptr <= end_); + ptr_ = ptr; + return *this; + } + + FMT_CONSTEXPR Char operator*() const + { + return ptr_ != end_ ? *ptr_ : 0; + } + + FMT_CONSTEXPR null_terminating_iterator operator++() + { + ++ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator++(int) + { + null_terminating_iterator result(*this); + ++ptr_; + return result; + } + + FMT_CONSTEXPR null_terminating_iterator operator--() + { + --ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) + { + return null_terminating_iterator(ptr_ + n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) + { + return null_terminating_iterator(ptr_ - n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) + { + ptr_ += n; + return *this; + } + + FMT_CONSTEXPR difference_type operator-(null_terminating_iterator other) const + { + return ptr_ - other.ptr_; + } + + FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const + { + return ptr_ != other.ptr_; + } + + bool operator>=(null_terminating_iterator other) const + { + return ptr_ >= other.ptr_; + } + + // This should be a friend specialization pointer_from but the latter + // doesn't compile by gcc 5.1 due to a compiler bug. + template + friend FMT_CONSTEXPR_DECL const CharT *pointer_from(null_terminating_iterator it); + +private: + const Char *ptr_; + const Char *end_; }; -#if FMT_USE_EXTERN_TEMPLATES -extern template int CharTraits::format_float -(char *buffer, std::size_t size, - const char* format, unsigned width, int precision, double value); -extern template int CharTraits::format_float -(char *buffer, std::size_t size, - const char* format, unsigned width, int precision, long double value); -#endif +template +FMT_CONSTEXPR const T *pointer_from(const T *p) +{ + return p; +} + +template +FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator it) +{ + return it.ptr_; +} -template <> -class CharTraits: public BasicCharTraits +// An output iterator that counts the number of objects written to it and +// discards them. +template +class counting_iterator { +private: + std::size_t count_; + mutable T blackhole_; + public: - static wchar_t convert(char value) + typedef std::output_iterator_tag iterator_category; + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef T *pointer; + typedef T &reference; + typedef counting_iterator _Unchecked_type; // Mark iterator as checked. + + counting_iterator() + : count_(0) { - return value; } - static wchar_t convert(wchar_t value) + + std::size_t count() const { - return value; + return count_; } - template - FMT_API static int format_float(wchar_t *buffer, std::size_t size, - const wchar_t *format, unsigned width, int precision, T value); -}; + counting_iterator &operator++() + { + ++count_; + return *this; + } -#if FMT_USE_EXTERN_TEMPLATES -extern template int CharTraits::format_float -(wchar_t *buffer, std::size_t size, - const wchar_t* format, unsigned width, int precision, double value); -extern template int CharTraits::format_float -(wchar_t *buffer, std::size_t size, - const wchar_t* format, unsigned width, int precision, long double value); -#endif + counting_iterator operator++(int) + { + auto it = *this; + ++*this; + return it; + } -// Checks if a number is negative - used to avoid warnings. -template -struct SignChecker -{ - template - static bool is_negative(T value) + T &operator*() const { - return value < 0; + return blackhole_; } }; -template <> -struct SignChecker +// An output iterator that truncates the output and counts the number of objects +// written to it. +template +class truncating_iterator { - template - static bool is_negative(T) +private: + typedef std::iterator_traits traits; + + OutputIt out_; + std::size_t limit_; + std::size_t count_; + mutable typename traits::value_type blackhole_; + +public: + typedef std::output_iterator_tag iterator_category; + typedef typename traits::value_type value_type; + typedef typename traits::difference_type difference_type; + typedef typename traits::pointer pointer; + typedef typename traits::reference reference; + typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. + + truncating_iterator(OutputIt out, std::size_t limit) + : out_(out) + , limit_(limit) + , count_(0) { - return false; + } + + OutputIt base() const + { + return out_; + } + std::size_t count() const + { + return count_; + } + + truncating_iterator &operator++() + { + if (count_++ < limit_) + ++out_; + return *this; + } + + truncating_iterator operator++(int) + { + auto it = *this; + ++*this; + return it; + } + + reference operator*() const + { + return count_ < limit_ ? *out_ : blackhole_; } }; // Returns true if value is negative, false otherwise. // Same as (value < 0) but doesn't produce warnings if T is an unsigned type. -template -inline bool is_negative(T value) +template +FMT_CONSTEXPR typename std::enable_if::is_signed, bool>::type is_negative(T value) { - return SignChecker::is_signed>::is_negative(value); + return value < 0; } - -// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. -template -struct TypeSelector -{ - typedef uint32_t Type; -}; - -template <> -struct TypeSelector +template +FMT_CONSTEXPR typename std::enable_if::is_signed, bool>::type is_negative(T) { - typedef uint64_t Type; -}; + return false; +} -template -struct IntTraits +template +struct int_traits { // Smallest of uint32_t and uint64_t that is large enough to represent // all values of T. - typedef typename - TypeSelector::digits <= 32>::Type MainType; + typedef typename std::conditional::digits <= 32, uint32_t, uint64_t>::type main_type; }; -FMT_API void report_unknown_type(char code, const char *type); - // Static data is placed in this class template to allow header-only // configuration. -template -struct FMT_API BasicData +template +struct FMT_API basic_data { static const uint32_t POWERS_OF_10_32[]; static const uint64_t POWERS_OF_10_64[]; + static const uint64_t POW10_SIGNIFICANDS[]; + static const int16_t POW10_EXPONENTS[]; static const char DIGITS[]; + static const char RESET_COLOR[]; + static const wchar_t WRESET_COLOR[]; }; #if FMT_USE_EXTERN_TEMPLATES -extern template struct BasicData; +extern template struct basic_data; #endif -typedef BasicData<> Data; +typedef basic_data<> data; #ifdef FMT_BUILTIN_CLZLL // Returns the number of decimal digits in n. Leading zeros are not counted @@ -1073,7 +1083,7 @@ inline unsigned count_digits(uint64_t n) // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1; + return to_unsigned(t) - (n < data::POWERS_OF_10_64[t]) + 1; } #else // Fallback version of count_digits used when __builtin_clz is not available. @@ -1085,65 +1095,169 @@ inline unsigned count_digits(uint64_t n) // Integer division is slow so do it for a group of four digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; + if (n < 10) + return count; + if (n < 100) + return count + 1; + if (n < 1000) + return count + 2; + if (n < 10000) + return count + 3; n /= 10000u; count += 4; } } #endif +#if FMT_HAS_CPP_ATTRIBUTE(always_inline) +#define FMT_ALWAYS_INLINE __attribute__((always_inline)) +#else +#define FMT_ALWAYS_INLINE +#endif + +template +inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; + +// Computes g = floor(log10(n)) and calls h.on(n); +template +inline char *lg(uint32_t n, Handler h) +{ + return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) + : n < 1000000 ? n < 10000 ? n < 1000 ? h.template on<2>(n) : h.template on<3>(n) + : n < 100000 ? h.template on<4>(n) : h.template on<5>(n) + : n < 100000000 ? n < 10000000 ? h.template on<6>(n) : h.template on<7>(n) + : n < 1000000000 ? h.template on<8>(n) : h.template on<9>(n); +} + +// An lg handler that formats a decimal number. +// Usage: lg(n, decimal_formatter(buffer)); +class decimal_formatter +{ +private: + char *buffer_; + + void write_pair(unsigned N, uint32_t index) + { + std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); + } + +public: + explicit decimal_formatter(char *buf) + : buffer_(buf) + { + } + + template + char *on(uint32_t u) + { + if (N == 0) + { + *buffer_ = static_cast(u) + '0'; + } + else if (N == 1) + { + write_pair(0, u); + } + else + { + // The idea of using 4.32 fixed-point numbers is based on + // https://github.com/jeaiii/itoa + unsigned n = N - 1; + unsigned a = n / 5 * n * 53 / 16; + uint64_t t = ((1ULL << (32 + a)) / data::POWERS_OF_10_32[n] + 1 - n / 9); + t = ((t * u) >> a) + n / 5 * 4; + write_pair(0, t >> 32); + for (unsigned i = 2; i < N; i += 2) + { + t = 100ULL * static_cast(t); + write_pair(i, t >> 32); + } + if (N % 2 == 0) + { + buffer_[N] = static_cast((10ULL * static_cast(t)) >> 32) + '0'; + } + } + return buffer_ += N + 1; + } +}; + +// An lg handler that formats a decimal number with a terminating null. +class decimal_formatter_null : public decimal_formatter +{ +public: + explicit decimal_formatter_null(char *buf) + : decimal_formatter(buf) + { + } + + template + char *on(uint32_t u) + { + char *buf = decimal_formatter::on(u); + *buf = '\0'; + return buf; + } +}; + #ifdef FMT_BUILTIN_CLZ // Optional version of count_digits for better performance on 32-bit platforms. inline unsigned count_digits(uint32_t n) { int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1; + return to_unsigned(t) - (n < data::POWERS_OF_10_32[t]) + 1; } #endif // A functor that doesn't add a thousands separator. -struct NoThousandsSep +struct no_thousands_sep { - template + typedef char char_type; + + template void operator()(Char *) - {} + { + } }; // A functor that adds a thousands separator. -class ThousandsSep +template +class add_thousands_sep { private: - fmt::StringRef sep_; + basic_string_view sep_; // Index of a decimal digit with the least significant digit having index 0. unsigned digit_index_; public: - explicit ThousandsSep(fmt::StringRef sep): sep_(sep), digit_index_(0) - {} + typedef Char char_type; + + explicit add_thousands_sep(basic_string_view sep) + : sep_(sep) + , digit_index_(0) + { + } - template void operator()(Char *&buffer) { if (++digit_index_ % 3 != 0) return; buffer -= sep_.size(); - std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), - internal::make_ptr(buffer, sep_.size())); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), internal::make_checked(buffer, sep_.size())); } }; +template +FMT_API Char thousands_sep(locale_provider *lp); + // Formats a decimal unsigned integer value writing into buffer. // thousands_sep is a functor that is called after writing each char to // add a thousands separator if necessary. -template -inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, - ThousandsSep thousands_sep) +template +inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, ThousandsSep thousands_sep) { buffer += num_digits; + Char *end = buffer; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead @@ -1151,33 +1265,67 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = static_cast((value % 100) * 2); value /= 100; - *--buffer = Data::DIGITS[index + 1]; + *--buffer = data::DIGITS[index + 1]; thousands_sep(buffer); - *--buffer = Data::DIGITS[index]; + *--buffer = data::DIGITS[index]; thousands_sep(buffer); } if (value < 10) { *--buffer = static_cast('0' + value); - return; + return end; } unsigned index = static_cast(value * 2); - *--buffer = Data::DIGITS[index + 1]; + *--buffer = data::DIGITS[index + 1]; thousands_sep(buffer); - *--buffer = Data::DIGITS[index]; + *--buffer = data::DIGITS[index]; + return end; +} + +template +inline Iterator format_decimal(Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) +{ + typedef typename ThousandsSep::char_type char_type; + // Buffer should be large enough to hold all digits (digits10 + 1) and null. + char_type buffer[std::numeric_limits::digits10 + 2]; + format_decimal(buffer, value, num_digits, sep); + return std::copy_n(buffer, num_digits, out); +} + +template +inline It format_decimal(It out, UInt value, unsigned num_digits) +{ + return format_decimal(out, value, num_digits, no_thousands_sep()); +} + +template +inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, bool upper = false) +{ + buffer += num_digits; + Char *end = buffer; + do + { + const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--buffer = BASE_BITS < 4 ? static_cast('0' + digit) : digits[digit]; + } while ((value >>= BASE_BITS) != 0); + return end; } -template -inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) +template +inline It format_uint(It out, UInt value, unsigned num_digits, bool upper = false) { - format_decimal(buffer, value, num_digits, NoThousandsSep()); - return; + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) + // and null. + char buffer[std::numeric_limits::digits / BASE_BITS + 2]; + format_uint(buffer, value, num_digits, upper); + return std::copy_n(buffer, num_digits, out); } #ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 +#define FMT_USE_WINDOWS_H 0 #elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 +#define FMT_USE_WINDOWS_H 1 #endif // Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. @@ -1185,16 +1333,16 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) #if FMT_USE_WINDOWS_H // A converter from UTF-8 to UTF-16. // It is only provided for Windows since other systems support UTF-8 natively. -class UTF8ToUTF16 +class utf8_to_utf16 { private: - MemoryBuffer buffer_; + wmemory_buffer buffer_; public: - FMT_API explicit UTF8ToUTF16(StringRef s); - operator WStringRef() const + FMT_API explicit utf8_to_utf16(string_view s); + operator wstring_view() const { - return WStringRef(&buffer_[0], size()); + return wstring_view(&buffer_[0], size()); } size_t size() const { @@ -1212,18 +1360,17 @@ public: // A converter from UTF-16 to UTF-8. // It is only provided for Windows since other systems support UTF-8 natively. -class UTF16ToUTF8 +class utf16_to_utf8 { private: - MemoryBuffer buffer_; + memory_buffer buffer_; public: - UTF16ToUTF8() - {} - FMT_API explicit UTF16ToUTF8(WStringRef s); - operator StringRef() const + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const { - return StringRef(&buffer_[0], size()); + return string_view(&buffer_[0], size()); } size_t size() const { @@ -1241,1620 +1388,1542 @@ public: // Performs conversion returning a system error code instead of // throwing exception on conversion error. This method may still throw // in case of memory allocation error. - FMT_API int convert(WStringRef s); + FMT_API int convert(wstring_view s); }; -FMT_API void format_windows_error(fmt::Writer &out, int error_code, - fmt::StringRef message) FMT_NOEXCEPT; +FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, fmt::string_view message) FMT_NOEXCEPT; #endif -// A formatting argument value. -struct Value +template +struct null { - template - struct StringValue - { - const Char *value; - std::size_t size; - }; - - typedef void(*FormatFunc)( - void *formatter, const void *arg, void *format_str_ptr); - - struct CustomValue - { - const void *value; - FormatFunc format; - }; - - union - { - int int_value; - unsigned uint_value; - LongLong long_long_value; - ULongLong ulong_long_value; - double double_value; - long double long_double_value; - const void *pointer; - StringValue string; - StringValue sstring; - StringValue ustring; - StringValue wstring; - CustomValue custom; - }; - - enum Type - { - NONE, NAMED_ARG, - // Integer types should go first, - INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR, - // followed by floating-point types. - DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, - CSTRING, STRING, WSTRING, POINTER, CUSTOM - }; }; +} // namespace internal -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in internal::MemoryBuffer. -struct Arg: Value +struct monostate { - Type type; }; -template -struct NamedArg; -template -struct NamedArgWithType; - -template -struct Null -{}; +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template +FMT_CONSTEXPR typename internal::result_of::type visit(Visitor &&vis, basic_format_arg arg) +{ + typedef typename Context::char_type char_type; + switch (arg.type_) + { + case internal::none_type: + break; + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + return vis(arg.value_.int_value); + case internal::uint_type: + return vis(arg.value_.uint_value); + case internal::long_long_type: + return vis(arg.value_.long_long_value); + case internal::ulong_long_type: + return vis(arg.value_.ulong_long_value); + case internal::bool_type: + return vis(arg.value_.int_value != 0); + case internal::char_type: + return vis(static_cast(arg.value_.int_value)); + case internal::double_type: + return vis(arg.value_.double_value); + case internal::long_double_type: + return vis(arg.value_.long_double_value); + case internal::cstring_type: + return vis(arg.value_.string.value); + case internal::string_type: + return vis(basic_string_view(arg.value_.string.value, arg.value_.string.size)); + case internal::pointer_type: + return vis(arg.value_.pointer); + case internal::custom_type: + return vis(typename basic_format_arg::handle(arg.value_.custom)); + } + return vis(monostate()); +} -// A helper class template to enable or disable overloads taking wide -// characters and strings in MakeValue. -template -struct WCharHelper +enum alignment { - typedef Null Supported; - typedef T Unsupported; + ALIGN_DEFAULT, + ALIGN_LEFT, + ALIGN_RIGHT, + ALIGN_CENTER, + ALIGN_NUMERIC }; -template -struct WCharHelper +// Flags. +enum { - typedef T Supported; - typedef Null Unsupported; + SIGN_FLAG = 1, + PLUS_FLAG = 2, + MINUS_FLAG = 4, + HASH_FLAG = 8 }; -typedef char Yes[1]; -typedef char No[2]; - -template -T &get(); - -// These are non-members to workaround an overload resolution bug in bcc32. -Yes &convert(fmt::ULongLong); -No &convert(...); - -template -struct ConvertToIntImpl +enum format_spec_tag { - enum - { - value = ENABLE_CONVERSION - }; + fill_tag, + align_tag, + width_tag, + type_tag }; -template -struct ConvertToIntImpl2 +// Format specifier. +template +class format_spec { - enum +private: + T value_; + +public: + typedef T value_type; + + explicit format_spec(T value) + : value_(value) { - value = false - }; -}; + } -template -struct ConvertToIntImpl2 -{ - enum + T value() const { - // Don't convert numeric types. - value = ConvertToIntImpl::is_specialized>::value - }; + return value_; + } }; -template -struct ConvertToInt +// template +// typedef format_spec fill_spec; +template +class fill_spec : public format_spec { - enum - { - enable_conversion = sizeof(fmt::internal::convert(get())) == sizeof(Yes) - }; - enum +public: + explicit fill_spec(Char value) + : format_spec(value) { - value = ConvertToIntImpl2::value - }; + } }; -#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ - template <> \ - struct ConvertToInt { enum { value = 0 }; } +typedef format_spec width_spec; +typedef format_spec type_spec; -// Silence warnings about convering float to int. -FMT_DISABLE_CONVERSION_TO_INT(float); -FMT_DISABLE_CONVERSION_TO_INT(double); -FMT_DISABLE_CONVERSION_TO_INT(long double); - -template -struct EnableIf -{}; - -template -struct EnableIf +// An empty format specifier. +struct empty_spec { - typedef T type; }; -template -struct Conditional +// An alignment specifier. +struct align_spec : empty_spec { - typedef T type; -}; + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of AlignSpec and its subclasses. + wchar_t fill_; + alignment align_; -template -struct Conditional -{ - typedef F type; -}; + FMT_CONSTEXPR align_spec(unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) + : width_(width) + , fill_(fill) + , align_(align) + { + } -// For bcc32 which doesn't understand ! in template arguments. -template -struct Not -{ - enum + FMT_CONSTEXPR unsigned width() const { - value = 0 - }; -}; + return width_; + } + FMT_CONSTEXPR wchar_t fill() const + { + return fill_; + } + FMT_CONSTEXPR alignment align() const + { + return align_; + } -template <> -struct Not -{ - enum + int precision() const { - value = 1 - }; + return -1; + } }; -template -struct False +// Format specifiers. +template +class basic_format_specs : public align_spec { - enum +public: + unsigned flags_; + int precision_; + Char type_; + + FMT_CONSTEXPR basic_format_specs(unsigned width = 0, char type = 0, wchar_t fill = ' ') + : align_spec(width, fill) + , flags_(0) + , precision_(-1) + , type_(type) { - value = 0 - }; + } + + FMT_CONSTEXPR bool flag(unsigned f) const + { + return (flags_ & f) != 0; + } + FMT_CONSTEXPR int precision() const + { + return precision_; + } + FMT_CONSTEXPR Char type() const + { + return type_; + } }; -template struct LConvCheck +typedef basic_format_specs format_specs; + +template +FMT_CONSTEXPR unsigned basic_parse_context::next_arg_id() +{ + if (next_arg_id_ >= 0) + return internal::to_unsigned(next_arg_id_++); + on_error("cannot switch from manual to automatic argument indexing"); + return 0; +} + +struct format_string { - LConvCheck(int) - {} }; -// Returns the thousands separator for the current locale. -// We check if ``lconv`` contains ``thousands_sep`` because on Android -// ``lconv`` is stubbed as an empty struct. -template -inline StringRef thousands_sep( - LConv *lc, LConvCheck = 0) +namespace internal { + +template +FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) { - return lc->thousands_sep; + switch (spec) + { + case 0: + case 'd': + handler.on_dec(); + break; + case 'x': + case 'X': + handler.on_hex(); + break; + case 'b': + case 'B': + handler.on_bin(); + break; + case 'o': + handler.on_oct(); + break; + case 'n': + handler.on_num(); + break; + default: + handler.on_error(); + } } -inline fmt::StringRef thousands_sep(...) +template +FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) { - return ""; + switch (spec) + { + case 0: + case 'g': + case 'G': + handler.on_general(); + break; + case 'e': + case 'E': + handler.on_exp(); + break; + case 'f': + case 'F': + handler.on_fixed(); + break; + case 'a': + case 'A': + handler.on_hex(); + break; + default: + handler.on_error(); + break; + } } -#define FMT_CONCAT(a, b) a##b - -#if FMT_GCC_VERSION >= 303 -# define FMT_UNUSED __attribute__((unused)) -#else -# define FMT_UNUSED -#endif +template +FMT_CONSTEXPR void handle_char_specs(const basic_format_specs &specs, Handler &&handler) +{ + if (specs.type() && specs.type() != 'c') + { + handler.on_int(); + return; + } + if (specs.align() == ALIGN_NUMERIC || specs.flag(~0u) != 0) + handler.on_error("invalid format specifier for char"); + handler.on_char(); +} -#ifndef FMT_USE_STATIC_ASSERT -# define FMT_USE_STATIC_ASSERT 0 -#endif +template +FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) +{ + if (spec == 0 || spec == 's') + handler.on_string(); + else if (spec == 'p') + handler.on_pointer(); + else + handler.on_error("invalid type specifier"); +} -#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ - (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 -# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) -#else -# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) -# define FMT_STATIC_ASSERT(cond, message) \ - typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED -#endif +template +FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) +{ + if (spec != 0 && spec != 's') + eh.on_error("invalid type specifier"); +} -template -void format_arg(Formatter &, const Char *, const T &) +template +FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) { - FMT_STATIC_ASSERT(False::value, - "Cannot format argument. To enable the use of ostream " - "operator<< include fmt/ostream.h. Otherwise provide " - "an overload of format_arg."); + if (spec != 0 && spec != 'p') + eh.on_error("invalid type specifier"); } -// Makes an Arg object from any type. -template -class MakeValue: public Arg +template +class int_type_checker : private ErrorHandler { public: - typedef typename Formatter::Char Char; - -private: - // The following two methods are private to disallow formatting of - // arbitrary pointers. If you want to output a pointer cast it to - // "void *" or "const void *". In particular, this forbids formatting - // of "[const] volatile char *" which is printed as bool by iostreams. - // Do not implement! - template - MakeValue(const T *value); - template - MakeValue(T *value); - - // The following methods are private to disallow formatting of wide - // characters and strings into narrow strings as in - // fmt::format("{}", L"test"); - // To fix this, use a wide format string: fmt::format(L"{}", L"test"). -#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED) - MakeValue(typename WCharHelper::Unsupported); -#endif - MakeValue(typename WCharHelper::Unsupported); - MakeValue(typename WCharHelper::Unsupported); - MakeValue(typename WCharHelper::Unsupported); - MakeValue(typename WCharHelper::Unsupported); - - void set_string(StringRef str) + FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) + : ErrorHandler(eh) { - string.value = str.data(); - string.size = str.size(); } - void set_string(WStringRef str) - { - wstring.value = str.data(); - wstring.size = str.size(); - } + FMT_CONSTEXPR void on_dec() {} + FMT_CONSTEXPR void on_hex() {} + FMT_CONSTEXPR void on_bin() {} + FMT_CONSTEXPR void on_oct() {} + FMT_CONSTEXPR void on_num() {} - // Formats an argument of a custom type, such as a user-defined class. - template - static void format_custom_arg( - void *formatter, const void *arg, void *format_str_ptr) + FMT_CONSTEXPR void on_error() { - format_arg(*static_cast(formatter), - *static_cast(format_str_ptr), - *static_cast(arg)); + ErrorHandler::on_error("invalid type specifier"); } +}; +template +class float_type_checker : private ErrorHandler +{ public: - MakeValue() - {} - -#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \ - MakeValue(Type value) { field = rhs; } \ - static uint64_t type(Type) { return Arg::TYPE; } - -#define FMT_MAKE_VALUE(Type, field, TYPE) \ - FMT_MAKE_VALUE_(Type, field, TYPE, value) + FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) + : ErrorHandler(eh) + { + } - FMT_MAKE_VALUE(bool, int_value, BOOL) - FMT_MAKE_VALUE(short, int_value, INT) - FMT_MAKE_VALUE(unsigned short, uint_value, UINT) - FMT_MAKE_VALUE(int, int_value, INT) - FMT_MAKE_VALUE(unsigned, uint_value, UINT) + FMT_CONSTEXPR void on_general() {} + FMT_CONSTEXPR void on_exp() {} + FMT_CONSTEXPR void on_fixed() {} + FMT_CONSTEXPR void on_hex() {} - MakeValue(long value) + FMT_CONSTEXPR void on_error() { - // To minimize the number of types we need to deal with, long is - // translated either to int or to long long depending on its size. - if (const_check(sizeof(long) == sizeof(int))) - int_value = static_cast(value); - else - long_long_value = value; + ErrorHandler::on_error("invalid type specifier"); } - static uint64_t type(long) +}; + +template +class char_specs_checker : public ErrorHandler +{ +private: + CharType type_; + +public: + FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) + : ErrorHandler(eh) + , type_(type) { - return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; } - MakeValue(unsigned long value) + FMT_CONSTEXPR void on_int() { - if (const_check(sizeof(unsigned long) == sizeof(unsigned))) - uint_value = static_cast(value); - else - ulong_long_value = value; + handle_int_type_spec(type_, int_type_checker(*this)); } - static uint64_t type(unsigned long) + FMT_CONSTEXPR void on_char() {} +}; + +template +class cstring_type_checker : public ErrorHandler +{ +public: + FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) + : ErrorHandler(eh) { - return sizeof(unsigned long) == sizeof(unsigned) ? - Arg::UINT : Arg::ULONG_LONG; } - FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) - FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) - FMT_MAKE_VALUE(float, double_value, DOUBLE) - FMT_MAKE_VALUE(double, double_value, DOUBLE) - FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) - FMT_MAKE_VALUE(signed char, int_value, INT) - FMT_MAKE_VALUE(unsigned char, uint_value, UINT) - FMT_MAKE_VALUE(char, int_value, CHAR) + FMT_CONSTEXPR void on_string() {} + FMT_CONSTEXPR void on_pointer() {} +}; -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) - MakeValue(typename WCharHelper::Supported value) +template +void arg_map::init(const basic_format_args &args) +{ + if (map_) + return; + map_ = new entry[args.max_size()]; + bool use_values = args.type(max_packed_args - 1) == internal::none_type; + if (use_values) { - int_value = value; + for (unsigned i = 0; /*nothing*/; ++i) + { + internal::type arg_type = args.type(i); + switch (arg_type) + { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.values_[i]); + break; + default: + break; // Do nothing. + } + } + return; } - static uint64_t type(wchar_t) + for (unsigned i = 0;; ++i) { - return Arg::CHAR; + switch (args.args_[i].type_) + { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.args_[i].value_); + break; + default: + break; // Do nothing. + } } -#endif +} -#define FMT_MAKE_STR_VALUE(Type, TYPE) \ - MakeValue(Type value) { set_string(value); } \ - static uint64_t type(Type) { return Arg::TYPE; } +template +class arg_formatter_base +{ +public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef basic_format_specs format_specs; - FMT_MAKE_VALUE(char *, string.value, CSTRING) - FMT_MAKE_VALUE(const char *, string.value, CSTRING) - FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING) - FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) - FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) - FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) - FMT_MAKE_STR_VALUE(const std::string &, STRING) - FMT_MAKE_STR_VALUE(StringRef, STRING) - FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) +private: + typedef basic_writer writer_type; + writer_type writer_; + format_specs &specs_; -#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \ - MakeValue(typename WCharHelper::Supported value) { \ - set_string(value); \ - } \ - static uint64_t type(Type) { return Arg::TYPE; } + FMT_DISALLOW_COPY_AND_ASSIGN(arg_formatter_base); - FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) - FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) - FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) - FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) + struct char_writer + { + char_type value; + template + void operator()(It &&it) const + { + *it++ = value; + } + }; - FMT_MAKE_VALUE(void *, pointer, POINTER) - FMT_MAKE_VALUE(const void *, pointer, POINTER) + void write_char(char_type value) + { + writer_.write_padded(1, specs_, char_writer{value}); + } - template - MakeValue(const T &value, - typename EnableIf::value>::value, int>::type = 0) + void write_pointer(const void *p) { - custom.value = &value; - custom.format = &format_custom_arg; + format_specs specs = specs_; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + writer_.write_int(reinterpret_cast(p), specs); } - template - MakeValue(const T &value, - typename EnableIf::value, int>::type = 0) +protected: + writer_type &writer() + { + return writer_; + } + format_specs &spec() + { + return specs_; + } + iterator out() { - int_value = value; + return writer_.out(); } - template - static uint64_t type(const T &) + void write(bool value) { - return ConvertToInt::value ? Arg::INT : Arg::CUSTOM; + writer_.write_str(string_view(value ? "true" : "false"), specs_); } - // Additional template param `Char_` is needed here because make_type always - // uses char. - template - MakeValue(const NamedArg &value) + void write(const char_type *value) { - pointer = &value; + if (!value) + FMT_THROW(format_error("string pointer is null")); + auto length = std::char_traits::length(value); + writer_.write_str(basic_string_view(value, length), specs_); } - template - MakeValue(const NamedArgWithType &value) + +public: + arg_formatter_base(Range r, format_specs &s) + : writer_(r) + , specs_(s) { - pointer = &value; } - template - static uint64_t type(const NamedArg &) + iterator operator()(monostate) { - return Arg::NAMED_ARG; + FMT_ASSERT(false, "invalid argument type"); + return out(); } - template - static uint64_t type(const NamedArgWithType &) + + template + typename std::enable_if::value, iterator>::type operator()(T value) { - return Arg::NAMED_ARG; + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same::value) + { + if (specs_.type_) + return (*this)(value ? 1 : 0); + write(value != 0); + } + else if (std::is_same::value) + { + internal::handle_char_specs(specs_, char_spec_handler(*this, static_cast(value))); + } + else + { + writer_.write_int(value, specs_); + } + return out(); } -}; -template -class MakeArg: public Arg -{ -public: - MakeArg() + template + typename std::enable_if::value, iterator>::type operator()(T value) { - type = Arg::NONE; + writer_.write_double(value, specs_); + return out(); } - template - MakeArg(const T &value) - : Arg(MakeValue(value)) + struct char_spec_handler : internal::error_handler + { + arg_formatter_base &formatter; + char_type value; + + char_spec_handler(arg_formatter_base &f, char_type val) + : formatter(f) + , value(val) + { + } + + void on_int() + { + formatter.writer_.write_int(value, formatter.specs_); + } + void on_char() + { + formatter.write_char(value); + } + }; + + struct cstring_spec_handler : internal::error_handler { - type = static_cast(MakeValue::type(value)); + arg_formatter_base &formatter; + const char_type *value; + + cstring_spec_handler(arg_formatter_base &f, const char_type *val) + : formatter(f) + , value(val) + { + } + + void on_string() + { + formatter.write(value); + } + void on_pointer() + { + formatter.write_pointer(value); + } + }; + + iterator operator()(const char_type *value) + { + internal::handle_cstring_type_spec(specs_.type_, cstring_spec_handler(*this, value)); + return out(); } -}; -template -struct NamedArg: Arg -{ - BasicStringRef name; + iterator operator()(basic_string_view value) + { + internal::check_string_type_spec(specs_.type_, internal::error_handler()); + writer_.write_str(value, specs_); + return out(); + } - template - NamedArg(BasicStringRef argname, const T &value) - : Arg(MakeArg< BasicFormatter >(value)), name(argname) - {} + iterator operator()(const void *value) + { + check_pointer_type_spec(specs_.type_, internal::error_handler()); + write_pointer(value); + return out(); + } }; -template -struct NamedArgWithType: NamedArg +template +struct is_format_string : std::integral_constant::value> { - NamedArgWithType(BasicStringRef argname, const T &value) - : NamedArg(argname, value) - {} }; -class RuntimeError: public std::runtime_error +template +FMT_CONSTEXPR bool is_name_start(Char c) { -protected: - RuntimeError(): std::runtime_error("") - {} - RuntimeError(const RuntimeError &rerr): std::runtime_error(rerr) - {} - ~RuntimeError() FMT_DTOR_NOEXCEPT; -}; + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} -template -class ArgMap; -} // namespace internal +// Parses the input as an unsigned integer. This function assumes that the +// first character is a digit and presence of a non-digit character at the end. +// it: an iterator pointing to the beginning of the input range. +template +FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) +{ + assert('0' <= *it && *it <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + unsigned big = max_int / 10; + do + { + // Check for overflow. + if (value > big) + { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + } while ('0' <= *it && *it <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} -/** An argument list. */ -class ArgList +template +class custom_formatter : public function { private: - // To reduce compiled code size per formatting function call, types of first - // MAX_PACKED_ARGS arguments are passed in the types_ field. - uint64_t types_; - union - { - // If the number of arguments is less than MAX_PACKED_ARGS, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const internal::Value *values_; - const internal::Arg *args_; - }; + Context &ctx_; - internal::Arg::Type type(unsigned index) const +public: + explicit custom_formatter(Context &ctx) + : ctx_(ctx) { - return type(types_, index); } - template - friend class internal::ArgMap; + bool operator()(typename basic_format_arg::handle h) const + { + h.format(ctx_); + return true; + } -public: - // Maximum number of arguments with packed types. + template + bool operator()(T) const + { + return false; + } +}; + +template +struct is_integer +{ enum { - MAX_PACKED_ARGS = 16 + value = + std::is_integral::value && !std::is_same::value && !std::is_same::value && !std::is_same::value }; +}; - ArgList(): types_(0) - {} +template +class width_checker : public function +{ +public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) + : handler_(eh) + { + } - ArgList(ULongLong types, const internal::Value *values) - : types_(types), values_(values) - {} - ArgList(ULongLong types, const internal::Arg *args) - : types_(types), args_(args) - {} + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T value) + { + if (is_negative(value)) + handler_.on_error("negative width"); + return static_cast(value); + } - uint64_t types() const + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T) { - return types_; + handler_.on_error("width is not integer"); + return 0; } - /** Returns the argument at specified index. */ - internal::Arg operator[](unsigned index) const +private: + ErrorHandler &handler_; +}; + +template +class precision_checker : public function +{ +public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) + : handler_(eh) { - using internal::Arg; - Arg arg; - bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE; - if (index < MAX_PACKED_ARGS) - { - Arg::Type arg_type = type(index); - internal::Value &val = arg; - if (arg_type != Arg::NONE) - val = use_values ? values_[index] : args_[index]; - arg.type = arg_type; - return arg; - } - if (use_values) - { - // The index is greater than the number of arguments that can be stored - // in values, so return a "none" argument. - arg.type = Arg::NONE; - return arg; - } - for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) - { - if (args_[i].type == Arg::NONE) - return args_[i]; - } - return args_[index]; } - static internal::Arg::Type type(uint64_t types, unsigned index) + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T value) { - unsigned shift = index * 4; - uint64_t mask = 0xf; - return static_cast( - (types & (mask << shift)) >> shift); + if (is_negative(value)) + handler_.on_error("negative precision"); + return static_cast(value); } -}; -#define FMT_DISPATCH(call) static_cast(this)->call + template + FMT_CONSTEXPR typename std::enable_if::value, unsigned long long>::type operator()(T) + { + handler_.on_error("precision is not integer"); + return 0; + } -/** -\rst -An argument visitor based on the `curiously recurring template pattern -`_. - -To use `~fmt::ArgVisitor` define a subclass that implements some or all of the -visit methods with the same signatures as the methods in `~fmt::ArgVisitor`, -for example, `~fmt::ArgVisitor::visit_int()`. -Pass the subclass as the *Impl* template parameter. Then calling -`~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method -specific to the argument type. For example, if the argument type is -``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass -will be called. If the subclass doesn't contain a method with this signature, -then a corresponding method of `~fmt::ArgVisitor` will be called. - -**Example**:: - -class MyArgVisitor : public fmt::ArgVisitor { -public: -void visit_int(int value) { fmt::print("{}", value); } -void visit_double(double value) { fmt::print("{}", value ); } -}; -\endrst -*/ -template -class ArgVisitor -{ private: - typedef internal::Arg Arg; + ErrorHandler &handler_; +}; +// A format specifier handler that sets fields in basic_format_specs. +template +class specs_setter +{ public: - void report_unhandled_arg() - {} - - Result visit_unhandled_arg() + explicit FMT_CONSTEXPR specs_setter(basic_format_specs &specs) + : specs_(specs) { - FMT_DISPATCH(report_unhandled_arg()); - return Result(); } - /** Visits an ``int`` argument. **/ - Result visit_int(int value) + FMT_CONSTEXPR specs_setter(const specs_setter &other) + : specs_(other.specs_) { - return FMT_DISPATCH(visit_any_int(value)); } - /** Visits a ``long long`` argument. **/ - Result visit_long_long(LongLong value) + FMT_CONSTEXPR void on_align(alignment align) + { + specs_.align_ = align; + } + FMT_CONSTEXPR void on_fill(Char fill) + { + specs_.fill_ = fill; + } + FMT_CONSTEXPR void on_plus() + { + specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; + } + FMT_CONSTEXPR void on_minus() + { + specs_.flags_ |= MINUS_FLAG; + } + FMT_CONSTEXPR void on_space() + { + specs_.flags_ |= SIGN_FLAG; + } + FMT_CONSTEXPR void on_hash() { - return FMT_DISPATCH(visit_any_int(value)); + specs_.flags_ |= HASH_FLAG; } - /** Visits an ``unsigned`` argument. **/ - Result visit_uint(unsigned value) + FMT_CONSTEXPR void on_zero() { - return FMT_DISPATCH(visit_any_int(value)); + specs_.align_ = ALIGN_NUMERIC; + specs_.fill_ = '0'; } - /** Visits an ``unsigned long long`` argument. **/ - Result visit_ulong_long(ULongLong value) + FMT_CONSTEXPR void on_width(unsigned width) { - return FMT_DISPATCH(visit_any_int(value)); + specs_.width_ = width; } + FMT_CONSTEXPR void on_precision(unsigned precision) + { + specs_.precision_ = static_cast(precision); + } + FMT_CONSTEXPR void end_precision() {} - /** Visits a ``bool`` argument. **/ - Result visit_bool(bool value) + FMT_CONSTEXPR void on_type(Char type) { - return FMT_DISPATCH(visit_any_int(value)); + specs_.type_ = type; } - /** Visits a ``char`` or ``wchar_t`` argument. **/ - Result visit_char(int value) +protected: + basic_format_specs &specs_; +}; + +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template +class specs_checker : public Handler +{ +public: + FMT_CONSTEXPR specs_checker(const Handler &handler, internal::type arg_type) + : Handler(handler) + , arg_type_(arg_type) { - return FMT_DISPATCH(visit_any_int(value)); } - /** Visits an argument of any integral type. **/ - template - Result visit_any_int(T) + FMT_CONSTEXPR specs_checker(const specs_checker &other) + : Handler(other) + , arg_type_(other.arg_type_) { - return FMT_DISPATCH(visit_unhandled_arg()); } - /** Visits a ``double`` argument. **/ - Result visit_double(double value) + FMT_CONSTEXPR void on_align(alignment align) { - return FMT_DISPATCH(visit_any_double(value)); + if (align == ALIGN_NUMERIC) + require_numeric_argument(); + Handler::on_align(align); } - /** Visits a ``long double`` argument. **/ - Result visit_long_double(long double value) + FMT_CONSTEXPR void on_plus() { - return FMT_DISPATCH(visit_any_double(value)); + check_sign(); + Handler::on_plus(); } - /** Visits a ``double`` or ``long double`` argument. **/ - template - Result visit_any_double(T) + FMT_CONSTEXPR void on_minus() { - return FMT_DISPATCH(visit_unhandled_arg()); + check_sign(); + Handler::on_minus(); } - /** Visits a null-terminated C string (``const char *``) argument. **/ - Result visit_cstring(const char *) + FMT_CONSTEXPR void on_space() { - return FMT_DISPATCH(visit_unhandled_arg()); + check_sign(); + Handler::on_space(); } - /** Visits a string argument. **/ - Result visit_string(Arg::StringValue) + FMT_CONSTEXPR void on_hash() { - return FMT_DISPATCH(visit_unhandled_arg()); + require_numeric_argument(); + Handler::on_hash(); } - /** Visits a wide string argument. **/ - Result visit_wstring(Arg::StringValue) + FMT_CONSTEXPR void on_zero() { - return FMT_DISPATCH(visit_unhandled_arg()); + require_numeric_argument(); + Handler::on_zero(); } - /** Visits a pointer argument. **/ - Result visit_pointer(const void *) + FMT_CONSTEXPR void end_precision() { - return FMT_DISPATCH(visit_unhandled_arg()); + if (is_integral(arg_type_) || arg_type_ == pointer_type) + this->on_error("precision not allowed for this argument type"); } - /** Visits an argument of a custom (user-defined) type. **/ - Result visit_custom(Arg::CustomValue) +private: + FMT_CONSTEXPR void require_numeric_argument() { - return FMT_DISPATCH(visit_unhandled_arg()); + if (!is_arithmetic(arg_type_)) + this->on_error("format specifier requires numeric argument"); } - /** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be - called. - \endrst - */ - Result visit(const Arg &arg) + FMT_CONSTEXPR void check_sign() { - switch (arg.type) + require_numeric_argument(); + if (is_integral(arg_type_) && arg_type_ != int_type && arg_type_ != long_long_type && arg_type_ != internal::char_type) { - case Arg::NONE: - case Arg::NAMED_ARG: - FMT_ASSERT(false, "invalid argument type"); - break; - case Arg::INT: - return FMT_DISPATCH(visit_int(arg.int_value)); - case Arg::UINT: - return FMT_DISPATCH(visit_uint(arg.uint_value)); - case Arg::LONG_LONG: - return FMT_DISPATCH(visit_long_long(arg.long_long_value)); - case Arg::ULONG_LONG: - return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); - case Arg::BOOL: - return FMT_DISPATCH(visit_bool(arg.int_value != 0)); - case Arg::CHAR: - return FMT_DISPATCH(visit_char(arg.int_value)); - case Arg::DOUBLE: - return FMT_DISPATCH(visit_double(arg.double_value)); - case Arg::LONG_DOUBLE: - return FMT_DISPATCH(visit_long_double(arg.long_double_value)); - case Arg::CSTRING: - return FMT_DISPATCH(visit_cstring(arg.string.value)); - case Arg::STRING: - return FMT_DISPATCH(visit_string(arg.string)); - case Arg::WSTRING: - return FMT_DISPATCH(visit_wstring(arg.wstring)); - case Arg::POINTER: - return FMT_DISPATCH(visit_pointer(arg.pointer)); - case Arg::CUSTOM: - return FMT_DISPATCH(visit_custom(arg.custom)); + this->on_error("format specifier requires signed argument"); } - return Result(); } + + internal::type arg_type_; }; -enum Alignment +template class Handler, typename T, typename Context, typename ErrorHandler> +FMT_CONSTEXPR void set_dynamic_spec(T &value, basic_format_arg arg, ErrorHandler eh) { - ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC -}; + unsigned long long big_value = visit(Handler(eh), arg); + if (big_value > (std::numeric_limits::max)()) + eh.on_error("number is too big"); + value = static_cast(big_value); +} -// Flags. -enum +struct auto_id { - SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, - CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. }; -// An empty format specifier. -struct EmptySpec -{}; - -// A type specifier. -template -struct TypeSpec: EmptySpec +// The standard format specifier handler with checking. +template +class specs_handler : public specs_setter { - Alignment align() const +public: + typedef typename Context::char_type char_type; + + FMT_CONSTEXPR specs_handler(basic_format_specs &specs, Context &ctx) + : specs_setter(specs) + , context_(ctx) { - return ALIGN_DEFAULT; } - unsigned width() const + + template + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - return 0; + set_dynamic_spec(this->specs_.width_, get_arg(arg_id), context_.error_handler()); } - int precision() const + + template + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - return -1; + set_dynamic_spec(this->specs_.precision_, get_arg(arg_id), context_.error_handler()); } - bool flag(unsigned) const + + void on_error(const char *message) { - return false; + context_.on_error(message); } - char type() const + +private: + FMT_CONSTEXPR basic_format_arg get_arg(auto_id) { - return TYPE; + return context_.next_arg(); } - char fill() const + + template + FMT_CONSTEXPR basic_format_arg get_arg(Id arg_id) { - return ' '; + context_.parse_context().check_arg_id(arg_id); + return context_.get_arg(arg_id); } + + Context &context_; }; -// A width specifier. -struct WidthSpec +// An argument reference. +template +struct arg_ref { - unsigned width_; - // Fill is always wchar_t and cast to char if necessary to avoid having - // two specialization of WidthSpec and its subclasses. - wchar_t fill_; - - WidthSpec(unsigned width, wchar_t fill): width_(width), fill_(fill) - {} + enum Kind + { + NONE, + INDEX, + NAME + }; - unsigned width() const + FMT_CONSTEXPR arg_ref() + : kind(NONE) + , index(0) { - return width_; } - wchar_t fill() const + FMT_CONSTEXPR explicit arg_ref(unsigned index) + : kind(INDEX) + , index(index) { - return fill_; } -}; - -// An alignment specifier. -struct AlignSpec: WidthSpec -{ - Alignment align_; - - AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) - : WidthSpec(width, fill), align_(align) - {} - - Alignment align() const + explicit arg_ref(basic_string_view name) + : kind(NAME) + , name(name) { - return align_; } - int precision() const + FMT_CONSTEXPR arg_ref &operator=(unsigned idx) { - return -1; + kind = INDEX; + index = idx; + return *this; } -}; - -// An alignment and type specifier. -template -struct AlignTypeSpec: AlignSpec -{ - AlignTypeSpec(unsigned width, wchar_t fill): AlignSpec(width, fill) - {} - bool flag(unsigned) const + Kind kind; + FMT_UNION { - return false; - } - char type() const - { - return TYPE; - } + unsigned index; + basic_string_view name; + }; }; -// A full format specifier. -struct FormatSpec: AlignSpec +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// differents sets of arguments (precompilation of format strings). +template +struct dynamic_format_specs : basic_format_specs { - unsigned flags_; - int precision_; - char type_; + arg_ref width_ref; + arg_ref precision_ref; +}; - FormatSpec( - unsigned width = 0, char type = 0, wchar_t fill = ' ') - : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) - {} +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template +class dynamic_specs_handler : public specs_setter +{ +public: + typedef typename ParseContext::char_type char_type; - bool flag(unsigned f) const + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs &specs, ParseContext &ctx) + : specs_setter(specs) + , specs_(specs) + , context_(ctx) { - return (flags_ & f) != 0; } - int precision() const + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) + : specs_setter(other) + , specs_(other.specs_) + , context_(other.context_) { - return precision_; } - char type() const + + template + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - return type_; + specs_.width_ref = make_arg_ref(arg_id); } -}; - -// An integer format specifier. -template , typename Char = char> -class IntFormatSpec: public SpecT -{ -private: - T value_; -public: - IntFormatSpec(T val, const SpecT &spec = SpecT()) - : SpecT(spec), value_(val) - {} + template + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) + { + specs_.precision_ref = make_arg_ref(arg_id); + } - T value() const + FMT_CONSTEXPR void on_error(const char *message) { - return value_; + context_.on_error(message); } -}; -// A string format specifier. -template -class StrFormatSpec: public AlignSpec -{ private: - const Char *str_; + typedef arg_ref arg_ref_type; -public: - template - StrFormatSpec(const Char *str, unsigned width, FillChar fill) - : AlignSpec(width, fill), str_(str) + template + FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - internal::CharTraits::convert(FillChar()); + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); } - const Char *str() const + FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { - return str_; + return arg_ref_type(context_.next_arg_id()); } -}; - -/** -Returns an integer format specifier to format the value in base 2. -*/ -IntFormatSpec > bin(int value); - -/** -Returns an integer format specifier to format the value in base 8. -*/ -IntFormatSpec > oct(int value); - -/** -Returns an integer format specifier to format the value in base 16 using -lower-case letters for the digits above 9. -*/ -IntFormatSpec > hex(int value); - -/** -Returns an integer formatter format specifier to format in base 16 using -upper-case letters for the digits above 9. -*/ -IntFormatSpec > hexu(int value); -/** -\rst -Returns an integer format specifier to pad the formatted argument with the -fill character to the specified width using the default (right) numeric -alignment. - -**Example**:: - -MemoryWriter out; -out << pad(hex(0xcafe), 8, '0'); -// out.str() == "0000cafe" - -\endrst -*/ -template -IntFormatSpec, Char> pad( - int value, unsigned width, Char fill = ' '); - -#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ -inline IntFormatSpec > bin(TYPE value) { \ - return IntFormatSpec >(value, TypeSpec<'b'>()); \ -} \ - \ -inline IntFormatSpec > oct(TYPE value) { \ - return IntFormatSpec >(value, TypeSpec<'o'>()); \ -} \ - \ -inline IntFormatSpec > hex(TYPE value) { \ - return IntFormatSpec >(value, TypeSpec<'x'>()); \ -} \ - \ -inline IntFormatSpec > hexu(TYPE value) { \ - return IntFormatSpec >(value, TypeSpec<'X'>()); \ -} \ - \ -template \ -inline IntFormatSpec > pad( \ - IntFormatSpec > f, unsigned width) { \ - return IntFormatSpec >( \ - f.value(), AlignTypeSpec(width, ' ')); \ -} \ - \ -/* For compatibility with older compilers we provide two overloads for pad, */ \ -/* one that takes a fill character and one that doesn't. In the future this */ \ -/* can be replaced with one overload making the template argument Char */ \ -/* default to char (C++11). */ \ -template \ -inline IntFormatSpec, Char> pad( \ - IntFormatSpec, Char> f, \ - unsigned width, Char fill) { \ - return IntFormatSpec, Char>( \ - f.value(), AlignTypeSpec(width, fill)); \ -} \ - \ -inline IntFormatSpec > pad( \ - TYPE value, unsigned width) { \ - return IntFormatSpec >( \ - value, AlignTypeSpec<0>(width, ' ')); \ -} \ - \ -template \ -inline IntFormatSpec, Char> pad( \ - TYPE value, unsigned width, Char fill) { \ - return IntFormatSpec, Char>( \ - value, AlignTypeSpec<0>(width, fill)); \ -} - -FMT_DEFINE_INT_FORMATTERS(int) -FMT_DEFINE_INT_FORMATTERS(long) -FMT_DEFINE_INT_FORMATTERS(unsigned) -FMT_DEFINE_INT_FORMATTERS(unsigned long) -FMT_DEFINE_INT_FORMATTERS(LongLong) -FMT_DEFINE_INT_FORMATTERS(ULongLong) - -/** -\rst -Returns a string formatter that pads the formatted argument with the fill -character to the specified width using the default (left) string alignment. - -**Example**:: - -std::string s = str(MemoryWriter() << pad("abc", 8)); -// s == "abc " - -\endrst -*/ -template -inline StrFormatSpec pad( - const Char *str, unsigned width, Char fill = ' ') -{ - return StrFormatSpec(str, width, fill); -} + dynamic_format_specs &specs_; + ParseContext &context_; +}; -inline StrFormatSpec pad( - const wchar_t *str, unsigned width, char fill = ' ') +template +FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { - return StrFormatSpec(str, width, fill); + typedef typename std::iterator_traits::value_type char_type; + char_type c = *it; + if (c == '}' || c == ':') + { + handler(); + return it; + } + if (c >= '0' && c <= '9') + { + unsigned index = parse_nonnegative_int(it, handler); + if (*it != '}' && *it != ':') + { + handler.on_error("invalid format string"); + return it; + } + handler(index); + return it; + } + if (!is_name_start(c)) + { + handler.on_error("invalid format string"); + return it; + } + auto start = it; + do + { + c = *++it; + } while (is_name_start(c) || ('0' <= c && c <= '9')); + handler(basic_string_view(pointer_from(start), to_unsigned(it - start))); + return it; } -namespace internal -{ - -template -class ArgMap +// Adapts SpecHandler to IDHandler API for dynamic width. +template +struct width_adapter { -private: - typedef std::vector< - std::pair, internal::Arg> > MapType; - typedef typename MapType::value_type Pair; - - MapType map_; + explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) + : handler(h) + { + } -public: - FMT_API void init(const ArgList &args); + FMT_CONSTEXPR void operator()() + { + handler.on_dynamic_width(auto_id()); + } + FMT_CONSTEXPR void operator()(unsigned id) + { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void operator()(basic_string_view id) + { + handler.on_dynamic_width(id); + } - const internal::Arg *find(const fmt::BasicStringRef &name) const + FMT_CONSTEXPR void on_error(const char *message) { - // The list is unsorted, so just return the first matching name. - for (typename MapType::const_iterator it = map_.begin(), end = map_.end(); - it != end; ++it) - { - if (it->first == name) - return &it->second; - } - return FMT_NULL; + handler.on_error(message); } + + SpecHandler &handler; }; -template -class ArgFormatterBase: public ArgVisitor +// Adapts SpecHandler to IDHandler API for dynamic precision. +template +struct precision_adapter { -private: - BasicWriter &writer_; - FormatSpec &spec_; - - FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase); - - void write_pointer(const void *p) + explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) + : handler(h) { - spec_.flags_ = HASH_FLAG; - spec_.type_ = 'x'; - writer_.write_int(reinterpret_cast(p), spec_); } -protected: - BasicWriter &writer() + FMT_CONSTEXPR void operator()() { - return writer_; + handler.on_dynamic_precision(auto_id()); } - FormatSpec &spec() + FMT_CONSTEXPR void operator()(unsigned id) { - return spec_; + handler.on_dynamic_precision(id); } - - void write(bool value) + FMT_CONSTEXPR void operator()(basic_string_view id) { - const char *str_value = value ? "true" : "false"; - Arg::StringValue str = { str_value, std::strlen(str_value) }; - writer_.write_str(str, spec_); + handler.on_dynamic_precision(id); } - void write(const char *value) + FMT_CONSTEXPR void on_error(const char *message) { - Arg::StringValue str = { value, value ? std::strlen(value) : 0 }; - writer_.write_str(str, spec_); + handler.on_error(message); } -public: - ArgFormatterBase(BasicWriter &w, FormatSpec &s) - : writer_(w), spec_(s) - {} + SpecHandler &handler; +}; - template - void visit_any_int(T value) +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +// it: an iterator pointing to the beginning of a null-terminated range of +// characters, possibly emulated via null_terminating_iterator, representing +// format specifiers. +template +FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) +{ + typedef typename std::iterator_traits::value_type char_type; + char_type c = *it; + if (c == '}' || !c) + return it; + + // Parse fill and alignment. + alignment align = ALIGN_DEFAULT; + int i = 1; + do { - writer_.write_int(value, spec_); + auto p = it + i; + switch (*p) + { + case '<': + align = ALIGN_LEFT; + break; + case '>': + align = ALIGN_RIGHT; + break; + case '=': + align = ALIGN_NUMERIC; + break; + case '^': + align = ALIGN_CENTER; + break; + } + if (align != ALIGN_DEFAULT) + { + if (p != it) + { + if (c == '{') + { + handler.on_error("invalid fill character '{'"); + return it; + } + it += 2; + handler.on_fill(c); + } + else + ++it; + handler.on_align(align); + break; + } + } while (--i >= 0); + + // Parse sign. + switch (*it) + { + case '+': + handler.on_plus(); + ++it; + break; + case '-': + handler.on_minus(); + ++it; + break; + case ' ': + handler.on_space(); + ++it; + break; + } + + if (*it == '#') + { + handler.on_hash(); + ++it; } - template - void visit_any_double(T value) + // Parse zero flag. + if (*it == '0') { - writer_.write_double(value, spec_); + handler.on_zero(); + ++it; } - void visit_bool(bool value) + // Parse width. + if ('0' <= *it && *it <= '9') { - if (spec_.type_) + handler.on_width(parse_nonnegative_int(it, handler)); + } + else if (*it == '{') + { + it = parse_arg_id(it + 1, width_adapter(handler)); + if (*it++ != '}') { - visit_any_int(value); - return; + handler.on_error("invalid format string"); + return it; } - write(value); } - void visit_char(int value) + // Parse precision. + if (*it == '.') { - if (spec_.type_ && spec_.type_ != 'c') + ++it; + if ('0' <= *it && *it <= '9') { - spec_.flags_ |= CHAR_FLAG; - writer_.write_int(value, spec_); - return; + handler.on_precision(parse_nonnegative_int(it, handler)); } - if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) - FMT_THROW(FormatError("invalid format specifier for char")); - typedef typename BasicWriter::CharPtr CharPtr; - Char fill = internal::CharTraits::cast(spec_.fill()); - CharPtr out = CharPtr(); - const unsigned CHAR_SIZE = 1; - if (spec_.width_ > CHAR_SIZE) - { - out = writer_.grow_buffer(spec_.width_); - if (spec_.align_ == ALIGN_RIGHT) - { - std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); - out += spec_.width_ - CHAR_SIZE; - } - else if (spec_.align_ == ALIGN_CENTER) - { - out = writer_.fill_padding(out, spec_.width_, - internal::const_check(CHAR_SIZE), fill); - } - else + else if (*it == '{') + { + it = parse_arg_id(it + 1, precision_adapter(handler)); + if (*it++ != '}') { - std::uninitialized_fill_n(out + CHAR_SIZE, - spec_.width_ - CHAR_SIZE, fill); + handler.on_error("invalid format string"); + return it; } } else { - out = writer_.grow_buffer(CHAR_SIZE); + handler.on_error("missing precision specifier"); + return it; } - *out = internal::CharTraits::cast(value); + handler.end_precision(); } - void visit_cstring(const char *value) - { - if (spec_.type_ == 'p') - return write_pointer(value); - write(value); - } + // Parse type. + if (*it != '}' && *it) + handler.on_type(*it++); + return it; +} - void visit_string(Arg::StringValue value) +template +struct id_adapter +{ + FMT_CONSTEXPR explicit id_adapter(Handler &h) + : handler(h) { - writer_.write_str(value, spec_); } - using ArgVisitor::visit_wstring; - - void visit_wstring(Arg::StringValue value) + FMT_CONSTEXPR void operator()() { - writer_.write_str(value, spec_); + handler.on_arg_id(); } - - void visit_pointer(const void *value) + FMT_CONSTEXPR void operator()(unsigned id) { - if (spec_.type_ && spec_.type_ != 'p') - report_unknown_type(spec_.type_, "pointer"); - write_pointer(value); + handler.on_arg_id(id); } -}; - -class FormatterBase -{ -private: - ArgList args_; - int next_arg_index_; - - // Returns the argument with specified index. - FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); - -protected: - const ArgList &args() const + FMT_CONSTEXPR void operator()(basic_string_view id) { - return args_; + handler.on_arg_id(id); } - explicit FormatterBase(const ArgList &args) + FMT_CONSTEXPR void on_error(const char *message) { - args_ = args; - next_arg_index_ = 0; + handler.on_error(message); } - // Returns the next argument. - Arg next_arg(const char *&error) - { - if (next_arg_index_ >= 0) - return do_get_arg(internal::to_unsigned(next_arg_index_++), error); - error = "cannot switch from manual to automatic argument indexing"; - return Arg(); - } + Handler &handler; +}; - // Checks if manual indexing is used and returns the argument with - // specified index. - Arg get_arg(unsigned arg_index, const char *&error) +template +FMT_CONSTEXPR void parse_format_string(Iterator it, Handler &&handler) +{ + typedef typename std::iterator_traits::value_type char_type; + auto start = it; + while (*it) { - return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg(); - } + char_type ch = *it++; + if (ch != '{' && ch != '}') + continue; + if (*it == ch) + { + handler.on_text(start, it); + start = ++it; + continue; + } + if (ch == '}') + { + handler.on_error("unmatched '}' in format string"); + return; + } + handler.on_text(start, it - 1); - bool check_no_auto_index(const char *&error) - { - if (next_arg_index_ > 0) + it = parse_arg_id(it, id_adapter(handler)); + if (*it == '}') { - error = "cannot switch from automatic to manual argument indexing"; - return false; + handler.on_replacement_field(it); + } + else if (*it == ':') + { + ++it; + it = handler.on_format_specs(it); + if (*it != '}') + { + handler.on_error("unknown format specifier"); + return; + } + } + else + { + handler.on_error("missing '}' in format string"); + return; } - next_arg_index_ = -1; - return true; - } - template - void write(BasicWriter &w, const Char *start, const Char *end) - { - if (start != end) - w << BasicStringRef(start, internal::to_unsigned(end - start)); + start = ++it; } -}; -} // namespace internal + handler.on_text(start, it); +} -/** -\rst -An argument formatter based on the `curiously recurring template pattern -`_. - -To use `~fmt::BasicArgFormatter` define a subclass that implements some or -all of the visit methods with the same signatures as the methods in -`~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. -Pass the subclass as the *Impl* template parameter. When a formatting -function processes an argument, it will dispatch to a visit method -specific to the argument type. For example, if the argument type is -``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass -will be called. If the subclass doesn't contain a method with this signature, -then a corresponding method of `~fmt::BasicArgFormatter` or its superclass -will be called. -\endrst -*/ -template -class BasicArgFormatter: public internal::ArgFormatterBase +template +FMT_CONSTEXPR const typename ParseContext::char_type *parse_format_specs(ParseContext &ctx) { -private: - BasicFormatter &formatter_; - const Char *format_; + // GCC 7.2 requires initializer. + formatter f{}; + return f.parse(ctx); +} +template +class format_string_checker +{ public: - /** - \rst - Constructs an argument formatter object. - *formatter* is a reference to the main formatter object, *spec* contains - format specifier information for standard argument types, and *fmt* points - to the part of the format string being parsed for custom argument types. - \endrst - */ - BasicArgFormatter(BasicFormatter &formatter, - FormatSpec &spec, const Char *fmt) - : internal::ArgFormatterBase(formatter.writer(), spec), - formatter_(formatter), format_(fmt) - {} - - /** Formats an argument of a custom (user-defined) type. */ - void visit_custom(internal::Arg::CustomValue c) + explicit FMT_CONSTEXPR format_string_checker(basic_string_view format_str, ErrorHandler eh) + : arg_id_(-1) + , context_(format_str, eh) + , parse_funcs_{&parse_format_specs...} { - c.format(&formatter_, c.value, &format_); } -}; - -/** The default argument formatter. */ -template -class ArgFormatter: public BasicArgFormatter, Char> -{ -public: - /** Constructs an argument formatter object. */ - ArgFormatter(BasicFormatter &formatter, - FormatSpec &spec, const Char *fmt) - : BasicArgFormatter, Char>(formatter, spec, fmt) - {} -}; -/** This template formats data and writes the output to a writer. */ -template -class BasicFormatter: private internal::FormatterBase -{ -public: - /** The character type for the output. */ - typedef CharType Char; - -private: - BasicWriter &writer_; - internal::ArgMap map_; + FMT_CONSTEXPR void on_text(const Char *, const Char *) {} - FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); - - using internal::FormatterBase::get_arg; + FMT_CONSTEXPR void on_arg_id() + { + arg_id_ = context_.next_arg_id(); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(unsigned id) + { + arg_id_ = id; + context_.check_arg_id(id); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(basic_string_view) {} - // Checks if manual indexing is used and returns the argument with - // specified name. - internal::Arg get_arg(BasicStringRef arg_name, const char *&error); + FMT_CONSTEXPR void on_replacement_field(const Char *) {} - // Parses argument index and returns corresponding argument. - internal::Arg parse_arg_index(const Char *&s); + FMT_CONSTEXPR const Char *on_format_specs(const Char *s) + { + context_.advance_to(s); + return to_unsigned(arg_id_) < NUM_ARGS ? parse_funcs_[arg_id_](context_) : s; + } - // Parses argument name and returns corresponding argument. - internal::Arg parse_arg_name(const Char *&s); + FMT_CONSTEXPR void on_error(const char *message) + { + context_.on_error(message); + } -public: - /** - \rst - Constructs a ``BasicFormatter`` object. References to the arguments and - the writer are stored in the formatter object so make sure they have - appropriate lifetimes. - \endrst - */ - BasicFormatter(const ArgList &args, BasicWriter &w) - : internal::FormatterBase(args), writer_(w) - {} +private: + typedef basic_parse_context parse_context_type; + enum + { + NUM_ARGS = sizeof...(Args) + }; - /** Returns a reference to the writer associated with this formatter. */ - BasicWriter &writer() + FMT_CONSTEXPR void check_arg_id() { - return writer_; + if (internal::to_unsigned(arg_id_) >= NUM_ARGS) + context_.on_error("argument index out of range"); } - /** Formats stored arguments and writes the output to the writer. */ - void format(BasicCStringRef format_str); + // Format specifier parsing function. + typedef const Char *(*parse_func)(parse_context_type &); - // Formats a single argument and advances format_str, a format string pointer. - const Char *format(const Char *&format_str, const internal::Arg &arg); + int arg_id_; + parse_context_type context_; + parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; }; -// Generates a comma-separated list with results of applying f to -// numbers 0..n-1. -# define FMT_GEN(n, f) FMT_GEN##n(f) -# define FMT_GEN1(f) f(0) -# define FMT_GEN2(f) FMT_GEN1(f), f(1) -# define FMT_GEN3(f) FMT_GEN2(f), f(2) -# define FMT_GEN4(f) FMT_GEN3(f), f(3) -# define FMT_GEN5(f) FMT_GEN4(f), f(4) -# define FMT_GEN6(f) FMT_GEN5(f), f(5) -# define FMT_GEN7(f) FMT_GEN6(f), f(6) -# define FMT_GEN8(f) FMT_GEN7(f), f(7) -# define FMT_GEN9(f) FMT_GEN8(f), f(8) -# define FMT_GEN10(f) FMT_GEN9(f), f(9) -# define FMT_GEN11(f) FMT_GEN10(f), f(10) -# define FMT_GEN12(f) FMT_GEN11(f), f(11) -# define FMT_GEN13(f) FMT_GEN12(f), f(12) -# define FMT_GEN14(f) FMT_GEN13(f), f(13) -# define FMT_GEN15(f) FMT_GEN14(f), f(14) - -namespace internal -{ -inline uint64_t make_type() +template +FMT_CONSTEXPR bool check_format_string(basic_string_view s, ErrorHandler eh = ErrorHandler()) { - return 0; + format_string_checker checker(s, eh); + parse_format_string(s.begin(), checker); + return true; } -template -inline uint64_t make_type(const T &arg) +template +void check_format_string(String format_str) { - return MakeValue< BasicFormatter >::type(arg); + FMT_CONSTEXPR_DECL bool invalid_format = + internal::check_format_string(string_view(format_str.data(), format_str.size())); + (void)invalid_format; } -template - struct ArgArray; - -template -struct ArgArray +// Specifies whether to format T using the standard formatter. +// It is not possible to use get_type in formatter specialization directly +// because of a bug in MSVC. +template +struct format_type : std::integral_constant::value != custom_type> { - typedef Value Type[N > 0 ? N : 1]; +}; -template -static Value make(const T &value) +// Specifies whether to format enums. +template +struct format_enum : std::integral_constant::value> { -#ifdef __clang__ - Value result = MakeValue(value); - // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang: - // https://github.com/fmtlib/fmt/issues/276 - (void)result.custom.format; - return result; -#else - return MakeValue(value); -#endif -} - }; +}; -template -struct ArgArray +template class Handler, typename Spec, typename Context> +void handle_dynamic_spec(Spec &value, arg_ref ref, Context &ctx) { - typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE - - template - static Arg make(const T &value) + typedef typename Context::char_type char_type; + switch (ref.kind) { - return MakeArg(value); + case arg_ref::NONE: + break; + case arg_ref::INDEX: + internal::set_dynamic_spec(value, ctx.get_arg(ref.index), ctx.error_handler()); + break; + case arg_ref::NAME: + internal::set_dynamic_spec(value, ctx.get_arg(ref.name), ctx.error_handler()); + break; } -}; - -#if FMT_USE_VARIADIC_TEMPLATES -template -inline uint64_t make_type(const Arg &first, const Args & ... tail) -{ - return make_type(first) | (make_type(tail...) << 4); } +} // namespace internal -#else - -struct ArgType +/** The default argument formatter. */ +template +class arg_formatter : public internal::function::iterator>, + public internal::arg_formatter_base { - uint64_t type; - - ArgType(): type(0) - {} - - template - ArgType(const T &arg) : type(make_type(arg)) - {} -}; +private: + typedef typename Range::value_type char_type; + typedef internal::arg_formatter_base base; + typedef basic_format_context context_type; -# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + context_type &ctx_; -inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) -{ - return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | - (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | - (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | - (t12.type << 48) | (t13.type << 52) | (t14.type << 56); -} -#endif -} // namespace internal - -# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n -# define FMT_MAKE_ARG_TYPE(n) T##n -# define FMT_MAKE_ARG(n) const T##n &v##n -# define FMT_ASSIGN_char(n) \ - arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) -# define FMT_ASSIGN_wchar_t(n) \ - arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) - -#if FMT_USE_VARIADIC_TEMPLATES -// Defines a variadic function returning void. -# define FMT_VARIADIC_VOID(func, arg_type) \ - template \ - void func(arg_type arg0, const Args & ... args) { \ - typedef fmt::internal::ArgArray ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make >(args)...}; \ - func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \ - } - -// Defines a variadic constructor. -# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ - template \ - ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ - typedef fmt::internal::ArgArray ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make >(args)...}; \ - func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \ - } +public: + typedef Range range; + typedef typename base::iterator iterator; + typedef typename base::format_specs format_specs; -#else + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *spec* contains format specifier information for standard argument types. + \endrst + */ + arg_formatter(context_type &ctx, format_specs &spec) + : base(Range(ctx.out()), spec) + , ctx_(ctx) + { + } -# define FMT_MAKE_REF(n) \ - fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) -# define FMT_MAKE_REF2(n) v##n - -// Defines a wrapper for a function taking one argument of type arg_type -// and n additional arguments of arbitrary types. -# define FMT_WRAP1(func, arg_type, n) \ - template \ - inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ - const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ - func(arg1, fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ - } - -// Emulates a variadic function returning void on a pre-C++11 compiler. -# define FMT_VARIADIC_VOID(func, arg_type) \ - inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ - FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ - FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ - FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ - FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ - FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) - -# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ - template \ - ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ - const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ - func(arg0, arg1, fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ - } - -// Emulates a variadic constructor on a pre-C++11 compiler. -# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) -#endif + using base::operator(); -// Generates a comma-separated list with results of applying f to pairs -// (argument, index). -#define FMT_FOR_EACH1(f, x0) f(x0, 0) -#define FMT_FOR_EACH2(f, x0, x1) \ - FMT_FOR_EACH1(f, x0), f(x1, 1) -#define FMT_FOR_EACH3(f, x0, x1, x2) \ - FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) -#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ - FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) -#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ - FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) -#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ - FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) -#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ - FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) -#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ - FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) -#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ - FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) -#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ - FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) + /** Formats an argument of a user-defined type. */ + iterator operator()(typename basic_format_arg::handle handle) + { + handle.format(ctx_); + return this->out(); + } +}; /** -An error returned by an operating system or a language runtime, -for example a file opening error. + An error returned by an operating system or a language runtime, + for example a file opening error. */ -class SystemError: public internal::RuntimeError +class system_error : public std::runtime_error { private: - void init(int err_code, CStringRef format_str, ArgList args); + FMT_API void init(int err_code, string_view format_str, format_args args); protected: int error_code_; - typedef char Char; // For FMT_VARIADIC_CTOR. - - SystemError() - {} + system_error() + : std::runtime_error("") + { + } public: /** - \rst - Constructs a :class:`fmt::SystemError` object with a description - formatted with `fmt::format_system_error`. *message* and additional - arguments passed into the constructor are formatted similarly to - `fmt::format`. - - **Example**:: - - // This throws a SystemError with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char *filename = "madeup"; - std::FILE *file = std::fopen(filename, "r"); - if (!file) - throw fmt::SystemError(errno, "cannot open file '{}'", filename); - \endrst + \rst + Constructs a :class:`fmt::system_error` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst */ - SystemError(int error_code, CStringRef message) + template + system_error(int error_code, string_view message, const Args &... args) + : std::runtime_error("") { - init(error_code, message, ArgList()); + init(error_code, message, make_format_args(args...)); } - FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) - - ~SystemError() FMT_DTOR_NOEXCEPT; int error_code() const { @@ -2863,671 +2932,582 @@ public: }; /** -\rst -Formats an error returned by an operating system or a language runtime, -for example a file opening error, and writes it to *out* in the following -form: - -.. parsed-literal:: -**: ** - -where ** is the passed message and ** is -the system message corresponding to the error code. -*error_code* is a system error code as given by ``errno``. -If *error_code* is not a valid error code such as -1, the system message -may look like "Unknown error -1" and is platform-dependent. -\endrst -*/ -FMT_API void format_system_error(fmt::Writer &out, int error_code, - fmt::StringRef message) FMT_NOEXCEPT; + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + **: ** + + where ** is the passed message and ** is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(internal::buffer &out, int error_code, fmt::string_view message) FMT_NOEXCEPT; /** -\rst -This template provides operations for formatting and writing data into -a character stream. The output is stored in a buffer provided by a subclass -such as :class:`fmt::BasicMemoryWriter`. - -You can use one of the following typedefs for common character types: - -+---------+----------------------+ -| Type | Definition | -+=========+======================+ -| Writer | BasicWriter | -+---------+----------------------+ -| WWriter | BasicWriter | -+---------+----------------------+ - -\endrst -*/ -template -class BasicWriter + This template provides operations for formatting and writing data into a + character range. + */ +template +class basic_writer { +public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef basic_format_specs format_specs; + private: - // Output buffer. - Buffer &buffer_; + // Output iterator. + iterator out_; - FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); + std::unique_ptr locale_; - typedef typename internal::CharTraits::CharPtr CharPtr; + FMT_DISALLOW_COPY_AND_ASSIGN(basic_writer); -#if FMT_SECURE_SCL - // Returns pointer value. - static Char *get(CharPtr p) + iterator out() const { - return p.base(); + return out_; } -#else - static Char *get(Char *p) + + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { - return p; + return internal::reserve(out_, n); } -#endif - // Fills the padding around the content and returns the pointer to the - // content area. - static CharPtr fill_padding(CharPtr buffer, - unsigned total_size, std::size_t content_size, wchar_t fill); + // Writes a value in the format + // + // where is written by f(it). + template + void write_padded(std::size_t size, const align_spec &spec, F f); - // Grows the buffer by n characters and returns a pointer to the newly - // allocated area. - CharPtr grow_buffer(std::size_t n) + template + struct padded_int_writer { - std::size_t size = buffer_.size(); - buffer_.resize(size + n); - return internal::make_ptr(&buffer_[size], n); - } + string_view prefix; + char_type fill; + std::size_t padding; + F f; + + template + void operator()(It &&it) const + { + if (prefix.size() != 0) + it = std::copy_n(prefix.data(), prefix.size(), it); + it = std::fill_n(it, padding, fill); + f(it); + } + }; - // Writes an unsigned decimal integer. - template - Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) + // Writes an integer in the format + // + // where are written by f(it). + template + void write_int(unsigned num_digits, string_view prefix, const Spec &spec, F f) { - unsigned num_digits = internal::count_digits(value); - Char *ptr = get(grow_buffer(prefix_size + num_digits)); - internal::format_decimal(ptr + prefix_size, value, num_digits); - return ptr; + std::size_t size = prefix.size() + num_digits; + char_type fill = static_cast(spec.fill()); + std::size_t padding = 0; + if (spec.align() == ALIGN_NUMERIC) + { + if (spec.width() > size) + { + padding = spec.width() - size; + size = spec.width(); + } + } + else if (spec.precision() > static_cast(num_digits)) + { + size = prefix.size() + static_cast(spec.precision()); + padding = static_cast(spec.precision()) - num_digits; + fill = '0'; + } + align_spec as = spec; + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + write_padded(size, as, padded_int_writer{prefix, fill, padding, f}); } // Writes a decimal integer. - template + template void write_decimal(Int value) { - typedef typename internal::IntTraits::MainType MainType; - MainType abs_value = static_cast(value); - if (internal::is_negative(value)) - { + typedef typename internal::int_traits::main_type main_type; + main_type abs_value = static_cast(value); + bool is_negative = internal::is_negative(value); + if (is_negative) abs_value = 0 - abs_value; - *write_unsigned_decimal(abs_value, 1) = '-'; + unsigned num_digits = internal::count_digits(abs_value); + auto &&it = reserve((is_negative ? 1 : 0) + num_digits); + if (is_negative) + *it++ = '-'; + internal::format_decimal(it, abs_value, num_digits); + } + + // The handle_int_type_spec handler that writes an integer. + template + struct int_writer + { + typedef typename internal::int_traits::main_type unsigned_type; + + basic_writer &writer; + const Spec &spec; + unsigned_type abs_value; + char prefix[4]; + unsigned prefix_size; + + string_view get_prefix() const + { + return string_view(prefix, prefix_size); } - else + + // Counts the number of digits in abs_value. BITS = log2(radix). + template + unsigned count_digits() const { - write_unsigned_decimal(abs_value, 0); + unsigned_type n = abs_value; + unsigned num_digits = 0; + do + { + ++num_digits; + } while ((n >>= BITS) != 0); + return num_digits; } - } - // Prepare a buffer for integer formatting. - CharPtr prepare_int_buffer(unsigned num_digits, - const EmptySpec &, const char *prefix, unsigned prefix_size) - { - unsigned size = prefix_size + num_digits; - CharPtr p = grow_buffer(size); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - return p + size - 1; - } + int_writer(basic_writer &w, Int value, const Spec &s) + : writer(w) + , spec(s) + , abs_value(static_cast(value)) + , prefix_size(0) + { + if (internal::is_negative(value)) + { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } + else if (spec.flag(SIGN_FLAG)) + { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + } - template - CharPtr prepare_int_buffer(unsigned num_digits, - const Spec &spec, const char *prefix, unsigned prefix_size); + struct dec_writer + { + unsigned_type abs_value; + unsigned num_digits; - // Formats an integer. - template - void write_int(T value, Spec spec); + template + void operator()(It &&it) const + { + it = internal::format_decimal(it, abs_value, num_digits); + } + }; - // Formats a floating-point number (double or long double). - template - void write_double(T value, const FormatSpec &spec); + void on_dec() + { + unsigned num_digits = internal::count_digits(abs_value); + writer.write_int(num_digits, get_prefix(), spec, dec_writer{abs_value, num_digits}); + } - // Writes a formatted string. - template - CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec); + struct hex_writer + { + int_writer &self; + unsigned num_digits; - template - void write_str(const internal::Arg::StringValue &str, - const FormatSpec &spec); + template + void operator()(It &&it) const + { + it = internal::format_uint<4>(it, self.abs_value, num_digits, self.spec.type() != 'x'); + } + }; - // This following methods are private to disallow writing wide characters - // and strings to a char stream. If you want to print a wide string as a - // pointer as std::ostream does, cast it to const void*. - // Do not implement! - void operator<<(typename internal::WCharHelper::Unsupported); - void operator<<( - typename internal::WCharHelper::Unsupported); + void on_hex() + { + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast(spec.type()); + } + unsigned num_digits = count_digits<4>(); + writer.write_int(num_digits, get_prefix(), spec, hex_writer{*this, num_digits}); + } - // Appends floating-point length specifier to the format string. - // The second argument is only used for overload resolution. - void append_float_length(Char *&format_ptr, long double) - { - *format_ptr++ = 'L'; - } + template + struct bin_writer + { + unsigned_type abs_value; + unsigned num_digits; - template - void append_float_length(Char *&, T) - {} + template + void operator()(It &&it) const + { + it = internal::format_uint(it, abs_value, num_digits); + } + }; - template - friend class internal::ArgFormatterBase; + void on_bin() + { + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast(spec.type()); + } + unsigned num_digits = count_digits<1>(); + writer.write_int(num_digits, get_prefix(), spec, bin_writer<1>{abs_value, num_digits}); + } - template - friend class BasicPrintfArgFormatter; + void on_oct() + { + unsigned num_digits = count_digits<3>(); + if (spec.flag(HASH_FLAG) && spec.precision() <= static_cast(num_digits)) + { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; + } + writer.write_int(num_digits, get_prefix(), spec, bin_writer<3>{abs_value, num_digits}); + } -protected: - /** - Constructs a ``BasicWriter`` object. - */ - explicit BasicWriter(Buffer &b): buffer_(b) - {} + enum + { + SEP_SIZE = 1 + }; -public: - /** - \rst - Destroys a ``BasicWriter`` object. - \endrst - */ - virtual ~BasicWriter() - {} + struct num_writer + { + unsigned_type abs_value; + unsigned size; + char_type sep; - /** - Returns the total number of characters written. - */ - std::size_t size() const + template + void operator()(It &&it) const + { + basic_string_view s(&sep, SEP_SIZE); + it = format_decimal(it, abs_value, size, internal::add_thousands_sep(s)); + } + }; + + void on_num() + { + unsigned num_digits = internal::count_digits(abs_value); + char_type sep = internal::thousands_sep(writer.locale_.get()); + unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); + writer.write_int(size, get_prefix(), spec, num_writer{abs_value, size, sep}); + } + + void on_error() + { + FMT_THROW(format_error("invalid type specifier")); + } + }; + + // Writes a formatted integer. + template + void write_int(T value, const Spec &spec) { - return buffer_.size(); + internal::handle_int_type_spec(spec.type(), int_writer(*this, value, spec)); } - /** - Returns a pointer to the output buffer content. No terminating null - character is appended. - */ - const Char *data() const FMT_NOEXCEPT + enum { - return &buffer_[0]; - } + INF_SIZE = 3 + }; // This is an enum to workaround a bug in MSVC. - /** - Returns a pointer to the output buffer content with terminating null - character appended. - */ - const Char *c_str() const + struct inf_or_nan_writer { - std::size_t size = buffer_.size(); - buffer_.reserve(size + 1); - buffer_[size] = '\0'; - return &buffer_[0]; - } + char sign; + const char *str; - /** - \rst - Returns the content of the output buffer as an `std::string`. - \endrst - */ - std::basic_string str() const + template + void operator()(It &&it) const + { + if (sign) + *it++ = sign; + it = std::copy_n(str, static_cast(INF_SIZE), it); + } + }; + + struct double_writer { - return std::basic_string(&buffer_[0], buffer_.size()); - } + size_t n; + char sign; + basic_memory_buffer &buffer; - /** - \rst - Writes formatted data. + template + void operator()(It &&it) + { + if (sign) + { + *it++ = sign; + --n; + } + it = std::copy_n(buffer.begin(), n, it); + } + }; + + // Formats a floating-point number (double or long double). + template + void write_double(T value, const format_specs &spec); + template + void write_double_sprintf(T value, const format_specs &spec, internal::basic_buffer &buffer); - *args* is an argument list representing arbitrary arguments. + template + struct str_writer + { + const Char *s; + std::size_t size; - **Example**:: + template + void operator()(It &&it) const + { + it = std::copy_n(s, size, it); + } + }; - MemoryWriter out; - out.write("Current point:\n"); - out.write("({:+f}, {:+f})", -3.14, 3.14); + // Writes a formatted string. + template + void write_str(const Char *s, std::size_t size, const align_spec &spec) + { + write_padded(size, spec, str_writer{s, size}); + } - This will write the following output to the ``out`` object: + template + void write_str(basic_string_view str, const format_specs &spec); - .. code-block:: none + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(char_type *&format_ptr, long double) + { + *format_ptr++ = 'L'; + } - Current point: - (-3.140000, +3.140000) + template + void append_float_length(char_type *&, T) + { + } - The output can be accessed using :func:`data()`, :func:`c_str` or - :func:`str` methods. + template + friend class internal::arg_formatter_base; - See also :ref:`syntax`. - \endrst - */ - void write(BasicCStringRef format, ArgList args) +public: + /** Constructs a ``basic_writer`` object. */ + explicit basic_writer(Range out) + : out_(out.begin()) { - BasicFormatter(args, *this).format(format); } - FMT_VARIADIC_VOID(write, BasicCStringRef) - BasicWriter &operator<<(int value) + void write(int value) + { + write_decimal(value); + } + void write(long value) { write_decimal(value); - return *this; } - BasicWriter &operator<<(unsigned value) + void write(long long value) { - return *this << IntFormatSpec(value); + write_decimal(value); } - BasicWriter &operator<<(long value) + + void write(unsigned value) { write_decimal(value); - return *this; } - BasicWriter &operator<<(unsigned long value) + void write(unsigned long value) { - return *this << IntFormatSpec(value); + write_decimal(value); } - BasicWriter &operator<<(LongLong value) + void write(unsigned long long value) { write_decimal(value); - return *this; } /** - \rst - Formats *value* and writes it to the stream. - \endrst - */ - BasicWriter &operator<<(ULongLong value) + \rst + Formats *value* and writes it to the buffer. + \endrst + */ + template + typename std::enable_if::value, void>::type write(T value, FormatSpec spec, FormatSpecs... specs) { - return *this << IntFormatSpec(value); + format_specs s(spec, specs...); + s.align_ = ALIGN_RIGHT; + write_int(value, s); } - BasicWriter &operator<<(double value) + void write(double value) { - write_double(value, FormatSpec()); - return *this; + write_double(value, format_specs()); } /** - \rst - Formats *value* using the general format for floating-point numbers - (``'g'``) and writes it to the stream. - \endrst - */ - BasicWriter &operator<<(long double value) + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the buffer. + \endrst + */ + void write(long double value) { - write_double(value, FormatSpec()); - return *this; + write_double(value, format_specs()); } - /** - Writes a character to the stream. - */ - BasicWriter &operator<<(char value) + /** Writes a character to the buffer. */ + void write(char value) { - buffer_.push_back(value); - return *this; + *reserve(1) = value; } - BasicWriter &operator<<( - typename internal::WCharHelper::Supported value) + void write(wchar_t value) { - buffer_.push_back(value); - return *this; + internal::require_wchar(); + *reserve(1) = value; } /** - \rst - Writes *value* to the stream. - \endrst - */ - BasicWriter &operator<<(fmt::BasicStringRef value) - { - const Char *str = value.data(); - buffer_.append(str, str + value.size()); - return *this; - } - - BasicWriter &operator<<( - typename internal::WCharHelper::Supported value) - { - const char *str = value.data(); - buffer_.append(str, str + value.size()); - return *this; - } - - template - BasicWriter &operator<<(IntFormatSpec spec) + \rst + Writes *value* to the buffer. + \endrst + */ + void write(string_view value) { - internal::CharTraits::convert(FillChar()); - write_int(spec.value(), spec); - return *this; + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); } - template - BasicWriter &operator<<(const StrFormatSpec &spec) + void write(wstring_view value) { - const StrChar *s = spec.str(); - write_str(s, std::char_traits::length(s), spec); - return *this; + internal::require_wchar(); + auto &&it = reserve(value.size()); + it = std::uninitialized_copy(value.begin(), value.end(), it); } - void clear() FMT_NOEXCEPT + template + void write(basic_string_view str, FormatSpecs... specs) { - buffer_.clear(); + write_str(str, format_specs(specs...)); } - Buffer &buffer() FMT_NOEXCEPT + template + typename std::enable_if::value>::type write(const T *p) { - return buffer_; + format_specs specs; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + write_int(reinterpret_cast(p), specs); } }; -template -template -typename BasicWriter::CharPtr BasicWriter::write_str( - const StrChar *s, std::size_t size, const AlignSpec &spec) +template +template +void basic_writer::write_padded(std::size_t size, const align_spec &spec, F f) { - CharPtr out = CharPtr(); - if (spec.width() > size) + unsigned width = spec.width(); + if (width <= size) + return f(reserve(size)); + auto &&it = reserve(width); + char_type fill = static_cast(spec.fill()); + std::size_t padding = width - size; + if (spec.align() == ALIGN_RIGHT) { - out = grow_buffer(spec.width()); - Char fill = internal::CharTraits::cast(spec.fill()); - if (spec.align() == ALIGN_RIGHT) - { - std::uninitialized_fill_n(out, spec.width() - size, fill); - out += spec.width() - size; - } - else if (spec.align() == ALIGN_CENTER) - { - out = fill_padding(out, spec.width(), size, fill); - } - else - { - std::uninitialized_fill_n(out + size, spec.width() - size, fill); - } + it = std::fill_n(it, padding, fill); + f(it); + } + else if (spec.align() == ALIGN_CENTER) + { + std::size_t left_padding = padding / 2; + it = std::fill_n(it, left_padding, fill); + f(it); + it = std::fill_n(it, padding - left_padding, fill); } else { - out = grow_buffer(size); + f(it); + it = std::fill_n(it, padding, fill); } - std::uninitialized_copy(s, s + size, out); - return out; } -template -template -void BasicWriter::write_str( - const internal::Arg::StringValue &s, const FormatSpec &spec) +template +template +void basic_writer::write_str(basic_string_view s, const format_specs &spec) { - // Check if StrChar is convertible to Char. - internal::CharTraits::convert(StrChar()); - if (spec.type_ && spec.type_ != 's') - internal::report_unknown_type(spec.type_, "string"); - const StrChar *str_value = s.value; - std::size_t str_size = s.size; - if (str_size == 0) - { - if (!str_value) - { - FMT_THROW(FormatError("string pointer is null")); - } - } + const Char *data = s.data(); + std::size_t size = s.size(); std::size_t precision = static_cast(spec.precision_); - if (spec.precision_ >= 0 && precision < str_size) - str_size = precision; - write_str(str_value, str_size, spec); -} - -template -typename BasicWriter::CharPtr -BasicWriter::fill_padding( - CharPtr buffer, unsigned total_size, - std::size_t content_size, wchar_t fill) -{ - std::size_t padding = total_size - content_size; - std::size_t left_padding = padding / 2; - Char fill_char = internal::CharTraits::cast(fill); - std::uninitialized_fill_n(buffer, left_padding, fill_char); - buffer += left_padding; - CharPtr content = buffer; - std::uninitialized_fill_n(buffer + content_size, - padding - left_padding, fill_char); - return content; + if (spec.precision_ >= 0 && precision < size) + size = precision; + write_str(data, size, spec); } -template -template -typename BasicWriter::CharPtr -BasicWriter::prepare_int_buffer( - unsigned num_digits, const Spec &spec, - const char *prefix, unsigned prefix_size) +template +struct float_spec_handler { - unsigned width = spec.width(); - Alignment align = spec.align(); - Char fill = internal::CharTraits::cast(spec.fill()); - if (spec.precision() > static_cast(num_digits)) - { - // Octal prefix '0' is counted as a digit, so ignore it if precision - // is specified. - if (prefix_size > 0 && prefix[prefix_size - 1] == '0') - --prefix_size; - unsigned number_size = - prefix_size + internal::to_unsigned(spec.precision()); - AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); - if (number_size >= width) - return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); - buffer_.reserve(width); - unsigned fill_size = width - number_size; - if (align != ALIGN_LEFT) - { - CharPtr p = grow_buffer(fill_size); - std::uninitialized_fill(p, p + fill_size, fill); - } - CharPtr result = prepare_int_buffer( - num_digits, subspec, prefix, prefix_size); - if (align == ALIGN_LEFT) - { - CharPtr p = grow_buffer(fill_size); - std::uninitialized_fill(p, p + fill_size, fill); - } - return result; - } - unsigned size = prefix_size + num_digits; - if (width <= size) - { - CharPtr p = grow_buffer(size); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - return p + size - 1; - } - CharPtr p = grow_buffer(width); - CharPtr end = p + width; - if (align == ALIGN_LEFT) - { - std::uninitialized_copy(prefix, prefix + prefix_size, p); - p += size; - std::uninitialized_fill(p, end, fill); - } - else if (align == ALIGN_CENTER) + Char type; + bool upper; + + explicit float_spec_handler(Char t) + : type(t) + , upper(false) { - p = fill_padding(p, width, size, fill); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - p += size; } - else + + void on_general() { - if (align == ALIGN_NUMERIC) - { - if (prefix_size != 0) - { - p = std::uninitialized_copy(prefix, prefix + prefix_size, p); - size -= prefix_size; - } - } + if (type == 'G') + upper = true; else - { - std::uninitialized_copy(prefix, prefix + prefix_size, end - size); - } - std::uninitialized_fill(p, end - size, fill); - p = end; + type = 'g'; } - return p - 1; -} -template -template -void BasicWriter::write_int(T value, Spec spec) -{ - unsigned prefix_size = 0; - typedef typename internal::IntTraits::MainType UnsignedType; - UnsignedType abs_value = static_cast(value); - char prefix[4] = ""; - if (internal::is_negative(value)) - { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } - else if (spec.flag(SIGN_FLAG)) - { - prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; - ++prefix_size; - } - switch (spec.type()) - { - case 0: - case 'd': - { - unsigned num_digits = internal::count_digits(abs_value); - CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; - internal::format_decimal(get(p), abs_value, 0); - break; - } - case 'x': - case 'X': + void on_exp() { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) - { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = spec.type(); - } - unsigned num_digits = 0; - do - { - ++num_digits; - } - while ((n >>= 4) != 0); - Char *p = get(prepare_int_buffer( - num_digits, spec, prefix, prefix_size)); - n = abs_value; - const char *digits = spec.type() == 'x' ? - "0123456789abcdef" : "0123456789ABCDEF"; - do - { - *p-- = digits[n & 0xf]; - } - while ((n >>= 4) != 0); - break; + if (type == 'E') + upper = true; } - case 'b': - case 'B': + + void on_fixed() { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) - { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = spec.type(); - } - unsigned num_digits = 0; - do - { - ++num_digits; - } - while ((n >>= 1) != 0); - Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); - n = abs_value; - do + if (type == 'F') { - *p-- = static_cast('0' + (n & 1)); + upper = true; +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif } - while ((n >>= 1) != 0); - break; } - case 'o': + + void on_hex() { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) - prefix[prefix_size++] = '0'; - unsigned num_digits = 0; - do - { - ++num_digits; - } - while ((n >>= 3) != 0); - Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); - n = abs_value; - do - { - *p-- = static_cast('0' + (n & 7)); - } - while ((n >>= 3) != 0); - break; + if (type == 'A') + upper = true; } - case 'n': + + void on_error() { - unsigned num_digits = internal::count_digits(abs_value); - fmt::StringRef sep = ""; -#ifndef ANDROID - sep = internal::thousands_sep(std::localeconv()); -#endif - unsigned size = static_cast( - num_digits + sep.size() * ((num_digits - 1) / 3)); - CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; - internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); - break; + FMT_THROW(format_error("invalid type specifier")); } - default: - internal::report_unknown_type( - spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); - break; - } -} +}; -template -template -void BasicWriter::write_double(T value, const FormatSpec &spec) +template +template +void basic_writer::write_double(T value, const format_specs &spec) { // Check type. - char type = spec.type(); - bool upper = false; - switch (type) - { - case 0: - type = 'g'; - break; - case 'e': - case 'f': - case 'g': - case 'a': - break; - case 'F': -#if FMT_MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; -#endif - // Fall through. - case 'E': - case 'G': - case 'A': - upper = true; - break; - default: - internal::report_unknown_type(type, "double"); - break; - } + float_spec_handler handler(spec.type()); + internal::handle_float_type_spec(spec.type(), handler); char sign = 0; // Use isnegative instead of value < 0 because the latter is always // false for NaN. - if (internal::FPUtil::isnegative(static_cast(value))) + if (internal::fputil::isnegative(static_cast(value))) { sign = '-'; value = -value; @@ -3537,72 +3517,100 @@ void BasicWriter::write_double(T value, const FormatSpec &spec) sign = spec.flag(PLUS_FLAG) ? '+' : ' '; } - if (internal::FPUtil::isnotanumber(value)) + struct write_inf_or_nan_t { - // Format NaN ourselves because sprintf's output is not consistent - // across platforms. - std::size_t nan_size = 4; - const char *nan = upper ? " NAN" : " nan"; - if (!sign) + basic_writer &writer; + format_specs spec; + char sign; + void operator()(const char *str) const { - --nan_size; - ++nan; + writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, inf_or_nan_writer{sign, str}); } - CharPtr out = write_str(nan, nan_size, spec); - if (sign) - *out = sign; - return; + } write_inf_or_nan = {*this, spec, sign}; + + // Format NaN and ininity ourselves because sprintf's output is not consistent + // across platforms. + if (internal::fputil::isnotanumber(value)) + return write_inf_or_nan(handler.upper ? "NAN" : "nan"); + if (internal::fputil::isinfinity(value)) + return write_inf_or_nan(handler.upper ? "INF" : "inf"); + + basic_memory_buffer buffer; + if (FMT_USE_GRISU && sizeof(T) <= sizeof(double) && std::numeric_limits::is_iec559) + { + internal::fp fp_value(static_cast(value)); + fp_value.normalize(); + // Find a cached power of 10 close to 1 / fp_value. + int dec_exp = 0; + const int min_exp = -60; + auto dec_pow = internal::get_cached_power(min_exp - (fp_value.e + internal::fp::significand_size), dec_exp); + internal::fp product = fp_value * dec_pow; + // Generate output. + internal::fp one(1ull << -product.e, product.e); + uint64_t hi = product.f >> -one.e; + uint64_t f = product.f & (one.f - 1); + typedef back_insert_range> range; + basic_writer w{range(buffer)}; + w.write(hi); + size_t digits = buffer.size(); + w.write('.'); + const unsigned max_digits = 18; + while (digits++ < max_digits) + { + f *= 10; + w.write(static_cast('0' + (f >> -one.e))); + f &= one.f - 1; + } + w.write('e'); + w.write(-dec_exp); } - - if (internal::FPUtil::isinfinity(value)) + else + { + format_specs normalized_spec(spec); + normalized_spec.type_ = handler.type; + write_double_sprintf(value, normalized_spec, buffer); + } + size_t n = buffer.size(); + align_spec as = spec; + if (spec.align() == ALIGN_NUMERIC) { - // Format infinity ourselves because sprintf's output is not consistent - // across platforms. - std::size_t inf_size = 4; - const char *inf = upper ? " INF" : " inf"; - if (!sign) + if (sign) { - --inf_size; - ++inf; + auto &&it = reserve(1); + *it++ = sign; + sign = 0; + if (as.width_) + --as.width_; } - CharPtr out = write_str(inf, inf_size, spec); - if (sign) - *out = sign; - return; + as.align_ = ALIGN_RIGHT; } - - std::size_t offset = buffer_.size(); - unsigned width = spec.width(); - if (sign) + else { - buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u)); - if (width > 0) - --width; - ++offset; + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + if (sign) + ++n; } + write_padded(n, as, double_writer{n, sign, buffer}); +} + +template +template +void basic_writer::write_double_sprintf(T value, const format_specs &spec, internal::basic_buffer &buffer) +{ + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); // Build format string. enum { MAX_FORMAT_SIZE = 10 }; // longest format: %#-*.*Lg - Char format[MAX_FORMAT_SIZE]; - Char *format_ptr = format; + char_type format[MAX_FORMAT_SIZE]; + char_type *format_ptr = format; *format_ptr++ = '%'; - unsigned width_for_sprintf = width; if (spec.flag(HASH_FLAG)) *format_ptr++ = '#'; - if (spec.align() == ALIGN_CENTER) - { - width_for_sprintf = 0; - } - else - { - if (spec.align() == ALIGN_LEFT) - *format_ptr++ = '-'; - if (width != 0) - *format_ptr++ = '*'; - } if (spec.precision() >= 0) { *format_ptr++ = '.'; @@ -3610,332 +3618,106 @@ void BasicWriter::write_double(T value, const FormatSpec &spec) } append_float_length(format_ptr, value); - *format_ptr++ = type; + *format_ptr++ = spec.type(); *format_ptr = '\0'; // Format using snprintf. - Char fill = internal::CharTraits::cast(spec.fill()); - unsigned n = 0; - Char *start = FMT_NULL; + char_type *start = FMT_NULL; for (;;) { - std::size_t buffer_size = buffer_.capacity() - offset; -#if FMT_MSC_VER - // MSVC's vsnprintf_s doesn't work with zero size, so reserve - // space for at least one extra character to make the size non-zero. - // Note that the buffer's capacity will increase by more than 1. - if (buffer_size == 0) - { - buffer_.reserve(offset + 1); - buffer_size = buffer_.capacity() - offset; - } -#endif - start = &buffer_[offset]; - int result = internal::CharTraits::format_float( - start, buffer_size, format, width_for_sprintf, spec.precision(), value); + std::size_t buffer_size = buffer.capacity(); + start = &buffer[0]; + int result = internal::char_traits::format_float(start, buffer_size, format, spec.precision(), value); if (result >= 0) { - n = internal::to_unsigned(result); - if (offset + n < buffer_.capacity()) - break; // The buffer is large enough - continue with formatting. - buffer_.reserve(offset + n + 1); + unsigned n = internal::to_unsigned(result); + if (n < buffer.capacity()) + { + buffer.resize(n); + break; // The buffer is large enough - continue with formatting. + } + buffer.reserve(n + 1); } else { // If result is negative we ask to increase the capacity by at least 1, // but as std::vector, the buffer grows exponentially. - buffer_.reserve(buffer_.capacity() + 1); - } - } - if (sign) - { - if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || - *start != ' ') - { - *(start - 1) = sign; - sign = 0; + buffer.reserve(buffer.capacity() + 1); } - else - { - *(start - 1) = fill; - } - ++n; - } - if (spec.align() == ALIGN_CENTER && spec.width() > n) - { - width = spec.width(); - CharPtr p = grow_buffer(width); - std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char)); - fill_padding(p, spec.width(), n, fill); - return; - } - if (spec.fill() != ' ' || sign) - { - while (*start == ' ') - *start++ = fill; - if (sign) - *(start - 1) = sign; } - grow_buffer(n); } -/** -\rst -This class template provides operations for formatting and writing data -into a character stream. The output is stored in a memory buffer that grows -dynamically. - -You can use one of the following typedefs for common character types -and the standard allocator: - -+---------------+-----------------------------------------------------+ -| Type | Definition | -+===============+=====================================================+ -| MemoryWriter | BasicMemoryWriter> | -+---------------+-----------------------------------------------------+ -| WMemoryWriter | BasicMemoryWriter> | -+---------------+-----------------------------------------------------+ - -**Example**:: - -MemoryWriter out; -out << "The answer is " << 42 << "\n"; -out.write("({:+f}, {:+f})", -3.14, 3.14); - -This will write the following output to the ``out`` object: - -.. code-block:: none - -The answer is 42 -(-3.140000, +3.140000) - -The output can be converted to an ``std::string`` with ``out.str()`` or -accessed as a C string with ``out.c_str()``. -\endrst -*/ -template > -class BasicMemoryWriter: public BasicWriter -{ -private: - internal::MemoryBuffer buffer_; - -public: - explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) - : BasicWriter(buffer_), buffer_(alloc) - {} - -#if FMT_USE_RVALUE_REFERENCES - /** - \rst - Constructs a :class:`fmt::BasicMemoryWriter` object moving the content - of the other object to it. - \endrst - */ - BasicMemoryWriter(BasicMemoryWriter &&other) - : BasicWriter(buffer_), buffer_(std::move(other.buffer_)) - {} - - /** - \rst - Moves the content of the other ``BasicMemoryWriter`` object to this one. - \endrst - */ - BasicMemoryWriter &operator=(BasicMemoryWriter &&other) - { - buffer_ = std::move(other.buffer_); - return *this; - } -#endif -}; - -typedef BasicMemoryWriter MemoryWriter; -typedef BasicMemoryWriter WMemoryWriter; - -/** -\rst -This class template provides operations for formatting and writing data -into a fixed-size array. For writing into a dynamically growing buffer -use :class:`fmt::BasicMemoryWriter`. - -Any write method will throw ``std::runtime_error`` if the output doesn't fit -into the array. - -You can use one of the following typedefs for common character types: - -+--------------+---------------------------+ -| Type | Definition | -+==============+===========================+ -| ArrayWriter | BasicArrayWriter | -+--------------+---------------------------+ -| WArrayWriter | BasicArrayWriter | -+--------------+---------------------------+ -\endrst -*/ -template -class BasicArrayWriter: public BasicWriter -{ -private: - internal::FixedBuffer buffer_; - -public: - /** - \rst - Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the - given size. - \endrst - */ - BasicArrayWriter(Char *array, std::size_t size) - : BasicWriter(buffer_), buffer_(array, size) - {} - - /** - \rst - Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the - size known at compile time. - \endrst - */ - template - explicit BasicArrayWriter(Char(&array)[SIZE]) - : BasicWriter(buffer_), buffer_(array, SIZE) - {} -}; - -typedef BasicArrayWriter ArrayWriter; -typedef BasicArrayWriter WArrayWriter; - // Reports a system error without throwing an exception. // Can be used to report errors from destructors. -FMT_API void report_system_error(int error_code, - StringRef message) FMT_NOEXCEPT; +FMT_API void report_system_error(int error_code, string_view message) FMT_NOEXCEPT; #if FMT_USE_WINDOWS_H /** A Windows error. */ -class WindowsError: public SystemError +class windows_error : public system_error { private: - FMT_API void init(int error_code, CStringRef format_str, ArgList args); + FMT_API void init(int error_code, string_view format_str, format_args args); public: /** - \rst - Constructs a :class:`fmt::WindowsError` object with the description - of the form - - .. parsed-literal:: - **: ** - - where ** is the formatted message and ** is the - system message corresponding to the error code. - *error_code* is a Windows error code as given by ``GetLastError``. - If *error_code* is not a valid error code such as -1, the system message - will look like "error -1". - - **Example**:: - - // This throws a WindowsError with the description - // cannot open file 'madeup': The system cannot find the file specified. - // or similar (system message may vary). - const char *filename = "madeup"; - LPOFSTRUCT of = LPOFSTRUCT(); - HFILE file = OpenFile(filename, &of, OF_READ); - if (file == HFILE_ERROR) { - throw fmt::WindowsError(GetLastError(), - "cannot open file '{}'", filename); - } - \endrst + \rst + Constructs a :class:`fmt::windows_error` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a windows_error with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst */ - WindowsError(int error_code, CStringRef message) + template + windows_error(int error_code, string_view message, const Args &... args) { - init(error_code, message, ArgList()); + init(error_code, message, make_format_args(args...)); } - FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef) }; // Reports a Windows error without throwing an exception. // Can be used to report errors from destructors. -FMT_API void report_windows_error(int error_code, - StringRef message) FMT_NOEXCEPT; +FMT_API void report_windows_error(int error_code, string_view message) FMT_NOEXCEPT; #endif -enum Color -{ - BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE -}; - -/** -Formats a string and prints it to stdout using ANSI escape sequences -to specify color (experimental). -Example: -print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); -*/ -FMT_API void print_colored(Color c, CStringRef format, ArgList args); - -/** -\rst -Formats arguments and returns the result as a string. - -**Example**:: - -std::string message = format("The answer is {}", 42); -\endrst -*/ -inline std::string format(CStringRef format_str, ArgList args) -{ - MemoryWriter w; - w.write(format_str, args); - return w.str(); -} - -inline std::wstring format(WCStringRef format_str, ArgList args) -{ - WMemoryWriter w; - w.write(format_str, args); - return w.str(); -} - -/** -\rst -Prints formatted data to the file *f*. - -**Example**:: - -print(stderr, "Don't {}!", "panic"); -\endrst -*/ -FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); - -/** -\rst -Prints formatted data to ``stdout``. - -**Example**:: - -print("Elapsed time: {0:.2f} seconds", 1.23); -\endrst -*/ -FMT_API void print(CStringRef format_str, ArgList args); - -/** -Fast integer formatter. -*/ -class FormatInt +/** Fast integer formatter. */ +class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. enum { - BUFFER_SIZE = std::numeric_limits::digits10 + 3 + BUFFER_SIZE = std::numeric_limits::digits10 + 3 }; mutable char buffer_[BUFFER_SIZE]; char *str_; - // Formats value in reverse and returns the number of digits. - char *format_decimal(ULongLong value) + // Formats value in reverse and returns a pointer to the beginning. + char *format_decimal(unsigned long long value) { - char *buffer_end = buffer_ + BUFFER_SIZE - 1; + char *ptr = buffer_ + BUFFER_SIZE - 1; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead @@ -3943,23 +3725,23 @@ private: // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = static_cast((value % 100) * 2); value /= 100; - *--buffer_end = internal::Data::DIGITS[index + 1]; - *--buffer_end = internal::Data::DIGITS[index]; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; } if (value < 10) { - *--buffer_end = static_cast('0' + value); - return buffer_end; + *--ptr = static_cast('0' + value); + return ptr; } unsigned index = static_cast(value * 2); - *--buffer_end = internal::Data::DIGITS[index + 1]; - *--buffer_end = internal::Data::DIGITS[index]; - return buffer_end; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + return ptr; } - void FormatSigned(LongLong value) + void format_signed(long long value) { - ULongLong abs_value = static_cast(value); + unsigned long long abs_value = static_cast(value); bool negative = value < 0; if (negative) abs_value = 0 - abs_value; @@ -3969,24 +3751,30 @@ private: } public: - explicit FormatInt(int value) + explicit format_int(int value) + { + format_signed(value); + } + explicit format_int(long value) + { + format_signed(value); + } + explicit format_int(long long value) + { + format_signed(value); + } + explicit format_int(unsigned value) + : str_(format_decimal(value)) { - FormatSigned(value); } - explicit FormatInt(long value) + explicit format_int(unsigned long value) + : str_(format_decimal(value)) { - FormatSigned(value); } - explicit FormatInt(LongLong value) + explicit format_int(unsigned long long value) + : str_(format_decimal(value)) { - FormatSigned(value); } - explicit FormatInt(unsigned value): str_(format_decimal(value)) - {} - explicit FormatInt(unsigned long value): str_(format_decimal(value)) - {} - explicit FormatInt(ULongLong value): str_(format_decimal(value)) - {} /** Returns the number of characters written to the output buffer. */ std::size_t size() const @@ -3995,18 +3783,18 @@ public: } /** - Returns a pointer to the output buffer content. No terminating null - character is appended. - */ + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ const char *data() const { return str_; } /** - Returns a pointer to the output buffer content with terminating null - character appended. - */ + Returns a pointer to the output buffer content with terminating null + character appended. + */ const char *c_str() const { buffer_[BUFFER_SIZE - 1] = '\0'; @@ -4014,10 +3802,10 @@ public: } /** - \rst - Returns the content of the output buffer as an ``std::string``. - \endrst - */ + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ std::string str() const { return std::string(str_, size()); @@ -4027,11 +3815,11 @@ public: // Formats a decimal integer value writing into buffer and returns // a pointer to the end of the formatted string. This function doesn't // write a terminating null character. -template +template inline void format_decimal(char *&buffer, T value) { - typedef typename internal::IntTraits::MainType MainType; - MainType abs_value = static_cast(value); + typedef typename internal::int_traits::main_type main_type; + main_type abs_value = static_cast(value); if (internal::is_negative(value)) { *buffer++ = '-'; @@ -4045,8 +3833,8 @@ inline void format_decimal(char *&buffer, T value) return; } unsigned index = static_cast(abs_value * 2); - *buffer++ = internal::Data::DIGITS[index]; - *buffer++ = internal::Data::DIGITS[index + 1]; + *buffer++ = internal::data::DIGITS[index]; + *buffer++ = internal::data::DIGITS[index + 1]; return; } unsigned num_digits = internal::count_digits(abs_value); @@ -4054,592 +3842,875 @@ inline void format_decimal(char *&buffer, T value) buffer += num_digits; } -/** -\rst -Returns a named argument for formatting functions. +// Formatter of objects of type T. +template +struct formatter::type, T>::value>::type> +{ + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template + FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) + { + auto it = internal::null_terminating_iterator(ctx); + typedef internal::dynamic_specs_handler handler_type; + auto type = internal::get_type::type, T>::value; + internal::specs_checker handler(handler_type(specs_, ctx), type); + it = parse_format_specs(it, handler); + auto type_spec = specs_.type(); + auto eh = ctx.error_handler(); + switch (type) + { + case internal::none_type: + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + case internal::uint_type: + case internal::long_long_type: + case internal::ulong_long_type: + case internal::bool_type: + handle_int_type_spec(type_spec, internal::int_type_checker(eh)); + break; + case internal::char_type: + handle_char_specs(specs_, internal::char_specs_checker(type_spec, eh)); + break; + case internal::double_type: + case internal::long_double_type: + handle_float_type_spec(type_spec, internal::float_type_checker(eh)); + break; + case internal::cstring_type: + internal::handle_cstring_type_spec(type_spec, internal::cstring_type_checker(eh)); + break; + case internal::string_type: + internal::check_string_type_spec(type_spec, eh); + break; + case internal::pointer_type: + internal::check_pointer_type_spec(type_spec, eh); + break; + case internal::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return pointer_from(it); + } -**Example**:: + template + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::handle_dynamic_spec(specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec(specs_.precision_, specs_.precision_ref, ctx); + typedef output_range range_type; + return visit(arg_formatter(ctx, specs_), internal::make_arg(val)); + } -print("Elapsed time: {s:.2f} seconds", arg("s", 1.23)); +private: + internal::dynamic_format_specs specs_; +}; -\endrst -*/ -template -inline internal::NamedArgWithType arg(StringRef name, const T &arg) +template +struct formatter::value>::type> : public formatter { - return internal::NamedArgWithType(name, arg); -} + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } +}; -template -inline internal::NamedArgWithType arg(WStringRef name, const T &arg) +// A formatter for types known only at run time such as variant alternatives. +// +// Usage: +// typedef std::variant variant; +// template <> +// struct formatter: dynamic_formatter<> { +// void format(buffer &buf, const variant &v, context &ctx) { +// visit([&](const auto &val) { format(buf, val, ctx); }, v); +// } +// }; +template +class dynamic_formatter { - return internal::NamedArgWithType(name, arg); -} +private: + struct null_handler : internal::error_handler + { + void on_align(alignment) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; + +public: + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + auto it = internal::null_terminating_iterator(ctx); + // Checks are deferred to formatting time when the argument type is known. + internal::dynamic_specs_handler handler(specs_, ctx); + it = parse_format_specs(it, handler); + return pointer_from(it); + } + + template + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) + { + handle_specs(ctx); + internal::specs_checker checker(null_handler(), internal::get_type::value); + checker.on_align(specs_.align()); + if (specs_.flags_ == 0) + { + // Do nothing. + } + else if (specs_.flag(SIGN_FLAG)) + { + if (specs_.flag(PLUS_FLAG)) + checker.on_plus(); + else + checker.on_space(); + } + else if (specs_.flag(MINUS_FLAG)) + { + checker.on_minus(); + } + else if (specs_.flag(HASH_FLAG)) + { + checker.on_hash(); + } + if (specs_.precision_ != -1) + checker.end_precision(); + typedef output_range range; + visit(arg_formatter(ctx, specs_), internal::make_arg(val)); + return ctx.out(); + } + +private: + template + void handle_specs(Context &ctx) + { + internal::handle_dynamic_spec(specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec(specs_.precision_, specs_.precision_ref, ctx); + } + + internal::dynamic_format_specs specs_; +}; -// The following two functions are deleted intentionally to disable -// nested named arguments as in ``format("{}", arg("a", arg("b", 42)))``. -template -void arg(StringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; -template -void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; +template +typename basic_format_context::format_arg basic_format_context::get_arg(basic_string_view name) +{ + map_.init(this->args()); + format_arg arg = map_.find(name); + if (arg.type() == internal::none_type) + this->on_error("argument not found"); + return arg; } -#if FMT_GCC_VERSION -// Use the system_header pragma to suppress warnings about variadic macros -// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't -// work. It is used at the end because we want to suppress as little warnings -// as possible. -# pragma GCC system_header -#endif +template +struct format_handler : internal::error_handler +{ + typedef internal::null_terminating_iterator iterator; + typedef typename ArgFormatter::range range; -// This is used to work around VC++ bugs in handling variadic macros. -#define FMT_EXPAND(args) args - -// Returns the number of arguments. -// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. -#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) -#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) -#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N -#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - -#define FMT_FOR_EACH_(N, f, ...) \ - FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) -#define FMT_FOR_EACH(f, ...) \ - FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) - -#define FMT_ADD_ARG_NAME(type, index) type arg##index -#define FMT_GET_ARG_NAME(type, index) arg##index - -#if FMT_USE_VARIADIC_TEMPLATES -# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ - template \ - ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - const Args & ... args) { \ - typedef fmt::internal::ArgArray ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make >(args)...}; \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ - fmt::ArgList(fmt::internal::make_type(args...), array)); \ - } -#else -// Defines a wrapper for a function taking __VA_ARGS__ arguments -// and n additional arguments of arbitrary types. -# define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ - template \ - inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - FMT_GEN(n, FMT_MAKE_ARG)) { \ - fmt::internal::ArgArray::Type arr; \ - FMT_GEN(n, FMT_ASSIGN_##Char); \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ - } - -# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ - inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ - } \ - FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 11, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 12, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 13, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 14, __VA_ARGS__) \ - FMT_WRAP(Char, ReturnType, func, call, 15, __VA_ARGS__) -#endif // FMT_USE_VARIADIC_TEMPLATES + format_handler(range r, basic_string_view str, basic_format_args format_args) + : context(r.begin(), str, format_args) + { + } -/** -\rst -Defines a variadic function with the specified return type, function name -and argument types passed as variable arguments to this macro. + void on_text(iterator begin, iterator end) + { + auto size = internal::to_unsigned(end - begin); + auto out = context.out(); + auto &&it = internal::reserve(out, size); + it = std::copy_n(begin, size, it); + context.advance_to(out); + } + + void on_arg_id() + { + arg = context.next_arg(); + } + void on_arg_id(unsigned id) + { + context.parse_context().check_arg_id(id); + arg = context.get_arg(id); + } + void on_arg_id(basic_string_view id) + { + arg = context.get_arg(id); + } + + void on_replacement_field(iterator it) + { + context.parse_context().advance_to(pointer_from(it)); + if (visit(internal::custom_formatter(context), arg)) + return; + basic_format_specs specs; + context.advance_to(visit(ArgFormatter(context, specs), arg)); + } + + iterator on_format_specs(iterator it) + { + auto &parse_ctx = context.parse_context(); + parse_ctx.advance_to(pointer_from(it)); + if (visit(internal::custom_formatter(context), arg)) + return iterator(parse_ctx); + basic_format_specs specs; + using internal::specs_handler; + internal::specs_checker> handler(specs_handler(specs, context), arg.type()); + it = parse_format_specs(it, handler); + if (*it != '}') + on_error("missing '}' in format string"); + parse_ctx.advance_to(pointer_from(it)); + context.advance_to(visit(ArgFormatter(context, specs), arg)); + return it; + } -**Example**:: + Context context; + basic_format_arg arg; +}; -void print_error(const char *file, int line, const char *format, -fmt::ArgList args) { -fmt::print("{}: {}: ", file, line); -fmt::print(format, args); +/** Formats arguments and writes the output to the range. */ +template +typename Context::iterator vformat_to(typename ArgFormatter::range out, basic_string_view format_str, basic_format_args args) +{ + typedef internal::null_terminating_iterator iterator; + format_handler h(out, format_str, args); + parse_format_string(iterator(format_str.begin(), format_str.end()), h); + return h.context.out(); } -FMT_VARIADIC(void, print_error, const char *, int, const char *) - -``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that -don't implement variadic templates. You don't have to use this macro if -you don't need legacy compiler support and can use variadic templates -directly:: - -template -void print_error(const char *file, int line, const char *format, -const Args & ... args) { -fmt::print("{}: {}: ", file, line); -fmt::print(format, args...); + +// Casts ``p`` to ``const void*`` for pointer formatting. +// Example: +// auto s = format("{}", ptr(p)); +template +inline const void *ptr(const T *p) +{ + return p; } -\endrst -*/ -#define FMT_VARIADIC(ReturnType, func, ...) \ - FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) -#define FMT_VARIADIC_W(ReturnType, func, ...) \ - FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) +template +struct arg_join +{ + It begin; + It end; + basic_string_view sep; + + arg_join(It begin, It end, basic_string_view sep) + : begin(begin) + , end(end) + , sep(sep) + { + } +}; + +template +struct formatter, Char> : formatter::value_type, Char> +{ + template + auto format(const arg_join &value, FormatContext &ctx) -> decltype(ctx.out()) + { + typedef formatter::value_type, Char> base; + auto it = value.begin; + auto out = ctx.out(); + if (it != value.end) + { + out = base::format(*it++, ctx); + while (it != value.end) + { + out = std::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = base::format(*it++, ctx); + } + } + return out; + } +}; -#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) +template +arg_join join(It begin, It end, string_view sep) +{ + return arg_join(begin, end, sep); +} -#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id) +template +arg_join join(It begin, It end, wstring_view sep) +{ + return arg_join(begin, end, sep); +} -/** -\rst -Convenient macro to capture the arguments' names and values into several -``fmt::arg(name, value)``. +// The following causes ICE in gcc 4.4. +#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) +template +auto join(const Range &range, string_view sep) -> arg_join +{ + return join(internal::begin(range), internal::end(range), sep); +} -**Example**:: +template +auto join(const Range &range, wstring_view sep) -> arg_join +{ + return join(internal::begin(range), internal::end(range), sep); +} +#endif -int x = 1, y = 2; -print("point: ({x}, {y})", FMT_CAPTURE(x, y)); -// same as: -// print("point: ({x}, {y})", arg("x", x), arg("y", y)); +/** + \rst + Converts *value* to ``std::string`` using the default format for type *T*. -\endrst -*/ -#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) + **Example**:: -#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__) + #include -namespace fmt + std::string answer = fmt::to_string(42); + \endrst + */ +template +std::string to_string(const T &value) { -FMT_VARIADIC(std::string, format, CStringRef) -FMT_VARIADIC_W(std::wstring, format, WCStringRef) -FMT_VARIADIC(void, print, CStringRef) -FMT_VARIADIC(void, print, std::FILE *, CStringRef) -FMT_VARIADIC(void, print_colored, Color, CStringRef) + std::string str; + internal::container_buffer buf(str); + writer(buf).write(value); + return str; +} -namespace internal +/** + Converts *value* to ``std::wstring`` using the default format for type *T*. + */ +template +std::wstring to_wstring(const T &value) { -template -inline bool is_name_start(Char c) + std::wstring str; + internal::container_buffer buf(str); + wwriter(buf).write(value); + return str; +} + +template +std::basic_string to_string(const basic_memory_buffer &buf) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; + return std::basic_string(buf.data(), buf.size()); } -// Parses an unsigned integer advancing s to the end of the parsed input. -// This function assumes that the first character of s is a digit. -template -unsigned parse_nonnegative_int(const Char *&s) +inline format_context::iterator vformat_to(internal::buffer &buf, string_view format_str, format_args args) { - assert('0' <= *s && *s <= '9'); - unsigned value = 0; - do - { - unsigned new_value = value * 10 + (*s++ - '0'); - // Check if value wrapped around. - if (new_value < value) - { - value = (std::numeric_limits::max)(); - break; - } - value = new_value; - } - while ('0' <= *s && *s <= '9'); - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits::max)(); - if (value > max_int) - FMT_THROW(FormatError("number is too big")); - return value; + typedef back_insert_range range; + return vformat_to>(buf, format_str, args); } -inline void require_numeric_argument(const Arg &arg, char spec) +inline wformat_context::iterator vformat_to(internal::wbuffer &buf, wstring_view format_str, wformat_args args) { - if (arg.type > Arg::LAST_NUMERIC_TYPE) - { - std::string message = - fmt::format("format specifier '{}' requires numeric argument", spec); - FMT_THROW(fmt::FormatError(message)); - } + typedef back_insert_range range; + return vformat_to>(buf, format_str, args); } -template -void check_sign(const Char *&s, const Arg &arg) +template +inline format_context::iterator format_to(basic_memory_buffer &buf, string_view format_str, const Args &... args) { - char sign = static_cast(*s); - require_numeric_argument(arg, sign); - if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) - { - FMT_THROW(FormatError(fmt::format( - "format specifier '{}' requires signed argument", sign))); - } - ++s; + return vformat_to(buf, format_str, make_format_args(args...)); } -} // namespace internal -template -inline internal::Arg BasicFormatter::get_arg( - BasicStringRef arg_name, const char *&error) +template +inline wformat_context::iterator format_to(basic_memory_buffer &buf, wstring_view format_str, const Args &... args) { - if (check_no_auto_index(error)) - { - map_.init(args()); - const internal::Arg *arg = map_.find(arg_name); - if (arg) - return *arg; - error = "argument not found"; - } - return internal::Arg(); + return vformat_to(buf, format_str, make_format_args(args...)); } -template -inline internal::Arg BasicFormatter::parse_arg_index(const Char *&s) +template +// using format_context_t = basic_format_context; +struct format_context_t { - const char *error = FMT_NULL; - internal::Arg arg = *s < '0' || *s > '9' ? - next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); - if (error) - { - FMT_THROW(FormatError( - *s != '}' && *s != ':' ? "invalid format string" : error)); - } - return arg; + typedef basic_format_context type; +}; + +template +// using format_args_t = basic_format_args>; +struct format_args_t +{ + typedef basic_format_args::type> type; +}; + +template +inline OutputIt vformat_to(OutputIt out, string_view format_str, typename format_args_t::type args) +{ + typedef output_range range; + return vformat_to>(range(out), format_str, args); +} +template +inline OutputIt vformat_to(OutputIt out, wstring_view format_str, typename format_args_t::type args) +{ + typedef output_range range; + return vformat_to>(range(out), format_str, args); } -template -inline internal::Arg BasicFormatter::parse_arg_name(const Char *&s) +/** + \rst + Formats arguments, writes the result to the output iterator ``out`` and returns + the iterator past the end of the output range. + + **Example**:: + + std::vector out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template +inline OutputIt format_to(OutputIt out, string_view format_str, const Args &... args) { - assert(internal::is_name_start(*s)); - const Char *start = s; - Char c; - do - { - c = *++s; - } - while (internal::is_name_start(c) || ('0' <= c && c <= '9')); - const char *error = FMT_NULL; - internal::Arg arg = get_arg(BasicStringRef(start, s - start), error); - if (error) - FMT_THROW(FormatError(error)); - return arg; + return vformat_to(out, format_str, make_format_args::type>(args...)); } -template -const Char *BasicFormatter::format( - const Char *&format_str, const internal::Arg &arg) +template +inline typename std::enable_if::value, std::back_insert_iterator>::type format_to( + std::back_insert_iterator out, string_view format_str, const Args &... args) { - using internal::Arg; - const Char *s = format_str; - FormatSpec spec; - if (*s == ':') - { - if (arg.type == Arg::CUSTOM) - { - arg.custom.format(this, arg.custom.value, &s); - return s; - } - ++s; - // Parse fill and alignment. - if (Char c = *s) - { - const Char *p = s + 1; - spec.align_ = ALIGN_DEFAULT; - do - { - switch (*p) - { - case '<': - spec.align_ = ALIGN_LEFT; - break; - case '>': - spec.align_ = ALIGN_RIGHT; - break; - case '=': - spec.align_ = ALIGN_NUMERIC; - break; - case '^': - spec.align_ = ALIGN_CENTER; - break; - } - if (spec.align_ != ALIGN_DEFAULT) - { - if (p != s) - { - if (c == '}') break; - if (c == '{') - FMT_THROW(FormatError("invalid fill character '{'")); - s += 2; - spec.fill_ = c; - } - else ++s; - if (spec.align_ == ALIGN_NUMERIC) - require_numeric_argument(arg, '='); - break; - } - } - while (--p >= s); - } + return vformat_to(out, format_str, make_format_args(args...)); +} - // Parse sign. - switch (*s) - { - case '+': - check_sign(s, arg); - spec.flags_ |= SIGN_FLAG | PLUS_FLAG; - break; - case '-': - check_sign(s, arg); - spec.flags_ |= MINUS_FLAG; - break; - case ' ': - check_sign(s, arg); - spec.flags_ |= SIGN_FLAG; - break; - } +template +inline typename std::enable_if::value, std::back_insert_iterator>::type format_to( + std::back_insert_iterator out, wstring_view format_str, const Args &... args) +{ + return vformat_to(out, format_str, make_format_args(args...)); +} - if (*s == '#') - { - require_numeric_argument(arg, '#'); - spec.flags_ |= HASH_FLAG; - ++s; - } +template +struct format_to_n_result +{ + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + std::size_t size; +}; - // Parse zero flag. - if (*s == '0') - { - require_numeric_argument(arg, '0'); - spec.align_ = ALIGN_NUMERIC; - spec.fill_ = '0'; - ++s; - } +template +using format_to_n_context = typename fmt::format_context_t>::type; - // Parse width. - if ('0' <= *s && *s <= '9') - { - spec.width_ = internal::parse_nonnegative_int(s); - } - else if (*s == '{') - { - ++s; - Arg width_arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - if (*s++ != '}') - FMT_THROW(FormatError("invalid format string")); - ULongLong value = 0; - switch (width_arg.type) - { - case Arg::INT: - if (width_arg.int_value < 0) - FMT_THROW(FormatError("negative width")); - value = width_arg.int_value; - break; - case Arg::UINT: - value = width_arg.uint_value; - break; - case Arg::LONG_LONG: - if (width_arg.long_long_value < 0) - FMT_THROW(FormatError("negative width")); - value = width_arg.long_long_value; - break; - case Arg::ULONG_LONG: - value = width_arg.ulong_long_value; - break; - default: - FMT_THROW(FormatError("width is not integer")); - } - if (value >(std::numeric_limits::max)()) - FMT_THROW(FormatError("number is too big")); - spec.width_ = static_cast(value); - } +template +using format_to_n_args = fmt::basic_format_args>; - // Parse precision. - if (*s == '.') - { - ++s; - spec.precision_ = 0; - if ('0' <= *s && *s <= '9') - { - spec.precision_ = internal::parse_nonnegative_int(s); - } - else if (*s == '{') - { - ++s; - Arg precision_arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - if (*s++ != '}') - FMT_THROW(FormatError("invalid format string")); - ULongLong value = 0; - switch (precision_arg.type) - { - case Arg::INT: - if (precision_arg.int_value < 0) - FMT_THROW(FormatError("negative precision")); - value = precision_arg.int_value; - break; - case Arg::UINT: - value = precision_arg.uint_value; - break; - case Arg::LONG_LONG: - if (precision_arg.long_long_value < 0) - FMT_THROW(FormatError("negative precision")); - value = precision_arg.long_long_value; - break; - case Arg::ULONG_LONG: - value = precision_arg.ulong_long_value; - break; - default: - FMT_THROW(FormatError("precision is not integer")); - } - if (value >(std::numeric_limits::max)()) - FMT_THROW(FormatError("number is too big")); - spec.precision_ = static_cast(value); - } - else - { - FMT_THROW(FormatError("missing precision specifier")); - } - if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) - { - FMT_THROW(FormatError( - fmt::format("precision not allowed in {} format specifier", - arg.type == Arg::POINTER ? "pointer" : "integer"))); - } - } +template +inline format_arg_store, Args...> make_format_to_n_args(const Args &... args) +{ + return format_arg_store, Args...>(args...); +} - // Parse type. - if (*s != '}' && *s) - spec.type_ = static_cast(*s++); - } +template +inline format_to_n_result vformat_to_n(OutputIt out, std::size_t n, string_view format_str, format_to_n_args args) +{ + typedef internal::truncating_iterator It; + auto it = vformat_to(It(out, n), format_str, args); + return {it.base(), it.count()}; +} - if (*s++ != '}') - FMT_THROW(FormatError("missing '}' in format string")); +/** + \rst + Formats arguments, writes up to ``n`` characters of the result to the output + iterator ``out`` and returns the total output size and the iterator past the + end + of the output range. + \endrst + */ +template +inline format_to_n_result format_to_n(OutputIt out, std::size_t n, string_view format_str, const Args &... args) +{ + return vformat_to_n(out, n, format_str, make_format_to_n_args(args...)); +} +template +inline format_to_n_result format_to_n(OutputIt out, std::size_t n, wstring_view format_str, const Args &... args) +{ + typedef internal::truncating_iterator It; + auto it = vformat_to(It(out, n), format_str, make_format_args::type>(args...)); + return {it.base(), it.count()}; +} - // Format argument. - ArgFormatter(*this, spec, s - 1).visit(arg); - return s; +inline std::string vformat(string_view format_str, format_args args) +{ + memory_buffer buffer; + vformat_to(buffer, format_str, args); + return fmt::to_string(buffer); } -template -void BasicFormatter::format(BasicCStringRef format_str) +inline std::wstring vformat(wstring_view format_str, wformat_args args) { - const Char *s = format_str.c_str(); - const Char *start = s; - while (*s) - { - Char c = *s++; - if (c != '{' && c != '}') continue; - if (*s == c) - { - write(writer_, start, s); - start = ++s; - continue; - } - if (c == '}') - FMT_THROW(FormatError("unmatched '}' in format string")); - write(writer_, start, s - 1); - internal::Arg arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - start = s = format(s, arg); - } - write(writer_, start, s); + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + return to_string(buffer); } -} // namespace fmt -#if FMT_USE_USER_DEFINED_LITERALS -namespace fmt +template +inline typename std::enable_if::value, std::string>::type format(String format_str, const Args &... args) { -namespace internal + internal::check_format_string(format_str); + return vformat(format_str.data(), make_format_args(args...)); +} + +template +inline typename std::enable_if::value>::type print(String format_str, const Args &... args) { + internal::check_format_string(format_str); + return vprint(format_str.data(), make_format_args(args...)); +} + +/** + Returns the number of characters in the output of + ``format(format_str, args...)``. + */ +template +inline std::size_t formatted_size(string_view format_str, const Args &... args) +{ + auto it = format_to(internal::counting_iterator(), format_str, args...); + return it.count(); +} + +// Experimental color support. +#ifdef FMT_EXTENDED_COLORS +enum class color : uint32_t +{ + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32, // rgb(154,205,50) +}; // enum class color + +// rgb is a struct for red, green and blue colors. +// We use rgb as name because some editors will show it as color direct in the +// editor. +struct rgb +{ + FMT_CONSTEXPR_DECL rgb() + : r(0) + , g(0) + , b(0) + { + } + FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) + : r(r_) + , g(g_) + , b(b_) + { + } + FMT_CONSTEXPR_DECL rgb(uint32_t hex) + : r((hex >> 16) & 0xFF) + , g((hex >> 8) & 0xFF) + , b((hex)&0xFF) + { + } + FMT_CONSTEXPR_DECL rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF) + , g((uint32_t(hex) >> 8) & 0xFF) + , b(uint32_t(hex) & 0xFF) + { + } + uint8_t r; + uint8_t g; + uint8_t b; +}; + +void vprint_rgb(rgb fd, string_view format, format_args args); +void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args); + +/** + Formats a string and prints it to stdout using ANSI escape sequences to + specify foreground color 'fd'. + Example: + fmt::print(fmt::color::red, "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +inline void print(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +/** + Formats a string and prints it to stdout using ANSI escape sequences to + specify foreground color 'fd' and background color 'bg'. + Example: + fmt::print(fmt::color::red, fmt::color::black, "Elapsed time: {0:.2f} + seconds", 1.23); + */ +template +inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, bg, format_str, make_format_args(args...)); +} +#endif // FMT_EXTENDED_COLORS + +#if FMT_USE_USER_DEFINED_LITERALS +namespace internal { -template -struct UdlFormat +#if FMT_UDL_TEMPLATE +template +class udl_formatter +{ +public: + template + std::basic_string operator()(const Args &... args) const + { + FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; + FMT_CONSTEXPR_DECL bool invalid_format = + check_format_string(basic_string_view(s, sizeof...(CHARS))); + (void)invalid_format; + return format(s, args...); + } +}; +#else +template +struct udl_formatter { const Char *str; - template - auto operator()(Args && ... args) const - -> decltype(format(str, std::forward(args)...)) + template + auto operator()(Args &&... args) const -> decltype(format(str, std::forward(args)...)) { return format(str, std::forward(args)...); } }; +#endif // FMT_UDL_TEMPLATE -template -struct UdlArg +template +struct udl_arg { const Char *str; - template - NamedArgWithType operator=(T &&value) const + template + named_arg operator=(T &&value) const { - return { str, std::forward(value) }; + return {str, std::forward(value)}; } }; } // namespace internal -inline namespace literals -{ +inline namespace literals { +#if FMT_UDL_TEMPLATE +template +FMT_CONSTEXPR internal::udl_formatter operator""_format() +{ + return {}; +} +#else /** -\rst -C++11 literal equivalent of :func:`fmt::format`. + \rst + User-defined literal equivalent of :func:`fmt::format`. -**Example**:: + **Example**:: -using namespace fmt::literals; -std::string message = "The answer is {}"_format(42); -\endrst -*/ -inline internal::UdlFormat -operator"" _format(const char *s, std::size_t) + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::udl_formatter operator"" _format(const char *s, std::size_t) { - return { s }; + return {s}; } -inline internal::UdlFormat -operator"" _format(const wchar_t *s, std::size_t) +inline internal::udl_formatter operator"" _format(const wchar_t *s, std::size_t) { - return { s }; + return {s}; } +#endif // FMT_UDL_TEMPLATE /** -\rst -C++11 literal equivalent of :func:`fmt::arg`. + \rst + User-defined literal equivalent of :func:`fmt::arg`. -**Example**:: + **Example**:: -using namespace fmt::literals; -print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); -\endrst -*/ -inline internal::UdlArg -operator"" _a(const char *s, std::size_t) + using namespace fmt::literals; + fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::udl_arg operator"" _a(const char *s, std::size_t) { - return { s }; + return {s}; } -inline internal::UdlArg -operator"" _a(const wchar_t *s, std::size_t) +inline internal::udl_arg operator"" _a(const wchar_t *s, std::size_t) { - return { s }; + return {s}; } - -} // inline namespace literals -} // namespace fmt +} // namespace literals #endif // FMT_USE_USER_DEFINED_LITERALS +FMT_END_NAMESPACE + +#define FMT_STRING(s) \ + [] { \ + struct S : fmt::format_string \ + { \ + static FMT_CONSTEXPR decltype(s) data() \ + { \ + return s; \ + } \ + static FMT_CONSTEXPR size_t size() \ + { \ + return sizeof(s); \ + } \ + }; \ + return S{}; \ + }() + +#ifndef FMT_NO_FMT_STRING_ALIAS +/** + \rst + Constructs a compile-time format string. -// Restore warnings. -#if FMT_GCC_VERSION >= 406 -# pragma GCC diagnostic pop -#endif + **Example**:: -#if defined(__clang__) && !defined(FMT_ICC_VERSION) -# pragma clang diagnostic pop + #include + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = format(fmt("{:d}"), "foo"); + \endrst + */ +#define fmt(s) FMT_STRING(s) #endif #ifdef FMT_HEADER_ONLY -# define FMT_FUNC inline -# include "format.cc" +#define FMT_FUNC inline +#include "format-inl.h" #else -# define FMT_FUNC +#define FMT_FUNC +#endif + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +#pragma GCC diagnostic pop #endif -#endif // FMT_FORMAT_H_ +#endif // FMT_FORMAT_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/locale.h b/ifs/include/extern/spdlog/fmt/bundled/locale.h new file mode 100644 index 000000000..0fa78c856 --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/locale.h @@ -0,0 +1,27 @@ +// Formatting library for C++ - locale support +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "format.h" +#include + +namespace fmt { +class locale +{ +private: + std::locale locale_; + +public: + explicit locale(std::locale loc = std::locale()) + : locale_(loc) + { + } + std::locale get() + { + return locale_; + } +}; +} // namespace fmt diff --git a/ifs/include/extern/spdlog/fmt/bundled/ostream.cc b/ifs/include/extern/spdlog/fmt/bundled/ostream.cc deleted file mode 100644 index 2148f3c17..000000000 --- a/ifs/include/extern/spdlog/fmt/bundled/ostream.cc +++ /dev/null @@ -1,37 +0,0 @@ -/* -Formatting library for C++ - std::ostream support - -Copyright (c) 2012 - 2016, Victor Zverovich -All rights reserved. - -For the license information refer to format.h. -*/ - -#include "ostream.h" - -namespace fmt { - - namespace internal { - FMT_FUNC void write(std::ostream &os, Writer &w) - { - const char *data = w.data(); - typedef internal::MakeUnsigned::Type UnsignedStreamSize; - UnsignedStreamSize size = w.size(); - UnsignedStreamSize max_size = - internal::to_unsigned((std::numeric_limits::max)()); - do { - UnsignedStreamSize n = size <= max_size ? size : max_size; - os.write(data, static_cast(n)); - data += n; - size -= n; - } while (size != 0); - } - } - - FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) - { - MemoryWriter w; - w.write(format_str, args); - internal::write(os, w); - } -} // namespace fmt diff --git a/ifs/include/extern/spdlog/fmt/bundled/ostream.h b/ifs/include/extern/spdlog/fmt/bundled/ostream.h index 3bdb375b2..439ded6e1 100644 --- a/ifs/include/extern/spdlog/fmt/bundled/ostream.h +++ b/ifs/include/extern/spdlog/fmt/bundled/ostream.h @@ -1,118 +1,173 @@ -/* -Formatting library for C++ - std::ostream support - -Copyright (c) 2012 - 2016, Victor Zverovich -All rights reserved. - -For the license information refer to format.h. -*/ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. #ifndef FMT_OSTREAM_H_ #define FMT_OSTREAM_H_ -// commented out by spdlog -// #include "format.h" +#include "format.h" #include -namespace fmt -{ - -namespace internal -{ +FMT_BEGIN_NAMESPACE +namespace internal { -template -class FormatBuf: public std::basic_streambuf +template +class formatbuf : public std::basic_streambuf { private: typedef typename std::basic_streambuf::int_type int_type; typedef typename std::basic_streambuf::traits_type traits_type; - Buffer &buffer_; - Char *start_; + basic_buffer &buffer_; public: - FormatBuf(Buffer &buffer): buffer_(buffer), start_(&buffer[0]) + formatbuf(basic_buffer &buffer) + : buffer_(buffer) { - this->setp(start_, start_ + buffer_.capacity()); } - int_type overflow(int_type ch = traits_type::eof()) +protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { if (!traits_type::eq_int_type(ch, traits_type::eof())) - { - size_t buf_size = size(); - buffer_.resize(buf_size); - buffer_.reserve(buf_size * 2); - - start_ = &buffer_[0]; - start_[buf_size] = traits_type::to_char_type(ch); - this->setp(start_ + buf_size + 1, start_ + buf_size * 2); - } + buffer_.push_back(static_cast(ch)); return ch; } - size_t size() const + std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE { - return to_unsigned(this->pptr() - start_); + buffer_.append(s, s + count); + return count; } }; -Yes &convert(std::ostream &); - -struct DummyStream: std::ostream +template +struct test_stream : std::basic_ostream { - DummyStream(); // Suppress a bogus warning in MSVC. - // Hide all operator<< overloads from std::ostream. - void operator<<(Null<>); +private: + struct null; + // Hide all operator<< from std::basic_ostream. + void operator<<(null); }; -No &operator<<(std::ostream &, int); - -template -struct ConvertToIntImpl +// Checks if T has a user-defined operator<< (e.g. not a member of +// std::ostream). +template +class is_streamable { - // Convert to int only if T doesn't have an overloaded operator<<. - enum - { - value = sizeof(convert(get() << get())) == sizeof(No) - }; +private: + template + static decltype(internal::declval &>() << internal::declval(), std::true_type()) test(int); + + template + static std::false_type test(...); + + typedef decltype(test(0)) result; + +public: + // std::string operator<< is not considered user-defined because we handle + // strings + // specially. + static const bool value = result::value && !std::is_same::value; }; -// Write the content of w to os. -void write(std::ostream &os, Writer &w); -} // namespace internal +// Disable conversion to int if T has an overloaded operator<< which is a free +// function (not a member of std::ostream). +template +class convert_to_int +{ +public: + static const bool value = convert_to_int::value && !is_streamable::value; +}; -// Formats a value. -template -void format_arg(BasicFormatter &f, - const Char *&format_str, const T &value) +// Write the content of buf to os. +template +void write(std::basic_ostream &os, basic_buffer &buf) { - internal::MemoryBuffer buffer; + const Char *data = buf.data(); + typedef std::make_unsigned::type UnsignedStreamSize; + UnsignedStreamSize size = buf.size(); + UnsignedStreamSize max_size = internal::to_unsigned((std::numeric_limits::max)()); + do + { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast(n)); + data += n; + size -= n; + } while (size != 0); +} - internal::FormatBuf format_buf(buffer); +template +void format_value(basic_buffer &buffer, const T &value) +{ + internal::formatbuf format_buf(buffer); std::basic_ostream output(&format_buf); + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output << value; - - BasicStringRef str(&buffer[0], format_buf.size()); - typedef internal::MakeArg< BasicFormatter > MakeArg; - format_str = f.format(format_str, MakeArg(str)); + buffer.resize(buffer.size()); } +// Disable builtin formatting of enums and use operator<< instead. +template +struct format_enum::value>::type> : std::false_type +{ +}; +} // namespace internal + +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct formatter::value>::type> : formatter, Char> +{ + + template + auto format(const T &value, Context &ctx) -> decltype(ctx.out()) + { + basic_memory_buffer buffer; + internal::format_value(buffer, value); + basic_string_view str(buffer.data(), buffer.size()); + formatter, Char>::format(str, ctx); + return ctx.out(); + } +}; + +template +inline void vprint( + std::basic_ostream &os, basic_string_view format_str, basic_format_args::type> args) +{ + basic_memory_buffer buffer; + vformat_to(buffer, format_str, args); + internal::write(os, buffer); +} /** -\rst -Prints formatted data to the stream *os*. + \rst + Prints formatted data to the stream *os*. -**Example**:: + **Example**:: -print(cerr, "Don't {}!", "panic"); -\endrst -*/ -FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); -FMT_VARIADIC(void, print, std::ostream &, CStringRef) -} // namespace fmt + fmt::print(cerr, "Don't {}!", "panic"); + \endrst + */ +template +inline void print(std::ostream &os, string_view format_str, const Args &... args) +{ + vprint(os, format_str, make_format_args(args...)); +} -#ifdef FMT_HEADER_ONLY -# include "ostream.cc" -#endif +template +inline void print(std::wostream &os, wstring_view format_str, const Args &... args) +{ + vprint(os, format_str, make_format_args(args...)); +} +FMT_END_NAMESPACE -#endif // FMT_OSTREAM_H_ +#endif // FMT_OSTREAM_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/posix.h b/ifs/include/extern/spdlog/fmt/bundled/posix.h new file mode 100644 index 000000000..20c758997 --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/posix.h @@ -0,0 +1,484 @@ +// A C++ interface to POSIX functions. +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +#undef __STRICT_ANSI__ +#endif + +#include +#include // for O_RDONLY +#include // for locale_t +#include +#include // for strtod_l + +#include + +#if defined __APPLE__ || defined(__FreeBSD__) +#include // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +#if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +#define FMT_POSIX(call) _##call +#else +#define FMT_POSIX(call) call +#endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +#define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +#define FMT_SYSTEM(call) call +#ifdef _WIN32 +// Fix warnings about deprecated symbols. +#define FMT_POSIX_CALL(call) ::_##call +#else +#define FMT_POSIX_CALL(call) ::call +#endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +#define FMT_RETRY_VAL(result, expression, error_result) \ + do \ + { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +#define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +FMT_BEGIN_NAMESPACE + +/** + \rst + A reference to a null-terminated string. It can be constructed from a C + string or ``std::string``. + + You can use one of the following typedefs for common character types: + + +---------------+-----------------------------+ + | Type | Definition | + +===============+=============================+ + | cstring_view | basic_cstring_view | + +---------------+-----------------------------+ + | wcstring_view | basic_cstring_view | + +---------------+-----------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(cstring_view format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class basic_cstring_view +{ +private: + const Char *data_; + +public: + /** Constructs a string reference object from a C string. */ + basic_cstring_view(const Char *s) + : data_(s) + { + } + + /** + \rst + Constructs a string reference from an ``std::string`` object. + \endrst + */ + basic_cstring_view(const std::basic_string &s) + : data_(s.c_str()) + { + } + + /** Returns the pointer to a C string. */ + const Char *c_str() const + { + return data_; + } +}; + +typedef basic_cstring_view cstring_view; +typedef basic_cstring_view wcstring_view; + +// An error code. +class error_code +{ +private: + int value_; + +public: + explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} + + int get() const FMT_NOEXCEPT + { + return value_; + } +}; + +// A buffered file. +class buffered_file +{ +private: + FILE *file_; + + friend class file; + + explicit buffered_file(FILE *f) + : file_(f) + { + } + +public: + // Constructs a buffered_file object which doesn't represent any file. + buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + FILE *file; + }; + +public: + // A "move constructor" for moving from a temporary. + buffered_file(Proxy p) FMT_NOEXCEPT : file_(p.file) {} + + // A "move constructor" for moving from an lvalue. + buffered_file(buffered_file &f) FMT_NOEXCEPT : file_(f.file_) + { + f.file_ = FMT_NULL; + } + + // A "move assignment operator" for moving from a temporary. + buffered_file &operator=(Proxy p) + { + close(); + file_ = p.file; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + buffered_file &operator=(buffered_file &other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // buffered_file file = buffered_file(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {file_}; + file_ = FMT_NULL; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(buffered_file); + +public: + buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) + { + other.file_ = FMT_NULL; + } + + buffered_file &operator=(buffered_file &&other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } +#endif + + // Opens a file. + FMT_API buffered_file(cstring_view filename, cstring_view mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT + { + return file_; + } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int(fileno)() const; + + void vprint(string_view format_str, format_args args) + { + fmt::vprint(file_, format_str, args); + } + + template + inline void print(string_view format_str, const Args &... args) + { + vprint(format_str, make_format_args(args...)); + } +}; + +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::system_error in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class file +{ +private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) + : fd_(fd) + { + } + +public: + // Possible values for the oflag argument to the constructor. + enum + { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a file object which doesn't represent any file. + file() FMT_NOEXCEPT : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + FMT_API file(cstring_view path, int oflag); + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + int fd; + }; + +public: + // A "move constructor" for moving from a temporary. + file(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {} + + // A "move constructor" for moving from an lvalue. + file(file &other) FMT_NOEXCEPT : fd_(other.fd_) + { + other.fd_ = -1; + } + + // A "move assignment operator" for moving from a temporary. + file &operator=(Proxy p) + { + close(); + fd_ = p.fd; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + file &operator=(file &other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // file f = file(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {fd_}; + fd_ = -1; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(file); + +public: + file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) + { + other.fd_ = -1; + } + + file &operator=(file &&other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } +#endif + + // Destroys the object closing the file it represents if any. + FMT_API ~file() FMT_DTOR_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT + { + return fd_; + } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API long long size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static file dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(file &read_end, file &write_end); + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + FMT_API buffered_file fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && !defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) +#define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale +{ +private: +#ifdef _MSC_VER + typedef _locale_t locale_t; + + enum + { + LC_NUMERIC_MASK = LC_NUMERIC + }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) + { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) + { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) + { + return _strtod_l(nptr, endptr, locale); + } +#endif + + locale_t locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(Locale); + +public: + typedef locale_t Type; + + Locale() + : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) + { + if (!locale_) + FMT_THROW(system_error(errno, "cannot create locale")); + } + ~Locale() + { + freelocale(locale_); + } + + Type get() const + { + return locale_; + } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const + { + char *end = FMT_NULL; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +FMT_END_NAMESPACE + +#if !FMT_USE_RVALUE_REFERENCES +namespace std { +// For compatibility with C++98. +inline fmt::buffered_file &move(fmt::buffered_file &f) +{ + return f; +} +inline fmt::file &move(fmt::file &f) +{ + return f; +} +} // namespace std +#endif + +#endif // FMT_POSIX_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/printf.h b/ifs/include/extern/spdlog/fmt/bundled/printf.h index 2b9ddfab0..1782c2eaa 100644 --- a/ifs/include/extern/spdlog/fmt/bundled/printf.h +++ b/ifs/include/extern/spdlog/fmt/bundled/printf.h @@ -1,31 +1,27 @@ -/* -Formatting library for C++ - -Copyright (c) 2012 - 2016, Victor Zverovich -All rights reserved. - -For the license information refer to format.h. -*/ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. #ifndef FMT_PRINTF_H_ #define FMT_PRINTF_H_ -#include // std::fill_n -#include // std::numeric_limits +#include // std::fill_n +#include // std::numeric_limits #include "ostream.h" -namespace fmt -{ -namespace internal -{ +FMT_BEGIN_NAMESPACE +namespace internal { // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. -template -struct IntChecker +template +struct int_checker { - template + template static bool fits_in_int(T value) { unsigned max = std::numeric_limits::max(); @@ -37,14 +33,13 @@ struct IntChecker } }; -template <> -struct IntChecker +template<> +struct int_checker { - template + template static bool fits_in_int(T value) { - return value >= std::numeric_limits::min() && - value <= std::numeric_limits::max(); + return value >= std::numeric_limits::min() && value <= std::numeric_limits::max(); } static bool fits_in_int(int) { @@ -52,160 +47,177 @@ struct IntChecker } }; -class PrecisionHandler: public ArgVisitor +class printf_precision_handler : public function { public: - void report_unhandled_arg() + template + typename std::enable_if::value, int>::type operator()(T value) { - FMT_THROW(FormatError("precision is not integer")); + if (!int_checker::is_signed>::fits_in_int(value)) + FMT_THROW(format_error("number is too big")); + return static_cast(value); } - template - int visit_any_int(T value) + template + typename std::enable_if::value, int>::type operator()(T) { - if (!IntChecker::is_signed>::fits_in_int(value)) - FMT_THROW(FormatError("number is too big")); - return static_cast(value); + FMT_THROW(format_error("precision is not integer")); + return 0; } }; -// IsZeroInt::visit(arg) returns true iff arg is a zero integer. -class IsZeroInt: public ArgVisitor +// An argument visitor that returns true iff arg is a zero integer. +class is_zero_int : public function { public: - template - bool visit_any_int(T value) + template + typename std::enable_if::value, bool>::type operator()(T value) { return value == 0; } + + template + typename std::enable_if::value, bool>::type operator()(T) + { + return false; + } }; -template -struct is_same +template +struct make_unsigned_or_bool : std::make_unsigned { - enum - { - value = 0 - }; }; -template -struct is_same +template<> +struct make_unsigned_or_bool { - enum - { - value = 1 - }; + typedef bool type; }; -// An argument visitor that converts an integer argument to T for printf, -// if T is an integral type. If T is void, the argument is converted to -// corresponding signed or unsigned type depending on the type specifier: -// 'd' and 'i' - signed, other - unsigned) -template -class ArgConverter: public ArgVisitor, void> +template +class arg_converter : public function { private: - internal::Arg &arg_; - wchar_t type_; + typedef typename Context::char_type Char; - FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); + basic_format_arg &arg_; + typename Context::char_type type_; public: - ArgConverter(internal::Arg &arg, wchar_t type) - : arg_(arg), type_(type) - {} + arg_converter(basic_format_arg &arg, Char type) + : arg_(arg) + , type_(type) + { + } - void visit_bool(bool value) + void operator()(bool value) { if (type_ != 's') - visit_any_int(value); + operator()(value); } - template - void visit_any_int(U value) + template + typename std::enable_if::value>::type operator()(U value) { bool is_signed = type_ == 'd' || type_ == 'i'; - using internal::Arg; - typedef typename internal::Conditional< - is_same::value, U, T>::type TargetType; - if (sizeof(TargetType) <= sizeof(int)) + typedef typename std::conditional::value, U, T>::type TargetType; + if (const_check(sizeof(TargetType) <= sizeof(int))) { // Extra casts are used to silence warnings. if (is_signed) { - arg_.type = Arg::INT; - arg_.int_value = static_cast(static_cast(value)); + arg_ = internal::make_arg(static_cast(static_cast(value))); } else { - arg_.type = Arg::UINT; - typedef typename internal::MakeUnsigned::Type Unsigned; - arg_.uint_value = static_cast(static_cast(value)); + typedef typename make_unsigned_or_bool::type Unsigned; + arg_ = internal::make_arg(static_cast(static_cast(value))); } } else { if (is_signed) { - arg_.type = Arg::LONG_LONG; // glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB. - arg_.long_long_value = static_cast(value); + arg_ = internal::make_arg(static_cast(value)); } else { - arg_.type = Arg::ULONG_LONG; - arg_.ulong_long_value = - static_cast::Type>(value); + arg_ = internal::make_arg(static_cast::type>(value)); } } } + + template + typename std::enable_if::value>::type operator()(U) + { + // No coversion needed for non-integral types. + } }; +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template +void convert_arg(basic_format_arg &arg, Char type) +{ + visit(arg_converter(arg, type), arg); +} + // Converts an integer argument to char for printf. -class CharConverter: public ArgVisitor +template +class char_converter : public function { private: - internal::Arg &arg_; + basic_format_arg &arg_; - FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); + FMT_DISALLOW_COPY_AND_ASSIGN(char_converter); public: - explicit CharConverter(internal::Arg &arg): arg_(arg) - {} + explicit char_converter(basic_format_arg &arg) + : arg_(arg) + { + } - template - void visit_any_int(T value) + template + typename std::enable_if::value>::type operator()(T value) { - arg_.type = internal::Arg::CHAR; - arg_.int_value = static_cast(value); + typedef typename Context::char_type Char; + arg_ = internal::make_arg(static_cast(value)); + } + + template + typename std::enable_if::value>::type operator()(T) + { + // No coversion needed for non-integral types. } }; // Checks if an argument is a valid printf width specifier and sets // left alignment if it is negative. -class WidthHandler: public ArgVisitor +template +class printf_width_handler : public function { private: - FormatSpec &spec_; + typedef basic_format_specs format_specs; - FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); + format_specs &spec_; -public: - explicit WidthHandler(FormatSpec &spec): spec_(spec) - {} + FMT_DISALLOW_COPY_AND_ASSIGN(printf_width_handler); - void report_unhandled_arg() +public: + explicit printf_width_handler(format_specs &spec) + : spec_(spec) { - FMT_THROW(FormatError("width is not integer")); } - template - unsigned visit_any_int(T value) + template + typename std::enable_if::value, unsigned>::type operator()(T value) { - typedef typename internal::IntTraits::MainType UnsignedType; + typedef typename internal::int_traits::main_type UnsignedType; UnsignedType width = static_cast(value); if (internal::is_negative(value)) { @@ -214,175 +226,230 @@ public: } unsigned int_max = std::numeric_limits::max(); if (width > int_max) - FMT_THROW(FormatError("number is too big")); + FMT_THROW(format_error("number is too big")); return static_cast(width); } + + template + typename std::enable_if::value, unsigned>::type operator()(T) + { + FMT_THROW(format_error("width is not integer")); + return 0; + } }; -} // namespace internal +} // namespace internal + +template +class printf_arg_formatter; + +template>>> +class basic_printf_context; /** -\rst -A ``printf`` argument formatter based on the `curiously recurring template -pattern `_. - -To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some -or all of the visit methods with the same signatures as the methods in -`~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. -Pass the subclass as the *Impl* template parameter. When a formatting -function processes an argument, it will dispatch to a visit method -specific to the argument type. For example, if the argument type is -``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass -will be called. If the subclass doesn't contain a method with this signature, -then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its -superclass will be called. -\endrst -*/ -template -class BasicPrintfArgFormatter: public internal::ArgFormatterBase + \rst + The ``printf`` argument formatter. + \endrst + */ +template +class printf_arg_formatter : public internal::function::iterator>, + public internal::arg_formatter_base { private: - void write_null_pointer() + typedef typename Range::value_type char_type; + typedef decltype(internal::declval().begin()) iterator; + typedef internal::arg_formatter_base base; + typedef basic_printf_context context_type; + + context_type &context_; + + void write_null_pointer(char) { this->spec().type_ = 0; this->write("(nil)"); } - typedef internal::ArgFormatterBase Base; + void write_null_pointer(wchar_t) + { + this->spec().type_ = 0; + this->write(L"(nil)"); + } public: + typedef typename base::format_specs format_specs; + /** - \rst - Constructs an argument formatter object. - *writer* is a reference to the output writer and *spec* contains format - specifier information for standard argument types. - \endrst - */ - BasicPrintfArgFormatter(BasicWriter &w, FormatSpec &s) - : internal::ArgFormatterBase(w, s) - {} - - /** Formats an argument of type ``bool``. */ - void visit_bool(bool value) - { - FormatSpec &fmt_spec = this->spec(); - if (fmt_spec.type_ != 's') - return this->visit_any_int(value); - fmt_spec.type_ = 0; - this->write(value); - } - - /** Formats a character. */ - void visit_char(int value) - { - const FormatSpec &fmt_spec = this->spec(); - BasicWriter &w = this->writer(); - if (fmt_spec.type_ && fmt_spec.type_ != 'c') - w.write_int(value, fmt_spec); - typedef typename BasicWriter::CharPtr CharPtr; - CharPtr out = CharPtr(); - if (fmt_spec.width_ > 1) + \rst + Constructs an argument formatter object. + *buffer* is a reference to the output buffer and *spec* contains format + specifier information for standard argument types. + \endrst + */ + printf_arg_formatter(internal::basic_buffer &buffer, format_specs &spec, context_type &ctx) + : base(back_insert_range>(buffer), spec) + , context_(ctx) + { + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same::value) { - Char fill = ' '; - out = w.grow_buffer(fmt_spec.width_); - if (fmt_spec.align_ != ALIGN_LEFT) - { - std::fill_n(out, fmt_spec.width_ - 1, fill); - out += fmt_spec.width_ - 1; - } - else - { - std::fill_n(out + 1, fmt_spec.width_ - 1, fill); - } + format_specs &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return base::operator()(value ? 1 : 0); + fmt_spec.type_ = 0; + this->write(value != 0); + } + else if (std::is_same::value) + { + format_specs &fmt_spec = this->spec(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + return (*this)(static_cast(value)); + fmt_spec.flags_ = 0; + fmt_spec.align_ = ALIGN_RIGHT; + return base::operator()(value); } else { - out = w.grow_buffer(1); + return base::operator()(value); } - *out = static_cast(value); + return this->out(); + } + + template + typename std::enable_if::value, iterator>::type operator()(T value) + { + return base::operator()(value); } /** Formats a null-terminated C string. */ - void visit_cstring(const char *value) + iterator operator()(const char *value) { if (value) - Base::visit_cstring(value); + base::operator()(value); else if (this->spec().type_ == 'p') - write_null_pointer(); + write_null_pointer(char_type()); else this->write("(null)"); + return this->out(); + } + + /** Formats a null-terminated wide C string. */ + iterator operator()(const wchar_t *value) + { + if (value) + base::operator()(value); + else if (this->spec().type_ == 'p') + write_null_pointer(char_type()); + else + this->write(L"(null)"); + return this->out(); + } + + iterator operator()(basic_string_view value) + { + return base::operator()(value); + } + + iterator operator()(monostate value) + { + return base::operator()(value); } /** Formats a pointer. */ - void visit_pointer(const void *value) + iterator operator()(const void *value) { if (value) - return Base::visit_pointer(value); + return base::operator()(value); this->spec().type_ = 0; - write_null_pointer(); + write_null_pointer(char_type()); + return this->out(); } /** Formats an argument of a custom (user-defined) type. */ - void visit_custom(internal::Arg::CustomValue c) + iterator operator()(typename basic_format_arg::handle handle) { - BasicFormatter formatter(ArgList(), this->writer()); - const Char format_str[] = { '}', 0 }; - const Char *format = format_str; - c.format(&formatter, c.value, &format); + handle.format(context_); + return this->out(); } }; -/** The default printf argument formatter. */ -template -class PrintfArgFormatter - : public BasicPrintfArgFormatter, Char> +template +struct printf_formatter { -public: - /** Constructs an argument formatter object. */ - PrintfArgFormatter(BasicWriter &w, FormatSpec &s) - : BasicPrintfArgFormatter, Char>(w, s) - {} + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } + + template + auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::format_value(internal::get_container(ctx.out()), value); + return ctx.out(); + } }; /** This template formats data and writes the output to a writer. */ -template > -class PrintfFormatter: private internal::FormatterBase +template +class basic_printf_context : private internal::context_base, Char> { +public: + /** The character type for the output. */ + typedef Char char_type; + + template + struct formatter_type + { + typedef printf_formatter type; + }; + private: - BasicWriter &writer_; + typedef internal::context_base base; + typedef typename base::format_arg format_arg; + typedef basic_format_specs format_specs; + typedef internal::null_terminating_iterator iterator; - void parse_flags(FormatSpec &spec, const Char *&s); + void parse_flags(format_specs &spec, iterator &it); // Returns the argument with specified index or, if arg_index is equal // to the maximum unsigned value, the next argument. - internal::Arg get_arg( - const Char *s, - unsigned arg_index = (std::numeric_limits::max)()); + format_arg get_arg(iterator it, unsigned arg_index = (std::numeric_limits::max)()); // Parses argument index, flags and width and returns the argument index. - unsigned parse_header(const Char *&s, FormatSpec &spec); + unsigned parse_header(iterator &it, format_specs &spec); public: /** - \rst - Constructs a ``PrintfFormatter`` object. References to the arguments and - the writer are stored in the formatter object so make sure they have - appropriate lifetimes. - \endrst - */ - explicit PrintfFormatter(const ArgList &al, BasicWriter &w) - : FormatterBase(al), writer_(w) - {} - - /** Formats stored arguments and writes the output to the writer. */ - FMT_API void format(BasicCStringRef format_str); + \rst + Constructs a ``printf_context`` object. References to the arguments and + the writer are stored in the context object so make sure they have + appropriate lifetimes. + \endrst + */ + basic_printf_context(OutputIt out, basic_string_view format_str, basic_format_args args) + : base(out, format_str, args) + { + } + + using base::advance_to; + using base::out; + using base::parse_context; + + /** Formats stored arguments and writes the output to the range. */ + void format(); }; -template -void PrintfFormatter::parse_flags(FormatSpec &spec, const Char *&s) +template +void basic_printf_context::parse_flags(format_specs &spec, iterator &it) { for (;;) { - switch (*s++) + switch (*it++) { case '-': spec.align_ = ALIGN_LEFT; @@ -400,39 +467,36 @@ void PrintfFormatter::parse_flags(FormatSpec &spec, const Char *&s) spec.flags_ |= HASH_FLAG; break; default: - --s; + --it; return; } } } -template -internal::Arg PrintfFormatter::get_arg(const Char *s, - unsigned arg_index) -{ - (void)s; - const char *error = FMT_NULL; - internal::Arg arg = arg_index == std::numeric_limits::max() ? - next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); - if (error) - FMT_THROW(FormatError(!*s ? "invalid format string" : error)); - return arg; +template +typename basic_printf_context::format_arg basic_printf_context::get_arg( + iterator it, unsigned arg_index) +{ + (void)it; + if (arg_index == std::numeric_limits::max()) + return this->do_get_arg(this->parse_context().next_arg_id()); + return base::get_arg(arg_index - 1); } -template -unsigned PrintfFormatter::parse_header( - const Char *&s, FormatSpec &spec) +template +unsigned basic_printf_context::parse_header(iterator &it, format_specs &spec) { unsigned arg_index = std::numeric_limits::max(); - Char c = *s; + char_type c = *it; if (c >= '0' && c <= '9') { // Parse an argument index (if followed by '$') or a width possibly // preceded with '0' flag(s). - unsigned value = internal::parse_nonnegative_int(s); - if (*s == '$') // value is an argument index - { - ++s; + internal::error_handler eh; + unsigned value = parse_nonnegative_int(it, eh); + if (*it == '$') + { // value is an argument index + ++it; arg_index = value; } else @@ -448,109 +512,117 @@ unsigned PrintfFormatter::parse_header( } } } - parse_flags(spec, s); + parse_flags(spec, it); // Parse width. - if (*s >= '0' && *s <= '9') + if (*it >= '0' && *it <= '9') { - spec.width_ = internal::parse_nonnegative_int(s); + internal::error_handler eh; + spec.width_ = parse_nonnegative_int(it, eh); } - else if (*s == '*') + else if (*it == '*') { - ++s; - spec.width_ = internal::WidthHandler(spec).visit(get_arg(s)); + ++it; + spec.width_ = visit(internal::printf_width_handler(spec), get_arg(it)); } return arg_index; } -template -void PrintfFormatter::format(BasicCStringRef format_str) +template +void basic_printf_context::format() { - const Char *start = format_str.c_str(); - const Char *s = start; - while (*s) + auto &buffer = internal::get_container(this->out()); + auto start = iterator(this->parse_context()); + auto it = start; + using internal::pointer_from; + while (*it) { - Char c = *s++; - if (c != '%') continue; - if (*s == c) + char_type c = *it++; + if (c != '%') + continue; + if (*it == c) { - write(writer_, start, s); - start = ++s; + buffer.append(pointer_from(start), pointer_from(it)); + start = ++it; continue; } - write(writer_, start, s - 1); + buffer.append(pointer_from(start), pointer_from(it) - 1); - FormatSpec spec; + format_specs spec; spec.align_ = ALIGN_RIGHT; // Parse argument index, flags and width. - unsigned arg_index = parse_header(s, spec); + unsigned arg_index = parse_header(it, spec); // Parse precision. - if (*s == '.') + if (*it == '.') { - ++s; - if ('0' <= *s && *s <= '9') + ++it; + if ('0' <= *it && *it <= '9') + { + internal::error_handler eh; + spec.precision_ = static_cast(parse_nonnegative_int(it, eh)); + } + else if (*it == '*') { - spec.precision_ = static_cast(internal::parse_nonnegative_int(s)); + ++it; + spec.precision_ = visit(internal::printf_precision_handler(), get_arg(it)); } - else if (*s == '*') + else { - ++s; - spec.precision_ = internal::PrecisionHandler().visit(get_arg(s)); + spec.precision_ = 0; } } - using internal::Arg; - Arg arg = get_arg(s, arg_index); - if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg)) + format_arg arg = get_arg(it, arg_index); + if (spec.flag(HASH_FLAG) && visit(internal::is_zero_int(), arg)) spec.flags_ &= ~internal::to_unsigned(HASH_FLAG); if (spec.fill_ == '0') { - if (arg.type <= Arg::LAST_NUMERIC_TYPE) + if (arg.is_arithmetic()) spec.align_ = ALIGN_NUMERIC; else - spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. } // Parse length and convert the argument to the required type. - using internal::ArgConverter; - switch (*s++) + using internal::convert_arg; + switch (*it++) { case 'h': - if (*s == 'h') - ArgConverter(arg, *++s).visit(arg); + if (*it == 'h') + convert_arg(arg, *++it); else - ArgConverter(arg, *s).visit(arg); + convert_arg(arg, *it); break; case 'l': - if (*s == 'l') - ArgConverter(arg, *++s).visit(arg); + if (*it == 'l') + convert_arg(arg, *++it); else - ArgConverter(arg, *s).visit(arg); + convert_arg(arg, *it); break; case 'j': - ArgConverter(arg, *s).visit(arg); + convert_arg(arg, *it); break; case 'z': - ArgConverter(arg, *s).visit(arg); + convert_arg(arg, *it); break; case 't': - ArgConverter(arg, *s).visit(arg); + convert_arg(arg, *it); break; case 'L': // printf produces garbage when 'L' is omitted for long double, no // need to do the same. break; default: - --s; - ArgConverter(arg, *s).visit(arg); + --it; + convert_arg(arg, *it); } // Parse type. - if (!*s) - FMT_THROW(FormatError("invalid format string")); - spec.type_ = static_cast(*s++); - if (arg.type <= Arg::LAST_INTEGER_TYPE) + if (!*it) + FMT_THROW(format_error("invalid format string")); + spec.type_ = static_cast(*it++); + if (arg.is_integral()) { // Normalize type. switch (spec.type_) @@ -560,99 +632,176 @@ void PrintfFormatter::format(BasicCStringRef format_str) spec.type_ = 'd'; break; case 'c': - // TODO: handle wchar_t - internal::CharConverter(arg).visit(arg); + // TODO: handle wchar_t better? + visit(internal::char_converter(arg), arg); break; } } - start = s; + start = it; // Format argument. - AF(writer_, spec).visit(arg); + visit(AF(buffer, spec, *this), arg); } - write(writer_, start, s); + buffer.append(pointer_from(start), pointer_from(it)); +} + +template +void printf(internal::basic_buffer &buf, basic_string_view format, basic_format_args args) +{ + Context(std::back_inserter(buf), format, args).format(); +} + +template +struct printf_context +{ + typedef basic_printf_context, typename Buffer::value_type> type; +}; + +template +inline format_arg_store::type, Args...> make_printf_args(const Args &... args) +{ + return format_arg_store::type, Args...>(args...); } +typedef basic_format_args::type> printf_args; +typedef basic_format_args::type> wprintf_args; -template -void printf(BasicWriter &w, BasicCStringRef format, ArgList args) +inline std::string vsprintf(string_view format, printf_args args) { - PrintfFormatter(args, w).format(format); + memory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); } /** -\rst -Formats arguments and returns the result as a string. + \rst + Formats arguments and returns the result as a string. -**Example**:: + **Example**:: -std::string message = fmt::sprintf("The answer is %d", 42); -\endrst + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst */ -inline std::string sprintf(CStringRef format, ArgList args) +template +inline std::string sprintf(string_view format_str, const Args &... args) +{ + return vsprintf(format_str, make_format_args::type>(args...)); +} + +inline std::wstring vsprintf(wstring_view format, wprintf_args args) { - MemoryWriter w; - printf(w, format, args); - return w.str(); + wmemory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); } -FMT_VARIADIC(std::string, sprintf, CStringRef) -inline std::wstring sprintf(WCStringRef format, ArgList args) +template +inline std::wstring sprintf(wstring_view format_str, const Args &... args) { - WMemoryWriter w; - printf(w, format, args); - return w.str(); + return vsprintf(format_str, make_format_args::type>(args...)); +} + +template +inline int vfprintf( + std::FILE *f, basic_string_view format, basic_format_args>::type> args) +{ + basic_memory_buffer buffer; + printf(buffer, format, args); + std::size_t size = buffer.size(); + return std::fwrite(buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast(size); } -FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) /** -\rst -Prints formatted data to the file *f*. + \rst + Prints formatted data to the file *f*. -**Example**:: + **Example**:: -fmt::fprintf(stderr, "Don't %s!", "panic"); -\endrst -*/ -FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); -FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +template +inline int fprintf(std::FILE *f, string_view format_str, const Args &... args) +{ + auto vargs = make_format_args::type>(args...); + return vfprintf(f, format_str, vargs); +} + +template +inline int fprintf(std::FILE *f, wstring_view format_str, const Args &... args) +{ + return vfprintf(f, format_str, make_format_args::type>(args...)); +} + +inline int vprintf(string_view format, printf_args args) +{ + return vfprintf(stdout, format, args); +} + +inline int vprintf(wstring_view format, wprintf_args args) +{ + return vfprintf(stdout, format, args); +} /** -\rst -Prints formatted data to ``stdout``. + \rst + Prints formatted data to ``stdout``. -**Example**:: + **Example**:: -fmt::printf("Elapsed time: %.2f seconds", 1.23); -\endrst -*/ -inline int printf(CStringRef format, ArgList args) + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +template +inline int printf(string_view format_str, const Args &... args) +{ + return vprintf(format_str, make_format_args::type>(args...)); +} + +template +inline int printf(wstring_view format_str, const Args &... args) +{ + return vprintf(format_str, make_format_args::type>(args...)); +} + +inline int vfprintf(std::ostream &os, string_view format_str, printf_args args) +{ + memory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast(buffer.size()); +} + +inline int vfprintf(std::wostream &os, wstring_view format_str, wprintf_args args) { - return fprintf(stdout, format, args); + wmemory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast(buffer.size()); } -FMT_VARIADIC(int, printf, CStringRef) /** -\rst -Prints formatted data to the stream *os*. + \rst + Prints formatted data to the stream *os*. -**Example**:: + **Example**:: -fprintf(cerr, "Don't %s!", "panic"); -\endrst -*/ -inline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) + fmt::fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +template +inline int fprintf(std::ostream &os, string_view format_str, const Args &... args) { - MemoryWriter w; - printf(w, format_str, args); - internal::write(os, w); - return static_cast(w.size()); + auto vargs = make_format_args::type>(args...); + return vfprintf(os, format_str, vargs); } -FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) -} // namespace fmt -#ifdef FMT_HEADER_ONLY -# include "printf.cc" -#endif +template +inline int fprintf(std::wostream &os, wstring_view format_str, const Args &... args) +{ + auto vargs = make_format_args::type>(args...); + return vfprintf(os, format_str, vargs); +} +FMT_END_NAMESPACE -#endif // FMT_PRINTF_H_ +#endif // FMT_PRINTF_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/ranges.h b/ifs/include/extern/spdlog/fmt/bundled/ranges.h new file mode 100644 index 000000000..ba2a979cc --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/ranges.h @@ -0,0 +1,344 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#include "format.h" +#include + +// output only up to N items from the range. +#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT +#define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct formatting_base +{ + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return ctx.begin(); + } +}; + +template +struct formatting_range : formatting_base +{ + static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the + // range. + Char prefix; + Char delimiter; + Char postfix; + formatting_range() + : prefix('{') + , delimiter(',') + , postfix('}') + { + } + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +template +struct formatting_tuple : formatting_base +{ + Char prefix; + Char delimiter; + Char postfix; + formatting_tuple() + : prefix('(') + , delimiter(',') + , postfix(')') + { + } + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +namespace internal { + +template +void copy(const RangeT &range, OutputIterator out) +{ + for (auto it = range.begin(), end = range.end(); it != end; ++it) + *out++ = *it; +} + +template +void copy(const char *str, OutputIterator out) +{ + const char *p_curr = str; + while (*p_curr) + { + *out++ = *p_curr++; + } +} + +template +void copy(char ch, OutputIterator out) +{ + *out++ = ch; +} + +/// Return true value if T has std::string interface, like std::string_view. +template +class is_like_std_string +{ + template + static auto check(U *p) -> decltype(p->find('a'), p->length(), p->data(), int()); + template + static void check(...); + +public: + static FMT_CONSTEXPR_DECL const bool value = !std::is_void(FMT_NULL))>::value; +}; + +template +struct conditional_helper +{ +}; + +template +struct is_range_ : std::false_type +{ +}; + +#if !FMT_MSC_VER || FMT_MSC_VER > 1800 +template +struct is_range_().begin()), decltype(internal::declval().end())>, void>::type> + : std::true_type +{ +}; +#endif + +/// tuple_size and tuple_element check. +template +class is_tuple_like_ +{ + template + static auto check(U *p) -> decltype(std::tuple_size::value, internal::declval::type>(), int()); + template + static void check(...); + +public: + static FMT_CONSTEXPR_DECL const bool value = !std::is_void(FMT_NULL))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 +template +using integer_sequence = std::integer_sequence; +template +using index_sequence = std::index_sequence; +template +using make_index_sequence = std::make_index_sequence; +#else +template +struct integer_sequence +{ + typedef T value_type; + + static FMT_CONSTEXPR std::size_t size() + { + return sizeof...(N); + } +}; + +template +using index_sequence = integer_sequence; + +template +struct make_integer_sequence : make_integer_sequence +{ +}; +template +struct make_integer_sequence : integer_sequence +{ +}; + +template +using make_index_sequence = make_integer_sequence; +#endif + +template +void for_each(index_sequence, Tuple &&tup, F &&f) FMT_NOEXCEPT +{ + using std::get; + // using free function get(T) now. + const int _[] = {0, ((void)f(get(tup)), 0)...}; + (void)_; // blocks warnings +} + +template +FMT_CONSTEXPR make_index_sequence::value> get_indexes(T const &) +{ + return {}; +} + +template +void for_each(Tuple &&tup, F &&f) +{ + const auto indexes = get_indexes(tup); + for_each(indexes, std::forward(tup), std::forward(f)); +} + +template +FMT_CONSTEXPR const char *format_str_quoted( + bool add_space, const Arg &, typename std::enable_if::type>::value>::type * = nullptr) +{ + return add_space ? " {}" : "{}"; +} + +template +FMT_CONSTEXPR const char *format_str_quoted( + bool add_space, const Arg &, typename std::enable_if::type>::value>::type * = nullptr) +{ + return add_space ? " \"{}\"" : "\"{}\""; +} + +FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char *) +{ + return add_space ? " \"{}\"" : "\"{}\""; +} +FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t *) +{ + return add_space ? L" \"{}\"" : L"\"{}\""; +} + +FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char) +{ + return add_space ? " '{}'" : "'{}'"; +} +FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t) +{ + return add_space ? L" '{}'" : L"'{}'"; +} + +} // namespace internal + +template +struct is_tuple_like +{ + static FMT_CONSTEXPR_DECL const bool value = internal::is_tuple_like_::value && !internal::is_range_::value; +}; + +template +struct formatter::value>::type> +{ +private: + // C++11 generic lambda for format() + template + struct format_each + { + template + void operator()(const T &v) + { + if (i > 0) + { + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), v), v); + ++i; + } + + formatting_tuple &formatting; + std::size_t &i; + typename std::add_lvalue_reference().out())>::type out; + }; + +public: + formatting_tuple formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return formatting.parse(ctx); + } + + template + auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) + { + auto out = ctx.out(); + std::size_t i = 0; + internal::copy(formatting.prefix, out); + + internal::for_each(values, format_each{formatting, i, out}); + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + + return ctx.out(); + } +}; + +template +struct is_range +{ + static FMT_CONSTEXPR_DECL const bool value = internal::is_range_::value && !internal::is_like_std_string::value; +}; + +template +struct formatter::value>::type> +{ + + formatting_range formatting; + + template + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + return formatting.parse(ctx); + } + + template + typename FormatContext::iterator format(const RangeT &values, FormatContext &ctx) + { + auto out = ctx.out(); + internal::copy(formatting.prefix, out); + std::size_t i = 0; + for (auto it = values.begin(), end = values.end(); it != end; ++it) + { + if (i > 0) + { + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), *it), *it); + if (++i > formatting.range_length_limit) + { + format_to(out, " ... "); + break; + } + } + if (formatting.add_prepostfix_space) + { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + return ctx.out(); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/ifs/include/extern/spdlog/fmt/bundled/time.h b/ifs/include/extern/spdlog/fmt/bundled/time.h new file mode 100644 index 000000000..3e1443b81 --- /dev/null +++ b/ifs/include/extern/spdlog/fmt/bundled/time.h @@ -0,0 +1,199 @@ +// Formatting library for C++ - time formatting +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include + +FMT_BEGIN_NAMESPACE + +namespace internal { +inline null<> localtime_r(...) +{ + return null<>(); +} +inline null<> localtime_s(...) +{ + return null<>(); +} +inline null<> gmtime_r(...) +{ + return null<>(); +} +inline null<> gmtime_s(...) +{ + return null<>(); +} +} // namespace internal + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) +{ + struct dispatcher + { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) + : time_(t) + { + } + + bool run() + { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::null<>) + { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::null<>) + { + using namespace fmt::internal; + std::tm *tm = std::localtime(&time_); + if (tm) + tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) +{ + struct dispatcher + { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t) + : time_(t) + { + } + + bool run() + { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::null<>) + { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::null<>) + { + std::tm *tm = std::gmtime(&time_); + if (tm) + tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +namespace internal { +inline std::size_t strftime(char *str, std::size_t count, const char *format, const std::tm *time) +{ + return std::strftime(str, count, format, time); +} + +inline std::size_t strftime(wchar_t *str, std::size_t count, const wchar_t *format, const std::tm *time) +{ + return std::wcsftime(str, count, format, time); +} +} // namespace internal + +template +struct formatter +{ + template + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + auto it = internal::null_terminating_iterator(ctx); + if (*it == ':') + ++it; + auto end = it; + while (*end && *end != '}') + ++end; + tm_format.reserve(end - it + 1); + using internal::pointer_from; + tm_format.append(pointer_from(it), pointer_from(end)); + tm_format.push_back('\0'); + return pointer_from(end); + } + + template + auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) + { + internal::basic_buffer &buf = internal::get_container(ctx.out()); + std::size_t start = buf.size(); + for (;;) + { + std::size_t size = buf.capacity() - start; + std::size_t count = internal::strftime(&buf[start], size, &tm_format[0], &tm); + if (count != 0) + { + buf.resize(start + count); + break; + } + if (size >= tm_format.size() * 256) + { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + return ctx.out(); + } + + basic_memory_buffer tm_format; +}; +FMT_END_NAMESPACE + +#endif // FMT_TIME_H_ diff --git a/ifs/include/extern/spdlog/fmt/fmt.h b/ifs/include/extern/spdlog/fmt/fmt.h index 51c536456..616af0cc7 100644 --- a/ifs/include/extern/spdlog/fmt/fmt.h +++ b/ifs/include/extern/spdlog/fmt/fmt.h @@ -1,5 +1,5 @@ // -// Copyright(c) 2016 Gabi Melman. +// Copyright(c) 2016-2018 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // @@ -11,19 +11,15 @@ // #if !defined(SPDLOG_FMT_EXTERNAL) - #ifndef FMT_HEADER_ONLY #define FMT_HEADER_ONLY #endif #ifndef FMT_USE_WINDOWS_H #define FMT_USE_WINDOWS_H 0 #endif - -#include - -#else //external fmtlib - +#include "bundled/core.h" +#include "bundled/format.h" +#else // external fmtlib +#include #include - #endif - diff --git a/ifs/include/extern/spdlog/fmt/ostr.h b/ifs/include/extern/spdlog/fmt/ostr.h index 466a71187..9902898f8 100644 --- a/ifs/include/extern/spdlog/fmt/ostr.h +++ b/ifs/include/extern/spdlog/fmt/ostr.h @@ -4,15 +4,15 @@ // #pragma once - -// include external or bundled copy of fmtlib's ostream support +// +// include bundled or external copy of fmtlib's ostream support // #if !defined(SPDLOG_FMT_EXTERNAL) - -#include -#include +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#include "bundled/ostream.h" +#include "fmt.h" #else #include #endif - - diff --git a/ifs/include/extern/spdlog/formatter.h b/ifs/include/extern/spdlog/formatter.h index fd0013414..a7ef6b8b5 100644 --- a/ifs/include/extern/spdlog/formatter.h +++ b/ifs/include/extern/spdlog/formatter.h @@ -5,41 +5,16 @@ #pragma once -#include +#include "fmt/fmt.h" +#include "spdlog/details/log_msg.h" -#include -#include -#include - -namespace spdlog -{ -namespace details -{ -class flag_formatter; -} +namespace spdlog { class formatter { public: - virtual ~formatter() {} - virtual void format(details::log_msg& msg) = 0; -}; - -class pattern_formatter : public formatter -{ - -public: - explicit pattern_formatter(const std::string& pattern); - pattern_formatter(const pattern_formatter&) = delete; - pattern_formatter& operator=(const pattern_formatter&) = delete; - void format(details::log_msg& msg) override; -private: - const std::string _pattern; - std::vector> _formatters; - void handle_flag(char flag); - void compile_pattern(const std::string& pattern); + virtual ~formatter() = default; + virtual void format(const details::log_msg &msg, fmt::memory_buffer &dest) = 0; + virtual std::unique_ptr clone() const = 0; }; -} - -#include - +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/logger.h b/ifs/include/extern/spdlog/logger.h index 8d75cef1a..91eec4bc3 100644 --- a/ifs/include/extern/spdlog/logger.h +++ b/ifs/include/extern/spdlog/logger.h @@ -1,94 +1,162 @@ // -// Copyright(c) 2015 Gabi Melman. +// Copyright(c) 2015-2108 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // #pragma once -// Thread safe logger (except for set_pattern(..), set_formatter(..) and set_error_handler()) +// Thread safe logger (except for set_pattern(..), set_formatter(..) and +// set_error_handler()) // Has name, log level, vector of std::shared sink pointers and formatter // Upon each log write the logger: -// 1. Checks if its log level is enough to log the message -// 2. Format the message using the formatter function -// 3. Pass the formatted message to its sinks to performa the actual logging +// 1. Checks if its log level is enough to log the message and if yes: +// 2. Call the underlying sinks to do the job. +// 3. Each sink use its own private copy of a formatter to format the message +// and send to its destination. +// +// The use of private formatter per sink provides the opportunity to cache some +// formatted data, +// and support customize format per each sink. -#include -#include +#include "spdlog/common.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" -#include #include #include +#include -namespace spdlog -{ +namespace spdlog { class logger { public: - logger(const std::string& logger_name, sink_ptr single_sink); - logger(const std::string& name, sinks_init_list); - template - logger(const std::string& name, const It& begin, const It& end); + logger(std::string name, sink_ptr single_sink); + logger(std::string name, sinks_init_list sinks); + + template + logger(std::string name, const It &begin, const It &end); virtual ~logger(); - logger(const logger&) = delete; - logger& operator=(const logger&) = delete; - - - template void log(level::level_enum lvl, const char* fmt, const Args&... args); - template void log(level::level_enum lvl, const char* msg); - template void trace(const char* fmt, const Args&... args); - template void debug(const char* fmt, const Args&... args); - template void info(const char* fmt, const Args&... args); - template void warn(const char* fmt, const Args&... args); - template void error(const char* fmt, const Args&... args); - template void critical(const char* fmt, const Args&... args); - - template void log(level::level_enum lvl, const T&); - template void trace(const T&); - template void debug(const T&); - template void info(const T&); - template void warn(const T&); - template void error(const T&); - template void critical(const T&); - - bool should_log(level::level_enum) const; - void set_level(level::level_enum); + + logger(const logger &) = delete; + logger &operator=(const logger &) = delete; + + template + void log(level::level_enum lvl, const char *fmt, const Args &... args); + + template + void log(level::level_enum lvl, const char *msg); + + template + void trace(const char *fmt, const Args &... args); + + template + void debug(const char *fmt, const Args &... args); + + template + void info(const char *fmt, const Args &... args); + + template + void warn(const char *fmt, const Args &... args); + + template + void error(const char *fmt, const Args &... args); + + template + void critical(const char *fmt, const Args &... args); + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template + void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args); + + template + void trace(const wchar_t *fmt, const Args &... args); + + template + void debug(const wchar_t *fmt, const Args &... args); + + template + void info(const wchar_t *fmt, const Args &... args); + + template + void warn(const wchar_t *fmt, const Args &... args); + + template + void error(const wchar_t *fmt, const Args &... args); + + template + void critical(const wchar_t *fmt, const Args &... args); +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + template + void log(level::level_enum lvl, const T &); + + template + void trace(const T &msg); + + template + void debug(const T &msg); + + template + void info(const T &msg); + + template + void warn(const T &msg); + + template + void error(const T &msg); + + template + void critical(const T &msg); + + bool should_log(level::level_enum msg_level) const; + void set_level(level::level_enum log_level); level::level_enum level() const; - const std::string& name() const; - void set_pattern(const std::string&); - void set_formatter(formatter_ptr); + const std::string &name() const; - // error handler - void set_error_handler(log_err_handler); - log_err_handler error_handler(); + // set formatting for the sinks in this logger. + // each sink will get a seperate instance of the formatter object. + void set_formatter(std::unique_ptr formatter); + void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); - // automatically call flush() if message level >= log_level + void flush(); void flush_on(level::level_enum log_level); - virtual void flush(); + const std::vector &sinks() const; - const std::vector& sinks() const; + std::vector &sinks(); + + void set_error_handler(log_err_handler err_handler); + log_err_handler error_handler(); protected: - virtual void _sink_it(details::log_msg&); - virtual void _set_pattern(const std::string&); - virtual void _set_formatter(formatter_ptr); - - // default error handler: print the error to stderr with the max rate of 1 message/minute - virtual void _default_err_handler(const std::string &msg); - - // return true if the given message level should trigger a flush - bool _should_flush_on(const details::log_msg&); - - const std::string _name; - std::vector _sinks; - formatter_ptr _formatter; - spdlog::level_t _level; - spdlog::level_t _flush_level; - log_err_handler _err_handler; - std::atomic _last_err_time; + virtual void sink_it_(details::log_msg &msg); + virtual void flush_(); + + bool should_flush_(const details::log_msg &msg); + + // default error handler: print the error to stderr with the max rate of 1 + // message/minute + void default_err_handler_(const std::string &msg); + + // increment the message count (only if + // defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) + void incr_msg_counter_(details::log_msg &msg); + + const std::string name_; + std::vector sinks_; + spdlog::level_t level_; + spdlog::level_t flush_level_; + log_err_handler err_handler_; + std::atomic last_err_time_; + std::atomic msg_counter_; + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + std::wstring_convert> wstring_converter_; + std::mutex wstring_converter_mutex_; +#endif }; -} +} // namespace spdlog -#include +#include "details/logger_impl.h" diff --git a/ifs/include/extern/spdlog/sinks/android_sink.h b/ifs/include/extern/spdlog/sinks/android_sink.h index d8c97e03b..fcb9ccbfa 100644 --- a/ifs/include/extern/spdlog/sinks/android_sink.h +++ b/ifs/include/extern/spdlog/sinks/android_sink.h @@ -5,49 +5,75 @@ #pragma once -#if defined(__ANDROID__) - -#include +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" +#include "spdlog/sinks/base_sink.h" +#include +#include #include #include -#include +#include -namespace spdlog -{ -namespace sinks -{ +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog { +namespace sinks { /* -* Android sink (logging using __android_log_write) -* __android_log_write is thread-safe. No lock is needed. -*/ -class android_sink : public sink + * Android sink (logging using __android_log_write) + */ +template +class android_sink SPDLOG_FINAL : public base_sink { public: - explicit android_sink(const std::string& tag = "spdlog"): _tag(tag) {} + explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) + : tag_(std::move(tag)) + , use_raw_msg_(use_raw_msg) + { + } - void log(const details::log_msg& msg) override +protected: + void sink_it_(const details::log_msg &msg) override { - const android_LogPriority priority = convert_to_android(msg.level); + const android_LogPriority priority = convert_to_android_(msg.level); + fmt::memory_buffer formatted; + if (use_raw_msg_) + { + details::fmt_helper::append_buf(msg.raw, formatted); + } + else + { + sink::formatter_->format(msg, formatted); + } + formatted.push_back('\0'); + const char *msg_output = formatted.data(); + // See system/core/liblog/logger_write.c for explanation of return value - const int ret = __android_log_write( - priority, _tag.c_str(), msg.formatted.c_str() - ); + int ret = __android_log_write(priority, tag_.c_str(), msg_output); + int retry_count = 0; + while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) + { + details::os::sleep_for_millis(5); + ret = __android_log_write(priority, tag_.c_str(), msg_output); + retry_count++; + } + if (ret < 0) { throw spdlog_ex("__android_log_write() failed", ret); } } - void flush() override - { - } + void flush_() override {} private: - static android_LogPriority convert_to_android(spdlog::level::level_enum level) + static android_LogPriority convert_to_android_(spdlog::level::level_enum level) { - switch(level) + switch (level) { case spdlog::level::trace: return ANDROID_LOG_VERBOSE; @@ -66,10 +92,26 @@ private: } } - std::string _tag; + std::string tag_; + bool use_raw_msg_; }; +using android_sink_mt = android_sink; +using android_sink_st = android_sink; +} // namespace sinks + +// Create and register android syslog logger + +template +inline std::shared_ptr android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create(logger_name, tag); } + +template +inline std::shared_ptr android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create(logger_name, tag); } -#endif +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/ansicolor_sink.h b/ifs/include/extern/spdlog/sinks/ansicolor_sink.h index 698210743..3f6c63b67 100644 --- a/ifs/include/extern/spdlog/sinks/ansicolor_sink.h +++ b/ifs/include/extern/spdlog/sinks/ansicolor_sink.h @@ -1,116 +1,156 @@ // -// Copyright(c) 2016 Kevin M. Godby (a modified version by spdlog). +// Copyright(c) 2017 spdlog authors. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // #pragma once -#include -#include +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" +#include +#include #include -#include +#include -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { /** - * @brief The ansi_color_sink is a decorator around another sink and prefixes - * the output with an ANSI escape sequence color code depending on the severity + * This sink prefixes the output with an ANSI escape sequence color code + * depending on the severity * of the message. + * If no color terminal detected, omit the escape codes. */ -class ansicolor_sink : public sink +template +class ansicolor_sink SPDLOG_FINAL : public sink { public: - ansicolor_sink(sink_ptr wrapped_sink); - virtual ~ansicolor_sink(); - - ansicolor_sink(const ansicolor_sink& other) = delete; - ansicolor_sink& operator=(const ansicolor_sink& other) = delete; - - virtual void log(const details::log_msg& msg) override; - virtual void flush() override; - - void set_color(level::level_enum color_level, const std::string& color); + using mutex_t = typename ConsoleMutex::mutex_t; + ansicolor_sink() + : target_file_(TargetStream::stream()) + , mutex_(ConsoleMutex::mutex()) + + { + should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); + colors_[level::trace] = white; + colors_[level::debug] = cyan; + colors_[level::info] = green; + colors_[level::warn] = yellow + bold; + colors_[level::err] = red + bold; + colors_[level::critical] = bold + on_red; + colors_[level::off] = reset; + } + + ~ansicolor_sink() override = default; + + ansicolor_sink(const ansicolor_sink &other) = delete; + ansicolor_sink &operator=(const ansicolor_sink &other) = delete; + + void set_color(level::level_enum color_level, const std::string &color) + { + std::lock_guard lock(mutex_); + colors_[color_level] = color; + } /// Formatting codes - const std::string reset = "\033[00m"; - const std::string bold = "\033[1m"; - const std::string dark = "\033[2m"; - const std::string underline = "\033[4m"; - const std::string blink = "\033[5m"; - const std::string reverse = "\033[7m"; - const std::string concealed = "\033[8m"; + const std::string reset = "\033[m"; + const std::string bold = "\033[1m"; + const std::string dark = "\033[2m"; + const std::string underline = "\033[4m"; + const std::string blink = "\033[5m"; + const std::string reverse = "\033[7m"; + const std::string concealed = "\033[8m"; + const std::string clear_line = "\033[K"; // Foreground colors - const std::string grey = "\033[30m"; - const std::string red = "\033[31m"; - const std::string green = "\033[32m"; - const std::string yellow = "\033[33m"; - const std::string blue = "\033[34m"; - const std::string magenta = "\033[35m"; - const std::string cyan = "\033[36m"; - const std::string white = "\033[37m"; + const std::string black = "\033[30m"; + const std::string red = "\033[31m"; + const std::string green = "\033[32m"; + const std::string yellow = "\033[33m"; + const std::string blue = "\033[34m"; + const std::string magenta = "\033[35m"; + const std::string cyan = "\033[36m"; + const std::string white = "\033[37m"; /// Background colors - const std::string on_grey = "\033[40m"; - const std::string on_red = "\033[41m"; - const std::string on_green = "\033[42m"; - const std::string on_yellow = "\033[43m"; - const std::string on_blue = "\033[44m"; + const std::string on_black = "\033[40m"; + const std::string on_red = "\033[41m"; + const std::string on_green = "\033[42m"; + const std::string on_yellow = "\033[43m"; + const std::string on_blue = "\033[44m"; const std::string on_magenta = "\033[45m"; - const std::string on_cyan = "\033[46m"; - const std::string on_white = "\033[47m"; - - -protected: - sink_ptr sink_; - std::map colors_; + const std::string on_cyan = "\033[46m"; + const std::string on_white = "\033[47m"; + + void log(const details::log_msg &msg) override + { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + std::lock_guard lock(mutex_); + + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (should_do_colors_ && msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + // in color range + print_ccode_(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + print_ccode_(reset); + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // no color + { + print_range_(formatted, 0, formatted.size()); + } + fflush(target_file_); + } + + void flush() override + { + std::lock_guard lock(mutex_); + fflush(target_file_); + } + + void set_pattern(const std::string &pattern) SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr sink_formatter) override + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + void print_ccode_(const std::string &color_code) + { + fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); + } + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); + } + + FILE *target_file_; + mutex_t &mutex_; + + bool should_do_colors_; + std::unordered_map colors_; }; -inline ansicolor_sink::ansicolor_sink(sink_ptr wrapped_sink) : sink_(wrapped_sink) -{ - colors_[level::trace] = cyan; - colors_[level::debug] = cyan; - colors_[level::info] = bold; - colors_[level::warn] = yellow + bold; - colors_[level::err] = red + bold; - colors_[level::critical] = bold + on_red; - colors_[level::off] = reset; -} - -inline void ansicolor_sink::log(const details::log_msg& msg) -{ - // Wrap the originally formatted message in color codes - const std::string& prefix = colors_[msg.level]; - const std::string& s = msg.formatted.str(); - const std::string& suffix = reset; - details::log_msg m; - m.level = msg.level; - m.logger_name = msg.logger_name; - m.time = msg.time; - m.thread_id = msg.thread_id; - m.formatted << prefix << s << suffix; - sink_->log(m); -} - -inline void ansicolor_sink::flush() -{ - sink_->flush(); -} +using ansicolor_stdout_sink_mt = ansicolor_sink; +using ansicolor_stdout_sink_st = ansicolor_sink; -inline void ansicolor_sink::set_color(level::level_enum color_level, const std::string& color) -{ - colors_[color_level] = color; -} - -inline ansicolor_sink::~ansicolor_sink() -{ - flush(); -} +using ansicolor_stderr_sink_mt = ansicolor_sink; +using ansicolor_stderr_sink_st = ansicolor_sink; } // namespace sinks -} // namespace spdlog +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/base_sink.h b/ifs/include/extern/spdlog/sinks/base_sink.h index c0e483b29..43863eae8 100644 --- a/ifs/include/extern/spdlog/sinks/base_sink.h +++ b/ifs/include/extern/spdlog/sinks/base_sink.h @@ -5,41 +5,59 @@ #pragma once // -// base sink templated over a mutex (either dummy or realy) -// concrete implementation should only overrid the _sink_it method. -// all locking is taken care of here so no locking needed by the implementers.. +// base sink templated over a mutex (either dummy or real) +// concrete implementation should override the sink_it_() and flush_() methods. +// locking is taken care of in this class - no locking needed by the +// implementers.. // -#include -#include -#include -#include +#include "spdlog/common.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" -#include - -namespace spdlog -{ -namespace sinks -{ -template -class base_sink:public sink +namespace spdlog { +namespace sinks { +template +class base_sink : public sink { public: - base_sink():_mutex() {} - virtual ~base_sink() = default; + base_sink() + : sink() + { + } + + base_sink(const base_sink &) = delete; + base_sink &operator=(const base_sink &) = delete; + + void log(const details::log_msg &msg) SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + sink_it_(msg); + } - base_sink(const base_sink&) = delete; - base_sink& operator=(const base_sink&) = delete; + void flush() SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + flush_(); + } + + void set_pattern(const std::string &pattern) SPDLOG_FINAL override + { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); + } - void log(const details::log_msg& msg) override + void set_formatter(std::unique_ptr sink_formatter) SPDLOG_FINAL override { - std::lock_guard lock(_mutex); - _sink_it(msg); + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); } protected: - virtual void _sink_it(const details::log_msg& msg) = 0; - Mutex _mutex; + virtual void sink_it_(const details::log_msg &msg) = 0; + virtual void flush_() = 0; + Mutex mutex_; }; -} -} +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/basic_file_sink.h b/ifs/include/extern/spdlog/sinks/basic_file_sink.h new file mode 100644 index 000000000..5db38e8af --- /dev/null +++ b/ifs/include/extern/spdlog/sinks/basic_file_sink.h @@ -0,0 +1,66 @@ +// +// Copyright(c) 2015-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Trivial file sink with single file as target + */ +template +class basic_file_sink SPDLOG_FINAL : public base_sink +{ +public: + explicit basic_file_sink(const filename_t &filename, bool truncate = false) + { + file_helper_.open(filename, truncate); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + details::file_helper file_helper_; +}; + +using basic_file_sink_mt = basic_file_sink; +using basic_file_sink_st = basic_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create(logger_name, filename, truncate); +} + +template +inline std::shared_ptr basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create(logger_name, filename, truncate); +} + +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/daily_file_sink.h b/ifs/include/extern/spdlog/sinks/daily_file_sink.h new file mode 100644 index 000000000..940d55d49 --- /dev/null +++ b/ifs/include/extern/spdlog/sinks/daily_file_sink.h @@ -0,0 +1,132 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct daily_filename_calculator +{ + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t &filename, const tm &now_tm) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + fmt::format_to( + w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); + return fmt::to_string(w); + } +}; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template +class daily_file_sink SPDLOG_FINAL : public base_sink +{ +public: + // create daily file sink which rotates on given time + daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false) + : base_filename_(std::move(base_filename)) + , rotation_h_(rotation_hour) + , rotation_m_(rotation_minute) + , truncate_(truncate) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + { + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + } + auto now = log_clock::now(); + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(now)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + if (msg.time >= rotation_tp_) + { + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(msg.time)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + tm now_tm(log_clock::time_point tp) + { + time_t tnow = log_clock::to_time_t(tp); + return spdlog::details::os::localtime(tnow); + } + + log_clock::time_point next_rotation_tp_() + { + auto now = log_clock::now(); + tm date = now_tm(now); + date.tm_hour = rotation_h_; + date.tm_min = rotation_m_; + date.tm_sec = 0; + auto rotation_time = log_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + { + return rotation_time; + } + return {rotation_time + std::chrono::hours(24)}; + } + + filename_t base_filename_; + int rotation_h_; + int rotation_m_; + log_clock::time_point rotation_tp_; + details::file_helper file_helper_; + bool truncate_; +}; + +using daily_file_sink_mt = daily_file_sink; +using daily_file_sink_st = daily_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr daily_logger_mt( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create(logger_name, filename, hour, minute, truncate); +} + +template +inline std::shared_ptr daily_logger_st( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create(logger_name, filename, hour, minute, truncate); +} +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/dist_sink.h b/ifs/include/extern/spdlog/sinks/dist_sink.h index 689170cd8..8d2f6f9e3 100644 --- a/ifs/include/extern/spdlog/sinks/dist_sink.h +++ b/ifs/include/extern/spdlog/sinks/dist_sink.h @@ -5,67 +5,64 @@ #pragma once -#include -#include -#include -#include +#include "base_sink.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/null_mutex.h" #include -#include #include +#include #include -// Distribution sink (mux). Stores a vector of sinks which get called when log is called +// Distribution sink (mux). Stores a vector of sinks which get called when log +// is called -namespace spdlog -{ -namespace sinks -{ -template -class dist_sink: public base_sink +namespace spdlog { +namespace sinks { + +template +class dist_sink : public base_sink { public: - explicit dist_sink() :_sinks() {} - dist_sink(const dist_sink&) = delete; - dist_sink& operator=(const dist_sink&) = delete; - virtual ~dist_sink() = default; + dist_sink() = default; + dist_sink(const dist_sink &) = delete; + dist_sink &operator=(const dist_sink &) = delete; -protected: - std::vector> _sinks; + void add_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::mutex_); + sinks_.push_back(sink); + } - void _sink_it(const details::log_msg& msg) override + void remove_sink(std::shared_ptr sink) { - for (auto &sink : _sinks) + std::lock_guard lock(base_sink::mutex_); + sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + for (auto &sink : sinks_) { - if( sink->should_log( msg.level)) + if (sink->should_log(msg.level)) { sink->log(msg); } } } -public: - void flush() override + void flush_() override { - std::lock_guard lock(base_sink::_mutex); - for (auto &sink : _sinks) + for (auto &sink : sinks_) sink->flush(); } - - void add_sink(std::shared_ptr sink) - { - std::lock_guard lock(base_sink::_mutex); - _sinks.push_back(sink); - } - - void remove_sink(std::shared_ptr sink) - { - std::lock_guard lock(base_sink::_mutex); - _sinks.erase(std::remove(_sinks.begin(), _sinks.end(), sink), _sinks.end()); - } + std::vector> sinks_; }; -typedef dist_sink dist_sink_mt; -typedef dist_sink dist_sink_st; -} -} +using dist_sink_mt = dist_sink; +using dist_sink_st = dist_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/file_sinks.h b/ifs/include/extern/spdlog/sinks/file_sinks.h deleted file mode 100644 index 163805baf..000000000 --- a/ifs/include/extern/spdlog/sinks/file_sinks.h +++ /dev/null @@ -1,239 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace spdlog -{ -namespace sinks -{ -/* - * Trivial file sink with single file as target - */ -template -class simple_file_sink : public base_sink < Mutex > -{ -public: - explicit simple_file_sink(const filename_t &filename, bool truncate = false):_force_flush(false) - { - _file_helper.open(filename, truncate); - } - void flush() override - { - _file_helper.flush(); - } - void set_force_flush(bool force_flush) - { - _force_flush = force_flush; - } - -protected: - void _sink_it(const details::log_msg& msg) override - { - _file_helper.write(msg); - if(_force_flush) - _file_helper.flush(); - } -private: - details::file_helper _file_helper; - bool _force_flush; -}; - -typedef simple_file_sink simple_file_sink_mt; -typedef simple_file_sink simple_file_sink_st; - -/* - * Rotating file sink based on size - */ -template -class rotating_file_sink : public base_sink < Mutex > -{ -public: - rotating_file_sink(const filename_t &base_filename, - std::size_t max_size, std::size_t max_files) : - _base_filename(base_filename), - _max_size(max_size), - _max_files(max_files), - _current_size(0), - _file_helper() - { - _file_helper.open(calc_filename(_base_filename, 0)); - _current_size = _file_helper.size(); //expensive. called only once - } - - void flush() override - { - _file_helper.flush(); - } - -protected: - void _sink_it(const details::log_msg& msg) override - { - _current_size += msg.formatted.size(); - if (_current_size > _max_size) - { - _rotate(); - _current_size = msg.formatted.size(); - } - _file_helper.write(msg); - } - -private: - static filename_t calc_filename(const filename_t& filename, std::size_t index) - { - std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - if (index) - w.write(SPDLOG_FILENAME_T("{}.{}"), filename, index); - else - w.write(SPDLOG_FILENAME_T("{}"), filename); - return w.str(); - } - - // Rotate files: - // log.txt -> log.txt.1 - // log.txt.1 -> log.txt.2 - // log.txt.2 -> log.txt.3 - // lo3.txt.3 -> delete - - void _rotate() - { - using details::os::filename_to_str; - _file_helper.close(); - for (auto i = _max_files; i > 0; --i) - { - filename_t src = calc_filename(_base_filename, i - 1); - filename_t target = calc_filename(_base_filename, i); - - if (details::file_helper::file_exists(target)) - { - if (details::os::remove(target) != 0) - { - throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); - } - } - if (details::file_helper::file_exists(src) && details::os::rename(src, target)) - { - throw spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); - } - } - _file_helper.reopen(true); - } - filename_t _base_filename; - std::size_t _max_size; - std::size_t _max_files; - std::size_t _current_size; - details::file_helper _file_helper; -}; - -typedef rotating_file_sink rotating_file_sink_mt; -typedef rotating_file_sinkrotating_file_sink_st; - -/* - * Default generator of daily log file names. - */ -struct default_daily_file_name_calculator -{ - // Create filename for the form basename.YYYY-MM-DD_hh-mm - static filename_t calc_filename(const filename_t& basename) - { - std::tm tm = spdlog::details::os::localtime(); - std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); - return w.str(); - } -}; - -/* - * Generator of daily log file names in format basename.YYYY-MM-DD - */ -struct dateonly_daily_file_name_calculator -{ - // Create filename for the form basename.YYYY-MM-DD - static filename_t calc_filename(const filename_t& basename) - { - std::tm tm = spdlog::details::os::localtime(); - std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - return w.str(); - } -}; - -/* - * Rotating file sink based on date. rotates at midnight - */ -template -class daily_file_sink :public base_sink < Mutex > -{ -public: - //create daily file sink which rotates on given time - daily_file_sink( - const filename_t& base_filename, - int rotation_hour, - int rotation_minute) : _base_filename(base_filename), - _rotation_h(rotation_hour), - _rotation_m(rotation_minute) - { - if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) - throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); - _rotation_tp = _next_rotation_tp(); - _file_helper.open(FileNameCalc::calc_filename(_base_filename)); - } - - void flush() override - { - _file_helper.flush(); - } - -protected: - void _sink_it(const details::log_msg& msg) override - { - if (std::chrono::system_clock::now() >= _rotation_tp) - { - _file_helper.open(FileNameCalc::calc_filename(_base_filename)); - _rotation_tp = _next_rotation_tp(); - } - _file_helper.write(msg); - } - -private: - std::chrono::system_clock::time_point _next_rotation_tp() - { - auto now = std::chrono::system_clock::now(); - time_t tnow = std::chrono::system_clock::to_time_t(now); - tm date = spdlog::details::os::localtime(tnow); - date.tm_hour = _rotation_h; - date.tm_min = _rotation_m; - date.tm_sec = 0; - auto rotation_time = std::chrono::system_clock::from_time_t(std::mktime(&date)); - if (rotation_time > now) - return rotation_time; - else - return std::chrono::system_clock::time_point(rotation_time + std::chrono::hours(24)); - } - - filename_t _base_filename; - int _rotation_h; - int _rotation_m; - std::chrono::system_clock::time_point _rotation_tp; - details::file_helper _file_helper; -}; - -typedef daily_file_sink daily_file_sink_mt; -typedef daily_file_sink daily_file_sink_st; -} -} diff --git a/ifs/include/extern/spdlog/sinks/msvc_sink.h b/ifs/include/extern/spdlog/sinks/msvc_sink.h index 16342ca26..a02849607 100644 --- a/ifs/include/extern/spdlog/sinks/msvc_sink.h +++ b/ifs/include/extern/spdlog/sinks/msvc_sink.h @@ -5,46 +5,46 @@ #pragma once -#if defined(_MSC_VER) +#if defined(_WIN32) -#include -#include +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" -#include +#include #include #include -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { /* -* MSVC sink (logging using OutputDebugStringA) -*/ -template -class msvc_sink : public base_sink < Mutex > + * MSVC sink (logging using OutputDebugStringA) + */ +template +class msvc_sink : public base_sink { public: - explicit msvc_sink() - { - } - - void flush() override - { - } + explicit msvc_sink() {} protected: - void _sink_it(const details::log_msg& msg) override + void sink_it_(const details::log_msg &msg) override { - OutputDebugStringA(msg.formatted.c_str()); + + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + OutputDebugStringA(fmt::to_string(formatted).c_str()); } + + void flush_() override {} }; -typedef msvc_sink msvc_sink_mt; -typedef msvc_sink msvc_sink_st; +using msvc_sink_mt = msvc_sink; +using msvc_sink_st = msvc_sink; + +using windebug_sink_mt = msvc_sink_mt; +using windebug_sink_st = msvc_sink_st; -} -} +} // namespace sinks +} // namespace spdlog #endif diff --git a/ifs/include/extern/spdlog/sinks/null_sink.h b/ifs/include/extern/spdlog/sinks/null_sink.h index 270d1a95c..73b8c2b7a 100644 --- a/ifs/include/extern/spdlog/sinks/null_sink.h +++ b/ifs/include/extern/spdlog/sinks/null_sink.h @@ -5,30 +5,24 @@ #pragma once -#include -#include +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" #include -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { -template -class null_sink : public base_sink < Mutex > +template +class null_sink : public base_sink { protected: - void _sink_it(const details::log_msg&) override - {} - - void flush() override - {} - + void sink_it_(const details::log_msg &) override {} + void flush_() override {} }; -typedef null_sink null_sink_st; -typedef null_sink null_sink_mt; -} -} +using null_sink_mt = null_sink; +using null_sink_st = null_sink; +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/ostream_sink.h b/ifs/include/extern/spdlog/sinks/ostream_sink.h index f5b74cbbf..5f31993c6 100644 --- a/ifs/include/extern/spdlog/sinks/ostream_sink.h +++ b/ifs/include/extern/spdlog/sinks/ostream_sink.h @@ -5,43 +5,47 @@ #pragma once -#include -#include +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" -#include #include +#include -namespace spdlog -{ -namespace sinks -{ -template -class ostream_sink: public base_sink +namespace spdlog { +namespace sinks { +template +class ostream_sink SPDLOG_FINAL : public base_sink { public: - explicit ostream_sink(std::ostream& os, bool force_flush=false) :_ostream(os), _force_flush(force_flush) {} - ostream_sink(const ostream_sink&) = delete; - ostream_sink& operator=(const ostream_sink&) = delete; - virtual ~ostream_sink() = default; + explicit ostream_sink(std::ostream &os, bool force_flush = false) + : ostream_(os) + , force_flush_(force_flush) + { + } + ostream_sink(const ostream_sink &) = delete; + ostream_sink &operator=(const ostream_sink &) = delete; protected: - void _sink_it(const details::log_msg& msg) override + void sink_it_(const details::log_msg &msg) override { - _ostream.write(msg.formatted.data(), msg.formatted.size()); - if (_force_flush) - _ostream.flush(); + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + ostream_.write(formatted.data(), formatted.size()); + if (force_flush_) + ostream_.flush(); } - void flush() override + void flush_() override { - _ostream.flush(); + ostream_.flush(); } - std::ostream& _ostream; - bool _force_flush; + std::ostream &ostream_; + bool force_flush_; }; -typedef ostream_sink ostream_sink_mt; -typedef ostream_sink ostream_sink_st; -} -} +using ostream_sink_mt = ostream_sink; +using ostream_sink_st = ostream_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/rotating_file_sink.h b/ifs/include/extern/spdlog/sinks/rotating_file_sink.h new file mode 100644 index 000000000..7ed2385a5 --- /dev/null +++ b/ifs/include/extern/spdlog/sinks/rotating_file_sink.h @@ -0,0 +1,144 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size +// +template +class rotating_file_sink SPDLOG_FINAL : public base_sink +{ +public: + rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) + : base_filename_(std::move(base_filename)) + , max_size_(max_size) + , max_files_(max_files) + { + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t &filename, std::size_t index) + { + typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + if (index != 0u) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); + } + return fmt::to_string(w); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + current_size_ += formatted.size(); + if (current_size_ > max_size_) + { + rotate_(); + current_size_ = formatted.size(); + } + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_() + { + using details::os::filename_to_str; + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) + { + filename_t src = calc_filename(base_filename_, i - 1); + filename_t target = calc_filename(base_filename_, i); + + if (details::file_helper::file_exists(target)) + { + if (details::os::remove(target) != 0) + { + throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); + } + } + if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0) + { + // if failed try again after small delay. + // this is a workaround to a windows issue, where very high rotation + // rates sometimes fail (because of antivirus?). + details::os::sleep_for_millis(20); + details::os::remove(target); + if (details::os::rename(src, target) != 0) + { + throw spdlog_ex( + "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + } + file_helper_.reopen(true); + } + + filename_t base_filename_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; +}; + +using rotating_file_sink_mt = rotating_file_sink; +using rotating_file_sink_st = rotating_file_sink; + +} // namespace sinks + +// +// factory functions +// + +template +inline std::shared_ptr rotating_logger_mt( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} + +template +inline std::shared_ptr rotating_logger_st( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/sink.h b/ifs/include/extern/spdlog/sinks/sink.h index 316696161..cb8aecb4b 100644 --- a/ifs/include/extern/spdlog/sinks/sink.h +++ b/ifs/include/extern/spdlog/sinks/sink.h @@ -3,51 +3,55 @@ // Distributed under the MIT License (http://opensource.org/licenses/MIT) // - #pragma once -#include +#include "spdlog/details/log_msg.h" +#include "spdlog/details/pattern_formatter.h" +#include "spdlog/formatter.h" -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { class sink { public: sink() + : level_(level::trace) + , formatter_(new pattern_formatter("%+")) { - _level = level::trace; } - virtual ~sink() {} - virtual void log(const details::log_msg& msg) = 0; - virtual void flush() = 0; - - bool should_log(level::level_enum msg_level) const; - void set_level(level::level_enum log_level); - level::level_enum level() const; + explicit sink(std::unique_ptr formatter) + : level_(level::trace) + , formatter_(std::move(formatter)){}; -private: - level_t _level; + virtual ~sink() = default; + virtual void log(const details::log_msg &msg) = 0; + virtual void flush() = 0; + virtual void set_pattern(const std::string &pattern) = 0; + virtual void set_formatter(std::unique_ptr sink_formatter) = 0; -}; + bool should_log(level::level_enum msg_level) const + { + return msg_level >= level_.load(std::memory_order_relaxed); + } -inline bool sink::should_log(level::level_enum msg_level) const -{ - return msg_level >= _level.load(std::memory_order_relaxed); -} + void set_level(level::level_enum log_level) + { + level_.store(log_level); + } -inline void sink::set_level(level::level_enum log_level) -{ - _level.store(log_level); -} + level::level_enum level() const + { + return static_cast(level_.load(std::memory_order_relaxed)); + } -inline level::level_enum sink::level() const -{ - return static_cast(_level.load(std::memory_order_relaxed)); -} +protected: + // sink log level - default is all + level_t level_; -} -} + // sink formatter - default is full format + std::unique_ptr formatter_; +}; +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/stdout_color_sinks.h b/ifs/include/extern/spdlog/sinks/stdout_color_sinks.h new file mode 100644 index 000000000..761e32a4b --- /dev/null +++ b/ifs/include/extern/spdlog/sinks/stdout_color_sinks.h @@ -0,0 +1,53 @@ +// +// Copyright(c) 2018 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/spdlog.h" +#ifdef _WIN32 +#include "spdlog/sinks/wincolor_sink.h" +#else +#include "spdlog/sinks/ansicolor_sink.h" +#endif + +namespace spdlog { +namespace sinks { +#ifdef _WIN32 +using stdout_color_sink_mt = wincolor_stdout_sink_mt; +using stdout_color_sink_st = wincolor_stdout_sink_st; +using stderr_color_sink_mt = wincolor_stderr_sink_mt; +using stderr_color_sink_st = wincolor_stderr_sink_st; +#else +using stdout_color_sink_mt = ansicolor_stdout_sink_mt; +using stdout_color_sink_st = ansicolor_stdout_sink_st; +using stderr_color_sink_mt = ansicolor_stderr_sink_mt; +using stderr_color_sink_st = ansicolor_stderr_sink_st; +#endif +} // namespace sinks + +template +inline std::shared_ptr stdout_color_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stdout_color_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_color_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_color_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/stdout_sinks.h b/ifs/include/extern/spdlog/sinks/stdout_sinks.h index d01090782..e12739c14 100644 --- a/ifs/include/extern/spdlog/sinks/stdout_sinks.h +++ b/ifs/include/extern/spdlog/sinks/stdout_sinks.h @@ -5,73 +5,96 @@ #pragma once -#include -#include +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/spdlog.h" #include #include #include +#include -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { + +namespace sinks { -template -class stdout_sink: public base_sink +template +class stdout_sink SPDLOG_FINAL : public sink { - using MyType = stdout_sink; public: + using mutex_t = typename ConsoleMutex::mutex_t; stdout_sink() - {} - static std::shared_ptr instance() + : mutex_(ConsoleMutex::mutex()) + , file_(TargetStream::stream()) { - static std::shared_ptr instance = std::make_shared(); - return instance; } + ~stdout_sink() override = default; - void _sink_it(const details::log_msg& msg) override + stdout_sink(const stdout_sink &other) = delete; + stdout_sink &operator=(const stdout_sink &other) = delete; + + void log(const details::log_msg &msg) override { - fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stdout); - flush(); + std::lock_guard lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + fwrite(formatted.data(), sizeof(char), formatted.size(), file_); + fflush(TargetStream::stream()); } void flush() override { - fflush(stdout); + std::lock_guard lock(mutex_); + fflush(file_); } -}; - -typedef stdout_sink stdout_sink_st; -typedef stdout_sink stdout_sink_mt; - -template -class stderr_sink: public base_sink -{ - using MyType = stderr_sink; -public: - stderr_sink() - {} - static std::shared_ptr instance() + void set_pattern(const std::string &pattern) override { - static std::shared_ptr instance = std::make_shared(); - return instance; + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); } - void _sink_it(const details::log_msg& msg) override + void set_formatter(std::unique_ptr sink_formatter) override { - fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stderr); - flush(); + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); } - void flush() override - { - fflush(stderr); - } +private: + mutex_t &mutex_; + FILE *file_; }; -typedef stderr_sink stderr_sink_mt; -typedef stderr_sink stderr_sink_st; +using stdout_sink_mt = stdout_sink; +using stdout_sink_st = stdout_sink; + +using stderr_sink_mt = stdout_sink; +using stderr_sink_st = stdout_sink; + +} // namespace sinks + +// factory methods +template +inline std::shared_ptr stdout_logger_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stdout_logger_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); +} + +template +inline std::shared_ptr stderr_logger_mt(const std::string &logger_name) +{ + return Factory::template create(logger_name); } + +template +inline std::shared_ptr stderr_logger_st(const std::string &logger_name) +{ + return Factory::template create(logger_name); } +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/syslog_sink.h b/ifs/include/extern/spdlog/sinks/syslog_sink.h index c2ce07d85..151e7a119 100644 --- a/ifs/include/extern/spdlog/sinks/syslog_sink.h +++ b/ifs/include/extern/spdlog/sinks/syslog_sink.h @@ -5,77 +5,87 @@ #pragma once -#include - -#ifdef SPDLOG_ENABLE_SYSLOG - -#include -#include +#include "spdlog/sinks/base_sink.h" +#include "spdlog/spdlog.h" #include #include #include - -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { /** * Sink that write to syslog using the `syscall()` library call. * * Locking is not needed, as `syslog()` itself is thread-safe. */ -class syslog_sink : public sink +template +class syslog_sink : public base_sink { public: // - syslog_sink(const std::string& ident = "", int syslog_option=0, int syslog_facility=LOG_USER): - _ident(ident) + explicit syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER) + : ident_(std::move(ident)) { - _priorities[static_cast(level::trace)] = LOG_DEBUG; - _priorities[static_cast(level::debug)] = LOG_DEBUG; - _priorities[static_cast(level::info)] = LOG_INFO; - _priorities[static_cast(level::warn)] = LOG_WARNING; - _priorities[static_cast(level::err)] = LOG_ERR; - _priorities[static_cast(level::critical)] = LOG_CRIT; - _priorities[static_cast(level::off)] = LOG_INFO; + priorities_[static_cast(level::trace)] = LOG_DEBUG; + priorities_[static_cast(level::debug)] = LOG_DEBUG; + priorities_[static_cast(level::info)] = LOG_INFO; + priorities_[static_cast(level::warn)] = LOG_WARNING; + priorities_[static_cast(level::err)] = LOG_ERR; + priorities_[static_cast(level::critical)] = LOG_CRIT; + priorities_[static_cast(level::off)] = LOG_INFO; - //set ident to be program name if empty - ::openlog(_ident.empty()? nullptr:_ident.c_str(), syslog_option, syslog_facility); + // set ident to be program name if empty + ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); } - ~syslog_sink() + + ~syslog_sink() override { ::closelog(); } - syslog_sink(const syslog_sink&) = delete; - syslog_sink& operator=(const syslog_sink&) = delete; + syslog_sink(const syslog_sink &) = delete; + syslog_sink &operator=(const syslog_sink &) = delete; - void log(const details::log_msg &msg) override - { - ::syslog(syslog_prio_from_level(msg), "%s", msg.raw.str().c_str()); - } - - void flush() override +protected: + void sink_it_(const details::log_msg &msg) override { + ::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.raw).c_str()); } + void flush_() override {} private: - std::array _priorities; - //must store the ident because the man says openlog might use the pointer as is and not a string copy - const std::string _ident; + std::array priorities_; + // must store the ident because the man says openlog might use the pointer as + // is and not a string copy + const std::string ident_; // // Simply maps spdlog's log level to syslog priority level. // int syslog_prio_from_level(const details::log_msg &msg) const { - return _priorities[static_cast(msg.level)]; + return priorities_[static_cast(msg.level)]; } }; -} + +using syslog_sink_mt = syslog_sink; +using syslog_sink_st = syslog_sink; +} // namespace sinks + +// Create and register a syslog logger +template +inline std::shared_ptr syslog_logger_mt( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create(logger_name, syslog_ident, syslog_option, syslog_facility); } -#endif +template +inline std::shared_ptr syslog_logger_st( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create(logger_name, syslog_ident, syslog_option, syslog_facility); +} +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/sinks/wincolor_sink.h b/ifs/include/extern/spdlog/sinks/wincolor_sink.h index 197e3f65c..8c63e7fcc 100644 --- a/ifs/include/extern/spdlog/sinks/wincolor_sink.h +++ b/ifs/include/extern/spdlog/sinks/wincolor_sink.h @@ -5,112 +5,135 @@ #pragma once -#include -#include -#include +#include "spdlog/common.h" +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/sink.h" +#include #include #include -#include +#include #include -namespace spdlog -{ -namespace sinks -{ +namespace spdlog { +namespace sinks { /* - * Windows color console sink. Uses WriteConsoleA to write to the console with colors + * Windows color console sink. Uses WriteConsoleA to write to the console with + * colors */ -template -class wincolor_sink: public base_sink +template +class wincolor_sink : public sink { public: const WORD BOLD = FOREGROUND_INTENSITY; const WORD RED = FOREGROUND_RED; + const WORD GREEN = FOREGROUND_GREEN; const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; - wincolor_sink(HANDLE std_handle): out_handle_(std_handle) + wincolor_sink() + : out_handle_(OutHandle::handle()) + , mutex_(ConsoleMutex::mutex()) { - colors_[level::trace] = CYAN; + colors_[level::trace] = WHITE; colors_[level::debug] = CYAN; - colors_[level::info] = WHITE | BOLD; + colors_[level::info] = GREEN; colors_[level::warn] = YELLOW | BOLD; - colors_[level::err] = RED | BOLD; // red bold + colors_[level::err] = RED | BOLD; // red bold colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background colors_[level::off] = 0; } - virtual ~wincolor_sink() + ~wincolor_sink() override { - flush(); + this->flush(); } - wincolor_sink(const wincolor_sink& other) = delete; - wincolor_sink& operator=(const wincolor_sink& other) = delete; + wincolor_sink(const wincolor_sink &other) = delete; + wincolor_sink &operator=(const wincolor_sink &other) = delete; + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard lock(mutex_); + colors_[level] = color; + } - virtual void _sink_it(const details::log_msg& msg) override + void log(const details::log_msg &msg) SPDLOG_FINAL override { - auto color = colors_[msg.level]; - auto orig_attribs = set_console_attribs(color); - WriteConsoleA(out_handle_, msg.formatted.data(), static_cast(msg.formatted.size()), nullptr, nullptr); - SetConsoleTextAttribute(out_handle_, orig_attribs); //reset to orig colors + std::lock_guard lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + + // in color range + auto orig_attribs = set_console_attribs(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + ::SetConsoleTextAttribute(out_handle_, + orig_attribs); // reset to orig colors + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // print without colors if color range is invalid + { + print_range_(formatted, 0, formatted.size()); + } } - virtual void flush() override + void flush() SPDLOG_FINAL override { // windows console always flushed? } - // change the color for the given level - void set_color(level::level_enum level, WORD color) + void set_pattern(const std::string &pattern) override SPDLOG_FINAL { - std::lock_guard lock(base_sink::_mutex); - colors_[level] = color; + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); } -private: - HANDLE out_handle_; - std::map colors_; + void set_formatter(std::unique_ptr sink_formatter) override SPDLOG_FINAL + { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); + } +private: + using mutex_t = typename ConsoleMutex::mutex_t; // set color and return the orig console attributes (for resetting later) WORD set_console_attribs(WORD attribs) { CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; - GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); - SetConsoleTextAttribute(out_handle_, attribs); - return orig_buffer_info.wAttributes; //return orig attribs + ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + WORD back_color = orig_buffer_info.wAttributes; + // retrieve the current background color + back_color &= static_cast(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); + // keep the background color unchanged + ::SetConsoleTextAttribute(out_handle_, attribs | back_color); + return orig_buffer_info.wAttributes; // return orig attribs } -}; -// -// windows color console to stdout -// -template -class wincolor_stdout_sink: public wincolor_sink -{ -public: - wincolor_stdout_sink() : wincolor_sink(GetStdHandle(STD_OUTPUT_HANDLE)) - {} -}; - -typedef wincolor_stdout_sink wincolor_stdout_sink_mt; -typedef wincolor_stdout_sink wincolor_stdout_sink_st; + // print a range of formatted message to console + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + auto size = static_cast(end - start); + ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); + } -// -// windows color console to stderr -// -template -class wincolor_stderr_sink: public wincolor_sink -{ -public: - wincolor_stderr_sink() : wincolor_sink(GetStdHandle(STD_ERROR_HANDLE)) - {} + HANDLE out_handle_; + mutex_t &mutex_; + std::unordered_map colors_; }; -typedef wincolor_stderr_sink wincolor_stderr_sink_mt; -typedef wincolor_stderr_sink wincolor_stderr_sink_st; +using wincolor_stdout_sink_mt = wincolor_sink; +using wincolor_stdout_sink_st = wincolor_sink; + +using wincolor_stderr_sink_mt = wincolor_sink; +using wincolor_stderr_sink_st = wincolor_sink; -} -} +} // namespace sinks +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/spdlog.h b/ifs/include/extern/spdlog/spdlog.h index bbf498120..40640ab97 100644 --- a/ifs/include/extern/spdlog/spdlog.h +++ b/ifs/include/extern/spdlog/spdlog.h @@ -1,5 +1,5 @@ // -// Copyright(c) 2015 Gabi Melman. +// Copyright(c) 2015-2018 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // // spdlog main header file. @@ -7,147 +7,128 @@ #pragma once -#define SPDLOG_VERSION "0.12.0" +#include "spdlog/common.h" +#include "spdlog/details/registry.h" +#include "spdlog/logger.h" +#include "spdlog/version.h" -#include -#include -#include - -#include -#include #include +#include +#include #include -namespace spdlog +namespace spdlog { + +// Default logger factory- creates synchronous loggers +struct synchronous_factory { + template + + static std::shared_ptr create(std::string logger_name, SinkArgs &&... args) + { + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(std::move(logger_name), std::move(sink)); + details::registry::instance().register_and_init(new_logger); + return new_logger; + } +}; + +using default_factory = synchronous_factory; + +// Create and register a logger with a templated sink type +// The logger's level, formatter and flush level will be set according the +// global settings. +// Example: +// spdlog::create("logger_name", "dailylog_filename", 11, 59); +template +inline std::shared_ptr create(std::string logger_name, SinkArgs &&... sink_args) +{ + return default_factory::create(std::move(logger_name), std::forward(sink_args)...); +} -// -// Return an existing logger or nullptr if a logger with such name doesn't exist. +// Return an existing logger or nullptr if a logger with such name doesn't +// exist. // example: spdlog::get("my_logger")->info("hello {}", "world"); -// -std::shared_ptr get(const std::string& name); +inline std::shared_ptr get(const std::string &name) +{ + return details::registry::instance().get(name); +} +// Set global formatter. Each sink in each logger will get a clone of this object +inline void set_formatter(std::unique_ptr formatter) +{ + details::registry::instance().set_formatter(std::move(formatter)); +} -// -// Set global formatting +// Set global format string. // example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); -// -void set_pattern(const std::string& format_string); -void set_formatter(formatter_ptr f); - -// -// Set global logging level for -// -void set_level(level::level_enum log_level); - -// -// Set global error handler -// -void set_error_handler(log_err_handler); - -// -// Turn on async mode (off by default) and set the queue size for each async_logger. -// effective only for loggers created after this call. -// queue_size: size of queue (must be power of 2): -// Each logger will pre-allocate a dedicated queue with queue_size entries upon construction. -// -// async_overflow_policy (optional, block_retry by default): -// async_overflow_policy::block_retry - if queue is full, block until queue has room for the new log entry. -// async_overflow_policy::discard_log_msg - never block and discard any new messages when queue overflows. -// -// worker_warmup_cb (optional): -// callback function that will be called in worker thread upon start (can be used to init stuff like thread affinity) -// -// worker_teardown_cb (optional): -// callback function that will be called in worker thread upon exit -// -void set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); - -// Turn off async mode -void set_sync_mode(); - - -// -// Create and register multi/single threaded basic file logger. -// Basic logger simply writes to given file without any limitatons or rotations. -// -std::shared_ptr basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate = false); -std::shared_ptr basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate = false); - -// -// Create and register multi/single threaded rotating file logger -// -std::shared_ptr rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); -std::shared_ptr rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); - -// -// Create file logger which creates new file on the given time (default in midnight): -// -std::shared_ptr daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); -std::shared_ptr daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); - -// -// Create and register stdout/stderr loggers -// -std::shared_ptr stdout_logger_mt(const std::string& logger_name); -std::shared_ptr stdout_logger_st(const std::string& logger_name); -std::shared_ptr stderr_logger_mt(const std::string& logger_name); -std::shared_ptr stderr_logger_st(const std::string& logger_name); -// -// Create and register colored stdout/stderr loggers -// -std::shared_ptr stdout_color_mt(const std::string& logger_name); -std::shared_ptr stdout_color_st(const std::string& logger_name); -std::shared_ptr stderr_color_mt(const std::string& logger_name); -std::shared_ptr stderr_color_st(const std::string& logger_name); - - -// -// Create and register a syslog logger -// -#ifdef SPDLOG_ENABLE_SYSLOG -std::shared_ptr syslog_logger(const std::string& logger_name, const std::string& ident = "", int syslog_option = 0); -#endif - -#if defined(__ANDROID__) -std::shared_ptr android_logger(const std::string& logger_name, const std::string& tag = "spdlog"); -#endif - -// Create and register a logger a single sink -std::shared_ptr create(const std::string& logger_name, const sink_ptr& sink); +inline void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local) +{ + set_formatter(std::unique_ptr(new pattern_formatter(std::move(pattern), time_type))); +} -// Create and register a logger with multiple sinks -std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks); -template -std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end); +// Set global logging level +inline void set_level(level::level_enum log_level) +{ + details::registry::instance().set_level(log_level); +} +// Set global flush level +inline void flush_on(level::level_enum log_level) +{ + details::registry::instance().flush_on(log_level); +} -// Create and register a logger with templated sink type -// Example: -// spdlog::create("mylog", "dailylog_filename"); -template -std::shared_ptr create(const std::string& logger_name, Args...); +// Start/Restart a periodic flusher thread +// Warning: Use only if all your loggers are thread safe! +inline void flush_every(std::chrono::seconds interval) +{ + details::registry::instance().flush_every(interval); +} +// Set global error handler +inline void set_error_handler(log_err_handler handler) +{ + details::registry::instance().set_error_handler(std::move(handler)); +} // Register the given logger with the given name -void register_logger(std::shared_ptr logger); +inline void register_logger(std::shared_ptr logger) +{ + details::registry::instance().register_logger(std::move(logger)); +} // Apply a user defined function on all registered loggers // Example: // spdlog::apply_all([&](std::shared_ptr l) {l->flush();}); -void apply_all(std::function)> fun); +inline void apply_all(const std::function)> &fun) +{ + details::registry::instance().apply_all(fun); +} // Drop the reference to the given logger -void drop(const std::string &name); +inline void drop(const std::string &name) +{ + details::registry::instance().drop(name); +} // Drop all references from the registry -void drop_all(); +inline void drop_all() +{ + details::registry::instance().drop_all(); +} +// stop any running threads started by spdlog and clean registry loggers +inline void shutdown() +{ + details::registry::instance().shutdown(); +} /////////////////////////////////////////////////////////////////////////////// // -// Trace & Debug can be switched on/off at compile time for zero cost debug statements. -// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in teakme.h to enable. +// Trace & Debug can be switched on/off at compile time for zero cost debug +// statements. +// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. // SPDLOG_TRACE(..) will also print current file and line. // // Example: @@ -160,19 +141,21 @@ void drop_all(); #ifdef SPDLOG_TRACE_ON #define SPDLOG_STR_H(x) #x #define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) -#define SPDLOG_TRACE(logger, ...) logger->trace("[" __FILE__ " line #" SPDLOG_STR_HELPER(__LINE__) "] " __VA_ARGS__) +#ifdef _MSC_VER +#define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ "(" SPDLOG_STR_HELPER(__LINE__) ") ] " __VA_ARGS__) +#else +#define SPDLOG_TRACE(logger, ...) \ + logger->trace("[ " __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) " ]" \ + " " __VA_ARGS__) +#endif #else -#define SPDLOG_TRACE(logger, ...) +#define SPDLOG_TRACE(logger, ...) (void)0 #endif #ifdef SPDLOG_DEBUG_ON #define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) #else -#define SPDLOG_DEBUG(logger, ...) +#define SPDLOG_DEBUG(logger, ...) (void)0 #endif - -} - - -#include +} // namespace spdlog diff --git a/ifs/include/extern/spdlog/tweakme.h b/ifs/include/extern/spdlog/tweakme.h index 86f66b9e0..53256450c 100644 --- a/ifs/include/extern/spdlog/tweakme.h +++ b/ifs/include/extern/spdlog/tweakme.h @@ -7,44 +7,56 @@ /////////////////////////////////////////////////////////////////////////////// // -// Edit this file to squeeze more performance, and to customize supported features +// Edit this file to squeeze more performance, and to customize supported +// features // /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// // Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. -// This clock is less accurate - can be off by dozens of millis - depending on the kernel HZ. +// This clock is less accurate - can be off by dozens of millis - depending on +// the kernel HZ. // Uncomment to use it instead of the regular clock. // // #define SPDLOG_CLOCK_COARSE /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// -// Uncomment if date/time logging is not needed and never appear in the log pattern. -// This will prevent spdlog from quering the clock on each log call. +// Uncomment if date/time logging is not needed and never appear in the log +// pattern. +// This will prevent spdlog from querying the clock on each log call. // -// WARNING: If the log pattern contains any date/time while this flag is on, the result is undefined. -// You must set new pattern(spdlog::set_pattern(..") without any date/time in it +// WARNING: If the log pattern contains any date/time while this flag is on, the +// result is undefined. +// You must set new pattern(spdlog::set_pattern(..") without any +// date/time in it // // #define SPDLOG_NO_DATETIME /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// // Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). -// This will prevent spdlog from quering the thread id on each log call. +// This will prevent spdlog from querying the thread id on each log call. // -// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is on, the result is undefined. +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is +// on, the result is undefined. // // #define SPDLOG_NO_THREAD_ID /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from caching thread ids in thread local storage. +// By default spdlog saves thread ids in tls to gain a few micros for each call. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined +// thread ids in the children logs. +// +// #define SPDLOG_DISABLE_TID_CACHING +/////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Uncomment if logger name logging is not needed. -// This will prevent spdlog from copying the logger name on each log call. +// This will prevent spdlog from copying the logger name on each log call. // // #define SPDLOG_NO_NAME /////////////////////////////////////////////////////////////////////////////// @@ -56,19 +68,10 @@ // #define SPDLOG_TRACE_ON /////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to avoid locking in the registry operations (spdlog::get(), spdlog::drop() spdlog::register()). -// Use only if your code never modifes concurrently the registry. -// Note that upon creating a logger the registry is modified by spdlog.. -// -// #define SPDLOG_NO_REGISTRY_MUTEX -/////////////////////////////////////////////////////////////////////////////// - - /////////////////////////////////////////////////////////////////////////////// // Uncomment to avoid spdlog's usage of atomic log levels -// Use only if your code never modifies a logger's log levels concurrently by different threads. +// Use only if your code never modifies a logger's log levels concurrently by +// different threads. // // #define SPDLOG_NO_ATOMIC_LEVELS /////////////////////////////////////////////////////////////////////////////// @@ -85,24 +88,45 @@ // #define SPDLOG_EOL ";-)\n" /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// // Uncomment to use your own copy of the fmt library instead of spdlog's copy. -// In this case spdlog will try to include so set your -I flag accordingly. +// In this case spdlog will try to include so set your -I flag +// accordingly. // // #define SPDLOG_FMT_EXTERNAL /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// -// Uncomment to enable syslog (disabled by default) +// Uncomment to enable wchar_t support (convert to utf8) // -// #define SPDLOG_ENABLE_SYSLOG +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT /////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////// // Uncomment to prevent child processes from inheriting log file descriptors // // #define SPDLOG_PREVENT_CHILD_FD /////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if your compiler doesn't support the "final" keyword. +// The final keyword allows more optimizations in release +// mode with recent compilers. See GCC's documentation for -Wsuggest-final-types +// for instance. +// +// #define SPDLOG_NO_FINAL +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable message counting feature. +// Use the %i in the logger pattern to display log message sequence id. +// +// #define SPDLOG_ENABLE_MESSAGE_COUNTER +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MT TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", +// "MY ERROR", "MY CRITICAL", "OFF" } +/////////////////////////////////////////////////////////////////////////////// diff --git a/ifs/include/extern/spdlog/version.h b/ifs/include/extern/spdlog/version.h new file mode 100644 index 000000000..f14078ec0 --- /dev/null +++ b/ifs/include/extern/spdlog/version.h @@ -0,0 +1,12 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#define SPDLOG_VER_MAJOR 1 +#define SPDLOG_VER_MINOR 1 +#define SPDLOG_VER_PATCH 0 + +#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) diff --git a/ifs/include/global/log_util.hpp b/ifs/include/global/log_util.hpp index d39c36299..5eeb214d0 100644 --- a/ifs/include/global/log_util.hpp +++ b/ifs/include/global/log_util.hpp @@ -1,8 +1,8 @@ #ifndef IFS_LOG_UITIL_HPP #define IFS_LOG_UITIL_HPP +#include -#include "extern/spdlog/spdlog.h" spdlog::level::level_enum get_spdlog_level(const std::string& level_str); spdlog::level::level_enum get_spdlog_level(unsigned int level); diff --git a/ifs/include/preload/preload_context.hpp b/ifs/include/preload/preload_context.hpp index 49dc2ecaf..07c74a1de 100644 --- a/ifs/include/preload/preload_context.hpp +++ b/ifs/include/preload/preload_context.hpp @@ -1,7 +1,8 @@ #ifndef IFS_PRELOAD_CTX_HPP #define IFS_PRELOAD_CTX_HPP -#include +#include +#include #include #include #include diff --git a/ifs/src/daemon/CMakeLists.txt b/ifs/src/daemon/CMakeLists.txt index 47070a7bc..ad2ee50ac 100644 --- a/ifs/src/daemon/CMakeLists.txt +++ b/ifs/src/daemon/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries(adafs_daemon storage distributor log_util + spdlog # margo libs ${ABT_LIBRARIES} mercury diff --git a/ifs/src/daemon/backend/data/CMakeLists.txt b/ifs/src/daemon/backend/data/CMakeLists.txt index 82a1ba2d5..1b333a825 100644 --- a/ifs/src/daemon/backend/data/CMakeLists.txt +++ b/ifs/src/daemon/backend/data/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources(storage target_link_libraries(storage PRIVATE + spdlog Boost::filesystem ${ABT_LIBRARIES} ) diff --git a/ifs/src/daemon/backend/data/chunk_storage.cpp b/ifs/src/daemon/backend/data/chunk_storage.cpp index ea0e098a0..ff92e0fb4 100644 --- a/ifs/src/daemon/backend/data/chunk_storage.cpp +++ b/ifs/src/daemon/backend/data/chunk_storage.cpp @@ -1,8 +1,8 @@ #include #include +#include #include -#include #include namespace bfs = boost::filesystem; diff --git a/ifs/src/daemon/backend/metadata/CMakeLists.txt b/ifs/src/daemon/backend/metadata/CMakeLists.txt index 471c5c878..ad39820b0 100644 --- a/ifs/src/daemon/backend/metadata/CMakeLists.txt +++ b/ifs/src/daemon/backend/metadata/CMakeLists.txt @@ -13,4 +13,5 @@ target_sources(metadata_db target_link_libraries(metadata_db RocksDB + spdlog ) diff --git a/ifs/src/daemon/backend/metadata/db.cpp b/ifs/src/daemon/backend/metadata/db.cpp index aa036d50c..5681b50d0 100644 --- a/ifs/src/daemon/backend/metadata/db.cpp +++ b/ifs/src/daemon/backend/metadata/db.cpp @@ -3,7 +3,6 @@ #include #include -#include MetadataDB::MetadataDB(const std::string& path): path(path) { // Optimize RocksDB. This is the easiest way to get RocksDB to perform well diff --git a/ifs/src/daemon/classes/fs_data.cpp b/ifs/src/daemon/classes/fs_data.cpp index 411292ae4..45c3ce0bf 100644 --- a/ifs/src/daemon/classes/fs_data.cpp +++ b/ifs/src/daemon/classes/fs_data.cpp @@ -1,3 +1,4 @@ +#include #include #include diff --git a/ifs/src/global/CMakeLists.txt b/ifs/src/global/CMakeLists.txt index 819d26a6b..9d5afb1e9 100644 --- a/ifs/src/global/CMakeLists.txt +++ b/ifs/src/global/CMakeLists.txt @@ -12,7 +12,9 @@ set_property(TARGET log_util PROPERTY POSITION_INDEPENDENT_CODE ON) target_sources(log_util PUBLIC ${INCLUDE_DIR}/global/log_util.hpp - ${INCLUDE_DIR}/extern/spdlog/spdlog.h PRIVATE ${CMAKE_CURRENT_LIST_DIR}/log_util.cpp ) +target_link_libraries(log_util + spdlog + ) diff --git a/ifs/src/global/log_util.cpp b/ifs/src/global/log_util.cpp index 65bb53699..94e695f27 100644 --- a/ifs/src/global/log_util.cpp +++ b/ifs/src/global/log_util.cpp @@ -1,4 +1,6 @@ #include "global/log_util.hpp" + +#include #include #include #include @@ -36,7 +38,7 @@ void setup_loggers(const std::vector& loggers_name, spdlog::level::level_enum level, const std::string& path) { /* Create common sink */ - auto file_sink = std::make_shared(path); + auto file_sink = std::make_shared(path); /* Create and configure loggers */ auto loggers = std::list>(); diff --git a/ifs/src/global/rpc/rpc_utils.cpp b/ifs/src/global/rpc/rpc_utils.cpp index 5e913312d..818b89051 100644 --- a/ifs/src/global/rpc/rpc_utils.cpp +++ b/ifs/src/global/rpc/rpc_utils.cpp @@ -1,6 +1,5 @@ #include -#include using namespace std; diff --git a/ifs/src/preload/preload_context.cpp b/ifs/src/preload/preload_context.cpp index 30df05659..b4a3b626e 100644 --- a/ifs/src/preload/preload_context.cpp +++ b/ifs/src/preload/preload_context.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include -- GitLab From 7250bfa5f8a7f25dc0dd989eb14229cf202458aa Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 8 Oct 2018 15:59:35 +0200 Subject: [PATCH 103/105] Setup build system for user-level API --- ifs/src/api/CMakeLists.txt | 11 +++++++++++ ifs/src/api/gfsctl.h | 0 ifs/src/api/libgfsctl.cpp | 0 3 files changed, 11 insertions(+) create mode 100644 ifs/src/api/CMakeLists.txt create mode 100644 ifs/src/api/gfsctl.h create mode 100644 ifs/src/api/libgfsctl.cpp diff --git a/ifs/src/api/CMakeLists.txt b/ifs/src/api/CMakeLists.txt new file mode 100644 index 000000000..54d585417 --- /dev/null +++ b/ifs/src/api/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.6) +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_library(gfsctl SHARED) + +target_sources(gfsctl + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/gfsctl.h + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/libgfsctl.cpp + ) diff --git a/ifs/src/api/gfsctl.h b/ifs/src/api/gfsctl.h new file mode 100644 index 000000000..e69de29bb diff --git a/ifs/src/api/libgfsctl.cpp b/ifs/src/api/libgfsctl.cpp new file mode 100644 index 000000000..e69de29bb -- GitLab From 90a2a3b7752298651b669d17e47a70bcc2898606 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 15 Oct 2018 11:16:03 +0200 Subject: [PATCH 104/105] Rename library and source files --- ifs/src/api/CMakeLists.txt | 13 +++++++++---- ifs/src/api/gekkofs.h | 25 +++++++++++++++++++++++++ ifs/src/api/gfsctl.h | 0 ifs/src/api/libgekkofs-api.cpp | 11 +++++++++++ ifs/src/api/libgfsctl.cpp | 0 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 ifs/src/api/gekkofs.h delete mode 100644 ifs/src/api/gfsctl.h create mode 100644 ifs/src/api/libgekkofs-api.cpp delete mode 100644 ifs/src/api/libgfsctl.cpp diff --git a/ifs/src/api/CMakeLists.txt b/ifs/src/api/CMakeLists.txt index 54d585417..0a628b038 100644 --- a/ifs/src/api/CMakeLists.txt +++ b/ifs/src/api/CMakeLists.txt @@ -1,11 +1,16 @@ cmake_minimum_required(VERSION 3.6) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_library(gfsctl SHARED) -target_sources(gfsctl +set(WARNINGS_FLAGS "-Wall -Wextra --pedantic -Wno-unused-parameter") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0") + +add_library(gekkofs_api SHARED "") + +target_sources(gekkofs_api PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/gfsctl.h + ${CMAKE_CURRENT_LIST_DIR}/gekkofs.h PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/libgfsctl.cpp + ${CMAKE_CURRENT_LIST_DIR}/libgekkofs-api.cpp ) diff --git a/ifs/src/api/gekkofs.h b/ifs/src/api/gekkofs.h new file mode 100644 index 000000000..7aa473c6b --- /dev/null +++ b/ifs/src/api/gekkofs.h @@ -0,0 +1,25 @@ +#ifndef __GEKKOFS_API_H__ +#define __GEKKOFS_API_H__ 1 + +#ifndef GEKKOFS_API_VERSION +#define GEKKOFS_API_VERSION 10 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +enum gekkofs_error_t { + GEKKOFS_SUCCESS = 0 +}; + +// TODO: add interfaces here +// +gekkofs_error_t +gekkofs_import(const char* pathname); + +#ifdef __cplusplus +} +#endif + +#endif // __GEKKOFS_API_H__ diff --git a/ifs/src/api/gfsctl.h b/ifs/src/api/gfsctl.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/ifs/src/api/libgekkofs-api.cpp b/ifs/src/api/libgekkofs-api.cpp new file mode 100644 index 000000000..eb4aea261 --- /dev/null +++ b/ifs/src/api/libgekkofs-api.cpp @@ -0,0 +1,11 @@ +#include "gekkofs.h" + +gekkofs_error_t +gekkofs_import(const char* pathname) { + + int b = 2; + unsigned int a = b; + + return GEKKOFS_SUCCESS; +} + diff --git a/ifs/src/api/libgfsctl.cpp b/ifs/src/api/libgfsctl.cpp deleted file mode 100644 index e69de29bb..000000000 -- GitLab From 149079e303ffcfd3226242f0d6ec0f3ed3d9f447 Mon Sep 17 00:00:00 2001 From: Alberto Miranda Date: Mon, 15 Oct 2018 13:28:58 +0200 Subject: [PATCH 105/105] Define basic API --- ifs/src/api/CMakeLists.txt | 10 ++ ifs/src/api/gekkofs.h | 203 ++++++++++++++++++++++++++++++++- ifs/src/api/libgekkofs-api.c | 1 + ifs/src/api/libgekkofs-api.cpp | 32 +++++- 4 files changed, 236 insertions(+), 10 deletions(-) create mode 120000 ifs/src/api/libgekkofs-api.c diff --git a/ifs/src/api/CMakeLists.txt b/ifs/src/api/CMakeLists.txt index 0a628b038..a3c50d1e4 100644 --- a/ifs/src/api/CMakeLists.txt +++ b/ifs/src/api/CMakeLists.txt @@ -1,12 +1,15 @@ cmake_minimum_required(VERSION 3.6) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_C_STANDARD 11) set(WARNINGS_FLAGS "-Wall -Wextra --pedantic -Wno-unused-parameter") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG -O3") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${WARNINGS_FLAGS} -g -O0") add_library(gekkofs_api SHARED "") +add_library(gekkofs_capi SHARED "") target_sources(gekkofs_api PUBLIC @@ -14,3 +17,10 @@ target_sources(gekkofs_api PRIVATE ${CMAKE_CURRENT_LIST_DIR}/libgekkofs-api.cpp ) + +target_sources(gekkofs_capi + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/gekkofs.h + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/libgekkofs-api.c + ) diff --git a/ifs/src/api/gekkofs.h b/ifs/src/api/gekkofs.h index 7aa473c6b..1f0779650 100644 --- a/ifs/src/api/gekkofs.h +++ b/ifs/src/api/gekkofs.h @@ -1,6 +1,9 @@ #ifndef __GEKKOFS_API_H__ #define __GEKKOFS_API_H__ 1 +#include +#include + #ifndef GEKKOFS_API_VERSION #define GEKKOFS_API_VERSION 10 #endif @@ -9,14 +12,202 @@ extern "C" { #endif -enum gekkofs_error_t { +/******************************************************************************/ +/** Common types */ +/******************************************************************************/ + +/** Server ID type */ +typedef uint32_t gfs_server_id_t; + +/** Server name type */ +typedef const char* gfs_server_name_t; + +/** File version ID type */ +typedef const char* gfs_vid_t; + +/** Error codes */ +typedef enum { + + /* Generic */ GEKKOFS_SUCCESS = 0 -}; -// TODO: add interfaces here -// -gekkofs_error_t -gekkofs_import(const char* pathname); + //TODO add more error codes +} gfs_error_t; + + + +/******************************************************************************/ +/** Definitions for file distribution policies */ +/******************************************************************************/ + +/** + * Pseudo-randomized file chunking: + * + * This policy splits a file imported into GekkoFS into fixed-sized chunks which + * are then pseudo-randomly distributed among all available data servers. + * */ +typedef struct { + + /** The algorithm to use for chunk distribution */ + enum { + /** Use C++ std::hash() function */ + FA_CHUNKING_ALGO_STDHASH + + //TODO add more algorithms + } fa_chunk_algo; + + /** The chunk size in bytes used for splitting files */ + size_t fa_chunk_size; + +} gfs_falloc_chunking_args_t; + + +/** + * File striping: + * + * This policy splits a file imported into GekkoFS into fixed-sized stripes + * which are then distributed in a round-robin fashion among all available data + * servers. + * */ +typedef struct { + + /** The stripe size in bytes used for splitting files */ + size_t fa_stripe_size; + +} gfs_falloc_striping_args_t; + +/** + * File allocation policy: + * + * This structure defines the file allocation policy to use when importing a + * file into GekkoFS. + * */ +typedef struct { + + /** Desired file allocation policy */ + enum { + /** The file is considered as an indivisible unit by GekkoFS, + * and hence will be managed by only one data server */ + GEKKOFS_FILE_UNIT, + + /** Pseudo-random file chunking (requires initializing the + * .fa_chunking_args field ) */ + GEKKOFS_FILE_CHUNKING, + + /** Round robin file striping (requires initializing the + * .fa_striping field ) */ + GEKKOFS_FILE_STRIPING + } fa_type; + + /** Specific arguments for the file allocation policy */ + union { + /** Arguments for GEKKOFS_FILE_CHUNKING */ + gfs_falloc_chunking_args_t fa_chunking_args; + + /** Arguments for GEKKOFS_FILE_STRIPING */ + gfs_falloc_striping_args_t fa_striping_args; + }; +} gfs_falloc_t; + + + +/******************************************************************************/ +/** Basic GekkoFS API */ +/******************************************************************************/ + +/** + * Import a file into GekkoFS + * + * @param: in_pathname the path to the file to import + * @param: gekkofs_pathname the path used in GekkoFS to refer to the file + * @param: alloc_policy the data allocation policy for the file + * + * @return: GEKKOFS_SUCCESS on success, + * TODO, otherwise + * */ +gfs_error_t +gfs_import(const char* in_pathname, + const char* gekkofs_pathname, + gfs_falloc_t alloc_policy); + + +/** + * Export a file out of GekkoFS into another POSIX filesystem, and remove it + * from GekkoFS when finished + * + * @param: gekkofs_pathname the path used in GekkoFS to refer to the file + * @param: out_pathname the path where the file should be exported + * @param: alloc_policy the data allocation policy for the file + * + * @return: GEKKOFS_SUCCESS on success, + * TODO, otherwise + * */ +gfs_error_t +gfs_export(const char* gekkofs_pathname, + const char* out_pathname); + + +/** + * Commands supported by gfs_fcntl() + * */ +typedef enum { + + /** Retrieve list of data server IDs where the file is located. + * Expected arguments: + * + * gfs_server_name_t* list[]: an input/output argument that can be + * initialized by the API with a NULL terminated array containing the + * names of the servers involved with this file. The array returned + * is malloc()'ed by the API and it is the callers responsibility to + * free() it when done. + **/ + F_GET_LOCATIONS, + + /** Create a new version of the file. + * Expected arguments: + * + * gfs_vid_t* new_vid: an input/output argument to be filled by the API + * with the ID generated for the new version. The argument returned + * is malloc()'ed by the API and it is the callers responsibility to + * free() it when done. + * */ + F_NEW_VERSION, + + /** Get the current version of the file. + * Expected arguments: + * + * gfs_vid_t* vid: an input/output argument to be filled by the API + * with the ID generated for the new version. The argument returned + * is malloc()'ed by the API and it is the callers responsibility to + * free() it when done. + * */ + F_GET_CURRENT_VERSION, + + /** Set the current version of the file. + * Expected arguments: + * + * const gfs_vid_t* vid: an input argument specifying the VID desired + * by the caller. + * */ + F_SET_CURRENT_VERSION +} gfs_cmd_t; + + +/** + * Manipulate a GekkoFS file + * + * @param: gekkofs_pathname the path used in GekkoFS to refer to the file + * @param: cmd the command to perform on the file (see the definition of + * gfs_cmd_t for details) + * + * @return: GEKKOFS_SUCCESS on success, + * TODO, otherwise + * */ +gfs_error_t +gfs_fcntl(const char* gekkofs_pathname, + gfs_cmd_t cmd, + gfs_server_name_t* foo[], + ... /* arg */); #ifdef __cplusplus } diff --git a/ifs/src/api/libgekkofs-api.c b/ifs/src/api/libgekkofs-api.c new file mode 120000 index 000000000..214d99af9 --- /dev/null +++ b/ifs/src/api/libgekkofs-api.c @@ -0,0 +1 @@ +libgekkofs-api.cpp \ No newline at end of file diff --git a/ifs/src/api/libgekkofs-api.cpp b/ifs/src/api/libgekkofs-api.cpp index eb4aea261..923dfa9e0 100644 --- a/ifs/src/api/libgekkofs-api.cpp +++ b/ifs/src/api/libgekkofs-api.cpp @@ -1,11 +1,35 @@ #include "gekkofs.h" -gekkofs_error_t -gekkofs_import(const char* pathname) { +gfs_error_t +gfs_import(const char* in_pathname, + const char* gekkofs_pathname, + gfs_falloc_t alloc_policy) { - int b = 2; - unsigned int a = b; + (void) in_pathname; + (void) gekkofs_pathname; return GEKKOFS_SUCCESS; } +gfs_error_t +gfs_export(const char* gekkofs_pathname, + const char* out_pathname) { + + (void) gekkofs_pathname; + (void) out_pathname; + + return GEKKOFS_SUCCESS; +} + + +gfs_error_t +gfs_fcntl(const char* gekkofs_pathname, + gfs_cmd_t cmd, + gfs_server_name_t* foo[], + ... /* arg */) { + + (void) gekkofs_pathname; + (void) cmd; + + return GEKKOFS_SUCCESS; +} -- GitLab