diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ee812ad --- /dev/null +++ b/.gitattributes @@ -0,0 +1,97 @@ +* text=auto !eol svneol=native#text/plain +*.gitattributes text svneol=native#text/plain + +# Scriptish formats +*.bat text svneol=native#text/plain +*.bsh text svneol=native#text/x-beanshell +*.cgi text svneol=native#text/plain +*.cmd text svneol=native#text/plain +*.js text svneol=native#text/javascript +*.php text svneol=native#text/x-php +*.pl text svneol=native#text/x-perl +*.pm text svneol=native#text/x-perl +*.py text svneol=native#text/x-python +*.sh eol=lf svneol=LF#text/x-sh +configure eol=lf svneol=LF#text/x-sh + +# Image formats +*.bmp binary svneol=unset#image/bmp +*.gif binary svneol=unset#image/gif +*.ico binary svneol=unset#image/ico +*.jpeg binary svneol=unset#image/jpeg +*.jpg binary svneol=unset#image/jpeg +*.png binary svneol=unset#image/png +*.tif binary svneol=unset#image/tiff +*.tiff binary svneol=unset#image/tiff +*.svg text svneol=native#image/svg%2Bxml + +# Data formats +*.pdf binary svneol=unset#application/pdf +*.avi binary svneol=unset#video/avi +*.doc binary svneol=unset#application/msword +*.dsp text svneol=crlf#text/plain +*.dsw text svneol=crlf#text/plain +*.eps binary svneol=unset#application/postscript +*.json text svneol=native#application/json +*.gz binary svneol=unset#application/gzip +*.mov binary svneol=unset#video/quicktime +*.mp3 binary svneol=unset#audio/mpeg +*.ppt binary svneol=unset#application/vnd.ms-powerpoint +*.ps binary svneol=unset#application/postscript +*.psd binary svneol=unset#application/photoshop +*.rdf binary svneol=unset#text/rdf +*.rss text svneol=unset#text/xml +*.rtf binary svneol=unset#text/rtf +*.sln text svneol=native#text/plain +*.swf binary svneol=unset#application/x-shockwave-flash +*.tgz binary svneol=unset#application/gzip +*.vcproj text svneol=native#text/xml +*.vcxproj text svneol=native#text/xml +*.vsprops text svneol=native#text/xml +*.wav binary svneol=unset#audio/wav +*.xls binary svneol=unset#application/vnd.ms-excel +*.zip binary svneol=unset#application/zip + +# Text formats +.htaccess text svneol=native#text/plain +*.bbk text svneol=native#text/xml +*.cmake text svneol=native#text/plain +*.css text svneol=native#text/css +*.dtd text svneol=native#text/xml +*.htm text svneol=native#text/html +*.html text svneol=native#text/html +*.ini text svneol=native#text/plain +*.log text svneol=native#text/plain +*.mak text svneol=native#text/plain +*.qbk text svneol=native#text/plain +*.rst text svneol=native#text/plain +*.sql text svneol=native#text/x-sql +*.txt text svneol=native#text/plain +*.xhtml text svneol=native#text/xhtml%2Bxml +*.xml text svneol=native#text/xml +*.xsd text svneol=native#text/xml +*.xsl text svneol=native#text/xml +*.xslt text svneol=native#text/xml +*.xul text svneol=native#text/xul +*.yml text svneol=native#text/plain +boost-no-inspect text svneol=native#text/plain +CHANGES text svneol=native#text/plain +COPYING text svneol=native#text/plain +INSTALL text svneol=native#text/plain +Jamfile text svneol=native#text/plain +Jamroot text svneol=native#text/plain +Jamfile.v2 text svneol=native#text/plain +Jamrules text svneol=native#text/plain +Makefile* text svneol=native#text/plain +README text svneol=native#text/plain +TODO text svneol=native#text/plain + +# Code formats +*.c text svneol=native#text/plain +*.cpp text svneol=native#text/plain +*.h text svneol=native#text/plain +*.hpp text svneol=native#text/plain +*.ipp text svneol=native#text/plain +*.tpp text svneol=native#text/plain +*.jam text svneol=native#text/plain +*.java text svneol=native#text/plain diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fd1acc0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,120 @@ +cmake_minimum_required(VERSION 3.2) + +include("cmake/HunterGate.cmake") +HunterGate( + URL "https://github.com/cpp-pm/hunter/archive/v0.23.262.tar.gz" + SHA1 "eb51e633e08cdbe2153caf255e9c23968fecb29d" +) + +project(s3 VERSION 4.1) + +add_library( + s3 + src/bucket.cpp + src/bucket_metadata.c + src/error_parser.c + src/general.c + src/multipart.cpp + src/object.c + src/request.cpp + src/request_context.cpp + src/response_headers_handler.cpp + src/service.c + src/service_access_logging.c + src/simplexml.c + src/util.c +) + +hunter_add_package(libxml2) +find_package(libxml2 CONFIG REQUIRED) +target_link_libraries(s3 PUBLIC libxml2::libxml2) + +hunter_add_package(CURL) +find_package(CURL CONFIG REQUIRED) +target_link_libraries(s3 PUBLIC CURL::libcurl) + +target_include_directories( + s3 PUBLIC $ +) + +target_compile_definitions( + s3 + PRIVATE + LIBS3_VER_MAJOR="${PROJECT_VERSION_MAJOR}" + LIBS3_VER_MINOR="${PROJECT_VERSION_MINOR}" + LIBS3_VER="${PROJECT_VERSION}" + __STRICT_ANSI__ + _ISOC99_SOURCE + _CRT_SECURE_NO_WARNINGS +) + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + target_compile_definitions(s3 PRIVATE _POSIX_C_SOURCE=200112L) +endif() + +if(NOT MSVC) ## Useful as a linker symbols validation test that someone didn't forget to annotate extern "C" + add_executable(s3exe + src/s3.c + ) + target_link_libraries(s3exe PRIVATE s3) + set_target_properties(s3exe PROPERTIES OUTPUT_NAME "s3") +endif() + +### Installation (https://github.com/forexample/package-example) { + +# Introduce variables: +# * CMAKE_INSTALL_LIBDIR +# * CMAKE_INSTALL_BINDIR +# * CMAKE_INSTALL_INCLUDEDIR +include(GNUInstallDirs) + +set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") +set(generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(version_config "${generated_dir}/${PROJECT_NAME}ConfigVersion.cmake") +set(project_config "${generated_dir}/${PROJECT_NAME}Config.cmake") +set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") +set(NAMESPACE "${PROJECT_NAME}::") + +include(CMakePackageConfigHelpers) + +# Use: +# * PROJECT_VERSION +write_basic_package_version_file( + "${version_config}" COMPATIBILITY SameMajorVersion +) + +# Use: +# * TARGETS_EXPORT_NAME +# * PROJECT_NAME +configure_package_config_file( + "cmake/template/Config.cmake.in" + "${project_config}" + INSTALL_DESTINATION "${config_install_dir}" +) + +install( + FILES "inc/libs3.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +install( + FILES "${project_config}" "${version_config}" + DESTINATION "${config_install_dir}" +) + +install( + EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${NAMESPACE}" + DESTINATION "${config_install_dir}" +) + +install( + TARGETS s3 + EXPORT "${TARGETS_EXPORT_NAME}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +) + +### } diff --git a/cmake/HunterGate.cmake b/cmake/HunterGate.cmake new file mode 100644 index 0000000..e78d3e8 --- /dev/null +++ b/cmake/HunterGate.cmake @@ -0,0 +1,528 @@ +# Copyright (c) 2013-2019, Ruslan Baratov +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * 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 HOLDER 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. + +# This is a gate file to Hunter package manager. +# Include this file using `include` command and add package you need, example: +# +# cmake_minimum_required(VERSION 3.2) +# +# include("cmake/HunterGate.cmake") +# HunterGate( +# URL "https://github.com/path/to/hunter/archive.tar.gz" +# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d" +# ) +# +# project(MyProject) +# +# hunter_add_package(Foo) +# hunter_add_package(Boo COMPONENTS Bar Baz) +# +# Projects: +# * https://github.com/hunter-packages/gate/ +# * https://github.com/ruslo/hunter + +option(HUNTER_ENABLED "Enable Hunter package manager support" ON) + +if(HUNTER_ENABLED) + if(CMAKE_VERSION VERSION_LESS "3.2") + message( + FATAL_ERROR + "At least CMake version 3.2 required for Hunter dependency management." + " Update CMake or set HUNTER_ENABLED to OFF." + ) + endif() +endif() + +include(CMakeParseArguments) # cmake_parse_arguments + +option(HUNTER_STATUS_PRINT "Print working status" ON) +option(HUNTER_STATUS_DEBUG "Print a lot info" OFF) +option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON) + +set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors") + +function(hunter_gate_status_print) + if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + message(STATUS "[hunter] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_status_debug) + if(HUNTER_STATUS_DEBUG) + foreach(print_message ${ARGV}) + string(TIMESTAMP timestamp) + message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}") + endforeach() + endif() +endfunction() + +function(hunter_gate_error_page error_page) + message("------------------------------ ERROR ------------------------------") + message(" ${HUNTER_ERROR_PAGE}/${error_page}.html") + message("-------------------------------------------------------------------") + message("") + message(FATAL_ERROR "") +endfunction() + +function(hunter_gate_internal_error) + message("") + foreach(print_message ${ARGV}) + message("[hunter ** INTERNAL **] ${print_message}") + endforeach() + message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_error_page("error.internal") +endfunction() + +function(hunter_gate_fatal_error) + cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}") + if("${hunter_ERROR_PAGE}" STREQUAL "") + hunter_gate_internal_error("Expected ERROR_PAGE") + endif() + message("") + foreach(x ${hunter_UNPARSED_ARGUMENTS}) + message("[hunter ** FATAL ERROR **] ${x}") + endforeach() + message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]") + message("") + hunter_gate_error_page("${hunter_ERROR_PAGE}") +endfunction() + +function(hunter_gate_user_error) + hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data") +endfunction() + +function(hunter_gate_self root version sha1 result) + string(COMPARE EQUAL "${root}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("root is empty") + endif() + + string(COMPARE EQUAL "${version}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("version is empty") + endif() + + string(COMPARE EQUAL "${sha1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("sha1 is empty") + endif() + + string(SUBSTRING "${sha1}" 0 7 archive_id) + + set( + hunter_self + "${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked" + ) + + set("${result}" "${hunter_self}" PARENT_SCOPE) +endfunction() + +# Set HUNTER_GATE_ROOT cmake variable to suitable value. +function(hunter_gate_detect_root) + # Check CMake variable + string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable") + return() + endif() + + # Check environment variable + string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty) + if(not_empty) + set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT detected by environment variable") + return() + endif() + + # Check HOME environment variable + string(COMPARE NOTEQUAL "$ENV{HOME}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable") + return() + endif() + + # Check SYSTEMDRIVE and USERPROFILE environment variable (windows only) + if(WIN32) + string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using SYSTEMDRIVE environment variable" + ) + return() + endif() + + string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result) + if(result) + set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE) + hunter_gate_status_debug( + "HUNTER_ROOT set using USERPROFILE environment variable" + ) + return() + endif() + endif() + + hunter_gate_fatal_error( + "Can't detect HUNTER_ROOT" + ERROR_PAGE "error.detect.hunter.root" + ) +endfunction() + +function(hunter_gate_download dir) + string( + COMPARE + NOTEQUAL + "$ENV{HUNTER_DISABLE_AUTOINSTALL}" + "" + disable_autoinstall + ) + if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL) + hunter_gate_fatal_error( + "Hunter not found in '${dir}'" + "Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'" + "Settings:" + " HUNTER_ROOT: ${HUNTER_GATE_ROOT}" + " HUNTER_SHA1: ${HUNTER_GATE_SHA1}" + ERROR_PAGE "error.run.install" + ) + endif() + string(COMPARE EQUAL "${dir}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("Empty 'dir' argument") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_SHA1 empty") + endif() + + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad) + if(is_bad) + hunter_gate_internal_error("HUNTER_GATE_URL empty") + endif() + + set(done_location "${dir}/DONE") + set(sha1_location "${dir}/SHA1") + + set(build_dir "${dir}/Build") + set(cmakelists "${dir}/CMakeLists.txt") + + hunter_gate_status_debug("Locking directory: ${dir}") + file(LOCK "${dir}" DIRECTORY GUARD FUNCTION) + hunter_gate_status_debug("Lock done") + + if(EXISTS "${done_location}") + # while waiting for lock other instance can do all the job + hunter_gate_status_debug("File '${done_location}' found, skip install") + return() + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(MAKE_DIRECTORY "${build_dir}") # check directory permissions + + # Disabling languages speeds up a little bit, reduces noise in the output + # and avoids path too long windows error + file( + WRITE + "${cmakelists}" + "cmake_minimum_required(VERSION 3.2)\n" + "project(HunterDownload LANGUAGES NONE)\n" + "include(ExternalProject)\n" + "ExternalProject_Add(\n" + " Hunter\n" + " URL\n" + " \"${HUNTER_GATE_URL}\"\n" + " URL_HASH\n" + " SHA1=${HUNTER_GATE_SHA1}\n" + " DOWNLOAD_DIR\n" + " \"${dir}\"\n" + " TLS_VERIFY\n" + " ${HUNTER_TLS_VERIFY}\n" + " SOURCE_DIR\n" + " \"${dir}/Unpacked\"\n" + " CONFIGURE_COMMAND\n" + " \"\"\n" + " BUILD_COMMAND\n" + " \"\"\n" + " INSTALL_COMMAND\n" + " \"\"\n" + ")\n" + ) + + if(HUNTER_STATUS_DEBUG) + set(logging_params "") + else() + set(logging_params OUTPUT_QUIET) + endif() + + hunter_gate_status_debug("Run generate") + + # Need to add toolchain file too. + # Otherwise on Visual Studio + MDD this will fail with error: + # "Could not find an appropriate version of the Windows 10 SDK installed on this machine" + if(EXISTS "${CMAKE_TOOLCHAIN_FILE}") + get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE) + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}") + else() + # 'toolchain_arg' can't be empty + set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=") + endif() + + string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make) + if(no_make) + set(make_arg "") + else() + # Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM + set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}") + endif() + + execute_process( + COMMAND + "${CMAKE_COMMAND}" + "-H${dir}" + "-B${build_dir}" + "-G${CMAKE_GENERATOR}" + "${toolchain_arg}" + ${make_arg} + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error( + "Configure project failed." + "To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}" + "In directory ${dir}" + ) + endif() + + hunter_gate_status_print( + "Initializing Hunter workspace (${HUNTER_GATE_SHA1})" + " ${HUNTER_GATE_URL}" + " -> ${dir}" + ) + execute_process( + COMMAND "${CMAKE_COMMAND}" --build "${build_dir}" + WORKING_DIRECTORY "${dir}" + RESULT_VARIABLE download_result + ${logging_params} + ) + + if(NOT download_result EQUAL 0) + hunter_gate_internal_error("Build project failed") + endif() + + file(REMOVE_RECURSE "${build_dir}") + file(REMOVE_RECURSE "${cmakelists}") + + file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}") + file(WRITE "${done_location}" "DONE") + + hunter_gate_status_debug("Finished") +endfunction() + +# Must be a macro so master file 'cmake/Hunter' can +# apply all variables easily just by 'include' command +# (otherwise PARENT_SCOPE magic needed) +macro(HunterGate) + if(HUNTER_GATE_DONE) + # variable HUNTER_GATE_DONE set explicitly for external project + # (see `hunter_download`) + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() + + # First HunterGate command will init Hunter, others will be ignored + get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET) + + if(NOT HUNTER_ENABLED) + # Empty function to avoid error "unknown function" + function(hunter_add_package) + endfunction() + + set( + _hunter_gate_disabled_mode_dir + "${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode" + ) + if(EXISTS "${_hunter_gate_disabled_mode_dir}") + hunter_gate_status_debug( + "Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}" + ) + list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}") + endif() + elseif(_hunter_gate_done) + hunter_gate_status_debug("Secondary HunterGate (use old settings)") + hunter_gate_self( + "${HUNTER_CACHED_ROOT}" + "${HUNTER_VERSION}" + "${HUNTER_SHA1}" + _hunter_self + ) + include("${_hunter_self}/cmake/Hunter") + else() + set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") + + string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name) + if(_have_project_name) + hunter_gate_fatal_error( + "Please set HunterGate *before* 'project' command. " + "Detected project: ${PROJECT_NAME}" + ERROR_PAGE "error.huntergate.before.project" + ) + endif() + + cmake_parse_arguments( + HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV} + ) + + string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1) + string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url) + string( + COMPARE + NOTEQUAL + "${HUNTER_GATE_UNPARSED_ARGUMENTS}" + "" + _have_unparsed + ) + string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global) + string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath) + + if(_have_unparsed) + hunter_gate_user_error( + "HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}" + ) + endif() + if(_empty_sha1) + hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory") + endif() + if(_empty_url) + hunter_gate_user_error("URL suboption of HunterGate is mandatory") + endif() + if(_have_global) + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)") + endif() + endif() + if(HUNTER_GATE_LOCAL) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)") + endif() + if(_have_filepath) + hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)") + endif() + endif() + if(_have_filepath) + if(_have_global) + hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)") + endif() + if(HUNTER_GATE_LOCAL) + hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)") + endif() + endif() + + hunter_gate_detect_root() # set HUNTER_GATE_ROOT + + # Beautify path, fix probable problems with windows path slashes + get_filename_component( + HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE + ) + hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}") + if(NOT HUNTER_ALLOW_SPACES_IN_PATH) + string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces) + if(NOT _contain_spaces EQUAL -1) + hunter_gate_fatal_error( + "HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces." + "Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error" + "(Use at your own risk!)" + ERROR_PAGE "error.spaces.in.hunter.root" + ) + endif() + endif() + + string( + REGEX + MATCH + "[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*" + HUNTER_GATE_VERSION + "${HUNTER_GATE_URL}" + ) + string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty) + if(_is_empty) + set(HUNTER_GATE_VERSION "unknown") + endif() + + hunter_gate_self( + "${HUNTER_GATE_ROOT}" + "${HUNTER_GATE_VERSION}" + "${HUNTER_GATE_SHA1}" + _hunter_self + ) + + set(_master_location "${_hunter_self}/cmake/Hunter") + get_filename_component(_archive_id_location "${_hunter_self}/.." ABSOLUTE) + set(_done_location "${_archive_id_location}/DONE") + set(_sha1_location "${_archive_id_location}/SHA1") + + # Check Hunter already downloaded by HunterGate + if(NOT EXISTS "${_done_location}") + hunter_gate_download("${_archive_id_location}") + endif() + + if(NOT EXISTS "${_done_location}") + hunter_gate_internal_error("hunter_gate_download failed") + endif() + + if(NOT EXISTS "${_sha1_location}") + hunter_gate_internal_error("${_sha1_location} not found") + endif() + file(READ "${_sha1_location}" _sha1_value) + string(COMPARE EQUAL "${_sha1_value}" "${HUNTER_GATE_SHA1}" _is_equal) + if(NOT _is_equal) + hunter_gate_internal_error( + "Short SHA1 collision:" + " ${_sha1_value} (from ${_sha1_location})" + " ${HUNTER_GATE_SHA1} (HunterGate)" + ) + endif() + if(NOT EXISTS "${_master_location}") + hunter_gate_user_error( + "Master file not found:" + " ${_master_location}" + "try to update Hunter/HunterGate" + ) + endif() + include("${_master_location}") + set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES) + endif() +endmacro() diff --git a/cmake/template/Config.cmake.in b/cmake/template/Config.cmake.in new file mode 100644 index 0000000..3458619 --- /dev/null +++ b/cmake/template/Config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +find_package(libxml2 CONFIG REQUIRED) +find_package(CURL CONFIG REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/inc/error_parser.h b/inc/error_parser.h index 86b2889..35905e1 100644 --- a/inc/error_parser.h +++ b/inc/error_parser.h @@ -37,6 +37,9 @@ #include "simplexml.h" #include "string_buffer.h" +#ifdef __cplusplus +extern "C" { +#endif #define EXTRA_DETAILS_SIZE 8 @@ -84,5 +87,8 @@ void error_parser_convert_status(ErrorParser *errorParser, S3Status *status); // Always call this void error_parser_deinitialize(ErrorParser *errorParser); +#ifdef __cplusplus +} +#endif #endif /* ERROR_PARSER_H */ diff --git a/inc/libs3.h b/inc/libs3.h index 3318ef2..7dddefb 100644 --- a/inc/libs3.h +++ b/inc/libs3.h @@ -34,8 +34,6 @@ #define LIBS3_H #include -#include - #ifdef __cplusplus extern "C" { @@ -240,6 +238,18 @@ extern "C" { #define S3_DEFAULT_REGION "us-east-1" +/** + * Maximal length of AWS access key + */ +#define S3_MAX_ACCESS_KEY_ID_LENGTH 128 + + +/** + * Maximal length of AWS region name + */ +#define S3_MAX_REGION_LENGTH 32 + + /** ************************************************************************** * Enumerations ************************************************************************** **/ @@ -1734,6 +1744,7 @@ S3Status S3_runonce_request_context(S3RequestContext *requestContext, S3Status S3_process_request_context(S3RequestContext *requestContext); +#if 0 // select() is no longer usable in modern code /** * This function, in conjunction allows callers to manually manage a set of * requests using an S3RequestContext. This function returns the set of file @@ -1767,7 +1778,7 @@ S3Status S3_process_request_context(S3RequestContext *requestContext); S3Status S3_get_request_context_fdsets(S3RequestContext *requestContext, fd_set *readFdSet, fd_set *writeFdSet, fd_set *exceptFdSet, int *maxFd); - +#endif /** * This function returns the maximum number of milliseconds that the caller of diff --git a/inc/request.h b/inc/request.h index a80971a..d7345e2 100644 --- a/inc/request.h +++ b/inc/request.h @@ -38,6 +38,10 @@ #include "response_headers_handler.h" #include "util.h" +#ifdef __cplusplus +extern "C" { +#endif + // Describes a type of HTTP request (these are our supported HTTP "verbs") typedef enum { @@ -194,4 +198,8 @@ void request_finish(Request *request); S3Status request_curl_code_to_status(CURLcode code); +#ifdef __cplusplus +} +#endif + #endif /* REQUEST_H */ diff --git a/inc/request_context.h b/inc/request_context.h index e91d9c8..9860447 100644 --- a/inc/request_context.h +++ b/inc/request_context.h @@ -35,6 +35,9 @@ #include "libs3.h" +#ifdef __cplusplus +extern "C" { +#endif typedef enum { @@ -58,4 +61,8 @@ struct S3RequestContext }; +#ifdef __cplusplus +} +#endif + #endif /* REQUEST_CONTEXT_H */ diff --git a/inc/response_headers_handler.h b/inc/response_headers_handler.h index b2341c2..95613d9 100644 --- a/inc/response_headers_handler.h +++ b/inc/response_headers_handler.h @@ -37,6 +37,9 @@ #include "string_buffer.h" #include "util.h" +#ifdef __cplusplus +extern "C" { +#endif typedef struct ResponseHeadersHandler { @@ -67,4 +70,8 @@ void response_headers_handler_add(ResponseHeadersHandler *handler, void response_headers_handler_done(ResponseHeadersHandler *handler, CURL *curl); +#ifdef __cplusplus +} +#endif + #endif /* RESPONSE_HEADERS_HANDLER_H */ diff --git a/inc/simplexml.h b/inc/simplexml.h index f12a22b..ab7dca7 100644 --- a/inc/simplexml.h +++ b/inc/simplexml.h @@ -35,6 +35,9 @@ #include "libs3.h" +#ifdef __cplusplus +extern "C" { +#endif // Simple XML callback. // @@ -79,4 +82,8 @@ S3Status simplexml_add(SimpleXml *simpleXml, const char *data, int dataLen); void simplexml_deinitialize(SimpleXml *simpleXml); +#ifdef __cplusplus +} +#endif + #endif /* SIMPLEXML_H */ diff --git a/inc/string_buffer.h b/inc/string_buffer.h index a4968ad..63e441a 100644 --- a/inc/string_buffer.h +++ b/inc/string_buffer.h @@ -35,6 +35,9 @@ #include +#ifdef __cplusplus +extern "C" { +#endif // Declare a string_buffer with the given name of the given maximum length #define string_buffer(name, len) \ @@ -110,4 +113,8 @@ } while (0) +#ifdef __cplusplus +} +#endif + #endif /* STRING_BUFFER_H */ diff --git a/inc/util.h b/inc/util.h index 8c66a55..7ec2608 100644 --- a/inc/util.h +++ b/inc/util.h @@ -38,6 +38,10 @@ #include #include "libs3.h" +#ifdef __cplusplus +extern "C" { +#endif + // acl groups #define ACS_URL "http://acs.amazonaws.com/groups/" @@ -69,12 +73,10 @@ #define MAX_CANONICALIZED_RESOURCE_SIZE \ (1 + 255 + 1 + MAX_URLENCODED_KEY_SIZE + (sizeof("?torrent") - 1) + 1) -#define MAX_ACCESS_KEY_ID_LENGTH 32 - // Maximum length of a credential string // ///s3/aws4_request #define MAX_CREDENTIAL_SIZE \ - (MAX_ACCESS_KEY_ID_LENGTH + 1) + 8 + 1 + 32 + sizeof("/s3/aws4_request") + (S3_MAX_ACCESS_KEY_ID_LENGTH + 1) + 8 + 1 + S3_MAX_REGION_LENGTH + sizeof("/s3/aws4_request") // Utilities ----------------------------------------------------------------- @@ -93,4 +95,8 @@ uint64_t parseUnsignedInt(const char *str); // easy function to write in any case int is_blank(char c); +#ifdef __cplusplus +} +#endif + #endif /* UTIL_H */ diff --git a/src/bucket.c b/src/bucket.cpp similarity index 99% rename from src/bucket.c rename to src/bucket.cpp index 6d30674..e4d6069 100644 --- a/src/bucket.c +++ b/src/bucket.cpp @@ -36,6 +36,8 @@ #include "request.h" #include "simplexml.h" +#include "vla.hpp" + // test bucket --------------------------------------------------------------- typedef struct TestBucketData @@ -484,7 +486,7 @@ static S3Status make_list_bucket_callback(ListBucketData *lbData) !strcmp(lbData->isTruncated, "1")) ? 1 : 0; // Convert the contents - S3ListBucketContent contents[lbData->contentsCount]; + vla contents(lbData->contentsCount); int contentsCount = lbData->contentsCount; for (i = 0; i < contentsCount; i++) { @@ -503,7 +505,7 @@ static S3Status make_list_bucket_callback(ListBucketData *lbData) // Make the common prefixes array int commonPrefixesCount = lbData->commonPrefixesCount; - char *commonPrefixes[commonPrefixesCount]; + vla commonPrefixes(commonPrefixesCount); for (i = 0; i < commonPrefixesCount; i++) { commonPrefixes[i] = lbData->commonPrefixes[i]; } @@ -511,7 +513,7 @@ static S3Status make_list_bucket_callback(ListBucketData *lbData) return (*(lbData->listBucketCallback)) (isTruncated, lbData->nextMarker, contentsCount, contents, commonPrefixesCount, - (const char **) commonPrefixes, lbData->callbackData); + (const char **) (char **) commonPrefixes, lbData->callbackData); } diff --git a/src/multipart.c b/src/multipart.cpp similarity index 99% rename from src/multipart.c rename to src/multipart.cpp index ccff5f8..4cb6d38 100644 --- a/src/multipart.c +++ b/src/multipart.cpp @@ -36,6 +36,9 @@ #include "request.h" #include "simplexml.h" +#include "vla.hpp" + +extern "C" { typedef struct InitialMultipartData { @@ -545,7 +548,7 @@ static S3Status make_list_multipart_callback(ListMultipartData *lmData) !strcmp(lmData->isTruncated, "1")) ? 1 : 0; // Convert the contents - S3ListMultipartUpload uploads[lmData->uploadsCount]; + vla uploads(lmData->uploadsCount); int uploadsCount = lmData->uploadsCount; for (i = 0; i < uploadsCount; i++) { @@ -565,7 +568,7 @@ static S3Status make_list_multipart_callback(ListMultipartData *lmData) // Make the common prefixes array int commonPrefixesCount = lmData->commonPrefixesCount; - char *commonPrefixes[commonPrefixesCount]; + vla commonPrefixes(commonPrefixesCount); for (i = 0; i < commonPrefixesCount; i++) { commonPrefixes[i] = lmData->commonPrefixes[i]; } @@ -573,7 +576,7 @@ static S3Status make_list_multipart_callback(ListMultipartData *lmData) return (*(lmData->listMultipartCallback)) (isTruncated, lmData->nextKeyMarker, lmData->nextUploadIdMarker, uploadsCount, uploads, commonPrefixesCount, - (const char **) commonPrefixes, lmData->callbackData); + (const char **) (char **) commonPrefixes, lmData->callbackData); } @@ -586,7 +589,7 @@ static S3Status make_list_parts_callback(ListPartsData *lpData) !strcmp(lpData->isTruncated, "1")) ? 1 : 0; // Convert the contents - S3ListPart Parts[lpData->partsCount]; + vla Parts(lpData->partsCount); int partsCount = lpData->partsCount; for (i = 0; i < partsCount; i++) { S3ListPart *partDest = &(Parts[i]); @@ -1105,3 +1108,5 @@ void S3_list_parts(S3BucketContext *bucketContext, const char *key, // Perform the request request_perform(¶ms, requestContext); } + +} // extern "C" \ No newline at end of file diff --git a/src/request.c b/src/request.cpp similarity index 97% rename from src/request.c rename to src/request.cpp index dd66863..54b7ed5 100644 --- a/src/request.c +++ b/src/request.cpp @@ -31,15 +31,16 @@ ************************************************************************** **/ #include -#include #include #include -#include #include #include "request.h" #include "request_context.h" #include "response_headers_handler.h" +#include "vla.hpp" +#include + #ifdef __APPLE__ #include #define S3_SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH @@ -55,11 +56,13 @@ //#define SIGNATURE_DEBUG +extern "C" { + static int verifyPeer; static char userAgentG[USER_AGENT_SIZE]; -static pthread_mutex_t requestStackMutexG; +static std::mutex requestStackMutexG; static Request *requestStackG[REQUEST_STACK_SIZE]; @@ -67,6 +70,24 @@ static int requestStackCountG; char defaultHostNameG[S3_MAX_HOSTNAME_SIZE]; +#ifdef _MSC_VER +#define strtok_r strtok_s +inline struct tm* gmtime_r(const time_t* timer, struct tm* buf) { + gmtime_s(buf, timer); + return buf; +} +struct utsname { + char sysname[32]; + char machine[64]; +}; +inline int uname(struct utsname* buf) { + memcpy(buf->sysname, "Windows", 9); + memcpy(buf->machine, "x86_64", 7); + return 0; +} +#else +#include +#endif typedef struct RequestComputedValues { @@ -829,7 +850,7 @@ static void sort_query_string(const char *queryString, char *result, tmp++; } - const char* params[numParams]; + vla params(numParams); // Where did strdup go?!?? int queryStringLen = strlen(queryString); @@ -880,12 +901,12 @@ static void canonicalize_query_string(const char *queryParams, *buffer = 0; -#define append(str) len += snprintf(&(buffer[len]), buffer_size - len, "%s", str) +#define append(str) len += snprintf(&(buffer[len]), buffer_size - len, "%s", (const char *) str) if (queryParams && queryParams[0]) { - char sorted[strlen(queryParams) * 2]; + vla sorted(strlen(queryParams) * 2); sorted[0] = '\0'; - sort_query_string(queryParams, sorted, sizeof(sorted)); + sort_query_string(queryParams, sorted, sorted.size_bytes()); append(sorted); } @@ -963,23 +984,26 @@ static S3Status compose_auth_header(const RequestParams *params, int len = 0; - char canonicalRequest[canonicalRequestLen]; + vla canonicalRequest(canonicalRequestLen); +#define canonicalbuf_append(format, ...) \ + len += snprintf(&(canonicalRequest[len]), canonicalRequest.size_bytes() - len, \ + format, __VA_ARGS__) #define buf_append(buf, format, ...) \ len += snprintf(&(buf[len]), sizeof(buf) - len, \ format, __VA_ARGS__) canonicalRequest[0] = '\0'; - buf_append(canonicalRequest, "%s\n", httpMethod); - buf_append(canonicalRequest, "%s\n", values->canonicalURI); - buf_append(canonicalRequest, "%s\n", values->canonicalQueryString); - buf_append(canonicalRequest, "%s\n", values->canonicalizedSignatureHeaders); - buf_append(canonicalRequest, "%s\n", values->signedHeaders); + canonicalbuf_append("%s\n", httpMethod); + canonicalbuf_append("%s\n", values->canonicalURI); + canonicalbuf_append("%s\n", values->canonicalQueryString); + canonicalbuf_append("%s\n", values->canonicalizedSignatureHeaders); + canonicalbuf_append("%s\n", values->signedHeaders); - buf_append(canonicalRequest, "%s", values->payloadHash); + canonicalbuf_append("%s", values->payloadHash); #ifdef SIGNATURE_DEBUG - printf("--\nCanonical Request:\n%s\n", canonicalRequest); + printf("--\nCanonical Request:\n%s\n", (char *) canonicalRequest); #endif len = 0; @@ -987,7 +1011,7 @@ static S3Status compose_auth_header(const RequestParams *params, #ifdef __APPLE__ CC_SHA256(canonicalRequest, strlen(canonicalRequest), canonicalRequestHash); #else - const unsigned char *rqstData = (const unsigned char*) canonicalRequest; + const unsigned char *rqstData = (const unsigned char*) (char *) canonicalRequest; SHA256(rqstData, strlen(canonicalRequest), canonicalRequestHash); #endif char canonicalRequestHashHex[2 * S3_SHA256_DIGEST_LENGTH + 1]; @@ -1016,8 +1040,8 @@ static S3Status compose_auth_header(const RequestParams *params, #endif const char *secretAccessKey = params->bucketContext.secretAccessKey; - char accessKey[strlen(secretAccessKey) + 5]; - snprintf(accessKey, sizeof(accessKey), "AWS4%s", secretAccessKey); + vla accessKey(strlen(secretAccessKey) + 5); + snprintf(accessKey, accessKey.size_bytes(), "AWS4%s", secretAccessKey); #ifdef __APPLE__ unsigned char dateKey[S3_SHA256_DIGEST_LENGTH]; @@ -1328,13 +1352,13 @@ static S3Status request_get(const RequestParams *params, // Try to get one from the request stack. We hold the lock for the // shortest time possible here. - pthread_mutex_lock(&requestStackMutexG); + requestStackMutexG.lock(); if (requestStackCountG) { request = requestStackG[--requestStackCountG]; } - pthread_mutex_unlock(&requestStackMutexG); + requestStackMutexG.unlock(); // If we got one, deinitialize it for re-use if (request) { @@ -1424,11 +1448,11 @@ static void request_destroy(Request *request) static void request_release(Request *request) { - pthread_mutex_lock(&requestStackMutexG); + requestStackMutexG.lock(); // If the request stack is full, destroy this one if (requestStackCountG == REQUEST_STACK_SIZE) { - pthread_mutex_unlock(&requestStackMutexG); + requestStackMutexG.unlock(); request_destroy(request); } // Else put this one at the front of the request stack; we do this because @@ -1437,7 +1461,7 @@ static void request_release(Request *request) // times out else { requestStackG[requestStackCountG++] = request; - pthread_mutex_unlock(&requestStackMutexG); + requestStackMutexG.unlock(); } } @@ -1461,8 +1485,6 @@ S3Status request_api_initialize(const char *userAgentInfo, int flags, return S3StatusUriTooLong; } - pthread_mutex_init(&requestStackMutexG, 0); - requestStackCountG = 0; if (!userAgentInfo || !*userAgentInfo) { @@ -1490,8 +1512,6 @@ S3Status request_api_initialize(const char *userAgentInfo, int flags, void request_api_deinitialize() { - pthread_mutex_destroy(&requestStackMutexG); - xmlCleanupParser(); while (requestStackCountG--) { request_destroy(requestStackG[requestStackCountG]); @@ -1790,3 +1810,5 @@ S3Status S3_generate_authenticated_query_string bucketContext, computed.urlEncodedKey, resource, queryParams); } + +} // extern "C" diff --git a/src/request_context.c b/src/request_context.cpp similarity index 88% rename from src/request_context.c rename to src/request_context.cpp index 345096b..b1b8b82 100644 --- a/src/request_context.c +++ b/src/request_context.cpp @@ -32,10 +32,10 @@ #include #include -#include #include "request.h" #include "request_context.h" +extern "C" { S3Status S3_create_request_context_ex(S3RequestContext **requestContextReturn, CURLM *curlm, @@ -104,27 +104,10 @@ S3Status S3_runall_request_context(S3RequestContext *requestContext) { int requestsRemaining; do { - fd_set readfds, writefds, exceptfds; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&exceptfds); - int maxfd; - S3Status status = S3_get_request_context_fdsets - (requestContext, &readfds, &writefds, &exceptfds, &maxfd); - if (status != S3StatusOK) { - return status; - } - // curl will return -1 if it hasn't even created any fds yet because - // none of the connections have started yet. In this case, don't - // do the select at all, because it will wait forever; instead, just - // skip it and go straight to running the underlying CURL handles - if (maxfd != -1) { - int64_t timeout = S3_get_request_context_timeout(requestContext); - struct timeval tv = { timeout / 1000, (timeout % 1000) * 1000 }; - select(maxfd + 1, &readfds, &writefds, &exceptfds, - (timeout == -1) ? 0 : &tv); - } - status = S3_runonce_request_context(requestContext, + int numfds = 0; + (void) curl_multi_wait(requestContext->curlm, NULL, 0, + S3_get_request_context_timeout(requestContext), &numfds); + S3Status status = S3_runonce_request_context(requestContext, &requestsRemaining); if (status != S3StatusOK) { return status; @@ -224,6 +207,7 @@ S3Status S3_process_request_context(S3RequestContext *requestContext) } +#if 0 // select() is no longer usable in modern code S3Status S3_get_request_context_fdsets(S3RequestContext *requestContext, fd_set *readFdSet, fd_set *writeFdSet, fd_set *exceptFdSet, int *maxFd) @@ -232,6 +216,7 @@ S3Status S3_get_request_context_fdsets(S3RequestContext *requestContext, exceptFdSet, maxFd) == CURLM_OK) ? S3StatusOK : S3StatusInternalError); } +#endif int64_t S3_get_request_context_timeout(S3RequestContext *requestContext) { @@ -250,3 +235,5 @@ void S3_set_request_context_verify_peer(S3RequestContext *requestContext, requestContext->verifyPeerSet = 1; requestContext->verifyPeer = (verifyPeer != 0); } + +} // extern "C" diff --git a/src/response_headers_handler.c b/src/response_headers_handler.cpp similarity index 98% rename from src/response_headers_handler.c rename to src/response_headers_handler.cpp index 82b8f10..174f962 100644 --- a/src/response_headers_handler.c +++ b/src/response_headers_handler.cpp @@ -32,9 +32,15 @@ #include #include -#include #include "response_headers_handler.h" +#ifdef _MSC_VER +#define strncasecmp _strnicmp +#else +#include +#endif + +extern "C" { void response_headers_handler_initialize(ResponseHeadersHandler *handler) { @@ -220,3 +226,5 @@ void response_headers_handler_done(ResponseHeadersHandler *handler, CURL *curl) handler->done = 1; } + +} // extern "C" diff --git a/src/vla.hpp b/src/vla.hpp new file mode 100644 index 0000000..dc5d5a1 --- /dev/null +++ b/src/vla.hpp @@ -0,0 +1,33 @@ +// Quick and dirty C++ emulation of C VLAs + +#ifndef S3_VLA_EMULATION_HPP +#define S3_VLA_EMULATION_HPP + +#include + +template class vla { + T* _ptr; + size_t _count; + +public: + vla(size_t count) + : _ptr(new T[count]), _count(count) {} + ~vla() { delete[] _ptr; } + vla(const vla&) = delete; + vla(vla&&) = delete; + vla& operator=(const vla&) = delete; + vla& operator=(vla&&) = delete; + const T& operator[](size_t i) const { + assert(i < _count); + return _ptr[i]; + } + T& operator[](size_t i) { + assert(i < _count); + return _ptr[i]; + } + operator T*() { return _ptr; } + size_t size() const { return _count; } + size_t size_bytes() const { return _count * sizeof(T); } +}; + +#endif