diff --git a/.gitignore b/.gitignore index c9b130621..eed2de186 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,13 @@ CMakeLists.txt.user* *~ *.autosave doc/_build/* -*~ *.kate-swp *.kdev4 win/ - admin/win/nsi/l10n/pofiles/*.po +*.swp +*~$ +build* +cscope.* +tags +t1.cfg diff --git a/CMakeLists.txt b/CMakeLists.txt index 7598b7231..c2cb70c21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,6 @@ endif() # find_package(Qt4 4.7.0 COMPONENTS QtDBus REQUIRED ) #endif() find_package(Neon REQUIRED) -find_package(Csync REQUIRED) find_package(QtKeychain REQUIRED) find_package(Sphinx) find_package(PdfLatex) @@ -94,6 +93,7 @@ add_definitions(-D_UNICODE) file( GLOB TRANS_FILES ${CMAKE_SOURCE_DIR}/translations/mirall_*.ts) set(TRANSLATIONS ${TRANS_FILES}) +add_subdirectory(csync) add_subdirectory(src) add_subdirectory(doc) diff --git a/cmake/modules/AddCMockaTest.cmake b/cmake/modules/AddCMockaTest.cmake new file mode 100644 index 000000000..b2d1ca8a4 --- /dev/null +++ b/cmake/modules/AddCMockaTest.cmake @@ -0,0 +1,23 @@ +# - ADD_CHECK_TEST(test_name test_source linklib1 ... linklibN) + +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2010 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +enable_testing() +include(CTest) + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) + set(CMAKE_C_FLAGS_PROFILING "-g -O0 -Wall -W -Wshadow -Wunused-variable -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers -Wwrite-strings -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Compiler Flags") + set(CMAKE_SHARED_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") + set(CMAKE_MODULE_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") + set(CMAKE_EXEC_LINKER_FLAGS_PROFILING " -fprofile-arcs -ftest-coverage" CACHE STRING "Profiling Linker Flags") +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW) + +function (ADD_CMOCKA_TEST _testName _testSource) + add_executable(${_testName} ${_testSource}) + target_link_libraries(${_testName} ${ARGN}) + add_test(${_testName} ${CMAKE_CURRENT_BINARY_DIR}/${_testName}) +endfunction (ADD_CMOCKA_TEST) diff --git a/cmake/modules/COPYING-CMAKE-SCRIPTS b/cmake/modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 000000000..4b417765f --- /dev/null +++ b/cmake/modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +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 copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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/cmake/modules/CheckCCompilerFlagSSP.cmake b/cmake/modules/CheckCCompilerFlagSSP.cmake new file mode 100644 index 000000000..2fe43954a --- /dev/null +++ b/cmake/modules/CheckCCompilerFlagSSP.cmake @@ -0,0 +1,26 @@ +# - Check whether the C compiler supports a given flag in the +# context of a stack checking compiler option. + +# CHECK_C_COMPILER_FLAG_SSP(FLAG VARIABLE) +# +# FLAG - the compiler flag +# VARIABLE - variable to store the result +# +# This actually calls check_c_source_compiles. +# See help for CheckCSourceCompiles for a listing of variables +# that can modify the build. + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +include(CheckCSourceCompiles) + +function(CHECK_C_COMPILER_FLAG_SSP _FLAG _RESULT) + set(SAFE_CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS}") + set(CMAKE_REQUIRED_DEFINITIONS "${_FLAG}") + check_c_source_compiles("int main(int argc, char **argv) { char buffer[256]; return buffer[argc]=0;}" ${_RESULT}) + set(CMAKE_REQUIRED_DEFINITIONS "${SAFE_CMAKE_REQUIRED_DEFINITIONS}") +endfunction(CHECK_C_COMPILER_FLAG_SSP) diff --git a/cmake/modules/CheckPrototypeDefinition.c.in b/cmake/modules/CheckPrototypeDefinition.c.in new file mode 100644 index 000000000..a97344ac3 --- /dev/null +++ b/cmake/modules/CheckPrototypeDefinition.c.in @@ -0,0 +1,29 @@ +@CHECK_PROTOTYPE_DEFINITION_HEADER@ + +static void cmakeRequireSymbol(int dummy, ...) { + (void) dummy; +} + +static void checkSymbol(void) { +#ifndef @CHECK_PROTOTYPE_DEFINITION_SYMBOL@ + cmakeRequireSymbol(0, &@CHECK_PROTOTYPE_DEFINITION_SYMBOL@); +#endif +} + +@CHECK_PROTOTYPE_DEFINITION_PROTO@ { + return @CHECK_PROTOTYPE_DEFINITION_RETURN@; +} + +#ifdef __CLASSIC_C__ +int main() { + int ac; + char*av[]; +#else +int main(int ac, char *av[]) { +#endif + checkSymbol(); + if (ac > 1000) { + return *av[0]; + } + return 0; +} diff --git a/cmake/modules/CheckPrototypeDefinition.cmake b/cmake/modules/CheckPrototypeDefinition.cmake new file mode 100644 index 000000000..2342b3c4f --- /dev/null +++ b/cmake/modules/CheckPrototypeDefinition.cmake @@ -0,0 +1,98 @@ +# - Check if the protoype we expect is correct. +# check_prototype_definition(FUNCTION PROTOTYPE RETURN HEADER VARIABLE) +# FUNCTION - The name of the function (used to check if prototype exists) +# PROTOTYPE- The prototype to check. +# RETURN - The return value of the function. +# HEADER - The header files required. +# VARIABLE - The variable to store the result. +# Example: +# check_prototype_definition(getpwent_r +# "struct passwd *getpwent_r(struct passwd *src, char *buf, int buflen)" +# "NULL" +# "unistd.h;pwd.h" +# SOLARIS_GETPWENT_R) +# The following variables may be set before calling this macro to +# modify the way the check is run: +# +# CMAKE_REQUIRED_FLAGS = string of compile command line flags +# CMAKE_REQUIRED_DEFINITIONS = list of macros to define (-DFOO=bar) +# CMAKE_REQUIRED_INCLUDES = list of include directories +# CMAKE_REQUIRED_LIBRARIES = list of libraries to link + +#============================================================================= +# Copyright 2005-2009 Kitware, Inc. +# Copyright 2010-2011 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) +# + + +get_filename_component(__check_proto_def_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) + + +function(CHECK_PROTOTYPE_DEFINITION _FUNCTION _PROTOTYPE _RETURN _HEADER _VARIABLE) + + if ("${_VARIABLE}" MATCHES "^${_VARIABLE}$") + set(CHECK_PROTOTYPE_DEFINITION_CONTENT "/* */\n") + + set(CHECK_PROTOTYPE_DEFINITION_FLAGS ${CMAKE_REQUIRED_FLAGS}) + if (CMAKE_REQUIRED_LIBRARIES) + set(CHECK_PROTOTYPE_DEFINITION_LIBS + LINK_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) + else() + set(CHECK_PROTOTYPE_DEFINITION_LIBS) + endif() + if (CMAKE_REQUIRED_INCLUDES) + set(CMAKE_SYMBOL_EXISTS_INCLUDES + "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_REQUIRED_INCLUDES}") + else() + set(CMAKE_SYMBOL_EXISTS_INCLUDES) + endif() + + foreach(_FILE ${_HEADER}) + set(CHECK_PROTOTYPE_DEFINITION_HEADER + "${CHECK_PROTOTYPE_DEFINITION_HEADER}#include <${_FILE}>\n") + endforeach() + + set(CHECK_PROTOTYPE_DEFINITION_SYMBOL ${_FUNCTION}) + set(CHECK_PROTOTYPE_DEFINITION_PROTO ${_PROTOTYPE}) + set(CHECK_PROTOTYPE_DEFINITION_RETURN ${_RETURN}) + + configure_file("${__check_proto_def_dir}/CheckPrototypeDefinition.c.in" + "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c" @ONLY) + + file(READ ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c _SOURCE) + + try_compile(${_VARIABLE} + ${CMAKE_BINARY_DIR} + ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckPrototypeDefinition.c + COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} + ${CHECK_PROTOTYPE_DEFINITION_LIBS} + CMAKE_FLAGS -DCOMPILE_DEFINITIONS:STRING=${CHECK_PROTOTYPE_DEFINITION_FLAGS} + "${CMAKE_SYMBOL_EXISTS_INCLUDES}" + OUTPUT_VARIABLE OUTPUT) + + if (${_VARIABLE}) + set(${_VARIABLE} 1 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") + message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - True") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log + "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} passed with the following output:\n" + "${OUTPUT}\n\n") + else () + message(STATUS "Checking prototype ${_FUNCTION} for ${_VARIABLE} - False") + set(${_VARIABLE} 0 CACHE INTERNAL "Have correct prototype for ${_FUNCTION}") + file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log + "Determining if the prototype ${_FUNCTION} exists for ${_VARIABLE} failed with the following output:\n" + "${OUTPUT}\n\n${_SOURCE}\n\n") + endif () + endif() + +endfunction() diff --git a/cmake/modules/DefineCMakeDefaults.cmake b/cmake/modules/DefineCMakeDefaults.cmake new file mode 100644 index 000000000..783583af3 --- /dev/null +++ b/cmake/modules/DefineCMakeDefaults.cmake @@ -0,0 +1,30 @@ +# Always include srcdir and builddir in include path +# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY} in +# about every subdir +# since cmake 2.4.0 +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the include dirs which are in the source or build tree +# before all other include dirs, so the headers in the sources +# are prefered over the already installed ones +# since cmake 2.4.1 +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Use colored output +# since cmake 2.4.0 +set(CMAKE_COLOR_MAKEFILE ON) + +# Define the generic version of the libraries here +set(GENERIC_LIB_VERSION "0.1.0") +set(GENERIC_LIB_SOVERSION "0") + +# set -Werror +set(CMAKE_ENABLE_WERROR ON) + +# Set the default build type to release with debug info +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE RelWithDebInfo + CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + ) +endif (NOT CMAKE_BUILD_TYPE) diff --git a/cmake/modules/DefineCompilerFlags.cmake b/cmake/modules/DefineCompilerFlags.cmake new file mode 100644 index 000000000..772c67ebe --- /dev/null +++ b/cmake/modules/DefineCompilerFlags.cmake @@ -0,0 +1,88 @@ +# define system dependent compiler flags + +include(CheckCCompilerFlag) +include(CheckCCompilerFlagSSP) + +# +# Define GNUCC compiler flags +# +if (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)") + + # add -Wconversion ? + # cannot be pedantic with sqlite3 directly linked + if (NOT CSYNC_STATIC_COMPILE_DIR) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -pedantic -pedantic-errors") + endif() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wshadow -Wmissing-prototypes -Wdeclaration-after-statement") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunused -Wfloat-equal -Wpointer-arith -Wwrite-strings -Wformat-security") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-format-attribute") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wmissing-format-attribute -D_GNU_SOURCE") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D__STDC_FORMAT_MACROS=1") + + set(CSYNC_STRICT OFF CACHE BOOL "Strict error checking, enabled -Werror and friends") + if (CSYNC_STRICT) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") + endif(CSYNC_STRICT) + + # with -fPIC + check_c_compiler_flag("-fPIC" WITH_FPIC) + if (WITH_FPIC AND NOT WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + endif (WITH_FPIC AND NOT WIN32) + + check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) + if (WITH_STACK_PROTECTOR AND NOT WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") + endif (WITH_STACK_PROTECTOR AND NOT WIN32) + + if (CMAKE_BUILD_TYPE) + string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + if (CMAKE_BUILD_TYPE_LOWER MATCHES "(release|relwithdebinfo|minsizerel)") + check_c_compiler_flag("-Wp,-D_FORTIFY_SOURCE=2" WITH_FORTIFY_SOURCE) + if (WITH_FORTIFY_SOURCE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wp,-D_FORTIFY_SOURCE=2") + endif (WITH_FORTIFY_SOURCE) + endif() + endif() +endif (${CMAKE_C_COMPILER_ID} MATCHES "(GNU|Clang)") + +if (UNIX AND NOT WIN32) + # + # Check for large filesystem support + # + if (CMAKE_SIZEOF_VOID_P MATCHES "8") + # with large file support + execute_process( + COMMAND + getconf LFS64_CFLAGS + OUTPUT_VARIABLE + _lfs_CFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + else (CMAKE_SIZEOF_VOID_P MATCHES "8") + # with large file support + execute_process( + COMMAND + getconf LFS_CFLAGS + OUTPUT_VARIABLE + _lfs_CFLAGS + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif (CMAKE_SIZEOF_VOID_P MATCHES "8") + if (_lfs_CFLAGS) + string(REGEX REPLACE "[\r\n]" " " "${_lfs_CFLAGS}" "${${_lfs_CFLAGS}}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_lfs_CFLAGS}") + endif (_lfs_CFLAGS) +else(UNIX AND NOT WIN32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FILE_OFFSET_BITS=64") +endif (UNIX AND NOT WIN32) + +if (MSVC) + # Use secure functions by defaualt and suppress warnings about + #"deprecated" functions + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1") +endif (MSVC) diff --git a/cmake/modules/DefinePlatformDefaults.cmake b/cmake/modules/DefinePlatformDefaults.cmake new file mode 100644 index 000000000..502d936b6 --- /dev/null +++ b/cmake/modules/DefinePlatformDefaults.cmake @@ -0,0 +1,28 @@ +# Set system vars + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(LINUX TRUE) +endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(FREEBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(OPENBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + set(NETBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + set(SOLARIS TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + +if (CMAKE_SYSTEM_NAME MATCHES "OS2") + set(OS2 TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OS2") diff --git a/cmake/modules/FindCMocka.cmake b/cmake/modules/FindCMocka.cmake new file mode 100644 index 000000000..2dd9fc5f8 --- /dev/null +++ b/cmake/modules/FindCMocka.cmake @@ -0,0 +1,49 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/cmake/modules/FindCsync.cmake b/cmake/modules/FindCsync.cmake deleted file mode 100644 index 1a5d37663..000000000 --- a/cmake/modules/FindCsync.cmake +++ /dev/null @@ -1,28 +0,0 @@ - -IF( DEFINED CSYNC_BUILD_PATH ) - SET(CSYNC_LIBRARY_PATH ${CSYNC_BUILD_PATH}) - SET(CSYNC_BINARY_DIR ${CSYNC_BUILD_PATH}) - - IF(WIN32) - SET(CSYNC_LIBRARY ${CSYNC_BUILD_PATH}/src/libocsync.dll) - ELSEIF( APPLE ) - SET(CSYNC_LIBRARY ${CSYNC_BUILD_PATH}/src/libocsync.dylib) - ELSE() - SET(CSYNC_LIBRARY ${CSYNC_BUILD_PATH}/src/libocsync.so) - ENDIF() -ELSE() - FIND_LIBRARY(CSYNC_LIBRARY NAMES ocsync HINTS $ENV{CSYNC_DIR}) -ENDIF() - -IF(NOT DEFINED CSYNC_INCLUDE_PATH) - FIND_PATH(CSYNC_INCLUDE_PATH NAMES csync.h HINTS $ENV{CSYNC_DIR} ) -ENDIF() - -SET(CSYNC_INCLUDE_DIR ${CSYNC_INCLUDE_PATH}) - -# handle the QUIETLY and REQUIRED arguments and set CSYNC_FOUND to TRUE if -# all listed variables are TRUE -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Csync DEFAULT_MSG CSYNC_LIBRARY CSYNC_INCLUDE_PATH) - -MARK_AS_ADVANCED( CSYNC_INCLUDE_PATH CSYNC_LIBRARY ) diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake new file mode 100644 index 000000000..46d99f058 --- /dev/null +++ b/cmake/modules/FindIconv.cmake @@ -0,0 +1,82 @@ +# - Try to find Iconv +# Once done this will define +# +# ICONV_FOUND - system has Iconv +# ICONV_INCLUDE_DIRS - the Iconv include directory +# ICONV_LIBRARIES - Link these to use Iconv +# ICONV_DEFINITIONS - Compiler switches required for using Iconv +# +# Copyright (c) 2013 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +include(CheckIncludeFile) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckPrototypeDefinition) + +find_path(ICONV_INCLUDE_DIR + NAMES + iconv.h sys/iconv.h +) + +set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) +check_include_file(iconv.h HAVE_ICONV_H) +check_include_file(sys/iconv.h HAVE_SYS_ICONV_H) +set(CMAKE_REQUIRED_INCLUDES) + +find_library(ICONV_LIBRARY + NAMES + iconv + libiconv + PATHS +) + +if (ICONV_LIBRARY) + get_filename_component(_ICONV_NAME ${ICONV_LIBRARY} NAME) + get_filename_component(_ICONV_PATH ${ICONV_LIBRARY} PATH) + check_library_exists(${_ICONV_NAME} iconv ${_ICONV_PATH} HAVE_ICONV) +else() + check_function_exists(iconv HAVE_ICONV) +endif() + +if (HAVE_ICONV_H OR HAVE_SYS_ICONV_H) + if (HAVE_ICONV_H) + set(_ICONV_PROTO_INCLUDE "iconv.h") + endif (HAVE_ICONV_H) + if (HAVE_SYS_ICONV_H) + set(_ICONV_PROTO_INCLUDE "sys/iconv.h") + endif (HAVE_SYS_ICONV_H) + + set(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) + check_prototype_definition(iconv + "size_t iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft)" + "-1" + ${_ICONV_PROTO_INCLUDE} + HAVE_ICONV_CONST) + set(CMAKE_REQUIRED_INCLUDES) +endif (HAVE_ICONV_H OR HAVE_SYS_ICONV_H) + +set(ICONV_INCLUDE_DIRS + ${ICONV_INCLUDE_DIR} +) + +if (ICONV_LIBRARY) + set(ICONV_LIBRARIES + ${ICONV_LIBRARIES} + ${ICONV_LIBRARY} + ) +endif (ICONV_LIBRARY) + +include(FindPackageHandleStandardArgs) +if (ICONV_LIBRARIES) + find_package_handle_standard_args(Iconv DEFAULT_MSG ICONV_LIBRARIES ICONV_INCLUDE_DIRS) +else() + find_package_handle_standard_args(Iconv DEFAULT_MSG ICONV_INCLUDE_DIRS) +endif() + +# show the ICONV_INCLUDE_DIRS and ICONV_LIBRARIES variables only in the advanced view +mark_as_advanced(ICONV_INCLUDE_DIRS ICONV_LIBRARIES) diff --git a/cmake/modules/FindLibSSH.cmake b/cmake/modules/FindLibSSH.cmake new file mode 100644 index 000000000..94dbf762b --- /dev/null +++ b/cmake/modules/FindLibSSH.cmake @@ -0,0 +1,96 @@ +# - Try to find LibSSH +# Once done this will define +# +# LIBSSH_FOUND - system has LibSSH +# LIBSSH_INCLUDE_DIRS - the LibSSH include directory +# LIBSSH_LIBRARIES - Link these to use LibSSH +# LIBSSH_DEFINITIONS - Compiler switches required for using LibSSH +# +# Copyright (c) 2009 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +if (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS) + # in cache already + set(LIBSSH_FOUND TRUE) +else (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS) + + find_path(LIBSSH_INCLUDE_DIR + NAMES + libssh/libssh.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ${CMAKE_INCLUDE_PATH} + ${CMAKE_INSTALL_PREFIX}/include + ) + + find_library(SSH_LIBRARY + NAMES + ssh + libssh + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib + ) + + if (LIBSSH_INCLUDE_DIR AND SSH_LIBRARY) + set(SSH_FOUND TRUE) + endif (LIBSSH_INCLUDE_DIR AND SSH_LIBRARY) + + set(LIBSSH_INCLUDE_DIRS + ${LIBSSH_INCLUDE_DIR} + ) + + if (SSH_FOUND) + set(LIBSSH_LIBRARIES + ${LIBSSH_LIBRARIES} + ${SSH_LIBRARY} + ) + + if (LibSSH_FIND_VERSION) + file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_MAJOR + REGEX "#define[ ]+LIBSSH_VERSION_MAJOR[ ]+[0-9]+") + # Older versions of libssh like libssh-0.2 have LIBSSH_VERSION but not LIBSSH_VERSION_MAJOR + if (LIBSSH_VERSION_MAJOR) + string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MAJOR ${LIBSSH_VERSION_MAJOR}) + file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_MINOR + REGEX "#define[ ]+LIBSSH_VERSION_MINOR[ ]+[0-9]+") + string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_MINOR ${LIBSSH_VERSION_MINOR}) + file(STRINGS ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h LIBSSH_VERSION_PATCH + REGEX "#define[ ]+LIBSSH_VERSION_MICRO[ ]+[0-9]+") + string(REGEX MATCH "[0-9]+" LIBSSH_VERSION_PATCH ${LIBSSH_VERSION_PATCH}) + + set(LibSSH_VERSION ${LIBSSH_VERSION_MAJOR}.${LIBSSH_VERSION_MINOR}.${LIBSSH_VERSION_PATCH}) + + include(FindPackageVersionCheck) + find_package_version_check(LibSSH DEFAULT_MSG) + else (LIBSSH_VERSION_MAJOR) + message(STATUS "LIBSSH_VERSION_MAJOR not found in ${LIBSSH_INCLUDE_DIR}/libssh/libssh.h, assuming libssh is too old") + set(LIBSSH_FOUND FALSE) + endif (LIBSSH_VERSION_MAJOR) + endif (LibSSH_FIND_VERSION) + endif (SSH_FOUND) + + # If the version is too old, but libs and includes are set, + # find_package_handle_standard_args will set LIBSSH_FOUND to TRUE again, + # so we need this if() here. + if (LIBSSH_FOUND) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibSSH DEFAULT_MSG LIBSSH_LIBRARIES LIBSSH_INCLUDE_DIRS) + endif (LIBSSH_FOUND) + + # show the LIBSSH_INCLUDE_DIRS and LIBSSH_LIBRARIES variables only in the advanced view + mark_as_advanced(LIBSSH_INCLUDE_DIRS LIBSSH_LIBRARIES) + +endif (LIBSSH_LIBRARIES AND LIBSSH_INCLUDE_DIRS) + diff --git a/cmake/modules/FindNeon.cmake b/cmake/modules/FindNeon.cmake index a02f093f9..26bf7c155 100644 --- a/cmake/modules/FindNeon.cmake +++ b/cmake/modules/FindNeon.cmake @@ -6,7 +6,7 @@ # NEON_LIBRARIES - Link these to use Neon # NEON_DEFINITIONS - Compiler switches required for using Neon # -# Copyright (c) 2011 Andreas Schneider +# Copyright (c) 2011-2013 Andreas Schneider # # Redistribution and use is allowed according to the terms of the New # BSD license. diff --git a/cmake/modules/FindOpenSSLCross.cmake b/cmake/modules/FindOpenSSLCross.cmake new file mode 100644 index 000000000..019171435 --- /dev/null +++ b/cmake/modules/FindOpenSSLCross.cmake @@ -0,0 +1,306 @@ +# - Try to find the OpenSSL encryption library +# Once done this will define +# +# OPENSSL_ROOT_DIR - Set this variable to the root installation of OpenSSL +# +# Read-Only variables: +# OPENSSL_FOUND - system has the OpenSSL library +# OPENSSL_INCLUDE_DIR - the OpenSSL include directory +# OPENSSL_LIBRARIES - The libraries needed to use OpenSSL +# OPENSSL_VERSION - This is set to $major.$minor.$revision$path (eg. 0.9.8s) + +#============================================================================= +# Copyright 2006-2009 Kitware, Inc. +# Copyright 2006 Alexander Neundorf +# Copyright 2009-2011 Mathieu Malaterre +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +if (UNIX) + find_package(PkgConfig QUIET) + pkg_check_modules(_OPENSSL QUIET openssl) +endif (UNIX) + +# http://www.slproweb.com/products/Win32OpenSSL.html +SET(_OPENSSL_ROOT_HINTS + $ENV{OPENSSL_ROOT_DIR} + ${OPENSSL_ROOT_DIR} + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" + ) +SET(_OPENSSL_ROOT_PATHS + "$ENV{PROGRAMFILES}/OpenSSL" + "$ENV{PROGRAMFILES}/OpenSSL-Win32" + "$ENV{PROGRAMFILES}/OpenSSL-Win64" + "C:/OpenSSL/" + "C:/OpenSSL-Win32/" + "C:/OpenSSL-Win64/" + ) +SET(_OPENSSL_ROOT_HINTS_AND_PATHS + HINTS ${_OPENSSL_ROOT_HINTS} + PATHS ${_OPENSSL_ROOT_PATHS} + ) + +FIND_PATH(OPENSSL_INCLUDE_DIR + NAMES + openssl/ssl.h + HINTS + ${_OPENSSL_INCLUDEDIR} + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +IF(WIN32 AND NOT CYGWIN) + # MINGW should go here too + IF(MSVC) + # /MD and /MDd are the standard values - if someone wants to use + # others, the libnames have to change here too + # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b + # TODO: handle /MT and static lib + # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: + # * MD for dynamic-release + # * MDd for dynamic-debug + # * MT for static-release + # * MTd for static-debug + + # Implementation details: + # We are using the libraries located in the VC subdir instead of the parent directory eventhough : + # libeay32MD.lib is identical to ../libeay32.lib, and + # ssleay32MD.lib is identical to ../ssleay32.lib + FIND_LIBRARY(LIB_EAY_DEBUG + NAMES + libeay32MDd + libeay32 + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "VC" + "lib/VC" + ) + + FIND_LIBRARY(LIB_EAY_RELEASE + NAMES + libeay32MD + libeay32 + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "VC" + "lib/VC" + ) + + FIND_LIBRARY(SSL_EAY_DEBUG + NAMES + ssleay32MDd + ssleay32 + ssl + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "VC" + "lib/VC" + ) + + FIND_LIBRARY(SSL_EAY_RELEASE + NAMES + ssleay32MD + ssleay32 + ssl + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "VC" + "lib/VC" + ) + + if( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) + set( OPENSSL_LIBRARIES + optimized ${SSL_EAY_RELEASE} debug ${SSL_EAY_DEBUG} + optimized ${LIB_EAY_RELEASE} debug ${LIB_EAY_DEBUG} + ) + else() + set( OPENSSL_LIBRARIES ${SSL_EAY_RELEASE} ${LIB_EAY_RELEASE} ) + endif() + MARK_AS_ADVANCED(SSL_EAY_DEBUG SSL_EAY_RELEASE) + MARK_AS_ADVANCED(LIB_EAY_DEBUG LIB_EAY_RELEASE) + ELSEIF(MINGW) + # same player, for MingW + FIND_LIBRARY(LIB_EAY + NAMES + libeay32 + crypto + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "lib/MinGW" + ) + + FIND_LIBRARY(SSL_EAY + NAMES + ssleay32 + ssl + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + "lib" + "lib/MinGW" + ) + + MARK_AS_ADVANCED(SSL_EAY LIB_EAY) + set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) + ELSE(MSVC) + # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: + FIND_LIBRARY(LIB_EAY + NAMES + libeay32 + HINTS + ${_OPENSSL_LIBDIR} + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + ) + + FIND_LIBRARY(SSL_EAY + NAMES + ssleay32 + HINTS + ${_OPENSSL_LIBDIR} + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + ) + + MARK_AS_ADVANCED(SSL_EAY LIB_EAY) + set( OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY} ) + ENDIF(MSVC) +ELSE(WIN32 AND NOT CYGWIN) + + FIND_LIBRARY(OPENSSL_SSL_LIBRARY + NAMES + ssl + ssleay32 + ssleay32MD + HINTS + ${_OPENSSL_LIBDIR} + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + ) + + FIND_LIBRARY(OPENSSL_CRYPTO_LIBRARY + NAMES + crypto + HINTS + ${_OPENSSL_LIBDIR} + ${_OPENSSL_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + ) + + MARK_AS_ADVANCED(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY) + + # compat defines + SET(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY}) + SET(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) + + SET(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) + +ENDIF(WIN32 AND NOT CYGWIN) + +function(from_hex HEX DEC) + string(TOUPPER "${HEX}" HEX) + set(_res 0) + string(LENGTH "${HEX}" _strlen) + + while (_strlen GREATER 0) + math(EXPR _res "${_res} * 16") + string(SUBSTRING "${HEX}" 0 1 NIBBLE) + string(SUBSTRING "${HEX}" 1 -1 HEX) + if (NIBBLE STREQUAL "A") + math(EXPR _res "${_res} + 10") + elseif (NIBBLE STREQUAL "B") + math(EXPR _res "${_res} + 11") + elseif (NIBBLE STREQUAL "C") + math(EXPR _res "${_res} + 12") + elseif (NIBBLE STREQUAL "D") + math(EXPR _res "${_res} + 13") + elseif (NIBBLE STREQUAL "E") + math(EXPR _res "${_res} + 14") + elseif (NIBBLE STREQUAL "F") + math(EXPR _res "${_res} + 15") + else() + math(EXPR _res "${_res} + ${NIBBLE}") + endif() + + string(LENGTH "${HEX}" _strlen) + endwhile() + + set(${DEC} ${_res} PARENT_SCOPE) +endfunction(from_hex) + +if (OPENSSL_INCLUDE_DIR) + if (_OPENSSL_VERSION) + set(OPENSSL_VERSION "${_OPENSSL_VERSION}") + elseif(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h") + file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str + REGEX "^#define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*") + + # The version number is encoded as 0xMNNFFPPS: major minor fix patch status + # The status gives if this is a developer or prerelease and is ignored here. + # Major, minor, and fix directly translate into the version numbers shown in + # the string. The patch field translates to the single character suffix that + # indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so + # on. + + string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$" + "\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}") + list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR) + list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR) + from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR) + list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX) + from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX) + list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH) + + if (NOT OPENSSL_VERSION_PATCH STREQUAL "00") + from_hex("${OPENSSL_VERSION_PATCH}" _tmp) + # 96 is the ASCII code of 'a' minus 1 + math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96") + unset(_tmp) + # Once anyone knows how OpenSSL would call the patch versions beyond 'z' + # this should be updated to handle that, too. This has not happened yet + # so it is simply ignored here for now. + string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING) + endif (NOT OPENSSL_VERSION_PATCH STREQUAL "00") + + set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}") + endif (_OPENSSL_VERSION) +endif (OPENSSL_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) + +if (OPENSSL_VERSION) + find_package_handle_standard_args(OpenSSL + REQUIRED_VARS + OPENSSL_LIBRARIES + OPENSSL_INCLUDE_DIR + VERSION_VAR + OPENSSL_VERSION + FAIL_MESSAGE + "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" + ) +else (OPENSSL_VERSION) + find_package_handle_standard_args(OpenSSL "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" + OPENSSL_LIBRARIES + OPENSSL_INCLUDE_DIR + ) +endif (OPENSSL_VERSION) + +MARK_AS_ADVANCED(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES) diff --git a/cmake/modules/FindPackageVersionCheck.cmake b/cmake/modules/FindPackageVersionCheck.cmake new file mode 100644 index 000000000..f5ac3cf46 --- /dev/null +++ b/cmake/modules/FindPackageVersionCheck.cmake @@ -0,0 +1,68 @@ +# FIND_PACKAGE_VERSION_CHECK(NAME (DEFAULT_MSG|"Custom failure message")) +# This function is intended to be used in FindXXX.cmake modules files. +# It handles NAME_FIND_VERSION and NAME_VERSION variables in a Module. +# +# Example: +# find_package(LibSSH 0.3.2) +# +# # check for the version and set it +# set(LibSSH_VERSION 0.3.0) +# find_package_version_check(LibSSH DEFAULT_MSG) +# +# +# Copyright (c) 2009 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. + +function(FIND_PACKAGE_VERSION_CHECK _NAME _FAIL_MSG) + string(TOUPPER ${_NAME} _NAME_UPPER) + set(_AGE "old") + + if(${_NAME}_FIND_VERSION_EXACT) + if (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + # exact version found + set(${_NAME_UPPER}_FOUND TRUE) + else (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + # exect version not found + set(${_NAME_UPPER}_FOUND FALSE) + # check if newer or older + if (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + set(_AGE "new") + else (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + set(_AGE "old") + endif (${_NAME}_FIND_VERSION VERSION_LESS ${_NAME}_VERSION) + endif (${_NAME}_FIND_VERSION VERSION_EQUAL ${_NAME}_VERSION) + else (${_NAME}_FIND_VERSION_EXACT) + if (${_NAME}_FIND_VERSION) + if (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + set(${_NAME_UPPER}_FOUND FALSE) + set(_AGE "old") + else (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + set(${_NAME_UPPER}_FOUND TRUE) + endif (${_NAME}_VERSION VERSION_LESS ${_NAME}_FIND_VERSION) + endif (${_NAME}_FIND_VERSION) + endif(${_NAME}_FIND_VERSION_EXACT) + + if ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + if (${_NAME}_FIND_VERSION_EXACT) + set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, version ${${_NAME}_FIND_VERSION} is required.") + else (${_NAME}_FIND_VERSION_EXACT) + set(_FAIL_MESSAGE "The installed ${_NAME} version ${${_NAME}_VERSION} is too ${_AGE}, at least version ${${_NAME}_FIND_VERSION} is required.") + endif (${_NAME}_FIND_VERSION_EXACT) + else ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + set(_FAIL_MESSAGE "${_FAIL_MSG}") + endif ("${_FAIL_MSG}" STREQUAL "DEFAULT_MSG") + + if (NOT ${_NAME_UPPER}_FOUND) + if (${_NAME}_FIND_REQUIRED) + message(FATAL_ERROR "${_FAIL_MESSAGE}") + else (${_NAME}_FIND_REQUIRED) + if (NOT ${_NAME}_FIND_QUIETLY) + message(STATUS "${_FAIL_MESSAGE}") + endif (NOT ${_NAME}_FIND_QUIETLY) + endif (${_NAME}_FIND_REQUIRED) + endif (NOT ${_NAME_UPPER}_FOUND) + + set(${_NAME_UPPER}_FOUND ${${_NAME_UPPER}_FOUND} PARENT_SCOPE) +endfunction(FIND_PACKAGE_VERSION_CHECK) diff --git a/cmake/modules/FindSMBClient.cmake b/cmake/modules/FindSMBClient.cmake new file mode 100644 index 000000000..730498edd --- /dev/null +++ b/cmake/modules/FindSMBClient.cmake @@ -0,0 +1,66 @@ +# - Try to find SMBClient +# Once done this will define +# +# SMBCLIENT_FOUND - system has SMBClient +# SMBCLIENT_INCLUDE_DIRS - the SMBClient include directory +# SMBCLIENT_LIBRARIES - Link these to use SMBClient +# SMBCLIENT_DEFINITIONS - Compiler switches required for using SMBClient +# +# Copyright (c) 2013 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) + # in cache already + set(SMBCLIENT_FOUND TRUE) +else (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_SMBCLIENT smbclient) + endif (PKG_CONFIG_FOUND) + + find_path(SMBCLIENT_INCLUDE_DIR + NAMES + libsmbclient.h + PATHS + ${_SMBCLIENT_INCLUDEDIR} + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(SMBCLIENT_LIBRARY + NAMES + smbclient + PATHS + ${_SMBCLIENT_LIBDIR} + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(SMBCLIENT_INCLUDE_DIRS + ${SMBCLIENT_INCLUDE_DIR} + ) + + if (SMBCLIENT_LIBRARY) + set(SMBCLIENT_LIBRARIES + ${SMBCLIENT_LIBRARIES} + ${SMBCLIENT_LIBRARY} + ) + endif (SMBCLIENT_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(SMBClient DEFAULT_MSG SMBCLIENT_LIBRARIES SMBCLIENT_INCLUDE_DIRS) + + # show the SMBCLIENT_INCLUDE_DIRS and SMBCLIENT_LIBRARIES variables only in the advanced view + mark_as_advanced(SMBCLIENT_INCLUDE_DIRS SMBCLIENT_LIBRARIES) + +endif (SMBCLIENT_LIBRARIES AND SMBCLIENT_INCLUDE_DIRS) + diff --git a/cmake/modules/FindSQLite3.cmake b/cmake/modules/FindSQLite3.cmake new file mode 100644 index 000000000..e34af6d8a --- /dev/null +++ b/cmake/modules/FindSQLite3.cmake @@ -0,0 +1,58 @@ +# - Try to find SQLite3 +# Once done this will define +# +# SQLITE3_FOUND - system has SQLite3 +# SQLITE3_INCLUDE_DIRS - the SQLite3 include directory +# SQLITE3_LIBRARIES - Link these to use SQLite3 +# SQLITE3_DEFINITIONS - Compiler switches required for using SQLite3 +# +# Copyright (c) 2009-2013 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (UNIX) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_SQLITE3 sqlite3) + endif (PKG_CONFIG_FOUND) +endif (UNIX) + +find_path(SQLITE3_INCLUDE_DIR + NAMES + sqlite3.h + PATHS + ${_SQLITE3_INCLUDEDIR} +) + +find_library(SQLITE3_LIBRARY + NAMES + sqlite3 + PATHS + ${_SQLITE3_LIBDIR} +) + +set(SQLITE3_INCLUDE_DIRS + ${SQLITE3_INCLUDE_DIR} +) + +if (SQLITE3_LIBRARY) + set(SQLITE3_LIBRARIES + ${SQLITE3_LIBRARIES} + ${SQLITE3_LIBRARY} + ) +endif (SQLITE3_LIBRARY) + +if (SQLite3_FIND_VERSION AND _SQLITE3_VERSION) + set(SQLite3_VERSION _SQLITE3_VERSION) +endif (SQLite3_FIND_VERSION AND _SQLITE3_VERSION) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLITE3_LIBRARIES SQLITE3_INCLUDE_DIRS) + +# show the SQLITE3_INCLUDE_DIRS and SQLITE3_LIBRARIES variables only in the advanced view +mark_as_advanced(SQLITE3_INCLUDE_DIRS SQLITE3_LIBRARIES) + diff --git a/cmake/modules/MacroAddCompileFlags.cmake b/cmake/modules/MacroAddCompileFlags.cmake new file mode 100644 index 000000000..8c5129609 --- /dev/null +++ b/cmake/modules/MacroAddCompileFlags.cmake @@ -0,0 +1,21 @@ +# - MACRO_ADD_COMPILE_FLAGS(target_name flag1 ... flagN) + +# Copyright (c) 2006, Oswald Buddenhagen, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +macro (MACRO_ADD_COMPILE_FLAGS _target) + + get_target_property(_flags ${_target} COMPILE_FLAGS) + if (_flags) + set(_flags ${_flags} ${ARGN}) + else (_flags) + set(_flags ${ARGN}) + endif (_flags) + + set_target_properties(${_target} PROPERTIES COMPILE_FLAGS ${_flags}) + +endmacro (MACRO_ADD_COMPILE_FLAGS) diff --git a/cmake/modules/MacroAddLinkFlags.cmake b/cmake/modules/MacroAddLinkFlags.cmake new file mode 100644 index 000000000..eaef15401 --- /dev/null +++ b/cmake/modules/MacroAddLinkFlags.cmake @@ -0,0 +1,20 @@ +# - MACRO_ADD_LINK_FLAGS(target_name flag1 ... flagN) + +# Copyright (c) 2006, Oswald Buddenhagen, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ADD_LINK_FLAGS _target) + + get_target_property(_flags ${_target} LINK_FLAGS) + if (_flags) + set(_flags "${_flags} ${ARGN}") + else (_flags) + set(_flags "${ARGN}") + endif (_flags) + + set_target_properties(${_target} PROPERTIES LINK_FLAGS "${_flags}") + +endmacro (MACRO_ADD_LINK_FLAGS) diff --git a/cmake/modules/MacroAddPlugin.cmake b/cmake/modules/MacroAddPlugin.cmake new file mode 100644 index 000000000..8ddf8d689 --- /dev/null +++ b/cmake/modules/MacroAddPlugin.cmake @@ -0,0 +1,30 @@ +# - MACRO_ADD_PLUGIN(name [WITH_PREFIX] file1 .. fileN) +# +# Create a plugin from the given source files. +# If WITH_PREFIX is given, the resulting plugin will have the +# prefix "lib", otherwise it won't. +# +# Copyright (c) 2006, Alexander Neundorf, +# Copyright (c) 2006, Laurent Montel, +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +macro (MACRO_ADD_PLUGIN _target_NAME _with_PREFIX) + + if (${_with_PREFIX} STREQUAL "WITH_PREFIX") + set(_first_SRC) + else (${_with_PREFIX} STREQUAL "WITH_PREFIX") + set(_first_SRC ${_with_PREFIX}) + endif (${_with_PREFIX} STREQUAL "WITH_PREFIX") + + add_library(${_target_NAME} MODULE ${_first_SRC} ${ARGN}) + + if (_first_SRC) + set_target_properties(${_target_NAME} PROPERTIES PREFIX "") + endif (_first_SRC) + +endmacro (MACRO_ADD_PLUGIN _name _sources) + diff --git a/cmake/modules/MacroCopyFile.cmake b/cmake/modules/MacroCopyFile.cmake new file mode 100644 index 000000000..72bfd0ded --- /dev/null +++ b/cmake/modules/MacroCopyFile.cmake @@ -0,0 +1,33 @@ +# - macro_copy_file(_src _dst) +# Copies a file to ${_dst} only if ${_src} is different (newer) than ${_dst} +# +# Example: +# macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/.) +# Copies file icon.png to ${CMAKE_CURRENT_BINARY_DIR} directory +# +# Copyright (c) 2006-2007 Wengo +# Copyright (c) 2006-2008 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING file. + + +macro (macro_copy_file _src _dst) + # Removes all path containing .svn or CVS or CMakeLists.txt during the copy + if (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") + + if (CMAKE_VERBOSE_MAKEFILE) + message(STATUS "Copy file from ${_src} to ${_dst}") + endif (CMAKE_VERBOSE_MAKEFILE) + + # Creates directory if necessary + get_filename_component(_path ${_dst} PATH) + file(MAKE_DIRECTORY ${_path}) + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${_src} ${_dst} + OUTPUT_QUIET + ) + endif (NOT ${_src} MATCHES ".*\\.svn|CVS|CMakeLists\\.txt.*") +endmacro (macro_copy_file) diff --git a/cmake/modules/MacroEnsureOutOfSourceBuild.cmake b/cmake/modules/MacroEnsureOutOfSourceBuild.cmake new file mode 100644 index 000000000..a2e948099 --- /dev/null +++ b/cmake/modules/MacroEnsureOutOfSourceBuild.cmake @@ -0,0 +1,17 @@ +# - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() +# MACRO_ENSURE_OUT_OF_SOURCE_BUILD() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) + + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) + if (_insource) + message(SEND_ERROR "${_errorMessage}") + message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") + endif (_insource) + +endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) diff --git a/cmake/modules/UseAsciidoc.cmake b/cmake/modules/UseAsciidoc.cmake new file mode 100644 index 000000000..42024b29e --- /dev/null +++ b/cmake/modules/UseAsciidoc.cmake @@ -0,0 +1,58 @@ +# - macro_asciidoc2man(inputfile outputfile) +# +# Create a manpage with asciidoc. +# Example: macro_asciidoc2man(foo.txt foo.1) +# +# Copyright (c) 2006, Andreas Schneider, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(MacroCopyFile) + +macro(MACRO_ASCIIDOC2MAN _a2m_input _a2m_output) + find_program(A2X + NAMES + a2x + ) + #message("+++ A2X: ${A2X}") + + if (A2X) + + #message("+++ ${A2X} --doctype=manpage --format=manpage --destination-dir=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${_a2m_input}") + macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/${_a2m_input} ${CMAKE_CURRENT_BINARY_DIR}/${_a2m_input}) + + execute_process( + COMMAND + ${A2X} --doctype=manpage --format=manpage ${_a2m_input} + WORKING_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR} + RESULT_VARIABLE + A2M_MAN_GENERATED + ERROR_QUIET + ) + + #message("+++ A2M_MAN_GENERATED: ${A2M_MAN_GENERATED}") + if (A2M_MAN_GENERATED EQUAL 0) + find_file(A2M_MAN_FILE + NAME + ${_a2m_output} + PATHS + ${CMAKE_CURRENT_BINARY_DIR} + NO_DEFAULT_PATH + ) + + if (A2M_MAN_FILE) + get_filename_component(A2M_MAN_CATEGORY ${A2M_MAN_FILE} EXT) + string(SUBSTRING ${A2M_MAN_CATEGORY} 1 1 A2M_MAN_CATEGORY) + install( + FILES + ${A2M_MAN_FILE} + DESTINATION + ${MAN_INSTALL_DIR}/man${A2M_MAN_CATEGORY} + ) + endif (A2M_MAN_FILE) + endif (A2M_MAN_GENERATED EQUAL 0) + + endif (A2X) +endmacro(MACRO_ASCIIDOC2MAN _a2m_input _a2m_file) diff --git a/cmake/modules/UseDoxygen.cmake b/cmake/modules/UseDoxygen.cmake new file mode 100644 index 000000000..3eb94906b --- /dev/null +++ b/cmake/modules/UseDoxygen.cmake @@ -0,0 +1,127 @@ +# -helper macro to add a "doc" target with CMake build system. +# and configure doxy.config.in to doxy.config +# +# target "doc" allows building the documentation with doxygen/dot on WIN32 and Linux +# Creates .chm windows help file if MS HTML help workshop +# (available from http://msdn.microsoft.com/workshop/author/htmlhelp) +# is installed with its DLLs in PATH. +# +# +# Please note, that the tools, e.g.: +# doxygen, dot, latex, dvips, makeindex, gswin32, etc. +# must be in path. +# +# Note about Visual Studio Projects: +# MSVS has its own path environment which may differ from the shell. +# See "Menu Tools/Options/Projects/VC++ Directories" in VS 7.1 +# +# author Jan Woetzel 2004-2006 +# www.mip.informatik.uni-kiel.de/~jw + + +FIND_PACKAGE(Doxygen) + +IF (DOXYGEN_FOUND) + + # click+jump in Emacs and Visual Studio (for doxy.config) (jw) + IF (CMAKE_BUILD_TOOL MATCHES "(msdev|devenv)") + SET(DOXY_WARN_FORMAT "\"$file($line) : $text \"") + ELSE (CMAKE_BUILD_TOOL MATCHES "(msdev|devenv)") + SET(DOXY_WARN_FORMAT "\"$file:$line: $text \"") + ENDIF (CMAKE_BUILD_TOOL MATCHES "(msdev|devenv)") + + # we need latex for doxygen because of the formulas + FIND_PACKAGE(LATEX) + IF (NOT LATEX_COMPILER) + MESSAGE(STATUS "latex command LATEX_COMPILER not found but usually required. You will probably get warnings and user inetraction on doxy run.") + ENDIF (NOT LATEX_COMPILER) + IF (NOT MAKEINDEX_COMPILER) + MESSAGE(STATUS "makeindex command MAKEINDEX_COMPILER not found but usually required.") + ENDIF (NOT MAKEINDEX_COMPILER) + IF (NOT DVIPS_CONVERTER) + MESSAGE(STATUS "dvips command DVIPS_CONVERTER not found but usually required.") + ENDIF (NOT DVIPS_CONVERTER) + FIND_PROGRAM(DOXYGEN_DOT_EXECUTABLE_PATH NAMES dot) + IF (DOXYGEN_DOT_EXECUTABLE_PATH) + SET(DOXYGEN_DOT_FOUND "YES") + ENDIF (DOXYGEN_DOT_EXECUTABLE_PATH) + + IF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config.in") + MESSAGE(STATUS "Generate ${CMAKE_CURRENT_BINARY_DIR}/doxy.config from doxy.config.in") + CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/doxy.config.in + ${CMAKE_CURRENT_BINARY_DIR}/doxy.config + @ONLY ) + # use (configured) doxy.config from (out of place) BUILD tree: + SET(DOXY_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/doxy.config") + ELSE (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config.in") + # use static hand-edited doxy.config from SOURCE tree: + SET(DOXY_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config") + IF (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config") + MESSAGE(STATUS "WARNING: using existing ${CMAKE_CURRENT_SOURCE_DIR}/doxy.config instead of configuring from doxy.config.in file.") + ELSE (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config") + IF (EXISTS "${CMAKE_MODULE_PATH}/doxy.config.in") + # using template doxy.config.in + MESSAGE(STATUS "Generate ${CMAKE_CURRENT_BINARY_DIR}/doxy.config from doxy.config.in") + CONFIGURE_FILE(${CMAKE_MODULE_PATH}/doxy.config.in + ${CMAKE_CURRENT_BINARY_DIR}/doxy.config + @ONLY ) + SET(DOXY_CONFIG "${CMAKE_CURRENT_BINARY_DIR}/doxy.config") + ELSE (EXISTS "${CMAKE_MODULE_PATH}/doxy.config.in") + # failed completely... + MESSAGE(SEND_ERROR "Please create ${CMAKE_CURRENT_SOURCE_DIR}/doxy.config.in (or doxy.config as fallback)") + ENDIF(EXISTS "${CMAKE_MODULE_PATH}/doxy.config.in") + + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config") + ENDIF(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/doxy.config.in") + + ADD_CUSTOM_TARGET(doc ${DOXYGEN_EXECUTABLE} ${DOXY_CONFIG} DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/doxy.config) + + # create a windows help .chm file using hhc.exe + # HTMLHelp DLL must be in path! + # fallback: use hhw.exe interactively + IF (WIN32) + FIND_PACKAGE(HTMLHelp) + IF (HTML_HELP_COMPILER) + SET (TMP "${CMAKE_CURRENT_BINARY_DIR}\\doc\\html\\index.hhp") + STRING(REGEX REPLACE "[/]" "\\\\" HHP_FILE ${TMP} ) + # MESSAGE(SEND_ERROR "DBG HHP_FILE=${HHP_FILE}") + ADD_CUSTOM_TARGET(winhelp ${HTML_HELP_COMPILER} ${HHP_FILE}) + ADD_DEPENDENCIES (winhelp doc) + + IF (NOT TARGET_DOC_SKIP_INSTALL) + # install windows help? + # determine useful name for output file + # should be project and version unique to allow installing + # multiple projects into one global directory + IF (EXISTS "${PROJECT_BINARY_DIR}/doc/html/index.chm") + IF (PROJECT_NAME) + SET(OUT "${PROJECT_NAME}") + ELSE (PROJECT_NAME) + SET(OUT "Documentation") # default + ENDIF(PROJECT_NAME) + IF (${PROJECT_NAME}_VERSION_MAJOR) + SET(OUT "${OUT}-${${PROJECT_NAME}_VERSION_MAJOR}") + IF (${PROJECT_NAME}_VERSION_MINOR) + SET(OUT "${OUT}.${${PROJECT_NAME}_VERSION_MINOR}") + IF (${PROJECT_NAME}_VERSION_PATCH) + SET(OUT "${OUT}.${${PROJECT_NAME}_VERSION_PATCH}") + ENDIF(${PROJECT_NAME}_VERSION_PATCH) + ENDIF(${PROJECT_NAME}_VERSION_MINOR) + ENDIF(${PROJECT_NAME}_VERSION_MAJOR) + # keep suffix + SET(OUT "${OUT}.chm") + + #MESSAGE("DBG ${PROJECT_BINARY_DIR}/doc/html/index.chm \n${OUT}") + # create target used by install and package commands + INSTALL(FILES "${PROJECT_BINARY_DIR}/doc/html/index.chm" + DESTINATION "doc" + RENAME "${OUT}" + ) + ENDIF(EXISTS "${PROJECT_BINARY_DIR}/doc/html/index.chm") + ENDIF(NOT TARGET_DOC_SKIP_INSTALL) + + ENDIF(HTML_HELP_COMPILER) + # MESSAGE(SEND_ERROR "HTML_HELP_COMPILER=${HTML_HELP_COMPILER}") + ENDIF (WIN32) +ENDIF(DOXYGEN_FOUND) + diff --git a/cmake/scripts/generate_findpackage_file b/cmake/scripts/generate_findpackage_file new file mode 100644 index 000000000..a29d2d8e0 --- /dev/null +++ b/cmake/scripts/generate_findpackage_file @@ -0,0 +1,154 @@ +#!/usr/bin/env ruby + +# Simple script to generate simple cmake modules for finding +# libraries (packages) +# +# usage: generate_findpackage_file +# then you will be prompted to enter the required parameters +# +##################################################################### +# +# Copyright (c) 2006 Alexander Neundorf +# Copyright (c) 2006 Andreas Schneider +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +# + +require 'readline' + +package=Readline.readline("Name of package: ") + +name=Readline.readline("\nYour Name (for copyright): ") + +email=Readline.readline("\nYour mail (for copyright): ") + +pkgconfig=Readline.readline("\npkgconfig package name (e.g. \"libxml-2.0\", leave empty to skip pkgconfig): ") + +header=Readline.readline("\nLook for header (e.g. \"jpeglib.h\" or \"libxml/xpath.h\"): ") + +incSubDir=Readline.readline("\nLook for header subdir (e.g. \"libxml2\", empty to skip ): ") + +libs=Readline.readline("\nLook for library (e.g. \"xml2\" or \"avcodec avutil\"): ") + +t = Time.now + +cmakeIncDirName=package.upcase+"_INCLUDE_DIR" +cmakeIncDirNames=package.upcase+"_INCLUDE_DIRS" +cmakeLibNames=package.upcase+"_LIBRARIES" +cmakeDefsName=package.upcase+"_DEFINITIONS" +cmakeFoundName=package.upcase+"_FOUND" +cmakeQuietName=package+"_FIND_QUIETLY" +cmakeRequiredName=package+"_FIND_REQUIRED" + +file=File.new("Find#{package}.cmake", "w+") + + +file.printf("# - Try to find #{package}\n") +file.printf("# Once done this will define\n") +file.printf("#\n") +file.printf("# #{cmakeFoundName} - system has #{package}\n") +file.printf("# #{cmakeIncDirNames} - the #{package} include directory\n") +file.printf("# #{cmakeLibNames} - Link these to use #{package}\n") +file.printf("# #{cmakeDefsName} - Compiler switches required for using #{package}\n") +file.printf("#\n") +file.printf("# Copyright (c) #{t.year} #{name} <#{email}>\n") +file.printf("#\n") +file.printf("# Redistribution and use is allowed according to the terms of the New\n") +file.printf("# BSD license.\n") +file.printf("# For details see the accompanying COPYING-CMAKE-SCRIPTS file.\n") +file.printf("#\n") + +file.printf("\n") +file.printf("\n") + +file.printf("if (#{cmakeLibNames} AND #{cmakeIncDirNames})\n") +file.printf(" # in cache already\n") +file.printf(" set(#{cmakeFoundName} TRUE)\n") +file.printf("else (#{cmakeLibNames} AND #{cmakeIncDirNames})\n") + +if not pkgconfig.empty? + file.printf(" find_package(PkgConfig)\n") + file.printf(" if (PKG_CONFIG_FOUND)\n") + file.printf(" pkg_check_modules(_#{package.upcase} #{pkgconfig})\n") + file.printf(" endif (PKG_CONFIG_FOUND)\n") +end + +file.printf("\n") + +file.printf(" find_path(#{cmakeIncDirName}\n") +file.printf(" NAMES\n") +file.printf(" #{header}\n") +file.printf(" PATHS\n") +if not pkgconfig.empty? + file.printf(" ${_#{package.upcase}_INCLUDEDIR}\n") +end +file.printf(" /usr/include\n") +file.printf(" /usr/local/include\n") +file.printf(" /opt/local/include\n") +file.printf(" /sw/include\n") + +if not incSubDir.empty? + file.printf(" PATH_SUFFIXES\n") + file.printf(" #{incSubDir}\n") +end +file.printf(" )\n") + +file.printf("\n") + +libs.split(" ").each do |lib| + file.printf(" find_library(#{lib.upcase}_LIBRARY\n") + file.printf(" NAMES\n") + file.printf(" #{lib}\n") + file.printf(" PATHS\n") + if not pkgconfig.empty? + file.printf(" ${_#{package.upcase}_LIBDIR}\n") + end + file.printf(" /usr/lib\n") + file.printf(" /usr/local/lib\n") + file.printf(" /opt/local/lib\n") + file.printf(" /sw/lib\n") + file.printf(" )\n") + file.printf("\n") +end + +file.printf(" set(#{cmakeIncDirNames}\n") +file.printf(" ${#{cmakeIncDirName}}\n") +file.printf(" )\n") + +file.printf("\n") + +libs.split(" ").each do |lib| + file.printf(" if (#{lib.upcase}_LIBRARY)\n") + file.printf(" set(#{cmakeLibNames}\n") + file.printf(" ${#{cmakeLibNames}}\n") + file.printf(" ${#{lib.upcase}_LIBRARY}\n") + file.printf(" )\n") + file.printf(" endif (#{lib.upcase}_LIBRARY)\n") + file.printf("\n") +end + +file.printf(" include(FindPackageHandleStandardArgs)\n") +file.printf(" find_package_handle_standard_args(#{package} DEFAULT_MSG #{cmakeLibNames} #{cmakeIncDirNames})\n") + +file.printf("\n") + +file.printf(" # show the #{cmakeIncDirNames} and #{cmakeLibNames} variables only in the advanced view\n") +file.printf(" mark_as_advanced(#{cmakeIncDirNames} #{cmakeLibNames})\n\n") + +file.printf("endif (#{cmakeLibNames} AND #{cmakeIncDirNames})\n\n") + +printf("Done, generated Find#{package}.cmake\n") + diff --git a/cmake/scripts/generate_lib_file b/cmake/scripts/generate_lib_file new file mode 100644 index 000000000..e3252bd7c --- /dev/null +++ b/cmake/scripts/generate_lib_file @@ -0,0 +1,135 @@ +#!/usr/bin/env ruby + +# simple script to generate CMakeLists.txt for wengophone libs +# +# usage: generate_lib_file +# then you will be prompted to enter the required parameters +# +##################################################################### +# +# Copyright (c) 2006 Andreas Schneider +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +# + +print("Name of project: ") +project=gets.chomp + +printf("\n") + +print("Other projects to includes (e.g. \"owutil tinyxml\", leave emtpy to skip): ") +otherprojects=gets.chomp + +printf("\n") + +print("Defininitions (leave empty to skip): ") +definitions=gets.chomp + +cmakePublicIncDirName = project.upcase+"_PUBLIC_INCLUDE_DIRS" +cmakePrivateIncDirName = project.upcase+"_PRIVATE_INCLUDE_DIRS" +cmakeLibName = project.upcase+"_LIBRARY" +cmakeLibNames = project.upcase+"_LINK_LIBRARIES" +cmakePublicDefsName = project.upcase+"_PUBLIC_DEFINITIONS" +cmakePrivateDefsName = project.upcase+"_PRIVATE_DEFINITIONS" + +file=File.new("CMakeLists.txt", "w+") + +file.printf("project(#{project})\n") +file.printf("\n") +file.printf("# needed include directories to build #{project}\n") +file.printf("# saves the variable in internal cache for later use\n") +file.printf("set(#{cmakePublicIncDirName}\n") +file.printf(" ${CMAKE_CURRENT_SOURCE_DIR}\n") +file.printf(" ${CMAKE_CURRENT_SOURCE_DIR}/include\n") +file.printf(" CACHE INTERNAL \"#{project} public include directories\"\n") +file.printf(")\n") + +file.printf("\n") + +file.printf("set(#{cmakePrivateIncDirName}\n") +otherprojects.split(" ").each do |otherproject| + file.printf(" ${#{otherproject.upcase}_PUBLIC_INCLUDE_DIRS}\n") +end +file.printf(" ${CMAKE_CURRENT_BINARY_DIR}\n") +file.printf(")\n") + +file.printf("\n") + +file.printf("set(#{cmakeLibName}\n") +file.printf(" #{project}\n") +file.printf(" CACHE INTERNAL \"#{project} library\"\n") +file.printf(")\n") + +file.printf("\n") + +file.printf("# #{project} lib and dependencies\n") +file.printf("set(#{cmakeLibNames}\n") +file.printf(" #{cmakeLibName}\n") +otherprojects.split(" ").each do |otherproject| + file.printf(" ${#{otherproject.upcase}_LIBRARIES}\n") +end +file.printf(")\n") + +file.printf("\n") + +if not definitions.empty? + file.printf("set(#{cmakePublicDefsName}\n") + file.printf(" #{definitions}\n") + file.printf(" CACHE INTERNAL \"#{project} public definitions\"\n") + file.printf(")\n") + + file.printf("\n") + + file.printf("set(#{cmakePrivateDefsName}\n") + file.printf(" #{definitions}\n") + file.printf(")\n") + + file.printf("\n") +end + +file.printf("set(#{project}_SRCS\n") +file.printf(" files.c\n") +file.printf(")\n") + +file.printf("\n") + +file.printf("include_directories(\n") +file.printf(" ${#{cmakePublicIncDirName}}\n") +file.printf(" ${#{cmakePrivateIncDirName}}\n") +file.printf(")\n") + +file.printf("\n") + +if not definitions.empty? + file.printf("add_definitions(\n") + file.printf(" ${#{cmakePublicDefsName}}\n") + file.printf(" ${#{cmakePrivateDefsName}}\n") + file.printf(")\n") + + file.printf("\n") +end + +file.printf("\n") + +file.printf("add_library(${#{cmakeLibName}} STATIC ${#{project}_SRCS})\n") + +file.printf("\n") + +file.printf("target_link_libraries(${#{cmakeLibNames}})\n") + +file.printf("\n") + +printf("Generated CMakeLists.txt for #{project}\n") + diff --git a/csync/AUTHORS b/csync/AUTHORS new file mode 100644 index 000000000..5fd81c08e --- /dev/null +++ b/csync/AUTHORS @@ -0,0 +1,3 @@ +Andreas Schneider +Klaas Freitag +Olivier Goffart diff --git a/csync/CMakeLists.txt b/csync/CMakeLists.txt new file mode 100644 index 000000000..d43a9dc9c --- /dev/null +++ b/csync/CMakeLists.txt @@ -0,0 +1,59 @@ + + +# global needed variables +set(APPLICATION_NAME "ocsync") + +set(APPLICATION_VERSION_MAJOR "0") +set(APPLICATION_VERSION_MINOR "91") +set(APPLICATION_VERSION_PATCH "4") + +set(APPLICATION_VERSION "${APPLICATION_VERSION_MAJOR}.${APPLICATION_VERSION_MINOR}.${APPLICATION_VERSION_PATCH}") + +set(LIBRARY_VERSION "0.2.1") +set(LIBRARY_SOVERSION "0") + +# add definitions +include(DefineCMakeDefaults) +include(DefinePlatformDefaults) +include(DefineCompilerFlags) +include(DefineOptions.cmake) + +include(DefineInstallationPaths) + +# add macros +include(MacroAddPlugin) +include(MacroCopyFile) + +if (NOT WIN32) + find_package(Iconv) +endif (NOT WIN32) +find_package(CMocka) +if (CMOCKA_FOUND AND UNIT_TESTING) + include(AddCMockaTest) +endif (CMOCKA_FOUND AND UNIT_TESTING) + +include(ConfigureChecks.cmake) + + +set(SOURCE_DIR ${CMAKE_SOURCE_DIR}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_CURRENT_BINARY_DIR}/CTestCustom.cmake) + +if (MEM_NULL_TESTS) + add_definitions(-DCSYNC_MEM_NULL_TESTS) +endif (MEM_NULL_TESTS) + +add_subdirectory(src) +add_subdirectory(config) +add_subdirectory(doc) + +if (CMOCKA_FOUND AND UNIT_TESTING) + add_subdirectory(tests) +endif (CMOCKA_FOUND AND UNIT_TESTING) + +configure_file(config_csync.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_csync.h) +configure_file(config_test.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_test.h) + + diff --git a/csync/COPYING b/csync/COPYING new file mode 100644 index 000000000..4362b4915 --- /dev/null +++ b/csync/COPYING @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/csync/CTestConfig.cmake b/csync/CTestConfig.cmake new file mode 100644 index 000000000..a65999e68 --- /dev/null +++ b/csync/CTestConfig.cmake @@ -0,0 +1,12 @@ +set(UPDATE_TYPE "true") + +set(MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_SOURCE_DIR}/tests/valgrind-csync.supp) + +set(CTEST_PROJECT_NAME "csync") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "mock.cryptomilk.org") +set(CTEST_DROP_LOCATION "/submit.php?project=${CTEST_PROJECT_NAME}") +set(CTEST_DROP_SITE_CDASH TRUE) + diff --git a/csync/CTestCustom.cmake b/csync/CTestCustom.cmake new file mode 100644 index 000000000..4afc9ab6a --- /dev/null +++ b/csync/CTestCustom.cmake @@ -0,0 +1,4 @@ +set(CTEST_CUSTOM_MEMCHECK_IGNORE + ${CTEST_CUSTOM_MEMCHECK_IGNORE} + check_std_c_jhash +) diff --git a/csync/ChangeLog b/csync/ChangeLog new file mode 100644 index 000000000..883a6cdc4 --- /dev/null +++ b/csync/ChangeLog @@ -0,0 +1,355 @@ +ChangeLog +========== +version 0.91.4 (released 2013-12-12, ownCloud Cleint 1.5.0) + * changelog added, version bumped. + +version 0.91.3 (released 2013-12-11, ownCloud Client 1.5.0rc1) + * Fix progress bar on win32 + * Fix network rate limiting on win32 + * Do not check for etag during failing requests + * Start quota timer only after the predecessor returned + * Remove tmp files in case of certain download problems + * Some valgrind fixes + * Theming fix: button behaviour + * Fix a case where a sync loop could happen. + * Multi-linguar installer + * Fix handling of quotes in etags written by older ownClouds + * Fix errno handling in update phase + * Make csync compile on FreeBSD + * Minor cleanups. + +version 0.91.2 (released 2013-12-10, ownCloud Client 1.5.0beta3) + * have translatable error message for indiv. file errors. + * Use uint64_t for inode on win32 to fix a type glitch. + * Add test that directrories are properly moved. + * Handle symlinks correctly. + * Do not longer recurse into ignored directories in update + phase. + * Added proper symlink detection for win32 platform. + +version 0.91.1 (released 2013-12-03, ownCloud Client 1.5.0beta2) + * Close database correctly to fix a potential crash (mirall#1229) + * Handle invalid inodes correctly. + * Use lstat rather than stat to detect symlinks correctly. + (core#6146) + +version 0.91.0 (released 2013-11-28, ownCloud Client 1.5.0beta1) + * fix ascii to int conversion for large numbers. + * add support for file ids, needed to detect server side moves. + * removed unused code, ie. database writing code that went to + mirall. + * add functions to query the database by fileid. + * add functions to read fileids from PUT replies. + * add server side move detection. + * enhanced test scripts + * Remove ne_sock_init and ne_sock_exit from owncloud module + (mirall#1115) + * Renamed 'md5' to 'etag' in code identifiers to avoid confusion. + * add new state EVAL_RENAME + * link the owncloud module directly rather than dl-loading it. + * add a content type header 'application/octet-stream' to PUTs. + * remove -gzip from etag header if its there. (mirall#1195) + * Many minor fixes, refactorings and improvements. + +version 0.90.4 (released 2013-10-18, ownCloud Client 1.4.2) + + * Count renamed and deleted files for progress information. + * Do not reset csync internal error state in helper funcs + and do not overwrite error messages. + That fixes error reporting to the client. + * Disable check on inodes on all platforms as inodes are not + reliable. + * Fix resuming after user aborting the sync process. + * enabled HBF debugging permanently. + +version 0.90.1 (released 2013-09-24, ownCloud Client 1.4.1) + * no more check on the local inode in updater for win32 (bug #779) + * detect if server does not send an etag after an upload + completed. + * fix crash in case of network timeout, reported as + https://github.com/owncloud/mirall/issues/1010 + * compile and cmake fixes for win32 + * fixed behaviour of csync_exclude + * documentation and spelling fixes. + +version 0.90.0 (released 2013-09-04, ownCloud Client 1.4.0) + * Added API to get progress information from csync. + * Added c_rename function to csync std. + * Fix: Do renames of files before any puts. + * Improved database integrity checks. + * Improvements of database writing efficiendy. + * Fix: stat file on win32 even if its opened by application. + * httpbf: configurable block size and threshold. + * Many fixes found by a Coverity check. + * Fix: use correct stat struct on all platforms + * Fix: download resuming. + * ownCloud module: Bandwith limitation added. + * Added ability to remove ignored files automatically. + * Fix: Use int64_t and friends + * Fix: Removed all compile warnings. + * Left excluded files and links in csync's tree to be able to show. + them to the user. + * Add OC-Total-Length header for better quota handling. + * Report inbetween progress + +version 0.80.0 (released 2013-06-25) + * Big file chunking (e.g. up/download of big files should now be no + problem anymore) + * Resuming (download of big files will resume) + * Fix false conflicts when database is corrupt/missing + * Fix false conflicts when file is locked + * Put legitimate conflict files only on client side + * Fix unreliable sync after push_file failed + * Fix rename due to inode cast error + * Make chunking work on nginx setups or through nginx proxies + * Improve error reporting in csync_update + * Clean progress database on csync_commit + * Fix issues detected by Coverity + * Fix conflict file appearing when a file cannot be stated + * Do not shadow server errors by not downloading files that have + failed to download in the past + +version 0.70.6 (released 2013-04-11) + * [Fixes] Try to avoid to upload incomplete files + * [Fixes] Increase read timeout to 300 seconds + * [Fixes] Handle IGNORE status correctly + * [Fixes] Set path and phash for ignored files + * [Fixes] Fix some issues discovered by Coverity + * [Fixes] Make sure to never allow empty pathes in rmdir + * [Fixes] Fix a crash caused by superfluous free() calls + +version 0.70.5 (released 2013-04-02) + * detect 'wrong' conflict files on client side. + * [Fixes] Give context to module to enable logging (cmd client). + * [Fixes] Fix version table contents. + * [Fixes] Fix handling of non statable files on Win32. + * [Fixes] Fix renames on clientside on read only shares. + * [Fixes] Various small fixes and improvements. + +version 0.70.4 (released 2013-02-26) + * [Win32] Ship with upto-date openssl version to fix SSL problems we saw. + * [Fixes] Fix crash during mkdir. + * [Fixes] Added workaround for problem that server sometimes does + not respond properly to PROPFIND (mirall#285) + * [Fixes] Fix handling of deletion of non empty or locked + directories. + * [Fixes] Fixed some potential memory leaks. + * [Fixes] Files with filenames with unix extensions + are ignored now. + +version 0.70.3 (released 2013-01-24) + * [Platform] Fix session cookie extraction (mirall bug #260). + +version 0.70.2 (released 2013-01-23) + * [Platform] Improved module parameter system. + * [Platform] New logging framework. Dropped log4c dependency. + * [Platform] New API to provide sync progress information. + * [Fixes] More efficiency for the ownCloud plugin through less HTTP requests to + the server. + * [Fixes] ownCloud plugin: Improved upload performance. + * [Fixes] Improved error reporting to mirall. + * [Fixes] ownCloud plugin: Improved interpretation of HTTP error codes. + * [Fixes] ownCloud plugin: Do not abort on errors with individual files any + more. + * [Fixes] Lots of other minor fixes. + * [MacOSX] Use libneon with proper big file support. + * [Win32] Use libneon with openSSL support now. + +version 0.70.0 and 0.70.1 were beta versions. + +version 0.60.2 (released 2012-11-26) + * Migration to cross platform testing system cmocka. + * Fixed variuos minor things incl. potential mem leaks. + * Clang fixes. + * Moved journal database to sync directory. + * Fixed more csync->ocsync renaming issues. + * Fixed statedb query below path. + * Fixed win32 Daylight Saving Time issues. + * Allow static linking with iniparser and sqlite. + * Win32: Fix CreateHandle function in local stat. + * Win32: More wide char fixes. + * Added version table to journal database. + * Fixes for HTTP reply computation. + * Stricter error checks on PROPFIND results. + * Workaround for DST influenced times from previous versions. + * Detect looping in mkdirs to fix sharing. + +version 0.60.1 (released 2012-10-18) + * Fix improper memory cleanup which could + cause memory leaks and crashes + * Fix memory leak + * Allow single quote (') in file names + * Remove stray temporary files + +version 0.60.0 (released 2012-10-10) + * simplification of pac based proxy support. + * syncing algorithm based on ids rather than on timestamps + * make it possible to relocate database + +version 0.50.11 (third beta version, released 2012-10-05) + * Renamed ownCloud version of csync to ocsync for ownCloud. + * Migration paths for csync database and config. + * Fixed that exclude patterns are also tested on files basenames. + * Fixed return type for query function if no database exists. + * minor code fixes + +version 0.50.10 (second beta version, released 2012-09-20) + * Fixed crash by removing a bogus free. + * More useful logging. + * ownCloud: Maintain the http session by handling the HTTP Cookie. + +version 0.50.9 (first beta version, released 2012-08-30) + * Fixed strncpy handling (mkdir on windows problem). + * extend database with columns uniq id and type. + * Use server maintained uniq IDs for update detection instead of + mtimes. + * Maintain uniq IDs in local database + * Handle change propagation through the file tree locally and remote. + * Added module to build a file tree from the local database (dbtree.c) + * Added methods to query IDs from the server and maintain it locally. + +version 0.50.8 (released 2012-08-10) + * Inode equivalent support for Win32 platforms to support rename + * ownCloud supports propagates renames from local to webdav MOVE + * ownCloud module works with proxy, settings from mirall. + * improved CMake modules (openSSL) + * Fixed namespace for lastmodified propset. + * Added cmocka based tests for ownCloud module. + * Added a config_test.h config_csync.header file. + * Fix link handling: Ignore symlinks. + * Modules can now report their capabilities to csync core. + * A lot of minor fixes and improvements. + +version 0.50.7 (released 2012-06-19) + * Added ability to log to a callback, ie. let the app + catch the log output + * Added push to remote without pushing to temp file first. + * Fixed file copy function to use wide character (win32). + * Fixed loading of statedb if user has special char (win32). + +version 0.50.6 (released 2012-05-18) + * Directories with 'strange' characters broke sync. (oC bug #613) + * Special characters in Windows did not sync correctly. (oC bug #478) + * Make neon redirecting + * Switch logging off on Apple to not fill the syslog. (oC bug #622) + +version 0.50.5 (released 2012-04-18) + * removed argp lib dependency + * simplified and fixed CMake files + * MacOS porting efforts + * more granular error reports, thread save + +version 0.50.0 (released 2013-08-01) + * Added owncloud module. + * Added support for more platforms: FreeBSD, Windows and MacOSX + * Added support for more compilers: CLang, MinGW and latest GCC + * Added a backup mode to the reconciler. + * Added new logging framework (removed log4c dependency). + * Added new config parser (removed iniparser dependency). + * Added cmocka tests. + * Added a way to exported file_tree_walk functions. + * Added capabilities for modules. + * Added possiblity to push information to the modules. + * Added iconv support to support various char sets. + * Added csync_commit() to rerun on the same context. + * Added content checking in conflict case. + * Added callbacks for progress information. + * Added get() and put() functions for modules. + * Improved database: more performance, more integrity checks + * Improved error reporting: status codes, custom errnos + * Fixed serveral bugs. + * Relicensed libcsync to LGPLv2.1+. + +version 0.44.0 (released 2010-02-15) + * Migrated sftp module to libssh 0.4. + * Added more cache entries to the default config. + * Added missing requirements. + * Fixed build warnings. + * Fixed some memory leaks using sftp attributes. + * Some code cleanups. + +version 0.43.0 (released 2009-05-25) + * Added SFTP support with libssh 0.3. + * Added possibility to pass userdata to the auth function. + * Added a better version function. + * Fixed CMake build. + * Fixed CMake find modules. + * Fixed some flaws in cstd functions. + * Documented all public functions. + +version 0.42.0 (released 2009-02-10) + * Small fix in the userguide. + +version 0.42.0 rc3 (released 2009-01-23) + libcsync: + * Added checks for unix extensions. + * Added more documentation to the userguide. + * Fixed loading of plugins. + * Fixed call for deletion functions. + * Normalize the path to / for the statedb filename. + * More change name of client options to be more descriptive. + +version 0.42.0 rc2 (released 2009-01-07) + libcsync: + * Fixed a bug in the creation of the statedb + * Completed userguide + +version 0.42.0 rc1 (released 2008-12-04) + libcsync: + * Reduced calls of syscall functions. + * Added own function to create a temporary filename. + * Fixed libsmbclient 3.0.x support. + +version 0.42.0 beta1 (released 2008-09-10) + + libcsync: + * Added a sftp module using libssh + * Added user guide (just a start) + * Added testcase for update detection + * Added a function to parse an uri to cstdlib + * Updated the manpage + * Fixed some segfaults in cstdlib + * Fixed some memory leaks + + csync: + * Improved the auth callback + +version 0.42.0 alpha4 (released 2008-07-02) + + libcsync: + * Added the possibility to run csync completely without a journal + * Improved chmod calls during propagation. Most of the time we use the + default mode and don't need to call chmod. + * Improved the exclude list handling in the file tree walker. This + increased the speed of the update detection. + * Fixed csync on PPC + * Fixed serveral small bugs + + csync: + * Added commandline option to run csync completely without a journal + * Added a manpage + +version 0.42.0 alpha3 (released 2008-06-25) + + libcsync: + * Added a tree merger to write a complete journal + * Added support to run csync without a journal + * Fixed kerberos support in csync_smb module + * Fixed closing of files after the copy + * Fixed update detection to detect special files (fifo, pipes, + char devices, ..) + * Fixed O_NOATIME flag on open() if we don't have the permission + + csync: + * Add a variable to run csync completely without a journal + +version 0.42.0 alpha2 (released 2008-06-16) + + libcsync: + * Peformance improvements + * Add more directories to the standard exclude file + * Bugfixes + +version 0.42.0 alpha1 (released 2008-06-02) + + * Initial release diff --git a/csync/ConfigureChecks.cmake b/csync/ConfigureChecks.cmake new file mode 100644 index 000000000..3429a9dcd --- /dev/null +++ b/csync/ConfigureChecks.cmake @@ -0,0 +1,75 @@ +include(CheckIncludeFile) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckCXXSourceCompiles) + +set(PACKAGE ${APPLICATION_NAME}) +set(VERSION ${APPLICATION_VERSION}) +set(DATADIR ${DATA_INSTALL_DIR}) +set(LIBDIR ${LIB_INSTALL_DIR}) +set(PLUGINDIR "${PLUGIN_INSTALL_DIR}-${LIBRARY_SOVERSION}") +set(SYSCONFDIR ${SYSCONF_INSTALL_DIR}) + +set(BINARYDIR ${CMAKE_BINARY_DIR}) +set(SOURCEDIR ${CMAKE_SOURCE_DIR}) + +# HEADER FILES +check_include_file(argp.h HAVE_ARGP_H) + +# FUNCTIONS +if (NOT LINUX) + # librt + check_library_exists(rt nanosleep "" HAVE_LIBRT) + + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ) +endif (NOT LINUX) + +check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) +if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} rt) +endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + +check_library_exists(dl dlopen "" HAVE_LIBDL) +if (HAVE_LIBDL) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} dl) +endif (HAVE_LIBDL) + +check_function_exists(asprintf HAVE_ASPRINTF) +if(NOT HAVE_ASPRINTF) + if(MINGW) + add_definitions( -D__USE_MINGW_ANSI_STDIO=1 ) + endif() +endif() + +check_function_exists(fnmatch HAVE_FNMATCH) +if(NOT HAVE_FNMATCH AND WIN32) + find_library(SHLWAPI_LIBRARY shlwapi) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} shlwapi) +endif() + +if(WIN32) + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32) +endif() + +check_function_exists(timegm HAVE_TIMEGM) +check_function_exists(strerror_r HAVE_STRERROR_R) +check_function_exists(utimes HAVE_UTIMES) +check_function_exists(lstat HAVE_LSTAT) +check_function_exists(asprintf HAVE_ASPRINTF) +if (WIN32) + check_function_exists(__mingw_asprintf HAVE___MINGW_ASPRINTF) +endif(WIN32) +if (UNIX AND HAVE_ASPRINTF) + add_definitions(-D_GNU_SOURCE) +endif (UNIX AND HAVE_ASPRINTF) +if (WIN32) + check_function_exists(__mingw_asprintf HAVE___MINGW_ASPRINTF) +endif(WIN32) + +if (UNIT_TESTING) + set(WITH_UNIT_TESTING ON) +endif (UNIT_TESTING) + +set(CSYNC_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} CACHE INTERNAL "csync required system libraries") diff --git a/csync/DefineOptions.cmake b/csync/DefineOptions.cmake new file mode 100644 index 000000000..9ba14800d --- /dev/null +++ b/csync/DefineOptions.cmake @@ -0,0 +1,5 @@ +if ( NOT WIN32 ) + option(WITH_ICONV "Build csync with iconv support" ON) +endif() +option(UNIT_TESTING "Build with unit tests" OFF) +option(MEM_NULL_TESTS "Enable NULL memory testing" OFF) diff --git a/csync/INSTALL b/csync/INSTALL new file mode 100644 index 000000000..86a683e6e --- /dev/null +++ b/csync/INSTALL @@ -0,0 +1,84 @@ +# How to build from source + +## Requirements + +### Common requirements + +In order to build csync, you need to install several components: + +- A C compiler +- [CMake](http://www.cmake.org) >= 2.6.0. +- [check](http://check.sourceforge.net) >= 0.9.5 +- [sqlite3](http://www.sqlite.org) >= 3.4 +- [libneon](http://www.webdav.org/neon/) >= 0.29.0 + +optional: +- [libsmbclient](http://www.samba.org) >= 3.5 +- [libssh](http://www.libssh.org) >= 0.5 + +sqlite3 is a runtime requirement. libsmbclient is needed for +the smb plugin, libssh for the sftp plugin. libneon is required for the +ownCloud plugin. + +Note that these version numbers are version we know works correctly. If you +build and run csync successfully with an older version, please let us know. + + +## Building +First, you need to configure the compilation, using CMake. Go inside the +`build` dir. Create it if it doesn't exist. + +GNU/Linux and MacOS X: + + cmake -DCMAKE_BUILD_TYPE=Debug .. + make + +### CMake standard options +Here is a list of the most interesting options provided out of the box by CMake. + +- CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel RelWithDebInfo) +- CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default to + /usr/local on GNU/Linux and MacOS X) +- CMAKE_C_COMPILER: The path to the C compiler +- CMAKE_CXX_COMPILER: The path to the C++ compiler + +### CMake options defined for csync + +Options are defined in the following files: + +- DefineOptions.cmake + +They can be changed with the -D option: + +`cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_LOG4C=OFF ..` + +### Browsing/editing CMake options + +In addition to passing options on the command line, you can browse and edit +CMake options using `cmakesetup` (Windows) or `ccmake` (GNU/Linux and MacOS X). + +- Go to the build dir +- On Windows: run `cmakesetup` +- On GNU/Linux and MacOS X: run `ccmake ..` + +## Installing + +Befor installing you can run the tests if everything is working: + + make test + +If you want to install csync after compilation run: + + make install + +## Running + +The csync binary can be found in the `build/client` directory. + +## About this document + +This document is written using [Markdown][] syntax, making it possible to +provide usable information in both plain text and HTML format. Whenever +modifying this document please use [Markdown][] syntax. + +[markdown]: http://www.daringfireball.net/projects/markdown diff --git a/csync/README b/csync/README new file mode 100644 index 000000000..e24ae64be --- /dev/null +++ b/csync/README @@ -0,0 +1,34 @@ +WHAT IS CSYNC? +============== + +csync is a lightweight utility to synchronize files between two directories on +a system or between multiple systems. + +It synchronizes bidirectionally and allows the user to keep two copies of files +and directories in sync. csync uses widely adopted protocols, such as smb or +sftp, so that there is no need for a server component. It is a user-level +program which means you don’t need to be a superuser or administrator. + +CONTRIBUTIONS +============= + +If you want to contribute to the development of the software then please join +the mailing list. Patches are accepted preferebly created with git and we are +always glad to receive feedback or suggestions to the address +csync-devel@csync.org. +More information on the various mailing lists can be found at +http://www.csync.org/communication/. + +You can also get the sourcecode straight from the git repository - see +http://git.csync.org/ + +DOCUMENTATION +============= + +As a user you can find a userguide which is shipped with this package or is +available at the website. For developers there is doxygen documentation and +comments in the source code itself. See + +http://www.csync.org/userguide/ +and +http://www.csync.org/api/ diff --git a/csync/config/CMakeLists.txt b/csync/config/CMakeLists.txt new file mode 100644 index 000000000..da2c51c69 --- /dev/null +++ b/csync/config/CMakeLists.txt @@ -0,0 +1,10 @@ +macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/ocsync.conf ${CMAKE_CURRENT_BINARY_DIR}/ocsync.conf) +macro_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/ocsync_exclude.conf ${CMAKE_CURRENT_BINARY_DIR}/ocsync_exclude.conf) + +install( + FILES + ocsync.conf + ocsync_exclude.conf + DESTINATION + ${SYSCONF_INSTALL_DIR}/ocsync +) diff --git a/csync/config/ocsync.conf b/csync/config/ocsync.conf new file mode 100644 index 000000000..fa537f5e1 --- /dev/null +++ b/csync/config/ocsync.conf @@ -0,0 +1,16 @@ +# max diff time between two recplicas in seconds +max_time_difference = 10 + +# max directory depth recursion +max_depth = 50 + +# NOT IN USE: +# sync symbolic links if the remote filesystem supports it. +#sync_symbolic_links = false + +# connection timeout in seconds +timeout = 30 + +# create a copy for backup for the file which has a conflict +#with_confilct_copies = no + diff --git a/csync/config/ocsync_exclude.conf b/csync/config/ocsync_exclude.conf new file mode 100644 index 000000000..d2b4dda85 --- /dev/null +++ b/csync/config/ocsync_exclude.conf @@ -0,0 +1,47 @@ +# Exclude patterns (one per line). Blank lines in the file and +# lines starting with '#' are ignored. +# +# You can use shell wildcard patterns. See the manpage of sh(1) +# +# If a pattern starts with ']', the ']' is removed from the pattern, +# And the files that matches that pattern will be ignored, but if +# their parent directory is removed on the other side, the file will +# be removed. Otherwise, ignored file prevent the directory from +# being removed. + +.beagle +.ccache +.csync* +.fontconfig +.java/ +.jpi_cache +.kde*/cache-* +.kde*/socket-* +.kde*/tmp-* +.mozilla/plugins +.mozilla/firefox/*/Cache +.netscape/cache/ +.snapshot +.thumbnails + +*.~* +]*.directory + +# exclude all object file +# *.o + +# exclude all bak files +# *.bak +# + +# ignore conflict files. +*_conflict-* + +.Temporary Items +.Trashes +.Document Revisions-V100 +.fseventd +.apdisk + +.htaccess + diff --git a/csync/config_csync.h.cmake b/csync/config_csync.h.cmake new file mode 100644 index 000000000..333cafcdc --- /dev/null +++ b/csync/config_csync.h.cmake @@ -0,0 +1,32 @@ +#cmakedefine PACKAGE "${APPLICATION_NAME}" +#cmakedefine VERSION "${APPLICATION_VERSION}" +#cmakedefine LOCALEDIR "${LOCALE_INSTALL_DIR}" +#cmakedefine DATADIR "${DATADIR}" +#cmakedefine LIBDIR "${LIBDIR}" +#cmakedefine PLUGINDIR "${PLUGINDIR}" +#cmakedefine SYSCONFDIR "${SYSCONFDIR}" +#cmakedefine BINARYDIR "${BINARYDIR}" +#cmakedefine SOURCEDIR "${SOURCEDIR}" + +#cmakedefine HAVE_CLOCK_GETTIME + +#cmakedefine WITH_LOG4C 1 +#cmakedefine WITH_ICONV 1 + +#cmakedefine HAVE_ARGP_H 1 +#cmakedefine HAVE_ICONV_H 1 +#cmakedefine HAVE_SYS_ICONV_H 1 + +#cmakedefine HAVE_TIMEGM 1 +#cmakedefine HAVE_STRERROR_R 1 +#cmakedefine HAVE_UTIMES 1 +#cmakedefine HAVE_LSTAT 1 +#cmakedefine HAVE_FNMATCH 1 +#cmakedefine HAVE___MINGW_ASPRINTF 1 +#cmakedefine HAVE_ICONV 1 +#cmakedefine HAVE_ICONV_CONST 1 + +#cmakedefine NEON_WITH_LFS 1 +#cmakedefine HAVE___MINGW_ASPRINTF 1 + +#cmakedefine WITH_UNIT_TESTING 1 diff --git a/csync/config_test.h.cmake b/csync/config_test.h.cmake new file mode 100644 index 000000000..7f03938ea --- /dev/null +++ b/csync/config_test.h.cmake @@ -0,0 +1,7 @@ + +/* mind to have trailing slashes! */ + +#define TESTFILES_DIR "@SOURCE_DIR@/tests/ownCloud/testfiles/" + +#define TEST_CONFIG_DIR "@SOURCE_DIR@/tests/ownCloud/" + diff --git a/csync/doc/CMakeLists.txt b/csync/doc/CMakeLists.txt new file mode 100644 index 000000000..24b27cc38 --- /dev/null +++ b/csync/doc/CMakeLists.txt @@ -0,0 +1,41 @@ +project(doc C) +# Build the documentation +# + +include(UseDoxygen OPTIONAL) + +file(GLOB _manpages *.[0-9].txt) +add_custom_target(man + COMMAND + ${CMAKE_CURRENT_SOURCE_DIR}/makeman.sh ${_manpages} + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_custom_target(userguide + COMMAND + ${CMAKE_CURRENT_SOURCE_DIR}/makeguide.sh ocsync.txt + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} +) + +if (UNIX) + install( + FILES + ocsync.1 + DESTINATION + ${MAN_INSTALL_DIR}/man1 + ) + set(DOC_INSTALL_PATH ${SHARE_INSTALL_PREFIX}/doc/ocsync) +endif(UNIX) + +if (WIN32) + set(DOC_INSTALL_PATH ${SHARE_INSTALL_PREFIX}/doc) +endif (WIN32) + +install( + DIRECTORY + userguide + DESTINATION + ${DOC_INSTALL_PATH} +) diff --git a/csync/doc/asciidoc.conf b/csync/doc/asciidoc.conf new file mode 100644 index 000000000..4ae2a831d --- /dev/null +++ b/csync/doc/asciidoc.conf @@ -0,0 +1,8 @@ +# +# Customization for csync documentation. +# +[specialwords] +ifndef::doctype-manpage[] +emphasizedwords=(?u)\\?\bCSYNC\b +monospacedwords=(?u)\\?\bcsync\(1\) +endif::doctype-manpage[] diff --git a/csync/doc/codeheader.c b/csync/doc/codeheader.c new file mode 100644 index 000000000..724fc163b --- /dev/null +++ b/csync/doc/codeheader.c @@ -0,0 +1,20 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ diff --git a/csync/doc/codeheader.h b/csync/doc/codeheader.h new file mode 100644 index 000000000..5182b0203 --- /dev/null +++ b/csync/doc/codeheader.h @@ -0,0 +1,26 @@ +/* + * + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_X_H +#define _CSYNC_X_H + +#endif /* _CSYNC_X_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/doc/doxy.config.in b/csync/doc/doxy.config.in new file mode 100644 index 000000000..c6731c441 --- /dev/null +++ b/csync/doc/doxy.config.in @@ -0,0 +1,1375 @@ +# Doxyfile 1.5.5 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = @PROJECT_NAME@ + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = @PROJECT_VERSION@ + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, +# and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = NO + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = YES + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = @CMAKE_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = @CMAKE_SOURCE_DIR@ + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = YES + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = @DOXY_WARN_FORMAT@ + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = @CMAKE_CURRENT_BINARY_DIR@/doxy.log + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = @CMAKE_SOURCE_DIR@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.cpp \ + *.cc \ + *.h \ + *.hh \ + *.hpp \ + *.dox + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */.git/* \ + */cmake/* \ + */doc/* \ + */iniparser/* \ + */build/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/tests \ + @CMAKE_CURRENT_SOURCE_DIR@/example + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = *.cpp \ + *.cc \ + *.h \ + *.hh \ + INSTALL \ + DEPENDENCIES \ + CHANGELOG \ + LICENSE \ + LGPL + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = YES + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = @CMAKE_CURRENT_SOURCE_DIR@ \ + @CMAKE_CURRENT_SOURCE_DIR@/img + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = NO + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 2 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = YES + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = @LATEX_COMPILER@ + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = @MAKEINDEX_COMPILER@ + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = YES + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = @CMAKE_CURRENT_BINARY_DIR@/html/@PROJECT_NAME@.TAGFILE + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = YES + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = @DOXYGEN_DOT_FOUND@ + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = YES + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = YES + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = @DOXYGEN_DOT_EXECUTABLE_PATH@ + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is enabled by default, which results in a transparent +# background. Warning: Depending on the platform used, enabling this option +# may lead to badly anti-aliased labels on the edges of a graph (i.e. they +# become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = YES + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/csync/doc/makeguide.sh b/csync/doc/makeguide.sh new file mode 100644 index 000000000..8794c1be2 --- /dev/null +++ b/csync/doc/makeguide.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Last Change: 2008-07-03 11:08:54 + +for f in $@; do + test "${f##*/}" = "CMakeLists.txt" && continue + echo -e "\e[32mCreating asciidoc html document ${f%.*}.html\e[0m" + asciidoc \ + --attribute=numbered \ + --attribute=icons \ + --attribute="iconsdir=./images/icons" \ + --attribute=toc \ + --backend=xhtml11 \ + --out-file="$(dirname $f)/userguide/${f%.*}.html" \ + $f + rm -f ${f%.*}.xml +done diff --git a/csync/doc/makeman.sh b/csync/doc/makeman.sh new file mode 100644 index 000000000..913d0f01e --- /dev/null +++ b/csync/doc/makeman.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Last Change: 2008-07-03 11:08:54 + +for f in $@; do + test "${f##*/}" = "CMakeLists.txt" && continue + echo -e "\e[32mCreating manpage ${f%.*}\e[0m" + a2x --doctype=manpage --format=manpage $f + rm -f ${f%.*}.xml +done diff --git a/csync/doc/ocsync.1 b/csync/doc/ocsync.1 new file mode 100644 index 000000000..ce10b7ed6 --- /dev/null +++ b/csync/doc/ocsync.1 @@ -0,0 +1,183 @@ +'\" t +.\" Title: ocsync +.\" Author: [see the "AUTHORS" section] +.\" Generator: DocBook XSL Stylesheets v1.76.1 +.\" Date: 12/22/2012 +.\" Manual: \ \& +.\" Source: \ \& +.\" Language: English +.\" +.TH "OCSYNC" "1" "12/22/2012" "\ \&" "\ \&" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +ocsync \- A commandline frontent for csync a user level bidirectional file synchronizer\&. +.SH "SYNOPSIS" +.sp +\fBocsync\fR [\fIOPTION\fR\&...] \fISOURCE\fR \fIDESTINATION\fR +.SH "DESCRIPTION" +.sp +ocsync is a client only bidirectional file synchronizer\&. It synchronizes the content of \fISOURCE\fR with \fIDESTINATION\fR and vice versa\&. The \fIDESTINATION\fR can be a local directory or a remote file server\&. +.sp +You can use ocsync for different things\&. The intention is to provide Roaming Home Directories for Linux but you can use it to synchronize your music collection or create a backup of a directory\&. +.SH "OPTIONS" +.PP +\fB\-\-create\-statedb\fR +.RS 4 +Run update detection and write the statedb (TESTING ONLY!) +.RE +.PP +\fB\-d, \-\-disable\-statedb\fR +.RS 4 +Disable the usage and creation of a statedb\&. +.RE +.PP +\fB\-\-exclude\-file=\fR\fB\fI\fR\fR +.RS 4 +Add an additional exclude file +.RE +.PP +\fB\-r, \-\-reconcile\fR +.RS 4 +Run ONLY update detection and reconcilation This option is for debugging +.RE +.PP +\fB\-u, \-\-update\fR +.RS 4 +Run ONLY the update detection This option is for debugging +.RE +.PP +\fB\-?, \-\-help\fR +.RS 4 +Print the help list +.RE +.PP +\fB\-V, \-\-version\fR +.RS 4 +Print program version +.RE +.SH "EXIT STATUS" +.PP +\fB0\fR +.RS 4 +Success +.RE +.PP +\fB1\fR +.RS 4 +Failure (syntax or usage error; configuration error; unexpected error)\&. +.RE +.SH "EXAMPLES" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +ocsync /home/user /backup/home/user +.sp +.if n \{\ +.RS 4 +.\} +.nf +Synchronizer two local directories\&. +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +ocsync /home/user smb://server/share/user +.sp +.if n \{\ +.RS 4 +.\} +.nf +Synchronize a home directory with a SMB share\&. +.fi +.if n \{\ +.RE +.\} +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +ocsync /home/user smb://user:password@server/share/user +.sp +.if n \{\ +.RS 4 +.\} +.nf +Synchronize a home directory with a SMB share and provide username and +password directly\&. +.fi +.if n \{\ +.RE +.\} +.RE +.SH "BUGS" +.sp +Please report bugs at https://dev\&.csync\&.org/\&. +.SH "SEE ALSO" +.sp +\fBlibocsync\fR(7) +.SH "AUTHORS" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Andreas Schneider +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +.sp -1 +.IP \(bu 2.3 +.\} +Klaas Freitag +.RE +.SH "COPYING" +.sp +Copyright \e(c) 2006\-2008 Andreas Schneider\&. Free use of this software is granted under the terms of the GNU General Public License (GPL)\&. diff --git a/csync/doc/ocsync.1.txt b/csync/doc/ocsync.1.txt new file mode 100644 index 000000000..9364d4666 --- /dev/null +++ b/csync/doc/ocsync.1.txt @@ -0,0 +1,104 @@ +ocsync(1) +======== + +NAME +---- + +ocsync - A commandline frontent for csync a user level bidirectional file +synchronizer. + + +SYNOPSIS +-------- + +*ocsync* ['OPTION'...] 'SOURCE' 'DESTINATION' + + +DESCRIPTION +----------- + +ocsync is a client only bidirectional file synchronizer. It synchronizes the +content of 'SOURCE' with 'DESTINATION' and vice versa. The 'DESTINATION' can +be a local directory or a remote file server. + +You can use ocsync for different things. The intention is to provide Roaming +Home Directories for Linux but you can use it to synchronize your music +collection or create a backup of a directory. + + +OPTIONS +------- + +*--create-statedb*:: + Run update detection and write the statedb + (TESTING ONLY!) +*-d, --disable-statedb*:: + Disable the usage and creation of a statedb. + +*--exclude-file=''*:: + Add an additional exclude file + +*-r, --reconcile*:: + Run ONLY update detection and reconcilation + This option is for debugging + +*-u, --update*:: + Run ONLY the update detection + This option is for debugging + +*-?, --help*:: + Print the help list + +*-V, --version*:: + Print program version + + +EXIT STATUS +----------- +*0*:: + Success + +*1*:: + Failure (syntax or usage error; configuration error; + unexpected error). + + +EXAMPLES +-------- + +* ocsync /home/user /backup/home/user + + Synchronizer two local directories. + +* ocsync /home/user smb://server/share/user + + Synchronize a home directory with a SMB share. + +* ocsync /home/user smb://user:password@server/share/user + + Synchronize a home directory with a SMB share and provide username and + password directly. + +BUGS +---- + +Please report bugs at https://dev.csync.org/. + + +SEE ALSO +-------- + +*libocsync*(7) + + +AUTHORS +------- + +* Andreas Schneider +* Klaas Freitag + +COPYING +------- +Copyright \(c) 2006-2008 Andreas Schneider. Free use of this software is +granted under the terms of the GNU General Public License (GPL). + diff --git a/csync/doc/ocsync.txt b/csync/doc/ocsync.txt new file mode 100644 index 000000000..230e6646c --- /dev/null +++ b/csync/doc/ocsync.txt @@ -0,0 +1,315 @@ +CSYNC User Guide +================ +Andreas Schneider +:Author Initials: ADS + +csync is a lightweight utility to synchronize files between two directories +on a system or between multiple systems. + +It synchronizes bidirectionally and allows the user to keep two copies of files +and directories in sync. csync uses widely adopted protocols, such as smb or +sftp, so that there is no need for a server component. It is a user-level +program which means you don't need to be a superuser or administrator. + +Together with a Pluggable Authentication Module (PAM), the intent is to provide +Roaming Home Directories for Linux (see <>). + +Introduction +------------ + +It is often the case that we have multiple copies (called replicas) of a +filesystem or part of a filesystem (for example on a notebook and desktop +computer). Changes to each replica are often made independently, and as a +result, they do not contain the same information. In that case, a file +synchronizer is used to make them consistent again, without losing any +information. + +The goal is to detect conflicting updates (files which have been modified) and +propagate non-conflicting updates to each replica. If there are no conflicts +left, we are done, and the replicas are identical. To resolve or handle +conflicts there are several algorithms available. They will be discussed +one of the following sections. + +Basics +------ + +This section describes some basics of file synchronization. + +Paths +~~~~~ +A path normally refers to a point which contains a set of files which should be +synchronized. It is specified relative to the root of the replica locally, but +has to be absolute if you use a protocol. The path is just a sequence of names +separated by '/'. + +NOTE: The path separator is always a forward slash '/', even for Windows. + +csync always uses the absolute path on remote replicas. This could +'sftp://gladiac:secret@myserver/home/gladiac' for sftp. + +What is an update? +~~~~~~~~~~~~~~~~~~ +The contents of a path could be a file, a directory or a symbolic link +(symbolic links are not supported yet). To be more precise, if the path refers +to: + +- a regular file: the contents of the file are the byte stream and the + metadata of the file. +- a directory: then the content is the metadata of the directory. +- a symbolic link: the content is the named file the link points to. + +csync keeps a record of each path which has been successfully synchronized. The +path gets compared with the record and if it has changed since the last +synchronization, we have an update. This is done by comparing the modification +or change (modification time of the metadata) time. This is the way how updates +are detected. + +What is a conflict? +~~~~~~~~~~~~~~~~~~~ +A path is conflicting if it fulfills the following conditions: + +1. it has been updated in one replica, +2. it or any of its descendants has been updated on the other replica too, and +3. its contents in are not identical. + +File Synchronization +-------------------- + +The primary goal of the file synchronizer is correctness. It may change +scattered or large parts of the filesystem. Since this in mostly not monitored +by the user, and the file synchronizer is in a position to harm the system, +csync must be safe, even in the case of unexpected errors (e.g. disk full). +What was done to make csync safe is described in the following sections. + + +One problem concerning correctness is the handling of conflicts. Each file +synchronizer tries to propagate conflicting changes to the other replica. At +the end both replicas should be identical. There are different strategies to +fulfill these goals. + +csync is a three-phase file synchronizer. The decision for this design was that +user interaction should be possible and it should be easy to understand the +process. The three phases are update detection, reconciliation and propagation. +These will be described in the following sections. + +Update detection +~~~~~~~~~~~~~~~~ +There are different strategies for update detection. csync uses a state-based +modtime-inode update detector. This means it uses the modification time to +detect updates. It doesn't require many resources. A record of each file is +stored in a database (called statedb) and compared with the current +modification time during update detection. If the file has changed since the +last synchronization an instruction is set to evaluate it during the +reconciliation phase. If we don't have a record for a file we investigate, it +is marked as new. + +It can be difficult to detect renaming of files. This problem is also solved +by the record we store in the statedb. If we don't find the file by the name +in the database, we search for the inode number. If the inode number is found +then the file has been renamed. + +Reconciliation +~~~~~~~~~~~~~~ +The most important component is the update detector, because the reconciler +depends on it. The correctness of reconciler is mandatory because it can damage +a filesystem. It decides which file: + +* Stays untouched +* Has a conflict +* Gets synchronized +* or is *deleted* + +A wrong decision of the reconciler leads in most cases to a loss of data. So +there are several conditions which a file synchronizer has to follow. + +Algorithms +^^^^^^^^^^ + +For conflict resolution several different algorithms could be implemented. The +most common algorithms are the merge and the conflict algorithm. The first +is a batch algorithm and the second is one which needs user interaction. + +Merge algorithm ++++++++++++++++ + +The merge algorithm is an algorithm which doesn't need any user interaction. It +is simple and used for example by Microsoft for Roaming Profiles. If it detects +a conflict (the same file changed on both replicas) then it will use the most +recent file and overwrite the other. This means you can loose some data, but +normally you want the latest file. + +Conflict algorithm +++++++++++++++++++ + +This is not implemented yet. + +If a file has a conflict the user has to decide which file should be used. + +Propagation +~~~~~~~~~~~ + +The next instance of the file synchronizer is the propagator. It uses the +calculated records to apply them on the current replica. + + +The propagator uses a two-phase-commit mechanism to simulate an atomic +filesystem operation. + +In the first phase we copy the file to a temporary file on the opposite +replica. This has the advantage that we can check if the file which has been +copied to the opposite replica has been transferred successfully. If the +connection gets interrupted during the transfer we still have the original +states of the file. This means no data will be lost. + +In the second phase the file on the opposite replica will be overwritten by +the temporary file. + +After a successful propagation we have to merge the trees to reflect the +current state of the filesystem tree. This updated tree will be written as a +journal into the state database. It will be used during the update detection of +the next synchronization. See above for a description of the state database +during synchronization. + +Robustness +~~~~~~~~~~ + +This is a very important topic. The file synchronizer should not crash, and if +it has crashed, there should be no loss of data. To achieve this goal there are +several mechanisms which will be discussed in the following sections. + +Crash resistance +^^^^^^^^^^^^^^^^ + +The synchronization process can be interrupted by different events, this can +be: + +* the system could be halted due to errors. +* the disk could be full or the quota exceeded. +* the network or power cable could be pulled out. +* the user could force a stop of the synchronization process. +* various communication errors could occur. + +That no data will be lost due to an event we enforce the following invariant: + +IMPORTANT: At every moment of the synchronization each file, has either its +original content or its correct final content. + +This means that the original content can not be incorrect, no data can be lost +until we overwrite it after a successful synchronization. Therefore, each +interrupted synchronization process is a partial sync and can be continued and +completed by simply running csync again. The only problem could be an error of +the filesystem, so we reach this invariant only approximately. + +Transfer errors +^^^^^^^^^^^^^^^ + +With the Two-Phase-Commit we check the file size after the file has transferred +and we are able to detect transfer errors. A more robust approach would be a +transfer protocol with checksums, but this is not doable at the moment. We may +add this in the future. + +Future filesystems, like btrfs, will help to compare checksums instead of the +filesize. This will make the synchronization safer. This does not imply that it +is unsafe now, but checksums are safer than simple filesize checks. + +Database loss +^^^^^^^^^^^^^ + +It is possible that the state database could get corrupted. If this happens, +all files get evaluated. In this case the file synchronizer wont delete any +file, but it could occur that deleted files will be restored from the other +replica. + +To prevent a corruption or loss of the database if an error occurs or the user +forces an abort, the synchronizer is working on a copy of the database and will +use a Two-Phase-Commit to save it at the end. + +Getting started +--------------- + +Installing csync +~~~~~~~~~~~~~~~~ + +See the `README` and `INSTALL` files for install prerequisites and +procedures. Packagers should take a look at <>. + +Using the commandline client +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The synopsis of the commandline client is + + csync [OPTION...] SOURCE DESTINATION + +It synchronizes the content of SOURCE with DESTINATION and vice versa. The +DESTINATION can be a local directory or a remote file server. + + csync /home/csync scheme://user:password@server:port/full/path + +Examples +^^^^^^^^ + +To synchronize two local directories: + + csync /home/csync/replica1 /home/csync/relplica2 + +Two synchronizer a local directory with an smb server, use + + csync /home/csync smb://rupert.galaxy.site/Users/csync + +If you use kerberos, you don't have to specify a username or a password. If you +don't use kerberos, the commandline client will ask about the user and the +password. If you don't want to be prompted, you can specify it on the +commandline: + + csync /home/csync smb://csync:secret@rupert.galaxy.site/Users/csync + +If you use the sftp protocol and want to specify a port, you do it the +following way: + + csync /home/csync sftp://csync@krikkit.galaxy.site:2222/home/csync + +The remote destination is supported by plugins. By default csync ships with smb +and sftp support. For more information, see the manpage of csync(1). + +Exclude lists +~~~~~~~~~~~~~ + +csync provides exclude lists with simple shell wildcard patterns. There is a +global exclude list, which is normally located in +'/etc/csync/csync_exclude.conf' and it has already some sane defaults. If you +run csync the first time, it will create an empty exclude list for the user. +This file will be '~/.csync/csync_exclude.conf'. csync considers both +configuration files and an additional one if you specify it. + +The entries in the file are newline separated. Use +'/etc/csync/csync_exclude.conf' as an example. + +Debug messages and dry run +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default the csync client logs to stderr and you can increase the debug +level with a commandline options. + +To simulate a run of the file synchronizer, you should set the priority to +'debug' for the categories 'csync.updater' and 'csync.reconciler' in the config +file '~/.csync/csync_log.conf'. Then run csync with the '--dry-run' option. +This will only run update detection and reconciliation. + +[[X80]] +The PAM module +~~~~~~~~~~~~~~ + +pam_csync is a PAM module to provide roaming home directories for a user +session. This module is aimed at environments with central file servers where a +user wishes to store his home directory. The Authentication Module verifies the +identity of a user and triggers a synchronization with the server on the first +login and the last logout. More information can be found in the manpage of the +module pam_csync(8) or pam itself pam(8). + + +[[X90]] +Appendix A: Packager Notes +-------------------------- + +Read the `README`, `INSTALL` and `FAQ` files (in the distribution root +directory). diff --git a/csync/doc/userguide/csync.html b/csync/doc/userguide/csync.html new file mode 100644 index 000000000..7322503fd --- /dev/null +++ b/csync/doc/userguide/csync.html @@ -0,0 +1,1119 @@ + + + + + +CSYNC User Guide + + + + + +
+
+
+

csync is a lightweight utility to synchronize files between two directories +on a system or between multiple systems.

+

It synchronizes bidirectionally and allows the user to keep two copies of files +and directories in sync. csync uses widely adopted protocols, such as smb or +sftp, so that there is no need for a server component. It is a user-level +program which means you don’t need to be a superuser or administrator.

+

Together with a Pluggable Authentication Module (PAM), the intent is to provide +Roaming Home Directories for Linux (see The PAM Module).

+
+
+
+

1. Introduction

+
+

It is often the case that we have multiple copies (called replicas) of a +filesystem or part of a filesystem (for example on a notebook and desktop +computer). Changes to each replica are often made independently, and as a +result, they do not contain the same information. In that case, a file +synchronizer is used to make them consistent again, without losing any +information.

+

The goal is to detect conflicting updates (files which have been modified) and +propagate non-conflicting updates to each replica. If there are no conflicts +left, we are done, and the replicas are identical. To resolve or handle +conflicts there are several algorithms available. They will be discussed +one of the following sections.

+
+
+
+

2. Basics

+
+

This section describes some basics of file synchronization.

+
+

2.1. Paths

+

A path normally refers to a point which contains a set of files which should be +synchronized. It is specified relative to the root of the replica locally, but +has to be absolute if you use a protocol. The path is just a sequence of names +separated by /.

+
+ + + +
+Note +The path separator is always a forward slash /, even for Windows.
+
+

csync always uses the absolute path on remote replicas. This could +sftp://gladiac:secret@myserver/home/gladiac for sftp.

+
+
+

2.2. What is an update?

+

The contents of a path could be a file, a directory or a symbolic link +(symbolic links are not supported yet). To be more precise, if the path refers +to:

+
    +
  • +

    +a regular file: the contents of the file are the byte stream and the + metadata of the file. +

    +
  • +
  • +

    +a directory: then the content is the metadata of the directory. +

    +
  • +
  • +

    +a symbolic link: the content is the named file the link points to. +

    +
  • +
+

csync keeps a record of each path which has been successfully synchronized. The +path gets compared with the record and if it has changed since the last +synchronization, we have an update. This is done by comparing the modification +or change (modification time of the metadata) time. This is the way how updates +are detected.

+
+
+

2.3. What is a conflict?

+

A path is conflicting if it fulfills the following conditions:

+
    +
  1. +

    +it has been updated in one replica, +

    +
  2. +
  3. +

    +it or any of its descendants has been updated on the other replica too, and +

    +
  4. +
  5. +

    +its contents in are not identical. +

    +
  6. +
+
+
+
+
+

3. File Synchronization

+
+

The primary goal of the file synchronizer is correctness. It may change +scattered or large parts of the filesystem. Since this in mostly not monitored +by the user, and the file synchronizer is in a position to harm the system, +csync must be safe, even in the case of unexpected errors (e.g. disk full). +What was done to make csync safe is described in the following sections.

+

One problem concerning correctness is the handling of conflicts. Each file +synchronizer tries to propagate conflicting changes to the other replica. At +the end both replicas should be identical. There are different strategies to +fulfill these goals.

+

csync is a three-phase file synchronizer. The decision for this design was that +user interaction should be possible and it should be easy to understand the +process. The three phases are update detection, reconciliation and propagation. +These will be described in the following sections.

+
+

3.1. Update detection

+

There are different strategies for update detection. csync uses a state-based +modtime-inode update detector. This means it uses the modification time to +detect updates. It doesn’t require many resources. A record of each file is +stored in a database (called statedb) and compared with the current +modification time during update detection. If the file has changed since the +last synchronization an instruction is set to evaluate it during the +reconciliation phase. If we don’t have a record for a file we investigate, it +is marked as new.

+

It can be difficult to detect renaming of files. This problem is also solved +by the record we store in the statedb. If we don’t find the file by the name +in the database, we search for the inode number. If the inode number is found +then the file has been renamed.

+
+
+

3.2. Reconciliation

+

The most important component is the update detector, because the reconciler +depends on it. The correctness of reconciler is mandatory because it can damage +a filesystem. It decides which file:

+
    +
  • +

    +Stays untouched +

    +
  • +
  • +

    +Has a conflict +

    +
  • +
  • +

    +Gets synchronized +

    +
  • +
  • +

    +or is deleted +

    +
  • +
+

A wrong decision of the reconciler leads in most cases to a loss of data. So +there are several conditions which a file synchronizer has to follow.

+
+

3.2.1. Algorithms

+

For conflict resolution several different algorithms could be implemented. The +most common algorithms are the merge and the conflict algorithm. The first +is a batch algorithm and the second is one which needs user interaction.

+
+
Merge algorithm
+

The merge algorithm is an algorithm which doesn’t need any user interaction. It +is simple and used for example by Microsoft for Roaming Profiles. If it detects +a conflict (the same file changed on both replicas) then it will use the most +recent file and overwrite the other. This means you can loose some data, but +normally you want the latest file.

+
+
+
Conflict algorithm
+

This is not implemented yet.

+

If a file has a conflict the user has to decide which file should be used.

+
+
+
+
+

3.3. Propagation

+

The next instance of the file synchronizer is the propagator. It uses the +calculated records to apply them on the current replica.

+

The propagator uses a two-phase-commit mechanism to simulate an atomic +filesystem operation.

+

In the first phase we copy the file to a temporary file on the opposite +replica. This has the advantage that we can check if the file which has been +copied to the opposite replica has been transferred successfully. If the +connection gets interrupted during the transfer we still have the original +states of the file. This means no data will be lost.

+

In the second phase the file on the opposite replica will be overwritten by +the temporary file.

+

After a successful propagation we have to merge the trees to reflect the +current state of the filesystem tree. This updated tree will be written as a +journal into the state database. It will be used during the update detection of +the next synchronization. See above for a description of the state database +during synchronization.

+
+
+

3.4. Robustness

+

This is a very important topic. The file synchronizer should not crash, and if +it has crashed, there should be no loss of data. To achieve this goal there are +several mechanisms which will be discussed in the following sections.

+
+

3.4.1. Crash resistance

+

The synchronization process can be interrupted by different events, this can +be:

+
    +
  • +

    +the system could be halted due to errors. +

    +
  • +
  • +

    +the disk could be full or the quota exceeded. +

    +
  • +
  • +

    +the network or power cable could be pulled out. +

    +
  • +
  • +

    +the user could force a stop of the synchronization process. +

    +
  • +
  • +

    +various communication errors could occur. +

    +
  • +
+

That no data will be lost due to an event we enforce the following invariant:

+
+ + + +
+Important +At every moment of the synchronization each file, has either its +original content or its correct final content.
+
+

This means that the original content can not be incorrect, no data can be lost +until we overwrite it after a successful synchronization. Therefore, each +interrupted synchronization process is a partial sync and can be continued and +completed by simply running csync again. The only problem could be an error of +the filesystem, so we reach this invariant only approximately.

+
+
+

3.4.2. Transfer errors

+

With the Two-Phase-Commit we check the file size after the file has transferred +and we are able to detect transfer errors. A more robust approach would be a +transfer protocol with checksums, but this is not doable at the moment. We may +add this in the future.

+

Future filesystems, like btrfs, will help to compare checksums instead of the +filesize. This will make the synchronization safer. This does not imply that it +is unsafe now, but checksums are safer than simple filesize checks.

+
+
+

3.4.3. Database loss

+

It is possible that the state database could get corrupted. If this happens, +all files get evaluated. In this case the file synchronizer wont delete any +file, but it could occur that deleted files will be restored from the other +replica.

+

To prevent a corruption or loss of the database if an error occurs or the user +forces an abort, the synchronizer is working on a copy of the database and will +use a Two-Phase-Commit to save it at the end.

+
+
+
+
+
+

4. Getting started

+
+
+

4.1. Installing csync

+

See the README and INSTALL files for install prerequisites and +procedures. Packagers should take a look at Appendix A: Packager Notes.

+
+
+

4.2. Using the commandline client

+

The synopsis of the commandline client is

+
+
+
csync [OPTION...] SOURCE DESTINATION
+
+

It synchronizes the content of SOURCE with DESTINATION and vice versa. The +DESTINATION can be a local directory or a remote file server.

+
+
+
csync /home/csync scheme://user:password@server:port/full/path
+
+
+

4.2.1. Examples

+

To synchronize two local directories:

+
+
+
csync /home/csync/replica1 /home/csync/relplica2
+
+

Two synchronizer a local directory with an smb server, use

+
+
+
csync /home/csync smb://rupert.galaxy.site/Users/csync
+
+

If you use kerberos, you don’t have to specify a username or a password. If you +don’t use kerberos, the commandline client will ask about the user and the +password. If you don’t want to be prompted, you can specify it on the +commandline:

+
+
+
csync /home/csync smb://csync:secret@rupert.galaxy.site/Users/csync
+
+

If you use the sftp protocol and want to specify a port, you do it the +following way:

+
+
+
csync /home/csync sftp://csync@krikkit.galaxy.site:2222/home/csync
+
+

The remote destination is supported by plugins. By default csync ships with smb +and sftp support. For more information, see the manpage of csync(1).

+
+
+
+

4.3. Exclude lists

+

csync provides exclude lists with simple shell wildcard patterns. There is a +global exclude list, which is normally located in +/etc/csync/csync_exclude.conf and it has already some sane defaults. If you +run csync the first time, it will create an empty exclude list for the user. +This file will be ~/.csync/csync_exclude.conf. csync considers both +configuration files and an additional one if you specify it.

+

The entries in the file are newline separated. Use +/etc/csync/csync_exclude.conf as an example.

+
+
+

4.4. Debug messages and dry run

+

By default the csync client logs to stderr and you can increase the debug +level with a commandline options.

+

To simulate a run of the file synchronizer, you should set the priority to +debug for the categories csync.updater and csync.reconciler in the config +file ~/.csync/csync_log.conf. Then run csync with the --dry-run option. +This will only run update detection and reconciliation.

+
+
+

4.5. The PAM module

+

pam_csync is a PAM module to provide roaming home directories for a user +session. This module is aimed at environments with central file servers where a +user wishes to store his home directory. The Authentication Module verifies the +identity of a user and triggers a synchronization with the server on the first +login and the last logout. More information can be found in the manpage of the +module pam_csync(8) or pam itself pam(8).

+
+
+
+
+

5. Appendix A: Packager Notes

+
+

Read the README, INSTALL and FAQ files (in the distribution root +directory).

+
+
+
+

+ + + diff --git a/csync/doc/userguide/images/icons/README b/csync/doc/userguide/images/icons/README new file mode 100644 index 000000000..f12b2a730 --- /dev/null +++ b/csync/doc/userguide/images/icons/README @@ -0,0 +1,5 @@ +Replaced the plain DocBook XSL admonition icons with Jimmac's DocBook +icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency +from the Jimmac icons to get round MS IE and FOP PNG incompatibilies. + +Stuart Rackham diff --git a/csync/doc/userguide/images/icons/callouts/1.png b/csync/doc/userguide/images/icons/callouts/1.png new file mode 100644 index 000000000..7d473430b Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/1.png differ diff --git a/csync/doc/userguide/images/icons/callouts/10.png b/csync/doc/userguide/images/icons/callouts/10.png new file mode 100644 index 000000000..997bbc824 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/10.png differ diff --git a/csync/doc/userguide/images/icons/callouts/11.png b/csync/doc/userguide/images/icons/callouts/11.png new file mode 100644 index 000000000..ce47dac3f Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/11.png differ diff --git a/csync/doc/userguide/images/icons/callouts/12.png b/csync/doc/userguide/images/icons/callouts/12.png new file mode 100644 index 000000000..31daf4e2f Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/12.png differ diff --git a/csync/doc/userguide/images/icons/callouts/13.png b/csync/doc/userguide/images/icons/callouts/13.png new file mode 100644 index 000000000..14021a89c Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/13.png differ diff --git a/csync/doc/userguide/images/icons/callouts/14.png b/csync/doc/userguide/images/icons/callouts/14.png new file mode 100644 index 000000000..64014b75f Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/14.png differ diff --git a/csync/doc/userguide/images/icons/callouts/15.png b/csync/doc/userguide/images/icons/callouts/15.png new file mode 100644 index 000000000..0d65765fc Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/15.png differ diff --git a/csync/doc/userguide/images/icons/callouts/2.png b/csync/doc/userguide/images/icons/callouts/2.png new file mode 100644 index 000000000..5d09341b2 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/2.png differ diff --git a/csync/doc/userguide/images/icons/callouts/3.png b/csync/doc/userguide/images/icons/callouts/3.png new file mode 100644 index 000000000..ef7b70047 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/3.png differ diff --git a/csync/doc/userguide/images/icons/callouts/4.png b/csync/doc/userguide/images/icons/callouts/4.png new file mode 100644 index 000000000..adb8364eb Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/4.png differ diff --git a/csync/doc/userguide/images/icons/callouts/5.png b/csync/doc/userguide/images/icons/callouts/5.png new file mode 100644 index 000000000..4d7eb4600 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/5.png differ diff --git a/csync/doc/userguide/images/icons/callouts/6.png b/csync/doc/userguide/images/icons/callouts/6.png new file mode 100644 index 000000000..0ba694af6 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/6.png differ diff --git a/csync/doc/userguide/images/icons/callouts/7.png b/csync/doc/userguide/images/icons/callouts/7.png new file mode 100644 index 000000000..472e96f8a Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/7.png differ diff --git a/csync/doc/userguide/images/icons/callouts/8.png b/csync/doc/userguide/images/icons/callouts/8.png new file mode 100644 index 000000000..5e60973c2 Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/8.png differ diff --git a/csync/doc/userguide/images/icons/callouts/9.png b/csync/doc/userguide/images/icons/callouts/9.png new file mode 100644 index 000000000..a0676d26c Binary files /dev/null and b/csync/doc/userguide/images/icons/callouts/9.png differ diff --git a/csync/doc/userguide/images/icons/caution.png b/csync/doc/userguide/images/icons/caution.png new file mode 100644 index 000000000..cb9d5ea0d Binary files /dev/null and b/csync/doc/userguide/images/icons/caution.png differ diff --git a/csync/doc/userguide/images/icons/example.png b/csync/doc/userguide/images/icons/example.png new file mode 100644 index 000000000..bba1c0010 Binary files /dev/null and b/csync/doc/userguide/images/icons/example.png differ diff --git a/csync/doc/userguide/images/icons/home.png b/csync/doc/userguide/images/icons/home.png new file mode 100644 index 000000000..37a5231ba Binary files /dev/null and b/csync/doc/userguide/images/icons/home.png differ diff --git a/csync/doc/userguide/images/icons/important.png b/csync/doc/userguide/images/icons/important.png new file mode 100644 index 000000000..1096c2329 Binary files /dev/null and b/csync/doc/userguide/images/icons/important.png differ diff --git a/csync/doc/userguide/images/icons/next.png b/csync/doc/userguide/images/icons/next.png new file mode 100644 index 000000000..64e126bdd Binary files /dev/null and b/csync/doc/userguide/images/icons/next.png differ diff --git a/csync/doc/userguide/images/icons/note.png b/csync/doc/userguide/images/icons/note.png new file mode 100644 index 000000000..841820f7c Binary files /dev/null and b/csync/doc/userguide/images/icons/note.png differ diff --git a/csync/doc/userguide/images/icons/prev.png b/csync/doc/userguide/images/icons/prev.png new file mode 100644 index 000000000..3e8f12fe2 Binary files /dev/null and b/csync/doc/userguide/images/icons/prev.png differ diff --git a/csync/doc/userguide/images/icons/tip.png b/csync/doc/userguide/images/icons/tip.png new file mode 100644 index 000000000..a3a029d89 Binary files /dev/null and b/csync/doc/userguide/images/icons/tip.png differ diff --git a/csync/doc/userguide/images/icons/up.png b/csync/doc/userguide/images/icons/up.png new file mode 100644 index 000000000..2db1ce62f Binary files /dev/null and b/csync/doc/userguide/images/icons/up.png differ diff --git a/csync/doc/userguide/images/icons/warning.png b/csync/doc/userguide/images/icons/warning.png new file mode 100644 index 000000000..0b0c419df Binary files /dev/null and b/csync/doc/userguide/images/icons/warning.png differ diff --git a/csync/src/CMakeLists.txt b/csync/src/CMakeLists.txt new file mode 100644 index 000000000..20676132b --- /dev/null +++ b/csync/src/CMakeLists.txt @@ -0,0 +1,134 @@ +project(libcsync) + +add_subdirectory(std) +add_subdirectory(httpbf) + +# Statically include sqlite +if (CSYNC_STATIC_COMPILE_DIR) + set(SQLITE3_INCLUDE_DIRS "") + set(SQLITE3_LIBRARIES "") + include_directories(${CSYNC_STATIC_COMPILE_DIR}) +else (CSYNC_STATIC_COMPILE_DIR) + find_package(SQLite3 3.3.9 REQUIRED) +endif() + +set(CSYNC_PUBLIC_INCLUDE_DIRS + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR} + CACHE INTERNAL "csync public include directories" +) + +set(CSYNC_PRIVATE_INCLUDE_DIRS + ${SQLITE3_INCLUDE_DIRS} + ${CSTDLIB_PUBLIC_INCLUDE_DIRS} + ${HTTPBF_PUBLIC_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} +) + +set(CSYNC_LIBRARY + ocsync + CACHE INTERNAL "ocsync library" +) + +set(CSYNC_LINK_LIBRARIES + ${CSYNC_LIBRARY} + ${CSTDLIB_LIBRARY} + ${CSYNC_REQUIRED_LIBRARIES} + ${SQLITE3_LIBRARIES} + ${NEON_LIBRARIES} + ${HTTPBF_LIBRARY} +) + +if(HAVE_ICONV AND WITH_ICONV) + list(APPEND CSYNC_PRIVATE_INCLUDE_DIRS ${ICONV_INCLUDE_DIR}) + list(APPEND CSYNC_LINK_LIBRARIES ${ICONV_LIBRARIES}) +endif() + +set(BLACKLIST_ON_ERROR 0 CACHE BOOL + "If an errors occurs three times on the same file, do not attempt to process that file any further.") + +if(BLACKLIST_ON_ERROR) + add_definitions(-DBLACKLIST_ON_ERROR) +endif() + +set(csync_SRCS + csync.c + csync_config.c + csync_exclude.c + csync_log.c + csync_statedb.c + csync_dbtree.c + csync_time.c + csync_util.c + csync_misc.c + csync_lock.c + + csync_update.c + csync_reconcile.c + + csync_rename.cc + + vio/csync_vio.c + vio/csync_vio_handle.c + vio/csync_vio_file_stat.c + vio/csync_vio_local.c + + csync_owncloud.c + csync_owncloud_recursive_propfind.c + csync_owncloud_util.c +) + +configure_file(csync_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/csync_version.h) + +set(csync_HDRS + ${CMAKE_CURRENT_BINARY_DIR}/csync_version.h + csync.h + vio/csync_vio.h + vio/csync_vio_file_stat.h + vio/csync_vio_handle.h + vio/csync_vio_method.h + vio/csync_vio_module.h +) + +# Statically include sqlite +if (CSYNC_STATIC_COMPILE_DIR) + list(APPEND csync_SRCS ${CSYNC_STATIC_COMPILE_DIR}/dictionary.c ${CSYNC_STATIC_COMPILE_DIR}/sqlite3.c) +endif() + +include_directories( + ${CSYNC_PUBLIC_INCLUDE_DIRS} + ${CSYNC_PRIVATE_INCLUDE_DIRS} +) + +add_library(${CSYNC_LIBRARY} SHARED ${csync_SRCS}) + +target_link_libraries(${CSYNC_LINK_LIBRARIES}) + +set_target_properties( + ${CSYNC_LIBRARY} + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} +) + +INSTALL( + TARGETS + ${CSYNC_LIBRARY} + LIBRARY DESTINATION + ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION + ${LIB_INSTALL_DIR} + RUNTIME DESTINATION + ${BIN_INSTALL_DIR} +) + +# INSTALL( +# FILES +# ${csync_HDRS} +# DESTINATION +# ${INCLUDE_INSTALL_DIR}/${APPLICATION_NAME} +# ) + diff --git a/csync/src/csync.c b/csync/src/csync.c new file mode 100644 index 000000000..48d901e91 --- /dev/null +++ b/csync/src/csync.c @@ -0,0 +1,1070 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ICONV_H +#include +#endif +#ifdef HAVE_SYS_ICONV_H +#include +#endif + +#include "c_lib.h" +#include "csync_private.h" +#include "csync_config.h" +#include "csync_exclude.h" +#include "csync_lock.h" +#include "csync_statedb.h" +#include "csync_time.h" +#include "csync_util.h" +#include "csync_misc.h" +#include "std/c_private.h" + +#include "csync_update.h" +#include "csync_reconcile.h" + +#include "vio/csync_vio.h" + +#include "csync_log.h" +#include "csync_rename.h" + +static int _key_cmp(const void *key, const void *data) { + uint64_t a; + csync_file_stat_t *b; + + a = *(uint64_t *) (key); + b = (csync_file_stat_t *) data; + + if (a < b->phash) { + return -1; + } else if (a > b->phash) { + return 1; + } + + return 0; +} + +static int _data_cmp(const void *key, const void *data) { + csync_file_stat_t *a, *b; + + a = (csync_file_stat_t *) key; + b = (csync_file_stat_t *) data; + + if (a->phash < b->phash) { + return -1; + } else if (a->phash > b->phash) { + return 1; + } + + return 0; +} + +int csync_create(CSYNC **csync, const char *local, const char *remote) { + CSYNC *ctx; + size_t len = 0; + char *home; + int rc; + + ctx = c_malloc(sizeof(CSYNC)); + if (ctx == NULL) { + return -1; + } + + ctx->status_code = CSYNC_STATUS_OK; + + /* remove trailing slashes */ + len = strlen(local); + while(len > 0 && local[len - 1] == '/') --len; + + ctx->local.uri = c_strndup(local, len); + if (ctx->local.uri == NULL) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + free(ctx); + return -1; + } + + /* remove trailing slashes */ + len = strlen(remote); + while(len > 0 && remote[len - 1] == '/') --len; + + ctx->remote.uri = c_strndup(remote, len); + if (ctx->remote.uri == NULL) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + free(ctx); + return -1; + } + + ctx->status_code = CSYNC_STATUS_OK; + ctx->options.max_depth = MAX_DEPTH; + ctx->options.max_time_difference = MAX_TIME_DIFFERENCE; + ctx->options.unix_extensions = 0; + ctx->options.with_conflict_copys=false; + ctx->options.local_only_mode = false; + + ctx->pwd.uid = getuid(); + ctx->pwd.euid = geteuid(); + + home = csync_get_user_home_dir(); + if (home == NULL) { + SAFE_FREE(ctx->local.uri); + SAFE_FREE(ctx->remote.uri); + SAFE_FREE(ctx); + errno = ENOMEM; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + return -1; + } + + rc = asprintf(&ctx->options.config_dir, "%s/%s", home, CSYNC_CONF_DIR); + SAFE_FREE(home); + if (rc < 0) { + SAFE_FREE(ctx->local.uri); + SAFE_FREE(ctx->remote.uri); + SAFE_FREE(ctx); + errno = ENOMEM; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + return -1; + } + + ctx->local.list = 0; + ctx->remote.list = 0; + ctx->current_fs = NULL; + + ctx->abort = false; + + *csync = ctx; + return 0; +} + +int csync_init(CSYNC *ctx) { + int rc; + time_t timediff = -1; + char *exclude = NULL; + char *lock = NULL; + char *config = NULL; + char errbuf[256] = {0}; + + if (ctx == NULL) { + errno = EBADF; + return -1; + } + + ctx->status_code = CSYNC_STATUS_OK; + + /* Do not initialize twice */ + if (ctx->status & CSYNC_STATUS_INIT) { + return 1; + } + + /* create dir if it doesn't exist */ + if (! c_isdir(ctx->options.config_dir)) { + c_mkdirs(ctx->options.config_dir, 0700); + } + + /* create lock file */ + if (asprintf(&lock, "%s/%s", ctx->local.uri, CSYNC_LOCK_FILE) < 0) { + rc = -1; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + goto out; + } + + if (csync_lock(lock) < 0) { + rc = -1; + ctx->status_code = CSYNC_STATUS_NO_LOCK; + goto out; + } + + /* load config file */ + if (asprintf(&config, "%s/%s", ctx->options.config_dir, CSYNC_CONF_FILE) < 0) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + rc = -1; + goto out; + } + + rc = csync_config_parse_file(ctx, config); + if (rc < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Could not load config file %s, using defaults.", config); + } + +#ifndef _WIN32 + /* load global exclude list */ + if (asprintf(&exclude, "%s/ocsync/%s", SYSCONFDIR, CSYNC_EXCLUDE_FILE) < 0) { + rc = -1; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + goto out; + } + + if (csync_exclude_load(ctx, exclude) < 0) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Could not load %s - %s", exclude, + errbuf); + } + SAFE_FREE(exclude); +#endif + /* load exclude list */ + if (asprintf(&exclude, "%s/%s", ctx->options.config_dir, + CSYNC_EXCLUDE_FILE) < 0) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + rc = -1; + goto out; + } + + if (csync_exclude_load(ctx, exclude) < 0) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "Could not load %s - %s", exclude, + errbuf); + } + + ctx->local.type = LOCAL_REPLICA; + + /* check for uri */ + if ( !ctx->options.local_only_mode && csync_fnmatch("*://*", ctx->remote.uri, 0) == 0) { + size_t len; + len = strstr(ctx->remote.uri, "://") - ctx->remote.uri; + /* get protocol */ + if (len > 0) { + char *module = NULL; + /* module name */ + module = c_strndup(ctx->remote.uri, len); + if (module == NULL) { + rc = -1; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + goto out; + } + /* load module */ +retry_vio_init: + rc = csync_vio_init(ctx, module, NULL); + if (rc < 0) { + len = strlen(module); + + if (len > 0 && module[len-1] == 's') { + module[len-1] = '\0'; + goto retry_vio_init; + } + /* Now vio init finally failed which means a module could not be found. */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "The csync module %s could not be loaded.", module); + SAFE_FREE(module); + ctx->status_code = CSYNC_STATUS_NO_MODULE; + goto out; + } + SAFE_FREE(module); + ctx->remote.type = REMOTE_REPLICA; + } + } else { + ctx->remote.type = LOCAL_REPLICA; + } + + if(!ctx->options.local_only_mode) { + if(ctx->module.capabilities.time_sync_required) { + timediff = csync_timediff(ctx); + if (timediff > ctx->options.max_time_difference) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Clock skew detected. The time difference is greater than %d seconds!", + ctx->options.max_time_difference); + ctx->status_code = CSYNC_STATUS_TIMESKEW; + rc = -1; + goto out; + } else if (timediff < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "Synchronisation is not possible!"); + ctx->status_code = CSYNC_STATUS_TIMESKEW; + rc = -1; + goto out; + } + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Module does not need time synchronization."); + } + + if(ctx->module.capabilities.unix_extensions == -1) { /* detect */ + if (csync_unix_extensions(ctx) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "Could not detect filesystem type."); + ctx->status_code = CSYNC_STATUS_FILESYSTEM_UNKNOWN; + rc = -1; + goto out; + } + } else { + /* The module specifies the value for the unix_extensions. */ + ctx->options.unix_extensions = ctx->module.capabilities.unix_extensions; + } + } + + if (ctx->options.timeout) + csync_vio_set_property(ctx, "timeout", &ctx->options.timeout); + + if (c_rbtree_create(&ctx->local.tree, _key_cmp, _data_cmp) < 0) { + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + rc = -1; + goto out; + } + + if (c_rbtree_create(&ctx->remote.tree, _key_cmp, _data_cmp) < 0) { + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + rc = -1; + goto out; + } + + ctx->status = CSYNC_STATUS_INIT; + + csync_lock_remove(lock); + + csync_set_module_property(ctx, "csync_context", ctx); + + /* initialize random generator */ + srand(time(NULL)); + + rc = 0; + +out: + SAFE_FREE(lock); + SAFE_FREE(exclude); + SAFE_FREE(config); + return rc; +} + +int csync_update(CSYNC *ctx) { + int rc = -1; + struct timespec start, finish; + char *lock = NULL; + + if (ctx == NULL) { + errno = EBADF; + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + /* try to create lock file */ + if (asprintf(&lock, "%s/%s", ctx->local.uri, CSYNC_LOCK_FILE) < 0) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + rc = -1; + return rc; + } + + if (csync_lock(lock) < 0) { + ctx->status_code = CSYNC_STATUS_NO_LOCK; + rc = -1; + return rc; + } + + SAFE_FREE(lock); + + /* create/load statedb */ + if (! csync_is_statedb_disabled(ctx)) { + rc = asprintf(&ctx->statedb.file, "%s/.csync_journal.db", + ctx->local.uri); + if (rc < 0) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + return rc; + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Journal: %s", ctx->statedb.file); + + if (csync_statedb_load(ctx, ctx->statedb.file, &ctx->statedb.db) < 0) { + ctx->status_code = CSYNC_STATUS_STATEDB_LOAD_ERROR; + rc = -1; + return rc; + } + } + + ctx->status_code = CSYNC_STATUS_OK; + + csync_memstat_check(); + + /* update detection for local replica */ + csync_gettime(&start); + ctx->current = LOCAL_REPLICA; + ctx->replica = ctx->local.type; + + rc = csync_ftw(ctx, ctx->local.uri, csync_walker, MAX_DEPTH); + if (rc < 0) { + if(ctx->status_code == CSYNC_STATUS_OK) + ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_UPDATE_ERROR); + return -1; + } + + csync_gettime(&finish); + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Update detection for local replica took %.2f seconds walking %zu files.", + c_secdiff(finish, start), c_rbtree_size(ctx->local.tree)); + csync_memstat_check(); + + if (rc < 0) { + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + return -1; + } + + /* update detection for remote replica */ + if( ! ctx->options.local_only_mode ) { + csync_gettime(&start); + ctx->current = REMOTE_REPLICA; + ctx->replica = ctx->remote.type; + + rc = csync_ftw(ctx, ctx->remote.uri, csync_walker, MAX_DEPTH); + if (rc < 0) { + if(ctx->status_code == CSYNC_STATUS_OK) + ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_UPDATE_ERROR); + return -1; + } + + + csync_gettime(&finish); + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Update detection for remote replica took %.2f seconds " + "walking %zu files.", + c_secdiff(finish, start), c_rbtree_size(ctx->remote.tree)); + csync_memstat_check(); + } + ctx->status |= CSYNC_STATUS_UPDATE; + + return 0; +} + +int csync_reconcile(CSYNC *ctx) { + int rc = -1; + struct timespec start, finish; + + if (ctx == NULL) { + errno = EBADF; + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + /* Reconciliation for local replica */ + csync_gettime(&start); + + ctx->current = LOCAL_REPLICA; + ctx->replica = ctx->local.type; + + rc = csync_reconcile_updates(ctx); + + csync_gettime(&finish); + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Reconciliation for local replica took %.2f seconds visiting %zu files.", + c_secdiff(finish, start), c_rbtree_size(ctx->local.tree)); + + if (rc < 0) { + if (!CSYNC_STATUS_IS_OK(ctx->status_code)) { + ctx->status_code = csync_errno_to_status( errno, CSYNC_STATUS_RECONCILE_ERROR ); + } + return -1; + } + + /* Reconciliation for local replica */ + csync_gettime(&start); + + ctx->current = REMOTE_REPLICA; + ctx->replica = ctx->remote.type; + + rc = csync_reconcile_updates(ctx); + + csync_gettime(&finish); + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Reconciliation for remote replica took %.2f seconds visiting %zu files.", + c_secdiff(finish, start), c_rbtree_size(ctx->remote.tree)); + + if (rc < 0) { + if (!CSYNC_STATUS_IS_OK(ctx->status_code)) { + ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_RECONCILE_ERROR ); + } + return -1; + } + + ctx->status |= CSYNC_STATUS_RECONCILE; + + return 0; +} + +/* + * local visitor which calls the user visitor with repacked stat info. + */ +static int _csync_treewalk_visitor(void *obj, void *data) { + int rc = 0; + csync_file_stat_t *cur = NULL; + CSYNC *ctx = NULL; + c_rbtree_visit_func *visitor = NULL; + _csync_treewalk_context *twctx = NULL; + TREE_WALK_FILE trav; + + cur = (csync_file_stat_t *) obj; + ctx = (CSYNC *) data; + + if (ctx == NULL) { + return -1; + } + + if (obj == NULL || data == NULL) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + twctx = (_csync_treewalk_context*) ctx->callbacks.userdata; + if (twctx == NULL) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + + if (twctx->instruction_filter > 0 && + !(twctx->instruction_filter & cur->instruction) ) { + return 0; + } + + visitor = (c_rbtree_visit_func*)(twctx->user_visitor); + if (visitor != NULL) { + trav.path = cur->path; + trav.size = cur->size; + trav.modtime = cur->modtime; + trav.uid = cur->uid; + trav.gid = cur->gid; + trav.mode = cur->mode; + trav.type = cur->type; + trav.instruction = cur->instruction; + trav.rename_path = cur->destpath; + trav.etag = cur->etag; + trav.file_id = cur->file_id; + + trav.error_status = cur->error_status; + trav.should_update_etag = cur->should_update_etag; + + rc = (*visitor)(&trav, twctx->userdata); + cur->instruction = trav.instruction; + if (trav.etag != cur->etag) { + SAFE_FREE(cur->etag); + cur->etag = c_strdup(trav.etag); + } + return rc; + } + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; +} + +/* + * treewalk function, called from its wrappers below. + * + * it encapsulates the user visitor function, the filter and the userdata + * into a treewalk_context structure and calls the rb treewalk function, + * which calls the local _csync_treewalk_visitor in this module. + * The user visitor is called from there. + */ +static int _csync_walk_tree(CSYNC *ctx, c_rbtree_t *tree, csync_treewalk_visit_func *visitor, int filter) +{ + _csync_treewalk_context tw_ctx; + int rc = -1; + + if (ctx == NULL) { + errno = EBADF; + return rc; + } + + if (visitor == NULL || tree == NULL) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return rc; + } + + tw_ctx.userdata = ctx->callbacks.userdata; + tw_ctx.user_visitor = visitor; + tw_ctx.instruction_filter = filter; + + ctx->callbacks.userdata = &tw_ctx; + + rc = c_rbtree_walk(tree, (void*) ctx, _csync_treewalk_visitor); + if( rc < 0 ) { + if( ctx->status_code == CSYNC_STATUS_OK ) + ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_TREE_ERROR); + } + ctx->callbacks.userdata = tw_ctx.userdata; + + return rc; +} + +/* + * wrapper function for treewalk on the remote tree + */ +int csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter) +{ + c_rbtree_t *tree = NULL; + int rc = -1; + + if(ctx != NULL) { + ctx->status_code = CSYNC_STATUS_OK; + tree = ctx->remote.tree; + } + + /* all error handling in the called function */ + rc = _csync_walk_tree(ctx, tree, visitor, filter); + return rc; +} + +/* + * wrapper function for treewalk on the local tree + */ +int csync_walk_local_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter) +{ + c_rbtree_t *tree = NULL; + int rc = -1; + + if (ctx != NULL) { + ctx->status_code = CSYNC_STATUS_OK; + tree = ctx->local.tree; + } + + /* all error handling in the called function */ + rc = _csync_walk_tree(ctx, tree, visitor, filter); + return rc; +} + +static void _tree_destructor(void *data) { + csync_file_stat_t *freedata = NULL; + + freedata = (csync_file_stat_t *) data; + csync_file_stat_free(freedata); +} + +/* reset all the list to empty. + * used by csync_commit and csync_destroy */ +static void _csync_clean_ctx(CSYNC *ctx) +{ + c_list_t * walk; + + /* destroy the rbtrees */ + if (c_rbtree_size(ctx->local.tree) > 0) { + c_rbtree_destroy(ctx->local.tree, _tree_destructor); + } + + if (c_rbtree_size(ctx->remote.tree) > 0) { + c_rbtree_destroy(ctx->remote.tree, _tree_destructor); + } + + csync_rename_destroy(ctx); + + for (walk = c_list_last(ctx->local.ignored_cleanup); walk != NULL; walk = c_list_prev(walk)) { + SAFE_FREE(walk->data); + } + for (walk = c_list_last(ctx->remote.ignored_cleanup); walk != NULL; walk = c_list_prev(walk)) { + SAFE_FREE(walk->data); + } + + /* free memory */ + c_rbtree_free(ctx->local.tree); + c_list_free(ctx->local.list); + c_list_free(ctx->local.ignored_cleanup); + c_rbtree_free(ctx->remote.tree); + c_list_free(ctx->remote.list); + c_list_free(ctx->remote.ignored_cleanup); + + ctx->remote.list = 0; + ctx->local.list = 0; + ctx->remote.ignored_cleanup = 0; + ctx->local.ignored_cleanup = 0; + + SAFE_FREE(ctx->statedb.file); +} + +int csync_commit(CSYNC *ctx) { + int rc = 0; + + if (ctx == NULL) { + return -1; + } + + ctx->status_code = CSYNC_STATUS_OK; + + if (ctx->statedb.db != NULL + && csync_statedb_close(ctx->statedb.file, ctx->statedb.db, 0) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "ERR: closing of statedb failed."); + rc = -1; + } + ctx->statedb.db = NULL; + + rc = csync_vio_commit(ctx); + if (rc < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "commit failed: %s", + ctx->error_string ? ctx->error_string : ""); + goto out; + } + + _csync_clean_ctx(ctx); + + ctx->remote.read_from_db = 0; + + /* Create new trees */ + rc = c_rbtree_create(&ctx->local.tree, _key_cmp, _data_cmp); + if (rc < 0) { + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + goto out; + } + + rc = c_rbtree_create(&ctx->remote.tree, _key_cmp, _data_cmp); + if (rc < 0) { + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + goto out; + } + + /* reset the progress */ + ctx->overall_progress.file_count = 0; + ctx->overall_progress.current_file_no = 0; + ctx->overall_progress.byte_sum = 0; + ctx->overall_progress.byte_current = 0; + + ctx->status = CSYNC_STATUS_INIT; + SAFE_FREE(ctx->error_string); + + rc = 0; + +out: + return rc; +} + +int csync_destroy(CSYNC *ctx) { + char *lock = NULL; + int rc; + + if (ctx == NULL) { + errno = EBADF; + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + if (ctx->statedb.db != NULL + && csync_statedb_close(ctx->statedb.file, ctx->statedb.db, 0) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "ERR: closing of statedb failed."); + rc = -1; + } + ctx->statedb.db = NULL; + + csync_vio_shutdown(ctx); + + /* destroy exclude list */ + csync_exclude_destroy(ctx); + + /* remove the lock file */ + rc = asprintf(&lock, "%s/%s", ctx->options.config_dir, CSYNC_LOCK_FILE); + if (rc > 0) { + csync_lock_remove(lock); + } + + _csync_clean_ctx(ctx); + + SAFE_FREE(ctx->local.uri); + SAFE_FREE(ctx->remote.uri); + SAFE_FREE(ctx->options.config_dir); + SAFE_FREE(ctx->error_string); + +#ifdef WITH_ICONV + c_close_iconv(); +#endif + + SAFE_FREE(ctx); + + SAFE_FREE(lock); + + return 0; +} + +/* Check if csync is the required version or get the version string. */ +const char *csync_version(int req_version) { + if (req_version <= LIBCSYNC_VERSION_INT) { + return CSYNC_STRINGIFY(LIBCSYNC_VERSION); + } + + return NULL; +} + +int csync_add_exclude_list(CSYNC *ctx, const char *path) { + if (ctx == NULL || path == NULL) { + return -1; + } + + return csync_exclude_load(ctx, path); +} + +void csync_clear_exclude_list(CSYNC *ctx) +{ + csync_exclude_clear(ctx); +} + +const char *csync_get_config_dir(CSYNC *ctx) { + if (ctx == NULL) { + return NULL; + } + + return ctx->options.config_dir; +} + +int csync_set_config_dir(CSYNC *ctx, const char *path) { + if (ctx == NULL || path == NULL) { + return -1; + } + + SAFE_FREE(ctx->options.config_dir); + ctx->options.config_dir = c_strdup(path); + if (ctx->options.config_dir == NULL) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + return -1; + } + + return 0; +} + +int csync_enable_statedb(CSYNC *ctx) { + if (ctx == NULL) { + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + if (ctx->status & CSYNC_STATUS_INIT) { + fprintf(stderr, "This function must be called before initialization."); + ctx->status_code = CSYNC_STATUS_CSYNC_STATUS_ERROR; + return -1; + } + + ctx->statedb.disabled = 0; + + return 0; +} + +int csync_disable_statedb(CSYNC *ctx) { + if (ctx == NULL) { + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + if (ctx->status & CSYNC_STATUS_INIT) { + fprintf(stderr, "This function must be called before initialization."); + ctx->status_code = CSYNC_STATUS_CSYNC_STATUS_ERROR; + return -1; + } + + ctx->statedb.disabled = 1; + + return 0; +} + +int csync_is_statedb_disabled(CSYNC *ctx) { + if (ctx == NULL) { + return -1; + } + return ctx->statedb.disabled; +} + +int csync_set_auth_callback(CSYNC *ctx, csync_auth_callback cb) { + if (ctx == NULL || cb == NULL) { + return -1; + } + + if (ctx->status & CSYNC_STATUS_INIT) { + ctx->status_code = CSYNC_STATUS_CSYNC_STATUS_ERROR; + fprintf(stderr, "This function must be called before initialization."); + return -1; + } + ctx->callbacks.auth_function = cb; + + return 0; +} + +const char *csync_get_statedb_file(CSYNC *ctx) { + if (ctx == NULL) { + return NULL; + } + ctx->status_code = CSYNC_STATUS_OK; + + return c_strdup(ctx->statedb.file); +} + +void *csync_get_userdata(CSYNC *ctx) { + if (ctx == NULL) { + return NULL; + } + return ctx->callbacks.userdata; +} + +int csync_set_userdata(CSYNC *ctx, void *userdata) { + if (ctx == NULL) { + return -1; + } + + ctx->callbacks.userdata = userdata; + + return 0; +} + +csync_auth_callback csync_get_auth_callback(CSYNC *ctx) { + if (ctx == NULL) { + return NULL; + } + + return ctx->callbacks.auth_function; +} + +int csync_set_status(CSYNC *ctx, int status) { + if (ctx == NULL || status < 0) { + return -1; + } + + ctx->status = status; + + return 0; +} + +CSYNC_STATUS csync_get_status(CSYNC *ctx) { + if (ctx == NULL) { + return -1; + } + + return ctx->status_code; +} + +int csync_enable_conflictcopys(CSYNC* ctx){ + if (ctx == NULL) { + return -1; + } + + if (ctx->status & CSYNC_STATUS_INIT) { + fprintf(stderr, "This function must be called before initialization."); + ctx->status_code = CSYNC_STATUS_CSYNC_STATUS_ERROR; + return -1; + } + + ctx->options.with_conflict_copys=true; + + return 0; +} + +int csync_set_local_only(CSYNC *ctx, bool local_only) { + if (ctx == NULL) { + return -1; + } + + ctx->status_code = CSYNC_STATUS_OK; + + if (ctx->status & CSYNC_STATUS_INIT) { + fprintf(stderr, "csync_set_local_only: This function must be called before initialization."); + ctx->status_code = CSYNC_STATUS_CSYNC_STATUS_ERROR; + return -1; + } + + ctx->options.local_only_mode=local_only; + + return 0; +} + +bool csync_get_local_only(CSYNC *ctx) { + if (ctx == NULL) { + return -1; + } + ctx->status_code = CSYNC_STATUS_OK; + + return ctx->options.local_only_mode; +} + +const char *csync_get_status_string(CSYNC *ctx) +{ + return csync_vio_get_status_string(ctx); +} + +#ifdef WITH_ICONV +int csync_set_iconv_codec(const char *from) +{ + c_close_iconv(); + + if (from != NULL) { + c_setup_iconv(from); + } + + return 0; +} +#endif + +void csync_request_abort(CSYNC *ctx) +{ + if (ctx != NULL) { + ctx->abort = true; + } +} + +void csync_resume(CSYNC *ctx) +{ + if (ctx != NULL) { + ctx->abort = false; + } +} + +int csync_abort_requested(CSYNC *ctx) +{ + if (ctx != NULL) { + return ctx->abort; + } else { + return (1 == 0); + } +} + +void csync_file_stat_free(csync_file_stat_t *st) +{ + if (st) { + SAFE_FREE(st->etag); + SAFE_FREE(st->destpath); + SAFE_FREE(st); + } +} + +int csync_set_module_property(CSYNC* ctx, const char* key, void* value) +{ + return csync_vio_set_property(ctx, key, value); +} + +int csync_set_progress_callback(CSYNC* ctx, csync_progress_callback cb) +{ + if (ctx == NULL) { + return -1; + } + if (cb == NULL ) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + + ctx->callbacks.progress_cb = cb; + ctx->status_code = CSYNC_STATUS_OK; + return 0; + +} + +csync_progress_callback csync_get_progress_callback(CSYNC *ctx) +{ + if (ctx == NULL) { + return NULL; + } + + return ctx->callbacks.progress_cb; +} diff --git a/csync/src/csync.h b/csync/src/csync.h new file mode 100644 index 000000000..5a903b383 --- /dev/null +++ b/csync/src/csync.h @@ -0,0 +1,636 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file csync.h + * + * @brief Application developer interface for csync. + * + * @defgroup csyncPublicAPI csync public API + * + * @{ + */ + +#ifndef _CSYNC_H +#define _CSYNC_H + +#include +#include +#include +#include +#include + +#include "csync_version.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * csync file declarations + */ +#define CSYNC_CONF_DIR ".ocsync" +#define CSYNC_CONF_FILE "ocsync.conf" +#define CSYNC_EXCLUDE_FILE "ocsync_exclude.conf" +#define CSYNC_LOCK_FILE ".csync.lock" + +/** + * Instruction enum. In the file traversal structure, it describes + * the csync state of a file. + */ +enum csync_status_codes_e { + CSYNC_STATUS_OK = 0, + + CSYNC_STATUS_ERROR = 1024, /* don't use this code, + just use in csync_status_ok */ + CSYNC_STATUS_UNSUCCESSFUL, + CSYNC_STATUS_NO_LOCK, + CSYNC_STATUS_STATEDB_LOAD_ERROR, + CSYNC_STATUS_STATEDB_WRITE_ERROR, + CSYNC_STATUS_NO_MODULE, + CSYNC_STATUS_TIMESKEW, + CSYNC_STATUS_FILESYSTEM_UNKNOWN, + CSYNC_STATUS_TREE_ERROR, + CSYNC_STATUS_MEMORY_ERROR, + CSYNC_STATUS_PARAM_ERROR, + CSYNC_STATUS_UPDATE_ERROR, + CSYNC_STATUS_RECONCILE_ERROR, + CSYNC_STATUS_PROPAGATE_ERROR, + CSYNC_STATUS_REMOTE_ACCESS_ERROR, + CSYNC_STATUS_REMOTE_CREATE_ERROR, + CSYNC_STATUS_REMOTE_STAT_ERROR, + CSYNC_STATUS_LOCAL_CREATE_ERROR, + CSYNC_STATUS_LOCAL_STAT_ERROR, + CSYNC_STATUS_PROXY_ERROR, + CSYNC_STATUS_LOOKUP_ERROR, + CSYNC_STATUS_SERVER_AUTH_ERROR, + CSYNC_STATUS_PROXY_AUTH_ERROR, + CSYNC_STATUS_CONNECT_ERROR, + CSYNC_STATUS_TIMEOUT, + CSYNC_STATUS_HTTP_ERROR, + CSYNC_STATUS_PERMISSION_DENIED, + CSYNC_STATUS_NOT_FOUND, + CSYNC_STATUS_FILE_EXISTS, + CSYNC_STATUS_OUT_OF_SPACE, + CSYNC_STATUS_QUOTA_EXCEEDED, + CSYNC_STATUS_SERVICE_UNAVAILABLE, + CSYNC_STATUS_FILE_SIZE_ERROR, + CSYNC_STATUS_CONTEXT_LOST, + CSYNC_STATUS_MERGE_FILETREE_ERROR, + CSYNC_STATUS_CSYNC_STATUS_ERROR, + CSYNC_STATUS_OPENDIR_ERROR, + CSYNC_STATUS_READDIR_ERROR, + CSYNC_STATUS_OPEN_ERROR, + CSYNC_STATUS_ABORTED, + /* Codes for file individual status: */ + CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK, + CSYNC_STATUS_INDIVIDUAL_IGNORE_LIST, + CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS +}; + +typedef enum csync_status_codes_e CSYNC_STATUS; + +#ifndef likely +# define likely(x) (x) +#endif +#ifndef unlikely +# define unlikely(x) (x) +#endif + +#define CSYNC_STATUS_IS_OK(x) (likely((x) == CSYNC_STATUS_OK)) +#define CSYNC_STATUS_IS_ERR(x) (unlikely((x) >= CSYNC_STATUS_ERROR)) +#define CSYNC_STATUS_IS_EQUAL(x, y) ((x) == (y)) + + +enum csync_instructions_e { + CSYNC_INSTRUCTION_NONE = 0x00000000, /* Nothing to do (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_EVAL = 0x00000001, /* There was changed compared to the DB (UPDATE) */ + CSYNC_INSTRUCTION_REMOVE = 0x00000002, /* The file need to be removed (RECONCILE) */ + CSYNC_INSTRUCTION_RENAME = 0x00000004, /* The file need to be renamed (RECONCILE) */ + CSYNC_INSTRUCTION_EVAL_RENAME= 0x00000800, /* The file is new, it is the destination of a rename (UPDATE) */ + CSYNC_INSTRUCTION_NEW = 0x00000008, /* The file is new compared to the db (UPDATE) */ + CSYNC_INSTRUCTION_CONFLICT = 0x00000010, /* The file need to be downloaded because it is a conflict (RECONCILE) */ + CSYNC_INSTRUCTION_IGNORE = 0x00000020, /* The file is ignored (UPDATE|RECONCILE) */ + CSYNC_INSTRUCTION_SYNC = 0x00000040, /* The file need to be pushed to the other remote (RECONCILE) */ + CSYNC_INSTRUCTION_STAT_ERROR = 0x00000080, + CSYNC_INSTRUCTION_ERROR = 0x00000100, + /* instructions for the propagator */ + CSYNC_INSTRUCTION_DELETED = 0x00000200, + CSYNC_INSTRUCTION_UPDATED = 0x00000400 +}; + +enum csync_ftw_type_e { + CSYNC_FTW_TYPE_FILE, + CSYNC_FTW_TYPE_SLINK, + CSYNC_FTW_TYPE_DIR, + CSYNC_FTW_TYPE_SKIP +}; + +enum csync_notify_type_e { + CSYNC_NOTIFY_INVALID, + CSYNC_NOTIFY_START_SYNC_SEQUENCE, + CSYNC_NOTIFY_START_DOWNLOAD, + CSYNC_NOTIFY_START_UPLOAD, + CSYNC_NOTIFY_PROGRESS, + CSYNC_NOTIFY_FINISHED_DOWNLOAD, + CSYNC_NOTIFY_FINISHED_UPLOAD, + CSYNC_NOTIFY_FINISHED_SYNC_SEQUENCE, + CSYNC_NOTIFY_START_DELETE, + CSYNC_NOTIFY_END_DELETE, + CSYNC_NOTIFY_ERROR +}; + +struct csync_progress_s { + enum csync_notify_type_e kind; + + /* individual file progress information */ + const char *path; + int64_t curr_bytes; + int64_t file_size; + + /* overall progress */ + int64_t overall_transmission_size; + int64_t current_overall_bytes; + int64_t overall_file_count; + int64_t current_file_no; + +}; +typedef struct csync_progress_s CSYNC_PROGRESS; + +/** + * CSync File Traversal structure. + * + * This structure is passed to the visitor function for every file + * which is seen. + * + */ + +struct csync_tree_walk_file_s { + const char *path; + int64_t size; + time_t modtime; +#ifdef _WIN32 + uint32_t uid; + uint32_t gid; +#else + uid_t uid; + gid_t gid; +#endif + mode_t mode; + enum csync_ftw_type_e type; + enum csync_instructions_e instruction; + + /* For directories: If the etag has been updated and need to be writen on the db */ + int should_update_etag; + + const char *rename_path; + const char *etag; + const char *file_id; + CSYNC_STATUS error_status; +}; +typedef struct csync_tree_walk_file_s TREE_WALK_FILE; + +/** + * csync handle + */ +typedef struct csync_s CSYNC; + +typedef int (*csync_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +typedef void (*csync_log_callback) (int verbosity, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief Check internal csync status. + * + * @param csync The context to check. + * + * @return true if status is error free, false for error states. + */ +bool csync_status_ok(CSYNC *ctx); + +/** + * @brief Allocate a csync context. + * + * @param csync The context variable to allocate. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_create(CSYNC **csync, const char *local, const char *remote); + +/** + * @brief Initialize the file synchronizer. + * + * This function loads the configuration, the statedb and locks the client. + * + * @param ctx The context to initialize. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_init(CSYNC *ctx); + +/** + * @brief Update detection + * + * @param ctx The context to run the update detection on. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_update(CSYNC *ctx); + +/** + * @brief Reconciliation + * + * @param ctx The context to run the reconciliation on. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_reconcile(CSYNC *ctx); + +/** + * @brief Propagation + * + * @param ctx The context to run the propagation on. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_propagate(CSYNC *ctx); + +/** + * @brief Commit the sync results to journal + * + * @param ctx The context to commit. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_commit(CSYNC *ctx); + +/** + * @brief Destroy the csync context + * + * Writes the statedb, unlocks csync and frees the memory. + * + * @param ctx The context to destroy. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_destroy(CSYNC *ctx); + +/** + * @brief Check if csync is the required version or get the version + * string. + * + * @param req_version The version required. + * + * @return If the version of csync is newer than the version + * required it will return a version string. + * NULL if the version is older. + * + * Example: + * + * @code + * if (csync_version(CSYNC_VERSION_INT(0,42,1)) == NULL) { + * fprintf(stderr, "libcsync version is too old!\n"); + * exit(1); + * } + * + * if (debug) { + * printf("csync %s\n", csync_version(0)); + * } + * @endcode + */ +const char *csync_version(int req_version); + +/** + * @brief Add an additional exclude list. + * + * @param ctx The context to add the exclude list. + * + * @param path The path pointing to the file. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_add_exclude_list(CSYNC *ctx, const char *path); + +/** + * @brief Removes all items imported from exclude lists. + * + * @param ctx The context to add the exclude list. + */ +void csync_clear_exclude_list(CSYNC *ctx); + +/** + * @brief Get the config directory. + * + * @param ctx The csync context. + * + * @return The path of the config directory or NULL on error. + */ +const char *csync_get_config_dir(CSYNC *ctx); + +/** + * @brief Change the config directory. + * + * @param ctx The csync context. + * + * @param path The path to the new config directory. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_config_dir(CSYNC *ctx, const char *path); + +/** + * @brief Remove the complete config directory. + * + * @param ctx The csync context. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_remove_config_dir(CSYNC *ctx); + +/** + * @brief Enable the usage of the statedb. It is enabled by default. + * + * @param ctx The csync context. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_enable_statedb(CSYNC *ctx); + +/** + * @brief Disable the usage of the statedb. It is enabled by default. + * + * @param ctx The csync context. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_disable_statedb(CSYNC *ctx); + +/** + * @brief Check if the statedb usage is enabled. + * + * @param ctx The csync context. + * + * @return 1 if it is enabled, 0 if it is disabled. + */ +int csync_is_statedb_disabled(CSYNC *ctx); + +/** + * @brief Get the userdata saved in the context. + * + * @param ctx The csync context. + * + * @return The userdata saved in the context, NULL if an error + * occured. + */ +void *csync_get_userdata(CSYNC *ctx); + +/** + * @brief Save userdata to the context which is passed to the auth + * callback function. + * + * @param ctx The csync context. + * + * @param userdata The userdata to be stored in the context. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_userdata(CSYNC *ctx, void *userdata); + +/** + * @brief Get the authentication callback set. + * + * @param ctx The csync context. + * + * @return The authentication callback set or NULL if an error + * occured. + */ +csync_auth_callback csync_get_auth_callback(CSYNC *ctx); + +/** + * @brief Set the authentication callback. + * + * @param ctx The csync context. + * + * @param cb The authentication callback. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_auth_callback(CSYNC *ctx, csync_auth_callback cb); + +/** + * @brief Set the log level. + * + * @param[in] level The log verbosity. + * + * @return 0 on success, < 0 if an error occured. + */ +int csync_set_log_level(int level); + +/** + * @brief Get the log verbosity + * + * @return The log verbosity, -1 on error. + */ +int csync_get_log_level(void); + +/** + * @brief Get the logging callback set. + * + * @return The logging callback set or NULL if an error + * occured. + */ +csync_log_callback csync_get_log_callback(void); + +/** + * @brief Set the logging callback. + * + * @param cb The logging callback. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_log_callback(csync_log_callback cb); + +/** + * @brief get the userdata set for the logging callback. + * + * @return The userdata or NULL. + */ +void *csync_get_log_userdata(void); + +/** + * @brief Set the userdata passed to the logging callback. + * + * @param[in] data The userdata to set. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_log_userdata(void *data); + +/** + * @brief Get the path of the statedb file used. + * + * @param ctx The csync context. + * + * @return The path to the statedb file, NULL if an error occured. + */ +const char *csync_get_statedb_file(CSYNC *ctx); + +/** + * @brief Enable the creation of backup copys if files are changed on both sides + * + * @param ctx The csync context. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_enable_conflictcopys(CSYNC *ctx); + +/** + * @brief Flag to tell csync that only a local run is intended. Call before csync_init + * + * @param local_only Bool flag to indicate local only mode. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_local_only( CSYNC *ctx, bool local_only ); + +/** + * @brief Retrieve the flag to tell csync that only a local run is intended. + * + * @return 1: stay local only, 0: local and remote mode + */ +bool csync_get_local_only( CSYNC *ctx ); + +/* Used for special modes or debugging */ +CSYNC_STATUS csync_get_status(CSYNC *ctx); + +/* Used for special modes or debugging */ +int csync_set_status(CSYNC *ctx, int status); + +typedef int csync_treewalk_visit_func(TREE_WALK_FILE* ,void*); + +/** + * @brief Walk the local file tree and call a visitor function for each file. + * + * @param ctx The csync context. + * @param visitor A callback function to handle the file info. + * @param filter A filter, built from or'ed csync_instructions_e + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_walk_local_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter); + +/** + * @brief Walk the remote file tree and call a visitor function for each file. + * + * @param ctx The csync context. + * @param visitor A callback function to handle the file info. + * @param filter A filter, built from and'ed csync_instructions_e + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_walk_remote_tree(CSYNC *ctx, csync_treewalk_visit_func *visitor, int filter); + +/** + * @brief Get the csync status string. + * + * @param ctx The csync context. + * + * @return A const pointer to a string with more precise status info. + */ +const char *csync_get_status_string(CSYNC *ctx); + +#ifdef WITH_ICONV +/** + * @brief Set iconv source codec for filenames. + * + * @param from Source codec. + * + * @return 0 on success, or an iconv error number. + */ +int csync_set_iconv_codec(const char *from); +#endif + +/** + * @brief Set a property to module + * + * @param ctx The csync context. + * + * @param key The property key + * + * @param value An opaque pointer to the data. + * + * @return 0 on success, less than 0 if an error occured. + */ +int csync_set_module_property(CSYNC *ctx, const char *key, void *value); + +/** + * @brief Callback definition for file progress callback. + * + * @param progress A struct containing progress information. + * + * @param userdata User defined data for the callback. + */ +typedef void (*csync_progress_callback)( CSYNC_PROGRESS *progress, void *userdata); + +/** + * @brief Set a progress callback. + * + * This callback reports about up- or download progress of a individual file + * as well as overall progress. + */ +int csync_set_progress_callback( CSYNC *ctx, csync_progress_callback cb); + +csync_progress_callback csync_get_progress_callback(CSYNC *ctx); + +/** + * @brief Aborts the current sync run as soon as possible. Can be called from another thread. + * + * @param ctx The csync context. + */ +void csync_request_abort(CSYNC *ctx); + +/** + * @brief Clears the abort flag. Can be called from another thread. + * + * @param ctx The csync context. + */ +void csync_resume(CSYNC *ctx); + +/** + * @brief Checks for the abort flag, to be used from the modules. + * + * @param ctx The csync context. + */ +int csync_abort_requested(CSYNC *ctx); + +#ifdef __cplusplus +} +#endif + +/** + * }@ + */ +#endif /* _CSYNC_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_config.c b/csync/src/csync_config.c new file mode 100644 index 000000000..f69a942ba --- /dev/null +++ b/csync/src/csync_config.c @@ -0,0 +1,362 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +/* #define _GNU_SOURCE */ +#include +#include + +#include "c_lib.h" +#include "c_private.h" +#include "csync_private.h" +#include "csync_config.h" +#include "csync_misc.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.config" +#include "csync_log.h" + +enum csync_config_opcode_e { + COC_UNSUPPORTED = -1, + COC_MAX_TIMEDIFF, + COC_MAX_DEPTH, + COC_WITH_CONFLICT_COPY, + COC_TIMEOUT +}; + +struct csync_config_keyword_table_s { + const char *name; + enum csync_config_opcode_e opcode; +}; + +static struct csync_config_keyword_table_s csync_config_keyword_table[] = { + { "max_depth", COC_MAX_DEPTH }, + { "max_time_difference", COC_MAX_TIMEDIFF }, + { "with_confilct_copies", COC_WITH_CONFLICT_COPY }, + { "timeout", COC_TIMEOUT }, + { NULL, COC_UNSUPPORTED } +}; + +static enum csync_config_opcode_e csync_config_get_opcode(char *keyword) { + int i; + + for (i = 0; csync_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, csync_config_keyword_table[i].name) == 0) { + return csync_config_keyword_table[i].opcode; + } + } + + return COC_UNSUPPORTED; +} + +static int _csync_config_copy_default (const char *config) { + int rc = 0; + +#ifdef _WIN32 + /* For win32, try to copy the conf file from the directory from where the app was started. */ + mbchar_t tcharbuf[MAX_PATH+1]; + char *buf; + int len = 0; + + + /* Get the path from where the application was started */ + len = GetModuleFileNameW(NULL, tcharbuf, MAX_PATH); + if(len== 0) { + rc = -1; + } else { + char *last_bslash; + + buf = c_utf8_from_locale(tcharbuf); + /* cut the trailing filename off */ + if ((last_bslash = strrchr(buf, '\\')) != NULL) { + *last_bslash='\0'; + } + + strncat(buf, "\\" CSYNC_CONF_FILE, MAX_PATH); + if(c_copy(buf, config, 0644) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Could not copy /%s to %s", buf, config ); + rc = -1; + } + c_free_locale_string(buf); + } +#else + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Copy %s/config/%s to %s", SYSCONFDIR, + CSYNC_CONF_FILE, config); +# ifdef WITH_UNIT_TESTING + rc = c_copy(BINARYDIR "/config/" CSYNC_CONF_FILE, config, 0644); +# else + rc = 0; +# endif + if (c_copy(SYSCONFDIR "/ocsync/" CSYNC_CONF_FILE, config, 0644) < 0) { + if (c_copy(BINARYDIR "/config/" CSYNC_CONF_FILE, config, 0644) < 0) { + rc = -1; + } + } +#endif + return rc; +} + +static char *csync_config_get_cmd(char **str) { + register char *c; + char *r; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"') { + *c = '\0'; + goto out; + } + } + } + + for (r = c; *c; c++) { + if (*c == '\n') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +static char *csync_config_get_token(char **str) { + register char *c; + char *r; + + c = csync_config_get_cmd(str); + + for (r = c; *c; c++) { + if (isblank(*c)) { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +static int csync_config_get_int(char **str, int notfound) { + char *p, *endp; + int i; + + p = csync_config_get_token(str); + if (p && *p) { + i = strtol(p, &endp, 10); + if (p == endp) { + return notfound; + } + return i; + } + + return notfound; +} + +static const char *csync_config_get_str_tok(char **str, const char *def) { + char *p; + p = csync_config_get_token(str); + if (p && *p) { + return p; + } + + return def; +} + +static int csync_config_get_yesno(char **str, int notfound) { + const char *p; + + p = csync_config_get_str_tok(str, NULL); + if (p == NULL) { + return notfound; + } + + if (strncasecmp(p, "yes", 3) == 0) { + return 1; + } else if (strncasecmp(p, "no", 2) == 0) { + return 0; + } + + return notfound; +} + +static int csync_config_parse_line(CSYNC *ctx, + const char *line, + unsigned int count) +{ + enum csync_config_opcode_e opcode; + char *s, *x; + char *keyword; + size_t len; + int i; + + x = s = c_strdup(line); + if (s == NULL) { + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = csync_config_get_token(&s); + if (keyword == NULL || keyword[0] == '#' || + keyword[0] == '\0' || keyword[0] == '\n') { + free(x); + return 0; + } + + opcode = csync_config_get_opcode(keyword); + + switch (opcode) { + case COC_MAX_DEPTH: + i = csync_config_get_int(&s, 50); + if (i > 0) { + ctx->options.max_depth = i; + } + break; + case COC_MAX_TIMEDIFF: + i = csync_config_get_int(&s, 10); + if (i >= 0) { + ctx->options.max_time_difference = i; + } + break; + case COC_WITH_CONFLICT_COPY: + i = csync_config_get_yesno(&s, -1); + if (i > 0) { + ctx->options.with_conflict_copys = true; + } else { + ctx->options.with_conflict_copys = false; + } + break; + case COC_TIMEOUT: + i = csync_config_get_int(&s, 0); + if (i > 0) { + ctx->options.timeout = i; + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Config: timeout = %d", + ctx->options.timeout); + } + break; + case COC_UNSUPPORTED: + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Unsupported option: %s, line: %d\n", + keyword, count); + break; + } + + free(x); + return 0; +} + +int csync_config_parse_file(CSYNC *ctx, const char *config) +{ + unsigned int count = 0; + char line[1024] = {0}; + char *s; + FILE *f; + + /* copy default config, if no config exists */ + if (! c_isfile(config)) { + /* check if there is still one csync.conf left over in $HOME/.csync + * and copy it over (migration path) + */ + char *home = NULL; + char *home_config = NULL; + char *config_file = NULL; + + /* there is no statedb at the expected place. */ + home = csync_get_user_home_dir(); + if( !c_streq(home, ctx->options.config_dir) ) { + int rc = -1; + + config_file = c_basename(config); + if( config_file ) { + rc = asprintf(&home_config, "%s/%s/%s", home, CSYNC_CONF_DIR, config_file); + SAFE_FREE(config_file); + } + + if (rc >= 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "config file %s not found, checking %s", + config, home_config); + + /* check the home file and copy to new statedb if existing. */ + if(c_isfile(home_config)) { + if (c_copy(home_config, config, 0644) < 0) { + /* copy failed, but that is not reason to die. */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "Could not copy config %s => %s", + home_config, config); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "Copied %s => %s", + home_config, config); + } + } + } + SAFE_FREE(home_config); + } + SAFE_FREE(home); + /* Still install the default one if nothing is there. */ + if( ! c_isfile(config)) { + if (_csync_config_copy_default(config) < 0) { + return -1; + } + } + } + + f = fopen(config, "r"); + if (f == NULL) { + return 0; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Reading configuration data from %s", + config); + + s = fgets(line, sizeof(line), f); + while (s != NULL) { + int rc; + count++; + + rc = csync_config_parse_line(ctx, line, count); + if (rc < 0) { + fclose(f); + return -1; + } + s = fgets(line, sizeof(line), f); + } + + fclose(f); + + return 0; +} diff --git a/csync/src/csync_config.h b/csync/src/csync_config.h new file mode 100644 index 000000000..2b53d412d --- /dev/null +++ b/csync/src/csync_config.h @@ -0,0 +1,40 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_CONFIG_H +#define _CSYNC_CONFIG_H + +/** + * @internal + * + * @brief Load the csync configuration. + * + * @param ctx The csync context to use. + * + * @param config The path to the config file. + * + * @return 0 on success, < 0 on error. + */ +int csync_config_load(CSYNC *ctx, const char *config); + +int csync_config_parse_file(CSYNC *ctx, const char *config); + +#endif /* _CSYNC_X_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_dbtree.c b/csync/src/csync_dbtree.c new file mode 100644 index 000000000..06277b15f --- /dev/null +++ b/csync/src/csync_dbtree.c @@ -0,0 +1,235 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include "csync_dbtree.h" +#include "c_lib.h" +#include "csync_private.h" +#include "csync_statedb.h" +#include "csync_util.h" +#include "c_macro.h" + + +#define CSYNC_LOG_CATEGORY_NAME "csync.dbtree" +#include "csync_log.h" + +struct dir_listing { + c_list_t *list; + unsigned int cnt; + c_list_t *entry; + char *dir; +}; + +csync_vio_method_handle_t *csync_dbtree_opendir(CSYNC *ctx, const char *name) +{ + + char *column = NULL; + const char *path = NULL; + csync_vio_file_stat_t *fs = NULL; + unsigned int c = 0; + c_strlist_t *list = NULL; + struct dir_listing *listing = NULL; + + /* "phash INTEGER(8)," + "pathlen INTEGER," + "path VARCHAR(4096)," + "inode INTEGER," + "uid INTEGER," + "gid INTEGER," + "mode INTEGER," + "modtime INTEGER(8)," + "type INTEGER," + "md5 VARCHAR(32)," // That's the etag + */ + + int col_count = 10; + if( strlen(name) < strlen(ctx->remote.uri)+1) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "name does not contain remote uri!"); + return NULL; + } + + path = name + strlen(ctx->remote.uri)+1; + + list = csync_statedb_get_below_path(ctx, path); + + if( ! list ) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Query result list is NULL!"); + return NULL; + } + /* list count must be a multiple of col_count */ + if( list->count % col_count != 0 ) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Wrong size of query result list"); + c_strlist_destroy( list ); + return NULL; + } + + listing = c_malloc(sizeof(struct dir_listing)); + ZERO_STRUCTP(listing); + if( listing == NULL ) { + c_strlist_destroy( list ); + errno = ENOMEM; + return NULL; + } + + listing->dir = c_strdup(path); + + for( c = 0; c < (list->count / col_count); c++) { + int base = c*col_count; + int cnt = 0; + int tpath_len = 0; + int type = 0; + + char *tpath = list->vector[base+1]; + /* check if the result points to a file directly below the search path + * by checking if there is another / in the result. + * If yes, skip it. + * FIXME: Find a better filter solution here. + */ + tpath += strlen(path)+1; /* jump over the search path */ + tpath_len = strlen( tpath ); + while( cnt < tpath_len ) { + if(*(tpath+cnt) == '/') { + /* CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Skipping entry: %s", list->vector[base+1]); */ + break; + } + cnt++; + } + if( cnt < tpath_len ) continue; + + if (!list->vector[base+8][0]) + continue; /* If etag is empty, the file was removed on the server */ + + fs = csync_vio_file_stat_new(); + fs->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + + column = list->vector[base+0]; /* phash */ + + column = list->vector[base+1]; /* path */ + fs->name = c_strdup(column+strlen(path)+1); + + column = list->vector[base+2]; /* inode */ + fs->inode = atoll(column); + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_INODE; + + column = list->vector[base+3]; /* uid */ + fs->uid = atoi(column); + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_UID; + + column = list->vector[base+4]; /* gid */ + fs->gid = atoi(column); + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_GID; + + column = list->vector[base+5]; /* mode */ + fs->mode = atoi(column); + // fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_M; + + column = list->vector[base+6]; /* modtime */ + fs->mtime = strtoul(column, NULL, 10); + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + + column = list->vector[base+7]; /* type */ + type = atoi(column); + /* Attention: the type of csync_ftw_type_e which is the source for + * the database entry is different from csync_vio_file_type_e which + * is the target file type here. Mapping is needed! + */ + switch( type ) { + case CSYNC_FTW_TYPE_DIR: + fs->type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + break; + case CSYNC_FTW_TYPE_FILE: + fs->type = CSYNC_VIO_FILE_TYPE_REGULAR; + break; + case CSYNC_FTW_TYPE_SLINK: + fs->type = CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK; + break; + default: + fs->type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + } + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + + column = list->vector[base+8]; /* etag */ + fs->etag = c_strdup(column); + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG; + + column = list->vector[base+9]; /* file id */ + csync_vio_file_stat_set_file_id(fs, column); + + /* store into result list. */ + listing->list = c_list_append( listing->list, fs ); + listing->cnt++; + } + + if(listing->cnt) + listing->entry = c_list_first( listing->list ); + + c_strlist_destroy( list ); + + return listing; +} + +int csync_dbtree_closedir(CSYNC *ctx, csync_vio_method_handle_t *dhandle) +{ + struct dir_listing *dl = NULL; + int rc = 0; + (void) ctx; + + if( dhandle != NULL ) { + dl = (struct dir_listing*) dhandle; + + c_list_free(dl->list); + SAFE_FREE(dl->dir); + SAFE_FREE(dl); + } + + return rc; +} + +csync_vio_file_stat_t *csync_dbtree_readdir(CSYNC *ctx, csync_vio_method_handle_t *dhandle) +{ + csync_vio_file_stat_t *fs = NULL; + struct dir_listing *dl = NULL; + (void) ctx; + + if( dhandle != NULL ) { + dl = (struct dir_listing*) dhandle; + if( dl->entry != NULL ) { + fs = (csync_vio_file_stat_t*) dl->entry->data; + + dl->entry = c_list_next( dl->entry); + } + } + + return fs; +} + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_dbtree.h b/csync/src/csync_dbtree.h new file mode 100644 index 000000000..a39138f23 --- /dev/null +++ b/csync/src/csync_dbtree.h @@ -0,0 +1,60 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file csync_dbtree.h + * + * @brief Private interface of csync + * + * @defgroup csyncdbtreeInternals csync statedb internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +#ifndef _CSYNC_DBTREE_H +#define _CSYNC_DBTREE_H + +#include "c_lib.h" +#include "csync_private.h" +#include "vio/csync_vio_handle.h" + +/** + * @brief Open a directory based on the statedb. + * + * This function reads the list of files within a directory from statedb and + * builds up a list in memory. + * + * @param ctx The csync context. + * @param name The directory name. + * + * @return 0 on success, less than 0 if an error occured with errno set. + */ +csync_vio_method_handle_t *csync_dbtree_opendir(CSYNC *ctx, const char *name); + +int csync_dbtree_closedir(CSYNC *ctx, csync_vio_method_handle_t *dhandle); + +csync_vio_file_stat_t *csync_dbtree_readdir(CSYNC *ctx, csync_vio_method_handle_t *dhandle); + +/** + * }@ + */ +#endif /* _CSYNC_DBTREE_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_exclude.c b/csync/src/csync_exclude.c new file mode 100644 index 000000000..68a6d61d5 --- /dev/null +++ b/csync/src/csync_exclude.c @@ -0,0 +1,286 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#include +#include +#include +#include + +#include "c_lib.h" + +#include "csync_private.h" +#include "csync_exclude.h" +#include "csync_misc.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.exclude" +#include "csync_log.h" + +static int _csync_exclude_add(CSYNC *ctx, const char *string) { + c_strlist_t *list; + + if (ctx->excludes == NULL) { + ctx->excludes = c_strlist_new(32); + if (ctx->excludes == NULL) { + return -1; + } + } + + if (ctx->excludes->count == ctx->excludes->size) { + list = c_strlist_expand(ctx->excludes, 2 * ctx->excludes->size); + if (list == NULL) { + return -1; + } + ctx->excludes = list; + } + + return c_strlist_add(ctx->excludes, string); +} + +int csync_exclude_load(CSYNC *ctx, const char *fname) { + int fd = -1; + int i = 0; + int rc = -1; + int64_t size; + char *buf = NULL; + char *entry = NULL; + mbchar_t *w_fname; + + if (ctx == NULL || fname == NULL) { + return -1; + } + +#ifdef _WIN32 + _fmode = _O_BINARY; +#endif + + w_fname = c_utf8_to_locale(fname); + if (w_fname == NULL) { + return -1; + } + + fd = _topen(w_fname, O_RDONLY); + c_free_locale_string(w_fname); + if (fd < 0) { + return -1; + } + + size = lseek(fd, 0, SEEK_END); + if (size < 0) { + rc = -1; + goto out; + } + lseek(fd, 0, SEEK_SET); + if (size == 0) { + rc = 0; + goto out; + } + buf = c_malloc(size + 1); + if (buf == NULL) { + rc = -1; + goto out; + } + + if (read(fd, buf, size) != size) { + rc = -1; + goto out; + } + buf[size] = '\0'; + + /* FIXME: Use fgets and don't add duplicates */ + entry = buf; + for (i = 0; i < size; i++) { + if (buf[i] == '\n' || buf[i] == '\r') { + if (entry != buf + i) { + buf[i] = '\0'; + if (*entry != '#') { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Adding entry: %s", entry); + rc = _csync_exclude_add(ctx, entry); + if (rc < 0) { + goto out; + } + } + } + entry = buf + i + 1; + } + } + + rc = 0; +out: + SAFE_FREE(buf); + close(fd); + return rc; +} + +void csync_exclude_clear(CSYNC *ctx) { + c_strlist_clear(ctx->excludes); +} + +void csync_exclude_destroy(CSYNC *ctx) { + c_strlist_destroy(ctx->excludes); +} + +CSYNC_EXCLUDE_TYPE csync_excluded(CSYNC *ctx, const char *path, int filetype) { + size_t i = 0; + const char *p = NULL; + char *bname = NULL; + char *dname = NULL; + char *prev_dname = NULL; + int rc = -1; + CSYNC_EXCLUDE_TYPE match = CSYNC_NOT_EXCLUDED; + CSYNC_EXCLUDE_TYPE type = CSYNC_NOT_EXCLUDED; + + /* exclude the lock file */ + if (c_streq( path, CSYNC_LOCK_FILE )) { + return CSYNC_FILE_SILENTLY_EXCLUDED; + } + + if (! ctx->options.unix_extensions) { + for (p = path; *p; p++) { + switch (*p) { + case '\\': + case ':': + case '?': + case '*': + case '"': + case '>': + case '<': + case '|': + return CSYNC_FILE_EXCLUDE_INVALID_CHAR; + default: + break; + } + } + } + + /* split up the path */ + dname = c_dirname(path); + bname = c_basename(path); + + if (bname == NULL || dname == NULL) { + match = CSYNC_NOT_EXCLUDED; + SAFE_FREE(bname); + SAFE_FREE(dname); + goto out; + } + + rc = csync_fnmatch(".csync_journal.db*", bname, 0); + if (rc == 0) { + match = CSYNC_FILE_SILENTLY_EXCLUDED; + SAFE_FREE(bname); + SAFE_FREE(dname); + goto out; + } + + SAFE_FREE(bname); + SAFE_FREE(dname); + + SAFE_FREE(bname); + SAFE_FREE(dname); + + if (ctx->excludes == NULL) { + goto out; + } + + /* Loop over all exclude patterns and evaluate the given path */ + for (i = 0; match == CSYNC_NOT_EXCLUDED && i < ctx->excludes->count; i++) { + bool match_dirs_only = false; + char *pattern_stored = c_strdup(ctx->excludes->vector[i]); + char* pattern = pattern_stored; + + type = CSYNC_FILE_EXCLUDE_LIST; + if (strlen(pattern) < 1) { + continue; + } + /* Ecludes starting with ']' means it can be cleanup */ + if (pattern[0] == ']') { + ++pattern; + if (filetype == CSYNC_FTW_TYPE_FILE) { + type = CSYNC_FILE_EXCLUDE_AND_REMOVE; + } + } + /* Check if the pattern applies to pathes only. */ + if (pattern[strlen(pattern)-1] == '/') { + match_dirs_only = true; + pattern[strlen(pattern)-1] = '\0'; /* Cut off the slash */ + } + + /* check if the pattern contains a / and if, compare to the whole path */ + if (strchr(pattern, '/')) { + rc = csync_fnmatch(pattern, path, FNM_PATHNAME); + if( rc == 0 ) { + match = type; + } + /* if the pattern requires a dir, but path is not, its still not excluded. */ + if (match_dirs_only && filetype != CSYNC_FTW_TYPE_DIR) { + match = CSYNC_NOT_EXCLUDED; + } + } + + /* if still not excluded, check each component of the path */ + if (match == CSYNC_NOT_EXCLUDED) { + int trailing_component = 1; + dname = c_dirname(path); + bname = c_basename(path); + + if (bname == NULL || dname == NULL) { + match = CSYNC_NOT_EXCLUDED; + goto out; + } + + /* Check each component of the path */ + do { + /* Do not check the bname if its a file and the pattern matches dirs only. */ + if ( !(trailing_component == 1 /* it is the trailing component */ + && match_dirs_only /* but only directories are matched by the pattern */ + && filetype == CSYNC_FTW_TYPE_FILE) ) { + /* Check the name component against the pattern */ + rc = csync_fnmatch(pattern, bname, 0); + if (rc == 0) { + match = type; + } + } + if (!(c_streq(dname, ".") || c_streq(dname, "/"))) { + rc = csync_fnmatch(pattern, dname, 0); + if (rc == 0) { + match = type; + } + } + trailing_component = 0; + prev_dname = dname; + SAFE_FREE(bname); + bname = c_basename(prev_dname); + dname = c_dirname(prev_dname); + SAFE_FREE(prev_dname); + + } while( match == CSYNC_NOT_EXCLUDED && !c_streq(dname, ".") + && !c_streq(dname, "/") ); + } + SAFE_FREE(pattern_stored); + SAFE_FREE(bname); + SAFE_FREE(dname); + } + +out: + + return match; +} + diff --git a/csync/src/csync_exclude.h b/csync/src/csync_exclude.h new file mode 100644 index 000000000..fabeecafc --- /dev/null +++ b/csync/src/csync_exclude.h @@ -0,0 +1,70 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_EXCLUDE_H +#define _CSYNC_EXCLUDE_H + +enum csync_exclude_type_e { + CSYNC_NOT_EXCLUDED = 0, + CSYNC_FILE_SILENTLY_EXCLUDED, + CSYNC_FILE_EXCLUDE_AND_REMOVE, + CSYNC_FILE_EXCLUDE_LIST, + CSYNC_FILE_EXCLUDE_INVALID_CHAR +}; +typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE; +/** + * @brief Load exclude list + * + * @param ctx The context of the synchronizer. + * @param fname The filename to load. + * + * @return 0 on success, -1 if an error occured with errno set. + */ +int csync_exclude_load(CSYNC *ctx, const char *fname); + +/** + * @brief Clear the exclude list in memory. + * + * @param ctx The synchronizer context. + */ +void csync_exclude_clear(CSYNC *ctx); + +/** + * @brief Destroy the exclude list in memory. + * + * @param ctx The synchronizer context. + */ +void csync_exclude_destroy(CSYNC *ctx); + +/** + * @brief Check if the given path should be excluded. + * + * This excludes also paths which can't be used without unix extensions. + * + * @param ctx The synchronizer context. + * @param path The patch to check. + * + * @return 2 if excluded and needs cleanup, 1 if excluded, 0 if not. + */ +CSYNC_EXCLUDE_TYPE csync_excluded(CSYNC *ctx, const char *path, int filetype); + +#endif /* _CSYNC_EXCLUDE_H */ + +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_lock.c b/csync/src/csync_lock.c new file mode 100644 index 000000000..7a4536872 --- /dev/null +++ b/csync/src/csync_lock.c @@ -0,0 +1,236 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "c_lib.h" +#include "csync_lock.h" +#include "csync.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.lock" +#include "csync_log.h" + +#ifdef _DO_CREATE_A_LOCK_FILE +static int _csync_lock_create(const char *lockfile) { + int fd = -1; + pid_t pid = 0; + int rc = -1; + char errbuf[256] = {0}; + char *ctmpfile = NULL; + char *dir = NULL; + char *buf = NULL; + mode_t mask; + + pid = getpid(); + + dir = c_dirname(lockfile); + if (dir == NULL) { + rc = -1; + goto out; + } + + if (asprintf(&ctmpfile, "%s/tmp_lock_XXXXXX", dir) < 0) { + rc = -1; + goto out; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Create temporary lock file: %s", ctmpfile); + mask = umask(0077); + fd = mkstemp(ctmpfile); + umask(mask); + if (fd < 0) { + strerror_r(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "Unable to create temporary lock file: %s - %s", + ctmpfile, + errbuf); + rc = -1; + goto out; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Write pid (%d) to temporary lock file: %s", pid, ctmpfile); + pid = asprintf(&buf, "%d\n", pid); + if (write(fd, buf, pid) == pid) { + /* Create lock file */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Create a hardlink from %s to %s.", ctmpfile, lockfile); + if (link(ctmpfile, lockfile) < 0 ) { + /* Oops, alredy locked */ + strerror_r(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, + "Already locked: %s - %s", + lockfile, + errbuf); + rc = -1; + goto out; + } + } else { + strerror_r(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "Can't create %s - %s", + ctmpfile, + errbuf); + rc = -1; + goto out; + } + + rc = 0; + +out: + if (fd > 0) { + close(fd); + } + if (ctmpfile) { + unlink(ctmpfile); + } + + SAFE_FREE(buf); + SAFE_FREE(dir); + SAFE_FREE(ctmpfile); + + return rc; +} + +static pid_t _csync_lock_read(const char *lockfile) { + char errbuf[256] = {0}; + char buf[8] = {0}; + long int tmp; + ssize_t rc; + int fd; + pid_t pid; + mbchar_t *wlockfile; + + /* Read PID from existing lock */ +#ifdef _WIN32 + _fmode = _O_BINARY; +#endif + + wlockfile = c_utf8_to_locale(lockfile); + if (wlockfile == NULL) { + return -1; + } + + fd = _topen(wlockfile, O_RDONLY); + c_free_locale_string(wlockfile); + if (fd < 0) { + return -1; + } + + rc = read(fd, buf, sizeof(buf)); + close(fd); + + if (rc <= 0) { + return -1; + } + + buf[sizeof(buf) - 1] = '\0'; + tmp = strtol(buf, NULL, 10); + if (tmp == 0 || tmp > 0xFFFF || errno == ERANGE) { + /* Broken lock file */ + strerror_r(ERANGE, errbuf, sizeof(errbuf)); + if (unlink(lockfile) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "Unable to remove broken lock %s - %s", + lockfile, + errbuf); + } + return -1; + } + pid = (pid_t)(tmp & 0xFFFF); + + /* Check if process is still alive */ + if (kill(pid, 0) < 0 && errno == ESRCH) { + /* Process is dead. Remove stale lock. */ + wlockfile = c_utf8_to_locale(lockfile); + + if (_tunlink(wlockfile) < 0) { + strerror_r(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "Unable to remove stale lock %s - %s", + lockfile, + errbuf); + } + c_free_locale_string(wlockfile); + return -1; + } + + return pid; +} +#endif + +int csync_lock(const char *lockfile) { +#ifdef _DO_CREATE_A_LOCK_FILE /* disable lock file for ownCloud client, not only _WIN32 */ + /* Check if lock already exists. */ + if (_csync_lock_read(lockfile) > 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Aborting, another synchronization process is running."); + return -1; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "Creating lock file: %s", lockfile); + + return _csync_lock_create(lockfile); +#else + (void) lockfile; + return 0; +#endif + +} + +void csync_lock_remove(const char *lockfile) { +#ifdef _DO_CREATE_A_LOCK_FILE +#ifndef _WIN32 + char errbuf[256] = {0}; + mbchar_t *wlockfile; + + /* You can't remove the lock if it is from another process */ + if (_csync_lock_read(lockfile) == getpid()) { + wlockfile = c_utf8_to_locale(lockfile); + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Removing lock file: %s", lockfile); + if (_tunlink(wlockfile) < 0) { + strerror_r(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "Unable to remove lock %s - %s", + lockfile, + errbuf); + } + c_free_locale_string(wlockfile); + } +#endif +#else + (void) lockfile; +#endif + +} + diff --git a/csync/src/csync_lock.h b/csync/src/csync_lock.h new file mode 100644 index 000000000..9ed4d54b9 --- /dev/null +++ b/csync/src/csync_lock.h @@ -0,0 +1,66 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_LOCK_H +#define _CSYNC_LOCK_H + +#include "csync.h" + +/** + * @file csync_lock.h + * + * @brief File locking + * + * This prevents csync to start the same synchronization task twice which could + * lead to several problems. + * + * @defgroup csyncLockingInternals csync file lockling internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +/** + * @brief Lock the client if possible. + * + * This functiion tries to lock the client with a lock file. + * + * @param lockfile The lock file to create. + * + * @return 0 if the lock was successfull, less than 0 if the lock file + * couldn't be created or if it is already locked. + */ +int csync_lock(const char *lockfile); + +/** + * @brief Remove the lockfile + * + * Only our own lock can be removed. This function can't remove a lock from + * another client. + * + * @param lockfile The lock file to remove. + */ +void csync_lock_remove(const char *lockfile); + +/** + * }@ + */ +#endif /* _CSYNC_LOCK_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_log.c b/csync/src/csync_log.c new file mode 100644 index 000000000..d5f34aa12 --- /dev/null +++ b/csync/src/csync_log.c @@ -0,0 +1,157 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#include +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include + +#include "csync_private.h" +#include "csync_log.h" + +CSYNC_THREAD int csync_log_level; +CSYNC_THREAD csync_log_callback csync_log_cb; +CSYNC_THREAD void *csync_log_userdata; + +static int current_timestring(int hires, char *buf, size_t len) +{ + char tbuf[64]; + struct timeval tv; + struct tm *tm; + time_t t; + + gettimeofday(&tv, NULL); + t = (time_t) tv.tv_sec; + + tm = localtime(&t); + if (tm == NULL) { + return -1; + } + + if (hires) { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s.%06ld", tbuf, (long) tv.tv_usec); + } else { + strftime(tbuf, sizeof(tbuf) - 1, "%Y/%m/%d %H:%M:%S", tm); + snprintf(buf, len, "%s", tbuf); + } + + return 0; +} + +static void csync_log_stderr(int verbosity, + const char *function, + const char *buffer) +{ + char date[64] = {0}; + int rc; + + rc = current_timestring(1, date, sizeof(date)); + if (rc == 0) { + fprintf(stderr, "[%s, %d] %s:", date+5, verbosity, function); + } else { + fprintf(stderr, "[%d] %s", verbosity, function); + } + + fprintf(stderr, " %s\n", buffer); +} +static void csync_log_function(int verbosity, + const char *function, + const char *buffer) +{ + csync_log_callback log_fn = csync_get_log_callback(); + if (log_fn) { + char buf[1024]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + + log_fn(verbosity, + function, + buf, + csync_get_log_userdata()); + return; + } + + csync_log_stderr(verbosity, function, buffer); +} + + +void csync_log(int verbosity, + const char *function, + const char *format, ...) +{ + char buffer[1024]; + va_list va; + + if (verbosity <= csync_get_log_level()) { + va_start(va, format); + vsnprintf(buffer, sizeof(buffer), format, va); + va_end(va); + csync_log_function(verbosity, function, buffer); + } +} + +int csync_set_log_level(int level) { + if (level < 0) { + return -1; + } + + csync_log_level = level; + + return 0; +} + +int csync_get_log_level(void) { + return csync_log_level; +} + +int csync_set_log_callback(csync_log_callback cb) { + if (cb == NULL) { + return -1; + } + + csync_log_cb = cb; + + return 0; +} + +csync_log_callback csync_get_log_callback(void) { + return csync_log_cb; +} + +void *csync_get_log_userdata(void) +{ + return csync_log_userdata; +} + +int csync_set_log_userdata(void *data) +{ + csync_log_userdata = data; + + return 0; +} + diff --git a/csync/src/csync_log.h b/csync/src/csync_log.h new file mode 100644 index 000000000..38f5f9cac --- /dev/null +++ b/csync/src/csync_log.h @@ -0,0 +1,75 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file csync_log.h + * + * @brief Logging interface of csync + * + * @defgroup csyncLogInternals csync logging internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +#ifndef _CSYNC_LOG_H +#define _CSYNC_LOG_H + +/* GCC have printf type attribute check. */ +#ifndef PRINTF_ATTRIBUTE +#ifdef __GNUC__ +#ifdef _WIN32 +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__gnu_printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#endif +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ +#endif /* ndef PRINTF_ATTRIBUTE */ + +enum csync_log_priority_e { + CSYNC_LOG_PRIORITY_NOLOG = 0, + CSYNC_LOG_PRIORITY_FATAL, + CSYNC_LOG_PRIORITY_ALERT, + CSYNC_LOG_PRIORITY_CRIT, + CSYNC_LOG_PRIORITY_ERROR, + CSYNC_LOG_PRIORITY_WARN, + CSYNC_LOG_PRIORITY_NOTICE, + CSYNC_LOG_PRIORITY_INFO, + CSYNC_LOG_PRIORITY_DEBUG, + CSYNC_LOG_PRIORITY_TRACE, + CSYNC_LOG_PRIORITY_NOTSET, + CSYNC_LOG_PRIORITY_UNKNOWN, +}; + +#define CSYNC_LOG(priority, ...) \ + csync_log(priority, __FUNCTION__, __VA_ARGS__) + +void csync_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/** + * }@ + */ +#endif /* _CSYNC_LOG_H */ + +/* vim: set ft=c.doxygen ts=4 sw=4 et cindent: */ diff --git a/csync/src/csync_macros.h b/csync/src/csync_macros.h new file mode 100644 index 000000000..0b0951209 --- /dev/null +++ b/csync/src/csync_macros.h @@ -0,0 +1,52 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_MACROS_H +#define _CSYNC_MACROS_H + +#include +#include + +/* How many elements there are in a static array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/* Some special custom errno values to report bugs properly. The BASE value + * should always be larger than the highest system errno. */ +#define CSYNC_CUSTOM_ERRNO_BASE 10000 + +#define ERRNO_GENERAL_ERROR CSYNC_CUSTOM_ERRNO_BASE+2 +#define ERRNO_LOOKUP_ERROR CSYNC_CUSTOM_ERRNO_BASE+3 +#define ERRNO_USER_UNKNOWN_ON_SERVER CSYNC_CUSTOM_ERRNO_BASE+4 +#define ERRNO_PROXY_AUTH CSYNC_CUSTOM_ERRNO_BASE+5 +#define ERRNO_CONNECT CSYNC_CUSTOM_ERRNO_BASE+6 +#define ERRNO_TIMEOUT CSYNC_CUSTOM_ERRNO_BASE+7 +#define ERRNO_PRECONDITION CSYNC_CUSTOM_ERRNO_BASE+8 +#define ERRNO_RETRY CSYNC_CUSTOM_ERRNO_BASE+9 +#define ERRNO_REDIRECT CSYNC_CUSTOM_ERRNO_BASE+10 +#define ERRNO_WRONG_CONTENT CSYNC_CUSTOM_ERRNO_BASE+11 +#define ERRNO_TIMEDELTA CSYNC_CUSTOM_ERRNO_BASE+12 +#define ERRNO_ERROR_STRING CSYNC_CUSTOM_ERRNO_BASE+13 +#define ERRNO_SERVICE_UNAVAILABLE CSYNC_CUSTOM_ERRNO_BASE+14 +#define ERRNO_QUOTA_EXCEEDED CSYNC_CUSTOM_ERRNO_BASE+15 +#define ERRNO_USER_ABORT CSYNC_CUSTOM_ERRNO_BASE+16 + +#endif /* _CSYNC_MACROS_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_misc.c b/csync/src/csync_misc.c new file mode 100644 index 000000000..0170b462b --- /dev/null +++ b/csync/src/csync_misc.c @@ -0,0 +1,292 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#if _WIN32 +# ifndef _WIN32_IE +# define _WIN32_IE 0x0501 // SHGetSpecialFolderPath +# endif +# include +#else /* _WIN32 */ +# include +# include +#endif /* _WIN32 */ + +#include "c_lib.h" +#include "csync_misc.h" +#include "csync_macros.h" +#include "csync_log.h" + +#ifdef _WIN32 +char *csync_get_user_home_dir(void) { + wchar_t tmp[MAX_PATH]; + char *szPath = NULL; + + if( SHGetFolderPathW( NULL, + CSIDL_PROFILE|CSIDL_FLAG_CREATE, + NULL, + 0, + tmp) == S_OK ) { + szPath = c_utf8_from_locale(tmp); + return szPath; + } + + return NULL; +} + +char *csync_get_local_username(void) { + DWORD size = 0; + char *user; + + /* get the size */ + GetUserName(NULL, &size); + + user = (char *) c_malloc(size); + if (user == NULL) { + return NULL; + } + + if (GetUserName(user, &size)) { + return user; + } + + return NULL; +} + +#else /* ************* !WIN32 ************ */ + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 4096 +#endif /* NSS_BUFLEN_PASSWD */ + +char *csync_get_user_home_dir(void) { + const char *envp; + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + int rc; + + envp = getenv("HOME"); + if (envp != NULL && envp[0] != '\0') { + return c_strdup(envp); + } + + /* Still nothing found, read the password file */ + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0) { + return c_strdup(pwd.pw_dir); + } + + return NULL; +} + +char *csync_get_local_username(void) { + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + char *name; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0) { + return NULL; + } + + name = c_strdup(pwd.pw_name); + + if (name == NULL) { + return NULL; + } + + return name; +} + +#endif /* ************* WIN32 ************ */ + +#ifdef HAVE_FNMATCH +#include + +int csync_fnmatch(__const char *__pattern, __const char *__name, int __flags) { + return fnmatch(__pattern, __name, __flags); +} + +#else /* HAVE_FNMATCH */ + +#include +int csync_fnmatch(__const char *__pattern, __const char *__name, int __flags) { + (void) __flags; + /* FIXME check if we rather should use the PathMatchSpecW variant here? */ + if(PathMatchSpec(__name, __pattern)) + return 0; + else + return 1; +} +#endif /* HAVE_FNMATCH */ + +CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status) +{ + CSYNC_STATUS status = CSYNC_STATUS_OK; + + switch (error) { + case 0: + status = CSYNC_STATUS_OK; + break; + /* The custom errnos first. */ + case ERRNO_GENERAL_ERROR: + status = CSYNC_STATUS_UNSUCCESSFUL; + break; + case ERRNO_LOOKUP_ERROR: /* In Neon: Server or proxy hostname lookup failed */ + status = CSYNC_STATUS_LOOKUP_ERROR; + break; + case ERRNO_USER_UNKNOWN_ON_SERVER: /* Neon: User authentication on server failed. */ + status = CSYNC_STATUS_SERVER_AUTH_ERROR; + break; + case ERRNO_PROXY_AUTH: + status = CSYNC_STATUS_PROXY_AUTH_ERROR; /* Neon: User authentication on proxy failed */ + break; + case ERRNO_CONNECT: + status = CSYNC_STATUS_CONNECT_ERROR; /* Network: Connection error */ + break; + case ERRNO_TIMEOUT: + status = CSYNC_STATUS_TIMEOUT; /* Network: Timeout error */ + break; + case ERRNO_QUOTA_EXCEEDED: + status = CSYNC_STATUS_QUOTA_EXCEEDED; /* Quota exceeded */ + break; + case ERRNO_SERVICE_UNAVAILABLE: + status = CSYNC_STATUS_SERVICE_UNAVAILABLE; /* Service temporarily down */ + break; + case EFBIG: + status = CSYNC_STATUS_FILE_SIZE_ERROR; /* File larger than 2MB */ + break; + case ERRNO_PRECONDITION: + case ERRNO_RETRY: + case ERRNO_REDIRECT: + case ERRNO_WRONG_CONTENT: + status = CSYNC_STATUS_HTTP_ERROR; + break; + + case ERRNO_TIMEDELTA: + status = CSYNC_STATUS_TIMESKEW; + break; + case EPERM: /* Operation not permitted */ + case EACCES: /* Permission denied */ + status = CSYNC_STATUS_PERMISSION_DENIED; + break; + case ENOENT: /* No such file or directory */ + status = CSYNC_STATUS_NOT_FOUND; + break; + case EAGAIN: /* Try again */ + status = CSYNC_STATUS_TIMEOUT; + break; + case EEXIST: /* File exists */ + status = CSYNC_STATUS_FILE_EXISTS; + break; + case EINVAL: + status = CSYNC_STATUS_PARAM_ERROR; + break; + case ENOSPC: + status = CSYNC_STATUS_OUT_OF_SPACE; + break; + + /* All the remaining basic errnos: */ + case EIO: /* I/O error */ + case ESRCH: /* No such process */ + case EINTR: /* Interrupted system call */ + case ENXIO: /* No such device or address */ + case E2BIG: /* Argument list too long */ + case ENOEXEC: /* Exec format error */ + case EBADF: /* Bad file number */ + case ECHILD: /* No child processes */ + case ENOMEM: /* Out of memory */ + case EFAULT: /* Bad address */ +#ifndef _WIN32 + case ENOTBLK: /* Block device required */ +#endif + case EBUSY: /* Device or resource busy */ + case EXDEV: /* Cross-device link */ + case ENODEV: /* No such device */ + case ENOTDIR: /* Not a directory */ + case EISDIR: /* Is a directory */ + case ENFILE: /* File table overflow */ + case EMFILE: /* Too many open files */ + case ENOTTY: /* Not a typewriter */ +#ifndef _WIN32 + case ETXTBSY: /* Text file busy */ +#endif + case ESPIPE: /* Illegal seek */ + case EROFS: /* Read-only file system */ + case EMLINK: /* Too many links */ + case EPIPE: /* Broken pipe */ + + case ERRNO_ERROR_STRING: + default: + status = default_status; + } + + return status; +} + +/* Remove possible quotes, and also the -gzip at the end + * Remove "-gzip" at the end (cf. https://github.com/owncloud/mirall/issues/1195) + * The caller must take ownership of the resulting string. + */ +char *csync_normalize_etag(const char *etag) +{ + int len = 0; + char *buf = NULL; + if (!etag) + return NULL; + + len = strlen(etag); + /* strip "XXXX-gzip" */ + if(len >= 7 && etag[0] == '"' && c_streq(etag + len - 6, "-gzip\"")) { + etag++; + len -= 7; + } + /* strip leading -gzip */ + if(len >= 5 && c_streq(etag + len - 5, "-gzip")) { + len -= 5; + } + /* strip normal quotes */ + if (etag[0] == '"' && etag[len-1] == '"') { + etag++; + len -= 2; + } + + buf = c_malloc( len+1 ); + strncpy( buf, etag, len ); + buf[len] = '\0'; + return buf; +} + diff --git a/csync/src/csync_misc.h b/csync/src/csync_misc.h new file mode 100644 index 000000000..c424283c6 --- /dev/null +++ b/csync/src/csync_misc.h @@ -0,0 +1,69 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_MISC_H +#define _CSYNC_MISC_H + +#include +#include + +#ifdef HAVE_FNMATCH +#include +#else +/* Steal this define to make csync_exclude compile. Note that if fnmatch + * is not defined it's probably Win32 which uses a different implementation + * than fmmatch anyway, which does not care for flags. + **/ +#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ +#endif + +char *csync_get_user_home_dir(void); +char *csync_get_local_username(void); + +int csync_fnmatch(__const char *__pattern, __const char *__name, int __flags); + +/** + * @brief csync_errno_to_status - errno to csync status code + * + * This function tries to convert the value of the current set errno + * to a csync status code. + * + * @return the corresponding csync error code. + */ +CSYNC_STATUS csync_errno_to_status(int error, CSYNC_STATUS default_status); + +struct csync_hbf_info_s { + int start_id; + int transfer_id; +}; +typedef struct csync_hbf_info_s csync_hbf_info_t; + +typedef struct { + int64_t file_count; + int64_t current_file_no; + int64_t byte_sum; + int64_t byte_current; +} csync_overall_progress_t; + + +char *csync_normalize_etag(const char *); + +#endif /* _CSYNC_MISC_H */ diff --git a/csync/src/csync_owncloud.c b/csync/src/csync_owncloud.c new file mode 100644 index 000000000..6243323a2 --- /dev/null +++ b/csync/src/csync_owncloud.c @@ -0,0 +1,1214 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2011 by Andreas Schneider + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "csync_owncloud.h" + +#include + +/* + * free the fetchCtx + */ +static void free_fetchCtx( struct listdir_context *ctx ) +{ + struct resource *newres, *res; + if( ! ctx ) return; + newres = ctx->list; + res = newres; + + ctx->ref--; + if (ctx->ref > 0) return; + + SAFE_FREE(ctx->target); + + while( res ) { + SAFE_FREE(res->uri); + SAFE_FREE(res->name); + SAFE_FREE(res->md5); + memset( res->file_id, 0, FILE_ID_BUF_SIZE+1 ); + + newres = res->next; + SAFE_FREE(res); + res = newres; + } + SAFE_FREE(ctx); +} + + +/* + * local variables. + */ + +struct dav_session_s dav_session; /* The DAV Session, initialised in dav_connect */ +int _connected = 0; /* flag to indicate if a connection exists, ie. + the dav_session is valid */ + +csync_auth_callback _authcb; +long long chunked_total_size = 0; +long long chunked_done = 0; + +struct listdir_context *propfind_cache = 0; + +bool is_first_propfind = true; + + +csync_vio_file_stat_t _stat_cache; +/* id cache, cache the ETag: header of a GET request */ +struct { char *uri; char *id; } _id_cache = { NULL, NULL }; + +static void clean_caches() { + clear_propfind_recursive_cache(); + + free_fetchCtx(propfind_cache); + propfind_cache = NULL; + + SAFE_FREE(_stat_cache.name); + SAFE_FREE(_stat_cache.etag ); + memset( _stat_cache.file_id, 0, FILE_ID_BUF_SIZE+1 ); + + SAFE_FREE(_id_cache.uri); + SAFE_FREE(_id_cache.id); +} + + + +#define PUT_BUFFER_SIZE 1024*5 + +char _buffer[PUT_BUFFER_SIZE]; + +/* + * helper method to build up a user text for SSL problems, called from the + * verify_sslcert callback. + */ +static void addSSLWarning( char *ptr, const char *warn, int len ) +{ + char *concatHere = ptr; + int remainingLen = 0; + + if( ! (warn && ptr )) return; + remainingLen = len - strlen(ptr); + if( remainingLen <= 0 ) return; + concatHere = ptr + strlen(ptr); /* put the write pointer to the end. */ + strncpy( concatHere, warn, remainingLen ); +} + +/* + * Callback to verify the SSL certificate, called from libneon. + * It analyzes the SSL problem, creates a user information text and passes + * it to the csync callback to ask the user. + */ +#define LEN 4096 +static int verify_sslcert(void *userdata, int failures, + const ne_ssl_certificate *certificate) +{ + char problem[LEN]; + char buf[MAX(NE_SSL_DIGESTLEN, NE_ABUFSIZ)]; + int ret = -1; + const ne_ssl_certificate *cert = certificate; + + (void) userdata; + memset( problem, 0, LEN ); + + while( cert ) { + + addSSLWarning( problem, "There are problems with the SSL certificate:\n", LEN ); + if( failures & NE_SSL_NOTYETVALID ) { + addSSLWarning( problem, " * The certificate is not yet valid.\n", LEN ); + } + if( failures & NE_SSL_EXPIRED ) { + addSSLWarning( problem, " * The certificate has expired.\n", LEN ); + } + + if( failures & NE_SSL_UNTRUSTED ) { + addSSLWarning( problem, " * The certificate is not trusted!\n", LEN ); + } + if( failures & NE_SSL_IDMISMATCH ) { + addSSLWarning( problem, " * The hostname for which the certificate was " + "issued does not match the hostname of the server\n", LEN ); + } + if( failures & NE_SSL_BADCHAIN ) { + addSSLWarning( problem, " * The certificate chain contained a certificate other than the server cert\n", LEN ); + } + if( failures & NE_SSL_REVOKED ) { + addSSLWarning( problem, " * The server certificate has been revoked by the issuing authority.\n", LEN ); + } + + if (ne_ssl_cert_digest(cert, buf) == 0) { + addSSLWarning( problem, "Certificate fingerprint: ", LEN ); + addSSLWarning( problem, buf, LEN ); + addSSLWarning( problem, "\n", LEN ); + } + cert = ne_ssl_cert_signedby( cert ); + } + addSSLWarning( problem, "Do you want to accept the certificate chain anyway?\nAnswer yes to do so and take the risk: ", LEN ); + + if( _authcb ){ + /* call the csync callback */ + DEBUG_WEBDAV("Call the csync callback for SSL problems"); + memset( buf, 0, NE_ABUFSIZ ); + (*_authcb) ( problem, buf, NE_ABUFSIZ-1, 1, 0, NULL ); + if( buf[0] == 'y' || buf[0] == 'Y') { + ret = 0; + } else { + DEBUG_WEBDAV("Authentication callback replied %s", buf ); + + } + } + DEBUG_WEBDAV("## VERIFY_SSL CERT: %d", ret ); + return ret; +} + +/* + * Authentication callback. Is set by ne_set_server_auth to be called + * from the neon lib to authenticate a request. + */ +static int ne_auth( void *userdata, const char *realm, int attempt, + char *username, char *password) +{ + char buf[NE_ABUFSIZ]; + + (void) userdata; + (void) realm; + + /* DEBUG_WEBDAV( "Authentication required %s", realm ); */ + if( username && password ) { + DEBUG_WEBDAV( "Authentication required %s", username ); + if( dav_session.user ) { + /* allow user without password */ + if( strlen( dav_session.user ) < NE_ABUFSIZ ) { + strcpy( username, dav_session.user ); + } + if( dav_session.pwd && strlen( dav_session.pwd ) < NE_ABUFSIZ ) { + strcpy( password, dav_session.pwd ); + } + } else if( _authcb != NULL ){ + /* call the csync callback */ + DEBUG_WEBDAV("Call the csync callback for %s", realm ); + memset( buf, 0, NE_ABUFSIZ ); + (*_authcb) ("Enter your username: ", buf, NE_ABUFSIZ-1, 1, 0, NULL ); + if( strlen(buf) < NE_ABUFSIZ ) { + strcpy( username, buf ); + } + memset( buf, 0, NE_ABUFSIZ ); + (*_authcb) ("Enter your password: ", buf, NE_ABUFSIZ-1, 0, 0, NULL ); + if( strlen(buf) < NE_ABUFSIZ) { + strcpy( password, buf ); + } + } else { + DEBUG_WEBDAV("I can not authenticate!"); + } + } + return attempt; +} + +/* + * Authentication callback. Is set by ne_set_proxy_auth to be called + * from the neon lib to authenticate against a proxy. The data to authenticate + * against comes from mirall throught vio_module_init function. + */ +static int ne_proxy_auth( void *userdata, const char *realm, int attempt, + char *username, char *password) +{ + (void) userdata; + (void) realm; + if( dav_session.proxy_user && strlen( dav_session.proxy_user ) < NE_ABUFSIZ) { + strcpy( username, dav_session.proxy_user ); + if( dav_session.proxy_pwd && strlen( dav_session.proxy_pwd ) < NE_ABUFSIZ) { + strcpy( password, dav_session.proxy_pwd ); + } + } + /* NTLM needs several attempts */ + return (attempt < 3) ? 0 : -1; +} + +/* Configure the proxy depending on the variables */ +static int configureProxy( ne_session *session ) +{ + int port = 8080; + int re = -1; + + if( ! session ) return -1; + if( ! dav_session.proxy_type ) return 0; /* Go by NoProxy per default */ + + if( dav_session.proxy_port > 0 ) { + port = dav_session.proxy_port; + } + + if( c_streq(dav_session.proxy_type, "NoProxy" )) { + DEBUG_WEBDAV("No proxy configured."); + re = 0; + } else if( c_streq(dav_session.proxy_type, "DefaultProxy") || + c_streq(dav_session.proxy_type, "HttpProxy") || + c_streq(dav_session.proxy_type, "HttpCachingProxy") || + c_streq(dav_session.proxy_type, "Socks5Proxy")) { + + if( dav_session.proxy_host ) { + DEBUG_WEBDAV("%s at %s:%d", dav_session.proxy_type, dav_session.proxy_host, port ); + if (c_streq(dav_session.proxy_type, "Socks5Proxy")) { + ne_session_socks_proxy(session, NE_SOCK_SOCKSV5, dav_session.proxy_host, port, + dav_session.proxy_user, dav_session.proxy_pwd); + } else { + ne_session_proxy(session, dav_session.proxy_host, port ); + } + re = 2; + } else { + DEBUG_WEBDAV("%s requested but no proxy host defined.", dav_session.proxy_type ); + /* we used to try ne_system_session_proxy here, but we should rather err out + to behave exactly like the caller. */ + } + } else { + DEBUG_WEBDAV( "Unsupported Proxy: %s", dav_session.proxy_type ); + } + + return re; +} + +/* + * This hook is called for with the response of a request. Here its checked + * if a Set-Cookie header is there for the PHPSESSID. The key is stored into + * the webdav session to be added to subsequent requests. + */ +static void post_request_hook(ne_request *req, void *userdata, const ne_status *status) +{ + const char *set_cookie_header = NULL; + const char *sc = NULL; + char *key = NULL; + + (void) userdata; + + if (dav_session.session_key) + return; /* We already have a session cookie, and we should ignore other ones */ + + if(!(status && req)) return; + if( status->klass == 2 || status->code == 401 ) { + /* successful request */ + set_cookie_header = ne_get_response_header( req, "Set-Cookie" ); + if( set_cookie_header ) { + DEBUG_WEBDAV(" Set-Cookie found: %s", set_cookie_header); + /* try to find a ', ' sequence which is the separator of neon if multiple Set-Cookie + * headers are there. + * The following code parses a string like this: + * Set-Cookie: 50ace6bd8a669=p537brtt048jh8srlp2tuep7em95nh9u98mj992fbqc47d1aecp1; + */ + sc = set_cookie_header; + while(sc) { + const char *sc_val = sc; + const char *sc_end = sc_val; + int cnt = 0; + int len = strlen(sc_val); /* The length of the rest of the header string. */ + + while( cnt < len && *sc_end != ';' && *sc_end != ',') { + cnt++; + sc_end++; + } + if( cnt == len ) { + /* exit: We are at the end. */ + sc = NULL; + } else if( *sc_end == ';' ) { + /* We are at the end of the session key. */ + int keylen = sc_end-sc_val; + if( key ) { + int oldlen = strlen(key); + key = c_realloc(key, oldlen + 2 + keylen+1); + strcpy(key + oldlen, "; "); + strncpy(key + oldlen + 2, sc_val, keylen); + key[oldlen + 2 + keylen] = '\0'; + } else { + key = c_malloc(keylen+1); + strncpy( key, sc_val, keylen ); + key[keylen] = '\0'; + } + + /* now search for a ',' to find a potential other header entry */ + while(cnt < len && *sc_end != ',') { + cnt++; + sc_end++; + } + if( cnt < len ) + sc = sc_end+2; /* mind the space after the comma */ + else + sc = NULL; + } else if( *sc_end == ',' ) { + /* A new entry is to check. */ + if( *(sc_end + 1) == ' ') { + sc = sc_end+2; + } else { + /* error condition */ + sc = NULL; + } + } + } + } + } else { + DEBUG_WEBDAV("Request failed, don't take session header."); + } + if( key ) { + DEBUG_WEBDAV("----> Session-key: %s", key); + SAFE_FREE(dav_session.session_key); + dav_session.session_key = key; + } +} + +/* + * this hook is called just after a request has been created, before its sent. + * Here it is used to set the proxy connection header if available. + */ +static void request_created_hook(ne_request *req, void *userdata, + const char *method, const char *requri) +{ + (void) userdata; + (void) method; + (void) requri; + + if( !req ) return; + + if(dav_session.proxy_type) { + /* required for NTLM */ + ne_add_request_header(req, "Proxy-Connection", "Keep-Alive"); + } +} + +/* + * this hook is called just before a request has been sent. + * Here it is used to set the session cookie if available. + */ +static void pre_send_hook(ne_request *req, void *userdata, + ne_buffer *header) +{ + (void) userdata; + + if( !req ) return; + + if(dav_session.session_key) { + ne_buffer_concat(header, "Cookie: ", dav_session.session_key, "\r\n", NULL); + } +} + +static int post_send_hook(ne_request *req, void *userdata, + const ne_status *status) +{ + const char *location; + + (void) userdata; + (void) status; + + location = ne_get_response_header(req, "Location"); + + if( !location ) return NE_OK; + + if( dav_session.redir_callback ) { + if( dav_session.redir_callback( dav_session.csync_ctx, location ) ) { + return NE_REDIRECT; + } else { + return NE_RETRY; + } + } + + return NE_REDIRECT; +} + +// as per http://sourceforge.net/p/predef/wiki/OperatingSystems/ +// extend as required +static const char* get_platform() { +#if defined (_WIN32) + return "Windows"; +#elif defined(__APPLE__) + return "Macintosh"; +#elif defined(__gnu_linux__) + return "Linux"; +#elif defined(__DragonFly__) + /* might also define __FreeBSD__ */ + return "DragonFlyBSD"; +#elif defined(__FreeBSD__) + return "FreeBSD"; +#elif defined(__NetBSD__) + return "NetBSD"; +#elif defined(__OpenBSD__) + return "OpenBSD"; +#elif defined(sun) || defined(__sun) + return "Solaris"; +#else + return "Unknown OS"; +#endif +} + +/* + * Connect to a DAV server + * This function sets the flag _connected if the connection is established + * and returns if the flag is set, so calling it frequently is save. + */ +static int dav_connect(const char *base_url) { + int useSSL = 0; + int rc; + char protocol[6] = {'\0'}; + char uaBuf[256]; + char *path = NULL; + char *scheme = NULL; + char *host = NULL; + unsigned int port = 0; + int proxystate = -1; + + if (_connected) { + return 0; + } + + rc = c_parse_uri( base_url, &scheme, &dav_session.user, &dav_session.pwd, &host, &port, &path ); + if( rc < 0 ) { + DEBUG_WEBDAV("Failed to parse uri %s", base_url ); + goto out; + } + + DEBUG_WEBDAV("* scheme %s", scheme ); + DEBUG_WEBDAV("* host %s", host ); + DEBUG_WEBDAV("* port %u", port ); + DEBUG_WEBDAV("* path %s", path ); + + if( strcmp( scheme, "owncloud" ) == 0 ) { + strcpy( protocol, "http"); + } else if( strcmp( scheme, "ownclouds" ) == 0 ) { + strcpy( protocol, "https"); + useSSL = 1; + } else { + DEBUG_WEBDAV("Invalid scheme %s, go outa here!", scheme ); + rc = -1; + goto out; + } + + DEBUG_WEBDAV("* user %s", dav_session.user ? dav_session.user : ""); + + if (port == 0) { + port = ne_uri_defaultport(protocol); + } + +#if 0 + rc = ne_sock_init(); + DEBUG_WEBDAV("ne_sock_init: %d", rc ); + if (rc < 0) { + rc = -1; + goto out; + } +#endif + + dav_session.ctx = ne_session_create( protocol, host, port); + + if (dav_session.ctx == NULL) { + DEBUG_WEBDAV("Session create with protocol %s failed", protocol ); + rc = -1; + goto out; + } + + if (dav_session.read_timeout == 0) + dav_session.read_timeout = 300; // set 300 seconds as default. + + ne_set_read_timeout(dav_session.ctx, dav_session.read_timeout); + + snprintf( uaBuf, sizeof(uaBuf), "Mozilla/5.0 (%s) csyncoC/%s", + get_platform(), CSYNC_STRINGIFY( LIBCSYNC_VERSION )); + ne_set_useragent( dav_session.ctx, uaBuf); + ne_set_server_auth(dav_session.ctx, ne_auth, 0 ); + + if( useSSL ) { + if (!ne_has_support(NE_FEATURE_SSL)) { + DEBUG_WEBDAV("Error: SSL is not enabled."); + rc = -1; + goto out; + } + + ne_ssl_trust_default_ca( dav_session.ctx ); + ne_ssl_set_verify( dav_session.ctx, verify_sslcert, 0 ); + } + + /* Hook called when a request is created. It sets the proxy connection header. */ + ne_hook_create_request( dav_session.ctx, request_created_hook, NULL ); + /* Hook called after response headers are read. It gets the Session ID. */ + ne_hook_post_headers( dav_session.ctx, post_request_hook, NULL ); + /* Hook called before a request is sent. It sets the cookies. */ + ne_hook_pre_send( dav_session.ctx, pre_send_hook, NULL ); + /* Hook called after request is dispatched. Used for handling possible redirections. */ + ne_hook_post_send( dav_session.ctx, post_send_hook, NULL ); + + /* Proxy support */ + proxystate = configureProxy( dav_session.ctx ); + if( proxystate < 0 ) { + DEBUG_WEBDAV("Error: Proxy-Configuration failed."); + } else if( proxystate > 0 ) { + ne_set_proxy_auth( dav_session.ctx, ne_proxy_auth, 0 ); + } + + _connected = 1; + rc = 0; +out: + SAFE_FREE(path); + SAFE_FREE(host); + SAFE_FREE(scheme); + return rc; +} + +/* + * result parsing list. + * This function is called to parse the result of the propfind request + * to list directories on the WebDAV server. I takes a single resource + * and fills a resource struct and stores it to the result list which + * is stored in the listdir_context. + */ +static void results(void *userdata, + const ne_uri *uri, + const ne_prop_result_set *set) +{ + struct listdir_context *fetchCtx = userdata; + struct resource *newres = 0; + const char *clength, *modtime = NULL; + const char *resourcetype = NULL; + const char *md5sum = NULL; + const char *file_id = NULL; + const ne_status *status = NULL; + char *path = ne_path_unescape( uri->path ); + + (void) status; + if( ! fetchCtx ) { + DEBUG_WEBDAV("No valid fetchContext"); + return; + } + + if( ! fetchCtx->target ) { + DEBUG_WEBDAV("error: target must not be zero!" ); + return; + } + + /* Fill the resource structure with the data about the file */ + newres = c_malloc(sizeof(struct resource)); + ZERO_STRUCTP(newres); + newres->uri = path; /* no need to strdup because ne_path_unescape already allocates */ + newres->name = c_basename( path ); + + modtime = ne_propset_value( set, &ls_props[0] ); + clength = ne_propset_value( set, &ls_props[1] ); + resourcetype = ne_propset_value( set, &ls_props[2] ); + md5sum = ne_propset_value( set, &ls_props[3] ); + file_id = ne_propset_value( set, &ls_props[4] ); + + newres->type = resr_normal; + if( clength == NULL && resourcetype && strncmp( resourcetype, "", 16 ) == 0) { + newres->type = resr_collection; + } + + if (modtime) { + newres->modtime = oc_httpdate_parse(modtime); + } + + /* DEBUG_WEBDAV("Parsing Modtime: %s -> %llu", modtime, (unsigned long long) newres->modtime ); */ + newres->size = 0; + if (clength) { + newres->size = atoll(clength); + /* DEBUG_WEBDAV("Parsed File size for %s from %s: %lld", newres->name, clength, (long long)newres->size ); */ + } + + if( md5sum ) { + newres->md5 = csync_normalize_etag(md5sum); + } + + csync_vio_set_file_id(newres->file_id, file_id); + + /* prepend the new resource to the result list */ + newres->next = fetchCtx->list; + fetchCtx->list = newres; + fetchCtx->result_count = fetchCtx->result_count + 1; + /* DEBUG_WEBDAV( "results for URI %s: %d %d", newres->name, (int)newres->size, (int)newres->type ); */ +} + + + +/* + * fetches a resource list from the WebDAV server. This is equivalent to list dir. + */ +static struct listdir_context *fetch_resource_list(const char *uri, int depth) +{ + struct listdir_context *fetchCtx; + int ret = 0; + ne_propfind_handler *hdl = NULL; + ne_request *request = NULL; + const char *content_type = NULL; + char *curi = NULL; + const ne_status *req_status = NULL; + + curi = _cleanPath( uri ); + + /* The old legacy one-level PROPFIND cache. Also gets filled + by the recursive cache if 'infinity' did not suceed. */ + if (propfind_cache) { + if (c_streq(curi, propfind_cache->target)) { + DEBUG_WEBDAV("fetch_resource_list Using simple PROPFIND cache %s", curi); + propfind_cache->ref++; + SAFE_FREE(curi); + return propfind_cache; + } + } + + fetchCtx = c_malloc( sizeof( struct listdir_context )); + if (!fetchCtx) { + errno = ENOMEM; + SAFE_FREE(curi); + return NULL; + } + fetchCtx->list = NULL; + fetchCtx->target = curi; + fetchCtx->currResource = NULL; + fetchCtx->ref = 1; + + /* do a propfind request and parse the results in the results function, set as callback */ + hdl = ne_propfind_create(dav_session.ctx, curi, depth); + + if(hdl) { + ret = ne_propfind_named(hdl, ls_props, results, fetchCtx); + request = ne_propfind_get_request( hdl ); + req_status = ne_get_status( request ); + } + + if( ret == NE_OK ) { + fetchCtx->currResource = fetchCtx->list; + /* Check the request status. */ + if( req_status && req_status->klass != 2 ) { + set_errno_from_http_errcode(req_status->code); + DEBUG_WEBDAV("ERROR: Request failed: status %d (%s)", req_status->code, + req_status->reason_phrase); + ret = NE_CONNECT; + set_error_message(req_status->reason_phrase); + oc_notify_progress( uri, CSYNC_NOTIFY_ERROR, + req_status->code, + (intptr_t)(req_status->reason_phrase) ); + } + DEBUG_WEBDAV("Simple propfind result code %d.", req_status->code); + } else { + if( ret == NE_ERROR && req_status->code == 404) { + errno = ENOENT; + } else { + set_errno_from_neon_errcode(ret); + } + } + + if( ret == NE_OK ) { + /* Check the content type. If the server has a problem, ie. database is gone or such, + * the content type is not xml but a html error message. Stop on processing if it's + * not XML. + * FIXME: Generate user error message from the reply content. + */ + content_type = ne_get_response_header( request, "Content-Type" ); + if( !(content_type && c_streq(content_type, "application/xml; charset=utf-8") ) ) { + DEBUG_WEBDAV("ERROR: Content type of propfind request not XML: %s.", + content_type ? content_type: ""); + errno = ERRNO_WRONG_CONTENT; + set_error_message("Server error: PROPFIND reply is not XML formatted!"); + ret = NE_CONNECT; + } + } + + if( ret != NE_OK ) { + const char *err = NULL; + set_errno_from_neon_errcode(ret); + + err = ne_get_error( dav_session.ctx ); + if(err) { + set_error_message(err); + } + DEBUG_WEBDAV("WRN: propfind named failed with %d, request error: %s", ret, err ? err : ""); + } + + if( hdl ) + ne_propfind_destroy(hdl); + + if( ret != NE_OK ) { + free_fetchCtx(fetchCtx); + return NULL; + } + + free_fetchCtx(propfind_cache); + propfind_cache = fetchCtx; + propfind_cache->ref++; + return fetchCtx; +} + +static struct listdir_context *fetch_resource_list_attempts(const char *uri, int depth) +{ + int i; + + struct listdir_context *fetchCtx = NULL; + for(i = 0; i < 10; ++i) { + fetchCtx = fetch_resource_list(uri, depth); + if(fetchCtx) break; + /* only loop in case the content is not XML formatted. Otherwise for every + * non successful stat (for non existing directories) its tried 10 times. */ + if( errno != ERRNO_WRONG_CONTENT ) break; + + DEBUG_WEBDAV("=> Errno after fetch resource list for %s: %d", uri, errno); + DEBUG_WEBDAV(" New attempt %i", i); + } + return fetchCtx; +} + +static void fill_stat_cache( csync_vio_file_stat_t *lfs ) { + + if( _stat_cache.name ) SAFE_FREE(_stat_cache.name); + if( _stat_cache.etag ) SAFE_FREE(_stat_cache.etag ); + + if( !lfs) return; + + _stat_cache.name = c_strdup(lfs->name); + _stat_cache.mtime = lfs->mtime; + _stat_cache.fields = lfs->fields; + _stat_cache.type = lfs->type; + _stat_cache.size = lfs->size; + csync_vio_file_stat_set_file_id(&_stat_cache, lfs->file_id); + + if( lfs->etag ) { + _stat_cache.etag = c_strdup(lfs->etag); + } +} + + + +/* + * file functions + */ +static int owncloud_stat(const char *uri, csync_vio_file_stat_t *buf) { + /* get props: + * modtime + * creattime + * size + */ + csync_vio_file_stat_t *lfs = NULL; + struct listdir_context *fetchCtx = NULL; + char *decodedUri = NULL; + int len = 0; + errno = 0; + + DEBUG_WEBDAV("owncloud_stat %s called", uri ); + + buf->name = c_basename(uri); + + if (buf->name == NULL) { + errno = ENOMEM; + return -1; + } + + if( _stat_cache.name && strcmp( buf->name, _stat_cache.name ) == 0 ) { + buf->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS; + buf->fields = _stat_cache.fields; + buf->type = _stat_cache.type; + buf->mtime = _stat_cache.mtime; + buf->size = _stat_cache.size; + buf->mode = _stat_perms( _stat_cache.type ); + buf->etag = NULL; + if( _stat_cache.etag ) { + buf->etag = c_strdup( _stat_cache.etag ); + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG; + } + csync_vio_file_stat_set_file_id( buf, _stat_cache.file_id ); + return 0; + } + DEBUG_WEBDAV("owncloud_stat => Could not find in stat cache %s", uri); + + /* fetch data via a propfind call. */ + /* fetchCtx = fetch_resource_list( uri, NE_DEPTH_ONE); */ + fetchCtx = fetch_resource_list_attempts( uri, NE_DEPTH_ONE); + DEBUG_WEBDAV("=> Errno after fetch resource list for %s: %d", uri, errno); + if (!fetchCtx) { + return -1; + } + + if( fetchCtx ) { + struct resource *res = fetchCtx->list; + while( res ) { + /* remove trailing slashes */ + len = strlen(res->uri); + while( len > 0 && res->uri[len-1] == '/' ) --len; + decodedUri = ne_path_unescape( fetchCtx->target ); /* allocates memory */ + + /* Only do the comparaison of the part of the string without the trailing + slashes, and make sure decodedUri is not too large */ + if( strncmp(res->uri, decodedUri, len ) == 0 && decodedUri[len] == '\0') { + SAFE_FREE( decodedUri ); + break; + } + res = res->next; + SAFE_FREE( decodedUri ); + } + if( res ) { + DEBUG_WEBDAV("Working on file %s", res->name ); + } else { + DEBUG_WEBDAV("ERROR: Result struct not valid!"); + } + + lfs = resourceToFileStat( res ); + if( lfs ) { + buf->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG; + + buf->fields = lfs->fields; + buf->type = lfs->type; + buf->mtime = lfs->mtime; + buf->size = lfs->size; + buf->mode = _stat_perms( lfs->type ); + buf->etag = NULL; + if( lfs->etag ) { + buf->etag = c_strdup( lfs->etag ); + } + csync_vio_file_stat_set_file_id( buf, lfs->file_id ); + + /* fill the static stat buf as input for the stat function */ + csync_vio_file_stat_destroy( lfs ); + } + + free_fetchCtx( fetchCtx ); + } + DEBUG_WEBDAV("STAT result from propfind: %s, mtime: %llu", buf->name ? buf->name:"NULL", + (unsigned long long) buf->mtime ); + + return 0; +} + +/* capabilities are currently: + * bool atomar_copy_support - oC provides atomar copy + * bool do_post_copy_stat - oC does not want the post copy check + * bool time_sync_required - oC does not require the time sync + * int unix_extensions - oC supports unix extensions. + * bool propagate_on_fd - oC supports the send_file method. + */ +static csync_vio_capabilities_t _owncloud_capabilities = { true, false, false, 0, true, false, false }; + +static csync_vio_capabilities_t *owncloud_capabilities(void) +{ +#ifdef _WIN32 + _owncloud_capabilities.unix_extensions = 0; +#endif + return &_owncloud_capabilities; +} + +static const char* owncloud_get_etag( const char *path ) +{ + ne_request *req = NULL; + const char *header = NULL; + char *uri = _cleanPath(path); + char *cbuf = NULL; + csync_vio_file_stat_t *fs = NULL; + bool doHeadRequest = false; + + if (_id_cache.uri && c_streq(path, _id_cache.uri)) { + header = _id_cache.id; + } + + doHeadRequest= false; /* ownCloud server doesn't have good support for HEAD yet */ + + if( !header && doHeadRequest ) { + int neon_stat; + /* Perform an HEAD request to the resource. HEAD delivers the + * ETag header back. */ + req = ne_request_create(dav_session.ctx, "HEAD", uri); + neon_stat = ne_request_dispatch(req); + set_errno_from_neon_errcode( neon_stat ); + + header = ne_get_response_header(req, "etag"); + } + /* If the request went wrong or the server did not respond correctly + * (that can happen for collections) a stat call is done which translates + * into a PROPFIND request. + */ + if( ! header ) { + /* ... and do a stat call. */ + fs = csync_vio_file_stat_new(); + if(fs == NULL) { + DEBUG_WEBDAV( "owncloud_get_etag: memory fault."); + errno = ENOMEM; + return NULL; + } + if( owncloud_stat( path, fs ) == 0 ) { + header = fs->etag; + } + } + + /* In case the result is surrounded by "" cut them away. */ + if( header ) { + cbuf = csync_normalize_etag(header); + } + + /* fix server problem: If we end up with an empty string, set something strange... */ + if( c_streq(cbuf, "") || c_streq(cbuf, "\"\"") ) { + SAFE_FREE(cbuf); + cbuf = c_strdup("empty_etag"); + } + + DEBUG_WEBDAV("Get file ID for %s: %s", path, cbuf ? cbuf:""); + if( fs ) csync_vio_file_stat_destroy(fs); + if( req ) ne_request_destroy(req); + SAFE_FREE(uri); + + + return cbuf; +} + +/* + * directory functions + */ +static csync_vio_method_handle_t *owncloud_opendir(const char *uri) { + struct listdir_context *fetchCtx = NULL; + char *curi = NULL; + + DEBUG_WEBDAV("opendir method called on %s", uri ); + + dav_connect( uri ); + + curi = _cleanPath( uri ); + if (is_first_propfind && !dav_session.no_recursive_propfind) { + is_first_propfind = false; + // Try to fill it + fill_recursive_propfind_cache(uri, curi); + } + if (propfind_recursive_cache) { + // Try to fetch from recursive cache (if we have one) + fetchCtx = get_listdir_context_from_recursive_cache(curi); + } + SAFE_FREE(curi); + is_first_propfind = false; + if (fetchCtx) { + return fetchCtx; + } + + /* fetchCtx = fetch_resource_list( uri, NE_DEPTH_ONE ); */ + fetchCtx = fetch_resource_list_attempts( uri, NE_DEPTH_ONE); + if( !fetchCtx ) { + /* errno is set properly in fetch_resource_list */ + DEBUG_WEBDAV("Errno set to %d", errno); + return NULL; + } else { + fetchCtx->currResource = fetchCtx->list; + DEBUG_WEBDAV("opendir returning handle %p (count=%d)", (void*) fetchCtx, fetchCtx->result_count ); + return fetchCtx; + } + /* no freeing of curi because its part of the fetchCtx and gets freed later */ +} + +static int owncloud_closedir(csync_vio_method_handle_t *dhandle) { + + struct listdir_context *fetchCtx = dhandle; + + DEBUG_WEBDAV("closedir method called %p!", dhandle); + + free_fetchCtx(fetchCtx); + + return 0; +} + +static csync_vio_file_stat_t *owncloud_readdir(csync_vio_method_handle_t *dhandle) { + + struct listdir_context *fetchCtx = dhandle; + + if( fetchCtx == NULL) { + /* DEBUG_WEBDAV("An empty dir or at end"); */ + return NULL; + } + + while( fetchCtx->currResource ) { + resource* currResource = fetchCtx->currResource; + char *escaped_path = NULL; + + /* set pointer to next element */ + fetchCtx->currResource = fetchCtx->currResource->next; + + /* It seems strange: first uri->path is unescaped to escape it in the next step again. + * The reason is that uri->path is not completely escaped (ie. it seems only to have + * spaces escaped), while the fetchCtx->target is fully escaped. + * See http://bugs.owncloud.org/thebuggenie/owncloud/issues/oc-613 + */ + escaped_path = ne_path_escape( currResource->uri ); + if (ne_path_compare(fetchCtx->target, escaped_path) != 0) { + csync_vio_file_stat_t* lfs = resourceToFileStat(currResource); + fill_stat_cache(lfs); + SAFE_FREE( escaped_path ); + return lfs; + } + + /* This is the target URI */ + SAFE_FREE( escaped_path ); + } + + return NULL; +} + +static char *owncloud_error_string() +{ + return dav_session.error_string; +} + +static int owncloud_commit() { + + clean_caches(); + + if( dav_session.ctx ) + ne_session_destroy( dav_session.ctx ); + /* DEBUG_WEBDAV( "********** vio_module_shutdown" ); */ + + dav_session.ctx = 0; + + // ne_sock_exit(); + _connected = 0; /* triggers dav_connect to go through the whole neon setup */ + + SAFE_FREE( dav_session.user ); + SAFE_FREE( dav_session.pwd ); + SAFE_FREE( dav_session.session_key); + SAFE_FREE( dav_session.error_string ); + + SAFE_FREE( dav_session.proxy_type ); + SAFE_FREE( dav_session.proxy_host ); + SAFE_FREE( dav_session.proxy_user ); + SAFE_FREE( dav_session.proxy_pwd ); + + return 0; +} + +static int owncloud_set_property(const char *key, void *data) { +#define READ_STRING_PROPERTY(P) \ + if (c_streq(key, #P)) { \ + SAFE_FREE(dav_session.P); \ + dav_session.P = c_strdup((const char*)data); \ + return 0; \ + } + READ_STRING_PROPERTY(session_key) + READ_STRING_PROPERTY(proxy_type) + READ_STRING_PROPERTY(proxy_host) + READ_STRING_PROPERTY(proxy_user) + READ_STRING_PROPERTY(proxy_pwd) +#undef READ_STRING_PROPERTY + + if (c_streq(key, "proxy_port")) { + dav_session.proxy_port = *(int*)(data); + return 0; + } + if (c_streq(key, "read_timeout") || c_streq(key, "timeout")) { + dav_session.read_timeout = *(int*)(data); + return 0; + } + if( c_streq(key, "csync_context")) { + dav_session.csync_ctx = data; + return 0; + } + if( c_streq(key, "hbf_info")) { + dav_session.chunk_info = (csync_hbf_info_t *)(data); + return 0; + } + if( c_streq(key, "get_dav_session")) { + /* Give the ne_session to the caller */ + *(ne_session**)data = dav_session.ctx; + return 0; + } + if( c_streq(key, "no_recursive_propfind")) { + dav_session.no_recursive_propfind = *(bool*)(data); + return 0; + } + if( c_streq(key, "hbf_block_size")) { + dav_session.hbf_block_size = *(int64_t*)(data); + return 0; + } + if( c_streq(key, "hbf_threshold")) { + dav_session.hbf_threshold = *(int64_t*)(data); + return 0; + } + if( c_streq(key, "bandwidth_limit_upload")) { + dav_session.bandwidth_limit_upload = *(int*)(data); + return 0; + } + if( c_streq(key, "bandwidth_limit_download")) { + dav_session.bandwidth_limit_download = *(int*)(data); + return 0; + } + if( c_streq(key, "overall_progress_data")) { + dav_session.overall_progress_data = (csync_overall_progress_t*)(data); + } + if( c_streq(key, "redirect_callback")) { + if (data) { + csync_owncloud_redirect_callback_t* cb_wrapper = data; + + dav_session.redir_callback = *cb_wrapper; + } else { + dav_session.redir_callback = NULL; + } + } + + return -1; +} + +csync_vio_method_t _method = { + .method_table_size = sizeof(csync_vio_method_t), + .get_capabilities = owncloud_capabilities, + .get_etag = owncloud_get_etag, + .open = 0, + .creat = 0, + .close = 0, + .read = 0, + .write = 0, + .sendfile = 0, + .lseek = 0, + .opendir = owncloud_opendir, + .closedir = owncloud_closedir, + .readdir = owncloud_readdir, + .mkdir = 0, + .rmdir = 0, + .stat = owncloud_stat, + .rename = 0, + .unlink = 0, + .chmod = 0, + .chown = 0, + .utimes = 0, + .set_property = owncloud_set_property, + .get_error_string = owncloud_error_string, + .commit = owncloud_commit + +}; + +csync_vio_method_t *vio_module_init(const char *method_name, const char *args, + csync_auth_callback cb, void *userdata) { + (void) method_name; + (void) args; + (void) userdata; + + _authcb = cb; + _connected = 0; /* triggers dav_connect to go through the whole neon setup */ + + memset(&dav_session, 0, sizeof(dav_session)); + + /* Disable it, Mirall can enable it for the first sync (= no DB)*/ + dav_session.no_recursive_propfind = true; + + return &_method; +} + +void vio_module_shutdown(csync_vio_method_t *method) { + (void) method; + + /* DEBUG_WEBDAV( "********** vio_module_shutdown" ); */ +} + +/* vim: set ts=4 sw=4 et cindent: */ diff --git a/csync/src/csync_owncloud.h b/csync/src/csync_owncloud.h new file mode 100644 index 000000000..5541ac8b7 --- /dev/null +++ b/csync/src/csync_owncloud.h @@ -0,0 +1,181 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2011 by Andreas Schneider + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef CSYNC_OWNCLOUD_H +#define CSYNC_OWNCLOUD_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "config_csync.h" +#ifdef NEON_WITH_LFS /* Switch on LFS in libneon. Never remove the NE_LFS! */ +#define NE_LFS +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "c_rbtree.h" + +#include "c_lib.h" +#include "csync.h" +#include "csync_misc.h" +#include "csync_macros.h" +#include "c_private.h" +#include "httpbf.h" + +#include "vio/csync_vio_module.h" +#include "vio/csync_vio_file_stat.h" +#include "vio/csync_vio.h" + +#include "csync_log.h" + + +#define DEBUG_WEBDAV(...) csync_log( 9, "oc_module", __VA_ARGS__); + +enum resource_type { + resr_normal = 0, + resr_collection, + resr_reference, + resr_error +}; + +/* Struct to store data for each resource found during an opendir operation. + * It represents a single file entry. + */ + +typedef struct resource { + char *uri; /* The complete uri */ + char *name; /* The filename only */ + + enum resource_type type; + int64_t size; + time_t modtime; + char* md5; + char file_id[FILE_ID_BUF_SIZE+1]; + + struct resource *next; +} resource; + +/* Struct to hold the context of a WebDAV PropFind operation to fetch + * a directory listing from the server. + */ +struct listdir_context { + struct resource *list; /* The list of result resources */ + struct resource *currResource; /* A pointer to the current resource */ + char *target; /* Request-URI of the PROPFIND */ + unsigned int result_count; /* number of elements stored in list */ + int ref; /* reference count, only destroy when it reaches 0 */ +}; + + +/* Our cache, key is a char* */ +extern c_rbtree_t *propfind_recursive_cache; +/* Values are propfind_recursive_element: */ +struct propfind_recursive_element { + struct resource *self; + struct resource *children; + struct propfind_recursive_element *parent; +}; +typedef struct propfind_recursive_element propfind_recursive_element_t; +void clear_propfind_recursive_cache(void); +struct listdir_context *get_listdir_context_from_recursive_cache(const char *curi); +void fill_recursive_propfind_cache(const char *uri, const char *curi); +struct listdir_context *get_listdir_context_from_cache(const char *curi); +void fetch_resource_list_recursive(const char *uri, const char *curi); + +typedef int (*csync_owncloud_redirect_callback_t)(CSYNC* ctx, const char* uri); + +/* Struct with the WebDAV session */ +struct dav_session_s { + ne_session *ctx; + char *user; + char *pwd; + + char *proxy_type; + char *proxy_host; + int proxy_port; + char *proxy_user; + char *proxy_pwd; + + char *session_key; + + char *error_string; + + int read_timeout; + + CSYNC *csync_ctx; + + csync_hbf_info_t *chunk_info; + + bool no_recursive_propfind; + int64_t hbf_block_size; + int64_t hbf_threshold; + + /* If 0, it is disabled. If >0, in Byte/seconds. If < 0, in % of the available bandwidth*/ + int bandwidth_limit_upload; + int bandwidth_limit_download; + + csync_overall_progress_t *overall_progress_data; + csync_owncloud_redirect_callback_t redir_callback; +}; +extern struct dav_session_s dav_session; + +/* The list of properties that is fetched in PropFind on a collection */ +static const ne_propname ls_props[] = { + { "DAV:", "getlastmodified" }, + { "DAV:", "getcontentlength" }, + { "DAV:", "resourcetype" }, + { "DAV:", "getetag"}, + { "http://owncloud.org/ns", "id"}, + { NULL, NULL } +}; + +void set_errno_from_http_errcode( int err ); +void set_error_message( const char *msg ); +void set_errno_from_neon_errcode( int neon_code ); +int http_result_code_from_session(void); +void set_errno_from_session(void); + +time_t oc_httpdate_parse( const char *date ); + +char *_cleanPath( const char* uri ); + +int _stat_perms( int type ); +csync_vio_file_stat_t *resourceToFileStat( struct resource *res ); + +void oc_notify_progress(const char *file, enum csync_notify_type_e kind, int64_t current_size, int64_t full_size); + +#endif /* CSYNC_OWNCLOUD_H */ diff --git a/csync/src/csync_owncloud_recursive_propfind.c b/csync/src/csync_owncloud_recursive_propfind.c new file mode 100644 index 000000000..4522fc3a1 --- /dev/null +++ b/csync/src/csync_owncloud_recursive_propfind.c @@ -0,0 +1,339 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2011 by Andreas Schneider + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "csync_owncloud.h" + +c_rbtree_t *propfind_recursive_cache = NULL; +int propfind_recursive_cache_depth = 0; +int propfind_recursive_cache_file_count = 0; +int propfind_recursive_cache_folder_count = 0; + + +static struct resource* resource_dup(struct resource* o) { + struct resource *r = c_malloc (sizeof( struct resource )); + ZERO_STRUCTP(r); + + r->uri = c_strdup(o->uri); + r->name = c_strdup(o->name); + r->type = o->type; + r->size = o->size; + r->modtime = o->modtime; + if( o->md5 ) { + r->md5 = c_strdup(o->md5); + } + r->next = o->next; + csync_vio_set_file_id(r->file_id, o->file_id); + + return r; +} +static void resource_free(struct resource* o) { + struct resource* old = NULL; + while (o) + { + old = o; + o = o->next; + SAFE_FREE(old->uri); + SAFE_FREE(old->name); + SAFE_FREE(old->md5); + SAFE_FREE(old); + } +} + +static void _tree_destructor(void *data) { + propfind_recursive_element_t *element = data; + resource_free(element->self); + resource_free(element->children); + SAFE_FREE(element); +} + +void clear_propfind_recursive_cache(void) +{ + if (propfind_recursive_cache) { + DEBUG_WEBDAV("clear_propfind_recursive_cache Invalidating.."); + c_rbtree_destroy(propfind_recursive_cache, _tree_destructor); + propfind_recursive_cache = NULL; + } +} + +struct listdir_context *get_listdir_context_from_recursive_cache(const char *curi) +{ + propfind_recursive_element_t *element = NULL; + struct listdir_context *fetchCtx = NULL; + struct resource *iterator, *r; + + if (!propfind_recursive_cache) { + DEBUG_WEBDAV("get_listdir_context_from_recursive_cache No cache"); + return NULL; + } + + element = c_rbtree_node_data(c_rbtree_find(propfind_recursive_cache, curi)); + if (!element) { + DEBUG_WEBDAV("get_listdir_context_from_recursive_cache No element %s in cache found", curi); + return NULL; + } + + /* Out of the element, create a listdir_context.. if we could be sure that it is immutable, we could ref instead.. need to investigate */ + fetchCtx = c_malloc( sizeof( struct listdir_context )); + ZERO_STRUCTP(fetchCtx); + fetchCtx->list = NULL; + fetchCtx->target = c_strdup(curi); + fetchCtx->currResource = NULL; + fetchCtx->ref = 1; + + iterator = element->children; + r = NULL; + while (iterator) { + r = resource_dup(iterator); + r->next = fetchCtx->list; + fetchCtx->list = r; + iterator = iterator->next; + fetchCtx->result_count++; + /* DEBUG_WEBDAV("get_listdir_context_from_cache Returning cache for %s element %s", fetchCtx->target, fetchCtx->list->uri); */ + } + + r = resource_dup(element->self); + r->next = fetchCtx->list; + fetchCtx->result_count++; + fetchCtx->list = r; + fetchCtx->currResource = fetchCtx->list; + DEBUG_WEBDAV("get_listdir_context_from_cache Returning cache for %s (%d elements)", fetchCtx->target, fetchCtx->result_count); + return fetchCtx; +} + +static int _key_cmp(const void *key, const void *b) { + const char *elementAUri = (char*)key; + const propfind_recursive_element_t *elementB = b; + return ne_path_compare(elementAUri, elementB->self->uri); +} +static int _data_cmp(const void *a, const void *b) { + const propfind_recursive_element_t *elementA = a; + const propfind_recursive_element_t *elementB = b; + return ne_path_compare(elementA->self->uri, elementB->self->uri); +} +static void propfind_results_recursive(void *userdata, + const ne_uri *uri, + const ne_prop_result_set *set) +{ + struct resource *newres = 0; + const char *clength, *modtime, *file_id = NULL; + const char *resourcetype = NULL; + const char *md5sum = NULL; + const ne_status *status = NULL; + char *path = ne_path_unescape( uri->path ); + char *parentPath; + char *propfindRootUri = (char*) userdata; + propfind_recursive_element_t *element = NULL; + propfind_recursive_element_t *pElement = NULL; + int depth = 0; + + (void) status; + (void) propfindRootUri; + + if (!propfind_recursive_cache) { + c_rbtree_create(&propfind_recursive_cache, _key_cmp, _data_cmp); + } + + /* Fill the resource structure with the data about the file */ + newres = c_malloc(sizeof(struct resource)); + ZERO_STRUCTP(newres); + + newres->uri = path; /* no need to strdup because ne_path_unescape already allocates */ + newres->name = c_basename( path ); + + modtime = ne_propset_value( set, &ls_props[0] ); + clength = ne_propset_value( set, &ls_props[1] ); + resourcetype = ne_propset_value( set, &ls_props[2] ); + md5sum = ne_propset_value( set, &ls_props[3] ); + file_id = ne_propset_value( set, &ls_props[4] ); + + newres->type = resr_normal; + if( resourcetype && strncmp( resourcetype, "", 16 ) == 0) { + newres->type = resr_collection; + propfind_recursive_cache_folder_count++; + } else { + /* DEBUG_WEBDAV("propfind_results_recursive %s [%d]", newres->uri, newres->type); */ + propfind_recursive_cache_file_count++; + } + + if (modtime) { + newres->modtime = oc_httpdate_parse(modtime); + } + + /* DEBUG_WEBDAV("Parsing Modtime: %s -> %llu", modtime, (unsigned long long) newres->modtime ); */ + newres->size = 0; + if (clength) { + newres->size = atoll(clength); + /* DEBUG_WEBDAV("Parsed File size for %s from %s: %lld", newres->name, clength, (long long)newres->size ); */ + } + + if( md5sum ) { + newres->md5 = csync_normalize_etag(md5sum); + } + + csync_vio_set_file_id(newres->file_id, file_id); + /* + DEBUG_WEBDAV("propfind_results_recursive %s [%s] %s", newres->uri, newres->type == resr_collection ? "collection" : "file", newres->md5); + */ + + /* Create new item in rb tree */ + if (newres->type == resr_collection) { + DEBUG_WEBDAV("propfind_results_recursive %s is a folder", newres->uri); + /* Check if in rb tree */ + element = c_rbtree_node_data(c_rbtree_find(propfind_recursive_cache,uri->path)); + /* If not, create a new item and insert it */ + if (!element) { + element = c_malloc(sizeof(propfind_recursive_element_t)); + element->self = resource_dup(newres); + element->children = NULL; + element->parent = NULL; + c_rbtree_insert(propfind_recursive_cache, element); + /* DEBUG_WEBDAV("results_recursive Added collection %s", newres->uri); */ + } + } + + /* Check for parent in tree. If exists: Insert it into the children elements there */ + parentPath = ne_path_parent(uri->path); + if (parentPath) { + propfind_recursive_element_t *parentElement = NULL; + + parentElement = c_rbtree_node_data(c_rbtree_find(propfind_recursive_cache,parentPath)); + free(parentPath); + + if (parentElement) { + newres->next = parentElement->children; + parentElement->children = newres; + + /* If the current result is a collection we also need to set its parent */ + if (element) + element->parent = parentElement; + + pElement = element; + while (pElement) { + depth++; + pElement = pElement->parent; + } + if (depth > propfind_recursive_cache_depth) { + DEBUG_WEBDAV("propfind_results_recursive %s new maximum tree depth %d", newres->uri, depth); + propfind_recursive_cache_depth = depth; + } + + /* DEBUG_WEBDAV("results_recursive Added child %s to collection %s", newres->uri, element->self->uri); */ + } else { + /* DEBUG_WEBDAV("results_recursive No parent %s found for child %s", parentPath, newres->uri); */ + resource_free(newres); + newres = NULL; + } + } + +} + +void fetch_resource_list_recursive(const char *uri, const char *curi) +{ + int ret = 0; + ne_propfind_handler *hdl = NULL; + ne_request *request = NULL; + const char *content_type = NULL; + const ne_status *req_status = NULL; + int depth = NE_DEPTH_INFINITE; + + DEBUG_WEBDAV("fetch_resource_list_recursive Starting recursive propfind %s %s", uri, curi); + + /* do a propfind request and parse the results in the results function, set as callback */ + hdl = ne_propfind_create(dav_session.ctx, curi, depth); + + if(hdl) { + ret = ne_propfind_named(hdl, ls_props, propfind_results_recursive, (void*)curi); + request = ne_propfind_get_request( hdl ); + req_status = ne_get_status( request ); + } + + if( ret == NE_OK ) { + /* Check the request status. */ + if( req_status && req_status->klass != 2 ) { + set_errno_from_http_errcode(req_status->code); + DEBUG_WEBDAV("ERROR: Request failed: status %d (%s)", req_status->code, + req_status->reason_phrase); + ret = NE_CONNECT; + set_error_message(req_status->reason_phrase); + oc_notify_progress(uri, CSYNC_NOTIFY_ERROR, req_status->code, (intptr_t)(req_status->reason_phrase)); + } + DEBUG_WEBDAV("Recursive propfind result code %d.", req_status ? req_status->code : 0); + } else { + if( ret == NE_ERROR && req_status->code == 404) { + errno = ENOENT; + } else { + set_errno_from_neon_errcode(ret); + } + } + + if( ret == NE_OK ) { + /* Check the content type. If the server has a problem, ie. database is gone or such, + * the content type is not xml but a html error message. Stop on processing if it's + * not XML. + * FIXME: Generate user error message from the reply content. + */ + content_type = ne_get_response_header( request, "Content-Type" ); + if( !(content_type && c_streq(content_type, "application/xml; charset=utf-8") ) ) { + DEBUG_WEBDAV("ERROR: Content type of propfind request not XML: %s.", + content_type ? content_type: ""); + errno = ERRNO_WRONG_CONTENT; + set_error_message("Server error: PROPFIND reply is not XML formatted!"); + ret = NE_CONNECT; + } + } + + if( ret != NE_OK ) { + const char *err = NULL; + + err = ne_get_error( dav_session.ctx ); + DEBUG_WEBDAV("WRN: propfind named failed with %d, request error: %s", ret, err ? err : ""); + } + + if( hdl ) + ne_propfind_destroy(hdl); + + if( ret != NE_OK ) { + return; + } + + return; +} + +/* Called by owncloud_opendir()->fetch_resource_list() to fill the cache */ +extern struct listdir_context *propfind_cache; +void fill_recursive_propfind_cache(const char *uri, const char *curi) { + fetch_resource_list_recursive(uri, curi); + + if (propfind_recursive_cache_depth <= 2) { + DEBUG_WEBDAV("fill_recursive_propfind_cache %s Server maybe did not give us an 'infinity' depth result", curi); + /* transform the cache to the normal cache in propfind_cache */ + propfind_cache = get_listdir_context_from_recursive_cache(curi); + /* clear the cache, it is bogus since the server returned only results for Depth 1 */ + clear_propfind_recursive_cache(); + } else { + DEBUG_WEBDAV("fill_recursive_propfind_cache %s We received %d elements deep for 'infinity' depth (%d folders, %d files)", + curi, + propfind_recursive_cache_depth, + propfind_recursive_cache_folder_count, + propfind_recursive_cache_file_count); + + } +} diff --git a/csync/src/csync_owncloud_util.c b/csync/src/csync_owncloud_util.c new file mode 100644 index 000000000..39a6f7770 --- /dev/null +++ b/csync/src/csync_owncloud_util.c @@ -0,0 +1,366 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2011 by Andreas Schneider + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "csync_owncloud.h" +#include "csync_misc.h" + +void set_error_message( const char *msg ) +{ + SAFE_FREE(dav_session.error_string); + if( msg ) + dav_session.error_string = c_strdup(msg); +} + +void set_errno_from_http_errcode( int err ) { + int new_errno = 0; + + switch(err) { + case 200: /* OK */ + case 201: /* Created */ + case 202: /* Accepted */ + case 203: /* Non-Authoritative Information */ + case 204: /* No Content */ + case 205: /* Reset Content */ + case 207: /* Multi-Status */ + case 304: /* Not Modified */ + new_errno = 0; + break; + case 401: /* Unauthorized */ + case 402: /* Payment Required */ + case 407: /* Proxy Authentication Required */ + case 405: + new_errno = EPERM; + break; + case 301: /* Moved Permanently */ + case 303: /* See Other */ + case 404: /* Not Found */ + case 410: /* Gone */ + new_errno = ENOENT; + break; + case 408: /* Request Timeout */ + case 504: /* Gateway Timeout */ + new_errno = EAGAIN; + break; + case 423: /* Locked */ + new_errno = EACCES; + break; + case 400: /* Bad Request */ + case 403: /* Forbidden */ + case 409: /* Conflict */ + case 411: /* Length Required */ + case 412: /* Precondition Failed */ + case 414: /* Request-URI Too Long */ + case 415: /* Unsupported Media Type */ + case 424: /* Failed Dependency */ + case 501: /* Not Implemented */ + new_errno = EINVAL; + break; + case 507: /* Insufficient Storage */ + new_errno = ENOSPC; + break; + case 206: /* Partial Content */ + case 300: /* Multiple Choices */ + case 302: /* Found */ + case 305: /* Use Proxy */ + case 306: /* (Unused) */ + case 307: /* Temporary Redirect */ + case 406: /* Not Acceptable */ + case 416: /* Requested Range Not Satisfiable */ + case 417: /* Expectation Failed */ + case 422: /* Unprocessable Entity */ + case 500: /* Internal Server Error */ + case 502: /* Bad Gateway */ + case 505: /* HTTP Version Not Supported */ + new_errno = EIO; + break; + case 503: /* Service Unavailable */ + new_errno = ERRNO_SERVICE_UNAVAILABLE; + break; + case 413: /* Request Entity too Large */ + new_errno = EFBIG; + break; + default: + new_errno = EIO; + } + + errno = new_errno; +} + +int http_result_code_from_session() { + const char *p = ne_get_error( dav_session.ctx ); + char *q; + int err; + + set_error_message(p); /* remember the error message */ + + err = strtol(p, &q, 10); + if (p == q) { + err = ERRNO_ERROR_STRING; + } + return err; +} + +void set_errno_from_session() { + int err = http_result_code_from_session(); + + if( err == EIO || err == ERRNO_ERROR_STRING) { + errno = err; + } else { + set_errno_from_http_errcode(err); + } +} + +void set_errno_from_neon_errcode( int neon_code ) { + + if( neon_code != NE_OK ) { + DEBUG_WEBDAV("Neon error code was %d", neon_code); + } + + switch(neon_code) { + case NE_OK: /* Success, but still the possiblity of problems */ + case NE_ERROR: /* Generic error; use ne_get_error(session) for message */ + set_errno_from_session(); /* Something wrong with http communication */ + break; + case NE_LOOKUP: /* Server or proxy hostname lookup failed */ + errno = ERRNO_LOOKUP_ERROR; + break; + case NE_AUTH: /* User authentication failed on server */ + errno = ERRNO_USER_UNKNOWN_ON_SERVER; + break; + case NE_PROXYAUTH: /* User authentication failed on proxy */ + errno = ERRNO_PROXY_AUTH; + break; + case NE_CONNECT: /* Could not connect to server */ + errno = ERRNO_CONNECT; + break; + case NE_TIMEOUT: /* Connection timed out */ + errno = ERRNO_TIMEOUT; + break; + case NE_FAILED: /* The precondition failed */ + errno = ERRNO_PRECONDITION; + break; + case NE_RETRY: /* Retry request (ne_end_request ONLY) */ + errno = ERRNO_RETRY; + break; + + case NE_REDIRECT: /* See ne_redirect.h */ + errno = ERRNO_REDIRECT; + break; + default: + errno = ERRNO_GENERAL_ERROR; + } +} + +/* cleanPath to return an escaped path of an uri */ +char *_cleanPath( const char* uri ) { + int rc = 0; + char *path = NULL; + char *re = NULL; + + rc = c_parse_uri( uri, NULL, NULL, NULL, NULL, NULL, &path ); + if( rc < 0 ) { + DEBUG_WEBDAV("Unable to cleanPath %s", uri ? uri: "" ); + re = NULL; + } else { + if(path) { + re = ne_path_escape( path ); + } + } + + SAFE_FREE( path ); + return re; +} + + +#ifndef HAVE_TIMEGM +#ifdef _WIN32 +static int is_leap(unsigned y) { + y += 1900; + return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); +} + +static time_t timegm(struct tm *tm) { + static const unsigned ndays[2][12] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; + + time_t res = 0; + int i; + + for (i = 70; i < tm->tm_year; ++i) + res += is_leap(i) ? 366 : 365; + + for (i = 0; i < tm->tm_mon; ++i) + res += ndays[is_leap(tm->tm_year)][i]; + res += tm->tm_mday - 1; + res *= 24; + res += tm->tm_hour; + res *= 60; + res += tm->tm_min; + res *= 60; + res += tm->tm_sec; + return res; +} +#else +/* A hopefully portable version of timegm */ +static time_t timegm(struct tm *tm ) { + time_t ret; + char *tz; + + tz = getenv("TZ"); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); + tzset(); + return ret; +} +#endif /* Platform switch */ +#endif /* HAVE_TIMEGM */ + +#define RFC1123_FORMAT "%3s, %02d %3s %4d %02d:%02d:%02d GMT" +static const char short_months[12][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; +/* + * This function is borrowed from libneon's ne_httpdate_parse. + * Unfortunately that one converts to local time but here UTC is + * needed. + * This one uses timegm instead, which returns UTC. + */ +time_t oc_httpdate_parse( const char *date ) { + struct tm gmt; + char wkday[4], mon[4]; + int n; + time_t result = 0; + + memset(&gmt, 0, sizeof(struct tm)); + + /* it goes: Sun, 06 Nov 1994 08:49:37 GMT */ + n = sscanf(date, RFC1123_FORMAT, + wkday, &gmt.tm_mday, mon, &gmt.tm_year, &gmt.tm_hour, + &gmt.tm_min, &gmt.tm_sec); + /* Is it portable to check n==7 here? */ + gmt.tm_year -= 1900; + for (n=0; n<12; n++) + if (strcmp(mon, short_months[n]) == 0) + break; + /* tm_mon comes out as 12 if the month is corrupt, which is desired, + * since the mktime will then fail */ + gmt.tm_mon = n; + gmt.tm_isdst = -1; + result = timegm(&gmt); + return result; +} + + +/* + * helper: convert a resource struct to file_stat struct. + */ +csync_vio_file_stat_t *resourceToFileStat( struct resource *res ) +{ + csync_vio_file_stat_t *lfs = NULL; + + if( ! res ) { + return NULL; + } + + lfs = c_malloc(sizeof(csync_vio_file_stat_t)); + if (lfs == NULL) { + errno = ENOMEM; + return NULL; + } + + lfs->name = c_strdup( res->name ); + + lfs->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + if( res->type == resr_normal ) { + lfs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + lfs->type = CSYNC_VIO_FILE_TYPE_REGULAR; + } else if( res->type == resr_collection ) { + lfs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + lfs->type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + } else { + DEBUG_WEBDAV("ERROR: Unknown resource type %d", res->type); + } + + lfs->mtime = res->modtime; + lfs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + lfs->size = res->size; + lfs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; + if( res->md5 ) { + lfs->etag = c_strdup(res->md5); + } + lfs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG; + csync_vio_file_stat_set_file_id(lfs, res->file_id); + + return lfs; +} + +/* WebDAV does not deliver permissions. Set a default here. */ +int _stat_perms( int type ) { + int ret = 0; + + if( type == CSYNC_VIO_FILE_TYPE_DIRECTORY ) { + /* DEBUG_WEBDAV("Setting mode in stat (dir)"); */ + /* directory permissions */ + ret = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR /* directory, rwx for user */ + | S_IRGRP | S_IXGRP /* rx for group */ + | S_IROTH | S_IXOTH; /* rx for others */ + } else { + /* regualar file permissions */ + /* DEBUG_WEBDAV("Setting mode in stat (file)"); */ + ret = S_IFREG | S_IRUSR | S_IWUSR /* regular file, user read & write */ + | S_IRGRP /* group read perm */ + | S_IROTH; /* others read perm */ + } + return ret; +} + +void oc_notify_progress(const char *file, enum csync_notify_type_e kind, int64_t current_size, int64_t full_size) +{ + csync_progress_callback progress_cb = csync_get_progress_callback(dav_session.csync_ctx); + + csync_overall_progress_t overall_progress; + ZERO_STRUCT(overall_progress); + + if( dav_session.overall_progress_data) { + overall_progress = *dav_session.overall_progress_data; + } + + if (progress_cb) { + CSYNC_PROGRESS progress; + progress.kind = kind; + progress.path = file; + progress.curr_bytes = current_size; + progress.file_size = full_size; + progress.overall_transmission_size = overall_progress.byte_sum; + progress.current_overall_bytes = overall_progress.byte_current+current_size; + progress.overall_file_count = overall_progress.file_count; + progress.current_file_no = overall_progress.current_file_no; + + progress_cb(&progress, csync_get_userdata(dav_session.csync_ctx)); + } +} diff --git a/csync/src/csync_private.h b/csync/src/csync_private.h new file mode 100644 index 000000000..2bc467a3c --- /dev/null +++ b/csync/src/csync_private.h @@ -0,0 +1,229 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file csync_private.h + * + * @brief Private interface of csync + * + * @defgroup csyncInternalAPI csync internal API + * + * @{ + */ + +#ifndef _CSYNC_PRIVATE_H +#define _CSYNC_PRIVATE_H + +#include +#include +#include + +#include "config_csync.h" +#include "c_lib.h" +#include "c_private.h" +#include "csync.h" +#include "csync_misc.h" +#include "vio/csync_vio_file_stat.h" + +#ifdef WITH_ICONV +#include +#endif + +#ifdef HAVE_ICONV_H +#include +#endif +#ifdef HAVE_SYS_ICONV_H +#include +#endif + +#include "vio/csync_vio_method.h" +#include "csync_macros.h" + +/** + * How deep to scan directories. + */ +#define MAX_DEPTH 50 + +/** + * Maximum time difference between two replicas in seconds + */ +#define MAX_TIME_DIFFERENCE 10 + +/** + * Maximum size of a buffer for transfer + */ +#ifndef MAX_XFER_BUF_SIZE +#define MAX_XFER_BUF_SIZE (16 * 1024) +#endif + +#define CSYNC_STATUS_INIT 1 << 0 +#define CSYNC_STATUS_UPDATE 1 << 1 +#define CSYNC_STATUS_RECONCILE 1 << 2 +#define CSYNC_STATUS_PROPAGATE 1 << 3 + +#define CSYNC_STATUS_DONE (CSYNC_STATUS_INIT | \ + CSYNC_STATUS_UPDATE | \ + CSYNC_STATUS_RECONCILE | \ + CSYNC_STATUS_PROPAGATE) + +enum csync_replica_e { + LOCAL_REPLICA, + REMOTE_REPLICA +}; + +typedef struct csync_file_stat_s csync_file_stat_t; + +/** + * @brief csync public structure + */ +struct csync_s { + struct { + csync_auth_callback auth_function; + csync_progress_callback progress_cb; + void *userdata; + } callbacks; + c_strlist_t *excludes; + + struct { + char *file; + sqlite3 *db; + int exists; + int disabled; + } statedb; + + struct { + char *uri; + c_rbtree_t *tree; + c_list_t *list; + enum csync_replica_e type; + c_list_t *ignored_cleanup; + } local; + + struct { + char *uri; + c_rbtree_t *tree; + c_list_t *list; + enum csync_replica_e type; + int read_from_db; + c_list_t *ignored_cleanup; + } remote; + + struct { + csync_vio_method_t *method; + csync_vio_method_finish_fn finish_fn; + csync_vio_capabilities_t capabilities; + } module; + + struct { + int max_depth; + int max_time_difference; + int sync_symbolic_links; + int unix_extensions; + char *config_dir; + bool with_conflict_copys; + bool local_only_mode; + int timeout; +#if defined(HAVE_ICONV) && defined(WITH_ICONV) + iconv_t iconv_cd; +#endif + } options; + + struct { + uid_t uid; + uid_t euid; + } pwd; + + csync_overall_progress_t overall_progress; + + struct csync_progressinfo_s *progress_info; + + /* replica we are currently walking */ + enum csync_replica_e current; + + /* replica we want to work on */ + enum csync_replica_e replica; + + /* Used in the update phase so changes in the sub directories can be notified to + parent directories */ + csync_file_stat_t *current_fs; + + /* csync error code */ + enum csync_status_codes_e status_code; + + char *error_string; + + int status; + volatile int abort; + void *rename_info; +}; + + +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct csync_file_stat_s { + uint64_t phash; /* u64 */ + time_t modtime; /* u64 */ + int64_t size; /* u64 */ + size_t pathlen; /* u64 */ + uint64_t inode; /* u64 */ + uid_t uid; /* u32 */ + gid_t gid; /* u32 */ + mode_t mode; /* u32 */ + int nlink; /* u32 */ + int type; /* u32 */ + int child_modified;/*bool*/ + int should_update_etag; /*bool */ + + char *destpath; /* for renames */ + const char *etag; + char file_id[FILE_ID_BUF_SIZE+1]; /* the ownCloud file id is fixed width of 21 byte. */ + CSYNC_STATUS error_status; + + enum csync_instructions_e instruction; /* u32 */ + char path[1]; /* u8 */ +} +#if !defined(__SUNPRO_C) && !defined(_MSC_VER) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +void csync_file_stat_free(csync_file_stat_t *st); + +/* + * context for the treewalk function + */ +struct _csync_treewalk_context_s +{ + csync_treewalk_visit_func *user_visitor; + int instruction_filter; + void *userdata; +}; +typedef struct _csync_treewalk_context_s _csync_treewalk_context; + +/** + * }@ + */ +#endif /* _CSYNC_PRIVATE_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_reconcile.c b/csync/src/csync_reconcile.c new file mode 100644 index 000000000..b6936e05a --- /dev/null +++ b/csync/src/csync_reconcile.c @@ -0,0 +1,299 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#include "csync_private.h" +#include "csync_reconcile.h" +#include "csync_util.h" +#include "csync_statedb.h" +#include "csync_rename.h" +#include "c_jhash.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.reconciler" +#include "csync_log.h" + +#include "inttypes.h" + +#define ACCEPTED_TIME_DIFF 5 +#define ONE_HOUR 3600 + +/* + * We merge replicas at the file level. The merged replica contains the + * superset of files that are on the local machine and server copies of + * the replica. In the case where the same file is in both the local + * and server copy, the file that was modified most recently is used. + * This means that new files are not deleted, and updated versions of + * existing files are not overwritten. + * + * When a file is updated, the merge algorithm compares the destination + * file with the the source file. If the destination file is newer + * (timestamp is newer), it is not overwritten. If both files, on the + * source and the destination, have been changed, the newer file wins. + */ +static int _csync_merge_algorithm_visitor(void *obj, void *data) { + csync_file_stat_t *cur = NULL; + csync_file_stat_t *other = NULL; + csync_file_stat_t *tmp = NULL; + uint64_t h = 0; + int len = 0; + + CSYNC *ctx = NULL; + c_rbtree_t *tree = NULL; + c_rbnode_t *node = NULL; + + cur = (csync_file_stat_t *) obj; + ctx = (CSYNC *) data; + + /* we need the opposite tree! */ + switch (ctx->current) { + case LOCAL_REPLICA: + tree = ctx->remote.tree; + break; + case REMOTE_REPLICA: + tree = ctx->local.tree; + break; + default: + break; + } + + node = c_rbtree_find(tree, &cur->phash); + + if (!node) { + /* Check the renamed path as well. */ + char *renamed_path = csync_rename_adjust_path(ctx, cur->path); + if (!c_streq(renamed_path, cur->path)) { + len = strlen( renamed_path ); + h = c_jhash64((uint8_t *) renamed_path, len, 0); + node = c_rbtree_find(tree, &h); + } + SAFE_FREE(renamed_path); + } + + /* file only found on current replica */ + if (node == NULL) { + switch(cur->instruction) { + /* file has been modified */ + case CSYNC_INSTRUCTION_EVAL: + cur->instruction = CSYNC_INSTRUCTION_NEW; + break; + /* file has been removed on the opposite replica */ + case CSYNC_INSTRUCTION_NONE: + cur->instruction = CSYNC_INSTRUCTION_REMOVE; + break; + case CSYNC_INSTRUCTION_EVAL_RENAME: + if(ctx->current == LOCAL_REPLICA ) { + /* use the old name to find the "other" node */ + tmp = csync_statedb_get_stat_by_inode(ctx->statedb.db, cur->inode); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Finding opposite temp through inode %" PRIu64 ": %s", + cur->inode, tmp ? "true":"false"); + } else if( ctx->current == REMOTE_REPLICA ) { + tmp = csync_statedb_get_stat_by_file_id(ctx->statedb.db, cur->file_id); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Finding opposite temp through file ID %s: %s", + cur->file_id, tmp ? "true":"false"); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Unknown replica..."); + } + + if( tmp ) { + if( tmp->path ) { + /* Find the temporar file in the other tree. */ + len = strlen( tmp->path ); + h = c_jhash64((uint8_t *) tmp->path, len, 0); + node = c_rbtree_find(tree, &h); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "PHash of temporary opposite (%s): %" PRIu64 " %s", + tmp->path , h, node ? "found": "not found" ); + if (!node) { + /* the renamed file could not be found in the opposite tree. That is because it + * is not longer existing there, maybe because it was renamed or deleted. + * The journal is cleaned up later after propagation. + */ + + } + } + + if(node) { + other = (csync_file_stat_t*)node->data; + } + + if(!other) { + cur->instruction = CSYNC_INSTRUCTION_NEW; + } else if (other->instruction == CSYNC_INSTRUCTION_NONE + || cur->type == CSYNC_FTW_TYPE_DIR) { + other->instruction = CSYNC_INSTRUCTION_RENAME; + other->destpath = c_strdup( cur->path ); + if( !c_streq(cur->file_id, "") ) { + csync_vio_set_file_id( other->file_id, cur->file_id ); + } + cur->instruction = CSYNC_INSTRUCTION_NONE; + } else if (other->instruction == CSYNC_INSTRUCTION_REMOVE) { + other->instruction = CSYNC_INSTRUCTION_RENAME; + other->destpath = c_strdup( cur->path ); + + if( !c_streq(cur->file_id, "") ) { + csync_vio_set_file_id( other->file_id, cur->file_id ); + } + + cur->instruction = CSYNC_INSTRUCTION_NONE; + } else if (other->instruction == CSYNC_INSTRUCTION_NEW) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "OOOO=> NEW detected in other tree!"); + cur->instruction = CSYNC_INSTRUCTION_CONFLICT; + } else { + cur->instruction = CSYNC_INSTRUCTION_NONE; + other->instruction = CSYNC_INSTRUCTION_SYNC; + } + + SAFE_FREE(tmp->etag); + SAFE_FREE(tmp); + } + + break; + default: + break; + } + } else { + bool is_equal_files = false; + /* + * file found on the other replica + */ + other = (csync_file_stat_t *) node->data; + + switch (cur->instruction) { + case CSYNC_INSTRUCTION_EVAL_RENAME: + /* If the file already exist on the other side, we have a conflict. + Abort the rename and consider it is a new file. */ + cur->instruction = CSYNC_INSTRUCTION_NEW; + /* fall trough */ + /* file on current replica is changed or new */ + case CSYNC_INSTRUCTION_EVAL: + case CSYNC_INSTRUCTION_NEW: + switch (other->instruction) { + /* file on other replica is changed or new */ + case CSYNC_INSTRUCTION_NEW: + case CSYNC_INSTRUCTION_EVAL: + if (other->type == CSYNC_VIO_FILE_TYPE_DIRECTORY && + cur->type == CSYNC_VIO_FILE_TYPE_DIRECTORY) { + is_equal_files = (other->modtime == cur->modtime); + } else { + is_equal_files = ((other->size == cur->size) && (other->modtime == cur->modtime)); + } + if (is_equal_files) { + /* The files are considered equal. */ + cur->instruction = CSYNC_INSTRUCTION_UPDATED; /* update the DB */ + other->instruction = CSYNC_INSTRUCTION_NONE; + + if( !cur->etag && other->etag ) cur->etag = c_strdup(other->etag); + } else if(ctx->current == REMOTE_REPLICA) { + if(ctx->options.with_conflict_copys) { + cur->instruction = CSYNC_INSTRUCTION_CONFLICT; + other->instruction = CSYNC_INSTRUCTION_NONE; + } else { + cur->instruction = CSYNC_INSTRUCTION_SYNC; + other->instruction = CSYNC_INSTRUCTION_NONE; + } + } else { + if(ctx->options.with_conflict_copys) { + cur->instruction = CSYNC_INSTRUCTION_NONE; + other->instruction = CSYNC_INSTRUCTION_CONFLICT; + } else { + cur->instruction = CSYNC_INSTRUCTION_NONE; + other->instruction = CSYNC_INSTRUCTION_SYNC; + } + } + + break; + /* file on the other replica has not been modified */ + case CSYNC_INSTRUCTION_NONE: + cur->instruction = CSYNC_INSTRUCTION_SYNC; + break; + case CSYNC_INSTRUCTION_IGNORE: + cur->instruction = CSYNC_INSTRUCTION_IGNORE; + break; + default: + break; + } + default: + break; + } + } + + //hide instruction NONE messages when log level is set to debug, + //only show these messages on log level trace + if(cur->instruction ==CSYNC_INSTRUCTION_NONE) + { + if(cur->type == CSYNC_FTW_TYPE_DIR) + { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, + "%-20s dir: %s", + csync_instruction_str(cur->instruction), + cur->path); + } + else + { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, + "%-20s file: %s", + csync_instruction_str(cur->instruction), + cur->path); + } + } + else + { + if(cur->type == CSYNC_FTW_TYPE_DIR) + { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "%-20s dir: %s", + csync_instruction_str(cur->instruction), + cur->path); + } + else + { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "%-20s file: %s", + csync_instruction_str(cur->instruction), + cur->path); + } + } + + return 0; +} + +int csync_reconcile_updates(CSYNC *ctx) { + int rc; + c_rbtree_t *tree = NULL; + + switch (ctx->current) { + case LOCAL_REPLICA: + tree = ctx->local.tree; + break; + case REMOTE_REPLICA: + tree = ctx->remote.tree; + break; + default: + break; + } + + rc = c_rbtree_walk(tree, (void *) ctx, _csync_merge_algorithm_visitor); + if( rc < 0 ) { + ctx->status_code = CSYNC_STATUS_RECONCILE_ERROR; + } + return rc; +} + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_reconcile.h b/csync/src/csync_reconcile.h new file mode 100644 index 000000000..207727f45 --- /dev/null +++ b/csync/src/csync_reconcile.h @@ -0,0 +1,60 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_RECONCILE_H +#define _CSYNC_RECONCILE_H + +/** + * @file csync_reconcile.h + * + * @brief Reconciliation + * + * The most important component is the update detector, because the reconciler + * depends on it. The correctness of reconciler is mandatory because it can + * damage a filesystem. It decides which file: + * + * - stays untouched + * - has a conflict + * - gets synchronized + * - or is deleted. + * + * @defgroup csyncReconcilationInternals csync reconciliation internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +/** + * @brief Reconcile the files. + * + * @param ctx The csync context to use. + * + * @return 0 on success, < 0 on error. + * + * @todo Add an argument to set the algorithm to use. + */ +int csync_reconcile_updates(CSYNC *ctx); + +/** + * }@ + */ +#endif /* _CSYNC_RECONCILE_H */ + +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_rename.cc b/csync/src/csync_rename.cc new file mode 100644 index 000000000..71ade82b1 --- /dev/null +++ b/csync/src/csync_rename.cc @@ -0,0 +1,94 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2012 by Olivier Goffart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +extern "C" { +#include "csync_private.h" +} + +#include +#include +#include +#include + +static std::string _parentDir(const std::string &path) { + int len = path.length(); + while(len > 0 && path[len-1]!='/') len--; + while(len > 0 && path[len-1]=='/') len--; + return path.substr(0, len); +} + +struct csync_rename_s { + static csync_rename_s *get(CSYNC *ctx) { + if (!ctx->rename_info) { + ctx->rename_info = new csync_rename_s; + } + return reinterpret_cast(ctx->rename_info); + } + + std::map folder_renamed_to; // map from->to + + struct renameop { + csync_file_stat_t *st; + bool operator<(const renameop &other) const { + return strlen(st->destpath) < strlen(other.st->destpath); + } + }; + std::vector todo; +}; + +static int _csync_rename_record(void *obj, void *data) { + CSYNC *ctx = reinterpret_cast(data); + csync_rename_s* d = csync_rename_s::get(ctx); + csync_file_stat_t *st = reinterpret_cast(obj); + + if ( st->instruction != CSYNC_INSTRUCTION_RENAME) + return 0; + + csync_rename_s::renameop op = { st }; + d->todo.push_back(op); + return 0; +} + +extern "C" { +void csync_rename_destroy(CSYNC* ctx) +{ + delete reinterpret_cast(ctx->rename_info); + ctx->rename_info = 0; +} + +void csync_rename_record(CSYNC* ctx, const char* from, const char* to) +{ + csync_rename_s::get(ctx)->folder_renamed_to[from] = to; +} + +char* csync_rename_adjust_path(CSYNC* ctx, const char* path) +{ + csync_rename_s* d = csync_rename_s::get(ctx); + for (std::string p = _parentDir(path); !p.empty(); p = _parentDir(p)) { + std::map< std::string, std::string >::iterator it = d->folder_renamed_to.find(p); + if (it != d->folder_renamed_to.end()) { + std::string rep = it->second + (path + p.length()); + return c_strdup(rep.c_str()); + } + } + return c_strdup(path); +} + +}; diff --git a/csync/src/csync_rename.h b/csync/src/csync_rename.h new file mode 100644 index 000000000..75247696c --- /dev/null +++ b/csync/src/csync_rename.h @@ -0,0 +1,35 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2012 by Olivier Goffart + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "csync.h" + +#ifdef __cplusplus +extern "C" { +#endif + +char *csync_rename_adjust_path(CSYNC *ctx, const char *path); +void csync_rename_destroy(CSYNC *ctx); +void csync_rename_record(CSYNC *ctx, const char *from, const char *to); + +#ifdef __cplusplus +} +#endif diff --git a/csync/src/csync_statedb.c b/csync/src/csync_statedb.c new file mode 100644 index 000000000..d095353a2 --- /dev/null +++ b/csync/src/csync_statedb.c @@ -0,0 +1,703 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "c_lib.h" +#include "csync_private.h" +#include "csync_statedb.h" +#include "csync_util.h" +#include "csync_misc.h" + +#include "c_string.h" +#include "c_jhash.h" +#include "csync_time.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.statedb" +#include "csync_log.h" +#include "csync_rename.h" + +#define BUF_SIZE 16 +#define HASH_QUERY "SELECT * FROM metadata WHERE phash=?1" + +static sqlite3_stmt* _by_hash_stmt = NULL; + +void csync_set_statedb_exists(CSYNC *ctx, int val) { + ctx->statedb.exists = val; +} + +int csync_get_statedb_exists(CSYNC *ctx) { + return ctx->statedb.exists; +} + +/* Set the hide attribute in win32. That makes it invisible in normal explorers */ +static void _csync_win32_hide_file( const char *file ) { +#ifdef _WIN32 + mbchar_t *fileName; + DWORD dwAttrs; + + if( !file ) return; + + fileName = c_utf8_to_locale( file ); + dwAttrs = GetFileAttributesW(fileName); + + if (dwAttrs==INVALID_FILE_ATTRIBUTES) return; + + if (!(dwAttrs & FILE_ATTRIBUTE_HIDDEN)) { + SetFileAttributesW(fileName, dwAttrs | FILE_ATTRIBUTE_HIDDEN ); + } + + c_free_locale_string(fileName); +#else + (void) file; +#endif +} + +static int _csync_check_db_integrity(sqlite3 *db) { + c_strlist_t *result = NULL; + int rc = -1; + + result = csync_statedb_query(db, "PRAGMA quick_check;"); + if (result != NULL) { + /* There is a result */ + if (result->count > 0) { + if (c_streq(result->vector[0], "ok")) { + rc = 0; + } + } + c_strlist_destroy(result); + } + + return rc; + +} + +static int _csync_statedb_check(const char *statedb) { + int fd = -1, rc; + ssize_t r; + char buf[BUF_SIZE] = {0}; + sqlite3 *db = NULL; + csync_stat_t sb; + + mbchar_t *wstatedb = c_utf8_to_locale(statedb); + + if (wstatedb == NULL) { + return -1; + } + + /* check db version */ +#ifdef _WIN32 + _fmode = _O_BINARY; +#endif + + fd = _topen(wstatedb, O_RDONLY); + + if (fd >= 0) { + /* Check size. Size of zero is a valid database actually. */ + rc = _tfstat(fd, &sb); + + if (rc == 0) { + if (sb.st_size == 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Database size is zero byte!"); + close(fd); + } else { + r = read(fd, (void *) buf, sizeof(buf) - 1); + close(fd); + if (r >= 0) { + buf[BUF_SIZE - 1] = '\0'; + if (c_streq(buf, "SQLite format 3")) { + if (sqlite3_open(statedb, &db ) == SQLITE_OK) { + rc = _csync_check_db_integrity(db); + if( sqlite3_close(db) != 0 ) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "WARN: sqlite3_close error!"); + } + + if( rc >= 0 ) { + /* everything is fine */ + c_free_locale_string(wstatedb); + return 0; + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Integrity check failed!"); + } else { + /* resources need to be freed even when open failed */ + sqlite3_close(db); + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "database corrupted, removing!"); + } + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite version mismatch"); + } + } + } + } + /* if it comes here, the database is broken and should be recreated. */ + _tunlink(wstatedb); + } + + c_free_locale_string(wstatedb); + + /* create database */ + rc = sqlite3_open(statedb, &db); + if (rc == SQLITE_OK) { + sqlite3_close(db); + _csync_win32_hide_file(statedb); + return 1; + } + sqlite3_close(db); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_open failed: %s %s", sqlite3_errmsg(db), statedb); + return -1; +} + +static int _csync_statedb_is_empty(sqlite3 *db) { + c_strlist_t *result = NULL; + int rc = 0; + + result = csync_statedb_query(db, "SELECT COUNT(phash) FROM metadata LIMIT 1 OFFSET 0;"); + if (result == NULL) { + rc = 1; + } + c_strlist_destroy(result); + + return rc; +} + +int csync_statedb_load(CSYNC *ctx, const char *statedb, sqlite3 **pdb) { + int rc = -1; + int check_rc = -1; + c_strlist_t *result = NULL; + char *statedb_tmp = NULL; + sqlite3 *db = NULL; + + /* csync_statedb_check tries to open the statedb and creates it in case + * its not there. + */ + check_rc = _csync_statedb_check(statedb); + if (check_rc < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: checking csync database failed - bail out."); + + rc = -1; + goto out; + } + + /* + * We want a two phase commit for the jounal, so we create a temporary copy + * of the database. + * The intention is that if something goes wrong we will not loose the + * statedb. + */ + rc = asprintf(&statedb_tmp, "%s.ctmp", statedb); + if (rc < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: could not create statedb name - bail out."); + rc = -1; + goto out; + } + + if (c_copy(statedb, statedb_tmp, 0644) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to copy statedb -> statedb_tmp - bail out."); + + rc = -1; + goto out; + } + + _csync_win32_hide_file( statedb_tmp ); + + /* Open or create the temporary database */ + if (sqlite3_open(statedb_tmp, &db) != SQLITE_OK) { + const char *errmsg= sqlite3_errmsg(ctx->statedb.db); + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "ERR: Failed to sqlite3 open statedb - bail out: %s.", + errmsg ? errmsg : ""); + + rc = -1; + goto out; + } + SAFE_FREE(statedb_tmp); + + /* If check_rc == 1 the database is new and empty as a result. */ + if ((check_rc == 1) || _csync_statedb_is_empty(db)) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "statedb doesn't exist"); + csync_set_statedb_exists(ctx, 0); + } else { + csync_set_statedb_exists(ctx, 1); + } + + /* optimization for speeding up SQLite */ + result = csync_statedb_query(db, "PRAGMA synchronous = FULL;"); + c_strlist_destroy(result); + result = csync_statedb_query(db, "PRAGMA case_sensitive_like = ON;"); + c_strlist_destroy(result); + + *pdb = db; + + return 0; +out: + sqlite3_close(db); + SAFE_FREE(statedb_tmp); + return rc; +} + +int csync_statedb_close(const char *statedb, sqlite3 *db, int jwritten) { + char *statedb_tmp = NULL; + mbchar_t* wstatedb_tmp = NULL; + int rc = 0; + + mbchar_t *mb_statedb = NULL; + + /* deallocate query resources */ + rc = sqlite3_finalize(_by_hash_stmt); + _by_hash_stmt = NULL; + + /* close the temporary database */ + sqlite3_close(db); + + if (asprintf(&statedb_tmp, "%s.ctmp", statedb) < 0) { + return -1; + } + + /* If we successfully synchronized, overwrite the original statedb */ + + /* + * Check the integrity of the tmp db. If ok, overwrite the old database with + * the tmp db. + */ + if (jwritten) { + /* statedb check returns either + * 0 : database exists and is fine + * 1 : new database was set up + * -1 : error. + */ + if (_csync_statedb_check(statedb_tmp) >= 0) { + /* New statedb is valid. */ + mb_statedb = c_utf8_to_locale(statedb); + + /* Move the tmp-db to the real one. */ + if (c_rename(statedb_tmp, statedb) < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Renaming tmp db to original db failed. (errno=%d)", errno); + rc = -1; + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, + "Successfully moved tmp db to original db."); + } + } else { + mb_statedb = c_utf8_to_locale(statedb_tmp); + _tunlink(mb_statedb); + + /* new statedb_tmp is not integer. */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, " ## csync tmp statedb corrupt. Original one is not replaced. "); + rc = -1; + } + c_free_locale_string(mb_statedb); + } + + wstatedb_tmp = c_utf8_to_locale(statedb_tmp); + if (wstatedb_tmp) { + _tunlink(wstatedb_tmp); + c_free_locale_string(wstatedb_tmp); + } + + SAFE_FREE(statedb_tmp); + + return rc; +} + +/* caller must free the memory */ +csync_file_stat_t *csync_statedb_get_stat_by_hash(sqlite3 *db, + uint64_t phash) +{ + csync_file_stat_t *st = NULL; + size_t len = 0; + int column_count = 0; + int rc; + + if( _by_hash_stmt == NULL ) { + rc = sqlite3_prepare_v2(db, HASH_QUERY, strlen(HASH_QUERY), &_by_hash_stmt, NULL); + if( rc != SQLITE_OK ) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "WRN: Unable to create stmt for hash query."); + return NULL; + } + } + + if( _by_hash_stmt == NULL ) { + return NULL; + } + + column_count = sqlite3_column_count(_by_hash_stmt); + + sqlite3_bind_int64(_by_hash_stmt, 1, (long long signed int)phash); + rc = sqlite3_step(_by_hash_stmt); + + if( rc == SQLITE_ROW ) { + if(column_count > 7) { + /* phash, pathlen, path, inode, uid, gid, mode, modtime */ + len = sqlite3_column_int(_by_hash_stmt, 1); + st = c_malloc(sizeof(csync_file_stat_t) + len + 1); + if (st == NULL) { + return NULL; + } + /* clear the whole structure */ + ZERO_STRUCTP(st); + + /* + * FIXME: + * We use an INTEGER(8) which is signed to the phash in the sqlite3 db, + * but the phash is an uint64_t. So for some values we get a string like + * "1.66514565505016e+19". For such a string strtoull() returns 1. + * phash = 1 + * + * st->phash = strtoull(result->vector[0], NULL, 10); + */ + + /* The query suceeded so use the phash we pass to the function. */ + st->phash = phash; + + st->pathlen = sqlite3_column_int(_by_hash_stmt, 1); + memcpy(st->path, (len ? (char*) sqlite3_column_text(_by_hash_stmt, 2) : ""), len + 1); + st->inode = sqlite3_column_int64(_by_hash_stmt,3); + st->uid = sqlite3_column_int(_by_hash_stmt, 4); + st->gid = sqlite3_column_int(_by_hash_stmt, 5); + st->mode = sqlite3_column_int(_by_hash_stmt, 6); + st->modtime = strtoul((char*)sqlite3_column_text(_by_hash_stmt, 7), NULL, 10); + + if(st && column_count > 8 ) { + st->type = sqlite3_column_int(_by_hash_stmt, 8); + } + + if(column_count > 9 && sqlite3_column_text(_by_hash_stmt, 9)) { + st->etag = c_strdup( (char*) sqlite3_column_text(_by_hash_stmt, 9) ); + } + if(column_count > 10 && sqlite3_column_text(_by_hash_stmt,10)) { + csync_vio_set_file_id(st->file_id, (char*) sqlite3_column_text(_by_hash_stmt, 10)); + } + } + } else { + /* SQLITE_DONE says there is no further row. That's not an error. */ + if (rc != SQLITE_DONE) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "sqlite hash query fail: %s", sqlite3_errmsg(db)); + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "No result record found for phash = %llu", + (long long unsigned int) phash); + SAFE_FREE(st); + } + + sqlite3_reset(_by_hash_stmt); + + return st; +} + +csync_file_stat_t *csync_statedb_get_stat_by_file_id( sqlite3 *db, + const char *file_id ) { + csync_file_stat_t *st = NULL; + c_strlist_t *result = NULL; + char *stmt = NULL; + size_t len = 0; + + if (!file_id) { + return 0; + } + if (c_streq(file_id, "")) { + return 0; + } + stmt = sqlite3_mprintf("SELECT * FROM metadata WHERE fileid='%q'", + file_id); + + if (stmt == NULL) { + return NULL; + } + + result = csync_statedb_query(db, stmt); + sqlite3_free(stmt); + if (result == NULL) { + return NULL; + } + + if (result->count <= 6) { + c_strlist_destroy(result); + return NULL; + } + + /* phash, pathlen, path, inode, uid, gid, mode, modtime */ + len = strlen(result->vector[2]); + st = c_malloc(sizeof(csync_file_stat_t) + len + 1); + if (st == NULL) { + c_strlist_destroy(result); + return NULL; + } + /* clear the whole structure */ + ZERO_STRUCTP(st); + + st->phash = atoll(result->vector[0]); + st->pathlen = atoi(result->vector[1]); + memcpy(st->path, (len ? result->vector[2] : ""), len + 1); + st->inode = atoll(result->vector[3]); + st->uid = atoi(result->vector[4]); + st->gid = atoi(result->vector[5]); + st->mode = atoi(result->vector[6]); + st->modtime = strtoul(result->vector[7], NULL, 10); + st->type = atoi(result->vector[8]); + if( result->vector[9] ) + st->etag = c_strdup(result->vector[9]); + + csync_vio_set_file_id(st->file_id, file_id); + + c_strlist_destroy(result); + + return st; + } + + +/* caller must free the memory */ +csync_file_stat_t *csync_statedb_get_stat_by_inode(sqlite3 *db, + uint64_t inode) { + csync_file_stat_t *st = NULL; + c_strlist_t *result = NULL; + char *stmt = NULL; + size_t len = 0; + + if (!inode) { + return NULL; + } + + stmt = sqlite3_mprintf("SELECT * FROM metadata WHERE inode='%lld'", + (long long signed int) inode); + if (stmt == NULL) { + return NULL; + } + + result = csync_statedb_query(db, stmt); + sqlite3_free(stmt); + if (result == NULL) { + return NULL; + } + + if (result->count <= 6) { + c_strlist_destroy(result); + return NULL; + } + + /* phash, pathlen, path, inode, uid, gid, mode, modtime */ + len = strlen(result->vector[2]); + st = c_malloc(sizeof(csync_file_stat_t) + len + 1); + if (st == NULL) { + c_strlist_destroy(result); + return NULL; + } + /* clear the whole structure */ + ZERO_STRUCTP(st); + + st->phash = atoll(result->vector[0]); + st->pathlen = atoi(result->vector[1]); + memcpy(st->path, (len ? result->vector[2] : ""), len + 1); + st->inode = atoll(result->vector[3]); + st->uid = atoi(result->vector[4]); + st->gid = atoi(result->vector[5]); + st->mode = atoi(result->vector[6]); + st->modtime = strtoul(result->vector[7], NULL, 10); + st->type = atoi(result->vector[8]); + if( result->vector[9] ) + st->etag = c_strdup(result->vector[9]); + csync_vio_set_file_id( st->file_id, result->vector[10]); + + c_strlist_destroy(result); + + return st; +} + +/* Get the etag. (it is called unique id for legacy reason + * and it is the field md5 in the database for legacy reason */ +char *csync_statedb_get_uniqId( CSYNC *ctx, uint64_t jHash, csync_vio_file_stat_t *buf ) { + char *ret = NULL; + c_strlist_t *result = NULL; + char *stmt = NULL; + (void)buf; + + if( ! csync_get_statedb_exists(ctx)) return ret; + + stmt = sqlite3_mprintf("SELECT md5, fileid FROM metadata WHERE phash='%lld'", jHash); + + result = csync_statedb_query(ctx->statedb.db, stmt); + sqlite3_free(stmt); + if (result == NULL) { + return NULL; + } + + if (result->count == 2) { + ret = c_strdup( result->vector[0] ); + csync_vio_file_stat_set_file_id(buf, result->vector[1]); + } + + c_strlist_destroy(result); + + return ret; +} + +c_strlist_t *csync_statedb_get_below_path( CSYNC *ctx, const char *path ) { + c_strlist_t *list = NULL; + char *stmt = NULL; + + stmt = sqlite3_mprintf("SELECT phash, path, inode, uid, gid, mode, modtime, type, md5, fileid " + "FROM metadata WHERE path LIKE('%q/%%')", path); + if (stmt == NULL) { + return NULL; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "SQL: %s", stmt); + + list = csync_statedb_query( ctx->statedb.db, stmt ); + + sqlite3_free(stmt); + + return list; +} + +/* query the statedb, caller must free the memory */ +c_strlist_t *csync_statedb_query(sqlite3 *db, + const char *statement) { + int err = SQLITE_OK; + int rc = SQLITE_OK; + size_t i = 0; + size_t busy_count = 0; + size_t retry_count = 0; + size_t column_count = 0; + sqlite3_stmt *stmt; + const char *tail = NULL; + const char *field = NULL; + c_strlist_t *result = NULL; + int row = 0; + + do { + /* compile SQL program into a virtual machine, reattempteing if busy */ + do { + if (busy_count) { + /* sleep 100 msec */ + usleep(100000); + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "sqlite3_prepare: BUSY counter: %zu", busy_count); + } + err = sqlite3_prepare(db, statement, -1, &stmt, &tail); + } while (err == SQLITE_BUSY && busy_count ++ < 120); + + if (err != SQLITE_OK) { + if (err == SQLITE_BUSY) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Gave up waiting for lock to clear"); + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, + "sqlite3_compile error: %s - on query %s", + sqlite3_errmsg(db), statement); + break; + } else { + busy_count = 0; + column_count = sqlite3_column_count(stmt); + + /* execute virtual machine by iterating over rows */ + for(;;) { + err = sqlite3_step(stmt); + + if (err == SQLITE_BUSY) { + if (busy_count++ > 120) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Busy counter has reached its maximum. Aborting this sql statement"); + break; + } + /* sleep 100 msec */ + usleep(100000); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "sqlite3_step: BUSY counter: %zu", busy_count); + continue; + } + + if (err == SQLITE_MISUSE) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite3_step: MISUSE!!"); + } + + if (err == SQLITE_DONE) { + if (result == NULL) { + result = c_strlist_new(1); + } + break; + } + + if (err == SQLITE_ERROR) { + break; + } + + row++; + if( result ) { + result = c_strlist_expand(result, row*column_count); + } else { + result = c_strlist_new(column_count); + } + + if (result == NULL) { + return NULL; + } + + /* iterate over columns */ + for (i = 0; i < column_count; i++) { + field = (const char *) sqlite3_column_text(stmt, i); + if (!field) + field = ""; + // CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "sqlite3_column_text: %s", field); + if (c_strlist_add(result, field) < 0) { + c_strlist_destroy(result); + return NULL; + } + } + } /* end infinite for loop */ + + /* deallocate vm resources */ + rc = sqlite3_finalize(stmt); + + if (err != SQLITE_DONE && rc != SQLITE_SCHEMA) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "sqlite_step error: %s - on query: %s", sqlite3_errmsg(db), statement); + if (result != NULL) { + c_strlist_destroy(result); + } + return NULL; + } + + if (rc == SQLITE_SCHEMA) { + retry_count ++; + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "SQLITE_SCHEMA error occurred on query: %s", statement); + if (retry_count < 10) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Retrying now."); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "RETRY count has reached its maximum. Aborting statement: %s", statement); + if (result != NULL) { + c_strlist_destroy(result); + } + result = c_strlist_new(1); + } + } + } + } while (rc == SQLITE_SCHEMA && retry_count < 10); + + return result; +} + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_statedb.h b/csync/src/csync_statedb.h new file mode 100644 index 000000000..feef102fe --- /dev/null +++ b/csync/src/csync_statedb.h @@ -0,0 +1,123 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file csync_private.h + * + * @brief Private interface of csync + * + * @defgroup csyncstatedbInternals csync statedb internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +#ifndef _CSYNC_STATEDB_H +#define _CSYNC_STATEDB_H + +#include "c_lib.h" +#include "csync_private.h" + +void csync_set_statedb_exists(CSYNC *ctx, int val); + +int csync_get_statedb_exists(CSYNC *ctx); + +/** + * @brief Load the statedb. + * + * This function tries to load the statedb. If it doesn't exists it creates + * the sqlite3 database, but doesn't create the tables. This will be done when + * csync gets destroyed. + * + * @param ctx The csync context. + * @param statedb Path to the statedb file (sqlite3 db). + * + * @return 0 on success, less than 0 if an error occured with errno set. + */ +int csync_statedb_load(CSYNC *ctx, const char *statedb, sqlite3 **pdb); + +int csync_statedb_close(const char *statedb, sqlite3 *db, int jwritten); + +csync_file_stat_t *csync_statedb_get_stat_by_hash(sqlite3 *db, uint64_t phash); + +csync_file_stat_t *csync_statedb_get_stat_by_inode(sqlite3 *db, uint64_t inode); + +csync_file_stat_t *csync_statedb_get_stat_by_file_id( sqlite3 *db, + const char *file_id ); + +char *csync_statedb_get_uniqId(CSYNC *ctx, uint64_t jHash, csync_vio_file_stat_t *buf); + +/** + * @brief Query all files metadata inside and below a path. + * @param ctx The csync context. + * @param path The path. + * + * This function queries all metadata of all files inside or below the + * given path. The result is a linear string list with a multiple of 9 + * entries. For each result file there are 9 strings which are phash, + * path, inode, uid, gid, mode, modtime, type and md5 (unique id). + * + * Note that not only the files in the given path are part of the result + * but also the files in directories below the given path. Ie. if the + * parameter path is /home/kf/test, we have /home/kf/test/file.txt in + * the result but also /home/kf/test/homework/another_file.txt + * + * @return A stringlist containing a multiple of 9 entries. + */ +c_strlist_t *csync_statedb_get_below_path(CSYNC *ctx, const char *path); + +/** + * @brief A generic statedb query. + * + * @param ctx The csync context. + * @param statement The SQL statement to execute + * + * @return A stringlist of the entries of a column. An emtpy stringlist if + * nothing has been found. NULL on error. + */ +c_strlist_t *csync_statedb_query(sqlite3 *db, const char *statement); + +/** + * @brief Insert function for the statedb. + * + * @param ctx The csync context. + * @param statement The SQL statement to insert into the statedb. + * + * @return The rowid of the most recent INSERT on success, 0 if the query + * wasn't successful. + */ +typedef struct csync_progressinfo_s { + struct csync_progressinfo_s *next; + uint64_t phash; + uint64_t modtime; + char *md5; + int error; + int chunk; + int transferId; + char *tmpfile; + char *error_string; +} csync_progressinfo_t; + +/** + * }@ + */ +#endif /* _CSYNC_STATEDB_H */ +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_time.c b/csync/src/csync_time.c new file mode 100644 index 000000000..038a26435 --- /dev/null +++ b/csync/src/csync_time.c @@ -0,0 +1,176 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#include "csync_time.h" +#include "vio/csync_vio.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.time" +#include "csync_log.h" + +#ifdef HAVE_CLOCK_GETTIME +# ifdef _POSIX_MONOTONIC_CLOCK +# define CSYNC_CLOCK CLOCK_MONOTONIC +# else +# define CSYNC_CLOCK CLOCK_REALTIME +# endif +#endif + + +int csync_gettime(struct timespec *tp) +{ +#ifdef HAVE_CLOCK_GETTIME + return clock_gettime(CSYNC_CLOCK, tp); +#else + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + return -1; + } + + tp->tv_sec = tv.tv_sec; + tp->tv_nsec = tv.tv_usec * 1000; +#endif + return 0; +} + +#undef CSYNC_CLOCK + +/* check time difference between the replicas */ +time_t csync_timediff(CSYNC *ctx) { + time_t timediff = -1; + char errbuf[256] = {0}; + char *luri = NULL; + char *ruri = NULL; + csync_vio_handle_t *fp = NULL; + csync_vio_file_stat_t *st = NULL; + csync_vio_handle_t *dp = NULL; + + /* try to open remote dir to get auth */ + ctx->replica = ctx->remote.type; + dp = csync_vio_opendir(ctx, ctx->remote.uri); + if (dp == NULL) { + /* + * To prevent problems especially with pam_csync we shouldn't try to create the + * remote directory here. Just fail! + */ + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Access dienied to remote uri: %s - %s", + ctx->remote.uri, + errbuf); + ctx->status_code = CSYNC_STATUS_REMOTE_ACCESS_ERROR; + return -1; + } + csync_vio_closedir(ctx, dp); + + if (asprintf(&luri, "%s/.csync_timediff.ctmp", ctx->local.uri) < 0) { + goto out; + } + + if (asprintf(&ruri, "%s/.csync_timediff.ctmp", ctx->remote.uri) < 0) { + goto out; + } + + /* create temporary file on local */ + ctx->replica = ctx->local.type; + fp = csync_vio_creat(ctx, luri, 0644); + if (fp == NULL) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Unable to create temporary file: %s - %s", + luri, + errbuf); + ctx->status_code = CSYNC_STATUS_LOCAL_CREATE_ERROR; + goto out; + } + csync_vio_close(ctx, fp); + + /* Get the modification time */ + st = csync_vio_file_stat_new(); + if (csync_vio_stat(ctx, luri, st) < 0) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Synchronisation is not possible! %s - %s", + luri, + errbuf); + ctx->status_code = CSYNC_STATUS_LOCAL_STAT_ERROR; + goto out; + } + timediff = st->mtime; + csync_vio_file_stat_destroy(st); + st = NULL; + + /* create temporary file on remote replica */ + ctx->replica = ctx->remote.type; + + fp = csync_vio_creat(ctx, ruri, 0644); + if (fp == NULL) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Unable to create temporary file: %s - %s", + ruri, + errbuf); + ctx->status_code = CSYNC_STATUS_REMOTE_CREATE_ERROR; + goto out; + } + csync_vio_close(ctx, fp); + + /* Get the modification time */ + st = csync_vio_file_stat_new(); + if (csync_vio_stat(ctx, ruri, st) < 0) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, + "Synchronisation is not possible! %s - %s", + ruri, + errbuf); + ctx->status_code = CSYNC_STATUS_REMOTE_STAT_ERROR; + goto out; + } + + /* calc time difference */ + timediff = llabs(timediff - st->mtime); + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Time difference: %ld seconds", timediff); + +out: + csync_vio_file_stat_destroy(st); + + ctx->replica = ctx->local.type; + csync_vio_unlink(ctx, luri); + SAFE_FREE(luri); + + ctx->replica = ctx->remote.type; + csync_vio_unlink(ctx, ruri); + SAFE_FREE(ruri); + + return timediff; +} + + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_time.h b/csync/src/csync_time.h new file mode 100644 index 000000000..b0b423a77 --- /dev/null +++ b/csync/src/csync_time.h @@ -0,0 +1,31 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_TIME_H +#define _CSYNC_TIME_H + +#include + +#include "csync_private.h" + +int csync_gettime(struct timespec *tp); +time_t csync_timediff(CSYNC *ctx); + +#endif /* _CSYNC_TIME_H */ diff --git a/csync/src/csync_update.c b/csync/src/csync_update.c new file mode 100644 index 000000000..987e145d7 --- /dev/null +++ b/csync/src/csync_update.c @@ -0,0 +1,653 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + +#include "c_lib.h" +#include "c_jhash.h" + +#include "csync_private.h" +#include "csync_exclude.h" +#include "csync_statedb.h" +#include "csync_update.h" +#include "csync_util.h" +#include "csync_misc.h" + +#include "vio/csync_vio.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.updater" +#include "csync_log.h" +#include "csync_rename.h" + +/* calculate the hash of a given uri */ +static uint64_t _hash_of_file(CSYNC *ctx, const char *file) { + const char *path; + int len; + uint64_t h = 0; + + if( ctx && file ) { + path = file; + switch (ctx->current) { + case LOCAL_REPLICA: + if (strlen(path) <= strlen(ctx->local.uri)) { + return 0; + } + path += strlen(ctx->local.uri) + 1; + break; + case REMOTE_REPLICA: + if (strlen(path) <= strlen(ctx->remote.uri)) { + return 0; + } + path += strlen(ctx->remote.uri) + 1; + break; + default: + path = NULL; + return 0; + break; + } + len = strlen(path); + + h = c_jhash64((uint8_t *) path, len, 0); + } + return h; +} + + +static int _csync_detect_update(CSYNC *ctx, const char *file, + const csync_vio_file_stat_t *fs, const int type) { + uint64_t h = 0; + size_t len = 0; + size_t size = 0; + const char *path = NULL; + csync_file_stat_t *st = NULL; + csync_file_stat_t *tmp = NULL; + CSYNC_EXCLUDE_TYPE excluded; + + if ((file == NULL) || (fs == NULL)) { + errno = EINVAL; + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + + path = file; + switch (ctx->current) { + case LOCAL_REPLICA: + if (strlen(path) <= strlen(ctx->local.uri)) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + path += strlen(ctx->local.uri) + 1; + break; + case REMOTE_REPLICA: + if (strlen(path) <= strlen(ctx->remote.uri)) { + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + path += strlen(ctx->remote.uri) + 1; + break; + default: + path = NULL; + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + return -1; + } + + len = strlen(path); + + /* Check if file is excluded */ + excluded = csync_excluded(ctx, path,type); + + if (excluded != CSYNC_NOT_EXCLUDED) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "%s excluded (%d)", path, excluded); + if (excluded == CSYNC_FILE_EXCLUDE_AND_REMOVE) { + switch (ctx->current) { + case LOCAL_REPLICA: + ctx->local.ignored_cleanup = c_list_append(ctx->local.ignored_cleanup, c_strdup(path)); + break; + case REMOTE_REPLICA: + ctx->remote.ignored_cleanup = c_list_append(ctx->remote.ignored_cleanup, c_strdup(path)); + break; + default: + break; + } + return 0; + } + if (excluded == CSYNC_FILE_SILENTLY_EXCLUDED) { + return 0; + } + } + + h = _hash_of_file(ctx, file ); + if( h == 0 ) { + return -1; + } + size = sizeof(csync_file_stat_t) + len + 1; + + st = c_malloc(size); + if (st == NULL) { + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + return -1; + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "==> file: %s - hash %llu, mtime: %llu, fileId: %s", + path, (unsigned long long ) h, (unsigned long long) fs->mtime, fs->file_id); + + /* Set instruction by default to none */ + st->instruction = CSYNC_INSTRUCTION_NONE; + st->etag = NULL; + st->child_modified = 0; + + /* check hardlink count */ + if (type == CSYNC_FTW_TYPE_FILE ) { + if( fs->nlink > 1) { + st->instruction = CSYNC_INSTRUCTION_IGNORE; + goto out; + } + + if (fs->mtime == 0) { + tmp = csync_statedb_get_stat_by_hash(ctx->statedb.db, h); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - mtime is zero!", path); + if (tmp == NULL) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - not found in db, IGNORE!", path); + st->instruction = CSYNC_INSTRUCTION_IGNORE; + } else { + SAFE_FREE(st); + st = tmp; + st->instruction = CSYNC_INSTRUCTION_NONE; + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s - tmp non zero, mtime %lu", path, st->modtime ); + tmp = NULL; + } + goto fastout; /* Skip copying of the etag. That's an important difference to upstream + * without etags. */ + } + } + + /* Ignore non statable files and other strange cases. */ + if (type == CSYNC_FTW_TYPE_SKIP) { + st->instruction = CSYNC_INSTRUCTION_NONE; + goto out; + } + if (excluded > CSYNC_NOT_EXCLUDED || type == CSYNC_FTW_TYPE_SLINK) { + if( type == CSYNC_FTW_TYPE_SLINK ) { + st->error_status = CSYNC_STATUS_INDIVIDUAL_IS_SYMLINK; /* Symbolic links are ignored. */ + } + st->instruction = CSYNC_INSTRUCTION_IGNORE; + goto out; + } + + /* Update detection: Check if a database entry exists. + * If not, the file is either new or has been renamed. To see if it is + * renamed, the db gets queried by the inode of the file as that one + * does not change on rename. + */ + if (csync_get_statedb_exists(ctx)) { + tmp = csync_statedb_get_stat_by_hash(ctx->statedb.db, h); + + if(tmp && tmp->phash == h ) { /* there is an entry in the database */ + /* we have an update! */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Database entry found, compare: %" PRId64 " <-> %" PRId64 ", etag: %s <-> %s, inode: %" PRId64 " <-> %" PRId64, + ((int64_t) fs->mtime), ((int64_t) tmp->modtime), fs->etag, tmp->etag, (uint64_t) fs->inode, (uint64_t) tmp->inode); + if( !fs->etag) { + st->instruction = CSYNC_INSTRUCTION_EVAL; + goto out; + } + if((ctx->current == REMOTE_REPLICA && !c_streq(fs->etag, tmp->etag )) + || (ctx->current == LOCAL_REPLICA && (fs->mtime != tmp->modtime +#if 0 + || fs->inode != tmp->inode +#endif + ))) { + /* Comparison of the local inode is disabled because people reported problems + * on windows with flacky inode values, see github bug #779 + * + * The inode needs to be observed because: + * $> echo a > a.txt ; echo b > b.txt + * both files have the same mtime + * sync them. + * $> rm a.txt && mv b.txt a.txt + * makes b.txt appearing as a.txt yet a sync is not performed because + * both have the same modtime as mv does not change that. + */ + st->instruction = CSYNC_INSTRUCTION_EVAL; + goto out; + } + if (type == CSYNC_FTW_TYPE_DIR && ctx->current == REMOTE_REPLICA + && c_streq(fs->file_id, tmp->file_id)) { + /* If both etag and file id are equal for a directory, read all contents from + * the database. */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Reading from database: %s", path); + ctx->remote.read_from_db = true; + } + st->instruction = CSYNC_INSTRUCTION_NONE; + } else { + enum csync_vio_file_type_e tmp_vio_type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + + /* check if it's a file and has been renamed */ + if (ctx->current == LOCAL_REPLICA) { + tmp = csync_statedb_get_stat_by_inode(ctx->statedb.db, fs->inode); + + /* translate the file type between the two stat types csync has. */ + if( tmp && tmp->type == 0 ) { + tmp_vio_type = CSYNC_VIO_FILE_TYPE_REGULAR; + } else if( tmp && tmp->type == 2 ) { + tmp_vio_type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + } else { + tmp_vio_type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + } + + if (tmp && tmp->inode == fs->inode && tmp_vio_type == fs->type + && (tmp->modtime == fs->mtime || fs->type == CSYNC_VIO_FILE_TYPE_DIRECTORY)) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "inodes: %" PRId64 " <-> %" PRId64, (uint64_t) tmp->inode, (uint64_t) fs->inode); + /* inode found so the file has been renamed */ + st->instruction = CSYNC_INSTRUCTION_EVAL_RENAME; + if (fs->type == CSYNC_VIO_FILE_TYPE_DIRECTORY) { + csync_rename_record(ctx, tmp->path, path); + } + goto out; + } else { + /* file not found in statedb */ + st->instruction = CSYNC_INSTRUCTION_NEW; + goto out; + } + } else { + /* Remote Replica Rename check */ + tmp = csync_statedb_get_stat_by_file_id(ctx->statedb.db, fs->file_id); + if(tmp ) { /* tmp existing at all */ + if ((tmp->type == CSYNC_FTW_TYPE_DIR && fs->type != CSYNC_VIO_FILE_TYPE_DIRECTORY) || + (tmp->type == CSYNC_FTW_TYPE_FILE && fs->type != CSYNC_VIO_FILE_TYPE_REGULAR)) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "WARN: file types different is not!"); + st->instruction = CSYNC_INSTRUCTION_NEW; + goto out; + } + st->instruction = CSYNC_INSTRUCTION_EVAL_RENAME; + if (fs->type == CSYNC_VIO_FILE_TYPE_DIRECTORY) { + csync_rename_record(ctx, tmp->path, path); + } else { + if( !c_streq(tmp->etag, fs->etag) ) { + /* CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "ETags are different!"); */ + /* File with different etag, don't do a rename, but download the file again */ + st->instruction = CSYNC_INSTRUCTION_NEW; + } + } + goto out; + + } else { + /* file not found in statedb */ + st->instruction = CSYNC_INSTRUCTION_NEW; + goto out; + } + } + } + } else { + st->instruction = CSYNC_INSTRUCTION_NEW; + } + +out: + + /* Set the ignored error string. */ + if (st->instruction == CSYNC_INSTRUCTION_IGNORE) { + if (excluded == CSYNC_FILE_EXCLUDE_LIST) { + st->error_status = CSYNC_STATUS_INDIVIDUAL_IGNORE_LIST; /* File listed on ignore list. */ + } else if (excluded == CSYNC_FILE_EXCLUDE_INVALID_CHAR) { + st->error_status = CSYNC_STATUS_INDIVIDUAL_IS_INVALID_CHARS; /* File contains invalid characters. */ + } + } + if (st->instruction != CSYNC_INSTRUCTION_NONE && st->instruction != CSYNC_INSTRUCTION_IGNORE + && type != CSYNC_FTW_TYPE_DIR) { + st->child_modified = 1; + } + ctx->current_fs = st; + + csync_file_stat_free(tmp); + st->inode = fs->inode; + st->mode = fs->mode; + st->size = fs->size; + st->modtime = fs->mtime; + st->uid = fs->uid; + st->gid = fs->gid; + st->nlink = fs->nlink; + st->type = type; + st->etag = NULL; + if( fs->etag ) { + st->etag = c_strdup(fs->etag); + } + csync_vio_set_file_id(st->file_id, fs->file_id); + +fastout: /* target if the file information is read from database into st */ + st->phash = h; + st->pathlen = len; + memcpy(st->path, (len ? path : ""), len + 1); + + switch (ctx->current) { + case LOCAL_REPLICA: + if (c_rbtree_insert(ctx->local.tree, (void *) st) < 0) { + SAFE_FREE(st); + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + return -1; + } + break; + case REMOTE_REPLICA: + if (c_rbtree_insert(ctx->remote.tree, (void *) st) < 0) { + SAFE_FREE(st); + ctx->status_code = CSYNC_STATUS_TREE_ERROR; + return -1; + } + break; + default: + break; + } + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "file: %s, instruction: %s <<=", st->path, + csync_instruction_str(st->instruction)); + + return 0; +} + +int csync_walker(CSYNC *ctx, const char *file, const csync_vio_file_stat_t *fs, + enum csync_ftw_flags_e flag) { + int rc = -1; + int type = CSYNC_FTW_TYPE_SKIP; + csync_file_stat_t *st = NULL; + uint64_t h; + + if (ctx->abort) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "Aborted!"); + ctx->status_code = CSYNC_STATUS_ABORTED; + return -1; + } + + switch (flag) { + case CSYNC_FTW_FLAG_FILE: + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "file: %s", file); + type = CSYNC_FTW_TYPE_FILE; + break; + case CSYNC_FTW_FLAG_DIR: /* enter directory */ + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "directory: %s", file); + type = CSYNC_FTW_TYPE_DIR; + break; + case CSYNC_FTW_FLAG_NSTAT: /* not statable file */ + /* if file was here before and now is not longer stat-able, still + * add it to the db, otherwise not. */ + h = _hash_of_file( ctx, file ); + if( h == 0 ) { + return 0; + } + st = csync_statedb_get_stat_by_hash(ctx->statedb.db, h); + if( !st ) { + return 0; + } + csync_file_stat_free(st); + st = NULL; + + type = CSYNC_FTW_TYPE_SKIP; + break; + case CSYNC_FTW_FLAG_SLINK: + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "symlink: %s - not supported", file); + type = CSYNC_FTW_TYPE_SLINK; + break; + case CSYNC_FTW_FLAG_DNR: + case CSYNC_FTW_FLAG_DP: + case CSYNC_FTW_FLAG_SLN: + default: + return 0; + break; + } + + rc = _csync_detect_update(ctx, file, fs, type ); + + return rc; +} + +/* File tree walker */ +int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn, + unsigned int depth) { + char errbuf[256] = {0}; + char *filename = NULL; + char *d_name = NULL; + csync_vio_handle_t *dh = NULL; + csync_vio_file_stat_t *dirent = NULL; + csync_vio_file_stat_t *fs = NULL; + csync_file_stat_t *previous_fs = NULL; + int read_from_db = 0; + int rc = 0; + int res = 0; + + bool do_read_from_db = (ctx->current == REMOTE_REPLICA && ctx->remote.read_from_db); + + if (uri[0] == '\0') { + errno = ENOENT; + ctx->status_code = CSYNC_STATUS_PARAM_ERROR; + goto error; + } + + read_from_db = ctx->remote.read_from_db; + + if ((dh = csync_vio_opendir(ctx, uri)) == NULL) { + int asp = 0; + /* permission denied */ + ctx->status_code = csync_errno_to_status(errno, CSYNC_STATUS_OPENDIR_ERROR); + if (errno == EACCES) { + return 0; + } else if(errno == EIO ) { + /* Proxy problems (ownCloud) */ + ctx->status_code = CSYNC_STATUS_PROXY_ERROR; + } else if(errno == ENOENT) { + asp = asprintf( &ctx->error_string, "%s", uri); + if (asp < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "asprintf failed!"); + } + } else { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, + "opendir failed for %s - %s (errno %d)", + uri, errbuf, errno); + } + goto error; + } + + while ((dirent = csync_vio_readdir(ctx, dh))) { + const char *path = NULL; + size_t ulen = 0; + int flen; + int flag; + + d_name = dirent->name; + if (d_name == NULL) { + ctx->status_code = CSYNC_STATUS_READDIR_ERROR; + goto error; + } + + /* skip "." and ".." */ + if (d_name[0] == '.' && (d_name[1] == '\0' + || (d_name[1] == '.' && d_name[2] == '\0'))) { + csync_vio_file_stat_destroy(dirent); + dirent = NULL; + continue; + } + + flen = asprintf(&filename, "%s/%s", uri, d_name); + if (flen < 0) { + csync_vio_file_stat_destroy(dirent); + dirent = NULL; + ctx->status_code = CSYNC_STATUS_MEMORY_ERROR; + goto error; + } + + /* Create relative path */ + switch (ctx->current) { + case LOCAL_REPLICA: + ulen = strlen(ctx->local.uri) + 1; + break; + case REMOTE_REPLICA: + ulen = strlen(ctx->remote.uri) + 1; + break; + default: + break; + } + + if (((size_t)flen) < ulen) { + csync_vio_file_stat_destroy(dirent); + dirent = NULL; + ctx->status_code = CSYNC_STATUS_UNSUCCESSFUL; + goto error; + } + + path = filename + ulen; + + /* skip ".csync_journal.db" and ".csync_journal.db.ctmp" */ + if (c_streq(path, ".csync_journal.db") + || c_streq(path, ".csync_journal.db.ctmp") + || c_streq(path, ".csync_journal.db.ctmp-journal") + || c_streq(path, ".csync-progressdatabase")) { + csync_vio_file_stat_destroy(dirent); + dirent = NULL; + SAFE_FREE(filename); + continue; + } + + /* == see if really stat has to be called. */ + if( do_read_from_db ) { + fs = dirent; + res = 0; + } else { + fs = csync_vio_file_stat_new(); + res = csync_vio_stat(ctx, filename, fs); + } + + if( res == 0) { + switch (fs->type) { + case CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK: + flag = CSYNC_FTW_FLAG_SLINK; + break; + case CSYNC_VIO_FILE_TYPE_DIRECTORY: + flag = CSYNC_FTW_FLAG_DIR; + break; + case CSYNC_VIO_FILE_TYPE_BLOCK_DEVICE: + case CSYNC_VIO_FILE_TYPE_CHARACTER_DEVICE: + case CSYNC_VIO_FILE_TYPE_SOCKET: + flag = CSYNC_FTW_FLAG_SPEC; + break; + case CSYNC_VIO_FILE_TYPE_FIFO: + flag = CSYNC_FTW_FLAG_SPEC; + break; + default: + flag = CSYNC_FTW_FLAG_FILE; + break; + }; + } else { + flag = CSYNC_FTW_FLAG_NSTAT; + } + + if( ctx->current == LOCAL_REPLICA ) { + char *etag = NULL; + int len = strlen( path ); + uint64_t h = c_jhash64((uint8_t *) path, len, 0); + etag = csync_statedb_get_uniqId( ctx, h, fs ); + if( etag ) { + SAFE_FREE(fs->etag); + fs->etag = etag; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ETAG; + } + if( c_streq(etag, "")) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Uniq ID from Database is EMPTY: %s", path); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Uniq ID from Database: %s -> %s", path, fs->etag ? fs->etag : "" ); + } + } + + previous_fs = ctx->current_fs; + + /* Call walker function for each file */ + rc = fn(ctx, filename, fs, flag); + /* this function may update ctx->current and ctx->read_from_db */ + + if (ctx->current_fs && previous_fs && ctx->current_fs->child_modified) { + previous_fs->child_modified = ctx->current_fs->child_modified; + } + + if( ! do_read_from_db ) { + csync_vio_file_stat_destroy(fs); + } else { + SAFE_FREE(fs->etag); + } + + if (rc < 0) { + if (CSYNC_STATUS_IS_OK(ctx->status_code)) { + ctx->status_code = CSYNC_STATUS_UPDATE_ERROR; + } + + csync_vio_closedir(ctx, dh); + ctx->current_fs = previous_fs; + goto done; + } + + if (flag == CSYNC_FTW_FLAG_DIR && depth + && (!ctx->current_fs || ctx->current_fs->instruction != CSYNC_INSTRUCTION_IGNORE)) { + rc = csync_ftw(ctx, filename, fn, depth - 1); + if (rc < 0) { + ctx->current_fs = previous_fs; + csync_vio_closedir(ctx, dh); + goto done; + } + + if (ctx->current_fs && !ctx->current_fs->child_modified + && ctx->current_fs->instruction == CSYNC_INSTRUCTION_EVAL) { + ctx->current_fs->instruction = CSYNC_INSTRUCTION_NONE; + ctx->current_fs->should_update_etag = true; + } + } + + if (ctx->current_fs && (ctx->current_fs->instruction == CSYNC_INSTRUCTION_EVAL || + ctx->current_fs->instruction == CSYNC_INSTRUCTION_NEW)) { + ctx->current_fs->should_update_etag = true; + } + + ctx->current_fs = previous_fs; + ctx->remote.read_from_db = read_from_db; + SAFE_FREE(filename); + csync_vio_file_stat_destroy(dirent); + dirent = NULL; + } + + csync_vio_closedir(ctx, dh); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, " <= Closing walk for %s with read_from_db %d", uri, read_from_db); + +done: + ctx->remote.read_from_db = read_from_db; + csync_vio_file_stat_destroy(dirent); + SAFE_FREE(filename); + return rc; +error: + if (dh != NULL) { + csync_vio_closedir(ctx, dh); + } + ctx->remote.read_from_db = read_from_db; + SAFE_FREE(filename); + return -1; +} + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_update.h b/csync/src/csync_update.h new file mode 100644 index 000000000..6f6dee441 --- /dev/null +++ b/csync/src/csync_update.h @@ -0,0 +1,100 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_UPDATE_H +#define _CSYNC_UPDATE_H + +#include "csync.h" +#include "vio/csync_vio_file_stat.h" + +/** + * @file csync_update.h + * + * @brief Update Detection + * + * TODO + * + * @defgroup csyncUpdateDetectionInternals csync update detection internals + * @ingroup csyncInternalAPI + * + * @{ + */ + +/** + * Types for files + */ +enum csync_ftw_flags_e { + CSYNC_FTW_FLAG_FILE, /* Regular file. */ + CSYNC_FTW_FLAG_DIR, /* Directory. */ + CSYNC_FTW_FLAG_DNR, /* Unreadable directory. */ + CSYNC_FTW_FLAG_NSTAT, /* Unstatable file. */ + CSYNC_FTW_FLAG_SLINK, /* Symbolic link. */ + CSYNC_FTW_FLAG_SPEC, /* Special file (fifo, ...). */ + /* These flags are only passed from the `nftw' function. */ + CSYNC_FTW_FLAG_DP, /* Directory, all subdirs have been visited. */ + CSYNC_FTW_FLAG_SLN /* Symbolic link naming non-existing file. */ +}; + +typedef int (*csync_walker_fn) (CSYNC *ctx, const char *file, + const csync_vio_file_stat_t *fs, enum csync_ftw_flags_e flag); + +/** + * @brief The walker function to use in the file tree walker. + * + * @param ctx The used csync context. + * + * @param file The file we are researching. + * + * @param fs The stat information we got. + * + * @param flag The flag describing the type of the file. + * + * @return 0 on success, < 0 on error. + */ +int csync_walker(CSYNC *ctx, const char *file, const csync_vio_file_stat_t *fs, + enum csync_ftw_flags_e flag); + +/** + * @brief The file tree walker. + * + * This function walks through the directory tree that is located under the uri + * specified. It calls a walker function which is provided as a function pointer + * once for each entry in the tree. By default, directories are handled before + * the files and subdirectories they contain (pre-order traversal). + * + * @param ctx The csync context to use. + * + * @param uri The uri/path to the directory tree to walk. + * + * @param fn The walker function to call once for each entry. + * + * @param depth The max depth to walk down the tree. + * + * @return 0 on success, < 0 on error. If fn() returns non-zero, then the tree + * walk is terminated and the value returned by fn() is returned as the + * result. + */ +int csync_ftw(CSYNC *ctx, const char *uri, csync_walker_fn fn, + unsigned int depth); + +#endif /* _CSYNC_UPDATE_H */ + +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/csync_util.c b/csync/src/csync_util.c new file mode 100644 index 000000000..df9b46db5 --- /dev/null +++ b/csync/src/csync_util.c @@ -0,0 +1,218 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include + +#include "c_jhash.h" +#include "csync_util.h" +#include "vio/csync_vio.h" + +#define CSYNC_LOG_CATEGORY_NAME "csync.util" +#include "csync_log.h" +#include "csync_statedb.h" + +typedef struct { + const char *instr_str; + enum csync_instructions_e instr_code; +} _instr_code_struct; + +static const _instr_code_struct _instr[] = +{ + { "INSTRUCTION_NONE", CSYNC_INSTRUCTION_NONE }, + { "INSTRUCTION_EVAL", CSYNC_INSTRUCTION_EVAL }, + { "INSTRUCTION_REMOVE", CSYNC_INSTRUCTION_REMOVE }, + { "INSTRUCTION_RENAME", CSYNC_INSTRUCTION_RENAME }, + { "INSTRUCTION_EVAL_RENAME", CSYNC_INSTRUCTION_EVAL_RENAME }, + { "INSTRUCTION_NEW", CSYNC_INSTRUCTION_NEW }, + { "INSTRUCTION_CONFLICT", CSYNC_INSTRUCTION_CONFLICT }, + { "INSTRUCTION_IGNORE", CSYNC_INSTRUCTION_IGNORE }, + { "INSTRUCTION_SYNC", CSYNC_INSTRUCTION_SYNC }, + { "INSTRUCTION_STAT_ERR", CSYNC_INSTRUCTION_STAT_ERROR }, + { "INSTRUCTION_ERROR", CSYNC_INSTRUCTION_ERROR }, + { "INSTRUCTION_DELETED", CSYNC_INSTRUCTION_DELETED }, + { "INSTRUCTION_UPDATED", CSYNC_INSTRUCTION_UPDATED }, + { NULL, CSYNC_INSTRUCTION_ERROR } +}; + +struct csync_memstat_s { + int size; + int resident; + int shared; + int trs; + int drs; + int lrs; + int dt; +}; + +const char *csync_instruction_str(enum csync_instructions_e instr) +{ + int idx = 0; + + while (_instr[idx].instr_str != NULL) { + if (_instr[idx].instr_code == instr) { + return _instr[idx].instr_str; + } + idx++; + } + + return "ERROR!"; +} + + +void csync_memstat_check(void) { + int s = 0; + struct csync_memstat_s m; + FILE* fp; + + /* get process memory stats */ + fp = fopen("/proc/self/statm","r"); + if (fp == NULL) { + return; + } + s = fscanf(fp, "%d%d%d%d%d%d%d", &m.size, &m.resident, &m.shared, &m.trs, + &m.drs, &m.lrs, &m.dt); + fclose(fp); + if (s == EOF) { + return; + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "Memory: %dK total size, %dK resident, %dK shared", + m.size * 4, m.resident * 4, m.shared * 4); +} + +int csync_unix_extensions(CSYNC *ctx) { + int rc = -1; + char *uri = NULL; + csync_vio_handle_t *fp = NULL; + + ctx->options.unix_extensions = 0; + + rc = asprintf(&uri, "%s/csync_unix_extension*test.ctmp", ctx->remote.uri); + if (rc < 0) { + goto out; + } + + ctx->replica = ctx->remote.type; + fp = csync_vio_creat(ctx, uri, 0644); + if (fp == NULL) { + rc = 0; + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, + "Disabled unix filesystem synchronization"); + goto out; + } + csync_vio_close(ctx, fp); + + ctx->options.unix_extensions = 1; + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "Enabled unix filesystem synchronization"); + + rc = 1; + +out: + csync_vio_unlink(ctx, uri); + SAFE_FREE(uri); + + return rc; +} + +void csync_win32_set_file_hidden( const char *file, bool h ) { +#ifdef _WIN32 + const mbchar_t *fileName; + DWORD dwAttrs; + if( !file ) return; + + fileName = c_utf8_to_locale( file ); + dwAttrs = GetFileAttributesW(fileName); + + if (dwAttrs==INVALID_FILE_ATTRIBUTES) return; + + if (h && !(dwAttrs & FILE_ATTRIBUTE_HIDDEN)) { + SetFileAttributesW(fileName, dwAttrs | FILE_ATTRIBUTE_HIDDEN ); + } else if (!h && (dwAttrs & FILE_ATTRIBUTE_HIDDEN)) { + SetFileAttributesW(fileName, dwAttrs & ~FILE_ATTRIBUTE_HIDDEN ); + } + + c_free_locale_string(fileName); +#else + (void) h; + (void) file; +#endif +} + +csync_vio_file_stat_t *csync_vio_convert_file_stat(csync_file_stat_t *st) { + csync_vio_file_stat_t *vfs = NULL; + + if (st == NULL) { + return NULL; + } + + vfs = csync_vio_file_stat_new(); + if (vfs == NULL) { + return NULL; + } + vfs->acl = NULL; + if (st->pathlen > 0) { + vfs->name = c_strdup(st->path); + } + vfs->uid = st->uid; + vfs->gid = st->gid; + + vfs->atime = 0; + vfs->mtime = st->modtime; + vfs->ctime = 0; + + vfs->size = st->size; + vfs->blksize = 0; /* Depricated. */ + vfs->blkcount = 0; + + vfs->mode = st->mode; + vfs->device = 0; + vfs->inode = st->inode; + vfs->nlink = st->nlink; + + /* fields. */ + vfs->fields = CSYNC_VIO_FILE_STAT_FIELDS_TYPE + + CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS + + CSYNC_VIO_FILE_STAT_FIELDS_INODE + + CSYNC_VIO_FILE_STAT_FIELDS_LINK_COUNT + + CSYNC_VIO_FILE_STAT_FIELDS_SIZE + + CSYNC_VIO_FILE_STAT_FIELDS_MTIME + + CSYNC_VIO_FILE_STAT_FIELDS_UID + + CSYNC_VIO_FILE_STAT_FIELDS_GID; + + if (st->type == CSYNC_FTW_TYPE_DIR) + vfs->type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + else if (st->type == CSYNC_FTW_TYPE_FILE) + vfs->type = CSYNC_VIO_FILE_TYPE_REGULAR; + else if (st->type == CSYNC_FTW_TYPE_SLINK) + vfs->type = CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK; + else + vfs->type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + + return vfs; +} diff --git a/csync/src/csync_util.h b/csync/src/csync_util.h new file mode 100644 index 000000000..10f5727af --- /dev/null +++ b/csync/src/csync_util.h @@ -0,0 +1,40 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_UTIL_H +#define _CSYNC_UTIL_H + +#include + +#include "csync_private.h" + +const char *csync_instruction_str(enum csync_instructions_e instr); + +void csync_memstat_check(void); + +int csync_unix_extensions(CSYNC *ctx); + +void csync_win32_set_file_hidden( const char *file, bool hidden ); + +/* Convert a csync_file_stat_t to csync_vio_file_stat_t */ +csync_vio_file_stat_t *csync_vio_convert_file_stat(csync_file_stat_t *st); + +#endif /* _CSYNC_UTIL_H */ diff --git a/csync/src/csync_version.h.in b/csync/src/csync_version.h.in new file mode 100644 index 000000000..41a6b077e --- /dev/null +++ b/csync/src/csync_version.h.in @@ -0,0 +1,52 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2006-2012 by Andreas Schneider + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _CSYNC_VERSION_H +#define _CSYNC_VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define CSYNC_STRINGIFY(s) CSYNC_TOSTRING(s) +#define CSYNC_TOSTRING(s) #s + +/* csync version macros */ +#define CSYNC_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define CSYNC_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define CSYNC_VERSION(a, b, c) CSYNC_VERSION_DOT(a, b, c) + +/* csync version */ +#define LIBCSYNC_VERSION_MAJOR @APPLICATION_VERSION_MAJOR@ +#define LIBCSYNC_VERSION_MINOR @APPLICATION_VERSION_MINOR@ +#define LIBCSYNC_VERSION_MICRO @APPLICATION_VERSION_PATCH@ + +#define LIBCSYNC_VERSION_INT CSYNC_VERSION_INT(LIBCSYNC_VERSION_MAJOR, \ + LIBCSYNC_VERSION_MINOR, \ + LIBCSYNC_VERSION_MICRO) +#define LIBCSYNC_VERSION CSYNC_VERSION(LIBCSYNC_VERSION_MAJOR, \ + LIBCSYNC_VERSION_MINOR, \ + LIBCSYNC_VERSION_MICRO) + +#ifdef __cplusplus +} +#endif + +#endif // _CSYNC_VERSION_H diff --git a/csync/src/httpbf/CMakeLists.txt b/csync/src/httpbf/CMakeLists.txt new file mode 100644 index 000000000..8b855e3dc --- /dev/null +++ b/csync/src/httpbf/CMakeLists.txt @@ -0,0 +1,56 @@ +project(httpbflib C) + +find_package(Neon) + + +set(HTTPBF_PUBLIC_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${NEON_INCLUDE_DIRS} + CACHE INTERNAL "httpbflib public include directories" +) + +set(HTTPBF_LIBRARY + httpbf + CACHE INTERNAL "httpbf library" +) + +set(HTTPBF_LINK_LIBRARIES + ${HTTPBF_LIBRARY} +) + +set(httpbf_SRCS + src/httpbf.c +) +set(httpbf_HEADERS + src/httpbf.h +) + +include_directories( + ${HTTPBF_PUBLIC_INCLUDE_DIRS} +) + +add_library(${HTTPBF_LIBRARY} STATIC ${httpbf_SRCS}) +target_link_libraries(${HTTPBF_LIBRARY} ${NEON_LIBRARIES}) + +if(NOT WIN32) + add_definitions( -fPIC ) +endif() + +INSTALL( + TARGETS + ${HTTPBF_LIBRARY} + LIBRARY DESTINATION + ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION + ${LIB_INSTALL_DIR} + RUNTIME DESTINATION + ${BIN_INSTALL_DIR} +) + +INSTALL( + FILES + ${httpbf_HEADERS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR} +) + diff --git a/csync/src/httpbf/README b/csync/src/httpbf/README new file mode 100644 index 000000000..31201ae6d --- /dev/null +++ b/csync/src/httpbf/README @@ -0,0 +1,29 @@ +This is a little code that does ownCloud file chunking. + +Basically to put a local file to an url: +(Also see the client example code in client dir.) + +/* Initialize the transfer, get a transfer struct. */ +hbf_transfer_t *trans = hbf_init_transfer( url ); + +Hbf_State state; +if( trans ) { + int fd = open_local_file( file ); + + /* create a neon session to use for the transfer */ + ne_session *session = create_neon_session(uri); + + if( session && fd > -1 ) { + /* Prepare the list of chunks, ie. calculate chunks and write back to trans. */ + state = hbf_splitlist(trans, fd); + + if( state == HBF_SUCCESS ) { + /* Transfer all the chunks through the HTTP session using PUT. */ + state = hbf_transfer( session, trans, "PUT" ); + } + } +} + +GET a large file: +Do GET Range requests. + diff --git a/csync/src/httpbf/client/CMakeLists.txt b/csync/src/httpbf/client/CMakeLists.txt new file mode 100644 index 000000000..ea371acb9 --- /dev/null +++ b/csync/src/httpbf/client/CMakeLists.txt @@ -0,0 +1,36 @@ +project(client C) + +set(CLIENT_EXECUTABLE httpbfclient CACHE INTERNAL "httpbf client") + +set(CLIENT_LINK_LIBRARIES ${NEON_LIBRARIES} ${HBF_LIBRARY}) + +set(HTTPBF_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/src}) + +if(NOT LINUX) + list(APPEND CLIENT_LINK_LIBRARIES ${ARGP_LIBRARIES}) +endif() + +set(client_SRCS + httpbf_client.c +) + +include_directories( + ${HTTPBF_INCLUDE_DIR} + ${HTTPBF_PUBLIC_INCLUDE_DIRS} +) + +add_executable(${CLIENT_EXECUTABLE} ${client_SRCS}) + +target_link_libraries(${CLIENT_EXECUTABLE} ${CLIENT_LINK_LIBRARIES}) + +set_target_properties( + ${CLIENT_EXECUTABLE} + PROPERTIES + OUTPUT_NAME + httpbf +) + +# install( TARGETS ${CLIENT_EXECUTABLE} DESTINATION ${BIN_INSTALL_DIR} ) +install(TARGETS ${CLIENT_EXECUTABLE} DESTINATION bin) + + diff --git a/csync/src/httpbf/client/httpbf_client.c b/csync/src/httpbf/client/httpbf_client.c new file mode 100644 index 000000000..36c7bebee --- /dev/null +++ b/csync/src/httpbf/client/httpbf_client.c @@ -0,0 +1,225 @@ +/* + * httpbf - send big files via http + * + * Copyright (c) 2012 Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "httpbf.h" + +/* Program documentation. */ +static char doc[] = "Usage: httpbf [OPTION...] LOCAL REMOTEDIR\n\ +httpbf - command line client to upload big files via http.\n\ +\n\ +Transfer a big file to a remote directory on ownCloud.\n\ +\n\ +-?, --help Give this help list\n\ + --usage Give a short usage message\n\ +-V, --version Print program version\n\ +"; + +static char _user[NE_ABUFSIZ]; +static char _pwd[NE_ABUFSIZ]; + +/* The options we understand. */ +static const struct option long_options[] = +{ + {"version", no_argument, 0, 'V' }, + {"usage", no_argument, 0, 'h' }, + {0, 0, 0, 0} +}; + +static const char* httpbf_version = "0.1"; + +static void print_version() +{ + printf( "%s\n", httpbf_version ); + exit(0); +} + +static void print_help() +{ + printf( "%s\n", doc ); + exit(0); +} + +static int ne_auth( void *userdata, const char *realm, int attempt, + char *username, char *password) +{ + (void) userdata; + (void) realm; + + if( username && password ) { + if( _user ) { + /* allow user without password */ + if( strlen( _user ) < NE_ABUFSIZ ) { + strcpy( username, _user ); + } + if( _pwd && strlen( _pwd ) < NE_ABUFSIZ ) { + strcpy( password, _pwd ); + } + } + } + return attempt; +} + + +static int parse_args(int argc, char **argv) +{ + while(optind < argc) { + int c = -1; + struct option *opt = NULL; + int result = getopt_long( argc, argv, "Vh", long_options, &c ); + + if( result == -1 ) { + break; + } + + switch(result) { + case 'V': + print_version(); + break; + case 'h': + print_help(); + break; + case 0: + opt = (struct option*)&(long_options[c]); + if(strcmp(opt->name, "no-name-yet")) { + + } else { + fprintf(stderr, "Argument: No idea what!\n"); + } + break; + default: + break; + } + } + return optind; +} + +static ne_session* create_neon_session( const char *url ) +{ + ne_uri uri; + ne_session *sess = NULL; + + memset( _user, 0, NE_ABUFSIZ ); + memset( _pwd, 0, NE_ABUFSIZ ); + + if( ne_uri_parse( url, &uri ) == 0 ) { + unsigned int port = ne_uri_defaultport(uri.scheme); + if( uri.userinfo ) { + char *slash = NULL; + strcpy( _user, uri.userinfo ); + slash = strchr( _user, ':'); + if( slash ) { + strcpy( _pwd, slash+1); + *slash = 0; + } + } + sess = ne_session_create(uri.scheme, uri.host, port); + ne_set_server_auth(sess, ne_auth, 0 ); + + ne_uri_free(&uri); + } + return sess; +} + +static int open_local_file( const char *file ) +{ + int fd = -1; + + if( !file ) return -1; + + fd = open(file, O_RDONLY); + return fd; +} + +static void transfer( const char* local, const char* uri ) +{ + if( !(local && uri )) return; + char *whole_url; + int len; + char *filename = basename(local); + if( ! filename ) { + return; + } + + len = strlen(filename)+strlen(uri)+2; + whole_url = malloc( len ); + strcpy(whole_url, uri); + strcat(whole_url, "/"); + strcat(whole_url, filename); + + hbf_transfer_t *trans = hbf_init_transfer( whole_url ); + Hbf_State state; + + if( trans ) { + ne_session *session = create_neon_session(uri); + if( session ) { + int fd = open_local_file( local ); + if( fd > -1 ) { + state = hbf_splitlist(trans, fd ); + if( state == HBF_SUCCESS ) { + state = hbf_transfer( session, trans, "PUT" ); + } + } + ne_session_destroy(session); + } + } + + if( state != HBF_SUCCESS ) { + printf("Upload failed: %s\n", hbf_error_string(state)); + printf(" HTTP result %d, Server Error: %s\n", + trans->status_code, trans->error_string ? trans->error_string : "" ); + } + /* Print the result of the recent transfer */ + hbf_free_transfer( trans ); + free(whole_url); +} + +int main(int argc, char **argv) { + int rc = 0; + char errbuf[256] = {0}; + + parse_args(argc, argv); + /* two options must remain as source and target */ + /* printf("ARGC: %d -> optind: %d\n", argc, optind ); */ + if( argc - optind < 2 ) { + print_help(); + } + + transfer( argv[optind], argv[optind+1]); + +} + +/* vim: set ts=8 sw=2 et cindent: */ diff --git a/csync/src/httpbf/src/CMakeLists.txt b/csync/src/httpbf/src/CMakeLists.txt new file mode 100644 index 000000000..dc57ab14c --- /dev/null +++ b/csync/src/httpbf/src/CMakeLists.txt @@ -0,0 +1,38 @@ +project(httpbf C) + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked + +set(HTTPBF_PUBLIC_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + CACHE INTERNAL "httpbf public include directories" +) + +set(HTTPBFLIB_PRIVATE_INCLUDE_DIRS +) + +set(HBF_LIBRARY + httpbf + CACHE INTERNAL "httpbflib library" +) + +set(HTTPBFLIB_LINK_LIBRARIES + ${HBF_LIBRARY} +) + +set(httpbflib_SRCS + httpbf.c +) + +include_directories( + ${NEON_INCLUDE_DIRS} + ${GLIB2_INCLUDE_DIRS} +) + +if(NOT WIN32) + add_definitions( -fPIC ) +endif() + +add_library(${HBF_LIBRARY} SHARED ${httpbflib_SRCS} ) + +target_link_libraries(${HBF_LIBRARY} ${NEON_LIBRARIES}) + diff --git a/csync/src/httpbf/src/httpbf.c b/csync/src/httpbf/src/httpbf.c new file mode 100644 index 000000000..9201afd90 --- /dev/null +++ b/csync/src/httpbf/src/httpbf.c @@ -0,0 +1,688 @@ +/* + * httpbf - send big files via http + * + * Copyright (c) 2012 Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpbf.h" + +#include +#include +#include + +// #ifdef NDEBUG +// #define DEBUG_HBF(...) +// #else +#define DEBUG_HBF(...) { if(transfer->log_cb) { \ + char buf[1024]; \ + snprintf(buf, 1024, __VA_ARGS__); \ + transfer->log_cb(__FUNCTION__, buf, transfer->user_data); \ + } } + +// #endif + +#define DEFAULT_BLOCK_SIZE (10*1024*1024) + +/* Platform specific defines go here. */ +#ifdef _WIN32 +#define _hbf_fstat _fstat64 +typedef struct stat64 hbf_stat_t; +#else +#define _hbf_fstat fstat +typedef struct stat hbf_stat_t; +#endif + +static int transfer_id( hbf_stat_t *sb ) { + struct timeval tp; + int res; + int r; + + if( gettimeofday(&tp, 0) < 0 ) { + return 0; + } + + /* build a Unique ID: + * take the current epoch and shift 8 bits up to keep the least bits. + * than add the milliseconds, again shift by 8 + * and finally add the least 8 bit of the inode of the file. + */ + res = tp.tv_sec; /* epoche value in seconds */ + res = res << 8; + r = (sb->st_ino & 0xFF); + res += r; /* least six bit of inode */ + res = res << sizeof(tp.tv_usec); + res += tp.tv_usec; /* milliseconds */ + + return res; +} + +hbf_transfer_t *hbf_init_transfer( const char *dest_uri ) { + hbf_transfer_t * transfer = NULL; + + transfer = malloc( sizeof(hbf_transfer_t) ); + memset(transfer, 0, sizeof(hbf_transfer_t)); + + /* store the target uri */ + transfer->url = strdup(dest_uri); + transfer->status_code = 200; + transfer->error_string = NULL; + transfer->start_id = 0; + transfer->block_size = DEFAULT_BLOCK_SIZE; + transfer->threshold = transfer->block_size; + transfer->modtime_accepted = 0; + + return transfer; +} + +/* Create the splitlist of a given file descriptor */ +Hbf_State hbf_splitlist(hbf_transfer_t *transfer, int fd ) { + hbf_stat_t sb; + int64_t num_blocks; + int64_t blk_size; + int64_t remainder = 0; + + if( ! transfer ) { + return HBF_PARAM_FAIL; + } + + if( fd <= 0 ) { + DEBUG_HBF("File descriptor is invalid."); + return HBF_PARAM_FAIL; + } + + if( _hbf_fstat(fd, &sb) < 0 ) { + DEBUG_HBF("Failed to stat the file descriptor: errno = %d", errno); + return HBF_FILESTAT_FAIL; + } + + /* Store the file characteristics. */ + transfer->fd = fd; + transfer->stat_size = sb.st_size; + transfer->modtime = sb.st_mtime; + transfer->previous_etag = NULL; +#ifndef NDEBUG + transfer->calc_size = 0; +#endif + + DEBUG_HBF("block_size: %" PRId64 " threshold: %" PRId64 " st_size: %" PRId64, transfer->block_size, transfer->threshold, sb.st_size ); + + + /* calc the number of blocks to split in */ + blk_size = transfer->block_size; + if (sb.st_size < transfer->threshold) { + blk_size = transfer->threshold; + } + + num_blocks = sb.st_size / blk_size; + + /* there migth be a remainder. */ + remainder = sb.st_size - num_blocks * blk_size; + + /* if there is a remainder, add one block */ + if( remainder > 0 ) { + num_blocks++; + } + + /* The file has size 0. There still needs to be at least one block. */ + if( sb.st_size == 0 ) { + num_blocks = 1; + blk_size = 0; + } + + DEBUG_HBF("num_blocks: %" PRId64 " rmainder: %" PRId64 " blk_size: %" PRId64, num_blocks, remainder, blk_size ); + + + if( num_blocks ) { + int cnt; + int64_t overall = 0; + /* create a datastructure for the transfer data */ + transfer->block_arr = calloc(num_blocks, sizeof(hbf_block_t*)); + transfer->block_cnt = num_blocks; + transfer->transfer_id = transfer_id(&sb); + transfer->start_id = 0; + + for( cnt=0; cnt < num_blocks; cnt++ ) { + /* allocate a block struct and fill */ + hbf_block_t *block = malloc( sizeof(hbf_block_t) ); + memset(block, 0, sizeof(hbf_block_t)); + + block->seq_number = cnt; + if( cnt > 0 ) { + block->start = cnt * blk_size; + } + block->size = blk_size; + block->state = HBF_NOT_TRANSFERED; + + /* consider the remainder if we're already at the end */ + if( cnt == num_blocks-1 && remainder > 0 ) { + block->size = remainder; + } + overall += block->size; + /* store the block data into the result array in the transfer */ + *((transfer->block_arr)+cnt) = block; + + DEBUG_HBF("created block %d (start: %" PRId64 " size: %" PRId64 ")", cnt, block->start, block->size ); + } + +#ifndef NDEBUG + transfer->calc_size = overall; +#endif + } + return HBF_SUCCESS; +} + +void hbf_free_transfer( hbf_transfer_t *transfer ) { + int cnt; + + if( !transfer ) return; + + for( cnt = 0; cnt < transfer->block_cnt; cnt++ ) { + hbf_block_t *block = transfer->block_arr[cnt]; + if( block->http_error_msg ) free( block->http_error_msg ); + if( block->etag ) free( block->etag ); + if( block ) free(block); + } + free( transfer->block_arr ); + free( transfer->url ); + free( transfer->file_id ); + if( transfer->error_string) free( (void*) transfer->error_string ); + + free( transfer ); +} + +static char* get_transfer_url( hbf_transfer_t *transfer, int indx ) { + char *res = NULL; + + hbf_block_t *block = NULL; + + if( ! transfer ) return NULL; + if( indx >= transfer->block_cnt ) return NULL; + + block = transfer->block_arr[indx]; + if( ! block ) return NULL; + + if( transfer->block_cnt == 1 ) { + /* Just one chunk. We send as an ordinary request without chunking. */ + res = strdup( transfer->url ); + } else { + char trans_id_str[32]; + char trans_block_str[32]; + char indx_str[32]; + int len = 1; /* trailing zero. */ + int tlen = 0; + + tlen = sprintf( trans_id_str, "%u", transfer->transfer_id ); + if( tlen < 0 ) { + return NULL; + } + len += tlen; + + tlen = sprintf( trans_block_str, "%u", transfer->block_cnt ); + if( tlen < 0 ) { + return NULL; + } + len += tlen; + + tlen = sprintf( indx_str, "%u", indx ); + if( tlen < 0 ) { + return NULL; + } + len += tlen; + + len += strlen(transfer->url); + len += strlen("-chunking---"); + + res = malloc(len); + if( res == NULL ) { + return NULL; + } + + /* Note: must be %u for unsigned because one does not want '--' */ + if( sprintf(res, "%s-chunking-%u-%u-%u", transfer->url, transfer->transfer_id, + transfer->block_cnt, indx ) < 0 ) { + return NULL; + } + } + return res; +} + +/* + * perform one transfer of one block. + * returns HBF_TRANSFER_SUCCESS if the transfer of this block was a success + * returns HBF_SUCCESS if the server aknoweldge that he received all the blocks + */ +static int _hbf_dav_request(hbf_transfer_t *transfer, ne_request *req, int fd, hbf_block_t *blk ) { + Hbf_State state = HBF_TRANSFER_SUCCESS; + int res; + const ne_status *req_status = NULL; + const char *etag = NULL; + + (void) transfer; + + if( ! (blk && req) ) return HBF_PARAM_FAIL; + + ne_set_request_body_fd(req, fd, blk->start, blk->size); + DEBUG_HBF("Block: %d , Start: %" PRId64 " and Size: %" PRId64 "", blk->seq_number, blk->start, blk->size ); + res = ne_request_dispatch(req); + + req_status = ne_get_status( req ); + + switch(res) { + case NE_OK: + blk->state = HBF_TRANSFER_FAILED; + state = HBF_FAIL; + etag = 0; + if( req_status->klass == 2 ) { + state = HBF_TRANSFER_SUCCESS; + blk->state = HBF_TRANSFER_SUCCESS; + etag = ne_get_response_header(req, "ETag"); + if (etag && etag[0]) { + /* When there is an etag, it means the transfer was complete */ + state = HBF_SUCCESS; + + if( etag[0] == '"' && etag[ strlen(etag)-1] == '"') { + int len = strlen( etag )-2; + blk->etag = malloc( len+1 ); + strncpy( blk->etag, etag+1, len ); + blk->etag[len] = '\0'; + } else { + blk->etag = strdup( etag ); + } + } else { + /* DEBUG_HBF("OOOOOOOO No etag returned!"); */ + } + + /* check if the server was able to set the mtime already. */ + etag = ne_get_response_header(req, "X-OC-MTime"); + if( etag && strcmp(etag, "accepted") == 0 ) { + /* the server acknowledged that the mtime was set. */ + transfer->modtime_accepted = 1; + } + + etag = ne_get_response_header(req, "OC-FileID"); + if( etag ) { + transfer->file_id = strdup( etag ); + } + } + break; + case NE_AUTH: + state = HBF_AUTH_FAIL; + blk->state = HBF_TRANSFER_FAILED; + break; + case NE_PROXYAUTH: + state = HBF_PROXY_AUTH_FAIL; + blk->state = HBF_TRANSFER_FAILED; + break; + case NE_CONNECT: + state = HBF_CONNECT_FAIL; + blk->state = HBF_TRANSFER_FAILED; + break; + case NE_TIMEOUT: + state = HBF_TIMEOUT_FAIL; + blk->state = HBF_TRANSFER_FAILED; + break; + case NE_ERROR: + state = HBF_FAIL; + blk->state = HBF_TRANSFER_FAILED; + break; + } + + blk->http_result_code = req_status->code; + if( req_status->reason_phrase ) { + blk->http_error_msg = strdup(req_status->reason_phrase); + } + + return state; +} + +Hbf_State hbf_validate_source_file( hbf_transfer_t *transfer ) { + Hbf_State state = HBF_SUCCESS; + hbf_stat_t sb; + + if( transfer == NULL ) { + state = HBF_PARAM_FAIL; + } + + if( state == HBF_SUCCESS ) { + if( transfer->fd <= 0 ) { + state = HBF_PARAM_FAIL; + } + } + + if( state == HBF_SUCCESS ) { + int rc = _hbf_fstat( transfer->fd, &sb ); + if( rc != 0 ) { + state = HBF_STAT_FAIL; + } + } + + if( state == HBF_SUCCESS ) { + if( sb.st_mtime != transfer->modtime || sb.st_size != transfer->stat_size ) { + state = HBF_SOURCE_FILE_CHANGE; + } + } + return state; +} + +/* Get the HTTP error code for the last request */ +static int _hbf_http_error_code(ne_session *session) { + const char *msg = ne_get_error( session ); + char *msg2; + int err; + err = strtol(msg, &msg2, 10); + if (msg == msg2) { + err = 0; + } + return err; +} + +static Hbf_State _hbf_transfer_no_chunk(ne_session *session, hbf_transfer_t *transfer, const char *verb) { + int res; + const ne_status* req_status; + + ne_request *req = ne_request_create(session, verb ? verb : "PUT", transfer->url); + if (!req) + return HBF_MEMORY_FAIL; + + ne_add_request_header( req, "Content-Type", "application/octet-stream"); + + ne_set_request_body_fd(req, transfer->fd, 0, transfer->stat_size); + DEBUG_HBF("HBF: chunking not supported for %s", transfer->url); + res = ne_request_dispatch(req); + req_status = ne_get_status( req ); + + if (res == NE_OK && req_status->klass == 2) { + ne_request_destroy(req); + return HBF_SUCCESS; + } + + if( transfer->error_string ) free( transfer->error_string ); + transfer->error_string = strdup( ne_get_error(session) ); + transfer->status_code = req_status->code; + ne_request_destroy(req); + return HBF_FAIL; +} + +Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const char *verb ) { + Hbf_State state = HBF_TRANSFER_SUCCESS; + int cnt; + + if( ! session ) { + state = HBF_SESSION_FAIL; + } + if( ! transfer ) { + state = HBF_SPLITLIST_FAIL; + } + if( ! verb ) { + state = HBF_PARAM_FAIL; + } + + if(state == HBF_TRANSFER_SUCCESS) { + DEBUG_HBF("%s request to %s", verb, transfer->url); + } + + for( cnt=0; state == HBF_TRANSFER_SUCCESS && cnt < transfer->block_cnt; cnt++ ) { + /* cnt goes from O to block_cnt, but block_id starts at start_id and wrap around + * That way if we have not finished uploaded when we reach block_cnt, we re-upload + * the beginning of the file that the server did not have in cache anymore. + */ + int block_id = (cnt + transfer->start_id) % transfer->block_cnt; + hbf_block_t *block = transfer->block_arr[block_id]; + char *transfer_url = NULL; + + if( ! block ) state = HBF_PARAM_FAIL; + + if( transfer->abort_cb ) { + int do_abort = (transfer->abort_cb)(transfer->user_data); + if( do_abort ) { + state = HBF_USER_ABORTED; + transfer->start_id = block_id % transfer->block_cnt; + } + } + + if( state == HBF_TRANSFER_SUCCESS ) { + transfer_url = get_transfer_url( transfer, block_id ); + if( ! transfer_url ) { + state = HBF_PARAM_FAIL; + } + } + + if( state == HBF_TRANSFER_SUCCESS ) { + if( transfer->block_cnt > 1 && cnt > 0 ) { + /* The block count is > 1, check size and mtime before transmitting. */ + state = hbf_validate_source_file(transfer); + if( state == HBF_SOURCE_FILE_CHANGE ) { + /* The source file has changed meanwhile */ + } + } + } + + if( state == HBF_TRANSFER_SUCCESS || state == HBF_SUCCESS ) { + ne_request *req = ne_request_create(session, verb, transfer_url); + + if( req ) { + char buf[21]; + + snprintf(buf, sizeof(buf), "%"PRId64, transfer->stat_size); + ne_add_request_header(req, "OC-Total-Length", buf); + if( transfer->modtime > 0 ) { + snprintf(buf, sizeof(buf), "%"PRId64, transfer->modtime); + ne_add_request_header(req, "X-OC-Mtime", buf); + } + + if( transfer->previous_etag ) { + ne_add_request_header(req, "If-Match", transfer->previous_etag); + } + + if( transfer->block_cnt > 1 ) { + ne_add_request_header(req, "OC-Chunked", "1"); + } + ne_add_request_header( req, "Content-Type", "application/octet-stream"); + + state = _hbf_dav_request(transfer, req, transfer->fd, block ); + + if( state != HBF_TRANSFER_SUCCESS && state != HBF_SUCCESS) { + if( transfer->error_string ) free( transfer->error_string ); + transfer->error_string = strdup( ne_get_error(session) ); + transfer->start_id = block_id % transfer->block_cnt; + /* Set the code of the last transmission. */ + state = HBF_FAIL; + transfer->status_code = transfer->block_arr[block_id]->http_result_code; + } + ne_request_destroy(req); + + if (transfer->block_cnt > 1 && state == HBF_SUCCESS && cnt == 0) { + /* Success on the first chunk is suspicious. + It could happen that the server did not support chunking */ + int rc = ne_delete(session, transfer_url); + if (rc == NE_OK && _hbf_http_error_code(session) == 204) { + /* If delete suceeded, it means some proxy strips the OC_CHUNKING header + start again without chunking: */ + free( transfer_url ); + return _hbf_transfer_no_chunk(session, transfer, verb); + } + } + + if (state == HBF_TRANSFER_SUCCESS && transfer->chunk_finished_cb) { + transfer->chunk_finished_cb(transfer, block_id, transfer->user_data); + } + + } else { + state = HBF_MEMORY_FAIL; + } + free( transfer_url ); + } + } + + /* do the source file validation finally (again). */ + if( state == HBF_TRANSFER_SUCCESS ) { + /* This means that no etag was returned on one of the chunks to indicate + * that the upload was finished. */ + state = HBF_TRANSFER_NOT_ACKED; + } + + return state; +} + +int hbf_fail_http_code( hbf_transfer_t *transfer ) +{ + int cnt; + + if( ! transfer ) return 0; + + for( cnt = 0; cnt < transfer->block_cnt; cnt++ ) { + int block_id = (cnt + transfer->start_id) % transfer->block_cnt; + hbf_block_t *block = transfer->block_arr[block_id]; + + if( block->state != HBF_NOT_TRANSFERED && block->state != HBF_TRANSFER_SUCCESS ) { + return block->http_result_code; + } + } + return 200; +} + +const char *hbf_transfer_etag( hbf_transfer_t *transfer ) +{ + int cnt; + const char *etag = NULL; + + if( ! transfer ) return 0; + + /* Loop over all parts and do a assertion that there is only one etag. */ + for( cnt = 0; cnt < transfer->block_cnt; cnt++ ) { + int block_id = (cnt + transfer->start_id) % transfer->block_cnt; + hbf_block_t *block = transfer->block_arr[block_id]; + if( block->etag ) { + if( etag && strcmp(etag, block->etag) != 0 ) { + /* multiple etags in the transfer, not equal. */ + DEBUG_HBF( "WARN: etags are not equal in blocks of one single transfer." ); + } + etag = block->etag; + } + } + return etag; +} + +const char *hbf_transfer_file_id( hbf_transfer_t *transfer ) +{ + const char *re = NULL; + if(transfer) { + re = transfer->file_id; + } + return re; +} + +const char *hbf_error_string(hbf_transfer_t *transfer, Hbf_State state) +{ + const char *re; + int cnt; + switch( state ) { + case HBF_SUCCESS: + re = "Ok."; + break; + case HBF_NOT_TRANSFERED: /* never tried to transfer */ + re = "Block was not yet tried to transfer."; + break; + case HBF_TRANSFER: /* transfer currently running */ + re = "Block is currently transferred."; + break; + case HBF_TRANSFER_FAILED: /* transfer tried but failed */ + re = "Block transfer failed."; + break; + case HBF_TRANSFER_SUCCESS: /* transfer succeeded. */ + re = "Block transfer successful."; + break; + case HBF_SPLITLIST_FAIL: /* the file could not be split */ + re = "Splitlist could not be computed."; + break; + case HBF_SESSION_FAIL: + re = "No valid session in transfer."; + break; + case HBF_FILESTAT_FAIL: + re = "Source file could not be stat'ed."; + break; + case HBF_PARAM_FAIL: + re = "Parameter fail."; + break; + case HBF_AUTH_FAIL: + re = "Authentication fail."; + break; + case HBF_PROXY_AUTH_FAIL: + re = "Proxy Authentication fail."; + break; + case HBF_CONNECT_FAIL: + re = "Connection could not be established."; + break; + case HBF_TIMEOUT_FAIL: + re = "Network timeout."; + break; + case HBF_MEMORY_FAIL: + re = "Out of memory."; + break; + case HBF_STAT_FAIL: + re = "Filesystem stat on file failed."; + break; + case HBF_SOURCE_FILE_CHANGE: + re = "Source file changed too often during upload."; + break; + case HBF_USER_ABORTED: + re = "Transmission aborted by user."; + break; + case HBF_TRANSFER_NOT_ACKED: + re = "The server did not provide an Etag."; + break; + case HBF_FAIL: + default: + for( cnt = 0; cnt < transfer->block_cnt; cnt++ ) { + int block_id = (cnt + transfer->start_id) % transfer->block_cnt; + hbf_block_t *block = transfer->block_arr[block_id]; + + if( block->state != HBF_NOT_TRANSFERED && block->state != HBF_TRANSFER_SUCCESS + && block->http_error_msg != NULL) { + return block->http_error_msg; + } + } + re = "Unknown error."; + } + return re; +} + +void hbf_set_abort_callback( hbf_transfer_t *transfer, hbf_abort_callback cb) +{ + if( transfer ) { + transfer->abort_cb = cb; + } +} + +void hbf_set_log_callback(hbf_transfer_t* transfer, hbf_log_callback cb) +{ + if( transfer ) { + transfer->log_cb = cb; + } +} diff --git a/csync/src/httpbf/src/httpbf.h b/csync/src/httpbf/src/httpbf.h new file mode 100644 index 000000000..ec1f94eef --- /dev/null +++ b/csync/src/httpbf/src/httpbf.h @@ -0,0 +1,141 @@ +/** + * http big file functions + * + * Copyright (c) 2012 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _HBF_SEND_H +#define _HBF_SEND_H + +#include "config_csync.h" +#ifdef NEON_WITH_LFS /* Switch on LFS in libneon. Never remove the NE_LFS! */ +#define NE_LFS +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum hbf_state_e { + HBF_SUCCESS, + HBF_NOT_TRANSFERED, /* never tried to transfer */ + HBF_TRANSFER, /* transfer currently running */ + HBF_TRANSFER_FAILED, /* transfer tried but failed */ + HBF_TRANSFER_SUCCESS, /* block transfer succeeded. */ + HBF_SPLITLIST_FAIL, /* the file could not be split */ + HBF_SESSION_FAIL, + HBF_FILESTAT_FAIL, + HBF_PARAM_FAIL, + HBF_AUTH_FAIL, + HBF_PROXY_AUTH_FAIL, + HBF_CONNECT_FAIL, + HBF_TIMEOUT_FAIL, + HBF_MEMORY_FAIL, + HBF_STAT_FAIL, + HBF_SOURCE_FILE_CHANGE, + HBF_USER_ABORTED, + HBF_TRANSFER_NOT_ACKED, + HBF_FAIL +}; + +typedef enum hbf_state_e Hbf_State; + +typedef struct hbf_block_s hbf_block_t; + +struct hbf_block_s { + int seq_number; + + int64_t start; + int64_t size; + + Hbf_State state; + int http_result_code; + char *http_error_msg; + char *etag; + + int tries; +}; + +typedef struct hbf_transfer_s hbf_transfer_t; + +/* Callback for to check on abort */ +typedef int (*hbf_abort_callback) (void *); +typedef void (*hbf_log_callback) (const char *, const char *, void*); +typedef void (*hbf_chunk_finished_callback) (hbf_transfer_t*,int, void*); + +struct hbf_transfer_s { + hbf_block_t **block_arr; + int block_cnt; + int fd; + int transfer_id; + char *url; + int start_id; + + int status_code; + char *error_string; + + int64_t stat_size; + time_t modtime; + int64_t block_size; + int64_t threshold; + + void *user_data; + hbf_abort_callback abort_cb; + hbf_log_callback log_cb; + hbf_chunk_finished_callback chunk_finished_cb; + int modtime_accepted; + const char *previous_etag; /* etag send as the If-Match http header */ + char *file_id; + +#ifndef NDEBUG + int64_t calc_size; +#endif +}; + +hbf_transfer_t *hbf_init_transfer( const char *dest_uri ); + +Hbf_State hbf_transfer( ne_session *session, hbf_transfer_t *transfer, const char *verb ); + +Hbf_State hbf_splitlist( hbf_transfer_t *transfer, int fd ); + +void hbf_free_transfer( hbf_transfer_t *transfer ); + +const char *hbf_error_string(hbf_transfer_t* transfer, Hbf_State state); + +const char *hbf_transfer_etag( hbf_transfer_t *transfer ); + +const char *hbf_transfer_file_id( hbf_transfer_t *transfer ); + +void hbf_set_abort_callback( hbf_transfer_t *transfer, hbf_abort_callback cb); +void hbf_set_log_callback( hbf_transfer_t *transfer, hbf_log_callback cb); + +/* returns an http (error) code of the transmission. If the transmission + * succeeded, the code is 200. If it failed, its the error code of the + * first part transmission that failed. + */ +int hbf_fail_http_code( hbf_transfer_t *transfer ); + +Hbf_State hbf_validate_source_file( hbf_transfer_t *transfer ); + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/csync/src/httpbf/tests/CMakeLists.txt b/csync/src/httpbf/tests/CMakeLists.txt new file mode 100644 index 000000000..4e6c647ef --- /dev/null +++ b/csync/src/httpbf/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +project(hbf_test C ) + +add_definitions(-DUNIT_TESTING=1) + +find_package(CMocka REQUIRED) + +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMOCKA_INCLUDE_DIRS} + ${NEON_INCLUDE_DIRS} + ${HTTPBF_PUBLIC_INCLUDE_DIRS} +) + +add_executable(send_test hbf_send_test.c) +target_link_libraries(send_test ${CMOCKA_LIBRARIES} ${NEON_LIBRARIES} ${HBF_LIBRARY} ) + + diff --git a/csync/src/httpbf/tests/church.jpg b/csync/src/httpbf/tests/church.jpg new file mode 100644 index 000000000..71bfd1a71 Binary files /dev/null and b/csync/src/httpbf/tests/church.jpg differ diff --git a/csync/src/httpbf/tests/hbf_send_test.c b/csync/src/httpbf/tests/hbf_send_test.c new file mode 100644 index 000000000..7f86a1860 --- /dev/null +++ b/csync/src/httpbf/tests/hbf_send_test.c @@ -0,0 +1,142 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "config_csync.h" + +#include + + +// A test case that does nothing and succeeds. +static void null_test_success(void **state) { + (void) state; +} + + +static char* test_file( const char* name ) { + if( ! name ) return 0; + + char path[260]; + strcpy( path, TESTFILEDIR); + if(path[strlen(TESTFILEDIR)-1] != '/') + strcat( path, "/"); + strcat( path, name ); + + return strdup(path); +} + +static void test_get_transfer_url( void **state ) { + const char *url = "http://example.org/owncloud"; + const char *turl = NULL; + char res[256]; + int i; + Hbf_State hbf_state; + + hbf_transfer_t *list = NULL; + list = hbf_init_transfer( url ); + assert_non_null( list ); + + /* open a file */ + int fd = open( test_file("church.jpg"), O_RDONLY ); + assert_true(fd >= 0); + + hbf_state = hbf_splitlist(list, fd); + assert_true( hbf_state == HBF_SUCCESS); + + for( i=0; i < list->block_cnt; i++ ) { + turl = get_transfer_url( list, i ); + + sprintf(res, "%s-chunking-%d-%d-%d", url, list->transfer_id, + list->block_cnt, i ); + printf( "XX: %s\n", res ); + assert_string_equal( turl, res ); + + } +} + +static void test_hbf_init_transfer( void **state ) { + hbf_transfer_t *list = NULL; + const char *url = "http://example.org/owncloud"; + + list = hbf_init_transfer( url ); + assert_non_null( list ); + assert_string_equal( url, list->url ); +} + +/* test with a file size that is not a multiply of the slize size. */ +static void test_hbf_splitlist_odd( void **state ){ + + hbf_transfer_t *list = NULL; + const char *dest_url = "http://localhost/ocm/remote.php/webdav/big/church.jpg"; + + /* open a file */ + int fd = open(test_file("church.jpg"), O_RDONLY); + assert_true(fd >= 0); + + int prev_id = 0; + int i; + + Hbf_State hbf_state; + + /* do a smoke test for uniqueness */ + for( i=0; i < 10000; i++) { + list = hbf_init_transfer(dest_url); + assert_non_null(list); + usleep(1); + hbf_state = hbf_splitlist(list, fd); + + assert_int_not_equal(list->transfer_id, prev_id); + prev_id = list->transfer_id; + hbf_free_transfer(list); + } + + list = hbf_init_transfer(dest_url); + assert_non_null(list); + + hbf_state = hbf_splitlist(list, fd); + assert_non_null(list); + assert_int_equal(list->calc_size, list->stat_size); + assert_int_not_equal(list->block_cnt, 0); + assert_true( hbf_state == HBF_SUCCESS); + + /* checks on the block list */ + int seen_zero_seq = 0; + int prev_seq = -1; + int64_t prev_block_end = -1; + + for( i=0; i < list->block_cnt; i++) { + hbf_block_t *blk = list->block_arr[i]; + assert_non_null(blk); + if( blk->seq_number == 0 ) seen_zero_seq++; + + assert_int_equal(prev_seq, blk->seq_number -1 ); + prev_seq = blk->seq_number; + + assert_true((prev_block_end+1) == (blk->start)); + prev_block_end = blk->start + blk->size; + } + /* Make sure we exactly saw blk->seq_number == 0 exactly one times */ + assert_int_equal( seen_zero_seq, 1 ); + + hbf_free_transfer( list ); +} + +int main(void) { + const UnitTest tests[] = { + unit_test(null_test_success), + unit_test(test_hbf_splitlist_odd), + unit_test(test_hbf_init_transfer), + unit_test(test_get_transfer_url) + }; + return run_tests(tests); +} diff --git a/csync/src/std/CMakeLists.txt b/csync/src/std/CMakeLists.txt new file mode 100644 index 000000000..d9cef2d6a --- /dev/null +++ b/csync/src/std/CMakeLists.txt @@ -0,0 +1,44 @@ +project(cstdlib C) + +set(CSTDLIB_PUBLIC_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + CACHE INTERNAL "cstdlib public include directories" +) + +set(CSTDLIB_PRIVATE_INCLUDE_DIRS + ${ICONV_INCLUDE_DIR} +) + +set(CSTDLIB_LIBRARY + cstdlib + CACHE INTERNAL "cstdlib library" +) + +set(CSTDLIB_LINK_LIBRARIES + ${CSTDLIB_LIBRARY} +) + +set(cstdlib_SRCS + c_alloc.c + c_dir.c + c_file.c + c_list.c + c_path.c + c_rbtree.c + c_string.c + c_time.c +) + +include_directories( + ${CSTDLIB_PUBLIC_INCLUDE_DIRS} + ${CSTDLIB_PRIVATE_INCLUDE_DIRS} +) + +add_library(${CSTDLIB_LIBRARY} STATIC ${cstdlib_SRCS}) +if(NOT WIN32) + add_definitions( -fPIC ) +endif() +if(NOT HAVE_FNMATCH AND WIN32) + # needed for PathMatchSpec for our fnmatch replacement + target_link_libraries(${CSTDLIB_LIBRARY} ${SHLWAPI_LIBRARY}) +endif() diff --git a/csync/src/std/c_alloc.c b/csync/src/std/c_alloc.c new file mode 100644 index 000000000..b87a3836f --- /dev/null +++ b/csync/src/std/c_alloc.c @@ -0,0 +1,89 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "c_macro.h" +#include "c_alloc.h" + +void *c_calloc(size_t count, size_t size) { + if (size == 0 || count == 0) { + return NULL; + } + +#ifdef CSYNC_MEM_NULL_TESTS + if (getenv("CSYNC_NOMEMORY")) { + return NULL; + } +#endif /* CSYNC_MEM_NULL_TESTS */ + +#undef calloc + return calloc(count, size); +#define calloc(x,y) DO_NOT_CALL_CALLOC__USE_XCALLOC_INSTEAD +} + +void *c_malloc(size_t size) { + if (size == 0) { + return NULL; + } +#undef malloc + return c_calloc(1, size); +#define malloc(x) DO_NOT_CALL_MALLOC__USE_XMALLOC_INSTEAD +} + +void *c_realloc(void *ptr, size_t size) { + +#ifdef CSYNC_MEM_NULL_TESTS + if (getenv("CSYNC_NOMEMORY")) { + return NULL; + } +#endif /* CSYNC_MEM_NULL_TESTS */ + +#undef realloc + return realloc(ptr, size); +#define realloc(x,y) DO_NOT_CALL_REALLOC__USE_XREALLOC_INSTEAD +} + +char *c_strdup(const char *str) { + char *ret; + ret = (char *) c_malloc(strlen(str) + 1); + if (ret == NULL) { + return NULL; + } + strcpy(ret, str); + return ret; +} + +char *c_strndup(const char *str, size_t size) { + char *ret; + size_t len; + len = strlen(str); + if (len > size) { + len = size; + } + ret = (char *) c_malloc(len + 1); + if (ret == NULL) { + return NULL; + } + strncpy(ret, str, len); + ret[size] = '\0'; + return ret; +} + diff --git a/csync/src/std/c_alloc.h b/csync/src/std/c_alloc.h new file mode 100644 index 000000000..6eeccd69d --- /dev/null +++ b/csync/src/std/c_alloc.h @@ -0,0 +1,116 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_alloc.h + * + * @brief Interface of the cynapses libc alloc function + * + * @defgroup cynLibraryAPI cynapses libc API (internal) + * + * @defgroup cynAllocInternals cynapses libc alloc functions + * @ingroup cynLibraryAPI + * + * @{ + */ + +#ifndef _C_ALLOC_H +#define _C_ALLOC_H + +#include + +#include "c_macro.h" + +/** + * @brief Allocates memory for an array. + * + * Allocates memory for an array of elements of bytes each and + * returns a pointer to the allocated memory. The memory is set to zero. + * + * @param count Amount of elements to allocate + * @param size Size in bytes of each element to allocate. + * + * @return A unique pointer value that can later be successfully passed to + * free(). If size or count is 0, NULL will be returned. + */ +void *c_calloc(size_t count, size_t size); + +/** + * @brief Allocates memory for an array. + * + * Allocates bytes of memory. The memory is set to zero. + * + * @param size Size in bytes to allocate. + * + * @return A unique pointer value that can later be successfully passed to + * free(). If size or count is 0, NULL will be returned. + */ +void *c_malloc(size_t size); + +/** + * @brief Changes the size of the memory block pointed to. + * + * Newly allocated memory will be uninitialized. + * + * @param ptr Pointer to the memory which should be resized. + * @param size Value to resize. + * + * @return If ptr is NULL, the call is equivalent to c_malloc(size); if size + * is equal to zero, the call is equivalent to free(ptr). Unless ptr + * is NULL, it must have been returned by an earlier call to + * c_malloc(), c_calloc() or c_realloc(). If the area pointed to was + * moved, a free(ptr) is done. + */ +void *c_realloc(void *ptr, size_t size); + +/** + * @brief Duplicate a string. + * + * The function returns a pointer to a newly allocated string which is a + * duplicate of the string str. + * + * @param str String to duplicate. + * + * @return Returns a pointer to the duplicated string, or NULL if insufficient + * memory was available. + * + */ +char *c_strdup(const char *str); + +/** + * @brief Duplicate a string. + * + * The function returns a pointer to a newly allocated string which is a + * duplicate of the string str of size bytes. + * + * @param str String to duplicate. + * + * @param size Size of the string to duplicate. + * + * @return Returns a pointer to the duplicated string, or NULL if insufficient + * memory was available. A terminating null byte '\0' is added. + * + */ +char *c_strndup(const char *str, size_t size); + +/** + * }@ + */ +#endif /* _C_ALLOC_H */ diff --git a/csync/src/std/c_dir.c b/csync/src/std/c_dir.c new file mode 100644 index 000000000..2edbebc8a --- /dev/null +++ b/csync/src/std/c_dir.c @@ -0,0 +1,189 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "c_private.h" +#include "c_macro.h" +#include "c_alloc.h" +#include "c_dir.h" +#include "c_string.h" + +int c_mkdirs(const char *path, mode_t mode) { + int tmp; + csync_stat_t sb; + mbchar_t *wpath = c_utf8_to_locale(path); + mbchar_t *swpath = NULL; + + if (path == NULL) { + errno = EINVAL; + return -1; + } + + if (_tstat(wpath, &sb) == 0) { + if (! S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + c_free_locale_string(wpath); + return -1; + } + } + + tmp = strlen(path); + while(tmp > 0 && path[tmp - 1] == '/') --tmp; + while(tmp > 0 && path[tmp - 1] != '/') --tmp; + while(tmp > 0 && path[tmp - 1] == '/') --tmp; + + if (tmp > 0) { + char subpath[tmp + 1]; + memcpy(subpath, path, tmp); + subpath[tmp] = '\0'; + swpath = c_utf8_to_locale(subpath); + if (_tstat(swpath, &sb) == 0) { + if (! S_ISDIR(sb.st_mode)) { + c_free_locale_string(swpath); + c_free_locale_string(wpath); + errno = ENOTDIR; + return -1; + } + } else if (errno != ENOENT) { + c_free_locale_string(wpath); + c_free_locale_string(swpath); + return -1; + } else if (c_mkdirs(subpath, mode) < 0) { + c_free_locale_string(swpath); + c_free_locale_string(wpath); + return -1; + } + } + tmp = _tmkdir(wpath, mode); + + c_free_locale_string(swpath); + c_free_locale_string(wpath); + + if ((tmp < 0) && (errno == EEXIST)) { + return 0; + } + return tmp; +} + +int c_rmdirs(const char *path) { + _TDIR *d; + struct _tdirent *dp; + csync_stat_t sb; + char *fname = NULL; + mbchar_t *wfname = NULL; + mbchar_t *wpath = c_utf8_to_locale(path); + char *rd_name = NULL; + + if ((d = _topendir(wpath)) != NULL) { + while( _tstat(wpath, &sb) == 0) { + /* if we can remove the directory we're done */ + if (_trmdir(wpath) == 0) { + break; + } + switch (errno) { + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + _tclosedir(d); + c_free_locale_string(wpath); + return 0; + } + + while ((dp = _treaddir(d)) != NULL) { + size_t len; + rd_name = c_utf8_from_locale(dp->d_name); + /* skip '.' and '..' */ + if( c_streq( rd_name, "." ) || c_streq( rd_name, ".." ) ) { + c_free_locale_string(rd_name); + continue; + } + + len = strlen(path) + strlen(rd_name) + 2; + fname = c_malloc(len); + if (fname == NULL) { + _tclosedir(d); + c_free_locale_string(rd_name); + c_free_locale_string(wpath); + return -1; + } + snprintf(fname, len, "%s/%s", path, rd_name); + wfname = c_utf8_to_locale(fname); + + /* stat the file */ + if (_tstat(wfname, &sb) != -1) { +#ifdef __unix__ + if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) { +#else + if (S_ISDIR(sb.st_mode)) { +#endif + if (_trmdir(wfname) < 0) { /* can't be deleted */ + if (errno == EACCES) { + _tclosedir(d); + SAFE_FREE(fname); + c_free_locale_string(wfname); + c_free_locale_string(rd_name); + c_free_locale_string(wpath); + return -1; + } + c_rmdirs(fname); + } + } else { + _tunlink(wfname); + } + } /* lstat */ + SAFE_FREE(fname); + c_free_locale_string(wfname); + c_free_locale_string(rd_name); + } /* readdir */ + + _trewinddir(d); + } + } else { + c_free_locale_string(wpath); + return -1; + } + c_free_locale_string(wpath); + _tclosedir(d); + return 0; +} + +int c_isdir(const char *path) { + csync_stat_t sb; + mbchar_t *wpath = c_utf8_to_locale(path); + int re = 0; + + if (path != NULL) { + if (_tstat (wpath, &sb) == 0 && S_ISDIR(sb.st_mode)) { + re = 1; + } + } + c_free_locale_string(wpath); + return re; +} + diff --git a/csync/src/std/c_dir.h b/csync/src/std/c_dir.h new file mode 100644 index 000000000..4b2333ba3 --- /dev/null +++ b/csync/src/std/c_dir.h @@ -0,0 +1,86 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_dir.h + * + * @brief Interface of the cynapses libc directory function + * + * @defgroup cynDirInternals cynapses libc directory functions + * @ingroup cynLibraryAPI + * + * @{ + */ + +#ifndef _C_DIR_H +#define _C_DIR_H + +#include + + +/** + * @brief Create parent directories as needed. + * + * The newly created directory will be owned by the effective user ID of the + * process. + * + * @param path The path to the directory to create. + * + * @param mode Specifies the permissions to use. It is modified + * by the process's umask in the usual way: the + * permissions of the created file are (mode & ~umask). + * + * @return 0 on success, < 0 on error with errno set: + * - EACCES The parent directory does not allow write + * permission to the process, or one of the directories + * - ENOTDIR if durl is not a directory + * - EINVAL NULL durl passed or smbc_init not called. + * - ENOMEM Insufficient memory was available. + * + * @see mkdir() + */ +int c_mkdirs(const char *path, mode_t mode); + +/** + * @brief Remove the directory and subdirectories including the content. + * + * This removes all directories and files recursivly. + * + * @param dir The directory to remove recusively. + * + * @return 0 on success, < 0 on error with errno set. + */ +int c_rmdirs(const char *dir); + +/** + * @brief Check if a path is a directory. + * + * @param path The path to check. + * + * @return 1 if the path is a directory, 0 if the path doesn't exist, is a + * file or can't be accessed. + */ +int c_isdir(const char *path); + +/** + * }@ + */ +#endif /* _CDIR_H */ + diff --git a/csync/src/std/c_file.c b/csync/src/std/c_file.c new file mode 100644 index 000000000..61984b3b6 --- /dev/null +++ b/csync/src/std/c_file.c @@ -0,0 +1,346 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef _WIN32 +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "c_file.h" +#include "c_string.h" + +#include "c_private.h" + +#ifdef _WIN32 +/* check if path is a symlink */ +int c_islink(const char *path) { + int re = 0; + + mbchar_t *wpath = 0; + DWORD dwAttrs; + WIN32_FIND_DATAW FindFileData; + HANDLE hFind; + + wpath = c_utf8_to_locale(path); + + dwAttrs = GetFileAttributesW(wpath); + if (dwAttrs != INVALID_FILE_ATTRIBUTES) { + + if ((dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT)) { + hFind = FindFirstFileW(wpath, &FindFileData ); + if (hFind != INVALID_HANDLE_VALUE) { + if( (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + (FindFileData.dwReserved0 & IO_REPARSE_TAG_SYMLINK) ) { + re = 1; + } + } + FindClose(hFind); + } + } + c_free_locale_string(wpath); + return re; +} +#endif + +/* check if path is a file */ +int c_isfile(const char *path) { + csync_stat_t sb; + mbchar_t *wpath = c_utf8_to_locale(path); + + int re = _tstat(wpath, &sb); + c_free_locale_string(wpath); + + if (re< 0) { + return 0; + } + +#ifdef __unix__ + if (S_ISREG(sb.st_mode) || S_ISLNK(sb.st_mode)) { +#else + if (S_ISREG(sb.st_mode)) { +#endif + return 1; + } + + return 0; +} + +/* copy file from src to dst, overwrites dst */ +#ifdef _WIN32 +int c_copy(const char* src, const char *dst, mode_t mode) { + int rc = -1; + mbchar_t *wsrc = 0; + mbchar_t *wdst = 0; + (void) mode; /* unused on win32 */ + if(src && dst) { + wsrc = c_utf8_to_locale(src); + wdst = c_utf8_to_locale(dst); + if (CopyFileW(wsrc, wdst, FALSE)) { + rc = 0; + } + c_free_locale_string(wsrc); + c_free_locale_string(wdst); + if( rc < 0 ) { + errno = GetLastError(); + } + } + return rc; +} +#else +int c_copy(const char* src, const char *dst, mode_t mode) { + int srcfd = -1; + int dstfd = -1; + int rc = -1; + ssize_t bread, bwritten; + csync_stat_t sb; + char buf[4096]; + + if (c_streq(src, dst)) { + return -1; + } + + srcfd = open(src, O_RDONLY, 0); + if (srcfd < 0) { + goto out; + } + + rc = _tfstat(srcfd, &sb); + if (rc < 0) { + goto out; + } + + if (S_ISDIR(sb.st_mode)) { + errno = EISDIR; + rc = -1; + goto out; + } + + if (mode == 0) { + mode = sb.st_mode; + } + + dstfd = open(dst, O_CREAT|O_WRONLY|O_TRUNC, mode); + if (dstfd < 0) { + rc = -1; + goto out; + } + + rc = _tfstat(dstfd, &sb); + if (rc == 0) { + if (S_ISDIR(sb.st_mode)) { + errno = EISDIR; + rc = -1; + goto out; + } + } + + for (;;) { + bread = read(srcfd, buf, sizeof(buf)); + if (bread == 0) { + /* done */ + break; + } else if (bread < 0) { + errno = ENODATA; + rc = -1; + goto out; + } + + bwritten = write(dstfd, buf, bread); + if (bwritten < 0) { + errno = ENODATA; + rc = -1; + goto out; + } + + if (bread != bwritten) { + errno = EFAULT; + rc = -1; + goto out; + } + } + +#ifdef __unix__ + fsync(dstfd); +#endif + + rc = 0; +out: + if (srcfd >= 0) { + close(srcfd); + } + if (dstfd >= 0) { + close(dstfd); + } + if (rc < 0 && c_isfile(dst)) { + unlink(dst); + } + return rc; +} +#endif + +int c_rename( const char *src, const char *dst ) { + mbchar_t *nuri = NULL; + mbchar_t *ouri = NULL; + int rc = 0; + + nuri = c_utf8_to_locale(dst); + if (nuri == NULL) { + return -1; + } + + ouri = c_utf8_to_locale(src); + if (ouri == NULL) { + c_free_locale_string(nuri); + return -1; + } + +#ifdef _WIN32 + { +#define MAX_TRIES_RENAME 3 + int err = 0; + int cnt = 0; + + do { + BOOL ok; + ok = MoveFileExW(ouri, + nuri, + MOVEFILE_COPY_ALLOWED + + MOVEFILE_REPLACE_EXISTING + + MOVEFILE_WRITE_THROUGH); + if (!ok) { + /* error */ + err = GetLastError(); + if( (err == ERROR_ACCESS_DENIED || + err == ERROR_LOCK_VIOLATION || + err == ERROR_SHARING_VIOLATION) && cnt < MAX_TRIES_RENAME ) { + cnt++; + Sleep(cnt*100); + continue; + } + } + break; + } while( 1 ); + if( err != 0 ) { + errno = err; + rc = -1; + } + } +#else + rc = rename(ouri, nuri); +#endif + + c_free_locale_string(nuri); + c_free_locale_string(ouri); + + return rc; +} + +int c_compare_file( const char *f1, const char *f2 ) { + mbchar_t *wf1, *wf2; + int fd1 = -1, fd2 = -1; + size_t size1, size2; + char buffer1[BUFFER_SIZE]; + char buffer2[BUFFER_SIZE]; + csync_stat_t stat1; + csync_stat_t stat2; + + int rc = -1; + + if(f1 == NULL || f2 == NULL) return -1; + + wf1 = c_utf8_to_locale(f1); + if(wf1 == NULL) { + return -1; + } + + wf2 = c_utf8_to_locale(f2); + if(wf2 == NULL) { + c_free_locale_string(wf1); + return -1; + } + +#ifdef _WIN32 + _fmode = _O_BINARY; +#endif + + fd1 = _topen(wf1, O_RDONLY); + if(fd1 < 0) { + rc = -1; + goto out; + } + + fd2 = _topen(wf2, O_RDONLY); + if(fd2 < 0) { + rc = -1; + goto out; + } + + /* compare size first. */ + rc = _tfstat(fd1, &stat1); + if (rc < 0) { + goto out; + } + + rc = _tfstat(fd2, &stat2); + if (rc < 0) { + goto out; + } + + /* if sizes are different, the files can not be equal. */ + if (stat1.st_size != stat2.st_size) { + rc = 0; + goto out; + } + + while( (size1 = read(fd1, buffer1, BUFFER_SIZE)) > 0 ) { + size2 = read( fd2, buffer2, BUFFER_SIZE ); + + if( size1 != size2 ) { + rc = 0; + goto out; + } + if(memcmp(buffer1, buffer2, size1) != 0) { + /* buffers are different */ + rc = 0; + goto out; + } + } + + rc = 1; + +out: + + if(fd1 > -1) close(fd1); + if(fd2 > -1) close(fd2); + + c_free_locale_string( wf1 ); + c_free_locale_string( wf2 ); + return rc; + +} diff --git a/csync/src/std/c_file.h b/csync/src/std/c_file.h new file mode 100644 index 000000000..d952b3976 --- /dev/null +++ b/csync/src/std/c_file.h @@ -0,0 +1,102 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_file.h + * + * @brief Interface of the cynapses libc file function + * + * @defgroup cynFileInternals cynapses libc file functions + * @ingroup cynLibraryAPI + * + * @{ + */ + +#ifndef _C_FILE_H +#define _C_FILE_H + +#include +#include + +#ifndef BUFFER_SIZE +#define BUFFER_SIZE (16 * 1024) +#endif + +#ifdef _WIN32 +/** + * @brief Check if a path is a link. + * + * @param path The path to check. + * + * @return 1 if the path is a symbolic link, 0 if the path doesn't + * exist or is something else. + */ +int c_islink(const char *path); +#endif + +/** + * @brief Check if a path is a regular file or a link. + * + * @param path The path to check. + * + * @return 1 if the path is a file, 0 if the path doesn't exist, is + * something else or can't be accessed. + */ +int c_isfile(const char *path); + +/** + * @brief copy a file from source to destination. + * + * @param src Path to the source file + * @param dst Path to the destination file + * @param mode File creation mode of the destination. If mode is 0 then the + * mode from the source will be used. + * + * @return 0 on success, less than 0 on error with errno set. + * EISDIR if src or dst is a file. + */ +int c_copy(const char *src, const char *dst, mode_t mode); + +/** + * @brief Compare the content of two files byte by byte. + * @param f1 Path of file 1 + * @param f2 Path of file 2 + * + * @return 0 if the files differ, 1 if the files are equal or -1 on + * error with errno set. + */ +int c_compare_file( const char *f1, const char *f2 ); + +/** + * @brief move a file from source to destination. + * + * @param src Path to the source file + * @param dst Path to the destination file + * + * @return 0 on success, less than 0 on error with errno set. + */ +int c_rename( const char *src, const char *dst ); + +/** + * }@ + */ +#endif /* _C_FILE_H */ + diff --git a/src/3rdparty/csync/c_jhash.h b/csync/src/std/c_jhash.h similarity index 100% rename from src/3rdparty/csync/c_jhash.h rename to csync/src/std/c_jhash.h diff --git a/csync/src/std/c_lib.h b/csync/src/std/c_lib.h new file mode 100644 index 000000000..3df4c598d --- /dev/null +++ b/csync/src/std/c_lib.h @@ -0,0 +1,67 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "c_macro.h" +#include "c_alloc.h" +#include "c_dir.h" +#include "c_file.h" +#include "c_list.h" +#include "c_path.h" +#include "c_rbtree.h" +#include "c_string.h" +#include "c_time.h" +#include "c_private.h" + +#ifndef UNIT_TESTING +#ifdef malloc +#undef malloc +#endif +#define malloc(x) DO_NOT_CALL_MALLOC__USE_C_MALLOC_INSTEAD + +#ifdef calloc +#undef calloc +#endif +#define calloc(x,y) DO_NOT_CALL_CALLOC__USE_C_CALLOC_INSTEAD + +#endif + +#ifdef realloc +#undef realloc +#endif +#define realloc(x,y) DO_NOT_CALL_REALLOC__USE_C_REALLOC_INSTEAD + +#ifdef dirname +#undef dirname +#endif +#define dirname(x) DO_NOT_CALL_MALLOC__USE_C_DIRNAME_INSTEAD + +#ifdef basename +#undef basename +#endif +#define basename(x) DO_NOT_CALL_MALLOC__USE_C_BASENAME_INSTEAD + +#ifdef strdup +#undef strdup +#endif +#define strdup(x) DO_NOT_CALL_STRDUP__USE_C_STRDUP_INSTEAD + diff --git a/csync/src/std/c_list.c b/csync/src/std/c_list.c new file mode 100644 index 000000000..5b98e777e --- /dev/null +++ b/csync/src/std/c_list.c @@ -0,0 +1,459 @@ +/* + * csync list -- a doubly-linked list + * + * This code is based on glist.{h,c} from glib + * ftp://ftp.gtk.org/pub/gtk/ + * Copyright (c) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (c) 2006-2013 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include "c_alloc.h" +#include "c_list.h" + +/* + * Adds a new element on to the end of the list. + */ +c_list_t *c_list_append(c_list_t *list, void *data) { + c_list_t *new; + c_list_t *last; + + new = c_list_alloc(); + if (new == NULL) { + return NULL; + } + new->data = data; + + if (list == NULL) { + return new; + } + + last = c_list_last(list); + + last->next = new; + new->prev = last; + + return list; +} + +/* + * Adds a new element on at the beginning of the list. + */ +c_list_t *c_list_prepend(c_list_t *list, void *data) { + c_list_t *new; + c_list_t *first; + + new = c_list_alloc(); + if (new == NULL) { + return NULL; + } + new->data = data; + + if (list != NULL) { + first = c_list_first(list); + + first->prev = new; + new->next = first; + } + + return new; +} + +/* + * Inserts a new element into the list at the given position. + */ +c_list_t *c_list_insert(c_list_t *list, void *data, long position) { + c_list_t *new; + c_list_t *temp; + + /* Handle wrong values for position */ + if (position < 0) { + return c_list_append (list, data); + } else if (position == 0) { + return c_list_prepend (list, data); + } + + temp = c_list_position(list, position); + + if (temp == NULL) { + return c_list_append(list, data); + } + + new = c_list_alloc(); + if (new == NULL) { + return NULL; + } + new->data = data; + + /* List is not empty */ + if (temp->prev) { + temp->prev->next = new; + new->prev = temp->prev; + } + + new->next = temp; + temp->prev = new; + + /* */ + if (temp == list) { + return new; + } + + return list; +} + +/* + * Inserts a new element into the list, using the given comparison function to + * determine its position. + */ +c_list_t *c_list_insert_sorted(c_list_t *list, void *data, + c_list_compare_fn fn) { + c_list_t *new; + c_list_t *temp; + int cmp; + + new = c_list_alloc(); + if (new == NULL) { + return NULL; + } + new->data = data; + + /* list is empty */ + if (list == NULL) { + return new; + } + + temp = list; + cmp = (fn)(data, temp->data); + + while ((temp->next) && (cmp > 0)) { + temp = temp->next; + + cmp = (fn)(data, temp->data); + } + + /* last element */ + if ((temp->next == NULL) && (cmp > 0)) { + temp->next = new; + new->prev = temp; + return list; + } + + /* first element */ + if (temp->prev) { + temp->prev->next = new; + new->prev = temp->prev; + } + + new->next = temp; + temp->prev = new; + + /* inserted before first */ + if (temp == list) { + return new; + } + + return list; +} + +/* + * Allocates space for one c_list_t element. + */ +c_list_t *c_list_alloc(void) { + c_list_t *list = NULL; + + list = c_malloc(sizeof(c_list_t)); + if (list == NULL) { + return NULL; + } + + list->data = NULL; + + list->prev = NULL; + list->next = NULL; + + return list; +} + +/* + * Removes an element from a c_list. If two elements contain the same data, + * only the first is removed. + */ +c_list_t *c_list_remove(c_list_t *list, void *data) { + c_list_t *temp; + + if (list == NULL || data == NULL) { + return NULL; + } + + temp = list; + + while (temp != NULL) { + if (temp->data != data) { + temp = temp->next; + } else { + /* not at first element */ + if (temp->prev) { + temp->prev->next = temp->next; + } + + /* not at last element */ + if (temp->next) { + temp->next->prev = temp->prev; + } + + /* first element */ + if (list == temp) { + list = list->next; + } + + SAFE_FREE(temp); + break; + } + } + + return list; +} + +/* + * Frees all elements from a c_list. + */ +void c_list_free(c_list_t *list) { + c_list_t *temp = NULL; + + if (list == NULL) { + return; + } + + list = c_list_last(list); + + while (list->prev != NULL) { + temp = list; + list = list->prev; + + SAFE_FREE(temp); + } + SAFE_FREE(list); +} + +/* + * Gets the next element in a c_list. + */ +c_list_t *c_list_next(c_list_t *list) { + if (list == NULL) { + return NULL; + } + + return list->next; +} + +/* + * Gets the previous element in a c_list. + */ +c_list_t *c_list_prev(c_list_t *list) { + if (list == NULL) { + return NULL; + } + + return list->prev; +} + +/* + * Gets the number of elements in a c_list + */ +unsigned long c_list_length(c_list_t *list) { + unsigned long length = 1; + + if (list == NULL) { + return 0; + } + + while (list->next) { + length++; + list = list->next; + } + + return length; +} + +/* + * Gets the first element in a c_list + */ +c_list_t *c_list_first(c_list_t *list) { + if (list != NULL) { + while (list->prev) { + list = list->prev; + } + } + + return list; +} + +/* + * Gets the last element in a c_list + */ +c_list_t *c_list_last(c_list_t *list) { + if (list != NULL) { + while (list->next) { + list = list->next; + } + } + + return list; +} + +/* + * Gets the element at the given positon in a c_list + */ +c_list_t *c_list_position(c_list_t *list, long position) { + if (list == NULL) { + return NULL; + } + + while ((position-- > 0) && list != NULL) { + list = list->next; + } + + return list; +} + +/* + * Finds the element in a c_list_t which contains the given data. + */ +c_list_t *c_list_find(c_list_t *list, const void *data) { + if (list == NULL) { + return NULL; + } + + while (list != NULL) { + if (list->data == data) { + break; + } + list = list->next; + } + return list; +} + +/* + * Finds an element, using a supplied function to find the desired + * element. + */ +c_list_t *c_list_find_custom(c_list_t *list, const void *data, + c_list_compare_fn fn) { + int cmp; + + if (list != NULL && fn != NULL) { + while (list != NULL) { + cmp = (*fn)(list->data, data); + if (cmp == 0) { + return list; + } + list = list->next; + } + } + + return NULL; +} + +/* + * Internal used function to merge 2 lists using a compare function + */ +static c_list_t *_c_list_merge(c_list_t *list1, c_list_t *list2, + c_list_compare_fn func) { + int cmp; + + /* lists are emty */ + if (list1 == NULL) { + return list2; + } else if (list2 == NULL) { + return list1; + } + + cmp = ((c_list_compare_fn) func)(list1->data, list2->data); + /* compare if it is smaller */ + if (cmp <= 0) { + list1->next = _c_list_merge(list1->next, list2, func); + if (list1->next) { + list1->next->prev = list1; + }return list1; + } else { + list2->next = _c_list_merge(list1, list2->next, func); + if (list2->next) { + list2->next->prev = list2; + } + return list2; + } +} + +/* + * Internally used function to split 2 lists. + */ +static c_list_t *_c_list_split(c_list_t *list) { + c_list_t *second = NULL; + + /* list is empty */ + if (list == NULL) { + return NULL; + } else if (list->next == NULL) { + /* list has only 1 element */ + return NULL; + } else { + /* split */ + second = list->next; + list->next = second->next; + /* is last element */ + if (list->next) { + list->next->prev = list; + } + + second->prev = NULL; + second->next = _c_list_split(second->next); + /* is last element */ + if (second->next) { + second->next->prev = second; + } + + return second; + } + + /* never reached */ + return NULL; +} + +/* + * Sorts the elements of a c_list. This is a merge sort. + */ +c_list_t *c_list_sort(c_list_t *list, c_list_compare_fn func) { + c_list_t *second; + + /* list is empty */ + if (list == NULL) { + return NULL; + } else if (list->next == NULL) { + /* list has only one element */ + return list; + } else { + /* split list */ + second = _c_list_split(list); + } + + return _c_list_merge(c_list_sort(list, func), c_list_sort(second, func), + func); +} + diff --git a/csync/src/std/c_list.h b/csync/src/std/c_list.h new file mode 100644 index 000000000..3d17761b1 --- /dev/null +++ b/csync/src/std/c_list.h @@ -0,0 +1,275 @@ +/* + * csync list -- a doubly-linked list + * + * This code is based on glist.{h,c} from glib + * ftp://ftp.gtk.org/pub/gtk/ + * Copyright (c) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (c) 2006-2013 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef _C_LIST_H +#define _C_LIST_H + +/** + * c_list -- a doubly-linked list. + * + * The c_list_t structure and its associated functions provide a standard + * doubly-linked list data structure. Each node has two links: one points to + * the previous node, or points to a null value or empty list if it is the + * first node; and one points to the next, or points to a null value or empty + * list if it is the final node. + * + * The data contained in each element can be simply pointers to any type of + * data. You are the owner of the data, this means you have to free the memory + * you have allocated for the data. + * + * @file c_list.h + */ + + +/** + * @typedef c_list_t + * Creates a type name for c_list_s + */ +typedef struct c_list_s c_list_t; +/** + * @struct c_list_s + * + * Used for each element in a doubly-linked list. The list can hold + * any kind of data. + */ +struct c_list_s { + /** Link to the next element in the list */ + struct c_list_s *next; + /** Link to the previous element in the list */ + struct c_list_s *prev; + + /** + * Holds the element's data, which can be a pointer to any kind of + * data. + */ + void *data; +}; + +/** + * Specifies the type of a comparison function used to compare two values. The + * value which should be returned depends on the context in which the + * c_list_compare_fn is used. + * + * @param a First parameter to compare with. + * + * @param b Second parameter to compare with. + * + * @return The function should return a number > 0 if the first + * parameter comes after the second parameter in the sort + * order. + */ +typedef int (*c_list_compare_fn) (const void *a, const void *b); + +/** + * Adds a new element on to the end of the list. + * + * @param list A pointer to c_list. + * + * @param data The data for the new element. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_append(c_list_t *list, void *data); + +/** + * Adds a new element on at the beginning of the list. + * + * @param list A pointer to c_list. + * + * @param data The data for the new element. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_prepend(c_list_t *list, void *data); + +/** + * Inserts a new element into the list at the given position. If the position + * is lesser than 0, the new element gets appended to the list, if the position + * is 0, we prepend the element and if the given position is greater than the + * length of the list, the element gets appended too. + * + * @param list A pointer to c_list. + * + * @param data The data for the new element. + * + * @param position The position to insert the element. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_insert(c_list_t *list, void *data, long position); + +/** + * Inserts a new element into the list, using the given comparison function to + * determine its position. + * + * @param list A pointer to c_list. + * + * @param data The data for the new element. + * + * @param fn The function to compare elements in the list. It + * should return a number > 0 if the first parameter comes + * after the second parameter in the sort order. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_insert_sorted(c_list_t *list, void *data, + c_list_compare_fn fn); + +/** + * Allocates space for one c_list_t element. + * + * @return A pointer to the newly-allocated element. + */ +c_list_t *c_list_alloc(void); + +/** + * Removes an element from a c_list. If two elements contain the same data, + * only the first is removed. + * + * @param list A pointer to c_list. + * + * @param data The data of the element to remove. + * + * @return The first element of the list, NULL on error. + */ +c_list_t *c_list_remove(c_list_t *list, void *data); + +/** + * Frees all elements from a c_list. + * + * @param list A pointer to c_list. + */ +void c_list_free(c_list_t *list); + +/** + * Gets the next element in a c_list. + * + * @param An element in a c_list. + * + * @return The next element, or NULL if there are no more + * elements. + */ +c_list_t *c_list_next(c_list_t *list); + +/** + * Gets the previous element in a c_list. + * + * @param An element in a c_list. + * + * @return The previous element, or NULL if there are no more + * elements. + */ +c_list_t *c_list_prev(c_list_t *list); + +/** + * Gets the number of elements in a c_list + * + * @param list A pointer to c_list. + * + * @return The number of elements + */ +unsigned long c_list_length(c_list_t *list); + +/** + * Gets the first element in a c_list + * + * @param list A pointer to c_list. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_first(c_list_t *list); + +/** + * Gets the last element in a c_list + * + * @param list A pointer to c_list. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_last(c_list_t *list); + +/** + * Gets the element at the given positon in a c_list. + * + * @param list A pointer to c_list. + * + * @param position The position of the element, counting from 0. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_position(c_list_t *list, long position); + +/** + * Finds the element in a c_list_t which contains the given data. + * + * @param list A pointer to c_list. + * + * @param data The data of the element to remove. + * + * @return The found element or NULL if it is not found. + */ +c_list_t *c_list_find(c_list_t *list, const void *data); + +/** + * Finds an element, using a supplied function to find the desired + * element. + * + * @param list A pointer to c_list. + * + * @param data The data of the element to remove. + * + * @param func The function to call for each element. It should + * return 0 when the desired element is found. + * + * @return The found element or NULL if it is not found. + */ +c_list_t *c_list_find_custom(c_list_t *list, const void *data, + c_list_compare_fn fn); + +/** + * Sorts the elements of a c_list. + * The algorithm used is Mergesort, because that works really well + * on linked lists, without requiring the O(N) extra space it needs + * when you do it on arrays. + * + * @param list A pointer to c_list. + * + * @param func The comparison function used to sort the c_list. This + * function is passed 2 elements of the GList and should + * return 0 if they are equal, a negative value if the + * first element comes before the second, or a positive + * value if the first element comes after the second. + * + * @return New start of the list, which may have changed, so make + * sure you store the new value. + */ +c_list_t *c_list_sort(c_list_t *list, c_list_compare_fn func); + +#endif /* _C_LIST_H */ + diff --git a/csync/src/std/c_macro.h b/csync/src/std/c_macro.h new file mode 100644 index 000000000..1c6986e21 --- /dev/null +++ b/csync/src/std/c_macro.h @@ -0,0 +1,114 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_macro.h + * + * @brief cynapses libc macro definitions + * + * @defgroup cynMacroInternals cynapses libc macro definitions + * @ingroup cynLibraryAPI + * + * @{ + */ +#ifndef _C_MACRO_H +#define _C_MACRO_H + +#include +#include + +#define INT_TO_POINTER(i) (void *) i +#define POINTER_TO_INT(p) *((int *) (p)) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((char *)(x), 0, sizeof(*(x))); } while(0) + +/** Free memory and zero the pointer */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free((void*)x); x=NULL;} } while(0) + +/** Get the smaller value */ +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +/** Get the bigger value */ +#define MAX(a,b) ((a) < (b) ? (b) : (a)) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/** Macro to make strerror_r work with -Werror=unused-result */ +#define C_STRERROR(errno, buf, size) if(strerror_r(errno, buf, size)) {} + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +#if (__GNUC__ >= 3) +# ifndef likely +# define likely(x) __builtin_expect(!!(x), 1) +# endif +# ifndef unlikely +# define unlikely(x) __builtin_expect(!!(x), 0) +# endif +#else /* __GNUC__ */ +# ifndef likely +# define likely(x) (x) +# endif +# ifndef unlikely +# define unlikely(x) (x) +# endif +#endif /* __GNUC__ */ + +/** + * }@ + */ + +#ifdef _WIN32 +/* missing errno codes on mingw */ +#ifndef ENOTBLK +#define ENOTBLK 15 +#endif +#ifndef ETXTBSY +#define ETXTBSY 26 +#endif +#ifndef ENOBUFS +#define ENOBUFS WSAENOBUFS +#endif +#endif /* _WIN32 */ + +#endif /* _C_MACRO_H */ + diff --git a/csync/src/std/c_path.c b/csync/src/std/c_path.c new file mode 100644 index 000000000..f0c422d5f --- /dev/null +++ b/csync/src/std/c_path.c @@ -0,0 +1,553 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include "c_private.h" +#include "c_alloc.h" +#include "c_path.h" + +/* + * dirname - parse directory component. + */ +char *c_dirname (const char *path) { + char *newbuf = NULL; + unsigned int len; + + if (path == NULL || *path == '\0') { + return c_strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return c_strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return c_strdup("."); + } else if (len == 1) { + return c_strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + newbuf = c_malloc(len + 1); + if (newbuf == NULL) { + return NULL; + } + + strncpy(newbuf, path, len); + newbuf[len] = '\0'; + + return newbuf; +} + +char *c_basename (const char *path) { + char *newbuf = NULL; + const char *s; + unsigned int len; + + if (path == NULL || *path == '\0') { + return c_strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return c_strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return c_strdup(path); + } + + newbuf = c_malloc(len + 1); + if (newbuf == NULL) { + return NULL; + } + + strncpy(newbuf, s, len); + newbuf[len] = '\0'; + + return newbuf; +} + + +char *c_tmpname(const char *templ) { + char *tmp = NULL; + char *target = NULL; + int rc; + int i = 0; + + if (!templ) { + goto err; + } + + /* If the template does not contain the XXXXXX it will be appended. */ + if( !strstr( templ, "XXXXXX" )) { + /* split up the path */ + char *path = c_dirname(templ); + char *base = c_basename(templ); + + if (!base) { + if (path) { + SAFE_FREE(path); + } + goto err; + } + /* Create real hidden files for unixoide. */ + if( path ) { +#ifdef _WIN32 + rc = asprintf(&target, "%s/%s.~XXXXXX", path, base); +#else + rc = asprintf(&target, "%s/.%s.~XXXXXX", path, base); +#endif + } else { +#ifdef _WIN32 + rc = asprintf(&target, "%s.~XXXXXX", base); +#else + rc = asprintf(&target, ".%s.~XXXXXX", base); +#endif + + } + SAFE_FREE(path); + SAFE_FREE(base); + + if (rc < 0) { + goto err; + } + } else { + target = c_strdup(templ); + } + + if (!target) { + goto err; + } + tmp = strstr( target, "XXXXXX" ); + if (!tmp) { + goto err; + } + + for (i = 0; i < 6; ++i) { +#ifdef _WIN32 + /* in win32 MAX_RAND is 32767, thus we can not shift that far, + * otherwise the last three chars are 0 + */ + int hexdigit = (rand() >> (i * 2)) & 0x1f; +#else + int hexdigit = (rand() >> (i * 5)) & 0x1f; +#endif + tmp[i] = hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0'; + } + + return target; + +err: + errno = EINVAL; + return NULL; +} + +int c_parse_uri(const char *uri, + char **scheme, + char **user, char **passwd, + char **host, unsigned int *port, + char **path) { + const char *p, *z; + + if (uri == NULL || *uri == '\0') { + return -1; + } + + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + p = z = uri; + + /* check for valid scheme; git+ssh, pop3 */ + while (isalpha((int) *p) || isdigit((int) *p) || + *p == '+' || *p == '-') { + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + p++; + } + + /* get scheme */ + if (*p == ':') { + if (scheme != NULL) { + *scheme = c_strndup(z, p - z); + + if (*scheme == NULL) { + errno = ENOMEM; + return -1; + } + } + p++; + z = p; + } + + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + p = z; + + /* do we have a hostname */ + if (p[0] == '/' && p[1] == '/') { + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + z += 2; + p = z; + + /* check for user and passwd */ + while (*p && *p != '@' && *p != '/') { + /* + * uri = scheme://user:password@host:port/path + * p = ^ or ^ + * z = ^ + */ + p++; + } + + /* check for user and password */ + if (*p == '@') { + const char *q; + + q = p; + + /* check if we have a password */ + while (q > z && *q != ':') { + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + * q = ^ + */ + q--; + } + + /* password found */ + if (*q == ':') { + if (user != NULL) { + *user = c_strndup(z, q - z); + if (*user == NULL) { + errno = ENOMEM; + if (scheme != NULL) SAFE_FREE(*scheme); + return -1; + } + } + + if (passwd != NULL) { + *passwd = c_strndup(q + 1, p - (q + 1)); + if (*passwd == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + if (user != NULL) SAFE_FREE(*user); + errno = ENOMEM; + return -1; + } + } + } else { + /* user only */ + if (user != NULL) { + *user = c_strndup(z, p - z); + if( *user == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + errno = ENOMEM; + return -1; + } + } + } + + p++; + z = p; + } + + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + p = z; + + /* check for IPv6 address */ + if (*p == '[') { + /* + * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path + * p = ^ + * z = ^ + */ + p++; + + /* check if we have a valid IPv6 address */ + while (*p && (isxdigit((int) *p) || *p == '.' || *p == ':')) { + /* + * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path + * p = ^ + * z = ^ + */ + p++; + } + + /* valid IPv6 address found */ + if (*p == ']') { + /* + * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path + * p = ^ + * z = ^ + */ + z++; + + if (host != NULL) { + *host = c_strndup(z, p - z); + if (*host == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + if (user != NULL) SAFE_FREE(*user); + if (passwd != NULL) SAFE_FREE(*passwd); + errno = ENOMEM; + return -1; + } + } + + /* + * uri = scheme://user:password@[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:port/path + * p = ^ + * z = ^ + */ + p++; + } else { + /* invalid IPv6 address, assume a hostname */ + p = z; + + while (*p && *p != ':' && *p != '/') { + p++; + /* + * uri = scheme://user:password@host:port/path + * p = ^ or ^ + * z = ^ + */ + } + + if (host != NULL) { + *host = c_strndup(z, p - z); + if (*host == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + if (user != NULL) SAFE_FREE(*user); + if (passwd != NULL) SAFE_FREE(*passwd); + errno = ENOMEM; + return -1; + } + } + } + } else { + /* check for hostname */ + while (*p && *p != ':' && *p != '/') { + /* + * uri = scheme://user:password@host:port/path + * p = ^ ^ + * z = ^ + */ + p++; + } + + if (host != NULL) { + *host = c_strndup(z, p - z); + if (*host == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + if (user != NULL) SAFE_FREE(*user); + if (passwd != NULL) SAFE_FREE(*passwd); + errno = ENOMEM; + return -1; + } + } + } + + /* check for port */ + if (*p == ':') { + char **e = NULL; + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + z = ++p; + + /* get only the digits */ + while (isdigit((int) *p)) { + /* + * uri = scheme://user:password@host:port/path + * p = ^ + * z = ^ + */ + e = (char **) &p; + p++; + } + + if (port != NULL) { + *port = strtoul(z, e, 0); + } + + /* + * uri = scheme://user:password@host:port/path + * p = ^ + */ + } + } + + if (*p == '\0') { + return 0; + } + + /* get the path with the leading slash */ + if (*p == '/') { + if (path != NULL) { + *path = c_strdup(p); + if (*path == NULL) { + if (scheme != NULL) SAFE_FREE(*scheme); + if (user != NULL) SAFE_FREE(*user); + if (passwd != NULL) SAFE_FREE(*passwd); + if (host != NULL) SAFE_FREE(*host); + errno = ENOMEM; + return -1; + } + } + + return 0; + } + + return -1; +} + + +/* + * http://refactormycode.com/codes/1345-extracting-directory-filename-and-extension-from-a-path + * Allocate a block of memory that holds the PATHINFO at the beginning + * followed by the actual path. Two extra bytes are allocated (+3 instead + * of just +1) to deal with shifting the filename and extension to protect the trailing '/' + * and the leading '.'. These extra bytes also double as the empty string, as + * well as a pad to keep from reading past the memory block. + * + */ +C_PATHINFO * c_split_path(const char* pathSrc) +{ + size_t length = strlen(pathSrc); + size_t len=0; + C_PATHINFO * pathinfo = (C_PATHINFO *) c_malloc(sizeof(C_PATHINFO) + length + 3); + + if (pathinfo) + { + char * path = (char *) &pathinfo[1]; // copy of the path + char * theEnd = &path[length + 1]; // second null terminator + char * extension; + char * lastSep; + + // Copy the original string and double null terminate it. + strcpy(path, pathSrc); + *theEnd = '\0'; + pathinfo->directory = theEnd; // Assume no path + pathinfo->extension = theEnd; // Assume no extension + pathinfo->filename = path; // Assume filename only + + lastSep = strrchr(path, '/'); + if (lastSep) + { + pathinfo->directory = path; // Pick up the path + + memmove(lastSep + 1, lastSep, strlen(lastSep)); + *lastSep++ ='/'; + *lastSep++ ='\0'; // Truncate directory + + pathinfo->filename = lastSep; // Pick up name after path + } + + // Start at the second character of the filename to handle + // filenames that start with '.' like ".login". + // We don't overrun the buffer in the cases of an empty path + // or a path that looks like "/usr/bin/" because of the extra + // byte. + + + extension = strrchr(&pathinfo->filename[1], '.'); + if (extension) + { + // Shift the extension over to protect the leading '.' since + // we need to truncate the filename. + memmove(extension + 1, extension, strlen(extension)); + pathinfo->extension = extension + 1; + + *extension = '\0'; // Truncate filename + } + else + { + len=strlen(pathinfo->filename); + if(len>1) + { + //tmp files from kate/kwrite "somefile~": '~' should be the extension + if(pathinfo->filename[len-1]=='~') + { + extension = &pathinfo->filename[len-1]; + memmove(extension + 1, extension, strlen(extension)); + pathinfo->extension = extension + 1; + *extension = '\0'; // Truncate filename + } + } + } + + } + + return pathinfo; +} + + diff --git a/csync/src/std/c_path.h b/csync/src/std/c_path.h new file mode 100644 index 000000000..e2d25eb3d --- /dev/null +++ b/csync/src/std/c_path.h @@ -0,0 +1,136 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_path.h + * + * @brief Interface of the cynapses libc path functions + * + * @defgroup cynPathInternals cynapses libc path functions + * @ingroup cynLibraryAPI + * + * @{ + */ + +#ifndef _C_PATH_H +#define _C_PATH_H + +#include "c_macro.h" + +/** + * @brief Parse directory component. + * + * dirname breaks a null-terminated pathname string into a directory component. + * In the usual case, c_dirname() returns the string up to, but not including, + * the final '/'. Trailing '/' characters are not counted as part of the + * pathname. The caller must free the memory. + * + * @param path The path to parse. + * + * @return The dirname of path or NULL if we can't allocate memory. If path + * does not contain a slash, c_dirname() returns the string ".". If + * path is the string "/", it returns the string "/". If path is + * NULL or an empty string, "." is returned. + */ +char *c_dirname(const char *path); + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * c_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @param path The path to parse. + * + * @return The filename of path or NULL if we can't allocate memory. If path + * is a the string "/", basename returns the string "/". If path is + * NULL or an empty string, "." is returned. + */ +char *c_basename (const char *path); + +/** + * @brief Make a temporary filename. + * + * @param templ The template to replace. If the template contains six X like + * 'XXXXXX', these are replaced by a random string. If not, the + * templ is interpreted as a path, and a name to a hidden file + * with six random is returned. + * The caller has to free the memory. + * + * @return a poitner to the random hidden filename or NULL. + */ +char *c_tmpname(const char *templ); + +/** + * @brief parse a uri and split it into components. + * + * parse_uri parses an uri in the format + * + * [:][//[[:]@][:]]/[] + * + * into its compoments. If you only want a special component, + * pass NULL for all other components. All components will be allocated if they have + * been found. + * + * @param uri The uri to parse. + * @param scheme String for the scheme component + * @param user String for the username component + * @param passwd String for the password component + * @param host String for the password component + * @param port Integer for the port + * @param path String for the path component with a leading slash. + * + * @return 0 on success, < 0 on error. + */ +int c_parse_uri(const char *uri, char **scheme, char **user, char **passwd, + char **host, unsigned int *port, char **path); + +/** + * @brief Parts of a path. + * + * @param directory '\0' terminated path including the final '/' + * + * @param filename '\0' terminated string + * + * @param extension '\0' terminated string + * + */ +typedef struct +{ + char * directory; + char * filename; + char * extension; +} C_PATHINFO; + +/** + * @brief Extracting directory, filename and extension from a path. + * + * @param pathSrc The path to parse. + * + * @return Returns a C_PATHINFO structure that should be freed using SAFE_FREE(). + */ +C_PATHINFO * c_split_path(const char* pathSrc); + + +/** + * }@ + */ +#endif /* _C_PATH_H */ diff --git a/csync/src/std/c_private.h b/csync/src/std/c_private.h new file mode 100644 index 000000000..fb50e82c8 --- /dev/null +++ b/csync/src/std/c_private.h @@ -0,0 +1,161 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Dominik Schmidt + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _C_PRIVATE_H +#define _C_PRIVATE_H + +#include "config_csync.h" + +/* cross platform defines */ +#include "config_csync.h" +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#endif + +#include + +#ifdef _WIN32 +#define EDQUOT 0 +#define ENODATA 0 +#define S_IRGRP 0 +#define S_IROTH 0 +#define S_IXGRP 0 +#define S_IXOTH 0 + +#define S_IFSOCK 10000 /* dummy val on Win32 */ +#define S_IFLNK 10001 /* dummy val on Win32 */ + +#define O_NOFOLLOW 0 +#define O_NOCTTY 0 + +#define uid_t int +#define gid_t int +#define nlink_t int +#define getuid() 0 +#define geteuid() 0 +#else +#include +#endif + +#ifndef ENODATA +#define ENODATA EPIPE +#endif + + +#ifdef _WIN32 +typedef struct stat64 csync_stat_t; +#define _FILE_OFFSET_BITS 64 +#else +typedef struct stat csync_stat_t; +#endif + +#ifndef O_NOATIME +#define O_NOATIME 0 +#endif + +#ifndef ENODATA +#define ENODATA EBADF +#endif + +#if !defined(HAVE_ASPRINTF) && defined(HAVE___MINGW_ASPRINTF) +#define asprintf __mingw_asprintf +#endif + +#ifndef HAVE_STRERROR_R +#define strerror_r(errnum, buf, buflen) snprintf(buf, buflen, "%s", strerror(errnum)) +#endif + +#ifndef HAVE_LSTAT +#define lstat _stat +#endif + +/* tchar definitions for clean win32 filenames */ +#ifndef _UNICODE +#define _UNICODE +#endif + +#if defined _WIN32 && defined _UNICODE +typedef wchar_t mbchar_t; +#define _topen _wopen +#define _tdirent _wdirent +#define _TDIR _WDIR +#define _topendir _wopendir +#define _tclosedir _wclosedir +#define _treaddir _wreaddir +#define _trewinddir _wrewinddir +#define _ttelldir _wtelldir +#define _tseekdir _wseekdir +#define _tcreat _wcreat +#define _tstat _wstat64 +#define _tfstat _fstat64 +#define _tunlink _wunlink +#define _tmkdir(X,Y) _wmkdir(X) +#define _trmdir _wrmdir +#define _tchmod _wchmod +#define _trewinddir _wrewinddir +#define _tchown(X, Y, Z) 0 /* no chown on Win32 */ +#else +typedef char mbchar_t; +#define _tdirent dirent +#define _topen open +#define _TDIR DIR +#define _topendir opendir +#define _tclosedir closedir +#define _treaddir readdir +#define _trewinddir rewinddir +#define _ttelldir telldir +#define _tseekdir seekdir +#define _tcreat creat +#define _tstat lstat +#define _tfstat fstat +#define _tunlink unlink +#define _tmkdir(X,Y) mkdir(X,Y) +#define _trmdir rmdir +#define _tchmod chmod +#define _trewinddir rewinddir +#define _tchown(X,Y,Z) chown(X,Y,Z) +#endif + +#ifdef WITH_ICONV +/** @internal */ +int c_setup_iconv(const char* to); +/** @internal */ +int c_close_iconv(void); +#endif + +/* FIXME: Implement TLS for OS X */ +#if defined(__GNUC__) && !defined(__APPLE__) +# define CSYNC_THREAD __thread +#elif defined(_MSC_VER) +# define CSYNC_THREAD __declspec(thread) +#else +# define CSYNC_THREAD +#endif + +#endif //_C_PRIVATE_H + +/* vim: set ft=c.doxygen ts=8 sw=2 et cindent: */ diff --git a/csync/src/std/c_rbtree.c b/csync/src/std/c_rbtree.c new file mode 100644 index 000000000..4b5efb4cd --- /dev/null +++ b/csync/src/std/c_rbtree.c @@ -0,0 +1,754 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2003-2004 by Andrew Suffield + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * This code was originally released under GPL but Andrew Suffield agreed to + * change it to LGPL. + */ + +/* + * static function don't have NULL pointer checks, segfaults are intended. + */ + +#include +#include +#include + +#include "c_alloc.h" +#include "c_rbtree.h" + +#define NIL &_sentinel /* all leafs are sentinels */ +static c_rbnode_t _sentinel = {NULL, NIL, NIL, NULL, NULL, BLACK}; + +int c_rbtree_create(c_rbtree_t **rbtree, c_rbtree_compare_func *key_compare, c_rbtree_compare_func *data_compare) { + c_rbtree_t *tree = NULL; + + if (rbtree == NULL || key_compare == NULL || data_compare == NULL) { + errno = EINVAL; + return -1; + } + + tree = c_malloc(sizeof(*tree)); + if (tree == NULL) { + return -1; + } + + tree->root = NIL; + tree->key_compare = key_compare; + tree->data_compare = data_compare; + tree->size = 0; + + *rbtree = tree; + + return 0; +} + +static c_rbnode_t *_rbtree_subtree_dup(const c_rbnode_t *node, c_rbtree_t *new_tree, c_rbnode_t *new_parent) { + c_rbnode_t *new_node = NULL; + + new_node = (c_rbnode_t*) c_malloc(sizeof(c_rbnode_t)); + + new_node->tree = new_tree; + new_node->data = node->data; + new_node->color = node->color; + new_node->parent = new_parent; + + if (node->left == NIL) { + new_node->left = NIL; + } else { + new_node->left = _rbtree_subtree_dup(node->left, new_tree, new_node); + } + + if (node->right == NIL) { + new_node->right = NIL; + } else { + new_node->right = _rbtree_subtree_dup(node->right, new_tree, new_node); + } + + return new_node; +} + +c_rbtree_t *c_rbtree_dup(const c_rbtree_t *tree) { + c_rbtree_t *new_tree = NULL; + + new_tree = (c_rbtree_t*) c_malloc(sizeof(c_rbtree_t)); + + new_tree->key_compare = tree->key_compare; + new_tree->data_compare = tree->data_compare; + new_tree->size = tree->size; + new_tree->root = _rbtree_subtree_dup(tree->root, new_tree, NULL); + + return new_tree; +} + +static int _rbtree_subtree_free(c_rbnode_t *node) { + assert(node); + + if (node->left != NIL) { + if (_rbtree_subtree_free(node->left) < 0) { + /* TODO: set errno? ECANCELED? */ + return -1; + } + } + + if (node->right != NIL) { + if (_rbtree_subtree_free(node->right) < 0) { + /* TODO: set errno? ECANCELED? */ + return -1; + } + } + + SAFE_FREE(node); + + return 0; +} + +int c_rbtree_free(c_rbtree_t *tree) { + if (tree == NULL) { + errno = EINVAL; + return -1; + } + + if (tree->root != NIL) { + _rbtree_subtree_free(tree->root); + } + + SAFE_FREE(tree); + + return 0; +} + +static int _rbtree_subtree_walk(c_rbnode_t *node, void *data, c_rbtree_visit_func *visitor) { + assert(node); + assert(data); + assert(visitor); + + if (node == NIL) { + return 0; + } + + if (_rbtree_subtree_walk(node->left, data, visitor) < 0) { + return -1; + } + + if ((*visitor)(node->data, data) < 0) { + return -1; + } + + if (_rbtree_subtree_walk(node->right, data, visitor) < 0) { + return -1; + } + + return 0; +} + +int c_rbtree_walk(c_rbtree_t *tree, void *data, c_rbtree_visit_func *visitor) { + if (tree == NULL || data == NULL || visitor == NULL) { + errno = EINVAL; + return -1; + } + + if (_rbtree_subtree_walk(tree->root, data, visitor) < 0) { + return -1; + } + + return 0; +} + +static c_rbnode_t *_rbtree_subtree_head(c_rbnode_t *node) { + assert(node); + + if (node == NIL) { + return node; + } + + while (node->left != NIL) { + node = node->left; + } + + return node; +} + +static c_rbnode_t *_rbtree_subtree_tail(c_rbnode_t *node) { + assert(node); + + if (node == NIL) { + return node; + } + + while (node->right != NIL) { + node = node->right; + } + + return node; +} + +c_rbnode_t *c_rbtree_head(c_rbtree_t *tree) { + c_rbnode_t *node = NULL; + + if (tree == NULL) { + errno = EINVAL; + return NULL; + } + + node = _rbtree_subtree_head(tree->root); + + return node != NIL ? node : NULL; +} + +c_rbnode_t *c_rbtree_tail(c_rbtree_t *tree) { + c_rbnode_t *node = NULL; + + if (tree == NULL) { + errno = EINVAL; + return NULL; + } + + node = _rbtree_subtree_tail(tree->root); + + return node != NIL ? node : NULL; +} + +c_rbnode_t *c_rbtree_node_next(c_rbnode_t *node) { + c_rbnode_t *parent = NULL; + + if (node == NULL) { + errno = EINVAL; + return NULL; + } + + if (node->right != NIL) { + c_rbnode_t *next = NULL; + next = _rbtree_subtree_head(node->right); + + return next != NIL ? next : NULL; + } + + parent = node->parent; + while (parent && node == parent->right) { + node = parent; + parent = node->parent; + } + + return parent != NULL ? parent : NULL; +} + +c_rbnode_t *c_rbtree_node_prev(c_rbnode_t *node) { + c_rbnode_t *parent = NULL; + + if (node == NULL) { + return NULL; + } + + if (node->left != NIL) { + c_rbnode_t *prev = NULL; + prev = _rbtree_subtree_tail(node->left); + return prev != NIL ? prev : NULL; + } + + parent = node->parent; + while (parent && node == parent->left) { + node = parent; + parent = node->parent; + } + + return parent != NULL ? parent : NULL; +} + +c_rbnode_t *c_rbtree_find(c_rbtree_t *tree, const void *key) { + int cmp = 0; + c_rbnode_t *node = NULL; + + if (tree == NULL) { + errno = EINVAL; + return NULL; + } + node = tree->root; + + while (node != NIL) { + cmp = tree->key_compare(key, node->data); + if (cmp == 0) { + return node; + } + + if (cmp < 0) { + node = node->left; + } else { + node = node->right; + } + } + + return NULL; +} + +static void _rbtree_subtree_left_rotate(c_rbnode_t *x) { + c_rbnode_t *y = NULL; + + assert(x); + + y = x->right; + + /* establish x-right link */ + x->right = y->left; + + if (y->left != NIL) { + y->left->parent = x; + } + + /* establish y->parent link */ + if (y != NIL) { + y->parent = x->parent; + } + + if (x->parent) { + if (x == x->parent->left) { + x->parent->left = y; + } else { + x->parent->right = y; + } + } else { + x->tree->root = y; + } + + /* link x and y */ + y->left = x; + if (x != NIL) { + x->parent = y; + } +} + +/* rotat node x to the right */ +static void _rbtree_subtree_right_rotate(c_rbnode_t *x) { + c_rbnode_t *y = NULL; + + assert(x); + + y = x->left; + + /* establish x->left link */ + x->left = y->right; + + if (y->right != NIL) { + y->right->parent = x; + } + + /* establish y->parent link */ + if (y != NIL) { + y->parent = x->parent; + } + + if (x->parent) { + if (x == x->parent->right) { + x->parent->right = y; + } else { + x->parent->left = y; + } + } else { + x->tree->root = y; + } + + /* link x and y */ + y->right = x; + if (x != NIL) { + x->parent = y; + } +} + +int c_rbtree_insert(c_rbtree_t *tree, void *data) { + int cmp = 0; + c_rbnode_t *current = NULL; + c_rbnode_t *parent = NULL; + c_rbnode_t *x = NULL; + + if (tree == NULL) { + errno = EINVAL; + return -1; + } + + /* First we do a classic binary tree insert */ + current = tree->root; + parent = NULL; + + while (current != NIL) { + cmp = tree->data_compare(data, current->data); + parent = current; + if (cmp == 0) { + return 1; + } else if (cmp < 0) { + current = current->left; + } else { + current = current->right; + } + } + + x = (c_rbnode_t *) c_malloc(sizeof(c_rbnode_t)); + if (x == NULL) { + errno = ENOMEM; + return -1; + } + + x->tree = tree; + x->data = data; + x->parent = parent; + x->left = NIL; + x->right = NIL; + x->color = RED; + + if (parent) { + /* Note that cmp still contains the comparison of data with + * parent->data, from the last pass through the loop above + */ + if (cmp < 0) { + parent->left = x; + } else { + parent->right = x; + } + } else { + tree->root = x; + } + + /* Insert fixup - check red-black properties */ + while (x != tree->root && x->parent->color == RED) { + /* we have a violation */ + if (x->parent == x->parent->parent->left) { + c_rbnode_t *y = NULL; + + y = x->parent->parent->right; + if (y->color == RED) { + x->parent->color = BLACK; + y->color = BLACK; + x->parent->parent->color = RED; + x = x->parent->parent; + } else { + /* uncle is back */ + if (x == x->parent->right) { + /* make x a left child */ + x = x->parent; + _rbtree_subtree_left_rotate(x); + } + x->parent->color = BLACK; + x->parent->parent->color = RED; + _rbtree_subtree_right_rotate(x->parent->parent); + } + } else { + c_rbnode_t *y = NULL; + + y = x->parent->parent->left; + if (y->color == RED) { + x->parent->color = BLACK; + y->color = BLACK; + x->parent->parent->color = RED; + x = x->parent->parent; + } else { + /* uncle is back */ + if (x == x->parent->left) { + x = x->parent; + _rbtree_subtree_right_rotate(x); + } + x->parent->color = BLACK; + x->parent->parent->color = RED; + _rbtree_subtree_left_rotate(x->parent->parent); + } + } + } /* end while */ + tree->root->color = BLACK; + + tree->size++; + + return 0; +} + +int c_rbtree_node_delete(c_rbnode_t *node) { + c_rbtree_t *tree; + c_rbnode_t *y; + c_rbnode_t *x; + + if (node == NULL || node == NIL) { + errno = EINVAL; + return -1; + } + + tree = node->tree; + + if (node->left == NIL || node->right == NIL) { + /* y has a NIL node as a child */ + y = node; + } else { + /* find tree successor with a NIL node as a child */ + y = node; + while(y->left != NIL) { + y = y->left; + } + } + + /* x is y's only child */ + if (y->left != NIL) { + x = y->left; + } else { + x = y->right; + } + + /* remove y from the parent chain */ + x->parent = y->parent; + + if (y->parent) { + if (y == y->parent->left) { + y->parent->left = x; + } else { + y->parent->right = x; + } + } else { + y->tree->root = x; + } + + /* If y is not the node we're deleting, splice it in place of that + * node + * + * The traditional code would call for us to simply copy y->data, but + * that would invalidate the wrong pointer - there might be external + * references to this node, and we must preserve its address. + */ + if (y != node) { + /* Update y */ + y->parent = node->parent; + y->left = node->left; + y->right = node->right; + + /* Update the children and the parent */ + if (y->left != NIL) { + y->left->parent = y; + } + if (y->right != NIL) { + y->right->parent = y; + } + if (y->parent != NULL) { + if (node == y->parent->left) { + y->parent->left = y; + } else { + y->parent->right = y; + } + } else { + y->tree->root = y; + } + } + + if (y->color == BLACK) { + while (x != y->tree->root && x->color == BLACK) { + if (x == x->parent->left) { + c_rbnode_t *w = NULL; + + w = x->parent->right; + + if (w->color == RED) { + w->color = BLACK; + x->parent->color = RED; + _rbtree_subtree_left_rotate(x->parent); + w = x->parent->right; + } + + if (w->left->color == BLACK && w->right->color == BLACK) { + w->color = RED; + x = x->parent; + } else { + if (w->right->color == BLACK) { + w->left->color = BLACK; + w->color = RED; + _rbtree_subtree_right_rotate(w); + w = x->parent->right; + } + w->color = x->parent->color; + x->parent->color = BLACK; + w->right->color = BLACK; + _rbtree_subtree_left_rotate(x->parent); + x = y->tree->root; + } + } else { + c_rbnode_t *w = NULL; + + w = x->parent->left; + if (w->color == RED) { + w->color = BLACK; + x->parent->color = RED; + _rbtree_subtree_right_rotate(x->parent); + w = x->parent->left; + } + + if (w->right->color == BLACK && w->left->color == BLACK) { + w->color = RED; + x = x->parent; + } else { + if (w->left->color == BLACK) { + w->right->color = BLACK; + w->color = RED; + _rbtree_subtree_left_rotate(w); + w = x->parent->left; + } + w->color = x->parent->color; + x->parent->color = BLACK; + w->left->color = BLACK; + _rbtree_subtree_right_rotate(x->parent); + x = y->tree->root; + } + } + } + x->color = BLACK; + } /* end if: y->color == BLACK */ + + /* node has now been spliced out of the tree */ + SAFE_FREE(y); + tree->size--; + + return 0; +} + +static int _rbtree_subtree_check_black_height(c_rbnode_t *node) { + int left = 0; + int right = 0; + + assert(node); + + if (node == NIL) { + return 0; + } + + left = _rbtree_subtree_check_black_height(node->left); + right = _rbtree_subtree_check_black_height(node->right); + if (left != right) { + return -1; + } + + return left + (node->color == BLACK); +} + +int c_rbtree_check_sanity(c_rbtree_t *tree) { + c_rbnode_t *node = NULL; + c_rbnode_t *prev = NULL; + c_rbnode_t *next = NULL; + c_rbnode_t *tail = NULL; + size_t size = 0; + + if (tree == NULL) { + errno = EINVAL; + return -1; + } + + if (! tree->key_compare || ! tree->data_compare) { + errno = EINVAL; + return -2; + } + + /* Iterate the tree */ + tail = c_rbtree_tail(tree); + for (node = c_rbtree_head(tree); node; node = next) { + if (node->tree != tree) { + return -4; + } + + /* We should never see a nil while iterating */ + if (node == NIL) { + return -5; + } + + /* node == tree-root iff node->parent == NIL */ + if (node == tree->root) { + if (node->parent != NULL) { + return -6; + } + } else { + if (node->parent == NULL) { + return -7; + } + } + + /* Invertability of the iterate functions */ + if (prev != c_rbtree_node_prev(node)) { + return -8; + } + + /* Check the iteration sequence */ + if (prev) { + if (tree->data_compare(prev->data, node->data) > 0) { + return -9; + } + + /* And the other way around, to make sure data_compare is stable */ + if (tree->data_compare(node->data, prev->data) < 0) { + return -10; + } + } + + /* The binary tree property */ + if (node->left != NIL) { + if (tree->data_compare(node->left->data, node->data) > 0) { + return -11; + } + + if (tree->data_compare(node->data, node->left->data) < 0) { + return -11; + } + } + + if (node->right != NIL) { + if (tree->data_compare(node->data, node->right->data) > 0) { + return -12; + } + + if (tree->data_compare(node->right->data, node->data) < 0) { + return -13; + } + } + + /* Red-black tree property 3: red nodes have black children */ + if (node->color == RED) { + if (node->left->color == RED) { + return -14; + } + if (node->right->color == RED) { + return -15; + } + } + + /* next == NULL if node == tail */ + next = c_rbtree_node_next(node); + if (next) { + if (node == tail) { + return -16; + } + } else { + if (node != tail) { + return -17; + } + } + + prev = node; + size++; + } /* end for loop */ + + if (size != tree->size) { + return -18; + } + + if (_rbtree_subtree_check_black_height(tree->root) < 0) { + return -19; + } + + return 0; +} diff --git a/csync/src/std/c_rbtree.h b/csync/src/std/c_rbtree.h new file mode 100644 index 000000000..771c83bc3 --- /dev/null +++ b/csync/src/std/c_rbtree.h @@ -0,0 +1,311 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2003-2004 by Andrew Suffield + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_rbtree.h + * + * @brief Interface of the cynapses libc red-black tree implementation + * + * A red-black tree is a type of self-balancing binary search tree. It is + * complex, but has good worst-case running time for its operations and is + * efficient in practice: it can search, insert, and delete in O(log n) + * time, where n is the number of elements in the tree. + * + * In red-black trees, the leaf nodes are not relevant and do not contain + * data. Therefore we use a sentinal node to save memory. All references + * from internal nodes to leaf nodes instead point to the sentinel node. + * + * In a red-black tree each node has a color attribute, the value of which + * is either red or black. In addition to the ordinary requirements imposed + * on binary search trees, the following additional requirements of any + * valid red-black tree apply: + * + * 1. A node is either red or black. + * 2. The root is black. + * 3. All leaves are black, even when the parent is black + * (The leaves are the null children.) + * 4. Both children of every red node are black. + * 5. Every simple path from a node to a descendant leaf contains the same + * number of black nodes, either counting or not counting the null black + * nodes. (Counting or not counting the null black nodes does not affect + * the structure as long as the choice is used consistently.). + * + * These constraints enforce a critical property of red-black trees: that the + * longest path from the root to a leaf is no more than twice as long as the + * shortest path from the root to a leaf in that tree. The result is that the + * tree is roughly balanced. Since operations such as inserting, deleting, and + * finding values requires worst-case time proportional to the height of the + * tree, this theoretical upper bound on the height allows red-black trees to + * be efficient in the worst-case, unlike ordinary binary search trees. + * + * http://en.wikipedia.org/wiki/Red-black_tree + * + * @defgroup cynRBTreeInternals cynapses libc red-black tree functions + * @ingroup cynLibraryAPI + * + * @{ + */ +#ifndef _C_RBTREE_H +#define _C_RBTREE_H + +/* Forward declarations */ +struct c_rbtree_s; typedef struct c_rbtree_s c_rbtree_t; +struct c_rbnode_s; typedef struct c_rbnode_s c_rbnode_t; + +/** + * Define the two colors for the red-black tree + */ +enum xrbcolor_e { BLACK = 0, RED }; typedef enum xrbcolor_e xrbcolor_t; + +/** + * @brief Callback function to compare a key with the data from a + * red-black tree node. + * + * @param key key as a generic pointer + * @param data data as a generic pointer + * + * @return It returns an integer less than, equal to, or greater than zero + * depending on the key or data you use. The function is similar + * to strcmp(). + */ +typedef int c_rbtree_compare_func(const void *key, const void *data); + +/** + * @brief Visit function for the c_rbtree_walk() function. + * + * This function will be called by c_rbtree_walk() for every node. It is up to + * the developer what the function does. The fist parameter is a node object + * the second can be of any kind. + * + * @param obj The node data that will be passed by c_rbtree_walk(). + * @param data Generic data pointer. + * + * @return 0 on success, < 0 on error. You should set errno. + * + */ +typedef int c_rbtree_visit_func(void *, void *); + +/** + * Structure that represents a red-black tree + */ +struct c_rbtree_s { + c_rbnode_t *root; + c_rbtree_compare_func *key_compare; + c_rbtree_compare_func *data_compare; + size_t size; +}; + +/** + * Structure that represents a node of a red-black tree + */ +struct c_rbnode_s { + c_rbtree_t *tree; + c_rbnode_t *left; + c_rbnode_t *right; + c_rbnode_t *parent; + void *data; + xrbcolor_t color; +}; + +/** + * @brief Create the red-black tree + * + * @param rbtree The pointer to assign the allocated memory. + * + * @param key_compare Callback function to compare a key with the data + * inside a reb-black tree node. + * + * @param data_compare Callback function to compare a key as data with thee + * data inside a red-black tree node. + * + * @return 0 on success, -1 if an error occured with errno set. + */ +int c_rbtree_create(c_rbtree_t **rbtree, c_rbtree_compare_func *key_compare, c_rbtree_compare_func *data_compare); + +/** + * @brief Duplicate a red-black tree. + * + * @param tree Tree to duplicate. + * + * @return Pointer to a new allocated duplicated rbtree. NULL if an error + * occured. + */ +c_rbtree_t *c_rbtree_dup(const c_rbtree_t *tree); + +/** + * @brief Free the structure of a red-black tree. + * + * You should call c_rbtree_destroy() before you call this function. + * + * @param tree The tree to free. + * + * @return 0 on success, less than 0 if an error occured. + */ +int c_rbtree_free(c_rbtree_t *tree); + +/** + * @brief Destroy the content and the nodes of an red-black tree. + * + * This is far from the most efficient way to walk a tree, but it is + * the *safest* way to destroy a tree - the destructor can do almost + * anything (as long as it does not create an infinite loop) to the + * tree structure without risk. + * + * If for some strange reason you need a faster destructor (think + * twice - speed and memory deallocation don't mix well) then consider + * stashing an llist of dataects and destroying that instead, and just + * using c_rbtree_free() on your tree. + * + * @param T The tree to destroy. + * @param DESTRUCTOR The destructor to call on a node to destroy. + */ +#define c_rbtree_destroy(T, DESTRUCTOR) \ + do { \ + if (T) { \ + c_rbnode_t *_c_rbtree_temp; \ + while ((_c_rbtree_temp = c_rbtree_head(T))) { \ + (DESTRUCTOR)(_c_rbtree_temp->data); \ + if (_c_rbtree_temp == c_rbtree_head(T)) { \ + c_rbtree_node_delete(_c_rbtree_temp); \ + } \ + } \ + } \ + SAFE_FREE(T); \ + } while (0); + +/** + * @brief Inserts a node into a red black tree. + * + * @param tree The red black tree to insert the node. + * @param data The data to insert into the tree. + * + * @return 0 on success, 1 if a duplicate has been found and < 0 if an error + * occured with errno set. + * EINVAL if a null pointer has been passed as the tree. + * ENOMEM if there is no memory left. + */ +int c_rbtree_insert(c_rbtree_t *tree, void *data); + +/** + * @brief Find a node in a red-black tree. + * + * c_rbtree_find() is searching for the given key in a red-black tree and + * returns the node if the key has been found. + * + * @param tree The tree to search. + * @param key The key to search for. + * + * @return If the key was found the node will be returned. On error NULL + * will be returned. + */ +c_rbnode_t *c_rbtree_find(c_rbtree_t *tree, const void *key); + +/** + * @brief Get the head of the red-black tree. + * + * @param tree The tree to get the head for. + * + * @return The head node. NULL if an error occured. + */ +c_rbnode_t *c_rbtree_head(c_rbtree_t *tree); + +/** + * @brief Get the tail of the red-black tree. + * + * @param tree The tree to get the tail for. + * + * @return The tail node. NULL if an error occured. + */ +c_rbnode_t *c_rbtree_tail(c_rbtree_t *tree); + +/** + * @brief Get the size of the red-black tree. + * + * @param T The tree to get the size from. + * + * @return The size of the red-black tree. + */ +#define c_rbtree_size(T) (T) == NULL ? 0 : ((T)->size) + +/** + * @brief Walk over a red-black tree. + * + * Walk over a red-black tree calling a visitor function for each node found. + * + * @param tree Tree to walk. + * @param data Data which should be passed to the visitor function. + * @param visitor Visitor function. This will be called for each node passed. + * + * @return 0 on sucess, less than 0 if an error occured. + */ +int c_rbtree_walk(c_rbtree_t *tree, void *data, c_rbtree_visit_func *visitor); + +/** + * @brief Delete a node in a red-black tree. + * + * @param node Node which should be deleted. + * + * @return 0 on success, -1 if an error occured. + */ +int c_rbtree_node_delete(c_rbnode_t *node); + +/** + * @brief Get the next node. + * + * @param node The node of which you want the next node. + * + * @return The next node, NULL if an error occured. + */ +c_rbnode_t *c_rbtree_node_next(c_rbnode_t *node); + +/** + * @brief Get the previous node. + * + * @param node The node of which you want the previous node. + * + * @return The previous node, NULL if an error occured. + */ +c_rbnode_t *c_rbtree_node_prev(c_rbnode_t *node); + +/** + * @brief Get the data of a node. + * + * @param N The node to get the data from. + * + * @return The data, NULL if an error occured. + */ +#define c_rbtree_node_data(N) ((N) ? ((N)->data) : NULL) + +/** + * @brief Perform a sanity check for a red-black tree. + * + * This is mostly for testing purposes. + * + * @param tree The tree to check. + * + * @return 0 on success, less than 0 if an error occured. + */ +int c_rbtree_check_sanity(c_rbtree_t *tree); + +/** + * }@ + */ +#endif /* _C_RBTREE_H */ diff --git a/csync/src/std/c_string.c b/csync/src/std/c_string.c new file mode 100644 index 000000000..a2c479839 --- /dev/null +++ b/csync/src/std/c_string.c @@ -0,0 +1,379 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "c_string.h" +#include "c_alloc.h" +#include "c_macro.h" + +#ifdef _WIN32 +#include +#endif + +#if defined(HAVE_ICONV) && defined(WITH_ICONV) +# ifdef HAVE_ICONV_H +# include +# endif +# ifdef HAVE_SYS_ICONV_H +# include +# endif + +typedef struct { + iconv_t to; + iconv_t from; +} iconv_conversions; + +CSYNC_THREAD iconv_conversions _iconvs = { NULL, NULL }; + +int c_setup_iconv(const char* to) { + _iconvs.to = iconv_open(to, "UTF-8"); + _iconvs.from = iconv_open("UTF-8", to); + + if (_iconvs.to == (iconv_t)-1 || _iconvs.from == (iconv_t)-1) + return -1; + + return 0; +} + +int c_close_iconv() { + int ret_to = 0; + int ret_from = 0; + if( _iconvs.to != (iconv_t) NULL ) { + ret_to = iconv_close(_iconvs.to); + } + if( _iconvs.from != (iconv_t) NULL ) { + ret_from = iconv_close(_iconvs.from); + } + + if (ret_to == -1 || ret_from == -1) + return -1; + + _iconvs.to = (iconv_t) 0; + _iconvs.from = (iconv_t) 0; + + return 0; +} + +enum iconv_direction { iconv_from_native, iconv_to_native }; + +static char *c_iconv(const char* str, enum iconv_direction dir) +{ +#ifdef HAVE_ICONV_CONST + const char *in = str; +#else + char *in = discard_const(str); +#endif + size_t size; + size_t outsize; + char *out; + char *out_in; + size_t ret; + + if (str == NULL) { + return NULL; + } + + if(_iconvs.from == NULL && _iconvs.to == NULL) { +#ifdef __APPLE__ + c_setup_iconv("UTF-8-MAC"); +#else + return c_strdup(str); +#endif + } + + size = strlen(in); + outsize = size*2; + out = c_malloc(outsize); + out_in = out; + + if (out == NULL) { + return NULL; + } + + if (dir == iconv_to_native) { + ret = iconv(_iconvs.to, &in, &size, &out, &outsize); + } else { + ret = iconv(_iconvs.from, &in, &size, &out, &outsize); + } + + if (ret == (size_t)-1) { + SAFE_FREE(out); + return NULL; + } + + return out_in; +} +#endif /* defined(HAVE_ICONV) && defined(WITH_ICONV) */ + +int c_streq(const char *a, const char *b) { + register const char *s1 = a; + register const char *s2 = b; + + if (s1 == NULL || s2 == NULL) { + return 0; + } + + while (*s1 == *s2++) { + if (*s1++ == '\0') { + return 1; + } + } + + return 0; +} + +c_strlist_t *c_strlist_new(size_t size) { + c_strlist_t *strlist = NULL; + + if (size == 0) { + errno = EINVAL; + return NULL; + } + + strlist = c_malloc(sizeof(c_strlist_t)); + if (strlist == NULL) { + return NULL; + } + + strlist->vector = (char **) c_malloc(size * sizeof(char *)); + if (strlist->vector == NULL) { + SAFE_FREE(strlist); + return NULL; + } + strlist->count = 0; + strlist->size = size; + + return strlist; +} + +c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size) { + if (strlist == NULL || size == 0) { + errno = EINVAL; + return NULL; + } + + if (strlist->size >= size) { + return strlist; + } + + strlist->vector = (char **) c_realloc(strlist->vector, size * sizeof(char *)); + if (strlist->vector == NULL) { + return NULL; + } + + strlist->size = size; + + return strlist; +} + +int c_strlist_add(c_strlist_t *strlist, const char *string) { + if (strlist == NULL || string == NULL) { + return -1; + } + + if (strlist->count < strlist->size) { + strlist->vector[strlist->count] = c_strdup(string); + if (strlist->vector[strlist->count] == NULL) { + return -1; + } + strlist->count++; + } else { + errno = ENOBUFS; + return -1; + } + + return 0; +} + +void c_strlist_clear(c_strlist_t *strlist) { + size_t i = 0; + + if (strlist == NULL) { + return; + } + + for (i = 0; i < strlist->count; i++) { + SAFE_FREE(strlist->vector[i]); + } + + strlist->count = 0; +} + +void c_strlist_destroy(c_strlist_t *strlist) { + + if (strlist == NULL) { + return; + } + + c_strlist_clear(strlist); + SAFE_FREE(strlist->vector); + SAFE_FREE(strlist); +} + +char *c_strreplace(char *src, const char *pattern, const char *repl) { + char *p = NULL; + + while ((p = strstr(src, pattern)) != NULL) { + size_t of = p - src; + size_t l = strlen(src); + size_t pl = strlen(pattern); + size_t rl = strlen(repl); + + if (rl > pl) { + src = (char *) c_realloc(src, strlen(src) + rl - pl + 1); + } + + if (rl != pl) { + memmove(src + of + rl, src + of + pl, l - of - pl + 1); + } + + strncpy(src + of, repl, rl); + } + + return src; +} + +char *c_uppercase(const char* str) { + char *new; + char *p; + + if (str == NULL) { + return NULL; + } + + new = c_strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = toupper(*p); + } + + return new; +} + +char *c_lowercase(const char* str) { + char *new; + char *p; + + if (str == NULL) { + return NULL; + } + + new = c_strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = tolower(*p); + } + + return new; +} + +/* Convert a wide multibyte String to UTF8 */ +char* c_utf8_from_locale(const mbchar_t *wstr) +{ + char *dst = NULL; +#ifdef _WIN32 + char *mdst = NULL; + int size_needed; + size_t len; +#endif + + if (wstr == NULL) { + return NULL; + } + +#ifdef _WIN32 + len = wcslen(wstr); + /* Call once to get the required size. */ + size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr, len, NULL, 0, NULL, NULL); + if (size_needed > 0) { + mdst = c_malloc(size_needed + 1); + if (mdst == NULL) { + errno = ENOMEM; + return NULL; + } + + memset(mdst, 0, size_needed + 1); + WideCharToMultiByte(CP_UTF8, 0, wstr, len, mdst, size_needed, NULL, NULL); + dst = mdst; + } +#else +#ifdef WITH_ICONV + dst = c_iconv(wstr, iconv_from_native); +#else + dst = (char*) wstr; +#endif +#endif + return dst; +} + +/* Convert a an UTF8 string to multibyte */ +mbchar_t* c_utf8_to_locale(const char *str) +{ + mbchar_t *dst = NULL; +#ifdef _WIN32 + size_t len; + int size_needed; +#endif + + if (str == NULL ) { + return NULL; + } + +#ifdef _WIN32 + len = strlen(str); + size_needed = MultiByteToWideChar(CP_UTF8, 0, str, len, NULL, 0); + if (size_needed > 0) { + int size_char = (size_needed + 1) * sizeof(mbchar_t); + dst = c_malloc(size_char); + if (dst == NULL) { + errno = ENOMEM; + return NULL; + } + + memset((void*)dst, 0, size_char); + MultiByteToWideChar(CP_UTF8, 0, str, -1, dst, size_needed); + } +#else +#ifdef WITH_ICONV + dst = c_iconv(str, iconv_to_native); +#else + dst = (_TCHAR*) str; +#endif +#endif + return dst; +} diff --git a/csync/src/std/c_string.h b/csync/src/std/c_string.h new file mode 100644 index 000000000..159ba867e --- /dev/null +++ b/csync/src/std/c_string.h @@ -0,0 +1,230 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * Copyright (c) 2012-2013 by Klaas Freitag + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file c_string.h + * + * @brief Interface of the cynapses string implementations + * + * @defgroup cynStringInternals cynapses libc string functions + * @ingroup cynLibraryAPI + * + * @{ + */ +#ifndef _C_STR_H +#define _C_STR_H + +#include "c_private.h" +#include "c_macro.h" + +#include + +struct c_strlist_s; typedef struct c_strlist_s c_strlist_t; + +/** + * @brief Structure for a stringlist + * + * Using a for loop you can access the strings saved in the vector. + * + * c_strlist_t strlist; + * int i; + * for (i = 0; i < strlist->count; i++) { + * printf("value: %s", strlist->vector[i]; + * } + */ +struct c_strlist_s { + /** The string vector */ + char **vector; + /** The count of the strings saved in the vector */ + size_t count; + /** Size of strings allocated */ + size_t size; +}; + +/** + * @brief Compare to strings if they are equal. + * + * @param a First string to compare. + * @param b Second string to compare. + * + * @return 1 if they are equal, 0 if not. + */ +int c_streq(const char *a, const char *b); + +/** + * @brief Create a new stringlist. + * + * @param size Size to allocate. + * + * @return Pointer to the newly allocated stringlist. NULL if an error occured. + */ +c_strlist_t *c_strlist_new(size_t size); + +/** + * @brief Expand the stringlist + * + * @param strlist Stringlist to expand + * @param size New size of the strlinglist to expand + * + * @return Pointer to the expanded stringlist. NULL if an error occured. + */ +c_strlist_t *c_strlist_expand(c_strlist_t *strlist, size_t size); + +/** + * @brief Add a string to the stringlist. + * + * Duplicates the string and stores it in the stringlist. + * + * @param strlist Stringlist to add the string. + * @param string String to add. + * + * @return 0 on success, less than 0 and errno set if an error occured. + * ENOBUFS if the list is full. + */ +int c_strlist_add(c_strlist_t *strlist, const char *string); + +/** + * @brief Removes all strings from the list. + * + * Frees the strings. + * + * @param strlist Stringlist to clear + */ +void c_strlist_clear(c_strlist_t *strlist); + +/** + * @brief Destroy the memory of the stringlist. + * + * Frees the strings and the stringlist. + * + * @param strlist Stringlist to destroy + */ +void c_strlist_destroy(c_strlist_t *strlist); + +/** + * @breif Replace a string with another string in a source string. + * + * @param src String to search for pattern. + * + * @param pattern Pattern to search for in the source string. + * + * @param repl The string which which should replace pattern if found. + * + * @return Return a pointer to the source string. + */ +char *c_strreplace(char *src, const char *pattern, const char *repl); + +/** + * @brief Uppercase a string. + * + * @param str The String to uppercase. + * + * @return The malloced uppered string or NULL on error. + */ +char *c_uppercase(const char* str); + +/** + * @brief Lowercase a string. + * + * @param str The String to lowercase. + * + * @return The malloced lowered string or NULL on error. + */ +char *c_lowercase(const char* str); + +/** + * @brief Convert a platform locale string to utf8. + * + * This function is part of the multi platform abstraction of basic file + * operations to handle various platform encoding correctly. + * + * Instead of using the standard file operations the multi platform aliases + * defined in c_private.h have to be used instead. + * + * To convert path names returned by these functions to the internally used + * utf8 format this function has to be used. The returned string has to + * be freed by c_free_locale_string(). On some platforms this method allocates + * memory and on others not but it has never to be cared about. + * + * @param str The multibyte encoded string to convert + * + * @return The malloced converted string or NULL on error. + * + * @see c_free_locale_string() + * @see c_utf8_to_locale() + * + */ + char* c_utf8_from_locale(const mbchar_t *str); + +/** + * @brief Convert a utf8 encoded string to platform specific locale. + * + * This function is part of the multi platform abstraction of basic file + * operations to handle various platform encoding correctly. + * + * Instead of using the standard file operations the multi platform aliases + * defined in c_private.h have to be used instead. + * + * To convert path names as input for the cross platform functions from the + * internally used utf8 format, this function has to be used. + * The returned string has to be freed by c_free_locale_string(). On some + * platforms this method allocates memory and on others not but it has never + * sto be cared about. + * + * @param str The utf8 string to convert. + * + * @return The malloced converted multibyte string or NULL on error. + * + * @see c_free_locale_string() + * @see c_utf8_from_locale() + * + */ +mbchar_t* c_utf8_to_locale(const char *wstr); + +#if defined(_WIN32) || defined(WITH_ICONV) + +/** + * @brief Free buffer malloced by c_utf8_from_locale or c_utf8_to_locale(). + * + * This function is part of the multi platform abstraction of basic file + * operations to handle various platform encoding correctly. + * + * Instead of using the standard file operations the multi platform aliases + * defined in c_private.h have to be used instead. + * + * This function frees the memory that was allocated by a previous call to + * c_utf8_to_locale() or c_utf8_from_locale(). + * + * @param buf The buffer to free. + * + * @see c_utf8_from_locale(), c_utf8_to_locale() + * + */ +#define c_free_locale_string(x) SAFE_FREE(x) +#else +#define c_free_locale_string(x) (void)x +#endif + +/** + * }@ + */ +#endif /* _C_STR_H */ + diff --git a/csync/src/std/c_time.c b/csync/src/std/c_time.c new file mode 100644 index 000000000..62d43c899 --- /dev/null +++ b/csync/src/std/c_time.c @@ -0,0 +1,149 @@ +/* + * c_time - time functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config_csync.h" +#include "c_private.h" +#include "c_string.h" + +#include "c_string.h" +#include "c_time.h" + +struct timespec c_tspecdiff(struct timespec time1, struct timespec time0) { + struct timespec ret; + int xsec = 0; + int sign = 1; + + if (time0.tv_nsec > time1.tv_nsec) { + xsec = (int) ((time0.tv_nsec - time1.tv_nsec) / (1E9 + 1)); + time0.tv_nsec -= (long int) (1E9 * xsec); + time0.tv_sec += xsec; + } + + if ((time1.tv_nsec - time0.tv_nsec) > 1E9) { + xsec = (int) ((time1.tv_nsec - time0.tv_nsec) / 1E9); + time0.tv_nsec += (long int) (1E9 * xsec); + time0.tv_sec -= xsec; + } + + ret.tv_sec = time1.tv_sec - time0.tv_sec; + ret.tv_nsec = time1.tv_nsec - time0.tv_nsec; + + if (time1.tv_sec < time0.tv_sec) { + sign = -1; + } + + ret.tv_sec = ret.tv_sec * sign; + + return ret; +} + +double c_secdiff(struct timespec clock1, struct timespec clock0) { + double ret; + struct timespec diff; + + diff = c_tspecdiff(clock1, clock0); + + ret = diff.tv_sec; + ret += (double) diff.tv_nsec / (double) 1E9; + + return ret; +} + + +#ifdef HAVE_UTIMES +int c_utimes(const char *uri, const struct timeval *times) { + mbchar_t *wuri = c_utf8_to_locale(uri); + int ret = utimes(wuri, times); + c_free_locale_string(wuri); + return ret; +} +#else // HAVE_UTIMES + +#ifdef _WIN32 +// implementation for utimes taken from KDE mingw headers + +#include +#include +#define CSYNC_SECONDS_SINCE_1601 11644473600LL +#define CSYNC_USEC_IN_SEC 1000000LL +//after Microsoft KB167296 +static void UnixTimevalToFileTime(struct timeval t, LPFILETIME pft) +{ + LONGLONG ll; + ll = Int32x32To64(t.tv_sec, CSYNC_USEC_IN_SEC*10) + t.tv_usec*10 + CSYNC_SECONDS_SINCE_1601*CSYNC_USEC_IN_SEC*10; + pft->dwLowDateTime = (DWORD)ll; + pft->dwHighDateTime = ll >> 32; +} + +int c_utimes(const char *uri, const struct timeval *times) { + FILETIME LastAccessTime; + FILETIME LastModificationTime; + HANDLE hFile; + + mbchar_t *wuri = c_utf8_to_locale( uri ); + + if(times) { + UnixTimevalToFileTime(times[0], &LastAccessTime); + UnixTimevalToFileTime(times[1], &LastModificationTime); + } + else { + GetSystemTimeAsFileTime(&LastAccessTime); + GetSystemTimeAsFileTime(&LastModificationTime); + } + + hFile=CreateFileW(wuri, FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL+FILE_FLAG_BACKUP_SEMANTICS, NULL); + if(hFile==INVALID_HANDLE_VALUE) { + switch(GetLastError()) { + case ERROR_FILE_NOT_FOUND: + errno=ENOENT; + break; + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + errno=ENOTDIR; + break; + /*case ERROR_WRITE_PROTECT: //CreateFile sets ERROR_ACCESS_DENIED on read-only devices + * errno=EROFS; + * break;*/ + case ERROR_ACCESS_DENIED: + errno=EACCES; + break; + default: + errno=ENOENT; //what other errors can occur? + } + + return -1; + } + + if(!SetFileTime(hFile, NULL, &LastAccessTime, &LastModificationTime)) { + //can this happen? + errno=ENOENT; + CloseHandle(hFile); + return -1; + } + + CloseHandle(hFile); + c_free_locale_string(wuri); + + return 0; +} + +#endif // _WIN32 +#endif // HAVE_UTIMES diff --git a/csync/src/std/c_time.h b/csync/src/std/c_time.h new file mode 100644 index 000000000..a5718c1c9 --- /dev/null +++ b/csync/src/std/c_time.h @@ -0,0 +1,56 @@ +/* + * c_time - time functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _C_TIME_H +#define _C_TIME_H + +#include +#include + +/** + * @brief Calculate time difference + * + * The c_tspecdiff function returns the time elapsed between time time1 and time + * time0 represented as timespec. + * + * @param time1 The time. + * @param time0 The time. + * + * @return time elapsed between time1 and time0. + */ +struct timespec c_tspecdiff(struct timespec time1, struct timespec time0); + +/** + * @brief Calculate time difference. + * + * The function returns the time elapsed between time clock1 and time + * clock0 represented as double (in seconds and milliseconds). + * + * @param clock1 The time. + * @param clock0 The time. + * + * @return time elapsed between clock1 and clock0 in seconds and + * milliseconds. + */ +double c_secdiff(struct timespec clock1, struct timespec clock0); + +int c_utimes(const char *uri, const struct timeval *times); + +#endif /* _C_TIME_H */ diff --git a/csync/src/vio/csync_vio.c b/csync/src/vio/csync_vio.c new file mode 100644 index 000000000..73bf630c7 --- /dev/null +++ b/csync/src/vio/csync_vio.c @@ -0,0 +1,676 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include + +#include "csync_private.h" +#include "csync_dbtree.h" +#include "csync_util.h" +#include "vio/csync_vio.h" +#include "vio/csync_vio_handle_private.h" +#include "vio/csync_vio_local.h" +#include "csync_statedb.h" +#include "std/c_jhash.h" + +#ifdef __APPLE__ +#include +#endif + +#define CSYNC_LOG_CATEGORY_NAME "csync.vio.main" + +#ifdef _WIN32 +#include +#define MODULE_EXTENSION "dll" +#else +#define MODULE_EXTENSION "so" +#endif + +#ifdef __APPLE__ +#include +#endif + +#include "csync_log.h" + +int csync_vio_init(CSYNC *ctx, const char *module, const char *args) { + + csync_vio_method_t *m = NULL; + csync_vio_method_init_fn init_fn; + + /* The owncloud module used to be dynamically loaded, but now it's just statically linked */ + extern csync_vio_method_t *vio_module_init(const char *method_name, const char *config_args, csync_auth_callback cb, void *userdata); + extern void vio_module_shutdown(csync_vio_method_t *); + init_fn = vio_module_init; + ctx->module.finish_fn = vio_module_shutdown; + + /* get the method struct */ + m = init_fn(module, args, csync_get_auth_callback(ctx), csync_get_userdata(ctx)); + if (m == NULL) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "module %s returned a NULL method", module); + return -1; + } + + /* Useful defaults to the module capabilities */ + ctx->module.capabilities.atomar_copy_support = false; + ctx->module.capabilities.do_post_copy_stat = true; + ctx->module.capabilities.time_sync_required = true; + ctx->module.capabilities.unix_extensions = -1; /* detect automatically */ + ctx->module.capabilities.use_send_file_to_propagate = false; /* do use read/write by default */ + + /* Load the module capabilities from the module if it implements the it. */ + if( VIO_METHOD_HAS_FUNC(m, get_capabilities)) { + ctx->module.capabilities = *(m->get_capabilities()); + } + + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: atomar copy support: %s", + ctx->module.capabilities.atomar_copy_support ? "yes": "no"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: post copy stat: %s", + ctx->module.capabilities.do_post_copy_stat ? "yes": "no"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: time sync required: %s", + ctx->module.capabilities.time_sync_required ? "yes": "no"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: unix extensions: %d", + ctx->module.capabilities.unix_extensions ); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: use send_file: %s", + ctx->module.capabilities.use_send_file_to_propagate ? "yes" : "no" ); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: get support: %s", + ctx->module.capabilities.get_support ? "yes" : "no" ); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "capabilities: put support: %s", + ctx->module.capabilities.put_support? "yes" : "no" ); + + /* Some basic checks */ + if (m->method_table_size == 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "module %s method table size is 0", module); + return -1; + } + + if (! VIO_METHOD_HAS_FUNC(m, opendir)) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "module %s has no opendir fn", module); + return -1; + } + + /* Useful defaults to the module capabilities */ + ctx->module.capabilities.atomar_copy_support = false; + ctx->module.capabilities.put_support = false; + ctx->module.capabilities.get_support = false; + + /* Load the module capabilities from the module if it implements the it. */ + if( VIO_METHOD_HAS_FUNC(m, get_capabilities)) { + ctx->module.capabilities = *(m->get_capabilities()); + } else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "module %s has no capabilities fn", module); + } + + if (! VIO_METHOD_HAS_FUNC(m, get_etag)) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "module %s has no get_etag fn", module); + } + + ctx->module.method = m; + + return 0; +} + +void csync_vio_shutdown(CSYNC *ctx) { + /* shutdown the plugin */ + if (ctx->module.finish_fn != NULL) { + (*ctx->module.finish_fn)(ctx->module.method); + } + + ctx->module.method = NULL; + ctx->module.finish_fn = NULL; +} + +csync_vio_handle_t *csync_vio_open(CSYNC *ctx, const char *uri, int flags, mode_t mode) { + csync_vio_handle_t *h = NULL; + csync_vio_method_handle_t *mh = NULL; + + switch(ctx->replica) { + case REMOTE_REPLICA: + mh = ctx->module.method->open(uri, flags, mode); + break; + case LOCAL_REPLICA: + mh = csync_vio_local_open(uri, flags, mode); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + h = csync_vio_handle_new(uri, mh); + if (h == NULL) { + return NULL; + } + + return h; +} + +csync_vio_handle_t *csync_vio_creat(CSYNC *ctx, const char *uri, mode_t mode) { + csync_vio_handle_t *h = NULL; + csync_vio_method_handle_t *mh = NULL; + + switch(ctx->replica) { + case REMOTE_REPLICA: + mh = ctx->module.method->creat(uri, mode); + break; + case LOCAL_REPLICA: + mh = csync_vio_local_creat(uri, mode); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + h = csync_vio_handle_new(uri, mh); + if (h == NULL) { + return NULL; + } + + return h; +} + +int csync_vio_close(CSYNC *ctx, csync_vio_handle_t *fhandle) { + int rc = -1; + + if (fhandle == NULL) { + errno = EBADF; + return -1; + } + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->close(fhandle->method_handle); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_close(fhandle->method_handle); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + /* handle->method_handle is free'd by the above close */ + SAFE_FREE(fhandle->uri); + SAFE_FREE(fhandle); + + return rc; +} + +int csync_vio_getfd(csync_vio_handle_t *fhandle) { + int fd = -1; + + if (fhandle == NULL) { + errno = EBADF; + return -1; + } + + fd = csync_vio_local_getfd( fhandle ); + // Return the correct handle here. + return fd; + +} + +int csync_vio_put(CSYNC *ctx, + csync_vio_handle_t *flocal, + csync_vio_handle_t *fremote, + csync_file_stat_t *st) { + int rc = 0; + csync_vio_file_stat_t *vfs = csync_vio_convert_file_stat(st); + + if (flocal == NULL) { + rc = -1; + } + if (vfs == NULL) { + rc = -1; + } + + if (rc == 0) { + rc = ctx->module.method->put(flocal->method_handle, + fremote->method_handle, + vfs); + } + csync_vio_file_stat_destroy(vfs); + return rc; +} + +int csync_vio_get(CSYNC *ctx, + csync_vio_handle_t *flocal, + csync_vio_handle_t *fremote, + csync_file_stat_t *st) { + int rc = 0; + csync_vio_file_stat_t *vfs = csync_vio_convert_file_stat(st); + + if (flocal == NULL) { + rc = -1; + } + if (vfs == NULL) { + rc = -1; + } + + if (rc == 0) { + rc = ctx->module.method->get(flocal->method_handle, + fremote->method_handle, + vfs); + } + csync_vio_file_stat_destroy(vfs); + return rc; +} + +ssize_t csync_vio_read(CSYNC *ctx, csync_vio_handle_t *fhandle, void *buf, size_t count) { + ssize_t rs = 0; + + if (fhandle == NULL) { + errno = EBADF; + return -1; + } + + switch(ctx->replica) { + case REMOTE_REPLICA: + rs = ctx->module.method->read(fhandle->method_handle, buf, count); + break; + case LOCAL_REPLICA: + rs = csync_vio_local_read(fhandle->method_handle, buf, count); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rs; +} + +ssize_t csync_vio_write(CSYNC *ctx, csync_vio_handle_t *fhandle, const void *buf, size_t count) { + ssize_t rs = 0; + + if (fhandle == NULL) { + errno = EBADF; + return -1; + } + + switch(ctx->replica) { + case REMOTE_REPLICA: + rs = ctx->module.method->write(fhandle->method_handle, buf, count); + break; + case LOCAL_REPLICA: + rs = csync_vio_local_write(fhandle->method_handle, buf, count); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rs; +} + +int csync_vio_sendfile(CSYNC *ctx, csync_vio_handle_t *sfp, csync_vio_handle_t *dst) { + int rc = 0; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->sendfile(sfp->method_handle, dst->method_handle); + break; + case LOCAL_REPLICA: + rc = ctx->module.method->sendfile(dst->method_handle, sfp->method_handle); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int64_t csync_vio_lseek(CSYNC *ctx, csync_vio_handle_t *fhandle, int64_t offset, int whence) { + int64_t ro = 0; + + switch(ctx->replica) { + case REMOTE_REPLICA: + ro = ctx->module.method->lseek(fhandle->method_handle, offset, whence); + break; + case LOCAL_REPLICA: + ro = csync_vio_local_lseek(fhandle->method_handle, offset, whence); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return ro; +} + +csync_vio_handle_t *csync_vio_opendir(CSYNC *ctx, const char *name) { + csync_vio_handle_t *h = NULL; + csync_vio_method_handle_t *mh = NULL; + + switch(ctx->replica) { + case REMOTE_REPLICA: + if(ctx->remote.read_from_db) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Reading directory %s from database", name); + mh = csync_dbtree_opendir(ctx, name); + } else { + mh = ctx->module.method->opendir(name); + } + break; + case LOCAL_REPLICA: + mh = csync_vio_local_opendir(name); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + h = csync_vio_handle_new(name, mh); + if (h == NULL) { + return NULL; + } + + return h; +} + +int csync_vio_closedir(CSYNC *ctx, csync_vio_handle_t *dhandle) { + int rc = -1; + + if (dhandle == NULL) { + errno = EBADF; + return -1; + } + + switch(ctx->replica) { + case REMOTE_REPLICA: + if(ctx->remote.read_from_db) { + rc = csync_dbtree_closedir(ctx, dhandle->method_handle); + } else { + rc = ctx->module.method->closedir(dhandle->method_handle); + } + break; + case LOCAL_REPLICA: + rc = csync_vio_local_closedir(dhandle->method_handle); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + SAFE_FREE(dhandle->uri); + SAFE_FREE(dhandle); + + return rc; +} + +csync_vio_file_stat_t *csync_vio_readdir(CSYNC *ctx, csync_vio_handle_t *dhandle) { + csync_vio_file_stat_t *fs = NULL; + + switch(ctx->replica) { + case REMOTE_REPLICA: + if(ctx->remote.read_from_db) { + fs = csync_dbtree_readdir(ctx, dhandle->method_handle); + } else { + fs = ctx->module.method->readdir(dhandle->method_handle); + } + break; + case LOCAL_REPLICA: + fs = csync_vio_local_readdir(dhandle->method_handle); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return fs; +} + +int csync_vio_mkdir(CSYNC *ctx, const char *uri, mode_t mode) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->mkdir(uri, mode); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_mkdir(uri, mode); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int csync_vio_mkdirs(CSYNC *ctx, const char *uri, mode_t mode) { + int tmp = 0; + char errbuf[256] = {0}; + csync_vio_file_stat_t *st = NULL; + + if (uri == NULL) { + errno = EINVAL; + return -1; + } + + tmp = strlen(uri); + while(tmp > 0 && uri[tmp - 1] == '/') --tmp; + while(tmp > 0 && uri[tmp - 1] != '/') --tmp; + while(tmp > 0 && uri[tmp - 1] == '/') --tmp; + + if (tmp > 0) { + char suburi[tmp + 1]; + memcpy(suburi, uri, tmp); + suburi[tmp] = '\0'; + + st = csync_vio_file_stat_new(); + if (csync_vio_stat(ctx, suburi, st) == 0) { + if (! S_ISDIR(st->mode)) { + csync_vio_file_stat_destroy(st); + errno = ENOTDIR; + return -1; + } + } else if (errno != ENOENT) { + C_STRERROR(errno, errbuf, sizeof(errbuf)); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "csync_vio_mkdirs stat failed: %s", + errbuf); + csync_vio_file_stat_destroy(st); + return -1; + } else if (csync_vio_mkdirs(ctx, suburi, mode) < 0) { + csync_vio_file_stat_destroy(st); + return -1; + } + csync_vio_file_stat_destroy(st); + } + + tmp = csync_vio_mkdir(ctx, uri, mode); + if ((tmp < 0) && (errno == EEXIST)) { + return 0; + } + + return tmp; +} + +int csync_vio_rmdir(CSYNC *ctx, const char *uri) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->rmdir(uri); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_rmdir(uri); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +const char *csync_vio_get_etag(CSYNC *ctx, const char *path) +{ + const char *re = NULL; + /* We always use the remote method here. */ + if(ctx->module.method && + VIO_METHOD_HAS_FUNC(ctx->module.method, get_etag)) { + re = ctx->module.method->get_etag(path); + } + return re; +} + +int csync_vio_stat(CSYNC *ctx, const char *uri, csync_vio_file_stat_t *buf) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->stat(uri, buf); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_stat(uri, buf); + if (rc < 0) { + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "Local stat failed, errno %d", errno); + } +#ifdef _WIN32 + else { + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Win32: STAT-inode for %s: %llu", uri, buf->inode ); + } +#endif + break; + default: + break; + } + + return rc; +} + +int csync_vio_rename(CSYNC *ctx, const char *olduri, const char *newuri) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->rename(olduri, newuri); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_rename(olduri, newuri); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int csync_vio_unlink(CSYNC *ctx, const char *uri) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->unlink(uri); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_unlink(uri); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int csync_vio_chmod(CSYNC *ctx, const char *uri, mode_t mode) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->chmod(uri, mode); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_chmod(uri, mode); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int csync_vio_chown(CSYNC *ctx, const char *uri, uid_t owner, gid_t group) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->chown(uri, owner, group); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_chown(uri, owner, group); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +int csync_vio_utimes(CSYNC *ctx, const char *uri, const struct timeval *times) { + int rc = -1; + + switch(ctx->replica) { + case REMOTE_REPLICA: + rc = ctx->module.method->utimes(uri, times); + break; + case LOCAL_REPLICA: + rc = csync_vio_local_utimes(uri, times); + break; + default: + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "Invalid replica (%d)", (int)ctx->replica); + break; + } + + return rc; +} + +char *csync_vio_get_status_string(CSYNC *ctx) { + if(ctx->error_string) { + return ctx->error_string; + } + if(VIO_METHOD_HAS_FUNC(ctx->module.method, get_error_string)) { + return ctx->module.method->get_error_string(); + } + return NULL; +} + +int csync_vio_set_property(CSYNC* ctx, const char* key, void* data) { + int rc = -1; + if(VIO_METHOD_HAS_FUNC(ctx->module.method, set_property)) + rc = ctx->module.method->set_property(key, data); + return rc; +} + +int csync_vio_commit(CSYNC *ctx) { + int rc = 0; + + if (VIO_METHOD_HAS_FUNC(ctx->module.method, commit)) { + rc = ctx->module.method->commit(); + } + + return rc; +} diff --git a/csync/src/vio/csync_vio.h b/csync/src/vio/csync_vio.h new file mode 100644 index 000000000..43d3fb2f7 --- /dev/null +++ b/csync/src/vio/csync_vio.h @@ -0,0 +1,75 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_H +#define _CSYNC_VIO_H + +#include +#include +#include +#include "c_private.h" +#include "vio/csync_vio_handle.h" +#include "vio/csync_vio_file_stat.h" +#include "csync_private.h" + +typedef struct fhandle_s { + int fd; +} fhandle_t; + +int csync_vio_init(CSYNC *ctx, const char *module, const char *args); +void csync_vio_shutdown(CSYNC *ctx); +csync_vio_handle_t *csync_vio_open(CSYNC *ctx, const char *uri, int flags, mode_t mode); +csync_vio_handle_t *csync_vio_creat(CSYNC *ctx, const char *uri, mode_t mode); +int csync_vio_close(CSYNC *ctx, csync_vio_handle_t *handle); +ssize_t csync_vio_read(CSYNC *ctx, csync_vio_handle_t *fhandle, void *buf, size_t count); +ssize_t csync_vio_write(CSYNC *ctx, csync_vio_handle_t *fhandle, const void *buf, size_t count); +int csync_vio_sendfile(CSYNC *ctx, csync_vio_handle_t *sfp, csync_vio_handle_t *dst); +int64_t csync_vio_lseek(CSYNC *ctx, csync_vio_handle_t *fhandle, int64_t offset, int whence); + +int csync_vio_put(CSYNC *ctx, csync_vio_handle_t *flocal, csync_vio_handle_t *fremote, csync_file_stat_t *st); +int csync_vio_get(CSYNC *ctx, csync_vio_handle_t *flocal, csync_vio_handle_t *fremote, csync_file_stat_t *st); + +csync_vio_handle_t *csync_vio_opendir(CSYNC *ctx, const char *name); +int csync_vio_closedir(CSYNC *ctx, csync_vio_handle_t *dhandle); +csync_vio_file_stat_t *csync_vio_readdir(CSYNC *ctx, csync_vio_handle_t *dhandle); + +int csync_vio_mkdir(CSYNC *ctx, const char *uri, mode_t mode); +int csync_vio_mkdirs(CSYNC *ctx, const char *uri, mode_t mode); +int csync_vio_rmdir(CSYNC *ctx, const char *uri); + +const char *csync_vio_get_etag(CSYNC *ctx, const char *path); +int csync_vio_stat(CSYNC *ctx, const char *uri, csync_vio_file_stat_t *buf); +int csync_vio_rename(CSYNC *ctx, const char *olduri, const char *newuri); +int csync_vio_unlink(CSYNC *ctx, const char *uri); + +int csync_vio_chmod(CSYNC *ctx, const char *uri, mode_t mode); +int csync_vio_chown(CSYNC *ctx, const char *uri, uid_t owner, gid_t group); + +int csync_vio_utimes(CSYNC *ctx, const char *uri, const struct timeval *times); + +int csync_vio_set_property(CSYNC *ctx, const char *key, void *data); + +char *csync_vio_get_status_string(CSYNC *ctx); + +int csync_vio_commit(CSYNC *ctx); + +int csync_vio_getfd(csync_vio_handle_t *fhandle); + +#endif /* _CSYNC_VIO_H */ diff --git a/csync/src/vio/csync_vio_file_stat.c b/csync/src/vio/csync_vio_file_stat.c new file mode 100644 index 000000000..010e851db --- /dev/null +++ b/csync/src/vio/csync_vio_file_stat.c @@ -0,0 +1,72 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "c_lib.h" +#include "vio/csync_vio_file_stat.h" + +csync_vio_file_stat_t *csync_vio_file_stat_new(void) { + csync_vio_file_stat_t *file_stat = NULL; + + file_stat = (csync_vio_file_stat_t *) c_malloc(sizeof(csync_vio_file_stat_t)); + if (file_stat == NULL) { + return NULL; + } + file_stat->etag = NULL; + memset(file_stat->file_id, 0, FILE_ID_BUF_SIZE+1); + return file_stat; +} + +void csync_vio_file_stat_destroy(csync_vio_file_stat_t *file_stat) { + /* FIXME: free rest */ + if (file_stat == NULL) { + return; + } + + if (file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_SYMLINK_NAME) { + SAFE_FREE(file_stat->u.symlink_name); + } + if (file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_CHECKSUM) { + SAFE_FREE(file_stat->u.checksum); + } + if (file_stat->fields & CSYNC_VIO_FILE_STAT_FIELDS_ETAG) { + SAFE_FREE(file_stat->etag); + } + SAFE_FREE(file_stat->name); + SAFE_FREE(file_stat); +} + +void csync_vio_file_stat_set_file_id( csync_vio_file_stat_t *dst, const char* src ) { + + csync_vio_set_file_id( dst->file_id, src ); + if( c_streq( dst->file_id, "" )) { + return; + } + dst->fields |= CSYNC_VIO_FILE_STAT_FIELDS_FILE_ID; +} + +void csync_vio_set_file_id( char* dst, const char *src ) { + if( src && dst ) { + if( strlen(src) > FILE_ID_BUF_SIZE ) { + strcpy(dst, ""); + } else { + strcpy(dst, src); + } + } +} diff --git a/csync/src/vio/csync_vio_file_stat.h b/csync/src/vio/csync_vio_file_stat.h new file mode 100644 index 000000000..535ec80ed --- /dev/null +++ b/csync/src/vio/csync_vio_file_stat.h @@ -0,0 +1,126 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_FILE_STAT_H +#define _CSYNC_VIO_FILE_STAT_H + +/* + * cannot include csync_private here because + * that would cause circular inclusion + */ +#include "c_private.h" +#include "csync.h" +#include +#include +#include +#include + +#define FILE_ID_BUF_SIZE 21 + +typedef struct csync_vio_file_stat_s csync_vio_file_stat_t; + +enum csync_vio_file_flags_e { + CSYNC_VIO_FILE_FLAGS_NONE = 0, + CSYNC_VIO_FILE_FLAGS_SYMLINK = 1 << 0, + CSYNC_VIO_FILE_FLAGS_LOCAL = 1 << 1 +}; + +enum csync_vio_file_type_e { + CSYNC_VIO_FILE_TYPE_UNKNOWN, + CSYNC_VIO_FILE_TYPE_REGULAR, + CSYNC_VIO_FILE_TYPE_DIRECTORY, + CSYNC_VIO_FILE_TYPE_FIFO, + CSYNC_VIO_FILE_TYPE_SOCKET, + CSYNC_VIO_FILE_TYPE_CHARACTER_DEVICE, + CSYNC_VIO_FILE_TYPE_BLOCK_DEVICE, + CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK +}; + +enum csync_vio_file_stat_fields_e { + CSYNC_VIO_FILE_STAT_FIELDS_NONE = 0, + CSYNC_VIO_FILE_STAT_FIELDS_TYPE = 1 << 0, + CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS = 1 << 1, + CSYNC_VIO_FILE_STAT_FIELDS_FLAGS = 1 << 2, + CSYNC_VIO_FILE_STAT_FIELDS_DEVICE = 1 << 3, + CSYNC_VIO_FILE_STAT_FIELDS_INODE = 1 << 4, + CSYNC_VIO_FILE_STAT_FIELDS_LINK_COUNT = 1 << 5, + CSYNC_VIO_FILE_STAT_FIELDS_SIZE = 1 << 6, + CSYNC_VIO_FILE_STAT_FIELDS_BLOCK_COUNT = 1 << 7, /* will be removed */ + CSYNC_VIO_FILE_STAT_FIELDS_BLOCK_SIZE = 1 << 8, /* will be removed */ + CSYNC_VIO_FILE_STAT_FIELDS_ATIME = 1 << 9, + CSYNC_VIO_FILE_STAT_FIELDS_MTIME = 1 << 10, + CSYNC_VIO_FILE_STAT_FIELDS_CTIME = 1 << 11, + CSYNC_VIO_FILE_STAT_FIELDS_SYMLINK_NAME = 1 << 12, + CSYNC_VIO_FILE_STAT_FIELDS_CHECKSUM = 1 << 13, + CSYNC_VIO_FILE_STAT_FIELDS_ACL = 1 << 14, + CSYNC_VIO_FILE_STAT_FIELDS_UID = 1 << 15, + CSYNC_VIO_FILE_STAT_FIELDS_GID = 1 << 16, + CSYNC_VIO_FILE_STAT_FIELDS_ETAG = 1 << 17, + CSYNC_VIO_FILE_STAT_FIELDS_FILE_ID = 1 << 18 +}; + + +struct csync_vio_file_stat_s { + union { + char *symlink_name; + char *checksum; + } u; + + void *acl; + char *name; + char *etag; + char file_id[FILE_ID_BUF_SIZE+1]; + + uid_t uid; + gid_t gid; + + time_t atime; + time_t mtime; + time_t ctime; + + int64_t size; + int64_t blksize; /* will be removed in future, not used in csync */ + unsigned long blkcount; /* will be removed in future, not used in csync */ + + mode_t mode; + + dev_t device; + uint64_t inode; + nlink_t nlink; + + enum csync_vio_file_stat_fields_e fields; + enum csync_vio_file_type_e type; + + enum csync_vio_file_flags_e flags; + + void *reserved1; + void *reserved2; + void *reserved3; +}; + +csync_vio_file_stat_t *csync_vio_file_stat_new(void); + +void csync_vio_file_stat_destroy(csync_vio_file_stat_t *fstat); + +void csync_vio_file_stat_set_file_id( csync_vio_file_stat_t* dst, const char* src ); + +void csync_vio_set_file_id(char* dst, const char *src ); + +#endif /* _CSYNC_VIO_METHOD_H */ diff --git a/csync/src/vio/csync_vio_handle.c b/csync/src/vio/csync_vio_handle.c new file mode 100644 index 000000000..cd798e8e1 --- /dev/null +++ b/csync/src/vio/csync_vio_handle.c @@ -0,0 +1,50 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "c_lib.h" + +#include "vio/csync_vio_handle_private.h" + +csync_vio_handle_t *csync_vio_handle_new(const char *uri, csync_vio_method_handle_t *method_handle) { + csync_vio_handle_t *new = NULL; + + if (uri == NULL || method_handle == NULL) { + return NULL; + } + + new = c_malloc(sizeof(csync_vio_handle_t)); + if (new == NULL) { + return NULL; + } + + new->uri = c_strdup(uri); + new->method_handle = method_handle; + + return new; +} + +void csync_vio_handle_destroy(csync_vio_handle_t *handle) { + if (handle == NULL || handle->uri == NULL) { + return; + } + + SAFE_FREE(handle->uri); + SAFE_FREE(handle); +} diff --git a/csync/src/vio/csync_vio_handle.h b/csync/src/vio/csync_vio_handle.h new file mode 100644 index 000000000..f06674e96 --- /dev/null +++ b/csync/src/vio/csync_vio_handle.h @@ -0,0 +1,27 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_HANDLE_H +#define _CSYNC_VIO_HANDLE_H + +typedef void csync_vio_method_handle_t; +typedef struct csync_vio_handle_s csync_vio_handle_t; + +#endif /* _CSYNC_VIO_HANDLE_H */ diff --git a/csync/src/vio/csync_vio_handle_private.h b/csync/src/vio/csync_vio_handle_private.h new file mode 100644 index 000000000..83c64980a --- /dev/null +++ b/csync/src/vio/csync_vio_handle_private.h @@ -0,0 +1,35 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_HANDLE_PRIVATE_H +#define _CSYNC_VIO_HANDLE_PRIVATE_H + +#include "vio/csync_vio_handle.h" + +struct csync_vio_handle_s { + char *uri; + + csync_vio_method_handle_t *method_handle; +}; + +csync_vio_handle_t *csync_vio_handle_new(const char *uri, csync_vio_method_handle_t *method_handle); +void csync_vio_handle_destroy(csync_vio_handle_t *handle); + +#endif /* _CSYNC_VIO_HANDLE_PRIVATE_H */ diff --git a/csync/src/vio/csync_vio_local.c b/csync/src/vio/csync_vio_local.c new file mode 100644 index 000000000..facadde74 --- /dev/null +++ b/csync/src/vio/csync_vio_local.c @@ -0,0 +1,490 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include "windows.h" +#define _UNICODE +#endif + +#include "c_private.h" +#include "c_lib.h" +#include "c_string.h" +#include "csync_util.h" +#include "csync_log.h" +#include "csync_vio.h" + +#include "vio/csync_vio_handle.h" +#include "vio/csync_vio_local.h" +#include "vio/csync_vio_handle_private.h" + + +int csync_vio_local_getfd(csync_vio_handle_t *hnd) +{ + fhandle_t *fh; + + if (hnd == NULL) { + return -1; + } + + fh = (struct fhandle_s*)(hnd); + + return fh->fd; +} + +/* the url comes in as utf-8 and in windows, it needs to be multibyte. */ +csync_vio_method_handle_t *csync_vio_local_open(const char *durl, int flags, mode_t mode) { + fhandle_t *handle = NULL; + int fd = -1; + mbchar_t *url = c_utf8_to_locale(durl); + + if ((fd = _topen(url, flags, mode)) < 0) { + c_free_locale_string(url); + return NULL; + } + + handle = c_malloc(sizeof(fhandle_t)); + if (handle == NULL) { + c_free_locale_string(url); + close(fd); + return NULL; + } + + handle->fd = fd; + + c_free_locale_string(url); + + return (csync_vio_method_handle_t *) handle; +} + +csync_vio_method_handle_t *csync_vio_local_creat(const char *durl, mode_t mode) { + fhandle_t *handle = NULL; + int fd = -1; + mbchar_t *url = c_utf8_to_locale(durl); + + if(( fd = _tcreat( url, mode)) < 0) { + c_free_locale_string(url); + return NULL; + } + + handle = c_malloc(sizeof(fhandle_t)); + if (handle == NULL) { + c_free_locale_string(url); + close(fd); + return NULL; + } + + handle->fd = fd; + c_free_locale_string(url); + return (csync_vio_method_handle_t *) handle; +} + +int csync_vio_local_close(csync_vio_method_handle_t *fhandle) { + int rc = -1; + fhandle_t *handle = NULL; + + if (fhandle == NULL) { + errno = EBADF; + return -1; + } + + handle = (fhandle_t *) fhandle; + + rc = close(handle->fd); + + SAFE_FREE(handle); + + return rc; +} + +ssize_t csync_vio_local_read(csync_vio_method_handle_t *fhandle, void *buf, size_t count) { + fhandle_t *handle = NULL; + + if (fhandle == NULL) { + errno = EBADF; + return (ssize_t) -1; + } + + handle = (fhandle_t *) fhandle; + + return read(handle->fd, buf, count); +} + +ssize_t csync_vio_local_write(csync_vio_method_handle_t *fhandle, const void *buf, size_t count) { + ssize_t n = 0; + fhandle_t *handle = NULL; + + if (fhandle == NULL) { + errno = EBADF; + return (ssize_t) -1; + } + + handle = (fhandle_t *) fhandle; + + /* safe_write */ + do { + n = write(handle->fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +int64_t csync_vio_local_lseek(csync_vio_method_handle_t *fhandle, int64_t offset, int whence) { + fhandle_t *handle = NULL; + + if (fhandle == NULL) { + return (int64_t) -1; + } + + handle = (fhandle_t *) fhandle; + + return lseek(handle->fd, offset, whence); +} + +/* + * directory functions + */ + +typedef struct dhandle_s { + _TDIR *dh; + char *path; +} dhandle_t; + +csync_vio_method_handle_t *csync_vio_local_opendir(const char *name) { + dhandle_t *handle = NULL; + mbchar_t *dirname = c_utf8_to_locale(name); + + handle = c_malloc(sizeof(dhandle_t)); + if (handle == NULL) { + c_free_locale_string(dirname); + return NULL; + } + + handle->dh = _topendir( dirname ); + if (handle->dh == NULL) { + c_free_locale_string(dirname); + SAFE_FREE(handle); + return NULL; + } + + handle->path = c_strdup(name); + c_free_locale_string(dirname); + + return (csync_vio_method_handle_t *) handle; +} + +int csync_vio_local_closedir(csync_vio_method_handle_t *dhandle) { + dhandle_t *handle = NULL; + int rc = -1; + + if (dhandle == NULL) { + errno = EBADF; + return -1; + } + + handle = (dhandle_t *) dhandle; + rc = _tclosedir(handle->dh); + + SAFE_FREE(handle->path); + SAFE_FREE(handle); + + return rc; +} + +csync_vio_file_stat_t *csync_vio_local_readdir(csync_vio_method_handle_t *dhandle) { + struct _tdirent *dirent = NULL; + + dhandle_t *handle = NULL; + csync_vio_file_stat_t *file_stat = NULL; + + handle = (dhandle_t *) dhandle; + + errno = 0; + dirent = _treaddir(handle->dh); + if (dirent == NULL) { + if (errno) { + goto err; + } else { + return NULL; + } + } + + file_stat = csync_vio_file_stat_new(); + if (file_stat == NULL) { + goto err; + } + + file_stat->name = c_utf8_from_locale(dirent->d_name); + file_stat->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + + /* Check for availability of d_type, see manpage. */ +#ifdef _DIRENT_HAVE_D_TYPE + switch (dirent->d_type) { + case DT_FIFO: + case DT_SOCK: + case DT_CHR: + case DT_BLK: + break; + case DT_DIR: + case DT_REG: + file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + if (dirent->d_type == DT_DIR) { + file_stat->type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + } else { + file_stat->type = CSYNC_VIO_FILE_TYPE_REGULAR; + } + break; + case DT_UNKNOWN: + file_stat->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + file_stat->type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + default: + break; + } +#endif + + return file_stat; + +err: + SAFE_FREE(file_stat); + + return NULL; +} + +int csync_vio_local_mkdir(const char *uri, mode_t mode) { + return c_mkdirs(uri, mode); +} + +int csync_vio_local_rmdir(const char *uri) { + mbchar_t *dirname = c_utf8_to_locale(uri); + int re = -1; + + re = _trmdir(dirname); + c_free_locale_string(dirname); + return re; +} + +#ifdef _WIN32 +static time_t FileTimeToUnixTime(FILETIME *filetime, DWORD *remainder) +{ + long long int t = filetime->dwHighDateTime; + t <<= 32; + t += (UINT32)filetime->dwLowDateTime; + t -= 116444736000000000LL; + if (t < 0) + { + if (remainder) *remainder = 9999999 - (-t - 1) % 10000000; + return -1 - ((-t - 1) / 10000000); + } + else + { + if (remainder) *remainder = t % 10000000; + return t / 10000000; + } +} +#endif + +int csync_vio_local_stat(const char *uri, csync_vio_file_stat_t *buf) { + csync_stat_t sb; +#ifdef _WIN32 + HANDLE h; +#endif + mbchar_t *wuri = c_utf8_to_locale( uri ); + + if( _tstat(wuri, &sb) < 0) { + c_free_locale_string(wuri); + return -1; + } + + buf->name = c_basename(uri); + + if (buf->name == NULL) { + csync_vio_file_stat_destroy(buf); + c_free_locale_string(wuri); + return -1; + } + buf->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + + switch(sb.st_mode & S_IFMT) { + case S_IFBLK: + buf->type = CSYNC_VIO_FILE_TYPE_BLOCK_DEVICE; + break; + case S_IFCHR: + buf->type = CSYNC_VIO_FILE_TYPE_CHARACTER_DEVICE; + break; + case S_IFDIR: + buf->type = CSYNC_VIO_FILE_TYPE_DIRECTORY; + break; + case S_IFIFO: + buf->type = CSYNC_VIO_FILE_TYPE_FIFO; + break; + case S_IFREG: + buf->type = CSYNC_VIO_FILE_TYPE_REGULAR; + break; +#ifndef _WIN32 + case S_IFLNK: + buf->type = CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK; + break; + case S_IFSOCK: + buf->type = CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK; + break; +#endif + default: + buf->type = CSYNC_VIO_FILE_TYPE_UNKNOWN; + break; + } + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + + buf->mode = sb.st_mode; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS; + + if (buf->type == CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK) { + /* FIXME: handle symlink */ + buf->flags = CSYNC_VIO_FILE_FLAGS_SYMLINK; + } else { + buf->flags = CSYNC_VIO_FILE_FLAGS_NONE; + } + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_FLAGS; + + buf->device = sb.st_dev; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_DEVICE; + + buf->inode = sb.st_ino; +#ifdef _WIN32 + /* Get the Windows file id as an inode replacement. */ + h = CreateFileW( wuri, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL+FILE_FLAG_BACKUP_SEMANTICS, NULL ); + if( h == INVALID_HANDLE_VALUE ) { + errno = GetLastError(); + c_free_locale_string(wuri); + return -1; + + } else { + FILETIME ftCreate, ftAccess, ftWrite; +// SYSTEMTIME stUTC; + + BY_HANDLE_FILE_INFORMATION fileInfo; + + if( GetFileInformationByHandle( h, &fileInfo ) ) { + ULARGE_INTEGER FileIndex; + FileIndex.HighPart = fileInfo.nFileIndexHigh; + FileIndex.LowPart = fileInfo.nFileIndexLow; + FileIndex.QuadPart &= 0x0000FFFFFFFFFFFF; + + /* printf("Index: %I64i\n", FileIndex.QuadPart); */ + + buf->inode = FileIndex.QuadPart; + } + + /* Get the file time with a win32 call rather than through stat. See + * http://www.codeproject.com/Articles/1144/Beating-the-Daylight-Savings-Time-bug-and-getting + * for deeper explanation. + */ + if( GetFileTime(h, &ftCreate, &ftAccess, &ftWrite) ) { + DWORD rem; + buf->atime = FileTimeToUnixTime(&ftAccess, &rem); + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ATIME; + + buf->mtime = FileTimeToUnixTime(&ftWrite, &rem); + /* CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "Local File MTime: %llu", (unsigned long long) buf->mtime ); */ + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + + buf->ctime = FileTimeToUnixTime(&ftCreate, &rem); + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_CTIME; + } + CloseHandle(h); + } + + /* check if it is a symlink on win32 */ + if (c_islink(uri)) { + buf->type = CSYNC_VIO_FILE_TYPE_SYMBOLIC_LINK; + } +#else /* non windows platforms: */ + + /* Both values are only initialized to zero as they are not used in csync */ + /* They are deprecated and will be rmemoved later. */ + buf->blksize = 0; + buf->blkcount = 0; + + buf->atime = sb.st_atime; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ATIME; + + buf->mtime = sb.st_mtime; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + + buf->ctime = sb.st_ctime; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_CTIME; +#endif + + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_INODE; + + buf->nlink = sb.st_nlink; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_LINK_COUNT; + + buf->uid = sb.st_uid; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_UID; + + buf->gid = sb.st_gid; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_GID; + + buf->size = sb.st_size; + buf->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; + + c_free_locale_string(wuri); + return 0; +} + +int csync_vio_local_rename(const char *olduri, const char *newuri) { + return c_rename(olduri, newuri); +} + +int csync_vio_local_unlink(const char *uri) { + mbchar_t *nuri = c_utf8_to_locale(uri); + int re = _tunlink( nuri ); + c_free_locale_string(nuri); + return re; +} + +int csync_vio_local_chmod(const char *uri, mode_t mode) { + mbchar_t *nuri = c_utf8_to_locale(uri); + int re = -1; + + re = _tchmod(nuri, mode); + c_free_locale_string(nuri); + return re; +} + +int csync_vio_local_chown(const char *uri, uid_t owner, gid_t group) { +#if defined _WIN32 + (void)uri; + (void)owner; + (void)group; +#endif + return _tchown(uri, owner, group); +} + +int csync_vio_local_utimes(const char *uri, const struct timeval *times) { + return c_utimes(uri, times); +} diff --git a/csync/src/vio/csync_vio_local.h b/csync/src/vio/csync_vio_local.h new file mode 100644 index 000000000..f838a022c --- /dev/null +++ b/csync/src/vio/csync_vio_local.h @@ -0,0 +1,52 @@ +/* + * libcsync -- a library to sync a directory with another + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_LOCAL_H +#define _CSYNC_VIO_LOCAL_H + +#include "vio/csync_vio_method.h" +#include + +int csync_vio_local_getfd(csync_vio_handle_t *hnd); + +csync_vio_method_handle_t *csync_vio_local_open(const char *durl, int flags, mode_t mode); +csync_vio_method_handle_t *csync_vio_local_creat(const char *durl, mode_t mode); +int csync_vio_local_close(csync_vio_method_handle_t *fhandle); +ssize_t csync_vio_local_read(csync_vio_method_handle_t *fhandle, void *buf, size_t count); +ssize_t csync_vio_local_write(csync_vio_method_handle_t *fhandle, const void *buf, size_t count); +int64_t csync_vio_local_lseek(csync_vio_method_handle_t *fhandle, int64_t offset, int whence); + +csync_vio_method_handle_t *csync_vio_local_opendir(const char *name); +int csync_vio_local_closedir(csync_vio_method_handle_t *dhandle); +csync_vio_file_stat_t *csync_vio_local_readdir(csync_vio_method_handle_t *dhandle); + +int csync_vio_local_mkdir(const char *uri, mode_t mode); +int csync_vio_local_rmdir(const char *uri); + +int csync_vio_local_stat(const char *uri, csync_vio_file_stat_t *buf); +int csync_vio_local_rename(const char *olduri, const char *newuri); +int csync_vio_local_unlink(const char *uri); + +int csync_vio_local_chmod(const char *uri, mode_t mode); +int csync_vio_local_chown(const char *uri, uid_t owner, gid_t group); + +int csync_vio_local_utimes(const char *uri, const struct timeval *times); + +#endif /* _CSYNC_VIO_LOCAL_H */ diff --git a/csync/src/vio/csync_vio_method.h b/csync/src/vio/csync_vio_method.h new file mode 100644 index 000000000..dbd91b2db --- /dev/null +++ b/csync/src/vio/csync_vio_method.h @@ -0,0 +1,123 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_METHOD_H +#define _CSYNC_VIO_METHOD_H + +#include + +#include "csync.h" +#include "vio/csync_vio_file_stat.h" +#include "vio/csync_vio_handle.h" + +#define VIO_METHOD_HAS_FUNC(method,func) \ + (method != NULL && method->func != NULL \ + && ((size_t)(((char *)&((method)->func)) - ((char *)(method))) < (method)->method_table_size)) + +typedef struct csync_vio_method_s csync_vio_method_t; + +/* module capabilities definition. + * remember to set useful defaults in csync_vio.c if you add something here. */ +struct csync_vio_capabilities_s { + bool atomar_copy_support; /* set to true if the backend provides atomar copy */ + bool do_post_copy_stat; /* true if csync should check the copy afterwards */ + bool time_sync_required; /* true if local and remote need to be time synced */ + int unix_extensions; /* -1: do csync detection, 0: no unix extensions, + 1: extensions available */ + bool use_send_file_to_propagate; /* if set, the module rather copies files using send_file than read and write */ + bool get_support; + bool put_support; +}; + +typedef struct csync_vio_capabilities_s csync_vio_capabilities_t; + +typedef csync_vio_method_t *(*csync_vio_method_init_fn)(const char *method_name, + const char *config_args, csync_auth_callback cb, void *userdata); +typedef void (*csync_vio_method_finish_fn)(csync_vio_method_t *method); + +typedef csync_vio_capabilities_t *(*csync_method_get_capabilities_fn)(void); +typedef const char* (*csync_method_get_etag_fn)(const char* path); +typedef csync_vio_method_handle_t *(*csync_method_open_fn)(const char *durl, int flags, mode_t mode); +typedef csync_vio_method_handle_t *(*csync_method_creat_fn)(const char *durl, mode_t mode); +typedef int (*csync_method_close_fn)(csync_vio_method_handle_t *fhandle); +typedef ssize_t (*csync_method_read_fn)(csync_vio_method_handle_t *fhandle, void *buf, size_t count); +typedef ssize_t (*csync_method_write_fn)(csync_vio_method_handle_t *fhandle, const void *buf, size_t count); +typedef int (*csync_method_sendfile_fn)(csync_vio_method_handle_t *src, csync_vio_method_handle_t *dst); +typedef int64_t (*csync_method_lseek_fn)(csync_vio_method_handle_t *fhandle, int64_t offset, int whence); + +typedef csync_vio_method_handle_t *(*csync_method_opendir_fn)(const char *name); +typedef int (*csync_method_closedir_fn)(csync_vio_method_handle_t *dhandle); +typedef csync_vio_file_stat_t *(*csync_method_readdir_fn)(csync_vio_method_handle_t *dhandle); + +typedef int (*csync_method_mkdir_fn)(const char *uri, mode_t mode); +typedef int (*csync_method_rmdir_fn)(const char *uri); + +typedef int (*csync_method_stat_fn)(const char *uri, csync_vio_file_stat_t *buf); +typedef int (*csync_method_rename_fn)(const char *olduri, const char *newuri); +typedef int (*csync_method_unlink_fn)(const char *uri); + +typedef int (*csync_method_chmod_fn)(const char *uri, mode_t mode); +typedef int (*csync_method_chown_fn)(const char *uri, uid_t owner, gid_t group); + +typedef int (*csync_method_utimes_fn)(const char *uri, const struct timeval times[2]); + +typedef int (*csync_method_set_property_fn)(const char *key, void *data); + +typedef char* (*csync_method_get_error_string_fn)(); + +typedef int (*csync_method_commit_fn)(); + +typedef int (*csync_method_get_fn)(csync_vio_method_handle_t *flocal, + csync_vio_method_handle_t *fremote, + csync_vio_file_stat_t *st); +typedef int (*csync_method_put_fn)(csync_vio_method_handle_t *flocal, + csync_vio_method_handle_t *fremote, + csync_vio_file_stat_t *st); + +struct csync_vio_method_s { + size_t method_table_size; /* Used for versioning */ + csync_method_get_capabilities_fn get_capabilities; + csync_method_open_fn open; + csync_method_creat_fn creat; + csync_method_close_fn close; + csync_method_read_fn read; + csync_method_write_fn write; + csync_method_lseek_fn lseek; + csync_method_opendir_fn opendir; + csync_method_closedir_fn closedir; + csync_method_readdir_fn readdir; + csync_method_mkdir_fn mkdir; + csync_method_rmdir_fn rmdir; + csync_method_stat_fn stat; + csync_method_rename_fn rename; + csync_method_unlink_fn unlink; + csync_method_chmod_fn chmod; + csync_method_chown_fn chown; + csync_method_utimes_fn utimes; + csync_method_set_property_fn set_property; + csync_method_get_error_string_fn get_error_string; + csync_method_commit_fn commit; + csync_method_put_fn put; + csync_method_get_fn get; + csync_method_get_etag_fn get_etag; + csync_method_sendfile_fn sendfile; +}; + +#endif /* _CSYNC_VIO_H */ diff --git a/csync/src/vio/csync_vio_module.h b/csync/src/vio/csync_vio_module.h new file mode 100644 index 000000000..c8abcb45a --- /dev/null +++ b/csync/src/vio/csync_vio_module.h @@ -0,0 +1,33 @@ +/* + * cynapses libc functions + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _CSYNC_VIO_MODULE_H +#define _CSYNC_VIO_MODULE_H + +#include "vio/csync_vio_method.h" + +extern csync_vio_method_t *vio_module_init(const char *method_name, + const char *args, csync_auth_callback cb, void *userdata); +extern void vio_module_shutdown(csync_vio_method_t *method); + +extern int csync_vio_getfd(csync_vio_handle_t *hnd); + + +#endif /* _CSYNC_VIO_MODULE_H */ diff --git a/csync/tests/CMakeLists.txt b/csync/tests/CMakeLists.txt new file mode 100644 index 000000000..02a6121ea --- /dev/null +++ b/csync/tests/CMakeLists.txt @@ -0,0 +1,73 @@ +project(tests C) + +set(TORTURE_LIBRARY torture) + +include_directories( + ${CSYNC_PUBLIC_INCLUDE_DIRS} + ${CSTDLIB_PUBLIC_INCLUDE_DIRS} + ${CMAKE_BINARY_DIR} + ${CMOCKA_INCLUDE_DIR} + ${HTTPBF_PUBLIC_INCLUDE_DIRS} +) + +include_directories(${CHECK_INCLUDE_DIRS}) +# create test library +add_library(${TORTURE_LIBRARY} STATIC torture.c cmdline.c) +target_link_libraries(${TORTURE_LIBRARY} ${CMOCKA_LIBRARIES} ${CSYNC_LIBRARY} ${CSTDLIB_LIBRARY}) + +set(TEST_TARGET_LIBRARIES ${TORTURE_LIBRARY}) + +# create tests + +# std +add_cmocka_test(check_std_c_alloc std_tests/check_std_c_alloc.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_dir std_tests/check_std_c_dir.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_file std_tests/check_std_c_file.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_jhash std_tests/check_std_c_jhash.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_list std_tests/check_std_c_list.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_path std_tests/check_std_c_path.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_rbtree std_tests/check_std_c_rbtree.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_str std_tests/check_std_c_str.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_std_c_time std_tests/check_std_c_time.c ${TEST_TARGET_LIBRARIES}) + +# csync tests +# This will be rewritten soon anyway. +#add_cmocka_test(check_logger log_tests/check_log.c ${TEST_TARGET_LIBRARIES}) + +add_cmocka_test(check_csync_create csync_tests/check_csync_create.c ${TEST_TARGET_LIBRARIES}) +# Disable lock testing for us. +# if(NOT WIN32) +# add_cmocka_test(check_csync_lock csync_tests/check_csync_lock.c ${TEST_TARGET_LIBRARIES}) +# endif() +add_cmocka_test(check_csync_log csync_tests/check_csync_log.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_config csync_tests/check_csync_config.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_exclude csync_tests/check_csync_exclude.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_statedb_load csync_tests/check_csync_statedb_load.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_time csync_tests/check_csync_time.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_util csync_tests/check_csync_util.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_misc csync_tests/check_csync_misc.c ${TEST_TARGET_LIBRARIES}) + +# csync tests which require init +add_cmocka_test(check_csync_init csync_tests/check_csync_init.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_statedb_query csync_tests/check_csync_statedb_query.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_csync_commit csync_tests/check_csync_commit.c ${TEST_TARGET_LIBRARIES}) + +# treewalk +add_cmocka_test(check_csync_treewalk csync_tests/check_csync_treewalk.c ${TEST_TARGET_LIBRARIES}) + +# vio +add_cmocka_test(check_vio_handle vio_tests/check_vio_handle.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_vio_file_stat vio_tests/check_vio_file_stat.c ${TEST_TARGET_LIBRARIES}) +add_cmocka_test(check_vio vio_tests/check_vio.c ${TEST_TARGET_LIBRARIES}) + +# sync +add_cmocka_test(check_csync_update csync_tests/check_csync_update.c ${TEST_TARGET_LIBRARIES}) + +# encoding +add_cmocka_test(check_encoding_functions encoding_tests/check_encoding.c ${TEST_TARGET_LIBRARIES}) + +# httpbf +set(TEST_HTTPBF_LIBRARIES ${TEST_TARGET_LIBRARIES} ${NEON_LIBRARIES}) +add_cmocka_test(check_httpbf httpbf_tests/hbf_send_test.c ${TEST_HTTPBF_LIBRARIES} ) + + diff --git a/csync/tests/cmdline.c b/csync/tests/cmdline.c new file mode 100644 index 000000000..be79a4450 --- /dev/null +++ b/csync/tests/cmdline.c @@ -0,0 +1,71 @@ +#include "config_csync.h" +#include "torture.h" + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "csync test 0.2"; +const char *argp_program_bug_address = ""; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "csync test"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make csync test more verbose", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case 'v': + arguments->verbose++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else + (void) argc; + (void) argv; + (void) arguments; +#endif /* HAVE_ARGP_H */ +} diff --git a/csync/tests/csync_tests/check_csync_commit.c b/csync/tests/csync_tests/check_csync_commit.c new file mode 100644 index 000000000..cbe507e13 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_commit.c @@ -0,0 +1,106 @@ +#include + +#include "torture.h" + +#include "csync_private.h" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void setup_module(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "dummy://foo/bar"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + + rc = csync_init(csync); + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_csync_commit_null(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = csync_commit(NULL); + assert_int_equal(rc, -1); +} + +static void check_csync_commit(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_commit(csync); + assert_int_equal(rc, 0); + + assert_int_equal(csync->status & CSYNC_STATUS_INIT, 1); +} + +static void check_csync_commit_dummy(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_commit(csync); + assert_int_equal(rc, 0); + + assert_int_equal(csync->status & CSYNC_STATUS_INIT, 1); + +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_commit_null, setup, teardown), + unit_test_setup_teardown(check_csync_commit, setup, teardown), + unit_test_setup_teardown(check_csync_commit_dummy, setup_module, teardown), + }; + + return run_tests(tests); +} diff --git a/csync/tests/csync_tests/check_csync_config.c b/csync/tests/csync_tests/check_csync_config.c new file mode 100644 index 000000000..66a1574aa --- /dev/null +++ b/csync/tests/csync_tests/check_csync_config.c @@ -0,0 +1,78 @@ +#include + +#include "torture.h" + +#define CSYNC_TEST 1 +#include "csync.h" +#include "csync_config.c" + +#define TESTCONF "/tmp/check_csync1/csync.conf" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + free(csync->options.config_dir); + csync->options.config_dir = c_strdup("/tmp/check_csync1/"); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_csync_config_copy_default(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = _csync_config_copy_default(TESTCONF); + assert_int_equal(rc, 0); +} + +static void check_csync_config_load(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = _csync_config_copy_default(TESTCONF); + assert_int_equal(rc, 0); + + rc = csync_config_parse_file(csync, TESTCONF); + assert_int_equal(rc, 0); + + assert_false(csync->options.with_conflict_copys); + assert_int_equal(csync->options.max_time_difference, 10); + assert_int_equal(csync->options.max_depth, 50); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_config_copy_default, setup, teardown), + unit_test_setup_teardown(check_csync_config_load, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_create.c b/csync/tests/csync_tests/check_csync_create.c new file mode 100644 index 000000000..c0d795fd2 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_create.c @@ -0,0 +1,49 @@ +#include +#include + +#include "torture.h" + +#include "csync_private.h" + + +static void check_csync_destroy_null(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = csync_destroy(NULL); + assert_int_equal(rc, -1); +} + +static void check_csync_create(void **state) +{ + CSYNC *csync; + char confdir[1024] = {0}; + int rc; + + (void) state; /* unused */ + + rc = csync_create(&csync, "/tmp/csync1", "/tmp/csync2"); + assert_int_equal(rc, 0); + + assert_int_equal(csync->options.max_depth, MAX_DEPTH); + assert_int_equal(csync->options.max_time_difference, MAX_TIME_DIFFERENCE); + + snprintf(confdir, sizeof(confdir), "%s/%s", getenv("HOME"), CSYNC_CONF_DIR); + assert_string_equal(csync->options.config_dir, confdir); + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_csync_destroy_null), + unit_test(check_csync_create), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_exclude.c b/csync/tests/csync_tests/check_csync_exclude.c new file mode 100644 index 000000000..f747ed0f2 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_exclude.c @@ -0,0 +1,160 @@ +#include "config_csync.h" +#include + +#include "torture.h" + +#define CSYNC_TEST 1 +#include "csync_exclude.c" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + free(csync->options.config_dir); + csync->options.config_dir = c_strdup("/tmp/check_csync1/"); + assert_non_null(csync->options.config_dir); + + *state = csync; +} + +static void setup_init(void **state) { + CSYNC *csync; + int rc; + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + free(csync->options.config_dir); + csync->options.config_dir = c_strdup("/tmp/check_csync1/"); + assert_non_null(csync->options.config_dir); + + rc = csync_exclude_load(csync, BINARYDIR "/config/" CSYNC_EXCLUDE_FILE); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_csync_exclude_add(void **state) +{ + CSYNC *csync = *state; + _csync_exclude_add(csync, (const char *) "/tmp/check_csync1/*"); + assert_string_equal(csync->excludes->vector[0], "/tmp/check_csync1/*"); +} + +static void check_csync_exclude_load(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_exclude_load(csync, BINARYDIR "/config/" CSYNC_EXCLUDE_FILE); + assert_int_equal(rc, 0); + + assert_string_equal(csync->excludes->vector[0], ".beagle"); +} + +static void check_csync_excluded(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_excluded(csync, "krawel_krawel", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_NOT_EXCLUDED); + rc = csync_excluded(csync, ".kde/share/config/kwin.eventsrc", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_NOT_EXCLUDED); + rc = csync_excluded(csync, ".kde4/cache-maximegalon/cache1.txt", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + rc = csync_excluded(csync, ".mozilla/plugins", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + /* + * Test for patterns in subdirs. '.beagle' is defined as a pattern and has + * to be found in top dir as well as in directories underneath. + */ + rc = csync_excluded(csync, ".beagle", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + rc = csync_excluded(csync, "foo/.beagle", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + rc = csync_excluded(csync, "foo/bar/.beagle", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + /* + * Pattern: .java/ + * A file wont be excluded but a directory .java will be. + */ + rc = csync_excluded(csync, ".java", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + rc = csync_excluded(csync, ".java", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_NOT_EXCLUDED); + + /* Files in the ignored dir .java will also be ignored. */ + rc = csync_excluded(csync, ".java/totally_amazing.jar", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + /* and also in subdirs */ + rc = csync_excluded(csync, "projects/.java/totally_amazing.jar", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + /* csync-journal is ignored in general silently. */ + rc = csync_excluded(csync, ".csync_journal.db", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + rc = csync_excluded(csync, ".csync_journal.db.ctmp", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + rc = csync_excluded(csync, "subdir/.csync_journal.db", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_SILENTLY_EXCLUDED); + + /* pattern ]*.directory - ignore and remove */ + rc = csync_excluded(csync, "my.directory", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_AND_REMOVE); + + rc = csync_excluded(csync, "/a_folder/my.directory", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_AND_REMOVE); + + /* Not excluded because the pattern .netscape/cache requires directory. */ + rc = csync_excluded(csync, ".netscape/cache", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_NOT_EXCLUDED); + + rc = csync_excluded(csync, ".netscape/cache", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + /* Excluded because the parent dir .netscape/cache is ingored. */ + rc = csync_excluded(csync, ".netscape/cache/foo", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + rc = csync_excluded(csync, ".netscape/cache/bar.txt", CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + rc = csync_excluded(csync, ".netscape/cache/longrun", CSYNC_FTW_TYPE_DIR); + assert_int_equal(rc, CSYNC_FILE_EXCLUDE_LIST); + + +} + + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_exclude_add, setup, teardown), + unit_test_setup_teardown(check_csync_exclude_load, setup, teardown), + unit_test_setup_teardown(check_csync_excluded, setup_init, teardown), + }; + + return run_tests(tests); +} diff --git a/csync/tests/csync_tests/check_csync_init.c b/csync/tests/csync_tests/check_csync_init.c new file mode 100644 index 000000000..79d274673 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_init.c @@ -0,0 +1,97 @@ +#include + +#include "torture.h" + +#include "csync_private.h" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void setup_module(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "dummy://foo/bar"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_csync_init_null(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = csync_init(NULL); + assert_int_equal(rc, -1); +} + +static void check_csync_init(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_init(csync); + assert_int_equal(rc, 0); + + assert_int_equal(csync->status & CSYNC_STATUS_INIT, 1); + + rc = csync_init(csync); + assert_int_equal(rc, 1); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_init_null, setup, teardown), + unit_test_setup_teardown(check_csync_init, setup, teardown), + unit_test_setup_teardown(check_csync_init, setup_module, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_lock.c b/csync/tests/csync_tests/check_csync_lock.c new file mode 100644 index 000000000..0ef76f64e --- /dev/null +++ b/csync/tests/csync_tests/check_csync_lock.c @@ -0,0 +1,94 @@ +#include +#include +#include + +#include +#include +#include + +#include "torture.h" + +#define CSYNC_TEST 1 +#include "std/c_file.h" +#include "csync_lock.h" + +#define TEST_LOCK "/tmp/check_csync/test" + +static void setup(void **state) { + int rc; + + (void) state; /* unused */ + + rc = system("mkdir -p /tmp/check_csync"); + assert_int_equal(rc, 0); +} + +static void teardown(void **state) { + int rc; + + (void) state; /* unused */ + + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); +} + +static void check_csync_lock(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = csync_lock(TEST_LOCK); + assert_int_equal(rc, 0); + + assert_true(c_isfile(TEST_LOCK)); + + rc = csync_lock(TEST_LOCK); + assert_int_equal(rc, -1); + + csync_lock_remove(TEST_LOCK); + assert_false(c_isfile(TEST_LOCK)); +} + +static void check_csync_lock_content(void **state) +{ + char buf[8] = {0}; + int fd, pid, rc; + + (void) state; /* unused */ + + rc = csync_lock(TEST_LOCK); + assert_int_equal(rc, 0); + + assert_true(c_isfile(TEST_LOCK)); + + /* open lock file */ + fd = open(TEST_LOCK, O_RDONLY); + assert_true(fd > 0); + + /* read content */ + pid = read(fd, buf, sizeof(buf)); + close(fd); + + assert_true(pid > 0); + + /* get pid */ + buf[sizeof(buf) - 1] = '\0'; + pid = strtol(buf, NULL, 10); + + assert_int_equal(pid, getpid()); + + csync_lock_remove(TEST_LOCK); + assert_false(c_isfile(TEST_LOCK)); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_lock, setup, teardown), + unit_test_setup_teardown(check_csync_lock_content, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_log.c b/csync/tests/csync_tests/check_csync_log.c new file mode 100644 index 000000000..860373343 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_log.c @@ -0,0 +1,136 @@ +#include +#include +#include + +#include "torture.h" + +#include "csync.h" +#include "csync_log.c" +#include "c_private.h" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_log_callback(int verbosity, + const char *function, + const char *buffer, + void *userdata) +{ + int rc; + + assert_true(verbosity >= 0); + assert_non_null(function); + assert_false(function[0] == '\0'); + assert_non_null(buffer); + assert_false(buffer[0] == '\0'); + + (void) userdata; /* unused */ + + rc = system("touch /tmp/check_csync1/cb_called"); + assert_int_equal(rc, 0); +} + +static void check_set_log_level(void **state) +{ + int rc; + + (void) state; + + rc = csync_set_log_level(-5); + assert_int_equal(rc, -1); + + rc = csync_set_log_level(5); + assert_int_equal(rc, 0); + + rc = csync_get_log_level(); + assert_int_equal(rc, 5); +} + +static void check_set_auth_callback(void **state) +{ + csync_log_callback log_fn; + int rc; + + (void) state; + + rc = csync_set_log_callback(NULL); + assert_int_equal(rc, -1); + + rc = csync_set_log_callback(check_log_callback); + assert_int_equal(rc, 0); + + log_fn = csync_get_log_callback(); + assert_non_null(log_fn); + assert_true(log_fn == &check_log_callback); +} + +static void check_logging(void **state) +{ + int rc; + csync_stat_t sb; + mbchar_t *path; + path = c_utf8_to_locale("/tmp/check_csync1/cb_called"); + + (void) state; /* unused */ + + assert_non_null(path); + + rc = csync_set_log_level(1); + assert_int_equal(rc, 0); + + rc = csync_set_log_callback(check_log_callback); + assert_int_equal(rc, 0); + + csync_log(1, __FUNCTION__, "rc = %d", rc); + + rc = _tstat(path, &sb); + + c_free_locale_string(path); + + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_set_log_level), + unit_test(check_set_auth_callback), + unit_test_setup_teardown(check_logging, setup, teardown), + }; + + return run_tests(tests); +} diff --git a/csync/tests/csync_tests/check_csync_misc.c b/csync/tests/csync_tests/check_csync_misc.c new file mode 100644 index 000000000..e6bd5001c --- /dev/null +++ b/csync/tests/csync_tests/check_csync_misc.c @@ -0,0 +1,38 @@ +#include "torture.h" + +#include "csync_misc.h" +#include + +static void check_csync_normalize_etag(void **state) +{ + char *str; + + (void) state; /* unused */ + +#define CHECK_NORMALIZE_ETAG(TEST, EXPECT) \ + str = csync_normalize_etag(TEST); \ + assert_string_equal(str, EXPECT); \ + free(str); + + + CHECK_NORMALIZE_ETAG("foo", "foo"); + CHECK_NORMALIZE_ETAG("\"foo\"", "foo"); + CHECK_NORMALIZE_ETAG("\"nar123\"", "nar123"); + CHECK_NORMALIZE_ETAG("", ""); + CHECK_NORMALIZE_ETAG("\"\"", ""); + + /* Test with -gzip (all combinaison) */ + CHECK_NORMALIZE_ETAG("foo-gzip", "foo"); + CHECK_NORMALIZE_ETAG("\"foo\"-gzip", "foo"); + CHECK_NORMALIZE_ETAG("\"foo-gzip\"", "foo"); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_csync_normalize_etag), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_statedb_load.c b/csync/tests/csync_tests/check_csync_statedb_load.c new file mode 100644 index 000000000..d2a898280 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_statedb_load.c @@ -0,0 +1,144 @@ +#include +#include + +#include "torture.h" + +#define CSYNC_TEST 1 +#include "csync_statedb.c" + +#define TESTDB "/tmp/check_csync1/test.db" +#define TESTDBTMP "/tmp/check_csync1/test.db.ctmp" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + csync_set_config_dir(csync, "/tmp/check_csync1/"); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void check_csync_statedb_check(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = system("mkdir -p /tmp/check_csync1"); + + /* old db */ + rc = system("echo \"SQLite format 2\" > /tmp/check_csync1/test.db"); + assert_int_equal(rc, 0); + rc = _csync_statedb_check(TESTDB); + assert_int_equal(rc, 1); + + /* db already exists */ + rc = _csync_statedb_check(TESTDB); + assert_int_equal(rc, 1); + + /* no db exists */ + rc = system("rm -f /tmp/check_csync1/test.db"); + assert_int_equal(rc, 0); + rc = _csync_statedb_check(TESTDB); + assert_int_equal(rc, 1); + + rc = _csync_statedb_check("/tmp/check_csync1/"); + assert_int_equal(rc, -1); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); +} + +static void check_csync_statedb_load(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + int rc; + mbchar_t *testdbtmp = c_utf8_to_locale(TESTDBTMP); + assert_non_null( testdbtmp ); + + rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); + assert_int_equal(rc, 0); + + rc = _tstat(testdbtmp, &sb); + assert_int_equal(rc, 0); + + sqlite3_close(csync->statedb.db); + c_free_locale_string(testdbtmp); +} + +static void check_csync_statedb_close(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + time_t modtime; + mbchar_t *testdb = c_utf8_to_locale(TESTDB); + int rc; + + /* statedb not written */ + csync_statedb_load(csync, TESTDB, &csync->statedb.db); + + rc = _tstat(testdb, &sb); + assert_int_equal(rc, 0); + modtime = sb.st_mtime; + + rc = csync_statedb_close(TESTDB, csync->statedb.db, 0); + assert_int_equal(rc, 0); + + rc = _tstat(testdb, &sb); + assert_int_equal(rc, 0); + assert_int_equal(modtime, sb.st_mtime); + + csync_statedb_load(csync, TESTDB, &csync->statedb.db); + + rc = _tstat(testdb, &sb); + assert_int_equal(rc, 0); + modtime = sb.st_mtime; + + /* wait a sec or the modtime will be the same */ + sleep(1); + + /* statedb written */ + rc = csync_statedb_close(TESTDB, csync->statedb.db, 1); + assert_int_equal(rc, 0); + + rc = _tstat(testdb, &sb); + assert_int_equal(rc, 0); + assert_true(modtime < sb.st_mtime); + + c_free_locale_string(testdb); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_statedb_check, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_load, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_close, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_statedb_query.c b/csync/tests/csync_tests/check_csync_statedb_query.c new file mode 100644 index 000000000..1cb018953 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_statedb_query.c @@ -0,0 +1,269 @@ +#include "torture.h" + +#define CSYNC_TEST 1 +#include "csync_statedb.c" + +#define TESTDB "/tmp/check_csync1/test.db" +#define TESTDBTMP "/tmp/check_csync1/test.db.ctmp" + + + +static void setup(void **state) +{ + CSYNC *csync; + int rc = 0; + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync"); + assert_int_equal(rc, 0); + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = csync_set_config_dir(csync, "/tmp/check_csync/"); + assert_int_equal(rc, 0); + rc = csync_init(csync); + assert_int_equal(rc, 0); + + rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void setup_db(void **state) +{ + CSYNC *csync; + char *stmt = NULL; + int rc = 0; + c_strlist_t *result = NULL; + + setup(state); + csync = *state; + + // rc = csync_statedb_create_tables(csync->statedb.db); + assert_int_equal(rc, 0); + + result = csync_statedb_query(csync->statedb.db, + "CREATE TABLE IF NOT EXISTS metadata (" + "phash INTEGER(8)," + "pathlen INTEGER," + "path VARCHAR(4096)," + "inode INTEGER," + "uid INTEGER," + "gid INTEGER," + "mode INTEGER," + "modtime INTEGER(8)," + "type INTEGER," + "md5 VARCHAR(32)," + "PRIMARY KEY(phash)" + ");" + ); + + assert_non_null(result); + c_strlist_destroy(result); + + + stmt = sqlite3_mprintf("INSERT INTO metadata" + "(phash, pathlen, path, inode, uid, gid, mode, modtime, type, md5) VALUES" + "(%lu, %d, '%q', %d, %d, %d, %d, %lu, %d, %lu);", + 42, + 42, + "It's a rainy day", + 23, + 42, + 42, + 42, + 42, + 2, + 43); + + // rc = csync_statedb_insert(csync->statedb.db, stmt); + sqlite3_free(stmt); +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc = 0; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = NULL; +} + + +static void check_csync_statedb_query_statement(void **state) +{ + CSYNC *csync = *state; + c_strlist_t *result; + + result = csync_statedb_query(csync->statedb.db, ""); + assert_null(result); + if (result != NULL) { + c_strlist_destroy(result); + } + + result = csync_statedb_query(csync->statedb.db, "SELECT;"); + assert_null(result); + if (result != NULL) { + c_strlist_destroy(result); + } +} + +static void check_csync_statedb_create_error(void **state) +{ + CSYNC *csync = *state; + c_strlist_t *result; + + result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));"); + assert_non_null(result); + c_strlist_destroy(result); + + result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));"); + assert_null(result); + + c_strlist_destroy(result); +} + +static void check_csync_statedb_insert_statement(void **state) +{ + CSYNC *csync = *state; + c_strlist_t *result; + int rc = 0; + + result = csync_statedb_query(csync->statedb.db, "CREATE TABLE test(phash INTEGER, text VARCHAR(10));"); + assert_non_null(result); + c_strlist_destroy(result); + + // rc = csync_statedb_insert(csync->statedb.db, "INSERT;"); + assert_int_equal(rc, 0); + // rc = csync_statedb_insert(csync->statedb.db, "INSERT"); + assert_int_equal(rc, 0); + // rc = csync_statedb_insert(csync->statedb.db, ""); + assert_int_equal(rc, 0); +} + + + +static void check_csync_statedb_drop_tables(void **state) +{ + // CSYNC *csync = *state; + int rc = 0; + (void) state; + + // rc = csync_statedb_drop_tables(csync->statedb.db); + assert_int_equal(rc, 0); + // rc = csync_statedb_create_tables(csync->statedb.db); + assert_int_equal(rc, 0); + // rc = csync_statedb_drop_tables(csync->statedb.db); + assert_int_equal(rc, 0); +} + +static void check_csync_statedb_insert_metadata(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + int i, rc = 0; + + // rc = csync_statedb_create_tables(csync->statedb.db); + assert_int_equal(rc, 0); + + for (i = 0; i < 100; i++) { + st = c_malloc(sizeof(csync_file_stat_t) + 30 ); + snprintf(st->path, 29, "file_%d" , i ); + st->phash = i; + + rc = c_rbtree_insert(csync->local.tree, (void *) st); + assert_int_equal(rc, 0); + } + + // rc = csync_statedb_insert_metadata(csync, csync->statedb.db); + assert_int_equal(rc, 0); +} + +static void check_csync_statedb_write(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + int i, rc; + + for (i = 0; i < 100; i++) { + st = c_malloc(sizeof(csync_file_stat_t) + 30); + snprintf(st->path, 29, "file_%d" , i ); + st->phash = i; + + rc = c_rbtree_insert(csync->local.tree, (void *) st); + assert_int_equal(rc, 0); + } + + // rc = csync_statedb_write(csync, csync->statedb.db); + assert_int_equal(rc, 0); +} + + +static void check_csync_statedb_get_stat_by_hash_not_found(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *tmp; + + tmp = csync_statedb_get_stat_by_hash(csync->statedb.db, (uint64_t) 666); + assert_null(tmp); + + free(tmp); +} + +static void check_csync_statedb_get_stat_by_inode(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *tmp; + + tmp = csync_statedb_get_stat_by_inode(csync->statedb.db, (ino_t) 23); + assert_non_null(tmp); + + assert_int_equal(tmp->phash, 42); + assert_int_equal(tmp->inode, 23); + + free(tmp); +} + +static void check_csync_statedb_get_stat_by_inode_not_found(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *tmp; + + tmp = csync_statedb_get_stat_by_inode(csync->statedb.db, (ino_t) 666); + assert_null(tmp); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_statedb_query_statement, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_create_error, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_insert_statement, setup, teardown), + /* unit_test_setup_teardown(check_csync_statedb_is_empty, setup, teardown), */ + /* unit_test_setup_teardown(check_csync_statedb_create_tables, setup, teardown), */ + unit_test_setup_teardown(check_csync_statedb_drop_tables, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_insert_metadata, setup, teardown), + unit_test_setup_teardown(check_csync_statedb_write, setup, teardown), + /* unit_test_setup_teardown(check_csync_statedb_get_stat_by_hash, setup_db, teardown), */ + unit_test_setup_teardown(check_csync_statedb_get_stat_by_hash_not_found, setup_db, teardown), + /* unit_test_setup_teardown(check_csync_statedb_get_stat_by_inode, setup_db, teardown), */ + unit_test_setup_teardown(check_csync_statedb_get_stat_by_inode_not_found, setup_db, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_time.c b/csync/tests/csync_tests/check_csync_time.c new file mode 100644 index 000000000..e41f7ffe8 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_time.c @@ -0,0 +1,50 @@ +#include "torture.h" + +#include "csync_time.h" + +static void setup(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); +} + +static void check_csync_time(void **state) +{ + CSYNC *csync = *state; + /* + * The creation should took less than 1 second, so the return + * value should be 0. + */ + assert_int_equal(csync_timediff(csync), 0); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_time, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_treewalk.c b/csync/tests/csync_tests/check_csync_treewalk.c new file mode 100644 index 000000000..7de6bb3b5 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_treewalk.c @@ -0,0 +1,183 @@ +#include + +#include "torture.h" + +#define CSYNC_TEST 1 +#include "csync_config.c" + +static void setup_local(void **state) { + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync"); + assert_int_equal(rc, 0); + + rc = system("echo \"This is test data\" > /tmp/check_csync1/testfile1.txt"); + assert_int_equal(rc, 0); + + rc = system("echo \"This is also test data\" > /tmp/check_csync1/testfile2.txt"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync/"); + assert_int_equal(rc, 0); + + rc = csync_init(csync); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown_local(void **state) { + CSYNC *csync = (CSYNC *)*state; + + csync_destroy(csync); + system("rm -rf /tmp/check_csync1"); + system("rm -rf /tmp/check_csync2"); + system("rm -rf /tmp/check_csync"); +} + +static void setup_remote(void **state) { + CSYNC *csync = (CSYNC *)*state; + int rc; + + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = system("echo \"This is test data\" > /tmp/check_csync2/testfile1.txt"); + assert_int_equal(rc, 0); + + rc = system("echo \"This is also test data\" > /tmp/check_csync2/testfile2.txt"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + + rc = csync_set_config_dir(csync, "/tmp/check_csync/"); + assert_int_equal(rc, 0); + + rc = csync_init(csync); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown_remote(void **state) { + CSYNC *csync = (CSYNC *)*state; + + csync_destroy(csync); + system("rm -rf /tmp/check_csync1"); + system("rm -rf /tmp/check_csync2"); +} + +static int visitor(TREE_WALK_FILE* file, void *userdata) +{ + int *file_count; + + assert_non_null(userdata); + assert_non_null(file); + + file_count = (int *)userdata; + + (*file_count)++; + + return 0; +} + +static void check_csync_treewalk_local(void **state) +{ + CSYNC *csync = (CSYNC *)*state; + int file_count = 0; + int rc; + + csync_set_userdata(csync, (void *)&file_count); + + rc = csync_walk_local_tree(csync, &visitor, 0); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 0); + + rc = csync_update(csync); + assert_int_equal(rc, 0); + + rc = csync_walk_local_tree(csync, &visitor, 0); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 2); +} + +static void check_csync_treewalk_remote(void **state) +{ + CSYNC *csync = (CSYNC *)*state; + int file_count = 0; + int rc; + + csync_set_userdata(csync, &file_count); + + assert_non_null(csync->remote.tree); + + rc = csync_walk_remote_tree(csync, &visitor, 0); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 0); + + /* reconcile doesn't update the tree */ + rc = csync_update(csync); + assert_int_equal(rc, 0); + + rc = csync_walk_remote_tree(csync, &visitor, 0); + assert_int_equal(rc, 0); + + assert_int_equal(file_count, 2); +} + +static void check_csync_treewalk_local_with_filter(void **state) +{ + CSYNC *csync = (CSYNC *)*state; + int file_count = 0; + int rc; + + csync_set_userdata(csync, &file_count); + + rc = csync_walk_local_tree(csync, &visitor, 0); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 0); + + rc = csync_update(csync); + assert_int_equal(rc, 0); + + rc = csync_walk_local_tree(csync, &visitor, CSYNC_INSTRUCTION_NEW); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 2 ); + + file_count = 0; + rc = csync_walk_local_tree(csync, + &visitor, + CSYNC_INSTRUCTION_NEW|CSYNC_INSTRUCTION_REMOVE); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 2); + + file_count = 0; + rc = csync_walk_local_tree(csync, &visitor, CSYNC_INSTRUCTION_RENAME); + assert_int_equal(rc, 0); + assert_int_equal(file_count, 0); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_treewalk_local, setup_local, teardown_local ), + unit_test_setup_teardown(check_csync_treewalk_remote, setup_remote, teardown_remote), + unit_test_setup_teardown(check_csync_treewalk_local_with_filter, setup_local, teardown_local) + }; + + return run_tests(tests); +} diff --git a/csync/tests/csync_tests/check_csync_update.c b/csync/tests/csync_tests/check_csync_update.c new file mode 100644 index 000000000..2e78313a5 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_update.c @@ -0,0 +1,431 @@ +#include "torture.h" + +#include "csync_update.c" + +#define TESTDB "/tmp/check_csync/journal.db" + +static void setup(void **state) +{ + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = csync_create(&csync, "/tmp/check_csync1", "/tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + rc = csync_init(csync); + assert_int_equal(rc, 0); + rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); + assert_int_equal(rc, 0); + + + *state = csync; +} + +static void setup_ftw(void **state) +{ + CSYNC *csync; + int rc; + + rc = system("mkdir -p /tmp/check_csync"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("mkdir -p /tmp/check_csync2"); + assert_int_equal(rc, 0); + rc = csync_create(&csync, "/tmp", "/tmp"); + assert_int_equal(rc, 0); + rc = csync_set_config_dir(csync, "/tmp/check_csync"); + assert_int_equal(rc, 0); + rc = csync_init(csync); + assert_int_equal(rc, 0); + rc = csync_statedb_load(csync, TESTDB, &csync->statedb.db); + assert_int_equal(rc, 0); + + *state = csync; +} + +static void teardown(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + *state = NULL; +} + +static void teardown_rm(void **state) { + int rc; + + teardown(state); + + rc = system("rm -rf /tmp/check_csync"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync1"); + assert_int_equal(rc, 0); + rc = system("rm -rf /tmp/check_csync2"); + assert_int_equal(rc, 0); +} + +/* create a file stat, caller must free memory */ +static csync_vio_file_stat_t* create_fstat(const char *name, + ino_t inode, + nlink_t nlink, + time_t mtime) +{ + csync_vio_file_stat_t *fs = NULL; + time_t t; + + fs = csync_vio_file_stat_new(); + if (fs == NULL) { + return NULL; + } + + if (name && *name) { + fs->name = c_strdup(name); + } else { + fs->name = c_strdup("file.txt"); + } + + if (fs->name == NULL) { + csync_vio_file_stat_destroy(fs); + return NULL; + } + + fs->fields = CSYNC_VIO_FILE_STAT_FIELDS_NONE; + + fs->type = CSYNC_VIO_FILE_TYPE_REGULAR; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_TYPE; + + fs->mode = 0644; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_PERMISSIONS; + + if (inode == 0) { + fs->inode = 619070; + } else { + fs->inode = inode; + } + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_INODE; + + fs->device = 0; + + fs->size = 157459; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_SIZE; + + if (nlink == 0) { + fs->nlink = 1; + } else { + fs->nlink = nlink; + } + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_LINK_COUNT; + + fs->uid = 1000; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_UID; + + fs->gid = 1000; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_GID; + + fs->blkcount = 312; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_BLOCK_COUNT; + + fs->blksize = 4096; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_BLOCK_SIZE; + + if (mtime == 0) { + fs->atime = fs->ctime = fs->mtime = time(&t); + } else { + fs->atime = fs->ctime = fs->mtime = mtime; + } + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_ATIME; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_CTIME; + fs->fields |= CSYNC_VIO_FILE_STAT_FIELDS_MTIME; + + return fs; +} + +static int failing_fn(CSYNC *ctx, + const char *file, + const csync_vio_file_stat_t *fs, + enum csync_ftw_flags_e flag) +{ + (void) ctx; + (void) file; + (void) fs; + (void) flag; + + return -1; +} + +/* detect a new file */ +static void check_csync_detect_update(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + csync_vio_file_stat_t *fs; + int rc; + + fs = create_fstat("file.txt", 0, 1, 1217597845); + assert_non_null(fs); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to new */ + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); + + /* create a statedb */ + csync_set_status(csync, 0xFFFF); + + csync_vio_file_stat_destroy(fs); +} + +/* Test behaviour in case no db is there. For that its important that the + * test before this one uses teardown_rm. + */ +static void check_csync_detect_update_db_none(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + csync_vio_file_stat_t *fs; + int rc; + + fs = create_fstat("file.txt", 0, 1, 1217597845); + assert_non_null(fs); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to new */ + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); + + /* set the instruction to UPDATED that it gets written to the statedb */ + st->instruction = CSYNC_INSTRUCTION_UPDATED; + + /* create a statedb */ + csync_set_status(csync, 0xFFFF); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_detect_update_db_eval(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + csync_vio_file_stat_t *fs; + int rc; + + fs = create_fstat("file.txt", 0, 1, 42); + assert_non_null(fs); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to new */ + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); + + /* set the instruction to UPDATED that it gets written to the statedb */ + st->instruction = CSYNC_INSTRUCTION_UPDATED; + + /* create a statedb */ + csync_set_status(csync, 0xFFFF); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_detect_update_db_rename(void **state) +{ + CSYNC *csync = *state; + // csync_file_stat_t *st; + + csync_vio_file_stat_t *fs; + int rc = 0; + char *stmt = NULL; + + // rc = csync_statedb_create_tables(csync->statedb.db); + + assert_int_equal(rc, 0); + stmt = sqlite3_mprintf("INSERT INTO metadata" + "(phash, pathlen, path, inode, uid, gid, mode, modtime,type,md5) VALUES" + "(%lld, %d, '%q', %d, %d, %d, %d, %lld, %d, '%q');", + (long long signed int)42, + 42, + "I_was_wurst_before_I_became_wurstsalat", + 619070, + 42, + 42, + 42, + (long long signed int)42, + 0, + "4711"); + + // rc = csync_statedb_insert(csync->statedb.db, stmt); + sqlite3_free(stmt); + + fs = create_fstat("wurst.txt", 0, 1, 42); + assert_non_null(fs); + csync_set_statedb_exists(csync, 1); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/wurst.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to rename */ + /* + * temporarily broken. + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_RENAME); + + st->instruction = CSYNC_INSTRUCTION_UPDATED; + */ + /* create a statedb */ + csync_set_status(csync, 0xFFFF); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_detect_update_db_new(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + csync_vio_file_stat_t *fs; + int rc; + + fs = create_fstat("file.txt", 42000, 1, 0); + assert_non_null(fs); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to new */ + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_NEW); + + /* set the instruction to UPDATED that it gets written to the statedb */ + st->instruction = CSYNC_INSTRUCTION_UPDATED; + + /* create a statedb */ + csync_set_status(csync, 0xFFFF); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_detect_update_nlink(void **state) +{ + CSYNC *csync = *state; + csync_file_stat_t *st; + csync_vio_file_stat_t *fs; + int rc; + + /* create vio file stat with nlink greater than 1 */ + fs = create_fstat("file.txt", 0, 7, 0); + assert_non_null(fs); + + /* add it to local tree */ + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, 0); + + /* the instruction should be set to ignore */ + st = c_rbtree_node_data(csync->local.tree->root); + assert_int_equal(st->instruction, CSYNC_INSTRUCTION_IGNORE); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_detect_update_null(void **state) +{ + CSYNC *csync = *state; + csync_vio_file_stat_t *fs; + int rc; + + fs = create_fstat("file.txt", 0, 1, 0); + assert_non_null(fs); + + rc = _csync_detect_update(csync, + NULL, + fs, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, -1); + + rc = _csync_detect_update(csync, + "/tmp/check_csync1/file.txt", + NULL, + CSYNC_FTW_TYPE_FILE); + assert_int_equal(rc, -1); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_ftw(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_ftw(csync, "/tmp", csync_walker, MAX_DEPTH); + assert_int_equal(rc, 0); +} + +static void check_csync_ftw_empty_uri(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_ftw(csync, "", csync_walker, MAX_DEPTH); + assert_int_equal(rc, -1); +} + +static void check_csync_ftw_failing_fn(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_ftw(csync, "/tmp", failing_fn, MAX_DEPTH); + assert_int_equal(rc, -1); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_csync_detect_update, setup, teardown_rm), + unit_test_setup_teardown(check_csync_detect_update_db_none, setup, teardown), + unit_test_setup_teardown(check_csync_detect_update_db_eval, setup, teardown), + unit_test_setup_teardown(check_csync_detect_update_db_rename, setup, teardown), + unit_test_setup_teardown(check_csync_detect_update_db_new, setup, teardown_rm), + unit_test_setup_teardown(check_csync_detect_update_nlink, setup, teardown_rm), + unit_test_setup_teardown(check_csync_detect_update_null, setup, teardown_rm), + + unit_test_setup_teardown(check_csync_ftw, setup_ftw, teardown_rm), + unit_test_setup_teardown(check_csync_ftw_empty_uri, setup_ftw, teardown_rm), + unit_test_setup_teardown(check_csync_ftw_failing_fn, setup_ftw, teardown_rm), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/csync_tests/check_csync_util.c b/csync/tests/csync_tests/check_csync_util.c new file mode 100644 index 000000000..7e16ea3a6 --- /dev/null +++ b/csync/tests/csync_tests/check_csync_util.c @@ -0,0 +1,34 @@ +#include "torture.h" + +#include "csync_util.h" + +static void check_csync_instruction_str(void **state) +{ + const char *str; + + (void) state; /* unused */ + + str = csync_instruction_str(CSYNC_INSTRUCTION_ERROR); + assert_string_equal(str, "INSTRUCTION_ERROR"); + + str = csync_instruction_str(0xFFFF); + assert_string_equal(str, "ERROR!"); +} + +static void check_csync_memstat(void **state) +{ + (void) state; /* unused */ + + csync_memstat_check(); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_csync_instruction_str), + unit_test(check_csync_memstat), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/encoding_tests/check_encoding.c b/csync/tests/encoding_tests/check_encoding.c new file mode 100644 index 000000000..45f7d6ff5 --- /dev/null +++ b/csync/tests/encoding_tests/check_encoding.c @@ -0,0 +1,137 @@ +#include "torture.h" + +#include "c_string.h" + +#ifdef _WIN32 +#include +#endif + +static void setup(void **state) +{ + int rc = 0; + + (void) state; /* unused */ + +#ifdef HAVE_ICONV +#ifdef __APPLE__ + /* this test only works on apple because linux does not know the + * UTF-8-MAC encoding that we use here. */ + rc = c_setup_iconv("UTF-8-MAC"); +#endif +#endif + assert_int_equal(rc, 0); +} + +static void teardown(void **state) +{ + int rc = 0; + + (void) state; /* unused */ +#ifdef HAVE_ICONV + // this must never crash + rc = c_close_iconv(); +#endif + assert_int_equal(rc, 0); +} + +static void check_iconv_to_native_normalization(void **state) +{ + mbchar_t *out = NULL; + const char *in= "\x48\xc3\xa4"; // UTF8 +#ifdef __APPLE__ + const char *exp_out = "\x48\x61\xcc\x88"; // UTF-8-MAC +#else + const char *exp_out = "\x48\xc3\xa4"; // UTF8 +#endif + + out = c_utf8_to_locale(in); + assert_string_equal(out, exp_out); + + c_free_locale_string(out); + assert_null(out); + + (void) state; /* unused */ +} + +static void check_iconv_from_native_normalization(void **state) +{ + char *out = NULL; +#ifdef _WIN32 + const mbchar_t *in = L"\x48\xc3\xa4"; // UTF-8 +#else +#ifdef __APPLE__ + const mbchar_t *in = "\x48\x61\xcc\x88"; // UTF-8-MAC +#else + const mbchar_t *in = "\x48\xc3\xa4"; // UTF-8 +#endif +#endif + const char *exp_out = "\x48\xc3\xa4"; // UTF-8 + + out = c_utf8_from_locale(in); + assert_string_equal(out, exp_out); + + c_free_locale_string(out); + assert_null(out); + + (void) state; /* unused */ +} + +static void check_iconv_ascii(void **state) +{ +#ifdef _WIN32 + const mbchar_t *in = L"abc/ABC\\123"; // UTF-8 +#else +#ifdef __APPLE__ + const mbchar_t *in = "abc/ABC\\123"; // UTF-8-MAC +#else + const mbchar_t *in = "abc/ABC\\123"; // UTF-8 +#endif +#endif + char *out = NULL; + const char *exp_out = "abc/ABC\\123"; + + out = c_utf8_from_locale(in); + assert_string_equal(out, exp_out); + + c_free_locale_string(out); + assert_null(out); + + (void) state; /* unused */ +} + +#define TESTSTRING "#cA\\#fߧ4" +#define LTESTSTRING L"#cA\\#fߧ4" + +static void check_to_multibyte(void **state) +{ + int rc = -1; + + mbchar_t *mb_string = c_utf8_to_locale( TESTSTRING ); + mbchar_t *mb_null = c_utf8_to_locale( NULL ); + + (void) state; + +#ifdef _WIN32 + assert_int_equal( wcscmp( LTESTSTRING, mb_string), 0 ); +#else + assert_string_equal(mb_string, TESTSTRING); +#endif + assert_true( mb_null == NULL ); + assert_int_equal(rc, -1); + + c_free_locale_string(mb_string); + c_free_locale_string(mb_null); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_to_multibyte, setup, teardown), + unit_test_setup_teardown(check_iconv_ascii, setup, teardown), + unit_test_setup_teardown(check_iconv_to_native_normalization, setup, teardown), + unit_test_setup_teardown(check_iconv_from_native_normalization, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/httpbf_tests/hbf_send_test.c b/csync/tests/httpbf_tests/hbf_send_test.c new file mode 100644 index 000000000..0c3438a59 --- /dev/null +++ b/csync/tests/httpbf_tests/hbf_send_test.c @@ -0,0 +1,216 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "config_test.h" + +#include "httpbf.c" + + +// A test case that does nothing and succeeds. +static void null_test_success(void **state) { + (void) state; +} + + +static char* test_file( const char* name ) { + char path[260]; + + if( ! name ) return 0; + + strcpy( path, TESTFILES_DIR); + if(path[strlen(TESTFILES_DIR)-1] != '/') + strcat( path, "/"); + strcat( path, name ); + + return strdup(path); +} + +static void test_get_transfer_url( void **state ) { + const char *url = "http://example.org/owncloud"; + const char *turl = NULL; + int fd; + Hbf_State hbf_state; + + hbf_transfer_t *list = NULL; + + (void) state; + list = hbf_init_transfer( url ); + assert_non_null( list ); + + /* open a file */ + fd = open( test_file("church.jpg"), O_RDONLY ); + assert_true(fd >= 0); + + hbf_state = hbf_splitlist(list, fd); + assert_true( hbf_state == HBF_SUCCESS); + assert_true( list->block_cnt == 1); + + turl = get_transfer_url( list, 0 ); + assert_non_null( turl ); + assert_string_equal( url, turl ); + + hbf_free_transfer( list ); +} + + +static void test_get_transfer_url_bigfile( void **state ) { + const char *url = "http://example.org/big_file"; + const char *turl = NULL; + char res[256]; + int i, fd; + Hbf_State hbf_state; + hbf_transfer_t *list = NULL; + + (void) state; + + list = hbf_init_transfer( url ); + assert_non_null( list ); + + list->threshold = list->block_size = (1024*1024); /* block size 1 MB */ + + /* open a file */ + fd = open( test_file("church.jpg"), O_RDONLY ); + assert_true(fd >= 0); + + hbf_state = hbf_splitlist(list, fd); + assert_true( hbf_state == HBF_SUCCESS); + assert_true( list->block_cnt == 2 ); + + for( i=0; i < list->block_cnt; i++ ) { + turl = get_transfer_url( list, i ); + assert_non_null(turl); + + sprintf(res, "%s-chunking-%u-%u-%u", url, list->transfer_id, + list->block_cnt, i ); + /* printf( "XX: %s\n", res ); */ + assert_string_equal( turl, res ); + } + hbf_free_transfer(list); +} + +static void test_hbf_init_transfer( void **state ) { + hbf_transfer_t *list = NULL; + const char *url = "http://example.org/owncloud"; + + (void) state; + + list = hbf_init_transfer( url ); + assert_non_null( list ); + assert_string_equal( url, list->url ); +} + +/* test with a file size that is not a multiply of the slize size. */ +static void test_hbf_splitlist_odd( void **state ){ + + hbf_transfer_t *list = NULL; + const char *dest_url = "http://localhost/ocm/remote.php/webdav/big/church.jpg"; + int prev_id = 0; + int i, fd; + Hbf_State hbf_state; + + (void) state; + + /* open a file */ + fd = open(test_file("church.jpg"), O_RDONLY); + assert_true(fd >= 0); + + /* do a smoke test for uniqueness */ + for( i=0; i < 10000; i++) { + list = hbf_init_transfer(dest_url); + assert_non_null(list); + usleep(1); + hbf_state = hbf_splitlist(list, fd); + + assert_int_not_equal(list->transfer_id, prev_id); + prev_id = list->transfer_id; + hbf_free_transfer(list); + } + + list = hbf_init_transfer(dest_url); + assert_non_null(list); + + hbf_state = hbf_splitlist(list, fd); + assert_non_null(list); +#ifndef NDEBUG + assert_int_equal(list->calc_size, list->stat_size); +#endif + assert_int_not_equal(list->block_cnt, 0); + assert_true( hbf_state == HBF_SUCCESS); + + /* checks on the block list */ + if( 1 ) { + int seen_zero_seq = 0; + int prev_seq = -1; + int64_t prev_block_end = -1; + + for( i=0; i < list->block_cnt; i++) { + hbf_block_t *blk = list->block_arr[i]; + assert_non_null(blk); + if( blk->seq_number == 0 ) seen_zero_seq++; + + assert_int_equal(prev_seq, blk->seq_number -1 ); + prev_seq = blk->seq_number; + + assert_true((prev_block_end+1) == (blk->start)); + prev_block_end = blk->start + blk->size; + } + /* Make sure we exactly saw blk->seq_number == 0 exactly one times */ + assert_int_equal( seen_zero_seq, 1 ); + } + hbf_free_transfer( list ); +} + +/* test with a file size that is not a multiply of the slize size. */ +static void test_hbf_splitlist_zero( void **state ){ + + hbf_transfer_t *list = NULL; + const char *dest_url = "http://localhost/ocm/remote.php/webdav/big/zerofile.txt"; + int fd; + Hbf_State hbf_state; + + (void) state; + + /* open a file */ + fd = open(test_file("zerofile.txt"), O_RDONLY); + assert_true(fd >= 0); + + list = hbf_init_transfer(dest_url); + assert_non_null(list); + + hbf_state = hbf_splitlist(list, fd); + assert_non_null(list); + assert_int_equal(list->stat_size, 0); +#ifndef NDEBUG + assert_int_equal(list->calc_size, list->stat_size); +#endif + assert_int_equal(list->block_cnt, 1); + + assert_true( hbf_state == HBF_SUCCESS); + + hbf_free_transfer( list ); +} + + + +int main(void) { + const UnitTest tests[] = { + unit_test(null_test_success), + unit_test(test_hbf_splitlist_odd), + unit_test(test_hbf_splitlist_zero), + unit_test(test_hbf_init_transfer), + unit_test(test_get_transfer_url), + unit_test(test_get_transfer_url_bigfile) + }; + return run_tests(tests); +} diff --git a/csync/tests/log_tests/check_log.c b/csync/tests/log_tests/check_log.c new file mode 100644 index 000000000..1911f6188 --- /dev/null +++ b/csync/tests/log_tests/check_log.c @@ -0,0 +1,79 @@ +#include + +#include "support.h" + +#include "config_csync.h" +#include "csync_log.h" + +static void setup(void) { + csync_log_init(); +} + +static void teardown(void) { + csync_log_fini(); +} + +START_TEST (log_create) +{ + fail_unless((csync_log_init() == 0), NULL); + fail_unless((csync_log_fini() == 0), NULL); +} +END_TEST + +START_TEST (log_load) +{ + char buf[256]; + snprintf(buf, (size_t) 256 - 1, "%s/%s", SOURCEDIR, "config/ocsync_log.conf"); + fail_unless(csync_log_load(buf) == 0); +} +END_TEST + +START_TEST (log_prio) +{ + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_FATAL, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ALERT, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_CRIT, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_ERROR, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_WARN, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTICE, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_INFO, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_DEBUG, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_TRACE, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_NOTSET, "log %s", "test"); + CSYNC_LOG(CSYNC_LOG_PRIORITY_UNKNOWN, "log %s", "test"); +} +END_TEST + +START_TEST (log_null) +{ + char *z = NULL; + CSYNC_LOG(CSYNC_LOG_PRIORITY_UNKNOWN, "log %s", z); +} +END_TEST + +static Suite *log_suite(void) { + Suite *s = suite_create("Logger"); + + create_case(s, "log_create", log_create); + create_case_fixture(s, "log_load", log_load, setup, teardown); + create_case_fixture(s, "log_prio", log_prio, setup, teardown); + create_case_fixture(s, "log_null", log_null, setup, teardown); + + return s; +} + +int main(void) { + int nf; + + Suite *s = log_suite(); + + SRunner *sr; + sr = srunner_create(s); + srunner_run_all(sr, CK_VERBOSE); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + diff --git a/csync/tests/ownCloud/HTTP/DAV.pm b/csync/tests/ownCloud/HTTP/DAV.pm new file mode 100644 index 000000000..cdc95ce19 --- /dev/null +++ b/csync/tests/ownCloud/HTTP/DAV.pm @@ -0,0 +1,2075 @@ +# +# Perl WebDAV client library +# + +package HTTP::DAV; + +use LWP; +use XML::DOM; +use Time::Local; +use HTTP::DAV::Lock; +use HTTP::DAV::ResourceList; +use HTTP::DAV::Resource; +use HTTP::DAV::Comms; +use URI::file; +use URI::Escape; +use FileHandle; +use File::Glob; + +#use Carp (cluck); +use Cwd (); # Can't import all of it, cwd clashes with our namespace. + +# Globals +$VERSION = '0.44'; +$VERSION_DATE = '2011/06/19'; + +# Set this up to 3 +$DEBUG = 0; + +use strict; +use vars qw($VERSION $VERSION_DATE $DEBUG); + +sub new { + my $class = shift; + my $self = bless {}, ref($class) || $class; + $self->_init(@_); + return $self; +} + +########################################################################### +sub clone { + my $self = @_; + my $class = ref($self); + my %clone = %{$self}; + bless {%clone}, $class; +} + +########################################################################### +{ + + sub _init { + my ( $self, @p ) = @_; + my ( $uri, $headers, $useragent ) + = HTTP::DAV::Utils::rearrange( [ 'URI', 'HEADERS', 'USERAGENT' ], + @p ); + + $self->{_lockedresourcelist} = HTTP::DAV::ResourceList->new(); + $self->{_comms} = HTTP::DAV::Comms->new( + -useragent => $useragent, + -headers => $headers + ); + if ($uri) { + $self->set_workingresource( $self->new_resource( -uri => $uri ) ); + } + + return $self; + } +} + +sub DebugLevel { + shift if ref( $_[0] ) =~ /HTTP/; + my $level = shift; + $level = 256 if !defined $level || $level eq ""; + + $DEBUG = $level; +} + +###################################################################### +# new_resource acts as a resource factory. +# It will create a new one for you each time you ask. +# Sometimes, if it holds state information about this +# URL, it may return an old populated object. +sub new_resource { + my ($self) = shift; + + #### + # This is the order of the arguments unless used as + # named parameters + my ($uri) = HTTP::DAV::Utils::rearrange( ['URI'], @_ ); + $uri = HTTP::DAV::Utils::make_uri($uri); + + #cluck "new_resource: now $uri\n"; + + my $resource = $self->{_lockedresourcelist}->get_member($uri); + if ($resource) { + print + "new_resource: For $uri, returning existing resource $resource\n" + if $HTTP::DAV::DEBUG > 2; + + # Just reset the url to honour trailing slash status. + $resource->set_uri($uri); + return $resource; + } + else { + print "new_resource: For $uri, creating new resource\n" + if $HTTP::DAV::DEBUG > 2; + return HTTP::DAV::Resource->new( + -Comms => $self->{_comms}, + -LockedResourceList => $self->{_lockedresourcelist}, + -uri => $uri, + -Client => $self + ); + } +} + +########################################################################### +# ACCESSOR METHODS + +# GET +sub get_user_agent { $_[0]->{_comms}->get_user_agent(); } +sub get_last_request { $_[0]->{_comms}->get_last_request(); } +sub get_last_response { $_[0]->{_comms}->get_last_response(); } +sub get_workingresource { $_[0]->{_workingresource} } + +sub get_workingurl { + $_[0]->{_workingresource}->get_uri() + if defined $_[0]->{_workingresource}; +} +sub get_lockedresourcelist { $_[0]->{_lockedresourcelist} } + +# SET +sub set_workingresource { $_[0]->{_workingresource} = $_[1]; } +sub credentials { shift->{_comms}->credentials(@_); } + +###################################################################### +# Error handling + +## Error conditions +my %err = ( + 'ERR_WRONG_ARGS' => 'Wrong number of arguments supplied.', + 'ERR_UNAUTHORIZED' => 'Unauthorized. ', + 'ERR_NULL_RESOURCE' => 'Not connected. Do an open first. ', + 'ERR_RESP_FAIL' => 'Server response: ', + 'ERR_501' => 'Server response: ', + 'ERR_405' => 'Server response: ', + 'ERR_GENERIC' => '', +); + +sub err { + my ( $self, $error, $mesg, $url ) = @_; + + my $err_msg; + $err_msg = ""; + $err_msg .= $err{$error} if defined $err{$error}; + $err_msg .= $mesg if defined $mesg; + $err_msg .= "ERROR" unless defined $err_msg; + + $self->{_message} = $err_msg; + my $callback = $self->{_callback}; + &$callback( 0, $err_msg, $url ) if $callback; + + if ( $self->{_multi_op} ) { + push( @{ $self->{_errors} }, $err_msg ); + } + $self->{_status} = 0; + + return 0; +} + +sub ok { + my ($self, $mesg, $url, $so_far, $length) = @_; + + $self->{_message} = $mesg; + + my $callback = $self->{_callback}; + &$callback(1, $mesg, $url, $so_far, $length) if $callback; + + if ($self->{_multi_op}) { + $self->{_status} = 1 unless $self->{_status} == 0; + } + else { + $self->{_status} = 1; + } + return 1; +} + +sub _start_multi_op { + my ($self, $mesg, $callback) = @_; + $self->{_multi_mesg} = $mesg || ""; + $self->{_status} = 1; + $self->{_errors} = []; + $self->{_multi_op} = 1; + $self->{_callback} = $callback if defined $callback; +} + +sub _end_multi_op { + my ($self) = @_; + $self->{_multi_op} = 0; + $self->{_callback} = undef; + my $message = $self->{_multi_mesg} . " "; + $message .= ( $self->{_status} ) ? "succeeded" : "failed"; + $self->{_message} = $message; + $self->{_multi_mesg} = undef; +} + +sub message { + my ($self) = @_; + return $self->{_message} || ""; +} + +sub errors { + my ($self) = @_; + my $err_ref = $self->{_errors} || []; + return @{ $err_ref }; +} + +sub is_success { + my ($self) = @_; + return $self->{_status}; +} + +###################################################################### +# Operations + +# CWD +sub cwd { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" ); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + $url = HTTP::DAV::Utils::make_trail_slash($url); + my $new_uri = $self->get_absolute_uri($url); + ($new_uri) = $self->get_globs($new_uri); + + return 0 unless ($new_uri); + + print "cwd: Changing to $new_uri\n" if $DEBUG; + return $self->open($new_uri); +} + +# DELETE +sub delete { + my ( $self, @p ) = @_; + my ( $url, $callback ) + = HTTP::DAV::Utils::rearrange( [ 'URL', 'CALLBACK' ], @p ); + + return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" ); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $new_url = $self->get_absolute_uri($url); + my @urls = $self->get_globs($new_url); + + $self->_start_multi_op( "delete $url", $callback ) if @urls > 1; + + foreach my $u (@urls) { + my $resource = $self->new_resource( -uri => $u ); + + my $resp = $resource->delete(); + + if ( $resp->is_success ) { + $self->ok( "deleted $u successfully", $u ); + } + else { + $self->err( 'ERR_RESP_FAIL', $resp->message(), $u ); + } + } + + $self->_end_multi_op() if @urls > 1; + + return $self->is_success; +} + +# GET +# Handles globs by doing multiple recursive gets +# GET dir* produces +# _get dir1, to_local +# _get dir2, to_local +# _get dir3, to_local +sub get { + my ( $self, @p ) = @_; + my ( $url, $to, $callback, $chunk ) + = HTTP::DAV::Utils::rearrange( [ 'URL', 'TO', 'CALLBACK', 'CHUNK' ], + @p ); + + return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" ); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + $self->_start_multi_op( "get $url", $callback ); + + my $new_url = $self->get_absolute_uri($url); + my (@urls) = $self->get_globs($new_url); + + return 0 unless ( $#urls > -1 ); + + ############ + # HANDLE -TO + # + $to ||= ''; + if ( $to eq '.' ) { + $to = Cwd::getcwd(); + } + + # If the TO argument is a file handle or a scalar + # then check that we only got one glob. If we got multiple + # globs, then we can't keep going because we can't write multiple files + # to one FileHandle. + if ( $#urls > 0 ) { + if ( ref($to) =~ /SCALAR/ ) { + return $self->err( 'ERR_WRONG_ARGS', + "Can't retrieve multiple files to a single scalar\n" ); + } + elsif ( ref($to) =~ /GLOB/ ) { + return $self->err( 'ERR_WRONG_ARGS', + "Can't retrieve multiple files to a single filehandle\n" ); + } + } + + # If it's a dir, remove last '/' from destination. + # Later we need to concatenate the destination filename. + if ( defined $to && $to ne '' && -d $to ) { + $to =~ s{/$}{}; + } + + # Foreach file... do the get. + foreach my $u (@urls) { + my ( $left, $leafname ) = HTTP::DAV::Utils::split_leaf($u); + + # Handle SCALARREF and GLOB cases + my $dest_file = $to; + + # Directories + if ( -d $to ) { + $dest_file = "$to/$leafname"; + + # Multiple targets + } + elsif ( !defined $to || $to eq "" ) { + $dest_file = $leafname; + } + + warn "get: $u -> $dest_file\n" if $DEBUG; + + # Setup the resource based on the passed url and do a propfind. + my $resource = $self->new_resource( -uri => $u ); + my $resp = $resource->propfind( -depth => 1 ); + + if ( $resp->is_error ) { + return $self->err( 'ERR_RESP_FAIL', $resp->message(), $u ); + } + + $self->_get( $resource, $dest_file, $callback, $chunk ); + } + + $self->_end_multi_op(); + return $self->is_success; +} + +# Note: is is expected that $resource has had +# a propfind depth 1 performed on it. +# +sub _get { + my ( $self, @p ) = @_; + my ( $resource, $local_name, $callback, $chunk ) + = HTTP::DAV::Utils::rearrange( + [ 'RESOURCE', 'TO', 'CALLBACK', 'CHUNK' ], @p ); + + my $url = $resource->get_uri(); + + # GET A DIRECTORY + if ( $resource->is_collection ) { + + # If the TO argument is a file handle, a scalar or empty + # then we + # can't keep going because we can't write multiple files + # to one FileHandle, scalar, etc. + if ( ref($local_name) =~ /SCALAR/ ) { + return $self->err( 'ERR_WRONG_ARGS', + "Can't retrieve a collection to a scalar\n", $url ); + } + elsif ( ref($local_name) =~ /GLOB/ ) { + return $self->err( 'ERR_WRONG_ARGS', + "Can't retrieve a collection to a filehandle\n", $url ); + } + elsif ( $local_name eq "" ) { + return $self->err( + 'ERR_GENERIC', + "Can't retrieve a collection without a target directory (-to).", + $url + ); + } + + # Try and make the directory locally + print "MKDIR $local_name (before escape)\n" if $DEBUG > 2; + + $local_name = URI::Escape::uri_unescape($local_name); + if ( !mkdir $local_name ) { + return $self->err( 'ERR_GENERIC', + "mkdir local:$local_name failed: $!" ); + } + + $self->ok("mkdir $local_name"); + + # This is the degenerate case for an empty dir. + print "Made directory $local_name\n" if $DEBUG > 2; + + my $resource_list = $resource->get_resourcelist(); + if ($resource_list) { + + # FOREACH FILE IN COLLECTION, GET IT. + foreach my $progeny_r ( $resource_list->get_resources() ) { + + my $progeny_url = $progeny_r->get_uri(); + print "Found progeny:$progeny_url\n" if $DEBUG > 2; + my $progeny_local_filename + = HTTP::DAV::Utils::get_leafname($progeny_url); + $progeny_local_filename + = URI::Escape::uri_unescape($progeny_local_filename); + + $progeny_local_filename + = URI::file->new($progeny_local_filename) + ->abs("$local_name/"); + + if ( $progeny_r->is_collection() ) { + $progeny_r->propfind( -depth => 1 ); + } + $self->_get( $progeny_r, $progeny_local_filename, $callback, + $chunk ); + + # } else { + # $self->_do_get_tofile($progeny_r,$progeny_local_filename); + # } + } + } + } + + # GET A FILE + else { + my $response; + my $name_ref = ref $local_name; + + if ( $callback || $name_ref =~ /SCALAR/ || $name_ref =~ /GLOB/ ) { + $self->{_so_far} = 0; + + my $fh; + my $put_to_scalar = 0; + + if ( $name_ref =~ /GLOB/ ) { + $fh = $local_name; + } + + elsif ( $name_ref =~ /SCALAR/ ) { + $put_to_scalar = 1; + $$local_name = ""; + } + + else { + $fh = FileHandle->new; + $local_name = URI::Escape::uri_unescape($local_name); + if (! $fh->open(">$local_name") ) { + return $self->err( + 'ERR_GENERIC', + "open \">$local_name\" failed: $!", + $url + ); + } + + # RT #29788, avoid file corruptions on Win32 + binmode $fh; + } + + $self->{_fh} = $fh; + + $response = $resource->get( + -chunk => $chunk, + -progress_callback => + + sub { + my ( $data, $response, $protocol ) = @_; + + $self->{_so_far} += length($data); + + my $fh = $self->{_fh}; + print $fh $data if defined $fh; + + $$local_name .= $data if ($put_to_scalar); + + my $user_callback = $self->{_callback}; + &$user_callback( -1, "transfer in progress", + $url, $self->{_so_far}, $response->content_length(), + $data ) + if defined $user_callback; + + } + + ); # end get( ... ); + + # Close the filehandle if it was set. + if ( defined $self->{_fh} ) { + $self->{_fh}->close(); + delete $self->{_fh}; + } + } + else { + $local_name = URI::Escape::uri_unescape($local_name); + $response = $resource->get( -save_to => $local_name ); + } + + # Handle response + if ( $response->is_error ) { + return $self->err( 'ERR_GENERIC', + "get $url failed: " . $response->message, $url ); + } + else { + return $self->ok( "get $url", $url, $self->{_so_far}, + $response->content_length() ); + } + + } + + return 1; +} + +# LOCK +sub lock { + my ( $self, @p ) = @_; + my ( $url, $owner, $depth, $timeout, $scope, $type, @other ) + = HTTP::DAV::Utils::rearrange( + [ 'URL', 'OWNER', 'DEPTH', 'TIMEOUT', 'SCOPE', 'TYPE' ], @p ); + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + $url = $resource->get_uri; + } + + # Make the lock + my $resp = $resource->lock( + -owner => $owner, + -depth => $depth, + -timeout => $timeout, + -scope => $scope, + -type => $type + ); + + if ( $resp->is_success() ) { + return $self->ok( "lock $url succeeded", $url ); + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message, $url ); + } +} + +# UNLOCK +sub unlock { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + $url = $resource->get_uri; + } + + # Make the lock + my $resp = $resource->unlock(); + if ( $resp->is_success ) { + return $self->ok( "unlock $url succeeded", $url ); + } + else { + + # The Resource.pm::lock routine has a hack + # where if it doesn't know the locktoken, it will + # just return an empty response with message "Client Error". + # Make a custom message for this case. + my $msg = $resp->message; + if ( $msg =~ /Client error/i ) { + $msg = "No locks found. Try steal"; + return $self->err( 'ERR_GENERIC', $msg, $url ); + } + else { + return $self->err( 'ERR_RESP_FAIL', $msg, $url ); + } + } +} + +sub steal { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + } + + # Go the steal + my $resp = $resource->forcefully_unlock_all(); + if ( $resp->is_success() ) { + return $self->ok( "steal succeeded", $url ); + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url ); + } +} + +# MKCOL +sub mkcol { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + return $self->err('ERR_WRONG_ARGS') if ( !defined $url || $url eq "" ); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + $url = HTTP::DAV::Utils::make_trail_slash($url); + my $new_url = $self->get_absolute_uri($url); + my $resource = $self->new_resource( -uri => $new_url ); + + # Make the lock + my $resp = $resource->mkcol(); + if ( $resp->is_success() ) { + return $self->ok( "mkcol $new_url", $new_url ); + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message(), $new_url ); + } +} + +# OPTIONS +sub options { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + #return $self->err('ERR_WRONG_ARGS') if (!defined $url || $url eq ""); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + $url = $resource->get_uri; + } + + # Make the call + my $resp = $resource->options(); + if ( $resp->is_success() ) { + $self->ok( "options $url succeeded", $url ); + return $resource->get_options(); + } + else { + $self->err( 'ERR_RESP_FAIL', $resp->message(), $url ); + return undef; + } +} + +# MOVE +sub move { return shift->_move_copy( "move", @_ ); } +sub copy { return shift->_move_copy( "copy", @_ ); } + +sub _move_copy { + my ( $self, $method, @p ) = @_; + my ( $url, $dest_url, $overwrite, $depth, $text, @other ) + = HTTP::DAV::Utils::rearrange( + [ 'URL', 'DEST', 'OVERWRITE', 'DEPTH', 'TEXT' ], @p ); + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + if (!( defined $url && $url ne "" && defined $dest_url && $dest_url ne "" + ) + ) + { + return $self->err( 'ERR_WRONG_ARGS', + "Must supply a source and destination url" ); + } + + $url = $self->get_absolute_uri($url); + $dest_url = $self->get_absolute_uri($dest_url); + my $resource = $self->new_resource( -uri => $url ); + my $dest_resource = $self->new_resource( -uri => $dest_url ); + + my $resp = $dest_resource->propfind( -depth => 1 ); + if ( $resp->is_success && $dest_resource->is_collection ) { + my $leafname = HTTP::DAV::Utils::get_leafname($url); + $dest_url = "$dest_url/$leafname"; + $dest_resource = $self->new_resource( -uri => $dest_url ); + } + + # Make the lock + $resp = $resource->$method( + -dest => $dest_resource, + -overwrite => $overwrite, + -depth => $depth, + -text => $text, + ); + + if ( $resp->is_success() ) { + return $self->ok( "$method $url to $dest_url succeeded", $url ); + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message, $url ); + } +} + +# OPEN +# Must be a collection resource +# $dav->open( -url => http://localhost/test/ ); +# $dav->open( localhost/test/ ); +# $dav->open( -url => localhost:81 ); +# $dav->open( localhost ); +sub open { + my ( $self, @p ) = @_; + my ($url) = HTTP::DAV::Utils::rearrange( ['URL'], @p ); + + my $resource; + if ( defined $url && $url ne "" ) { + $url = HTTP::DAV::Utils::make_trail_slash($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + $url = $resource->get_uri() if ($resource); + return $self->err('ERR_WRONG_ARGS') + if ( !defined $url || $url eq "" ); + } + + my $response = $resource->propfind( -depth => 0 ); + #print $response->as_string; + # print $resource->as_string; + + my $result = $self->what_happened($url, $resource, $response); + if ($result->{success} == 0) { + return $self->err($result->{error_type}, $result->{error_msg}, $url); + } + + # If it is a collection but the URI doesn't end in a trailing slash. + # Then we need to reopen with the / + elsif ($resource->is_collection + && $url !~ m#/\s*$# ) + { + my $newurl = $url . "/"; + print "Redirecting to $newurl\n" if $DEBUG > 1; + return $self->open($newurl); + } + + # If it is not a collection then we + # can't open it. + # elsif ( !$resource->is_collection ) { + # return $self->err( 'ERR_GENERIC', + # "Operation failed. You can only open a collection (directory)", + # $url ); + # } + else { + $self->set_workingresource($resource); + return $self->ok( "Connected to $url", $url ); + } + + return $self->err( 'ERR_GENERIC', $url ); +} + +# Performs a propfind and then returns the populated +# resource. The resource will have a resourcelist if +# it is a collection. +sub propfind { + my ( $self, @p ) = @_; + my ( $url, $depth ) = HTTP::DAV::Utils::rearrange( [ 'URL', 'DEPTH' ], @p ); + + # depth = 1 is the default + if (! defined $depth) { + $depth = 1; + } + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + } + + # Make the call + my $resp = $resource->propfind( -depth => $depth ); + if ( $resp->is_success() ) { + $resource->build_ls($resource); + $self->ok( "propfind " . $resource->get_uri() . " succeeded", $url ); + return $resource; + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url ); + } +} + +# Set a property on the resource +sub set_prop { + my ( $self, @p ) = @_; + my ( $url, $namespace, $propname, $propvalue, $nsabbr ) + = HTTP::DAV::Utils::rearrange( + [ 'URL', 'NAMESPACE', 'PROPNAME', 'PROPVALUE', 'NSABBR' ], @p ); + $self->proppatch( + -url => $url, + -namespace => $namespace, + -propname => $propname, + -propvalue => $propvalue, + -action => "set", + -nsabbr => $nsabbr, + ); +} + +# Unsets a property on the resource +sub unset_prop { + my ( $self, @p ) = @_; + my ( $url, $namespace, $propname, $nsabbr ) + = HTTP::DAV::Utils::rearrange( + [ 'URL', 'NAMESPACE', 'PROPNAME', 'NSABBR' ], @p ); + $self->proppatch( + -url => $url, + -namespace => $namespace, + -propname => $propname, + -action => "remove", + -nsabbr => $nsabbr, + ); +} + +# Performs a proppatch on the resource +sub proppatch { + my ( $self, @p ) = @_; + my ( $url, $namespace, $propname, $propvalue, $action, $nsabbr ) + = HTTP::DAV::Utils::rearrange( + [ 'URL', 'NAMESPACE', 'PROPNAME', 'PROPVALUE', 'ACTION', 'NSABBR' ], + @p ); + + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + my $resource; + if ($url) { + $url = $self->get_absolute_uri($url); + $resource = $self->new_resource( -uri => $url ); + } + else { + $resource = $self->get_workingresource(); + } + + # Make the call + my $resp = $resource->proppatch( + -namespace => $namespace, + -propname => $propname, + -propvalue => $propvalue, + -action => $action, + -nsabbr => $nsabbr + ); + + if ( $resp->is_success() ) { + $resource->build_ls($resource); + $self->ok( "proppatch " . $resource->get_uri() . " succeeded", $url ); + return $resource; + } + else { + return $self->err( 'ERR_RESP_FAIL', $resp->message(), $url ); + } +} + +###################################################################### +sub put { + my ( $self, @p ) = @_; + my ( $local, $url, $callback, $custom_headers ) + = HTTP::DAV::Utils::rearrange( [ 'LOCAL', 'URL', 'CALLBACK', 'HEADERS' ], @p ); + + if ( ref($local) eq "SCALAR" ) { + $self->_start_multi_op( 'put ' . ${$local}, $callback ); + $self->_put(@p); + } + else { + $self->_start_multi_op( 'put ' . $local, $callback ); + $local =~ s/\ /\\ /g; + my @globs = glob("$local"); + foreach my $file (@globs) { + print "Starting put of $file\n" if $HTTP::DAV::DEBUG > 1; + $self->_put( + -local => $file, + -url => $url, + -callback => $callback, + -headers => $custom_headers, + ); + } + } + $self->_end_multi_op(); + return $self->is_success; +} + +sub _put { + my ( $self, @p ) = @_; + my ( $local, $url, $custom_headers ) + = HTTP::DAV::Utils::rearrange( [ 'LOCAL', 'URL', 'HEADERS' ], @p ); + + return $self->err('ERR_WRONG_ARGS') + if ( !defined $local || $local eq "" ); + return $self->err('ERR_NULL_RESOURCE') + unless $self->get_workingresource(); + + # Check if they passed a reference to content rather than a filename. + my $content_ptr = ( ref($local) eq "SCALAR" ) ? 1 : 0; + + # Setup the resource based on the passed url + # Check if the remote resource exists and is a collection. + $url = $self->get_absolute_uri($url); + my $resource = $self->new_resource($url); + my $response = $resource->propfind( -depth => 0 ); + my $leaf_name; + if ( $response->is_success && $resource->is_collection && !$content_ptr ) + { + + # Add one / to the end of the collection + $url =~ s/\/*$//g; #Strip em + $url .= "/"; #Add one + $leaf_name = HTTP::DAV::Utils::get_leafname($local); + } + else { + $leaf_name = HTTP::DAV::Utils::get_leafname($url); + } + + my $target = $self->get_absolute_uri( $leaf_name, $url ); + + print "$local => $target ($url, $leaf_name)\n"; + + # PUT A DIRECTORY + if ( !$content_ptr && -d $local ) { + + # mkcol + # Return 0 if fail because the error will have already + # been set by the mkcol routine + if ( $self->mkcol($target, -headers => $custom_headers) ) { + if ( !opendir( DIR, $local ) ) { + $self->err( 'ERR_GENERIC', "chdir to \"$local\" failed: $!" ); + } + else { + my @files = readdir(DIR); + close DIR; + foreach my $file (@files) { + next if $file eq "."; + next if $file eq ".."; + my $progeny = "$local/$file"; + $progeny =~ s#//#/#g; # Fold down double slashes + $self->_put( + -local => $progeny, + -url => "$target/$file", + ); + } + } + } + + # PUT A FILE + } + else { + my $content = ""; + my $fail = 0; + if ($content_ptr) { + $content = $$local; + } + else { + if ( !CORE::open( F, $local ) ) { + $self->err( 'ERR_GENERIC', + "Couldn't open local file $local: $!" ); + $fail = 1; + } + else { + binmode F; + while () { $content .= $_; } + close F; + } + } + + if ( !$fail ) { + my $resource = $self->new_resource( -uri => $target ); + my $response = $resource->put($content,$custom_headers); + if ( $response->is_success ) { + $self->ok( "put $target (" . length($content) . " bytes)", + $target ); + } + else { + $self->err( 'ERR_RESP_FAIL', + "put failed " . $response->message(), $target ); + } + } + } +} + +###################################################################### +# UTILITY FUNCTION +# get_absolute_uri: +# Synopsis: $new_url = get_absolute_uri("/foo/bar") +# Takes a URI (or string) +# and returns the absolute URI based +# on the remote current working directory +sub get_absolute_uri { + my ( $self, @p ) = @_; + my ( $rel_uri, $base_uri ) + = HTTP::DAV::Utils::rearrange( [ 'REL_URI', 'BASE_URI' ], @p ); + + local $URI::URL::ABS_REMOTE_LEADING_DOTS = 1; + if ( !defined $base_uri ) { + $base_uri = $self->get_workingresource()->get_uri(); + } + + if ($base_uri) { + my $new_url = URI->new_abs( $rel_uri, $base_uri ); + return $new_url; + } + else { + $rel_uri; + } +} + +## Takes a $dav->get_globs(URI) +# Where URI may contain wildcards at the leaf level: +# URI: +# http://www.host.org/perldav/test*.html +# /perldav/test?.html +# test[12].html +# +# Performs a propfind to determine the url's that match +# +sub get_globs { + my ( $self, $url ) = @_; + my @urls = (); + my ( $left, $leafname ) = HTTP::DAV::Utils::split_leaf($url); + + # We need to unescape it because it may have been encoded. + $leafname = URI::Escape::uri_unescape($leafname); + + if ( $leafname =~ /[\*\?\[]/ ) { + my $resource = $self->new_resource( -uri => $left ); + my $resp = $resource->propfind( -depth => 1 ); + if ( $resp->is_error ) { + $self->err( 'ERR_RESP_FAIL', $resp->message(), $left ); + return (); + } + + $leafname = HTTP::DAV::Utils::glob2regex($leafname); + my $rl = $resource->get_resourcelist(); + if ($rl) { + my $match = 0; + + # We eval this because a bogus leafname could bomb the regex. + eval { + foreach my $progeny ( $rl->get_resources() ) + { + my $progeny_url = $progeny->get_uri; + my $progeny_leaf + = HTTP::DAV::Utils::get_leafname($progeny_url); + if ( $progeny_leaf =~ /^$leafname$/ ) { + print "Matched $progeny_url\n" + if $HTTP::DAV::DEBUG > 1; + $match++; + push( @urls, $progeny_url ); + } + else { + print "Skipped $progeny_url\n" + if $HTTP::DAV::DEBUG > 1; + } + } + }; + $self->err( 'ERR_GENERIC', "No match found" ) unless ($match); + } + } + else { + push( @urls, $url ); + } + + return @urls; +} + +sub what_happened { + my ($self, $url, $resource, $response) = @_; + + if (! $response->is_error()) { + return { success => 1 } + } + + my $error_type; + my $error_msg; + + # Method not allowed + if ($response->status_line =~ m{405}) { + $error_type = 'ERR_405'; + $error_msg = $response->status_line; + } + # 501 most probably means your LWP doesn't support SSL + elsif ($response->status_line =~ m{501}) { + $error_type = 'ERR_501'; + $error_msg = $response->status_line; + } + elsif ($response->www_authenticate) { + $error_type = 'ERR_UNAUTHORIZED'; + $error_msg = $response->www_authenticate; + } + elsif ( !$resource->is_dav_compliant ) { + $error_type = 'ERR_GENERIC'; + $error_msg = qq{The URL "$url" is not DAV enabled or not accessible.}; + } + else { + $error_type = 'ERR_RESP_FAIL'; + my $message = $response->message(); + $error_msg = qq{Could not access $url: $message}; + } + + return { + success => 0, + error_type => $error_type, + error_msg => $error_msg, + } + +} + +1; + +__END__ + + +=head1 NAME + +HTTP::DAV - A WebDAV client library for Perl5 + +=head1 SYNOPSIS + + # DAV script that connects to a webserver, safely makes + # a new directory and uploads all html files in + # the /tmp directory. + + use HTTP::DAV; + + $d = HTTP::DAV->new(); + $url = "http://host.org:8080/dav/"; + + $d->credentials( + -user => "pcollins", + -pass => "mypass", + -url => $url, + -realm => "DAV Realm" + ); + + $d->open( -url => $url ) + or die("Couldn't open $url: " .$d->message . "\n"); + + # Make a null lock on newdir + $d->lock( -url => "$url/newdir", -timeout => "10m" ) + or die "Won't put unless I can lock for 10 minutes\n"; + + # Make a new directory + $d->mkcol( -url => "$url/newdir" ) + or die "Couldn't make newdir at $url\n"; + + # Upload multiple files to newdir. + if ( $d->put( -local => "/tmp/*.html", -url => $url ) ) { + print "successfully uploaded multiple files to $url\n"; + } else { + print "put failed: " . $d->message . "\n"; + } + + $d->unlock( -url => $url ); + +=head1 DESCRIPTION + +HTTP::DAV is a Perl API for interacting with and modifying content on webservers using the WebDAV protocol. Now you can LOCK, DELETE and PUT files and much more on a DAV-enabled webserver. + +HTTP::DAV is part of the PerlDAV project hosted at http://www.webdav.org/perldav/ and has the following features: + +=over 4 + +=item * + +Full RFC2518 method support. OPTIONS, TRACE, GET, HEAD, DELETE, PUT, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK. + +=item * + +A fully object-oriented API. + +=item * + +Recursive GET and PUT for site backups and other scripted transfers. + +=item * + +Transparent lock handling when performing LOCK/COPY/UNLOCK sequences. + +=item * + +http and https support (https requires the Crypt::SSLeay library). See INSTALLATION. + +=item * + +Basic AND Digest authentication support (Digest auth requires the MD5 library). See INSTALLATION. + +=item * + +C, a fully-functional ftp-style interface written on top of the HTTP::DAV API and bundled by default with the HTTP::DAV library. (If you've already installed HTTP::DAV, then dave will also have been installed (probably into /usr/local/bin). You can see it's man page by typing "perldoc dave" or going to http://www.webdav.org/perldav/dave/. + +=item * + +It is built on top of the popular LWP (Library for WWW access in Perl). This means that HTTP::DAV inherits proxy support, redirect handling, basic (and digest) authorization and many other HTTP operations. See C for more information. + +=item * + +Popular server support. HTTP::DAV has been tested against the following servers: mod_dav, IIS5, Xythos webfile server and mydocsonline. The library is growing an impressive interoperability suite which also serves as useful "sample scripts". See "make test" and t/*. + +=back + +C essentially has two API's, one which is accessed through this module directly (HTTP::DAV) and is a simple abstraction to the rest of the HTTP::DAV::* Classes. The other interface consists of the HTTP::DAV::* classes which if required allow you to get "down and dirty" with your DAV and HTTP interactions. + +The methods provided in C should do most of what you want. If, however, you need more control over the client's operations or need more info about the server's responses then you will need to understand the rest of the HTTP::DAV::* interfaces. A good place to start is with the C and C documentation. + +=head1 METHODS + +=head2 METHOD CALLING: Named vs Unnamed parameters + +You can pass parameters to C methods in one of two ways: named or unnamed. + +Named parameters provides for a simpler/easier to use interface. A named interface affords more readability and allows the developer to ignore a specific order on the parameters. (named parameters are also case insensitive) + +Each argument name is preceded by a dash. Neither case nor order matters in the argument list. -url, -Url, and -URL are all acceptable. In fact, only the first argument needs to begin with a dash. If a dash is present in the first argument, C assumes dashes for the subsequent ones. + +Each method can also be called with unnamed parameters which often makes sense for methods with only one parameter. But the developer will need to ensure that the parameters are passed in the correct order (as listed in the docs). + + Doc: method( -url=>$url, [-depth=>$depth] ) + Named: $d->method( -url=>$url, -depth=>$d ); # VALID + Named: $d->method( -Depth=>$d, -Url=>$url ); # VALID + Named: $d->method( Depth=>$d, Url=>$url ); # INVALID (needs -) + Named: $d->method( -Arg2=>$val2 ); # INVALID, ARG1 is not optional + Unnamed: $d->method( $val1 ); # VALID + Unnamed: $d->method( $val2,$val1 ); # INVALID, ARG1 must come first. + +IMPORTANT POINT!!!! If you specify a named parameter first but then forget for the second and third parameters, you WILL get weird things happen. E.g. this is bad: + + $d->method( -url=>$url, $arg2, $arg3 ); # BAD BAD BAD + +=head2 THINGS YOU NEED TO KNOW + +In all of the methods specified in L there are some common concepts you'll need to understand: + +=over 4 + +=item * URLs represent an absolute or relative URI. + + -url=>"host.org/dav_dir/" # Absolute + -url=>"/dav_dir/" # Relative + -url=>"file.txt" # Relative + +You can only use a relative URL if you have already "open"ed an absolute URL. + +The HTTP::DAV module now consistently uses the named parameter: URL. The lower-level HTTP::DAV::Resource interface inconsistently interchanges URL and URI. I'm working to resolve this, in the meantime, you'll just need to remember to use the right one by checking the documentation if you need to mix up your use of both interfaces. + +=item * GLOBS + +Some methods accept wildcards in the URL. A wildcard can be used to indicate that the command should perform the command on all Resources that match the wildcard. These wildcards are called GLOBS. + +The glob may contain the characters "*", "?" and the set operator "[...]" where ... contains multiple characters ([1t2]) or a range such ([1-5]). For the curious, the glob is converted to a regex and then matched: "*" to ".*", "?" to ".", and the [] is left untouched. + +It is important to note that globs only operate at the leaf-level. For instance "/my_dir/*/file.txt" is not a valid glob. + +If a glob matches no URL's the command will fail (which normally means returns 0). + +Globs are useful in conjunction with L to provide feedback as each operation completes. + +See the documentation for each method to determine whether it supports globbing. + +Globs are useful for interactive style applications (see the source code for C as an example). + +Example globs: + + $dav1->delete(-url=>"/my_dir/file[1-3]"); # Matches file1, file2, file3 + $dav1->delete(-url=>"/my_dir/file[1-3]*.txt");# Matches file1*.txt,file2*.txt,file3*.txt + $dav1->delete(-url=>"/my_dir/*/file.txt"); # Invalid. Can only match at leaf-level + +=item * CALLBACKS + +Callbacks are used by some methods (primarily get and put) to give the caller some insight as to how the operation is progressing. A callback allows you to define a subroutine as defined below and pass a reference (\&ref) to the method. + +The rationale behind the callback is that a recursive get/put or an operation against many files (using a C) can actually take a long time to complete. + +Example callback: + + $d->get( -url=>$url, -to=>$to, -callback=>\&mycallback ); + +Your callback function MUST accept arguments as follows: + sub cat_callback { + my($status,$mesg,$url,$so_far,$length,$data) = @_; + ... + } + +The C argument specifies whether the operation has succeeded (1), failed (0), or is in progress (-1). + +The C argument is a status message. The status message could contain any string and often contains useful error messages or success messages. + +The C the remote URL. + +The C, C - these parameters indicate how many bytes have been downloaded and how many we should expect. This is useful for doing "56% to go" style-gauges. + +The C parameter - is the actual data transferred. The C command uses this to print the data to the screen. This value will be empty for C. + +See the source code of C for a useful sample of how to setup a callback. + +Note that these arguments are NOT named parameters. + +All error messages set during a "multi-operation" request (for instance a recursive get/put) are also retrievable via the C function once the operation has completed. See C for more information. + +=back + +=head2 PUBLIC METHODS + +=over 4 + +=item B + +=item B + +Creates a new C client + + $d = HTTP::DAV->new(); + +The C<-useragent> parameter allows you to pass your own B and expects an C object. See the C program for an advanced example of a custom UserAgent that interactively prompts the user for their username and password. + +The C<-headers> parameter allows you to specify a list of headers to be sent along with all requests. This can be either a hashref like: + + { "X-My-Header" => "value", ... } + +or a L object. + +=item B + +sets authorization credentials for a C and/or C. + +When the client hits a protected resource it will check these credentials to see if either the C or C match the authorization response. + +Either C or C must be provided. + +returns no value + +Example: + + $d->credentials( -url=>'myhost.org:8080/test/', + -user=>'pcollins', + -pass=>'mypass'); + +=item B + +sets the debug level to C<$val>. 0=off 3=noisy. + +C<$val> default is 0. + +returns no value. + +When the value is greater than 1, the C module will log all of the client<=>server interactions into /tmp/perldav_debug.txt. + +=back + +=head2 DAV OPERATIONS + +For all of the following operations, URL can be absolute (http://host.org/dav/) or relative (../dir2/). The only operation that requires an absolute URL is open. + +=over 4 + +=item B + +copies one remote resource to another + +=over 4 + +=item C<-url> + +is the remote resource you'd like to copy. Mandatory + +=item C<-dest> + +is the remote target for the copy command. Mandatory + +=item C<-overwrite> + +optionally indicates whether the server should fail if the target exists. Valid values are "T" and "F" (1 and 0 are synonymous). Default is T. + +=item C<-depth> + +optionally indicates whether the server should do a recursive copy or not. Valid values are 0 and (1 or "infinity"). Default is "infinity" (1). + +=back + +The return value is always 1 or 0 indicating success or failure. + +Requires a working resource to be set before being called. See C. + +Note: if either C<'URL'> or C<'DEST'> are locked by this dav client, then the lock headers will be taken care of automatically. If the either of the two URL's are locked by someone else, the server should reject the request. + +B + + $d->open(-url=>"host.org/dav_dir/"); + +Recursively copy dir1/ to dir2/ + + $d->copy(-url=>"dir1/", -dest=>"dir2/"); + +Non-recursively and non-forcefully copy dir1/ to dir2/ + + $d->copy(-url=>"dir1/", -dest=>"dir2/",-overwrite=>0,-depth=>0); + +Create a copy of dir1/file.txt as dir2/file.txt + + $d->cwd(-url=>"dir1/"); + $d->copy("file.txt","../dir2"); + +Create a copy of file.txt as dir2/new_file.txt + + $d->copy("file.txt","/dav_dir/dir2/new_file.txt") + +=item B + +changes the remote working directory. + +This is synonymous to open except that the URL can be relative and may contain a C (the first match in a glob will be used). + + $d->open("host.org/dav_dir/dir1/"); + $d->cwd("../dir2"); + $d->cwd(-url=>"../dir1"); + +The return value is always 1 or 0 indicating success or failure. + +Requires a working resource to be set before being called. See C. + +You can not cwd to files, only collections (directories). + +=item B + +deletes a remote resource. + + $d->open("host.org/dav_dir/"); + $d->delete("index.html"); + $d->delete("./dir1"); + $d->delete(-url=>"/dav_dir/dir2/file*",-callback=>\&mycallback); + +=item C<-url> + +is the remote resource(s) you'd like to delete. It can be a file, directory or C. + +=item C<-callback> is a reference to a callback function which will be called everytime a file is deleted. This is mainly useful when used in conjunction with L deletes. See L + +The return value is always 1 or 0 indicating success or failure. + +Requires a working resource to be set before being called. See C. + +This command will recursively delete directories. BE CAREFUL of uninitialised file variables in situation like this: $d->delete("$dir/$file"). This will trash your $dir if $file is not set. + +=item B + +downloads the file or directory at C to the local location indicated by C. + +=over 4 + +=item C<-url> + +is the remote resource you'd like to get. It can be a file or directory or a "glob". + +=item C<-to> + +is where you'd like to put the remote resource. The -to parameter can be: + + - a B indicating where to save the contents. + + - a B. + + - a reference to a B into which the contents will be saved. + +If the C<-url> matches multiple files (via a glob or a directory download), then the C routine will return an error if you try to use a FileHandle reference or a scalar reference. + +=item C<-callback> + +is a reference to a callback function which will be called everytime a file is completed downloading. The idea of the callback function is that some recursive get's can take a very long time and the user may require some visual feedback. See L for an examples and how to use a callback. + +=back + +The return value of get is always 1 or 0 indicating whether the entire get sequence was a success or if there was ANY failures. For instance, in a recursive get, if the server couldn't open 1 of the 10 remote files, for whatever reason, then the return value will be 0. This is so that you can have your script call the C routine to handle error conditions. + +Previous versions of HTTP::DAV allowed the return value to be the file contents if no -to attribute was supplied. This functionality is deprecated. + +Requires a working resource to be set before being called. See C. + +B + + $d->open("host.org/dav_dir/"); + +Recursively get remote my_dir/ to . + + $d->get("my_dir/","."); + +Recursively get remote my_dir/ to /tmp/my_dir/ calling &mycallback($success,$mesg) everytime a file operation is completed. + + $d->get("my_dir","/tmp",\&mycallback); + +Get remote my_dir/index.html to /tmp/index.html + + $d->get(-url=>"/dav_dir/my_dir/index.html",-to=>"/tmp"); + +Get remote index.html to /tmp/index1.html + + $d->get("index.html","/tmp/index1.html"); + +Get remote index.html to a filehandle + + my $fh = new FileHandle; + $fh->open(">/tmp/index1.html"); + $d->get("index.html",\$fh); + +Get remote index.html as a scalar (into the string $file_contents): + + my $file_contents; + $d->get("index.html",\$file_contents); + +Get all of the files matching the globs file1* and file2*: + + $d->get("file[12]*","/tmp"); + +Get all of the files matching the glob file?.html: + + $d->get("file?.html","/tmp"); # downloads file1.html and file2.html but not file3.html or file1.txt + +Invalid glob: + + $d->get("/dav_dir/*/index.html","/tmp"); # Can not glob like this. + +=item B + +locks a resource. If URL is not specified, it will lock the current working resource (opened resource). + + $d->lock( -url => "index.html", + -owner => "Patrick Collins", + -depth => "infinity", + -scope => "exclusive", + -type => "write", + -timeout => "10h" ) + +See C lock() for details of the above parameters. + +The return value is always 1 or 0 indicating success or failure. + +Requires a working resource to be set before being called. See C. + +When you lock a resource, the lock is held against the current HTTP::DAV object. In fact, the locks are held in a C object. You can operate against all of the locks that you have created as follows: + + ## Print and unlock all locks that we own. + my $rl_obj = $d->get_lockedresourcelist(); + foreach $resource ( $rl_obj->get_resources() ) { + @locks = $resource->get_locks(-owned=>1); + foreach $lock ( @locks ) { + print $resource->get_uri . "\n"; + print $lock->as_string . "\n"; + } + ## Unlock them? + $resource->unlock; + } + +Typically, a simple $d->unlock($uri) will suffice. + +B + + $d->lock($uri, -timeout=>"1d"); + ... + $d->put("/tmp/index.html",$uri); + $d->unlock($uri); + +=item B + +make a remote collection (directory) + +The return value is always 1 or 0 indicating success or failure. + +Requires a working resource to be set before being called. See C. + + $d->open("host.org/dav_dir/"); + $d->mkcol("new_dir"); # Should succeed + $d->mkcol("/dav_dir/new_dir"); # Should succeed + $d->mkcol("/dav_dir/new_dir/xxx/yyy"); # Should fail + +=item B + +moves one remote resource to another + +=over 4 + +=item C<-url> + +is the remote resource you'd like to move. Mandatory + +=item C<-dest> + +is the remote target for the move command. Mandatory + +=item C<-overwrite> + +optionally indicates whether the server should fail if the target exists. Valid values are "T" and "F" (1 and 0 are synonymous). Default is T. + +=back + +Requires a working resource to be set before being called. See C. + +The return value is always 1 or 0 indicating success or failure. + +Note: if either C<'URL'> or C<'DEST'> are locked by this dav client, then the lock headers will be taken care of automatically. If either of the two URL's are locked by someone else, the server should reject the request. + +B + + $d->open(-url=>"host.org/dav_dir/"); + +move dir1/ to dir2/ + + $d->move(-url=>"dir1/", -dest=>"dir2/"); + +non-forcefully move dir1/ to dir2/ + + $d->move(-url=>"dir1/", -dest=>"dir2/",-overwrite=>0); + +Move dir1/file.txt to dir2/file.txt + + $d->cwd(-url=>"dir1/"); + $d->move("file.txt","../dir2"); + +move file.txt to dir2/new_file.txt + + $d->move("file.txt","/dav_dir/dir2/new_file.txt") + +=item B + +opens the directory (collection resource) at URL. + +open will perform a propfind against URL. If the server does not understand the request then the open will fail. + +Similarly, if the server indicates that the resource at URL is NOT a collection, the open command will fail. + +=item B + +Performs an OPTIONS request against the URL or the working resource if URL is not supplied. + +Requires a working resource to be set before being called. See C. + +The return value is a string of comma separated OPTIONS that the server states are legal for URL or undef otherwise. + +A fully compliant DAV server may offer as many methods as: OPTIONS, TRACE, GET, HEAD, DELETE, PUT, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK + +Note: IIS5 does not support PROPPATCH or LOCK on collections. + +Example: + + $options = $d->options($url); + print $options . "\n"; + if ($options=~ /\bPROPPATCH\b/) { + print "OK to proppatch\n"; + } + +Or, put more simply: + + if ( $d->options($url) =~ /\bPROPPATCH\b/ ) { + print "OK to proppatch\n"; + } + +=item B + +Perform a propfind against URL at DEPTH depth. + +C<-depth> can be used to specify how deep the propfind goes. "0" is collection only. "1" is collection and it's immediate members (This is the default value). "infinity" is the entire directory tree. Note that most DAV compliant servers deny "infinity" depth propfinds for security reasons. + +Requires a working resource to be set before being called. See C. + +The return value is an C object on success or 0 on failure. + +The Resource object can be used for interrogating properties or performing other operations. + + ## Print collection or content length + if ( $r=$d->propfind( -url=>"/my_dir", -depth=>1) ) { + if ( $r->is_collection ) { + print "Collection\n" + print $r->get_resourcelist->as_string . "\n" + } else { + print $r->get_property("getcontentlength") ."\n"; + } + } + +Please note that although you may set a different namespace for a property of a resource during a set_prop, HTTP::DAV currently ignores all XML namespaces so you will get clashes if two properties have the same name but in different namespaces. Currently this is unavoidable but I'm working on the solution. + +=item B + +If C<-action> equals "set" then we set a property named C<-propname> to C<-propvalue> in the namespace C<-namespace> for C<-url>. + +If C<-action> equals "remove" then we unset a property named C<-propname> in the namespace C<-namespace> for C<-url>. + +If no action is supplied then the default action is "set". + +The return value is an C object on success or 0 on failure. + +The Resource object can be used for interrogating properties or performing other operations. + +To explicitly set a namespace in which to set the propname then you can use the C<-namespace> and C<-nsabbr> (namespace abbreviation) parameters. But you're welcome to play around with DAV namespaces. + +Requires a working resource to be set before being called. See C. + +It is recommended that you use C and C instead of proppatch for readability. + +C simply calls Cset)> and C calls C"remove")> + +See C and C for examples. + +=item B + +uploads the files or directories at C<-local> to the remote destination at C<-url>. + +C<-local> points to a file, directory or series of files or directories (indicated by a glob). + +If the filename contains any of the characters `*', `?' or `[' it is a candidate for filename substitution, also known as ``globbing''. This word is then regarded as a pattern (``glob-pattern''), and replaced with an alphabetically sorted list of file names which match the pattern. + +One can upload/put a string by passing a reference to a scalar in the -local parameter. See example below. + +put requires a working resource to be set before being called. See C. + +The return value is always 1 or 0 indicating success or failure. + +See L for a description of what the optional callback parameter does. + +You can also pass a C<-headers> argument. That allows to specify custom HTTP headers. It can be either a hashref with header names and values, or a L object. + +B + +Put a string to the server: + + my $myfile = "This is the contents of a file to be uploaded\n"; + $d->put(-local=>\$myfile,-url=>"http://www.host.org/dav_dir/file.txt"); + +Put a local file to the server: + + $d->put(-local=>"/tmp/index.html",-url=>"http://www.host.org/dav_dir/"); + +Put a series of local files to the server: + + In these examples, /tmp contains file1.html, file1, file2.html, + file2.txt, file3.html, file2/ + + $d->put(-local=>"/tmp/file[12]*",-url=>"http://www.host.org/dav_dir/"); + + uploads file1.html, file1, file2.html, file2.txt and the directory file2/ to dav_dir/. + +=item B + +Sets a property named C<-propname> to C<-propvalue> in the namespace C<-namespace> for C<-url>. + +Requires a working resource to be set before being called. See C. + +The return value is an C object on success or 0 on failure. + +The Resource object can be used for interrogating properties or performing other operations. + +Example: + + if ( $r = $d->set_prop(-url=>$url, + -namespace=>"dave", + -propname=>"author", + -propvalue=>"Patrick Collins" + ) ) { + print "Author property set\n"; + } else { + print "set_prop failed:" . $d->message . "\n"; + } + +See the note in propfind about namespace support in HTTP::DAV. They're settable, but not readable. + + + +=item B + +forcefully steals any locks held against URL. + +steal will perform a propfind against URL and then, any locks that are found will be unlocked one by one regardless of whether we own them or not. + +Requires a working resource to be set before being called. See C. + +The return value is always 1 or 0 indicating success or failure. If multiple locks are found and unlocking one of them fails then the operation will be aborted. + + if ($d->steal()) { + print "Steal succeeded\n"; + } else { + print "Steal failed: ". $d->message() . "\n"; + } + +=item B + +unlocks any of our locks on URL. + +Requires a working resource to be set before being called. See C. + +The return value is always 1 or 0 indicating success or failure. + + if ($d->unlock()) { + print "Unlock succeeded\n"; + } else { + print "Unlock failed: ". $d->message() . "\n"; + } + +=item B + +Unsets a property named C<-propname> in the namespace C<-namespace> for C<-url>. +Requires a working resource to be set before being called. See C. + +The return value is an C object on success or 0 on failure. + +The Resource object can be used for interrogating properties or performing other operations. + +Example: + + if ( $r = $d->unset_prop(-url=>$url, + -namespace=>"dave", + -propname=>"author", + ) ) { + print "Author property was unset\n"; + } else { + print "set_prop failed:" . $d->message . "\n"; + } + +See the note in propfind about namespace support in HTTP::DAV. They're settable, but not readable. + +=back + +=head2 ACCESSOR METHODS + +=over 4 + +=item B + +Returns the clients' working C object. + +You may want to interact with the C object +to modify request headers or provide advanced authentication +procedures. See dave for an advanced authentication procedure. + +=item B + +Takes no arguments and returns the clients' last outgoing C object. + +You would only use this to inspect a request that has already occurred. + +If you would like to modify the C BEFORE the HTTP request takes place (for instance to add another header), you will need to get the C using C and interact with that. + +=item B + +Returns the currently "opened" or "working" resource (C). + +The working resource is changed whenever you open a url or use the cwd command. + +e.g. + $r = $d->get_workingresource + print "pwd: " . $r->get_uri . "\n"; + +=item B + +Returns the currently "opened" or "working" C. + +The working resource is changed whenever you open a url or use the cwd command. + + print "pwd: " . $d->get_workingurl . "\n"; + +=item B + +Returns an C object that represents all of the locks we've created using THIS dav client. + + print "pwd: " . $d->get_workingurl . "\n"; + +=item B + +This is a useful utility function which joins C and C and returns a new URI. + +If C is not supplied then the current working resource (as indicated by get_workingurl) is used. If C is not set and there is no current working resource the C will be returned. + +For instance: + $d->open("http://host.org/webdav/dir1/"); + + # Returns "http://host.org/webdav/dir2/" + $d->get_absolute_uri(-rel_uri=>"../dir2"); + + # Returns "http://x.org/dav/dir2/file.txt" + $d->get_absolute_uri(-rel_uri =>"dir2/file.txt", + ->base_uri=>"http://x.org/dav/"); + +Note that it subtly takes care of trailing slashes. + +=back + +=head2 ERROR HANDLING METHODS + +=over 4 + +=item B + +C gets the last success or error message. + +The return value is always a scalar (string) and will change everytime a dav operation is invoked (lock, cwd, put, etc). + +See also C for operations which contain multiple error messages. + +=item B + +Returns an @array of error messages that had been set during a multi-request operation. + +Some of C's operations perform multiple request to the server. At the time of writing only put and get are considered multi-request since they can operate recursively requiring many HTTP requests. + +In these situations you should check the errors array if to determine if any of the requests failed. + +The C function is used for multi-request operations and not to be confused with a multi-status server response. A multi-status server response is when the server responds with multiple error messages for a SINGLE request. To deal with multi-status responses, see C. + + # Recursive put + if (!$d->put( "/tmp/my_dir", $url ) ) { + # Get the overall message + print $d->message; + # Get the individual messages + foreach $err ( $d->errors ) { print " Error:$err\n" } + } + +=item B + +Returns the status of the last DAV operation performed through the HTTP::DAV interface. + +This value will always be the same as the value returned from an HTTP::DAV::method. For instance: + + # This will always evaluate to true + ($d->lock($url) eq $d->is_success) ? + +You may want to use the is_success method if you didn't capture the return value immediately. But in most circumstances you're better off just evaluating as follows: + if($d->lock($url)) { ... } + +=item B + +Takes no arguments and returns the last seen C object. + +You may want to use this if you have just called a propfind and need the individual error messages returned in a MultiStatus. + +If you find that you're using get_last_response() method a lot, you may be better off using the more advanced C interface and interacting with the HTTP::DAV::* interfaces directly as discussed in the intro. For instance, if you find that you're always wanting a detailed understanding of the server's response headers or messages, then you're probably better off using the C methods and interpreting the C directly. + +To perform detailed analysis of the server's response (if for instance you got back a multistatus response) you can call C which will return to you the most recent response object (always the result of the last operation, PUT, PROPFIND, etc). With the returned HTTP::DAV::Response object you can handle multi-status responses. + +For example: + + # Print all of the messages in a multistatus response + if (! $d->unlock($url) ) { + $response = $d->get_last_response(); + if ($response->is_multistatus() ) { + foreach $num ( 0 .. $response->response_count() ) { + ($err_code,$mesg,$url,$desc) = + $response->response_bynum($num); + print "$mesg ($err_code) for $url\n"; + } + } + } + +=back + +=head2 ADVANCED METHODS + +=over 4 + +=item B + +Creates a new resource object with which to play. +This is the preferred way of creating an C object if required. +Why? Because each Resource object needs to sit within a global HTTP::DAV client. +Also, because the new_resource routine checks the C locked resource +list before creating a new object. + + $dav->new_resource( -uri => "http://..." ); + +=item B + +Sets the current working resource to URL. + +You shouldn't need this method. Call open or cwd to set the working resource. + +You CAN call C but you will need to perform a +C immediately following it to ensure that the working +resource is valid. + +=back + +=head1 INSTALLATION, TODO, MAILING LISTS and REVISION HISTORY + +[OUTDATED] + +Please see the primary HTTP::DAV webpage at +(http://www.webdav.org/perldav/http-dav/) +or the README file in this library. + +=head1 SEE ALSO + +You'll want to also read: + +=over * + +=item C + +=item C + +=item C + +=back + +and maybe if you're more inquisitive: + +=over * + +=item C + +=item C + +=item C + +=item C + +=item C + +=item C + +=back + +=head1 AUTHOR AND COPYRIGHT + +This module is Copyright (C) 2001-2008 by + + Patrick Collins + G03 Gloucester Place, Kensington + Sydney, Australia + + Email: pcollins@cpan.org + Phone: +61 2 9663 4916 + +All rights reserved. + +Current co-maintainer of the module is Cosimo Streppone +for Opera Software ASA, L. + +You may distribute this module under the terms of either the +GNU General Public License or the Artistic License, +as specified in the Perl README file. + +=cut diff --git a/csync/tests/ownCloud/README b/csync/tests/ownCloud/README new file mode 100644 index 000000000..121dbccbb --- /dev/null +++ b/csync/tests/ownCloud/README @@ -0,0 +1,37 @@ +t1 - an integration test script for csync syncing to ownCloud. + +Note: This test script uses perl HTTP::DAV. This package needs to +be in version 0.46 at least. Many distros deliver older versions. +A working version is part of the github checkout. + +Note: This test script uses perl HTTP::DAV. This package needs to +be in version 0.46 at least. Many distros deliver older versions. +Update than. + +t1 uses a perl WebDAV client lib to sync to an existing instance of +ownCloud. For that, various files are copied around, synced and the +results are tested through their existance, the filesize and the +modification times. All tests are asserts, which means that the +scripts stops if a test fails. + +How to call: + +First, configure the script. For that, create a file t1.cfg. There +is t1.cfg.in as an example. Yeah, this test script is not secure, +make sure to run it with a weak account and in a save environment. + +Second, unpack the test file collection with +tar xf testfiles.tar.xz +in the directory where the tarball can be found. + +To start the script, call ./t1.pl on the commandline. A lot of +output is generated. If the script does not fail, everything works. + +Before it actually ends, it takes a four seconds break for you to +interrupt with Ctrl-C. If you don't do that, it removes all its +traces... + +Have fun, +Klaas Freitag + + diff --git a/csync/tests/ownCloud/exclude.cfg b/csync/tests/ownCloud/exclude.cfg new file mode 100644 index 000000000..8e857f5c7 --- /dev/null +++ b/csync/tests/ownCloud/exclude.cfg @@ -0,0 +1 @@ +*_conflict-* diff --git a/csync/tests/ownCloud/ownCloud/Test.pm b/csync/tests/ownCloud/ownCloud/Test.pm new file mode 100644 index 000000000..6eab047e6 --- /dev/null +++ b/csync/tests/ownCloud/ownCloud/Test.pm @@ -0,0 +1,539 @@ +# +# Copyright (c) 2013 Klaas Freitag +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# +################################################################ +# Contributors: +# Klaas Freitag +# +package ownCloud::Test; + +use strict; +use Exporter; + +use HTTP::DAV; +use Data::Dumper; +use File::Glob ':glob'; +use Carp::Assert; +use Digest::MD5; +use Unicode::Normalize; +use LWP::UserAgent; +use HTTP::Request::Common; +use File::Basename; + +use Encode qw(from_to); +use utf8; +if ($^O eq "darwin") { + eval "require Encode::UTF8Mac"; +} + +use open ':encoding(utf8)'; + +use vars qw( @ISA @EXPORT @EXPORT_OK $d %config); + +our $owncloud = "http://localhost/oc/remote.php/webdav/"; +our $user = "joe"; +our $passwd = 'XXXXX'; # Mind to be secure. +our $ld_libpath = "/home/joe/owncloud.com/buildcsync/modules"; +our $csync = "/home/joe/owncloud.com/buildcsync/client/ocsync"; +our $remoteDir; +our $localDir; +our $infoCnt = 1; + + +@ISA = qw(Exporter); +@EXPORT = qw( initTesting createRemoteDir createLocalDir cleanup csync + assertLocalDirs assertLocalAndRemoteDir glob_put put_to_dir + putToDirLWP localDir remoteDir localCleanup createLocalFile md5OfFile + remoteCleanup server initLocalDir initRemoteDir moveRemoteFile + printInfo remoteFileId); + +sub server +{ + return $owncloud; +} + +sub fromFileName($) +{ + my ($file) = @_; + if ( $^O eq "darwin" ) { + my $fromFileName = NFC( Encode::decode('utf-8', $file) ); + return $fromFileName; + } else { + return $file; + } +} + + +sub initTesting(;$) +{ + my ($prefix) = @_; + + if( -r "./t1.cfg" ) { + my %config = do 't1.cfg'; + warn "Could not parse t1.cfg: $!\n" unless %config; + warn "Could not do t1.cfg: $@\n" if $@; + + $user = $config{user} if( $config{user} ); + $passwd = $config{passwd} if( $config{passwd} ); + $owncloud = $config{url} if( $config{url} ); + $ld_libpath = $config{ld_libpath} if( $config{ld_libpath} ); + $csync = $config{csync} if( $config{csync} ); + print "Read t1.cfg: $config{url}\n"; + } + + $owncloud .= "/" unless( $owncloud =~ /\/$/ ); + + + print "Connecting to ownCloud at ". $owncloud ."\n"; + $d = HTTP::DAV->new(); + + $d->credentials( -url=> $owncloud, -realm=>"ownCloud", + -user=> $user, + -pass=> $passwd ); + # $d->DebugLevel(3); + $prefix = "t1" unless( defined $prefix ); + + my $dirId = sprintf("%#.3o", rand(1000)); + my $dir = sprintf( "%s-%s/", $prefix, $dirId ); + + $localDir = $dir; + $localDir .= "/" unless( $localDir =~ /\/$/ ); + $remoteDir = $dir; + + initRemoteDir(); + initLocalDir(); + printf( "Test directory name is %s\n", $dir ); +} + +# Call this first to create the unique test dir stored in +# the global var $remoteDir; +sub initRemoteDir +{ + $d->open( $owncloud ); + $owncloud .= $remoteDir; + + my $re = $d->mkcol( $owncloud ); + if( $re == 0 ) { + print "Failed to create test dir $owncloud\n"; + exit 1; + } + # $owncloud .= $remoteDir; +} + +sub initLocalDir +{ + mkdir ($localDir, 0777 ); +} + +sub createRemoteDir(;$) +{ + my ($dir) = @_; + + my $url = $owncloud . $dir; + + $d->open( $owncloud ); + print $d->message . "\n"; + + my $re = $d->mkcol( $url ); + if( $re == 0 ) { + print "Failed to create directory <$url>: $re\n"; + exit 1; + } + $d->open( $url ); + return $re; + +} + +sub createLocalDir($) +{ + my ($dir) = (@_); + + $dir = $localDir . $dir; + print "Creating local dir: $dir\n"; + mkdir( $dir, 0777 ); +} + +sub cleanup() +{ + # ================================================================== + + print "\n################################################\n"; + printf( " all cool - %d tests succeeded in %s.\n", $infoCnt-1, $remoteDir); + print "#################################################\n"; + + print "\nInterrupt before cleanup in 4 seconds...\n"; + sleep(4); + + remoteCleanup( '' ); + localCleanup( '' ); + +} + +sub remoteCleanup($) +{ + my ($dir) = @_; + $d->open( -url => $owncloud . $dir ); + + print "Cleaning Remote!\n"; + + my $re = $d->delete( $owncloud . $dir ); + + if( $re == 0 ) { + print "Failed to clenup directory <$owncloud $dir>\n"; + } + return $re; +} + +sub localCleanup($) +{ + my ($dir) = @_; + # don't play child games here: + $dir = "$localDir/$dir"; + system( "rm -rf $dir" ); +} + +sub csync( ) +{ + my $url = $owncloud; + $url =~ s#^http://##; # Remove the leading http:// + $url = "owncloud://$user:$passwd@". $url; + print "CSync URL: $url\n"; + + my $args = ""; # "--exclude-file=exclude.cfg -c"; + my $cmd = "LD_LIBRARY_PATH=$ld_libpath $csync $args $localDir $url"; + print "Starting: $cmd\n"; + + system( $cmd ) == 0 or die("CSync died!\n"); +} + +# +# Check local directories if they have the same content. +# +sub assertLocalDirs( $$ ) +{ + my ($dir1, $dir2) = @_; + print "Asserting $dir1 <-> $dir2\n"; + + opendir(my $dh, $dir1 ) || die; + while(readdir $dh) { + assert( -e "$dir2/$_" ); + next if( -d "$dir1/$_"); # don't compare directory sizes. + my $s1 = -s "$dir1/$_"; + my $s2 = -s "$dir2/$_"; + assert( $s1 == $s2, "$dir1/$_ <-> $dir2/$_" ); + } + closedir $dh; +} + +sub localDir() +{ + return $localDir; +} + +sub remoteDir() +{ + return $remoteDir; +} +# +# Check if a local and a remote dir have the same content +# + +sub assertFile($$) +{ + my ($localFile, $res) = @_; + + print "Asserting $localFile and " . $res->get_property("rel_uri") . "\n"; + + my $remoteModTime = $res->get_property( "lastmodifiedepoch" ) ; + + my $localFile2 = $localFile; + if ($^O eq "darwin") { + from_to($localFile2, 'utf-8-mac', 'utf-8'); + } + my $stat_ok = stat( $localFile2 ); + print " *** STAT failed for $localFile2\n" unless( $stat_ok ); + my @info = stat( $localFile2 ); + my $localModTime = $info[9]; + assert( $remoteModTime == $localModTime, "Modified-Times differ: remote: $remoteModTime <-> local: $localModTime" ); + print "local versuse Remote modtime: $localModTime <-> $remoteModTime\n"; + # check for the same file size + my $localSize = $info[7]; + my $remoteSize = $res->get_property( "getcontentlength" ); + if( $remoteSize ) { # directories do not have a contentlength + print "Local versus Remote size: $localSize <-> $remoteSize\n"; + assert( $localSize == $remoteSize, "File sizes differ" ); + } +} + +sub registerSeen($$) +{ + my ($seenRef, $file) = @_; + $seenRef->{$file} = 1; +} + +sub traverse( $$ ) +{ + my ($remote, $acceptConflicts) = @_; + $remote .= '/' unless $remote =~ /(^|\/)$/; + printf("===============> $remote\n"); + + my $url = $owncloud . $remote; + my %seen; + + if( my $r = $d->propfind( -url => $url, -depth => 1 ) ) { + + if( $r->get_resourcelist ) { + foreach my $res ( $r->get_resourcelist->get_resources() ) { + my $filename = $res->get_property("rel_uri"); + + if( $res->is_collection ) { + # print "Checking " . $res-> get_uri()->as_string ."\n"; + print "Traversing into directory: $filename\n"; + my $dirname = $remote . $filename; + traverse( $dirname, $acceptConflicts ); + registerSeen( \%seen, $localDir . $dirname ); + } else { + # Check files here. + print "Checking file: $remote$filename\n"; + my $localFile = $localDir . $remote . $filename; + registerSeen( \%seen, $localFile ); + # $localFile =~ s/t1-\d+\//t1\//; + + assertFile( $localFile, $res ); + } + } + } + } else { + print "Propfind failed: " . $d->message() . "\n"; + } + + # Check the directory contents + my $localpath = localDir(); + $localpath .= $remote if( $remote ne "/" ); + print "#### localpath = " . $localpath . "\n"; + opendir(my $dh, $localpath ) || die; + # print Dumper( %seen ); + while( readdir $dh ) { + next if( /^\.+$/ ); + my $f = $localpath . fromFileName($_); + chomp $f; + assert( -e $f ); + my $isHere = undef; + if( exists $seen{$f} ) { + $isHere = 1; + $seen{$f} = 2; + } + if( !$isHere && exists $seen{$f . "/"} ) { + $isHere = 1; + $seen{$f."/"} = 3; + } + + $isHere = 1 if( $acceptConflicts && !$isHere && $f =~ /_conflict/ ); + $isHere = 1 if( $f =~ /\.csync/ ); + assert( $isHere, "Filename local, but not remote: $f" ); + } + + # Check if there was something remote that we havent locally. + foreach my $f ( keys %seen ) { + assert( $seen{$f} > 1, "File on remote, but not locally: $f " . $seen{$f} ); + } + # print Dumper %seen; + print "<================ Done $remote\n"; + closedir $dh; +} + +sub assertLocalAndRemoteDir( $$ ) +{ + my ($remote, $acceptConflicts ) = @_; + # %seen = (); + traverse( $remote, $acceptConflicts ); +} + +sub glob_put( $$ ) +{ + my( $globber, $target ) = @_; + + # $target = $owncloud . $target; + + $d->open( $target ); + + my @puts = bsd_glob( $globber ); + foreach my $llfile( @puts ) { + my $lfile = fromFileName($llfile); + if( $lfile =~ /.*\/(.+)$/g ) { + my $rfile = $1; + my $puturl = "$target"."$rfile"; + if( -d $lfile ) { + $d->mkcol( $puturl ); + } else { + $lfile = $llfile; + $puturl = $target; + print " *** Putting $lfile to $puturl\n"; + # putToDirLWP( $lfile, $puturl ); + put_to_dir($lfile, $puturl); + + # if( ! $d->put( -local=>$lfile, -url=> $puturl ) ) { + #print " ### FAILED to put: ". $d->message . '\n'; + # s} + } + } + + } +} + +sub put_to_dir( $$ ) +{ + my ($file, $dir) = @_; + + $dir .="/" unless $dir =~ /\/$/; + $d->open($dir); + + my $filename = $file; + $filename =~ s/^.*\///; + my $puturl = $owncloud . $dir. $filename; + print "put_to_dir puts to $puturl\n"; + unless ($d->put( -local => $file, -url => $puturl )) { + print " ### FAILED to put a single file!\n"; + } +} + +# The HTTP DAV module often does a PROPFIND before it really PUTs. That +# is not neccessary if we know that the directory is really there. +# Use this function in this case: +sub putToDirLWP($$) +{ + my ($file, $dir) = @_; + + $dir .="/" unless $dir =~ /\/$/; + + my $filename = $file; + my $basename = basename $filename; + + $dir =~ s/^\.\///; + my $puturl = $owncloud . $dir. $basename; + # print "putToDir LWP puts $filename to $puturl\n"; + die("Could not open $filename: $!") unless( open FILE, "$filename" ); + binmode FILE, ":utf8";; + my $string = ; + close FILE; + + my $ua = LWP::UserAgent->new(); + $ua->agent( "ownCloudTest_$localDir"); + my $req = PUT $puturl, Content_Type => 'application/octet-stream', + Content => $string; + $req->authorization_basic($user, $passwd); + my $response = $ua->request($req); + + if ($response->is_success()) { + # print "OK: ", $response->content; + } else { + die( "HTTP PUT failed: " . $response->as_string ); + } +} + +sub createLocalFile( $$ ) +{ + my ($fname, $size) = @_; + $size = 1024 unless( $size ); + + my $md5 = Digest::MD5->new; + + open(FILE, ">", $fname) or die "Can't open $fname for writing ($!)"; + + my $minimum = 32; + my $range = 96; + + for (my $bytes = 0; $bytes < $size; $bytes += 4) { + my $rand = int(rand($range ** 4)); + my $string = ''; + for (1..4) { + $string .= chr($rand % $range + $minimum); + $rand = int($rand / $range); + } + print FILE $string; + $md5->add($string); + } + close FILE; + return $md5->hexdigest; +} + +sub md5OfFile( $ ) +{ + my ($file) = @_; + + open FILE, "$file"; + + my $ctx = Digest::MD5->new; + $ctx->addfile (*FILE); + my $hash = $ctx->hexdigest; + close (FILE); + + return $hash; +} + +sub moveRemoteFile($$) +{ + my ($from, $to) = @_; + + my $fromUrl = $owncloud . $from; + my $toUrl = $owncloud . $to; + + $d->move($fromUrl, $toUrl); + +} + +sub printInfo($) +{ + my ($info) = @_; + my $tt = 6+length( $info ); + + print "#" x $tt; + printf( "\n# %2d. %s", $infoCnt, $info ); + print "\n" unless $info =~ /\n$/; + print "#" x $tt; + print "\n"; + + $infoCnt++; +} + +sub remoteFileId($$) +{ + my ($fromDir, $file) = @_; + my $fromUrl = $owncloud . $fromDir; + my $id; + + if( my $r = $d->propfind( -url => $fromUrl, -depth => 1 ) ) { + if ( $r->is_collection ) { + # print "Collection\n"; + + foreach my $res ( $r->get_resourcelist->get_resources() ) { + my $filename = $res->get_property("rel_uri"); + # print "OOOOOOOOOOOOOO $filename " . $res->get_property('id') . "\n"; + if( $file eq $filename || $filename eq $file . "/" ) { + $id = $res->get_property('id') || ""; + } + } + } else { + # print "OOOOOOOOOOOOOOOOOOO " . $r->get_property("rel_uri"); + $id = $r->get_property('id') || ""; + } + } + print "## ID of $file: $id\n"; + return $id; +} + +# diff --git a/csync/tests/ownCloud/t1.cfg.in b/csync/tests/ownCloud/t1.cfg.in new file mode 100644 index 000000000..0dcc0d962 --- /dev/null +++ b/csync/tests/ownCloud/t1.cfg.in @@ -0,0 +1,5 @@ + user => "joe", + passwd => "secret", + url => "http://localhost/ocm/remote.php/webdav/", + ld_libpath => "/home/joe/owncloud/csync/csync-build/modules", + csync => "/home/joe/owncloud/csync/csync-build/client/csync" diff --git a/csync/tests/ownCloud/t1.pl b/csync/tests/ownCloud/t1.pl new file mode 100755 index 000000000..feb09f6f0 --- /dev/null +++ b/csync/tests/ownCloud/t1.pl @@ -0,0 +1,141 @@ +#!/usr/bin/perl +# +# Test script for the ownCloud module of csync. +# This script requires a running ownCloud instance accessible via HTTP. +# It does quite some fancy tests and asserts the results. +# +# Copyright (C) by Klaas Freitag +# + +use lib "."; + +use Carp::Assert; +use File::Copy; +use ownCloud::Test; + +use strict; + +print "Hello, this is t1, a tester for csync with ownCloud.\n"; + +initTesting(); + +print "Copy some files to the remote location\n"; +createRemoteDir( "remoteToLocal1" ); +createRemoteDir( "remoteToLocal1/rtl1" ); +createRemoteDir( "remoteToLocal1/rtl1/rtl11" ); +createRemoteDir( "remoteToLocal1/rtl2" ); +createRemoteDir( "remoteToLocal1/rtl4" ); + +glob_put( 'toremote1/*', "remoteToLocal1/" ); +glob_put( 'toremote1/rtl1/*', "remoteToLocal1/rtl1/" ); +glob_put( 'toremote1/rtl1/rtl11/*', "remoteToLocal1/rtl1/rtl11/" ); +glob_put( 'toremote1/rtl2/*', "remoteToLocal1/rtl2/" ); +glob_put( 'toremote1/rtl4/*', "remoteToLocal1/rtl4/" ); + +# call csync, sync local t1 to remote t1 +csync(); + +# Check if the files from toremote1 are now in t1/remoteToLocal1 +# they should have taken the way via the ownCloud. +print "Assert the local file copy\n"; +assertLocalDirs( 'toremote1', localDir().'remoteToLocal1' ); + +# Check if the synced files from ownCloud have the same timestamp as the local ones. +print "\nNow assert remote 'toremote1' with local " . localDir() . " :\n"; +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# remove a local file. +printInfo( "\nRemove a local file\n" ); +unlink( localDir() . 'remoteToLocal1/kernelcrash.txt' ); +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# add local files to a new dir1 +printInfo( "Add some more files to local:"); +my $locDir = localDir() . 'fromLocal1'; + +mkdir( $locDir ); +assert( -d $locDir ); +foreach my $file ( <./tolocal1/*> ) { + print "Copying $file to $locDir\n"; + copy( $file, $locDir ); +} +csync( ); +print "\nAssert local and remote dirs.\n"; +assertLocalAndRemoteDir( 'fromLocal1', 0); + +# move a local file +printInfo( "Move a file locally." ); +move( "$locDir/kramer.jpg", "$locDir/oldtimer.jpg" ); +csync( ); +assertLocalAndRemoteDir( 'fromLocal1', 0); + +# move a local directory. +printInfo( "Move a local directory." ); +move( localDir() . 'remoteToLocal1/rtl1', localDir(). 'remoteToLocal1/rtlX'); +csync(); +assertLocalAndRemoteDir( 'fromLocal1', 0); + +# remove a local dir +printInfo( "Remove a local directory."); +localCleanup( 'remoteToLocal1/rtlX' ); +csync(); +assertLocalAndRemoteDir( 'fromLocal1', 0); +assert( ! -e localDir().'remoteToLocal1/rtlX' ); + +# create a false conflict, only the mtimes are changed, by content are equal. +printInfo( "Create a false conflict."); +my $srcFile = 'toremote1/kernelcrash.txt'; +put_to_dir( $srcFile, 'remoteToLocal1' ); +system( "sleep 2 && touch " . localDir() . "remoteToLocal1/kernelcrash.txt" ); +csync( ); +assertLocalAndRemoteDir( 'fromLocal1', 0); + +# create a true conflict. +printInfo( "Create a conflict." ); +system( "echo \"This is more stuff\" >> /tmp/kernelcrash.txt" ); +put_to_dir( '/tmp/kernelcrash.txt', 'remoteToLocal1' ); +system( "sleep 2 && touch " . localDir() . "remoteToLocal1/kernelcrash.txt" ); +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 1); + +my $localMD5 = md5OfFile( localDir().'remoteToLocal1/kernelcrash.txt' ); +my $realMD5 = md5OfFile( '/tmp/kernelcrash.txt' ); +print "MD5 compare $localMD5 <-> $realMD5\n"; +assert( $localMD5 eq $realMD5 ); +assert( glob(localDir().'remoteToLocal1/kernelcrash_conflict-*.txt' ) ); + + +# prepare test for issue 1329, rtlX need to be modified +# [https://github.com/owncloud/mirall/issues/1329] +printInfo( "Add a local directory"); +system("cp -r 'toremote1/rtl1/' '" . localDir(). "remoteToLocal1/rtlX'"); +csync(); +assertLocalAndRemoteDir( 'fromLocal1', 0); + +# remove a local dir (still for issue 1329) +printInfo( "Remove that directory."); +localCleanup( 'remoteToLocal1/rtlX' ); +csync(); +assertLocalAndRemoteDir( 'fromLocal1', 0); +assert( ! -e localDir().'remoteToLocal1/rtlX' ); + + +# add it back again (still for issue 1329) +printInfo( "Add back the local dir."); +system("cp -r 'toremote1/rtl1/' '" . localDir(). "remoteToLocal1/rtlX'"); +assert( -e localDir().'remoteToLocal1/rtlX' ); +assert( -e localDir().'remoteToLocal1/rtlX/rtl11/file.txt' ); +csync(); +assertLocalAndRemoteDir( 'fromLocal1', 0); +assert( -e localDir().'remoteToLocal1/rtlX' ); +assert( -e localDir().'remoteToLocal1/rtlX/rtl11/file.txt' ); + + + +# ================================================================== + +cleanup(); + +# -- + diff --git a/csync/tests/ownCloud/t2.pl b/csync/tests/ownCloud/t2.pl new file mode 100755 index 000000000..940b066f5 --- /dev/null +++ b/csync/tests/ownCloud/t2.pl @@ -0,0 +1,166 @@ +#!/usr/bin/perl +# +# Test script for the ownCloud module of csync. +# This script requires a running ownCloud instance accessible via HTTP. +# It does quite some fancy tests and asserts the results. +# +# Copyright (C) by Klaas Freitag +# + +use lib "."; + +use Carp::Assert; +use File::Copy; +use ownCloud::Test; + +use strict; + +sub getInode($) +{ + my ($filename) = @_; + my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, + $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename); + + return $ino; +} + +print "Hello, this is t2, a tester for remote renaming\n"; + +initTesting(); + +print "Copy some files to the remote location\n"; +createRemoteDir( "remoteToLocal1" ); +createRemoteDir( "remoteToLocal1/rtl1" ); +createRemoteDir( "remoteToLocal1/rtl1/rtl11" ); +createRemoteDir( "remoteToLocal1/rtl2" ); + +glob_put( 'toremote1/*', "remoteToLocal1/" ); +glob_put( 'toremote1/rtl1/*', "remoteToLocal1/rtl1/" ); +glob_put( 'testfiles/*', "remoteToLocal1/rtl1/rtl11/" ); +glob_put( 'toremote1/rtl2/*', "remoteToLocal1/rtl2/" ); + +# call csync, sync local t1 to remote t1 +printInfo("Initial sync, sync stuff down."); +csync(); + +# Check if the files from toremote1 are now in t1/remoteToLocal1 +# they should have taken the way via the ownCloud. +print "Assert the local file copy\n"; +assertLocalDirs( localDir().'remoteToLocal1', 'toremote1' ); + +# Check if the synced files from ownCloud have the same timestamp as the local ones. +print "\nNow assert remote 'toremote1' with local " . localDir() . " :\n"; +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# Do some remote moves: + +# First a simple file move. +printInfo("Simply move a file to another name."); +my $inode = getInode('remoteToLocal1/kernelcrash.txt'); +moveRemoteFile( 'remoteToLocal1/kernelcrash.txt', 'remoteToLocal1/kernel.txt'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); +my $inode2 = getInode( 'remoteToLocal1/kernel.txt'); +assert( $inode == $inode2, "Inode has changed!"); + +printInfo("Move a file into a sub directory."); +# now move the file into a sub directory +$inode = getInode('remoteToLocal1/kernel.txt'); +moveRemoteFile( 'remoteToLocal1/kernel.txt', 'remoteToLocal1/rtl1/'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); +$inode = getInode('remoteToLocal1/rtl1/kernel.txt'); +assert( $inode == $inode2, "Inode has changed 2!"); + +printInfo("Move an existing directory."); +# move an existing directory +$inode = getInode('remoteToLocal1/rtl1'); +moveRemoteFile( 'remoteToLocal1/rtl1', 'remoteToLocal1/movedRtl1'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); +$inode = getInode('remoteToLocal1/movedRtl1'); +assert( $inode == $inode2, "Inode has changed 3!"); + +printInfo( "Move a file in a directory and than move the dir." ); +# move a file in a directory and than move the directory +moveRemoteFile('remoteToLocal1/movedRtl1/rtl11/zerofile.txt', 'remoteToLocal1/movedRtl1/rtl11/centofile.txt'); +moveRemoteFile( 'remoteToLocal1/movedRtl1', 'remoteToLocal1/againRtl1'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +printInfo("Move a directory and than move a file within it."); + +# move a directory and than move a file within the directory +moveRemoteFile( 'remoteToLocal1/againRtl1', 'remoteToLocal1/moved2Rtl1'); +moveRemoteFile('remoteToLocal1/moved2Rtl1/rtl11/centofile.txt', 'remoteToLocal1/moved2Rtl1/tripofile.txt'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +printInfo("Rename file loally and remotely to a different name."); +# Rename a file locally and the same file remotely to another name. +move( localDir() . 'remoteToLocal1/moved2Rtl1/tripofile.txt', localDir() . 'remoteToLocal1/moved2Rtl1/meckafile.txt' ); + +moveRemoteFile( 'remoteToLocal1/moved2Rtl1/tripofile.txt', 'remoteToLocal1/moved2Rtl1/sofiafile.txt' ); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# Change a file remotely and than move the directory +printInfo( "Move a directory remotely with a changed file in it."); + +my $md5 = createLocalFile( '/tmp/sofiafile.txt', 43 ); +put_to_dir( '/tmp/sofiafile.txt', 'remoteToLocal1/moved2Rtl1' ); + +moveRemoteFile( 'remoteToLocal1/moved2Rtl1', 'remoteToLocal1/newDir'); + +# Now in remoteToLocal1/newDir/sofiafile.txt we should have content... +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +my $newMd5 = md5OfFile( localDir().'remoteToLocal1/newDir/sofiafile.txt' ); +print "MD5 compare $md5 <-> $newMd5\n"; +assert( $md5 eq $newMd5 ); + +# Move a directory on remote but remove the dir locally +printInfo("Move a directory remotely, but remove the local one"); +moveRemoteFile( 'remoteToLocal1/newDir', 'remoteToLocal1/newDir2'); + +system( "rm -rf " . localDir() . 'remoteToLocal1/newDir'); +# move a file but create a file with the same name locally. +moveRemoteFile( 'remoteToLocal1/newDir2/sofiafile.txt', 'remoteToLocal1/constantinopel.txt' ); +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# Move a file remotely and create one with the same name on the +# local repo. +printInfo("Move remotely and create a local file with same name"); + +moveRemoteFile('remoteToLocal1/rtl2/kb1.jpg', 'remoteToLocal1/rtl2/kb1moved.jpg'); +move( localDir().'remoteToLocal1/rtl2/kb1.jpg', localDir().'remoteToLocal1/rtl2/kb1_local_gone.jpg'); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +## make new directory remote +printInfo("Create a remote dir, put in a file and move it, but have a similar one locally."); + +createRemoteDir('remoteToLocal1/rtl2/newRemoteDir'); + +my $firstMd5 = createLocalFile( '/tmp/donat12.txt', 4096 ); +put_to_dir( '/tmp/donat12.txt', 'remoteToLocal1/rtl2/newRemoteDir/' ); +moveRemoteFile('remoteToLocal1/rtl2/newRemoteDir/donat12.txt', + 'remoteToLocal1/rtl2/newRemoteDir/donat.txt'); +mkdir( localDir().'remoteToLocal1/rtl2/newRemoteDir' ); +createLocalFile( localDir(). 'remoteToLocal1/rtl2/newRemoteDir/donat.txt', 8021 ); + +csync(); +assertLocalAndRemoteDir( 'remoteToLocal1', 1); + +cleanup(); + +# -- diff --git a/csync/tests/ownCloud/t3.pl b/csync/tests/ownCloud/t3.pl new file mode 100755 index 000000000..5ab50e343 --- /dev/null +++ b/csync/tests/ownCloud/t3.pl @@ -0,0 +1,111 @@ +#!/usr/bin/perl +# +# Test script for the ownCloud module of csync. +# This script requires a running ownCloud instance accessible via HTTP. +# It does quite some fancy tests and asserts the results. +# +# Copyright (C) by Olivier Goffart +# + +use lib "."; + +use Carp::Assert; +use File::Copy; +use ownCloud::Test; + +use strict; + +print "Hello, this is t3, a tester for renaming directories\n"; + +initTesting(); + +printInfo( "Copy some files to the remote location\n" ); +createRemoteDir( "remoteToLocal1" ); +createRemoteDir( "remoteToLocal1/rtl1" ); +createRemoteDir( "remoteToLocal1/rtl1/rtl11" ); +createRemoteDir( "remoteToLocal1/rtl2" ); + +glob_put( 'toremote1/*', "remoteToLocal1/" ); +glob_put( 'toremote1/rtl1/*', "remoteToLocal1/rtl1/" ); +glob_put( 'testfiles/*', "remoteToLocal1/rtl1/rtl11/" ); +glob_put( 'toremote1/rtl2/*', "remoteToLocal1/rtl2/" ); + +# call csync, sync local t1 to remote t1 +csync(); + +# Check if the files from toremote1 are now in t1/remoteToLocal1 +# they should have taken the way via the ownCloud. +printInfo( "Assert the local file copy\n" ); +assertLocalDirs( localDir().'remoteToLocal1', 'toremote1' ); + +# Check if the synced files from ownCloud have the same timestamp as the local ones. +printInfo( "Now assert remote 'toremote1' with local " . localDir() ); +assertLocalAndRemoteDir( 'remoteToLocal1', 0); + +# Make a new directory, moves a sub directory into. Remove the parent directory. +# create a new file on the server in the directory that will be renamed +printInfo( "Create a new directory and move subdirs into." ); +my $newfile_md5 = createLocalFile(localDir()."remoteToLocal1/rtl1/rtl11/newfile.dat", 123); +unlink( localDir() . 'remoteToLocal1/rtl1/rtl11/test.txt' ); +mkdir( localDir() . 'newdir' ); +move( localDir() . 'remoteToLocal1/rtl1', localDir() . 'newdir/rtl1' ); +system( "rm -rf " . localDir() . 'remoteToLocal1' ); +system( "echo \"my file\" >> /tmp/myfile.txt" ); +put_to_dir( '/tmp/myfile.txt', 'remoteToLocal1/rtl1/rtl11' ); + +my $fileid = remoteFileId( 'remoteToLocal1/rtl1/', 'rtl11' ); +my $fid2 = remoteFileId( 'remoteToLocal1/rtl1/', 'La ced' ); +assert($fid2 eq "" or $fileid ne $fid2, "File IDs are equal" ); + +csync(); +my $newFileId = remoteFileId( 'newdir/rtl1/', 'rtl11' ); +my $newfid2 = remoteFileId( 'newdir/rtl1/', 'La ced' ); +assert($newFileId eq "" or $newFileId ne $newfid2, "File IDs are equal" ); + +assert( $fileid eq $newFileId, "file ID mixup: 'newdir/rtl1/rtl11" ); +assert( $fid2 eq $newfid2, "file ID mixup: 'newdir/La ced" ); + +assertLocalAndRemoteDir( 'newdir', 0); + +assert( -e localDir().'newdir/rtl1/rtl11/newfile.dat' ); +assert( -e localDir().'newdir/rtl1/rtl11/myfile.txt' ); +assert( ! -e localDir().'newdir/rtl11/test.txt' ); +assert( ! -e localDir().'remoteToLocal1' ); + +printInfo("Move file and create another one with the same name."); +move( localDir() . 'newdir/myfile.txt', localDir() . 'newdir/oldfile.txt' ); +system( "echo \"super new\" >> " . localDir() . 'newdir/myfile.txt' ); + +#Add some files for the next test. +system( "echo \"un\" > " . localDir() . '1.txt' ); +system( "echo \"deux\" > " . localDir() . '2.txt' ); +mkdir( localDir() . 'newdir2' ); + +csync(); +assertLocalAndRemoteDir( 'newdir', 0); + + +printInfo("Rename a directory that was just changed"); +# newdir was changed so it's etag is not yet saved in the database, but still it needs to be moved. +my $newdirId = remoteFileId( localDir(), 'newdir' ); +my $newdir2Id = remoteFileId( localDir(), 'newdir2' ); +move(localDir() . 'newdir' , localDir() . 'newdir3'); +move(localDir() . 'newdir2' , localDir() . 'newdir4'); + + +# FIXME: this test is currently failing +# see csync_update.c in _csyn_detect_update, the commen near the commented fs->inode != tmp->inode +# unlink( localDir() . '1.txt' ); +# move( localDir() . '2.txt', localDir() . '1.txt' ); + +csync(); +assertLocalAndRemoteDir( '', 0); +my $newdir3Id = remoteFileId( localDir(), 'newdir3' ); +my $newdir4Id = remoteFileId( localDir(), 'newdir4' ); +assert( $newdirId eq $newdir3Id, "newdir was not MOVE'd to newdir3?" ); +assert( $newdir2Id eq $newdir4Id, "newdir2 was not MOVE'd to newdir4?" ); + + +cleanup(); + +# -- diff --git a/csync/tests/ownCloud/t4.pl b/csync/tests/ownCloud/t4.pl new file mode 100755 index 000000000..57159fe07 --- /dev/null +++ b/csync/tests/ownCloud/t4.pl @@ -0,0 +1,118 @@ +#!/usr/bin/perl +# +# Test script for the ownCloud module of csync. +# This script requires a running ownCloud instance accessible via HTTP. +# It does quite some fancy tests and asserts the results. +# +# Copyright (C) by Olivier Goffart +# + +use lib "."; + +use Carp::Assert; +use File::Copy; +use ownCloud::Test; + +use strict; + +print "Hello, this is t4, a tester for A) files that cannot be stated and B) excluded files\n"; +# stat error occours on windsows when the file is busy for example + +initTesting(); + +printInfo( "Copy some files to the remote location" ); +mkdir( localDir() . 'test_stat' ); +system( "echo foobar > " . localDir() . 'test_stat/file.txt' ); + +# call csync, sync local t1 to remote t1 +csync(); + +# Check if the files from toremote1 are now in t1/remoteToLocal1 +# they should have taken the way via the ownCloud. +print "Assert the local file copy\n"; +assertLocalAndRemoteDir( '', 0 ); + + +printInfo( "Make a file not statable" ); + + +system( "echo foobar2 >> " . localDir() . 'test_stat/file.txt' ); +#make the file not statable by changing the directory right +system( "chmod 400 " . localDir() . 'test_stat' ); + + +csync(); + +# TODO: some check here. + + + +printInfo("Add a file in a read only directory"); + +system( "echo \"Hello World\" >> /tmp/kernelcrash.txt" ); +put_to_dir( '/tmp/kernelcrash.txt', 'test_stat' ); + +csync(); + +assert( ! -e localDir().'test_stat/kernelcrash' ); + + +printInfo("Restore the original rights"); + +system( "chmod 700 " . localDir() . 'test_stat' ); +system( "echo foobar3 >> " . localDir() . 'test_stat/file.txt' ); + +csync(); + +print "Check if everything is still the same\n"; + +assertLocalAndRemoteDir( '', 0 ); + +# TODO: Check that the file content is fine on the server and that there was no conflict +assert( -e localDir().'test_stat/file.txt' ); +assert( -e localDir().'test_stat/kernelcrash.txt' ); + +my $localMD5 = md5OfFile( localDir().'test_stat/kernelcrash.txt' ); +my $realMD5 = md5OfFile( '/tmp/kernelcrash.txt' ); +print "MD5 compare $localMD5 <-> $realMD5\n"; +assert( $localMD5 eq $realMD5 ); + + +printInfo("Added a file that is on the ignore list"); +# (*.directory is in the ignored list that needs cleanup) +# (it is names with _conflict) because i want the conflicft detection of assertLocalAndRemoteDir to work +system( "echo dir >> " . localDir() . 'test_stat/file_conflict.directory' ); +csync(); +# The file_conflict.directory is seen as a conflict +assertLocalAndRemoteDir( '', 1 ); +# TODO: check that the file_conflict.directory is indeed NOT on the server + +printInfo("Remove a directory containing a local file\n"); +remoteCleanup('test_stat'); + +#Add an executable file for next test +system( "echo echo hello >> " . localDir() . 'echo.sh' ); +chmod 0751, localDir() . 'echo.sh'; + +csync(); +assertLocalAndRemoteDir( '', 0 ); + +open(my $fh, "<", localDir() . 'echo.sh'); +my $perm = (stat $fh)[2] & 07777; +assert( $perm eq 0751, "permissions not kept" ); + + +printInfo("Modify a file in the remote and check its permission\n"); +system( "echo \"echo bonjour\" > /tmp/echo.sh" ); +put_to_dir( '/tmp/echo.sh', "" ); +csync(); +assertLocalAndRemoteDir( '', 0 ); + +open(my $fh, "<", localDir() . 'echo.sh'); +my $perm = (stat $fh)[2] & 07777; +assert( $perm eq 0751, "permissions not kept" ); + + +cleanup(); + +# -- diff --git a/csync/tests/ownCloud/testfiles.tar.xz b/csync/tests/ownCloud/testfiles.tar.xz new file mode 100644 index 000000000..ee5631c0b Binary files /dev/null and b/csync/tests/ownCloud/testfiles.tar.xz differ diff --git a/csync/tests/ownCloud/testfiles/church.jpg b/csync/tests/ownCloud/testfiles/church.jpg new file mode 100644 index 000000000..71bfd1a71 Binary files /dev/null and b/csync/tests/ownCloud/testfiles/church.jpg differ diff --git a/csync/tests/ownCloud/testfiles/red_is_the_rose.jpg b/csync/tests/ownCloud/testfiles/red_is_the_rose.jpg new file mode 100644 index 000000000..4100de3f5 Binary files /dev/null and b/csync/tests/ownCloud/testfiles/red_is_the_rose.jpg differ diff --git a/csync/tests/ownCloud/testfiles/test.txt b/csync/tests/ownCloud/testfiles/test.txt new file mode 100644 index 000000000..f4586ae5b --- /dev/null +++ b/csync/tests/ownCloud/testfiles/test.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + +Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. + +At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur diff --git a/csync/tests/ownCloud/testfiles/zerofile.txt b/csync/tests/ownCloud/testfiles/zerofile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/csync/tests/ownCloud/toremote1/rtl1/La ced b/csync/tests/ownCloud/toremote1/rtl1/La ced new file mode 100644 index 000000000..055dab254 --- /dev/null +++ b/csync/tests/ownCloud/toremote1/rtl1/La ced @@ -0,0 +1 @@ +A new text. diff --git a/csync/tests/ownCloud/toremote1/rtl1/rtl11/file.txt b/csync/tests/ownCloud/toremote1/rtl1/rtl11/file.txt new file mode 100644 index 000000000..3b7d77338 --- /dev/null +++ b/csync/tests/ownCloud/toremote1/rtl1/rtl11/file.txt @@ -0,0 +1 @@ +hello Olivier diff --git a/csync/tests/ownCloud/toremote1/rtl2/kb1.jpg b/csync/tests/ownCloud/toremote1/rtl2/kb1.jpg new file mode 100644 index 000000000..f10585c8a Binary files /dev/null and b/csync/tests/ownCloud/toremote1/rtl2/kb1.jpg differ diff --git a/csync/tests/ownCloud/toremote1/rtl2/mettre.xls b/csync/tests/ownCloud/toremote1/rtl2/mettre.xls new file mode 100644 index 000000000..ee3e60378 --- /dev/null +++ b/csync/tests/ownCloud/toremote1/rtl2/mettre.xls @@ -0,0 +1 @@ +some content. diff --git a/csync/tests/ownCloud/toremote1/rtl4/quitte.pdf b/csync/tests/ownCloud/toremote1/rtl4/quitte.pdf new file mode 100644 index 000000000..df5495f0a Binary files /dev/null and b/csync/tests/ownCloud/toremote1/rtl4/quitte.pdf differ diff --git a/csync/tests/ownCloud/toremote1/rtl4/red_is_the_rose.jpg b/csync/tests/ownCloud/toremote1/rtl4/red_is_the_rose.jpg new file mode 100644 index 000000000..4100de3f5 Binary files /dev/null and b/csync/tests/ownCloud/toremote1/rtl4/red_is_the_rose.jpg differ diff --git a/csync/tests/ownCloud/toremote1/special_chars.tar.gz b/csync/tests/ownCloud/toremote1/special_chars.tar.gz new file mode 100644 index 000000000..bc3d802e6 Binary files /dev/null and b/csync/tests/ownCloud/toremote1/special_chars.tar.gz differ diff --git a/csync/tests/std_tests/check_std_c_alloc.c b/csync/tests/std_tests/check_std_c_alloc.c new file mode 100644 index 000000000..59d15a7af --- /dev/null +++ b/csync/tests/std_tests/check_std_c_alloc.c @@ -0,0 +1,70 @@ +#include "torture.h" + +#include "std/c_alloc.h" + +struct test_s { + int answer; +}; + +static void check_c_malloc(void **state) +{ + struct test_s *p = NULL; + + (void) state; /* unused */ + + p = c_malloc(sizeof(struct test_s)); + assert_non_null(p); + assert_int_equal(p->answer, 0); + p->answer = 42; + assert_int_equal(p->answer, 42); + free(p); +} + +static void check_c_malloc_zero(void **state) +{ + void *p; + + (void) state; /* unused */ + + p = c_malloc((size_t) 0); + assert_null(p); +} + +static void check_c_strdup(void **state) +{ + const char *str = "test"; + char *tdup = NULL; + + (void) state; /* unused */ + + tdup = c_strdup(str); + assert_string_equal(tdup, str); + + free(tdup); +} + +static void check_c_strndup(void **state) +{ + const char *str = "test"; + char *tdup = NULL; + + (void) state; /* unused */ + + tdup = c_strndup(str, 3); + assert_memory_equal(tdup, "tes", 3); + + free(tdup); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_malloc), + unit_test(check_c_malloc_zero), + unit_test(check_c_strdup), + unit_test(check_c_strndup), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_dir.c b/csync/tests/std_tests/check_std_c_dir.c new file mode 100644 index 000000000..041fa3b34 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_dir.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include + +#include "torture.h" + +#include "std/c_private.h" +#include "std/c_dir.h" +#include "std/c_string.h" + +const char *check_dir = "/tmp/check/c_mkdirs//with/check//"; +const char *check_file = "/tmp/check/c_mkdirs/with/check/foobar.txt"; + +static void setup(void **state) { + int rc; + + (void) state; /* unused */ + + rc = c_mkdirs(check_dir, 0755); + assert_int_equal(rc, 0); + rc = system("touch /tmp/check/c_mkdirs/with/check/foobar.txt"); + assert_int_equal(rc, 0); +} + +static void teardown(void **state) { + int rc; + + (void) state; /* unused */ + + rc = c_rmdirs(check_dir); + assert_int_equal(rc, 0); +} + +static int test_dir(const char *path, mode_t mode) { + csync_stat_t sb; + if (lstat(path, &sb) < 0) { + return -1; + } + + if (! S_ISDIR(sb.st_mode)) { + return -1; + } + + /* FIXME */ + if ((sb.st_mode & mode) == mode) { + return 0; + } + + return -1; +} + +static void check_c_mkdirs_rmdirs(void **state) +{ + csync_stat_t sb; + int rc; + mbchar_t *wcheck_dir; + + (void) state; /* unused */ + + rc = c_mkdirs(check_dir, 0755); + assert_int_equal(rc, 0); + rc = test_dir(check_dir, 0755); + assert_int_equal(rc, 0); + rc = c_rmdirs(check_dir); + assert_int_equal(rc, 0); + wcheck_dir = c_utf8_to_locale(check_dir); + rc = _tstat(wcheck_dir, &sb); + c_free_locale_string(wcheck_dir); + assert_int_equal(rc, -1); +} + +static void check_c_mkdirs_mode(void **state) +{ + csync_stat_t sb; + int rc; + mbchar_t *wcheck_dir; + + (void) state; /* unused */ + rc = c_mkdirs(check_dir, 0700); + assert_int_equal(rc, 0); + rc = test_dir(check_dir, 0700); + assert_int_equal(rc, 0); + rc = c_rmdirs(check_dir); + assert_int_equal(rc, 0); + wcheck_dir = c_utf8_to_locale(check_dir); + rc = _tstat(wcheck_dir, &sb); + assert_int_equal(rc, -1); + c_free_locale_string(wcheck_dir); +} + +static void check_c_mkdirs_existing_path(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_mkdirs(check_dir, 0755); + assert_int_equal(rc, 0); +} + +static void check_c_mkdirs_file(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_mkdirs(check_file, 0755); + assert_int_equal(rc, -1); + assert_int_equal(errno, ENOTDIR); +} + +static void check_c_mkdirs_null(void **state) +{ + (void) state; /* unused */ + + assert_int_equal(c_mkdirs(NULL, 0755), -1); +} + +static void check_c_isdir(void **state) +{ + (void) state; /* unused */ + + assert_int_equal(c_isdir(check_dir), 1); +} + +static void check_c_isdir_on_file(void **state) +{ + (void) state; /* unused */ + + assert_int_equal(c_isdir(check_file), 0); +} + +static void check_c_isdir_null(void **state) +{ + (void) state; /* unused */ + + assert_int_equal(c_isdir(NULL), 0); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_mkdirs_rmdirs), + unit_test(check_c_mkdirs_mode), + unit_test_setup_teardown(check_c_mkdirs_existing_path, setup, teardown), + unit_test_setup_teardown(check_c_mkdirs_file, setup, teardown), + unit_test(check_c_mkdirs_null), + unit_test_setup_teardown(check_c_isdir, setup, teardown), + unit_test_setup_teardown(check_c_isdir_on_file, setup, teardown), + unit_test(check_c_isdir_null), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_file.c b/csync/tests/std_tests/check_std_c_file.c new file mode 100644 index 000000000..2c294ca23 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_file.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include + +#include "torture.h" + +#include "std/c_private.h" +#include "std/c_file.h" +#include "std/c_string.h" + +const char *check_dir = "/tmp/check"; +const char *check_src_file = "/tmp/check/foo.txt"; +const char *check_dst_file = "/tmp/check/bar.txt"; + +static int test_file(const char *path, mode_t mode) { + csync_stat_t sb; + mbchar_t *mbpath = c_utf8_to_locale(path); + int rc = _tstat(mbpath, &sb); + c_free_locale_string(mbpath); + + if (rc < 0) { + return -1; + } + + if (! S_ISREG(sb.st_mode)) { + return -1; + } + + if ((sb.st_mode & mode) == mode) { + return 0; + } + + return -1; +} + +static void setup(void **state) { + int rc; + + (void) state; /* unused */ + + rc = system("mkdir -p /tmp/check"); + assert_int_equal(rc, 0); + rc = system("echo 42 > /tmp/check/foo.txt"); + assert_int_equal(rc, 0); +} + +static void teardown(void **state) { + int rc; + + (void) state; /* unused */ + + rc = system("rm -rf /tmp/check"); + assert_int_equal(rc, 0); +} + +static void check_c_copy(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_copy(check_src_file, check_dst_file, 0644); + assert_int_equal(rc, 0); + rc = test_file(check_dst_file, 0644); + assert_int_equal(rc, 0); +} + +static void check_c_copy_same_file(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_copy(check_src_file, check_src_file, 0644); + assert_int_equal(rc, -1); +} + +static void check_c_copy_isdir(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_copy(check_src_file, check_dir, 0644); + assert_int_equal(rc, -1); + assert_int_equal(errno, EISDIR); + + rc = c_copy(check_dir, check_dst_file, 0644); + assert_int_equal(rc, -1); + assert_int_equal(errno, ENOENT); +} + +static void check_c_compare_file(void **state) +{ + int rc; + (void) state; + + rc = c_copy(check_src_file, check_dst_file, 0644); + assert_int_equal(rc, 0); + + rc = c_compare_file( check_src_file, check_dst_file ); + assert_int_equal(rc, 1); + + /* Check error conditions */ + rc = c_compare_file( NULL, check_dst_file ); + assert_int_equal(rc, -1); + rc = c_compare_file( check_dst_file, NULL ); + assert_int_equal(rc, -1); + rc = c_compare_file( NULL, NULL ); + assert_int_equal(rc, -1); + + rc = c_compare_file( check_src_file, "/I_do_not_exist_in_the_filesystem.dummy"); + assert_int_equal(rc, -1); + rc = c_compare_file( "/I_do_not_exist_in_the_filesystem.dummy", check_dst_file); + assert_int_equal(rc, -1); + + rc = system("echo \"hallo42\" > /tmp/check/foo.txt"); + assert_int_equal(rc, 0); + rc = system("echo \"hallo52\" > /tmp/check/bar.txt"); + assert_int_equal(rc, 0); + rc = c_compare_file( check_src_file, check_dst_file ); + assert_int_equal(rc, 0); + + /* Create two 1MB random files */ + rc = system("dd if=/dev/urandom of=/tmp/check/foo.txt bs=1024 count=1024"); + assert_int_equal(rc, 0); + rc = system("dd if=/dev/urandom of=/tmp/check/bar.txt bs=1024 count=1024"); + assert_int_equal(rc, 0); + rc = c_compare_file( check_src_file, check_dst_file ); + assert_int_equal(rc, 0); + + /* Create two 1MB random files with different size */ + rc = system("dd if=/dev/urandom of=/tmp/check/foo.txt bs=1024 count=1024"); + assert_int_equal(rc, 0); + rc = system("dd if=/dev/urandom of=/tmp/check/bar.txt bs=1024 count=1020"); + assert_int_equal(rc, 0); + rc = c_compare_file( check_src_file, check_dst_file ); + assert_int_equal(rc, 0); + + /* compare two big files which are equal */ + rc = c_copy(check_src_file, check_dst_file, 0644); + assert_int_equal(rc, 0); + + rc = c_compare_file( check_src_file, check_dst_file ); + assert_int_equal(rc, 1); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test_setup_teardown(check_c_copy, setup, teardown), + unit_test(check_c_copy_same_file), + unit_test_setup_teardown(check_c_copy_isdir, setup, teardown), + unit_test_setup_teardown(check_c_compare_file, setup, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_jhash.c b/csync/tests/std_tests/check_std_c_jhash.c new file mode 100644 index 000000000..9d8384de9 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_jhash.c @@ -0,0 +1,292 @@ +/* + * Tests are taken form lookup2.c and lookup8.c + * by Bob Jenkins, December 1996, Public Domain. + * + * See http://burtleburtle.net/bob/hash/evahash.html + */ +#include "torture.h" + +#include "std/c_jhash.h" + +#define HASHSTATE 1 +#define HASHLEN 1 +#define MAXPAIR 80 +#define MAXLEN 70 + +static void check_c_jhash_trials(void **state) +{ + uint8_t qa[MAXLEN+1], qb[MAXLEN+2], *a = &qa[0], *b = &qb[1]; + uint32_t c[HASHSTATE], d[HASHSTATE], i, j=0, k, l, m, z; + uint32_t e[HASHSTATE],f[HASHSTATE],g[HASHSTATE],h[HASHSTATE]; + uint32_t x[HASHSTATE],y[HASHSTATE]; + uint32_t hlen; + + (void) state; /* unused */ + + for (hlen=0; hlen < MAXLEN; ++hlen) { + z=0; + for (i=0; i>(8-j)); + c[0] = c_jhash(a, hlen, m); + b[i] ^= ((k+1)<>(8-j)); + d[0] = c_jhash(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l=0; lz) z=k; + if (k==MAXPAIR) { + print_error("Some bit didn't change: "); + print_error("%.8x %.8x %.8x %.8x %.8x %.8x ", + e[0], f[0], g[0], h[0], x[0], y[0]); + print_error("i %d j %d m %d len %d\n",i,j,m,hlen); + } + if (z==MAXPAIR) goto done; + } + } + } + done: + if (z < MAXPAIR) { + assert_true(z < MAXPAIR); + // print_error("%u trials needed, should be less than 40\n", z/2); + } + } +} + +static void check_c_jhash_alignment_problems(void **state) +{ + uint32_t test; + uint8_t buf[MAXLEN+20], *b; + uint32_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country"; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; + uint32_t h,i,j,ref,x,y; + + (void) state; /* unused */ + + test = c_jhash(q, sizeof(q)-1, (uint32_t)0); + assert_true(test == c_jhash(qq+1, sizeof(q)-1, (uint32_t)0)); + assert_true(test == c_jhash(qq+1, sizeof(q)-1, (uint32_t)0)); + assert_true(test == c_jhash(qqq+2, sizeof(q)-1, (uint32_t)0)); + assert_true(test == c_jhash(qqqq+3, sizeof(q)-1, (uint32_t)0)); + for (h=0, b=buf+1; h<8; ++h, ++b) { + for (i=0; i>(8-j)); + c[0] = c_jhash64(a, hlen, m); + b[i] ^= ((k+1)<>(8-j)); + d[0] = c_jhash64(b, hlen, m); + /* check every bit is 1, 0, set, and not set at least once */ + for (l=0; lz) z=k; + if (k==MAXPAIR) { +#if 0 + print_error("Some bit didn't change: "); + print_error("%.8llx %.8llx %.8llx %.8llx %.8llx %.8llx ", + (long long unsigned int) e[0], + (long long unsigned int) f[0], + (long long unsigned int) g[0], + (long long unsigned int) h[0], + (long long unsigned int) x[0], + (long long unsigned int) y[0]); + print_error("i %d j %d m %d len %d\n", + (uint32_t)i,(uint32_t)j,(uint32_t)m,(uint32_t)hlen); +#endif + } + if (z==MAXPAIR) goto done; + } + } + } + done: + if (z < MAXPAIR) { +#if 0 + print_error("%lu trials needed, should be less than 40", z/2); +#endif + assert_true(z < MAXPAIR); + } + } +} + +static void check_c_jhash64_alignment_problems(void **state) +{ + uint8_t buf[MAXLEN+20], *b; + uint64_t len; + uint8_t q[] = "This is the time for all good men to come to the aid of their country"; + uint8_t qq[] = "xThis is the time for all good men to come to the aid of their country"; + uint8_t qqq[] = "xxThis is the time for all good men to come to the aid of their country"; + uint8_t qqqq[] = "xxxThis is the time for all good men to come to the aid of their country"; + uint8_t o[] = "xxxxThis is the time for all good men to come to the aid of their country"; + uint8_t oo[] = "xxxxxThis is the time for all good men to come to the aid of their country"; + uint8_t ooo[] = "xxxxxxThis is the time for all good men to come to the aid of their country"; + uint8_t oooo[] = "xxxxxxxThis is the time for all good men to come to the aid of their country"; + uint64_t h,i,j,ref,t,x,y; + + (void) state; /* unused */ + + h = c_jhash64(q+0, (uint64_t)(sizeof(q)-1), (uint64_t)0); + t = h; + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(qq+1, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(qqq+2, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(qqqq+3, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(o+4, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(oo+5, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(ooo+6, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + h = c_jhash64(oooo+7, (uint64_t)(sizeof(q)-1), (uint64_t)0); + assert_true(t == h); + // , "%.8lx%.8lx\n", (uint32_t)h, (uint32_t)(h>>32)); + for (h=0, b=buf+1; h<8; ++h, ++b) { + for (i=0; i +#include + +#include "torture.h" + +#include "std/c_list.h" + +typedef struct test_s { + int key; + int number; +} test_t; + +/* compare function for sorting */ +static int list_cmp(const void *key, const void *data) { + test_t *a, *b; + + a = (test_t *) key; + b = (test_t *) data; + + if (a->key < b->key) { + return -1; + } else if (a->key > b->key) { + return 1; + } + + return 0; +} + +static void setup_complete_list(void **state) { + c_list_t *list = NULL; + int i = 0; + srand(1); + + for (i = 0; i < 100; i++) { + test_t *testdata = NULL; + + testdata = malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = i; + testdata->number = rand() % 100; + + list = c_list_append(list, (void *) testdata); + assert_non_null(list); + } + + *state = list; +} + +static void setup_random_list(void **state) { + c_list_t *list = NULL; + int i = 0; + srand(1); + + for (i = 0; i < 100; i++) { + test_t *testdata; + + testdata = malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = i; + testdata->number = rand() % 100; + + /* random insert */ + if (testdata->number > 49) { + list = c_list_prepend(list, (void *) testdata); + } else { + list = c_list_append(list, (void *) testdata); + } + + assert_non_null(list); + } + + *state = list; +} + +static void teardown_destroy_list(void **state) { + c_list_t *list = *state; + c_list_t *walk = NULL; + + for (walk = c_list_first(list); walk != NULL; walk = c_list_next(walk)) { + test_t *data; + + data = (test_t *) walk->data; + free(data); + } + c_list_free(list); + *state = NULL; +} + +/* + * Start tests + */ +static void check_c_list_alloc(void **state) +{ + c_list_t *new = NULL; + + (void) state; /* unused */ + + new = c_list_alloc(); + assert_non_null(new); + + free(new); +} + +static void check_c_list_remove_null(void **state) +{ + (void) state; /* unused */ + + assert_null(c_list_remove(NULL, NULL)); +} + +static void check_c_list_append(void **state) +{ + c_list_t *list = *state; + + list = c_list_append(list, (void *) 5); + assert_non_null(list); + + list = c_list_remove(list, (void *) 5); + assert_null(list); +} + +static void check_c_list_prepend(void **state) +{ + c_list_t *list = *state; + + list = c_list_prepend(list, (void *) 5); + assert_non_null(list); + + list = c_list_remove(list, (void *) 5); + assert_null(list); +} + +static void check_c_list_first(void **state) +{ + c_list_t *list = *state; + + c_list_t *first = NULL; + test_t *data = NULL; + + first = c_list_first(list); + assert_non_null(first); + + data = first->data; + assert_int_equal(data->key, 0); +} + +static void check_c_list_last(void **state) +{ + c_list_t *list = *state; + c_list_t *last = NULL; + test_t *data = NULL; + + last = c_list_last(list); + assert_non_null(list); + + data = last->data; + assert_int_equal(data->key, 99); +} + +static void check_c_list_next(void **state) +{ + c_list_t *list = *state; + c_list_t *first = NULL; + c_list_t *next = NULL; + test_t *data = NULL; + + first = c_list_first(list); + assert_non_null(first); + next = c_list_next(first); + assert_non_null(next); + + data = next->data; + assert_int_equal(data->key, 1); +} + +static void check_c_list_prev(void **state) +{ + c_list_t *list = *state; + c_list_t *last = NULL; + c_list_t *prev = NULL; + test_t *data = NULL; + + last = c_list_last(list); + assert_non_null(last); + prev = c_list_prev(last); + assert_non_null(prev); + + data = prev->data; + assert_int_equal(data->key, 98); +} + +static void check_c_list_length(void **state) +{ + c_list_t *list = *state; + unsigned long len = 0; + + len = c_list_length(list); + assert_int_equal(len, 100); +} + +static void check_c_list_position(void **state) +{ + c_list_t *list = *state; + c_list_t *pos = NULL; + test_t *data = NULL; + + pos = c_list_position(list, 50); + assert_non_null(pos); + + data = pos->data; + assert_int_equal(data->key, 50); +} + +static void check_c_list_insert(void **state) +{ + c_list_t *list = *state; + c_list_t *pos = NULL; + test_t *data = NULL; + + data = malloc(sizeof(test_t)); + assert_non_null(data); + + data->key = data->number = 123; + + list = c_list_insert(list, (void *) data, 50); + data = NULL; + + pos = c_list_position(list, 50); + assert_non_null(pos); + + data = pos->data; + assert_int_equal(data->key, 123); +} + +static void check_c_list_find(void **state) +{ + c_list_t *list = *state; + c_list_t *find = NULL; + test_t *data = NULL; + + data = malloc(sizeof(test_t)); + assert_non_null(data); + + data->key = data->number = 123; + + list = c_list_insert(list, (void *) data, 50); + + find = c_list_find(list, data); + assert_memory_equal(data, find->data, sizeof(test_t)); +} + +static void check_c_list_find_custom(void **state) +{ + c_list_t *list = *state; + c_list_t *find = NULL; + test_t *data = NULL; + + data = malloc(sizeof(test_t)); + assert_non_null(data); + + data->key = data->number = 123; + + list = c_list_insert(list, (void *) data, 50); + + find = c_list_find_custom(list, data, list_cmp); + assert_memory_equal(data, find->data, sizeof(test_t)); +} + +static void check_c_list_insert_sorted(void **state) +{ + c_list_t *list = *state; + int i = 0; + c_list_t *walk = NULL; + c_list_t *new = NULL; + test_t *data = NULL; + + /* create the list */ + for (walk = c_list_first(list); walk != NULL; walk = c_list_next(walk)) { + new = c_list_insert_sorted(new, walk->data, list_cmp); + } + + /* check the list */ + for (walk = c_list_first(new); walk != NULL; walk = c_list_next(walk)) { + data = (test_t *) walk->data; + assert_int_equal(data->key, i); + i++; + } + + c_list_free(new); +} + +static void check_c_list_sort(void **state) +{ + c_list_t *list = *state; + int i = 0; + test_t *data = NULL; + c_list_t *walk = NULL; + + /* sort list */ + list = c_list_sort(list, list_cmp); + assert_non_null(list); + + /* check the list */ + for (walk = c_list_first(list); walk != NULL; walk = c_list_next(walk)) { + data = (test_t *) walk->data; + assert_int_equal(data->key, i); + i++; + } +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_list_alloc), + unit_test(check_c_list_remove_null), + unit_test(check_c_list_append), + unit_test(check_c_list_prepend), + unit_test_setup_teardown(check_c_list_first, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_last, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_next, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_prev, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_length, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_position, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_find, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_find_custom, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_insert, setup_complete_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_insert_sorted, setup_random_list, teardown_destroy_list), + unit_test_setup_teardown(check_c_list_sort, setup_random_list, teardown_destroy_list), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_path.c b/csync/tests/std_tests/check_std_c_path.c new file mode 100644 index 000000000..82c7f56a1 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_path.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include + +#include "torture.h" + +#include "std/c_path.h" + +static void check_c_basename(void **state) +{ + char *bname; + + (void) state; /* unused */ + + bname = c_basename("/usr/lib"); + assert_string_equal(bname, "lib"); + free(bname); + + bname = c_basename("/usr//"); + assert_string_equal(bname, "usr"); + free(bname); + + bname = c_basename("usr"); + assert_string_equal(bname, "usr"); + free(bname); + + bname = c_basename("///"); + assert_string_equal(bname, "/"); + free(bname); + + bname = c_basename("/"); + assert_string_equal(bname, "/"); + free(bname); + + bname = c_basename("."); + assert_string_equal(bname, "."); + free(bname); + + bname = c_basename(".."); + assert_string_equal(bname, ".."); + free(bname); + + bname = c_basename(""); + assert_string_equal(bname, "."); + free(bname); + + bname = c_basename(NULL); + assert_string_equal(bname, "."); + free(bname); +} + +static void check_c_basename_uri(void **state) +{ + char *bname = NULL; + + (void) state; /* unused */ + + bname = c_basename("smb://server/share/dir/"); + assert_string_equal(bname, "dir"); + free(bname); +} + +static void check_c_dirname(void **state) +{ + char *dname; + + (void) state; /* unused */ + + dname = c_dirname("/usr/lib"); + assert_string_equal(dname, "/usr"); + free(dname); + + dname = c_dirname("/usr//"); + assert_string_equal(dname, "/"); + free(dname); + + dname = c_dirname("usr"); + assert_string_equal(dname, "."); + free(dname); + + dname = c_dirname("/"); + assert_string_equal(dname, "/"); + free(dname); + + dname = c_dirname("///"); + assert_string_equal(dname, "/"); + free(dname); + + dname = c_dirname("."); + assert_string_equal(dname, "."); + free(dname); + + dname = c_dirname(".."); + assert_string_equal(dname, "."); + free(dname); + + dname = c_dirname(NULL); + assert_string_equal(dname, "."); + free(dname); +} + +static void check_c_dirname_uri(void **state) +{ + char *dname; + + (void) state; /* unused */ + + dname = c_dirname("smb://server/share/dir"); + assert_string_equal(dname, "smb://server/share"); + free(dname); +} + +static void check_c_tmpname(void **state) +{ + char tmpl[22]={0}; + char prev[22]={0}; + char *tmp; + int i = 0; + + (void) state; /* unused */ + + srand((unsigned)time(NULL)); + + /* remember the last random value and compare the new one against. + * They may never be the same. */ + for(i = 0; i < 100; i++){ + strcpy(tmpl, "check_tmpname.XXXXXX"); + tmp = c_tmpname(tmpl); + assert_non_null(tmp); + + if (strlen(prev)) { + assert_string_not_equal(tmp, prev); + } + strcpy(prev, tmp); + SAFE_FREE(tmp); + } +} + +static void check_c_parse_uri(void **state) +{ + const char *test_scheme = "git+ssh"; + const char *test_user = "gladiac"; + const char *test_passwd = "secret"; + const char *test_host = "git.csync.org"; + const char *test_path = "/srv/git/csync.git"; + + char *scheme = NULL; + char *user = NULL; + char *passwd = NULL; + char *host = NULL; + unsigned int port; + char *path = NULL; + char uri[1024] = {0}; + int rc; + + (void) state; /* unused */ + + rc = snprintf(uri, sizeof(uri), "%s://%s:%s@%s:22%s", + test_scheme, test_user, test_passwd, test_host, test_path); + assert_true(rc); + + rc = c_parse_uri(uri, &scheme, &user, &passwd, &host, &port, &path); + assert_int_equal(rc, 0); + + assert_string_equal(test_scheme, scheme); + assert_string_equal(test_user, user); + assert_string_equal(test_passwd, passwd); + assert_string_equal(test_host, host); + assert_int_equal(port, 22); + assert_string_equal(test_path, path); + + free(scheme); + free(user); + free(passwd); + free(host); + free(path); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_basename), + unit_test(check_c_basename_uri), + unit_test(check_c_dirname), + unit_test(check_c_dirname_uri), + unit_test(check_c_parse_uri), + unit_test(check_c_tmpname), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_rbtree.c b/csync/tests/std_tests/check_std_c_rbtree.c new file mode 100644 index 000000000..9e13ddee2 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_rbtree.c @@ -0,0 +1,367 @@ +#include +#include + +#include "torture.h" + +#include "std/c_alloc.h" +#include "std/c_rbtree.h" + +typedef struct test_s { + int key; + int number; +} test_t; + +static int data_cmp(const void *key, const void *data) { + test_t *a, *b; + + a = (test_t *) key; + b = (test_t *) data; + + if (a->key < b->key) { + return -1; + } else if (a->key > b->key) { + return 1; + } + + return 0; +} + +static int key_cmp(const void *key, const void *data) { + int a; + test_t *b; + + a = POINTER_TO_INT(key); + b = (test_t *) data; + + if (a < b->key) { + return -1; + } else if (a > b->key) { + return 1; + } + + return 0; +} + +static int visitor(void *obj, void *data) { + test_t *a; + test_t *b; + + a = (test_t *) obj; + b = (test_t *) data; + + if (a->key == b->key) { + a->number = 42; + } + + return 0; +} + +static void destructor(void *data) { + test_t *freedata = NULL; + + freedata = (test_t *) data; + SAFE_FREE(freedata); +} + +static void setup(void **state) { + c_rbtree_t *tree = NULL; + int rc; + + rc = c_rbtree_create(&tree, key_cmp, data_cmp); + assert_int_equal(rc, 0); + + *state = tree; +} + +static void setup_complete_tree(void **state) { + c_rbtree_t *tree = NULL; + int i = 0; + int rc; + + rc = c_rbtree_create(&tree, key_cmp, data_cmp); + assert_int_equal(rc, 0); + + for (i = 0; i < 100; i++) { + test_t *testdata = NULL; + + testdata = c_malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = i; + + rc = c_rbtree_insert(tree, (void *) testdata); + assert_int_equal(rc, 0); + } + + *state = tree; +} + +static void teardown(void **state) { + c_rbtree_t *tree = *state; + + c_rbtree_destroy(tree, destructor); + c_rbtree_free(tree); + + *state = NULL; +} + +static void check_c_rbtree_create_free(void **state) +{ + c_rbtree_t *tree = NULL; + int rc; + + (void) state; /* unused */ + + rc = c_rbtree_create(&tree, key_cmp, data_cmp); + assert_int_equal(rc, 0); + assert_int_equal(tree->size, 0); + + rc = c_rbtree_free(tree); + assert_int_equal(rc, 0); +} + +static void check_c_rbtree_create_null(void **state) +{ + c_rbtree_t *tree = NULL; + int rc; + + (void) state; /* unused */ + + rc = c_rbtree_create(NULL, key_cmp, data_cmp); + assert_int_equal(rc, -1); + + rc = c_rbtree_create(&tree, NULL, data_cmp); + assert_int_equal(rc, -1); + + rc = c_rbtree_create(&tree, key_cmp, NULL); + assert_int_equal(rc, -1); +} + +static void check_c_rbtree_free_null(void **state) +{ + int rc; + + (void) state; /* unused */ + + rc = c_rbtree_free(NULL); + assert_int_equal(rc, -1); +} + +static void check_c_rbtree_insert_delete(void **state) +{ + c_rbtree_t *tree = NULL; + c_rbnode_t *node = NULL; + test_t *testdata = NULL; + int rc; + + (void) state; /* unused */ + + rc = c_rbtree_create(&tree, key_cmp, data_cmp); + assert_int_equal(rc, 0); + + testdata = malloc(sizeof(test_t)); + testdata->key = 42; + + rc = c_rbtree_insert(tree, (void *) testdata); + assert_int_equal(rc, 0); + + node = c_rbtree_head(tree); + assert_non_null(node); + + testdata = c_rbtree_node_data(node); + SAFE_FREE(testdata); + rc = c_rbtree_node_delete(node); + assert_int_equal(rc, 0); + + c_rbtree_free(tree); +} + +static void check_c_rbtree_insert_random(void **state) +{ + c_rbtree_t *tree = *state; + int i = 0, rc; + + for (i = 0; i < 100; i++) { + test_t *testdata = NULL; + + testdata = malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = i; + + rc = c_rbtree_insert(tree, testdata); + assert_int_equal(rc, 0); + + } + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); +} + +static void check_c_rbtree_insert_duplicate(void **state) +{ + c_rbtree_t *tree = *state; + test_t *testdata; + int rc; + + testdata = malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = 42; + + rc = c_rbtree_insert(tree, (void *) testdata); + assert_int_equal(rc, 0); + + /* add again */ + testdata = malloc(sizeof(test_t)); + assert_non_null(testdata); + + testdata->key = 42; + + /* check for duplicate */ + rc = c_rbtree_insert(tree, (void *) testdata); + assert_int_equal(rc, 1); + + free(testdata); +} + +static void check_c_rbtree_find(void **state) +{ + c_rbtree_t *tree = *state; + int rc, i = 42; + c_rbnode_t *node; + test_t *testdata; + + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); + + /* find the node with the key 42 */ + node = c_rbtree_find(tree, (void *) &i); + assert_non_null(node); + + testdata = (test_t *) c_rbtree_node_data(node); + assert_int_equal(testdata->key, 42); +} + +static void check_c_rbtree_delete(void **state) +{ + c_rbtree_t *tree = *state; + int rc, i = 42; + c_rbnode_t *node = NULL; + test_t *freedata = NULL; + + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); + + node = c_rbtree_find(tree, (void *) &i); + assert_non_null(node); + + freedata = (test_t *) c_rbtree_node_data(node); + free(freedata); + rc = c_rbtree_node_delete(node); + assert_int_equal(rc, 0); + + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); +} + +static void check_c_rbtree_walk(void **state) +{ + c_rbtree_t *tree = *state; + int rc, i = 42; + test_t *testdata; + c_rbnode_t *node; + + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); + + testdata = (test_t *) c_malloc(sizeof(test_t)); + testdata->key = 42; + + rc = c_rbtree_walk(tree, testdata, visitor); + assert_int_equal(rc, 0); + + /* find the node with the key 42 */ + node = c_rbtree_find(tree, (void *) &i); + assert_non_null(node); + free(testdata); + + testdata = (test_t *) c_rbtree_node_data(node); + assert_int_equal(testdata->number, 42); +} + +static void check_c_rbtree_walk_null(void **state) +{ + c_rbtree_t *tree = *state; + int rc, i = 42; + test_t *testdata; + c_rbnode_t *node; + + rc = c_rbtree_check_sanity(tree); + assert_int_equal(rc, 0); + + testdata = (test_t *) malloc(sizeof(test_t)); + testdata->key = 42; + + rc = c_rbtree_walk(NULL, testdata, visitor); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + rc = c_rbtree_walk(tree, NULL, visitor); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + rc = c_rbtree_walk(tree, testdata, NULL); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* find the node with the key 42 */ + node = c_rbtree_find(tree, (void *) &i); + assert_non_null(node); + + free(testdata); +} + +static void check_c_rbtree_dup(void **state) +{ + c_rbtree_t *tree = *state; + c_rbtree_t *duptree = NULL; + int rc = -1; + + duptree = c_rbtree_dup(tree); + assert_non_null(duptree); + + rc = c_rbtree_check_sanity(duptree); + assert_int_equal(rc, 0); + + c_rbtree_free(duptree); +} + +#if 0 +static void check_c_rbtree_x) +{ + int rc = -1; + + rc = c_rbtree_check_sanity(tree); + fail_unless(rc == 0, "c_rbtree_check_sanity failed with return code %d", rc); +} +#endif + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_rbtree_create_free), + unit_test(check_c_rbtree_create_null), + unit_test(check_c_rbtree_free_null), + unit_test(check_c_rbtree_insert_delete), + unit_test_setup_teardown(check_c_rbtree_insert_random, setup, teardown), + unit_test_setup_teardown(check_c_rbtree_insert_duplicate, setup, teardown), + unit_test_setup_teardown(check_c_rbtree_find, setup_complete_tree, teardown), + unit_test_setup_teardown(check_c_rbtree_delete, setup_complete_tree, teardown), + unit_test_setup_teardown(check_c_rbtree_walk, setup_complete_tree, teardown), + unit_test_setup_teardown(check_c_rbtree_walk_null, setup_complete_tree, teardown), + unit_test_setup_teardown(check_c_rbtree_dup, setup_complete_tree, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_str.c b/csync/tests/std_tests/check_std_c_str.c new file mode 100644 index 000000000..8e008021f --- /dev/null +++ b/csync/tests/std_tests/check_std_c_str.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include + +#include "torture.h" + +#include "std/c_string.h" + +static void check_c_streq_equal(void **state) +{ + (void) state; /* unused */ + + assert_true(c_streq("test", "test")); +} + +static void check_c_streq_not_equal(void **state) +{ + (void) state; /* unused */ + + assert_false(c_streq("test", "test2")); +} + +static void check_c_streq_null(void **state) +{ + (void) state; /* unused */ + + assert_false(c_streq(NULL, "test")); + assert_false(c_streq("test", NULL)); + assert_false(c_streq(NULL, NULL)); +} + +static void check_c_strlist_new(void **state) +{ + c_strlist_t *strlist = NULL; + + (void) state; /* unused */ + + strlist = c_strlist_new(42); + assert_non_null(strlist); + assert_int_equal(strlist->size, 42); + assert_int_equal(strlist->count, 0); + + c_strlist_destroy(strlist); +} + +static void check_c_strlist_add(void **state) +{ + int rc; + size_t i = 0; + c_strlist_t *strlist = NULL; + + (void) state; /* unused */ + + strlist = c_strlist_new(42); + assert_non_null(strlist); + assert_int_equal(strlist->size, 42); + assert_int_equal(strlist->count, 0); + + for (i = 0; i < strlist->size; i++) { + rc = c_strlist_add(strlist, (char *) "foobar"); + assert_int_equal(rc, 0); + } + + assert_int_equal(strlist->count, 42); + assert_string_equal(strlist->vector[0], "foobar"); + assert_string_equal(strlist->vector[41], "foobar"); + + c_strlist_destroy(strlist); +} + +static void check_c_strlist_expand(void **state) +{ + c_strlist_t *strlist; + size_t i = 0; + int rc; + + (void) state; /* unused */ + + strlist = c_strlist_new(42); + assert_non_null(strlist); + assert_int_equal(strlist->size, 42); + assert_int_equal(strlist->count, 0); + + strlist = c_strlist_expand(strlist, 84); + assert_non_null(strlist); + assert_int_equal(strlist->size, 84); + + for (i = 0; i < strlist->size; i++) { + rc = c_strlist_add(strlist, (char *) "foobar"); + assert_int_equal(rc, 0); + } + + c_strlist_destroy(strlist); +} + +static void check_c_strreplace(void **state) +{ + char *str = strdup("/home/%(USER)"); + + (void) state; /* unused */ + + str = c_strreplace(str, "%(USER)", "csync"); + assert_string_equal(str, "/home/csync"); + + free(str); +} + +static void check_c_lowercase(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_lowercase("LoWeRcASE"); + assert_string_equal(str, "lowercase"); + + free(str); +} + +static void check_c_lowercase_empty(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_lowercase(""); + assert_string_equal(str, ""); + + free(str); +} + +static void check_c_lowercase_null(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_lowercase(NULL); + assert_null(str); +} + +static void check_c_uppercase(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_uppercase("upperCASE"); + assert_string_equal(str, "UPPERCASE"); + + free(str); +} + +static void check_c_uppercase_empty(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_uppercase(""); + assert_string_equal(str, ""); + + free(str); +} + +static void check_c_uppercase_null(void **state) +{ + char *str; + + (void) state; /* unused */ + + str = c_uppercase(NULL); + assert_null(str); +} + + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_streq_equal), + unit_test(check_c_streq_not_equal), + unit_test(check_c_streq_null), + unit_test(check_c_strlist_new), + unit_test(check_c_strlist_add), + unit_test(check_c_strlist_expand), + unit_test(check_c_strreplace), + unit_test(check_c_lowercase), + unit_test(check_c_lowercase_empty), + unit_test(check_c_lowercase_null), + unit_test(check_c_uppercase), + unit_test(check_c_uppercase_empty), + unit_test(check_c_uppercase_null), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/std_tests/check_std_c_time.c b/csync/tests/std_tests/check_std_c_time.c new file mode 100644 index 000000000..7249a6ce7 --- /dev/null +++ b/csync/tests/std_tests/check_std_c_time.c @@ -0,0 +1,82 @@ +#include +#include + +#include "torture.h" + +#include "csync_time.h" +#include "std/c_time.h" + +static void check_c_tspecdiff(void **state) +{ + struct timespec start, finish, diff; + + (void) state; /* unused */ + + csync_gettime(&start); + csync_gettime(&finish); + + diff = c_tspecdiff(finish, start); + + assert_int_equal(diff.tv_sec, 0); + assert_true(diff.tv_nsec >= 0); +} + +static void check_c_tspecdiff_five(void **state) +{ + struct timespec start, finish, diff; + + (void) state; /* unused */ + + csync_gettime(&start); + sleep(5); + csync_gettime(&finish); + + diff = c_tspecdiff(finish, start); + + assert_int_equal(diff.tv_sec, 5); + assert_true(diff.tv_nsec > 0); +} + +static void check_c_secdiff(void **state) +{ + struct timespec start, finish; + double diff; + + (void) state; /* unused */ + + csync_gettime(&start); + csync_gettime(&finish); + + diff = c_secdiff(finish, start); + + assert_true(diff >= 0.00 && diff < 1.00); +} + +static void check_c_secdiff_three(void **state) +{ + struct timespec start, finish; + double diff; + + (void) state; /* unused */ + + csync_gettime(&start); + sleep(3); + csync_gettime(&finish); + + diff = c_secdiff(finish, start); + + assert_true(diff > 3.00 && diff < 4.00); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_c_tspecdiff), + unit_test(check_c_tspecdiff_five), + unit_test(check_c_secdiff), + unit_test(check_c_secdiff_three), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/torture.c b/csync/tests/torture.c new file mode 100644 index 000000000..fd8e10a52 --- /dev/null +++ b/csync/tests/torture.c @@ -0,0 +1,22 @@ +#include "config_csync.h" + +#include "torture.h" + +static int verbosity; + +int torture_csync_verbosity(void) +{ + return verbosity; +} + +int main(int argc, char **argv) +{ + struct argument_s arguments; + + arguments.verbose = 0; + torture_cmdline_parse(argc, argv, &arguments); + verbosity = arguments.verbose; + + return torture_run_tests(); +} + diff --git a/csync/tests/torture.h b/csync/tests/torture.h new file mode 100644 index 000000000..fd8715fcf --- /dev/null +++ b/csync/tests/torture.h @@ -0,0 +1,25 @@ +#ifndef _TORTURE_H +#define _TORTURE_H + +#include +#include +#include + +#include + +/* Used by main to communicate with parse_opt. */ +struct argument_s { + char *args[2]; + int verbose; +}; + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); + +int torture_csync_verbosity(void); + +/* + * This function must be defined in every unit test file. + */ +int torture_run_tests(void); + +#endif /* _TORTURE_H */ diff --git a/csync/tests/valgrind-csync.supp b/csync/tests/valgrind-csync.supp new file mode 100644 index 000000000..c4d10e40c --- /dev/null +++ b/csync/tests/valgrind-csync.supp @@ -0,0 +1,61 @@ +### SQLITE3 + +{ + Invalid read of size 4 in sqlite3 + Memcheck:Addr4 + obj:/usr/lib*/libsqlite3.so* + fun:sqlite3_close + fun:csync_statedb_close +} + +{ + sqlite3 jump on uninit value + Memcheck:Cond + obj:/usr/lib*/libsqlite3.so* + fun:sqlite3_vmprintf + fun:sqlite3_mprintf +} + +{ + sqlite3 uninit value + Memcheck:Value8 + obj:/usr/lib*/libsqlite3.so* + fun:sqlite3_vmprintf + fun:sqlite3_mprintf +} + +### GLIBC + +{ + glibc dlopen leak + Memcheck:Leak + fun:calloc + obj:/lib*/libdl-2.*.so + fun:dlopen +} + +{ + glibc dlopen leak + Memcheck:Leak + fun:calloc + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 + fun:csync_vio_init +} + +{ + glibc dlerror leak + Memcheck:Leak + fun:realloc + fun:vasprintf + fun:asprintf + fun:dlerror +} + +{ + glibc fnmatch invalid read + Memcheck:Addr1 + fun:__GI_strlen + fun:fnmatch@@GLIBC_* + fun:csync_fnmatch +} diff --git a/csync/tests/vio_tests/check_vio.c b/csync/tests/vio_tests/check_vio.c new file mode 100644 index 000000000..21df68fd2 --- /dev/null +++ b/csync/tests/vio_tests/check_vio.c @@ -0,0 +1,555 @@ +#include +#include +#include +#include +#include +#include + +#include "torture.h" + +#include "csync_private.h" +#include "vio/csync_vio.h" + +#define CSYNC_TEST_DIR "/tmp/csync_test/" +#define CSYNC_TEST_DIRS "/tmp/csync_test/this/is/a/mkdirs/test" +#define CSYNC_TEST_FILE "/tmp/csync_test/file.txt" + +#define MKDIR_MASK (S_IRWXU |S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) + +#define WD_BUFFER_SIZE 255 + +static char wd_buffer[WD_BUFFER_SIZE]; + +static void setup(void **state) +{ + CSYNC *csync; + int rc; + + assert_non_null(getcwd(wd_buffer, WD_BUFFER_SIZE)); + + rc = system("rm -rf /tmp/csync_test"); + assert_int_equal(rc, 0); + + rc = csync_create(&csync, "/tmp/csync1", "/tmp/csync2"); + assert_int_equal(rc, 0); + + csync->replica = LOCAL_REPLICA; + + *state = csync; +} + +static void setup_dir(void **state) { + int rc; + mbchar_t *dir = c_utf8_to_locale(CSYNC_TEST_DIR); + + setup(state); + + rc = _tmkdir(dir, MKDIR_MASK); + c_free_locale_string(dir); + assert_int_equal(rc, 0); + + assert_non_null(getcwd(wd_buffer, WD_BUFFER_SIZE)); + + rc = chdir(CSYNC_TEST_DIR); + assert_int_equal(rc, 0); +} + +static void setup_file(void **state) { + int rc; + + setup_dir(state); + + rc = system("echo \"This is a test\" > /tmp/csync_test/file.txt"); + assert_int_equal(rc, 0); +} + +static void teardown(void **state) { + CSYNC *csync = *state; + int rc; + + rc = csync_destroy(csync); + assert_int_equal(rc, 0); + + rc = chdir(wd_buffer); + assert_int_equal(rc, 0); + + rc = system("rm -rf /tmp/csync_test/"); + assert_int_equal(rc, 0); + + *state = NULL; +} + + +/* + * Test directory function + */ + +static void check_csync_vio_mkdir(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + int rc; + mbchar_t *dir = c_utf8_to_locale(CSYNC_TEST_DIR); + + rc = csync_vio_mkdir(csync, CSYNC_TEST_DIR, MKDIR_MASK); + assert_int_equal(rc, 0); + + rc = _tstat(dir, &sb); + assert_int_equal(rc, 0); + + _trmdir(dir); + c_free_locale_string(dir); +} + +static void check_csync_vio_mkdirs(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + int rc; + mbchar_t *dir = c_utf8_to_locale(CSYNC_TEST_DIR); + + rc = csync_vio_mkdirs(csync, CSYNC_TEST_DIRS, MKDIR_MASK); + assert_int_equal(rc, 0); + + rc = _tstat(dir, &sb); + assert_int_equal(rc, 0); + + _trmdir(dir); + c_free_locale_string(dir); +} + +static void check_csync_vio_mkdirs_some_exist(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + mbchar_t *this_dir = c_utf8_to_locale("/tmp/csync_test/this"); + mbchar_t *stat_dir = c_utf8_to_locale(CSYNC_TEST_DIRS); + int rc; + + rc = _tmkdir(this_dir, MKDIR_MASK); + assert_int_equal(rc, 0); + rc = csync_vio_mkdirs(csync, CSYNC_TEST_DIRS, MKDIR_MASK); + assert_int_equal(rc, 0); + + rc = _tstat(stat_dir, &sb); + assert_int_equal(rc, 0); + + _trmdir(stat_dir); + c_free_locale_string(this_dir); + c_free_locale_string(stat_dir); +} + +static void check_csync_vio_rmdir(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + int rc; + + rc = csync_vio_mkdir(csync, CSYNC_TEST_DIR, MKDIR_MASK); + assert_int_equal(rc, 0); + + rc = lstat(CSYNC_TEST_DIR, &sb); + assert_int_equal(rc, 0); + + rc = csync_vio_rmdir(csync, CSYNC_TEST_DIR); + assert_int_equal(rc, 0); + + rc = lstat(CSYNC_TEST_DIR, &sb); + assert_int_equal(rc, -1); +} + +static void check_csync_vio_opendir(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *dh; + int rc; + + dh = csync_vio_opendir(csync, CSYNC_TEST_DIR); + assert_non_null(dh); + + rc = csync_vio_closedir(csync, dh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_opendir_perm(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *dh; + int rc; + mbchar_t *dir = c_utf8_to_locale(CSYNC_TEST_DIR); + + assert_non_null(dir); + + rc = _tmkdir(dir, (S_IWUSR|S_IXUSR)); + assert_int_equal(rc, 0); + + dh = csync_vio_opendir(csync, CSYNC_TEST_DIR); + assert_null(dh); + assert_int_equal(errno, EACCES); + + _tchmod(dir, MKDIR_MASK); + c_free_locale_string(dir); +} + +static void check_csync_vio_closedir_null(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_vio_closedir(csync, NULL); + assert_int_equal(rc, -1); +} + +static void check_csync_vio_readdir(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *dh; + csync_vio_file_stat_t *dirent; + int rc; + + dh = csync_vio_opendir(csync, CSYNC_TEST_DIR); + assert_non_null(dh); + + dirent = csync_vio_readdir(csync, dh); + assert_non_null(dirent); + + csync_vio_file_stat_destroy(dirent); + rc = csync_vio_closedir(csync, dh); + assert_int_equal(rc, 0); +} + +/* + * Test file functions (open, read, write, close ...) + */ + +static void check_csync_vio_close_null(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_vio_close(csync, NULL); + assert_int_equal(rc, -1); +} + +static void check_csync_vio_creat_close(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh; + int rc; + + fh = csync_vio_creat(csync, CSYNC_TEST_FILE, 0644); + assert_non_null(fh); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_open_close(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh; + int rc; + + fh = csync_vio_open(csync, CSYNC_TEST_FILE, O_RDONLY, 0644); + assert_non_null(fh); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_read_null(void **state) +{ + CSYNC *csync = *state; + char test[16] = {0}; + int rc; + + rc = csync_vio_read(csync, NULL, test, 10); + assert_int_equal(rc, -1); +} + +static void check_csync_vio_read(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh; + char test[16] = {0}; + int rc; + + fh = csync_vio_open(csync, CSYNC_TEST_FILE, O_RDONLY, 0644); + assert_non_null(fh); + + rc = csync_vio_read(csync, fh, test, 14); + assert_int_equal(rc, 14); + + assert_string_equal(test, "This is a test"); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_read_0(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh = NULL; + char test[16] = {0}; + int rc; + + fh = csync_vio_open(csync, CSYNC_TEST_FILE, O_RDONLY, 0644); + assert_non_null(fh); + + rc = csync_vio_read(csync, fh, test, 0); + assert_int_equal(rc, 0); + + assert_true(test[0] == '\0'); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_write_null(void **state) +{ + CSYNC *csync = *state; + char test[16] = {0}; + int rc; + + rc = csync_vio_write(csync, NULL, test, 10); + assert_int_equal(rc, -1); +} + +static void check_csync_vio_write(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh; + char str[] = "This is a test"; + char test[16] = {0}; + int rc; + + fh = csync_vio_creat(csync, CSYNC_TEST_FILE, 0644); + assert_non_null(fh); + + rc = csync_vio_write(csync, fh, str, sizeof(str)); + assert_int_equal(rc, sizeof(str)); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); + + fh = csync_vio_open(csync, CSYNC_TEST_FILE, O_RDONLY, 0644); + assert_non_null(fh); + + rc = csync_vio_read(csync, fh, test, 14); + assert_int_equal(rc, 14); + + assert_string_equal(test, "This is a test"); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +static void check_csync_vio_lseek(void **state) +{ + CSYNC *csync = *state; + csync_vio_method_handle_t *fh; + char test[16] = {0}; + int rc; + + fh = csync_vio_open(csync, CSYNC_TEST_FILE, O_RDONLY, 0644); + assert_non_null(fh); + + rc = csync_vio_lseek(csync, fh, 10, SEEK_SET); + assert_int_equal(rc, 10); + + rc = csync_vio_read(csync, fh, test, 4); + assert_int_equal(rc, 4); + + assert_string_equal(test, "test"); + + rc = csync_vio_close(csync, fh); + assert_int_equal(rc, 0); +} + +/* + * Test for general functions (stat, chmod, chown, ...) + */ + +static void check_csync_vio_stat_dir(void **state) +{ + CSYNC *csync = *state; + csync_vio_file_stat_t *fs; + int rc; + + fs = csync_vio_file_stat_new(); + assert_non_null(fs); + + rc = csync_vio_stat(csync, CSYNC_TEST_DIR, fs); + assert_int_equal(rc, 0); + + assert_string_equal(fs->name, "csync_test"); + assert_int_equal(fs->type, CSYNC_VIO_FILE_TYPE_DIRECTORY); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_vio_stat_file(void **state) +{ + CSYNC *csync = *state; + csync_vio_file_stat_t *fs; + int rc; + + fs = csync_vio_file_stat_new(); + assert_non_null(fs); + + rc = csync_vio_stat(csync, CSYNC_TEST_FILE, fs); + assert_int_equal(rc, 0); + + assert_string_equal(fs->name, "file.txt"); + assert_int_equal(fs->type, CSYNC_VIO_FILE_TYPE_REGULAR); + + csync_vio_file_stat_destroy(fs); +} + +static void check_csync_vio_rename_dir(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + int rc; + + mbchar_t *dir = c_utf8_to_locale("test"); + mbchar_t *dir2 = c_utf8_to_locale("test2"); + + assert_non_null(dir); + assert_non_null(dir2); + + rc = _tmkdir(dir, MKDIR_MASK); + assert_int_equal(rc, 0); + + rc = csync_vio_rename(csync, "test", "test2"); + assert_int_equal(rc, 0); + + + rc = _tstat(dir2, &sb); + assert_int_equal(rc, 0); + + c_free_locale_string(dir); + c_free_locale_string(dir2); +} + +static void check_csync_vio_rename_file(void **state) +{ + CSYNC *csync = *state; + mbchar_t *file = c_utf8_to_locale(CSYNC_TEST_DIR "file2.txt"); + csync_stat_t sb; + int rc; + + rc = csync_vio_rename(csync, CSYNC_TEST_FILE, CSYNC_TEST_DIR "file2.txt"); + assert_int_equal(rc, 0); + + rc = _tstat(file, &sb); + assert_int_equal(rc, 0); + + c_free_locale_string(file); +} + +static void check_csync_vio_unlink(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + mbchar_t *file = c_utf8_to_locale(CSYNC_TEST_FILE); + int rc; + + rc = csync_vio_unlink(csync, CSYNC_TEST_FILE); + assert_int_equal(rc, 0); + + rc = _tstat(file, &sb); + assert_int_equal(rc, -1); + + c_free_locale_string(file); +} + +static void check_csync_vio_chmod(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_vio_chmod(csync, CSYNC_TEST_FILE, 0777); + assert_int_equal(rc, 0); +} + +#ifndef _WIN32 +static void check_csync_vio_chown(void **state) +{ + CSYNC *csync = *state; + int rc; + + rc = csync_vio_chown(csync, CSYNC_TEST_FILE, getuid(), getgid()); + assert_int_equal(rc, 0); +} +#endif + +static void check_csync_vio_utimes(void **state) +{ + CSYNC *csync = *state; + csync_stat_t sb; + struct timeval times[2]; + long modtime = 0; + mbchar_t *file = c_utf8_to_locale(CSYNC_TEST_FILE); + int rc; + + rc = _tstat(file, &sb); + assert_int_equal(rc, 0); + modtime = sb.st_mtime + 10; + + times[0].tv_sec = modtime; + times[0].tv_usec = 0; + + times[1].tv_sec = modtime; + times[1].tv_usec = 0; + + rc = csync_vio_utimes(csync, CSYNC_TEST_FILE, times); + assert_int_equal(rc, 0); + + rc = _tstat(file, &sb); + assert_int_equal(rc, 0); + + assert_int_equal(modtime, sb.st_mtime); + + c_free_locale_string(file); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + + unit_test_setup_teardown(check_csync_vio_mkdir, setup, teardown), + unit_test_setup_teardown(check_csync_vio_mkdirs, setup, teardown), + unit_test_setup_teardown(check_csync_vio_mkdirs_some_exist, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_rmdir, setup, teardown), + unit_test_setup_teardown(check_csync_vio_opendir, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_opendir_perm, setup, teardown), + unit_test(check_csync_vio_closedir_null), + unit_test_setup_teardown(check_csync_vio_readdir, setup_dir, teardown), + + unit_test_setup_teardown(check_csync_vio_close_null, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_creat_close, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_open_close, setup_file, teardown), + unit_test(check_csync_vio_read_null), + unit_test_setup_teardown(check_csync_vio_read, setup_file, teardown), + unit_test_setup_teardown(check_csync_vio_read_0, setup_file, teardown), + unit_test(check_csync_vio_write_null), + unit_test_setup_teardown(check_csync_vio_write, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_lseek, setup_file, teardown), + + unit_test_setup_teardown(check_csync_vio_stat_dir, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_stat_file, setup_file, teardown), + + unit_test_setup_teardown(check_csync_vio_rename_dir, setup_dir, teardown), + unit_test_setup_teardown(check_csync_vio_rename_file, setup_file, teardown), + unit_test_setup_teardown(check_csync_vio_unlink, setup_file, teardown), + unit_test_setup_teardown(check_csync_vio_chmod, setup_file, teardown), +#ifndef _WIN32 + unit_test_setup_teardown(check_csync_vio_chown, setup_file, teardown), +#endif + unit_test_setup_teardown(check_csync_vio_utimes, setup_file, teardown), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/vio_tests/check_vio_file_stat.c b/csync/tests/vio_tests/check_vio_file_stat.c new file mode 100644 index 000000000..2c8f65fbb --- /dev/null +++ b/csync/tests/vio_tests/check_vio_file_stat.c @@ -0,0 +1,26 @@ +#include "torture.h" + +#include "vio/csync_vio_file_stat.h" + +static void check_csync_vio_file_stat_new(void **state) +{ + csync_vio_file_stat_t *tstat; + + (void) state; /* unused */ + + tstat = csync_vio_file_stat_new(); + assert_non_null(tstat); + + csync_vio_file_stat_destroy(tstat); +} + + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_csync_vio_file_stat_new), + }; + + return run_tests(tests); +} + diff --git a/csync/tests/vio_tests/check_vio_handle.c b/csync/tests/vio_tests/check_vio_handle.c new file mode 100644 index 000000000..61908bac4 --- /dev/null +++ b/csync/tests/vio_tests/check_vio_handle.c @@ -0,0 +1,55 @@ +#include + +#include "torture.h" + +#include "vio/csync_vio_handle.h" +#include "vio/csync_vio_handle_private.h" + +static void check_csync_vio_handle_new(void **state) +{ + int *number; + csync_vio_handle_t *handle; + + (void) state; /* unused */ + + number = malloc(sizeof(int)); + *number = 42; + + handle = csync_vio_handle_new("/tmp", (csync_vio_method_handle_t *) number); + assert_non_null(handle); + assert_string_equal(handle->uri, "/tmp"); + + free(handle->method_handle); + + csync_vio_handle_destroy(handle); +} + +static void check_csync_vio_handle_new_null(void **state) +{ + int *number; + csync_vio_handle_t *handle; + + (void) state; /* unused */ + + number = malloc(sizeof(int)); + *number = 42; + + handle = csync_vio_handle_new(NULL, (csync_vio_method_handle_t *) number); + assert_null(handle); + + handle = csync_vio_handle_new((char *) "/tmp", NULL); + assert_null(handle); + + free(number); +} + +int torture_run_tests(void) +{ + const UnitTest tests[] = { + unit_test(check_csync_vio_handle_new), + unit_test(check_csync_vio_handle_new_null), + }; + + return run_tests(tests); +} + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1636e698d..29bbcdfac 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,7 +55,6 @@ set(3rdparty_INC ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qtsingleapplication ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/QProgressIndicator ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/fancylineedit - ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/csync ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/qjson ) @@ -172,22 +171,10 @@ IF (NOT APPLE) ) ENDIF(NOT APPLE) -IF( DEFINED CSYNC_BUILD_PATH ) -SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbf.a) -ELSE() -FIND_LIBRARY(HTTPBF_LIBRARY NAMES httpbf HINTS $ENV{CSYNC_DIR}) -ENDIF() - -IF( DEFINED CSYNC_BUILD_PATH ) -SET(HTTPBF_LIBRARY ${CSYNC_BUILD_PATH}/src/httpbf/libhttpbf.a) -ELSE() -FIND_LIBRARY(HTTPBF_LIBRARY NAMES httpbflib HINTS $ENV{CSYNC_DIR}) -ENDIF() - list(APPEND libsync_LINK_TARGETS ${QT_LIBRARIES} - ${CSYNC_LIBRARY} - ${HTTPBF_LIBRARY} + ocsync + httpbf ${OS_SPECIFIC_LINK_LIBRARIES} ) @@ -320,7 +307,7 @@ IF( APPLE ) ENDIF() # csync is required. -include_directories(${CSYNC_INCLUDE_DIR}/csync ${CSYNC_INCLUDE_DIR} ${CSYNC_INCLUDE_DIR}/httpbf/src ${CSYNC_BUILD_PATH}/src) +include_directories(../csync/src ../csync/src/httpbf/src ${CMAKE_CURRENT_BINARY_DIR}/../csync ${CMAKE_CURRENT_BINARY_DIR}/../csync/src ) include_directories(${3rdparty_INC}) qt_wrap_cpp(mirallMoc ${mirall_HEADERS}) @@ -406,7 +393,6 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES ) target_link_libraries( ${APPLICATION_EXECUTABLE} ${QT_LIBRARIES} ) target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} ) -target_link_libraries( ${APPLICATION_EXECUTABLE} ${CSYNC_LIBRARY} ) target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} ) install(TARGETS ${APPLICATION_EXECUTABLE} @@ -440,7 +426,6 @@ qt5_use_modules(${owncloudcmd_NAME} Network Sql) set_target_properties(${owncloudcmd_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_OUTPUT_DIRECTORY} ) target_link_libraries(${owncloudcmd_NAME} ${synclib_NAME}) -target_link_libraries(${owncloudcmd_NAME} ${CSYNC_LIBRARY}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/mirall) if(BUILD_OWNCLOUD_OSX_BUNDLE) install(TARGETS ${owncloudcmd_NAME} DESTINATION ${OWNCLOUD_OSX_BUNDLE}/Contents/MacOS) diff --git a/src/mirall/syncjournaldb.cpp b/src/mirall/syncjournaldb.cpp index 2679ba285..27eadb1b1 100644 --- a/src/mirall/syncjournaldb.cpp +++ b/src/mirall/syncjournaldb.cpp @@ -17,12 +17,13 @@ #include #include -#include #include #include "syncjournaldb.h" #include "syncjournalfilerecord.h" +#include "../../csync/src/std/c_jhash.h" + #define QSQLITE "QSQLITE" namespace Mirall {