From 2f4a3792bbfdb4e05cf7468059b3f6308f5ed91f Mon Sep 17 00:00:00 2001 From: LibArchive Upstream Date: Mon, 19 Dec 2011 18:30:59 -0500 Subject: libarchive 3.0.1-r3950 (reduced) Extract upstream libarchive using the following shell code. url=https://libarchive.googlecode.com/svn/trunk v=3.0.1 r=3950 paths=" CMakeLists.txt COPYING CTestConfig.cmake build/cmake build/pkgconfig build/utils build/version libarchive/*.* " date=$(svn log -q -c$r $url | sed -n "/^r/ {s/[^|]*|[^|]*|//;p;}") svn export -r$r $url libarchive-$v-r$r && mkdir libarchive-$v-r$r-reduced && (cd libarchive-$v-r$r && tar c $paths) | (cd libarchive-$v-r$r-reduced && tar x) echo "r$r date: $date" --- CMakeLists.txt | 1106 +++ COPYING | 60 + CTestConfig.cmake | 11 + build/cmake/AddTest28.cmake | 107 + build/cmake/CheckFileOffsetBits.c | 14 + build/cmake/CheckFileOffsetBits.cmake | 44 + build/cmake/CheckFuncs.cmake | 49 + build/cmake/CheckFuncs_stub.c.in | 16 + build/cmake/CheckHeaderDirent.cmake | 32 + build/cmake/CheckStructMember.cmake | 43 + build/cmake/CheckTypeExists.cmake | 42 + build/cmake/FindLZMA.cmake | 48 + build/cmake/config.h.in | 1101 +++ build/pkgconfig/libarchive.pc.in | 11 + build/utils/gen_archive_string_composition_h.sh | 418 + build/version | 1 + libarchive/CMakeLists.txt | 172 + libarchive/archive.h | 832 ++ libarchive/archive_acl.c | 1264 +++ libarchive/archive_acl_private.h | 87 + libarchive/archive_check_magic.c | 174 + libarchive/archive_crc32.h | 78 + libarchive/archive_crypto.c | 1427 ++++ libarchive/archive_crypto_private.h | 376 + libarchive/archive_endian.h | 162 + libarchive/archive_entry.3 | 148 + libarchive/archive_entry.c | 1651 ++++ libarchive/archive_entry.h | 620 ++ libarchive/archive_entry_acl.3 | 233 + libarchive/archive_entry_copy_bhfi.c | 75 + libarchive/archive_entry_copy_stat.c | 79 + libarchive/archive_entry_link_resolver.c | 444 ++ libarchive/archive_entry_linkify.3 | 224 + libarchive/archive_entry_locale.h | 88 + libarchive/archive_entry_paths.3 | 151 + libarchive/archive_entry_perms.3 | 207 + libarchive/archive_entry_private.h | 176 + libarchive/archive_entry_sparse.c | 156 + libarchive/archive_entry_stat.3 | 272 + libarchive/archive_entry_stat.c | 118 + libarchive/archive_entry_strmode.c | 87 + libarchive/archive_entry_time.3 | 127 + libarchive/archive_entry_xattr.c | 158 + libarchive/archive_options.c | 164 + libarchive/archive_options_private.h | 47 + libarchive/archive_platform.h | 165 + libarchive/archive_ppmd7.c | 1164 +++ libarchive/archive_ppmd7_private.h | 119 + libarchive/archive_ppmd_private.h | 158 + libarchive/archive_private.h | 150 + libarchive/archive_rb.c | 701 ++ libarchive/archive_rb.h | 100 + libarchive/archive_read.3 | 250 + libarchive/archive_read.c | 1358 ++++ libarchive/archive_read_data.3 | 142 + libarchive/archive_read_data_into_fd.c | 139 + libarchive/archive_read_disk.3 | 315 + libarchive/archive_read_disk_entry_from_file.c | 1005 +++ libarchive/archive_read_disk_posix.c | 2309 ++++++ libarchive/archive_read_disk_private.h | 71 + libarchive/archive_read_disk_set_standard_lookup.c | 311 + libarchive/archive_read_disk_windows.c | 1983 +++++ libarchive/archive_read_extract.3 | 135 + libarchive/archive_read_extract.c | 183 + libarchive/archive_read_filter.3 | 127 + libarchive/archive_read_format.3 | 175 + libarchive/archive_read_free.3 | 91 + libarchive/archive_read_header.3 | 89 + libarchive/archive_read_new.3 | 57 + libarchive/archive_read_open.3 | 231 + libarchive/archive_read_open_fd.c | 181 + libarchive/archive_read_open_file.c | 177 + libarchive/archive_read_open_filename.c | 509 ++ libarchive/archive_read_open_memory.c | 186 + libarchive/archive_read_private.h | 210 + libarchive/archive_read_set_options.3 | 207 + libarchive/archive_read_set_options.c | 148 + libarchive/archive_read_support_filter_all.c | 75 + libarchive/archive_read_support_filter_bzip2.c | 370 + libarchive/archive_read_support_filter_compress.c | 454 ++ libarchive/archive_read_support_filter_gzip.c | 476 ++ libarchive/archive_read_support_filter_none.c | 52 + libarchive/archive_read_support_filter_program.c | 476 ++ libarchive/archive_read_support_filter_rpm.c | 288 + libarchive/archive_read_support_filter_uu.c | 680 ++ libarchive/archive_read_support_filter_xz.c | 985 +++ libarchive/archive_read_support_format_7zip.c | 3687 +++++++++ libarchive/archive_read_support_format_all.c | 87 + libarchive/archive_read_support_format_ar.c | 625 ++ libarchive/archive_read_support_format_by_code.c | 74 + libarchive/archive_read_support_format_cab.c | 3315 ++++++++ libarchive/archive_read_support_format_cpio.c | 1048 +++ libarchive/archive_read_support_format_empty.c | 93 + libarchive/archive_read_support_format_iso9660.c | 3213 ++++++++ libarchive/archive_read_support_format_lha.c | 2745 +++++++ libarchive/archive_read_support_format_mtree.c | 1744 +++++ libarchive/archive_read_support_format_rar.c | 2574 +++++++ libarchive/archive_read_support_format_raw.c | 187 + libarchive/archive_read_support_format_tar.c | 2692 +++++++ libarchive/archive_read_support_format_xar.c | 3324 ++++++++ libarchive/archive_read_support_format_zip.c | 1233 +++ libarchive/archive_string.c | 4228 ++++++++++ libarchive/archive_string.h | 237 + libarchive/archive_string_composition.h | 1351 ++++ libarchive/archive_string_sprintf.c | 186 + libarchive/archive_util.3 | 222 + libarchive/archive_util.c | 465 ++ libarchive/archive_virtual.c | 147 + libarchive/archive_windows.c | 794 ++ libarchive/archive_windows.h | 267 + libarchive/archive_write.3 | 261 + libarchive/archive_write.c | 709 ++ libarchive/archive_write_add_filter_bzip2.c | 335 + libarchive/archive_write_add_filter_compress.c | 445 ++ libarchive/archive_write_add_filter_gzip.c | 356 + libarchive/archive_write_add_filter_none.c | 43 + libarchive/archive_write_add_filter_program.c | 327 + libarchive/archive_write_add_filter_xz.c | 502 ++ libarchive/archive_write_blocksize.3 | 112 + libarchive/archive_write_data.3 | 60 + libarchive/archive_write_disk.3 | 401 + libarchive/archive_write_disk_posix.c | 2844 +++++++ libarchive/archive_write_disk_private.h | 38 + .../archive_write_disk_set_standard_lookup.c | 262 + libarchive/archive_write_disk_windows.c | 2525 ++++++ libarchive/archive_write_filter.3 | 98 + libarchive/archive_write_finish_entry.3 | 74 + libarchive/archive_write_format.3 | 98 + libarchive/archive_write_free.3 | 81 + libarchive/archive_write_header.3 | 71 + libarchive/archive_write_new.3 | 56 + libarchive/archive_write_open.3 | 233 + libarchive/archive_write_open_fd.c | 144 + libarchive/archive_write_open_file.c | 109 + libarchive/archive_write_open_filename.c | 248 + libarchive/archive_write_open_memory.c | 114 + libarchive/archive_write_private.h | 136 + libarchive/archive_write_set_format.c | 76 + libarchive/archive_write_set_format_7zip.c | 2275 ++++++ libarchive/archive_write_set_format_ar.c | 564 ++ libarchive/archive_write_set_format_by_name.c | 86 + libarchive/archive_write_set_format_cpio.c | 464 ++ libarchive/archive_write_set_format_cpio_newc.c | 420 + libarchive/archive_write_set_format_gnutar.c | 684 ++ libarchive/archive_write_set_format_iso9660.c | 8114 ++++++++++++++++++++ libarchive/archive_write_set_format_mtree.c | 1468 ++++ libarchive/archive_write_set_format_pax.c | 1833 +++++ libarchive/archive_write_set_format_shar.c | 642 ++ libarchive/archive_write_set_format_ustar.c | 692 ++ libarchive/archive_write_set_format_xar.c | 3168 ++++++++ libarchive/archive_write_set_format_zip.c | 784 ++ libarchive/archive_write_set_options.3 | 437 ++ libarchive/archive_write_set_options.c | 124 + libarchive/config_freebsd.h | 160 + libarchive/cpio.5 | 325 + libarchive/filter_fork.c | 161 + libarchive/filter_fork.h | 41 + libarchive/filter_fork_windows.c | 113 + libarchive/libarchive-formats.5 | 361 + libarchive/libarchive.3 | 256 + libarchive/libarchive_changes.3 | 341 + libarchive/libarchive_internals.3 | 365 + libarchive/mtree.5 | 269 + libarchive/tar.5 | 947 +++ 164 files changed, 100292 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 COPYING create mode 100644 CTestConfig.cmake create mode 100644 build/cmake/AddTest28.cmake create mode 100644 build/cmake/CheckFileOffsetBits.c create mode 100644 build/cmake/CheckFileOffsetBits.cmake create mode 100644 build/cmake/CheckFuncs.cmake create mode 100644 build/cmake/CheckFuncs_stub.c.in create mode 100644 build/cmake/CheckHeaderDirent.cmake create mode 100644 build/cmake/CheckStructMember.cmake create mode 100644 build/cmake/CheckTypeExists.cmake create mode 100644 build/cmake/FindLZMA.cmake create mode 100644 build/cmake/config.h.in create mode 100644 build/pkgconfig/libarchive.pc.in create mode 100644 build/utils/gen_archive_string_composition_h.sh create mode 100644 build/version create mode 100644 libarchive/CMakeLists.txt create mode 100644 libarchive/archive.h create mode 100644 libarchive/archive_acl.c create mode 100644 libarchive/archive_acl_private.h create mode 100644 libarchive/archive_check_magic.c create mode 100644 libarchive/archive_crc32.h create mode 100644 libarchive/archive_crypto.c create mode 100644 libarchive/archive_crypto_private.h create mode 100644 libarchive/archive_endian.h create mode 100644 libarchive/archive_entry.3 create mode 100644 libarchive/archive_entry.c create mode 100644 libarchive/archive_entry.h create mode 100644 libarchive/archive_entry_acl.3 create mode 100644 libarchive/archive_entry_copy_bhfi.c create mode 100644 libarchive/archive_entry_copy_stat.c create mode 100644 libarchive/archive_entry_link_resolver.c create mode 100644 libarchive/archive_entry_linkify.3 create mode 100644 libarchive/archive_entry_locale.h create mode 100644 libarchive/archive_entry_paths.3 create mode 100644 libarchive/archive_entry_perms.3 create mode 100644 libarchive/archive_entry_private.h create mode 100644 libarchive/archive_entry_sparse.c create mode 100644 libarchive/archive_entry_stat.3 create mode 100644 libarchive/archive_entry_stat.c create mode 100644 libarchive/archive_entry_strmode.c create mode 100644 libarchive/archive_entry_time.3 create mode 100644 libarchive/archive_entry_xattr.c create mode 100644 libarchive/archive_options.c create mode 100644 libarchive/archive_options_private.h create mode 100644 libarchive/archive_platform.h create mode 100644 libarchive/archive_ppmd7.c create mode 100644 libarchive/archive_ppmd7_private.h create mode 100644 libarchive/archive_ppmd_private.h create mode 100644 libarchive/archive_private.h create mode 100644 libarchive/archive_rb.c create mode 100644 libarchive/archive_rb.h create mode 100644 libarchive/archive_read.3 create mode 100644 libarchive/archive_read.c create mode 100644 libarchive/archive_read_data.3 create mode 100644 libarchive/archive_read_data_into_fd.c create mode 100644 libarchive/archive_read_disk.3 create mode 100644 libarchive/archive_read_disk_entry_from_file.c create mode 100644 libarchive/archive_read_disk_posix.c create mode 100644 libarchive/archive_read_disk_private.h create mode 100644 libarchive/archive_read_disk_set_standard_lookup.c create mode 100644 libarchive/archive_read_disk_windows.c create mode 100644 libarchive/archive_read_extract.3 create mode 100644 libarchive/archive_read_extract.c create mode 100644 libarchive/archive_read_filter.3 create mode 100644 libarchive/archive_read_format.3 create mode 100644 libarchive/archive_read_free.3 create mode 100644 libarchive/archive_read_header.3 create mode 100644 libarchive/archive_read_new.3 create mode 100644 libarchive/archive_read_open.3 create mode 100644 libarchive/archive_read_open_fd.c create mode 100644 libarchive/archive_read_open_file.c create mode 100644 libarchive/archive_read_open_filename.c create mode 100644 libarchive/archive_read_open_memory.c create mode 100644 libarchive/archive_read_private.h create mode 100644 libarchive/archive_read_set_options.3 create mode 100644 libarchive/archive_read_set_options.c create mode 100644 libarchive/archive_read_support_filter_all.c create mode 100644 libarchive/archive_read_support_filter_bzip2.c create mode 100644 libarchive/archive_read_support_filter_compress.c create mode 100644 libarchive/archive_read_support_filter_gzip.c create mode 100644 libarchive/archive_read_support_filter_none.c create mode 100644 libarchive/archive_read_support_filter_program.c create mode 100644 libarchive/archive_read_support_filter_rpm.c create mode 100644 libarchive/archive_read_support_filter_uu.c create mode 100644 libarchive/archive_read_support_filter_xz.c create mode 100644 libarchive/archive_read_support_format_7zip.c create mode 100644 libarchive/archive_read_support_format_all.c create mode 100644 libarchive/archive_read_support_format_ar.c create mode 100644 libarchive/archive_read_support_format_by_code.c create mode 100644 libarchive/archive_read_support_format_cab.c create mode 100644 libarchive/archive_read_support_format_cpio.c create mode 100644 libarchive/archive_read_support_format_empty.c create mode 100644 libarchive/archive_read_support_format_iso9660.c create mode 100644 libarchive/archive_read_support_format_lha.c create mode 100644 libarchive/archive_read_support_format_mtree.c create mode 100644 libarchive/archive_read_support_format_rar.c create mode 100644 libarchive/archive_read_support_format_raw.c create mode 100644 libarchive/archive_read_support_format_tar.c create mode 100644 libarchive/archive_read_support_format_xar.c create mode 100644 libarchive/archive_read_support_format_zip.c create mode 100644 libarchive/archive_string.c create mode 100644 libarchive/archive_string.h create mode 100644 libarchive/archive_string_composition.h create mode 100644 libarchive/archive_string_sprintf.c create mode 100644 libarchive/archive_util.3 create mode 100644 libarchive/archive_util.c create mode 100644 libarchive/archive_virtual.c create mode 100644 libarchive/archive_windows.c create mode 100644 libarchive/archive_windows.h create mode 100644 libarchive/archive_write.3 create mode 100644 libarchive/archive_write.c create mode 100644 libarchive/archive_write_add_filter_bzip2.c create mode 100644 libarchive/archive_write_add_filter_compress.c create mode 100644 libarchive/archive_write_add_filter_gzip.c create mode 100644 libarchive/archive_write_add_filter_none.c create mode 100644 libarchive/archive_write_add_filter_program.c create mode 100644 libarchive/archive_write_add_filter_xz.c create mode 100644 libarchive/archive_write_blocksize.3 create mode 100644 libarchive/archive_write_data.3 create mode 100644 libarchive/archive_write_disk.3 create mode 100644 libarchive/archive_write_disk_posix.c create mode 100644 libarchive/archive_write_disk_private.h create mode 100644 libarchive/archive_write_disk_set_standard_lookup.c create mode 100644 libarchive/archive_write_disk_windows.c create mode 100644 libarchive/archive_write_filter.3 create mode 100644 libarchive/archive_write_finish_entry.3 create mode 100644 libarchive/archive_write_format.3 create mode 100644 libarchive/archive_write_free.3 create mode 100644 libarchive/archive_write_header.3 create mode 100644 libarchive/archive_write_new.3 create mode 100644 libarchive/archive_write_open.3 create mode 100644 libarchive/archive_write_open_fd.c create mode 100644 libarchive/archive_write_open_file.c create mode 100644 libarchive/archive_write_open_filename.c create mode 100644 libarchive/archive_write_open_memory.c create mode 100644 libarchive/archive_write_private.h create mode 100644 libarchive/archive_write_set_format.c create mode 100644 libarchive/archive_write_set_format_7zip.c create mode 100644 libarchive/archive_write_set_format_ar.c create mode 100644 libarchive/archive_write_set_format_by_name.c create mode 100644 libarchive/archive_write_set_format_cpio.c create mode 100644 libarchive/archive_write_set_format_cpio_newc.c create mode 100644 libarchive/archive_write_set_format_gnutar.c create mode 100644 libarchive/archive_write_set_format_iso9660.c create mode 100644 libarchive/archive_write_set_format_mtree.c create mode 100644 libarchive/archive_write_set_format_pax.c create mode 100644 libarchive/archive_write_set_format_shar.c create mode 100644 libarchive/archive_write_set_format_ustar.c create mode 100644 libarchive/archive_write_set_format_xar.c create mode 100644 libarchive/archive_write_set_format_zip.c create mode 100644 libarchive/archive_write_set_options.3 create mode 100644 libarchive/archive_write_set_options.c create mode 100644 libarchive/config_freebsd.h create mode 100644 libarchive/cpio.5 create mode 100644 libarchive/filter_fork.c create mode 100644 libarchive/filter_fork.h create mode 100644 libarchive/filter_fork_windows.c create mode 100644 libarchive/libarchive-formats.5 create mode 100644 libarchive/libarchive.3 create mode 100644 libarchive/libarchive_changes.3 create mode 100644 libarchive/libarchive_internals.3 create mode 100644 libarchive/mtree.5 create mode 100644 libarchive/tar.5 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..aed3c0a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,1106 @@ +# +# +PROJECT(libarchive C) +# +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.3 FATAL_ERROR) +SET(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake") +if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${libarchive_BINARY_DIR}/bin) +endif() +SET(CMAKE_BUILD_TYPE "Debug") + +# On MacOS, prefer MacPorts libraries to system libraries. +# I haven't come up with a compelling argument for this to be conditional. +list(APPEND CMAKE_PREFIX_PATH /opt/local) + +# +# Version - read from 'version' file. +# +FILE(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/build/version _version) +STRING(REGEX REPLACE + "^([0-9])[0-9][0-9][0-9][0-9][0-9][0-9][a-z]?$" "\\1" _major ${_version}) +STRING(REGEX REPLACE + "^[0-9]([0-9][0-9][0-9])[0-9][0-9][0-9][a-z]?$" "\\1" _minor ${_version}) +STRING(REGEX REPLACE + "^[0-9][0-9][0-9][0-9]([0-9][0-9][0-9])[a-z]?$" "\\1" _revision ${_version}) +STRING(REGEX REPLACE + "^[0-9][0-9][0-9][0-9][0-9][0-9][0-9]([a-z]?)$" "\\1" _quality ${_version}) +SET(_version_number ${_major}${_minor}${_revision}) +STRING(REGEX REPLACE "[0]*([^0]*[0-9])$" "\\1" _trimmed_minor ${_minor}) +STRING(REGEX REPLACE "[0]*([^0]*[0-9])$" "\\1" _trimmed_revision ${_revision}) +# +SET(VERSION "${_major}.${_trimmed_minor}.${_trimmed_revision}${_quality}") +SET(BSDCPIO_VERSION_STRING "${VERSION}") +SET(BSDTAR_VERSION_STRING "${VERSION}") +SET(LIBARCHIVE_VERSION_NUMBER "${_version_number}") +SET(LIBARCHIVE_VERSION_STRING "${VERSION}") + +# INTERFACE_VERSION increments with every release +# libarchive 2.7 == interface version 9 = 2 + 7 +# libarchive 2.8 == interface version 10 = 2 + 8 +# libarchive 3.0 == interface version 11 +# libarchive 3.x == interface version 11 + x +math(EXPR INTERFACE_VERSION "11 + ${_minor}") + +# Set SOVERSION == Interface version +# ?? Should there be more here ?? +SET(SOVERSION "${INTERFACE_VERSION}") + +# Especially for early development, we want to be a little +# aggressive about diagnosing build problems; this can get +# relaxed somewhat in final shipping versions. +IF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") + ADD_DEFINITIONS(-Wall -Werror) + SET(CMAKE_REQUIRED_FLAGS "-Wall -Werror") +ENDIF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") + +# Enable CTest/CDash support +include(CTest) + +# Provide ADD_TEST_28 macro to approximate CMake 2.8 ADD_TEST(NAME). +# TODO: Require CMake 2.8 and drop this workaround (perhaps late 2010). +INCLUDE(AddTest28) + +OPTION(ENABLE_NETTLE "Enable use of Nettle" ON) +OPTION(ENABLE_OPENSSL "Enable use of OpenSSL" ON) +OPTION(ENABLE_TAR "Enable tar building" ON) +OPTION(ENABLE_TAR_SHARED "Enable dynamic build of tar" FALSE) +OPTION(ENABLE_CPIO "Enable cpio building" ON) +OPTION(ENABLE_CPIO_SHARED "Enable dynamic build of cpio" FALSE) +OPTION(ENABLE_XATTR "Enable extended attribute support" ON) +OPTION(ENABLE_ACL "Enable ACL support" ON) +OPTION(ENABLE_ICONV "Enable iconv support" ON) +OPTION(ENABLE_TEST "Enable unit and regression tests" ON) + +IF(ENABLE_TEST) + ENABLE_TESTING() +ENDIF(ENABLE_TEST) + +IF(WIN32) + SET(_WIN32_WINNT 0x0500 CACHE INTERNAL "Setting _WIN32_WINNT to 0x0500 for Windows 2000 APIs") + SET(WINVER 0x0500 CACHE INTERNAL "Setting WINVER to 0x0500 for Windows 2000 APIs") +ENDIF(WIN32) + +# +INCLUDE(CheckCSourceCompiles) +INCLUDE(CheckCSourceRuns) +INCLUDE(CheckFileOffsetBits) +INCLUDE(CheckFuncs) +INCLUDE(CheckHeaderDirent) +INCLUDE(CheckIncludeFile) +INCLUDE(CheckIncludeFiles) +INCLUDE(CheckLibraryExists) +INCLUDE(CheckStructMember) +INCLUDE(CheckSymbolExists) +INCLUDE(CheckTypeExists) +INCLUDE(CheckTypeSize) + +# +# Generate list.h +# +MACRO (GENERATE_LIST_H _listfile _cmlist __list_sources) + SET(_argv ${ARGV}) + # Remove _listfile and _cmlist from _argv + LIST(REMOVE_AT _argv 0 1) + IF (NOT EXISTS "${_listfile}" OR + ${_cmlist} IS_NEWER_THAN "${_listfile}") + + MESSAGE(STATUS "Generating ${_listfile}") + FILE(WRITE ${_listfile} "") + FOREACH (testfile ${_argv}) + IF (testfile MATCHES "^test_[^/]+[.]c$") + FILE(STRINGS ${testfile} testvar REGEX "^DEFINE_TEST") + FOREACH (deftest ${testvar}) + FILE(APPEND ${_listfile} "${deftest}\n") + ENDFOREACH (deftest) + ENDIF (testfile MATCHES "^test_[^/]+[.]c$") + ENDFOREACH (testfile) + + ENDIF (NOT EXISTS "${_listfile}" OR + ${_cmlist} IS_NEWER_THAN "${_listfile}") +ENDMACRO (GENERATE_LIST_H) +# +# Generate installation rules for man pages. +# +MACRO (INSTALL_MAN __mans) + FOREACH (_man ${ARGV}) + STRING(REGEX REPLACE "^.+[.]([1-9])" "\\1" _mansect ${_man}) + INSTALL(FILES ${_man} DESTINATION "share/man/man${_mansect}") + ENDFOREACH (_man) +ENDMACRO (INSTALL_MAN __mans) + +# +# Check compress/decompress libraries +# +IF(WIN32 AND NOT CMAKE_CL_64 AND NOT CYGWIN) + # GnuWin32 is only for Win32, not Win64. + SET(__GNUWIN32PATH "C:/Program Files/GnuWin32") +ENDIF(WIN32 AND NOT CMAKE_CL_64 AND NOT CYGWIN) +IF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}") + # You have to add a path availabel DLL file into PATH environment variable. + # Maybe DLL path is "C:/Program Files/GnuWin32/bin". + # The zlib and the bzip2 Setup program have installed programs and DLLs into + # "C:/Program Files/GnuWin32" by default. + # This is convenience setting for Windows. + SET(CMAKE_PREFIX_PATH ${__GNUWIN32PATH} $(CMAKE_PREFIX_PATH)) + # + # If you didn't use Setup program or installed into nonstandard path, + # cmake cannot find out your zlib or bzip2 libraries and include files, + # you should execute cmake with -DCMAKE_PREFIX_PATH option. + # e.g. + # cmake -DCMAKE_PREFIX_PATH= + # + # If compiling error occured in zconf.h, You may need patch to zconf.h. + #--- zconf.h.orig 2005-07-21 00:40:26.000000000 + #+++ zconf.h 2009-01-19 11:39:10.093750000 + #@@ -286,7 +286,7 @@ + # + # #if 1 /* HAVE_UNISTD_H -- this line is updated by ./configure */ + # # include /* for off_t */ + #-# include /* for SEEK_* and off_t */ + #+# include /* for SEEK_* and off_t */ + # # ifdef VMS + # # include /* for off_t */ + # # endif +ENDIF(DEFINED __GNUWIN32PATH AND EXISTS "${__GNUWIN32PATH}") + +SET(ADDITIONAL_LIBS "") +# +# Find ZLIB +# +FIND_PACKAGE(ZLIB) +IF(ZLIB_FOUND) + SET(HAVE_LIBZ 1) + SET(HAVE_ZLIB_H 1) + INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${ZLIB_LIBRARIES}) + IF(WIN32 AND NOT CYGWIN) + SET(CMAKE_REQUIRED_INCLUDES ${ZLIB_INCLUDE_DIR}) + SET(CMAKE_REQUIRED_LIBRARIES ${ZLIB_LIBRARIES}) + CHECK_C_SOURCE_Runs( + "#ifndef ZLIB_WINAPI\n#define ZLIB_WINAPI\n#endif\n#include \nint main() {uLong f = zlibCompileFlags(); return (f&(1U<<10))?0:-1; }" + ZLIB_WINAPI) + ENDIF(WIN32 AND NOT CYGWIN) +ENDIF(ZLIB_FOUND) +MARK_AS_ADVANCED(CLEAR ZLIB_INCLUDE_DIR) +MARK_AS_ADVANCED(CLEAR ZLIB_LIBRARY) +# +# Find BZip2 +# +FIND_PACKAGE(BZip2) +IF(BZIP2_FOUND) + SET(HAVE_LIBBZ2 1) + SET(HAVE_BZLIB_H 1) + INCLUDE_DIRECTORIES(${BZIP2_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${BZIP2_LIBRARIES}) +ENDIF(BZIP2_FOUND) +MARK_AS_ADVANCED(CLEAR BZIP2_INCLUDE_DIR) +MARK_AS_ADVANCED(CLEAR BZIP2_LIBRARY) +# +# Find LZMA +# +FIND_PACKAGE(LZMA) +IF(LZMA_FOUND) + SET(HAVE_LIBLZMA 1) + SET(HAVE_LZMA_H 1) + INCLUDE_DIRECTORIES(${LZMA_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${LZMA_LIBRARIES}) +ELSEIF(LZMADEC_FOUND) + SET(HAVE_LIBLZMADEC 1) + SET(HAVE_LZMADEC_H 1) + INCLUDE_DIRECTORIES(${LZMADEC_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${LZMADEC_LIBRARIES}) +ENDIF(LZMA_FOUND) + +# +# Check headers +# +CHECK_HEADER_DIRENT() + +SET(INCLUDES "") +MACRO (LA_CHECK_INCLUDE_FILE header var) + CHECK_INCLUDE_FILES("${INCLUDES};${header}" ${var}) + IF (${var}) + SET(INCLUDES ${INCLUDES} ${header}) + ENDIF (${var}) +ENDMACRO (LA_CHECK_INCLUDE_FILE) + +# Some FreeBSD headers assume sys/types.h was already included. +LA_CHECK_INCLUDE_FILE("sys/types.h" HAVE_SYS_TYPES_H) + +# Alphabetize the rest unless there's a compelling reason +LA_CHECK_INCLUDE_FILE("acl/libacl.h" HAVE_ACL_LIBACL_H) +LA_CHECK_INCLUDE_FILE("ctype.h" HAVE_CTYPE_H) +LA_CHECK_INCLUDE_FILE("copyfile.h" HAVE_COPYFILE_H) +LA_CHECK_INCLUDE_FILE("direct.h" HAVE_DIRECT_H) +LA_CHECK_INCLUDE_FILE("dlfcn.h" HAVE_DLFCN_H) +LA_CHECK_INCLUDE_FILE("errno.h" HAVE_ERRNO_H) +LA_CHECK_INCLUDE_FILE("ext2fs/ext2_fs.h" HAVE_EXT2FS_EXT2_FS_H) + +CHECK_C_SOURCE_COMPILES("#include +#include +int main(void) { return EXT2_IOC_GETFLAGS; }" HAVE_WORKING_EXT2_IOC_GETFLAGS) + +LA_CHECK_INCLUDE_FILE("fcntl.h" HAVE_FCNTL_H) +LA_CHECK_INCLUDE_FILE("grp.h" HAVE_GRP_H) +LA_CHECK_INCLUDE_FILE("inttypes.h" HAVE_INTTYPES_H) +LA_CHECK_INCLUDE_FILE("io.h" HAVE_IO_H) +LA_CHECK_INCLUDE_FILE("langinfo.h" HAVE_LANGINFO_H) +LA_CHECK_INCLUDE_FILE("limits.h" HAVE_LIMITS_H) +LA_CHECK_INCLUDE_FILE("linux/fiemap.h" HAVE_LINUX_FIEMAP_H) +LA_CHECK_INCLUDE_FILE("linux/fs.h" HAVE_LINUX_FS_H) +LA_CHECK_INCLUDE_FILE("linux/magic.h" HAVE_LINUX_MAGIC_H) +LA_CHECK_INCLUDE_FILE("locale.h" HAVE_LOCALE_H) +LA_CHECK_INCLUDE_FILE("memory.h" HAVE_MEMORY_H) +LA_CHECK_INCLUDE_FILE("paths.h" HAVE_PATHS_H) +LA_CHECK_INCLUDE_FILE("poll.h" HAVE_POLL_H) +LA_CHECK_INCLUDE_FILE("process.h" HAVE_PROCESS_H) +LA_CHECK_INCLUDE_FILE("pwd.h" HAVE_PWD_H) +LA_CHECK_INCLUDE_FILE("regex.h" HAVE_REGEX_H) +LA_CHECK_INCLUDE_FILE("signal.h" HAVE_SIGNAL_H) +LA_CHECK_INCLUDE_FILE("stdarg.h" HAVE_STDARG_H) +LA_CHECK_INCLUDE_FILE("stdint.h" HAVE_STDINT_H) +LA_CHECK_INCLUDE_FILE("stdlib.h" HAVE_STDLIB_H) +LA_CHECK_INCLUDE_FILE("string.h" HAVE_STRING_H) +LA_CHECK_INCLUDE_FILE("strings.h" HAVE_STRINGS_H) +LA_CHECK_INCLUDE_FILE("sys/acl.h" HAVE_SYS_ACL_H) +LA_CHECK_INCLUDE_FILE("sys/cdefs.h" HAVE_SYS_CDEFS_H) +LA_CHECK_INCLUDE_FILE("sys/ioctl.h" HAVE_SYS_IOCTL_H) +LA_CHECK_INCLUDE_FILE("sys/mkdev.h" HAVE_SYS_MKDEV_H) +LA_CHECK_INCLUDE_FILE("sys/mount.h" HAVE_SYS_MOUNT_H) +LA_CHECK_INCLUDE_FILE("sys/param.h" HAVE_SYS_PARAM_H) +LA_CHECK_INCLUDE_FILE("sys/poll.h" HAVE_SYS_POLL_H) +LA_CHECK_INCLUDE_FILE("sys/select.h" HAVE_SYS_SELECT_H) +LA_CHECK_INCLUDE_FILE("sys/stat.h" HAVE_SYS_STAT_H) +LA_CHECK_INCLUDE_FILE("sys/statfs.h" HAVE_SYS_STATFS_H) +LA_CHECK_INCLUDE_FILE("sys/statvfs.h" HAVE_SYS_STATVFS_H) +LA_CHECK_INCLUDE_FILE("sys/time.h" HAVE_SYS_TIME_H) +LA_CHECK_INCLUDE_FILE("sys/utime.h" HAVE_SYS_UTIME_H) +LA_CHECK_INCLUDE_FILE("sys/utsname.h" HAVE_SYS_UTSNAME_H) +LA_CHECK_INCLUDE_FILE("sys/vfs.h" HAVE_SYS_VFS_H) +LA_CHECK_INCLUDE_FILE("sys/wait.h" HAVE_SYS_WAIT_H) +LA_CHECK_INCLUDE_FILE("time.h" HAVE_TIME_H) +LA_CHECK_INCLUDE_FILE("unistd.h" HAVE_UNISTD_H) +LA_CHECK_INCLUDE_FILE("utime.h" HAVE_UTIME_H) +LA_CHECK_INCLUDE_FILE("wchar.h" HAVE_WCHAR_H) +LA_CHECK_INCLUDE_FILE("wctype.h" HAVE_WCTYPE_H) +LA_CHECK_INCLUDE_FILE("windows.h" HAVE_WINDOWS_H) +# Following files need windwos.h, so we should test it after windows.h test. +LA_CHECK_INCLUDE_FILE("wincrypt.h" HAVE_WINCRYPT_H) +LA_CHECK_INCLUDE_FILE("winioctl.h" HAVE_WINIOCTL_H) + +# +# Check whether use of __EXTENSIONS__ is safe. +# We need some macro such as _GNU_SOURCE to use extension functions. +# +SET(_INCLUDE_FILES) +FOREACH (it ${_HEADER}) + SET(_INCLUDE_FILES "${_INCLUDE_FILES}#include <${it}>\n") +ENDFOREACH (it) + +CHECK_C_SOURCE_COMPILES( + "#define __EXTENSIONS__ 1 + ${_INCLUDE_FILES} + int main() { return 0;}" + SAFE_TO_DEFINE_EXTENSIONS) + +# +# Find Nettle +# +IF(ENABLE_NETTLE) + CHECK_LIBRARY_EXISTS(nettle "nettle_sha1_digest" "" NETTLE_FOUND) + IF(NETTLE_FOUND) + SET(CMAKE_REQUIRED_LIBRARIES "nettle") + FIND_LIBRARY(NETTLE_LIBRARY NAMES nettle) + LIST(APPEND ADDITIONAL_LIBS ${NETTLE_LIBRARY}) + ELSE(NETTLE_FOUND) + SET(ENABLE_NETTLE OFF) + ENDIF(NETTLE_FOUND) +ENDIF(ENABLE_NETTLE) + +# +# Find OpenSSL +# (Except on Mac, where OpenSSL is deprecated.) +# +IF(ENABLE_OPENSSL AND NOT CMAKE_SYSTEM_NAME MATCHES "Darwin") + FIND_PACKAGE(OpenSSL) +ELSE() + SET(OPENSSL_FOUND FALSE) # Override cached value +ENDIF() + +# FreeBSD libmd +CHECK_LIBRARY_EXISTS(md "MD5Init" "" LIBMD_FOUND) +IF(LIBMD_FOUND) + SET(CMAKE_REQUIRED_LIBRARIES "md") + FIND_LIBRARY(LIBMD_LIBRARY NAMES md) + LIST(APPEND ADDITIONAL_LIBS ${LIBMD_LIBRARY}) +ENDIF(LIBMD_FOUND) + +# +# How to prove that CRYPTO functions, which have several names on various +# platforms, just see if archive_crypto.c can compile and link against +# required libraries. +# +MACRO(CHECK_CRYPTO ALGORITHMS IMPLEMENTATION) + IF(HAVE_SYS_TYPES_H) + SET(CRYPTO_HEADER_CONFIG "#define HAVE_SYS_TYPES_H 1\n") + ELSE(HAVE_SYS_TYPES_H) + SET(CRYPTO_HEADER_CONFIG "") + ENDIF(HAVE_SYS_TYPES_H) + + FOREACH(ALGORITHM ${ALGORITHMS}) + STRING(TOLOWER "${ALGORITHM}" lower_algorithm) + STRING(TOUPPER "${ALGORITHM}" algorithm) + IF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND NOT OPENSSL_FOUND) + SET(ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} FALSE) + ELSEIF("${IMPLEMENTATION}" MATCHES "^NETTLE$" AND NOT NETTLE_FOUND) + SET(ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} FALSE) + ENDIF("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND NOT OPENSSL_FOUND) + + IF(NOT DEFINED ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + # Probe the local implementation for whether this + # crypto implementation is available on this platform. + SET(TRY_CRYPTO_REQUIRED_INCLUDES + "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_CURRENT_SOURCE_DIR}/libarchive;${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp") + SET(TRY_CRYPTO_REQUIRED_LIBS) + IF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) + SET(TRY_CRYPTO_REQUIRED_INCLUDES + "${TRY_CRYPTO_REQUIRED_INCLUDES};${OPENSSL_INCLUDE_DIR}") + SET(TRY_CRYPTO_REQUIRED_LIBS + "-DLINK_LIBRARIES:STRING=${OPENSSL_LIBRARIES}") + ELSEIF("${IMPLEMENTATION}" MATCHES "^NETTLE$" AND NETTLE_FOUND) + SET(TRY_CRYPTO_REQUIRED_LIBS + "-DLINK_LIBRARIES:STRING=${NETTLE_LIBRARY}") + ELSEIF("${IMPLEMENTATION}" MATCHES "^LIBMD$" AND LIBMD_FOUND) + SET(TRY_CRYPTO_REQUIRED_LIBS + "-DLINK_LIBRARIES:STRING=${LIBMD_LIBRARY}") + ENDIF("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) + + FILE(READ "${CMAKE_CURRENT_SOURCE_DIR}/libarchive/archive_crypto.c" + ARCHIVE_CRYPTO_C) + + SET(SOURCE " +#define ARCHIVE_${algorithm}_COMPILE_TEST +#define ARCHIVE_CRYPTO_${algorithm}_${IMPLEMENTATION} +#define PLATFORM_CONFIG_H \"check_crypto_md.h\" + +${ARCHIVE_CRYPTO_C} + +int +main(int argc, char **argv) +{ + archive_${lower_crypto}_ctx ctx; + archive_${lower_crypto}_init(&ctx); + archive_${lower_crypto}_update(&ctx, *argv, argc); + archive_${lower_crypto}_final(&ctx, NULL); + return 0; +} +") + + FILE(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/check_crypto_md.h" "") + FILE(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/check_crypto_md.c" "${SOURCE}") + MESSAGE(STATUS "Checking support for ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}") + + TRY_COMPILE(ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} + ${CMAKE_BINARY_DIR} + ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/check_crypto_md.c + CMAKE_FLAGS + "${TRY_CRYPTO_REQUIRED_LIBS}" + "${TRY_CRYPTO_REQUIRED_INCLUDES}" + OUTPUT_VARIABLE OUTPUT) + + # Inform user whether or not we found it; if not, log why we didn't. + IF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + MESSAGE(STATUS "Checking support for ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} -- found") + ELSE (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + MESSAGE(STATUS "Checking support for ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} -- not found") + FILE(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log + "Checking support for ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION} failed with the following output:\n" + "${OUTPUT}\n" + "Source file was:\n${SOURCE}\n") + ENDIF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + ENDIF(NOT DEFINED ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + + # Add appropriate libs/includes depending on whether the implementation + # was found on this platform. + IF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + IF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${OPENSSL_LIBRARIES}) + LIST(REMOVE_DUPLICATES ADDITIONAL_LIBS) + ENDIF ("${IMPLEMENTATION}" MATCHES "^OPENSSL$" AND OPENSSL_FOUND) + ENDIF (ARCHIVE_CRYPTO_${ALGORITHM}_${IMPLEMENTATION}) + ENDFOREACH(ALGORITHM ${ALGORITHMS}) +ENDMACRO(CHECK_CRYPTO ALGORITHMS IMPLEMENTATION) + +# +# CRYPTO functions on Windows is defined at archive_windows.c, thus we do not +# need the test what the functions can be mapped to archive_{crypto name}_init, +# archive_{crypto name}_update and archive_{crypto name}_final. +# The functions on Windows use CALG_{crypto name} macro to create a crypt object +# and then we need to know what CALG_{crypto name} macros is available to show +# ARCHIVE_CRYPTO_{crypto name}_WIN macros because Windows 2000 and earlier version +# of Windows XP do not support SHA256, SHA384 and SHA512. +# +MACRO(CHECK_CRYPTO_WIN CRYPTO_LIST) + IF(WIN32 AND NOT CYGWIN) + FOREACH(CRYPTO ${CRYPTO_LIST}) + IF(NOT DEFINED ARCHIVE_CRYPTO_${CRYPTO}_WIN) + STRING(TOUPPER "${CRYPTO}" crypto) + SET(ALGID "") + IF ("${CRYPTO}" MATCHES "^MD5$") + SET(ALGID "CALG_MD5") + ENDIF ("${CRYPTO}" MATCHES "^MD5$") + IF ("${CRYPTO}" MATCHES "^SHA1$") + SET(ALGID "CALG_SHA1") + ENDIF ("${CRYPTO}" MATCHES "^SHA1$") + IF ("${CRYPTO}" MATCHES "^SHA256$") + SET(ALGID "CALG_SHA_256") + ENDIF ("${CRYPTO}" MATCHES "^SHA256$") + IF ("${CRYPTO}" MATCHES "^SHA384$") + SET(ALGID "CALG_SHA_384") + ENDIF ("${CRYPTO}" MATCHES "^SHA384$") + IF ("${CRYPTO}" MATCHES "^SHA512$") + SET(ALGID "CALG_SHA_512") + ENDIF ("${CRYPTO}" MATCHES "^SHA512$") + + SET(SOURCE "#define ${crypto}_COMPILE_TEST +#define _WIN32_WINNT ${_WIN32_WINNT} +#define WINVER ${WINVER} +#include +#include + +int +main(int argc, char **argv) +{ + return ${ALGID}; +} +") + SET(SOURCE_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/check_crypto_win.c") + + FILE(WRITE "${SOURCE_FILE}" "${SOURCE}") + MESSAGE(STATUS "Checking support for ARCHIVE_CRYPTO_${CRYPTO}_WIN") + + TRY_COMPILE(ARCHIVE_CRYPTO_${CRYPTO}_WIN + ${CMAKE_BINARY_DIR} + ${SOURCE_FILE} + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES:STRING=${CMAKE_CURRENT_SOURCE_DIR}/libarchive" + OUTPUT_VARIABLE OUTPUT) + + IF (ARCHIVE_CRYPTO_${CRYPTO}_WIN) + MESSAGE(STATUS + "Checking support for ARCHIVE_CRYPTO_${CRYPTO}_WIN -- found") + ELSE (ARCHIVE_CRYPTO_${CRYPTO}_WIN) + MESSAGE(STATUS + "Checking support for ARCHIVE_CRYPTO_${CRYPTO}_WIN -- not found") + FILE(APPEND + ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log + "Checking support for ARCHIVE_CRYPTO_${CRYPTO}_WIN failed with the following output:\n" + "${OUTPUT}\n" + "Source file was:\n${SOURCE}\n") + ENDIF (ARCHIVE_CRYPTO_${CRYPTO}_WIN) + + ENDIF(NOT DEFINED ARCHIVE_CRYPTO_${CRYPTO}_WIN) + ENDFOREACH(CRYPTO) + ENDIF(WIN32 AND NOT CYGWIN) +ENDMACRO(CHECK_CRYPTO_WIN CRYPTO_LIST) + +# +# Find iconv +# POSIX defines the second arg as const char ** +# and requires it to be in libc. But we can accept +# a non-const argument here and can support iconv() +# being in libiconv. +# +MACRO(CHECK_ICONV LIB TRY_ICONV_CONST) + IF(NOT HAVE_ICONV) + CHECK_C_SOURCE_COMPILES( + "#include + #include + int main() { + ${TRY_ICONV_CONST} char *ccp; + iconv_t cd = iconv_open(\"\", \"\"); + iconv(cd, &ccp, (size_t *)0, (char **)0, (size_t *)0); + iconv_close(cd); + return 0; + }" + HAVE_ICONV_${LIB}_${TRY_ICONV_CONST}) + IF(HAVE_ICONV_${LIB}_${TRY_ICONV_CONST}) + SET(HAVE_ICONV true) + SET(ICONV_CONST ${TRY_ICONV_CONST}) + ENDIF(HAVE_ICONV_${LIB}_${TRY_ICONV_CONST}) + ENDIF(NOT HAVE_ICONV) +ENDMACRO(CHECK_ICONV TRY_ICONV_CONST) + +IF(ENABLE_ICONV) + FIND_PATH(ICONV_INCLUDE_DIR iconv.h) + IF(ICONV_INCLUDE_DIR) + #SET(INCLUDES ${INCLUDES} "iconv.h") + SET(HAVE_ICONV_H 1) + INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR}) + SET(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR}) + CHECK_ICONV("libc" "const") + CHECK_ICONV("libc" "") + + # If iconv isn't in libc and we have a libiconv, try that. + FIND_LIBRARY(LIBICONV_PATH iconv) + IF(NOT HAVE_ICONV AND LIBICONV_PATH) + LIST(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBICONV_PATH}) + CHECK_ICONV("libiconv" "const") + CHECK_ICONV("libiconv" "") + IF (HAVE_ICONV) + LIST(APPEND ADDITIONAL_LIBS ${LIBICONV_PATH}) + ENDIF(HAVE_ICONV) + ENDIF(NOT HAVE_ICONV AND LIBICONV_PATH) + ENDIF(ICONV_INCLUDE_DIR) + # + # Find locale_charset() for libiconv. + # + IF(LIBICONV_PATH) + CHECK_INCLUDE_FILES("localcharset.h" HAVE_LOCALCHARSET_H) + CHECK_FUNCTION_EXISTS_GLIBC(locale_charset HAVE_LOCALE_CHARSET) + IF(NOT HAVE_LOCALE_CHARSET) + FIND_LIBRARY(LIBCHARSET_PATH charset) + IF(LIBCHARSET_PATH) + SET(CMAKE_REQUIRED_LIBRARIES ${LIBCHARSET_PATH}) + CHECK_FUNCTION_EXISTS_GLIBC(locale_charset HAVE_LOCALE_CHARSET) + IF(HAVE_LOCALE_CHARSET) + LIST(APPEND ADDITIONAL_LIBS ${LIBCHARSET_PATH}) + ENDIF(HAVE_LOCALE_CHARSET) + ENDIF(LIBCHARSET_PATH) + ENDIF(NOT HAVE_LOCALE_CHARSET) + ENDIF(LIBICONV_PATH) +ELSE(ENABLE_ICONV) + # Make sure ICONV variables are not in CACHE after ENABLE_ICONV disabled + # (once enabled). + UNSET(HAVE_LOCALE_CHARSET CACHE) + UNSET(HAVE_ICONV CACHE) + UNSET(HAVE_ICONV_libc_ CACHE) + UNSET(HAVE_ICONV_libc_const CACHE) + UNSET(HAVE_ICONV_libiconv_ CACHE) + UNSET(HAVE_ICONV_libiconv_const CACHE) + UNSET(ICONV_INCLUDE_DIR CACHE) + UNSET(LIBICONV_PATH CACHE) +ENDIF(ENABLE_ICONV) + +# +# Find Libxml2 +# +FIND_PACKAGE(LibXml2) +IF(LIBXML2_FOUND) + INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${LIBXML2_LIBRARIES}) + SET(HAVE_LIBXML2 1) + # libxml2's include files use iconv.h + SET(CMAKE_REQUIRED_INCLUDES ${ICONV_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}) + CHECK_INCLUDE_FILES("libxml/xmlreader.h" HAVE_LIBXML_XMLREADER_H) + CHECK_INCLUDE_FILES("libxml/xmlwriter.h" HAVE_LIBXML_XMLWRITER_H) + SET(CMAKE_REQUIRED_INCLUDES "") +ELSE(LIBXML2_FOUND) + # + # Find Expat + # + FIND_PACKAGE(EXPAT) + IF(EXPAT_FOUND) + INCLUDE_DIRECTORIES(${EXPAT_INCLUDE_DIR}) + LIST(APPEND ADDITIONAL_LIBS ${EXPAT_LIBRARIES}) + SET(HAVE_LIBEXPAT 1) + LA_CHECK_INCLUDE_FILE("expat.h" HAVE_EXPAT_H) + ENDIF(EXPAT_FOUND) +ENDIF(LIBXML2_FOUND) + +# +# Check functions +# +IF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") + # + # During checking functions, we should use -fno-builtin to avoid the + # failure of function detection which failure is an error "conflicting + # types for built-in function" caused by using -Werror option. + # + SET(SAVE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + SET(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -fno-builtin") +ENDIF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") +CHECK_SYMBOL_EXISTS(_CrtSetReportMode "crtdbg.h" HAVE__CrtSetReportMode) +CHECK_FUNCTION_EXISTS_GLIBC(chflags HAVE_CHFLAGS) +CHECK_FUNCTION_EXISTS_GLIBC(chown HAVE_CHOWN) +CHECK_FUNCTION_EXISTS_GLIBC(chroot HAVE_CHROOT) +CHECK_FUNCTION_EXISTS_GLIBC(ctime_r HAVE_CTIME_R) +CHECK_FUNCTION_EXISTS_GLIBC(fchdir HAVE_FCHDIR) +CHECK_FUNCTION_EXISTS_GLIBC(fchflags HAVE_FCHFLAGS) +CHECK_FUNCTION_EXISTS_GLIBC(fchmod HAVE_FCHMOD) +CHECK_FUNCTION_EXISTS_GLIBC(fchown HAVE_FCHOWN) +CHECK_FUNCTION_EXISTS_GLIBC(fcntl HAVE_FCNTL) +CHECK_FUNCTION_EXISTS_GLIBC(fdopendir HAVE_FDOPENDIR) +CHECK_FUNCTION_EXISTS_GLIBC(fork HAVE_FORK) +CHECK_FUNCTION_EXISTS_GLIBC(fstat HAVE_FSTAT) +CHECK_FUNCTION_EXISTS_GLIBC(fstatat HAVE_FSTATAT) +CHECK_FUNCTION_EXISTS_GLIBC(fstatfs HAVE_FSTATFS) +CHECK_FUNCTION_EXISTS_GLIBC(fstatvfs HAVE_FSTATVFS) +CHECK_FUNCTION_EXISTS_GLIBC(ftruncate HAVE_FTRUNCATE) +CHECK_FUNCTION_EXISTS_GLIBC(futimens HAVE_FUTIMENS) +CHECK_FUNCTION_EXISTS_GLIBC(futimes HAVE_FUTIMES) +CHECK_FUNCTION_EXISTS_GLIBC(futimesat HAVE_FUTIMESAT) +CHECK_FUNCTION_EXISTS_GLIBC(geteuid HAVE_GETEUID) +CHECK_FUNCTION_EXISTS_GLIBC(getgrgid_r HAVE_GETGRGID_R) +CHECK_FUNCTION_EXISTS_GLIBC(getgrnam_r HAVE_GETGRNAM_R) +CHECK_FUNCTION_EXISTS_GLIBC(getpwnam_r HAVE_GETPWNAM_R) +CHECK_FUNCTION_EXISTS_GLIBC(getpwuid_r HAVE_GETPWUID_R) +CHECK_FUNCTION_EXISTS_GLIBC(getpid HAVE_GETPID) +CHECK_FUNCTION_EXISTS_GLIBC(getvfsbyname HAVE_GETVFSBYNAME) +CHECK_FUNCTION_EXISTS_GLIBC(gmtime_r HAVE_GMTIME_R) +CHECK_FUNCTION_EXISTS_GLIBC(lchflags HAVE_LCHFLAGS) +CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD) +CHECK_FUNCTION_EXISTS_GLIBC(lchown HAVE_LCHOWN) +CHECK_FUNCTION_EXISTS_GLIBC(link HAVE_LINK) +CHECK_FUNCTION_EXISTS_GLIBC(localtime_r HAVE_LOCALTIME_R) +CHECK_FUNCTION_EXISTS_GLIBC(lstat HAVE_LSTAT) +CHECK_FUNCTION_EXISTS_GLIBC(lutimes HAVE_LUTIMES) +CHECK_FUNCTION_EXISTS_GLIBC(mbrtowc HAVE_MBRTOWC) +CHECK_FUNCTION_EXISTS_GLIBC(mbsnrtowcs HAVE_MBSNRTOWCS) +CHECK_FUNCTION_EXISTS_GLIBC(memmove HAVE_MEMMOVE) +CHECK_FUNCTION_EXISTS_GLIBC(mkdir HAVE_MKDIR) +CHECK_FUNCTION_EXISTS_GLIBC(mkfifo HAVE_MKFIFO) +CHECK_FUNCTION_EXISTS_GLIBC(mknod HAVE_MKNOD) +CHECK_FUNCTION_EXISTS_GLIBC(mkstemp HAVE_MKSTEMP) +CHECK_FUNCTION_EXISTS_GLIBC(nl_langinfo HAVE_NL_LANGINFO) +CHECK_FUNCTION_EXISTS_GLIBC(openat HAVE_OPENAT) +CHECK_FUNCTION_EXISTS_GLIBC(pipe HAVE_PIPE) +CHECK_FUNCTION_EXISTS_GLIBC(poll HAVE_POLL) +CHECK_FUNCTION_EXISTS_GLIBC(readlink HAVE_READLINK) +CHECK_FUNCTION_EXISTS_GLIBC(select HAVE_SELECT) +CHECK_FUNCTION_EXISTS_GLIBC(setenv HAVE_SETENV) +CHECK_FUNCTION_EXISTS_GLIBC(setlocale HAVE_SETLOCALE) +CHECK_FUNCTION_EXISTS_GLIBC(sigaction HAVE_SIGACTION) +CHECK_FUNCTION_EXISTS_GLIBC(statfs HAVE_STATFS) +CHECK_FUNCTION_EXISTS_GLIBC(statvfs HAVE_STATVFS) +CHECK_FUNCTION_EXISTS_GLIBC(strchr HAVE_STRCHR) +CHECK_FUNCTION_EXISTS_GLIBC(strdup HAVE_STRDUP) +CHECK_FUNCTION_EXISTS_GLIBC(strerror HAVE_STRERROR) +CHECK_FUNCTION_EXISTS_GLIBC(strncpy_s HAVE_STRNCPY_S) +CHECK_FUNCTION_EXISTS_GLIBC(strrchr HAVE_STRRCHR) +CHECK_FUNCTION_EXISTS_GLIBC(symlink HAVE_SYMLINK) +CHECK_FUNCTION_EXISTS_GLIBC(timegm HAVE_TIMEGM) +CHECK_FUNCTION_EXISTS_GLIBC(tzset HAVE_TZSET) +CHECK_FUNCTION_EXISTS_GLIBC(unsetenv HAVE_UNSETENV) +CHECK_FUNCTION_EXISTS_GLIBC(utime HAVE_UTIME) +CHECK_FUNCTION_EXISTS_GLIBC(utimes HAVE_UTIMES) +CHECK_FUNCTION_EXISTS_GLIBC(utimensat HAVE_UTIMENSAT) +CHECK_FUNCTION_EXISTS_GLIBC(vfork HAVE_VFORK) +CHECK_FUNCTION_EXISTS_GLIBC(wcrtomb HAVE_WCRTOMB) +CHECK_FUNCTION_EXISTS_GLIBC(wcscmp HAVE_WCSCMP) +CHECK_FUNCTION_EXISTS_GLIBC(wcscpy HAVE_WCSCPY) +CHECK_FUNCTION_EXISTS_GLIBC(wcslen HAVE_WCSLEN) +CHECK_FUNCTION_EXISTS_GLIBC(wcsnrtombs HAVE_WCSNRTOMBS) +CHECK_FUNCTION_EXISTS_GLIBC(wctomb HAVE_WCTOMB) +CHECK_FUNCTION_EXISTS_GLIBC(_ctime64_s HAVE__CTIME64_S) +CHECK_FUNCTION_EXISTS_GLIBC(_fseeki64 HAVE__FSEEKI64) +CHECK_FUNCTION_EXISTS_GLIBC(_get_timezone HAVE__GET_TIMEZONE) +CHECK_FUNCTION_EXISTS_GLIBC(_localtime64_s HAVE__LOCALTIME64_S) +CHECK_FUNCTION_EXISTS_GLIBC(_mkgmtime64 HAVE__MKGMTIME64) + +SET(CMAKE_REQUIRED_LIBRARIES "") +CHECK_FUNCTION_EXISTS(cygwin_conv_path HAVE_CYGWIN_CONV_PATH) +CHECK_FUNCTION_EXISTS(fseeko HAVE_FSEEKO) +CHECK_FUNCTION_EXISTS(strerror_r HAVE_STRERROR_R) +CHECK_FUNCTION_EXISTS(strftime HAVE_STRFTIME) +CHECK_FUNCTION_EXISTS(vprintf HAVE_VPRINTF) +CHECK_FUNCTION_EXISTS(wmemcmp HAVE_WMEMCMP) +CHECK_FUNCTION_EXISTS(wmemcpy HAVE_WMEMCPY) + +# Restore CMAKE_REQUIRED_FLAGS +IF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") + SET(CMAKE_REQUIRED_FLAGS ${SAVE_CMAKE_REQUIRED_FLAGS}) +ENDIF ("CMAKE_C_COMPILER_ID" MATCHES "^GNU$") + +# Make sure we have the POSIX version of readdir_r, not the +# older 2-argument version. +CHECK_C_SOURCE_COMPILES( + "#include \nint main() {DIR *d = opendir(\".\"); struct dirent e,*r; return readdir_r(d,&e,&r);}" + HAVE_READDIR_R) + + +# Only detect readlinkat() if we also have AT_FDCWD in unistd.h. +# NOTE: linux requires fcntl.h for AT_FDCWD. +CHECK_C_SOURCE_COMPILES( + "#include \n#include \nint main() {char buf[10]; return readlinkat(AT_FDCWD, \"\", buf, 0);}" + HAVE_READLINKAT) + + +# To verify major(), we need to both include the header +# of interest and verify that the result can be linked. +# CHECK_FUNCTION_EXISTS doesn't accept a header argument, +# CHECK_SYMBOL_EXISTS doesn't test linkage. +CHECK_C_SOURCE_COMPILES( + "#include \nint main() { return major(256); }" + MAJOR_IN_MKDEV) +CHECK_C_SOURCE_COMPILES( + "#include \nint main() { return major(256); }" + MAJOR_IN_SYSMACROS) + +IF(HAVE_STRERROR_R) + SET(HAVE_DECL_STRERROR_R 1) +ENDIF(HAVE_STRERROR_R) + +# +# Check defines +# +SET(headers "limits.h") +IF(HAVE_STDINT_H) + LIST(APPEND headers "stdint.h") +ENDIF(HAVE_STDINT_H) +IF(HAVE_INTTYPES_H) + LIST(APPEND headers "inttypes.h") +ENDIF(HAVE_INTTYPES_H) +CHECK_SYMBOL_EXISTS(EFTYPE "errno.h" HAVE_EFTYPE) +CHECK_SYMBOL_EXISTS(EILSEQ "errno.h" HAVE_EILSEQ) +CHECK_SYMBOL_EXISTS(D_MD_ORDER "langinfo.h" HAVE_D_MD_ORDER) +CHECK_SYMBOL_EXISTS(INT64_MAX "${headers}" HAVE_DECL_INT64_MAX) +CHECK_SYMBOL_EXISTS(INT64_MIN "${headers}" HAVE_DECL_INT64_MIN) +CHECK_SYMBOL_EXISTS(UINT32_MAX "${headers}" HAVE_DECL_UINT32_MAX) +CHECK_SYMBOL_EXISTS(UINT64_MAX "${headers}" HAVE_DECL_UINT64_MAX) +CHECK_SYMBOL_EXISTS(SIZE_MAX "${headers}" HAVE_DECL_SIZE_MAX) +CHECK_SYMBOL_EXISTS(SSIZE_MAX "limits.h" HAVE_DECL_SSIZE_MAX) + +# +# Check struct members +# +# Check for tm_gmtoff in struct tm +CHECK_STRUCT_MEMBER("struct tm" tm_gmtoff + "time.h" HAVE_STRUCT_TM_TM_GMTOFF) +CHECK_STRUCT_MEMBER("struct tm" __tm_gmtoff + "time.h" HAVE_STRUCT_TM___TM_GMTOFF) + +# Check for f_namemax in struct statfs +CHECK_STRUCT_MEMBER("struct statfs" f_namemax + "sys/param.h;sys/mount.h" HAVE_STRUCT_STATFS_F_NAMEMAX) + +# Check for birthtime in struct stat +CHECK_STRUCT_MEMBER("struct stat" st_birthtime + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_BIRTHTIME) + +# Check for high-resolution timestamps in struct stat +CHECK_STRUCT_MEMBER("struct stat" st_birthtimespec.tv_nsec + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC) +CHECK_STRUCT_MEMBER("struct stat" st_mtimespec.tv_nsec + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC) +CHECK_STRUCT_MEMBER("struct stat" st_mtim.tv_nsec + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) +CHECK_STRUCT_MEMBER("struct stat" st_mtime_n + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIME_N) +CHECK_STRUCT_MEMBER("struct stat" st_umtime + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_UMTIME) +CHECK_STRUCT_MEMBER("struct stat" st_mtime_usec + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_MTIME_USEC) +# Check for block size support in struct stat +CHECK_STRUCT_MEMBER("struct stat" st_blksize + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_BLKSIZE) +# Check for st_flags in struct stat (BSD fflags) +CHECK_STRUCT_MEMBER("struct stat" st_flags + "sys/types.h;sys/stat.h" HAVE_STRUCT_STAT_ST_FLAGS) +# +# +CHECK_STRUCT_MEMBER("struct tm" tm_sec + "sys/types.h;sys/time.h;time.h" TIME_WITH_SYS_TIME) + +# +# Check for integer types +# +# +CHECK_TYPE_SIZE("short" SIZE_OF_SHORT) +CHECK_TYPE_SIZE("int" SIZE_OF_INT) +CHECK_TYPE_SIZE("long" SIZE_OF_LONG) +CHECK_TYPE_SIZE("long long" SIZE_OF_LONG_LONG) + +CHECK_TYPE_SIZE("unsigned short" SIZE_OF_UNSIGNED_SHORT) +CHECK_TYPE_SIZE("unsigned" SIZE_OF_UNSIGNED) +CHECK_TYPE_SIZE("unsigned long" SIZE_OF_UNSIGNED_LONG) +CHECK_TYPE_SIZE("unsigned long long" SIZE_OF_UNSIGNED_LONG_LONG) + +CHECK_TYPE_SIZE("__int64" __INT64) +CHECK_TYPE_SIZE("unsigned __int64" UNSIGNED___INT64) + +CHECK_TYPE_SIZE(int16_t INT16_T) +CHECK_TYPE_SIZE(int32_t INT32_T) +CHECK_TYPE_SIZE(int64_t INT64_T) +CHECK_TYPE_SIZE(intmax_t INTMAX_T) +CHECK_TYPE_SIZE(uint8_t UINT8_T) +CHECK_TYPE_SIZE(uint16_t UINT16_T) +CHECK_TYPE_SIZE(uint32_t UINT32_T) +CHECK_TYPE_SIZE(uint64_t UINT64_T) +CHECK_TYPE_SIZE(uintmax_t UINTMAX_T) + +CHECK_TYPE_SIZE(dev_t DEV_T) +IF(NOT HAVE_DEV_T) + IF(MSVC) + SET(dev_t "unsigned int") + ENDIF(MSVC) +ENDIF(NOT HAVE_DEV_T) +# +CHECK_TYPE_SIZE(gid_t GID_T) +IF(NOT HAVE_GID_T) + IF(WIN32) + SET(gid_t "short") + ELSE(WIN32) + SET(gid_t "unsigned int") + ENDIF(WIN32) +ENDIF(NOT HAVE_GID_T) +# +CHECK_TYPE_SIZE(id_t ID_T) +IF(NOT HAVE_ID_T) + IF(WIN32) + SET(id_t "short") + ELSE(WIN32) + SET(id_t "unsigned int") + ENDIF(WIN32) +ENDIF(NOT HAVE_ID_T) +# +CHECK_TYPE_SIZE(mode_t MODE_T) +IF(NOT HAVE_MODE_T) + IF(WIN32) + SET(mode_t "unsigned short") + ELSE(WIN32) + SET(mode_t "int") + ENDIF(WIN32) +ENDIF(NOT HAVE_MODE_T) +# +CHECK_TYPE_SIZE(off_t OFF_T) +IF(NOT HAVE_OFF_T) + SET(off_t "__int64") +ENDIF(NOT HAVE_OFF_T) +# +CHECK_TYPE_SIZE(size_t SIZE_T) +IF(NOT HAVE_SIZE_T) + IF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(size_t "uint64_t") + ELSE("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(size_t "uint32_t") + ENDIF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) +ENDIF(NOT HAVE_SIZE_T) +# +CHECK_TYPE_SIZE(ssize_t SSIZE_T) +IF(NOT HAVE_SSIZE_T) + IF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(ssize_t "int64_t") + ELSE("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(ssize_t "long") + ENDIF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) +ENDIF(NOT HAVE_SSIZE_T) +# +CHECK_TYPE_SIZE(uid_t UID_T) +IF(NOT HAVE_UID_T) + IF(WIN32) + SET(uid_t "short") + ELSE(WIN32) + SET(uid_t "unsigned int") + ENDIF(WIN32) +ENDIF(NOT HAVE_UID_T) +# +CHECK_TYPE_SIZE(pid_t PID_T) +IF(NOT HAVE_PID_T) + IF(WIN32) + SET(pid_t "int") + ELSE(WIN32) + MESSAGE(FATAL_ERROR "pid_t doesn't exist on this platform?") + ENDIF(WIN32) +ENDIF(NOT HAVE_PID_T) +# +CHECK_TYPE_SIZE(intptr_t INTPTR_T) +IF(NOT HAVE_INTPTR_T) + IF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(intptr_t "int64_t") + ELSE() + SET(intptr_t "int32_t") + ENDIF() +ENDIF(NOT HAVE_INTPTR_T) +# +CHECK_TYPE_SIZE(uintptr_t UINTPTR_T) +IF(NOT HAVE_UINTPTR_T) + IF("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + SET(uintptr_t "uint64_t") + ELSE() + SET(uintptr_t "uint32_t") + ENDIF() +ENDIF(NOT HAVE_UINTPTR_T) +# +CHECK_TYPE_SIZE(wchar_t SIZEOF_WCHAR_T) +IF(HAVE_SIZEOF_WCHAR_T) + SET(HAVE_WCHAR_T 1) +ENDIF(HAVE_SIZEOF_WCHAR_T) +# +# Check if _FILE_OFFSET_BITS macro needed for large files +# +CHECK_FILE_OFFSET_BITS() + +# +# Check for Extended Attribute libraries, headers, and functions +# +IF(ENABLE_XATTR) + LA_CHECK_INCLUDE_FILE(attr/xattr.h HAVE_ATTR_XATTR_H) + LA_CHECK_INCLUDE_FILE(sys/xattr.h HAVE_SYS_XATTR_H) + LA_CHECK_INCLUDE_FILE(sys/extattr.h HAVE_SYS_EXTATTR_H) + CHECK_LIBRARY_EXISTS(attr "setxattr" "" HAVE_LIBATTR) + IF(HAVE_LIBATTR) + SET(CMAKE_REQUIRED_LIBRARIES "attr") + ENDIF(HAVE_LIBATTR) + CHECK_SYMBOL_EXISTS(EXTATTR_NAMESPACE_USER "sys/types.h;sys/extattr.h" HAVE_DECL_EXTATTR_NAMESPACE_USER) + CHECK_FUNCTION_EXISTS_GLIBC(extattr_get_file HAVE_EXTATTR_GET_FILE) + CHECK_FUNCTION_EXISTS_GLIBC(extattr_list_file HAVE_EXTATTR_LIST_FILE) + CHECK_FUNCTION_EXISTS_GLIBC(extattr_set_fd HAVE_EXTATTR_SET_FD) + CHECK_FUNCTION_EXISTS_GLIBC(extattr_set_file HAVE_EXTATTR_SET_FILE) + CHECK_FUNCTION_EXISTS_GLIBC(fgetxattr HAVE_FGETXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(flistxattr HAVE_FLISTXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(fsetxattr HAVE_FSETXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(getxattr HAVE_GETXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(lgetxattr HAVE_LGETXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(listxattr HAVE_LISTXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(llistxattr HAVE_LLISTXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(lsetxattr HAVE_LSETXATTR) + CHECK_FUNCTION_EXISTS_GLIBC(fgetea HAVE_FGETEA) + CHECK_FUNCTION_EXISTS_GLIBC(flistea HAVE_FLISTEA) + CHECK_FUNCTION_EXISTS_GLIBC(fsetea HAVE_FSETEA) + CHECK_FUNCTION_EXISTS_GLIBC(getea HAVE_GETEA) + CHECK_FUNCTION_EXISTS_GLIBC(lgetea HAVE_LGETEA) + CHECK_FUNCTION_EXISTS_GLIBC(listea HAVE_LISTEA) + CHECK_FUNCTION_EXISTS_GLIBC(llistea HAVE_LLISTEA) + CHECK_FUNCTION_EXISTS_GLIBC(lsetea HAVE_LSETEA) +ELSE(ENABLE_XATTR) + SET(HAVE_ATTR_LIB FALSE) + SET(HAVE_ATTR_XATTR_H FALSE) + SET(HAVE_DECL_EXTATTR_NAMESPACE_USER FALSE) + SET(HAVE_EXTATTR_GET_FILE FALSE) + SET(HAVE_EXTATTR_LIST_FILE FALSE) + SET(HAVE_EXTATTR_SET_FD FALSE) + SET(HAVE_EXTATTR_SET_FILE FALSE) + SET(HAVE_FGETEA FALSE) + SET(HAVE_FGETXATTR FALSE) + SET(HAVE_FLISTEA FALSE) + SET(HAVE_FLISTXATTR FALSE) + SET(HAVE_FSETEA FALSE) + SET(HAVE_FSETXATTR FALSE) + SET(HAVE_GETEA FALSE) + SET(HAVE_GETXATTR FALSE) + SET(HAVE_LGETEA FALSE) + SET(HAVE_LGETXATTR FALSE) + SET(HAVE_LISTEA FALSE) + SET(HAVE_LISTXATTR FALSE) + SET(HAVE_LLISTEA FALSE) + SET(HAVE_LLISTXATTR FALSE) + SET(HAVE_LSETEA FALSE) + SET(HAVE_LSETXATTR FALSE) + SET(HAVE_SYS_EXTATTR_H FALSE) + SET(HAVE_SYS_XATTR_H FALSE) +ENDIF(ENABLE_XATTR) + +# +# Check for ACL libraries, headers, and functions +# +# The ACL support in libarchive is written against the POSIX1e draft, +# which was never officially approved and varies quite a bit across +# platforms. Worse, some systems have completely non-POSIX acl functions, +# which makes the following checks rather more complex than I would like. +# +IF(ENABLE_ACL) + CHECK_LIBRARY_EXISTS(acl "acl_get_file" "" HAVE_LIBACL) + IF(HAVE_LIBACL) + SET(CMAKE_REQUIRED_LIBRARIES "acl") + FIND_LIBRARY(ACL_LIBRARY NAMES acl) + LIST(APPEND ADDITIONAL_LIBS ${ACL_LIBRARY}) + ENDIF(HAVE_LIBACL) + # + CHECK_FUNCTION_EXISTS_GLIBC(acl_create_entry HAVE_ACL_CREATE_ENTRY) + CHECK_FUNCTION_EXISTS_GLIBC(acl_init HAVE_ACL_INIT) + CHECK_FUNCTION_EXISTS_GLIBC(acl_set_fd HAVE_ACL_SET_FD) + CHECK_FUNCTION_EXISTS_GLIBC(acl_set_fd_np HAVE_ACL_SET_FD_NP) + CHECK_FUNCTION_EXISTS_GLIBC(acl_set_file HAVE_ACL_SET_FILE) + CHECK_TYPE_EXISTS(acl_permset_t "${INCLUDES}" HAVE_ACL_PERMSET_T) + + # The "acl_get_perm()" function was omitted from the POSIX draft. + # (It's a pretty obvious oversight; otherwise, there's no way to + # test for specific permissions in a permset.) Linux uses the obvious + # name, FreeBSD adds _np to mark it as "non-Posix extension." + # Test for both as a double-check that we really have POSIX-style ACL support. + CHECK_FUNCTION_EXISTS(acl_get_perm HAVE_ACL_GET_PERM) + CHECK_FUNCTION_EXISTS(acl_get_perm_np HAVE_ACL_GET_PERM_NP) + CHECK_FUNCTION_EXISTS(acl_get_link HAVE_ACL_GET_LINK) + CHECK_FUNCTION_EXISTS(acl_get_link_np HAVE_ACL_GET_LINK_NP) + + # MacOS has an acl.h that isn't POSIX. It can be detected by + # checking for ACL_USER + CHECK_SYMBOL_EXISTS(ACL_USER "${INCLUDES}" HAVE_ACL_USER) +ELSE(ENABLE_ACL) + # If someone runs cmake, then disables ACL support, we need + # to forcibly override the cached values for these. + SET(HAVE_ACL_CREATE_ENTRY FALSE) + SET(HAVE_ACL_GET_LINK FALSE) + SET(HAVE_ACL_GET_LINK_NP FALSE) + SET(HAVE_ACL_GET_PERM FALSE) + SET(HAVE_ACL_GET_PERM_NP FALSE) + SET(HAVE_ACL_INIT FALSE) + SET(HAVE_ACL_LIB FALSE) + SET(HAVE_ACL_PERMSET_T FALSE) + SET(HAVE_ACL_SET_FD FALSE) + SET(HAVE_ACL_SET_FD_NP FALSE) + SET(HAVE_ACL_SET_FILE FALSE) + SET(HAVE_ACL_USER FALSE) +ENDIF(ENABLE_ACL) + +# +# Check MD5/RMD160/SHA support +# NOTE: Crypto checks must be run last before generating config.h +# +CHECK_CRYPTO("MD5;RMD160;SHA1;SHA256;SHA384;SHA512" LIBC) +CHECK_CRYPTO("SHA256;SHA384;SHA512" LIBC2) +CHECK_CRYPTO("SHA256;SHA384;SHA512" LIBC3) +CHECK_CRYPTO("MD5;SHA1;SHA256;SHA384;SHA512" LIBSYSTEM) +CHECK_CRYPTO("MD5;RMD160;SHA1;SHA256;SHA384;SHA512" NETTLE) +CHECK_CRYPTO("MD5;RMD160;SHA1;SHA256;SHA384;SHA512" OPENSSL) + +# Libmd has to be probed after OpenSSL. +CHECK_CRYPTO("MD5;RMD160;SHA1;SHA256;SHA512" LIBMD) + +CHECK_CRYPTO_WIN("MD5;SHA1;SHA256;SHA384;SHA512") + +# Generate "config.h" from "build/cmake/config.h.in" +CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/build/cmake/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h) +INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_BINARY_DIR}) +ADD_DEFINITIONS(-DHAVE_CONFIG_H) + +# +# Register installation of PDF documents. +# +IF(WIN32 AND NOT CYGWIN) + # + # On Windows platform, It's better that we install PDF documents + # on one's computer. + # These PDF documents are available in the release package. + # + IF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/doc/pdf) + INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doc/pdf + DESTINATION share/man + FILES_MATCHING PATTERN "*.pdf" + ) + ENDIF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/doc/pdf) +ENDIF(WIN32 AND NOT CYGWIN) +# +# +# +INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/libarchive) +# +IF(MSVC) + ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) +ENDIF(MSVC) + +# We need CoreServices on Mac OS. +IF(APPLE) + LIST(APPEND ADDITIONAL_LIBS "-framework CoreServices") +ENDIF(APPLE) + +IF(ENABLE_TEST) + ADD_CUSTOM_TARGET(run_all_tests) +ENDIF(ENABLE_TEST) + +add_subdirectory(libarchive) +add_subdirectory(tar) +add_subdirectory(cpio) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..b258806 --- /dev/null +++ b/COPYING @@ -0,0 +1,60 @@ +The libarchive distribution as a whole is Copyright by Tim Kientzle +and is subject to the copyright notice reproduced at the bottom of +this file. + +Each individual file in this distribution should have a clear +copyright/licensing statement at the beginning of the file. If any do +not, please let me know and I will rectify it. The following is +intended to summarize the copyright status of the individual files; +the actual statements in the files are controlling. + +* Except as listed below, all C sources (including .c and .h files) + and documentation files are subject to the copyright notice reproduced + at the bottom of this file. + +* The following source files are also subject in whole or in part to + a 3-clause UC Regents copyright; please read the individual source + files for details: + libarchive/archive_entry.c + libarchive/archive_read_support_filter_compress.c + libarchive/archive_write_set_filter_compress.c + libarchive/mtree.5 + tar/matching.c + +* The following source files are in the public domain: + tar/getdate.c + +* The build files---including Makefiles, configure scripts, + and auxiliary scripts used as part of the compile process---have + widely varying licensing terms. Please check individual files before + distributing them to see if those restrictions apply to you. + +I intend for all new source code to use the license below and hope over +time to replace code with other licenses with new implementations that +do use the license below. The varying licensing of the build scripts +seems to be an unavoidable mess. + + +Copyright (c) 2003-2009 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer + in this position and unchanged. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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/CTestConfig.cmake b/CTestConfig.cmake new file mode 100644 index 0000000..7a09742 --- /dev/null +++ b/CTestConfig.cmake @@ -0,0 +1,11 @@ +# TODO: This file should be moved into the build/cmake directory... + +# The libarchive CDash page appears at +# http://my.cdash.org/index.php?project=libarchive +set(CTEST_PROJECT_NAME "libarchive") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "my.cdash.org") +set(CTEST_DROP_LOCATION "/submit.php?project=libarchive") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/build/cmake/AddTest28.cmake b/build/cmake/AddTest28.cmake new file mode 100644 index 0000000..ab26a9a --- /dev/null +++ b/build/cmake/AddTest28.cmake @@ -0,0 +1,107 @@ +# - Macro approximating the CMake 2.8 ADD_TEST(NAME) signature. +# ADD_TEST_28(NAME COMMAND [arg1 [arg2 ...]]) +# - The name of the test +# - The test executable +# [argN...] - Arguments to the test executable +# This macro approximates the ADD_TEST(NAME) signature provided in +# CMake 2.8 but works with CMake 2.6 too. See CMake 2.8 documentation +# of ADD_TEST()for details. +# +# This macro automatically replaces a that names an +# executable target with the target location. A generator expression +# of the form "$" is supported in both the command +# and arguments of the test. Howerver, this macro only works for +# targets without per-config output name properties set. +# +# Example usage: +# add_test(NAME mytest COMMAND testDriver --exe $) +# This creates a test "mytest" whose command runs a testDriver tool +# passing the full path to the executable file produced by target +# "myexe". + +#============================================================================= +# Copyright 2009 Kitware, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer +# in this position and unchanged. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. +#============================================================================= + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6.3) + +# CMake 2.8 supports ADD_TEST(NAME) natively. +IF(NOT "${CMAKE_VERSION}" VERSION_LESS "2.8") + MACRO(ADD_TEST_28) + ADD_TEST(${ARGV}) + ENDMACRO() + RETURN() +ENDIF() + +# Simulate ADD_TEST(NAME) signature from CMake 2.8. +MACRO(ADD_TEST_28 NAME name COMMAND command) + # Enforce the signature. + IF(NOT "x${NAME}" STREQUAL "xNAME") + MESSAGE(FATAL_ERROR "First ADD_TEST_28 argument must be \"NAME\"") + ENDIF() + IF(NOT "x${COMMAND}" STREQUAL "xCOMMAND") + MESSAGE(FATAL_ERROR "Third ADD_TEST_28 argument must be \"COMMAND\"") + ENDIF() + + # Perform "COMMAND myexe ..." substitution. + SET(cmd "${command}") + IF(TARGET "${cmd}") + _ADD_TEST_28_GET_EXE(${cmd} cmd) + ENDIF() + + # Perform "COMMAND ... $ ..." substitution. + SET(target_file "\\$") + SET(args) + FOREACH(ARG ${cmd} ${ARGN}) + SET(arg "${ARG}") + IF("${arg}" MATCHES "${target_file}") + STRING(REGEX REPLACE "${target_file}" "\\1" tgt "${arg}") + IF(TARGET "${tgt}") + _ADD_TEST_28_GET_EXE(${tgt} exe) + STRING(REGEX REPLACE "${target_file}" "${exe}" arg "${arg}") + ENDIF() + ENDIF() + LIST(APPEND args "${arg}") + ENDFOREACH() + + # Invoke old ADD_TEST() signature with transformed arguments. + ADD_TEST(${name} ${args}) +ENDMACRO() + +# Get the test-time location of an executable target. +MACRO(_ADD_TEST_28_GET_EXE tgt exe_var) + # The LOCATION property gives a build-time location. + GET_TARGET_PROPERTY(${exe_var} ${tgt} LOCATION) + + # In single-configuration generatrs the build-time and test-time + # locations are the same because there is no per-config variable + # reference. In multi-configuration generators the following + # substitution converts the build-time configuration variable + # reference to a test-time configuration variable reference. + IF(CMAKE_CONFIGURATION_TYPES) + STRING(REPLACE "${CMAKE_CFG_INTDIR}" "\${CTEST_CONFIGURATION_TYPE}" + ${exe_var} "${${exe_var}}") + ENDIF(CMAKE_CONFIGURATION_TYPES) +ENDMACRO() diff --git a/build/cmake/CheckFileOffsetBits.c b/build/cmake/CheckFileOffsetBits.c new file mode 100644 index 0000000..d948fec --- /dev/null +++ b/build/cmake/CheckFileOffsetBits.c @@ -0,0 +1,14 @@ +#include + +#define KB ((off_t)1024) +#define MB ((off_t)1024 * KB) +#define GB ((off_t)1024 * MB) +#define TB ((off_t)1024 * GB) +int t2[(((64 * GB -1) % 671088649) == 268434537) + && (((TB - (64 * GB -1) + 255) % 1792151290) == 305159546)? 1: -1]; + +int main() +{ + ; + return 0; +} diff --git a/build/cmake/CheckFileOffsetBits.cmake b/build/cmake/CheckFileOffsetBits.cmake new file mode 100644 index 0000000..4132b38 --- /dev/null +++ b/build/cmake/CheckFileOffsetBits.cmake @@ -0,0 +1,44 @@ +# - Check if _FILE_OFFSET_BITS macro needed for large files +# CHECK_FILE_OFFSET_BITS () +# +# 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 +# Copyright (c) 2009, Michihiro NAKAJIMA +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +#INCLUDE(CheckCXXSourceCompiles) + +GET_FILENAME_COMPONENT(_selfdir_CheckFileOffsetBits + "${CMAKE_CURRENT_LIST_FILE}" PATH) + +MACRO (CHECK_FILE_OFFSET_BITS) + IF(NOT DEFINED _FILE_OFFSET_BITS) + MESSAGE(STATUS "Cheking _FILE_OFFSET_BITS for large files") + TRY_COMPILE(__WITHOUT_FILE_OFFSET_BITS_64 + ${CMAKE_CURRENT_BINARY_DIR} + ${_selfdir_CheckFileOffsetBits}/CheckFileOffsetBits.c + COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS}) + IF(NOT __WITHOUT_FILE_OFFSET_BITS_64) + TRY_COMPILE(__WITH_FILE_OFFSET_BITS_64 + ${CMAKE_CURRENT_BINARY_DIR} + ${_selfdir_CheckFileOffsetBits}/CheckFileOffsetBits.c + COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} -D_FILE_OFFSET_BITS=64) + ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64) + + IF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) + SET(_FILE_OFFSET_BITS 64 CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files") + MESSAGE(STATUS "Cheking _FILE_OFFSET_BITS for large files - needed") + ELSE(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) + SET(_FILE_OFFSET_BITS "" CACHE INTERNAL "_FILE_OFFSET_BITS macro needed for large files") + MESSAGE(STATUS "Cheking _FILE_OFFSET_BITS for large files - not needed") + ENDIF(NOT __WITHOUT_FILE_OFFSET_BITS_64 AND __WITH_FILE_OFFSET_BITS_64) + ENDIF(NOT DEFINED _FILE_OFFSET_BITS) + +ENDMACRO (CHECK_FILE_OFFSET_BITS) + diff --git a/build/cmake/CheckFuncs.cmake b/build/cmake/CheckFuncs.cmake new file mode 100644 index 0000000..0670df9 --- /dev/null +++ b/build/cmake/CheckFuncs.cmake @@ -0,0 +1,49 @@ +# Check if the system has the specified function; treat glibc "stub" +# functions as nonexistent: +# CHECK_FUNCTION_EXISTS_GLIBC (FUNCTION FUNCVAR) +# +# FUNCTION - the function(s) where the prototype should be declared +# FUNCVAR - variable to define if the function does exist +# +# In particular, this understands the glibc convention of +# defining macros __stub_XXXX or __stub___XXXX if the function +# does appear in the library but is merely a stub that does nothing. +# By detecting this case, we can select alternate behavior on +# platforms that don't support this functionality. +# +# 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 +# Copyright (c) 2009, Michihiro NAKAJIMA +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +INCLUDE(CheckFunctionExists) +GET_FILENAME_COMPONENT(_selfdir_CheckFunctionExistsGlibc + "${CMAKE_CURRENT_LIST_FILE}" PATH) + +MACRO (CHECK_FUNCTION_EXISTS_GLIBC _FUNC _FUNCVAR) + IF(NOT DEFINED ${_FUNCVAR}) + SET(CHECK_STUB_FUNC_1 "__stub_${_FUNC}") + SET(CHECK_STUB_FUNC_2 "__stub___${_FUNC}") + CONFIGURE_FILE( ${_selfdir_CheckFunctionExistsGlibc}/CheckFuncs_stub.c.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake.tmp/CheckFuncs_stub.c IMMEDIATE) + TRY_COMPILE(__stub + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/cmake.tmp/CheckFuncs_stub.c + COMPILE_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} + CMAKE_FLAGS + -DCOMPILE_DEFINITIONS:STRING=${MACRO_CHECK_INCLUDE_FILE_FLAGS} + "${CHECK_INCLUDE_FILE_C_INCLUDE_DIRS}") + IF (__stub) + SET("${_FUNCVAR}" "" CACHE INTERNAL "Have function ${_FUNC}") + ELSE (__stub) + CHECK_FUNCTION_EXISTS("${_FUNC}" "${_FUNCVAR}") + ENDIF (__stub) + ENDIF(NOT DEFINED ${_FUNCVAR}) +ENDMACRO (CHECK_FUNCTION_EXISTS_GLIBC) + diff --git a/build/cmake/CheckFuncs_stub.c.in b/build/cmake/CheckFuncs_stub.c.in new file mode 100644 index 0000000..50da414 --- /dev/null +++ b/build/cmake/CheckFuncs_stub.c.in @@ -0,0 +1,16 @@ +#ifdef __STDC__ +#include +#else +#include +#endif + +int +main() +{ +#if defined ${CHECK_STUB_FUNC_1} || defined ${CHECK_STUB_FUNC_2} + return 0; +#else +this system have stub + return 0; +#endif +} diff --git a/build/cmake/CheckHeaderDirent.cmake b/build/cmake/CheckHeaderDirent.cmake new file mode 100644 index 0000000..e9a7ea8 --- /dev/null +++ b/build/cmake/CheckHeaderDirent.cmake @@ -0,0 +1,32 @@ +# - Check if the system has the specified type +# CHECK_HEADER_DIRENT (HEADER1 HEARDER2 ...) +# +# HEADER - the header(s) where the prototype should be declared +# +# 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 +# Copyright (c) 2009, Michihiro NAKAJIMA +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +INCLUDE(CheckTypeExists) + +MACRO (CHECK_HEADER_DIRENT) + CHECK_TYPE_EXISTS("DIR *" dirent.h HAVE_DIRENT_H) + IF(NOT HAVE_DIRENT_H) + CHECK_TYPE_EXISTS("DIR *" sys/ndir.h HAVE_SYS_NDIR_H) + IF(NOT HAVE_SYS_NDIR_H) + CHECK_TYPE_EXISTS("DIR *" ndir.h HAVE_NDIR_H) + IF(NOT HAVE_NDIR_H) + CHECK_TYPE_EXISTS("DIR *" sys/dir.h HAVE_SYS_DIR_H) + ENDIF(NOT HAVE_NDIR_H) + ENDIF(NOT HAVE_SYS_NDIR_H) + ENDIF(NOT HAVE_DIRENT_H) +ENDMACRO (CHECK_HEADER_DIRENT) + diff --git a/build/cmake/CheckStructMember.cmake b/build/cmake/CheckStructMember.cmake new file mode 100644 index 0000000..05ddb3a --- /dev/null +++ b/build/cmake/CheckStructMember.cmake @@ -0,0 +1,43 @@ +# - Check if the given struct or class has the specified member variable +# CHECK_STRUCT_MEMBER (STRUCT MEMBER HEADER VARIABLE) +# +# STRUCT - the name of the struct or class you are interested in +# MEMBER - the member which existence you want to check +# HEADER - the header(s) where the prototype should be declared +# VARIABLE - variable to store the result +# +# 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 + +# 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) + +MACRO (CHECK_STRUCT_MEMBER _STRUCT _MEMBER _HEADER _RESULT) + SET(_INCLUDE_FILES) + FOREACH (it ${_HEADER}) + SET(_INCLUDE_FILES "${_INCLUDE_FILES}#include <${it}>\n") + ENDFOREACH (it) + + SET(_CHECK_STRUCT_MEMBER_SOURCE_CODE " +${_INCLUDE_FILES} +int main() +{ + static ${_STRUCT} tmp; + if (sizeof(tmp.${_MEMBER})) + return 0; + return 0; +} +") + CHECK_C_SOURCE_COMPILES("${_CHECK_STRUCT_MEMBER_SOURCE_CODE}" ${_RESULT}) + +ENDMACRO (CHECK_STRUCT_MEMBER) + diff --git a/build/cmake/CheckTypeExists.cmake b/build/cmake/CheckTypeExists.cmake new file mode 100644 index 0000000..b05234f --- /dev/null +++ b/build/cmake/CheckTypeExists.cmake @@ -0,0 +1,42 @@ +# - Check if the system has the specified type +# CHECK_TYPE_EXISTS (TYPE HEADER VARIABLE) +# +# TYPE - the name of the type or struct or class you are interested in +# HEADER - the header(s) where the prototype should be declared +# VARIABLE - variable to store the result +# +# 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 +# Copyright (c) 2009, Michihiro NAKAJIMA +# 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) + +MACRO (CHECK_TYPE_EXISTS _TYPE _HEADER _RESULT) + SET(_INCLUDE_FILES) + FOREACH (it ${_HEADER}) + SET(_INCLUDE_FILES "${_INCLUDE_FILES}#include <${it}>\n") + ENDFOREACH (it) + + SET(_CHECK_TYPE_EXISTS_SOURCE_CODE " +${_INCLUDE_FILES} +int main() +{ + static ${_TYPE} tmp; + if (sizeof(tmp)) + return 0; + return 0; +} +") + CHECK_C_SOURCE_COMPILES("${_CHECK_TYPE_EXISTS_SOURCE_CODE}" ${_RESULT}) + +ENDMACRO (CHECK_TYPE_EXISTS) + diff --git a/build/cmake/FindLZMA.cmake b/build/cmake/FindLZMA.cmake new file mode 100644 index 0000000..0b46b2c --- /dev/null +++ b/build/cmake/FindLZMA.cmake @@ -0,0 +1,48 @@ +# - Find lzma and lzmadec +# Find the native LZMA includes and library +# +# LZMA_INCLUDE_DIR - where to find lzma.h, etc. +# LZMA_LIBRARIES - List of libraries when using liblzma. +# LZMA_FOUND - True if liblzma found. +# LZMADEC_INCLUDE_DIR - where to find lzmadec.h, etc. +# LZMADEC_LIBRARIES - List of libraries when using liblzmadec. +# LZMADEC_FOUND - True if liblzmadec found. + +IF (LZMA_INCLUDE_DIR) + # Already in cache, be silent + SET(LZMA_FIND_QUIETLY TRUE) +ENDIF (LZMA_INCLUDE_DIR) + +FIND_PATH(LZMA_INCLUDE_DIR lzma.h) +FIND_LIBRARY(LZMA_LIBRARY NAMES lzma liblzma) + +# handle the QUIETLY and REQUIRED arguments and set LZMA_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(LZMA DEFAULT_MSG LZMA_LIBRARY LZMA_INCLUDE_DIR) + +IF(LZMA_FOUND) + SET( LZMA_LIBRARIES ${LZMA_LIBRARY} ) +ELSE(LZMA_FOUND) + SET( LZMA_LIBRARIES ) + + IF (LZMADEC_INCLUDE_DIR) + # Already in cache, be silent + SET(LZMADEC_FIND_QUIETLY TRUE) + ENDIF (LZMADEC_INCLUDE_DIR) + + FIND_PATH(LZMADEC_INCLUDE_DIR lzmadec.h) + FIND_LIBRARY(LZMADEC_LIBRARY NAMES lzmadec ) + + # handle the QUIETLY and REQUIRED arguments and set LZMADEC_FOUND to TRUE if + # all listed variables are TRUE + INCLUDE(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(LZMADEC DEFAULT_MSG LZMADEC_LIBRARY + LZMADEC_INCLUDE_DIR) + + IF(LZMADEC_FOUND) + SET( LZMADEC_LIBRARIES ${LZMADEC_LIBRARY} ) + ELSE(LZMADEC_FOUND) + SET( LZMADEC_LIBRARIES ) + ENDIF(LZMADEC_FOUND) +ENDIF(LZMA_FOUND) diff --git a/build/cmake/config.h.in b/build/cmake/config.h.in new file mode 100644 index 0000000..b169051 --- /dev/null +++ b/build/cmake/config.h.in @@ -0,0 +1,1101 @@ +/* config.h. Generated from build/cmake/config.h.in by cmake configure */ + +/* + * Ensure we have C99-style int64_t, etc, all defined. + */ + +/* First, we need to know if the system has already defined them. */ +#cmakedefine HAVE_INT16_T +#cmakedefine HAVE_INT32_T +#cmakedefine HAVE_INT64_T +#cmakedefine HAVE_INTMAX_T + +#cmakedefine HAVE_UINT8_T +#cmakedefine HAVE_UINT16_T +#cmakedefine HAVE_UINT32_T +#cmakedefine HAVE_UINT64_T +#cmakedefine HAVE_UINTMAX_T + +/* We might have the types we want under other spellings. */ +#cmakedefine HAVE___INT64 +#cmakedefine HAVE_U_INT64_T +#cmakedefine HAVE_UNSIGNED___INT64 + +/* The sizes of various standard integer types. */ +@SIZE_OF_SHORT_CODE@ +@SIZE_OF_INT_CODE@ +@SIZE_OF_LONG_CODE@ +@SIZE_OF_LONG_LONG_CODE@ +@SIZE_OF_UNSIGNED_SHORT_CODE@ +@SIZE_OF_UNSIGNED_CODE@ +@SIZE_OF_UNSIGNED_LONG_CODE@ +@SIZE_OF_UNSIGNED_LONG_LONG_CODE@ + +/* + * If we lack int64_t, define it to the first of __int64, int, long, and long long + * that exists and is the right size. + */ +#if !defined(HAVE_INT64_T) && defined(HAVE___INT64) +typedef __int64 int64_t; +#define HAVE_INT64_T +#endif + +#if !defined(HAVE_INT64_T) && SIZE_OF_INT == 8 +typedef int int64_t; +#define HAVE_INT64_T +#endif + +#if !defined(HAVE_INT64_T) && SIZE_OF_LONG == 8 +typedef long int64_t; +#define HAVE_INT64_T +#endif + +#if !defined(HAVE_INT64_T) && SIZE_OF_LONG_LONG == 8 +typedef long long int64_t; +#define HAVE_INT64_T +#endif + +#if !defined(HAVE_INT64_T) +#error No 64-bit integer type was found. +#endif + +/* + * Similarly for int32_t + */ +#if !defined(HAVE_INT32_T) && SIZE_OF_INT == 4 +typedef long int32_t; +#define HAVE_INT32_T +#endif + +#if !defined(HAVE_INT32_T) && SIZE_OF_LONG == 4 +typedef long int32_t; +#define HAVE_INT32_T +#endif + +#if !defined(HAVE_INT32_T) +#error No 32-bit integer type was found. +#endif + +/* + * Similarly for int16_t + */ +#if !defined(HAVE_INT16_T) && SIZE_OF_INT == 2 +typedef int int16_t; +#define HAVE_INT16_T +#endif + +#if !defined(HAVE_INT16_T) && SIZE_OF_SHORT == 2 +typedef short int16_t; +#define HAVE_INT16_T +#endif + +#if !defined(HAVE_INT16_T) +#error No 16-bit integer type was found. +#endif + +/* + * Similarly for uint64_t + */ +#if !defined(HAVE_UINT64_T) && defined(HAVE_UNSIGNED___INT64) +typedef unsigned __int64 uint64_t; +#define HAVE_UINT64_T +#endif + +#if !defined(HAVE_UINT64_T) && SIZE_OF_UNSIGNED == 8 +typedef unsigned uint64_t; +#define HAVE_UINT64_T +#endif + +#if !defined(HAVE_UINT64_T) && SIZE_OF_UNSIGNED_LONG == 8 +typedef unsigned long uint64_t; +#define HAVE_UINT64_T +#endif + +#if !defined(HAVE_UINT64_T) && SIZE_OF_UNSIGNED_LONG_LONG == 8 +typedef unsigned long long uint64_t; +#define HAVE_UINT64_T +#endif + +#if !defined(HAVE_UINT64_T) +#error No 64-bit unsigned integer type was found. +#endif + + +/* + * Similarly for uint32_t + */ +#if !defined(HAVE_UINT32_T) && SIZE_OF_UNSIGNED == 4 +typedef unsigned uint32_t; +#define HAVE_UINT32_T +#endif + +#if !defined(HAVE_UINT32_T) && SIZE_OF_UNSIGNED_LONG == 4 +typedef unsigned long uint32_t; +#define HAVE_UINT32_T +#endif + +#if !defined(HAVE_UINT32_T) +#error No 32-bit unsigned integer type was found. +#endif + +/* + * Similarly for uint16_t + */ +#if !defined(HAVE_UINT16_T) && SIZE_OF_UNSIGNED == 2 +typedef unsigned uint16_t; +#define HAVE_UINT16_T +#endif + +#if !defined(HAVE_UINT16_T) && SIZE_OF_UNSIGNED_SHORT == 2 +typedef unsigned short uint16_t; +#define HAVE_UINT16_T +#endif + +#if !defined(HAVE_UINT16_T) +#error No 16-bit unsigned integer type was found. +#endif + +/* + * Similarly for uint8_t + */ +#if !defined(HAVE_UINT8_T) +typedef unsigned char uint8_t; +#define HAVE_UINT8_T +#endif + +#if !defined(HAVE_UINT16_T) +#error No 8-bit unsigned integer type was found. +#endif + +/* Define intmax_t and uintmax_t if they are not already defined. */ +#if !defined(HAVE_INTMAX_T) +typedef int64_t intmax_t; +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#endif + +#if !defined(HAVE_UINTMAX_T) +typedef uint64_t uintmax_t; +#endif + +/* Define ZLIB_WINAPI if zlib was built on Visual Studio. */ +#cmakedefine ZLIB_WINAPI 1 + +/* MD5 via ARCHIVE_CRYPTO_MD5_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_LIBC 1 + +/* MD5 via ARCHIVE_CRYPTO_MD5_LIBSYSTEM supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_LIBSYSTEM 1 + +/* MD5 via ARCHIVE_CRYPTO_MD5_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_NETTLE 1 + +/* MD5 via ARCHIVE_CRYPTO_MD5_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_OPENSSL 1 + +/* MD5 via ARCHIVE_CRYPTO_MD5_WIN supported. */ +#cmakedefine ARCHIVE_CRYPTO_MD5_WIN 1 + +/* RMD160 via ARCHIVE_CRYPTO_RMD160_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_RMD160_LIBC 1 + +/* RMD160 via ARCHIVE_CRYPTO_RMD160_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_RMD160_NETTLE 1 + +/* RMD160 via ARCHIVE_CRYPTO_RMD160_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_RMD160_OPENSSL 1 + +/* SHA1 via ARCHIVE_CRYPTO_SHA1_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_LIBC 1 + +/* SHA1 via ARCHIVE_CRYPTO_SHA1_LIBSYSTEM supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_LIBSYSTEM 1 + +/* SHA1 via ARCHIVE_CRYPTO_SHA1_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_NETTLE 1 + +/* SHA1 via ARCHIVE_CRYPTO_SHA1_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_OPENSSL 1 + +/* SHA1 via ARCHIVE_CRYPTO_SHA1_WIN supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA1_WIN 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_LIBC 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_LIBC2 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_LIBC2 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_LIBC3 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_LIBC3 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_LIBSYSTEM supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_LIBSYSTEM 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_NETTLE 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_OPENSSL 1 + +/* SHA256 via ARCHIVE_CRYPTO_SHA256_WIN supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA256_WIN 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_LIBC 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_LIBC2 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_LIBC2 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_LIBC3 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_LIBC3 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_LIBSYSTEM supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_LIBSYSTEM 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_NETTLE 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_OPENSSL 1 + +/* SHA384 via ARCHIVE_CRYPTO_SHA384_WIN supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA384_WIN 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_LIBC supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_LIBC 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_LIBC2 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_LIBC2 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_LIBC3 supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_LIBC3 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_LIBSYSTEM supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_LIBSYSTEM 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_NETTLE supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_NETTLE 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_OPENSSL supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_OPENSSL 1 + +/* SHA512 via ARCHIVE_CRYPTO_SHA512_WIN supported. */ +#cmakedefine ARCHIVE_CRYPTO_SHA512_WIN 1 + +/* Version number of bsdcpio */ +#cmakedefine BSDCPIO_VERSION_STRING "${BSDCPIO_VERSION_STRING}" + +/* Version number of bsdtar */ +#cmakedefine BSDTAR_VERSION_STRING "${BSDTAR_VERSION_STRING}" + +/* Define to 1 if you have the `acl_create_entry' function. */ +#cmakedefine HAVE_ACL_CREATE_ENTRY 1 + +/* Define to 1 if you have the `acl_get_link' function. */ +#cmakedefine HAVE_ACL_GET_LINK 1 + +/* Define to 1 if you have the `acl_get_link_np' function. */ +#cmakedefine HAVE_ACL_GET_LINK_NP 1 + +/* Define to 1 if you have the `acl_get_perm' function. */ +#cmakedefine HAVE_ACL_GET_PERM 1 + +/* Define to 1 if you have the `acl_get_perm_np' function. */ +#cmakedefine HAVE_ACL_GET_PERM_NP 1 + +/* Define to 1 if you have the `acl_init' function. */ +#cmakedefine HAVE_ACL_INIT 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ACL_LIBACL_H 1 + +/* Define to 1 if the system has the type `acl_permset_t'. */ +#cmakedefine HAVE_ACL_PERMSET_T 1 + +/* Define to 1 if you have the `acl_set_fd' function. */ +#cmakedefine HAVE_ACL_SET_FD 1 + +/* Define to 1 if you have the `acl_set_fd_np' function. */ +#cmakedefine HAVE_ACL_SET_FD_NP 1 + +/* Define to 1 if you have the `acl_set_file' function. */ +#cmakedefine HAVE_ACL_SET_FILE 1 + +/* True for systems with POSIX ACL support */ +#cmakedefine HAVE_ACL_USER 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ATTR_XATTR_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_BSDXML_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_BZLIB_H 1 + +/* Define to 1 if you have the `chflags' function. */ +#cmakedefine HAVE_CHFLAGS 1 + +/* Define to 1 if you have the `chown' function. */ +#cmakedefine HAVE_CHOWN 1 + +/* Define to 1 if you have the `chroot' function. */ +#cmakedefine HAVE_CHROOT 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_COPYFILE_H 1 + +/* Define to 1 if you have the `ctime_r' function. */ +#cmakedefine HAVE_CTIME_R 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_CTYPE_H 1 + +/* Define to 1 if you have the `cygwin_conv_path' function. */ +#cmakedefine HAVE_CYGWIN_CONV_PATH 1 + +/* Define to 1 if you have the declaration of `INT64_MAX', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_INT64_MAX 1 + +/* Define to 1 if you have the declaration of `INT64_MIN', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_INT64_MIN 1 + +/* Define to 1 if you have the declaration of `SIZE_MAX', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_SIZE_MAX 1 + +/* Define to 1 if you have the declaration of `SSIZE_MAX', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_SSIZE_MAX 1 + +/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_STRERROR_R 1 + +/* Define to 1 if you have the declaration of `UINT32_MAX', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_UINT32_MAX 1 + +/* Define to 1 if you have the declaration of `UINT64_MAX', and to 0 if you + don't. */ +#cmakedefine HAVE_DECL_UINT64_MAX 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DIRECT_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#cmakedefine HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_DLFCN_H 1 + +/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */ +#cmakedefine HAVE_DOPRNT 1 + +/* Define to 1 if nl_langinfo supports D_MD_ORDER */ +#cmakedefine HAVE_D_MD_ORDER 1 + +/* A possible errno value for invalid file format errors */ +#cmakedefine HAVE_EFTYPE 1 + +/* A possible errno value for invalid file format errors */ +#cmakedefine HAVE_EILSEQ 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_EXPAT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_EXT2FS_EXT2_FS_H 1 + +/* Define to 1 if you have the `extattr_get_file' function. */ +#cmakedefine HAVE_EXTATTR_GET_FILE 1 + +/* Define to 1 if you have the `extattr_list_file' function. */ +#cmakedefine HAVE_EXTATTR_LIST_FILE 1 + +/* Define to 1 if you have the `extattr_set_fd' function. */ +#cmakedefine HAVE_EXTATTR_SET_FD 1 + +/* Define to 1 if you have the `extattr_set_file' function. */ +#cmakedefine HAVE_EXTATTR_SET_FILE 1 + +/* Define to 1 if EXTATTR_NAMESPACE_USER is defined in sys/extattr.h. */ +#cmakedefine HAVE_DECL_EXTATTR_NAMESPACE_USER 1 + +/* Define to 1 if you have the `fchdir' function. */ +#cmakedefine HAVE_FCHDIR 1 + +/* Define to 1 if you have the `fchflags' function. */ +#cmakedefine HAVE_FCHFLAGS 1 + +/* Define to 1 if you have the `fchmod' function. */ +#cmakedefine HAVE_FCHMOD 1 + +/* Define to 1 if you have the `fchown' function. */ +#cmakedefine HAVE_FCHOWN 1 + +/* Define to 1 if you have the `fcntl' function. */ +#cmakedefine HAVE_FCNTL 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fdopendir' function. */ +#cmakedefine HAVE_FDOPENDIR 1 + +/* Define to 1 if you have the `fgetea' function. */ +#cmakedefine HAVE_FGETEA 1 + +/* Define to 1 if you have the `fgetxattr' function. */ +#cmakedefine HAVE_FGETXATTR 1 + +/* Define to 1 if you have the `flistea' function. */ +#cmakedefine HAVE_FLISTEA 1 + +/* Define to 1 if you have the `flistxattr' function. */ +#cmakedefine HAVE_FLISTXATTR 1 + +/* Define to 1 if you have the `fork' function. */ +#cmakedefine HAVE_FORK 1 + +/* Define to 1 if fseeko (and presumably ftello) exists and is declared. */ +#cmakedefine HAVE_FSEEKO 1 + +/* Define to 1 if you have the `fsetea' function. */ +#cmakedefine HAVE_FSETEA 1 + +/* Define to 1 if you have the `fsetxattr' function. */ +#cmakedefine HAVE_FSETXATTR 1 + +/* Define to 1 if you have the `fstat' function. */ +#cmakedefine HAVE_FSTAT 1 + +/* Define to 1 if you have the `fstatat' function. */ +#cmakedefine HAVE_FSTATAT 1 + +/* Define to 1 if you have the `fstatfs' function. */ +#cmakedefine HAVE_FSTATFS 1 + +/* Define to 1 if you have the `fstatvfs' function. */ +#cmakedefine HAVE_FSTATVFS 1 + +/* Define to 1 if you have the `ftruncate' function. */ +#cmakedefine HAVE_FTRUNCATE 1 + +/* Define to 1 if you have the `futimens' function. */ +#cmakedefine HAVE_FUTIMENS 1 + +/* Define to 1 if you have the `futimes' function. */ +#cmakedefine HAVE_FUTIMES 1 + +/* Define to 1 if you have the `futimesat' function. */ +#cmakedefine HAVE_FUTIMESAT 1 + +/* Define to 1 if you have the `getea' function. */ +#cmakedefine HAVE_GETEA 1 + +/* Define to 1 if you have the `geteuid' function. */ +#cmakedefine HAVE_GETEUID 1 + +/* Define to 1 if you have the `getgrgid_r' function. */ +#cmakedefine HAVE_GETGRGID_R 1 + +/* Define to 1 if you have the `getgrnam_r' function. */ +#cmakedefine HAVE_GETGRNAM_R 1 + +/* Define to 1 if you have the `getpid' function. */ +#cmakedefine HAVE_GETPID 1 + +/* Define to 1 if you have the `getpwnam_r' function. */ +#cmakedefine HAVE_GETPWNAM_R 1 + +/* Define to 1 if you have the `getpwuid_r' function. */ +#cmakedefine HAVE_GETPWUID_R 1 + +/* Define to 1 if you have the `getvfsbyname' function. */ +#cmakedefine HAVE_GETVFSBYNAME 1 + +/* Define to 1 if you have the `getxattr' function. */ +#cmakedefine HAVE_GETXATTR 1 + +/* Define to 1 if you have the `gmtime_r' function. */ +#cmakedefine HAVE_GMTIME_R 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_GRP_H 1 + +/* Define to 1 if you have the `iconv' function. */ +#cmakedefine HAVE_ICONV 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ICONV_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IO_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LANGINFO_H 1 + +/* Define to 1 if you have the `lchflags' function. */ +#cmakedefine HAVE_LCHFLAGS 1 + +/* Define to 1 if you have the `lchmod' function. */ +#cmakedefine HAVE_LCHMOD 1 + +/* Define to 1 if you have the `lchown' function. */ +#cmakedefine HAVE_LCHOWN 1 + +/* Define to 1 if you have the `lgetea' function. */ +#cmakedefine HAVE_LGETEA 1 + +/* Define to 1 if you have the `lgetxattr' function. */ +#cmakedefine HAVE_LGETXATTR 1 + +/* Define to 1 if you have the `acl' library (-lacl). */ +#cmakedefine HAVE_LIBACL 1 + +/* Define to 1 if you have the `attr' library (-lattr). */ +#cmakedefine HAVE_LIBATTR 1 + +/* Define to 1 if you have the `bsdxml' library (-lbsdxml). */ +#cmakedefine HAVE_LIBBSDXML 1 + +/* Define to 1 if you have the `bz2' library (-lbz2). */ +#cmakedefine HAVE_LIBBZ2 1 + +/* Define to 1 if you have the `expat' library (-lexpat). */ +#cmakedefine HAVE_LIBEXPAT 1 + +/* Define to 1 if you have the `lzma' library (-llzma). */ +#cmakedefine HAVE_LIBLZMA 1 + +/* Define to 1 if you have the `lzmadec' library (-llzmadec). */ +#cmakedefine HAVE_LIBLZMADEC 1 + +/* Define to 1 if you have the `xml2' library (-lxml2). */ +#cmakedefine HAVE_LIBXML2 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIBXML_XMLREADER_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIBXML_XMLWRITER_H 1 + +/* Define to 1 if you have the `z' library (-lz). */ +#cmakedefine HAVE_LIBZ 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIMITS_H 1 + +/* Define to 1 if you have the `link' function. */ +#cmakedefine HAVE_LINK 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_FIEMAP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_FS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LINUX_MAGIC_H 1 + +/* Define to 1 if you have the `listea' function. */ +#cmakedefine HAVE_LISTEA 1 + +/* Define to 1 if you have the `listxattr' function. */ +#cmakedefine HAVE_LISTXATTR 1 + +/* Define to 1 if you have the `llistea' function. */ +#cmakedefine HAVE_LLISTEA 1 + +/* Define to 1 if you have the `llistxattr' function. */ +#cmakedefine HAVE_LLISTXATTR 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LOCALCHARSET_H 1 + +/* Define to 1 if you have the `locale_charset' function. */ +#cmakedefine HAVE_LOCALE_CHARSET 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LOCALE_H 1 + +/* Define to 1 if you have the `localtime_r' function. */ +#cmakedefine HAVE_LOCALTIME_R 1 + +/* Define to 1 if the system has the type `long long int'. */ +#cmakedefine HAVE_LONG_LONG_INT 1 + +/* Define to 1 if you have the `lsetea' function. */ +#cmakedefine HAVE_LSETEA 1 + +/* Define to 1 if you have the `lsetxattr' function. */ +#cmakedefine HAVE_LSETXATTR 1 + +/* Define to 1 if you have the `lstat' function. */ +#cmakedefine HAVE_LSTAT 1 + +/* Define to 1 if `lstat' has the bug that it succeeds when given the + zero-length file name argument. */ +#cmakedefine HAVE_LSTAT_EMPTY_STRING_BUG 1 + +/* Define to 1 if you have the `lutimes' function. */ +#cmakedefine HAVE_LUTIMES 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LZMADEC_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LZMA_H 1 + +/* Define to 1 if you have the `mbrtowc' function. */ +#cmakedefine HAVE_MBRTOWC 1 + +/* Define to 1 if you have the `mbsnrtowcs' function. */ +#cmakedefine HAVE_MBSNRTOWCS 1 + +/* Define to 1 if you have the `memmove' function. */ +#cmakedefine HAVE_MEMMOVE 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkdir' function. */ +#cmakedefine HAVE_MKDIR 1 + +/* Define to 1 if you have the `mkfifo' function. */ +#cmakedefine HAVE_MKFIFO 1 + +/* Define to 1 if you have the `mknod' function. */ +#cmakedefine HAVE_MKNOD 1 + +/* Define to 1 if you have the `mkstemp' function. */ +#cmakedefine HAVE_MKSTEMP 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +#cmakedefine HAVE_NDIR_H 1 + +/* Define to 1 if you have the `nl_langinfo' function. */ +#cmakedefine HAVE_NL_LANGINFO 1 + +/* Define to 1 if you have the `openat' function. */ +#cmakedefine HAVE_OPENAT 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PATHS_H 1 + +/* Define to 1 if you have the `pipe' function. */ +#cmakedefine HAVE_PIPE 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PROCESS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PWD_H 1 + +/* Define to 1 if you have the `readdir_r' function. */ +#cmakedefine HAVE_READDIR_R 1 + +/* Define to 1 if you have the `readlink' function. */ +#cmakedefine HAVE_READLINK 1 + +/* Define to 1 if you have the `readlinkat' function. */ +#cmakedefine HAVE_READLINKAT 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_REGEX_H 1 + +/* Define to 1 if you have the `select' function. */ +#cmakedefine HAVE_SELECT 1 + +/* Define to 1 if you have the `setenv' function. */ +#cmakedefine HAVE_SETENV 1 + +/* Define to 1 if you have the `setlocale' function. */ +#cmakedefine HAVE_SETLOCALE 1 + +/* Define to 1 if you have the `sigaction' function. */ +#cmakedefine HAVE_SIGACTION 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the `statfs' function. */ +#cmakedefine HAVE_STATFS 1 + +/* Define to 1 if you have the `statvfs' function. */ +#cmakedefine HAVE_STATVFS 1 + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +#cmakedefine HAVE_STAT_EMPTY_STRING_BUG 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDARG_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strchr' function. */ +#cmakedefine HAVE_STRCHR 1 + +/* Define to 1 if you have the `strdup' function. */ +#cmakedefine HAVE_STRDUP 1 + +/* Define to 1 if you have the `strerror' function. */ +#cmakedefine HAVE_STRERROR 1 + +/* Define to 1 if you have the `strerror_r' function. */ +#cmakedefine HAVE_STRERROR_R 1 + +/* Define to 1 if you have the `strftime' function. */ +#cmakedefine HAVE_STRFTIME 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STRING_H 1 + +/* Define to 1 if you have the `strrchr' function. */ +#cmakedefine HAVE_STRRCHR 1 + +/* Define to 1 if `f_namemax' is a member of `struct statfs'. */ +#cmakedefine HAVE_STRUCT_STATFS_F_NAMEMAX 1 + +/* Define to 1 if `st_birthtime' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_BIRTHTIME 1 + +/* Define to 1 if `st_birthtimespec.tv_nsec' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC 1 + +/* Define to 1 if `st_blksize' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_BLKSIZE 1 + +/* Define to 1 if `st_flags' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_FLAGS 1 + +/* Define to 1 if `st_mtimespec.tv_nsec' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 + +/* Define to 1 if `st_mtime_n' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_MTIME_N 1 + +/* Define to 1 if `st_mtime_usec' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_MTIME_USEC 1 + +/* Define to 1 if `st_mtim.tv_nsec' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC 1 + +/* Define to 1 if `st_umtime' is a member of `struct stat'. */ +#cmakedefine HAVE_STRUCT_STAT_ST_UMTIME 1 + +/* Define to 1 if `tm_gmtoff' is a member of `struct tm'. */ +#cmakedefine HAVE_STRUCT_TM_TM_GMTOFF 1 + +/* Define to 1 if `__tm_gmtoff' is a member of `struct tm'. */ +#cmakedefine HAVE_STRUCT_TM___TM_GMTOFF 1 + +/* Define to 1 if you have the `symlink' function. */ +#cmakedefine HAVE_SYMLINK 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_ACL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_CDEFS_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#cmakedefine HAVE_SYS_DIR_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_EA_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_EXTATTR_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_MKDEV_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_MOUNT_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#cmakedefine HAVE_SYS_NDIR_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STATFS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STATVFS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTSNAME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_VFS_H 1 + +/* Define to 1 if you have that is POSIX.1 compatible. */ +#cmakedefine HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_XATTR_H 1 + +/* Define to 1 if you have the `timegm' function. */ +#cmakedefine HAVE_TIMEGM 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TIME_H 1 + +/* Define to 1 if you have the `tzset' function. */ +#cmakedefine HAVE_TZSET 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `unsetenv' function. */ +#cmakedefine HAVE_UNSETENV 1 + +/* Define to 1 if the system has the type `unsigned long long'. */ +#cmakedefine HAVE_UNSIGNED_LONG_LONG 1 + +/* Define to 1 if the system has the type `unsigned long long int'. */ +#cmakedefine HAVE_UNSIGNED_LONG_LONG_INT 1 + +/* Define to 1 if you have the `utime' function. */ +#cmakedefine HAVE_UTIME 1 + +/* Define to 1 if you have the `utimensat' function. */ +#cmakedefine HAVE_UTIMENSAT 1 + +/* Define to 1 if you have the `utimes' function. */ +#cmakedefine HAVE_UTIMES 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTIME_H 1 + +/* Define to 1 if you have the `vfork' function. */ +#cmakedefine HAVE_VFORK 1 + +/* Define to 1 if you have the `vprintf' function. */ +#cmakedefine HAVE_VPRINTF 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WCHAR_H 1 + +/* Define to 1 if the system has the type `wchar_t'. */ +#cmakedefine HAVE_WCHAR_T 1 + +/* Define to 1 if you have the `wcrtomb' function. */ +#cmakedefine HAVE_WCRTOMB 1 + +/* Define to 1 if you have the `wcscmp' function. */ +#cmakedefine HAVE_WCSCMP 1 + +/* Define to 1 if you have the `wcscpy' function. */ +#cmakedefine HAVE_WCSCPY 1 + +/* Define to 1 if you have the `wcslen' function. */ +#cmakedefine HAVE_WCSLEN 1 + +/* Define to 1 if you have the `wcsnrtombs' function. */ +#cmakedefine HAVE_WCSNRTOMBS 1 + +/* Define to 1 if you have the `wctomb' function. */ +#cmakedefine HAVE_WCTOMB 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WCTYPE_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINCRYPT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINDOWS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WINIOCTL_H 1 + +/* Define to 1 if you have _CrtSetReportMode in */ +#cmakedefine HAVE__CrtSetReportMode 1 + +/* Define to 1 if you have the `wmemcmp' function. */ +#cmakedefine HAVE_WMEMCMP 1 + +/* Define to 1 if you have the `wmemcpy' function. */ +#cmakedefine HAVE_WMEMCPY 1 + +/* Define to 1 if you have a working EXT2_IOC_GETFLAGS */ +#cmakedefine HAVE_WORKING_EXT2_IOC_GETFLAGS 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ZLIB_H 1 + +/* Define to 1 if you have the `_ctime64_s' function. */ +#cmakedefine HAVE__CTIME64_S 1 + +/* Define to 1 if you have the `_fseeki64' function. */ +#cmakedefine HAVE__FSEEKI64 1 + +/* Define to 1 if you have the `_get_timezone' function. */ +#cmakedefine HAVE__GET_TIMEZONE 1 + +/* Define to 1 if you have the `_localtime64_s' function. */ +#cmakedefine HAVE__LOCALTIME64_S 1 + +/* Define to 1 if you have the `_mkgmtime64' function. */ +#cmakedefine HAVE__MKGMTIME64 1 + +/* Define as const if the declaration of iconv() needs const. */ +#define ICONV_CONST ${ICONV_CONST} + +/* Version number of libarchive as a single integer */ +#cmakedefine LIBARCHIVE_VERSION_NUMBER "${LIBARCHIVE_VERSION_NUMBER}" + +/* Version number of libarchive */ +#cmakedefine LIBARCHIVE_VERSION_STRING "${LIBARCHIVE_VERSION_STRING}" + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +#cmakedefine LSTAT_FOLLOWS_SLASHED_SYMLINK 1 + +/* Define to 1 if `major', `minor', and `makedev' are declared in . + */ +#cmakedefine MAJOR_IN_MKDEV 1 + +/* Define to 1 if `major', `minor', and `makedev' are declared in + . */ +#cmakedefine MAJOR_IN_SYSMACROS 1 + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +#cmakedefine NO_MINUS_C_MINUS_O 1 + +/* The size of `wchar_t', as computed by sizeof. */ +#cmakedefine SIZEOF_WCHAR_T ${SIZEOF_WCHAR_T} + +/* Define to 1 if strerror_r returns char *. */ +#cmakedefine STRERROR_R_CHAR_P 1 + +/* Define to 1 if you can safely include both and . */ +#cmakedefine TIME_WITH_SYS_TIME 1 + +/* + * Some platform requires a macro to use extension functions. + */ +#cmakedefine SAFE_TO_DEFINE_EXTENSIONS 1 +#ifdef SAFE_TO_DEFINE_EXTENSIONS +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# define _ALL_SOURCE 1 +#endif +/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# define _POSIX_PTHREAD_SEMANTICS 1 +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# define _TANDEM_SOURCE 1 +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# define __EXTENSIONS__ 1 +#endif +#endif /* SAFE_TO_DEFINE_EXTENSIONS */ + +/* Version number of package */ +#cmakedefine VERSION "${VERSION}" + +/* Number of bits in a file offset, on hosts where this is settable. */ +#cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} + +/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */ +#cmakedefine _LARGEFILE_SOURCE 1 + +/* Define for large files, on AIX-style hosts. */ +#cmakedefine _LARGE_FILES ${_LARGE_FILES} + +/* Define for Windows to use Windows 2000+ APIs. */ +#cmakedefine _WIN32_WINNT ${_WIN32_WINNT} +#cmakedefine WINVER ${WINVER} + +/* Define to empty if `const' does not conform to ANSI C. */ +#cmakedefine const ${const} + +/* Define to `int' if doesn't define. */ +#cmakedefine gid_t ${gid_t} + +/* Define to `unsigned long' if does not define. */ +#cmakedefine id_t ${id_t} + +/* Define to `int' if does not define. */ +#cmakedefine mode_t ${mode_t} + +/* Define to `long long' if does not define. */ +#cmakedefine off_t ${off_t} + +/* Define to `int' if doesn't define. */ +#cmakedefine pid_t ${pid_t} + +/* Define to `unsigned int' if does not define. */ +#cmakedefine size_t ${size_t} + +/* Define to `int' if does not define. */ +#cmakedefine ssize_t ${ssize_t} + +/* Define to `int' if doesn't define. */ +#cmakedefine uid_t ${uid_t} + +/* Define to `int' if does not define. */ +#cmakedefine intptr_t ${intptr_t} + +/* Define to `unsigned int' if does not define. */ +#cmakedefine uintptr_t ${uintptr_t} diff --git a/build/pkgconfig/libarchive.pc.in b/build/pkgconfig/libarchive.pc.in new file mode 100644 index 0000000..95d7159 --- /dev/null +++ b/build/pkgconfig/libarchive.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libarchive +Description: library that can create and read several streaming archive formats +Version: @VERSION@ +Cflags: -I${includedir} +Libs: -L${libdir} -larchive +Libs.private: @LIBS@ diff --git a/build/utils/gen_archive_string_composition_h.sh b/build/utils/gen_archive_string_composition_h.sh new file mode 100644 index 0000000..95dbe16 --- /dev/null +++ b/build/utils/gen_archive_string_composition_h.sh @@ -0,0 +1,418 @@ +#!/bin/sh +# +# This needs http://unicode.org/Public/UNIDATA/UnicodeData.txt +# +inputfile="$1" # Expect UnicodeData.txt +outfile=archive_string_composition.h +pickout=/tmp/mk_unicode_composition_tbl$$.awk +################################################################################# +# +# Append the file header of "archive_string_composition.h" +# +################################################################################# +append_copyright() +{ +cat > ${outfile} < ${pickout} <>8) <= 0x%X && u_decomposable_blocks[(uc)>>8])\\n", highnum + printf "static const char u_decomposable_blocks[0x%X+1] = {\\n\\t", highnum + # + # Output blockmap + for (i = 0; i <= highnum; i++) { + if (i != 0 && i % 32 == 0) + printf "\\n\\t" + # Additionally Hangul[11XX(17), AC00(172) - D7FF(215)] is decomposable. + if (blockmap[i] || i == 17 || (i >= 172 && i <= 215)) + printf "1," + else + printf "0," + } + printf "\\n};\\n\\n" + # + # Output a macro to get a canonical combining class. + # + print "/* Get Canonical Combining Class(CCC). */" + printf "#define CCC(uc)\\t\\\\\n" + printf "\\t(((uc) > 0x%s)?0:\\\\\\n", max + printf "\\tccc_val[ccc_val_index[ccc_index[(uc)>>8]][((uc)>>4)&0x0F]][(uc)&0x0F])\\n" + print "" + # + # Output a canonical combining class value table. + # + midcnt = 0 + printf "/* The table of the value of Canonical Cimbining Class */\\n" + print "static const unsigned char ccc_val[][16] = {" + print " /* idx=0: XXXX0 - XXXXF */" + print " { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }," + for (h = 0; h <= highnum; h++) { + if (!blockmap[h]) + continue; + for (m = 0; m < 16; m++) { + if (!xx_blockmap[h, m]) + continue; + midcnt++ + printf " /* idx=%d: %03X%1X0 - %03X%1XF */\\n {", midcnt, h, m, h, m + for (l = 0; l < 15; l++) { + printf "%d, ", xxx_blockmap[h, m, l] + } + printf "%d },\n", xxx_blockmap[h, m, 15] + } + } + printf "};\n" + # + # Output the index table of the canonical combining class value table. + # + cnt = 0 + midcnt = 0 + printf "\\n/* The index table to ccc_val[*][16] */\\n" + print "static const unsigned char ccc_val_index[][16] = {" + print " /* idx=0: XXX00 - XXXFF */" + print " { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }," + for (h = 0; h <= highnum; h++) { + if (!blockmap[h]) + continue; + cnt++ + printf " /* idx=%d: %03X00 - %03XFF */\\n {", cnt, h, h + for (m = 0; m < 16; m++) { + if (m != 0) + printf "," + if (xx_blockmap[h, m]) { + midcnt++ + printf "%2d", midcnt + } else + printf " 0" + } + printf " },\\n" + } + printf "};\\n" + # + # Output the index table to the index table of the canonical combining + # class value table. + # + printf "\\n/* The index table to ccc_val_index[*][16] */\\n" + printf "static const unsigned char ccc_index[] = {\\n ", h + cnt = 0 + for (h = 0; h <= highnum; h++) { + if (h != 0 && h % 24 == 0) + printf "\\n " + if (blockmap[h]) { + cnt++; + printf "%2d,", cnt + } else + printf " 0," + } + print "};" + print "" + print "#endif /* ARCHIVE_STRING_COMPOSITION_H_INCLUDED */" +} +# +# +function hextoi(hex) +{ + dec = 0 + for (i=0; i < length(hex); i++) { + x = substr(hex, i+1, 1) + if (x ~/[0-9]/) + dec = dec * 16 + x; + else if (x == "A") + dec = dec * 16 + 10; + else if (x == "B") + dec = dec * 16 + 11; + else if (x == "C") + dec = dec * 16 + 12; + else if (x == "D") + dec = dec * 16 + 13; + else if (x == "E") + dec = dec * 16 + 14; + else if (x == "F") + dec = dec * 16 + 15; + } + return dec +} +# +# Collect Canonical Combining Class values. +# +\$4 ~/^[0-9A-F]+$/ { + if (\$4 !~/^0$/) { + if (min == "") { + min = \$1 + } + max = \$1 + high = substr(\$1, 1, length(\$1) -2) + highnum = hextoi(high) + mid = substr(\$1, length(\$1) -1, 1) + midnum = hextoi(mid) + low = substr(\$1, length(\$1), 1) + lownum = hextoi(low) + blockmap[highnum] = 1 + xx_blockmap[highnum, midnum] = 1 + xxx_blockmap[highnum, midnum, lownum] = \$4 + } +} +# +# Following code points are not decomposed in MAC OS. +# U+2000 - U+2FFF +# U+F900 - U+FAFF +# U+2F800 - U+2FAFF +# +#\$1 ~/^2[0-9A-F][0-9A-F][0-9A-F]\$/ { +# next +#} +#\$1 ~/^F[9A][0-9A-F][0-9A-F]\$/ { +# next +#} +#\$1 ~/^2F[89A][0-9A-F][0-9A-F]\$/ { +# next +#} +# +# Exclusion code points specified by +# http://unicode.org/Public/UNIDATA/CompositionExclusions.txt +## +# 1. Script Specifices +## +\$1 ~/^095[89ABCDEF]\$/ { + next +} +\$1 ~/^09D[CDF]\$/ { + next +} +\$1 ~/^0A3[36]\$/ { + next +} +\$1 ~/^0A5[9ABE]\$/ { + next +} +\$1 ~/^0B5[CD]\$/ { + next +} +\$1 ~/^0F4[3D]\$/ { + next +} +\$1 ~/^0F5[27C]\$/ { + next +} +\$1 ~/^0F69\$/ { + next +} +\$1 ~/^0F7[68]\$/ { + next +} +\$1 ~/^0F9[3D]\$/ { + next +} +\$1 ~/^0FA[27C]\$/ { + next +} +\$1 ~/^0FB9\$/ { + next +} +\$1 ~/^FB1[DF]\$/ { + next +} +\$1 ~/^FB2[ABCDEF]\$/ { + next +} +\$1 ~/^FB3[012345689ABCE]\$/ { + next +} +\$1 ~/^FB4[01346789ABCDE]\$/ { + next +} +## +# 2. Post Composition Version precomposed characters +## +\$1 ~/^2ADC\$/ { + next +} +\$1 ~/^1D15[EF]\$/ { + next +} +\$1 ~/^1D16[01234]\$/ { + next +} +\$1 ~/^1D1B[BCDEF]\$/ { + next +} +\$1 ~/^1D1C0\$/ { + next +} +## +# 3. Singleton Decompositions +## +\$1 ~/^034[01]\$/ { + next +} +\$1 ~/^037[4E]\$/ { + next +} +\$1 ~/^0387\$/ { + next +} +\$1 ~/^1F7[13579BD]\$/ { + next +} +\$1 ~/^1FB[BE]\$/ { + next +} +\$1 ~/^1FC[9B]\$/ { + next +} +\$1 ~/^1FD[3B]\$/ { + next +} +\$1 ~/^1FE[3BEF]\$/ { + next +} +\$1 ~/^1FF[9BD]\$/ { + next +} +\$1 ~/^200[01]\$/ { + next +} +\$1 ~/^212[6AB]\$/ { + next +} +\$1 ~/^232[9A]\$/ { + next +} +\$1 ~/^F9[0-9A-F][0-9A-F]\$/ { + next +} +\$1 ~/^FA0[0-9A-D]\$/ { + next +} +\$1 ~/^FA1[025-9A-E]\$/ { + next +} +\$1 ~/^FA2[0256A-D]\$/ { + next +} +\$1 ~/^FA[3-5][0-9A-F]\$/ { + next +} +\$1 ~/^FA6[0-9A-D]\$/ { + next +} +\$1 ~/^FA[7-9A-C][0-9A-F]\$/ { + next +} +\$1 ~/^FAD[0-9]\$/ { + next +} +\$1 ~/^2F[89][0-9A-F][0-9A-F]\$/ { + next +} +\$1 ~/^2FA0[0-9A-F]\$/ { + next +} +\$1 ~/^2FA1[0-9A-D]\$/ { + next +} +## +# 4. Non-Starter Decompositions +## +\$1 ~/^0344\$/ { + next +} +\$1 ~/^0F7[35]\$/ { + next +} +\$1 ~/^0F81\$/ { + next +} +# +# Output combinations for NFD ==> NFC. +# +\$6 ~/^[0-9A-F]+ [0-9A-F]+\$/ { + split(\$6, cp, " ") + if (length(\$1) == 4) + print "0"cp[1], "0"cp[2], "0"\$1 | cmd + else + print cp[1], cp[2], \$1 | cmd +} +AWK_END +################################################################################# +# +# Run awk a script. +# +################################################################################# +append_copyright +awk -f ${pickout} ${inputfile} >> ${outfile} +# +# Remove awk the script. +rm ${pickout} diff --git a/build/version b/build/version new file mode 100644 index 0000000..6ff875b --- /dev/null +++ b/build/version @@ -0,0 +1 @@ +3000001b diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt new file mode 100644 index 0000000..a801fb2 --- /dev/null +++ b/libarchive/CMakeLists.txt @@ -0,0 +1,172 @@ + +############################################ +# +# How to build libarchive +# +############################################ + +# Public headers +SET(include_HEADERS + archive.h + archive_entry.h +) + +# Sources and private headers +SET(libarchive_SOURCES + archive_acl.c + archive_check_magic.c + archive_crypto.c + archive_crypto_private.h + archive_endian.h + archive_entry.c + archive_entry.h + archive_entry_copy_stat.c + archive_entry_link_resolver.c + archive_entry_locale.h + archive_entry_private.h + archive_entry_sparse.c + archive_entry_stat.c + archive_entry_strmode.c + archive_entry_xattr.c + archive_options.c + archive_options_private.h + archive_platform.h + archive_ppmd_private.h + archive_ppmd7.c + archive_ppmd7_private.h + archive_private.h + archive_rb.c + archive_rb.h + archive_read.c + archive_read_data_into_fd.c + archive_read_disk_entry_from_file.c + archive_read_disk_posix.c + archive_read_disk_private.h + archive_read_disk_set_standard_lookup.c + archive_read_extract.c + archive_read_open_fd.c + archive_read_open_file.c + archive_read_open_filename.c + archive_read_open_memory.c + archive_read_private.h + archive_read_set_options.c + archive_read_support_filter_all.c + archive_read_support_filter_bzip2.c + archive_read_support_filter_compress.c + archive_read_support_filter_gzip.c + archive_read_support_filter_none.c + archive_read_support_filter_program.c + archive_read_support_filter_rpm.c + archive_read_support_filter_uu.c + archive_read_support_filter_xz.c + archive_read_support_format_7zip.c + archive_read_support_format_all.c + archive_read_support_format_ar.c + archive_read_support_format_by_code.c + archive_read_support_format_cab.c + archive_read_support_format_cpio.c + archive_read_support_format_empty.c + archive_read_support_format_iso9660.c + archive_read_support_format_lha.c + archive_read_support_format_mtree.c + archive_read_support_format_rar.c + archive_read_support_format_raw.c + archive_read_support_format_tar.c + archive_read_support_format_xar.c + archive_read_support_format_zip.c + archive_string.c + archive_string.h + archive_string_composition.h + archive_string_sprintf.c + archive_util.c + archive_virtual.c + archive_write.c + archive_write_disk_posix.c + archive_write_disk_private.h + archive_write_disk_set_standard_lookup.c + archive_write_private.h + archive_write_open_fd.c + archive_write_open_file.c + archive_write_open_filename.c + archive_write_open_memory.c + archive_write_add_filter_bzip2.c + archive_write_add_filter_compress.c + archive_write_add_filter_gzip.c + archive_write_add_filter_none.c + archive_write_add_filter_program.c + archive_write_add_filter_xz.c + archive_write_set_format.c + archive_write_set_format_7zip.c + archive_write_set_format_ar.c + archive_write_set_format_by_name.c + archive_write_set_format_cpio.c + archive_write_set_format_cpio_newc.c + archive_write_set_format_gnutar.c + archive_write_set_format_iso9660.c + archive_write_set_format_mtree.c + archive_write_set_format_pax.c + archive_write_set_format_shar.c + archive_write_set_format_ustar.c + archive_write_set_format_xar.c + archive_write_set_format_zip.c + archive_write_set_options.c + filter_fork.c + filter_fork.h +) + +# Man pages +SET(libarchive_MANS + archive_entry.3 + archive_entry_acl.3 + archive_entry_linkify.3 + archive_entry_paths.3 + archive_entry_perms.3 + archive_entry_stat.3 + archive_entry_time.3 + archive_read.3 + archive_read_disk.3 + archive_read_set_options.3 + archive_util.3 + archive_write.3 + archive_write_disk.3 + archive_write_set_options.3 + cpio.5 + libarchive.3 + libarchive_internals.3 + libarchive-formats.5 + mtree.5 + tar.5 +) + +IF(WIN32 AND NOT CYGWIN) + LIST(APPEND libarchive_SOURCES archive_entry_copy_bhfi.c) + LIST(APPEND libarchive_SOURCES archive_read_disk_windows.c) + LIST(APPEND libarchive_SOURCES archive_windows.c) + LIST(APPEND libarchive_SOURCES archive_windows.h) + LIST(APPEND libarchive_SOURCES archive_write_disk_windows.c) + LIST(APPEND libarchive_SOURCES filter_fork_windows.c) +ENDIF(WIN32 AND NOT CYGWIN) + +# Libarchive is a shared library +ADD_LIBRARY(archive SHARED ${libarchive_SOURCES} ${include_HEADERS}) +TARGET_LINK_LIBRARIES(archive ${ADDITIONAL_LIBS}) +SET_TARGET_PROPERTIES(archive PROPERTIES SOVERSION ${SOVERSION}) + +# archive_static is a static library +ADD_LIBRARY(archive_static STATIC ${libarchive_SOURCES} ${include_HEADERS}) +SET_TARGET_PROPERTIES(archive_static PROPERTIES COMPILE_DEFINITIONS + LIBARCHIVE_STATIC) +# On Posix systems, libarchive.so and libarchive.a can co-exist. +IF(NOT WIN32 OR CYGWIN) + SET_TARGET_PROPERTIES(archive_static PROPERTIES OUTPUT_NAME archive) +ENDIF(NOT WIN32 OR CYGWIN) + +# How to install the libraries +INSTALL(TARGETS archive archive_static + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) +INSTALL_MAN(${libarchive_MANS}) +INSTALL(FILES ${include_HEADERS} DESTINATION include) + +add_subdirectory(test) diff --git a/libarchive/archive.h b/libarchive/archive.h new file mode 100644 index 0000000..14c2aed --- /dev/null +++ b/libarchive/archive.h @@ -0,0 +1,832 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: src/lib/libarchive/archive.h.in,v 1.50 2008/05/26 17:00:22 kientzle Exp $ + */ + +#ifndef ARCHIVE_H_INCLUDED +#define ARCHIVE_H_INCLUDED + +#include +#include /* for wchar_t */ +#include /* For FILE * */ + +/* + * Note: archive.h is for use outside of libarchive; the configuration + * headers (config.h, archive_platform.h, etc.) are purely internal. + * Do NOT use HAVE_XXX configuration macros to control the behavior of + * this header! If you must conditionalize, use predefined compiler and/or + * platform macros. + */ +#if defined(__BORLANDC__) && __BORLANDC__ >= 0x560 +# include +#elif !defined(__WATCOMC__) && !defined(_MSC_VER) && !defined(__INTERIX) && !defined(__BORLANDC__) && !defined(_SCO_DS) +# include +#endif + +/* Get appropriate definitions of standard POSIX-style types. */ +/* These should match the types used in 'struct stat' */ +#if defined(_WIN32) && !defined(__CYGWIN__) +# define __LA_INT64_T __int64 +# if defined(_SSIZE_T_DEFINED) || defined(_SSIZE_T_) +# define __LA_SSIZE_T ssize_t +# elif defined(_WIN64) +# define __LA_SSIZE_T __int64 +# else +# define __LA_SSIZE_T long +# endif +# if defined(__BORLANDC__) +# define __LA_UID_T uid_t +# define __LA_GID_T gid_t +# else +# define __LA_UID_T short +# define __LA_GID_T short +# endif +#else +# include /* ssize_t, uid_t, and gid_t */ +# if defined(_SCO_DS) +# define __LA_INT64_T long long +# else +# define __LA_INT64_T int64_t +# endif +# define __LA_SSIZE_T ssize_t +# define __LA_UID_T uid_t +# define __LA_GID_T gid_t +#endif + +/* + * On Windows, define LIBARCHIVE_STATIC if you're building or using a + * .lib. The default here assumes you're building a DLL. Only + * libarchive source should ever define __LIBARCHIVE_BUILD. + */ +#if ((defined __WIN32__) || (defined _WIN32) || defined(__CYGWIN__)) && (!defined LIBARCHIVE_STATIC) +# ifdef __LIBARCHIVE_BUILD +# ifdef __GNUC__ +# define __LA_DECL __attribute__((dllexport)) extern +# else +# define __LA_DECL __declspec(dllexport) +# endif +# else +# ifdef __GNUC__ +# define __LA_DECL +# else +# define __LA_DECL __declspec(dllimport) +# endif +# endif +#else +/* Static libraries or non-Windows needs no special declaration. */ +# define __LA_DECL +#endif + +#if defined(__GNUC__) && __GNUC__ >= 3 && !defined(__MINGW32__) +#define __LA_PRINTF(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define __LA_PRINTF(fmtarg, firstvararg) /* nothing */ +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The version number is provided as both a macro and a function. + * The macro identifies the installed header; the function identifies + * the library version (which may not be the same if you're using a + * dynamically-linked version of the library). Of course, if the + * header and library are very different, you should expect some + * strangeness. Don't do that. + */ + +/* + * The version number is expressed as a single integer that makes it + * easy to compare versions at build time: for version a.b.c, the + * version number is printf("%d%03d%03d",a,b,c). For example, if you + * know your application requires version 2.12.108 or later, you can + * assert that ARCHIVE_VERSION >= 2012108. + * + * This single-number format was introduced with libarchive 1.9.0 in + * the libarchive 1.x family and libarchive 2.2.4 in the libarchive + * 2.x family. The following may be useful if you really want to do + * feature detection for earlier libarchive versions (which defined + * ARCHIVE_API_VERSION and ARCHIVE_API_FEATURE instead): + * + * #ifndef ARCHIVE_VERSION_NUMBER + * #define ARCHIVE_VERSION_NUMBER \ + * (ARCHIVE_API_VERSION * 1000000 + ARCHIVE_API_FEATURE * 1000) + * #endif + */ +/* Note: Compiler will complain if this does not match archive_entry.h! */ +#define ARCHIVE_VERSION_NUMBER 3000001 +__LA_DECL int archive_version_number(void); + +/* + * Textual name/version of the library, useful for version displays. + */ +#define ARCHIVE_VERSION_STRING "libarchive 3.0.1b" +__LA_DECL const char * archive_version_string(void); + +/* Declare our basic types. */ +struct archive; +struct archive_entry; + +/* + * Error codes: Use archive_errno() and archive_error_string() + * to retrieve details. Unless specified otherwise, all functions + * that return 'int' use these codes. + */ +#define ARCHIVE_EOF 1 /* Found end of archive. */ +#define ARCHIVE_OK 0 /* Operation was successful. */ +#define ARCHIVE_RETRY (-10) /* Retry might succeed. */ +#define ARCHIVE_WARN (-20) /* Partial success. */ +/* For example, if write_header "fails", then you can't push data. */ +#define ARCHIVE_FAILED (-25) /* Current operation cannot complete. */ +/* But if write_header is "fatal," then this archive is dead and useless. */ +#define ARCHIVE_FATAL (-30) /* No more operations are possible. */ + +/* + * As far as possible, archive_errno returns standard platform errno codes. + * Of course, the details vary by platform, so the actual definitions + * here are stored in "archive_platform.h". The symbols are listed here + * for reference; as a rule, clients should not need to know the exact + * platform-dependent error code. + */ +/* Unrecognized or invalid file format. */ +/* #define ARCHIVE_ERRNO_FILE_FORMAT */ +/* Illegal usage of the library. */ +/* #define ARCHIVE_ERRNO_PROGRAMMER_ERROR */ +/* Unknown or unclassified error. */ +/* #define ARCHIVE_ERRNO_MISC */ + +/* + * Callbacks are invoked to automatically read/skip/write/open/close the + * archive. You can provide your own for complex tasks (like breaking + * archives across multiple tapes) or use standard ones built into the + * library. + */ + +/* Returns pointer and size of next block of data from archive. */ +typedef __LA_SSIZE_T archive_read_callback(struct archive *, + void *_client_data, const void **_buffer); + +/* Skips at most request bytes from archive and returns the skipped amount. + * This may skip fewer bytes than requested; it may even skip zero bytes. + * If you do skip fewer bytes than requested, libarchive will invoke your + * read callback and discard data as necessary to make up the full skip. + */ +typedef __LA_INT64_T archive_skip_callback(struct archive *, + void *_client_data, __LA_INT64_T request); + +/* Seeks to specified location in the file and returns the position. + * Whence values are SEEK_SET, SEEK_CUR, SEEK_END from stdio.h. + * Return ARCHIVE_FATAL if the seek fails for any reason. + */ +typedef __LA_INT64_T archive_seek_callback(struct archive *, + void *_client_data, __LA_INT64_T offset, int whence); + +/* Returns size actually written, zero on EOF, -1 on error. */ +typedef __LA_SSIZE_T archive_write_callback(struct archive *, + void *_client_data, + const void *_buffer, size_t _length); + +typedef int archive_open_callback(struct archive *, void *_client_data); + +typedef int archive_close_callback(struct archive *, void *_client_data); + +/* + * Codes to identify various stream filters. + */ +#define ARCHIVE_FILTER_NONE 0 +#define ARCHIVE_FILTER_GZIP 1 +#define ARCHIVE_FILTER_BZIP2 2 +#define ARCHIVE_FILTER_COMPRESS 3 +#define ARCHIVE_FILTER_PROGRAM 4 +#define ARCHIVE_FILTER_LZMA 5 +#define ARCHIVE_FILTER_XZ 6 +#define ARCHIVE_FILTER_UU 7 +#define ARCHIVE_FILTER_RPM 8 +#define ARCHIVE_FILTER_LZIP 9 + +#if ARCHIVE_VERSION_NUMBER < 4000000 +#define ARCHIVE_COMPRESSION_NONE ARCHIVE_FILTER_NONE +#define ARCHIVE_COMPRESSION_GZIP ARCHIVE_FILTER_GZIP +#define ARCHIVE_COMPRESSION_BZIP2 ARCHIVE_FILTER_BZIP2 +#define ARCHIVE_COMPRESSION_COMPRESS ARCHIVE_FILTER_COMPRESS +#define ARCHIVE_COMPRESSION_PROGRAM ARCHIVE_FILTER_PROGRAM +#define ARCHIVE_COMPRESSION_LZMA ARCHIVE_FILTER_LZMA +#define ARCHIVE_COMPRESSION_XZ ARCHIVE_FILTER_XZ +#define ARCHIVE_COMPRESSION_UU ARCHIVE_FILTER_UU +#define ARCHIVE_COMPRESSION_RPM ARCHIVE_FILTER_RPM +#define ARCHIVE_COMPRESSION_LZIP ARCHIVE_FILTER_LZIP +#endif + +/* + * Codes returned by archive_format. + * + * Top 16 bits identifies the format family (e.g., "tar"); lower + * 16 bits indicate the variant. This is updated by read_next_header. + * Note that the lower 16 bits will often vary from entry to entry. + * In some cases, this variation occurs as libarchive learns more about + * the archive (for example, later entries might utilize extensions that + * weren't necessary earlier in the archive; in this case, libarchive + * will change the format code to indicate the extended format that + * was used). In other cases, it's because different tools have + * modified the archive and so different parts of the archive + * actually have slightly different formats. (Both tar and cpio store + * format codes in each entry, so it is quite possible for each + * entry to be in a different format.) + */ +#define ARCHIVE_FORMAT_BASE_MASK 0xff0000 +#define ARCHIVE_FORMAT_CPIO 0x10000 +#define ARCHIVE_FORMAT_CPIO_POSIX (ARCHIVE_FORMAT_CPIO | 1) +#define ARCHIVE_FORMAT_CPIO_BIN_LE (ARCHIVE_FORMAT_CPIO | 2) +#define ARCHIVE_FORMAT_CPIO_BIN_BE (ARCHIVE_FORMAT_CPIO | 3) +#define ARCHIVE_FORMAT_CPIO_SVR4_NOCRC (ARCHIVE_FORMAT_CPIO | 4) +#define ARCHIVE_FORMAT_CPIO_SVR4_CRC (ARCHIVE_FORMAT_CPIO | 5) +#define ARCHIVE_FORMAT_CPIO_AFIO_LARGE (ARCHIVE_FORMAT_CPIO | 6) +#define ARCHIVE_FORMAT_SHAR 0x20000 +#define ARCHIVE_FORMAT_SHAR_BASE (ARCHIVE_FORMAT_SHAR | 1) +#define ARCHIVE_FORMAT_SHAR_DUMP (ARCHIVE_FORMAT_SHAR | 2) +#define ARCHIVE_FORMAT_TAR 0x30000 +#define ARCHIVE_FORMAT_TAR_USTAR (ARCHIVE_FORMAT_TAR | 1) +#define ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE (ARCHIVE_FORMAT_TAR | 2) +#define ARCHIVE_FORMAT_TAR_PAX_RESTRICTED (ARCHIVE_FORMAT_TAR | 3) +#define ARCHIVE_FORMAT_TAR_GNUTAR (ARCHIVE_FORMAT_TAR | 4) +#define ARCHIVE_FORMAT_ISO9660 0x40000 +#define ARCHIVE_FORMAT_ISO9660_ROCKRIDGE (ARCHIVE_FORMAT_ISO9660 | 1) +#define ARCHIVE_FORMAT_ZIP 0x50000 +#define ARCHIVE_FORMAT_EMPTY 0x60000 +#define ARCHIVE_FORMAT_AR 0x70000 +#define ARCHIVE_FORMAT_AR_GNU (ARCHIVE_FORMAT_AR | 1) +#define ARCHIVE_FORMAT_AR_BSD (ARCHIVE_FORMAT_AR | 2) +#define ARCHIVE_FORMAT_MTREE 0x80000 +#define ARCHIVE_FORMAT_RAW 0x90000 +#define ARCHIVE_FORMAT_XAR 0xA0000 +#define ARCHIVE_FORMAT_LHA 0xB0000 +#define ARCHIVE_FORMAT_CAB 0xC0000 +#define ARCHIVE_FORMAT_RAR 0xD0000 +#define ARCHIVE_FORMAT_7ZIP 0xE0000 + +/*- + * Basic outline for reading an archive: + * 1) Ask archive_read_new for an archive reader object. + * 2) Update any global properties as appropriate. + * In particular, you'll certainly want to call appropriate + * archive_read_support_XXX functions. + * 3) Call archive_read_open_XXX to open the archive + * 4) Repeatedly call archive_read_next_header to get information about + * successive archive entries. Call archive_read_data to extract + * data for entries of interest. + * 5) Call archive_read_finish to end processing. + */ +__LA_DECL struct archive *archive_read_new(void); + +/* + * The archive_read_support_XXX calls enable auto-detect for this + * archive handle. They also link in the necessary support code. + * For example, if you don't want bzlib linked in, don't invoke + * support_compression_bzip2(). The "all" functions provide the + * obvious shorthand. + */ + +#if ARCHIVE_VERSION_NUMBER < 4000000 +__LA_DECL int archive_read_support_compression_all(struct archive *); +__LA_DECL int archive_read_support_compression_bzip2(struct archive *); +__LA_DECL int archive_read_support_compression_compress(struct archive *); +__LA_DECL int archive_read_support_compression_gzip(struct archive *); +__LA_DECL int archive_read_support_compression_lzip(struct archive *); +__LA_DECL int archive_read_support_compression_lzma(struct archive *); +__LA_DECL int archive_read_support_compression_none(struct archive *); +__LA_DECL int archive_read_support_compression_program(struct archive *, + const char *command); +__LA_DECL int archive_read_support_compression_program_signature + (struct archive *, const char *, + const void * /* match */, size_t); + +__LA_DECL int archive_read_support_compression_rpm(struct archive *); +__LA_DECL int archive_read_support_compression_uu(struct archive *); +__LA_DECL int archive_read_support_compression_xz(struct archive *); +#endif + +__LA_DECL int archive_read_support_filter_all(struct archive *); +__LA_DECL int archive_read_support_filter_bzip2(struct archive *); +__LA_DECL int archive_read_support_filter_compress(struct archive *); +__LA_DECL int archive_read_support_filter_gzip(struct archive *); +__LA_DECL int archive_read_support_filter_lzip(struct archive *); +__LA_DECL int archive_read_support_filter_lzma(struct archive *); +__LA_DECL int archive_read_support_filter_none(struct archive *); +__LA_DECL int archive_read_support_filter_program(struct archive *, + const char *command); +__LA_DECL int archive_read_support_filter_program_signature + (struct archive *, const char *, + const void * /* match */, size_t); + +__LA_DECL int archive_read_support_filter_rpm(struct archive *); +__LA_DECL int archive_read_support_filter_uu(struct archive *); +__LA_DECL int archive_read_support_filter_xz(struct archive *); + +__LA_DECL int archive_read_support_format_7zip(struct archive *); +__LA_DECL int archive_read_support_format_all(struct archive *); +__LA_DECL int archive_read_support_format_ar(struct archive *); +__LA_DECL int archive_read_support_format_by_code(struct archive *, int); +__LA_DECL int archive_read_support_format_cab(struct archive *); +__LA_DECL int archive_read_support_format_cpio(struct archive *); +__LA_DECL int archive_read_support_format_empty(struct archive *); +__LA_DECL int archive_read_support_format_gnutar(struct archive *); +__LA_DECL int archive_read_support_format_iso9660(struct archive *); +__LA_DECL int archive_read_support_format_lha(struct archive *); +__LA_DECL int archive_read_support_format_mtree(struct archive *); +__LA_DECL int archive_read_support_format_rar(struct archive *); +__LA_DECL int archive_read_support_format_raw(struct archive *); +__LA_DECL int archive_read_support_format_tar(struct archive *); +__LA_DECL int archive_read_support_format_xar(struct archive *); +__LA_DECL int archive_read_support_format_zip(struct archive *); + +/* Set various callbacks. */ +__LA_DECL int archive_read_set_open_callback(struct archive *, + archive_open_callback *); +__LA_DECL int archive_read_set_read_callback(struct archive *, + archive_read_callback *); +__LA_DECL int archive_read_set_seek_callback(struct archive *, + archive_seek_callback *); +__LA_DECL int archive_read_set_skip_callback(struct archive *, + archive_skip_callback *); +__LA_DECL int archive_read_set_close_callback(struct archive *, + archive_close_callback *); +/* The callback data is provided to all of the callbacks above. */ +__LA_DECL int archive_read_set_callback_data(struct archive *, void *); +/* Opening freezes the callbacks. */ +__LA_DECL int archive_read_open1(struct archive *); + +/* Convenience wrappers around the above. */ +__LA_DECL int archive_read_open(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_close_callback *); +__LA_DECL int archive_read_open2(struct archive *, void *_client_data, + archive_open_callback *, archive_read_callback *, + archive_skip_callback *, archive_close_callback *); + +/* + * A variety of shortcuts that invoke archive_read_open() with + * canned callbacks suitable for common situations. The ones that + * accept a block size handle tape blocking correctly. + */ +/* Use this if you know the filename. Note: NULL indicates stdin. */ +__LA_DECL int archive_read_open_filename(struct archive *, + const char *_filename, size_t _block_size); +__LA_DECL int archive_read_open_filename_w(struct archive *, + const wchar_t *_filename, size_t _block_size); +/* archive_read_open_file() is a deprecated synonym for ..._open_filename(). */ +__LA_DECL int archive_read_open_file(struct archive *, + const char *_filename, size_t _block_size); +/* Read an archive that's stored in memory. */ +__LA_DECL int archive_read_open_memory(struct archive *, + void * buff, size_t size); +/* A more involved version that is only used for internal testing. */ +__LA_DECL int archive_read_open_memory2(struct archive *a, void *buff, + size_t size, size_t read_size); +/* Read an archive that's already open, using the file descriptor. */ +__LA_DECL int archive_read_open_fd(struct archive *, int _fd, + size_t _block_size); +/* Read an archive that's already open, using a FILE *. */ +/* Note: DO NOT use this with tape drives. */ +__LA_DECL int archive_read_open_FILE(struct archive *, FILE *_file); + +/* Parses and returns next entry header. */ +__LA_DECL int archive_read_next_header(struct archive *, + struct archive_entry **); + +/* Parses and returns next entry header using the archive_entry passed in */ +__LA_DECL int archive_read_next_header2(struct archive *, + struct archive_entry *); + +/* + * Retrieve the byte offset in UNCOMPRESSED data where last-read + * header started. + */ +__LA_DECL __LA_INT64_T archive_read_header_position(struct archive *); + +/* Read data from the body of an entry. Similar to read(2). */ +__LA_DECL __LA_SSIZE_T archive_read_data(struct archive *, + void *, size_t); + +/* + * A zero-copy version of archive_read_data that also exposes the file offset + * of each returned block. Note that the client has no way to specify + * the desired size of the block. The API does guarantee that offsets will + * be strictly increasing and that returned blocks will not overlap. + */ +__LA_DECL int archive_read_data_block(struct archive *a, + const void **buff, size_t *size, __LA_INT64_T *offset); + +/*- + * Some convenience functions that are built on archive_read_data: + * 'skip': skips entire entry + * 'into_buffer': writes data into memory buffer that you provide + * 'into_fd': writes data to specified filedes + */ +__LA_DECL int archive_read_data_skip(struct archive *); +__LA_DECL int archive_read_data_into_buffer(struct archive *, + void *buffer, __LA_SSIZE_T len); +__LA_DECL int archive_read_data_into_fd(struct archive *, int fd); + +/* + * Set read options. + */ +/* Apply option to the format only. */ +__LA_DECL int archive_read_set_format_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to the filter only. */ +__LA_DECL int archive_read_set_filter_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to both the format and the filter. */ +__LA_DECL int archive_read_set_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option string to both the format and the filter. */ +__LA_DECL int archive_read_set_options(struct archive *_a, + const char *opts); + +/*- + * Convenience function to recreate the current entry (whose header + * has just been read) on disk. + * + * This does quite a bit more than just copy data to disk. It also: + * - Creates intermediate directories as required. + * - Manages directory permissions: non-writable directories will + * be initially created with write permission enabled; when the + * archive is closed, dir permissions are edited to the values specified + * in the archive. + * - Checks hardlinks: hardlinks will not be extracted unless the + * linked-to file was also extracted within the same session. (TODO) + */ + +/* The "flags" argument selects optional behavior, 'OR' the flags you want. */ + +/* Default: Do not try to set owner/group. */ +#define ARCHIVE_EXTRACT_OWNER (0x0001) +/* Default: Do obey umask, do not restore SUID/SGID/SVTX bits. */ +#define ARCHIVE_EXTRACT_PERM (0x0002) +/* Default: Do not restore mtime/atime. */ +#define ARCHIVE_EXTRACT_TIME (0x0004) +/* Default: Replace existing files. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE (0x0008) +/* Default: Try create first, unlink only if create fails with EEXIST. */ +#define ARCHIVE_EXTRACT_UNLINK (0x0010) +/* Default: Do not restore ACLs. */ +#define ARCHIVE_EXTRACT_ACL (0x0020) +/* Default: Do not restore fflags. */ +#define ARCHIVE_EXTRACT_FFLAGS (0x0040) +/* Default: Do not restore xattrs. */ +#define ARCHIVE_EXTRACT_XATTR (0x0080) +/* Default: Do not try to guard against extracts redirected by symlinks. */ +/* Note: With ARCHIVE_EXTRACT_UNLINK, will remove any intermediate symlink. */ +#define ARCHIVE_EXTRACT_SECURE_SYMLINKS (0x0100) +/* Default: Do not reject entries with '..' as path elements. */ +#define ARCHIVE_EXTRACT_SECURE_NODOTDOT (0x0200) +/* Default: Create parent directories as needed. */ +#define ARCHIVE_EXTRACT_NO_AUTODIR (0x0400) +/* Default: Overwrite files, even if one on disk is newer. */ +#define ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER (0x0800) +/* Detect blocks of 0 and write holes instead. */ +#define ARCHIVE_EXTRACT_SPARSE (0x1000) +/* Default: Do not restore Mac extended metadata. */ +/* This has no effect except on Mac OS. */ +#define ARCHIVE_EXTRACT_MAC_METADATA (0x2000) + +__LA_DECL int archive_read_extract(struct archive *, struct archive_entry *, + int flags); +__LA_DECL int archive_read_extract2(struct archive *, struct archive_entry *, + struct archive * /* dest */); +__LA_DECL void archive_read_extract_set_progress_callback(struct archive *, + void (*_progress_func)(void *), void *_user_data); + +/* Record the dev/ino of a file that will not be written. This is + * generally set to the dev/ino of the archive being read. */ +__LA_DECL void archive_read_extract_set_skip_file(struct archive *, + __LA_INT64_T, __LA_INT64_T); + +/* Close the file and release most resources. */ +__LA_DECL int archive_read_close(struct archive *); +/* Release all resources and destroy the object. */ +/* Note that archive_read_free will call archive_read_close for you. */ +__LA_DECL int archive_read_free(struct archive *); +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Synonym for archive_read_free() for backwards compatibility. */ +__LA_DECL int archive_read_finish(struct archive *); +#endif + +/*- + * To create an archive: + * 1) Ask archive_write_new for an archive writer object. + * 2) Set any global properties. In particular, you should set + * the compression and format to use. + * 3) Call archive_write_open to open the file (most people + * will use archive_write_open_file or archive_write_open_fd, + * which provide convenient canned I/O callbacks for you). + * 4) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to write the header + * - archive_write_data to write the entry data + * 5) archive_write_close to close the output + * 6) archive_write_free to cleanup the writer and release resources + */ +__LA_DECL struct archive *archive_write_new(void); +__LA_DECL int archive_write_set_bytes_per_block(struct archive *, + int bytes_per_block); +__LA_DECL int archive_write_get_bytes_per_block(struct archive *); +/* XXX This is badly misnamed; suggestions appreciated. XXX */ +__LA_DECL int archive_write_set_bytes_in_last_block(struct archive *, + int bytes_in_last_block); +__LA_DECL int archive_write_get_bytes_in_last_block(struct archive *); + +/* The dev/ino of a file that won't be archived. This is used + * to avoid recursively adding an archive to itself. */ +__LA_DECL int archive_write_set_skip_file(struct archive *, + __LA_INT64_T, __LA_INT64_T); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +__LA_DECL int archive_write_set_compression_bzip2(struct archive *); +__LA_DECL int archive_write_set_compression_compress(struct archive *); +__LA_DECL int archive_write_set_compression_gzip(struct archive *); +__LA_DECL int archive_write_set_compression_lzip(struct archive *); +__LA_DECL int archive_write_set_compression_lzma(struct archive *); +__LA_DECL int archive_write_set_compression_none(struct archive *); +__LA_DECL int archive_write_set_compression_program(struct archive *, + const char *cmd); +__LA_DECL int archive_write_set_compression_xz(struct archive *); +#endif + +__LA_DECL int archive_write_add_filter_bzip2(struct archive *); +__LA_DECL int archive_write_add_filter_compress(struct archive *); +__LA_DECL int archive_write_add_filter_gzip(struct archive *); +__LA_DECL int archive_write_add_filter_lzip(struct archive *); +__LA_DECL int archive_write_add_filter_lzma(struct archive *); +__LA_DECL int archive_write_add_filter_none(struct archive *); +__LA_DECL int archive_write_add_filter_program(struct archive *, + const char *cmd); +__LA_DECL int archive_write_add_filter_xz(struct archive *); + + +/* A convenience function to set the format based on the code or name. */ +__LA_DECL int archive_write_set_format(struct archive *, int format_code); +__LA_DECL int archive_write_set_format_by_name(struct archive *, + const char *name); +/* To minimize link pollution, use one or more of the following. */ +__LA_DECL int archive_write_set_format_7zip(struct archive *); +__LA_DECL int archive_write_set_format_ar_bsd(struct archive *); +__LA_DECL int archive_write_set_format_ar_svr4(struct archive *); +__LA_DECL int archive_write_set_format_cpio(struct archive *); +__LA_DECL int archive_write_set_format_cpio_newc(struct archive *); +__LA_DECL int archive_write_set_format_gnutar(struct archive *); +__LA_DECL int archive_write_set_format_iso9660(struct archive *); +__LA_DECL int archive_write_set_format_mtree(struct archive *); +/* TODO: int archive_write_set_format_old_tar(struct archive *); */ +__LA_DECL int archive_write_set_format_pax(struct archive *); +__LA_DECL int archive_write_set_format_pax_restricted(struct archive *); +__LA_DECL int archive_write_set_format_shar(struct archive *); +__LA_DECL int archive_write_set_format_shar_dump(struct archive *); +__LA_DECL int archive_write_set_format_ustar(struct archive *); +__LA_DECL int archive_write_set_format_xar(struct archive *); +__LA_DECL int archive_write_set_format_zip(struct archive *); +__LA_DECL int archive_write_open(struct archive *, void *, + archive_open_callback *, archive_write_callback *, + archive_close_callback *); +__LA_DECL int archive_write_open_fd(struct archive *, int _fd); +__LA_DECL int archive_write_open_filename(struct archive *, const char *_file); +__LA_DECL int archive_write_open_filename_w(struct archive *, + const wchar_t *_file); +/* A deprecated synonym for archive_write_open_filename() */ +__LA_DECL int archive_write_open_file(struct archive *, const char *_file); +__LA_DECL int archive_write_open_FILE(struct archive *, FILE *); +/* _buffSize is the size of the buffer, _used refers to a variable that + * will be updated after each write into the buffer. */ +__LA_DECL int archive_write_open_memory(struct archive *, + void *_buffer, size_t _buffSize, size_t *_used); + +/* + * Note that the library will truncate writes beyond the size provided + * to archive_write_header or pad if the provided data is short. + */ +__LA_DECL int archive_write_header(struct archive *, + struct archive_entry *); +__LA_DECL __LA_SSIZE_T archive_write_data(struct archive *, + const void *, size_t); + +/* This interface is currently only available for archive_write_disk handles. */ +__LA_DECL __LA_SSIZE_T archive_write_data_block(struct archive *, + const void *, size_t, __LA_INT64_T); + +__LA_DECL int archive_write_finish_entry(struct archive *); +__LA_DECL int archive_write_close(struct archive *); +/* This can fail if the archive wasn't already closed, in which case + * archive_write_free() will implicitly call archive_write_close(). */ +__LA_DECL int archive_write_free(struct archive *); +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Synonym for archive_write_free() for backwards compatibility. */ +__LA_DECL int archive_write_finish(struct archive *); +#endif + +/* + * Set write options. + */ +/* Apply option to the format only. */ +__LA_DECL int archive_write_set_format_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to the filter only. */ +__LA_DECL int archive_write_set_filter_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option to both the format and the filter. */ +__LA_DECL int archive_write_set_option(struct archive *_a, + const char *m, const char *o, + const char *v); +/* Apply option string to both the format and the filter. */ +__LA_DECL int archive_write_set_options(struct archive *_a, + const char *opts); + +/*- + * ARCHIVE_WRITE_DISK API + * + * To create objects on disk: + * 1) Ask archive_write_disk_new for a new archive_write_disk object. + * 2) Set any global properties. In particular, you probably + * want to set the options. + * 3) For each entry: + * - construct an appropriate struct archive_entry structure + * - archive_write_header to create the file/dir/etc on disk + * - archive_write_data to write the entry data + * 4) archive_write_free to cleanup the writer and release resources + * + * In particular, you can use this in conjunction with archive_read() + * to pull entries out of an archive and create them on disk. + */ +__LA_DECL struct archive *archive_write_disk_new(void); +/* This file will not be overwritten. */ +__LA_DECL int archive_write_disk_set_skip_file(struct archive *, + __LA_INT64_T, __LA_INT64_T); +/* Set flags to control how the next item gets created. + * This accepts a bitmask of ARCHIVE_EXTRACT_XXX flags defined above. */ +__LA_DECL int archive_write_disk_set_options(struct archive *, + int flags); +/* + * The lookup functions are given uname/uid (or gname/gid) pairs and + * return a uid (gid) suitable for this system. These are used for + * restoring ownership and for setting ACLs. The default functions + * are naive, they just return the uid/gid. These are small, so reasonable + * for applications that don't need to preserve ownership; they + * are probably also appropriate for applications that are doing + * same-system backup and restore. + */ +/* + * The "standard" lookup functions use common system calls to lookup + * the uname/gname, falling back to the uid/gid if the names can't be + * found. They cache lookups and are reasonably fast, but can be very + * large, so they are not used unless you ask for them. In + * particular, these match the specifications of POSIX "pax" and old + * POSIX "tar". + */ +__LA_DECL int archive_write_disk_set_standard_lookup(struct archive *); +/* + * If neither the default (naive) nor the standard (big) functions suit + * your needs, you can write your own and register them. Be sure to + * include a cleanup function if you have allocated private data. + */ +__LA_DECL int archive_write_disk_set_group_lookup(struct archive *, + void * /* private_data */, + __LA_INT64_T (*)(void *, const char *, __LA_INT64_T), + void (* /* cleanup */)(void *)); +__LA_DECL int archive_write_disk_set_user_lookup(struct archive *, + void * /* private_data */, + __LA_INT64_T (*)(void *, const char *, __LA_INT64_T), + void (* /* cleanup */)(void *)); +__LA_DECL __LA_INT64_T archive_write_disk_gid(struct archive *, const char *, __LA_INT64_T); +__LA_DECL __LA_INT64_T archive_write_disk_uid(struct archive *, const char *, __LA_INT64_T); + +/* + * ARCHIVE_READ_DISK API + * + * This is still evolving and somewhat experimental. + */ +__LA_DECL struct archive *archive_read_disk_new(void); +/* The names for symlink modes here correspond to an old BSD + * command-line argument convention: -L, -P, -H */ +/* Follow all symlinks. */ +__LA_DECL int archive_read_disk_set_symlink_logical(struct archive *); +/* Follow no symlinks. */ +__LA_DECL int archive_read_disk_set_symlink_physical(struct archive *); +/* Follow symlink initially, then not. */ +__LA_DECL int archive_read_disk_set_symlink_hybrid(struct archive *); +/* TODO: Handle Linux stat32/stat64 ugliness. */ +__LA_DECL int archive_read_disk_entry_from_file(struct archive *, + struct archive_entry *, int /* fd */, const struct stat *); +/* Look up gname for gid or uname for uid. */ +/* Default implementations are very, very stupid. */ +__LA_DECL const char *archive_read_disk_gname(struct archive *, __LA_INT64_T); +__LA_DECL const char *archive_read_disk_uname(struct archive *, __LA_INT64_T); +/* "Standard" implementation uses getpwuid_r, getgrgid_r and caches the + * results for performance. */ +__LA_DECL int archive_read_disk_set_standard_lookup(struct archive *); +/* You can install your own lookups if you like. */ +__LA_DECL int archive_read_disk_set_gname_lookup(struct archive *, + void * /* private_data */, + const char *(* /* lookup_fn */)(void *, __LA_INT64_T), + void (* /* cleanup_fn */)(void *)); +__LA_DECL int archive_read_disk_set_uname_lookup(struct archive *, + void * /* private_data */, + const char *(* /* lookup_fn */)(void *, __LA_INT64_T), + void (* /* cleanup_fn */)(void *)); +/* Start traversal. */ +__LA_DECL int archive_read_disk_open(struct archive *, const char *); +__LA_DECL int archive_read_disk_open_w(struct archive *, const wchar_t *); +/* + * Request that current entry be visited. If you invoke it on every + * directory, you'll get a physical traversal. This is ignored if the + * current entry isn't a directory or a link to a directory. So, if + * you invoke this on every returned path, you'll get a full logical + * traversal. + */ +__LA_DECL int archive_read_disk_descend(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem_is_synthetic(struct archive *); +__LA_DECL int archive_read_disk_current_filesystem_is_remote(struct archive *); +/* Request that the access time of the entry visited by travesal be restored. */ +__LA_DECL int archive_read_disk_set_atime_restored(struct archive *); + +/* + * Accessor functions to read/set various information in + * the struct archive object: + */ + +/* Number of filters in the current filter pipeline. */ +/* Filter #0 is the one closest to the format, -1 is a synonym for the + * last filter, which is always the pseudo-filter that wraps the + * client callbacks. */ +__LA_DECL int archive_filter_count(struct archive *); +__LA_DECL __LA_INT64_T archive_filter_bytes(struct archive *, int); +__LA_DECL int archive_filter_code(struct archive *, int); +__LA_DECL const char * archive_filter_name(struct archive *, int); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* These don't properly handle multiple filters, so are deprecated and + * will eventually be removed. */ +/* As of libarchive 3.0, this is an alias for archive_filter_bytes(a, -1); */ +__LA_DECL __LA_INT64_T archive_position_compressed(struct archive *); +/* As of libarchive 3.0, this is an alias for archive_filter_bytes(a, 0); */ +__LA_DECL __LA_INT64_T archive_position_uncompressed(struct archive *); +/* As of libarchive 3.0, this is an alias for archive_filter_name(a, 0); */ +__LA_DECL const char *archive_compression_name(struct archive *); +/* As of libarchive 3.0, this is an alias for archive_filter_code(a, 0); */ +__LA_DECL int archive_compression(struct archive *); +#endif + +__LA_DECL int archive_errno(struct archive *); +__LA_DECL const char *archive_error_string(struct archive *); +__LA_DECL const char *archive_format_name(struct archive *); +__LA_DECL int archive_format(struct archive *); +__LA_DECL void archive_clear_error(struct archive *); +__LA_DECL void archive_set_error(struct archive *, int _err, + const char *fmt, ...) __LA_PRINTF(3, 4); +__LA_DECL void archive_copy_error(struct archive *dest, + struct archive *src); +__LA_DECL int archive_file_count(struct archive *); + +#ifdef __cplusplus +} +#endif + +/* These are meaningless outside of this header. */ +#undef __LA_DECL +#undef __LA_GID_T +#undef __LA_UID_T + +/* These need to remain defined because they're used in the + * callback type definitions. XXX Fix this. This is ugly. XXX */ +/* #undef __LA_INT64_T */ +/* #undef __LA_SSIZE_T */ + +#endif /* !ARCHIVE_H_INCLUDED */ diff --git a/libarchive/archive_acl.c b/libarchive/archive_acl.c new file mode 100644 index 0000000..4747a4c --- /dev/null +++ b/libarchive/archive_acl.c @@ -0,0 +1,1264 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif + +#include "archive_acl_private.h" +#include "archive_entry.h" +#include "archive_private.h" + +#undef max +#define max(a, b) ((a)>(b)?(a):(b)) + +#ifndef HAVE_WMEMCMP +/* Good enough for simple equality testing, but not for sorting. */ +#define wmemcmp(a,b,i) memcmp((a), (b), (i) * sizeof(wchar_t)) +#endif + +static int acl_special(struct archive_acl *acl, + int type, int permset, int tag); +static struct archive_acl_entry *acl_new_entry(struct archive_acl *acl, + int type, int permset, int tag, int id); +static int isint_w(const wchar_t *start, const wchar_t *end, int *result); +static int ismode_w(const wchar_t *start, const wchar_t *end, int *result); +static void next_field_w(const wchar_t **wp, const wchar_t **start, + const wchar_t **end, wchar_t *sep); +static int prefix_w(const wchar_t *start, const wchar_t *end, + const wchar_t *test); +static void append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag, + const wchar_t *wname, int perm, int id); +static void append_id_w(wchar_t **wp, int id); +static int isint(const char *start, const char *end, int *result); +static int ismode(const char *start, const char *end, int *result); +static void next_field(const char **p, const char **start, + const char **end, char *sep); +static int prefix(const char *start, const char *end, + const char *test); +static void append_entry(char **p, const char *prefix, int tag, + const char *name, int perm, int id); +static void append_id(char **p, int id); + +void +archive_acl_clear(struct archive_acl *acl) +{ + struct archive_acl_entry *ap; + + while (acl->acl_head != NULL) { + ap = acl->acl_head->next; + archive_mstring_clean(&acl->acl_head->name); + free(acl->acl_head); + acl->acl_head = ap; + } + if (acl->acl_text_w != NULL) { + free(acl->acl_text_w); + acl->acl_text_w = NULL; + } + if (acl->acl_text != NULL) { + free(acl->acl_text); + acl->acl_text = NULL; + } + acl->acl_p = NULL; + acl->acl_state = 0; /* Not counting. */ +} + +void +archive_acl_copy(struct archive_acl *dest, struct archive_acl *src) +{ + struct archive_acl_entry *ap, *ap2; + + archive_acl_clear(dest); + + dest->mode = src->mode; + ap = src->acl_head; + while (ap != NULL) { + ap2 = acl_new_entry(dest, + ap->type, ap->permset, ap->tag, ap->id); + if (ap2 != NULL) + archive_mstring_copy(&ap2->name, &ap->name); + ap = ap->next; + } +} + +int +archive_acl_add_entry(struct archive_acl *acl, + int type, int permset, int tag, int id, const char *name) +{ + struct archive_acl_entry *ap; + + if (acl_special(acl, type, permset, tag) == 0) + return ARCHIVE_OK; + ap = acl_new_entry(acl, type, permset, tag, id); + if (ap == NULL) { + /* XXX Error XXX */ + return ARCHIVE_FAILED; + } + if (name != NULL && *name != '\0') + archive_mstring_copy_mbs(&ap->name, name); + else + archive_mstring_clean(&ap->name); + return ARCHIVE_OK; +} + +int +archive_acl_add_entry_w_len(struct archive_acl *acl, + int type, int permset, int tag, int id, const wchar_t *name, size_t len) +{ + struct archive_acl_entry *ap; + + if (acl_special(acl, type, permset, tag) == 0) + return ARCHIVE_OK; + ap = acl_new_entry(acl, type, permset, tag, id); + if (ap == NULL) { + /* XXX Error XXX */ + return ARCHIVE_FAILED; + } + if (name != NULL && *name != L'\0' && len > 0) + archive_mstring_copy_wcs_len(&ap->name, name, len); + else + archive_mstring_clean(&ap->name); + return ARCHIVE_OK; +} + +int +archive_acl_add_entry_len_l(struct archive_acl *acl, + int type, int permset, int tag, int id, const char *name, size_t len, + struct archive_string_conv *sc) +{ + struct archive_acl_entry *ap; + int r; + + if (acl_special(acl, type, permset, tag) == 0) + return ARCHIVE_OK; + ap = acl_new_entry(acl, type, permset, tag, id); + if (ap == NULL) { + /* XXX Error XXX */ + return ARCHIVE_FAILED; + } + if (name != NULL && *name != '\0' && len > 0) { + r = archive_mstring_copy_mbs_len_l(&ap->name, name, len, sc); + } else { + r = 0; + archive_mstring_clean(&ap->name); + } + if (r == 0) + return (ARCHIVE_OK); + else if (errno == ENOMEM) + return (ARCHIVE_FATAL); + else + return (ARCHIVE_WARN); +} + +/* + * If this ACL entry is part of the standard POSIX permissions set, + * store the permissions in the stat structure and return zero. + */ +static int +acl_special(struct archive_acl *acl, int type, int permset, int tag) +{ + if (type == ARCHIVE_ENTRY_ACL_TYPE_ACCESS + && ((permset & ~007) == 0)) { + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + acl->mode &= ~0700; + acl->mode |= (permset & 7) << 6; + return (0); + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + acl->mode &= ~0070; + acl->mode |= (permset & 7) << 3; + return (0); + case ARCHIVE_ENTRY_ACL_OTHER: + acl->mode &= ~0007; + acl->mode |= permset & 7; + return (0); + } + } + return (1); +} + +/* + * Allocate and populate a new ACL entry with everything but the + * name. + */ +static struct archive_acl_entry * +acl_new_entry(struct archive_acl *acl, + int type, int permset, int tag, int id) +{ + struct archive_acl_entry *ap, *aq; + + /* Type argument must be a valid NFS4 or POSIX.1e type. + * The type must agree with anything already set and + * the permset must be compatible. */ + if (type & ARCHIVE_ENTRY_ACL_TYPE_NFS4) { + if (acl->acl_types & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) { + return (NULL); + } + if (permset & + ~(ARCHIVE_ENTRY_ACL_PERMS_NFS4 + | ARCHIVE_ENTRY_ACL_INHERITANCE_NFS4)) { + return (NULL); + } + } else if (type & ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) { + if (acl->acl_types & ~ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) { + return (NULL); + } + if (permset & ~ARCHIVE_ENTRY_ACL_PERMS_POSIX1E) { + return (NULL); + } + } else { + return (NULL); + } + + /* Verify the tag is valid and compatible with NFS4 or POSIX.1e. */ + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER: + case ARCHIVE_ENTRY_ACL_USER_OBJ: + case ARCHIVE_ENTRY_ACL_GROUP: + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + /* Tags valid in both NFS4 and POSIX.1e */ + break; + case ARCHIVE_ENTRY_ACL_MASK: + case ARCHIVE_ENTRY_ACL_OTHER: + /* Tags valid only in POSIX.1e. */ + if (type & ~ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) { + return (NULL); + } + break; + case ARCHIVE_ENTRY_ACL_EVERYONE: + /* Tags valid only in NFS4. */ + if (type & ~ARCHIVE_ENTRY_ACL_TYPE_NFS4) { + return (NULL); + } + break; + default: + /* No other values are valid. */ + return (NULL); + } + + if (acl->acl_text_w != NULL) { + free(acl->acl_text_w); + acl->acl_text_w = NULL; + } + if (acl->acl_text != NULL) { + free(acl->acl_text); + acl->acl_text = NULL; + } + + /* If there's a matching entry already in the list, overwrite it. */ + ap = acl->acl_head; + aq = NULL; + while (ap != NULL) { + if (ap->type == type && ap->tag == tag && ap->id == id) { + ap->permset = permset; + return (ap); + } + aq = ap; + ap = ap->next; + } + + /* Add a new entry to the end of the list. */ + ap = (struct archive_acl_entry *)malloc(sizeof(*ap)); + if (ap == NULL) + return (NULL); + memset(ap, 0, sizeof(*ap)); + if (aq == NULL) + acl->acl_head = ap; + else + aq->next = ap; + ap->type = type; + ap->tag = tag; + ap->id = id; + ap->permset = permset; + acl->acl_types |= type; + return (ap); +} + +/* + * Return a count of entries matching "want_type". + */ +int +archive_acl_count(struct archive_acl *acl, int want_type) +{ + int count; + struct archive_acl_entry *ap; + + count = 0; + ap = acl->acl_head; + while (ap != NULL) { + if ((ap->type & want_type) != 0) + count++; + ap = ap->next; + } + + if (count > 0 && ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) + count += 3; + return (count); +} + +/* + * Prepare for reading entries from the ACL data. Returns a count + * of entries matching "want_type", or zero if there are no + * non-extended ACL entries of that type. + */ +int +archive_acl_reset(struct archive_acl *acl, int want_type) +{ + int count, cutoff; + + count = archive_acl_count(acl, want_type); + + /* + * If the only entries are the three standard ones, + * then don't return any ACL data. (In this case, + * client can just use chmod(2) to set permissions.) + */ + if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) + cutoff = 3; + else + cutoff = 0; + + if (count > cutoff) + acl->acl_state = ARCHIVE_ENTRY_ACL_USER_OBJ; + else + acl->acl_state = 0; + acl->acl_p = acl->acl_head; + return (count); +} + + +/* + * Return the next ACL entry in the list. Fake entries for the + * standard permissions and include them in the returned list. + */ +int +archive_acl_next(struct archive *a, struct archive_acl *acl, int want_type, int *type, + int *permset, int *tag, int *id, const char **name) +{ + *name = NULL; + *id = -1; + + /* + * The acl_state is either zero (no entries available), -1 + * (reading from list), or an entry type (retrieve that type + * from ae_stat.aest_mode). + */ + if (acl->acl_state == 0) + return (ARCHIVE_WARN); + + /* The first three access entries are special. */ + if ((want_type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + switch (acl->acl_state) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + *permset = (acl->mode >> 6) & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + acl->acl_state = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + return (ARCHIVE_OK); + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + *permset = (acl->mode >> 3) & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + acl->acl_state = ARCHIVE_ENTRY_ACL_OTHER; + return (ARCHIVE_OK); + case ARCHIVE_ENTRY_ACL_OTHER: + *permset = acl->mode & 7; + *type = ARCHIVE_ENTRY_ACL_TYPE_ACCESS; + *tag = ARCHIVE_ENTRY_ACL_OTHER; + acl->acl_state = -1; + acl->acl_p = acl->acl_head; + return (ARCHIVE_OK); + default: + break; + } + } + + while (acl->acl_p != NULL && (acl->acl_p->type & want_type) == 0) + acl->acl_p = acl->acl_p->next; + if (acl->acl_p == NULL) { + acl->acl_state = 0; + *type = 0; + *permset = 0; + *tag = 0; + *id = -1; + *name = NULL; + return (ARCHIVE_EOF); /* End of ACL entries. */ + } + *type = acl->acl_p->type; + *permset = acl->acl_p->permset; + *tag = acl->acl_p->tag; + *id = acl->acl_p->id; + if (archive_mstring_get_mbs(a, &acl->acl_p->name, name) != 0) + *name = NULL; + acl->acl_p = acl->acl_p->next; + return (ARCHIVE_OK); +} + +/* + * Generate a text version of the ACL. The flags parameter controls + * the style of the generated ACL. + */ +const wchar_t * +archive_acl_text_w(struct archive *a, struct archive_acl *acl, int flags) +{ + int count; + size_t length; + const wchar_t *wname; + const wchar_t *prefix; + wchar_t separator; + struct archive_acl_entry *ap; + int id; + wchar_t *wp; + + if (acl->acl_text_w != NULL) { + free (acl->acl_text_w); + acl->acl_text_w = NULL; + } + + separator = L','; + count = 0; + length = 0; + ap = acl->acl_head; + while (ap != NULL) { + if ((ap->type & flags) != 0) { + count++; + if ((flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) && + (ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT)) + length += 8; /* "default:" */ + length += 5; /* tag name */ + length += 1; /* colon */ + if (archive_mstring_get_wcs(a, &ap->name, &wname) == 0 && + wname != NULL) + length += wcslen(wname); + else + length += sizeof(uid_t) * 3 + 1; + length ++; /* colon */ + length += 3; /* rwx */ + length += 1; /* colon */ + length += max(sizeof(uid_t), sizeof(gid_t)) * 3 + 1; + length ++; /* newline */ + } + ap = ap->next; + } + + if (count > 0 && ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) { + length += 10; /* "user::rwx\n" */ + length += 11; /* "group::rwx\n" */ + length += 11; /* "other::rwx\n" */ + } + + if (count == 0) + return (NULL); + + /* Now, allocate the string and actually populate it. */ + wp = acl->acl_text_w = (wchar_t *)malloc(length * sizeof(wchar_t)); + if (wp == NULL) + __archive_errx(1, "No memory to generate the text version of the ACL"); + count = 0; + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_USER_OBJ, NULL, + acl->mode & 0700, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL, + acl->mode & 0070, -1); + *wp++ = ','; + append_entry_w(&wp, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL, + acl->mode & 0007, -1); + count += 3; + + ap = acl->acl_head; + while (ap != NULL) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0 && + archive_mstring_get_wcs(a, &ap->name, &wname) == 0) { + *wp++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry_w(&wp, NULL, ap->tag, wname, + ap->permset, id); + count++; + } + ap = ap->next; + } + } + + + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) { + if (flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) + prefix = L"default:"; + else + prefix = NULL; + ap = acl->acl_head; + count = 0; + while (ap != NULL) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0 && + archive_mstring_get_wcs(a, &ap->name, &wname) == 0) { + if (count > 0) + *wp++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry_w(&wp, prefix, ap->tag, + wname, ap->permset, id); + count ++; + } + ap = ap->next; + } + } + + return (acl->acl_text_w); +} + + +static void +append_id_w(wchar_t **wp, int id) +{ + if (id < 0) + id = 0; + if (id > 9) + append_id_w(wp, id / 10); + *(*wp)++ = L"0123456789"[id % 10]; +} + +static void +append_entry_w(wchar_t **wp, const wchar_t *prefix, int tag, + const wchar_t *wname, int perm, int id) +{ + if (prefix != NULL) { + wcscpy(*wp, prefix); + *wp += wcslen(*wp); + } + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + wname = NULL; + id = -1; + /* FALLTHROUGH */ + case ARCHIVE_ENTRY_ACL_USER: + wcscpy(*wp, L"user"); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + wname = NULL; + id = -1; + /* FALLTHROUGH */ + case ARCHIVE_ENTRY_ACL_GROUP: + wcscpy(*wp, L"group"); + break; + case ARCHIVE_ENTRY_ACL_MASK: + wcscpy(*wp, L"mask"); + wname = NULL; + id = -1; + break; + case ARCHIVE_ENTRY_ACL_OTHER: + wcscpy(*wp, L"other"); + wname = NULL; + id = -1; + break; + } + *wp += wcslen(*wp); + *(*wp)++ = L':'; + if (wname != NULL) { + wcscpy(*wp, wname); + *wp += wcslen(*wp); + } else if (tag == ARCHIVE_ENTRY_ACL_USER + || tag == ARCHIVE_ENTRY_ACL_GROUP) { + append_id_w(wp, id); + id = -1; + } + *(*wp)++ = L':'; + *(*wp)++ = (perm & 0444) ? L'r' : L'-'; + *(*wp)++ = (perm & 0222) ? L'w' : L'-'; + *(*wp)++ = (perm & 0111) ? L'x' : L'-'; + if (id != -1) { + *(*wp)++ = L':'; + append_id_w(wp, id); + } + **wp = L'\0'; +} + +int +archive_acl_text_l(struct archive_acl *acl, int flags, + const char **acl_text, size_t *acl_text_len, + struct archive_string_conv *sc) +{ + int count; + size_t length; + const char *name; + const char *prefix; + char separator; + struct archive_acl_entry *ap; + size_t len; + int id, r; + char *p; + + if (acl->acl_text != NULL) { + free (acl->acl_text); + acl->acl_text = NULL; + } + + *acl_text = NULL; + if (acl_text_len != NULL) + *acl_text_len = 0; + separator = ','; + count = 0; + length = 0; + ap = acl->acl_head; + while (ap != NULL) { + if ((ap->type & flags) != 0) { + count++; + if ((flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) && + (ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT)) + length += 8; /* "default:" */ + length += 5; /* tag name */ + length += 1; /* colon */ + r = archive_mstring_get_mbs_l( + &ap->name, &name, &len, sc); + if (r != 0) + return (-1); + if (len > 0 && name != NULL) + length += len; + else + length += sizeof(uid_t) * 3 + 1; + length ++; /* colon */ + length += 3; /* rwx */ + length += 1; /* colon */ + length += max(sizeof(uid_t), sizeof(gid_t)) * 3 + 1; + length ++; /* newline */ + } + ap = ap->next; + } + + if (count > 0 && ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0)) { + length += 10; /* "user::rwx\n" */ + length += 11; /* "group::rwx\n" */ + length += 11; /* "other::rwx\n" */ + } + + if (count == 0) + return (0); + + /* Now, allocate the string and actually populate it. */ + p = acl->acl_text = (char *)malloc(length); + if (p == NULL) + __archive_errx(1, "No memory to generate the text version of the ACL"); + count = 0; + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) { + append_entry(&p, NULL, ARCHIVE_ENTRY_ACL_USER_OBJ, NULL, + acl->mode & 0700, -1); + *p++ = ','; + append_entry(&p, NULL, ARCHIVE_ENTRY_ACL_GROUP_OBJ, NULL, + acl->mode & 0070, -1); + *p++ = ','; + append_entry(&p, NULL, ARCHIVE_ENTRY_ACL_OTHER, NULL, + acl->mode & 0007, -1); + count += 3; + + for (ap = acl->acl_head; ap != NULL; ap = ap->next) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_ACCESS) == 0) + continue; + r = archive_mstring_get_mbs_l( + &ap->name, &name, &len, sc); + if (r != 0) + return (-1); + *p++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry(&p, NULL, ap->tag, name, + ap->permset, id); + count++; + } + } + + + if ((flags & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0) { + if (flags & ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT) + prefix = "default:"; + else + prefix = NULL; + count = 0; + for (ap = acl->acl_head; ap != NULL; ap = ap->next) { + if ((ap->type & ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) == 0) + continue; + r = archive_mstring_get_mbs_l( + &ap->name, &name, &len, sc); + if (r != 0) + return (-1); + if (count > 0) + *p++ = separator; + if (flags & ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID) + id = ap->id; + else + id = -1; + append_entry(&p, prefix, ap->tag, + name, ap->permset, id); + count ++; + } + } + + *acl_text = acl->acl_text; + if (acl_text_len != NULL) + *acl_text_len = strlen(acl->acl_text); + return (0); +} + +static void +append_id(char **p, int id) +{ + if (id < 0) + id = 0; + if (id > 9) + append_id(p, id / 10); + *(*p)++ = "0123456789"[id % 10]; +} + +static void +append_entry(char **p, const char *prefix, int tag, + const char *name, int perm, int id) +{ + if (prefix != NULL) { + strcpy(*p, prefix); + *p += strlen(*p); + } + switch (tag) { + case ARCHIVE_ENTRY_ACL_USER_OBJ: + name = NULL; + id = -1; + /* FALLTHROUGH */ + case ARCHIVE_ENTRY_ACL_USER: + strcpy(*p, "user"); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + name = NULL; + id = -1; + /* FALLTHROUGH */ + case ARCHIVE_ENTRY_ACL_GROUP: + strcpy(*p, "group"); + break; + case ARCHIVE_ENTRY_ACL_MASK: + strcpy(*p, "mask"); + name = NULL; + id = -1; + break; + case ARCHIVE_ENTRY_ACL_OTHER: + strcpy(*p, "other"); + name = NULL; + id = -1; + break; + } + *p += strlen(*p); + *(*p)++ = ':'; + if (name != NULL) { + strcpy(*p, name); + *p += strlen(*p); + } else if (tag == ARCHIVE_ENTRY_ACL_USER + || tag == ARCHIVE_ENTRY_ACL_GROUP) { + append_id(p, id); + id = -1; + } + *(*p)++ = ':'; + *(*p)++ = (perm & 0444) ? 'r' : '-'; + *(*p)++ = (perm & 0222) ? 'w' : '-'; + *(*p)++ = (perm & 0111) ? 'x' : '-'; + if (id != -1) { + *(*p)++ = ':'; + append_id(p, id); + } + **p = '\0'; +} + +/* + * Parse a textual ACL. This automatically recognizes and supports + * extensions described above. The 'type' argument is used to + * indicate the type that should be used for any entries not + * explicitly marked as "default:". + */ +int +archive_acl_parse_w(struct archive_acl *acl, + const wchar_t *text, int default_type) +{ + struct { + const wchar_t *start; + const wchar_t *end; + } field[4], name; + + int fields, n; + int type, tag, permset, id; + wchar_t sep; + + while (text != NULL && *text != L'\0') { + /* + * Parse the fields out of the next entry, + * advance 'text' to start of next entry. + */ + fields = 0; + do { + const wchar_t *start, *end; + next_field_w(&text, &start, &end, &sep); + if (fields < 4) { + field[fields].start = start; + field[fields].end = end; + } + ++fields; + } while (sep == L':'); + + /* Set remaining fields to blank. */ + for (n = fields; n < 4; ++n) + field[n].start = field[n].end = NULL; + + /* Check for a numeric ID in field 1 or 3. */ + id = -1; + isint_w(field[1].start, field[1].end, &id); + /* Field 3 is optional. */ + if (id == -1 && fields > 3) + isint_w(field[3].start, field[3].end, &id); + + /* + * Solaris extension: "defaultuser::rwx" is the + * default ACL corresponding to "user::rwx", etc. + */ + if (field[0].end - field[0].start > 7 + && wmemcmp(field[0].start, L"default", 7) == 0) { + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + field[0].start += 7; + } else + type = default_type; + + name.start = name.end = NULL; + if (prefix_w(field[0].start, field[0].end, L"user")) { + if (!ismode_w(field[2].start, field[2].end, &permset)) + return (ARCHIVE_WARN); + if (id != -1 || field[1].start < field[1].end) { + tag = ARCHIVE_ENTRY_ACL_USER; + name = field[1]; + } else + tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (prefix_w(field[0].start, field[0].end, L"group")) { + if (!ismode_w(field[2].start, field[2].end, &permset)) + return (ARCHIVE_WARN); + if (id != -1 || field[1].start < field[1].end) { + tag = ARCHIVE_ENTRY_ACL_GROUP; + name = field[1]; + } else + tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (prefix_w(field[0].start, field[0].end, L"other")) { + if (fields == 2 + && field[1].start < field[1].end + && ismode_w(field[1].start, field[1].end, &permset)) { + /* This is Solaris-style "other:rwx" */ + } else if (fields == 3 + && field[1].start == field[1].end + && field[2].start < field[2].end + && ismode_w(field[2].start, field[2].end, &permset)) { + /* This is FreeBSD-style "other::rwx" */ + } else + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_OTHER; + } else if (prefix_w(field[0].start, field[0].end, L"mask")) { + if (fields == 2 + && field[1].start < field[1].end + && ismode_w(field[1].start, field[1].end, &permset)) { + /* This is Solaris-style "mask:rwx" */ + } else if (fields == 3 + && field[1].start == field[1].end + && field[2].start < field[2].end + && ismode_w(field[2].start, field[2].end, &permset)) { + /* This is FreeBSD-style "mask::rwx" */ + } else + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_MASK; + } else + return (ARCHIVE_WARN); + + /* Add entry to the internal list. */ + archive_acl_add_entry_w_len(acl, type, permset, + tag, id, name.start, name.end - name.start); + } + return (ARCHIVE_OK); +} + +/* + * Parse a string to a positive decimal integer. Returns true if + * the string is non-empty and consists only of decimal digits, + * false otherwise. + */ +static int +isint_w(const wchar_t *start, const wchar_t *end, int *result) +{ + int n = 0; + if (start >= end) + return (0); + while (start < end) { + if (*start < '0' || *start > '9') + return (0); + if (n > (INT_MAX / 10) || + (n == INT_MAX / 10 && (*start - '0') > INT_MAX % 10)) { + n = INT_MAX; + } else { + n *= 10; + n += *start - '0'; + } + start++; + } + *result = n; + return (1); +} + +/* + * Parse a string as a mode field. Returns true if + * the string is non-empty and consists only of mode characters, + * false otherwise. + */ +static int +ismode_w(const wchar_t *start, const wchar_t *end, int *permset) +{ + const wchar_t *p; + + if (start >= end) + return (0); + p = start; + *permset = 0; + while (p < end) { + switch (*p++) { + case 'r': case 'R': + *permset |= ARCHIVE_ENTRY_ACL_READ; + break; + case 'w': case 'W': + *permset |= ARCHIVE_ENTRY_ACL_WRITE; + break; + case 'x': case 'X': + *permset |= ARCHIVE_ENTRY_ACL_EXECUTE; + break; + case '-': + break; + default: + return (0); + } + } + return (1); +} + +/* + * Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated + * to point to just after the separator. *start points to the first + * character of the matched text and *end just after the last + * character of the matched identifier. In particular *end - *start + * is the length of the field body, not including leading or trailing + * whitespace. + */ +static void +next_field_w(const wchar_t **wp, const wchar_t **start, + const wchar_t **end, wchar_t *sep) +{ + /* Skip leading whitespace to find start of field. */ + while (**wp == L' ' || **wp == L'\t' || **wp == L'\n') { + (*wp)++; + } + *start = *wp; + + /* Scan for the separator. */ + while (**wp != L'\0' && **wp != L',' && **wp != L':' && + **wp != L'\n') { + (*wp)++; + } + *sep = **wp; + + /* Trim trailing whitespace to locate end of field. */ + *end = *wp - 1; + while (**end == L' ' || **end == L'\t' || **end == L'\n') { + (*end)--; + } + (*end)++; + + /* Adjust scanner location. */ + if (**wp != L'\0') + (*wp)++; +} + +/* + * Return true if the characters [start...end) are a prefix of 'test'. + * This makes it easy to handle the obvious abbreviations: 'u' for 'user', etc. + */ +static int +prefix_w(const wchar_t *start, const wchar_t *end, const wchar_t *test) +{ + if (start == end) + return (0); + + if (*start++ != *test++) + return (0); + + while (start < end && *start++ == *test++) + ; + + if (start < end) + return (0); + + return (1); +} + +/* + * Parse a textual ACL. This automatically recognizes and supports + * extensions described above. The 'type' argument is used to + * indicate the type that should be used for any entries not + * explicitly marked as "default:". + */ +int +archive_acl_parse_l(struct archive_acl *acl, + const char *text, int default_type, struct archive_string_conv *sc) +{ + struct { + const char *start; + const char *end; + } field[4], name; + + int fields, n, r, ret = ARCHIVE_OK; + int type, tag, permset, id; + char sep; + + while (text != NULL && *text != '\0') { + /* + * Parse the fields out of the next entry, + * advance 'text' to start of next entry. + */ + fields = 0; + do { + const char *start, *end; + next_field(&text, &start, &end, &sep); + if (fields < 4) { + field[fields].start = start; + field[fields].end = end; + } + ++fields; + } while (sep == ':'); + + /* Set remaining fields to blank. */ + for (n = fields; n < 4; ++n) + field[n].start = field[n].end = NULL; + + /* Check for a numeric ID in field 1 or 3. */ + id = -1; + isint(field[1].start, field[1].end, &id); + /* Field 3 is optional. */ + if (id == -1 && fields > 3) + isint(field[3].start, field[3].end, &id); + + /* + * Solaris extension: "defaultuser::rwx" is the + * default ACL corresponding to "user::rwx", etc. + */ + if (field[0].end - field[0].start > 7 + && memcmp(field[0].start, "default", 7) == 0) { + type = ARCHIVE_ENTRY_ACL_TYPE_DEFAULT; + field[0].start += 7; + } else + type = default_type; + + name.start = name.end = NULL; + if (prefix(field[0].start, field[0].end, "user")) { + if (!ismode(field[2].start, field[2].end, &permset)) + return (ARCHIVE_WARN); + if (id != -1 || field[1].start < field[1].end) { + tag = ARCHIVE_ENTRY_ACL_USER; + name = field[1]; + } else + tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (prefix(field[0].start, field[0].end, "group")) { + if (!ismode(field[2].start, field[2].end, &permset)) + return (ARCHIVE_WARN); + if (id != -1 || field[1].start < field[1].end) { + tag = ARCHIVE_ENTRY_ACL_GROUP; + name = field[1]; + } else + tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (prefix(field[0].start, field[0].end, "other")) { + if (fields == 2 + && field[1].start < field[1].end + && ismode(field[1].start, field[1].end, &permset)) { + /* This is Solaris-style "other:rwx" */ + } else if (fields == 3 + && field[1].start == field[1].end + && field[2].start < field[2].end + && ismode(field[2].start, field[2].end, &permset)) { + /* This is FreeBSD-style "other::rwx" */ + } else + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_OTHER; + } else if (prefix(field[0].start, field[0].end, "mask")) { + if (fields == 2 + && field[1].start < field[1].end + && ismode(field[1].start, field[1].end, &permset)) { + /* This is Solaris-style "mask:rwx" */ + } else if (fields == 3 + && field[1].start == field[1].end + && field[2].start < field[2].end + && ismode(field[2].start, field[2].end, &permset)) { + /* This is FreeBSD-style "mask::rwx" */ + } else + return (ARCHIVE_WARN); + tag = ARCHIVE_ENTRY_ACL_MASK; + } else + return (ARCHIVE_WARN); + + /* Add entry to the internal list. */ + r = archive_acl_add_entry_len_l(acl, type, permset, + tag, id, name.start, name.end - name.start, sc); + if (r < ARCHIVE_WARN) + return (r); + if (r != ARCHIVE_OK) + ret = ARCHIVE_WARN; + } + return (ret); +} + +/* + * Parse a string to a positive decimal integer. Returns true if + * the string is non-empty and consists only of decimal digits, + * false otherwise. + */ +static int +isint(const char *start, const char *end, int *result) +{ + int n = 0; + if (start >= end) + return (0); + while (start < end) { + if (*start < '0' || *start > '9') + return (0); + if (n > (INT_MAX / 10) || + (n == INT_MAX / 10 && (*start - '0') > INT_MAX % 10)) { + n = INT_MAX; + } else { + n *= 10; + n += *start - '0'; + } + start++; + } + *result = n; + return (1); +} + +/* + * Parse a string as a mode field. Returns true if + * the string is non-empty and consists only of mode characters, + * false otherwise. + */ +static int +ismode(const char *start, const char *end, int *permset) +{ + const char *p; + + if (start >= end) + return (0); + p = start; + *permset = 0; + while (p < end) { + switch (*p++) { + case 'r': case 'R': + *permset |= ARCHIVE_ENTRY_ACL_READ; + break; + case 'w': case 'W': + *permset |= ARCHIVE_ENTRY_ACL_WRITE; + break; + case 'x': case 'X': + *permset |= ARCHIVE_ENTRY_ACL_EXECUTE; + break; + case '-': + break; + default: + return (0); + } + } + return (1); +} + +/* + * Match "[:whitespace:]*(.*)[:whitespace:]*[:,\n]". *wp is updated + * to point to just after the separator. *start points to the first + * character of the matched text and *end just after the last + * character of the matched identifier. In particular *end - *start + * is the length of the field body, not including leading or trailing + * whitespace. + */ +static void +next_field(const char **p, const char **start, + const char **end, char *sep) +{ + /* Skip leading whitespace to find start of field. */ + while (**p == ' ' || **p == '\t' || **p == '\n') { + (*p)++; + } + *start = *p; + + /* Scan for the separator. */ + while (**p != '\0' && **p != ',' && **p != ':' && **p != '\n') { + (*p)++; + } + *sep = **p; + + /* Trim trailing whitespace to locate end of field. */ + *end = *p - 1; + while (**end == ' ' || **end == '\t' || **end == '\n') { + (*end)--; + } + (*end)++; + + /* Adjust scanner location. */ + if (**p != '\0') + (*p)++; +} + +/* + * Return true if the characters [start...end) are a prefix of 'test'. + * This makes it easy to handle the obvious abbreviations: 'u' for 'user', etc. + */ +static int +prefix(const char *start, const char *end, const char *test) +{ + if (start == end) + return (0); + + if (*start++ != *test++) + return (0); + + while (start < end && *start++ == *test++) + ; + + if (start < end) + return (0); + + return (1); +} diff --git a/libarchive/archive_acl_private.h b/libarchive/archive_acl_private.h new file mode 100644 index 0000000..1421adb --- /dev/null +++ b/libarchive/archive_acl_private.h @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD$ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_ACL_PRIVATE_H_INCLUDED +#define ARCHIVE_ACL_PRIVATE_H_INCLUDED + +#include "archive_string.h" + +struct archive_acl_entry { + struct archive_acl_entry *next; + int type; /* E.g., access or default */ + int tag; /* E.g., user/group/other/mask */ + int permset; /* r/w/x bits */ + int id; /* uid/gid for user/group */ + struct archive_mstring name; /* uname/gname */ +}; + +struct archive_acl { + mode_t mode; + struct archive_acl_entry *acl_head; + struct archive_acl_entry *acl_p; + int acl_state; /* See acl_next for details. */ + wchar_t *acl_text_w; + char *acl_text; + int acl_types; +}; + +void archive_acl_clear(struct archive_acl *); +void archive_acl_copy(struct archive_acl *, struct archive_acl *); +int archive_acl_count(struct archive_acl *, int); +int archive_acl_reset(struct archive_acl *, int); +int archive_acl_next(struct archive *, struct archive_acl *, int, + int *, int *, int *, int *, const char **); + +int archive_acl_add_entry(struct archive_acl *, int, int, int, int, const char *); +int archive_acl_add_entry_w_len(struct archive_acl *, + int, int, int, int, const wchar_t *, size_t); +int archive_acl_add_entry_len(struct archive_acl *, + int, int, int, int, const char *, size_t); + +const wchar_t *archive_acl_text_w(struct archive *, struct archive_acl *, int); +int archive_acl_text_l(struct archive_acl *, int, const char **, size_t *, + struct archive_string_conv *); + +/* + * Private ACL parser. This is private because it handles some + * very weird formats that clients should not be messing with. + * Clients should only deal with their platform-native formats. + * Because of the need to support many formats cleanly, new arguments + * are likely to get added on a regular basis. Clients who try to use + * this interface are likely to be surprised when it changes. + */ +int archive_acl_parse_w(struct archive_acl *, + const wchar_t *, int /* type */); +int archive_acl_parse_l(struct archive_acl *, + const char *, int /* type */, + struct archive_string_conv *); + +#endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */ diff --git a/libarchive/archive_check_magic.c b/libarchive/archive_check_magic.c new file mode 100644 index 0000000..9122955 --- /dev/null +++ b/libarchive/archive_check_magic.c @@ -0,0 +1,174 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_check_magic.c 201089 2009-12-28 02:20:23Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#if defined(_WIN32) && !defined(__CYGWIN__) +#include +#include +#endif + +#include "archive_private.h" + +static void +errmsg(const char *m) +{ + size_t s = strlen(m); + ssize_t written; + + while (s > 0) { + written = write(2, m, strlen(m)); + if (written <= 0) + return; + m += written; + s -= written; + } +} + +static void +diediedie(void) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) + /* Cause a breakpoint exception */ + DebugBreak(); +#endif + abort(); /* Terminate the program abnormally. */ +} + +static const char * +state_name(unsigned s) +{ + switch (s) { + case ARCHIVE_STATE_NEW: return ("new"); + case ARCHIVE_STATE_HEADER: return ("header"); + case ARCHIVE_STATE_DATA: return ("data"); + case ARCHIVE_STATE_EOF: return ("eof"); + case ARCHIVE_STATE_CLOSED: return ("closed"); + case ARCHIVE_STATE_FATAL: return ("fatal"); + default: return ("??"); + } +} + +static const char * +archive_handle_type_name(unsigned m) +{ + switch (m) { + case ARCHIVE_WRITE_MAGIC: return ("archive_write"); + case ARCHIVE_READ_MAGIC: return ("archive_read"); + case ARCHIVE_WRITE_DISK_MAGIC: return ("archive_write_disk"); + case ARCHIVE_READ_DISK_MAGIC: return ("archive_read_disk"); + default: return NULL; + } +} + + +static char * +write_all_states(char *buff, unsigned int states) +{ + unsigned int lowbit; + + buff[0] = '\0'; + + /* A trick for computing the lowest set bit. */ + while ((lowbit = states & (1 + ~states)) != 0) { + states &= ~lowbit; /* Clear the low bit. */ + strcat(buff, state_name(lowbit)); + if (states != 0) + strcat(buff, "/"); + } + return buff; +} + +/* + * Check magic value and current state. + * Magic value mismatches are fatal and result in calls to abort(). + * State mismatches return ARCHIVE_FATAL. + * Otherwise, returns ARCHIVE_OK. + * + * This is designed to catch serious programming errors that violate + * the libarchive API. + */ +int +__archive_check_magic(struct archive *a, unsigned int magic, + unsigned int state, const char *function) +{ + char states1[64]; + char states2[64]; + const char *handle_type; + + /* + * If this isn't some form of archive handle, + * then the library user has screwed up so bad that + * we don't even have a reliable way to report an error. + */ + handle_type = archive_handle_type_name(a->magic); + + if (!handle_type) { + errmsg("PROGRAMMER ERROR: Function "); + errmsg(function); + errmsg(" invoked with invalid archive handle.\n"); + diediedie(); + } + + if (a->magic != magic) { + archive_set_error(a, -1, + "PROGRAMMER ERROR: Function '%s' invoked" + " on '%s' archive object, which is not supported.", + function, + handle_type); + a->state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + + if ((a->state & state) == 0) { + /* If we're already FATAL, don't overwrite the error. */ + if (a->state != ARCHIVE_STATE_FATAL) + archive_set_error(a, -1, + "INTERNAL ERROR: Function '%s' invoked with" + " archive structure in state '%s'," + " should be in state '%s'", + function, + write_all_states(states1, a->state), + write_all_states(states2, state)); + a->state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + return ARCHIVE_OK; +} diff --git a/libarchive/archive_crc32.h b/libarchive/archive_crc32.h new file mode 100644 index 0000000..cd633af --- /dev/null +++ b/libarchive/archive_crc32.h @@ -0,0 +1,78 @@ +/*- + * Copyright (c) 2009 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_crc32.h 201102 2009-12-28 03:11:36Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +/* + * When zlib is unavailable, we should still be able to validate + * uncompressed zip archives. That requires us to be able to compute + * the CRC32 check value. This is a drop-in compatible replacement + * for crc32() from zlib. It's slower than the zlib implementation, + * but still pretty fast: This runs about 300MB/s on my 3GHz P4 + * compared to about 800MB/s for the zlib implementation. + */ +static unsigned long +crc32(unsigned long crc, const void *_p, size_t len) +{ + unsigned long crc2, b, i; + const unsigned char *p = _p; + static volatile int crc_tbl_inited = 0; + static unsigned long crc_tbl[256]; + + if (!crc_tbl_inited) { + for (b = 0; b < 256; ++b) { + crc2 = b; + for (i = 8; i > 0; --i) { + if (crc2 & 1) + crc2 = (crc2 >> 1) ^ 0xedb88320UL; + else + crc2 = (crc2 >> 1); + } + crc_tbl[b] = crc2; + } + crc_tbl_inited = 1; + } + + crc = crc ^ 0xffffffffUL; + /* A use of this loop is about 20% - 30% faster than + * no use version in any optimization option of gcc. */ + for (;len >= 8; len -= 8) { + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + } + while (len--) + crc = crc_tbl[(crc ^ *p++) & 0xff] ^ (crc >> 8); + return (crc ^ 0xffffffffUL); +} diff --git a/libarchive/archive_crypto.c b/libarchive/archive_crypto.c new file mode 100644 index 0000000..2caf571 --- /dev/null +++ b/libarchive/archive_crypto.c @@ -0,0 +1,1427 @@ +/*- +* Copyright (c) 2003-2007 Tim Kientzle +* Copyright (c) 2011 Andres Mejia +* Copyright (c) 2011 Michihiro NAKAJIMA +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "archive_platform.h" + +#include "archive.h" +#include "archive_crypto_private.h" + +/* In particular, force the configure probe to break if it tries + * to test a combination of OpenSSL and libmd. */ +#if defined(ARCHIVE_CRYPTO_OPENSSL) && defined(ARCHIVE_CRYPTO_LIBMD) +#error Cannot use both OpenSSL and libmd. +#endif + +/* + * Message digest functions for Windows platform. + */ +#if defined(ARCHIVE_CRYPTO_MD5_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA1_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA256_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA384_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA512_WIN) + +/* + * Initialize a Message digest. + */ +static int +win_crypto_init(Digest_CTX *ctx, ALG_ID algId) +{ + + ctx->valid = 0; + if (!CryptAcquireContext(&ctx->cryptProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + if (GetLastError() != (DWORD)NTE_BAD_KEYSET) + return (ARCHIVE_FAILED); + if (!CryptAcquireContext(&ctx->cryptProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_NEWKEYSET)) + return (ARCHIVE_FAILED); + } + + if (!CryptCreateHash(ctx->cryptProv, algId, 0, 0, &ctx->hash)) { + CryptReleaseContext(ctx->cryptProv, 0); + return (ARCHIVE_FAILED); + } + + ctx->valid = 1; + return (ARCHIVE_OK); +} + +/* + * Update a Message digest. + */ +static int +win_crypto_Update(Digest_CTX *ctx, const unsigned char *buf, size_t len) +{ + + if (!ctx->valid) + return (ARCHIVE_FAILED); + + CryptHashData(ctx->hash, + (unsigned char *)(uintptr_t)buf, + (DWORD)len, 0); + return (ARCHIVE_OK); +} + +static int +win_crypto_Final(unsigned char *buf, size_t bufsize, Digest_CTX *ctx) +{ + DWORD siglen = bufsize; + + if (!ctx->valid) + return (ARCHIVE_FAILED); + + CryptGetHashParam(ctx->hash, HP_HASHVAL, buf, &siglen, 0); + CryptDestroyHash(ctx->hash); + CryptReleaseContext(ctx->cryptProv, 0); + ctx->valid = 0; + return (ARCHIVE_OK); +} + +#endif /* defined(ARCHIVE_CRYPTO_*_WIN) */ + + +/* MD5 implementations */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBC) + +static int +__archive_libc_md5init(archive_md5_ctx *ctx) +{ + MD5Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + MD5Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_md5final(archive_md5_ctx *ctx, void *md) +{ + MD5Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_MD5_LIBMD) + +static int +__archive_libmd_md5init(archive_md5_ctx *ctx) +{ + MD5Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + MD5Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_md5final(archive_md5_ctx *ctx, void *md) +{ + MD5Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) + +static int +__archive_libsystem_md5init(archive_md5_ctx *ctx) +{ + CC_MD5_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + CC_MD5_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_md5final(archive_md5_ctx *ctx, void *md) +{ + CC_MD5_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_MD5_NETTLE) + +static int +__archive_nettle_md5init(archive_md5_ctx *ctx) +{ + md5_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + md5_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_md5final(archive_md5_ctx *ctx, void *md) +{ + md5_digest(ctx, MD5_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_MD5_OPENSSL) + +static int +__archive_openssl_md5init(archive_md5_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_md5()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_md5final(archive_md5_ctx *ctx, void *md) +{ + /* HACK: archive_write_set_format_xar.c is finalizing empty contexts, so + * this is meant to cope with that. Real fix is probably to fix + * archive_write_set_format_xar.c + */ + if (ctx->digest) + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_MD5_WIN) + +static int +__archive_windowsapi_md5init(archive_md5_ctx *ctx) +{ + return (win_crypto_init(ctx, CALG_MD5)); +} + +static int +__archive_windowsapi_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + return (win_crypto_Update(ctx, indata, insize)); +} + +static int +__archive_windowsapi_md5final(archive_md5_ctx *ctx, void *md) +{ + return (win_crypto_Final(md, 16, ctx)); +} + +#else + +static int +__archive_stub_md5init(archive_md5_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_md5update(archive_md5_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_md5final(archive_md5_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* RIPEMD160 implementations */ +#if defined(ARCHIVE_CRYPTO_RMD160_LIBC) + +static int +__archive_libc_ripemd160init(archive_rmd160_ctx *ctx) +{ + RMD160Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_ripemd160update(archive_rmd160_ctx *ctx, const void *indata, + size_t insize) +{ + RMD160Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_ripemd160final(archive_rmd160_ctx *ctx, void *md) +{ + RMD160Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_RMD160_LIBMD) + +static int +__archive_libmd_ripemd160init(archive_rmd160_ctx *ctx) +{ + RIPEMD160_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_ripemd160update(archive_rmd160_ctx *ctx, const void *indata, + size_t insize) +{ + RIPEMD160_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_ripemd160final(archive_rmd160_ctx *ctx, void *md) +{ + RIPEMD160_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_RMD160_NETTLE) + +static int +__archive_nettle_ripemd160init(archive_rmd160_ctx *ctx) +{ + ripemd160_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_ripemd160update(archive_rmd160_ctx *ctx, const void *indata, + size_t insize) +{ + ripemd160_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_ripemd160final(archive_rmd160_ctx *ctx, void *md) +{ + ripemd160_digest(ctx, RIPEMD160_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_RMD160_OPENSSL) + +static int +__archive_openssl_ripemd160init(archive_rmd160_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_ripemd160()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_ripemd160update(archive_rmd160_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_ripemd160final(archive_rmd160_ctx *ctx, void *md) +{ + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#else + +static int +__archive_stub_ripemd160init(archive_rmd160_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_ripemd160update(archive_rmd160_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_ripemd160final(archive_rmd160_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* SHA1 implementations */ +#if defined(ARCHIVE_CRYPTO_SHA1_LIBC) + +static int +__archive_libc_sha1init(archive_sha1_ctx *ctx) +{ + SHA1Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + SHA1Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha1final(archive_sha1_ctx *ctx, void *md) +{ + SHA1Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBMD) + +static int +__archive_libmd_sha1init(archive_sha1_ctx *ctx) +{ + SHA1_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + SHA1_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha1final(archive_sha1_ctx *ctx, void *md) +{ + SHA1_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBSYSTEM) + +static int +__archive_libsystem_sha1init(archive_sha1_ctx *ctx) +{ + CC_SHA1_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + CC_SHA1_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha1final(archive_sha1_ctx *ctx, void *md) +{ + CC_SHA1_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA1_NETTLE) + +static int +__archive_nettle_sha1init(archive_sha1_ctx *ctx) +{ + sha1_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + sha1_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha1final(archive_sha1_ctx *ctx, void *md) +{ + sha1_digest(ctx, SHA1_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA1_OPENSSL) + +static int +__archive_openssl_sha1init(archive_sha1_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_sha1()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha1final(archive_sha1_ctx *ctx, void *md) +{ + /* HACK: archive_write_set_format_xar.c is finalizing empty contexts, so + * this is meant to cope with that. Real fix is probably to fix + * archive_write_set_format_xar.c + */ + if (ctx->digest) + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA1_WIN) + +static int +__archive_windowsapi_sha1init(archive_sha1_ctx *ctx) +{ + return (win_crypto_init(ctx, CALG_SHA1)); +} + +static int +__archive_windowsapi_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + return (win_crypto_Update(ctx, indata, insize)); +} + +static int +__archive_windowsapi_sha1final(archive_sha1_ctx *ctx, void *md) +{ + return (win_crypto_Final(md, 20, ctx)); +} + +#else + +static int +__archive_stub_sha1init(archive_sha1_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha1update(archive_sha1_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha1final(archive_sha1_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* SHA256 implementations */ +#if defined(ARCHIVE_CRYPTO_SHA256_LIBC) + +static int +__archive_libc_sha256init(archive_sha256_ctx *ctx) +{ + SHA256_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + SHA256_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha256final(archive_sha256_ctx *ctx, void *md) +{ + SHA256_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC2) + +static int +__archive_libc2_sha256init(archive_sha256_ctx *ctx) +{ + SHA256Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + SHA256Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha256final(archive_sha256_ctx *ctx, void *md) +{ + SHA256Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC3) + +static int +__archive_libc3_sha256init(archive_sha256_ctx *ctx) +{ + SHA256Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + SHA256Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha256final(archive_sha256_ctx *ctx, void *md) +{ + SHA256Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBMD) + +static int +__archive_libmd_sha256init(archive_sha256_ctx *ctx) +{ + SHA256_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + SHA256_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha256final(archive_sha256_ctx *ctx, void *md) +{ + SHA256_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBSYSTEM) + +static int +__archive_libsystem_sha256init(archive_sha256_ctx *ctx) +{ + CC_SHA256_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + CC_SHA256_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha256final(archive_sha256_ctx *ctx, void *md) +{ + CC_SHA256_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_NETTLE) + +static int +__archive_nettle_sha256init(archive_sha256_ctx *ctx) +{ + sha256_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + sha256_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha256final(archive_sha256_ctx *ctx, void *md) +{ + sha256_digest(ctx, SHA256_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) + +static int +__archive_openssl_sha256init(archive_sha256_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_sha256()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha256final(archive_sha256_ctx *ctx, void *md) +{ + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA256_WIN) + +static int +__archive_windowsapi_sha256init(archive_sha256_ctx *ctx) +{ + return (win_crypto_init(ctx, CALG_SHA_256)); +} + +static int +__archive_windowsapi_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + return (win_crypto_Update(ctx, indata, insize)); +} + +static int +__archive_windowsapi_sha256final(archive_sha256_ctx *ctx, void *md) +{ + return (win_crypto_Final(md, 32, ctx)); +} + +#else + +static int +__archive_stub_sha256init(archive_sha256_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha256update(archive_sha256_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha256final(archive_sha256_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* SHA384 implementations */ +#if defined(ARCHIVE_CRYPTO_SHA384_LIBC) + +static int +__archive_libc_sha384init(archive_sha384_ctx *ctx) +{ + SHA384_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + SHA384_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha384final(archive_sha384_ctx *ctx, void *md) +{ + SHA384_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC2) + +static int +__archive_libc2_sha384init(archive_sha384_ctx *ctx) +{ + SHA384Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + SHA384Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha384final(archive_sha384_ctx *ctx, void *md) +{ + SHA384Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC3) + +static int +__archive_libc3_sha384init(archive_sha384_ctx *ctx) +{ + SHA384Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + SHA384Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha384final(archive_sha384_ctx *ctx, void *md) +{ + SHA384Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBSYSTEM) + +static int +__archive_libsystem_sha384init(archive_sha384_ctx *ctx) +{ + CC_SHA384_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + CC_SHA384_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha384final(archive_sha384_ctx *ctx, void *md) +{ + CC_SHA384_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_NETTLE) + +static int +__archive_nettle_sha384init(archive_sha384_ctx *ctx) +{ + sha384_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + sha384_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha384final(archive_sha384_ctx *ctx, void *md) +{ + sha384_digest(ctx, SHA384_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_OPENSSL) + +static int +__archive_openssl_sha384init(archive_sha384_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_sha384()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha384final(archive_sha384_ctx *ctx, void *md) +{ + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA384_WIN) + +static int +__archive_windowsapi_sha384init(archive_sha384_ctx *ctx) +{ + return (win_crypto_init(ctx, CALG_SHA_384)); +} + +static int +__archive_windowsapi_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + return (win_crypto_Update(ctx, indata, insize)); +} + +static int +__archive_windowsapi_sha384final(archive_sha384_ctx *ctx, void *md) +{ + return (win_crypto_Final(md, 48, ctx)); +} + +#else + +static int +__archive_stub_sha384init(archive_sha384_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha384update(archive_sha384_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha384final(archive_sha384_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* SHA512 implementations */ +#if defined(ARCHIVE_CRYPTO_SHA512_LIBC) + +static int +__archive_libc_sha512init(archive_sha512_ctx *ctx) +{ + SHA512_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + SHA512_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc_sha512final(archive_sha512_ctx *ctx, void *md) +{ + SHA512_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC2) + +static int +__archive_libc2_sha512init(archive_sha512_ctx *ctx) +{ + SHA512Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + SHA512Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc2_sha512final(archive_sha512_ctx *ctx, void *md) +{ + SHA512Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC3) + +static int +__archive_libc3_sha512init(archive_sha512_ctx *ctx) +{ + SHA512Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + SHA512Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libc3_sha512final(archive_sha512_ctx *ctx, void *md) +{ + SHA512Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBMD) + +static int +__archive_libmd_sha512init(archive_sha512_ctx *ctx) +{ + SHA512_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + SHA512_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libmd_sha512final(archive_sha512_ctx *ctx, void *md) +{ + SHA512_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBSYSTEM) + +static int +__archive_libsystem_sha512init(archive_sha512_ctx *ctx) +{ + CC_SHA512_Init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + CC_SHA512_Update(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_libsystem_sha512final(archive_sha512_ctx *ctx, void *md) +{ + CC_SHA512_Final(md, ctx); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_NETTLE) + +static int +__archive_nettle_sha512init(archive_sha512_ctx *ctx) +{ + sha512_init(ctx); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + sha512_update(ctx, insize, indata); + return (ARCHIVE_OK); +} + +static int +__archive_nettle_sha512final(archive_sha512_ctx *ctx, void *md) +{ + sha512_digest(ctx, SHA512_DIGEST_SIZE, md); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_OPENSSL) + +static int +__archive_openssl_sha512init(archive_sha512_ctx *ctx) +{ + EVP_DigestInit(ctx, EVP_sha512()); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + EVP_DigestUpdate(ctx, indata, insize); + return (ARCHIVE_OK); +} + +static int +__archive_openssl_sha512final(archive_sha512_ctx *ctx, void *md) +{ + EVP_DigestFinal(ctx, md, NULL); + return (ARCHIVE_OK); +} + +#elif defined(ARCHIVE_CRYPTO_SHA512_WIN) + +static int +__archive_windowsapi_sha512init(archive_sha512_ctx *ctx) +{ + return (win_crypto_init(ctx, CALG_SHA_512)); +} + +static int +__archive_windowsapi_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + return (win_crypto_Update(ctx, indata, insize)); +} + +static int +__archive_windowsapi_sha512final(archive_sha512_ctx *ctx, void *md) +{ + return (win_crypto_Final(md, 64, ctx)); +} + +#else + +static int +__archive_stub_sha512init(archive_sha512_ctx *ctx) +{ + (void)ctx; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha512update(archive_sha512_ctx *ctx, const void *indata, + size_t insize) +{ + (void)ctx; /* UNUSED */ + (void)indata; /* UNUSED */ + (void)insize; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +static int +__archive_stub_sha512final(archive_sha512_ctx *ctx, void *md) +{ + (void)ctx; /* UNUSED */ + (void)md; /* UNUSED */ + return (ARCHIVE_FAILED); +} + +#endif + +/* NOTE: Crypto functions are set based on availability and by the following + * order of preference. + * 1. libc + * 2. libc2 + * 3. libc3 + * 4. libSystem + * 5. OpenSSL + * 6. Windows API + */ +const struct archive_crypto __archive_crypto = +{ +/* MD5 */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBC) + &__archive_libc_md5init, + &__archive_libc_md5update, + &__archive_libc_md5final, +#elif defined(ARCHIVE_CRYPTO_MD5_LIBMD) + &__archive_libmd_md5init, + &__archive_libmd_md5update, + &__archive_libmd_md5final, +#elif defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) + &__archive_libsystem_md5init, + &__archive_libsystem_md5update, + &__archive_libsystem_md5final, +#elif defined(ARCHIVE_CRYPTO_MD5_NETTLE) + &__archive_nettle_md5init, + &__archive_nettle_md5update, + &__archive_nettle_md5final, +#elif defined(ARCHIVE_CRYPTO_MD5_OPENSSL) + &__archive_openssl_md5init, + &__archive_openssl_md5update, + &__archive_openssl_md5final, +#elif defined(ARCHIVE_CRYPTO_MD5_WIN) + &__archive_windowsapi_md5init, + &__archive_windowsapi_md5update, + &__archive_windowsapi_md5final, +#elif !defined(ARCHIVE_MD5_COMPILE_TEST) + &__archive_stub_md5init, + &__archive_stub_md5update, + &__archive_stub_md5final, +#endif + +/* RIPEMD160 */ +#if defined(ARCHIVE_CRYPTO_RMD160_LIBC) + &__archive_libc_ripemd160init, + &__archive_libc_ripemd160update, + &__archive_libc_ripemd160final, +#elif defined(ARCHIVE_CRYPTO_RMD160_LIBMD) + &__archive_libmd_ripemd160init, + &__archive_libmd_ripemd160update, + &__archive_libmd_ripemd160final, +#elif defined(ARCHIVE_CRYPTO_RMD160_NETTLE) + &__archive_nettle_ripemd160init, + &__archive_nettle_ripemd160update, + &__archive_nettle_ripemd160final, +#elif defined(ARCHIVE_CRYPTO_RMD160_OPENSSL) + &__archive_openssl_ripemd160init, + &__archive_openssl_ripemd160update, + &__archive_openssl_ripemd160final, +#elif !defined(ARCHIVE_RMD160_COMPILE_TEST) + &__archive_stub_ripemd160init, + &__archive_stub_ripemd160update, + &__archive_stub_ripemd160final, +#endif + +/* SHA1 */ +#if defined(ARCHIVE_CRYPTO_SHA1_LIBC) + &__archive_libc_sha1init, + &__archive_libc_sha1update, + &__archive_libc_sha1final, +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBMD) + &__archive_libmd_sha1init, + &__archive_libmd_sha1update, + &__archive_libmd_sha1final, +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBSYSTEM) + &__archive_libsystem_sha1init, + &__archive_libsystem_sha1update, + &__archive_libsystem_sha1final, +#elif defined(ARCHIVE_CRYPTO_SHA1_NETTLE) + &__archive_nettle_sha1init, + &__archive_nettle_sha1update, + &__archive_nettle_sha1final, +#elif defined(ARCHIVE_CRYPTO_SHA1_OPENSSL) + &__archive_openssl_sha1init, + &__archive_openssl_sha1update, + &__archive_openssl_sha1final, +#elif defined(ARCHIVE_CRYPTO_SHA1_WIN) + &__archive_windowsapi_sha1init, + &__archive_windowsapi_sha1update, + &__archive_windowsapi_sha1final, +#elif !defined(ARCHIVE_SHA1_COMPILE_TEST) + &__archive_stub_sha1init, + &__archive_stub_sha1update, + &__archive_stub_sha1final, +#endif + +/* SHA256 */ +#if defined(ARCHIVE_CRYPTO_SHA256_LIBC) + &__archive_libc_sha256init, + &__archive_libc_sha256update, + &__archive_libc_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC2) + &__archive_libc2_sha256init, + &__archive_libc2_sha256update, + &__archive_libc2_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC3) + &__archive_libc3_sha256init, + &__archive_libc3_sha256update, + &__archive_libc3_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBMD) + &__archive_libmd_sha256init, + &__archive_libmd_sha256update, + &__archive_libmd_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBSYSTEM) + &__archive_libsystem_sha256init, + &__archive_libsystem_sha256update, + &__archive_libsystem_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_NETTLE) + &__archive_nettle_sha256init, + &__archive_nettle_sha256update, + &__archive_nettle_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) + &__archive_openssl_sha256init, + &__archive_openssl_sha256update, + &__archive_openssl_sha256final, +#elif defined(ARCHIVE_CRYPTO_SHA256_WIN) + &__archive_windowsapi_sha256init, + &__archive_windowsapi_sha256update, + &__archive_windowsapi_sha256final, +#elif !defined(ARCHIVE_SHA256_COMPILE_TEST) + &__archive_stub_sha256init, + &__archive_stub_sha256update, + &__archive_stub_sha256final, +#endif + +/* SHA384 */ +#if defined(ARCHIVE_CRYPTO_SHA384_LIBC) + &__archive_libc_sha384init, + &__archive_libc_sha384update, + &__archive_libc_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC2) + &__archive_libc2_sha384init, + &__archive_libc2_sha384update, + &__archive_libc2_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC3) + &__archive_libc3_sha384init, + &__archive_libc3_sha384update, + &__archive_libc3_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBSYSTEM) + &__archive_libsystem_sha384init, + &__archive_libsystem_sha384update, + &__archive_libsystem_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_NETTLE) + &__archive_nettle_sha384init, + &__archive_nettle_sha384update, + &__archive_nettle_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_OPENSSL) + &__archive_openssl_sha384init, + &__archive_openssl_sha384update, + &__archive_openssl_sha384final, +#elif defined(ARCHIVE_CRYPTO_SHA384_WIN) + &__archive_windowsapi_sha384init, + &__archive_windowsapi_sha384update, + &__archive_windowsapi_sha384final, +#elif !defined(ARCHIVE_SHA384_COMPILE_TEST) + &__archive_stub_sha384init, + &__archive_stub_sha384update, + &__archive_stub_sha384final, +#endif + +/* SHA512 */ +#if defined(ARCHIVE_CRYPTO_SHA512_LIBC) + &__archive_libc_sha512init, + &__archive_libc_sha512update, + &__archive_libc_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC2) + &__archive_libc2_sha512init, + &__archive_libc2_sha512update, + &__archive_libc2_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC3) + &__archive_libc3_sha512init, + &__archive_libc3_sha512update, + &__archive_libc3_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBMD) + &__archive_libmd_sha512init, + &__archive_libmd_sha512update, + &__archive_libmd_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBSYSTEM) + &__archive_libsystem_sha512init, + &__archive_libsystem_sha512update, + &__archive_libsystem_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_NETTLE) + &__archive_nettle_sha512init, + &__archive_nettle_sha512update, + &__archive_nettle_sha512final, +#elif defined(ARCHIVE_CRYPTO_SHA512_OPENSSL) + &__archive_openssl_sha512init, + &__archive_openssl_sha512update, + &__archive_openssl_sha512final +#elif defined(ARCHIVE_CRYPTO_SHA512_WIN) + &__archive_windowsapi_sha512init, + &__archive_windowsapi_sha512update, + &__archive_windowsapi_sha512final +#elif !defined(ARCHIVE_SHA512_COMPILE_TEST) + &__archive_stub_sha512init, + &__archive_stub_sha512update, + &__archive_stub_sha512final +#endif +}; diff --git a/libarchive/archive_crypto_private.h b/libarchive/archive_crypto_private.h new file mode 100644 index 0000000..f8b1fb3 --- /dev/null +++ b/libarchive/archive_crypto_private.h @@ -0,0 +1,376 @@ +/*- +* Copyright (c) 2003-2007 Tim Kientzle +* Copyright (c) 2011 Andres Mejia +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_CRYPTO_PRIVATE_H_INCLUDED +#define ARCHIVE_CRYPTO_PRIVATE_H_INCLUDED + +/* + * Crypto support in various Operating Systems: + * + * NetBSD: + * - MD5 and SHA1 in libc: without _ after algorithm name + * - SHA2 in libc: with _ after algorithm name + * + * OpenBSD: + * - MD5, SHA1 and SHA2 in libc: without _ after algorithm name + * - OpenBSD 4.4 and earlier have SHA2 in libc with _ after algorithm name + * + * DragonFly and FreeBSD: + * - MD5 libmd: without _ after algorithm name + * - SHA1, SHA256 and SHA512 in libmd: with _ after algorithm name + * + * Mac OS X (10.4 and later): + * - MD5, SHA1 and SHA2 in libSystem: with CC_ prefix and _ after algorithm name + * + * OpenSSL: + * - MD5, SHA1 and SHA2 in libcrypto: with _ after algorithm name + * + * Windows: + * - MD5, SHA1 and SHA2 in archive_crypto.c using Windows crypto API + */ + +/* libc crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBC) +#include +#endif +#if defined(ARCHIVE_CRYPTO_RMD160_LIBC) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA1_LIBC) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA256_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBC3) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBC3) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBC3) +#include +#endif + +/* libmd crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_RMD160_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_SHA1_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBMD) +#define ARCHIVE_CRYPTO_LIBMD 1 +#endif + +#if defined(ARCHIVE_CRYPTO_MD5_LIBMD) +#include +#endif +#if defined(ARCHIVE_CRYPTO_RMD160_LIBMD) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA1_LIBMD) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA256_LIBMD) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA512_LIBMD) +#include +#endif + +/* libSystem crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA1_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBSYSTEM) +#include +#endif + +/* Nettle crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_NETTLE) +#include +#endif +#if defined(ARCHIVE_CRYPTO_RMD160_NETTLE) +#include +#endif +#if defined(ARCHIVE_CRYPTO_SHA1_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA256_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA384_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA512_NETTLE) +#include +#endif + +/* OpenSSL crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_RMD160_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA1_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA384_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA512_OPENSSL) +#define ARCHIVE_CRYPTO_OPENSSL 1 +#include +#endif + +/* Windows crypto headers */ +#if defined(ARCHIVE_CRYPTO_MD5_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA1_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA256_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA384_WIN) ||\ + defined(ARCHIVE_CRYPTO_SHA512_WIN) +#include +typedef struct { + int valid; + HCRYPTPROV cryptProv; + HCRYPTHASH hash; +} Digest_CTX; +#endif + +/* typedefs */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBC) +typedef MD5_CTX archive_md5_ctx; +#elif defined(ARCHIVE_CRYPTO_MD5_LIBMD) +typedef MD5_CTX archive_md5_ctx; +#elif defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) +typedef CC_MD5_CTX archive_md5_ctx; +#elif defined(ARCHIVE_CRYPTO_MD5_NETTLE) +typedef struct md5_ctx archive_md5_ctx; +#elif defined(ARCHIVE_CRYPTO_MD5_OPENSSL) +typedef EVP_MD_CTX archive_md5_ctx; +#elif defined(ARCHIVE_CRYPTO_MD5_WIN) +typedef Digest_CTX archive_md5_ctx; +#else +typedef unsigned char archive_md5_ctx; +#endif + +#if defined(ARCHIVE_CRYPTO_RMD160_LIBC) +typedef RMD160_CTX archive_rmd160_ctx; +#elif defined(ARCHIVE_CRYPTO_RMD160_LIBMD) +typedef RIPEMD160_CTX archive_rmd160_ctx; +#elif defined(ARCHIVE_CRYPTO_RMD160_NETTLE) +typedef struct ripemd160_ctx archive_rmd160_ctx; +#elif defined(ARCHIVE_CRYPTO_RMD160_OPENSSL) +typedef EVP_MD_CTX archive_rmd160_ctx; +#else +typedef unsigned char archive_rmd160_ctx; +#endif + +#if defined(ARCHIVE_CRYPTO_SHA1_LIBC) +typedef SHA1_CTX archive_sha1_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBMD) +typedef SHA1_CTX archive_sha1_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA1_LIBSYSTEM) +typedef CC_SHA1_CTX archive_sha1_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA1_NETTLE) +typedef struct sha1_ctx archive_sha1_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA1_OPENSSL) +typedef EVP_MD_CTX archive_sha1_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA1_WIN) +typedef Digest_CTX archive_sha1_ctx; +#else +typedef unsigned char archive_sha1_ctx; +#endif + +#if defined(ARCHIVE_CRYPTO_SHA256_LIBC) +typedef SHA256_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC2) +typedef SHA256_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBC3) +typedef SHA2_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBMD) +typedef SHA256_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_LIBSYSTEM) +typedef CC_SHA256_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_NETTLE) +typedef struct sha256_ctx archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) +typedef EVP_MD_CTX archive_sha256_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA256_WIN) +typedef Digest_CTX archive_sha256_ctx; +#else +typedef unsigned char archive_sha256_ctx; +#endif + +#if defined(ARCHIVE_CRYPTO_SHA384_LIBC) +typedef SHA384_CTX archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC2) +typedef SHA384_CTX archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBC3) +typedef SHA2_CTX archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_LIBSYSTEM) +typedef CC_SHA512_CTX archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_NETTLE) +typedef struct sha384_ctx archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_OPENSSL) +typedef EVP_MD_CTX archive_sha384_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA384_WIN) +typedef Digest_CTX archive_sha384_ctx; +#else +typedef unsigned char archive_sha384_ctx; +#endif + +#if defined(ARCHIVE_CRYPTO_SHA512_LIBC) +typedef SHA512_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC2) +typedef SHA512_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBC3) +typedef SHA2_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBMD) +typedef SHA512_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_LIBSYSTEM) +typedef CC_SHA512_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_NETTLE) +typedef struct sha512_ctx archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_OPENSSL) +typedef EVP_MD_CTX archive_sha512_ctx; +#elif defined(ARCHIVE_CRYPTO_SHA512_WIN) +typedef Digest_CTX archive_sha512_ctx; +#else +typedef unsigned char archive_sha512_ctx; +#endif + +/* defines */ +#if defined(ARCHIVE_CRYPTO_MD5_LIBC) ||\ + defined(ARCHIVE_CRYPTO_MD5_LIBMD) || \ + defined(ARCHIVE_CRYPTO_MD5_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_MD5_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_MD5_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_MD5_WIN) +#define ARCHIVE_HAS_MD5 +#endif +#define archive_md5_init(ctx)\ + __archive_crypto.md5init(ctx) +#define archive_md5_final(ctx, md)\ + __archive_crypto.md5final(ctx, md) +#define archive_md5_update(ctx, buf, n)\ + __archive_crypto.md5update(ctx, buf, n) + +#if defined(ARCHIVE_CRYPTO_RMD160_LIBC) ||\ + defined(ARCHIVE_CRYPTO_RMD160_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_RMD160_OPENSSL) +#define ARCHIVE_HAS_RMD160 +#endif +#define archive_rmd160_init(ctx)\ + __archive_crypto.rmd160init(ctx) +#define archive_rmd160_final(ctx, md)\ + __archive_crypto.rmd160final(ctx, md) +#define archive_rmd160_update(ctx, buf, n)\ + __archive_crypto.rmd160update(ctx, buf, n) + +#if defined(ARCHIVE_CRYPTO_SHA1_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA1_LIBMD) || \ + defined(ARCHIVE_CRYPTO_SHA1_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA1_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA1_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA1_WIN) +#define ARCHIVE_HAS_SHA1 +#endif +#define archive_sha1_init(ctx)\ + __archive_crypto.sha1init(ctx) +#define archive_sha1_final(ctx, md)\ + __archive_crypto.sha1final(ctx, md) +#define archive_sha1_update(ctx, buf, n)\ + __archive_crypto.sha1update(ctx, buf, n) + +#if defined(ARCHIVE_CRYPTO_SHA256_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBC3) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_SHA256_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA256_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA256_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA256_WIN) +#define ARCHIVE_HAS_SHA256 +#endif +#define archive_sha256_init(ctx)\ + __archive_crypto.sha256init(ctx) +#define archive_sha256_final(ctx, md)\ + __archive_crypto.sha256final(ctx, md) +#define archive_sha256_update(ctx, buf, n)\ + __archive_crypto.sha256update(ctx, buf, n) + +#if defined(ARCHIVE_CRYPTO_SHA384_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBC3) ||\ + defined(ARCHIVE_CRYPTO_SHA384_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA384_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA384_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA384_WIN) +#define ARCHIVE_HAS_SHA384 +#endif +#define archive_sha384_init(ctx)\ + __archive_crypto.sha384init(ctx) +#define archive_sha384_final(ctx, md)\ + __archive_crypto.sha384final(ctx, md) +#define archive_sha384_update(ctx, buf, n)\ + __archive_crypto.sha384update(ctx, buf, n) + +#if defined(ARCHIVE_CRYPTO_SHA512_LIBC) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBC2) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBC3) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBMD) ||\ + defined(ARCHIVE_CRYPTO_SHA512_LIBSYSTEM) ||\ + defined(ARCHIVE_CRYPTO_SHA512_NETTLE) ||\ + defined(ARCHIVE_CRYPTO_SHA512_OPENSSL) ||\ + defined(ARCHIVE_CRYPTO_SHA512_WIN) +#define ARCHIVE_HAS_SHA512 +#endif +#define archive_sha512_init(ctx)\ + __archive_crypto.sha512init(ctx) +#define archive_sha512_final(ctx, md)\ + __archive_crypto.sha512final(ctx, md) +#define archive_sha512_update(ctx, buf, n)\ + __archive_crypto.sha512update(ctx, buf, n) + +/* Minimal interface to crypto functionality for internal use in libarchive */ +struct archive_crypto +{ + /* Message Digest */ + int (*md5init)(archive_md5_ctx *ctx); + int (*md5update)(archive_md5_ctx *, const void *, size_t); + int (*md5final)(archive_md5_ctx *, void *); + int (*rmd160init)(archive_rmd160_ctx *); + int (*rmd160update)(archive_rmd160_ctx *, const void *, size_t); + int (*rmd160final)(archive_rmd160_ctx *, void *); + int (*sha1init)(archive_sha1_ctx *); + int (*sha1update)(archive_sha1_ctx *, const void *, size_t); + int (*sha1final)(archive_sha1_ctx *, void *); + int (*sha256init)(archive_sha256_ctx *); + int (*sha256update)(archive_sha256_ctx *, const void *, size_t); + int (*sha256final)(archive_sha256_ctx *, void *); + int (*sha384init)(archive_sha384_ctx *); + int (*sha384update)(archive_sha384_ctx *, const void *, size_t); + int (*sha384final)(archive_sha384_ctx *, void *); + int (*sha512init)(archive_sha512_ctx *); + int (*sha512update)(archive_sha512_ctx *, const void *, size_t); + int (*sha512final)(archive_sha512_ctx *, void *); +}; + +extern const struct archive_crypto __archive_crypto; + +#endif diff --git a/libarchive/archive_endian.h b/libarchive/archive_endian.h new file mode 100644 index 0000000..edc90ee --- /dev/null +++ b/libarchive/archive_endian.h @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2002 Thomas Moestl + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/lib/libarchive/archive_endian.h 201085 2009-12-28 02:17:15Z kientzle $ + * + * Borrowed from FreeBSD's + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +/* Note: This is a purely internal header! */ +/* Do not use this outside of libarchive internal code! */ + +#ifndef ARCHIVE_ENDIAN_H_INCLUDED +#define ARCHIVE_ENDIAN_H_INCLUDED + + +/* + * Disabling inline keyword for compilers known to choke on it: + * - Watcom C++ in C code. (For any version?) + * - SGI MIPSpro + * - Microsoft Visual C++ 6.0 (supposedly newer versions too) + */ +#if defined(__WATCOMC__) || defined(__sgi) || defined(__hpux) || defined(__BORLANDC__) +#define inline +#elif defined(_MSC_VER) +#define inline __inline +#endif + +/* Alignment-agnostic encode/decode bytestream to/from little/big endian. */ + +static inline uint16_t +archive_be16dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[0] << 8) | p[1]); +} + +static inline uint32_t +archive_be32dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); +} + +static inline uint64_t +archive_be64dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return (((uint64_t)archive_be32dec(p) << 32) | archive_be32dec(p + 4)); +} + +static inline uint16_t +archive_le16dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[1] << 8) | p[0]); +} + +static inline uint32_t +archive_le32dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return ((p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]); +} + +static inline uint64_t +archive_le64dec(const void *pp) +{ + unsigned char const *p = (unsigned char const *)pp; + + return (((uint64_t)archive_le32dec(p + 4) << 32) | archive_le32dec(p)); +} + +static inline void +archive_be16enc(void *pp, uint16_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = (u >> 8) & 0xff; + p[1] = u & 0xff; +} + +static inline void +archive_be32enc(void *pp, uint32_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = (u >> 24) & 0xff; + p[1] = (u >> 16) & 0xff; + p[2] = (u >> 8) & 0xff; + p[3] = u & 0xff; +} + +static inline void +archive_be64enc(void *pp, uint64_t u) +{ + unsigned char *p = (unsigned char *)pp; + + archive_be32enc(p, u >> 32); + archive_be32enc(p + 4, u & 0xffffffff); +} + +static inline void +archive_le16enc(void *pp, uint16_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = u & 0xff; + p[1] = (u >> 8) & 0xff; +} + +static inline void +archive_le32enc(void *pp, uint32_t u) +{ + unsigned char *p = (unsigned char *)pp; + + p[0] = u & 0xff; + p[1] = (u >> 8) & 0xff; + p[2] = (u >> 16) & 0xff; + p[3] = (u >> 24) & 0xff; +} + +static inline void +archive_le64enc(void *pp, uint64_t u) +{ + unsigned char *p = (unsigned char *)pp; + + archive_le32enc(p, u & 0xffffffff); + archive_le32enc(p + 4, u >> 32); +} + +#endif diff --git a/libarchive/archive_entry.3 b/libarchive/archive_entry.3 new file mode 100644 index 0000000..1bd7cfb --- /dev/null +++ b/libarchive/archive_entry.3 @@ -0,0 +1,148 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_entry.3,v 1.18 2008/05/26 17:00:22 kientzle Exp $ +.\" +.Dd Feburary 22, 2010 +.Dt archive_entry 3 +.Os +.Sh NAME +.Nm archive_entry_clear , +.Nm archive_entry_clone , +.Nm archive_entry_free , +.Nm archive_entry_new , +.Nd functions for managing archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft "struct archive_entry *" +.Fn archive_entry_clear "struct archive_entry *" +.Ft struct archive_entry * +.Fn archive_entry_clone "struct archive_entry *" +.Ft void +.Fn archive_entry_free "struct archive_entry *" +.Ft struct archive_entry * +.Fn archive_entry_new "void" +.Sh DESCRIPTION +These functions create and manipulate data objects that +represent entries within an archive. +You can think of a +.Tn struct archive_entry +as a heavy-duty version of +.Tn struct stat : +it includes everything from +.Tn struct stat +plus associated pathname, textual group and user names, etc. +These objects are used by +.Xr libarchive 3 +to represent the metadata associated with a particular +entry in an archive. +.Ss Create and Destroy +There are functions to allocate, destroy, clear, and copy +.Va archive_entry +objects: +.Bl -tag -compact -width indent +.It Fn archive_entry_clear +Erases the object, resetting all internal fields to the +same state as a newly-created object. +This is provided to allow you to quickly recycle objects +without thrashing the heap. +.It Fn archive_entry_clone +A deep copy operation; all text fields are duplicated. +.It Fn archive_entry_free +Releases the +.Tn struct archive_entry +object. +.It Fn archive_entry_new +Allocate and return a blank +.Tn struct archive_entry +object. +.El +.Ss Function groups +Due to high number of functions, the accessor functions can be found in +man pages grouped by the purpose. +.Bl -tag -width ".Xr archive_entry_perms 3" +.It Xr archive_entry_acl 3 +Access Control List manipulation +.It Xr archive_entry_paths 3 +Path name manipulation +.It Xr archive_entry_perms 3 +User, group and mode manipulation +.It Xr archive_entry_stat 3 +Functions not in the other groups and copying to/from +.Vt struct stat . +.It Xr archive_entry_time 3 +Time field manipulation +.El +.Pp +Most of the functions set or read entries in an object. +Such functions have one of the following forms: +.Bl -tag -compact -width indent +.It Fn archive_entry_set_XXXX +Stores the provided data in the object. +In particular, for strings, the pointer is stored, +not the referenced string. +.It Fn archive_entry_copy_XXXX +As above, except that the referenced data is copied +into the object. +.It Fn archive_entry_XXXX +Returns the specified data. +In the case of strings, a const-qualified pointer to +the string is returned. +.El +String data can be set or accessed as wide character strings +or normal +.Va char +strings. +The functions that use wide character strings are suffixed with +.Cm _w . +Note that these are different representations of the same data: +For example, if you store a narrow string and read the corresponding +wide string, the object will transparently convert formats +using the current locale. +Similarly, if you store a wide string and then store a +narrow string for the same data, the previously-set wide string will +be discarded in favor of the new data. +.Pp +.\" .Sh EXAMPLE +.\" .Sh RETURN VALUES +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry_acl 3 , +.Xr archive_entry_paths 3 , +.Xr archive_entry_perms 3 , +.Xr archive_entry_time 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.\" .Sh BUGS diff --git a/libarchive/archive_entry.c b/libarchive/archive_entry.c new file mode 100644 index 0000000..cbdda8a --- /dev/null +++ b/libarchive/archive_entry.c @@ -0,0 +1,1651 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry.c 201096 2009-12-28 02:41:27Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#if MAJOR_IN_MKDEV +#include +#define HAVE_MAJOR +#elif MAJOR_IN_SYSMACROS +#include +#define HAVE_MAJOR +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_LINUX_FS_H +#include /* for Linux file flags */ +#endif +/* + * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h. + * As the include guards don't agree, the order of include is important. + */ +#ifdef HAVE_LINUX_EXT2_FS_H +#include /* for Linux file flags */ +#endif +#if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__) +#include /* for Linux file flags */ +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif + +#include "archive.h" +#include "archive_acl_private.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_entry_private.h" + +#if !defined(HAVE_MAJOR) && !defined(major) +/* Replacement for major/minor/makedev. */ +#define major(x) ((int)(0x00ff & ((x) >> 8))) +#define minor(x) ((int)(0xffff00ff & (x))) +#define makedev(maj,min) ((0xff00 & ((maj)<<8)) | (0xffff00ff & (min))) +#endif + +/* Play games to come up with a suitable makedev() definition. */ +#ifdef __QNXNTO__ +/* QNX. */ +#include +#define ae_makedev(maj, min) makedev(ND_LOCAL_NODE, (maj), (min)) +#elif defined makedev +/* There's a "makedev" macro. */ +#define ae_makedev(maj, min) makedev((maj), (min)) +#elif defined mkdev || ((defined _WIN32 || defined __WIN32__) && !defined(__CYGWIN__)) +/* Windows. */ +#define ae_makedev(maj, min) mkdev((maj), (min)) +#else +/* There's a "makedev" function. */ +#define ae_makedev(maj, min) makedev((maj), (min)) +#endif + +/* + * This adjustment is needed to support the following idiom for adding + * 1000ns to the stored time: + * archive_entry_set_atime(archive_entry_atime(), + * archive_entry_atime_nsec() + 1000) + * The additional if() here compensates for ambiguity in the C standard, + * which permits two possible interpretations of a % b when a is negative. + */ +#define FIX_NS(t,ns) \ + do { \ + t += ns / 1000000000; \ + ns %= 1000000000; \ + if (ns < 0) { --t; ns += 1000000000; } \ + } while (0) + +static char * ae_fflagstostr(unsigned long bitset, unsigned long bitclear); +static const wchar_t *ae_wcstofflags(const wchar_t *stringp, + unsigned long *setp, unsigned long *clrp); +static const char *ae_strtofflags(const char *stringp, + unsigned long *setp, unsigned long *clrp); + +#ifndef HAVE_WCSCPY +static wchar_t * wcscpy(wchar_t *s1, const wchar_t *s2) +{ + wchar_t *dest = s1; + while ((*s1 = *s2) != L'\0') + ++s1, ++s2; + return dest; +} +#endif +#ifndef HAVE_WCSLEN +static size_t wcslen(const wchar_t *s) +{ + const wchar_t *p = s; + while (*p != L'\0') + ++p; + return p - s; +} +#endif +#ifndef HAVE_WMEMCMP +/* Good enough for simple equality testing, but not for sorting. */ +#define wmemcmp(a,b,i) memcmp((a), (b), (i) * sizeof(wchar_t)) +#endif + +/**************************************************************************** + * + * Public Interface + * + ****************************************************************************/ + +struct archive_entry * +archive_entry_clear(struct archive_entry *entry) +{ + if (entry == NULL) + return (NULL); + archive_mstring_clean(&entry->ae_fflags_text); + archive_mstring_clean(&entry->ae_gname); + archive_mstring_clean(&entry->ae_hardlink); + archive_mstring_clean(&entry->ae_pathname); + archive_mstring_clean(&entry->ae_sourcepath); + archive_mstring_clean(&entry->ae_symlink); + archive_mstring_clean(&entry->ae_uname); + archive_entry_copy_mac_metadata(entry, NULL, 0); + archive_acl_clear(&entry->acl); + archive_entry_xattr_clear(entry); + archive_entry_sparse_clear(entry); + free(entry->stat); + memset(entry, 0, sizeof(*entry)); + return entry; +} + +struct archive_entry * +archive_entry_clone(struct archive_entry *entry) +{ + struct archive_entry *entry2; + struct ae_xattr *xp; + struct ae_sparse *sp; + size_t s; + const void *p; + + /* Allocate new structure and copy over all of the fields. */ + /* TODO: Should we copy the archive over? Or require a new archive + * as an argument? */ + entry2 = archive_entry_new2(entry->archive); + if (entry2 == NULL) + return (NULL); + entry2->ae_stat = entry->ae_stat; + entry2->ae_fflags_set = entry->ae_fflags_set; + entry2->ae_fflags_clear = entry->ae_fflags_clear; + + /* TODO: XXX If clone can have a different archive, what do we do here if + * character sets are different? XXX */ + archive_mstring_copy(&entry2->ae_fflags_text, &entry->ae_fflags_text); + archive_mstring_copy(&entry2->ae_gname, &entry->ae_gname); + archive_mstring_copy(&entry2->ae_hardlink, &entry->ae_hardlink); + archive_mstring_copy(&entry2->ae_pathname, &entry->ae_pathname); + archive_mstring_copy(&entry2->ae_sourcepath, &entry->ae_sourcepath); + archive_mstring_copy(&entry2->ae_symlink, &entry->ae_symlink); + entry2->ae_set = entry->ae_set; + archive_mstring_copy(&entry2->ae_uname, &entry->ae_uname); + + /* Copy ACL data over. */ + archive_acl_copy(&entry2->acl, &entry->acl); + + /* Copy Mac OS metadata. */ + p = archive_entry_mac_metadata(entry, &s); + archive_entry_copy_mac_metadata(entry2, p, s); + + /* Copy xattr data over. */ + xp = entry->xattr_head; + while (xp != NULL) { + archive_entry_xattr_add_entry(entry2, + xp->name, xp->value, xp->size); + xp = xp->next; + } + + /* Copy sparse data over. */ + sp = entry->sparse_head; + while (sp != NULL) { + archive_entry_sparse_add_entry(entry2, + sp->offset, sp->length); + sp = sp->next; + } + + return (entry2); +} + +void +archive_entry_free(struct archive_entry *entry) +{ + archive_entry_clear(entry); + free(entry); +} + +struct archive_entry * +archive_entry_new(void) +{ + return archive_entry_new2(NULL); +} + +struct archive_entry * +archive_entry_new2(struct archive *a) +{ + struct archive_entry *entry; + + entry = (struct archive_entry *)malloc(sizeof(*entry)); + if (entry == NULL) + return (NULL); + memset(entry, 0, sizeof(*entry)); + entry->archive = a; + return (entry); +} + +/* + * Functions for reading fields from an archive_entry. + */ + +time_t +archive_entry_atime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_atime); +} + +long +archive_entry_atime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_atime_nsec); +} + +int +archive_entry_atime_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_ATIME); +} + +time_t +archive_entry_birthtime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_birthtime); +} + +long +archive_entry_birthtime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_birthtime_nsec); +} + +int +archive_entry_birthtime_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_BIRTHTIME); +} + +time_t +archive_entry_ctime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ctime); +} + +int +archive_entry_ctime_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_CTIME); +} + +long +archive_entry_ctime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ctime_nsec); +} + +dev_t +archive_entry_dev(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return ae_makedev(entry->ae_stat.aest_devmajor, + entry->ae_stat.aest_devminor); + else + return (entry->ae_stat.aest_dev); +} + +int +archive_entry_dev_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_DEV); +} + +dev_t +archive_entry_devmajor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return (entry->ae_stat.aest_devmajor); + else + return major(entry->ae_stat.aest_dev); +} + +dev_t +archive_entry_devminor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_dev_is_broken_down) + return (entry->ae_stat.aest_devminor); + else + return minor(entry->ae_stat.aest_dev); +} + +mode_t +archive_entry_filetype(struct archive_entry *entry) +{ + return (AE_IFMT & entry->acl.mode); +} + +void +archive_entry_fflags(struct archive_entry *entry, + unsigned long *set, unsigned long *clear) +{ + *set = entry->ae_fflags_set; + *clear = entry->ae_fflags_clear; +} + +/* + * Note: if text was provided, this just returns that text. If you + * really need the text to be rebuilt in a canonical form, set the + * text, ask for the bitmaps, then set the bitmaps. (Setting the + * bitmaps clears any stored text.) This design is deliberate: if + * we're editing archives, we don't want to discard flags just because + * they aren't supported on the current system. The bitmap<->text + * conversions are platform-specific (see below). + */ +const char * +archive_entry_fflags_text(struct archive_entry *entry) +{ + const char *f; + char *p; + + if (archive_mstring_get_mbs(entry->archive, + &entry->ae_fflags_text, &f) == 0 && f != NULL) + return (f); + + if (entry->ae_fflags_set == 0 && entry->ae_fflags_clear == 0) + return (NULL); + + p = ae_fflagstostr(entry->ae_fflags_set, entry->ae_fflags_clear); + if (p == NULL) + return (NULL); + + archive_mstring_copy_mbs(&entry->ae_fflags_text, p); + free(p); + if (archive_mstring_get_mbs(entry->archive, + &entry->ae_fflags_text, &f) == 0) + return (f); + return (NULL); +} + +int64_t +archive_entry_gid(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_gid); +} + +const char * +archive_entry_gname(struct archive_entry *entry) +{ + const char *p; + if (archive_mstring_get_mbs(entry->archive, &entry->ae_gname, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_gname_w(struct archive_entry *entry) +{ + const wchar_t *p; + if (archive_mstring_get_wcs(entry->archive, &entry->ae_gname, &p) == 0) + return (p); + return (NULL); +} + +int +_archive_entry_gname_l(struct archive_entry *entry, + const char **p, size_t *len, struct archive_string_conv *sc) +{ + return (archive_mstring_get_mbs_l(&entry->ae_gname, p, len, sc)); +} + +const char * +archive_entry_hardlink(struct archive_entry *entry) +{ + const char *p; + if ((entry->ae_set & AE_SET_HARDLINK) && archive_mstring_get_mbs( + entry->archive, &entry->ae_hardlink, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_hardlink_w(struct archive_entry *entry) +{ + const wchar_t *p; + if ((entry->ae_set & AE_SET_HARDLINK) && archive_mstring_get_wcs( + entry->archive, &entry->ae_hardlink, &p) == 0) + return (p); + return (NULL); +} + +int +_archive_entry_hardlink_l(struct archive_entry *entry, + const char **p, size_t *len, struct archive_string_conv *sc) +{ + if ((entry->ae_set & AE_SET_HARDLINK) == 0) { + *p = NULL; + *len = 0; + return (0); + } + return (archive_mstring_get_mbs_l(&entry->ae_hardlink, p, len, sc)); +} + +int64_t +archive_entry_ino(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ino); +} + +int +archive_entry_ino_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_INO); +} + +int64_t +archive_entry_ino64(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_ino); +} + +mode_t +archive_entry_mode(struct archive_entry *entry) +{ + return (entry->acl.mode); +} + +time_t +archive_entry_mtime(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_mtime); +} + +long +archive_entry_mtime_nsec(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_mtime_nsec); +} + +int +archive_entry_mtime_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_MTIME); +} + +unsigned int +archive_entry_nlink(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_nlink); +} + +const char * +archive_entry_pathname(struct archive_entry *entry) +{ + const char *p; + if (archive_mstring_get_mbs( + entry->archive, &entry->ae_pathname, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_pathname_w(struct archive_entry *entry) +{ + const wchar_t *p; + if (archive_mstring_get_wcs( + entry->archive, &entry->ae_pathname, &p) == 0) + return (p); + return (NULL); +} + +int +_archive_entry_pathname_l(struct archive_entry *entry, + const char **p, size_t *len, struct archive_string_conv *sc) +{ + return (archive_mstring_get_mbs_l(&entry->ae_pathname, p, len, sc)); +} + +mode_t +archive_entry_perm(struct archive_entry *entry) +{ + return (~AE_IFMT & entry->acl.mode); +} + +dev_t +archive_entry_rdev(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return ae_makedev(entry->ae_stat.aest_rdevmajor, + entry->ae_stat.aest_rdevminor); + else + return (entry->ae_stat.aest_rdev); +} + +dev_t +archive_entry_rdevmajor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevmajor); + else + return major(entry->ae_stat.aest_rdev); +} + +dev_t +archive_entry_rdevminor(struct archive_entry *entry) +{ + if (entry->ae_stat.aest_rdev_is_broken_down) + return (entry->ae_stat.aest_rdevminor); + else + return minor(entry->ae_stat.aest_rdev); +} + +int64_t +archive_entry_size(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_size); +} + +int +archive_entry_size_is_set(struct archive_entry *entry) +{ + return (entry->ae_set & AE_SET_SIZE); +} + +const char * +archive_entry_sourcepath(struct archive_entry *entry) +{ + const char *p; + if (archive_mstring_get_mbs( + entry->archive, &entry->ae_sourcepath, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_sourcepath_w(struct archive_entry *entry) +{ + const wchar_t *p; + if (archive_mstring_get_wcs( + entry->archive, &entry->ae_sourcepath, &p) == 0) + return (p); + return (NULL); +} + +const char * +archive_entry_symlink(struct archive_entry *entry) +{ + const char *p; + if ((entry->ae_set & AE_SET_SYMLINK) && archive_mstring_get_mbs( + entry->archive, &entry->ae_symlink, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_symlink_w(struct archive_entry *entry) +{ + const wchar_t *p; + if ((entry->ae_set & AE_SET_SYMLINK) && archive_mstring_get_wcs( + entry->archive, &entry->ae_symlink, &p) == 0) + return (p); + return (NULL); +} + +int +_archive_entry_symlink_l(struct archive_entry *entry, + const char **p, size_t *len, struct archive_string_conv *sc) +{ + if ((entry->ae_set & AE_SET_SYMLINK) == 0) { + *p = NULL; + *len = 0; + return (0); + } + return (archive_mstring_get_mbs_l( &entry->ae_symlink, p, len, sc)); +} + +int64_t +archive_entry_uid(struct archive_entry *entry) +{ + return (entry->ae_stat.aest_uid); +} + +const char * +archive_entry_uname(struct archive_entry *entry) +{ + const char *p; + if (archive_mstring_get_mbs(entry->archive, &entry->ae_uname, &p) == 0) + return (p); + return (NULL); +} + +const wchar_t * +archive_entry_uname_w(struct archive_entry *entry) +{ + const wchar_t *p; + if (archive_mstring_get_wcs(entry->archive, &entry->ae_uname, &p) == 0) + return (p); + return (NULL); +} + +int +_archive_entry_uname_l(struct archive_entry *entry, + const char **p, size_t *len, struct archive_string_conv *sc) +{ + return (archive_mstring_get_mbs_l(&entry->ae_uname, p, len, sc)); +} + +/* + * Functions to set archive_entry properties. + */ + +void +archive_entry_set_filetype(struct archive_entry *entry, unsigned int type) +{ + entry->stat_valid = 0; + entry->acl.mode &= ~AE_IFMT; + entry->acl.mode |= AE_IFMT & type; +} + +void +archive_entry_set_fflags(struct archive_entry *entry, + unsigned long set, unsigned long clear) +{ + archive_mstring_clean(&entry->ae_fflags_text); + entry->ae_fflags_set = set; + entry->ae_fflags_clear = clear; +} + +const char * +archive_entry_copy_fflags_text(struct archive_entry *entry, + const char *flags) +{ + archive_mstring_copy_mbs(&entry->ae_fflags_text, flags); + return (ae_strtofflags(flags, + &entry->ae_fflags_set, &entry->ae_fflags_clear)); +} + +const wchar_t * +archive_entry_copy_fflags_text_w(struct archive_entry *entry, + const wchar_t *flags) +{ + archive_mstring_copy_wcs(&entry->ae_fflags_text, flags); + return (ae_wcstofflags(flags, + &entry->ae_fflags_set, &entry->ae_fflags_clear)); +} + +void +archive_entry_set_gid(struct archive_entry *entry, int64_t g) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_gid = g; +} + +void +archive_entry_set_gname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_gname, name); +} + +void +archive_entry_copy_gname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_gname, name); +} + +void +archive_entry_copy_gname_w(struct archive_entry *entry, const wchar_t *name) +{ + archive_mstring_copy_wcs(&entry->ae_gname, name); +} + +int +archive_entry_update_gname_utf8(struct archive_entry *entry, const char *name) +{ + if (archive_mstring_update_utf8(entry->archive, + &entry->ae_gname, name) == 0) + return (1); + return (0); +} + +int +_archive_entry_copy_gname_l(struct archive_entry *entry, + const char *name, size_t len, struct archive_string_conv *sc) +{ + return (archive_mstring_copy_mbs_len_l(&entry->ae_gname, name, len, sc)); +} + +void +archive_entry_set_ino(struct archive_entry *entry, int64_t ino) +{ + entry->stat_valid = 0; + entry->ae_set |= AE_SET_INO; + entry->ae_stat.aest_ino = ino; +} + +void +archive_entry_set_ino64(struct archive_entry *entry, int64_t ino) +{ + entry->stat_valid = 0; + entry->ae_set |= AE_SET_INO; + entry->ae_stat.aest_ino = ino; +} + +void +archive_entry_set_hardlink(struct archive_entry *entry, const char *target) +{ + archive_mstring_copy_mbs(&entry->ae_hardlink, target); + if (target != NULL) + entry->ae_set |= AE_SET_HARDLINK; + else + entry->ae_set &= ~AE_SET_HARDLINK; +} + +void +archive_entry_copy_hardlink(struct archive_entry *entry, const char *target) +{ + archive_mstring_copy_mbs(&entry->ae_hardlink, target); + if (target != NULL) + entry->ae_set |= AE_SET_HARDLINK; + else + entry->ae_set &= ~AE_SET_HARDLINK; +} + +void +archive_entry_copy_hardlink_w(struct archive_entry *entry, const wchar_t *target) +{ + archive_mstring_copy_wcs(&entry->ae_hardlink, target); + if (target != NULL) + entry->ae_set |= AE_SET_HARDLINK; + else + entry->ae_set &= ~AE_SET_HARDLINK; +} + +int +archive_entry_update_hardlink_utf8(struct archive_entry *entry, const char *target) +{ + if (target != NULL) + entry->ae_set |= AE_SET_HARDLINK; + else + entry->ae_set &= ~AE_SET_HARDLINK; + if (archive_mstring_update_utf8(entry->archive, + &entry->ae_hardlink, target) == 0) + return (1); + return (0); +} + +int +_archive_entry_copy_hardlink_l(struct archive_entry *entry, + const char *target, size_t len, struct archive_string_conv *sc) +{ + int r; + + r = archive_mstring_copy_mbs_len_l(&entry->ae_hardlink, + target, len, sc); + if (target != NULL && r == 0) + entry->ae_set |= AE_SET_HARDLINK; + else + entry->ae_set &= ~AE_SET_HARDLINK; + return (r); +} + +void +archive_entry_set_atime(struct archive_entry *entry, time_t t, long ns) +{ + FIX_NS(t, ns); + entry->stat_valid = 0; + entry->ae_set |= AE_SET_ATIME; + entry->ae_stat.aest_atime = t; + entry->ae_stat.aest_atime_nsec = ns; +} + +void +archive_entry_unset_atime(struct archive_entry *entry) +{ + archive_entry_set_atime(entry, 0, 0); + entry->ae_set &= ~AE_SET_ATIME; +} + +void +archive_entry_set_birthtime(struct archive_entry *entry, time_t t, long ns) +{ + FIX_NS(t, ns); + entry->stat_valid = 0; + entry->ae_set |= AE_SET_BIRTHTIME; + entry->ae_stat.aest_birthtime = t; + entry->ae_stat.aest_birthtime_nsec = ns; +} + +void +archive_entry_unset_birthtime(struct archive_entry *entry) +{ + archive_entry_set_birthtime(entry, 0, 0); + entry->ae_set &= ~AE_SET_BIRTHTIME; +} + +void +archive_entry_set_ctime(struct archive_entry *entry, time_t t, long ns) +{ + FIX_NS(t, ns); + entry->stat_valid = 0; + entry->ae_set |= AE_SET_CTIME; + entry->ae_stat.aest_ctime = t; + entry->ae_stat.aest_ctime_nsec = ns; +} + +void +archive_entry_unset_ctime(struct archive_entry *entry) +{ + archive_entry_set_ctime(entry, 0, 0); + entry->ae_set &= ~AE_SET_CTIME; +} + +void +archive_entry_set_dev(struct archive_entry *entry, dev_t d) +{ + entry->stat_valid = 0; + entry->ae_set |= AE_SET_DEV; + entry->ae_stat.aest_dev_is_broken_down = 0; + entry->ae_stat.aest_dev = d; +} + +void +archive_entry_set_devmajor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_set |= AE_SET_DEV; + entry->ae_stat.aest_dev_is_broken_down = 1; + entry->ae_stat.aest_devmajor = m; +} + +void +archive_entry_set_devminor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_set |= AE_SET_DEV; + entry->ae_stat.aest_dev_is_broken_down = 1; + entry->ae_stat.aest_devminor = m; +} + +/* Set symlink if symlink is already set, else set hardlink. */ +void +archive_entry_set_link(struct archive_entry *entry, const char *target) +{ + if (entry->ae_set & AE_SET_SYMLINK) + archive_mstring_copy_mbs(&entry->ae_symlink, target); + else + archive_mstring_copy_mbs(&entry->ae_hardlink, target); +} + +/* Set symlink if symlink is already set, else set hardlink. */ +void +archive_entry_copy_link(struct archive_entry *entry, const char *target) +{ + if (entry->ae_set & AE_SET_SYMLINK) + archive_mstring_copy_mbs(&entry->ae_symlink, target); + else + archive_mstring_copy_mbs(&entry->ae_hardlink, target); +} + +/* Set symlink if symlink is already set, else set hardlink. */ +void +archive_entry_copy_link_w(struct archive_entry *entry, const wchar_t *target) +{ + if (entry->ae_set & AE_SET_SYMLINK) + archive_mstring_copy_wcs(&entry->ae_symlink, target); + else + archive_mstring_copy_wcs(&entry->ae_hardlink, target); +} + +int +archive_entry_update_link_utf8(struct archive_entry *entry, const char *target) +{ + int r; + if (entry->ae_set & AE_SET_SYMLINK) + r = archive_mstring_update_utf8(entry->archive, + &entry->ae_symlink, target); + else + r = archive_mstring_update_utf8(entry->archive, + &entry->ae_hardlink, target); + return ((r == 0)? 1: 0); +} + +int +_archive_entry_copy_link_l(struct archive_entry *entry, + const char *target, size_t len, struct archive_string_conv *sc) +{ + int r; + + if (entry->ae_set & AE_SET_SYMLINK) + r = archive_mstring_copy_mbs_len_l(&entry->ae_symlink, + target, len, sc); + else + r = archive_mstring_copy_mbs_len_l(&entry->ae_hardlink, + target, len, sc); + return (r); +} + +void +archive_entry_set_mode(struct archive_entry *entry, mode_t m) +{ + entry->stat_valid = 0; + entry->acl.mode = m; +} + +void +archive_entry_set_mtime(struct archive_entry *entry, time_t t, long ns) +{ + FIX_NS(t, ns); + entry->stat_valid = 0; + entry->ae_set |= AE_SET_MTIME; + entry->ae_stat.aest_mtime = t; + entry->ae_stat.aest_mtime_nsec = ns; +} + +void +archive_entry_unset_mtime(struct archive_entry *entry) +{ + archive_entry_set_mtime(entry, 0, 0); + entry->ae_set &= ~AE_SET_MTIME; +} + +void +archive_entry_set_nlink(struct archive_entry *entry, unsigned int nlink) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_nlink = nlink; +} + +void +archive_entry_set_pathname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_pathname, name); +} + +void +archive_entry_copy_pathname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_pathname, name); +} + +void +archive_entry_copy_pathname_w(struct archive_entry *entry, const wchar_t *name) +{ + archive_mstring_copy_wcs(&entry->ae_pathname, name); +} + +int +archive_entry_update_pathname_utf8(struct archive_entry *entry, const char *name) +{ + if (archive_mstring_update_utf8(entry->archive, + &entry->ae_pathname, name) == 0) + return (1); + return (0); +} + +int +_archive_entry_copy_pathname_l(struct archive_entry *entry, + const char *name, size_t len, struct archive_string_conv *sc) +{ + return (archive_mstring_copy_mbs_len_l(&entry->ae_pathname, + name, len, sc)); +} + +void +archive_entry_set_perm(struct archive_entry *entry, mode_t p) +{ + entry->stat_valid = 0; + entry->acl.mode &= AE_IFMT; + entry->acl.mode |= ~AE_IFMT & p; +} + +void +archive_entry_set_rdev(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev = m; + entry->ae_stat.aest_rdev_is_broken_down = 0; +} + +void +archive_entry_set_rdevmajor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdevmajor = m; +} + +void +archive_entry_set_rdevminor(struct archive_entry *entry, dev_t m) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_rdev_is_broken_down = 1; + entry->ae_stat.aest_rdevminor = m; +} + +void +archive_entry_set_size(struct archive_entry *entry, int64_t s) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_size = s; + entry->ae_set |= AE_SET_SIZE; +} + +void +archive_entry_unset_size(struct archive_entry *entry) +{ + archive_entry_set_size(entry, 0); + entry->ae_set &= ~AE_SET_SIZE; +} + +void +archive_entry_copy_sourcepath(struct archive_entry *entry, const char *path) +{ + archive_mstring_copy_mbs(&entry->ae_sourcepath, path); +} + +void +archive_entry_copy_sourcepath_w(struct archive_entry *entry, const wchar_t *path) +{ + archive_mstring_copy_wcs(&entry->ae_sourcepath, path); +} + +void +archive_entry_set_symlink(struct archive_entry *entry, const char *linkname) +{ + archive_mstring_copy_mbs(&entry->ae_symlink, linkname); + if (linkname != NULL) + entry->ae_set |= AE_SET_SYMLINK; + else + entry->ae_set &= ~AE_SET_SYMLINK; +} + +void +archive_entry_copy_symlink(struct archive_entry *entry, const char *linkname) +{ + archive_mstring_copy_mbs(&entry->ae_symlink, linkname); + if (linkname != NULL) + entry->ae_set |= AE_SET_SYMLINK; + else + entry->ae_set &= ~AE_SET_SYMLINK; +} + +void +archive_entry_copy_symlink_w(struct archive_entry *entry, const wchar_t *linkname) +{ + archive_mstring_copy_wcs(&entry->ae_symlink, linkname); + if (linkname != NULL) + entry->ae_set |= AE_SET_SYMLINK; + else + entry->ae_set &= ~AE_SET_SYMLINK; +} + +int +archive_entry_update_symlink_utf8(struct archive_entry *entry, const char *linkname) +{ + if (linkname != NULL) + entry->ae_set |= AE_SET_SYMLINK; + else + entry->ae_set &= ~AE_SET_SYMLINK; + if (archive_mstring_update_utf8(entry->archive, + &entry->ae_symlink, linkname) == 0) + return (1); + return (0); +} + +int +_archive_entry_copy_symlink_l(struct archive_entry *entry, + const char *linkname, size_t len, struct archive_string_conv *sc) +{ + int r; + + r = archive_mstring_copy_mbs_len_l(&entry->ae_symlink, + linkname, len, sc); + if (linkname != NULL && r == 0) + entry->ae_set |= AE_SET_SYMLINK; + else + entry->ae_set &= ~AE_SET_SYMLINK; + return (r); +} + +void +archive_entry_set_uid(struct archive_entry *entry, int64_t u) +{ + entry->stat_valid = 0; + entry->ae_stat.aest_uid = u; +} + +void +archive_entry_set_uname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_uname, name); +} + +void +archive_entry_copy_uname(struct archive_entry *entry, const char *name) +{ + archive_mstring_copy_mbs(&entry->ae_uname, name); +} + +void +archive_entry_copy_uname_w(struct archive_entry *entry, const wchar_t *name) +{ + archive_mstring_copy_wcs(&entry->ae_uname, name); +} + +int +archive_entry_update_uname_utf8(struct archive_entry *entry, const char *name) +{ + if (archive_mstring_update_utf8(entry->archive, + &entry->ae_uname, name) == 0) + return (1); + return (0); +} + +int +_archive_entry_copy_uname_l(struct archive_entry *entry, + const char *name, size_t len, struct archive_string_conv *sc) +{ + return (archive_mstring_copy_mbs_len_l(&entry->ae_uname, + name, len, sc)); +} + +const void * +archive_entry_mac_metadata(struct archive_entry *entry, size_t *s) +{ + *s = entry->mac_metadata_size; + return entry->mac_metadata; +} + +void +archive_entry_copy_mac_metadata(struct archive_entry *entry, + const void *p, size_t s) +{ + free(entry->mac_metadata); + if (p == NULL || s == 0) { + entry->mac_metadata = NULL; + entry->mac_metadata_size = 0; + } else { + entry->mac_metadata_size = s; + entry->mac_metadata = malloc(s); + if (entry->mac_metadata == NULL) + abort(); + memcpy(entry->mac_metadata, p, s); + } +} + +/* + * ACL management. The following would, of course, be a lot simpler + * if: 1) the last draft of POSIX.1e were a really thorough and + * complete standard that addressed the needs of ACL archiving and 2) + * everyone followed it faithfully. Alas, neither is true, so the + * following is a lot more complex than might seem necessary to the + * uninitiated. + */ + +struct archive_acl * +archive_entry_acl(struct archive_entry *entry) +{ + return &entry->acl; +} + +void +archive_entry_acl_clear(struct archive_entry *entry) +{ + archive_acl_clear(&entry->acl); +} + +/* + * Add a single ACL entry to the internal list of ACL data. + */ +int +archive_entry_acl_add_entry(struct archive_entry *entry, + int type, int permset, int tag, int id, const char *name) +{ + return archive_acl_add_entry(&entry->acl, type, permset, tag, id, name); +} + +/* + * As above, but with a wide-character name. + */ +int +archive_entry_acl_add_entry_w(struct archive_entry *entry, + int type, int permset, int tag, int id, const wchar_t *name) +{ + return archive_acl_add_entry_w_len(&entry->acl, + type, permset, tag, id, name, wcslen(name)); +} + +/* + * Return a count of entries matching "want_type". + */ +int +archive_entry_acl_count(struct archive_entry *entry, int want_type) +{ + return archive_acl_count(&entry->acl, want_type); +} + +/* + * Prepare for reading entries from the ACL data. Returns a count + * of entries matching "want_type", or zero if there are no + * non-extended ACL entries of that type. + */ +int +archive_entry_acl_reset(struct archive_entry *entry, int want_type) +{ + return archive_acl_reset(&entry->acl, want_type); +} + +/* + * Return the next ACL entry in the list. Fake entries for the + * standard permissions and include them in the returned list. + */ +int +archive_entry_acl_next(struct archive_entry *entry, int want_type, int *type, + int *permset, int *tag, int *id, const char **name) +{ + return archive_acl_next(entry->archive, &entry->acl, want_type, type, permset, tag, id, name); +} + +/* + * Generate a text version of the ACL. The flags parameter controls + * the style of the generated ACL. + */ +const wchar_t * +archive_entry_acl_text_w(struct archive_entry *entry, int flags) +{ + return archive_acl_text_w(entry->archive, &entry->acl, flags); +} + +const char * +archive_entry_acl_text(struct archive_entry *entry, int flags) +{ + const char *p; + if (archive_acl_text_l(&entry->acl, flags, &p, NULL, NULL) != 0 + && errno == ENOMEM) + return (NULL); + return (p); +} + +int +_archive_entry_acl_text_l(struct archive_entry *entry, int flags, + const char **acl_text, size_t *len, struct archive_string_conv *sc) +{ + return (archive_acl_text_l(&entry->acl, flags, acl_text, len, sc)); +} + +/* + * Following code is modified from UC Berkeley sources, and + * is subject to the following copyright notice. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +static struct flag { + const char *name; + const wchar_t *wname; + unsigned long set; + unsigned long clear; +} flags[] = { + /* Preferred (shorter) names per flag first, all prefixed by "no" */ +#ifdef SF_APPEND + { "nosappnd", L"nosappnd", SF_APPEND, 0 }, + { "nosappend", L"nosappend", SF_APPEND, 0 }, +#endif +#ifdef EXT2_APPEND_FL /* 'a' */ + { "nosappnd", L"nosappnd", EXT2_APPEND_FL, 0 }, + { "nosappend", L"nosappend", EXT2_APPEND_FL, 0 }, +#endif +#ifdef SF_ARCHIVED + { "noarch", L"noarch", SF_ARCHIVED, 0 }, + { "noarchived", L"noarchived", SF_ARCHIVED, 0 }, +#endif +#ifdef SF_IMMUTABLE + { "noschg", L"noschg", SF_IMMUTABLE, 0 }, + { "noschange", L"noschange", SF_IMMUTABLE, 0 }, + { "nosimmutable", L"nosimmutable", SF_IMMUTABLE, 0 }, +#endif +#ifdef EXT2_IMMUTABLE_FL /* 'i' */ + { "noschg", L"noschg", EXT2_IMMUTABLE_FL, 0 }, + { "noschange", L"noschange", EXT2_IMMUTABLE_FL, 0 }, + { "nosimmutable", L"nosimmutable", EXT2_IMMUTABLE_FL, 0 }, +#endif +#ifdef SF_NOUNLINK + { "nosunlnk", L"nosunlnk", SF_NOUNLINK, 0 }, + { "nosunlink", L"nosunlink", SF_NOUNLINK, 0 }, +#endif +#ifdef SF_SNAPSHOT + { "nosnapshot", L"nosnapshot", SF_SNAPSHOT, 0 }, +#endif +#ifdef UF_APPEND + { "nouappnd", L"nouappnd", UF_APPEND, 0 }, + { "nouappend", L"nouappend", UF_APPEND, 0 }, +#endif +#ifdef UF_IMMUTABLE + { "nouchg", L"nouchg", UF_IMMUTABLE, 0 }, + { "nouchange", L"nouchange", UF_IMMUTABLE, 0 }, + { "nouimmutable", L"nouimmutable", UF_IMMUTABLE, 0 }, +#endif +#ifdef UF_NODUMP + { "nodump", L"nodump", 0, UF_NODUMP}, +#endif +#ifdef EXT2_NODUMP_FL /* 'd' */ + { "nodump", L"nodump", 0, EXT2_NODUMP_FL}, +#endif +#ifdef UF_OPAQUE + { "noopaque", L"noopaque", UF_OPAQUE, 0 }, +#endif +#ifdef UF_NOUNLINK + { "nouunlnk", L"nouunlnk", UF_NOUNLINK, 0 }, + { "nouunlink", L"nouunlink", UF_NOUNLINK, 0 }, +#endif +#ifdef EXT2_UNRM_FL + { "nouunlink", L"nouunlink", EXT2_UNRM_FL, 0}, +#endif + +#ifdef EXT2_BTREE_FL + { "nobtree", L"nobtree", EXT2_BTREE_FL, 0 }, +#endif + +#ifdef EXT2_ECOMPR_FL + { "nocomperr", L"nocomperr", EXT2_ECOMPR_FL, 0 }, +#endif + +#ifdef EXT2_COMPR_FL /* 'c' */ + { "nocompress", L"nocompress", EXT2_COMPR_FL, 0 }, +#endif + +#ifdef EXT2_NOATIME_FL /* 'A' */ + { "noatime", L"noatime", 0, EXT2_NOATIME_FL}, +#endif + +#ifdef EXT2_DIRTY_FL + { "nocompdirty",L"nocompdirty", EXT2_DIRTY_FL, 0}, +#endif + +#ifdef EXT2_COMPRBLK_FL +#ifdef EXT2_NOCOMPR_FL + { "nocomprblk", L"nocomprblk", EXT2_COMPRBLK_FL, EXT2_NOCOMPR_FL}, +#else + { "nocomprblk", L"nocomprblk", EXT2_COMPRBLK_FL, 0}, +#endif +#endif +#ifdef EXT2_DIRSYNC_FL + { "nodirsync", L"nodirsync", EXT2_DIRSYNC_FL, 0}, +#endif +#ifdef EXT2_INDEX_FL + { "nohashidx", L"nohashidx", EXT2_INDEX_FL, 0}, +#endif +#ifdef EXT2_IMAGIC_FL + { "noimagic", L"noimagic", EXT2_IMAGIC_FL, 0}, +#endif +#ifdef EXT3_JOURNAL_DATA_FL + { "nojournal", L"nojournal", EXT3_JOURNAL_DATA_FL, 0}, +#endif +#ifdef EXT2_SECRM_FL + { "nosecuredeletion",L"nosecuredeletion",EXT2_SECRM_FL, 0}, +#endif +#ifdef EXT2_SYNC_FL + { "nosync", L"nosync", EXT2_SYNC_FL, 0}, +#endif +#ifdef EXT2_NOTAIL_FL + { "notail", L"notail", 0, EXT2_NOTAIL_FL}, +#endif +#ifdef EXT2_TOPDIR_FL + { "notopdir", L"notopdir", EXT2_TOPDIR_FL, 0}, +#endif +#ifdef EXT2_RESERVED_FL + { "noreserved", L"noreserved", EXT2_RESERVED_FL, 0}, +#endif + + { NULL, NULL, 0, 0 } +}; + +/* + * fflagstostr -- + * Convert file flags to a comma-separated string. If no flags + * are set, return the empty string. + */ +static char * +ae_fflagstostr(unsigned long bitset, unsigned long bitclear) +{ + char *string, *dp; + const char *sp; + unsigned long bits; + struct flag *flag; + size_t length; + + bits = bitset | bitclear; + length = 0; + for (flag = flags; flag->name != NULL; flag++) + if (bits & (flag->set | flag->clear)) { + length += strlen(flag->name) + 1; + bits &= ~(flag->set | flag->clear); + } + + if (length == 0) + return (NULL); + string = (char *)malloc(length); + if (string == NULL) + return (NULL); + + dp = string; + for (flag = flags; flag->name != NULL; flag++) { + if (bitset & flag->set || bitclear & flag->clear) { + sp = flag->name + 2; + } else if (bitset & flag->clear || bitclear & flag->set) { + sp = flag->name; + } else + continue; + bitset &= ~(flag->set | flag->clear); + bitclear &= ~(flag->set | flag->clear); + if (dp > string) + *dp++ = ','; + while ((*dp++ = *sp++) != '\0') + ; + dp--; + } + + *dp = '\0'; + return (string); +} + +/* + * strtofflags -- + * Take string of arguments and return file flags. This + * version works a little differently than strtofflags(3). + * In particular, it always tests every token, skipping any + * unrecognized tokens. It returns a pointer to the first + * unrecognized token, or NULL if every token was recognized. + * This version is also const-correct and does not modify the + * provided string. + */ +static const char * +ae_strtofflags(const char *s, unsigned long *setp, unsigned long *clrp) +{ + const char *start, *end; + struct flag *flag; + unsigned long set, clear; + const char *failed; + + set = clear = 0; + start = s; + failed = NULL; + /* Find start of first token. */ + while (*start == '\t' || *start == ' ' || *start == ',') + start++; + while (*start != '\0') { + /* Locate end of token. */ + end = start; + while (*end != '\0' && *end != '\t' && + *end != ' ' && *end != ',') + end++; + for (flag = flags; flag->name != NULL; flag++) { + if (memcmp(start, flag->name, end - start) == 0) { + /* Matched "noXXXX", so reverse the sense. */ + clear |= flag->set; + set |= flag->clear; + break; + } else if (memcmp(start, flag->name + 2, end - start) + == 0) { + /* Matched "XXXX", so don't reverse. */ + set |= flag->set; + clear |= flag->clear; + break; + } + } + /* Ignore unknown flag names. */ + if (flag->name == NULL && failed == NULL) + failed = start; + + /* Find start of next token. */ + start = end; + while (*start == '\t' || *start == ' ' || *start == ',') + start++; + + } + + if (setp) + *setp = set; + if (clrp) + *clrp = clear; + + /* Return location of first failure. */ + return (failed); +} + +/* + * wcstofflags -- + * Take string of arguments and return file flags. This + * version works a little differently than strtofflags(3). + * In particular, it always tests every token, skipping any + * unrecognized tokens. It returns a pointer to the first + * unrecognized token, or NULL if every token was recognized. + * This version is also const-correct and does not modify the + * provided string. + */ +static const wchar_t * +ae_wcstofflags(const wchar_t *s, unsigned long *setp, unsigned long *clrp) +{ + const wchar_t *start, *end; + struct flag *flag; + unsigned long set, clear; + const wchar_t *failed; + + set = clear = 0; + start = s; + failed = NULL; + /* Find start of first token. */ + while (*start == L'\t' || *start == L' ' || *start == L',') + start++; + while (*start != L'\0') { + /* Locate end of token. */ + end = start; + while (*end != L'\0' && *end != L'\t' && + *end != L' ' && *end != L',') + end++; + for (flag = flags; flag->wname != NULL; flag++) { + if (wmemcmp(start, flag->wname, end - start) == 0) { + /* Matched "noXXXX", so reverse the sense. */ + clear |= flag->set; + set |= flag->clear; + break; + } else if (wmemcmp(start, flag->wname + 2, end - start) + == 0) { + /* Matched "XXXX", so don't reverse. */ + set |= flag->set; + clear |= flag->clear; + break; + } + } + /* Ignore unknown flag names. */ + if (flag->wname == NULL && failed == NULL) + failed = start; + + /* Find start of next token. */ + start = end; + while (*start == L'\t' || *start == L' ' || *start == L',') + start++; + + } + + if (setp) + *setp = set; + if (clrp) + *clrp = clear; + + /* Return location of first failure. */ + return (failed); +} + + +#ifdef TEST +#include +int +main(int argc, char **argv) +{ + struct archive_entry *entry = archive_entry_new(); + unsigned long set, clear; + const wchar_t *remainder; + + remainder = archive_entry_copy_fflags_text_w(entry, L"nosappnd dump archive,,,,,,,"); + archive_entry_fflags(entry, &set, &clear); + + wprintf(L"set=0x%lX clear=0x%lX remainder='%ls'\n", set, clear, remainder); + + wprintf(L"new flags='%s'\n", archive_entry_fflags_text(entry)); + return (0); +} +#endif diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h new file mode 100644 index 0000000..fcd7657 --- /dev/null +++ b/libarchive/archive_entry.h @@ -0,0 +1,620 @@ +/*- + * Copyright (c) 2003-2008 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_entry.h 201096 2009-12-28 02:41:27Z kientzle $ + */ + +#ifndef ARCHIVE_ENTRY_H_INCLUDED +#define ARCHIVE_ENTRY_H_INCLUDED + +/* Note: Compiler will complain if this does not match archive.h! */ +#define ARCHIVE_VERSION_NUMBER 3000001 + +/* + * Note: archive_entry.h is for use outside of libarchive; the + * configuration headers (config.h, archive_platform.h, etc.) are + * purely internal. Do NOT use HAVE_XXX configuration macros to + * control the behavior of this header! If you must conditionalize, + * use predefined compiler and/or platform macros. + */ + +#include +#include /* for wchar_t */ +#include + +#if defined(_WIN32) && !defined(__CYGWIN__) +#include +#endif + +/* Get appropriate definitions of standard POSIX-style types. */ +/* These should match the types used in 'struct stat' */ +#if defined(_WIN32) && !defined(__CYGWIN__) +#define __LA_INT64_T __int64 +# if defined(__BORLANDC__) +# define __LA_UID_T uid_t /* Remove in libarchive 3.2 */ +# define __LA_GID_T gid_t /* Remove in libarchive 3.2 */ +# define __LA_DEV_T dev_t +# define __LA_MODE_T mode_t +# else +# define __LA_UID_T short /* Remove in libarchive 3.2 */ +# define __LA_GID_T short /* Remove in libarchive 3.2 */ +# define __LA_DEV_T unsigned int +# define __LA_MODE_T unsigned short +# endif +#else +#include +# if defined(_SCO_DS) +# define __LA_INT64_T long long +# else +# define __LA_INT64_T int64_t +# endif +# define __LA_UID_T uid_t /* Remove in libarchive 3.2 */ +# define __LA_GID_T gid_t /* Remove in libarchive 3.2 */ +# define __LA_DEV_T dev_t +# define __LA_MODE_T mode_t +#endif + +/* + * Remove this for libarchive 3.2, since ino_t is no longer used. + */ +#define __LA_INO_T ino_t + + +/* + * On Windows, define LIBARCHIVE_STATIC if you're building or using a + * .lib. The default here assumes you're building a DLL. Only + * libarchive source should ever define __LIBARCHIVE_BUILD. + */ +#if ((defined __WIN32__) || (defined _WIN32) || defined(__CYGWIN__)) && (!defined LIBARCHIVE_STATIC) +# ifdef __LIBARCHIVE_BUILD +# ifdef __GNUC__ +# define __LA_DECL __attribute__((dllexport)) extern +# else +# define __LA_DECL __declspec(dllexport) +# endif +# else +# ifdef __GNUC__ +# define __LA_DECL +# else +# define __LA_DECL __declspec(dllimport) +# endif +# endif +#else +/* Static libraries on all platforms and shared libraries on non-Windows. */ +# define __LA_DECL +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Description of an archive entry. + * + * You can think of this as "struct stat" with some text fields added in. + * + * TODO: Add "comment", "charset", and possibly other entries that are + * supported by "pax interchange" format. However, GNU, ustar, cpio, + * and other variants don't support these features, so they're not an + * excruciatingly high priority right now. + * + * TODO: "pax interchange" format allows essentially arbitrary + * key/value attributes to be attached to any entry. Supporting + * such extensions may make this library useful for special + * applications (e.g., a package manager could attach special + * package-management attributes to each entry). + */ +struct archive; +struct archive_entry; + +/* + * File-type constants. These are returned from archive_entry_filetype() + * and passed to archive_entry_set_filetype(). + * + * These values match S_XXX defines on every platform I've checked, + * including Windows, AIX, Linux, Solaris, and BSD. They're + * (re)defined here because platforms generally don't define the ones + * they don't support. For example, Windows doesn't define S_IFLNK or + * S_IFBLK. Instead of having a mass of conditional logic and system + * checks to define any S_XXX values that aren't supported locally, + * I've just defined a new set of such constants so that + * libarchive-based applications can manipulate and identify archive + * entries properly even if the hosting platform can't store them on + * disk. + * + * These values are also used directly within some portable formats, + * such as cpio. If you find a platform that varies from these, the + * correct solution is to leave these alone and translate from these + * portable values to platform-native values when entries are read from + * or written to disk. + */ +#define AE_IFMT 0170000 +#define AE_IFREG 0100000 +#define AE_IFLNK 0120000 +#define AE_IFSOCK 0140000 +#define AE_IFCHR 0020000 +#define AE_IFBLK 0060000 +#define AE_IFDIR 0040000 +#define AE_IFIFO 0010000 + +/* + * Basic object manipulation + */ + +__LA_DECL struct archive_entry *archive_entry_clear(struct archive_entry *); +/* The 'clone' function does a deep copy; all of the strings are copied too. */ +__LA_DECL struct archive_entry *archive_entry_clone(struct archive_entry *); +__LA_DECL void archive_entry_free(struct archive_entry *); +__LA_DECL struct archive_entry *archive_entry_new(void); + +/* + * This form of archive_entry_new2() will pull character-set + * conversion information from the specified archive handle. The + * older archive_entry_new(void) form is equivalent to calling + * archive_entry_new2(NULL) and will result in the use of an internal + * default character-set conversion. + */ +__LA_DECL struct archive_entry *archive_entry_new2(struct archive *); + +/* + * Retrieve fields from an archive_entry. + * + * There are a number of implicit conversions among these fields. For + * example, if a regular string field is set and you read the _w wide + * character field, the entry will implicitly convert narrow-to-wide + * using the current locale. Similarly, dev values are automatically + * updated when you write devmajor or devminor and vice versa. + * + * In addition, fields can be "set" or "unset." Unset string fields + * return NULL, non-string fields have _is_set() functions to test + * whether they've been set. You can "unset" a string field by + * assigning NULL; non-string fields have _unset() functions to + * unset them. + * + * Note: There is one ambiguity in the above; string fields will + * also return NULL when implicit character set conversions fail. + * This is usually what you want. + */ +__LA_DECL time_t archive_entry_atime(struct archive_entry *); +__LA_DECL long archive_entry_atime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_atime_is_set(struct archive_entry *); +__LA_DECL time_t archive_entry_birthtime(struct archive_entry *); +__LA_DECL long archive_entry_birthtime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_birthtime_is_set(struct archive_entry *); +__LA_DECL time_t archive_entry_ctime(struct archive_entry *); +__LA_DECL long archive_entry_ctime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_ctime_is_set(struct archive_entry *); +__LA_DECL dev_t archive_entry_dev(struct archive_entry *); +__LA_DECL int archive_entry_dev_is_set(struct archive_entry *); +__LA_DECL dev_t archive_entry_devmajor(struct archive_entry *); +__LA_DECL dev_t archive_entry_devminor(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_filetype(struct archive_entry *); +__LA_DECL void archive_entry_fflags(struct archive_entry *, + unsigned long * /* set */, + unsigned long * /* clear */); +__LA_DECL const char *archive_entry_fflags_text(struct archive_entry *); +__LA_DECL __LA_INT64_T archive_entry_gid(struct archive_entry *); +__LA_DECL const char *archive_entry_gname(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_gname_w(struct archive_entry *); +__LA_DECL const char *archive_entry_hardlink(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_hardlink_w(struct archive_entry *); +__LA_DECL __LA_INT64_T archive_entry_ino(struct archive_entry *); +__LA_DECL __LA_INT64_T archive_entry_ino64(struct archive_entry *); +__LA_DECL int archive_entry_ino_is_set(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_mode(struct archive_entry *); +__LA_DECL time_t archive_entry_mtime(struct archive_entry *); +__LA_DECL long archive_entry_mtime_nsec(struct archive_entry *); +__LA_DECL int archive_entry_mtime_is_set(struct archive_entry *); +__LA_DECL unsigned int archive_entry_nlink(struct archive_entry *); +__LA_DECL const char *archive_entry_pathname(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_pathname_w(struct archive_entry *); +__LA_DECL __LA_MODE_T archive_entry_perm(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdev(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdevmajor(struct archive_entry *); +__LA_DECL dev_t archive_entry_rdevminor(struct archive_entry *); +__LA_DECL const char *archive_entry_sourcepath(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_sourcepath_w(struct archive_entry *); +__LA_DECL __LA_INT64_T archive_entry_size(struct archive_entry *); +__LA_DECL int archive_entry_size_is_set(struct archive_entry *); +__LA_DECL const char *archive_entry_strmode(struct archive_entry *); +__LA_DECL const char *archive_entry_symlink(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_symlink_w(struct archive_entry *); +__LA_DECL __LA_INT64_T archive_entry_uid(struct archive_entry *); +__LA_DECL const char *archive_entry_uname(struct archive_entry *); +__LA_DECL const wchar_t *archive_entry_uname_w(struct archive_entry *); + +/* + * Set fields in an archive_entry. + * + * Note: Before libarchive 2.4, there were 'set' and 'copy' versions + * of the string setters. 'copy' copied the actual string, 'set' just + * stored the pointer. In libarchive 2.4 and later, strings are + * always copied. + */ + +__LA_DECL void archive_entry_set_atime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_atime(struct archive_entry *); +#if defined(_WIN32) && !defined(__CYGWIN__) +__LA_DECL void archive_entry_copy_bhfi(struct archive_entry *, BY_HANDLE_FILE_INFORMATION *); +#endif +__LA_DECL void archive_entry_set_birthtime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_birthtime(struct archive_entry *); +__LA_DECL void archive_entry_set_ctime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_ctime(struct archive_entry *); +__LA_DECL void archive_entry_set_dev(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_devmajor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_devminor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_filetype(struct archive_entry *, unsigned int); +__LA_DECL void archive_entry_set_fflags(struct archive_entry *, + unsigned long /* set */, unsigned long /* clear */); +/* Returns pointer to start of first invalid token, or NULL if none. */ +/* Note that all recognized tokens are processed, regardless. */ +__LA_DECL const char *archive_entry_copy_fflags_text(struct archive_entry *, + const char *); +__LA_DECL const wchar_t *archive_entry_copy_fflags_text_w(struct archive_entry *, + const wchar_t *); +__LA_DECL void archive_entry_set_gid(struct archive_entry *, __LA_INT64_T); +__LA_DECL void archive_entry_set_gname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_gname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_gname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_gname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_hardlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_hardlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_hardlink_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_hardlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_ino(struct archive_entry *, __LA_INT64_T); +__LA_DECL void archive_entry_set_ino64(struct archive_entry *, __LA_INT64_T); +__LA_DECL void archive_entry_set_link(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_link(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_link_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_link_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_mode(struct archive_entry *, __LA_MODE_T); +__LA_DECL void archive_entry_set_mtime(struct archive_entry *, time_t, long); +__LA_DECL void archive_entry_unset_mtime(struct archive_entry *); +__LA_DECL void archive_entry_set_nlink(struct archive_entry *, unsigned int); +__LA_DECL void archive_entry_set_pathname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_pathname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_pathname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_pathname_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_perm(struct archive_entry *, __LA_MODE_T); +__LA_DECL void archive_entry_set_rdev(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_rdevmajor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_rdevminor(struct archive_entry *, dev_t); +__LA_DECL void archive_entry_set_size(struct archive_entry *, __LA_INT64_T); +__LA_DECL void archive_entry_unset_size(struct archive_entry *); +__LA_DECL void archive_entry_copy_sourcepath(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_sourcepath_w(struct archive_entry *, const wchar_t *); +__LA_DECL void archive_entry_set_symlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_symlink(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_symlink_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_symlink_utf8(struct archive_entry *, const char *); +__LA_DECL void archive_entry_set_uid(struct archive_entry *, __LA_INT64_T); +__LA_DECL void archive_entry_set_uname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_uname(struct archive_entry *, const char *); +__LA_DECL void archive_entry_copy_uname_w(struct archive_entry *, const wchar_t *); +__LA_DECL int archive_entry_update_uname_utf8(struct archive_entry *, const char *); +/* + * Routines to bulk copy fields to/from a platform-native "struct + * stat." Libarchive used to just store a struct stat inside of each + * archive_entry object, but this created issues when trying to + * manipulate archives on systems different than the ones they were + * created on. + * + * TODO: On Linux, provide both stat32 and stat64 versions of these functions. + */ +__LA_DECL const struct stat *archive_entry_stat(struct archive_entry *); +__LA_DECL void archive_entry_copy_stat(struct archive_entry *, const struct stat *); + +/* + * Storage for Mac OS-specific AppleDouble metadata information. + * Apple-format tar files store a separate binary blob containing + * encoded metadata with ACL, extended attributes, etc. + * This provides a place to store that blob. + */ + +__LA_DECL const void * archive_entry_mac_metadata(struct archive_entry *, size_t *); +__LA_DECL void archive_entry_copy_mac_metadata(struct archive_entry *, const void *, size_t); + +/* + * ACL routines. This used to simply store and return text-format ACL + * strings, but that proved insufficient for a number of reasons: + * = clients need control over uname/uid and gname/gid mappings + * = there are many different ACL text formats + * = would like to be able to read/convert archives containing ACLs + * on platforms that lack ACL libraries + * + * This last point, in particular, forces me to implement a reasonably + * complete set of ACL support routines. + */ + +/* + * Permission bits. + */ +#define ARCHIVE_ENTRY_ACL_EXECUTE 0x00000001 +#define ARCHIVE_ENTRY_ACL_WRITE 0x00000002 +#define ARCHIVE_ENTRY_ACL_READ 0x00000004 +#define ARCHIVE_ENTRY_ACL_READ_DATA 0x00000008 +#define ARCHIVE_ENTRY_ACL_LIST_DIRECTORY 0x00000008 +#define ARCHIVE_ENTRY_ACL_WRITE_DATA 0x00000010 +#define ARCHIVE_ENTRY_ACL_ADD_FILE 0x00000010 +#define ARCHIVE_ENTRY_ACL_APPEND_DATA 0x00000020 +#define ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY 0x00000020 +#define ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS 0x00000040 +#define ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS 0x00000080 +#define ARCHIVE_ENTRY_ACL_DELETE_CHILD 0x00000100 +#define ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES 0x00000200 +#define ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES 0x00000400 +#define ARCHIVE_ENTRY_ACL_DELETE 0x00000800 +#define ARCHIVE_ENTRY_ACL_READ_ACL 0x00001000 +#define ARCHIVE_ENTRY_ACL_WRITE_ACL 0x00002000 +#define ARCHIVE_ENTRY_ACL_WRITE_OWNER 0x00004000 +#define ARCHIVE_ENTRY_ACL_SYNCHRONIZE 0x00008000 + +#define ARCHIVE_ENTRY_ACL_PERMS_POSIX1E \ + (ARCHIVE_ENTRY_ACL_EXECUTE \ + | ARCHIVE_ENTRY_ACL_WRITE \ + | ARCHIVE_ENTRY_ACL_READ) + +#define ARCHIVE_ENTRY_ACL_PERMS_NFS4 \ + (ARCHIVE_ENTRY_ACL_EXECUTE \ + | ARCHIVE_ENTRY_ACL_READ_DATA \ + | ARCHIVE_ENTRY_ACL_LIST_DIRECTORY \ + | ARCHIVE_ENTRY_ACL_WRITE_DATA \ + | ARCHIVE_ENTRY_ACL_ADD_FILE \ + | ARCHIVE_ENTRY_ACL_APPEND_DATA \ + | ARCHIVE_ENTRY_ACL_ADD_SUBDIRECTORY \ + | ARCHIVE_ENTRY_ACL_READ_NAMED_ATTRS \ + | ARCHIVE_ENTRY_ACL_WRITE_NAMED_ATTRS \ + | ARCHIVE_ENTRY_ACL_DELETE_CHILD \ + | ARCHIVE_ENTRY_ACL_READ_ATTRIBUTES \ + | ARCHIVE_ENTRY_ACL_WRITE_ATTRIBUTES \ + | ARCHIVE_ENTRY_ACL_DELETE \ + | ARCHIVE_ENTRY_ACL_READ_ACL \ + | ARCHIVE_ENTRY_ACL_WRITE_ACL \ + | ARCHIVE_ENTRY_ACL_WRITE_OWNER \ + | ARCHIVE_ENTRY_ACL_SYNCHRONIZE) + +/* + * Inheritance values (NFS4 ACLs only); included in permset. + */ +#define ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT 0x02000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT 0x04000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT 0x08000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY 0x10000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS 0x20000000 +#define ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS 0x40000000 + +#define ARCHIVE_ENTRY_ACL_INHERITANCE_NFS4 \ + (ARCHIVE_ENTRY_ACL_ENTRY_FILE_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_DIRECTORY_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_NO_PROPAGATE_INHERIT \ + | ARCHIVE_ENTRY_ACL_ENTRY_INHERIT_ONLY \ + | ARCHIVE_ENTRY_ACL_ENTRY_SUCCESSFUL_ACCESS \ + | ARCHIVE_ENTRY_ACL_ENTRY_FAILED_ACCESS) + +/* We need to be able to specify combinations of these. */ +#define ARCHIVE_ENTRY_ACL_TYPE_ACCESS 256 /* POSIX.1e only */ +#define ARCHIVE_ENTRY_ACL_TYPE_DEFAULT 512 /* POSIX.1e only */ +#define ARCHIVE_ENTRY_ACL_TYPE_ALLOW 1024 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_DENY 2048 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_AUDIT 4096 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_ALARM 8192 /* NFS4 only */ +#define ARCHIVE_ENTRY_ACL_TYPE_POSIX1E (ARCHIVE_ENTRY_ACL_TYPE_ACCESS \ + | ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) +#define ARCHIVE_ENTRY_ACL_TYPE_NFS4 (ARCHIVE_ENTRY_ACL_TYPE_ALLOW \ + | ARCHIVE_ENTRY_ACL_TYPE_DENY \ + | ARCHIVE_ENTRY_ACL_TYPE_AUDIT \ + | ARCHIVE_ENTRY_ACL_TYPE_ALARM) + +/* Tag values mimic POSIX.1e */ +#define ARCHIVE_ENTRY_ACL_USER 10001 /* Specified user. */ +#define ARCHIVE_ENTRY_ACL_USER_OBJ 10002 /* User who owns the file. */ +#define ARCHIVE_ENTRY_ACL_GROUP 10003 /* Specified group. */ +#define ARCHIVE_ENTRY_ACL_GROUP_OBJ 10004 /* Group who owns the file. */ +#define ARCHIVE_ENTRY_ACL_MASK 10005 /* Modify group access (POSIX.1e only) */ +#define ARCHIVE_ENTRY_ACL_OTHER 10006 /* Public (POSIX.1e only) */ +#define ARCHIVE_ENTRY_ACL_EVERYONE 10107 /* Everyone (NFS4 only) */ + +/* + * Set the ACL by clearing it and adding entries one at a time. + * Unlike the POSIX.1e ACL routines, you must specify the type + * (access/default) for each entry. Internally, the ACL data is just + * a soup of entries. API calls here allow you to retrieve just the + * entries of interest. This design (which goes against the spirit of + * POSIX.1e) is useful for handling archive formats that combine + * default and access information in a single ACL list. + */ +__LA_DECL void archive_entry_acl_clear(struct archive_entry *); +__LA_DECL int archive_entry_acl_add_entry(struct archive_entry *, + int /* type */, int /* permset */, int /* tag */, + int /* qual */, const char * /* name */); +__LA_DECL int archive_entry_acl_add_entry_w(struct archive_entry *, + int /* type */, int /* permset */, int /* tag */, + int /* qual */, const wchar_t * /* name */); + +/* + * To retrieve the ACL, first "reset", then repeatedly ask for the + * "next" entry. The want_type parameter allows you to request only + * certain types of entries. + */ +__LA_DECL int archive_entry_acl_reset(struct archive_entry *, int /* want_type */); +__LA_DECL int archive_entry_acl_next(struct archive_entry *, int /* want_type */, + int * /* type */, int * /* permset */, int * /* tag */, + int * /* qual */, const char ** /* name */); +__LA_DECL int archive_entry_acl_next_w(struct archive_entry *, int /* want_type */, + int * /* type */, int * /* permset */, int * /* tag */, + int * /* qual */, const wchar_t ** /* name */); + +/* + * Construct a text-format ACL. The flags argument is a bitmask that + * can include any of the following: + * + * ARCHIVE_ENTRY_ACL_TYPE_ACCESS - Include POSIX.1e "access" entries. + * ARCHIVE_ENTRY_ACL_TYPE_DEFAULT - Include POSIX.1e "default" entries. + * ARCHIVE_ENTRY_ACL_TYPE_NFS4 - Include NFS4 entries. + * ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID - Include extra numeric ID field in + * each ACL entry. ('star' introduced this for POSIX.1e, this flag + * also applies to NFS4.) + * ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT - Include "default:" before each + * default ACL entry, as used in old Solaris ACLs. + */ +#define ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID 1024 +#define ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT 2048 +__LA_DECL const wchar_t *archive_entry_acl_text_w(struct archive_entry *, + int /* flags */); +__LA_DECL const char *archive_entry_acl_text(struct archive_entry *, + int /* flags */); + +/* Return a count of entries matching 'want_type' */ +__LA_DECL int archive_entry_acl_count(struct archive_entry *, int /* want_type */); + +/* Return an opaque ACL object. */ +/* There's not yet anything clients can actually do with this... */ +struct archive_acl; +__LA_DECL struct archive_acl *archive_entry_acl(struct archive_entry *); + +/* + * extended attributes + */ + +__LA_DECL void archive_entry_xattr_clear(struct archive_entry *); +__LA_DECL void archive_entry_xattr_add_entry(struct archive_entry *, + const char * /* name */, const void * /* value */, + size_t /* size */); + +/* + * To retrieve the xattr list, first "reset", then repeatedly ask for the + * "next" entry. + */ + +__LA_DECL int archive_entry_xattr_count(struct archive_entry *); +__LA_DECL int archive_entry_xattr_reset(struct archive_entry *); +__LA_DECL int archive_entry_xattr_next(struct archive_entry *, + const char ** /* name */, const void ** /* value */, size_t *); + +/* + * sparse + */ + +__LA_DECL void archive_entry_sparse_clear(struct archive_entry *); +__LA_DECL void archive_entry_sparse_add_entry(struct archive_entry *, + __LA_INT64_T /* offset */, __LA_INT64_T /* length */); + +/* + * To retrieve the xattr list, first "reset", then repeatedly ask for the + * "next" entry. + */ + +__LA_DECL int archive_entry_sparse_count(struct archive_entry *); +__LA_DECL int archive_entry_sparse_reset(struct archive_entry *); +__LA_DECL int archive_entry_sparse_next(struct archive_entry *, + __LA_INT64_T * /* offset */, __LA_INT64_T * /* length */); + +/* + * Utility to match up hardlinks. + * + * The 'struct archive_entry_linkresolver' is a cache of archive entries + * for files with multiple links. Here's how to use it: + * 1. Create a lookup object with archive_entry_linkresolver_new() + * 2. Tell it the archive format you're using. + * 3. Hand each archive_entry to archive_entry_linkify(). + * That function will return 0, 1, or 2 entries that should + * be written. + * 4. Call archive_entry_linkify(resolver, NULL) until + * no more entries are returned. + * 5. Call archive_entry_linkresolver_free(resolver) to free resources. + * + * The entries returned have their hardlink and size fields updated + * appropriately. If an entry is passed in that does not refer to + * a file with multiple links, it is returned unchanged. The intention + * is that you should be able to simply filter all entries through + * this machine. + * + * To make things more efficient, be sure that each entry has a valid + * nlinks value. The hardlink cache uses this to track when all links + * have been found. If the nlinks value is zero, it will keep every + * name in the cache indefinitely, which can use a lot of memory. + * + * Note that archive_entry_size() is reset to zero if the file + * body should not be written to the archive. Pay attention! + */ +struct archive_entry_linkresolver; + +/* + * There are three different strategies for marking hardlinks. + * The descriptions below name them after the best-known + * formats that rely on each strategy: + * + * "Old cpio" is the simplest, it always returns any entry unmodified. + * As far as I know, only cpio formats use this. Old cpio archives + * store every link with the full body; the onus is on the dearchiver + * to detect and properly link the files as they are restored. + * "tar" is also pretty simple; it caches a copy the first time it sees + * any link. Subsequent appearances are modified to be hardlink + * references to the first one without any body. Used by all tar + * formats, although the newest tar formats permit the "old cpio" strategy + * as well. This strategy is very simple for the dearchiver, + * and reasonably straightforward for the archiver. + * "new cpio" is trickier. It stores the body only with the last + * occurrence. The complication is that we might not + * see every link to a particular file in a single session, so + * there's no easy way to know when we've seen the last occurrence. + * The solution here is to queue one link until we see the next. + * At the end of the session, you can enumerate any remaining + * entries by calling archive_entry_linkify(NULL) and store those + * bodies. If you have a file with three links l1, l2, and l3, + * you'll get the following behavior if you see all three links: + * linkify(l1) => NULL (the resolver stores l1 internally) + * linkify(l2) => l1 (resolver stores l2, you write l1) + * linkify(l3) => l2, l3 (all links seen, you can write both). + * If you only see l1 and l2, you'll get this behavior: + * linkify(l1) => NULL + * linkify(l2) => l1 + * linkify(NULL) => l2 (at end, you retrieve remaining links) + * As the name suggests, this strategy is used by newer cpio variants. + * It's noticeably more complex for the archiver, slightly more complex + * for the dearchiver than the tar strategy, but makes it straightforward + * to restore a file using any link by simply continuing to scan until + * you see a link that is stored with a body. In contrast, the tar + * strategy requires you to rescan the archive from the beginning to + * correctly extract an arbitrary link. + */ + +__LA_DECL struct archive_entry_linkresolver *archive_entry_linkresolver_new(void); +__LA_DECL void archive_entry_linkresolver_set_strategy( + struct archive_entry_linkresolver *, int /* format_code */); +__LA_DECL void archive_entry_linkresolver_free(struct archive_entry_linkresolver *); +__LA_DECL void archive_entry_linkify(struct archive_entry_linkresolver *, + struct archive_entry **, struct archive_entry **); +__LA_DECL struct archive_entry *archive_entry_partial_links( + struct archive_entry_linkresolver *res, unsigned int *links); + +#ifdef __cplusplus +} +#endif + +/* This is meaningless outside of this header. */ +#undef __LA_DECL + +#endif /* !ARCHIVE_ENTRY_H_INCLUDED */ diff --git a/libarchive/archive_entry_acl.3 b/libarchive/archive_entry_acl.3 new file mode 100644 index 0000000..896b35a --- /dev/null +++ b/libarchive/archive_entry_acl.3 @@ -0,0 +1,233 @@ +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd February 21, 2010 +.Dt archive_entry_acl 3 +.Os +.Sh NAME +.Nm archive_entry_acl_add_entry , +.Nm archive_entry_acl_add_entry_w , +.Nm archive_entry_acl_clear , +.Nm archive_entry_acl_count , +.Nm archive_entry_acl_next , +.Nm archive_entry_acl_next_w , +.Nm archive_entry_acl_reset , +.Nm archive_entry_acl_text_w +.Nd functions for manipulating Access Control Lists in archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft void +.Fo archive_entry_acl_add_entry +.Fa "struct archive_entry *a" +.Fa "int type" +.Fa "int permset" +.Fa "int tag" +.Fa "int qualifier" +.Fa "const char *name" +.Fc +.Ft void +.Fo archive_entry_acl_add_entry_w +.Fa "struct archive_entry *a" +.Fa "int type" +.Fa "int permset" +.Fa "int tag" +.Fa "int qualifier" +.Fa "const wchar_t *name" +.Fc +.Ft void +.Fn archive_entry_acl_clear "struct archive_entry *a" +.Ft int +.Fn archive_entry_acl_count "struct archive_entry *a" "int type" +.Ft int +.Fo archive_entry_acl_next +.Fa "struct archive_entry *a" +.Fa "int type" +.Fa "int *ret_type" +.Fa "int *ret_permset" +.Fa "int *ret_tag" +.Fa "int *ret_qual" +.Fa "const char **ret_name" +.Fc +.Ft int +.Fo archive_entry_acl_next_w +.Fa "struct archive_entry *a" +.Fa "int type" +.Fa "int *ret_type" +.Fa "int *ret_permset" +.Fa "int *ret_tag" +.Fa "int *ret_qual" +.Fa "const wchar_t **ret_name" +.Fc +.Ft int +.Fn archive_entry_acl_reset "struct archive_entry *a" "int type" +.Ft const wchar_t * +.Fn archive_entry_acl_text_w "struct archive_entry *a" "int flags" +.\" enum? +.Sh DESCRIPTION +An +.Dq Access Control List +is a generalisation of the classic Unix permission system. +The ACL interface of +.Nm libarchive +is derived from the POSIX.1e draft, but restricted to simplify dealing +with practical implementations in various Operating Systems and archive formats. +.Pp +An ACL consists of a number of independent entries. +Each entry specifies the permission set as bitmask of basic permissions. +Valid permissions are: +.Bl -tag -offset indent -compact -width "ARCHIVE_ENTRY_ACL_EXECUTE" +.It Dv ARCHIVE_ENTRY_ACL_EXECUTE +.It Dv ARCHIVE_ENTRY_ACL_WRITE +.It Dv ARCHIVE_ENTRY_ACL_READ +.El +The permissions correspond to the normal Unix permissions. +.Pp +The tag specifies the principal to which the permission applies. +Valid values are: +.Bl -tag -offset indent -compact -width "ARCHIVE_ENTRY_ACL_GROUP_OBJ" +.It Dv ARCHIVE_ENTRY_ACL_USER +The user specified by the name field. +.It Dv ARCHIVE_ENTRY_ACL_USER_OBJ +The owner of the file. +.It Dv ARCHIVE_ENTRY_ACL_GROUP +The group specied by the name field. +.It Dv ARCHIVE_ENTRY_ACL_GROUP_OBJ +The group who owns the file. +.It Dv ARCHIVE_ENTRY_ACL_MASK +The maximum permissions to be obtained via group permissions. +.It Dv ARCHIVE_ENTRY_ACL_OTHER +Any principal who doesn't have a user or group entry. +.El +The principals +.Dv ARCHIVE_ENTRY_ACL_USER_OBJ , +.Dv ARCHIVE_ENTRY_ACL_GROUP_OBJ +and +.Dv ARCHIVE_ENTRY_ACL_OTHER +are equivalent to user, group and other in the classic Unix permission +model and specify non-extended ACL entries. +.Pp +All files have an access ACL +.Pq Dv ARCHIVE_ENTRY_ACL_TYPE_ACCESS . +This specifies the permissions required for access to the file itself. +Directories have an additional ACL +.Pq Dv ARCHIVE_ENTRY_ACL_TYPE_DEFAULT , +which controlls the initial access ACL for newly created directory entries. +.Pp +.Fn archive_entry_acl_add_entry +and +.Fn archive_entry_acl_add_entry_w +add a single ACL entry. +For the access ACL and non-extended principals, the classic Unix permissions +are updated. +.Pp +.Fn archive_entry_acl_clear +removes all ACL entries and resets the enumeration pointer. +.Pp +.Fn archive_entry_acl_count +counts the ACL entries that have the given type mask. +.Fa type +can be the bitwise-or of +.Dv ARCHIVE_ENTRY_ACL_TYPE_ACCESS +and +.Dv ARCHIVE_ENTRY_ACL_TYPE_DEFAULT . +If +.Dv ARCHIVE_ENTRY_ACL_TYPE_ACCESS +is included and at least one extended ACL entry is found, +the three non-extened ACLs are added. +.Pp +.Fn archive_entry_acl_next +and +.Fn archive_entry_acl_next_w +return the next entry of the ACL list. +This functions may only be called after +.Fn archive_entry_acl_reset +has indicated the presence of extended ACL entries. +.Pp +.Fn archive_entry_acl_reset +prepare reading the list of ACL entries with +.Fn archive_entry_acl_next +or +.Fn archive_entry_acl_next_w . +The function returns either 0, if no non-extended ACLs are found. +In this case, the access permissions should be obtained by +.Xr archive_entry_mode 3 +or set using +.Xr chmod 2 . +Otherwise, the function returns the same value as +.Fn archive_entry_acl_count . +.Pp +.Fn archive_entry_acl_text_w +converts the ACL entries for the given type mask into a wide string. +In addition to the normal type flags, +.Dv ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID +and +.Dv ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT +can be specified to further customize the result. +The returned long string is valid until the next call to +.Fn archive_entry_acl_clear , +.Fn archive_entry_acl_add_entry , +.Fn archive_entry_acl_add_entry_w +or +.Fn archive_entry_acl_text_w . +.Sh RETURN VALUES +.Fn archive_entry_acl_count +and +.Fn archive_entry_acl_reset +returns the number of ACL entries that match the given type mask. +If the type mask includes +.Dv ARCHIVE_ENTRY_ACL_TYPE_ACCESS +and at least one extended ACL entry exists, the three classic Unix +permissions are counted. +.Pp +.Fn archive_entry_acl_next +and +.Fn archive_entry_acl_next_w +return +.Dv ARCHIVE_OK +on success, +.Dv ARCHIVE_EOF +if no more ACL entries exist +and +.Dv ARCHIVE_WARN +if +.Fn archive_entry_acl_reset +has not been called first. +.Pp +.Fn archive_entry_text_w +returns a wide string representation of the ACL entrise matching the +given type mask. +The returned long string is valid until the next call to +.Fn archive_entry_acl_clear , +.Fn archive_entry_acl_add_entry , +.Fn archive_entry_acl_add_entry_w +or +.Fn archive_entry_acl_text_w . +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 +.Sh BUGS +.Dv ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID +and +.Dv ARCHIVE_ENTRY_ACL_STYLE_MARK_DEFAULT +are not documented. diff --git a/libarchive/archive_entry_copy_bhfi.c b/libarchive/archive_entry_copy_bhfi.c new file mode 100644 index 0000000..7a4bc9c --- /dev/null +++ b/libarchive/archive_entry_copy_bhfi.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive_private.h" +#include "archive_entry.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) + +__inline static void +fileTimeToUtc(const FILETIME *filetime, time_t *time, long *ns) +{ + ULARGE_INTEGER utc; + + utc.HighPart = filetime->dwHighDateTime; + utc.LowPart = filetime->dwLowDateTime; + if (utc.QuadPart >= EPOC_TIME) { + utc.QuadPart -= EPOC_TIME; + *time = (time_t)(utc.QuadPart / 10000000); /* milli seconds base */ + *ns = (long)(utc.QuadPart % 10000000) * 100;/* nano seconds base */ + } else { + *time = 0; + *ns = 0; + } +} + +void +archive_entry_copy_bhfi(struct archive_entry *entry, + BY_HANDLE_FILE_INFORMATION *bhfi) +{ + time_t secs; + long nsecs; + + fileTimeToUtc(&bhfi->ftLastAccessTime, &secs, &nsecs); + archive_entry_set_atime(entry, secs, nsecs); + fileTimeToUtc(&bhfi->ftLastWriteTime, &secs, &nsecs); + archive_entry_set_mtime(entry, secs, nsecs); + fileTimeToUtc(&bhfi->ftCreationTime, &secs, &nsecs); + archive_entry_set_birthtime(entry, secs, nsecs); + archive_entry_set_ctime(entry, secs, nsecs); + archive_entry_set_dev(entry, bhfi->dwVolumeSerialNumber); + archive_entry_set_ino64(entry, (((int64_t)bhfi->nFileIndexHigh) << 32) + + bhfi->nFileIndexLow); + archive_entry_set_nlink(entry, bhfi->nNumberOfLinks); + archive_entry_set_size(entry, (((int64_t)bhfi->nFileSizeHigh) << 32) + + bhfi->nFileSizeLow); + /* archive_entry_set_mode(entry, st->st_mode); */ +} +#endif diff --git a/libarchive/archive_entry_copy_stat.c b/libarchive/archive_entry_copy_stat.c new file mode 100644 index 0000000..37d4d6e --- /dev/null +++ b/libarchive/archive_entry_copy_stat.c @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_copy_stat.c 189466 2009-03-07 00:52:02Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" + +void +archive_entry_copy_stat(struct archive_entry *entry, const struct stat *st) +{ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + archive_entry_set_atime(entry, st->st_atime, st->st_atimespec.tv_nsec); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctimespec.tv_nsec); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtimespec.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + archive_entry_set_atime(entry, st->st_atime, st->st_atim.tv_nsec); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctim.tv_nsec); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtim.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_MTIME_N + archive_entry_set_atime(entry, st->st_atime, st->st_atime_n); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctime_n); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtime_n); +#elif HAVE_STRUCT_STAT_ST_UMTIME + archive_entry_set_atime(entry, st->st_atime, st->st_uatime * 1000); + archive_entry_set_ctime(entry, st->st_ctime, st->st_uctime * 1000); + archive_entry_set_mtime(entry, st->st_mtime, st->st_umtime * 1000); +#elif HAVE_STRUCT_STAT_ST_MTIME_USEC + archive_entry_set_atime(entry, st->st_atime, st->st_atime_usec * 1000); + archive_entry_set_ctime(entry, st->st_ctime, st->st_ctime_usec * 1000); + archive_entry_set_mtime(entry, st->st_mtime, st->st_mtime_usec * 1000); +#else + archive_entry_set_atime(entry, st->st_atime, 0); + archive_entry_set_ctime(entry, st->st_ctime, 0); + archive_entry_set_mtime(entry, st->st_mtime, 0); +#endif +#if HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC + archive_entry_set_birthtime(entry, st->st_birthtime, st->st_birthtimespec.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_BIRTHTIME + archive_entry_set_birthtime(entry, st->st_birthtime, 0); +#else + archive_entry_unset_birthtime(entry); +#endif + archive_entry_set_dev(entry, st->st_dev); + archive_entry_set_gid(entry, st->st_gid); + archive_entry_set_uid(entry, st->st_uid); + archive_entry_set_ino(entry, st->st_ino); + archive_entry_set_nlink(entry, st->st_nlink); + archive_entry_set_rdev(entry, st->st_rdev); + archive_entry_set_size(entry, st->st_size); + archive_entry_set_mode(entry, st->st_mode); +} diff --git a/libarchive/archive_entry_link_resolver.c b/libarchive/archive_entry_link_resolver.c new file mode 100644 index 0000000..a18144d --- /dev/null +++ b/libarchive/archive_entry_link_resolver.c @@ -0,0 +1,444 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_link_resolver.c 201100 2009-12-28 03:05:31Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" + +/* + * This is mostly a pretty straightforward hash table implementation. + * The only interesting bit is the different strategies used to + * match up links. These strategies match those used by various + * archiving formats: + * tar - content stored with first link, remainder refer back to it. + * This requires us to match each subsequent link up with the + * first appearance. + * cpio - Old cpio just stored body with each link, match-ups were + * implicit. This is trivial. + * new cpio - New cpio only stores body with last link, match-ups + * are implicit. This is actually quite tricky; see the notes + * below. + */ + +/* Users pass us a format code, we translate that into a strategy here. */ +#define ARCHIVE_ENTRY_LINKIFY_LIKE_TAR 0 +#define ARCHIVE_ENTRY_LINKIFY_LIKE_MTREE 1 +#define ARCHIVE_ENTRY_LINKIFY_LIKE_OLD_CPIO 2 +#define ARCHIVE_ENTRY_LINKIFY_LIKE_NEW_CPIO 3 + +/* Initial size of link cache. */ +#define links_cache_initial_size 1024 + +struct links_entry { + struct links_entry *next; + struct links_entry *previous; + struct archive_entry *canonical; + struct archive_entry *entry; + size_t hash; + unsigned int links; /* # links not yet seen */ +}; + +struct archive_entry_linkresolver { + struct links_entry **buckets; + struct links_entry *spare; + unsigned long number_entries; + size_t number_buckets; + int strategy; +}; + +#define NEXT_ENTRY_DEFERRED 1 +#define NEXT_ENTRY_PARTIAL 2 +#define NEXT_ENTRY_ALL (NEXT_ENTRY_DEFERRED | NEXT_ENTRY_PARTIAL) + +static struct links_entry *find_entry(struct archive_entry_linkresolver *, + struct archive_entry *); +static void grow_hash(struct archive_entry_linkresolver *); +static struct links_entry *insert_entry(struct archive_entry_linkresolver *, + struct archive_entry *); +static struct links_entry *next_entry(struct archive_entry_linkresolver *, + int); + +struct archive_entry_linkresolver * +archive_entry_linkresolver_new(void) +{ + struct archive_entry_linkresolver *res; + + /* Check for positive power-of-two */ + if (links_cache_initial_size == 0 || + (links_cache_initial_size & (links_cache_initial_size - 1)) != 0) + return (NULL); + + res = calloc(1, sizeof(struct archive_entry_linkresolver)); + if (res == NULL) + return (NULL); + res->number_buckets = links_cache_initial_size; + res->buckets = calloc(res->number_buckets, sizeof(res->buckets[0])); + if (res->buckets == NULL) { + free(res); + return (NULL); + } + return (res); +} + +void +archive_entry_linkresolver_set_strategy(struct archive_entry_linkresolver *res, + int fmt) +{ + int fmtbase = fmt & ARCHIVE_FORMAT_BASE_MASK; + + switch (fmtbase) { + case ARCHIVE_FORMAT_7ZIP: + case ARCHIVE_FORMAT_AR: + case ARCHIVE_FORMAT_ZIP: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_OLD_CPIO; + break; + case ARCHIVE_FORMAT_CPIO: + switch (fmt) { + case ARCHIVE_FORMAT_CPIO_SVR4_NOCRC: + case ARCHIVE_FORMAT_CPIO_SVR4_CRC: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_NEW_CPIO; + break; + default: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_OLD_CPIO; + break; + } + break; + case ARCHIVE_FORMAT_MTREE: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_MTREE; + break; + case ARCHIVE_FORMAT_ISO9660: + case ARCHIVE_FORMAT_SHAR: + case ARCHIVE_FORMAT_TAR: + case ARCHIVE_FORMAT_XAR: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_TAR; + break; + default: + res->strategy = ARCHIVE_ENTRY_LINKIFY_LIKE_OLD_CPIO; + break; + } +} + +void +archive_entry_linkresolver_free(struct archive_entry_linkresolver *res) +{ + struct links_entry *le; + + if (res == NULL) + return; + + while ((le = next_entry(res, NEXT_ENTRY_ALL)) != NULL) + archive_entry_free(le->entry); + free(res->buckets); + free(res); +} + +void +archive_entry_linkify(struct archive_entry_linkresolver *res, + struct archive_entry **e, struct archive_entry **f) +{ + struct links_entry *le; + struct archive_entry *t; + + *f = NULL; /* Default: Don't return a second entry. */ + + if (*e == NULL) { + le = next_entry(res, NEXT_ENTRY_DEFERRED); + if (le != NULL) { + *e = le->entry; + le->entry = NULL; + } + return; + } + + /* If it has only one link, then we're done. */ + if (archive_entry_nlink(*e) == 1) + return; + /* Directories, devices never have hardlinks. */ + if (archive_entry_filetype(*e) == AE_IFDIR + || archive_entry_filetype(*e) == AE_IFBLK + || archive_entry_filetype(*e) == AE_IFCHR) + return; + + switch (res->strategy) { + case ARCHIVE_ENTRY_LINKIFY_LIKE_TAR: + le = find_entry(res, *e); + if (le != NULL) { + archive_entry_unset_size(*e); + archive_entry_copy_hardlink(*e, + archive_entry_pathname(le->canonical)); + } else + insert_entry(res, *e); + return; + case ARCHIVE_ENTRY_LINKIFY_LIKE_MTREE: + le = find_entry(res, *e); + if (le != NULL) { + archive_entry_copy_hardlink(*e, + archive_entry_pathname(le->canonical)); + } else + insert_entry(res, *e); + return; + case ARCHIVE_ENTRY_LINKIFY_LIKE_OLD_CPIO: + /* This one is trivial. */ + return; + case ARCHIVE_ENTRY_LINKIFY_LIKE_NEW_CPIO: + le = find_entry(res, *e); + if (le != NULL) { + /* + * Put the new entry in le, return the + * old entry from le. + */ + t = *e; + *e = le->entry; + le->entry = t; + /* Make the old entry into a hardlink. */ + archive_entry_unset_size(*e); + archive_entry_copy_hardlink(*e, + archive_entry_pathname(le->canonical)); + /* If we ran out of links, return the + * final entry as well. */ + if (le->links == 0) { + *f = le->entry; + le->entry = NULL; + } + } else { + /* + * If we haven't seen it, tuck it away + * for future use. + */ + le = insert_entry(res, *e); + le->entry = *e; + *e = NULL; + } + return; + default: + break; + } + return; +} + +static struct links_entry * +find_entry(struct archive_entry_linkresolver *res, + struct archive_entry *entry) +{ + struct links_entry *le; + size_t hash, bucket; + dev_t dev; + int64_t ino; + + /* Free a held entry. */ + if (res->spare != NULL) { + archive_entry_free(res->spare->canonical); + archive_entry_free(res->spare->entry); + free(res->spare); + res->spare = NULL; + } + + dev = archive_entry_dev(entry); + ino = archive_entry_ino64(entry); + hash = (size_t)(dev ^ ino); + + /* Try to locate this entry in the links cache. */ + bucket = hash & (res->number_buckets - 1); + for (le = res->buckets[bucket]; le != NULL; le = le->next) { + if (le->hash == hash + && dev == archive_entry_dev(le->canonical) + && ino == archive_entry_ino64(le->canonical)) { + /* + * Decrement link count each time and release + * the entry if it hits zero. This saves + * memory and is necessary for detecting + * missed links. + */ + --le->links; + if (le->links > 0) + return (le); + /* Remove it from this hash bucket. */ + if (le->previous != NULL) + le->previous->next = le->next; + if (le->next != NULL) + le->next->previous = le->previous; + if (res->buckets[bucket] == le) + res->buckets[bucket] = le->next; + res->number_entries--; + /* Defer freeing this entry. */ + res->spare = le; + return (le); + } + } + return (NULL); +} + +static struct links_entry * +next_entry(struct archive_entry_linkresolver *res, int mode) +{ + struct links_entry *le; + size_t bucket; + + /* Free a held entry. */ + if (res->spare != NULL) { + archive_entry_free(res->spare->canonical); + archive_entry_free(res->spare->entry); + free(res->spare); + res->spare = NULL; + } + + /* Look for next non-empty bucket in the links cache. */ + for (bucket = 0; bucket < res->number_buckets; bucket++) { + for (le = res->buckets[bucket]; le != NULL; le = le->next) { + if (le->entry != NULL && + (mode & NEXT_ENTRY_DEFERRED) == 0) + continue; + if (le->entry == NULL && + (mode & NEXT_ENTRY_PARTIAL) == 0) + continue; + /* Remove it from this hash bucket. */ + if (le->next != NULL) + le->next->previous = le->previous; + if (le->previous != NULL) + le->previous->next = le->next; + else + res->buckets[bucket] = le->next; + res->number_entries--; + /* Defer freeing this entry. */ + res->spare = le; + return (le); + } + } + return (NULL); +} + +static struct links_entry * +insert_entry(struct archive_entry_linkresolver *res, + struct archive_entry *entry) +{ + struct links_entry *le; + size_t hash, bucket; + + /* Add this entry to the links cache. */ + le = calloc(1, sizeof(struct links_entry)); + if (le == NULL) + return (NULL); + le->canonical = archive_entry_clone(entry); + + /* If the links cache is getting too full, enlarge the hash table. */ + if (res->number_entries > res->number_buckets * 2) + grow_hash(res); + + hash = archive_entry_dev(entry) ^ archive_entry_ino64(entry); + bucket = hash & (res->number_buckets - 1); + + /* If we could allocate the entry, record it. */ + if (res->buckets[bucket] != NULL) + res->buckets[bucket]->previous = le; + res->number_entries++; + le->next = res->buckets[bucket]; + le->previous = NULL; + res->buckets[bucket] = le; + le->hash = hash; + le->links = archive_entry_nlink(entry) - 1; + return (le); +} + +static void +grow_hash(struct archive_entry_linkresolver *res) +{ + struct links_entry *le, **new_buckets; + size_t new_size; + size_t i, bucket; + + /* Try to enlarge the bucket list. */ + new_size = res->number_buckets * 2; + if (new_size < res->number_buckets) + return; + new_buckets = calloc(new_size, sizeof(struct links_entry *)); + + if (new_buckets == NULL) + return; + + for (i = 0; i < res->number_buckets; i++) { + while (res->buckets[i] != NULL) { + /* Remove entry from old bucket. */ + le = res->buckets[i]; + res->buckets[i] = le->next; + + /* Add entry to new bucket. */ + bucket = le->hash & (new_size - 1); + + if (new_buckets[bucket] != NULL) + new_buckets[bucket]->previous = le; + le->next = new_buckets[bucket]; + le->previous = NULL; + new_buckets[bucket] = le; + } + } + free(res->buckets); + res->buckets = new_buckets; + res->number_buckets = new_size; +} + +struct archive_entry * +archive_entry_partial_links(struct archive_entry_linkresolver *res, + unsigned int *links) +{ + struct archive_entry *e; + struct links_entry *le; + + /* Free a held entry. */ + if (res->spare != NULL) { + archive_entry_free(res->spare->canonical); + archive_entry_free(res->spare->entry); + free(res->spare); + res->spare = NULL; + } + + le = next_entry(res, NEXT_ENTRY_PARTIAL); + if (le != NULL) { + e = le->canonical; + if (links != NULL) + *links = le->links; + le->canonical = NULL; + } else { + e = NULL; + if (links != NULL) + *links = 0; + } + return (e); +} diff --git a/libarchive/archive_entry_linkify.3 b/libarchive/archive_entry_linkify.3 new file mode 100644 index 0000000..6b1b9a5 --- /dev/null +++ b/libarchive/archive_entry_linkify.3 @@ -0,0 +1,224 @@ +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd February 20, 2010 +.Dt archive_entry_linkify 3 +.Os +.Sh NAME +.Nm archive_entry_linkresolver , +.Nm archive_entry_linkresolver_new , +.Nm archive_entry_linkresolver_set_strategy , +.Nm archive_entry_linkresolver_free , +.Nm archive_entry_linkify +.Nd hardlink resolver functions +.Sh LIBRARY +.Lb libarchive +.Sh SYNOPSIS +.In archive_entry.h +.Ft struct archive_entry_linkresolver * +.Fn archive_entry_linkresolver_new void +.Ft void +.Fo archive_entry_linkresolver_set_strategy +.Fa "struct archive_entry_linkresolver *resolver" +.Fa "int format" +.Fc +.Ft void +.Fo archive_entry_linkresolver_free +.Fa "struct archive_entry_linkresolver *resolver" +.Fc +.Ft void +.Fo archive_entry_linkify +.Fa "struct archive_entry_linkresolver *resolver" +.Fa "struct archive_entry **entry" +.Fa "struct archive_entry **sparse" +.Fc +.Sh DESCRIPTION +Programs that want to create archives have to deal with hardlinks. +Hardlinks are handled in different ways by the archive formats. +The basic strategies are: +.Bl -enum +.It +Ignore hardlinks and store the body for each reference (old cpio, zip). +.It +Store the body the first time an inode is seen (ustar, pax). +.It +Store the body the last time an inode is seen (new cpio). +.El +.Pp +The +.Nm +functions help by providing a unified interface and handling the complexity +behind the scene. +.Pp +The +.Nm +functions assume that +.Vt archive_entry +instances have valid nlinks, inode and device values. +The inode and device value is used to match entries. +The nlinks value is used to determined if all references have been found and +if the internal references can be recycled. +.Pp +The +.Fn archive_entry_linkresolver_new +function allocates a new link resolver. +The instance can be freed using +.Fn archive_entry_linkresolver_free . +All deferred entries are flushed and the internal storage is freed. +.Pp +The +.Fn archive_entry_linkresolver_set_strategy +function selects the optimal hardlink strategy for the given format. +The format code can be obtained from +.Xr archive_format 3 . +The function can be called more than once, but it is recommended to +flush all deferred entries first. +.Pp +The +.Fn archive_entry_linkify +function is the core of +.Nm . +The +.Fn entry +argument points to the +.Vt archive_entry +that should be written. +Depending on the strategy one of the following actions is taken: +.Bl -enum +.It +For the simple archive formats +.Va *entry +is left unmodified and +.Va *sparse +is set to +.Dv NULL . +.It +For tar like archive formats, +.Va *sparse +is set to +.Dv NULL . +If +.Va *entry +is +.Dv NULL , +no action is taken. +If the hardlink count of +.Va *entry +is larger than 1 and the file type is a regular file or symbolic link, +the internal list is searched for a matching inode. +If such an inode is found, the link count is decremented and the file size +of +.Va *entry +is set to 0 to notify that no body should be written. +If no such inode is found, a copy of the entry is added to the internal cache +with a link count reduced by one. +.It +For new cpio like archive formats a value for +.Va *entry +of +.Dv NULL +is used to flush deferred entries. +In that case +.Va *entry +is set to an arbitrary deferred entry and the entry itself is removed from the +internal list. +If the internal list is empty, +.Va *entry +is set to +.Dv NULL . +In either case, +.Va *sparse +is set to +.Dv NULL +and the function returns. +If the hardlink count of +.Va *entry +is one or the file type is a directory or device, +.Va *sparse +is set to +.Dv NULL +and no further action is taken. +Otherwise, the internal list is searched for a matching inode. +If such an inode is not found, the entry is added to the internal list, +both +.Va *entry +and +.Va *sparse +are set to +.Dv NULL +and the function returns. +If such an inode is found, the link count is decremented. +If it remains larger than one, the existing entry on the internal list +is swapped with +.Va *entry +after retaining the link count. +The existing entry is returned in +.Va *entry . +If the link count reached one, the new entry is also removed from the +internal list and returned in +.Va *sparse . +Otherwise +.Va *sparse +is set to +.Dv NULL . +.El +.Pp +The general usage is therefore: +.Bl -enum +.It +For each new archive entry, call +.Fn archive_entry_linkify . +.It +Keep in mind that the entries returned may have a size of 0 now. +.It +If +.Va *entry +is not +.Dv NULL , +archive it. +.It +If +.Va *sparse +is not +.Dv NULL , +archive it. +.It +After all entries have been written to disk, call +.Fn archive_entry_linkify +with +.Va *entry +set to +.Dv NULL +and archive the returned entry as long as it is not +.Dv NULL . +.El +.Sh RETURN VALUES +.Fn archive_entry_linkresolver_new +returns +.Dv NULL +on +.Xr malloc 3 +failures. +.Sh SEE ALSO +.Xr archive_entry 3 diff --git a/libarchive/archive_entry_locale.h b/libarchive/archive_entry_locale.h new file mode 100644 index 0000000..02e024a --- /dev/null +++ b/libarchive/archive_entry_locale.h @@ -0,0 +1,88 @@ +/*- + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD$ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_ENTRY_LOCALE_H_INCLUDED +#define ARCHIVE_ENTRY_LOCALE_H_INCLUDED + +struct archive_entry; +struct archive_string_conv; + +/* + * Utility functions to set and get entry attributes by translating + * character-set. These are designed for use in format readers and writers. + * + * The return code and interface of these are quite different from other + * functions for archive_entry defined in archive_entry.h. + * Common return code are: + * Return 0 if the string conversion succeeded. + * Return -1 if the string conversion failed. + */ + +#define archive_entry_gname_l _archive_entry_gname_l +int _archive_entry_gname_l(struct archive_entry *, + const char **, size_t *, struct archive_string_conv *); +#define archive_entry_hardlink_l _archive_entry_hardlink_l +int _archive_entry_hardlink_l(struct archive_entry *, + const char **, size_t *, struct archive_string_conv *); +#define archive_entry_pathname_l _archive_entry_pathname_l +int _archive_entry_pathname_l(struct archive_entry *, + const char **, size_t *, struct archive_string_conv *); +#define archive_entry_symlink_l _archive_entry_symlink_l +int _archive_entry_symlink_l(struct archive_entry *, + const char **, size_t *, struct archive_string_conv *); +#define archive_entry_uname_l _archive_entry_uname_l +int _archive_entry_uname_l(struct archive_entry *, + const char **, size_t *, struct archive_string_conv *); +#define archive_entry_acl_text_l _archive_entry_acl_text_l +int _archive_entry_acl_text_l(struct archive_entry *, int, + const char **, size_t *, struct archive_string_conv *); + + +#define archive_entry_copy_gname_l _archive_entry_copy_gname_l +int _archive_entry_copy_gname_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); +#define archive_entry_copy_hardlink_l _archive_entry_copy_hardlink_l +int _archive_entry_copy_hardlink_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); +#define archive_entry_copy_link_l _archive_entry_copy_link_l +int _archive_entry_copy_link_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); +#define archive_entry_copy_pathname_l _archive_entry_copy_pathname_l +int _archive_entry_copy_pathname_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); +#define archive_entry_copy_symlink_l _archive_entry_copy_symlink_l +int _archive_entry_copy_symlink_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); +#define archive_entry_copy_uname_l _archive_entry_copy_uname_l +int _archive_entry_copy_uname_l(struct archive_entry *, + const char *, size_t, struct archive_string_conv *); + +#endif /* ARCHIVE_ENTRY_LOCALE_H_INCLUDED */ diff --git a/libarchive/archive_entry_paths.3 b/libarchive/archive_entry_paths.3 new file mode 100644 index 0000000..05243c0 --- /dev/null +++ b/libarchive/archive_entry_paths.3 @@ -0,0 +1,151 @@ +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd February 22, 2010 +.Dt archive_entry_paths 3 +.Os +.Sh NAME +.Nm archive_entry_hardlink , +.Nm archive_entry_hardlink_w , +.Nm archive_entry_set_hardlink , +.Nm archive_entry_copy_hardlink , +.Nm archive_entry_copy_hardlink_w , +.Nm archve_entry_update_hardlink_utf8 , +.Nm archive_entry_set_link , +.Nm archive_entry_copy_link , +.Nm archive_entry_copy_link_w , +.Nm archve_entry_update_link_utf8 , +.Nm archive_entry_pathname , +.Nm archive_entry_pathname_w , +.Nm archive_entry_set_pathname , +.Nm archive_entry_copy_pathname , +.Nm archive_entry_copy_pathname_w , +.Nm archve_entry_update_pathname_utf8 , +.Nm archive_entry_sourcepath , +.Nm archive_entry_copy_sourcepath , +.Nm archive_entry_symlink, +.Nm archive_entry_symlink_w, +.Nm archive_entry_set_symlink , +.Nm archive_entry_copy_symlink , +.Nm archive_entry_copy_symlink_w , +.Nm archve_entry_update_symlink_utf8 +.Nd functions for manipulating path names in archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft const char * +.Fn archive_entry_hardlink "struct archive_entry *a" +.Ft const wchar_t * +.Fn archive_entry_hardlink_w "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_hardlink "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_hardlink "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_hardlink_w "struct archive_entry *a "const wchar_t *path" +.Ft int +.Fn archive_entry_update_hardlink_utf8 "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_set_link "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_link "struct archive_entry *a" " const char *path" +.Ft void +.Fn archive_entry_copy_link_w "struct archive_entry *a" " const wchar_t *path" +.Ft int +.Fn archive_entry_update_link_utf8 "struct archive_entry *a" " const char *path" +.Ft const char * +.Fn archive_entry_pathname "struct archive_entry *a" +.Ft const wchar_t * +.Fn archive_entry_pathname_w "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_pathname "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_pathname "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_pathname_w "struct archive_entry *a" "const wchar_t *path" +.Ft int +.Fn archive_entry_update_pathname_utf8 "struct archive_entry *a" "const char *path" +.Ft const char * +.Fn archive_entry_sourcepath "struct archive_entry *a" +.Ft void +.Fn archive_entry_copy_sourcepath "struct archive_entry *a" "const char *path" +.Ft const char * +.Fn archive_entry_symlink "struct archive_entry *a" +.Ft const wchar_t * +.Fn archive_entry_symlink_w "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_symlink "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_symlink "struct archive_entry *a" "const char *path" +.Ft void +.Fn archive_entry_copy_symlink_w "struct archive_entry *a" "const wchar_t *path" +.Ft int +.Fn archive_entry_update_symlink_utf8 "struct archive_entry *a" "const char *path" +.Sh DESCRIPTION +Path names supported by +.Xr archive_entry 3 : +.Bl -tag -width "sourcepath" -compact +.It hardlink +Destination of the hardlink. +.It link +Update only. +For a symlink, update the destination. +Otherwise, make the entry a hardlink and alter +the destination for that. +.It pathname +Path in the archive +.It sourcepath +Path on the disk for use by +.Xr archive_read_disk 3 . +.It symlink +Destination of the symbolic link. +.El +.Pp +Path names can be provided in one of three different ways: +.Bl -tag -width "wchar_t *" +.It char * +Multibyte strings in the current locale. +.It wchar_t * +Wide character strings in the current locale. +The accessor functions are named +.Fn XXX_w . +.It UTF-8 +Unicode strings encoded as UTF-8. +This are convience functions to update both the multibyte and wide +character strings at the same time. +.El +.Pp +The sourcepath is a pure filesystem concept and never stored in an +archive directly. +.Pp +For that reason, it is only available as multibyte string. +The link path is a convience function for conditionally setting +hardlink or symlink destination. +It doesn't have a corresponding get accessor function. +.Pp +.Fn archive_entry_set_XXX +is an alias for +.Fn archive_entry_copy_XXX . +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 diff --git a/libarchive/archive_entry_perms.3 b/libarchive/archive_entry_perms.3 new file mode 100644 index 0000000..452a19f --- /dev/null +++ b/libarchive/archive_entry_perms.3 @@ -0,0 +1,207 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd February 22, 2010 +.Dt archive_entry_perms 3 +.Os +.Sh NAME +.Nm archive_entry_gid , +.Nm archive_entry_set_gid , +.Nm archive_entry_uid , +.Nm archive_entry_set_uid , +.Nm archive_entry_perm , +.Nm archive_entry_set_perm , +.Nm archive_entry_strmode , +.Nm archive_entry_uname +.Nm archive_entry_uname_w +.Nm archive_entry_set_uname , +.Nm archive_entry_copy_uname , +.Nm archive_entry_copy_uname_w , +.Nm archive_entry_update_uname_utf8 , +.Nm archive_entry_gname , +.Nm archive_entry_gname_w , +.Nm archive_entry_set_gname , +.Nm archive_entry_copy_gname , +.Nm archive_entry_copy_gname_w , +.Nm archive_entry_update_gname_utf8 , +.Nm archive_entry_fflags , +.Nm archive_entry_fflags_text , +.Nm archive_entry_set_fflags , +.Nm archive_entry_copy_fflags_text , +.Nm archive_entry_copy_fflags_text_w +.Nd functions for manipulating ownership and permissions in archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft gid_t +.Fn archive_entry_gid "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_gid "struct archive_entry *a" "gid_t gid" +.Ft uid_t +.Fn archive_entry_uid "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_uid "struct archive_entry *a" "uid_t uid" +.Ft mode_t +.Fn archive_entry_perm "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_perm "struct archive_entry *a" "mode_t mode" +.Ft const char * +.Fn archive_entry_strmode "struct archive_entry *a" +.Ft const char * +.Fn archive_entry_gname "struct archive_entry *a" +.Ft const wchar_t * +.Fn archive_entry_gname_w "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_gname "struct archive_entry *a" "const char *a" +.Ft void +.Fn archive_entry_copy_gname "struct archive_entry *a" "const char *name" +.Ft void +.Fn archive_entry_copy_gname_w "struct archive_entry *a" "const wchar_t *name" +.Ft int +.Fn archive_entry_update_gname_utf8 "struct archive_entry *a" "const char *name" +.Ft const char * +.Fn archive_entry_uname "struct archive_entry *a" +.Ft const wchar_t * +.Fn archive_entry_uname_w "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_uname "struct archive_entry *a" "const char *name" +.Ft void +.Fn archive_entry_copy_uname "struct archive_entry *a" "const char *name" +.Ft void +.Fn archive_entry_copy_uname_w "struct archive_entry *a" "const wchar_t *name" +.Ft int +.Fn archive_entry_update_uname_utf8 "struct archive_entry *a" "const char *name" +.Ft void +.Fo archive_entry_fflags +.Fa "struct archive_entry *a" +.Fa "unsigned long *set_bits" +.Fa "unsigned long *clear_bits" +.Fc +.Ft const char * +.Fn archive_entry_fflags_text "struct archive_entry *a" +.Ft void +.Fo archive_entry_set_fflags +.Fa "struct archive_entry *a" +.Fa "unsigned long set_bits" +.Fa "unsigned long clear_bits" +.Fc +.Ft const char * +.Fn archive_entry_copy_fflags_text "struct archive_entry *a" "const char *text" +.Ft const wchar_t * +.Fn archive_entry_copy_fflags_text_w "struct archive_entry *a" "const wchar_t *text" +.Sh DESCRIPTION +.Ss User id, group id and mode +The functions +.Fn archive_entry_uid , +.Fn archive_entry_gid , +and +.Fn archive_entry_perm +can be used to extract the user id, group id and permission from the given entry. +The corresponding functions +.Fn archive_entry_set_uid , +.Fn archive_entry_set_gid , +and +.Fn archive_entry_set_perm +store the given user id, group id and permission in the entry. +The permission is also set as side effect of calling +.Fn archive_entry_set_mode . +.Pp +.Fn archive_entry_strmode +returns a string representation of the permission as used by the long mode of +.Xr ls 1 . +.Ss User and group name +User and group names can be provided in one of three different ways: +.Bl -tag -width "wchar_t *" +.It char * +Multibyte strings in the current locale. +.It wchar_t * +Wide character strings in the current locale. +The accessor functions are named +.Fn XXX_w . +.It UTF-8 +Unicode strings encoded as UTF-8. +This are convience functions to update both the multibyte and wide +character strings at the same time. +.El +.Pp +.Fn archive_entry_set_XXX +is an alias for +.Fn archive_entry_copy_XXX . +.Ss File Flags +File flags are transparently converted between a bitmap +representation and a textual format. +For example, if you set the bitmap and ask for text, the library +will build a canonical text format. +However, if you set a text format and request a text format, +you will get back the same text, even if it is ill-formed. +If you need to canonicalize a textual flags string, you should first set the +text form, then request the bitmap form, then use that to set the bitmap form. +Setting the bitmap format will clear the internal text representation +and force it to be reconstructed when you next request the text form. +.Pp +The bitmap format consists of two integers, one containing bits +that should be set, the other specifying bits that should be +cleared. +Bits not mentioned in either bitmap will be ignored. +Usually, the bitmap of bits to be cleared will be set to zero. +In unusual circumstances, you can force a fully-specified set +of file flags by setting the bitmap of flags to clear to the complement +of the bitmap of flags to set. +(This differs from +.Xr fflagstostr 3 , +which only includes names for set bits.) +Converting a bitmap to a textual string is a platform-specific +operation; bits that are not meaningful on the current platform +will be ignored. +.Pp +The canonical text format is a comma-separated list of flag names. +The +.Fn archive_entry_copy_fflags_text +and +.Fn archive_entry_copy_fflags_text_w +functions parse the provided text and sets the internal bitmap values. +This is a platform-specific operation; names that are not meaningful +on the current platform will be ignored. +The function returns a pointer to the start of the first name that was not +recognized, or NULL if every name was recognized. +Note that every name \(em including names that follow an unrecognized +name \(em will be evaluated, and the bitmaps will be set to reflect +every name that is recognized. +(In particular, this differs from +.Xr strtofflags 3 , +which stops parsing at the first unrecognized name.) +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 , +.Xr archive_entry_acl 3 , +.Xr archive_read_disk 3 , +.Xr archive_write_disk 3 +.Sh BUGS +The platform types +.Vt uid_t +and +.Vt gid_t +are often 16 or 32 bit wide. +In this case it is possible that the ids can not be correctly restored +from archives and get truncated. diff --git a/libarchive/archive_entry_private.h b/libarchive/archive_entry_private.h new file mode 100644 index 0000000..e3547c3 --- /dev/null +++ b/libarchive/archive_entry_private.h @@ -0,0 +1,176 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_entry_private.h 201096 2009-12-28 02:41:27Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_ENTRY_PRIVATE_H_INCLUDED +#define ARCHIVE_ENTRY_PRIVATE_H_INCLUDED + +#include "archive_acl_private.h" +#include "archive_string.h" + +struct ae_xattr { + struct ae_xattr *next; + + char *name; + void *value; + size_t size; +}; + +struct ae_sparse { + struct ae_sparse *next; + + int64_t offset; + int64_t length; +}; + +/* + * Description of an archive entry. + * + * Basically, this is a "struct stat" with a few text fields added in. + * + * TODO: Add "comment", "charset", and possibly other entries + * that are supported by "pax interchange" format. However, GNU, ustar, + * cpio, and other variants don't support these features, so they're not an + * excruciatingly high priority right now. + * + * TODO: "pax interchange" format allows essentially arbitrary + * key/value attributes to be attached to any entry. Supporting + * such extensions may make this library useful for special + * applications (e.g., a package manager could attach special + * package-management attributes to each entry). There are tricky + * API issues involved, so this is not going to happen until + * there's a real demand for it. + * + * TODO: Design a good API for handling sparse files. + */ +struct archive_entry { + struct archive *archive; + + /* + * Note that ae_stat.st_mode & AE_IFMT can be 0! + * + * This occurs when the actual file type of the object is not + * in the archive. For example, 'tar' archives store + * hardlinks without marking the type of the underlying + * object. + */ + + /* + * We have a "struct aest" for holding file metadata rather than just + * a "struct stat" because on some platforms the "struct stat" has + * fields which are too narrow to hold the range of possible values; + * we don't want to lose information if we read an archive and write + * out another (e.g., in "tar -cf new.tar @old.tar"). + * + * The "stat" pointer points to some form of platform-specific struct + * stat; it is declared as a void * rather than a struct stat * as + * some platforms have multiple varieties of stat structures. + */ + void *stat; + int stat_valid; /* Set to 0 whenever a field in aest changes. */ + + struct aest { + int64_t aest_atime; + uint32_t aest_atime_nsec; + int64_t aest_ctime; + uint32_t aest_ctime_nsec; + int64_t aest_mtime; + uint32_t aest_mtime_nsec; + int64_t aest_birthtime; + uint32_t aest_birthtime_nsec; + int64_t aest_gid; + int64_t aest_ino; + uint32_t aest_nlink; + uint64_t aest_size; + int64_t aest_uid; + /* + * Because converting between device codes and + * major/minor values is platform-specific and + * inherently a bit risky, we only do that conversion + * lazily. That way, we will do a better job of + * preserving information in those cases where no + * conversion is actually required. + */ + int aest_dev_is_broken_down; + dev_t aest_dev; + dev_t aest_devmajor; + dev_t aest_devminor; + int aest_rdev_is_broken_down; + dev_t aest_rdev; + dev_t aest_rdevmajor; + dev_t aest_rdevminor; + } ae_stat; + + int ae_set; /* bitmap of fields that are currently set */ +#define AE_SET_HARDLINK 1 +#define AE_SET_SYMLINK 2 +#define AE_SET_ATIME 4 +#define AE_SET_CTIME 8 +#define AE_SET_MTIME 16 +#define AE_SET_BIRTHTIME 32 +#define AE_SET_SIZE 64 +#define AE_SET_INO 128 +#define AE_SET_DEV 256 + + /* + * Use aes here so that we get transparent mbs<->wcs conversions. + */ + struct archive_mstring ae_fflags_text; /* Text fflags per fflagstostr(3) */ + unsigned long ae_fflags_set; /* Bitmap fflags */ + unsigned long ae_fflags_clear; + struct archive_mstring ae_gname; /* Name of owning group */ + struct archive_mstring ae_hardlink; /* Name of target for hardlink */ + struct archive_mstring ae_pathname; /* Name of entry */ + struct archive_mstring ae_symlink; /* symlink contents */ + struct archive_mstring ae_uname; /* Name of owner */ + + /* Not used within libarchive; useful for some clients. */ + struct archive_mstring ae_sourcepath; /* Path this entry is sourced from. */ + + void *mac_metadata; + size_t mac_metadata_size; + + /* ACL support. */ + struct archive_acl acl; + + /* extattr support. */ + struct ae_xattr *xattr_head; + struct ae_xattr *xattr_p; + + /* sparse support. */ + struct ae_sparse *sparse_head; + struct ae_sparse *sparse_tail; + struct ae_sparse *sparse_p; + + /* Miscellaneous. */ + char strmode[12]; +}; + +#endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */ diff --git a/libarchive/archive_entry_sparse.c b/libarchive/archive_entry_sparse.c new file mode 100644 index 0000000..10c5447 --- /dev/null +++ b/libarchive/archive_entry_sparse.c @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_entry_private.h" + +/* + * sparse handling + */ + +void +archive_entry_sparse_clear(struct archive_entry *entry) +{ + struct ae_sparse *sp; + + while (entry->sparse_head != NULL) { + sp = entry->sparse_head->next; + free(entry->sparse_head); + entry->sparse_head = sp; + } + entry->sparse_tail = NULL; +} + +void +archive_entry_sparse_add_entry(struct archive_entry *entry, + int64_t offset, int64_t length) +{ + struct ae_sparse *sp; + + if (offset < 0 || length < 0) + /* Invalid value */ + return; + if (offset + length < 0 || + offset + length > archive_entry_size(entry)) + /* A value of "length" parameter is too large. */ + return; + if ((sp = entry->sparse_tail) != NULL) { + if (sp->offset + sp->length > offset) + /* Invalid value. */ + return; + if (sp->offset + sp->length == offset) { + if (sp->offset + sp->length + length < 0) + /* A value of "length" parameter is + * too large. */ + return; + /* Expand existing sparse block size. */ + sp->length += length; + return; + } + } + + if ((sp = (struct ae_sparse *)malloc(sizeof(*sp))) == NULL) + /* XXX Error XXX */ + return; + + sp->offset = offset; + sp->length = length; + sp->next = NULL; + + if (entry->sparse_head == NULL) + entry->sparse_head = entry->sparse_tail = sp; + else { + /* Add a new sparse block to the tail of list. */ + if (entry->sparse_tail != NULL) + entry->sparse_tail->next = sp; + entry->sparse_tail = sp; + } +} + + +/* + * returns number of the sparse entries + */ +int +archive_entry_sparse_count(struct archive_entry *entry) +{ + struct ae_sparse *sp; + int count = 0; + + for (sp = entry->sparse_head; sp != NULL; sp = sp->next) + count++; + + /* + * Sanity check if this entry is exactly sparse. + * If amount of sparse blocks is just one and it indicates the whole + * file data, we should remove it and return zero. + */ + if (count == 1) { + sp = entry->sparse_head; + if (sp->offset == 0 && + sp->length >= archive_entry_size(entry)) { + count = 0; + archive_entry_sparse_clear(entry); + } + } + + return (count); +} + +int +archive_entry_sparse_reset(struct archive_entry * entry) +{ + entry->sparse_p = entry->sparse_head; + + return archive_entry_sparse_count(entry); +} + +int +archive_entry_sparse_next(struct archive_entry * entry, + int64_t *offset, int64_t *length) +{ + if (entry->sparse_p) { + *offset = entry->sparse_p->offset; + *length = entry->sparse_p->length; + + entry->sparse_p = entry->sparse_p->next; + + return (ARCHIVE_OK); + } else { + *offset = 0; + *length = 0; + return (ARCHIVE_WARN); + } +} + +/* + * end of sparse handling + */ diff --git a/libarchive/archive_entry_stat.3 b/libarchive/archive_entry_stat.3 new file mode 100644 index 0000000..a988ecf --- /dev/null +++ b/libarchive/archive_entry_stat.3 @@ -0,0 +1,272 @@ +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd May 12, 2008 +.Dt archive_entry 3 +.Os +.Sh NAME +.Nm archive_entry_stat , +.Nm archive_entry_copy_stat , +.Nm archive_entry_filetype , +.Nm archive_entry_set_filetype , +.Nm archive_entry_mode , +.Nm archive_entry_set_mode , +.Nm archive_entry_size , +.Nm archive_entry_size_is_set , +.Nm archive_entry_set_size , +.Nm archive_entry_unset_size , +.Nm archive_entry_dev , +.Nm archive_entry_set_dev , +.Nm archive_entry_dev_is_set , +.Nm archive_entry_devmajor , +.Nm archive_entry_set_devmajor , +.Nm archive_entry_devminor , +.Nm archive_entry_set_devminor , +.Nm archive_entry_ino , +.Nm archive_entry_set_ino , +.Nm archive_entry_ino_is_set , +.Nm archive_entry_ino64 , +.Nm archive_entry_set_ino64 , +.Nm archive_entry_nlink , +.Nm archive_entry_rdev , +.Nm archive_entry_set_rdev , +.Nm archive_entry_rdevmajor , +.Nm archive_entry_set_rdevmajor , +.Nm archive_entry_rdevminor , +.Nm archive_entry_set_rdevminor , +.Nd accessor functions for manipulating archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft const struct stat * +.Fn archive_entry_stat "struct archive_entry *a" +.Ft void +.Fn archive_entry_copy_stat "struct archive_entry *a" "const struct stat *sb" +.Ft mode_t +.Fn archive_entry_filetype "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_filetype "struct archive_entry *a" "unsigned int type" +.Ft mode_t +.Fn archive_entry_mode "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_mode "struct archive_entry *a" "mode_t mode" +.Ft int64_t +.Fn archive_entry_size "struct archive_entry *a" +.Ft int +.Fn archive_entry_size_is_set "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_size "struct archive_entry *a" "int64_t size" +.Ft void +.Fn archive_entry_unset_size "struct archive_entry *a" +.Ft dev_t +.Fn archive_entry_dev "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_dev "struct archive_entry *a" "dev_t dev" +.Ft int +.Fn archive_entry_dev_is_set "struct archive_entry *a" +.Ft dev_t +.Fn archive_entry_devmajor "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_devmajor "struct archive_entry *a" "dev_t major" +.Ft dev_t +.Fn archive_entry_devminor "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_devminor "struct archive_entry *a" "dev_t minor" +.Ft ino_t +.Fn archive_entry_ino "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_ino "struct archive_entry *a" "unsigned long ino" +.Ft int +.Fn archive_entry_ino_is_set "struct archive_entry *a" +.Ft int64_t +.Fn archive_entry_ino64 "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_ino64 "struct archive_entry *a" "int64_t ino" +.Ft unsigned int +.Fn archive_entry_nlink "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_nlink "struct archive_entry *a" "unsigned int count" +.Ft dev_t +.Fn archive_entry_rdev "struct archive_entry *a" +.Ft dev_t +.Fn archive_entry_rdevmajor "struct archive_entry *a" +.Ft dev_t +.Fn archive_entry_rdevminor "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_rdev "struct archive_entry *a" "dev_t dev" +.Ft void +.Fn archive_entry_set_rdevmajor "struct archive_entry *a" "dev_t major" +.Ft void +.Fn archive_entry_set_rdevminor "struct archive_entry *a" "dev_t minor" +.Sh DESCRIPTION +.Ss Copying to and from Vt struct stat +The function +.Fn archive_entry_stat +converts the various fields stored in the archive entry to the format +used by +.Xr stat 2 . +The return value remains valid until either +.Fn archive_entry_clear +or +.Fn archive_entry_free +is called. +It is not affected by calls to the set accessor functions. +It currently sets the following values in +.Vt struct stat : +.Vt st_atime , +.Vt st_ctime , +.Vt st_dev , +.Vt st_gid , +.Vt st_ino , +.Vt st_mode , +.Vt st_mtime , +.Vt st_nlink , +.Vt st_rdev , +.Vt st_size , +.Vt st_uid . +In addition, +.Vt st_birthtime +and high-precision information for time-related fields +will be included on platforms that support it. +.Pp +The function +.Fn archive_entry_copy_stat +copies fields from the platform's +.Vt struct stat . +Fields not provided by +.Vt struct stat +are unchanged. +.Ss General accessor functions +The functions +.Fn archive_entry_filetype +and +.Fn archive_entry_set_filetype +get respectively set the filetype. +The file type is one of the following constants: +.Bl -tag -width "AE_IFSOCK" -compact -offset indent +.It AE_IFREG +Regular file +.It AE_IFLNK +Symbolic link +.It AE_IFSOCK +Socket +.It AE_IFCHR +Character device +.It AE_IFBLK +Block device +.It AE_IFDIR +Directory +.It AE_IFIFO +Named pipe (fifo) +.El +Not all file types are supported by all platforms. +The constants used by +.Xr stat 2 +may have different numeric values from the +corresponding constants above. +.Pp +The functions +.Fn archive_entry_mode +and +.Fn archive_entry_set_mode +get/set a combination of file type and permissions and provide the +equivalent of +.Va st_mode . +Use of +.Fn archive_entry_filetype +and +.Fn archive_entry_perm +for getting and +.Fn archive_entry_set_filetype +and +.Fn archive_entry_set_perm +for setting is recommended. +.Pp +The function +.Fn archive_entry_size +returns the file size, if it has been set, and 0 otherwise. +.Fn archive_entry_size +can be used to query that status. +.Fn archive_entry_set_size +and +.Fn archive_entry_unset_size +set and unset the size, respectively. +.Pp +The number of references (hardlinks) can be obtained by calling +.Fn archive_entry_nlinks +and set with +.Fn archive_entry_set_nlinks . +.Ss Identifying unique files +The functions +.Fn archive_entry_dev +and +.Fn archive_entry_ino64 +are used by +.Xr archive_entry_linkify 3 +to find hardlinks. +The pair of device and inode is suppossed to identify hardlinked files. +.Pp +The device major and minor number can be obtained independently using +.Fn archive_entry_devmajor +and +.Fn archive_entry_devminor . +The device can be set either via +.Fn archive_entry_set_dev +or by the combination of major and minor number using +.Fn archive_entry_set_devmajor +and +.Fn archive_entry_set_devminor . +.Pp +The inode number can be obtained using +.Fn archive_entry_ino . +This is a legacy interface that uses the platform +.Vt ino_t , +which may be very small. +To set the inode number, +.Fn archive_entry_set_ino64 +is the preferred interface. +.Ss Accessor functions for block and character devices +Block and character devices are characterised either using a device number +or a pair of major and minor number. +The combined device number can be obtained with +.Fn archive_device_rdev +and set with +.Fn archive_device_set_rdev . +The major and minor numbers are accessed by +.Fn archive_device_rdevmajor , +.Fn archive_device_rdevminor +.Fn archive_device_set_rdevmajor +and +.Fn archive_device_set_rdevminor . +.Pp +The process of splitting the combined device number into major and +minor number and the reverse process of combing them differs between +platforms. +Some archive formats use the combined form, while other formats use +the split form. +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry_acl 3 , +.Xr archive_entry_perms 3 , +.Xr archive_entry_time 3 , +.Xr stat 2 diff --git a/libarchive/archive_entry_stat.c b/libarchive/archive_entry_stat.c new file mode 100644 index 0000000..d5ed1cb --- /dev/null +++ b/libarchive/archive_entry_stat.c @@ -0,0 +1,118 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_stat.c 201100 2009-12-28 03:05:31Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "archive_entry.h" +#include "archive_entry_private.h" + +const struct stat * +archive_entry_stat(struct archive_entry *entry) +{ + struct stat *st; + if (entry->stat == NULL) { + entry->stat = calloc(1, sizeof(*st)); + if (entry->stat == NULL) + return (NULL); + entry->stat_valid = 0; + } + + /* + * If none of the underlying fields have been changed, we + * don't need to regenerate. In theory, we could use a bitmap + * here to flag only those items that have changed, but the + * extra complexity probably isn't worth it. It will be very + * rare for anyone to change just one field then request a new + * stat structure. + */ + if (entry->stat_valid) + return (entry->stat); + + st = entry->stat; + /* + * Use the public interfaces to extract items, so that + * the appropriate conversions get invoked. + */ + st->st_atime = archive_entry_atime(entry); +#if HAVE_STRUCT_STAT_ST_BIRTHTIME + st->st_birthtime = archive_entry_birthtime(entry); +#endif + st->st_ctime = archive_entry_ctime(entry); + st->st_mtime = archive_entry_mtime(entry); + st->st_dev = archive_entry_dev(entry); + st->st_gid = archive_entry_gid(entry); + st->st_uid = archive_entry_uid(entry); + st->st_ino = archive_entry_ino64(entry); + st->st_nlink = archive_entry_nlink(entry); + st->st_rdev = archive_entry_rdev(entry); + st->st_size = archive_entry_size(entry); + st->st_mode = archive_entry_mode(entry); + + /* + * On systems that support high-res timestamps, copy that + * information into struct stat. + */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + st->st_atimespec.tv_nsec = archive_entry_atime_nsec(entry); + st->st_ctimespec.tv_nsec = archive_entry_ctime_nsec(entry); + st->st_mtimespec.tv_nsec = archive_entry_mtime_nsec(entry); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + st->st_atim.tv_nsec = archive_entry_atime_nsec(entry); + st->st_ctim.tv_nsec = archive_entry_ctime_nsec(entry); + st->st_mtim.tv_nsec = archive_entry_mtime_nsec(entry); +#elif HAVE_STRUCT_STAT_ST_MTIME_N + st->st_atime_n = archive_entry_atime_nsec(entry); + st->st_ctime_n = archive_entry_ctime_nsec(entry); + st->st_mtime_n = archive_entry_mtime_nsec(entry); +#elif HAVE_STRUCT_STAT_ST_UMTIME + st->st_uatime = archive_entry_atime_nsec(entry) / 1000; + st->st_uctime = archive_entry_ctime_nsec(entry) / 1000; + st->st_umtime = archive_entry_mtime_nsec(entry) / 1000; +#elif HAVE_STRUCT_STAT_ST_MTIME_USEC + st->st_atime_usec = archive_entry_atime_nsec(entry) / 1000; + st->st_ctime_usec = archive_entry_ctime_nsec(entry) / 1000; + st->st_mtime_usec = archive_entry_mtime_nsec(entry) / 1000; +#endif +#if HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC + st->st_birthtimespec.tv_nsec = archive_entry_birthtime_nsec(entry); +#endif + + /* + * TODO: On Linux, store 32 or 64 here depending on whether + * the cached stat structure is a stat32 or a stat64. This + * will allow us to support both variants interchangably. + */ + entry->stat_valid = 1; + + return (st); +} diff --git a/libarchive/archive_entry_strmode.c b/libarchive/archive_entry_strmode.c new file mode 100644 index 0000000..16cb3f7 --- /dev/null +++ b/libarchive/archive_entry_strmode.c @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_entry_strmode.c,v 1.4 2008/06/15 05:14:01 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive_entry.h" +#include "archive_entry_private.h" + +const char * +archive_entry_strmode(struct archive_entry *entry) +{ + static const mode_t permbits[] = + { 0400, 0200, 0100, 0040, 0020, 0010, 0004, 0002, 0001 }; + char *bp = entry->strmode; + mode_t mode; + int i; + + /* Fill in a default string, then selectively override. */ + strcpy(bp, "?rwxrwxrwx "); + + mode = archive_entry_mode(entry); + switch (archive_entry_filetype(entry)) { + case AE_IFREG: bp[0] = '-'; break; + case AE_IFBLK: bp[0] = 'b'; break; + case AE_IFCHR: bp[0] = 'c'; break; + case AE_IFDIR: bp[0] = 'd'; break; + case AE_IFLNK: bp[0] = 'l'; break; + case AE_IFSOCK: bp[0] = 's'; break; + case AE_IFIFO: bp[0] = 'p'; break; + default: + if (archive_entry_hardlink(entry) != NULL) { + bp[0] = 'h'; + break; + } + } + + for (i = 0; i < 9; i++) + if (!(mode & permbits[i])) + bp[i+1] = '-'; + + if (mode & S_ISUID) { + if (mode & 0100) bp[3] = 's'; + else bp[3] = 'S'; + } + if (mode & S_ISGID) { + if (mode & 0010) bp[6] = 's'; + else bp[6] = 'S'; + } + if (mode & S_ISVTX) { + if (mode & 0001) bp[9] = 't'; + else bp[9] = 'T'; + } + if (archive_entry_acl_count(entry, ARCHIVE_ENTRY_ACL_TYPE_ACCESS)) + bp[10] = '+'; + + return (bp); +} diff --git a/libarchive/archive_entry_time.3 b/libarchive/archive_entry_time.3 new file mode 100644 index 0000000..7787ca5 --- /dev/null +++ b/libarchive/archive_entry_time.3 @@ -0,0 +1,127 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" Copyright (c) 2010 Joerg Sonnenberger +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_entry.3,v 1.18 2008/05/26 17:00:22 kientzle Exp $ +.\" +.Dd February 21, 2010 +.Dt archive_entry_time 3 +.Os +.Sh NAME +.Nm archive_entry_atime , +.Nm archive_entry_atime_nsec , +.Nm archive_entry_atime_is_set , +.Nm archive_entry_set_atime , +.Nm archive_entry_unset_atime , +.Nm archive_entry_birthtime , +.Nm archive_entry_birthtime_nsec , +.Nm archive_entry_birthtime_is_set , +.Nm archive_entry_set_birthtime , +.Nm archive_entry_unset_birthtime , +.Nm archive_entry_ctime , +.Nm archive_entry_ctime_nsec , +.Nm archive_entry_ctime_is_set , +.Nm archive_entry_set_ctime , +.Nm archive_entry_unset_ctime , +.Nm archive_entry_mtime , +.Nm archive_entry_mtime_nsec , +.Nm archive_entry_mtime_is_set , +.Nm archive_entry_set_mtime , +.Nm archive_entry_unset_mtime , +.Nd functions for manipulating times in archive entry descriptions +.Sh SYNOPSIS +.In archive_entry.h +.Ft time_t +.Fn archive_entry_atime "struct archive_entry *a" +.Ft long +.Fn archive_entry_atime_nsec "struct archive_entry *a" +.Ft int +.Fn archive_entry_atime_is_set "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_atime "struct archive_entry *a" "time_t sec" "long nanosec" +.Ft void +.Fn archive_entry_unset_atime "struct archive_entry *a" +.Ft time_t +.Fn archive_entry_birthtime "struct archive_entry *a" +.Ft long +.Fn archive_entry_birthtime_nsec "struct archive_entry *a" +.Ft int +.Fn archive_entry_birthtime_is_set "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_birthtime "struct archive_entry *a" "time_t sec" "long nanosec" +.Ft void +.Fn archive_entry_unset_birthtime "struct archive_entry *a" +.Ft time_t +.Fn archive_entry_ctime "struct archive_entry *a" +.Ft long +.Fn archive_entry_ctime_nsec "struct archive_entry *a" +.Ft int +.Fn archive_entry_ctime_is_set "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_ctime "struct archive_entry *a" "time_t sec" "long nanosec" +.Ft void +.Fn archive_entry_unset_ctime "struct archive_entry *a" +.Ft time_t +.Fn archive_entry_mtime "struct archive_entry *a" +.Ft long +.Fn archive_entry_mtime_nsec "struct archive_entry *a" +.Ft int +.Fn archive_entry_mtime_is_set "struct archive_entry *a" +.Ft void +.Fn archive_entry_set_mtime "struct archive_entry *a" "time_t sec" "long nanosec" +.Ft void +.Fn archive_entry_unset_mtime "struct archive_entry *a" +.Sh DESCRIPTION +These functions create and manipulate the time fields in an +.Vt archive_entry . +Supported time fields are atime (access time), birthtime (creation time), +ctime (last time an inode property was changed) and mtime (modification time). +.Pp +.Xr libarchive 3 +provides a high-resolution interface. +The timestamps are truncated automatically depending on the archive format +(for archiving) or the filesystem capabilities (for restoring). +.Pp +All timestamp fields are optional. +The +.Fn XXX_unset +functions can be used to mark the corresponding field as missing. +The current state can be queried using +.Fn XXX_is_set . +Unset time fields have a second and nanosecond field of 0. +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.\" .Sh BUGS diff --git a/libarchive/archive_entry_xattr.c b/libarchive/archive_entry_xattr.c new file mode 100644 index 0000000..a3efe7c --- /dev/null +++ b/libarchive/archive_entry_xattr.c @@ -0,0 +1,158 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_entry_xattr.c 201096 2009-12-28 02:41:27Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_LINUX_FS_H +#include /* for Linux file flags */ +#endif +/* + * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h. + * As the include guards don't agree, the order of include is important. + */ +#ifdef HAVE_LINUX_EXT2_FS_H +#include /* for Linux file flags */ +#endif +#if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__) +#include /* for Linux file flags */ +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_entry_private.h" + +/* + * extended attribute handling + */ + +void +archive_entry_xattr_clear(struct archive_entry *entry) +{ + struct ae_xattr *xp; + + while (entry->xattr_head != NULL) { + xp = entry->xattr_head->next; + free(entry->xattr_head->name); + free(entry->xattr_head->value); + free(entry->xattr_head); + entry->xattr_head = xp; + } + + entry->xattr_head = NULL; +} + +void +archive_entry_xattr_add_entry(struct archive_entry *entry, + const char *name, const void *value, size_t size) +{ + struct ae_xattr *xp; + + for (xp = entry->xattr_head; xp != NULL; xp = xp->next) + ; + + if ((xp = (struct ae_xattr *)malloc(sizeof(struct ae_xattr))) == NULL) + /* XXX Error XXX */ + return; + + xp->name = strdup(name); + if ((xp->value = malloc(size)) != NULL) { + memcpy(xp->value, value, size); + xp->size = size; + } else + xp->size = 0; + + xp->next = entry->xattr_head; + entry->xattr_head = xp; +} + + +/* + * returns number of the extended attribute entries + */ +int +archive_entry_xattr_count(struct archive_entry *entry) +{ + struct ae_xattr *xp; + int count = 0; + + for (xp = entry->xattr_head; xp != NULL; xp = xp->next) + count++; + + return count; +} + +int +archive_entry_xattr_reset(struct archive_entry * entry) +{ + entry->xattr_p = entry->xattr_head; + + return archive_entry_xattr_count(entry); +} + +int +archive_entry_xattr_next(struct archive_entry * entry, + const char **name, const void **value, size_t *size) +{ + if (entry->xattr_p) { + *name = entry->xattr_p->name; + *value = entry->xattr_p->value; + *size = entry->xattr_p->size; + + entry->xattr_p = entry->xattr_p->next; + + return (ARCHIVE_OK); + } else { + *name = NULL; + *value = NULL; + *size = (size_t)0; + return (ARCHIVE_WARN); + } +} + +/* + * end of xattr handling + */ diff --git a/libarchive/archive_options.c b/libarchive/archive_options.c new file mode 100644 index 0000000..962572c --- /dev/null +++ b/libarchive/archive_options.c @@ -0,0 +1,164 @@ +/*- + * Copyright (c) 2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive_options_private.h" + +static const char * +parse_option(const char **str, + const char **mod, const char **opt, const char **val); + +int +_archive_set_option(struct archive *a, + const char *m, const char *o, const char *v, + int magic, const char *fn, option_handler use_option) +{ + const char *mp, *op, *vp; + + archive_check_magic(a, magic, ARCHIVE_STATE_NEW, fn); + + mp = m != NULL && m[0] == '\0' ? NULL : m; + op = o != NULL && o[0] == '\0' ? NULL : o; + vp = v != NULL && v[0] == '\0' ? NULL : v; + + if (op == NULL && vp == NULL) + return (ARCHIVE_OK); + if (op == NULL) + return (ARCHIVE_FAILED); + + return use_option(a, mp, op, vp); +} + +int +_archive_set_either_option(struct archive *a, const char *m, const char *o, const char *v, + option_handler use_format_option, option_handler use_filter_option) +{ + int r1, r2; + + if (o == NULL && v == NULL) + return (ARCHIVE_OK); + if (o == NULL) + return (ARCHIVE_FAILED); + + r1 = use_format_option(a, m, o, v); + if (r1 == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + r2 = use_filter_option(a, m, o, v); + if (r2 == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + return r1 > r2 ? r1 : r2; +} + +int +_archive_set_options(struct archive *a, const char *options, + int magic, const char *fn, option_handler use_option) +{ + int allok = 1, anyok = 0, r; + char *data; + const char *s, *mod, *opt, *val; + + archive_check_magic(a, magic, ARCHIVE_STATE_NEW, fn); + + if (options == NULL || options[0] == '\0') + return ARCHIVE_OK; + + data = (char *)malloc(strlen(options) + 1); + strcpy(data, options); + s = (const char *)data; + + do { + mod = opt = val = NULL; + + parse_option(&s, &mod, &opt, &val); + + r = use_option(a, mod, opt, val); + if (r == ARCHIVE_FATAL) { + free(data); + return (ARCHIVE_FATAL); + } + if (r == ARCHIVE_OK) + anyok = 1; + else + allok = 0; + } while (s != NULL); + + free(data); + return allok ? ARCHIVE_OK : anyok ? ARCHIVE_WARN : ARCHIVE_FAILED; +} + +static const char * +parse_option(const char **s, const char **m, const char **o, const char **v) +{ + const char *end, *mod, *opt, *val; + char *p; + + end = NULL; + mod = NULL; + opt = *s; + val = "1"; + + p = strchr(opt, ','); + + if (p != NULL) { + *p = '\0'; + end = ((const char *)p) + 1; + } + + if (0 == strlen(opt)) { + *s = end; + *m = NULL; + *o = NULL; + *v = NULL; + return end; + } + + p = strchr(opt, ':'); + if (p != NULL) { + *p = '\0'; + mod = opt; + opt = ++p; + } + + p = strchr(opt, '='); + if (p != NULL) { + *p = '\0'; + val = ++p; + } else if (opt[0] == '!') { + ++opt; + val = NULL; + } + + *s = end; + *m = mod; + *o = opt; + *v = val; + + return end; +} + diff --git a/libarchive/archive_options_private.h b/libarchive/archive_options_private.h new file mode 100644 index 0000000..6ef0165 --- /dev/null +++ b/libarchive/archive_options_private.h @@ -0,0 +1,47 @@ +/*- + * Copyright (c) 2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive_private.h" + +typedef int (*option_handler)(struct archive *a, + const char *mod, const char *opt, const char *val); + +int +_archive_set_option(struct archive *a, + const char *mod, const char *opt, const char *val, + int magic, const char *fn, option_handler use_option); + +int +_archive_set_options(struct archive *a, const char *options, + int magic, const char *fn, option_handler use_option); + +int +_archive_set_either_option(struct archive *a, + const char *m, const char *o, const char *v, + option_handler use_format_option, option_handler use_filter_option); + diff --git a/libarchive/archive_platform.h b/libarchive/archive_platform.h new file mode 100644 index 0000000..ce2f482 --- /dev/null +++ b/libarchive/archive_platform.h @@ -0,0 +1,165 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_platform.h 201090 2009-12-28 02:22:04Z kientzle $ + */ + +/* !!ONLY FOR USE INTERNALLY TO LIBARCHIVE!! */ + +/* + * This header is the first thing included in any of the libarchive + * source files. As far as possible, platform-specific issues should + * be dealt with here and not within individual source files. I'm + * actively trying to minimize #if blocks within the main source, + * since they obfuscate the code. + */ + +#ifndef ARCHIVE_PLATFORM_H_INCLUDED +#define ARCHIVE_PLATFORM_H_INCLUDED + +/* archive.h and archive_entry.h require this. */ +#define __LIBARCHIVE_BUILD 1 + +#if defined(PLATFORM_CONFIG_H) +/* Use hand-built config.h in environments that need it. */ +#include PLATFORM_CONFIG_H +#elif defined(HAVE_CONFIG_H) +/* Most POSIX platforms use the 'configure' script to build config.h */ +#include "config.h" +#else +/* Warn if the library hasn't been (automatically or manually) configured. */ +#error Oops: No config.h and no pre-built configuration in archive_platform.h. +#endif + +/* It should be possible to get rid of this by extending the feature-test + * macros to cover Windows API functions, probably along with non-trivial + * refactoring of code to find structures that sit more cleanly on top of + * either Windows or Posix APIs. */ +#if (defined(__WIN32__) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) +#include "archive_windows.h" +#endif + +/* + * The config files define a lot of feature macros. The following + * uses those macros to select/define replacements and include key + * headers as required. + */ + +/* Get a real definition for __FBSDID if we can */ +#if HAVE_SYS_CDEFS_H +#include +#endif + +/* If not, define it so as to avoid dangling semicolons. */ +#ifndef __FBSDID +#define __FBSDID(a) struct _undefined_hack +#endif + +/* Try to get standard C99-style integer type definitions. */ +#if HAVE_INTTYPES_H +#include +#endif +#if HAVE_STDINT_H +#include +#endif + +/* Borland warns about its own constants! */ +#if defined(__BORLANDC__) +# if HAVE_DECL_UINT64_MAX +# undef UINT64_MAX +# undef HAVE_DECL_UINT64_MAX +# endif +# if HAVE_DECL_UINT64_MIN +# undef UINT64_MIN +# undef HAVE_DECL_UINT64_MIN +# endif +# if HAVE_DECL_INT64_MAX +# undef INT64_MAX +# undef HAVE_DECL_INT64_MAX +# endif +# if HAVE_DECL_INT64_MIN +# undef INT64_MIN +# undef HAVE_DECL_INT64_MIN +# endif +#endif + +/* Some platforms lack the standard *_MAX definitions. */ +#if !HAVE_DECL_SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif +#if !HAVE_DECL_SSIZE_MAX +#define SSIZE_MAX ((ssize_t)(SIZE_MAX >> 1)) +#endif +#if !HAVE_DECL_UINT32_MAX +#define UINT32_MAX (~(uint32_t)0) +#endif +#if !HAVE_DECL_UINT64_MAX +#define UINT64_MAX (~(uint64_t)0) +#endif +#if !HAVE_DECL_INT64_MAX +#define INT64_MAX ((int64_t)(UINT64_MAX >> 1)) +#endif +#if !HAVE_DECL_INT64_MIN +#define INT64_MIN ((int64_t)(~INT64_MAX)) +#endif + +/* + * If this platform has , acl_create(), acl_init(), + * acl_set_file(), and ACL_USER, we assume it has the rest of the + * POSIX.1e draft functions used in archive_read_extract.c. + */ +#if HAVE_SYS_ACL_H && HAVE_ACL_CREATE_ENTRY && HAVE_ACL_INIT && HAVE_ACL_SET_FILE && HAVE_ACL_USER +#define HAVE_POSIX_ACL 1 +#endif + +/* + * If we can't restore metadata using a file descriptor, then + * for compatibility's sake, close files before trying to restore metadata. + */ +#if defined(HAVE_FCHMOD) || defined(HAVE_FUTIMES) || defined(HAVE_ACL_SET_FD) || defined(HAVE_ACL_SET_FD_NP) || defined(HAVE_FCHOWN) +#define CAN_RESTORE_METADATA_FD +#endif + +/* Set up defaults for internal error codes. */ +#ifndef ARCHIVE_ERRNO_FILE_FORMAT +#if HAVE_EFTYPE +#define ARCHIVE_ERRNO_FILE_FORMAT EFTYPE +#else +#if HAVE_EILSEQ +#define ARCHIVE_ERRNO_FILE_FORMAT EILSEQ +#else +#define ARCHIVE_ERRNO_FILE_FORMAT EINVAL +#endif +#endif +#endif + +#ifndef ARCHIVE_ERRNO_PROGRAMMER +#define ARCHIVE_ERRNO_PROGRAMMER EINVAL +#endif + +#ifndef ARCHIVE_ERRNO_MISC +#define ARCHIVE_ERRNO_MISC (-1) +#endif + +#endif /* !ARCHIVE_PLATFORM_H_INCLUDED */ diff --git a/libarchive/archive_ppmd7.c b/libarchive/archive_ppmd7.c new file mode 100644 index 0000000..b2e8c3a --- /dev/null +++ b/libarchive/archive_ppmd7.c @@ -0,0 +1,1164 @@ +/* Ppmd7.c -- PPMdH codec +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#include "archive_platform.h" + +#include + +#include "archive_ppmd7_private.h" + +#ifdef PPMD_32BIT + #define Ppmd7_GetPtr(p, ptr) (ptr) + #define Ppmd7_GetContext(p, ptr) (ptr) + #define Ppmd7_GetStats(p, ctx) ((ctx)->Stats) +#else + #define Ppmd7_GetPtr(p, offs) ((void *)((p)->Base + (offs))) + #define Ppmd7_GetContext(p, offs) ((CPpmd7_Context *)Ppmd7_GetPtr((p), (offs))) + #define Ppmd7_GetStats(p, ctx) ((CPpmd_State *)Ppmd7_GetPtr((p), ((ctx)->Stats))) +#endif + +#define Ppmd7_GetBinSumm(p) \ + &p->BinSumm[Ppmd7Context_OneState(p->MinContext)->Freq - 1][p->PrevSuccess + \ + p->NS2BSIndx[Ppmd7_GetContext(p, p->MinContext->Suffix)->NumStats - 1] + \ + (p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]) + \ + 2 * p->HB2Flag[Ppmd7Context_OneState(p->MinContext)->Symbol] + \ + ((p->RunLength >> 26) & 0x20)] + +#define kTopValue (1 << 24) +#define MAX_FREQ 124 +#define UNIT_SIZE 12 + +#define U2B(nu) ((UInt32)(nu) * UNIT_SIZE) +#define U2I(nu) (p->Units2Indx[(nu) - 1]) +#define I2U(indx) (p->Indx2Units[indx]) + +#ifdef PPMD_32BIT + #define REF(ptr) (ptr) +#else + #define REF(ptr) ((UInt32)((Byte *)(ptr) - (p)->Base)) +#endif + +#define STATS_REF(ptr) ((CPpmd_State_Ref)REF(ptr)) + +#define CTX(ref) ((CPpmd7_Context *)Ppmd7_GetContext(p, ref)) +#define STATS(ctx) Ppmd7_GetStats(p, ctx) +#define ONE_STATE(ctx) Ppmd7Context_OneState(ctx) +#define SUFFIX(ctx) CTX((ctx)->Suffix) + +static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; +static const Byte PPMD7_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; + +typedef CPpmd7_Context * CTX_PTR; + +struct CPpmd7_Node_; + +typedef + #ifdef PPMD_32BIT + struct CPpmd7_Node_ * + #else + UInt32 + #endif + CPpmd7_Node_Ref; + +typedef struct CPpmd7_Node_ +{ + UInt16 Stamp; /* must be at offset 0 as CPpmd7_Context::NumStats. Stamp=0 means free */ + UInt16 NU; + CPpmd7_Node_Ref Next; /* must be at offset >= 4 */ + CPpmd7_Node_Ref Prev; +} CPpmd7_Node; + +#ifdef PPMD_32BIT + #define NODE(ptr) (ptr) +#else + #define NODE(offs) ((CPpmd7_Node *)(p->Base + (offs))) +#endif + +static void Ppmd7_Update1(CPpmd7 *p); +static void Ppmd7_Update1_0(CPpmd7 *p); +static void Ppmd7_Update2(CPpmd7 *p); +static void Ppmd7_UpdateBin(CPpmd7 *p); +static CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, + UInt32 *scale); + +/* ----------- Base ----------- */ + +static void Ppmd7_Construct(CPpmd7 *p) +{ + unsigned i, k, m; + + p->Base = 0; + + for (i = 0, k = 0; i < PPMD_NUM_INDEXES; i++) + { + unsigned step = (i >= 12 ? 4 : (i >> 2) + 1); + do { p->Units2Indx[k++] = (Byte)i; } while(--step); + p->Indx2Units[i] = (Byte)k; + } + + p->NS2BSIndx[0] = (0 << 1); + p->NS2BSIndx[1] = (1 << 1); + memset(p->NS2BSIndx + 2, (2 << 1), 9); + memset(p->NS2BSIndx + 11, (3 << 1), 256 - 11); + + for (i = 0; i < 3; i++) + p->NS2Indx[i] = (Byte)i; + for (m = i, k = 1; i < 256; i++) + { + p->NS2Indx[i] = (Byte)m; + if (--k == 0) + k = (++m) - 2; + } + + memset(p->HB2Flag, 0, 0x40); + memset(p->HB2Flag + 0x40, 8, 0x100 - 0x40); +} + +static void Ppmd7_Free(CPpmd7 *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->Base); + p->Size = 0; + p->Base = 0; +} + +static Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAlloc *alloc) +{ + if (p->Base == 0 || p->Size != size) + { + Ppmd7_Free(p, alloc); + p->AlignOffset = + #ifdef PPMD_32BIT + (4 - size) & 3; + #else + 4 - (size & 3); + #endif + if ((p->Base = (Byte *)alloc->Alloc(alloc, p->AlignOffset + size + #ifndef PPMD_32BIT + + UNIT_SIZE + #endif + )) == 0) + return False; + p->Size = size; + } + return True; +} + +static void InsertNode(CPpmd7 *p, void *node, unsigned indx) +{ + *((CPpmd_Void_Ref *)node) = p->FreeList[indx]; + p->FreeList[indx] = REF(node); +} + +static void *RemoveNode(CPpmd7 *p, unsigned indx) +{ + CPpmd_Void_Ref *node = (CPpmd_Void_Ref *)Ppmd7_GetPtr(p, p->FreeList[indx]); + p->FreeList[indx] = *node; + return node; +} + +static void SplitBlock(CPpmd7 *p, void *ptr, unsigned oldIndx, unsigned newIndx) +{ + unsigned i, nu = I2U(oldIndx) - I2U(newIndx); + ptr = (Byte *)ptr + U2B(I2U(newIndx)); + if (I2U(i = U2I(nu)) != nu) + { + unsigned k = I2U(--i); + InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); + } + InsertNode(p, ptr, i); +} + +static void GlueFreeBlocks(CPpmd7 *p) +{ + #ifdef PPMD_32BIT + CPpmd7_Node headItem; + CPpmd7_Node_Ref head = &headItem; + #else + CPpmd7_Node_Ref head = p->AlignOffset + p->Size; + #endif + + CPpmd7_Node_Ref n = head; + unsigned i; + + p->GlueCount = 255; + + /* create doubly-linked list of free blocks */ + for (i = 0; i < PPMD_NUM_INDEXES; i++) + { + UInt16 nu = I2U(i); + CPpmd7_Node_Ref next = (CPpmd7_Node_Ref)p->FreeList[i]; + p->FreeList[i] = 0; + while (next != 0) + { + CPpmd7_Node *node = NODE(next); + node->Next = n; + n = NODE(n)->Prev = next; + next = *(const CPpmd7_Node_Ref *)node; + node->Stamp = 0; + node->NU = (UInt16)nu; + } + } + NODE(head)->Stamp = 1; + NODE(head)->Next = n; + NODE(n)->Prev = head; + if (p->LoUnit != p->HiUnit) + ((CPpmd7_Node *)p->LoUnit)->Stamp = 1; + + /* Glue free blocks */ + while (n != head) + { + CPpmd7_Node *node = NODE(n); + UInt32 nu = (UInt32)node->NU; + for (;;) + { + CPpmd7_Node *node2 = NODE(n) + nu; + nu += node2->NU; + if (node2->Stamp != 0 || nu >= 0x10000) + break; + NODE(node2->Prev)->Next = node2->Next; + NODE(node2->Next)->Prev = node2->Prev; + node->NU = (UInt16)nu; + } + n = node->Next; + } + + /* Fill lists of free blocks */ + for (n = NODE(head)->Next; n != head;) + { + CPpmd7_Node *node = NODE(n); + unsigned nu; + CPpmd7_Node_Ref next = node->Next; + for (nu = node->NU; nu > 128; nu -= 128, node += 128) + InsertNode(p, node, PPMD_NUM_INDEXES - 1); + if (I2U(i = U2I(nu)) != nu) + { + unsigned k = I2U(--i); + InsertNode(p, node + k, nu - k - 1); + } + InsertNode(p, node, i); + n = next; + } +} + +static void *AllocUnitsRare(CPpmd7 *p, unsigned indx) +{ + unsigned i; + void *retVal; + if (p->GlueCount == 0) + { + GlueFreeBlocks(p); + if (p->FreeList[indx] != 0) + return RemoveNode(p, indx); + } + i = indx; + do + { + if (++i == PPMD_NUM_INDEXES) + { + UInt32 numBytes = U2B(I2U(indx)); + p->GlueCount--; + return ((UInt32)(p->UnitsStart - p->Text) > numBytes) ? (p->UnitsStart -= numBytes) : (NULL); + } + } + while (p->FreeList[i] == 0); + retVal = RemoveNode(p, i); + SplitBlock(p, retVal, i, indx); + return retVal; +} + +static void *AllocUnits(CPpmd7 *p, unsigned indx) +{ + UInt32 numBytes; + if (p->FreeList[indx] != 0) + return RemoveNode(p, indx); + numBytes = U2B(I2U(indx)); + if (numBytes <= (UInt32)(p->HiUnit - p->LoUnit)) + { + void *retVal = p->LoUnit; + p->LoUnit += numBytes; + return retVal; + } + return AllocUnitsRare(p, indx); +} + +#define MyMem12Cpy(dest, src, num) \ + { UInt32 *d = (UInt32 *)dest; const UInt32 *s = (const UInt32 *)src; UInt32 n = num; \ + do { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += 3; } while(--n); } + +static void *ShrinkUnits(CPpmd7 *p, void *oldPtr, unsigned oldNU, unsigned newNU) +{ + unsigned i0 = U2I(oldNU); + unsigned i1 = U2I(newNU); + if (i0 == i1) + return oldPtr; + if (p->FreeList[i1] != 0) + { + void *ptr = RemoveNode(p, i1); + MyMem12Cpy(ptr, oldPtr, newNU); + InsertNode(p, oldPtr, i0); + return ptr; + } + SplitBlock(p, oldPtr, i0, i1); + return oldPtr; +} + +#define SUCCESSOR(p) ((CPpmd_Void_Ref)((p)->SuccessorLow | ((UInt32)(p)->SuccessorHigh << 16))) + +static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) +{ + (p)->SuccessorLow = (UInt16)((UInt32)(v) & 0xFFFF); + (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF); +} + +static void RestartModel(CPpmd7 *p) +{ + unsigned i, k, m; + + memset(p->FreeList, 0, sizeof(p->FreeList)); + p->Text = p->Base + p->AlignOffset; + p->HiUnit = p->Text + p->Size; + p->LoUnit = p->UnitsStart = p->HiUnit - p->Size / 8 / UNIT_SIZE * 7 * UNIT_SIZE; + p->GlueCount = 0; + + p->OrderFall = p->MaxOrder; + p->RunLength = p->InitRL = -(Int32)((p->MaxOrder < 12) ? p->MaxOrder : 12) - 1; + p->PrevSuccess = 0; + + p->MinContext = p->MaxContext = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ + p->MinContext->Suffix = 0; + p->MinContext->NumStats = 256; + p->MinContext->SummFreq = 256 + 1; + p->FoundState = (CPpmd_State *)p->LoUnit; /* AllocUnits(p, PPMD_NUM_INDEXES - 1); */ + p->LoUnit += U2B(256 / 2); + p->MinContext->Stats = REF(p->FoundState); + for (i = 0; i < 256; i++) + { + CPpmd_State *s = &p->FoundState[i]; + s->Symbol = (Byte)i; + s->Freq = 1; + SetSuccessor(s, 0); + } + + for (i = 0; i < 128; i++) + for (k = 0; k < 8; k++) + { + UInt16 *dest = p->BinSumm[i] + k; + UInt16 val = (UInt16)(PPMD_BIN_SCALE - kInitBinEsc[k] / (i + 2)); + for (m = 0; m < 64; m += 8) + dest[m] = val; + } + + for (i = 0; i < 25; i++) + for (k = 0; k < 16; k++) + { + CPpmd_See *s = &p->See[i][k]; + s->Summ = (UInt16)((5 * i + 10) << (s->Shift = PPMD_PERIOD_BITS - 4)); + s->Count = 4; + } +} + +static void Ppmd7_Init(CPpmd7 *p, unsigned maxOrder) +{ + p->MaxOrder = maxOrder; + RestartModel(p); + p->DummySee.Shift = PPMD_PERIOD_BITS; + p->DummySee.Summ = 0; /* unused */ + p->DummySee.Count = 64; /* unused */ +} + +static CTX_PTR CreateSuccessors(CPpmd7 *p, Bool skip) +{ + CPpmd_State upState; + CTX_PTR c = p->MinContext; + CPpmd_Byte_Ref upBranch = (CPpmd_Byte_Ref)SUCCESSOR(p->FoundState); + CPpmd_State *ps[PPMD7_MAX_ORDER]; + unsigned numPs = 0; + + if (!skip) + ps[numPs++] = p->FoundState; + + while (c->Suffix) + { + CPpmd_Void_Ref successor; + CPpmd_State *s; + c = SUFFIX(c); + if (c->NumStats != 1) + { + for (s = STATS(c); s->Symbol != p->FoundState->Symbol; s++); + } + else + s = ONE_STATE(c); + successor = SUCCESSOR(s); + if (successor != upBranch) + { + c = CTX(successor); + if (numPs == 0) + return c; + break; + } + ps[numPs++] = s; + } + + upState.Symbol = *(const Byte *)Ppmd7_GetPtr(p, upBranch); + SetSuccessor(&upState, upBranch + 1); + + if (c->NumStats == 1) + upState.Freq = ONE_STATE(c)->Freq; + else + { + UInt32 cf, s0; + CPpmd_State *s; + for (s = STATS(c); s->Symbol != upState.Symbol; s++); + cf = s->Freq - 1; + s0 = c->SummFreq - c->NumStats - cf; + upState.Freq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((2 * cf + 3 * s0 - 1) / (2 * s0)))); + } + + do + { + /* Create Child */ + CTX_PTR c1; /* = AllocContext(p); */ + if (p->HiUnit != p->LoUnit) + c1 = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); + else if (p->FreeList[0] != 0) + c1 = (CTX_PTR)RemoveNode(p, 0); + else + { + c1 = (CTX_PTR)AllocUnitsRare(p, 0); + if (!c1) + return NULL; + } + c1->NumStats = 1; + *ONE_STATE(c1) = upState; + c1->Suffix = REF(c); + SetSuccessor(ps[--numPs], REF(c1)); + c = c1; + } + while (numPs != 0); + + return c; +} + +static void SwapStates(CPpmd_State *t1, CPpmd_State *t2) +{ + CPpmd_State tmp = *t1; + *t1 = *t2; + *t2 = tmp; +} + +static void UpdateModel(CPpmd7 *p) +{ + CPpmd_Void_Ref successor, fSuccessor = SUCCESSOR(p->FoundState); + CTX_PTR c; + unsigned s0, ns; + + if (p->FoundState->Freq < MAX_FREQ / 4 && p->MinContext->Suffix != 0) + { + c = SUFFIX(p->MinContext); + + if (c->NumStats == 1) + { + CPpmd_State *s = ONE_STATE(c); + if (s->Freq < 32) + s->Freq++; + } + else + { + CPpmd_State *s = STATS(c); + if (s->Symbol != p->FoundState->Symbol) + { + do { s++; } while (s->Symbol != p->FoundState->Symbol); + if (s[0].Freq >= s[-1].Freq) + { + SwapStates(&s[0], &s[-1]); + s--; + } + } + if (s->Freq < MAX_FREQ - 9) + { + s->Freq += 2; + c->SummFreq += 2; + } + } + } + + if (p->OrderFall == 0) + { + p->MinContext = p->MaxContext = CreateSuccessors(p, True); + if (p->MinContext == 0) + { + RestartModel(p); + return; + } + SetSuccessor(p->FoundState, REF(p->MinContext)); + return; + } + + *p->Text++ = p->FoundState->Symbol; + successor = REF(p->Text); + if (p->Text >= p->UnitsStart) + { + RestartModel(p); + return; + } + + if (fSuccessor) + { + if (fSuccessor <= successor) + { + CTX_PTR cs = CreateSuccessors(p, False); + if (cs == NULL) + { + RestartModel(p); + return; + } + fSuccessor = REF(cs); + } + if (--p->OrderFall == 0) + { + successor = fSuccessor; + p->Text -= (p->MaxContext != p->MinContext); + } + } + else + { + SetSuccessor(p->FoundState, successor); + fSuccessor = REF(p->MinContext); + } + + s0 = p->MinContext->SummFreq - (ns = p->MinContext->NumStats) - (p->FoundState->Freq - 1); + + for (c = p->MaxContext; c != p->MinContext; c = SUFFIX(c)) + { + unsigned ns1; + UInt32 cf, sf; + if ((ns1 = c->NumStats) != 1) + { + if ((ns1 & 1) == 0) + { + /* Expand for one UNIT */ + unsigned oldNU = ns1 >> 1; + unsigned i = U2I(oldNU); + if (i != U2I(oldNU + 1)) + { + void *ptr = AllocUnits(p, i + 1); + void *oldPtr; + if (!ptr) + { + RestartModel(p); + return; + } + oldPtr = STATS(c); + MyMem12Cpy(ptr, oldPtr, oldNU); + InsertNode(p, oldPtr, i); + c->Stats = STATS_REF(ptr); + } + } + c->SummFreq = (UInt16)(c->SummFreq + (2 * ns1 < ns) + 2 * ((4 * ns1 <= ns) & (c->SummFreq <= 8 * ns1))); + } + else + { + CPpmd_State *s = (CPpmd_State*)AllocUnits(p, 0); + if (!s) + { + RestartModel(p); + return; + } + *s = *ONE_STATE(c); + c->Stats = REF(s); + if (s->Freq < MAX_FREQ / 4 - 1) + s->Freq <<= 1; + else + s->Freq = MAX_FREQ - 4; + c->SummFreq = (UInt16)(s->Freq + p->InitEsc + (ns > 3)); + } + cf = 2 * (UInt32)p->FoundState->Freq * (c->SummFreq + 6); + sf = (UInt32)s0 + c->SummFreq; + if (cf < 6 * sf) + { + cf = 1 + (cf > sf) + (cf >= 4 * sf); + c->SummFreq += 3; + } + else + { + cf = 4 + (cf >= 9 * sf) + (cf >= 12 * sf) + (cf >= 15 * sf); + c->SummFreq = (UInt16)(c->SummFreq + cf); + } + { + CPpmd_State *s = STATS(c) + ns1; + SetSuccessor(s, successor); + s->Symbol = p->FoundState->Symbol; + s->Freq = (Byte)cf; + c->NumStats = (UInt16)(ns1 + 1); + } + } + p->MaxContext = p->MinContext = CTX(fSuccessor); +} + +static void Rescale(CPpmd7 *p) +{ + unsigned i, adder, sumFreq, escFreq; + CPpmd_State *stats = STATS(p->MinContext); + CPpmd_State *s = p->FoundState; + { + CPpmd_State tmp = *s; + for (; s != stats; s--) + s[0] = s[-1]; + *s = tmp; + } + escFreq = p->MinContext->SummFreq - s->Freq; + s->Freq += 4; + adder = (p->OrderFall != 0); + s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq = s->Freq; + + i = p->MinContext->NumStats - 1; + do + { + escFreq -= (++s)->Freq; + s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq += s->Freq; + if (s[0].Freq > s[-1].Freq) + { + CPpmd_State *s1 = s; + CPpmd_State tmp = *s1; + do + s1[0] = s1[-1]; + while (--s1 != stats && tmp.Freq > s1[-1].Freq); + *s1 = tmp; + } + } + while (--i); + + if (s->Freq == 0) + { + unsigned numStats = p->MinContext->NumStats; + unsigned n0, n1; + do { i++; } while ((--s)->Freq == 0); + escFreq += i; + p->MinContext->NumStats = (UInt16)(p->MinContext->NumStats - i); + if (p->MinContext->NumStats == 1) + { + CPpmd_State tmp = *stats; + do + { + tmp.Freq = (Byte)(tmp.Freq - (tmp.Freq >> 1)); + escFreq >>= 1; + } + while (escFreq > 1); + InsertNode(p, stats, U2I(((numStats + 1) >> 1))); + *(p->FoundState = ONE_STATE(p->MinContext)) = tmp; + return; + } + n0 = (numStats + 1) >> 1; + n1 = (p->MinContext->NumStats + 1) >> 1; + if (n0 != n1) + p->MinContext->Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); + } + p->MinContext->SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); + p->FoundState = STATS(p->MinContext); +} + +static CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *escFreq) +{ + CPpmd_See *see; + unsigned nonMasked = p->MinContext->NumStats - numMasked; + if (p->MinContext->NumStats != 256) + { + see = p->See[p->NS2Indx[nonMasked - 1]] + + (nonMasked < (unsigned)SUFFIX(p->MinContext)->NumStats - p->MinContext->NumStats) + + 2 * (p->MinContext->SummFreq < 11 * p->MinContext->NumStats) + + 4 * (numMasked > nonMasked) + + p->HiBitsFlag; + { + unsigned r = (see->Summ >> see->Shift); + see->Summ = (UInt16)(see->Summ - r); + *escFreq = r + (r == 0); + } + } + else + { + see = &p->DummySee; + *escFreq = 1; + } + return see; +} + +static void NextContext(CPpmd7 *p) +{ + CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); + if (p->OrderFall == 0 && (Byte *)c > p->Text) + p->MinContext = p->MaxContext = c; + else + UpdateModel(p); +} + +static void Ppmd7_Update1(CPpmd7 *p) +{ + CPpmd_State *s = p->FoundState; + s->Freq += 4; + p->MinContext->SummFreq += 4; + if (s[0].Freq > s[-1].Freq) + { + SwapStates(&s[0], &s[-1]); + p->FoundState = --s; + if (s->Freq > MAX_FREQ) + Rescale(p); + } + NextContext(p); +} + +static void Ppmd7_Update1_0(CPpmd7 *p) +{ + p->PrevSuccess = (2 * p->FoundState->Freq > p->MinContext->SummFreq); + p->RunLength += p->PrevSuccess; + p->MinContext->SummFreq += 4; + if ((p->FoundState->Freq += 4) > MAX_FREQ) + Rescale(p); + NextContext(p); +} + +static void Ppmd7_UpdateBin(CPpmd7 *p) +{ + p->FoundState->Freq = (Byte)(p->FoundState->Freq + (p->FoundState->Freq < 128 ? 1: 0)); + p->PrevSuccess = 1; + p->RunLength++; + NextContext(p); +} + +static void Ppmd7_Update2(CPpmd7 *p) +{ + p->MinContext->SummFreq += 4; + if ((p->FoundState->Freq += 4) > MAX_FREQ) + Rescale(p); + p->RunLength = p->InitRL; + UpdateModel(p); +} + +/* ---------- Decode ---------- */ + +static Bool Ppmd_RangeDec_Init(CPpmd7z_RangeDec *p) +{ + unsigned i; + p->Low = p->Bottom = 0; + p->Range = 0xFFFFFFFF; + for (i = 0; i < 4; i++) + p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + return (p->Code < 0xFFFFFFFF); +} + +static Bool Ppmd7z_RangeDec_Init(CPpmd7z_RangeDec *p) +{ + if (p->Stream->Read((void *)p->Stream) != 0) + return False; + return Ppmd_RangeDec_Init(p); +} + +static Bool PpmdRAR_RangeDec_Init(CPpmd7z_RangeDec *p) +{ + if (!Ppmd_RangeDec_Init(p)) + return False; + p->Bottom = 0x8000; + return True; +} + +static UInt32 Range_GetThreshold(void *pp, UInt32 total) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + return (p->Code - p->Low) / (p->Range /= total); +} + +static void Range_Normalize(CPpmd7z_RangeDec *p) +{ + while (1) + { + if((p->Low ^ (p->Low + p->Range)) >= kTopValue) + { + if(p->Range >= p->Bottom) + break; + else + p->Range = -p->Low & (p->Bottom - 1); + } + p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + p->Range <<= 8; + p->Low <<= 8; + } +} + +static void Range_Decode_7z(void *pp, UInt32 start, UInt32 size) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + p->Code -= start * p->Range; + p->Range *= size; + Range_Normalize(p); +} + +static void Range_Decode_RAR(void *pp, UInt32 start, UInt32 size) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + p->Low += start * p->Range; + p->Range *= size; + Range_Normalize(p); +} + +static UInt32 Range_DecodeBit_7z(void *pp, UInt32 size0) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + UInt32 newBound = (p->Range >> 14) * size0; + UInt32 symbol; + if (p->Code < newBound) + { + symbol = 0; + p->Range = newBound; + } + else + { + symbol = 1; + p->Code -= newBound; + p->Range -= newBound; + } + Range_Normalize(p); + return symbol; +} + +static UInt32 Range_DecodeBit_RAR(void *pp, UInt32 size0) +{ + CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; + UInt32 bit, value = p->p.GetThreshold(p, PPMD_BIN_SCALE); + if(value < size0) + { + bit = 0; + p->p.Decode(p, 0, size0); + } + else + { + bit = 1; + p->p.Decode(p, size0, PPMD_BIN_SCALE - size0); + } + return bit; +} + +static void Ppmd7z_RangeDec_CreateVTable(CPpmd7z_RangeDec *p) +{ + p->p.GetThreshold = Range_GetThreshold; + p->p.Decode = Range_Decode_7z; + p->p.DecodeBit = Range_DecodeBit_7z; +} + +static void PpmdRAR_RangeDec_CreateVTable(CPpmd7z_RangeDec *p) +{ + p->p.GetThreshold = Range_GetThreshold; + p->p.Decode = Range_Decode_RAR; + p->p.DecodeBit = Range_DecodeBit_RAR; +} + +#define MASK(sym) ((signed char *)charMask)[sym] + +static int Ppmd7_DecodeSymbol(CPpmd7 *p, IPpmd7_RangeDec *rc) +{ + size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) + { + CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); + unsigned i; + UInt32 count, hiCnt; + if ((count = rc->GetThreshold(rc, p->MinContext->SummFreq)) < (hiCnt = s->Freq)) + { + Byte symbol; + rc->Decode(rc, 0, s->Freq); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update1_0(p); + return symbol; + } + p->PrevSuccess = 0; + i = p->MinContext->NumStats - 1; + do + { + if ((hiCnt += (++s)->Freq) > count) + { + Byte symbol; + rc->Decode(rc, hiCnt - s->Freq, s->Freq); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update1(p); + return symbol; + } + } + while (--i); + if (count >= p->MinContext->SummFreq) + return -2; + p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; + rc->Decode(rc, hiCnt, p->MinContext->SummFreq - hiCnt); + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + i = p->MinContext->NumStats - 1; + do { MASK((--s)->Symbol) = 0; } while (--i); + } + else + { + UInt16 *prob = Ppmd7_GetBinSumm(p); + if (rc->DecodeBit(rc, *prob) == 0) + { + Byte symbol; + *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); + symbol = (p->FoundState = Ppmd7Context_OneState(p->MinContext))->Symbol; + Ppmd7_UpdateBin(p); + return symbol; + } + *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); + p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(Ppmd7Context_OneState(p->MinContext)->Symbol) = 0; + p->PrevSuccess = 0; + } + for (;;) + { + CPpmd_State *ps[256], *s; + UInt32 freqSum, count, hiCnt; + CPpmd_See *see; + unsigned i, num, numMasked = p->MinContext->NumStats; + do + { + p->OrderFall++; + if (!p->MinContext->Suffix) + return -1; + p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + } + while (p->MinContext->NumStats == numMasked); + hiCnt = 0; + s = Ppmd7_GetStats(p, p->MinContext); + i = 0; + num = p->MinContext->NumStats - numMasked; + do + { + int k = (int)(MASK(s->Symbol)); + hiCnt += (s->Freq & k); + ps[i] = s++; + i -= k; + } + while (i != num); + + see = Ppmd7_MakeEscFreq(p, numMasked, &freqSum); + freqSum += hiCnt; + count = rc->GetThreshold(rc, freqSum); + + if (count < hiCnt) + { + Byte symbol; + CPpmd_State **pps = ps; + for (hiCnt = 0; (hiCnt += (*pps)->Freq) <= count; pps++); + s = *pps; + rc->Decode(rc, hiCnt - s->Freq, s->Freq); + Ppmd_See_Update(see); + p->FoundState = s; + symbol = s->Symbol; + Ppmd7_Update2(p); + return symbol; + } + if (count >= freqSum) + return -2; + rc->Decode(rc, hiCnt, freqSum - hiCnt); + see->Summ = (UInt16)(see->Summ + freqSum); + do { MASK(ps[--i]->Symbol) = 0; } while (i != 0); + } +} + +/* ---------- Encode ---------- Ppmd7Enc.c */ + +#define kTopValue (1 << 24) + +static void Ppmd7z_RangeEnc_Init(CPpmd7z_RangeEnc *p) +{ + p->Low = 0; + p->Range = 0xFFFFFFFF; + p->Cache = 0; + p->CacheSize = 1; +} + +static void RangeEnc_ShiftLow(CPpmd7z_RangeEnc *p) +{ + if ((UInt32)p->Low < (UInt32)0xFF000000 || (unsigned)(p->Low >> 32) != 0) + { + Byte temp = p->Cache; + do + { + p->Stream->Write(p->Stream, (Byte)(temp + (Byte)(p->Low >> 32))); + temp = 0xFF; + } + while(--p->CacheSize != 0); + p->Cache = (Byte)((UInt32)p->Low >> 24); + } + p->CacheSize++; + p->Low = (UInt32)p->Low << 8; +} + +static void RangeEnc_Encode(CPpmd7z_RangeEnc *p, UInt32 start, UInt32 size, UInt32 total) +{ + p->Low += start * (p->Range /= total); + p->Range *= size; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void RangeEnc_EncodeBit_0(CPpmd7z_RangeEnc *p, UInt32 size0) +{ + p->Range = (p->Range >> 14) * size0; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void RangeEnc_EncodeBit_1(CPpmd7z_RangeEnc *p, UInt32 size0) +{ + UInt32 newBound = (p->Range >> 14) * size0; + p->Low += newBound; + p->Range -= newBound; + while (p->Range < kTopValue) + { + p->Range <<= 8; + RangeEnc_ShiftLow(p); + } +} + +static void Ppmd7z_RangeEnc_FlushData(CPpmd7z_RangeEnc *p) +{ + unsigned i; + for (i = 0; i < 5; i++) + RangeEnc_ShiftLow(p); +} + + +#define MASK(sym) ((signed char *)charMask)[sym] + +static void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol) +{ + size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) + { + CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); + UInt32 sum; + unsigned i; + if (s->Symbol == symbol) + { + RangeEnc_Encode(rc, 0, s->Freq, p->MinContext->SummFreq); + p->FoundState = s; + Ppmd7_Update1_0(p); + return; + } + p->PrevSuccess = 0; + sum = s->Freq; + i = p->MinContext->NumStats - 1; + do + { + if ((++s)->Symbol == symbol) + { + RangeEnc_Encode(rc, sum, s->Freq, p->MinContext->SummFreq); + p->FoundState = s; + Ppmd7_Update1(p); + return; + } + sum += s->Freq; + } + while (--i); + + p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + i = p->MinContext->NumStats - 1; + do { MASK((--s)->Symbol) = 0; } while (--i); + RangeEnc_Encode(rc, sum, p->MinContext->SummFreq - sum, p->MinContext->SummFreq); + } + else + { + UInt16 *prob = Ppmd7_GetBinSumm(p); + CPpmd_State *s = Ppmd7Context_OneState(p->MinContext); + if (s->Symbol == symbol) + { + RangeEnc_EncodeBit_0(rc, *prob); + *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); + p->FoundState = s; + Ppmd7_UpdateBin(p); + return; + } + else + { + RangeEnc_EncodeBit_1(rc, *prob); + *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); + p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; + PPMD_SetAllBitsIn256Bytes(charMask); + MASK(s->Symbol) = 0; + p->PrevSuccess = 0; + } + } + for (;;) + { + UInt32 escFreq; + CPpmd_See *see; + CPpmd_State *s; + UInt32 sum; + unsigned i, numMasked = p->MinContext->NumStats; + do + { + p->OrderFall++; + if (!p->MinContext->Suffix) + return; /* EndMarker (symbol = -1) */ + p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + } + while (p->MinContext->NumStats == numMasked); + + see = Ppmd7_MakeEscFreq(p, numMasked, &escFreq); + s = Ppmd7_GetStats(p, p->MinContext); + sum = 0; + i = p->MinContext->NumStats; + do + { + int cur = s->Symbol; + if (cur == symbol) + { + UInt32 low = sum; + CPpmd_State *s1 = s; + do + { + sum += (s->Freq & (int)(MASK(s->Symbol))); + s++; + } + while (--i); + RangeEnc_Encode(rc, low, s1->Freq, sum + escFreq); + Ppmd_See_Update(see); + p->FoundState = s1; + Ppmd7_Update2(p); + return; + } + sum += (s->Freq & (int)(MASK(cur))); + MASK(cur) = 0; + s++; + } + while (--i); + + RangeEnc_Encode(rc, sum, escFreq, sum + escFreq); + see->Summ = (UInt16)(see->Summ + sum + escFreq); + } +} + +const IPpmd7 __archive_ppmd7_functions = +{ + &Ppmd7_Construct, + &Ppmd7_Alloc, + &Ppmd7_Free, + &Ppmd7_Init, + &Ppmd7z_RangeDec_CreateVTable, + &PpmdRAR_RangeDec_CreateVTable, + &Ppmd7z_RangeDec_Init, + &PpmdRAR_RangeDec_Init, + &Ppmd7_DecodeSymbol, + &Ppmd7z_RangeEnc_Init, + &Ppmd7z_RangeEnc_FlushData, + &Ppmd7_EncodeSymbol +}; diff --git a/libarchive/archive_ppmd7_private.h b/libarchive/archive_ppmd7_private.h new file mode 100644 index 0000000..3a6b9eb --- /dev/null +++ b/libarchive/archive_ppmd7_private.h @@ -0,0 +1,119 @@ +/* Ppmd7.h -- PPMdH compression codec +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +/* This code supports virtual RangeDecoder and includes the implementation +of RangeCoder from 7z, instead of RangeCoder from original PPMd var.H. +If you need the compatibility with original PPMd var.H, you can use external RangeDecoder */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_PPMD7_PRIVATE_H_INCLUDED +#define ARCHIVE_PPMD7_PRIVATE_H_INCLUDED + +#include "archive_ppmd_private.h" + +#define PPMD7_MIN_ORDER 2 +#define PPMD7_MAX_ORDER 64 + +#define PPMD7_MIN_MEM_SIZE (1 << 11) +#define PPMD7_MAX_MEM_SIZE (0xFFFFFFFF - 12 * 3) + +struct CPpmd7_Context_; + +typedef + #ifdef PPMD_32BIT + struct CPpmd7_Context_ * + #else + UInt32 + #endif + CPpmd7_Context_Ref; + +typedef struct CPpmd7_Context_ +{ + UInt16 NumStats; + UInt16 SummFreq; + CPpmd_State_Ref Stats; + CPpmd7_Context_Ref Suffix; +} CPpmd7_Context; + +#define Ppmd7Context_OneState(p) ((CPpmd_State *)&(p)->SummFreq) + +typedef struct +{ + CPpmd7_Context *MinContext, *MaxContext; + CPpmd_State *FoundState; + unsigned OrderFall, InitEsc, PrevSuccess, MaxOrder, HiBitsFlag; + Int32 RunLength, InitRL; /* must be 32-bit at least */ + + UInt32 Size; + UInt32 GlueCount; + Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; + UInt32 AlignOffset; + + Byte Indx2Units[PPMD_NUM_INDEXES]; + Byte Units2Indx[128]; + CPpmd_Void_Ref FreeList[PPMD_NUM_INDEXES]; + Byte NS2Indx[256], NS2BSIndx[256], HB2Flag[256]; + CPpmd_See DummySee, See[25][16]; + UInt16 BinSumm[128][64]; +} CPpmd7; + +/* ---------- Decode ---------- */ + +typedef struct +{ + UInt32 (*GetThreshold)(void *p, UInt32 total); + void (*Decode)(void *p, UInt32 start, UInt32 size); + UInt32 (*DecodeBit)(void *p, UInt32 size0); +} IPpmd7_RangeDec; + +typedef struct +{ + IPpmd7_RangeDec p; + UInt32 Range; + UInt32 Code; + UInt32 Low; + UInt32 Bottom; + IByteIn *Stream; +} CPpmd7z_RangeDec; + +/* ---------- Encode ---------- */ + +typedef struct +{ + UInt64 Low; + UInt32 Range; + Byte Cache; + UInt64 CacheSize; + IByteOut *Stream; +} CPpmd7z_RangeEnc; + +typedef struct +{ + /* Base Functions */ + void (*Ppmd7_Construct)(CPpmd7 *p); + Bool (*Ppmd7_Alloc)(CPpmd7 *p, UInt32 size, ISzAlloc *alloc); + void (*Ppmd7_Free)(CPpmd7 *p, ISzAlloc *alloc); + void (*Ppmd7_Init)(CPpmd7 *p, unsigned maxOrder); + #define Ppmd7_WasAllocated(p) ((p)->Base != NULL) + + /* Decode Functions */ + void (*Ppmd7z_RangeDec_CreateVTable)(CPpmd7z_RangeDec *p); + void (*PpmdRAR_RangeDec_CreateVTable)(CPpmd7z_RangeDec *p); + Bool (*Ppmd7z_RangeDec_Init)(CPpmd7z_RangeDec *p); + Bool (*PpmdRAR_RangeDec_Init)(CPpmd7z_RangeDec *p); + #define Ppmd7z_RangeDec_IsFinishedOK(p) ((p)->Code == 0) + int (*Ppmd7_DecodeSymbol)(CPpmd7 *p, IPpmd7_RangeDec *rc); + + /* Encode Functions */ + void (*Ppmd7z_RangeEnc_Init)(CPpmd7z_RangeEnc *p); + void (*Ppmd7z_RangeEnc_FlushData)(CPpmd7z_RangeEnc *p); + + void (*Ppmd7_EncodeSymbol)(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol); +} IPpmd7; + +extern const IPpmd7 __archive_ppmd7_functions; +#endif diff --git a/libarchive/archive_ppmd_private.h b/libarchive/archive_ppmd_private.h new file mode 100644 index 0000000..2666768 --- /dev/null +++ b/libarchive/archive_ppmd_private.h @@ -0,0 +1,158 @@ +/* Ppmd.h -- PPMD codec common code +2010-03-12 : Igor Pavlov : Public domain +This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_PPMD_PRIVATE_H_INCLUDED +#define ARCHIVE_PPMD_PRIVATE_H_INCLUDED + +#include + +#include "archive_read_private.h" + +/*** Begin defined in Types.h ***/ + +#if !defined(ZCONF_H) +typedef unsigned char Byte; +#endif +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +typedef int Bool; +#define True 1 +#define False 0 + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + struct archive_read *a; + Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ +} IByteIn; + +typedef struct +{ + struct archive_write *a; + void (*Write)(void *p, Byte b); +} IByteOut; + + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +/*** End defined in Types.h ***/ +/*** Begin defined in CpuArch.h ***/ + +#if defined(_M_IX86) || defined(__i386__) +#define MY_CPU_X86 +#endif + +#if defined(MY_CPU_X86) || defined(_M_ARM) +#define MY_CPU_32BIT +#endif + +#ifdef MY_CPU_32BIT +#define PPMD_32BIT +#endif + +/*** End defined in CpuArch.h ***/ + +#define PPMD_INT_BITS 7 +#define PPMD_PERIOD_BITS 7 +#define PPMD_BIN_SCALE (1 << (PPMD_INT_BITS + PPMD_PERIOD_BITS)) + +#define PPMD_GET_MEAN_SPEC(summ, shift, round) (((summ) + (1 << ((shift) - (round)))) >> (shift)) +#define PPMD_GET_MEAN(summ) PPMD_GET_MEAN_SPEC((summ), PPMD_PERIOD_BITS, 2) +#define PPMD_UPDATE_PROB_0(prob) ((prob) + (1 << PPMD_INT_BITS) - PPMD_GET_MEAN(prob)) +#define PPMD_UPDATE_PROB_1(prob) ((prob) - PPMD_GET_MEAN(prob)) + +#define PPMD_N1 4 +#define PPMD_N2 4 +#define PPMD_N3 4 +#define PPMD_N4 ((128 + 3 - 1 * PPMD_N1 - 2 * PPMD_N2 - 3 * PPMD_N3) / 4) +#define PPMD_NUM_INDEXES (PPMD_N1 + PPMD_N2 + PPMD_N3 + PPMD_N4) + +/* SEE-contexts for PPM-contexts with masked symbols */ +typedef struct +{ + UInt16 Summ; /* Freq */ + Byte Shift; /* Speed of Freq change; low Shift is for fast change */ + Byte Count; /* Count to next change of Shift */ +} CPpmd_See; + +#define Ppmd_See_Update(p) if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) \ + { (p)->Summ <<= 1; (p)->Count = (Byte)(3 << (p)->Shift++); } + +typedef struct +{ + Byte Symbol; + Byte Freq; + UInt16 SuccessorLow; + UInt16 SuccessorHigh; +} CPpmd_State; + +typedef + #ifdef PPMD_32BIT + CPpmd_State * + #else + UInt32 + #endif + CPpmd_State_Ref; + +typedef + #ifdef PPMD_32BIT + void * + #else + UInt32 + #endif + CPpmd_Void_Ref; + +typedef + #ifdef PPMD_32BIT + Byte * + #else + UInt32 + #endif + CPpmd_Byte_Ref; + +#define PPMD_SetAllBitsIn256Bytes(p) \ + { unsigned i; for (i = 0; i < 256 / sizeof(p[0]); i += 8) { \ + p[i+7] = p[i+6] = p[i+5] = p[i+4] = p[i+3] = p[i+2] = p[i+1] = p[i+0] = ~(size_t)0; }} + +#endif diff --git a/libarchive/archive_private.h b/libarchive/archive_private.h new file mode 100644 index 0000000..9941e96 --- /dev/null +++ b/libarchive/archive_private.h @@ -0,0 +1,150 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_private.h 201098 2009-12-28 02:58:14Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_PRIVATE_H_INCLUDED +#define ARCHIVE_PRIVATE_H_INCLUDED + +#if HAVE_ICONV_H +#include +#endif + +#include "archive.h" +#include "archive_string.h" + +#if defined(__GNUC__) && (__GNUC__ > 2 || \ + (__GNUC__ == 2 && __GNUC_MINOR__ >= 5)) +#define __LA_DEAD __attribute__((__noreturn__)) +#else +#define __LA_DEAD +#endif + +#define ARCHIVE_WRITE_MAGIC (0xb0c5c0deU) +#define ARCHIVE_READ_MAGIC (0xdeb0c5U) +#define ARCHIVE_WRITE_DISK_MAGIC (0xc001b0c5U) +#define ARCHIVE_READ_DISK_MAGIC (0xbadb0c5U) + +#define ARCHIVE_STATE_NEW 1U +#define ARCHIVE_STATE_HEADER 2U +#define ARCHIVE_STATE_DATA 4U +#define ARCHIVE_STATE_EOF 0x10U +#define ARCHIVE_STATE_CLOSED 0x20U +#define ARCHIVE_STATE_FATAL 0x8000U +#define ARCHIVE_STATE_ANY (0xFFFFU & ~ARCHIVE_STATE_FATAL) + +struct archive_vtable { + int (*archive_close)(struct archive *); + int (*archive_free)(struct archive *); + int (*archive_write_header)(struct archive *, + struct archive_entry *); + int (*archive_write_finish_entry)(struct archive *); + ssize_t (*archive_write_data)(struct archive *, + const void *, size_t); + ssize_t (*archive_write_data_block)(struct archive *, + const void *, size_t, int64_t); + + int (*archive_read_next_header)(struct archive *, + struct archive_entry **); + int (*archive_read_next_header2)(struct archive *, + struct archive_entry *); + int (*archive_read_data_block)(struct archive *, + const void **, size_t *, int64_t *); + + int (*archive_filter_count)(struct archive *); + int64_t (*archive_filter_bytes)(struct archive *, int); + int (*archive_filter_code)(struct archive *, int); + const char * (*archive_filter_name)(struct archive *, int); +}; + +struct archive_string_conv; + +struct archive { + /* + * The magic/state values are used to sanity-check the + * client's usage. If an API function is called at a + * ridiculous time, or the client passes us an invalid + * pointer, these values allow me to catch that. + */ + unsigned int magic; + unsigned int state; + + /* + * Some public API functions depend on the "real" type of the + * archive object. + */ + struct archive_vtable *vtable; + + int archive_format; + const char *archive_format_name; + + int compression_code; /* Currently active compression. */ + const char *compression_name; + + /* Number of file entries processed. */ + int file_count; + + int archive_error_number; + const char *error; + struct archive_string error_string; + + char *current_code; + unsigned current_codepage; /* Current ACP(ANSI CodePage). */ + unsigned current_oemcp; /* Current OEMCP(OEM CodePage). */ + struct archive_string_conv *sconv; +}; + +/* Check magic value and state; return(ARCHIVE_FATAL) if it isn't valid. */ +int __archive_check_magic(struct archive *, unsigned int magic, + unsigned int state, const char *func); +#define archive_check_magic(a, expected_magic, allowed_states, function_name) \ + do { \ + int magic_test = __archive_check_magic((a), (expected_magic), \ + (allowed_states), (function_name)); \ + if (magic_test == ARCHIVE_FATAL) \ + return ARCHIVE_FATAL; \ + } while (0) + +void __archive_errx(int retvalue, const char *msg) __LA_DEAD; + +int __archive_mktemp(const char *tmpdir); + +int __archive_clean(struct archive *); + +#define err_combine(a,b) ((a) < (b) ? (a) : (b)) + +#if defined(__BORLANDC__) || (defined(_MSC_VER) && _MSC_VER <= 1300) +# define ARCHIVE_LITERAL_LL(x) x##i64 +# define ARCHIVE_LITERAL_ULL(x) x##ui64 +#else +# define ARCHIVE_LITERAL_LL(x) x##ll +# define ARCHIVE_LITERAL_ULL(x) x##ull +#endif + +#endif diff --git a/libarchive/archive_rb.c b/libarchive/archive_rb.c new file mode 100644 index 0000000..f8035d9 --- /dev/null +++ b/libarchive/archive_rb.c @@ -0,0 +1,701 @@ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Matt Thomas . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Based on: NetBSD: rb.c,v 1.6 2010/04/30 13:58:09 joerg Exp + */ + +#include "archive_platform.h" + +#include + +#include "archive_rb.h" + +/* Keep in sync with archive_rb.h */ +#define RB_DIR_LEFT 0 +#define RB_DIR_RIGHT 1 +#define RB_DIR_OTHER 1 +#define rb_left rb_nodes[RB_DIR_LEFT] +#define rb_right rb_nodes[RB_DIR_RIGHT] + +#define RB_FLAG_POSITION 0x2 +#define RB_FLAG_RED 0x1 +#define RB_FLAG_MASK (RB_FLAG_POSITION|RB_FLAG_RED) +#define RB_FATHER(rb) \ + ((struct archive_rb_node *)((rb)->rb_info & ~RB_FLAG_MASK)) +#define RB_SET_FATHER(rb, father) \ + ((void)((rb)->rb_info = (uintptr_t)(father)|((rb)->rb_info & RB_FLAG_MASK))) + +#define RB_SENTINEL_P(rb) ((rb) == NULL) +#define RB_LEFT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_left) +#define RB_RIGHT_SENTINEL_P(rb) RB_SENTINEL_P((rb)->rb_right) +#define RB_FATHER_SENTINEL_P(rb) RB_SENTINEL_P(RB_FATHER((rb))) +#define RB_CHILDLESS_P(rb) \ + (RB_SENTINEL_P(rb) || (RB_LEFT_SENTINEL_P(rb) && RB_RIGHT_SENTINEL_P(rb))) +#define RB_TWOCHILDREN_P(rb) \ + (!RB_SENTINEL_P(rb) && !RB_LEFT_SENTINEL_P(rb) && !RB_RIGHT_SENTINEL_P(rb)) + +#define RB_POSITION(rb) \ + (((rb)->rb_info & RB_FLAG_POSITION) ? RB_DIR_RIGHT : RB_DIR_LEFT) +#define RB_RIGHT_P(rb) (RB_POSITION(rb) == RB_DIR_RIGHT) +#define RB_LEFT_P(rb) (RB_POSITION(rb) == RB_DIR_LEFT) +#define RB_RED_P(rb) (!RB_SENTINEL_P(rb) && ((rb)->rb_info & RB_FLAG_RED) != 0) +#define RB_BLACK_P(rb) (RB_SENTINEL_P(rb) || ((rb)->rb_info & RB_FLAG_RED) == 0) +#define RB_MARK_RED(rb) ((void)((rb)->rb_info |= RB_FLAG_RED)) +#define RB_MARK_BLACK(rb) ((void)((rb)->rb_info &= ~RB_FLAG_RED)) +#define RB_INVERT_COLOR(rb) ((void)((rb)->rb_info ^= RB_FLAG_RED)) +#define RB_ROOT_P(rbt, rb) ((rbt)->rbt_root == (rb)) +#define RB_SET_POSITION(rb, position) \ + ((void)((position) ? ((rb)->rb_info |= RB_FLAG_POSITION) : \ + ((rb)->rb_info &= ~RB_FLAG_POSITION))) +#define RB_ZERO_PROPERTIES(rb) ((void)((rb)->rb_info &= ~RB_FLAG_MASK)) +#define RB_COPY_PROPERTIES(dst, src) \ + ((void)((dst)->rb_info ^= ((dst)->rb_info ^ (src)->rb_info) & RB_FLAG_MASK)) +#define RB_SWAP_PROPERTIES(a, b) do { \ + uintptr_t xorinfo = ((a)->rb_info ^ (b)->rb_info) & RB_FLAG_MASK; \ + (a)->rb_info ^= xorinfo; \ + (b)->rb_info ^= xorinfo; \ + } while (/*CONSTCOND*/ 0) + +static void __archive_rb_tree_insert_rebalance(struct archive_rb_tree *, + struct archive_rb_node *); +static void __archive_rb_tree_removal_rebalance(struct archive_rb_tree *, + struct archive_rb_node *, unsigned int); + +#define RB_SENTINEL_NODE NULL + +#define T 1 +#define F 0 + +void +__archive_rb_tree_init(struct archive_rb_tree *rbt, + const struct archive_rb_tree_ops *ops) +{ + rbt->rbt_ops = ops; + *((const struct archive_rb_node **)&rbt->rbt_root) = RB_SENTINEL_NODE; +} + +struct archive_rb_node * +__archive_rb_tree_find_node(struct archive_rb_tree *rbt, const void *key) +{ + archive_rbto_compare_key_fn compare_key = rbt->rbt_ops->rbto_compare_key; + struct archive_rb_node *parent = rbt->rbt_root; + + while (!RB_SENTINEL_P(parent)) { + const signed int diff = (*compare_key)(parent, key); + if (diff == 0) + return parent; + parent = parent->rb_nodes[diff > 0]; + } + + return NULL; +} + +struct archive_rb_node * +__archive_rb_tree_find_node_geq(struct archive_rb_tree *rbt, const void *key) +{ + archive_rbto_compare_key_fn compare_key = rbt->rbt_ops->rbto_compare_key; + struct archive_rb_node *parent = rbt->rbt_root; + struct archive_rb_node *last = NULL; + + while (!RB_SENTINEL_P(parent)) { + const signed int diff = (*compare_key)(parent, key); + if (diff == 0) + return parent; + if (diff < 0) + last = parent; + parent = parent->rb_nodes[diff > 0]; + } + + return last; +} + +struct archive_rb_node * +__archive_rb_tree_find_node_leq(struct archive_rb_tree *rbt, const void *key) +{ + archive_rbto_compare_key_fn compare_key = rbt->rbt_ops->rbto_compare_key; + struct archive_rb_node *parent = rbt->rbt_root; + struct archive_rb_node *last = NULL; + + while (!RB_SENTINEL_P(parent)) { + const signed int diff = (*compare_key)(parent, key); + if (diff == 0) + return parent; + if (diff > 0) + last = parent; + parent = parent->rb_nodes[diff > 0]; + } + + return last; +} + +int +__archive_rb_tree_insert_node(struct archive_rb_tree *rbt, + struct archive_rb_node *self) +{ + archive_rbto_compare_nodes_fn compare_nodes = rbt->rbt_ops->rbto_compare_nodes; + struct archive_rb_node *parent, *tmp; + unsigned int position; + int rebalance; + + tmp = rbt->rbt_root; + /* + * This is a hack. Because rbt->rbt_root is just a + * struct archive_rb_node *, just like rb_node->rb_nodes[RB_DIR_LEFT], + * we can use this fact to avoid a lot of tests for root and know + * that even at root, updating + * RB_FATHER(rb_node)->rb_nodes[RB_POSITION(rb_node)] will + * update rbt->rbt_root. + */ + parent = (struct archive_rb_node *)(void *)&rbt->rbt_root; + position = RB_DIR_LEFT; + + /* + * Find out where to place this new leaf. + */ + while (!RB_SENTINEL_P(tmp)) { + const signed int diff = (*compare_nodes)(tmp, self); + if (diff == 0) { + /* + * Node already exists; don't insert. + */ + return F; + } + parent = tmp; + position = (diff > 0); + tmp = parent->rb_nodes[position]; + } + + /* + * Initialize the node and insert as a leaf into the tree. + */ + RB_SET_FATHER(self, parent); + RB_SET_POSITION(self, position); + if (parent == (struct archive_rb_node *)(void *)&rbt->rbt_root) { + RB_MARK_BLACK(self); /* root is always black */ + rebalance = F; + } else { + /* + * All new nodes are colored red. We only need to rebalance + * if our parent is also red. + */ + RB_MARK_RED(self); + rebalance = RB_RED_P(parent); + } + self->rb_left = parent->rb_nodes[position]; + self->rb_right = parent->rb_nodes[position]; + parent->rb_nodes[position] = self; + + /* + * Rebalance tree after insertion + */ + if (rebalance) + __archive_rb_tree_insert_rebalance(rbt, self); + + return T; +} + +/* + * Swap the location and colors of 'self' and its child @ which. The child + * can not be a sentinel node. This is our rotation function. However, + * since it preserves coloring, it great simplifies both insertion and + * removal since rotation almost always involves the exchanging of colors + * as a separate step. + */ +/*ARGSUSED*/ +static void +__archive_rb_tree_reparent_nodes( + struct archive_rb_node *old_father, const unsigned int which) +{ + const unsigned int other = which ^ RB_DIR_OTHER; + struct archive_rb_node * const grandpa = RB_FATHER(old_father); + struct archive_rb_node * const old_child = old_father->rb_nodes[which]; + struct archive_rb_node * const new_father = old_child; + struct archive_rb_node * const new_child = old_father; + + /* + * Exchange descendant linkages. + */ + grandpa->rb_nodes[RB_POSITION(old_father)] = new_father; + new_child->rb_nodes[which] = old_child->rb_nodes[other]; + new_father->rb_nodes[other] = new_child; + + /* + * Update ancestor linkages + */ + RB_SET_FATHER(new_father, grandpa); + RB_SET_FATHER(new_child, new_father); + + /* + * Exchange properties between new_father and new_child. The only + * change is that new_child's position is now on the other side. + */ + RB_SWAP_PROPERTIES(new_father, new_child); + RB_SET_POSITION(new_child, other); + + /* + * Make sure to reparent the new child to ourself. + */ + if (!RB_SENTINEL_P(new_child->rb_nodes[which])) { + RB_SET_FATHER(new_child->rb_nodes[which], new_child); + RB_SET_POSITION(new_child->rb_nodes[which], which); + } + +} + +static void +__archive_rb_tree_insert_rebalance(struct archive_rb_tree *rbt, + struct archive_rb_node *self) +{ + struct archive_rb_node * father = RB_FATHER(self); + struct archive_rb_node * grandpa; + struct archive_rb_node * uncle; + unsigned int which; + unsigned int other; + + for (;;) { + /* + * We are red and our parent is red, therefore we must have a + * grandfather and he must be black. + */ + grandpa = RB_FATHER(father); + which = (father == grandpa->rb_right); + other = which ^ RB_DIR_OTHER; + uncle = grandpa->rb_nodes[other]; + + if (RB_BLACK_P(uncle)) + break; + + /* + * Case 1: our uncle is red + * Simply invert the colors of our parent and + * uncle and make our grandparent red. And + * then solve the problem up at his level. + */ + RB_MARK_BLACK(uncle); + RB_MARK_BLACK(father); + if (RB_ROOT_P(rbt, grandpa)) { + /* + * If our grandpa is root, don't bother + * setting him to red, just return. + */ + return; + } + RB_MARK_RED(grandpa); + self = grandpa; + father = RB_FATHER(self); + if (RB_BLACK_P(father)) { + /* + * If our greatgrandpa is black, we're done. + */ + return; + } + } + + /* + * Case 2&3: our uncle is black. + */ + if (self == father->rb_nodes[other]) { + /* + * Case 2: we are on the same side as our uncle + * Swap ourselves with our parent so this case + * becomes case 3. Basically our parent becomes our + * child. + */ + __archive_rb_tree_reparent_nodes(father, other); + } + /* + * Case 3: we are opposite a child of a black uncle. + * Swap our parent and grandparent. Since our grandfather + * is black, our father will become black and our new sibling + * (former grandparent) will become red. + */ + __archive_rb_tree_reparent_nodes(grandpa, which); + + /* + * Final step: Set the root to black. + */ + RB_MARK_BLACK(rbt->rbt_root); +} + +static void +__archive_rb_tree_prune_node(struct archive_rb_tree *rbt, + struct archive_rb_node *self, int rebalance) +{ + const unsigned int which = RB_POSITION(self); + struct archive_rb_node *father = RB_FATHER(self); + + /* + * Since we are childless, we know that self->rb_left is pointing + * to the sentinel node. + */ + father->rb_nodes[which] = self->rb_left; + + /* + * Rebalance if requested. + */ + if (rebalance) + __archive_rb_tree_removal_rebalance(rbt, father, which); +} + +/* + * When deleting an interior node + */ +static void +__archive_rb_tree_swap_prune_and_rebalance(struct archive_rb_tree *rbt, + struct archive_rb_node *self, struct archive_rb_node *standin) +{ + const unsigned int standin_which = RB_POSITION(standin); + unsigned int standin_other = standin_which ^ RB_DIR_OTHER; + struct archive_rb_node *standin_son; + struct archive_rb_node *standin_father = RB_FATHER(standin); + int rebalance = RB_BLACK_P(standin); + + if (standin_father == self) { + /* + * As a child of self, any childen would be opposite of + * our parent. + */ + standin_son = standin->rb_nodes[standin_which]; + } else { + /* + * Since we aren't a child of self, any childen would be + * on the same side as our parent. + */ + standin_son = standin->rb_nodes[standin_other]; + } + + if (RB_RED_P(standin_son)) { + /* + * We know we have a red child so if we flip it to black + * we don't have to rebalance. + */ + RB_MARK_BLACK(standin_son); + rebalance = F; + + if (standin_father != self) { + /* + * Change the son's parentage to point to his grandpa. + */ + RB_SET_FATHER(standin_son, standin_father); + RB_SET_POSITION(standin_son, standin_which); + } + } + + if (standin_father == self) { + /* + * If we are about to delete the standin's father, then when + * we call rebalance, we need to use ourselves as our father. + * Otherwise remember our original father. Also, sincef we are + * our standin's father we only need to reparent the standin's + * brother. + * + * | R --> S | + * | Q S --> Q T | + * | t --> | + * + * Have our son/standin adopt his brother as his new son. + */ + standin_father = standin; + } else { + /* + * | R --> S . | + * | / \ | T --> / \ | / | + * | ..... | S --> ..... | T | + * + * Sever standin's connection to his father. + */ + standin_father->rb_nodes[standin_which] = standin_son; + /* + * Adopt the far son. + */ + standin->rb_nodes[standin_other] = self->rb_nodes[standin_other]; + RB_SET_FATHER(standin->rb_nodes[standin_other], standin); + /* + * Use standin_other because we need to preserve standin_which + * for the removal_rebalance. + */ + standin_other = standin_which; + } + + /* + * Move the only remaining son to our standin. If our standin is our + * son, this will be the only son needed to be moved. + */ + standin->rb_nodes[standin_other] = self->rb_nodes[standin_other]; + RB_SET_FATHER(standin->rb_nodes[standin_other], standin); + + /* + * Now copy the result of self to standin and then replace + * self with standin in the tree. + */ + RB_COPY_PROPERTIES(standin, self); + RB_SET_FATHER(standin, RB_FATHER(self)); + RB_FATHER(standin)->rb_nodes[RB_POSITION(standin)] = standin; + + if (rebalance) + __archive_rb_tree_removal_rebalance(rbt, standin_father, standin_which); +} + +/* + * We could do this by doing + * __archive_rb_tree_node_swap(rbt, self, which); + * __archive_rb_tree_prune_node(rbt, self, F); + * + * But it's more efficient to just evalate and recolor the child. + */ +static void +__archive_rb_tree_prune_blackred_branch( + struct archive_rb_node *self, unsigned int which) +{ + struct archive_rb_node *father = RB_FATHER(self); + struct archive_rb_node *son = self->rb_nodes[which]; + + /* + * Remove ourselves from the tree and give our former child our + * properties (position, color, root). + */ + RB_COPY_PROPERTIES(son, self); + father->rb_nodes[RB_POSITION(son)] = son; + RB_SET_FATHER(son, father); +} +/* + * + */ +void +__archive_rb_tree_remove_node(struct archive_rb_tree *rbt, + struct archive_rb_node *self) +{ + struct archive_rb_node *standin; + unsigned int which; + + /* + * In the following diagrams, we (the node to be removed) are S. Red + * nodes are lowercase. T could be either red or black. + * + * Remember the major axiom of the red-black tree: the number of + * black nodes from the root to each leaf is constant across all + * leaves, only the number of red nodes varies. + * + * Thus removing a red leaf doesn't require any other changes to a + * red-black tree. So if we must remove a node, attempt to rearrange + * the tree so we can remove a red node. + * + * The simpliest case is a childless red node or a childless root node: + * + * | T --> T | or | R --> * | + * | s --> * | + */ + if (RB_CHILDLESS_P(self)) { + const int rebalance = RB_BLACK_P(self) && !RB_ROOT_P(rbt, self); + __archive_rb_tree_prune_node(rbt, self, rebalance); + return; + } + if (!RB_TWOCHILDREN_P(self)) { + /* + * The next simpliest case is the node we are deleting is + * black and has one red child. + * + * | T --> T --> T | + * | S --> R --> R | + * | r --> s --> * | + */ + which = RB_LEFT_SENTINEL_P(self) ? RB_DIR_RIGHT : RB_DIR_LEFT; + __archive_rb_tree_prune_blackred_branch(self, which); + return; + } + + /* + * We invert these because we prefer to remove from the inside of + * the tree. + */ + which = RB_POSITION(self) ^ RB_DIR_OTHER; + + /* + * Let's find the node closes to us opposite of our parent + * Now swap it with ourself, "prune" it, and rebalance, if needed. + */ + standin = __archive_rb_tree_iterate(rbt, self, which); + __archive_rb_tree_swap_prune_and_rebalance(rbt, self, standin); +} + +static void +__archive_rb_tree_removal_rebalance(struct archive_rb_tree *rbt, + struct archive_rb_node *parent, unsigned int which) +{ + + while (RB_BLACK_P(parent->rb_nodes[which])) { + unsigned int other = which ^ RB_DIR_OTHER; + struct archive_rb_node *brother = parent->rb_nodes[other]; + + /* + * For cases 1, 2a, and 2b, our brother's children must + * be black and our father must be black + */ + if (RB_BLACK_P(parent) + && RB_BLACK_P(brother->rb_left) + && RB_BLACK_P(brother->rb_right)) { + if (RB_RED_P(brother)) { + /* + * Case 1: Our brother is red, swap its + * position (and colors) with our parent. + * This should now be case 2b (unless C or E + * has a red child which is case 3; thus no + * explicit branch to case 2b). + * + * B -> D + * A d -> b E + * C E -> A C + */ + __archive_rb_tree_reparent_nodes(parent, other); + brother = parent->rb_nodes[other]; + } else { + /* + * Both our parent and brother are black. + * Change our brother to red, advance up rank + * and go through the loop again. + * + * B -> *B + * *A D -> A d + * C E -> C E + */ + RB_MARK_RED(brother); + if (RB_ROOT_P(rbt, parent)) + return; /* root == parent == black */ + which = RB_POSITION(parent); + parent = RB_FATHER(parent); + continue; + } + } + /* + * Avoid an else here so that case 2a above can hit either + * case 2b, 3, or 4. + */ + if (RB_RED_P(parent) + && RB_BLACK_P(brother) + && RB_BLACK_P(brother->rb_left) + && RB_BLACK_P(brother->rb_right)) { + /* + * We are black, our father is red, our brother and + * both nephews are black. Simply invert/exchange the + * colors of our father and brother (to black and red + * respectively). + * + * | f --> F | + * | * B --> * b | + * | N N --> N N | + */ + RB_MARK_BLACK(parent); + RB_MARK_RED(brother); + break; /* We're done! */ + } else { + /* + * Our brother must be black and have at least one + * red child (it may have two). + */ + if (RB_BLACK_P(brother->rb_nodes[other])) { + /* + * Case 3: our brother is black, our near + * nephew is red, and our far nephew is black. + * Swap our brother with our near nephew. + * This result in a tree that matches case 4. + * (Our father could be red or black). + * + * | F --> F | + * | x B --> x B | + * | n --> n | + */ + __archive_rb_tree_reparent_nodes(brother, which); + brother = parent->rb_nodes[other]; + } + /* + * Case 4: our brother is black and our far nephew + * is red. Swap our father and brother locations and + * change our far nephew to black. (these can be + * done in either order so we change the color first). + * The result is a valid red-black tree and is a + * terminal case. (again we don't care about the + * father's color) + * + * If the father is red, we will get a red-black-black + * tree: + * | f -> f --> b | + * | B -> B --> F N | + * | n -> N --> | + * + * If the father is black, we will get an all black + * tree: + * | F -> F --> B | + * | B -> B --> F N | + * | n -> N --> | + * + * If we had two red nephews, then after the swap, + * our former father would have a red grandson. + */ + RB_MARK_BLACK(brother->rb_nodes[other]); + __archive_rb_tree_reparent_nodes(parent, other); + break; /* We're done! */ + } + } +} + +struct archive_rb_node * +__archive_rb_tree_iterate(struct archive_rb_tree *rbt, + struct archive_rb_node *self, const unsigned int direction) +{ + const unsigned int other = direction ^ RB_DIR_OTHER; + + if (self == NULL) { + self = rbt->rbt_root; + if (RB_SENTINEL_P(self)) + return NULL; + while (!RB_SENTINEL_P(self->rb_nodes[direction])) + self = self->rb_nodes[direction]; + return self; + } + /* + * We can't go any further in this direction. We proceed up in the + * opposite direction until our parent is in direction we want to go. + */ + if (RB_SENTINEL_P(self->rb_nodes[direction])) { + while (!RB_ROOT_P(rbt, self)) { + if (other == RB_POSITION(self)) + return RB_FATHER(self); + self = RB_FATHER(self); + } + return NULL; + } + + /* + * Advance down one in current direction and go down as far as possible + * in the opposite direction. + */ + self = self->rb_nodes[direction]; + while (!RB_SENTINEL_P(self->rb_nodes[other])) + self = self->rb_nodes[other]; + return self; +} diff --git a/libarchive/archive_rb.h b/libarchive/archive_rb.h new file mode 100644 index 0000000..4562e9e --- /dev/null +++ b/libarchive/archive_rb.h @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Matt Thomas . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Based on NetBSD: rb.h,v 1.13 2009/08/16 10:57:01 yamt Exp + */ +#ifndef ARCHIVE_RB_H_ +#define ARCHIVE_RB_H_ + +struct archive_rb_node { + struct archive_rb_node *rb_nodes[2]; + /* + * rb_info contains the two flags and the parent back pointer. + * We put the two flags in the low two bits since we know that + * rb_node will have an alignment of 4 or 8 bytes. + */ + uintptr_t rb_info; +}; + +#define ARCHIVE_RB_DIR_LEFT 0 +#define ARCHIVE_RB_DIR_RIGHT 1 + +#define ARCHIVE_RB_TREE_MIN(T) \ + __archive_rb_tree_iterate((T), NULL, ARCHIVE_RB_DIR_LEFT) +#define ARCHIVE_RB_TREE_MAX(T) \ + __archive_rb_tree_iterate((T), NULL, ARCHIVE_RB_DIR_RIGHT) +#define ARCHIVE_RB_TREE_FOREACH(N, T) \ + for ((N) = ARCHIVE_RB_TREE_MIN(T); (N); \ + (N) = __archive_rb_tree_iterate((T), (N), ARCHIVE_RB_DIR_RIGHT)) +#define ARCHIVE_RB_TREE_FOREACH_REVERSE(N, T) \ + for ((N) = ARCHIVE_RB_TREE_MAX(T); (N); \ + (N) = __archive_rb_tree_iterate((T), (N), ARCHIVE_RB_DIR_LEFT)) + +/* + * archive_rbto_compare_nodes_fn: + * return a positive value if the first node < the second node. + * return a negative value if the first node > the second node. + * return 0 if they are considered same. + * + * archive_rbto_compare_key_fn: + * return a positive value if the node < the key. + * return a negative value if the node > the key. + * return 0 if they are considered same. + */ + +typedef signed int (*const archive_rbto_compare_nodes_fn)(const struct archive_rb_node *, + const struct archive_rb_node *); +typedef signed int (*const archive_rbto_compare_key_fn)(const struct archive_rb_node *, + const void *); + +struct archive_rb_tree_ops { + archive_rbto_compare_nodes_fn rbto_compare_nodes; + archive_rbto_compare_key_fn rbto_compare_key; +}; + +struct archive_rb_tree { + struct archive_rb_node *rbt_root; + const struct archive_rb_tree_ops *rbt_ops; +}; + +void __archive_rb_tree_init(struct archive_rb_tree *, + const struct archive_rb_tree_ops *); +int __archive_rb_tree_insert_node(struct archive_rb_tree *, + struct archive_rb_node *); +struct archive_rb_node * + __archive_rb_tree_find_node(struct archive_rb_tree *, const void *); +struct archive_rb_node * + __archive_rb_tree_find_node_geq(struct archive_rb_tree *, const void *); +struct archive_rb_node * + __archive_rb_tree_find_node_leq(struct archive_rb_tree *, const void *); +void __archive_rb_tree_remove_node(struct archive_rb_tree *, struct archive_rb_node *); +struct archive_rb_node * + __archive_rb_tree_iterate(struct archive_rb_tree *, + struct archive_rb_node *, const unsigned int); + +#endif /* ARCHIVE_RB_H_*/ diff --git a/libarchive/archive_read.3 b/libarchive/archive_read.3 new file mode 100644 index 0000000..70527fb --- /dev/null +++ b/libarchive/archive_read.3 @@ -0,0 +1,250 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read.3 191595 2009-04-27 20:13:13Z kientzle $ +.\" +.Dd March 23, 2011 +.Dt archive_read 3 +.Os +.Sh NAME +.Nm archive_read +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Sh DESCRIPTION +These functions provide a complete API for reading streaming archives. +The general process is to first create the +.Tn struct archive +object, set options, initialize the reader, iterate over the archive +headers and associated data, then close the archive and release all +resources. +.\" +.Ss Create archive object +See +.Xr archive_read_new 3 . +.Pp +To read an archive, you must first obtain an initialized +.Tn struct archive +object from +.Fn archive_read_new . +.\" +.Ss Enable filters and formats +See +.Xr archive_read_filter 3 +and +.Xr archive_read_format 3 . +.Pp +You can then modify this object for the desired operations with the +various +.Fn archive_read_set_XXX +and +.Fn archive_read_support_XXX +functions. +In particular, you will need to invoke appropriate +.Fn archive_read_support_XXX +functions to enable the corresponding compression and format +support. +Note that these latter functions perform two distinct operations: +they cause the corresponding support code to be linked into your +program, and they enable the corresponding auto-detect code. +Unless you have specific constraints, you will generally want +to invoke +.Fn archive_read_support_filter_all +and +.Fn archive_read_support_format_all +to enable auto-detect for all formats and compression types +currently supported by the library. +.\" +.Ss Set options +See +.Xr archive_read_set_options 3 . +.\" +.Ss Open archive +See +.Xr archive_read_open 3 . +.Pp +Once you have prepared the +.Tn struct archive +object, you call +.Fn archive_read_open +to actually open the archive and prepare it for reading. +There are several variants of this function; +the most basic expects you to provide pointers to several +functions that can provide blocks of bytes from the archive. +There are convenience forms that allow you to +specify a filename, file descriptor, +.Ft "FILE *" +object, or a block of memory from which to read the archive data. +Note that the core library makes no assumptions about the +size of the blocks read; +callback functions are free to read whatever block size is +most appropriate for the medium. +.\" +.Ss Consume archive +See +.Xr archive_read_header 3 , +.Xr archive_read_data 3 +and +.Xr archive_read_extract 3 . +.Pp +Each archive entry consists of a header followed by a certain +amount of data. +You can obtain the next header with +.Fn archive_read_next_header , +which returns a pointer to an +.Tn struct archive_entry +structure with information about the current archive element. +If the entry is a regular file, then the header will be followed +by the file data. +You can use +.Fn archive_read_data +(which works much like the +.Xr read 2 +system call) +to read this data from the archive, or +.Fn archive_read_data_block +which provides a slightly more efficient interface. +You may prefer to use the higher-level +.Fn archive_read_data_skip , +which reads and discards the data for this entry, +.Fn archive_read_data_to_file , +which copies the data to the provided file descriptor, or +.Fn archive_read_extract , +which recreates the specified entry on disk and copies data +from the archive. +In particular, note that +.Fn archive_read_extract +uses the +.Tn struct archive_entry +structure that you provide it, which may differ from the +entry just read from the archive. +In particular, many applications will want to override the +pathname, file permissions, or ownership. +.\" +.Ss Release resources +See +.Xr archive_read_free 3 . +.Pp +Once you have finished reading data from the archive, you +should call +.Fn archive_read_close +to close the archive, then call +.Fn archive_read_free +to release all resources, including all memory allocated by the library. +.\" +.Sh EXAMPLE +The following illustrates basic usage of the library. +In this example, +the callback functions are simply wrappers around the standard +.Xr open 2 , +.Xr read 2 , +and +.Xr close 2 +system calls. +.Bd -literal -offset indent +void +list_archive(const char *name) +{ + struct mydata *mydata; + struct archive *a; + struct archive_entry *entry; + + mydata = malloc(sizeof(struct mydata)); + a = archive_read_new(); + mydata->name = name; + archive_read_support_filter_all(a); + archive_read_support_format_all(a); + archive_read_open(a, mydata, myopen, myread, myclose); + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + printf("%s\en",archive_entry_pathname(entry)); + archive_read_data_skip(a); + } + archive_read_free(a); + free(mydata); +} + +ssize_t +myread(struct archive *a, void *client_data, const void **buff) +{ + struct mydata *mydata = client_data; + + *buff = mydata->buff; + return (read(mydata->fd, mydata->buff, 10240)); +} + +int +myopen(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + mydata->fd = open(mydata->name, O_RDONLY); + return (mydata->fd >= 0 ? ARCHIVE_OK : ARCHIVE_FATAL); +} + +int +myclose(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + if (mydata->fd > 0) + close(mydata->fd); + return (ARCHIVE_OK); +} +.Ed +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read_new 3 , +.Xr archive_read_data 3 , +.Xr archive_read_extract 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_header 3 , +.Xr archive_read_open 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Many traditional archiver programs treat +empty files as valid empty archives. +For example, many implementations of +.Xr tar 1 +allow you to append entries to an empty file. +Of course, it is impossible to determine the format of an empty file +by inspecting the contents, so this library treats empty files as +having a special +.Dq empty +format. diff --git a/libarchive/archive_read.c b/libarchive/archive_read.c new file mode 100644 index 0000000..441be53 --- /dev/null +++ b/libarchive/archive_read.c @@ -0,0 +1,1358 @@ +/*- + * Copyright (c) 2003-2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the "essential" portions of the read API, that + * is, stuff that will probably always be used by any client that + * actually needs to read an archive. Optional pieces have been, as + * far as possible, separated out into separate files to avoid + * needlessly bloating statically-linked clients. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read.c 201157 2009-12-29 05:30:23Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#define minimum(a, b) (a < b ? a : b) + +static int choose_filters(struct archive_read *); +static int choose_format(struct archive_read *); +static void free_filters(struct archive_read *); +static int close_filters(struct archive_read *); +static struct archive_vtable *archive_read_vtable(void); +static int64_t _archive_filter_bytes(struct archive *, int); +static int _archive_filter_code(struct archive *, int); +static const char *_archive_filter_name(struct archive *, int); +static int _archive_filter_count(struct archive *); +static int _archive_read_close(struct archive *); +static int _archive_read_data_block(struct archive *, + const void **, size_t *, int64_t *); +static int _archive_read_free(struct archive *); +static int _archive_read_next_header(struct archive *, + struct archive_entry **); +static int _archive_read_next_header2(struct archive *, + struct archive_entry *); +static int64_t advance_file_pointer(struct archive_read_filter *, int64_t); + +static struct archive_vtable * +archive_read_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_filter_bytes = _archive_filter_bytes; + av.archive_filter_code = _archive_filter_code; + av.archive_filter_name = _archive_filter_name; + av.archive_filter_count = _archive_filter_count; + av.archive_read_data_block = _archive_read_data_block; + av.archive_read_next_header = _archive_read_next_header; + av.archive_read_next_header2 = _archive_read_next_header2; + av.archive_free = _archive_read_free; + av.archive_close = _archive_read_close; + inited = 1; + } + return (&av); +} + +/* + * Allocate, initialize and return a struct archive object. + */ +struct archive * +archive_read_new(void) +{ + struct archive_read *a; + + a = (struct archive_read *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_READ_MAGIC; + + a->archive.state = ARCHIVE_STATE_NEW; + a->entry = archive_entry_new2(&a->archive); + a->archive.vtable = archive_read_vtable(); + + return (&a->archive); +} + +/* + * Record the do-not-extract-to file. This belongs in archive_read_extract.c. + */ +void +archive_read_extract_set_skip_file(struct archive *_a, int64_t d, int64_t i) +{ + struct archive_read *a = (struct archive_read *)_a; + + if (ARCHIVE_OK != __archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_extract_set_skip_file")) + return; + a->skip_file_set = 1; + a->skip_file_dev = d; + a->skip_file_ino = i; +} + +/* + * Open the archive + */ +int +archive_read_open(struct archive *a, void *client_data, + archive_open_callback *client_opener, archive_read_callback *client_reader, + archive_close_callback *client_closer) +{ + /* Old archive_read_open() is just a thin shell around + * archive_read_open1. */ + archive_read_set_open_callback(a, client_opener); + archive_read_set_read_callback(a, client_reader); + archive_read_set_close_callback(a, client_closer); + archive_read_set_callback_data(a, client_data); + return archive_read_open1(a); +} + + +int +archive_read_open2(struct archive *a, void *client_data, + archive_open_callback *client_opener, + archive_read_callback *client_reader, + archive_skip_callback *client_skipper, + archive_close_callback *client_closer) +{ + /* Old archive_read_open2() is just a thin shell around + * archive_read_open1. */ + archive_read_set_callback_data(a, client_data); + archive_read_set_open_callback(a, client_opener); + archive_read_set_read_callback(a, client_reader); + archive_read_set_skip_callback(a, client_skipper); + archive_read_set_close_callback(a, client_closer); + return archive_read_open1(a); +} + +static ssize_t +client_read_proxy(struct archive_read_filter *self, const void **buff) +{ + ssize_t r; + r = (self->archive->client.reader)(&self->archive->archive, + self->data, buff); + return (r); +} + +static int64_t +client_skip_proxy(struct archive_read_filter *self, int64_t request) +{ + if (request < 0) + __archive_errx(1, "Negative skip requested."); + if (request == 0) + return 0; + + if (self->archive->client.skipper != NULL) { + /* Seek requests over 1GiB are broken down into + * multiple seeks. This avoids overflows when the + * requests get passed through 32-bit arguments. */ + int64_t skip_limit = (int64_t)1 << 30; + int64_t total = 0; + for (;;) { + int64_t get, ask = request; + if (ask > skip_limit) + ask = skip_limit; + get = (self->archive->client.skipper)(&self->archive->archive, + self->data, ask); + if (get == 0) + return (total); + request -= get; + total += get; + } + return total; + } else if (self->archive->client.seeker != NULL + && request > 64 * 1024) { + /* If the client provided a seeker but not a skipper, + * we can use the seeker to skip forward. + * + * Note: This isn't always a good idea. The client + * skipper is allowed to skip by less than requested + * if it needs to maintain block alignment. The + * seeker is not allowed to play such games, so using + * the seeker here may be a performance loss compared + * to just reading and discarding. That's why we + * only do this for skips of over 64k. + */ + int64_t before = self->position; + int64_t after = (self->archive->client.seeker)(&self->archive->archive, + self->data, request, SEEK_CUR); + if (after != before + request) + return ARCHIVE_FATAL; + return after - before; + } + return 0; +} + +static int64_t +client_seek_proxy(struct archive_read_filter *self, int64_t offset, int whence) +{ + /* DO NOT use the skipper here! If we transparently handled + * forward seek here by using the skipper, that will break + * other libarchive code that assumes a successful forward + * seek means it can also seek backwards. + */ + if (self->archive->client.seeker == NULL) + return (ARCHIVE_FAILED); + return (self->archive->client.seeker)(&self->archive->archive, + self->data, offset, whence); +} + +static int +client_close_proxy(struct archive_read_filter *self) +{ + int r = ARCHIVE_OK; + + if (self->archive->client.closer != NULL) + r = (self->archive->client.closer)((struct archive *)self->archive, + self->data); + return (r); +} + +int +archive_read_set_open_callback(struct archive *_a, + archive_open_callback *client_opener) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_open_callback"); + a->client.opener = client_opener; + return ARCHIVE_OK; +} + +int +archive_read_set_read_callback(struct archive *_a, + archive_read_callback *client_reader) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_read_callback"); + a->client.reader = client_reader; + return ARCHIVE_OK; +} + +int +archive_read_set_skip_callback(struct archive *_a, + archive_skip_callback *client_skipper) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_skip_callback"); + a->client.skipper = client_skipper; + return ARCHIVE_OK; +} + +int +archive_read_set_seek_callback(struct archive *_a, + archive_seek_callback *client_seeker) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_seek_callback"); + a->client.seeker = client_seeker; + return ARCHIVE_OK; +} + +int +archive_read_set_close_callback(struct archive *_a, + archive_close_callback *client_closer) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_close_callback"); + a->client.closer = client_closer; + return ARCHIVE_OK; +} + +int +archive_read_set_callback_data(struct archive *_a, void *client_data) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_set_callback_data"); + a->client.data = client_data; + return ARCHIVE_OK; +} + +int +archive_read_open1(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter *filter; + int slot, e; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_open"); + archive_clear_error(&a->archive); + + if (a->client.reader == NULL) { + archive_set_error(&a->archive, EINVAL, + "No reader function provided to archive_read_open"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + + /* Open data source. */ + if (a->client.opener != NULL) { + e =(a->client.opener)(&a->archive, a->client.data); + if (e != 0) { + /* If the open failed, call the closer to clean up. */ + if (a->client.closer) + (a->client.closer)(&a->archive, a->client.data); + return (e); + } + } + + filter = calloc(1, sizeof(*filter)); + if (filter == NULL) + return (ARCHIVE_FATAL); + filter->bidder = NULL; + filter->upstream = NULL; + filter->archive = a; + filter->data = a->client.data; + filter->read = client_read_proxy; + filter->skip = client_skip_proxy; + filter->seek = client_seek_proxy; + filter->close = client_close_proxy; + filter->name = "none"; + filter->code = ARCHIVE_COMPRESSION_NONE; + a->filter = filter; + + /* Build out the input pipeline. */ + e = choose_filters(a); + if (e < ARCHIVE_WARN) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + + slot = choose_format(a); + if (slot < 0) { + close_filters(a); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + a->format = &(a->formats[slot]); + + a->archive.state = ARCHIVE_STATE_HEADER; + return (e); +} + +/* + * Allow each registered stream transform to bid on whether + * it wants to handle this stream. Repeat until we've finished + * building the pipeline. + */ +static int +choose_filters(struct archive_read *a) +{ + int number_bidders, i, bid, best_bid; + struct archive_read_filter_bidder *bidder, *best_bidder; + struct archive_read_filter *filter; + ssize_t avail; + int r; + + for (;;) { + number_bidders = sizeof(a->bidders) / sizeof(a->bidders[0]); + + best_bid = 0; + best_bidder = NULL; + + bidder = a->bidders; + for (i = 0; i < number_bidders; i++, bidder++) { + if (bidder->bid != NULL) { + bid = (bidder->bid)(bidder, a->filter); + if (bid > best_bid) { + best_bid = bid; + best_bidder = bidder; + } + } + } + + /* If no bidder, we're done. */ + if (best_bidder == NULL) { + /* Verify the filter by asking it for some data. */ + __archive_read_filter_ahead(a->filter, 1, &avail); + if (avail < 0) { + close_filters(a); + free_filters(a); + return (ARCHIVE_FATAL); + } + a->archive.compression_name = a->filter->name; + a->archive.compression_code = a->filter->code; + return (ARCHIVE_OK); + } + + filter + = (struct archive_read_filter *)calloc(1, sizeof(*filter)); + if (filter == NULL) + return (ARCHIVE_FATAL); + filter->bidder = best_bidder; + filter->archive = a; + filter->upstream = a->filter; + a->filter = filter; + r = (best_bidder->init)(a->filter); + if (r != ARCHIVE_OK) { + close_filters(a); + free_filters(a); + return (ARCHIVE_FATAL); + } + } +} + +/* + * Read header of next entry. + */ +static int +_archive_read_next_header2(struct archive *_a, struct archive_entry *entry) +{ + struct archive_read *a = (struct archive_read *)_a; + int r1 = ARCHIVE_OK, r2; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_read_next_header"); + + archive_entry_clear(entry); + archive_clear_error(&a->archive); + + /* + * If client didn't consume entire data, skip any remainder + * (This is especially important for GNU incremental directories.) + */ + if (a->archive.state == ARCHIVE_STATE_DATA) { + r1 = archive_read_data_skip(&a->archive); + if (r1 == ARCHIVE_EOF) + archive_set_error(&a->archive, EIO, + "Premature end-of-file."); + if (r1 == ARCHIVE_EOF || r1 == ARCHIVE_FATAL) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + } + + /* Record start-of-header offset in uncompressed stream. */ + a->header_position = a->filter->position; + + ++_a->file_count; + r2 = (a->format->read_header)(a, entry); + + /* + * EOF and FATAL are persistent at this layer. By + * modifying the state, we guarantee that future calls to + * read a header or read data will fail. + */ + switch (r2) { + case ARCHIVE_EOF: + a->archive.state = ARCHIVE_STATE_EOF; + --_a->file_count;/* Revert a file counter. */ + break; + case ARCHIVE_OK: + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_WARN: + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_RETRY: + break; + case ARCHIVE_FATAL: + a->archive.state = ARCHIVE_STATE_FATAL; + break; + } + + a->read_data_output_offset = 0; + a->read_data_remaining = 0; + /* EOF always wins; otherwise return the worst error. */ + return (r2 < r1 || r2 == ARCHIVE_EOF) ? r2 : r1; +} + +int +_archive_read_next_header(struct archive *_a, struct archive_entry **entryp) +{ + int ret; + struct archive_read *a = (struct archive_read *)_a; + *entryp = NULL; + ret = _archive_read_next_header2(_a, a->entry); + *entryp = a->entry; + return ret; +} + +/* + * Allow each registered format to bid on whether it wants to handle + * the next entry. Return index of winning bidder. + */ +static int +choose_format(struct archive_read *a) +{ + int slots; + int i; + int bid, best_bid; + int best_bid_slot; + + slots = sizeof(a->formats) / sizeof(a->formats[0]); + best_bid = -1; + best_bid_slot = -1; + + /* Set up a->format for convenience of bidders. */ + a->format = &(a->formats[0]); + for (i = 0; i < slots; i++, a->format++) { + if (a->format->bid) { + bid = (a->format->bid)(a, best_bid); + if (bid == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + if (a->filter->position != 0) + __archive_read_seek(a, 0, SEEK_SET); + if ((bid > best_bid) || (best_bid_slot < 0)) { + best_bid = bid; + best_bid_slot = i; + } + } + } + + /* + * There were no bidders; this is a serious programmer error + * and demands a quick and definitive abort. + */ + if (best_bid_slot < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "No formats registered"); + return (ARCHIVE_FATAL); + } + + /* + * There were bidders, but no non-zero bids; this means we + * can't support this stream. + */ + if (best_bid < 1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized archive format"); + return (ARCHIVE_FATAL); + } + + return (best_bid_slot); +} + +/* + * Return the file offset (within the uncompressed data stream) where + * the last header started. + */ +int64_t +archive_read_header_position(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_header_position"); + return (a->header_position); +} + +/* + * Read data from an archive entry, using a read(2)-style interface. + * This is a convenience routine that just calls + * archive_read_data_block and copies the results into the client + * buffer, filling any gaps with zero bytes. Clients using this + * API can be completely ignorant of sparse-file issues; sparse files + * will simply be padded with nulls. + * + * DO NOT intermingle calls to this function and archive_read_data_block + * to read a single entry body. + */ +ssize_t +archive_read_data(struct archive *_a, void *buff, size_t s) +{ + struct archive_read *a = (struct archive_read *)_a; + char *dest; + const void *read_buf; + size_t bytes_read; + size_t len; + int r; + + bytes_read = 0; + dest = (char *)buff; + + while (s > 0) { + if (a->read_data_remaining == 0) { + read_buf = a->read_data_block; + r = _archive_read_data_block(&a->archive, &read_buf, + &a->read_data_remaining, &a->read_data_offset); + a->read_data_block = read_buf; + if (r == ARCHIVE_EOF) + return (bytes_read); + /* + * Error codes are all negative, so the status + * return here cannot be confused with a valid + * byte count. (ARCHIVE_OK is zero.) + */ + if (r < ARCHIVE_OK) + return (r); + } + + if (a->read_data_offset < a->read_data_output_offset) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Encountered out-of-order sparse blocks"); + return (ARCHIVE_RETRY); + } + + /* Compute the amount of zero padding needed. */ + if (a->read_data_output_offset + s < + a->read_data_offset) { + len = s; + } else if (a->read_data_output_offset < + a->read_data_offset) { + len = a->read_data_offset - + a->read_data_output_offset; + } else + len = 0; + + /* Add zeroes. */ + memset(dest, 0, len); + s -= len; + a->read_data_output_offset += len; + dest += len; + bytes_read += len; + + /* Copy data if there is any space left. */ + if (s > 0) { + len = a->read_data_remaining; + if (len > s) + len = s; + memcpy(dest, a->read_data_block, len); + s -= len; + a->read_data_block += len; + a->read_data_remaining -= len; + a->read_data_output_offset += len; + a->read_data_offset += len; + dest += len; + bytes_read += len; + } + } + return (bytes_read); +} + +#if ARCHIVE_API_VERSION < 3 +/* + * Obsolete function provided for compatibility only. Note that the API + * of this function doesn't allow the caller to detect if the remaining + * data from the archive entry is shorter than the buffer provided, or + * even if an error occurred while reading data. + */ +int +archive_read_data_into_buffer(struct archive *a, void *d, ssize_t len) +{ + + archive_read_data(a, d, len); + return (ARCHIVE_OK); +} +#endif + +/* + * Skip over all remaining data in this entry. + */ +int +archive_read_data_skip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r; + const void *buff; + size_t size; + int64_t offset; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_skip"); + + if (a->format->read_data_skip != NULL) + r = (a->format->read_data_skip)(a); + else { + while ((r = archive_read_data_block(&a->archive, + &buff, &size, &offset)) + == ARCHIVE_OK) + ; + } + + if (r == ARCHIVE_EOF) + r = ARCHIVE_OK; + + a->archive.state = ARCHIVE_STATE_HEADER; + return (r); +} + +/* + * Read the next block of entry data from the archive. + * This is a zero-copy interface; the client receives a pointer, + * size, and file offset of the next available block of data. + * + * Returns ARCHIVE_OK if the operation is successful, ARCHIVE_EOF if + * the end of entry is encountered. + */ +static int +_archive_read_data_block(struct archive *_a, + const void **buff, size_t *size, int64_t *offset) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_block"); + + if (a->format->read_data == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_PROGRAMMER, + "Internal error: " + "No format_read_data_block function registered"); + return (ARCHIVE_FATAL); + } + + return (a->format->read_data)(a, buff, size, offset); +} + +static int +close_filters(struct archive_read *a) +{ + struct archive_read_filter *f = a->filter; + int r = ARCHIVE_OK; + /* Close each filter in the pipeline. */ + while (f != NULL) { + struct archive_read_filter *t = f->upstream; + if (!f->closed && f->close != NULL) { + int r1 = (f->close)(f); + f->closed = 1; + if (r1 < r) + r = r1; + } + free(f->buffer); + f->buffer = NULL; + f = t; + } + return r; +} + +static void +free_filters(struct archive_read *a) +{ + while (a->filter != NULL) { + struct archive_read_filter *t = a->filter->upstream; + free(a->filter); + a->filter = t; + } +} + +/* + * return the count of # of filters in use + */ +static int +_archive_filter_count(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter *p = a->filter; + int count = 0; + while(p) { + count++; + p = p->upstream; + } + return count; +} + +/* + * Close the file and all I/O. + */ +static int +_archive_read_close(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r = ARCHIVE_OK, r1 = ARCHIVE_OK; + + archive_check_magic(&a->archive, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_close"); + if (a->archive.state == ARCHIVE_STATE_CLOSED) + return (ARCHIVE_OK); + archive_clear_error(&a->archive); + a->archive.state = ARCHIVE_STATE_CLOSED; + + /* TODO: Clean up the formatters. */ + + /* Release the filter objects. */ + r1 = close_filters(a); + if (r1 < r) + r = r1; + + return (r); +} + +/* + * Release memory and other resources. + */ +static int +_archive_read_free(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int i, n; + int slots; + int r = ARCHIVE_OK; + + if (_a == NULL) + return (ARCHIVE_OK); + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_free"); + if (a->archive.state != ARCHIVE_STATE_CLOSED + && a->archive.state != ARCHIVE_STATE_FATAL) + r = archive_read_close(&a->archive); + + /* Call cleanup functions registered by optional components. */ + if (a->cleanup_archive_extract != NULL) + r = (a->cleanup_archive_extract)(a); + + /* Cleanup format-specific data. */ + slots = sizeof(a->formats) / sizeof(a->formats[0]); + for (i = 0; i < slots; i++) { + a->format = &(a->formats[i]); + if (a->formats[i].cleanup) + (a->formats[i].cleanup)(a); + } + + /* Free the filters */ + free_filters(a); + + /* Release the bidder objects. */ + n = sizeof(a->bidders)/sizeof(a->bidders[0]); + for (i = 0; i < n; i++) { + if (a->bidders[i].free != NULL) { + int r1 = (a->bidders[i].free)(&a->bidders[i]); + if (r1 < r) + r = r1; + } + } + + archive_string_free(&a->archive.error_string); + if (a->entry) + archive_entry_free(a->entry); + a->archive.magic = 0; + __archive_clean(&a->archive); + free(a); + return (r); +} + +static struct archive_read_filter * +get_filter(struct archive *_a, int n) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter *f = a->filter; + /* We use n == -1 for 'the last filter', which is always the client proxy. */ + if (n == -1 && f != NULL) { + struct archive_read_filter *last = f; + f = f->upstream; + while (f != NULL) { + last = f; + f = f->upstream; + } + return (last); + } + if (n < 0) + return NULL; + while (n > 0 && f != NULL) { + f = f->upstream; + --n; + } + return (f); +} + +static int +_archive_filter_code(struct archive *_a, int n) +{ + struct archive_read_filter *f = get_filter(_a, n); + return f == NULL ? -1 : f->code; +} + +static const char * +_archive_filter_name(struct archive *_a, int n) +{ + struct archive_read_filter *f = get_filter(_a, n); + return f == NULL ? NULL : f->name; +} + +static int64_t +_archive_filter_bytes(struct archive *_a, int n) +{ + struct archive_read_filter *f = get_filter(_a, n); + return f == NULL ? -1 : f->position; +} + +/* + * Used internally by read format handlers to register their bid and + * initialization functions. + */ +int +__archive_read_register_format(struct archive_read *a, + void *format_data, + const char *name, + int (*bid)(struct archive_read *, int), + int (*options)(struct archive_read *, const char *, const char *), + int (*read_header)(struct archive_read *, struct archive_entry *), + int (*read_data)(struct archive_read *, const void **, size_t *, int64_t *), + int (*read_data_skip)(struct archive_read *), + int (*cleanup)(struct archive_read *)) +{ + int i, number_slots; + + archive_check_magic(&a->archive, + ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "__archive_read_register_format"); + + number_slots = sizeof(a->formats) / sizeof(a->formats[0]); + + for (i = 0; i < number_slots; i++) { + if (a->formats[i].bid == bid) + return (ARCHIVE_WARN); /* We've already installed */ + if (a->formats[i].bid == NULL) { + a->formats[i].bid = bid; + a->formats[i].options = options; + a->formats[i].read_header = read_header; + a->formats[i].read_data = read_data; + a->formats[i].read_data_skip = read_data_skip; + a->formats[i].cleanup = cleanup; + a->formats[i].data = format_data; + a->formats[i].name = name; + return (ARCHIVE_OK); + } + } + + archive_set_error(&a->archive, ENOMEM, + "Not enough slots for format registration"); + return (ARCHIVE_FATAL); +} + +/* + * Used internally by decompression routines to register their bid and + * initialization functions. + */ +int +__archive_read_get_bidder(struct archive_read *a, + struct archive_read_filter_bidder **bidder) +{ + int i, number_slots; + + number_slots = sizeof(a->bidders) / sizeof(a->bidders[0]); + + for (i = 0; i < number_slots; i++) { + if (a->bidders[i].bid == NULL) { + memset(a->bidders + i, 0, sizeof(a->bidders[0])); + *bidder = (a->bidders + i); + return (ARCHIVE_OK); + } + } + + archive_set_error(&a->archive, ENOMEM, + "Not enough slots for filter registration"); + return (ARCHIVE_FATAL); +} + +/* + * The next section implements the peek/consume internal I/O + * system used by archive readers. This system allows simple + * read-ahead for consumers while preserving zero-copy operation + * most of the time. + * + * The two key operations: + * * The read-ahead function returns a pointer to a block of data + * that satisfies a minimum request. + * * The consume function advances the file pointer. + * + * In the ideal case, filters generate blocks of data + * and __archive_read_ahead() just returns pointers directly into + * those blocks. Then __archive_read_consume() just bumps those + * pointers. Only if your request would span blocks does the I/O + * layer use a copy buffer to provide you with a contiguous block of + * data. + * + * A couple of useful idioms: + * * "I just want some data." Ask for 1 byte and pay attention to + * the "number of bytes available" from __archive_read_ahead(). + * Consume whatever you actually use. + * * "I want to output a large block of data." As above, ask for 1 byte, + * emit all that's available (up to whatever limit you have), consume + * it all, then repeat until you're done. This effectively means that + * you're passing along the blocks that came from your provider. + * * "I want to peek ahead by a large amount." Ask for 4k or so, then + * double and repeat until you get an error or have enough. Note + * that the I/O layer will likely end up expanding its copy buffer + * to fit your request, so use this technique cautiously. This + * technique is used, for example, by some of the format tasting + * code that has uncertain look-ahead needs. + */ + +/* + * Looks ahead in the input stream: + * * If 'avail' pointer is provided, that returns number of bytes available + * in the current buffer, which may be much larger than requested. + * * If end-of-file, *avail gets set to zero. + * * If error, *avail gets error code. + * * If request can be met, returns pointer to data. + * * If minimum request cannot be met, returns NULL. + * + * Note: If you just want "some data", ask for 1 byte and pay attention + * to *avail, which will have the actual amount available. If you + * know exactly how many bytes you need, just ask for that and treat + * a NULL return as an error. + * + * Important: This does NOT move the file pointer. See + * __archive_read_consume() below. + */ +const void * +__archive_read_ahead(struct archive_read *a, size_t min, ssize_t *avail) +{ + return (__archive_read_filter_ahead(a->filter, min, avail)); +} + +const void * +__archive_read_filter_ahead(struct archive_read_filter *filter, + size_t min, ssize_t *avail) +{ + ssize_t bytes_read; + size_t tocopy; + + if (filter->fatal) { + if (avail) + *avail = ARCHIVE_FATAL; + return (NULL); + } + + /* + * Keep pulling more data until we can satisfy the request. + */ + for (;;) { + + /* + * If we can satisfy from the copy buffer (and the + * copy buffer isn't empty), we're done. In particular, + * note that min == 0 is a perfectly well-defined + * request. + */ + if (filter->avail >= min && filter->avail > 0) { + if (avail != NULL) + *avail = filter->avail; + return (filter->next); + } + + /* + * We can satisfy directly from client buffer if everything + * currently in the copy buffer is still in the client buffer. + */ + if (filter->client_total >= filter->client_avail + filter->avail + && filter->client_avail + filter->avail >= min) { + /* "Roll back" to client buffer. */ + filter->client_avail += filter->avail; + filter->client_next -= filter->avail; + /* Copy buffer is now empty. */ + filter->avail = 0; + filter->next = filter->buffer; + /* Return data from client buffer. */ + if (avail != NULL) + *avail = filter->client_avail; + return (filter->client_next); + } + + /* Move data forward in copy buffer if necessary. */ + if (filter->next > filter->buffer && + filter->next + min > filter->buffer + filter->buffer_size) { + if (filter->avail > 0) + memmove(filter->buffer, filter->next, filter->avail); + filter->next = filter->buffer; + } + + /* If we've used up the client data, get more. */ + if (filter->client_avail <= 0) { + if (filter->end_of_file) { + if (avail != NULL) + *avail = 0; + return (NULL); + } + bytes_read = (filter->read)(filter, + &filter->client_buff); + if (bytes_read < 0) { /* Read error. */ + filter->client_total = filter->client_avail = 0; + filter->client_next = filter->client_buff = NULL; + filter->fatal = 1; + if (avail != NULL) + *avail = ARCHIVE_FATAL; + return (NULL); + } + if (bytes_read == 0) { /* Premature end-of-file. */ + filter->client_total = filter->client_avail = 0; + filter->client_next = filter->client_buff = NULL; + filter->end_of_file = 1; + /* Return whatever we do have. */ + if (avail != NULL) + *avail = filter->avail; + return (NULL); + } + filter->client_total = bytes_read; + filter->client_avail = filter->client_total; + filter->client_next = filter->client_buff; + } + else + { + /* + * We can't satisfy the request from the copy + * buffer or the existing client data, so we + * need to copy more client data over to the + * copy buffer. + */ + + /* Ensure the buffer is big enough. */ + if (min > filter->buffer_size) { + size_t s, t; + char *p; + + /* Double the buffer; watch for overflow. */ + s = t = filter->buffer_size; + if (s == 0) + s = min; + while (s < min) { + t *= 2; + if (t <= s) { /* Integer overflow! */ + archive_set_error( + &filter->archive->archive, + ENOMEM, + "Unable to allocate copy buffer"); + filter->fatal = 1; + if (avail != NULL) + *avail = ARCHIVE_FATAL; + return (NULL); + } + s = t; + } + /* Now s >= min, so allocate a new buffer. */ + p = (char *)malloc(s); + if (p == NULL) { + archive_set_error( + &filter->archive->archive, + ENOMEM, + "Unable to allocate copy buffer"); + filter->fatal = 1; + if (avail != NULL) + *avail = ARCHIVE_FATAL; + return (NULL); + } + /* Move data into newly-enlarged buffer. */ + if (filter->avail > 0) + memmove(p, filter->next, filter->avail); + free(filter->buffer); + filter->next = filter->buffer = p; + filter->buffer_size = s; + } + + /* We can add client data to copy buffer. */ + /* First estimate: copy to fill rest of buffer. */ + tocopy = (filter->buffer + filter->buffer_size) + - (filter->next + filter->avail); + /* Don't waste time buffering more than we need to. */ + if (tocopy + filter->avail > min) + tocopy = min - filter->avail; + /* Don't copy more than is available. */ + if (tocopy > filter->client_avail) + tocopy = filter->client_avail; + + memcpy(filter->next + filter->avail, filter->client_next, + tocopy); + /* Remove this data from client buffer. */ + filter->client_next += tocopy; + filter->client_avail -= tocopy; + /* add it to copy buffer. */ + filter->avail += tocopy; + } + } +} + +/* + * Move the file pointer forward. + */ +int64_t +__archive_read_consume(struct archive_read *a, int64_t request) +{ + return (__archive_read_filter_consume(a->filter, request)); +} + +int64_t +__archive_read_filter_consume(struct archive_read_filter * filter, + int64_t request) +{ + int64_t skipped; + + if (request == 0) + return 0; + + skipped = advance_file_pointer(filter, request); + if (skipped == request) + return (skipped); + /* We hit EOF before we satisfied the skip request. */ + if (skipped < 0) /* Map error code to 0 for error message below. */ + skipped = 0; + archive_set_error(&filter->archive->archive, + ARCHIVE_ERRNO_MISC, + "Truncated input file (needed %jd bytes, only %jd available)", + (intmax_t)request, (intmax_t)skipped); + return (ARCHIVE_FATAL); +} + +/* + * Advance the file pointer by the amount requested. + * Returns the amount actually advanced, which may be less than the + * request if EOF is encountered first. + * Returns a negative value if there's an I/O error. + */ +static int64_t +advance_file_pointer(struct archive_read_filter *filter, int64_t request) +{ + int64_t bytes_skipped, total_bytes_skipped = 0; + ssize_t bytes_read; + size_t min; + + if (filter->fatal) + return (-1); + + /* Use up the copy buffer first. */ + if (filter->avail > 0) { + min = minimum(request, (int64_t)filter->avail); + filter->next += min; + filter->avail -= min; + request -= min; + filter->position += min; + total_bytes_skipped += min; + } + + /* Then use up the client buffer. */ + if (filter->client_avail > 0) { + min = minimum(request, (int64_t)filter->client_avail); + filter->client_next += min; + filter->client_avail -= min; + request -= min; + filter->position += min; + total_bytes_skipped += min; + } + if (request == 0) + return (total_bytes_skipped); + + /* If there's an optimized skip function, use it. */ + if (filter->skip != NULL) { + bytes_skipped = (filter->skip)(filter, request); + if (bytes_skipped < 0) { /* error */ + filter->fatal = 1; + return (bytes_skipped); + } + filter->position += bytes_skipped; + total_bytes_skipped += bytes_skipped; + request -= bytes_skipped; + if (request == 0) + return (total_bytes_skipped); + } + + /* Use ordinary reads as necessary to complete the request. */ + for (;;) { + bytes_read = (filter->read)(filter, &filter->client_buff); + if (bytes_read < 0) { + filter->client_buff = NULL; + filter->fatal = 1; + return (bytes_read); + } + + if (bytes_read == 0) { + filter->client_buff = NULL; + filter->end_of_file = 1; + return (total_bytes_skipped); + } + + if (bytes_read >= request) { + filter->client_next = + ((const char *)filter->client_buff) + request; + filter->client_avail = bytes_read - request; + filter->client_total = bytes_read; + total_bytes_skipped += request; + filter->position += request; + return (total_bytes_skipped); + } + + filter->position += bytes_read; + total_bytes_skipped += bytes_read; + request -= bytes_read; + } +} + +/** + * Returns ARCHIVE_FAILED if seeking isn't supported. + */ +int64_t +__archive_read_seek(struct archive_read *a, int64_t offset, int whence) +{ + return __archive_read_filter_seek(a->filter, offset, whence); +} + +int64_t +__archive_read_filter_seek(struct archive_read_filter *filter, int64_t offset, int whence) +{ + int64_t r; + + if (filter->closed || filter->fatal) + return (ARCHIVE_FATAL); + if (filter->seek == NULL) + return (ARCHIVE_FAILED); + r = filter->seek(filter, offset, whence); + if (r >= 0) { + /* + * Ouch. Clearing the buffer like this hurts, especially + * at bid time. A lot of our efficiency at bid time comes + * from having bidders reuse the data we've already read. + * + * TODO: If the seek request is in data we already + * have, then don't call the seek callback. + * + * TODO: Zip seeks to end-of-file at bid time. If + * other formats also start doing this, we may need to + * find a way for clients to fudge the seek offset to + * a block boundary. + * + * Hmmm... If whence was SEEK_END, we know the file + * size is (r - offset). Can we use that to simplify + * the TODO items above? + */ + filter->avail = filter->client_avail = 0; + filter->next = filter->buffer; + filter->position = r; + filter->end_of_file = 0; + } + return r; +} diff --git a/libarchive/archive_read_data.3 b/libarchive/archive_read_data.3 new file mode 100644 index 0000000..a5898e7 --- /dev/null +++ b/libarchive/archive_read_data.3 @@ -0,0 +1,142 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 22, 2011 +.Dt archive_read_data 3 +.Os +.Sh NAME +.Nm archive_read_data +.Nm archive_read_data_block , +.Nm archive_read_data_skip , +.\" #if ARCHIVE_API_VERSION < 3 +.Nm archive_read_data_into_buffer , +.\" #endif +.Nm archive_read_data_into_fd +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft ssize_t +.Fn archive_read_data "struct archive *" "void *buff" "size_t len" +.Ft int +.Fo archive_read_data_block +.Fa "struct archive *" +.Fa "const void **buff" +.Fa "size_t *len" +.Fa "off_t *offset" +.Fc +.Ft int +.Fn archive_read_data_skip "struct archive *" +.\" #if ARCHIVE_API_VERSION < 3 +.Ft int +.Fn archive_read_data_into_buffer "struct archive *" "void *" "ssize_t len" +.\" #endif +.Ft int +.Fn archive_read_data_into_fd "struct archive *" "int fd" +.\" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Fn archive_read_data +Read data associated with the header just read. +Internally, this is a convenience function that calls +.Fn archive_read_data_block +and fills any gaps with nulls so that callers see a single +continuous stream of data. +.It Fn archive_read_data_block +Return the next available block of data for this entry. +Unlike +.Fn archive_read_data , +the +.Fn archive_read_data_block +function avoids copying data and allows you to correctly handle +sparse files, as supported by some archive formats. +The library guarantees that offsets will increase and that blocks +will not overlap. +Note that the blocks returned from this function can be much larger +than the block size read from disk, due to compression +and internal buffer optimizations. +.It Fn archive_read_data_skip +A convenience function that repeatedly calls +.Fn archive_read_data_block +to skip all of the data for this archive entry. +Note that this function is invoked automatically by +.Fn archive_read_next_header2 +if the previous entry was not completely consumed. +.\" #if ARCHIVE_API_VERSION < 3 +.It Fn archive_read_data_into_buffer +This function is deprecated and will be removed. +Use +.Fn archive_read_data +instead. +.\" #endif +.It Fn archive_read_data_into_fd +A convenience function that repeatedly calls +.Fn archive_read_data_block +to copy the entire entry to the provided file descriptor. +.El +.\" +.Sh RETURN VALUES +Most functions return zero on success, non-zero on error. +The possible return codes include: +.Cm ARCHIVE_OK +(the operation succeeded), +.Cm ARCHIVE_WARN +(the operation succeeded but a non-critical error was encountered), +.Cm ARCHIVE_EOF +(end-of-archive was encountered), +.Cm ARCHIVE_RETRY +(the operation failed but can be retried), +and +.Cm ARCHIVE_FATAL +(there was a fatal error; the archive should be closed immediately). +.Pp +.Fn archive_read_data +returns a count of bytes actually read or zero at the end of the entry. +On error, a value of +.Cm ARCHIVE_FATAL , +.Cm ARCHIVE_WARN , +or +.Cm ARCHIVE_RETRY +is returned. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_extract 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_header 3 , +.Xr archive_read_open 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 diff --git a/libarchive/archive_read_data_into_fd.c b/libarchive/archive_read_data_into_fd.c new file mode 100644 index 0000000..04d3ab0 --- /dev/null +++ b/libarchive/archive_read_data_into_fd.c @@ -0,0 +1,139 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_data_into_fd.c,v 1.16 2008/05/23 05:01:29 cperciva Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" + +/* Maximum amount of data to write at one time. */ +#define MAX_WRITE (1024 * 1024) + +/* + * This implementation minimizes copying of data and is sparse-file aware. + */ +int +pad_to(struct archive *a, int fd, int can_lseek, + size_t nulls_size, const char *nulls, + int64_t target_offset, int64_t actual_offset) +{ + size_t to_write; + ssize_t bytes_written; + + if (can_lseek) { + actual_offset = lseek(fd, + target_offset - actual_offset, SEEK_CUR); + if (actual_offset != target_offset) { + archive_set_error(a, errno, "Seek error"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); + } + while (target_offset > actual_offset) { + to_write = nulls_size; + if (target_offset < actual_offset + nulls_size) + to_write = (size_t)(target_offset - actual_offset); + bytes_written = write(fd, nulls, to_write); + if (bytes_written < 0) { + archive_set_error(a, errno, "Write error"); + return (ARCHIVE_FATAL); + } + actual_offset += bytes_written; + } + return (ARCHIVE_OK); +} + + +int +archive_read_data_into_fd(struct archive *a, int fd) +{ + struct stat st; + int r, r2; + const void *buff; + size_t size, bytes_to_write; + ssize_t bytes_written; + int64_t target_offset; + int64_t actual_offset = 0; + int can_lseek; + char *nulls = NULL; + size_t nulls_size = 16384; + + archive_check_magic(a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_into_fd"); + + can_lseek = (fstat(fd, &st) == 0) && S_ISREG(st.st_mode); + if (!can_lseek) + nulls = calloc(1, nulls_size); + + while ((r = archive_read_data_block(a, &buff, &size, &target_offset)) == + ARCHIVE_OK) { + const char *p = buff; + if (target_offset > actual_offset) { + r = pad_to(a, fd, can_lseek, nulls_size, nulls, + target_offset, actual_offset); + if (r != ARCHIVE_OK) + break; + actual_offset = target_offset; + } + while (size > 0) { + bytes_to_write = size; + if (bytes_to_write > MAX_WRITE) + bytes_to_write = MAX_WRITE; + bytes_written = write(fd, p, bytes_to_write); + if (bytes_written < 0) { + archive_set_error(a, errno, "Write error"); + r = ARCHIVE_FATAL; + goto cleanup; + } + actual_offset += bytes_written; + p += bytes_written; + size -= bytes_written; + } + } + + if (r == ARCHIVE_EOF && target_offset > actual_offset) { + r2 = pad_to(a, fd, can_lseek, nulls_size, nulls, + target_offset, actual_offset); + if (r2 != ARCHIVE_OK) + r = r2; + } + +cleanup: + free(nulls); + if (r != ARCHIVE_EOF) + return (r); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_disk.3 b/libarchive/archive_read_disk.3 new file mode 100644 index 0000000..d3b6101 --- /dev/null +++ b/libarchive/archive_read_disk.3 @@ -0,0 +1,315 @@ +.\" Copyright (c) 2003-2009 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read_disk.3 190957 2009-04-12 05:04:02Z kientzle $ +.\" +.Dd March 10, 2009 +.Dt archive_read_disk 3 +.Os +.Sh NAME +.Nm archive_read_disk_new , +.Nm archive_read_disk_set_symlink_logical , +.Nm archive_read_disk_set_symlink_physical , +.Nm archive_read_disk_set_symlink_hybrid , +.Nm archive_read_disk_entry_from_file , +.Nm archive_read_disk_gname , +.Nm archive_read_disk_uname , +.Nm archive_read_disk_set_uname_lookup , +.Nm archive_read_disk_set_gname_lookup , +.Nm archive_read_disk_set_standard_lookup , +.Nm archive_read_close , +.Nm archive_read_finish , +.Nm archive_read_free +.Nd functions for reading objects from disk +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_read_disk_new "void" +.Ft int +.Fn archive_read_disk_set_symlink_logical "struct archive *" +.Ft int +.Fn archive_read_disk_set_symlink_physical "struct archive *" +.Ft int +.Fn archive_read_disk_set_symlink_hybrid "struct archive *" +.Ft int +.Fn archive_read_disk_gname "struct archive *" "gid_t" +.Ft int +.Fn archive_read_disk_uname "struct archive *" "uid_t" +.Ft int +.Fo archive_read_disk_set_gname_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "const char *(*lookup)(void *, gid_t)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fo archive_read_disk_set_uname_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "const char *(*lookup)(void *, uid_t)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fn archive_read_disk_set_standard_lookup "struct archive *" +.Ft int +.Fo archive_read_disk_entry_from_file +.Fa "struct archive *" +.Fa "struct archive_entry *" +.Fa "int fd" +.Fa "const struct stat *" +.Fc +.Ft int +.Fn archive_read_close "struct archive *" +.Ft int +.Fn archive_read_finish "struct archive *" +.Ft int +.Fn archive_read_free "struct archive *" +.Sh DESCRIPTION +These functions provide an API for reading information about +objects on disk. +In particular, they provide an interface for populating +.Tn struct archive_entry +objects. +.Bl -tag -width indent +.It Fn archive_read_disk_new +Allocates and initializes a +.Tn struct archive +object suitable for reading object information from disk. +.It Xo +.Fn archive_read_disk_set_symlink_logical , +.Fn archive_read_disk_set_symlink_physical , +.Fn archive_read_disk_set_symlink_hybrid +.Xc +This sets the mode used for handling symbolic links. +The +.Dq logical +mode follows all symbolic links. +The +.Dq physical +mode does not follow any symbolic links. +The +.Dq hybrid +mode currently behaves identically to the +.Dq logical +mode. +.It Xo +.Fn archive_read_disk_gname , +.Fn archive_read_disk_uname +.Xc +Returns a user or group name given a gid or uid value. +By default, these always return a NULL string. +.It Xo +.Fn archive_read_disk_set_gname_lookup , +.Fn archive_read_disk_set_uname_lookup +.Xc +These allow you to override the functions used for +user and group name lookups. +You may also provide a +.Tn void * +pointer to a private data structure and a cleanup function for +that data. +The cleanup function will be invoked when the +.Tn struct archive +object is destroyed or when new lookup functions are registered. +.It Fn archive_read_disk_set_standard_lookup +This convenience function installs a standard set of user +and group name lookup functions. +These functions use +.Xr getpwuid 3 +and +.Xr getgrgid 3 +to convert ids to names, defaulting to NULL if the names cannot +be looked up. +These functions also implement a simple memory cache to reduce +the number of calls to +.Xr getpwuid 3 +and +.Xr getgrgid 3 . +.It Fn archive_read_disk_entry_from_file +Populates a +.Tn struct archive_entry +object with information about a particular file. +The +.Tn archive_entry +object must have already been created with +.Xr archive_entry_new 3 +and at least one of the source path or path fields must already be set. +(If both are set, the source path will be used.) +.Pp +Information is read from disk using the path name from the +.Tn struct archive_entry +object. +If a file descriptor is provided, some information will be obtained using +that file descriptor, on platforms that support the appropriate +system calls. +.Pp +If a pointer to a +.Tn struct stat +is provided, information from that structure will be used instead +of reading from the disk where appropriate. +This can provide performance benefits in scenarios where +.Tn struct stat +information has already been read from the disk as a side effect +of some other operation. +(For example, directory traversal libraries often provide this information.) +.Pp +Where necessary, user and group ids are converted to user and group names +using the currently registered lookup functions above. +This affects the file ownership fields and ACL values in the +.Tn struct archive_entry +object. +.It Fn archive_read_close +Does nothing for +.Tn archive_read_disk +handles. +.It Fn archive_read_finish +This is a deprecated synonym for +.Fn archive_read_free . +.It Fn archive_read_free +Invokes +.Fn archive_read_close +if it was not invoked manually, then releases all resources. +.El +More information about the +.Va struct archive +object and the overall design of the library can be found in the +.Xr libarchive 3 +overview. +.Sh EXAMPLE +The following illustrates basic usage of the library by +showing how to use it to copy an item on disk into an archive. +.Bd -literal -offset indent +void +file_to_archive(struct archive *a, const char *name) +{ + char buff[8192]; + size_t bytes_read; + struct archive *ard; + struct archive_entry *entry; + int fd; + + ard = archive_read_disk_new(); + archive_read_disk_set_standard_lookup(ard); + entry = archive_entry_new(); + fd = open(name, O_RDONLY); + if (fd < 0) + return; + archive_entry_copy_pathname(entry, name); + archive_read_disk_entry_from_file(ard, entry, fd, NULL); + archive_write_header(a, entry); + while ((bytes_read = read(fd, buff, sizeof(buff))) > 0) + archive_write_data(a, buff, bytes_read); + archive_write_finish_entry(a); + archive_read_free(ard); + archive_entry_free(entry); +} +.Ed +.Sh RETURN VALUES +Most functions return +.Cm ARCHIVE_OK +(zero) on success, or one of several negative +error codes for errors. +Specific error codes include: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +.Pp +.Fn archive_read_disk_new +returns a pointer to a newly-allocated +.Tn struct archive +object or NULL if the allocation failed for any reason. +.Pp +.Fn archive_read_disk_gname +and +.Fn archive_read_disk_uname +return +.Tn const char * +pointers to the textual name or NULL if the lookup failed for any reason. +The returned pointer points to internal storage that +may be reused on the next call to either of these functions; +callers should copy the string if they need to continue accessing it. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr archive_read 3 , +.Xr archive_util 3 , +.Xr archive_write 3 , +.Xr archive_write_disk 3 , +.Xr tar 1 , +.Xr libarchive 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +The +.Nm archive_read_disk +interface was added to +.Nm libarchive 2.6 +and first appeared in +.Fx 8.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@freebsd.org . +.Sh BUGS +The +.Dq standard +user name and group name lookup functions are not the defaults because +.Xr getgrgid 3 +and +.Xr getpwuid 3 +are sometimes too large for particular applications. +The current design allows the application author to use a more +compact implementation when appropriate. +.Pp +The full list of metadata read from disk by +.Fn archive_read_disk_entry_from_file +is necessarily system-dependent. +.Pp +The +.Fn archive_read_disk_entry_from_file +function reads as much information as it can from disk. +Some method should be provided to limit this so that clients who +do not need ACLs, for instance, can avoid the extra work needed +to look up such information. +.Pp +This API should provide a set of methods for walking a directory tree. +That would make it a direct parallel of the +.Xr archive_read 3 +API. +When such methods are implemented, the +.Dq hybrid +symbolic link mode will make sense. diff --git a/libarchive/archive_read_disk_entry_from_file.c b/libarchive/archive_read_disk_entry_from_file.c new file mode 100644 index 0000000..cc39151 --- /dev/null +++ b/libarchive/archive_read_disk_entry_from_file.c @@ -0,0 +1,1005 @@ +/*- + * Copyright (c) 2003-2009 Tim Kientzle + * Copyright (c) 2010 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_entry_from_file.c 201084 2009-12-28 02:14:09Z kientzle $"); + +/* This is the tree-walking code for POSIX systems. */ +#if !defined(_WIN32) || defined(__CYGWIN__) + +#ifdef HAVE_SYS_TYPES_H +/* Mac OSX requires sys/types.h before sys/acl.h. */ +#include +#endif +#ifdef HAVE_SYS_ACL_H +#include +#endif +#ifdef HAVE_SYS_EXTATTR_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_XATTR_H +#include +#endif +#ifdef HAVE_SYS_EA_H +#include +#endif +#ifdef HAVE_ACL_LIBACL_H +#include +#endif +#ifdef HAVE_ATTR_XATTR_H +#include +#endif +#ifdef HAVE_COPYFILE_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_LINUX_FIEMAP_H +#include +#endif +#ifdef HAVE_LINUX_FS_H +#include +#endif +/* + * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h. + * As the include guards don't agree, the order of include is important. + */ +#ifdef HAVE_LINUX_EXT2_FS_H +#include /* for Linux file flags */ +#endif +#if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__) +#include /* Linux file flags, broken on Cygwin */ +#endif +#ifdef HAVE_PATHS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_disk_private.h" + +/* + * Linux and FreeBSD plug this obvious hole in POSIX.1e in + * different ways. + */ +#if HAVE_ACL_GET_PERM +#define ACL_GET_PERM acl_get_perm +#elif HAVE_ACL_GET_PERM_NP +#define ACL_GET_PERM acl_get_perm_np +#endif + +static int setup_acls_posix1e(struct archive_read_disk *, + struct archive_entry *, int fd); +static int setup_mac_metadata(struct archive_read_disk *, + struct archive_entry *, int fd); +static int setup_xattrs(struct archive_read_disk *, + struct archive_entry *, int fd); +static int setup_sparse(struct archive_read_disk *, + struct archive_entry *, int fd); + +int +archive_read_disk_entry_from_file(struct archive *_a, + struct archive_entry *entry, + int fd, + const struct stat *st) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + const char *path, *name; + struct stat s; + int initial_fd = fd; + int r, r1; + + archive_clear_error(_a); + path = archive_entry_sourcepath(entry); + if (path == NULL) + path = archive_entry_pathname(entry); + + if (a->tree == NULL) { + if (st == NULL) { +#if HAVE_FSTAT + if (fd >= 0) { + if (fstat(fd, &s) != 0) { + archive_set_error(&a->archive, errno, + "Can't fstat"); + return (ARCHIVE_FAILED); + } + } else +#endif +#if HAVE_LSTAT + if (!a->follow_symlinks) { + if (lstat(path, &s) != 0) { + archive_set_error(&a->archive, errno, + "Can't lstat %s", path); + return (ARCHIVE_FAILED); + } + } else +#endif + if (stat(path, &s) != 0) { + archive_set_error(&a->archive, errno, + "Can't stat %s", path); + return (ARCHIVE_FAILED); + } + st = &s; + } + archive_entry_copy_stat(entry, st); + } + + /* Lookup uname/gname */ + name = archive_read_disk_uname(_a, archive_entry_uid(entry)); + if (name != NULL) + archive_entry_copy_uname(entry, name); + name = archive_read_disk_gname(_a, archive_entry_gid(entry)); + if (name != NULL) + archive_entry_copy_gname(entry, name); + +#ifdef HAVE_STRUCT_STAT_ST_FLAGS + /* On FreeBSD, we get flags for free with the stat. */ + /* TODO: Does this belong in copy_stat()? */ + if (st->st_flags != 0) + archive_entry_set_fflags(entry, st->st_flags, 0); +#endif + +#if defined(EXT2_IOC_GETFLAGS) && defined(HAVE_WORKING_EXT2_IOC_GETFLAGS) + /* Linux requires an extra ioctl to pull the flags. Although + * this is an extra step, it has a nice side-effect: We get an + * open file descriptor which we can use in the subsequent lookups. */ + if ((S_ISREG(st->st_mode) || S_ISDIR(st->st_mode))) { + if (fd < 0) + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd >= 0) { + unsigned long stflags; + int r = ioctl(fd, EXT2_IOC_GETFLAGS, &stflags); + if (r == 0 && stflags != 0) + archive_entry_set_fflags(entry, stflags, 0); + } + } +#endif + +#if defined(HAVE_READLINK) || defined(HAVE_READLINKAT) + if (S_ISLNK(st->st_mode)) { + size_t linkbuffer_len = st->st_size + 1; + char *linkbuffer; + int lnklen; + + linkbuffer = malloc(linkbuffer_len); + if (linkbuffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't read link data"); + return (ARCHIVE_FAILED); + } +#ifdef HAVE_READLINKAT + if (a->entry_wd_fd >= 0) + lnklen = readlinkat(a->entry_wd_fd, path, + linkbuffer, linkbuffer_len); + else +#endif /* HAVE_READLINKAT */ + lnklen = readlink(path, linkbuffer, linkbuffer_len); + if (lnklen < 0) { + archive_set_error(&a->archive, errno, + "Couldn't read link data"); + free(linkbuffer); + return (ARCHIVE_FAILED); + } + linkbuffer[lnklen] = 0; + archive_entry_set_symlink(entry, linkbuffer); + free(linkbuffer); + } +#endif /* HAVE_READLINK || HAVE_READLINKAT */ + + r = setup_acls_posix1e(a, entry, fd); + r1 = setup_xattrs(a, entry, fd); + if (r1 < r) + r = r1; + r1 = setup_mac_metadata(a, entry, fd); + if (r1 < r) + r = r1; + r1 = setup_sparse(a, entry, fd); + if (r1 < r) + r = r1; + + /* If we opened the file earlier in this function, close it. */ + if (initial_fd != fd) + close(fd); + return (r); +} + +#ifdef __APPLE__ +/* + * The Mac OS "copyfile()" API copies the extended metadata for a + * file into a separate file in AppleDouble format (see RFC 1740). + * + * Mac OS tar and cpio implementations store this extended + * metadata as a separate entry just before the regular entry + * with a "._" prefix added to the filename. + * + * Note that this is currently done unconditionally; the tar program has + * an option to discard this information before the archive is written. + * + * TODO: If there's a failure, report it and return ARCHIVE_WARN. + */ +static int +setup_mac_metadata(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + int tempfd = -1; + int copyfile_flags = COPYFILE_NOFOLLOW | COPYFILE_ACL | COPYFILE_XATTR; + struct stat copyfile_stat; + int ret = ARCHIVE_OK; + void *buff; + int have_attrs; + const char *name, *tempdir, *tempfile = NULL; + + name = archive_entry_sourcepath(entry); + if (name == NULL) + name = archive_entry_pathname(entry); + if (name == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't open file to read extended attributes: No name"); + return (ARCHIVE_WARN); + } + + /* Short-circuit if there's nothing to do. */ + have_attrs = copyfile(name, NULL, 0, copyfile_flags | COPYFILE_CHECK); + if (have_attrs == -1) { + archive_set_error(&a->archive, errno, + "Could not check extended attributes"); + return (ARCHIVE_WARN); + } + if (have_attrs == 0) + return (ARCHIVE_OK); + + tempdir = NULL; + if (issetugid() == 0) + tempdir = getenv("TMPDIR"); + if (tempdir == NULL) + tempdir = _PATH_TMP; + tempfile = tempnam(tempdir, "tar.md."); + + /* XXX I wish copyfile() could pack directly to a memory + * buffer; that would avoid the temp file here. For that + * matter, it would be nice if fcopyfile() actually worked, + * that would reduce the many open/close races here. */ + if (copyfile(name, tempfile, 0, copyfile_flags | COPYFILE_PACK)) { + archive_set_error(&a->archive, errno, + "Could not pack extended attributes"); + ret = ARCHIVE_WARN; + goto cleanup; + } + tempfd = open(tempfile, O_RDONLY); + if (tempfd < 0) { + archive_set_error(&a->archive, errno, + "Could not open extended attribute file"); + ret = ARCHIVE_WARN; + goto cleanup; + } + if (fstat(tempfd, ©file_stat)) { + archive_set_error(&a->archive, errno, + "Could not check size of extended attributes"); + ret = ARCHIVE_WARN; + goto cleanup; + } + buff = malloc(copyfile_stat.st_size); + if (buff == NULL) { + archive_set_error(&a->archive, errno, + "Could not allocate memory for extended attributes"); + ret = ARCHIVE_WARN; + goto cleanup; + } + if (copyfile_stat.st_size != read(tempfd, buff, copyfile_stat.st_size)) { + archive_set_error(&a->archive, errno, + "Could not read extended attributes into memory"); + ret = ARCHIVE_WARN; + goto cleanup; + } + archive_entry_copy_mac_metadata(entry, buff, copyfile_stat.st_size); + +cleanup: + if (tempfd >= 0) + close(tempfd); + if (tempfile != NULL) + unlink(tempfile); + return (ret); +} + +#else + +/* + * Stub implementation for non-Mac systems. + */ +static int +setup_mac_metadata(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + (void)fd; /* UNUSED */ + return (ARCHIVE_OK); +} +#endif + + +#ifdef HAVE_POSIX_ACL +static void setup_acl_posix1e(struct archive_read_disk *a, + struct archive_entry *entry, acl_t acl, int archive_entry_acl_type); + +static int +setup_acls_posix1e(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + const char *accpath; + acl_t acl; + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) + accpath = archive_entry_pathname(entry); + + archive_entry_acl_clear(entry); + + /* Retrieve access ACL from file. */ + if (fd >= 0) + acl = acl_get_fd(fd); +#if HAVE_ACL_GET_LINK_NP + else if (!a->follow_symlinks) + acl = acl_get_link_np(accpath, ACL_TYPE_ACCESS); +#else + else if ((!a->follow_symlinks) + && (archive_entry_filetype(entry) == AE_IFLNK)) + /* We can't get the ACL of a symlink, so we assume it can't + have one. */ + acl = NULL; +#endif + else + acl = acl_get_file(accpath, ACL_TYPE_ACCESS); + if (acl != NULL) { + setup_acl_posix1e(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS); + acl_free(acl); + } + + /* Only directories can have default ACLs. */ + if (S_ISDIR(archive_entry_mode(entry))) { + acl = acl_get_file(accpath, ACL_TYPE_DEFAULT); + if (acl != NULL) { + setup_acl_posix1e(a, entry, acl, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT); + acl_free(acl); + } + } + return (ARCHIVE_OK); +} + +/* + * Translate POSIX.1e ACL into libarchive internal structure. + */ +static void +setup_acl_posix1e(struct archive_read_disk *a, + struct archive_entry *entry, acl_t acl, int archive_entry_acl_type) +{ + acl_tag_t acl_tag; + acl_entry_t acl_entry; + acl_permset_t acl_permset; + int s, ae_id, ae_tag, ae_perm; + const char *ae_name; + + s = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry); + while (s == 1) { + ae_id = -1; + ae_name = NULL; + + acl_get_tag_type(acl_entry, &acl_tag); + if (acl_tag == ACL_USER) { + ae_id = (int)*(uid_t *)acl_get_qualifier(acl_entry); + ae_name = archive_read_disk_uname(&a->archive, ae_id); + ae_tag = ARCHIVE_ENTRY_ACL_USER; + } else if (acl_tag == ACL_GROUP) { + ae_id = (int)*(gid_t *)acl_get_qualifier(acl_entry); + ae_name = archive_read_disk_gname(&a->archive, ae_id); + ae_tag = ARCHIVE_ENTRY_ACL_GROUP; + } else if (acl_tag == ACL_MASK) { + ae_tag = ARCHIVE_ENTRY_ACL_MASK; + } else if (acl_tag == ACL_USER_OBJ) { + ae_tag = ARCHIVE_ENTRY_ACL_USER_OBJ; + } else if (acl_tag == ACL_GROUP_OBJ) { + ae_tag = ARCHIVE_ENTRY_ACL_GROUP_OBJ; + } else if (acl_tag == ACL_OTHER) { + ae_tag = ARCHIVE_ENTRY_ACL_OTHER; + } else { + /* Skip types that libarchive can't support. */ + continue; + } + + acl_get_permset(acl_entry, &acl_permset); + ae_perm = 0; + /* + * acl_get_perm() is spelled differently on different + * platforms; see above. + */ + if (ACL_GET_PERM(acl_permset, ACL_EXECUTE)) + ae_perm |= ARCHIVE_ENTRY_ACL_EXECUTE; + if (ACL_GET_PERM(acl_permset, ACL_READ)) + ae_perm |= ARCHIVE_ENTRY_ACL_READ; + if (ACL_GET_PERM(acl_permset, ACL_WRITE)) + ae_perm |= ARCHIVE_ENTRY_ACL_WRITE; + + archive_entry_acl_add_entry(entry, + archive_entry_acl_type, ae_perm, ae_tag, + ae_id, ae_name); + + s = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry); + } +} +#else +static int +setup_acls_posix1e(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + (void)fd; /* UNUSED */ + return (ARCHIVE_OK); +} +#endif + +#if (HAVE_FGETXATTR && HAVE_FLISTXATTR && HAVE_LISTXATTR && \ + HAVE_LLISTXATTR && HAVE_GETXATTR && HAVE_LGETXATTR) || \ + (HAVE_FGETEA && HAVE_FLISTEA && HAVE_LISTEA) + +/* + * Linux and AIX extended attribute support. + * + * TODO: By using a stack-allocated buffer for the first + * call to getxattr(), we might be able to avoid the second + * call entirely. We only need the second call if the + * stack-allocated buffer is too small. But a modest buffer + * of 1024 bytes or so will often be big enough. Same applies + * to listxattr(). + */ + + +static int +setup_xattr(struct archive_read_disk *a, + struct archive_entry *entry, const char *name, int fd) +{ + ssize_t size; + void *value = NULL; + const char *accpath; + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) + accpath = archive_entry_pathname(entry); + +#if HAVE_FGETXATTR + if (fd >= 0) + size = fgetxattr(fd, name, NULL, 0); + else if (!a->follow_symlinks) + size = lgetxattr(accpath, name, NULL, 0); + else + size = getxattr(accpath, name, NULL, 0); +#elif HAVE_FGETEA + if (fd >= 0) + size = fgetea(fd, name, NULL, 0); + else if (!a->follow_symlinks) + size = lgetea(accpath, name, NULL, 0); + else + size = getea(accpath, name, NULL, 0); +#endif + + if (size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't query extended attribute"); + return (ARCHIVE_WARN); + } + + if (size > 0 && (value = malloc(size)) == NULL) { + archive_set_error(&a->archive, errno, "Out of memory"); + return (ARCHIVE_FATAL); + } + +#if HAVE_FGETXATTR + if (fd >= 0) + size = fgetxattr(fd, name, value, size); + else if (!a->follow_symlinks) + size = lgetxattr(accpath, name, value, size); + else + size = getxattr(accpath, name, value, size); +#elif HAVE_FGETEA + if (fd >= 0) + size = fgetea(fd, name, value, size); + else if (!a->follow_symlinks) + size = lgetea(accpath, name, value, size); + else + size = getea(accpath, name, value, size); +#endif + + if (size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't read extended attribute"); + return (ARCHIVE_WARN); + } + + archive_entry_xattr_add_entry(entry, name, value, size); + + free(value); + return (ARCHIVE_OK); +} + +static int +setup_xattrs(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + char *list, *p; + const char *path; + ssize_t list_size; + + path = archive_entry_sourcepath(entry); + if (path == NULL) + path = archive_entry_pathname(entry); + +#if HAVE_FLISTXATTR + if (fd >= 0) + list_size = flistxattr(fd, NULL, 0); + else if (!a->follow_symlinks) + list_size = llistxattr(path, NULL, 0); + else + list_size = listxattr(path, NULL, 0); +#elif HAVE_FLISTEA + if (fd >= 0) + list_size = flistea(fd, NULL, 0); + else if (!a->follow_symlinks) + list_size = llistea(path, NULL, 0); + else + list_size = listea(path, NULL, 0); +#endif + + if (list_size == -1) { + if (errno == ENOTSUP || errno == ENOSYS) + return (ARCHIVE_OK); + archive_set_error(&a->archive, errno, + "Couldn't list extended attributes"); + return (ARCHIVE_WARN); + } + + if (list_size == 0) + return (ARCHIVE_OK); + + if ((list = malloc(list_size)) == NULL) { + archive_set_error(&a->archive, errno, "Out of memory"); + return (ARCHIVE_FATAL); + } + +#if HAVE_FLISTXATTR + if (fd >= 0) + list_size = flistxattr(fd, list, list_size); + else if (!a->follow_symlinks) + list_size = llistxattr(path, list, list_size); + else + list_size = listxattr(path, list, list_size); +#elif HAVE_FLISTEA + if (fd >= 0) + list_size = flistea(fd, list, list_size); + else if (!a->follow_symlinks) + list_size = llistea(path, list, list_size); + else + list_size = listea(path, list, list_size); +#endif + + if (list_size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't retrieve extended attributes"); + free(list); + return (ARCHIVE_WARN); + } + + for (p = list; (p - list) < list_size; p += strlen(p) + 1) { + if (strncmp(p, "system.", 7) == 0 || + strncmp(p, "xfsroot.", 8) == 0) + continue; + setup_xattr(a, entry, p, fd); + } + + free(list); + return (ARCHIVE_OK); +} + +#elif HAVE_EXTATTR_GET_FILE && HAVE_EXTATTR_LIST_FILE && \ + HAVE_DECL_EXTATTR_NAMESPACE_USER + +/* + * FreeBSD extattr interface. + */ + +/* TODO: Implement this. Follow the Linux model above, but + * with FreeBSD-specific system calls, of course. Be careful + * to not include the system extattrs that hold ACLs; we handle + * those separately. + */ +static int +setup_xattr(struct archive_read_disk *a, struct archive_entry *entry, + int namespace, const char *name, const char *fullname, int fd); + +static int +setup_xattr(struct archive_read_disk *a, struct archive_entry *entry, + int namespace, const char *name, const char *fullname, int fd) +{ + ssize_t size; + void *value = NULL; + const char *accpath; + + accpath = archive_entry_sourcepath(entry); + if (accpath == NULL) + accpath = archive_entry_pathname(entry); + + if (fd >= 0) + size = extattr_get_fd(fd, namespace, name, NULL, 0); + else if (!a->follow_symlinks) + size = extattr_get_link(accpath, namespace, name, NULL, 0); + else + size = extattr_get_file(accpath, namespace, name, NULL, 0); + + if (size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't query extended attribute"); + return (ARCHIVE_WARN); + } + + if (size > 0 && (value = malloc(size)) == NULL) { + archive_set_error(&a->archive, errno, "Out of memory"); + return (ARCHIVE_FATAL); + } + + if (fd >= 0) + size = extattr_get_fd(fd, namespace, name, value, size); + else if (!a->follow_symlinks) + size = extattr_get_link(accpath, namespace, name, value, size); + else + size = extattr_get_file(accpath, namespace, name, value, size); + + if (size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't read extended attribute"); + return (ARCHIVE_WARN); + } + + archive_entry_xattr_add_entry(entry, fullname, value, size); + + free(value); + return (ARCHIVE_OK); +} + +static int +setup_xattrs(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + char buff[512]; + char *list, *p; + ssize_t list_size; + const char *path; + int namespace = EXTATTR_NAMESPACE_USER; + + path = archive_entry_sourcepath(entry); + if (path == NULL) + path = archive_entry_pathname(entry); + + if (fd >= 0) + list_size = extattr_list_fd(fd, namespace, NULL, 0); + else if (!a->follow_symlinks) + list_size = extattr_list_link(path, namespace, NULL, 0); + else + list_size = extattr_list_file(path, namespace, NULL, 0); + + if (list_size == -1 && errno == EOPNOTSUPP) + return (ARCHIVE_OK); + if (list_size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't list extended attributes"); + return (ARCHIVE_WARN); + } + + if (list_size == 0) + return (ARCHIVE_OK); + + if ((list = malloc(list_size)) == NULL) { + archive_set_error(&a->archive, errno, "Out of memory"); + return (ARCHIVE_FATAL); + } + + if (fd >= 0) + list_size = extattr_list_fd(fd, namespace, list, list_size); + else if (!a->follow_symlinks) + list_size = extattr_list_link(path, namespace, list, list_size); + else + list_size = extattr_list_file(path, namespace, list, list_size); + + if (list_size == -1) { + archive_set_error(&a->archive, errno, + "Couldn't retrieve extended attributes"); + free(list); + return (ARCHIVE_WARN); + } + + p = list; + while ((p - list) < list_size) { + size_t len = 255 & (int)*p; + char *name; + + strcpy(buff, "user."); + name = buff + strlen(buff); + memcpy(name, p + 1, len); + name[len] = '\0'; + setup_xattr(a, entry, namespace, name, buff, fd); + p += 1 + len; + } + + free(list); + return (ARCHIVE_OK); +} + +#else + +/* + * Generic (stub) extended attribute support. + */ +static int +setup_xattrs(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + (void)fd; /* UNUSED */ + return (ARCHIVE_OK); +} + +#endif + +#if defined(HAVE_LINUX_FIEMAP_H) + +/* + * Linux sparse interface. + * + * The FIEMAP ioctl returns an "extent" for each physical allocation + * on disk. We need to process those to generate a more compact list + * of logical file blocks. We also need to be very careful to use + * FIEMAP_FLAG_SYNC here, since there are reports that Linux sometimes + * does not report allocations for newly-written data that hasn't + * been synced to disk. + * + * It's important to return a minimal sparse file list because we want + * to not trigger sparse file extensions if we don't have to, since + * not all readers support them. + */ + +static int +setup_sparse(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + char buff[4096]; + struct fiemap *fm; + struct fiemap_extent *fe; + int64_t size; + int count, do_fiemap; + int initial_fd = fd; + int exit_sts = ARCHIVE_OK; + + if (archive_entry_filetype(entry) != AE_IFREG + || archive_entry_size(entry) <= 0 + || archive_entry_hardlink(entry) != NULL) + return (ARCHIVE_OK); + + if (fd < 0) { + const char *path; + + path = archive_entry_sourcepath(entry); + if (path == NULL) + path = archive_entry_pathname(entry); + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + archive_set_error(&a->archive, errno, + "Can't open `%s'", path); + return (ARCHIVE_FAILED); + } + } + + count = (sizeof(buff) - sizeof(*fm))/sizeof(*fe); + fm = (struct fiemap *)buff; + fm->fm_start = 0; + fm->fm_length = ~0ULL;; + fm->fm_flags = FIEMAP_FLAG_SYNC; + fm->fm_extent_count = count; + do_fiemap = 1; + size = archive_entry_size(entry); + for (;;) { + int i, r; + + r = ioctl(fd, FS_IOC_FIEMAP, fm); + if (r < 0) { + /* When errno is ENOTTY, it is better we should + * return ARCHIVE_OK because an earlier version + *(<2.6.28) cannot perfom FS_IOC_FIEMAP. + * We should also check if errno is EOPNOTSUPP, + * it means "Operation not supported". */ + if (errno != ENOTTY && errno != EOPNOTSUPP) { + archive_set_error(&a->archive, errno, + "FIEMAP failed"); + exit_sts = ARCHIVE_FAILED; + } + goto exit_setup_sparse; + } + if (fm->fm_mapped_extents == 0) + break; + fe = fm->fm_extents; + for (i = 0; i < fm->fm_mapped_extents; i++, fe++) { + if (!(fe->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) { + /* The fe_length of the last block does not + * adjust itself to its size files. */ + int64_t length = fe->fe_length; + if (fe->fe_logical + length > size) + length -= fe->fe_logical + length - size; + if (fe->fe_logical == 0 && length == size) { + /* This is not sparse. */ + do_fiemap = 0; + break; + } + if (length > 0) + archive_entry_sparse_add_entry(entry, + fe->fe_logical, length); + } + if (fe->fe_flags & FIEMAP_EXTENT_LAST) + do_fiemap = 0; + } + if (do_fiemap) { + fe = fm->fm_extents + fm->fm_mapped_extents -1; + fm->fm_start = fe->fe_logical + fe->fe_length; + } else + break; + } +exit_setup_sparse: + if (initial_fd != fd) + close(fd); + return (exit_sts); +} + +#elif defined(SEEK_HOLE) && defined(SEEK_DATA) && defined(_PC_MIN_HOLE_SIZE) + +/* + * FreeBSD and Solaris sparse interface. + */ + +static int +setup_sparse(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + int64_t size; + int initial_fd = fd; + off_t initial_off; /* FreeBSD/Solaris only, so off_t okay here */ + off_t off_s, off_e; /* FreeBSD/Solaris only, so off_t okay here */ + int exit_sts = ARCHIVE_OK; + + if (archive_entry_filetype(entry) != AE_IFREG + || archive_entry_size(entry) <= 0 + || archive_entry_hardlink(entry) != NULL) + return (ARCHIVE_OK); + + /* Does filesystem support the reporting of hole ? */ + if (fd >= 0) { + if (fpathconf(fd, _PC_MIN_HOLE_SIZE) <= 0) + return (ARCHIVE_OK); + initial_off = lseek(fd, 0, SEEK_CUR); + if (initial_off != 0) + lseek(fd, 0, SEEK_SET); + } else { + const char *path; + + path = archive_entry_sourcepath(entry); + if (path == NULL) + path = archive_entry_pathname(entry); + if (pathconf(path, _PC_MIN_HOLE_SIZE) <= 0) + return (ARCHIVE_OK); + fd = open(path, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + archive_set_error(&a->archive, errno, + "Can't open `%s'", path); + return (ARCHIVE_FAILED); + } + initial_off = 0; + } + + off_s = 0; + size = archive_entry_size(entry); + while (off_s < size) { + off_s = lseek(fd, off_s, SEEK_DATA); + if (off_s == (off_t)-1) { + if (errno == ENXIO) + break;/* no more hole */ + archive_set_error(&a->archive, errno, + "lseek(SEEK_HOLE) failed"); + exit_sts = ARCHIVE_FAILED; + goto exit_setup_sparse; + } + off_e = lseek(fd, off_s, SEEK_HOLE); + if (off_s == (off_t)-1) { + if (errno == ENXIO) { + off_e = lseek(fd, 0, SEEK_END); + if (off_e != (off_t)-1) + break;/* no more data */ + } + archive_set_error(&a->archive, errno, + "lseek(SEEK_DATA) failed"); + exit_sts = ARCHIVE_FAILED; + goto exit_setup_sparse; + } + if (off_s == 0 && off_e == size) + break;/* This is not spase. */ + archive_entry_sparse_add_entry(entry, off_s, + off_e - off_s); + off_s = off_e; + } +exit_setup_sparse: + if (initial_fd != fd) + close(fd); + else + lseek(fd, initial_off, SEEK_SET); + return (exit_sts); +} + +#else + +/* + * Generic (stub) sparse support. + */ +static int +setup_sparse(struct archive_read_disk *a, + struct archive_entry *entry, int fd) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + (void)fd; /* UNUSED */ + return (ARCHIVE_OK); +} + +#endif + +#endif /* !defined(_WIN32) || defined(__CYGWIN__) */ + diff --git a/libarchive/archive_read_disk_posix.c b/libarchive/archive_read_disk_posix.c new file mode 100644 index 0000000..7a9c3d8 --- /dev/null +++ b/libarchive/archive_read_disk_posix.c @@ -0,0 +1,2309 @@ +/*- + * Copyright (c) 2003-2009 Tim Kientzle + * Copyright (c) 2010,2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* This is the tree-walking code for POSIX systems. */ +#if !defined(_WIN32) || defined(__CYGWIN__) + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_STATFS_H +#include +#endif +#ifdef HAVE_SYS_STATVFS_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_LINUX_MAGIC_H +#include +#endif +#ifdef HAVE_DIRECT_H +#include +#endif +#ifdef HAVE_DIRENT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_string.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_disk_private.h" + +#ifndef HAVE_FCHDIR +#error fchdir function required. +#endif +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/*- + * This is a new directory-walking system that addresses a number + * of problems I've had with fts(3). In particular, it has no + * pathname-length limits (other than the size of 'int'), handles + * deep logical traversals, uses considerably less memory, and has + * an opaque interface (easier to modify in the future). + * + * Internally, it keeps a single list of "tree_entry" items that + * represent filesystem objects that require further attention. + * Non-directories are not kept in memory: they are pulled from + * readdir(), returned to the client, then freed as soon as possible. + * Any directory entry to be traversed gets pushed onto the stack. + * + * There is surprisingly little information that needs to be kept for + * each item on the stack. Just the name, depth (represented here as the + * string length of the parent directory's pathname), and some markers + * indicating how to get back to the parent (via chdir("..") for a + * regular dir or via fchdir(2) for a symlink). + */ +/* + * TODO: + * 1) Loop checking. + * 3) Arbitrary logical traversals by closing/reopening intermediate fds. + */ + +struct restore_time { + const char *name; + time_t mtime; + long mtime_nsec; + time_t atime; + long atime_nsec; + mode_t filetype; + int noatime; +}; + +struct tree_entry { + int depth; + struct tree_entry *next; + struct tree_entry *parent; + struct archive_string name; + size_t dirname_length; + int64_t dev; + int64_t ino; + int flags; + int filesystem_id; + /* How to return back to the parent of a symlink. */ + int symlink_parent_fd; + /* How to restore time of a directory. */ + struct restore_time restore_time; +}; + +struct filesystem { + int64_t dev; + int synthetic; + int remote; + int noatime; +#if defined(HAVE_READDIR_R) + size_t name_max; +#endif + long incr_xfer_size; + long max_xfer_size; + long min_xfer_size; + long xfer_align; + + /* + * Buffer used for reading file contents. + */ + /* Exactly allocated memory pointer. */ + unsigned char *allocation_ptr; + /* Pointer adjusted to the filesystem alignment . */ + unsigned char *buff; + size_t buff_size; +}; + +/* Definitions for tree_entry.flags bitmap. */ +#define isDir 1 /* This entry is a regular directory. */ +#define isDirLink 2 /* This entry is a symbolic link to a directory. */ +#define needsFirstVisit 4 /* This is an initial entry. */ +#define needsDescent 8 /* This entry needs to be previsited. */ +#define needsOpen 16 /* This is a directory that needs to be opened. */ +#define needsAscent 32 /* This entry needs to be postvisited. */ + +/* + * Local data for this package. + */ +struct tree { + struct tree_entry *stack; + struct tree_entry *current; + DIR *d; +#define INVALID_DIR_HANDLE NULL + struct dirent *de; +#if defined(HAVE_READDIR_R) + struct dirent *dirent; + size_t dirent_allocated; +#endif + int flags; + int visit_type; + /* Error code from last failed operation. */ + int tree_errno; + + /* Dynamically-sized buffer for holding path */ + struct archive_string path; + + /* Last path element */ + const char *basename; + /* Leading dir length */ + size_t dirname_length; + + int depth; + int openCount; + int maxOpenCount; + int initial_dir_fd; + int working_dir_fd; + + struct stat lst; + struct stat st; + int descend; + int nlink; + /* How to restore time of a file. */ + struct restore_time restore_time; + + struct entry_sparse { + int64_t length; + int64_t offset; + } *sparse_list, *current_sparse; + int sparse_count; + int sparse_list_size; + + char initial_symlink_mode; + char symlink_mode; + struct filesystem *current_filesystem; + struct filesystem *filesystem_table; + int current_filesystem_id; + int max_filesystem_id; + int allocated_filesytem; + + int entry_fd; + int entry_eof; + int64_t entry_remaining_bytes; + int64_t entry_total; + unsigned char *entry_buff; + size_t entry_buff_size; +}; + +/* Definitions for tree.flags bitmap. */ +#define hasStat 16 /* The st entry is valid. */ +#define hasLstat 32 /* The lst entry is valid. */ +#define onWorkingDir 64 /* We are on the working dir where we are + * reading directory entry at this time. */ +#define needsRestoreTimes 128 + +static int +tree_dir_next_posix(struct tree *t); + +#ifdef HAVE_DIRENT_D_NAMLEN +/* BSD extension; avoids need for a strlen() call. */ +#define D_NAMELEN(dp) (dp)->d_namlen +#else +#define D_NAMELEN(dp) (strlen((dp)->d_name)) +#endif + +/* Initiate/terminate a tree traversal. */ +static struct tree *tree_open(const char *, int, int); +static struct tree *tree_reopen(struct tree *, const char *, int); +static void tree_close(struct tree *); +static void tree_free(struct tree *); +static void tree_push(struct tree *, const char *, int, int64_t, int64_t, + struct restore_time *); +static int tree_enter_initial_dir(struct tree *); +static int tree_enter_working_dir(struct tree *); +static int tree_current_dir_fd(struct tree *); + +/* + * tree_next() returns Zero if there is no next entry, non-zero if + * there is. Note that directories are visited three times. + * Directories are always visited first as part of enumerating their + * parent; that is a "regular" visit. If tree_descend() is invoked at + * that time, the directory is added to a work list and will + * subsequently be visited two more times: once just after descending + * into the directory ("postdescent") and again just after ascending + * back to the parent ("postascent"). + * + * TREE_ERROR_DIR is returned if the descent failed (because the + * directory couldn't be opened, for instance). This is returned + * instead of TREE_POSTDESCENT/TREE_POSTASCENT. TREE_ERROR_DIR is not a + * fatal error, but it does imply that the relevant subtree won't be + * visited. TREE_ERROR_FATAL is returned for an error that left the + * traversal completely hosed. Right now, this is only returned for + * chdir() failures during ascent. + */ +#define TREE_REGULAR 1 +#define TREE_POSTDESCENT 2 +#define TREE_POSTASCENT 3 +#define TREE_ERROR_DIR -1 +#define TREE_ERROR_FATAL -2 + +static int tree_next(struct tree *); + +/* + * Return information about the current entry. + */ + +/* + * The current full pathname, length of the full pathname, and a name + * that can be used to access the file. Because tree does use chdir + * extensively, the access path is almost never the same as the full + * current path. + * + * TODO: On platforms that support it, use openat()-style operations + * to eliminate the chdir() operations entirely while still supporting + * arbitrarily deep traversals. This makes access_path troublesome to + * support, of course, which means we'll need a rich enough interface + * that clients can function without it. (In particular, we'll need + * tree_current_open() that returns an open file descriptor.) + * + */ +static const char *tree_current_path(struct tree *); +static const char *tree_current_access_path(struct tree *); + +/* + * Request the lstat() or stat() data for the current path. Since the + * tree package needs to do some of this anyway, and caches the + * results, you should take advantage of it here if you need it rather + * than make a redundant stat() or lstat() call of your own. + */ +static const struct stat *tree_current_stat(struct tree *); +static const struct stat *tree_current_lstat(struct tree *); +static int tree_current_is_symblic_link_target(struct tree *); + +/* The following functions use tricks to avoid a certain number of + * stat()/lstat() calls. */ +/* "is_physical_dir" is equivalent to S_ISDIR(tree_current_lstat()->st_mode) */ +static int tree_current_is_physical_dir(struct tree *); +/* "is_dir" is equivalent to S_ISDIR(tree_current_stat()->st_mode) */ +static int tree_current_is_dir(struct tree *); +static int update_current_filesystem(struct archive_read_disk *a, + int64_t dev); +static int setup_current_filesystem(struct archive_read_disk *); +static int tree_target_is_same_as_parent(struct tree *, const struct stat *); + +static int _archive_read_disk_open(struct archive *, const char *); +static int _archive_read_free(struct archive *); +static int _archive_read_close(struct archive *); +static int _archive_read_data_block(struct archive *, + const void **, size_t *, int64_t *); +static int _archive_read_next_header2(struct archive *, + struct archive_entry *); +static const char *trivial_lookup_gname(void *, int64_t gid); +static const char *trivial_lookup_uname(void *, int64_t uid); +static int setup_sparse(struct archive_read_disk *, struct archive_entry *); +static int close_and_restore_time(int fd, struct tree *, + struct restore_time *); + + +static struct archive_vtable * +archive_read_disk_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_free = _archive_read_free; + av.archive_close = _archive_read_close; + av.archive_read_data_block = _archive_read_data_block; + av.archive_read_next_header2 = _archive_read_next_header2; + inited = 1; + } + return (&av); +} + +const char * +archive_read_disk_gname(struct archive *_a, int64_t gid) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + if (ARCHIVE_OK != __archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_gname")) + return (NULL); + if (a->lookup_gname == NULL) + return (NULL); + return ((*a->lookup_gname)(a->lookup_gname_data, gid)); +} + +const char * +archive_read_disk_uname(struct archive *_a, int64_t uid) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + if (ARCHIVE_OK != __archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_uname")) + return (NULL); + if (a->lookup_uname == NULL) + return (NULL); + return ((*a->lookup_uname)(a->lookup_uname_data, uid)); +} + +int +archive_read_disk_set_gname_lookup(struct archive *_a, + void *private_data, + const char * (*lookup_gname)(void *private, int64_t gid), + void (*cleanup_gname)(void *private)) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_gname_lookup"); + + if (a->cleanup_gname != NULL && a->lookup_gname_data != NULL) + (a->cleanup_gname)(a->lookup_gname_data); + + a->lookup_gname = lookup_gname; + a->cleanup_gname = cleanup_gname; + a->lookup_gname_data = private_data; + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_uname_lookup(struct archive *_a, + void *private_data, + const char * (*lookup_uname)(void *private, int64_t uid), + void (*cleanup_uname)(void *private)) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_uname_lookup"); + + if (a->cleanup_uname != NULL && a->lookup_uname_data != NULL) + (a->cleanup_uname)(a->lookup_uname_data); + + a->lookup_uname = lookup_uname; + a->cleanup_uname = cleanup_uname; + a->lookup_uname_data = private_data; + return (ARCHIVE_OK); +} + +/* + * Create a new archive_read_disk object and initialize it with global state. + */ +struct archive * +archive_read_disk_new(void) +{ + struct archive_read_disk *a; + + a = (struct archive_read_disk *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_READ_DISK_MAGIC; + a->archive.state = ARCHIVE_STATE_NEW; + a->archive.vtable = archive_read_disk_vtable(); + a->lookup_uname = trivial_lookup_uname; + a->lookup_gname = trivial_lookup_gname; + a->entry_wd_fd = -1; + return (&a->archive); +} + +static int +_archive_read_free(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + int r; + + if (_a == NULL) + return (ARCHIVE_OK); + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_free"); + + if (a->archive.state != ARCHIVE_STATE_CLOSED) + r = _archive_read_close(&a->archive); + else + r = ARCHIVE_OK; + + tree_free(a->tree); + if (a->cleanup_gname != NULL && a->lookup_gname_data != NULL) + (a->cleanup_gname)(a->lookup_gname_data); + if (a->cleanup_uname != NULL && a->lookup_uname_data != NULL) + (a->cleanup_uname)(a->lookup_uname_data); + archive_string_free(&a->archive.error_string); + a->archive.magic = 0; + __archive_clean(&a->archive); + free(a); + return (r); +} + +static int +_archive_read_close(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_close"); + + if (a->archive.state != ARCHIVE_STATE_FATAL) + a->archive.state = ARCHIVE_STATE_CLOSED; + + tree_close(a->tree); + + return (ARCHIVE_OK); +} + +static void +setup_symlink_mode(struct archive_read_disk *a, char symlink_mode, + int follow_symlinks) +{ + a->symlink_mode = symlink_mode; + a->follow_symlinks = follow_symlinks; + if (a->tree != NULL) { + a->tree->initial_symlink_mode = a->symlink_mode; + a->tree->symlink_mode = a->symlink_mode; + } +} + +int +archive_read_disk_set_symlink_logical(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_logical"); + setup_symlink_mode(a, 'L', 1); + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_symlink_physical(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_physical"); + setup_symlink_mode(a, 'P', 0); + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_symlink_hybrid(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_hybrid"); + setup_symlink_mode(a, 'H', 1);/* Follow symlinks initially. */ + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_atime_restored(struct archive *_a) +{ +#ifndef HAVE_UTIMES + static int warning_done = 0; +#endif + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_restore_atime"); +#ifdef HAVE_UTIMES + a->restore_time = 1; + if (a->tree != NULL) + a->tree->flags |= needsRestoreTimes; + return (ARCHIVE_OK); +#else + if (warning_done) + /* Warning was already emitted; suppress further warnings. */ + return (ARCHIVE_OK); + + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Cannot restore access time on this system"); + warning_done = 1; + return (ARCHIVE_WARN); +#endif +} + +/* + * Trivial implementations of gname/uname lookup functions. + * These are normally overridden by the client, but these stub + * versions ensure that we always have something that works. + */ +static const char * +trivial_lookup_gname(void *private_data, int64_t gid) +{ + (void)private_data; /* UNUSED */ + (void)gid; /* UNUSED */ + return (NULL); +} + +static const char * +trivial_lookup_uname(void *private_data, int64_t uid) +{ + (void)private_data; /* UNUSED */ + (void)uid; /* UNUSED */ + return (NULL); +} + +/* + * Allocate memory for the reading buffer adjusted to the filesystem + * alignment. + */ +static int +setup_suitable_read_buffer(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + struct filesystem *cf = t->current_filesystem; + size_t asize; + size_t s; + + if (cf->allocation_ptr == NULL) { + /* If we couldn't get a filesystem alignment, + * we use 4096 as default value but we won't use + * O_DIRECT to open() and openat() operations. */ + long xfer_align = (cf->xfer_align == -1)?4096:cf->xfer_align; + + if (cf->max_xfer_size != -1) + asize = cf->max_xfer_size + xfer_align; + else { + long incr = cf->incr_xfer_size; + /* Some platform does not set a proper value to + * incr_xfer_size.*/ + if (incr < 0) + incr = cf->min_xfer_size; + if (cf->min_xfer_size < 0) { + incr = xfer_align; + asize = xfer_align; + } else + asize = cf->min_xfer_size; + + /* Increase a buffer size up to 64K bytes in + * a proper incremant size. */ + while (asize < 1024*64) + asize += incr; + /* Take a margin to adjust to the filesystem + * alignment. */ + asize += xfer_align; + } + cf->allocation_ptr = malloc(asize); + if (cf->allocation_ptr == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + + /* + * Calculate proper address for the filesystem. + */ + s = (uintptr_t)cf->allocation_ptr; + s %= xfer_align; + if (s > 0) + s = xfer_align - s; + + /* + * Set a read buffer pointer in the proper alignment of + * the current filesystem. + */ + cf->buff = cf->allocation_ptr + s; + cf->buff_size = asize - xfer_align; + } + return (ARCHIVE_OK); +} + +static int +_archive_read_data_block(struct archive *_a, const void **buff, + size_t *size, int64_t *offset) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t = a->tree; + int r; + ssize_t bytes; + size_t buffbytes; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_block"); + + if (t->entry_eof || t->entry_remaining_bytes <= 0) { + r = ARCHIVE_EOF; + goto abort_read_data; + } + + /* + * Open the current file. + */ + if (t->entry_fd < 0) { + int flags = O_RDONLY | O_BINARY; + + /* + * Eliminate or reduce cache effects if we can. + * + * Carefully consider this to be enabled. + */ +#if defined(O_DIRECT) && 0/* Disabled for now */ + if (t->current_filesystem->xfer_align != -1 && + t->nlink == 1) + flags |= O_DIRECT; +#endif +#if defined(O_NOATIME) + /* + * Linux has O_NOATIME flag; use it if we need. + */ + if ((t->flags & needsRestoreTimes) != 0 && + t->restore_time.noatime == 0) + flags |= O_NOATIME; + do { +#endif +#ifdef HAVE_OPENAT + t->entry_fd = openat(tree_current_dir_fd(t), + tree_current_access_path(t), flags); +#else + tree_enter_working_dir(t); + t->entry_fd = open(tree_current_access_path(t), flags); +#endif +#if defined(O_NOATIME) + /* + * When we did open the file with O_NOATIME flag, + * if successful, set 1 to t->restore_time.noatime + * not to restore an atime of the file later. + * if failed by EPERM, retry it without O_NOATIME flag. + */ + if (flags & O_NOATIME) { + if (t->entry_fd >= 0) + t->restore_time.noatime = 1; + else if (errno == EPERM) { + flags &= ~O_NOATIME; + continue; + } + } + } while (0); +#endif + if (t->entry_fd < 0) { + archive_set_error(&a->archive, errno, + "Couldn't open %s", tree_current_path(t)); + r = ARCHIVE_FAILED; + tree_enter_initial_dir(t); + goto abort_read_data; + } + tree_enter_initial_dir(t); + } + + /* + * Allocate read buffer if not allocated. + */ + if (t->current_filesystem->allocation_ptr == NULL) { + r = setup_suitable_read_buffer(a); + if (r != ARCHIVE_OK) { + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + } + t->entry_buff = t->current_filesystem->buff; + t->entry_buff_size = t->current_filesystem->buff_size; + + buffbytes = t->entry_buff_size; + if (buffbytes > t->current_sparse->length) + buffbytes = t->current_sparse->length; + + /* + * Skip hole. + * TODO: Should we consider t->current_filesystem->xfer_align? + */ + if (t->current_sparse->offset > t->entry_total) { + if (lseek(t->entry_fd, + (off_t)t->current_sparse->offset, SEEK_SET) < 0) { + archive_set_error(&a->archive, errno, "Seek error"); + r = ARCHIVE_FATAL; + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + bytes = t->current_sparse->offset - t->entry_total; + t->entry_remaining_bytes -= bytes; + t->entry_total += bytes; + } + + /* + * Read file contents. + */ + if (buffbytes > 0) { + bytes = read(t->entry_fd, t->entry_buff, buffbytes); + if (bytes < 0) { + archive_set_error(&a->archive, errno, "Read error"); + r = ARCHIVE_FATAL; + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + } else + bytes = 0; + if (bytes == 0) { + /* Get EOF */ + t->entry_eof = 1; + r = ARCHIVE_EOF; + goto abort_read_data; + } + *buff = t->entry_buff; + *size = bytes; + *offset = t->entry_total; + t->entry_total += bytes; + t->entry_remaining_bytes -= bytes; + if (t->entry_remaining_bytes == 0) { + /* Close the current file descriptor */ + close_and_restore_time(t->entry_fd, t, &t->restore_time); + t->entry_fd = -1; + t->entry_eof = 1; + } + t->current_sparse->offset += bytes; + t->current_sparse->length -= bytes; + if (t->current_sparse->length == 0 && !t->entry_eof) + t->current_sparse++; + return (ARCHIVE_OK); + +abort_read_data: + *buff = NULL; + *size = 0; + *offset = t->entry_total; + if (t->entry_fd >= 0) { + /* Close the current file descriptor */ + close_and_restore_time(t->entry_fd, t, &t->restore_time); + t->entry_fd = -1; + } + return (r); +} + +static int +_archive_read_next_header2(struct archive *_a, struct archive_entry *entry) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t; + const struct stat *st; /* info to use for this entry */ + const struct stat *lst;/* lstat() information */ + int descend, fd = -1, r; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_read_next_header2"); + + t = a->tree; + if (t->entry_fd >= 0) { + close_and_restore_time(t->entry_fd, t, &t->restore_time); + t->entry_fd = -1; + } +#if !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR)) + /* Restore working directory. */ + tree_enter_working_dir(t); +#endif + st = NULL; + lst = NULL; + do { + switch (tree_next(t)) { + case TREE_ERROR_FATAL: + archive_set_error(&a->archive, t->tree_errno, + "%s: Unable to continue traversing directory tree", + tree_current_path(t)); + a->archive.state = ARCHIVE_STATE_FATAL; + tree_enter_initial_dir(t); + return (ARCHIVE_FATAL); + case TREE_ERROR_DIR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: Couldn't visit directory", + tree_current_path(t)); + tree_enter_initial_dir(t); + return (ARCHIVE_FAILED); + case 0: + tree_enter_initial_dir(t); + return (ARCHIVE_EOF); + case TREE_POSTDESCENT: + case TREE_POSTASCENT: + break; + case TREE_REGULAR: + lst = tree_current_lstat(t); + if (lst == NULL) { + archive_set_error(&a->archive, errno, + "%s: Cannot stat", + tree_current_path(t)); + tree_enter_initial_dir(t); + return (ARCHIVE_FAILED); + } + break; + } + } while (lst == NULL); + + /* + * Distinguish 'L'/'P'/'H' symlink following. + */ + switch(t->symlink_mode) { + case 'H': + /* 'H': After the first item, rest like 'P'. */ + t->symlink_mode = 'P'; + /* 'H': First item (from command line) like 'L'. */ + /* FALLTHROUGH */ + case 'L': + /* 'L': Do descend through a symlink to dir. */ + descend = tree_current_is_dir(t); + /* 'L': Follow symlinks to files. */ + a->symlink_mode = 'L'; + a->follow_symlinks = 1; + /* 'L': Archive symlinks as targets, if we can. */ + st = tree_current_stat(t); + if (st != NULL && !tree_target_is_same_as_parent(t, st)) + break; + /* If stat fails, we have a broken symlink; + * in that case, don't follow the link. */ + /* FALLTHROUGH */ + default: + /* 'P': Don't descend through a symlink to dir. */ + descend = tree_current_is_physical_dir(t); + /* 'P': Don't follow symlinks to files. */ + a->symlink_mode = 'P'; + a->follow_symlinks = 0; + /* 'P': Archive symlinks as symlinks. */ + st = lst; + break; + } + + if (update_current_filesystem(a, st->st_dev) != ARCHIVE_OK) { + a->archive.state = ARCHIVE_STATE_FATAL; + tree_enter_initial_dir(t); + return (ARCHIVE_FATAL); + } + t->descend = descend; + + archive_entry_set_pathname(entry, tree_current_path(t)); + archive_entry_copy_sourcepath(entry, tree_current_access_path(t)); + archive_entry_copy_stat(entry, st); + + /* Save the times to be restored. */ + t->restore_time.mtime = archive_entry_mtime(entry); + t->restore_time.mtime_nsec = archive_entry_mtime_nsec(entry); + t->restore_time.atime = archive_entry_atime(entry); + t->restore_time.atime_nsec = archive_entry_atime_nsec(entry); + t->restore_time.filetype = archive_entry_filetype(entry); + t->restore_time.noatime = t->current_filesystem->noatime; + +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + /* + * Open the current file to freely gather its metadata anywhere in + * working directory. + * Note: A symbolic link file cannot be opened with O_NOFOLLOW. + */ + if (a->follow_symlinks || archive_entry_filetype(entry) != AE_IFLNK) + fd = openat(tree_current_dir_fd(t), tree_current_access_path(t), + O_RDONLY | O_NONBLOCK); + /* Restore working directory if openat() operation failed or + * the file is a symbolic link. */ + if (fd < 0) + tree_enter_working_dir(t); + + /* The current direcotry fd is needed at + * archive_read_disk_entry_from_file() function to read link data + * with readlinkat(). */ + a->entry_wd_fd = tree_current_dir_fd(t); +#endif + + /* + * Populate the archive_entry with metadata from the disk. + */ + r = archive_read_disk_entry_from_file(&(a->archive), entry, fd, st); + + /* Close the file descriptor used for reding the current file + * metadata at archive_read_disk_entry_from_file(). */ + if (fd >= 0) + close(fd); + + /* Return to the initial directory. */ + tree_enter_initial_dir(t); + archive_entry_copy_sourcepath(entry, tree_current_path(t)); + + /* + * EOF and FATAL are persistent at this layer. By + * modifying the state, we guarantee that future calls to + * read a header or read data will fail. + */ + switch (r) { + case ARCHIVE_EOF: + a->archive.state = ARCHIVE_STATE_EOF; + break; + case ARCHIVE_OK: + case ARCHIVE_WARN: + t->entry_total = 0; + if (archive_entry_filetype(entry) == AE_IFREG) { + t->nlink = archive_entry_nlink(entry); + t->entry_remaining_bytes = archive_entry_size(entry); + t->entry_eof = (t->entry_remaining_bytes == 0)? 1: 0; + if (!t->entry_eof && + setup_sparse(a, entry) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + t->entry_remaining_bytes = 0; + t->entry_eof = 1; + } + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_RETRY: + break; + case ARCHIVE_FATAL: + a->archive.state = ARCHIVE_STATE_FATAL; + break; + } + + return (r); +} + +static int +setup_sparse(struct archive_read_disk *a, struct archive_entry *entry) +{ + struct tree *t = a->tree; + int64_t length, offset; + int i; + + t->sparse_count = archive_entry_sparse_reset(entry); + if (t->sparse_count+1 > t->sparse_list_size) { + free(t->sparse_list); + t->sparse_list_size = t->sparse_count + 1; + t->sparse_list = malloc(sizeof(t->sparse_list[0]) * + t->sparse_list_size); + if (t->sparse_list == NULL) { + t->sparse_list_size = 0; + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + } + for (i = 0; i < t->sparse_count; i++) { + archive_entry_sparse_next(entry, &offset, &length); + t->sparse_list[i].offset = offset; + t->sparse_list[i].length = length; + } + if (i == 0) { + t->sparse_list[i].offset = 0; + t->sparse_list[i].length = archive_entry_size(entry); + } else { + t->sparse_list[i].offset = archive_entry_size(entry); + t->sparse_list[i].length = 0; + } + t->current_sparse = t->sparse_list; + + return (ARCHIVE_OK); +} + +/* + * Called by the client to mark the directory just returned from + * tree_next() as needing to be visited. + */ +int +archive_read_disk_descend(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t = a->tree; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_descend"); + + if (t->visit_type != TREE_REGULAR || !t->descend) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignored the request descending the current object"); + return (ARCHIVE_WARN); + } + + if (tree_current_is_physical_dir(t)) { + tree_push(t, t->basename, t->current_filesystem_id, + t->lst.st_dev, t->lst.st_ino, &t->restore_time); + t->stack->flags |= isDir; + } else if (tree_current_is_dir(t)) { + tree_push(t, t->basename, t->current_filesystem_id, + t->st.st_dev, t->st.st_ino, &t->restore_time); + t->stack->flags |= isDirLink; + } + t->descend = 0; + return (ARCHIVE_OK); +} + +int +archive_read_disk_open(struct archive *_a, const char *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_NEW | ARCHIVE_STATE_CLOSED, + "archive_read_disk_open"); + archive_clear_error(&a->archive); + + return (_archive_read_disk_open(_a, pathname)); +} + +int +archive_read_disk_open_w(struct archive *_a, const wchar_t *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct archive_string path; + int ret; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_NEW | ARCHIVE_STATE_CLOSED, + "archive_read_disk_open_w"); + archive_clear_error(&a->archive); + + /* Make a char string from a wchar_t string. */ + archive_string_init(&path); + if (archive_string_append_from_wcs(&path, pathname, + wcslen(pathname)) != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't convert a path to a char string"); + a->archive.state = ARCHIVE_STATE_FATAL; + ret = ARCHIVE_FATAL; + } else + ret = _archive_read_disk_open(_a, path.s); + + archive_string_free(&path); + return (ret); +} + +static int +_archive_read_disk_open(struct archive *_a, const char *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + if (a->tree != NULL) + a->tree = tree_reopen(a->tree, pathname, a->restore_time); + else + a->tree = tree_open(pathname, a->symlink_mode, + a->restore_time); + if (a->tree == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate tar data"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + a->archive.state = ARCHIVE_STATE_HEADER; + + return (ARCHIVE_OK); +} + +/* + * Return a current filesystem ID which is index of the filesystem entry + * you've visited through archive_read_disk. + */ +int +archive_read_disk_current_filesystem(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem_id); +} + +static int +update_current_filesystem(struct archive_read_disk *a, int64_t dev) +{ + struct tree *t = a->tree; + int i, fid; + + if (t->current_filesystem != NULL && + t->current_filesystem->dev == dev) + return (ARCHIVE_OK); + + for (i = 0; i < t->max_filesystem_id; i++) { + if (t->filesystem_table[i].dev == dev) { + /* There is the filesytem ID we've already generated. */ + t->current_filesystem_id = i; + t->current_filesystem = &(t->filesystem_table[i]); + return (ARCHIVE_OK); + } + } + + /* + * This is the new filesytem which we have to generate a new ID for. + */ + fid = t->max_filesystem_id++; + if (t->max_filesystem_id > t->allocated_filesytem) { + size_t s; + + s = t->max_filesystem_id * 2; + t->filesystem_table = realloc(t->filesystem_table, + s * sizeof(*t->filesystem_table)); + if (t->filesystem_table == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate tar data"); + return (ARCHIVE_FATAL); + } + t->allocated_filesytem = s; + } + t->current_filesystem_id = fid; + t->current_filesystem = &(t->filesystem_table[fid]); + t->current_filesystem->dev = dev; + t->current_filesystem->allocation_ptr = NULL; + t->current_filesystem->buff = NULL; + + /* Setup the current filesystem properties which depend on + * platform specific. */ + return (setup_current_filesystem(a)); +} + +/* + * Returns 1 if current filesystem is generated filesystem, 0 if it is not + * or -1 if it is unknown. + */ +int +archive_read_disk_current_filesystem_is_synthetic(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem->synthetic); +} + +/* + * Returns 1 if current filesystem is remote filesystem, 0 if it is not + * or -1 if it is unknown. + */ +int +archive_read_disk_current_filesystem_is_remote(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem->remote); +} + +#if defined(_PC_REC_INCR_XFER_SIZE) && defined(_PC_REC_MAX_XFER_SIZE) &&\ + defined(_PC_REC_MIN_XFER_SIZE) && defined(_PC_REC_XFER_ALIGN) +static int +get_xfer_size(struct tree *t, int fd, const char *path) +{ + t->current_filesystem->xfer_align = -1; + errno = 0; + if (fd >= 0) { + t->current_filesystem->incr_xfer_size = + fpathconf(fd, _PC_REC_INCR_XFER_SIZE); + t->current_filesystem->max_xfer_size = + fpathconf(fd, _PC_REC_MAX_XFER_SIZE); + t->current_filesystem->min_xfer_size = + fpathconf(fd, _PC_REC_MIN_XFER_SIZE); + t->current_filesystem->xfer_align = + fpathconf(fd, _PC_REC_XFER_ALIGN); + } else if (path != NULL) { + t->current_filesystem->incr_xfer_size = + pathconf(path, _PC_REC_INCR_XFER_SIZE); + t->current_filesystem->max_xfer_size = + pathconf(path, _PC_REC_MAX_XFER_SIZE); + t->current_filesystem->min_xfer_size = + pathconf(path, _PC_REC_MIN_XFER_SIZE); + t->current_filesystem->xfer_align = + pathconf(path, _PC_REC_XFER_ALIGN); + } + /* At least we need an alignment size. */ + if (t->current_filesystem->xfer_align == -1) + return ((errno == EINVAL)?1:-1); + else + return (0); +} +#else +static int +get_xfer_size(struct tree *t, int fd, const char *path) +{ + (void)t; /* UNUSED */ + (void)fd; /* UNUSED */ + (void)path; /* UNUSED */ + return (1);/* Not supported */ +} +#endif + +#if defined(HAVE_STATFS) && defined(HAVE_FSTATFS) && defined(MNT_LOCAL) \ + && !defined(ST_LOCAL) + +/* + * Gather current filesystem properties on FreeBSD, OpenBSD and Mac OS X. + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + struct statfs sfs; +#if defined(HAVE_GETVFSBYNAME) && defined(VFCF_SYNTHETIC) + struct xvfsconf vfc; +#endif + int r, xr = 0; +#if !defined(HAVE_STRUCT_STATFS_F_NAMEMAX) + long nm; +#endif + + t->current_filesystem->synthetic = -1; + t->current_filesystem->remote = -1; + if (tree_current_is_symblic_link_target(t)) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + /* + * Get file system statistics on any directory + * where current is. + */ + int fd = openat(tree_current_dir_fd(t), + tree_current_access_path(t), O_RDONLY); + if (fd < 0) { + archive_set_error(&a->archive, errno, + "openat failed"); + return (ARCHIVE_FAILED); + } + r = fstatfs(fd, &sfs); + if (r == 0) + xr = get_xfer_size(t, fd, NULL); + close(fd); +#else + r = statfs(tree_current_access_path(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, tree_current_access_path(t)); +#endif + } else { + r = fstatfs(tree_current_dir_fd(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, tree_current_dir_fd(t), NULL); + } + if (r == -1 || xr == -1) { + archive_set_error(&a->archive, errno, "statfs failed"); + return (ARCHIVE_FAILED); + } else if (xr == 1) { + /* pathconf(_PC_REX_*) operations are not supported. */ + t->current_filesystem->xfer_align = sfs.f_bsize; + t->current_filesystem->max_xfer_size = -1; + t->current_filesystem->min_xfer_size = sfs.f_iosize; + t->current_filesystem->incr_xfer_size = sfs.f_iosize; + } + if (sfs.f_flags & MNT_LOCAL) + t->current_filesystem->remote = 0; + else + t->current_filesystem->remote = 1; + +#if defined(HAVE_GETVFSBYNAME) && defined(VFCF_SYNTHETIC) + r = getvfsbyname(sfs.f_fstypename, &vfc); + if (r == -1) { + archive_set_error(&a->archive, errno, "getvfsbyname failed"); + return (ARCHIVE_FAILED); + } + if (vfc.vfc_flags & VFCF_SYNTHETIC) + t->current_filesystem->synthetic = 1; + else + t->current_filesystem->synthetic = 0; +#endif + + if (sfs.f_flags & MNT_NOATIME) + t->current_filesystem->noatime = 1; + else + t->current_filesystem->noatime = 0; + +#if defined(HAVE_READDIR_R) + /* Set maximum filename length. */ +#if defined(HAVE_STRUCT_STATFS_F_NAMEMAX) + t->current_filesystem->name_max = sfs.f_namemax; +#else + /* Mac OS X does not have f_namemax in struct statfs. */ + if (tree_current_is_symblic_link_target(t)) + nm = pathconf(tree_current_access_path(t), _PC_NAME_MAX); + else + nm = fpathconf(tree_current_dir_fd(t), _PC_NAME_MAX); + if (nm == -1) + t->current_filesystem->name_max = NAME_MAX; + else + t->current_filesystem->name_max = nm; +#endif +#endif /* HAVE_READDIR_R */ + return (ARCHIVE_OK); +} + +#elif (defined(HAVE_STATVFS) || defined(HAVE_FSTATVFS)) && defined(ST_LOCAL) + +/* + * Gather current filesystem properties on NetBSD + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + struct statvfs sfs; + int r, xr = 0; + + t->current_filesystem->synthetic = -1; + if (tree_current_is_symblic_link_target(t)) { + r = statvfs(tree_current_access_path(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, tree_current_access_path(t)); + } else { +#ifdef HAVE_FSTATVFS + r = fstatvfs(tree_current_dir_fd(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, tree_current_dir_fd(t), NULL); +#else + r = statvfs(".", &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, "."); +#endif + } + if (r == -1 || xr == -1) { + t->current_filesystem->remote = -1; + archive_set_error(&a->archive, errno, "statvfs failed"); + return (ARCHIVE_FAILED); + } else if (xr == 1) { + /* Usuall come here unless NetBSD supports _PC_REC_XFER_ALIGN + * for pathconf() function. */ + t->current_filesystem->xfer_align = sfs.f_frsize; + t->current_filesystem->max_xfer_size = -1; + t->current_filesystem->min_xfer_size = sfs.f_iosize; + t->current_filesystem->incr_xfer_size = sfs.f_iosize; + } + if (sfs.f_flag & ST_LOCAL) + t->current_filesystem->remote = 0; + else + t->current_filesystem->remote = 1; + + if (sfs.f_flag & ST_NOATIME) + t->current_filesystem->noatime = 1; + else + t->current_filesystem->noatime = 0; + + /* Set maximum filename length. */ + t->current_filesystem->name_max = sfs.f_namemax; + return (ARCHIVE_OK); +} + +#elif defined(HAVE_SYS_STATFS_H) && defined(HAVE_LINUX_MAGIC_H) &&\ + defined(HAVE_STATFS) && defined(HAVE_FSTATFS) +/* + * Note: statfs is deprecated since LSB 3.2 + */ + +#ifndef CIFS_SUPER_MAGIC +#define CIFS_SUPER_MAGIC 0xFF534D42 +#endif +#ifndef DEVFS_SUPER_MAGIC +#define DEVFS_SUPER_MAGIC 0x1373 +#endif + +/* + * Gather current filesystem properties on Linux + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + struct statfs sfs; + struct statvfs svfs; + int r, vr = 0, xr = 0; + + if (tree_current_is_symblic_link_target(t)) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + /* + * Get file system statistics on any directory + * where current is. + */ + int fd = openat(tree_current_dir_fd(t), + tree_current_access_path(t), O_RDONLY); + if (fd < 0) { + archive_set_error(&a->archive, errno, + "openat failed"); + return (ARCHIVE_FAILED); + } + vr = fstatvfs(fd, &svfs);/* for f_flag, mount flags */ + r = fstatfs(fd, &sfs); + if (r == 0) + xr = get_xfer_size(t, fd, NULL); + close(fd); +#else + vr = statvfs(tree_current_access_path(t), &svfs); + r = statfs(tree_current_access_path(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, tree_current_access_path(t)); +#endif + } else { +#ifdef HAVE_FSTATFS + vr = fstatvfs(tree_current_dir_fd(t), &svfs); + r = fstatfs(tree_current_dir_fd(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, tree_current_dir_fd(t), NULL); +#elif defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) +#error "Unexpected case. Please tell us about this error." +#else + vr = statvfs(".", &svfs); + r = statfs(".", &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, "."); +#endif + } + if (r == -1 || xr == -1 || vr == -1) { + t->current_filesystem->synthetic = -1; + t->current_filesystem->remote = -1; + archive_set_error(&a->archive, errno, "statfs failed"); + return (ARCHIVE_FAILED); + } else if (xr == 1) { + /* pathconf(_PC_REX_*) operations are not supported. */ + t->current_filesystem->xfer_align = svfs.f_frsize; + t->current_filesystem->max_xfer_size = -1; + t->current_filesystem->min_xfer_size = svfs.f_bsize; + t->current_filesystem->incr_xfer_size = svfs.f_bsize; + } + switch (sfs.f_type) { + case AFS_SUPER_MAGIC: + case CIFS_SUPER_MAGIC: + case CODA_SUPER_MAGIC: + case NCP_SUPER_MAGIC:/* NetWare */ + case NFS_SUPER_MAGIC: + case SMB_SUPER_MAGIC: + t->current_filesystem->remote = 1; + t->current_filesystem->synthetic = 0; + break; + case DEVFS_SUPER_MAGIC: + case PROC_SUPER_MAGIC: + case USBDEVICE_SUPER_MAGIC: + t->current_filesystem->remote = 0; + t->current_filesystem->synthetic = 1; + break; + default: + t->current_filesystem->remote = 0; + t->current_filesystem->synthetic = 0; + break; + } + +#if defined(ST_NOATIME) + if (svfs.f_flag & ST_NOATIME) + t->current_filesystem->noatime = 1; + else +#endif + t->current_filesystem->noatime = 0; + +#if defined(HAVE_READDIR_R) + /* Set maximum filename length. */ + t->current_filesystem->name_max = sfs.f_namelen; +#endif + return (ARCHIVE_OK); +} + +#elif defined(HAVE_SYS_STATVFS_H) &&\ + (defined(HAVE_STATVFS) || defined(HAVE_FSTATVFS)) + +/* + * Gather current filesystem properties on other posix platform. + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + struct statvfs sfs; + int r, xr = 0; + + t->current_filesystem->synthetic = -1;/* Not supported */ + t->current_filesystem->remote = -1;/* Not supported */ + if (tree_current_is_symblic_link_target(t)) { +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + /* + * Get file system statistics on any directory + * where current is. + */ + int fd = openat(tree_current_dir_fd(t), + tree_current_access_path(t), O_RDONLY); + if (fd < 0) { + archive_set_error(&a->archive, errno, + "openat failed"); + return (ARCHIVE_FAILED); + } + r = fstatvfs(fd, &sfs); + if (r == 0) + xr = get_xfer_size(t, fd, NULL); + close(fd); +#else + r = statvfs(tree_current_access_path(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, tree_current_access_path(t)); +#endif + } else { +#ifdef HAVE_FSTATVFS + r = fstatvfs(tree_current_dir_fd(t), &sfs); + if (r == 0) + xr = get_xfer_size(t, tree_current_dir_fd(t), NULL); +#elif defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) +#error "Unexpected case. Please tell us about this error." +#else + r = statvfs(".", &sfs); + if (r == 0) + xr = get_xfer_size(t, -1, "."); +#endif + } + if (r == -1 || xr == -1) { + t->current_filesystem->synthetic = -1; + t->current_filesystem->remote = -1; + archive_set_error(&a->archive, errno, "statvfs failed"); + return (ARCHIVE_FAILED); + } else if (xr == 1) { + /* pathconf(_PC_REX_*) operations are not supported. */ + t->current_filesystem->xfer_align = sfs.f_frsize; + t->current_filesystem->max_xfer_size = -1; + t->current_filesystem->min_xfer_size = sfs.f_bsize; + t->current_filesystem->incr_xfer_size = sfs.f_bsize; + } + +#if defined(ST_NOATIME) + if (sfs.f_flag & ST_NOATIME) + t->current_filesystem->noatime = 1; + else +#endif + t->current_filesystem->noatime = 0; + +#if defined(HAVE_READDIR_R) + /* Set maximum filename length. */ + t->current_filesystem->name_max = sfs.f_namemax; +#endif + return (ARCHIVE_OK); +} + +#else + +/* + * Generic: Gather current filesystem properties. + * TODO: Is this generic function really needed? + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; +#if defined(_PC_NAME_MAX) && defined(HAVE_READDIR_R) + long nm; +#endif + t->current_filesystem->synthetic = -1;/* Not supported */ + t->current_filesystem->remote = -1;/* Not supported */ + t->current_filesystem->noatime = 0; + (void)get_xfer_size(t, -1, ".");/* Dummy call to avoid build error. */ + t->current_filesystem->xfer_align = -1;/* Unknown */ + t->current_filesystem->max_xfer_size = -1; + t->current_filesystem->min_xfer_size = -1; + t->current_filesystem->incr_xfer_size = -1; + +#if defined(HAVE_READDIR_R) + /* Set maximum filename length. */ +# if defined(_PC_NAME_MAX) + if (tree_current_is_symblic_link_target(t)) + nm = pathconf(tree_current_access_path(t), _PC_NAME_MAX); + else + nm = fpathconf(tree_current_dir_fd(t), _PC_NAME_MAX); + if (nm == -1) +# endif /* _PC_NAME_MAX */ + /* + * Some sysmtes (HP-UX or others?) incorrectly defined + * NAME_MAX macro to be a smaller value. + */ +# if defined(NAME_MAX) && NAME_MAX >= 255 + t->current_filesystem->name_max = NAME_MAX; +# else + /* No way to get a trusted value of maximum filename + * length. */ + t->current_filesystem->name_max = PATH_MAX; +# endif /* NAME_MAX */ +# if defined(_PC_NAME_MAX) + else + t->current_filesystem->name_max = nm; +# endif /* _PC_NAME_MAX */ +#endif /* HAVE_READDIR_R */ + return (ARCHIVE_OK); +} + +#endif + +static int +close_and_restore_time(int fd, struct tree *t, struct restore_time *rt) +{ +#ifndef HAVE_UTIMES + (void)a; /* UNUSED */ + return (close(fd)); +#else +#if defined(HAVE_FUTIMENS) && !defined(__CYGWIN__) + struct timespec timespecs[2]; +#endif + struct timeval times[2]; + + if ((t->flags & needsRestoreTimes) == 0 || rt->noatime) { + if (fd >= 0) + return (close(fd)); + else + return (0); + } + +#if defined(HAVE_FUTIMENS) && !defined(__CYGWIN__) + timespecs[1].tv_sec = rt->mtime; + timespecs[1].tv_nsec = rt->mtime_nsec; + + timespecs[0].tv_sec = rt->atime; + timespecs[0].tv_nsec = rt->atime_nsec; + /* futimens() is defined in POSIX.1-2008. */ + if (futimens(fd, timespecs) == 0) + return (close(fd)); +#endif + + times[1].tv_sec = rt->mtime; + times[1].tv_usec = rt->mtime_nsec / 1000; + + times[0].tv_sec = rt->atime; + times[0].tv_usec = rt->atime_nsec / 1000; + +#if !defined(HAVE_FUTIMENS) && defined(HAVE_FUTIMES) && !defined(__CYGWIN__) + if (futimes(fd, times) == 0) + return (close(fd)); +#endif + close(fd); +#if defined(HAVE_FUTIMESAT) + if (futimesat(tree_current_dir_fd(t), rt->name, times) == 0) + return (0); +#endif +#ifdef HAVE_LUTIMES + if (lutimes(rt->name, times) != 0) +#else + if (AE_IFLNK != rt->filetype && utimes(rt->name, times) != 0) +#endif + return (-1); +#endif + return (0); +} + +/* + * Add a directory path to the current stack. + */ +static void +tree_push(struct tree *t, const char *path, int filesystem_id, + int64_t dev, int64_t ino, struct restore_time *rt) +{ + struct tree_entry *te; + + te = malloc(sizeof(*te)); + memset(te, 0, sizeof(*te)); + te->next = t->stack; + te->parent = t->current; + if (te->parent) + te->depth = te->parent->depth + 1; + t->stack = te; + archive_string_init(&te->name); + te->symlink_parent_fd = -1; + archive_strcpy(&te->name, path); + te->flags = needsDescent | needsOpen | needsAscent; + te->filesystem_id = filesystem_id; + te->dev = dev; + te->ino = ino; + te->dirname_length = t->dirname_length; + te->restore_time.name = te->name.s; + if (rt != NULL) { + te->restore_time.mtime = rt->mtime; + te->restore_time.mtime_nsec = rt->mtime_nsec; + te->restore_time.atime = rt->atime; + te->restore_time.atime_nsec = rt->atime_nsec; + te->restore_time.filetype = rt->filetype; + te->restore_time.noatime = rt->noatime; + } +} + +/* + * Append a name to the current dir path. + */ +static void +tree_append(struct tree *t, const char *name, size_t name_length) +{ + size_t size_needed; + + t->path.s[t->dirname_length] = '\0'; + t->path.length = t->dirname_length; + /* Strip trailing '/' from name, unless entire name is "/". */ + while (name_length > 1 && name[name_length - 1] == '/') + name_length--; + + /* Resize pathname buffer as needed. */ + size_needed = name_length + t->dirname_length + 2; + archive_string_ensure(&t->path, size_needed); + /* Add a separating '/' if it's needed. */ + if (t->dirname_length > 0 && t->path.s[archive_strlen(&t->path)-1] != '/') + archive_strappend_char(&t->path, '/'); + t->basename = t->path.s + archive_strlen(&t->path); + archive_strncat(&t->path, name, name_length); + t->restore_time.name = t->basename; +} + +/* + * Open a directory tree for traversal. + */ +static struct tree * +tree_open(const char *path, int symlink_mode, int restore_time) +{ + struct tree *t; + + if ((t = malloc(sizeof(*t))) == NULL) + return (NULL); + memset(t, 0, sizeof(*t)); + archive_string_init(&t->path); + archive_string_ensure(&t->path, 31); + t->initial_symlink_mode = symlink_mode; + return (tree_reopen(t, path, restore_time)); +} + +static struct tree * +tree_reopen(struct tree *t, const char *path, int restore_time) +{ + t->flags = (restore_time)?needsRestoreTimes:0; + t->visit_type = 0; + t->tree_errno = 0; + t->dirname_length = 0; + t->depth = 0; + t->descend = 0; + t->current = NULL; + t->d = INVALID_DIR_HANDLE; + t->symlink_mode = t->initial_symlink_mode; + archive_string_empty(&t->path); + t->entry_fd = -1; + t->entry_eof = 0; + t->entry_remaining_bytes = 0; + + /* First item is set up a lot like a symlink traversal. */ + tree_push(t, path, 0, 0, 0, NULL); + t->stack->flags = needsFirstVisit; + t->maxOpenCount = t->openCount = 1; + t->initial_dir_fd = open(".", O_RDONLY); + t->working_dir_fd = dup(t->initial_dir_fd); + return (t); +} + +static int +tree_descent(struct tree *t) +{ + int r = 0; + +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + int new_fd; + t->dirname_length = archive_strlen(&t->path); + new_fd = openat(t->working_dir_fd, t->stack->name.s, O_RDONLY); + if (new_fd < 0) { + t->tree_errno = errno; + r = TREE_ERROR_DIR; + } else { + t->depth++; + /* If it is a link, set up fd for the ascent. */ + if (t->stack->flags & isDirLink) { + t->stack->symlink_parent_fd = t->working_dir_fd; + t->openCount++; + if (t->openCount > t->maxOpenCount) + t->maxOpenCount = t->openCount; + } else + close(t->working_dir_fd); + t->working_dir_fd = new_fd; + } +#else + /* If it is a link, set up fd for the ascent. */ + if (t->stack->flags & isDirLink) + t->stack->symlink_parent_fd = t->working_dir_fd; + else { + close(t->working_dir_fd); + t->openCount--; + } + t->working_dir_fd = -1; + t->dirname_length = archive_strlen(&t->path); + if (chdir(t->stack->name.s) != 0) + { + t->tree_errno = errno; + r = TREE_ERROR_DIR; + } else { + t->depth++; + t->working_dir_fd = open(".", O_RDONLY); + t->openCount++; + if (t->openCount > t->maxOpenCount) + t->maxOpenCount = t->openCount; + } +#endif + return (r); +} + +/* + * We've finished a directory; ascend back to the parent. + */ +static int +tree_ascend(struct tree *t) +{ + struct tree_entry *te; + int r = 0, prev_dir_fd; + + te = t->stack; + prev_dir_fd = t->working_dir_fd; +#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && defined(HAVE_FDOPENDIR) + if (te->flags & isDirLink) + t->working_dir_fd = te->symlink_parent_fd; + else { + int new_fd = openat(t->working_dir_fd, "..", O_RDONLY); + if (new_fd < 0) { + t->tree_errno = errno; + r = TREE_ERROR_FATAL; + } else + t->working_dir_fd = new_fd; + } +#else + if (te->flags & isDirLink) { + if (fchdir(te->symlink_parent_fd) != 0) { + t->tree_errno = errno; + r = TREE_ERROR_FATAL; + } else + t->working_dir_fd = te->symlink_parent_fd; + } else { + if (chdir("..") != 0) { + t->tree_errno = errno; + r = TREE_ERROR_FATAL; + } else + t->working_dir_fd = open(".", O_RDONLY); + } +#endif + if (r == 0) { + /* Current directory has been changed, we should + * close an fd of previous working directory. */ + close_and_restore_time(prev_dir_fd, t, &te->restore_time); + if (te->flags & isDirLink) { + t->openCount--; + te->symlink_parent_fd = -1; + } + t->depth--; + } + return (r); +} + +/* + * Return to the initial directory where tree_open() was performed. + */ +static int +tree_enter_initial_dir(struct tree *t) +{ + int r = 0; + + if (t->flags & onWorkingDir) { + r = fchdir(t->initial_dir_fd); + if (r == 0) + t->flags &= ~onWorkingDir; + } + return (r); +} + +/* + * Restore working directory of directory traversals. + */ +static int +tree_enter_working_dir(struct tree *t) +{ + int r = 0; + + /* + * Change the current directory if really needed. + * Sometimes this is unneeded when we did not do + * descent. + */ + if (t->depth > 0 && (t->flags & onWorkingDir) == 0) { + r = fchdir(t->working_dir_fd); + if (r == 0) + t->flags |= onWorkingDir; + } + return (r); +} + +static int +tree_current_dir_fd(struct tree *t) +{ + return (t->working_dir_fd); +} + +/* + * Pop the working stack. + */ +static void +tree_pop(struct tree *t) +{ + struct tree_entry *te; + + t->path.s[t->dirname_length] = '\0'; + t->path.length = t->dirname_length; + if (t->stack == t->current && t->current != NULL) + t->current = t->current->parent; + te = t->stack; + t->stack = te->next; + t->dirname_length = te->dirname_length; + t->basename = t->path.s + t->dirname_length; + while (t->basename[0] == '/') + t->basename++; + archive_string_free(&te->name); + free(te); +} + +/* + * Get the next item in the tree traversal. + */ +static int +tree_next(struct tree *t) +{ + int r; + + while (t->stack != NULL) { + /* If there's an open dir, get the next entry from there. */ + if (t->d != INVALID_DIR_HANDLE) { + r = tree_dir_next_posix(t); + if (r == 0) + continue; + return (r); + } + + if (t->stack->flags & needsFirstVisit) { + /* Top stack item needs a regular visit. */ + t->current = t->stack; + tree_append(t, t->stack->name.s, + archive_strlen(&(t->stack->name))); + /* t->dirname_length = t->path_length; */ + /* tree_pop(t); */ + t->stack->flags &= ~needsFirstVisit; + return (t->visit_type = TREE_REGULAR); + } else if (t->stack->flags & needsDescent) { + /* Top stack item is dir to descend into. */ + t->current = t->stack; + tree_append(t, t->stack->name.s, + archive_strlen(&(t->stack->name))); + t->stack->flags &= ~needsDescent; + r = tree_descent(t); + if (r != 0) { + tree_pop(t); + t->visit_type = r; + } else + t->visit_type = TREE_POSTDESCENT; + return (t->visit_type); + } else if (t->stack->flags & needsOpen) { + t->stack->flags &= ~needsOpen; + r = tree_dir_next_posix(t); + if (r == 0) + continue; + return (r); + } else if (t->stack->flags & needsAscent) { + /* Top stack item is dir and we're done with it. */ + r = tree_ascend(t); + tree_pop(t); + t->visit_type = r != 0 ? r : TREE_POSTASCENT; + return (t->visit_type); + } else { + /* Top item on stack is dead. */ + tree_pop(t); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + } + } + return (t->visit_type = 0); +} + +static int +tree_dir_next_posix(struct tree *t) +{ + int r; + const char *name; + size_t namelen; + + if (t->d == NULL) { +#if defined(HAVE_READDIR_R) + size_t dirent_size; +#endif + +#if defined(HAVE_FDOPENDIR) + if ((t->d = fdopendir(dup(t->working_dir_fd))) == NULL) { +#else + if ((t->d = opendir(".")) == NULL) { +#endif + r = tree_ascend(t); /* Undo "chdir" */ + tree_pop(t); + t->tree_errno = errno; + t->visit_type = r != 0 ? r : TREE_ERROR_DIR; + return (t->visit_type); + } +#if defined(HAVE_READDIR_R) + dirent_size = offsetof(struct dirent, d_name) + + t->filesystem_table[t->current->filesystem_id].name_max + 1; + if (t->dirent == NULL || t->dirent_allocated < dirent_size) { + free(t->dirent); + t->dirent = malloc(dirent_size); + if (t->dirent == NULL) { + closedir(t->d); + t->d = INVALID_DIR_HANDLE; + (void)tree_ascend(t); + tree_pop(t); + t->tree_errno = ENOMEM; + t->visit_type = TREE_ERROR_DIR; + return (t->visit_type); + } + t->dirent_allocated = dirent_size; + } +#endif /* HAVE_READDIR_R */ + } + for (;;) { +#if defined(HAVE_READDIR_R) + r = readdir_r(t->d, t->dirent, &t->de); + if (r != 0 || t->de == NULL) { +#else + errno = 0; + t->de = readdir(t->d); + if (t->de == NULL) { + r = errno; +#endif + closedir(t->d); + t->d = INVALID_DIR_HANDLE; + if (r != 0) { + t->tree_errno = r; + t->visit_type = TREE_ERROR_DIR; + return (t->visit_type); + } else + return (0); + } + name = t->de->d_name; + namelen = D_NAMELEN(t->de); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + if (name[0] == '.' && name[1] == '\0') + continue; + if (name[0] == '.' && name[1] == '.' && name[2] == '\0') + continue; + tree_append(t, name, namelen); + return (t->visit_type = TREE_REGULAR); + } +} + + +/* + * Get the stat() data for the entry just returned from tree_next(). + */ +static const struct stat * +tree_current_stat(struct tree *t) +{ + if (!(t->flags & hasStat)) { +#ifdef HAVE_FSTATAT + if (fstatat(tree_current_dir_fd(t), + tree_current_access_path(t), &t->st, 0) != 0) +#else + if (stat(tree_current_access_path(t), &t->st) != 0) +#endif + return NULL; + t->flags |= hasStat; + } + return (&t->st); +} + +/* + * Get the lstat() data for the entry just returned from tree_next(). + */ +static const struct stat * +tree_current_lstat(struct tree *t) +{ + if (!(t->flags & hasLstat)) { +#ifdef HAVE_FSTATAT + if (fstatat(tree_current_dir_fd(t), + tree_current_access_path(t), &t->lst, + AT_SYMLINK_NOFOLLOW) != 0) +#else + if (lstat(tree_current_access_path(t), &t->lst) != 0) +#endif + return NULL; + t->flags |= hasLstat; + } + return (&t->lst); +} + +/* + * Test whether current entry is a dir or link to a dir. + */ +static int +tree_current_is_dir(struct tree *t) +{ + const struct stat *st; + /* + * If we already have lstat() info, then try some + * cheap tests to determine if this is a dir. + */ + if (t->flags & hasLstat) { + /* If lstat() says it's a dir, it must be a dir. */ + if (S_ISDIR(tree_current_lstat(t)->st_mode)) + return 1; + /* Not a dir; might be a link to a dir. */ + /* If it's not a link, then it's not a link to a dir. */ + if (!S_ISLNK(tree_current_lstat(t)->st_mode)) + return 0; + /* + * It's a link, but we don't know what it's a link to, + * so we'll have to use stat(). + */ + } + + st = tree_current_stat(t); + /* If we can't stat it, it's not a dir. */ + if (st == NULL) + return 0; + /* Use the definitive test. Hopefully this is cached. */ + return (S_ISDIR(st->st_mode)); +} + +/* + * Test whether current entry is a physical directory. Usually, we + * already have at least one of stat() or lstat() in memory, so we + * use tricks to try to avoid an extra trip to the disk. + */ +static int +tree_current_is_physical_dir(struct tree *t) +{ + const struct stat *st; + + /* + * If stat() says it isn't a dir, then it's not a dir. + * If stat() data is cached, this check is free, so do it first. + */ + if ((t->flags & hasStat) + && (!S_ISDIR(tree_current_stat(t)->st_mode))) + return 0; + + /* + * Either stat() said it was a dir (in which case, we have + * to determine whether it's really a link to a dir) or + * stat() info wasn't available. So we use lstat(), which + * hopefully is already cached. + */ + + st = tree_current_lstat(t); + /* If we can't stat it, it's not a dir. */ + if (st == NULL) + return 0; + /* Use the definitive test. Hopefully this is cached. */ + return (S_ISDIR(st->st_mode)); +} + +/* + * Test whether the same file has been in the tree as its parent. + */ +static int +tree_target_is_same_as_parent(struct tree *t, const struct stat *st) +{ + struct tree_entry *te; + + for (te = t->current->parent; te != NULL; te = te->parent) { + if (te->dev == st->st_dev && te->ino == st->st_ino) + return (1); + } + return (0); +} + +/* + * Test whether the current file is symbolic link target and + * on the other filesystem. + */ +static int +tree_current_is_symblic_link_target(struct tree *t) +{ + static const struct stat *lst, *st; + + lst = tree_current_lstat(t); + st = tree_current_stat(t); + return (st != NULL && st->st_dev == t->current_filesystem->dev && + st->st_dev != lst->st_dev); +} + +/* + * Return the access path for the entry just returned from tree_next(). + */ +static const char * +tree_current_access_path(struct tree *t) +{ + return (t->basename); +} + +/* + * Return the full path for the entry just returned from tree_next(). + */ +static const char * +tree_current_path(struct tree *t) +{ + return (t->path.s); +} + +/* + * Terminate the traversal. + */ +static void +tree_close(struct tree *t) +{ + + if (t == NULL) + return; + if (t->entry_fd >= 0) { + close_and_restore_time(t->entry_fd, t, &t->restore_time); + t->entry_fd = -1; + } + /* Close the handle of readdir(). */ + if (t->d != INVALID_DIR_HANDLE) { + closedir(t->d); + t->d = INVALID_DIR_HANDLE; + } + /* Release anything remaining in the stack. */ + while (t->stack != NULL) { + if (t->stack->flags & isDirLink) + close(t->stack->symlink_parent_fd); + tree_pop(t); + } + if (t->working_dir_fd >= 0) { + close(t->working_dir_fd); + t->working_dir_fd = -1; + } + if (t->initial_dir_fd >= 0) { + close(t->initial_dir_fd); + t->initial_dir_fd = -1; + } +} + +/* + * Release any resources. + */ +static void +tree_free(struct tree *t) +{ + int i; + + if (t == NULL) + return; + archive_string_free(&t->path); +#if defined(HAVE_READDIR_R) + free(t->dirent); +#endif + free(t->sparse_list); + for (i = 0; i < t->max_filesystem_id; i++) + free(t->filesystem_table[i].allocation_ptr); + free(t->filesystem_table); + free(t); +} + +#endif diff --git a/libarchive/archive_read_disk_private.h b/libarchive/archive_read_disk_private.h new file mode 100644 index 0000000..4446474 --- /dev/null +++ b/libarchive/archive_read_disk_private.h @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 2003-2009 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_read_disk_private.h 201105 2009-12-28 03:20:54Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_READ_DISK_PRIVATE_H_INCLUDED +#define ARCHIVE_READ_DISK_PRIVATE_H_INCLUDED + +struct tree; + +struct archive_read_disk { + struct archive archive; + + /* + * Symlink mode is one of 'L'ogical, 'P'hysical, or 'H'ybrid, + * following an old BSD convention. 'L' follows all symlinks, + * 'P' follows none, 'H' follows symlinks only for the first + * item. + */ + char symlink_mode; + + /* + * Since symlink interaction changes, we need to track whether + * we're following symlinks for the current item. 'L' mode above + * sets this true, 'P' sets it false, 'H' changes it as we traverse. + */ + char follow_symlinks; /* Either 'L' or 'P'. */ + + /* Directory traversals. */ + struct tree *tree; + + /* Set 1 if users request to restore atime . */ + int restore_time; + int entry_wd_fd; + + const char * (*lookup_gname)(void *private, int64_t gid); + void (*cleanup_gname)(void *private); + void *lookup_gname_data; + const char * (*lookup_uname)(void *private, int64_t uid); + void (*cleanup_uname)(void *private); + void *lookup_uname_data; +}; + +#endif diff --git a/libarchive/archive_read_disk_set_standard_lookup.c b/libarchive/archive_read_disk_set_standard_lookup.c new file mode 100644 index 0000000..3bc52c7 --- /dev/null +++ b/libarchive/archive_read_disk_set_standard_lookup.c @@ -0,0 +1,311 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_disk_set_standard_lookup.c 201109 2009-12-28 03:30:31Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#ifdef HAVE_PWD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) +int +archive_read_disk_set_standard_lookup(struct archive *a) +{ + archive_set_error(a, -1, "Standard lookups not available on Windows"); + return (ARCHIVE_FATAL); +} +#else /* ! (_WIN32 && !__CYGWIN__) */ +#define name_cache_size 127 + +static const char * const NO_NAME = "(noname)"; + +struct name_cache { + struct archive *archive; + char *buff; + size_t buff_size; + int probes; + int hits; + size_t size; + struct { + id_t id; + const char *name; + } cache[name_cache_size]; +}; + +static const char * lookup_gname(void *, int64_t); +static const char * lookup_uname(void *, int64_t); +static void cleanup(void *); +static const char * lookup_gname_helper(struct name_cache *, id_t gid); +static const char * lookup_uname_helper(struct name_cache *, id_t uid); + +/* + * Installs functions that use getpwuid()/getgrgid()---along with + * a simple cache to accelerate such lookups---into the archive_read_disk + * object. This is in a separate file because getpwuid()/getgrgid() + * can pull in a LOT of library code (including NIS/LDAP functions, which + * pull in DNS resolveers, etc). This can easily top 500kB, which makes + * it inappropriate for some space-constrained applications. + * + * Applications that are size-sensitive may want to just use the + * real default functions (defined in archive_read_disk.c) that just + * use the uid/gid without the lookup. Or define your own custom functions + * if you prefer. + */ +int +archive_read_disk_set_standard_lookup(struct archive *a) +{ + struct name_cache *ucache = malloc(sizeof(struct name_cache)); + struct name_cache *gcache = malloc(sizeof(struct name_cache)); + + if (ucache == NULL || gcache == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate uname/gname lookup cache"); + free(ucache); + free(gcache); + return (ARCHIVE_FATAL); + } + + memset(ucache, 0, sizeof(*ucache)); + ucache->archive = a; + ucache->size = name_cache_size; + memset(gcache, 0, sizeof(*gcache)); + gcache->archive = a; + gcache->size = name_cache_size; + + archive_read_disk_set_gname_lookup(a, gcache, lookup_gname, cleanup); + archive_read_disk_set_uname_lookup(a, ucache, lookup_uname, cleanup); + + return (ARCHIVE_OK); +} + +static void +cleanup(void *data) +{ + struct name_cache *cache = (struct name_cache *)data; + size_t i; + + if (cache != NULL) { + for (i = 0; i < cache->size; i++) { + if (cache->cache[i].name != NULL && + cache->cache[i].name != NO_NAME) + free((void *)(uintptr_t)cache->cache[i].name); + } + free(cache->buff); + free(cache); + } +} + +/* + * Lookup uid/gid from uname/gname, return NULL if no match. + */ +static const char * +lookup_name(struct name_cache *cache, + const char * (*lookup_fn)(struct name_cache *, id_t), id_t id) +{ + const char *name; + int slot; + + + cache->probes++; + + slot = id % cache->size; + if (cache->cache[slot].name != NULL) { + if (cache->cache[slot].id == id) { + cache->hits++; + if (cache->cache[slot].name == NO_NAME) + return (NULL); + return (cache->cache[slot].name); + } + if (cache->cache[slot].name != NO_NAME) + free((void *)(uintptr_t)cache->cache[slot].name); + cache->cache[slot].name = NULL; + } + + name = (lookup_fn)(cache, id); + if (name == NULL) { + /* Cache and return the negative response. */ + cache->cache[slot].name = NO_NAME; + cache->cache[slot].id = id; + return (NULL); + } + + cache->cache[slot].name = name; + cache->cache[slot].id = id; + return (cache->cache[slot].name); +} + +static const char * +lookup_uname(void *data, int64_t uid) +{ + struct name_cache *uname_cache = (struct name_cache *)data; + return (lookup_name(uname_cache, + &lookup_uname_helper, (id_t)uid)); +} + +#if HAVE_GETPWUID_R +static const char * +lookup_uname_helper(struct name_cache *cache, id_t id) +{ + struct passwd pwent, *result; + char * nbuff; + size_t nbuff_size; + int r; + + if (cache->buff_size == 0) { + cache->buff_size = 256; + cache->buff = malloc(cache->buff_size); + } + if (cache->buff == NULL) + return (NULL); + for (;;) { + result = &pwent; /* Old getpwuid_r ignores last arg. */ + r = getpwuid_r((uid_t)id, &pwent, + cache->buff, cache->buff_size, &result); + if (r == 0) + break; + if (r != ERANGE) + break; + /* ERANGE means our buffer was too small, but POSIX + * doesn't tell us how big the buffer should be, so + * we just double it and try again. Because the buffer + * is kept around in the cache object, we shouldn't + * have to do this very often. */ + nbuff_size = cache->buff_size * 2; + nbuff = realloc(cache->buff, nbuff_size); + if (nbuff == NULL) + break; + cache->buff = nbuff; + cache->buff_size = nbuff_size; + } + if (r != 0) { + archive_set_error(cache->archive, errno, + "Can't lookup user for id %d", (int)id); + return (NULL); + } + if (result == NULL) + return (NULL); + + return strdup(result->pw_name); +} +#else +static const char * +lookup_uname_helper(struct name_cache *cache, id_t id) +{ + struct passwd *result; + + result = getpwuid((uid_t)id); + + if (result == NULL) + return (NULL); + + return strdup(result->pw_name); +} +#endif + +static const char * +lookup_gname(void *data, int64_t gid) +{ + struct name_cache *gname_cache = (struct name_cache *)data; + return (lookup_name(gname_cache, + &lookup_gname_helper, (id_t)gid)); +} + +#if HAVE_GETGRGID_R +static const char * +lookup_gname_helper(struct name_cache *cache, id_t id) +{ + struct group grent, *result; + char * nbuff; + size_t nbuff_size; + int r; + + if (cache->buff_size == 0) { + cache->buff_size = 256; + cache->buff = malloc(cache->buff_size); + } + if (cache->buff == NULL) + return (NULL); + for (;;) { + result = &grent; /* Old getgrgid_r ignores last arg. */ + r = getgrgid_r((gid_t)id, &grent, + cache->buff, cache->buff_size, &result); + if (r == 0) + break; + if (r != ERANGE) + break; + /* ERANGE means our buffer was too small, but POSIX + * doesn't tell us how big the buffer should be, so + * we just double it and try again. */ + nbuff_size = cache->buff_size * 2; + nbuff = realloc(cache->buff, nbuff_size); + if (nbuff == NULL) + break; + cache->buff = nbuff; + cache->buff_size = nbuff_size; + } + if (r != 0) { + archive_set_error(cache->archive, errno, + "Can't lookup group for id %d", (int)id); + return (NULL); + } + if (result == NULL) + return (NULL); + + return strdup(result->gr_name); +} +#else +static const char * +lookup_gname_helper(struct name_cache *cache, id_t id) +{ + struct group *result; + + result = getgrgid((gid_t)id); + + if (result == NULL) + return (NULL); + + return strdup(result->gr_name); +} +#endif + +#endif /* ! (_WIN32 && !__CYGWIN__) */ diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c new file mode 100644 index 0000000..d8a1f55 --- /dev/null +++ b/libarchive/archive_read_disk_windows.c @@ -0,0 +1,1983 @@ +/*- + * Copyright (c) 2003-2009 Tim Kientzle + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#ifdef HAVE_SYS_PARAM_H +#include +#endif +#ifdef HAVE_SYS_MOUNT_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_STATVFS_H +#include +#endif +#ifdef HAVE_SYS_VFS_H +#include +#endif +#ifdef HAVE_LINUX_MAGIC_H +#include +#endif +#ifdef HAVE_DIRECT_H +#include +#endif +#ifdef HAVE_DIRENT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#if defined(HAVE_WINIOCTL_H) && !defined(__CYGWIN__) +#include +#endif + +#include "archive.h" +#include "archive_string.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_disk_private.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef IO_REPARSE_TAG_SYMLINK +/* Old SDKs do not provide IO_REPARSE_TAG_SYMLINK */ +#define IO_REPARSE_TAG_SYMLINK 0xA000000CL +#endif + +static BOOL SetFilePointerEx_perso(HANDLE hFile, + LARGE_INTEGER liDistanceToMove, + PLARGE_INTEGER lpNewFilePointer, + DWORD dwMoveMethod) +{ + LARGE_INTEGER li; + li.QuadPart = liDistanceToMove.QuadPart; + li.LowPart = SetFilePointer( + hFile, li.LowPart, &li.HighPart, dwMoveMethod); + if(lpNewFilePointer) { + lpNewFilePointer->QuadPart = li.QuadPart; + } + return li.LowPart != -1 || GetLastError() == NO_ERROR; +} + +/*- + * This is a new directory-walking system that addresses a number + * of problems I've had with fts(3). In particular, it has no + * pathname-length limits (other than the size of 'int'), handles + * deep logical traversals, uses considerably less memory, and has + * an opaque interface (easier to modify in the future). + * + * Internally, it keeps a single list of "tree_entry" items that + * represent filesystem objects that require further attention. + * Non-directories are not kept in memory: they are pulled from + * readdir(), returned to the client, then freed as soon as possible. + * Any directory entry to be traversed gets pushed onto the stack. + * + * There is surprisingly little information that needs to be kept for + * each item on the stack. Just the name, depth (represented here as the + * string length of the parent directory's pathname), and some markers + * indicating how to get back to the parent (via chdir("..") for a + * regular dir or via fchdir(2) for a symlink). + */ +/* + * TODO: + * 1) Loop checking. + * 3) Arbitrary logical traversals by closing/reopening intermediate fds. + */ + +struct restore_time { + const wchar_t *full_path; + FILETIME lastWriteTime; + FILETIME lastAccessTime; + mode_t filetype; +}; + +struct tree_entry { + int depth; + struct tree_entry *next; + struct tree_entry *parent; + size_t full_path_dir_length; + struct archive_wstring name; + struct archive_wstring full_path; + size_t dirname_length; + int64_t dev; + int64_t ino; + int flags; + int filesystem_id; + /* How to restore time of a directory. */ + struct restore_time restore_time; +}; + +struct filesystem { + int64_t dev; + int synthetic; + int remote; +}; + +/* Definitions for tree_entry.flags bitmap. */ +#define isDir 1 /* This entry is a regular directory. */ +#define isDirLink 2 /* This entry is a symbolic link to a directory. */ +#define needsFirstVisit 4 /* This is an initial entry. */ +#define needsDescent 8 /* This entry needs to be previsited. */ +#define needsOpen 16 /* This is a directory that needs to be opened. */ +#define needsAscent 32 /* This entry needs to be postvisited. */ + +/* + * On Windows, "first visit" is handled as a pattern to be handed to + * _findfirst(). This is consistent with Windows conventions that + * file patterns are handled within the application. On Posix, + * "first visit" is just returned to the client. + */ + +/* + * Local data for this package. + */ +struct tree { + struct tree_entry *stack; + struct tree_entry *current; + HANDLE d; +#define INVALID_DIR_HANDLE INVALID_HANDLE_VALUE + WIN32_FIND_DATAW _findData; + WIN32_FIND_DATAW *findData; + int flags; + int visit_type; + /* Error code from last failed operation. */ + int tree_errno; + + /* A full path with "\\?\" prefix. */ + struct archive_wstring full_path; + size_t full_path_dir_length; + /* Dynamically-sized buffer for holding path */ + struct archive_wstring path; + + /* Last path element */ + const wchar_t *basename; + /* Leading dir length */ + size_t dirname_length; + + int depth; + + BY_HANDLE_FILE_INFORMATION lst; + BY_HANDLE_FILE_INFORMATION st; + int descend; + /* How to restore time of a file. */ + struct restore_time restore_time; + + struct entry_sparse { + int64_t length; + int64_t offset; + } *sparse_list, *current_sparse; + int sparse_count; + int sparse_list_size; + + char initial_symlink_mode; + char symlink_mode; + struct filesystem *current_filesystem; + struct filesystem *filesystem_table; + int current_filesystem_id; + int max_filesystem_id; + int allocated_filesytem; + + HANDLE entry_fh; + int entry_eof; + int64_t entry_remaining_bytes; + int64_t entry_total; + unsigned char *entry_buff; + size_t entry_buff_size; +}; + +#define bhfi_dev(bhfi) ((bhfi)->dwVolumeSerialNumber) +/* Treat FileIndex as i-node. We should remove a sequence number + * which is high-16-bits of nFileIndexHigh. */ +#define bhfi_ino(bhfi) \ + ((((int64_t)((bhfi)->nFileIndexHigh & 0x0000FFFFUL)) << 32) \ + + (bhfi)->nFileIndexLow) + +/* Definitions for tree.flags bitmap. */ +#define hasStat 16 /* The st entry is valid. */ +#define hasLstat 32 /* The lst entry is valid. */ +#define needsRestoreTimes 128 + +static int +tree_dir_next_windows(struct tree *t, const wchar_t *pattern); + +#ifdef HAVE_DIRENT_D_NAMLEN +/* BSD extension; avoids need for a strlen() call. */ +#define D_NAMELEN(dp) (dp)->d_namlen +#else +#define D_NAMELEN(dp) (strlen((dp)->d_name)) +#endif + +/* Initiate/terminate a tree traversal. */ +static struct tree *tree_open(const wchar_t *, int, int); +static struct tree *tree_reopen(struct tree *, const wchar_t *, int); +static void tree_close(struct tree *); +static void tree_free(struct tree *); +static void tree_push(struct tree *, const wchar_t *, const wchar_t *, + int, int64_t, int64_t, struct restore_time *); + +/* + * tree_next() returns Zero if there is no next entry, non-zero if + * there is. Note that directories are visited three times. + * Directories are always visited first as part of enumerating their + * parent; that is a "regular" visit. If tree_descend() is invoked at + * that time, the directory is added to a work list and will + * subsequently be visited two more times: once just after descending + * into the directory ("postdescent") and again just after ascending + * back to the parent ("postascent"). + * + * TREE_ERROR_DIR is returned if the descent failed (because the + * directory couldn't be opened, for instance). This is returned + * instead of TREE_POSTDESCENT/TREE_POSTASCENT. TREE_ERROR_DIR is not a + * fatal error, but it does imply that the relevant subtree won't be + * visited. TREE_ERROR_FATAL is returned for an error that left the + * traversal completely hosed. Right now, this is only returned for + * chdir() failures during ascent. + */ +#define TREE_REGULAR 1 +#define TREE_POSTDESCENT 2 +#define TREE_POSTASCENT 3 +#define TREE_ERROR_DIR -1 +#define TREE_ERROR_FATAL -2 + +static int tree_next(struct tree *); + +/* + * Return information about the current entry. + */ + +/* + * The current full pathname, length of the full pathname, and a name + * that can be used to access the file. Because tree does use chdir + * extensively, the access path is almost never the same as the full + * current path. + * + */ +static const wchar_t *tree_current_path(struct tree *); +static const wchar_t *tree_current_access_path(struct tree *); + +/* + * Request the lstat() or stat() data for the current path. Since the + * tree package needs to do some of this anyway, and caches the + * results, you should take advantage of it here if you need it rather + * than make a redundant stat() or lstat() call of your own. + */ +static const BY_HANDLE_FILE_INFORMATION *tree_current_stat(struct tree *); +static const BY_HANDLE_FILE_INFORMATION *tree_current_lstat(struct tree *); + +/* The following functions use tricks to avoid a certain number of + * stat()/lstat() calls. */ +/* "is_physical_dir" is equivalent to S_ISDIR(tree_current_lstat()->st_mode) */ +static int tree_current_is_physical_dir(struct tree *); +/* "is_physical_link" is equivalent to S_ISLNK(tree_current_lstat()->st_mode) */ +static int tree_current_is_physical_link(struct tree *); +/* Instead of archive_entry_copy_stat for BY_HANDLE_FILE_INFORMATION */ +static void tree_archive_entry_copy_bhfi(struct archive_entry *, + struct tree *, const BY_HANDLE_FILE_INFORMATION *); +/* "is_dir" is equivalent to S_ISDIR(tree_current_stat()->st_mode) */ +static int tree_current_is_dir(struct tree *); +static int update_current_filesystem(struct archive_read_disk *a, + int64_t dev); +static int setup_current_filesystem(struct archive_read_disk *); +static int tree_target_is_same_as_parent(struct tree *, + const BY_HANDLE_FILE_INFORMATION *); + +static int _archive_read_disk_open_w(struct archive *, const wchar_t *); +static int _archive_read_free(struct archive *); +static int _archive_read_close(struct archive *); +static int _archive_read_data_block(struct archive *, + const void **, size_t *, int64_t *); +static int _archive_read_next_header2(struct archive *, + struct archive_entry *); +static const char *trivial_lookup_gname(void *, int64_t gid); +static const char *trivial_lookup_uname(void *, int64_t uid); +static int setup_sparse(struct archive_read_disk *, struct archive_entry *); +static int close_and_restore_time(HANDLE, struct tree *, + struct restore_time *); +static int setup_sparse_from_disk(struct archive_read_disk *, + struct archive_entry *, HANDLE); + + + +static struct archive_vtable * +archive_read_disk_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_free = _archive_read_free; + av.archive_close = _archive_read_close; + av.archive_read_data_block = _archive_read_data_block; + av.archive_read_next_header2 = _archive_read_next_header2; + inited = 1; + } + return (&av); +} + +const char * +archive_read_disk_gname(struct archive *_a, int64_t gid) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + if (ARCHIVE_OK != __archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_gname")) + return (NULL); + if (a->lookup_gname == NULL) + return (NULL); + return ((*a->lookup_gname)(a->lookup_gname_data, gid)); +} + +const char * +archive_read_disk_uname(struct archive *_a, int64_t uid) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + if (ARCHIVE_OK != __archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_uname")) + return (NULL); + if (a->lookup_uname == NULL) + return (NULL); + return ((*a->lookup_uname)(a->lookup_uname_data, uid)); +} + +int +archive_read_disk_set_gname_lookup(struct archive *_a, + void *private_data, + const char * (*lookup_gname)(void *private, int64_t gid), + void (*cleanup_gname)(void *private)) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_gname_lookup"); + + if (a->cleanup_gname != NULL && a->lookup_gname_data != NULL) + (a->cleanup_gname)(a->lookup_gname_data); + + a->lookup_gname = lookup_gname; + a->cleanup_gname = cleanup_gname; + a->lookup_gname_data = private_data; + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_uname_lookup(struct archive *_a, + void *private_data, + const char * (*lookup_uname)(void *private, int64_t uid), + void (*cleanup_uname)(void *private)) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_uname_lookup"); + + if (a->cleanup_uname != NULL && a->lookup_uname_data != NULL) + (a->cleanup_uname)(a->lookup_uname_data); + + a->lookup_uname = lookup_uname; + a->cleanup_uname = cleanup_uname; + a->lookup_uname_data = private_data; + return (ARCHIVE_OK); +} + +/* + * Create a new archive_read_disk object and initialize it with global state. + */ +struct archive * +archive_read_disk_new(void) +{ + struct archive_read_disk *a; + + a = (struct archive_read_disk *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_READ_DISK_MAGIC; + a->archive.state = ARCHIVE_STATE_NEW; + a->archive.vtable = archive_read_disk_vtable(); + a->lookup_uname = trivial_lookup_uname; + a->lookup_gname = trivial_lookup_gname; + a->entry_wd_fd = -1; + return (&a->archive); +} + +static int +_archive_read_free(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + int r; + + if (_a == NULL) + return (ARCHIVE_OK); + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_free"); + + if (a->archive.state != ARCHIVE_STATE_CLOSED) + r = _archive_read_close(&a->archive); + else + r = ARCHIVE_OK; + + tree_free(a->tree); + if (a->cleanup_gname != NULL && a->lookup_gname_data != NULL) + (a->cleanup_gname)(a->lookup_gname_data); + if (a->cleanup_uname != NULL && a->lookup_uname_data != NULL) + (a->cleanup_uname)(a->lookup_uname_data); + archive_string_free(&a->archive.error_string); + a->archive.magic = 0; + free(a); + return (r); +} + +static int +_archive_read_close(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_read_close"); + + if (a->archive.state != ARCHIVE_STATE_FATAL) + a->archive.state = ARCHIVE_STATE_CLOSED; + + tree_close(a->tree); + + return (ARCHIVE_OK); +} + +static void +setup_symlink_mode(struct archive_read_disk *a, char symlink_mode, + int follow_symlinks) +{ + a->symlink_mode = symlink_mode; + a->follow_symlinks = follow_symlinks; + if (a->tree != NULL) { + a->tree->initial_symlink_mode = a->symlink_mode; + a->tree->symlink_mode = a->symlink_mode; + } +} + +int +archive_read_disk_set_symlink_logical(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_logical"); + setup_symlink_mode(a, 'L', 1); + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_symlink_physical(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_physical"); + setup_symlink_mode(a, 'P', 0); + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_symlink_hybrid(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_set_symlink_hybrid"); + setup_symlink_mode(a, 'H', 1);/* Follow symlinks initially. */ + return (ARCHIVE_OK); +} + +int +archive_read_disk_set_atime_restored(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_read_disk_restore_atime"); + a->restore_time = 1; + if (a->tree != NULL) + a->tree->flags |= needsRestoreTimes; + return (ARCHIVE_OK); +} + +/* + * Trivial implementations of gname/uname lookup functions. + * These are normally overridden by the client, but these stub + * versions ensure that we always have something that works. + */ +static const char * +trivial_lookup_gname(void *private_data, int64_t gid) +{ + (void)private_data; /* UNUSED */ + (void)gid; /* UNUSED */ + return (NULL); +} + +static const char * +trivial_lookup_uname(void *private_data, int64_t uid) +{ + (void)private_data; /* UNUSED */ + (void)uid; /* UNUSED */ + return (NULL); +} + +static int +_archive_read_data_block(struct archive *_a, const void **buff, + size_t *size, int64_t *offset) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t = a->tree; + int r; + int64_t bytes; + size_t buffbytes; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_data_block"); + + if (t->entry_eof || t->entry_remaining_bytes <= 0) { + r = ARCHIVE_EOF; + goto abort_read_data; + } + + /* Allocate read buffer. */ + if (t->entry_buff == NULL) { + t->entry_buff = malloc(1024 * 64); + if (t->entry_buff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory"); + r = ARCHIVE_FATAL; + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + t->entry_buff_size = 1024 * 64; + } + + buffbytes = t->entry_buff_size; + if (buffbytes > t->current_sparse->length) + buffbytes = t->current_sparse->length; + + /* + * Skip hole. + */ + if (t->current_sparse->offset > t->entry_total) { + LARGE_INTEGER distance; + distance.QuadPart = t->current_sparse->offset; + if (!SetFilePointerEx_perso(t->entry_fh, distance, NULL, FILE_BEGIN)) { + DWORD lasterr; + + lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, "Seek error"); + r = ARCHIVE_FATAL; + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + bytes = t->current_sparse->offset - t->entry_total; + t->entry_remaining_bytes -= bytes; + t->entry_total += bytes; + } + if (buffbytes > 0) { + DWORD bytes_read; + if (!ReadFile(t->entry_fh, t->entry_buff, + (uint32_t)buffbytes, &bytes_read, NULL)) { + DWORD lasterr; + + lasterr = GetLastError(); + if (lasterr == ERROR_NO_DATA) + errno = EAGAIN; + else if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, "Read error"); + r = ARCHIVE_FATAL; + a->archive.state = ARCHIVE_STATE_FATAL; + goto abort_read_data; + } + bytes = bytes_read; + } else + bytes = 0; + if (bytes == 0) { + /* Get EOF */ + t->entry_eof = 1; + r = ARCHIVE_EOF; + goto abort_read_data; + } + *buff = t->entry_buff; + *size = bytes; + *offset = t->entry_total; + t->entry_total += bytes; + t->entry_remaining_bytes -= bytes; + if (t->entry_remaining_bytes == 0) { + /* Close the current file descriptor */ + close_and_restore_time(t->entry_fh, t, &t->restore_time); + t->entry_fh = INVALID_HANDLE_VALUE; + t->entry_eof = 1; + } + t->current_sparse->offset += bytes; + t->current_sparse->length -= bytes; + if (t->current_sparse->length == 0 && !t->entry_eof) + t->current_sparse++; + return (ARCHIVE_OK); + +abort_read_data: + *buff = NULL; + *size = 0; + *offset = t->entry_total; + if (t->entry_fh != INVALID_HANDLE_VALUE) { + /* Close the current file descriptor */ + close_and_restore_time(t->entry_fh, t, &t->restore_time); + t->entry_fh = INVALID_HANDLE_VALUE; + } + return (r); +} + +static int +_archive_read_next_header2(struct archive *_a, struct archive_entry *entry) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t; + const BY_HANDLE_FILE_INFORMATION *st; + const BY_HANDLE_FILE_INFORMATION *lst; + const char*name; + int descend, r; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_read_next_header2"); + + t = a->tree; + if (t->entry_fh != INVALID_HANDLE_VALUE) { + close_and_restore_time(t->entry_fh, t, &t->restore_time); + t->entry_fh = INVALID_HANDLE_VALUE; + } + st = NULL; + lst = NULL; + do { + switch (tree_next(t)) { + case TREE_ERROR_FATAL: + archive_set_error(&a->archive, t->tree_errno, + "%ls: Unable to continue traversing directory tree", + tree_current_path(t)); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + case TREE_ERROR_DIR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%ls: Couldn't visit directory", + tree_current_path(t)); + return (ARCHIVE_FAILED); + case 0: + return (ARCHIVE_EOF); + case TREE_POSTDESCENT: + case TREE_POSTASCENT: + break; + case TREE_REGULAR: + lst = tree_current_lstat(t); + if (lst == NULL) { + archive_set_error(&a->archive, errno, + "%ls: Cannot stat", + tree_current_path(t)); + return (ARCHIVE_FAILED); + } + break; + } + } while (lst == NULL); + + /* + * Distinguish 'L'/'P'/'H' symlink following. + */ + switch(t->symlink_mode) { + case 'H': + /* 'H': After the first item, rest like 'P'. */ + t->symlink_mode = 'P'; + /* 'H': First item (from command line) like 'L'. */ + /* FALLTHROUGH */ + case 'L': + /* 'L': Do descend through a symlink to dir. */ + descend = tree_current_is_dir(t); + /* 'L': Follow symlinks to files. */ + a->symlink_mode = 'L'; + a->follow_symlinks = 1; + /* 'L': Archive symlinks as targets, if we can. */ + st = tree_current_stat(t); + if (st != NULL && !tree_target_is_same_as_parent(t, st)) + break; + /* If stat fails, we have a broken symlink; + * in that case, don't follow the link. */ + /* FALLTHROUGH */ + default: + /* 'P': Don't descend through a symlink to dir. */ + descend = tree_current_is_physical_dir(t); + /* 'P': Don't follow symlinks to files. */ + a->symlink_mode = 'P'; + a->follow_symlinks = 0; + /* 'P': Archive symlinks as symlinks. */ + st = lst; + break; + } + + if (update_current_filesystem(a, bhfi_dev(st)) != ARCHIVE_OK) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + t->descend = descend; + + archive_entry_copy_pathname_w(entry, tree_current_path(t)); + archive_entry_copy_sourcepath_w(entry, tree_current_access_path(t)); + tree_archive_entry_copy_bhfi(entry, t, st); + + /* Save the times to be restored. */ + t->restore_time.lastWriteTime = st->ftLastWriteTime; + t->restore_time.lastAccessTime = st->ftLastAccessTime; + t->restore_time.filetype = archive_entry_filetype(entry); + + /* Lookup uname/gname */ + name = archive_read_disk_uname(_a, archive_entry_uid(entry)); + if (name != NULL) + archive_entry_copy_uname(entry, name); + name = archive_read_disk_gname(_a, archive_entry_gid(entry)); + if (name != NULL) + archive_entry_copy_gname(entry, name); + + r = ARCHIVE_OK; + if (archive_entry_filetype(entry) == AE_IFREG && + archive_entry_size(entry) > 0) { + t->entry_fh = CreateFileW(tree_current_access_path(t), + GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, NULL); + if (t->entry_fh == INVALID_HANDLE_VALUE) { + archive_set_error(&a->archive, errno, + "Couldn't open %ls", tree_current_path(a->tree)); + return (ARCHIVE_FAILED); + } + + /* Find sparse data from the disk. */ + if (archive_entry_hardlink(entry) == NULL && + (st->dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0) + r = setup_sparse_from_disk(a, entry, t->entry_fh); + } + + /* + * EOF and FATAL are persistent at this layer. By + * modifying the state, we guarantee that future calls to + * read a header or read data will fail. + */ + switch (r) { + case ARCHIVE_EOF: + a->archive.state = ARCHIVE_STATE_EOF; + break; + case ARCHIVE_OK: + case ARCHIVE_WARN: + t->entry_total = 0; + if (archive_entry_filetype(entry) == AE_IFREG) { + t->entry_remaining_bytes = archive_entry_size(entry); + t->entry_eof = (t->entry_remaining_bytes == 0)? 1: 0; + if (!t->entry_eof && + setup_sparse(a, entry) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + t->entry_remaining_bytes = 0; + t->entry_eof = 1; + } + a->archive.state = ARCHIVE_STATE_DATA; + break; + case ARCHIVE_RETRY: + break; + case ARCHIVE_FATAL: + a->archive.state = ARCHIVE_STATE_FATAL; + break; + } + + return (r); +} + +static int +setup_sparse(struct archive_read_disk *a, struct archive_entry *entry) +{ + struct tree *t = a->tree; + int64_t length, offset; + int i; + + t->sparse_count = archive_entry_sparse_reset(entry); + if (t->sparse_count+1 > t->sparse_list_size) { + free(t->sparse_list); + t->sparse_list_size = t->sparse_count + 1; + t->sparse_list = malloc(sizeof(t->sparse_list[0]) * + t->sparse_list_size); + if (t->sparse_list == NULL) { + t->sparse_list_size = 0; + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + } + for (i = 0; i < t->sparse_count; i++) { + archive_entry_sparse_next(entry, &offset, &length); + t->sparse_list[i].offset = offset; + t->sparse_list[i].length = length; + } + if (i == 0) { + t->sparse_list[i].offset = 0; + t->sparse_list[i].length = archive_entry_size(entry); + } else { + t->sparse_list[i].offset = archive_entry_size(entry); + t->sparse_list[i].length = 0; + } + t->current_sparse = t->sparse_list; + + return (ARCHIVE_OK); +} + +/* + * Called by the client to mark the directory just returned from + * tree_next() as needing to be visited. + */ +int +archive_read_disk_descend(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct tree *t = a->tree; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_descend"); + + if (t->visit_type != TREE_REGULAR || !t->descend) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignored the request descending the current object"); + return (ARCHIVE_WARN); + } + + if (tree_current_is_physical_dir(t)) { + tree_push(t, t->basename, t->full_path.s, + t->current_filesystem_id, + bhfi_dev(&(t->lst)), bhfi_ino(&(t->lst)), + &t->restore_time); + t->stack->flags |= isDir; + } else if (tree_current_is_dir(t)) { + tree_push(t, t->basename, t->full_path.s, + t->current_filesystem_id, + bhfi_dev(&(t->st)), bhfi_ino(&(t->st)), + &t->restore_time); + t->stack->flags |= isDirLink; + } + t->descend = 0; + return (ARCHIVE_OK); +} + +int +archive_read_disk_open(struct archive *_a, const char *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + struct archive_wstring wpath; + int ret; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_NEW | ARCHIVE_STATE_CLOSED, + "archive_read_disk_open"); + archive_clear_error(&a->archive); + + /* Make a wchar_t string from a char string. */ + archive_string_init(&wpath); + if (archive_wstring_append_from_mbs(&wpath, pathname, + strlen(pathname)) != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't convert a path to a wchar_t string"); + a->archive.state = ARCHIVE_STATE_FATAL; + ret = ARCHIVE_FATAL; + } else + ret = _archive_read_disk_open_w(_a, wpath.s); + + archive_wstring_free(&wpath); + return (ret); +} + +int +archive_read_disk_open_w(struct archive *_a, const wchar_t *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, + ARCHIVE_STATE_NEW | ARCHIVE_STATE_CLOSED, + "archive_read_disk_open_w"); + archive_clear_error(&a->archive); + + return (_archive_read_disk_open_w(_a, pathname)); +} + +static int +_archive_read_disk_open_w(struct archive *_a, const wchar_t *pathname) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + if (a->tree != NULL) + a->tree = tree_reopen(a->tree, pathname, a->restore_time); + else + a->tree = tree_open(pathname, a->symlink_mode, a->restore_time); + if (a->tree == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate direcotry traversal data"); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + a->archive.state = ARCHIVE_STATE_HEADER; + + return (ARCHIVE_OK); +} + +/* + * Return a current filesystem ID which is index of the filesystem entry + * you've visited through archive_read_disk. + */ +int +archive_read_disk_current_filesystem(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem_id); +} + +static int +update_current_filesystem(struct archive_read_disk *a, int64_t dev) +{ + struct tree *t = a->tree; + int i, fid; + + if (t->current_filesystem != NULL && + t->current_filesystem->dev == dev) + return (ARCHIVE_OK); + + for (i = 0; i < t->max_filesystem_id; i++) { + if (t->filesystem_table[i].dev == dev) { + /* There is the filesytem ID we've already generated. */ + t->current_filesystem_id = i; + t->current_filesystem = &(t->filesystem_table[i]); + return (ARCHIVE_OK); + } + } + + /* + * There is a new filesytem, we generate a new ID for. + */ + fid = t->max_filesystem_id++; + if (t->max_filesystem_id > t->allocated_filesytem) { + size_t s; + + s = t->max_filesystem_id * 2; + t->filesystem_table = realloc(t->filesystem_table, + s * sizeof(*t->filesystem_table)); + if (t->filesystem_table == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate tar data"); + return (ARCHIVE_FATAL); + } + t->allocated_filesytem = s; + } + t->current_filesystem_id = fid; + t->current_filesystem = &(t->filesystem_table[fid]); + t->current_filesystem->dev = dev; + + return (setup_current_filesystem(a)); +} + +/* + * Returns 1 if current filesystem is generated filesystem, 0 if it is not + * or -1 if it is unknown. + */ +int +archive_read_disk_current_filesystem_is_synthetic(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem->synthetic); +} + +/* + * Returns 1 if current filesystem is remote filesystem, 0 if it is not + * or -1 if it is unknown. + */ +int +archive_read_disk_current_filesystem_is_remote(struct archive *_a) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + + archive_check_magic(_a, ARCHIVE_READ_DISK_MAGIC, ARCHIVE_STATE_DATA, + "archive_read_disk_current_filesystem"); + + return (a->tree->current_filesystem->remote); +} + +/* + * If symlink is broken, statfs or statvfs will fail. + * Use its directory path instead. + */ +static wchar_t * +safe_path_for_statfs(struct tree *t) +{ + const wchar_t *path; + wchar_t *cp, *p = NULL; + + path = tree_current_access_path(t); + if (tree_current_stat(t) == NULL) { + p = _wcsdup(path); + cp = wcsrchr(p, '/'); + if (cp != NULL && wcslen(cp) >= 2) { + cp[1] = '.'; + cp[2] = '\0'; + path = p; + } + } else + p = _wcsdup(path); + return (p); +} + +/* + * Get conditions of synthetic and remote on Windows + */ +static int +setup_current_filesystem(struct archive_read_disk *a) +{ + struct tree *t = a->tree; + wchar_t vol[256]; + wchar_t *path; + + t->current_filesystem->synthetic = -1;/* Not supported */ + path = safe_path_for_statfs(t); + if (!GetVolumePathNameW(path, vol, sizeof(vol)/sizeof(vol[0]))) { + free(path); + t->current_filesystem->remote = -1; + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "GetVolumePathName failed: %d", (int)GetLastError()); + return (ARCHIVE_FAILED); + } + free(path); + switch (GetDriveTypeW(vol)) { + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: + t->current_filesystem->remote = -1; + break; + case DRIVE_REMOTE: + t->current_filesystem->remote = 1; + break; + default: + t->current_filesystem->remote = 0; + break; + } + + return (ARCHIVE_OK); +} + +static int +close_and_restore_time(HANDLE h, struct tree *t, struct restore_time *rt) +{ + HANDLE handle; + int r = 0; + + if (h == INVALID_HANDLE_VALUE && AE_IFLNK == rt->filetype) + return (0); + + /* Close a file descritor. + * It will not be used for SetFileTime() because it has been opened + * by a read only mode. + */ + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); + if ((t->flags & needsRestoreTimes) == 0) + return (r); + + handle = CreateFileW(rt->full_path, FILE_WRITE_ATTRIBUTES, + 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (handle == INVALID_HANDLE_VALUE) { + errno = EINVAL; + return (-1); + } + + if (SetFileTime(handle, NULL, &rt->lastAccessTime, + &rt->lastWriteTime) == 0) { + errno = EINVAL; + r = -1; + } else + r = 0; + CloseHandle(handle); + return (r); +} + +/* + * Add a directory path to the current stack. + */ +static void +tree_push(struct tree *t, const wchar_t *path, const wchar_t *full_path, + int filesystem_id, int64_t dev, int64_t ino, struct restore_time *rt) +{ + struct tree_entry *te; + + te = malloc(sizeof(*te)); + memset(te, 0, sizeof(*te)); + te->next = t->stack; + te->parent = t->current; + if (te->parent) + te->depth = te->parent->depth + 1; + t->stack = te; + archive_string_init(&te->name); + archive_wstrcpy(&te->name, path); + archive_string_init(&te->full_path); + archive_wstrcpy(&te->full_path, full_path); + te->flags = needsDescent | needsOpen | needsAscent; + te->filesystem_id = filesystem_id; + te->dev = dev; + te->ino = ino; + te->dirname_length = t->dirname_length; + te->full_path_dir_length = t->full_path_dir_length; + te->restore_time.full_path = te->full_path.s; + if (rt != NULL) { + te->restore_time.lastWriteTime = rt->lastWriteTime; + te->restore_time.lastAccessTime = rt->lastAccessTime; + te->restore_time.filetype = rt->filetype; + } +} + +/* + * Append a name to the current dir path. + */ +static void +tree_append(struct tree *t, const wchar_t *name, size_t name_length) +{ + size_t size_needed; + + t->path.s[t->dirname_length] = L'\0'; + t->path.length = t->dirname_length; + /* Strip trailing '/' from name, unless entire name is "/". */ + while (name_length > 1 && name[name_length - 1] == L'/') + name_length--; + + /* Resize pathname buffer as needed. */ + size_needed = name_length + t->dirname_length + 2; + archive_wstring_ensure(&t->path, size_needed); + /* Add a separating '/' if it's needed. */ + if (t->dirname_length > 0 && + t->path.s[archive_strlen(&t->path)-1] != L'/') + archive_wstrappend_wchar(&t->path, L'/'); + t->basename = t->path.s + archive_strlen(&t->path); + archive_wstrncat(&t->path, name, name_length); + t->restore_time.full_path = t->basename; + if (t->full_path_dir_length > 0) { + t->full_path.s[t->full_path_dir_length] = L'\0'; + t->full_path.length = t->full_path_dir_length; + size_needed = name_length + t->full_path_dir_length + 2; + archive_wstring_ensure(&t->full_path, size_needed); + /* Add a separating '\' if it's needed. */ + if (t->full_path.s[archive_strlen(&t->full_path)-1] != L'\\') + archive_wstrappend_wchar(&t->full_path, L'\\'); + archive_wstrncat(&t->full_path, name, name_length); + t->restore_time.full_path = t->full_path.s; + } +} + +/* + * Open a directory tree for traversal. + */ +static struct tree * +tree_open(const wchar_t *path, int symlink_mode, int restore_time) +{ + struct tree *t; + + t = malloc(sizeof(*t)); + memset(t, 0, sizeof(*t)); + archive_string_init(&(t->full_path)); + archive_string_init(&t->path); + archive_wstring_ensure(&t->path, 15); + t->initial_symlink_mode = symlink_mode; + return (tree_reopen(t, path, restore_time)); +} + +static struct tree * +tree_reopen(struct tree *t, const wchar_t *path, int restore_time) +{ + struct archive_wstring ws; + wchar_t *pathname, *p, *base; + + t->flags = (restore_time)?needsRestoreTimes:0; + t->visit_type = 0; + t->tree_errno = 0; + t->full_path_dir_length = 0; + t->dirname_length = 0; + t->depth = 0; + t->descend = 0; + t->current = NULL; + t->d = INVALID_DIR_HANDLE; + t->symlink_mode = t->initial_symlink_mode; + archive_string_empty(&(t->full_path)); + archive_string_empty(&t->path); + t->entry_fh = INVALID_HANDLE_VALUE; + t->entry_eof = 0; + t->entry_remaining_bytes = 0; + + /* Get wchar_t strings from char strings. */ + archive_string_init(&ws); + archive_wstrcpy(&ws, path); + pathname = ws.s; + /* Get a full-path-name. */ + p = __la_win_permissive_name_w(pathname); + if (p == NULL) + goto failed; + archive_wstrcpy(&(t->full_path), p); + free(p); + + /* Convert path separators from '\' to '/' */ + for (p = pathname; *p != L'\0'; ++p) { + if (*p == L'\\') + *p = L'/'; + } + base = pathname; + + /* First item is set up a lot like a symlink traversal. */ + /* printf("Looking for wildcard in %s\n", path); */ + /* TODO: wildcard detection here screws up on \\?\c:\ UNC names */ + if (wcschr(base, L'*') || wcschr(base, L'?')) { + // It has a wildcard in it... + // Separate the last element. + p = wcsrchr(base, L'/'); + if (p != NULL) { + *p = L'\0'; + tree_append(t, base, p - base); + t->dirname_length = archive_strlen(&t->path); + base = p + 1; + } + p = wcsrchr(t->full_path.s, L'\\'); + if (p != NULL) { + *p = L'\0'; + t->full_path.length = wcslen(t->full_path.s); + t->full_path_dir_length = archive_strlen(&t->full_path); + } + } + tree_push(t, base, t->full_path.s, 0, 0, 0, NULL); + archive_wstring_free(&ws); + t->stack->flags = needsFirstVisit; + return (t); +failed: + archive_wstring_free(&ws); + tree_free(t); + return (NULL); +} + +static int +tree_descent(struct tree *t) +{ + t->dirname_length = archive_strlen(&t->path); + t->full_path_dir_length = archive_strlen(&t->full_path); + t->depth++; + return (0); +} + +/* + * We've finished a directory; ascend back to the parent. + */ +static int +tree_ascend(struct tree *t) +{ + struct tree_entry *te; + + te = t->stack; + t->depth--; + close_and_restore_time(INVALID_DIR_HANDLE, t, &te->restore_time); + return (0); +} + +/* + * Pop the working stack. + */ +static void +tree_pop(struct tree *t) +{ + struct tree_entry *te; + + t->full_path.s[t->full_path_dir_length] = L'\0'; + t->full_path.length = t->full_path_dir_length; + t->path.s[t->dirname_length] = L'\0'; + t->path.length = t->dirname_length; + if (t->stack == t->current && t->current != NULL) + t->current = t->current->parent; + te = t->stack; + t->stack = te->next; + t->dirname_length = te->dirname_length; + t->basename = t->path.s + t->dirname_length; + t->full_path_dir_length = te->full_path_dir_length; + while (t->basename[0] == L'/') + t->basename++; + archive_wstring_free(&te->name); + archive_wstring_free(&te->full_path); + free(te); +} + +/* + * Get the next item in the tree traversal. + */ +static int +tree_next(struct tree *t) +{ + int r; + + while (t->stack != NULL) { + /* If there's an open dir, get the next entry from there. */ + if (t->d != INVALID_DIR_HANDLE) { + r = tree_dir_next_windows(t, NULL); + if (r == 0) + continue; + return (r); + } + + if (t->stack->flags & needsFirstVisit) { + wchar_t *d = t->stack->name.s; + t->stack->flags &= ~needsFirstVisit; + if (wcschr(d, L'*') || wcschr(d, L'?')) { + r = tree_dir_next_windows(t, d); + if (r == 0) + continue; + return (r); + } else { + HANDLE h = FindFirstFileW(d, &t->_findData); + if (h == INVALID_DIR_HANDLE) { + t->tree_errno = errno; + t->visit_type = TREE_ERROR_DIR; + return (t->visit_type); + } + t->findData = &t->_findData; + FindClose(h); + } + /* Top stack item needs a regular visit. */ + t->current = t->stack; + tree_append(t, t->stack->name.s, + archive_strlen(&(t->stack->name))); + //t->dirname_length = t->path_length; + //tree_pop(t); + t->stack->flags &= ~needsFirstVisit; + return (t->visit_type = TREE_REGULAR); + } else if (t->stack->flags & needsDescent) { + /* Top stack item is dir to descend into. */ + t->current = t->stack; + tree_append(t, t->stack->name.s, + archive_strlen(&(t->stack->name))); + t->stack->flags &= ~needsDescent; + r = tree_descent(t); + if (r != 0) { + tree_pop(t); + t->visit_type = r; + } else + t->visit_type = TREE_POSTDESCENT; + return (t->visit_type); + } else if (t->stack->flags & needsOpen) { + t->stack->flags &= ~needsOpen; + r = tree_dir_next_windows(t, L"*"); + if (r == 0) + continue; + return (r); + } else if (t->stack->flags & needsAscent) { + /* Top stack item is dir and we're done with it. */ + r = tree_ascend(t); + tree_pop(t); + t->visit_type = r != 0 ? r : TREE_POSTASCENT; + return (t->visit_type); + } else { + /* Top item on stack is dead. */ + tree_pop(t); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + } + } + return (t->visit_type = 0); +} + +static int +tree_dir_next_windows(struct tree *t, const wchar_t *pattern) +{ + const wchar_t *name; + size_t namelen; + int r; + + for (;;) { + if (pattern != NULL) { + struct archive_wstring pt; + + archive_string_init(&pt); + archive_wstring_ensure(&pt, + archive_strlen(&(t->full_path)) + + 2 + wcslen(pattern)); + archive_wstring_copy(&pt, &(t->full_path)); + archive_wstrappend_wchar(&pt, L'\\'); + archive_wstrcat(&pt, pattern); + t->d = FindFirstFileW(pt.s, &t->_findData); + archive_wstring_free(&pt); + if (t->d == INVALID_DIR_HANDLE) { + r = tree_ascend(t); /* Undo "chdir" */ + tree_pop(t); + t->tree_errno = errno; + t->visit_type = r != 0 ? r : TREE_ERROR_DIR; + return (t->visit_type); + } + t->findData = &t->_findData; + pattern = NULL; + } else if (!FindNextFileW(t->d, &t->_findData)) { + FindClose(t->d); + t->d = INVALID_DIR_HANDLE; + t->findData = NULL; + return (0); + } + name = t->findData->cFileName; + namelen = wcslen(name); + t->flags &= ~hasLstat; + t->flags &= ~hasStat; + if (name[0] == L'.' && name[1] == L'\0') + continue; + if (name[0] == L'.' && name[1] == L'.' && name[2] == L'\0') + continue; + tree_append(t, name, namelen); + return (t->visit_type = TREE_REGULAR); + } +} + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) +static void +fileTimeToUtc(const FILETIME *filetime, time_t *time, long *ns) +{ + ULARGE_INTEGER utc; + + utc.HighPart = filetime->dwHighDateTime; + utc.LowPart = filetime->dwLowDateTime; + if (utc.QuadPart >= EPOC_TIME) { + utc.QuadPart -= EPOC_TIME; + /* milli seconds base */ + *time = (time_t)(utc.QuadPart / 10000000); + /* nano seconds base */ + *ns = (long)(utc.QuadPart % 10000000) * 100; + } else { + *time = 0; + *ns = 0; + } +} + +static void +entry_copy_bhfi(struct archive_entry *entry, const wchar_t *path, + const WIN32_FIND_DATAW *findData, + const BY_HANDLE_FILE_INFORMATION *bhfi) +{ + time_t secs; + long nsecs; + mode_t mode; + + fileTimeToUtc(&bhfi->ftLastAccessTime, &secs, &nsecs); + archive_entry_set_atime(entry, secs, nsecs); + fileTimeToUtc(&bhfi->ftLastWriteTime, &secs, &nsecs); + archive_entry_set_mtime(entry, secs, nsecs); + fileTimeToUtc(&bhfi->ftCreationTime, &secs, &nsecs); + archive_entry_set_birthtime(entry, secs, nsecs); + archive_entry_set_ctime(entry, secs, nsecs); + archive_entry_set_dev(entry, bhfi_dev(bhfi)); + archive_entry_set_ino64(entry, bhfi_ino(bhfi)); + if (bhfi->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + archive_entry_set_nlink(entry, bhfi->nNumberOfLinks + 1); + else + archive_entry_set_nlink(entry, bhfi->nNumberOfLinks); + archive_entry_set_size(entry, + (((int64_t)bhfi->nFileSizeHigh) << 32) + + bhfi->nFileSizeLow); + archive_entry_set_uid(entry, 0); + archive_entry_set_gid(entry, 0); + archive_entry_set_rdev(entry, 0); + + mode = S_IRUSR | S_IRGRP | S_IROTH; + if ((bhfi->dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + mode |= S_IWUSR | S_IWGRP | S_IWOTH; + if ((bhfi->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + findData != NULL && + findData->dwReserved0 == IO_REPARSE_TAG_SYMLINK) + mode |= S_IFLNK; + else if (bhfi->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + else { + const wchar_t *p; + + mode |= S_IFREG; + p = wcsrchr(path, L'.'); + if (p != NULL && wcslen(p) == 4) { + switch (p[1]) { + case L'B': case L'b': + if ((p[2] == L'A' || p[2] == L'a' ) && + (p[3] == L'T' || p[3] == L't' )) + mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + case L'C': case L'c': + if (((p[2] == L'M' || p[2] == L'm' ) && + (p[3] == L'D' || p[3] == L'd' )) || + ((p[2] == L'M' || p[2] == L'm' ) && + (p[3] == L'D' || p[3] == L'd' ))) + mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + case L'E': case L'e': + if ((p[2] == L'X' || p[2] == L'x' ) && + (p[3] == L'E' || p[3] == L'e' )) + mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + default: + break; + } + } + } + archive_entry_set_mode(entry, mode); +} + +static void +tree_archive_entry_copy_bhfi(struct archive_entry *entry, struct tree *t, + const BY_HANDLE_FILE_INFORMATION *bhfi) +{ + entry_copy_bhfi(entry, tree_current_path(t), t->findData, bhfi); +} + +static int +tree_current_file_information(struct tree *t, BY_HANDLE_FILE_INFORMATION *st, + int sim_lstat) +{ + HANDLE h; + int r; + DWORD flag = FILE_FLAG_BACKUP_SEMANTICS; + + if (sim_lstat && tree_current_is_physical_link(t)) + flag |= FILE_FLAG_OPEN_REPARSE_POINT; + h = CreateFileW(tree_current_access_path(t), 0, 0, NULL, + OPEN_EXISTING, flag, NULL); + if (h == INVALID_HANDLE_VALUE) + return (0); + r = GetFileInformationByHandle(h, st); + CloseHandle(h); + return (r); +} + +/* + * Get the stat() data for the entry just returned from tree_next(). + */ +static const BY_HANDLE_FILE_INFORMATION * +tree_current_stat(struct tree *t) +{ + if (!(t->flags & hasStat)) { + if (!tree_current_file_information(t, &t->st, 0)) + return NULL; + t->flags |= hasStat; + } + return (&t->st); +} + +/* + * Get the lstat() data for the entry just returned from tree_next(). + */ +static const BY_HANDLE_FILE_INFORMATION * +tree_current_lstat(struct tree *t) +{ + if (!(t->flags & hasLstat)) { + if (!tree_current_file_information(t, &t->lst, 1)) + return NULL; + t->flags |= hasLstat; + } + return (&t->lst); +} + +/* + * Test whether current entry is a dir or link to a dir. + */ +static int +tree_current_is_dir(struct tree *t) +{ + if (t->findData) + return (t->findData->dwFileAttributes + & FILE_ATTRIBUTE_DIRECTORY); + return (0); +} + +/* + * Test whether current entry is a physical directory. Usually, we + * already have at least one of stat() or lstat() in memory, so we + * use tricks to try to avoid an extra trip to the disk. + */ +static int +tree_current_is_physical_dir(struct tree *t) +{ + if (tree_current_is_physical_link(t)) + return (0); + return (tree_current_is_dir(t)); +} + +/* + * Test whether current entry is a symbolic link. + */ +static int +tree_current_is_physical_link(struct tree *t) +{ + if (t->findData) + return ((t->findData->dwFileAttributes + & FILE_ATTRIBUTE_REPARSE_POINT) && + (t->findData->dwReserved0 + == IO_REPARSE_TAG_SYMLINK)); + return (0); +} + +/* + * Test whether the same file has been in the tree as its parent. + */ +static int +tree_target_is_same_as_parent(struct tree *t, + const BY_HANDLE_FILE_INFORMATION *st) +{ + struct tree_entry *te; + int64_t dev = bhfi_dev(st); + int64_t ino = bhfi_ino(st); + + for (te = t->current->parent; te != NULL; te = te->parent) { + if (te->dev == dev && te->ino == ino) + return (1); + } + return (0); +} + +/* + * Return the access path for the entry just returned from tree_next(). + */ +static const wchar_t * +tree_current_access_path(struct tree *t) +{ + return (t->full_path.s); +} + +/* + * Return the full path for the entry just returned from tree_next(). + */ +static const wchar_t * +tree_current_path(struct tree *t) +{ + return (t->path.s); +} + +/* + * Terminate the traversal. + */ +static void +tree_close(struct tree *t) +{ + + if (t == NULL) + return; + if (t->entry_fh != INVALID_HANDLE_VALUE) { + close_and_restore_time(t->entry_fh, t, &t->restore_time); + t->entry_fh = INVALID_HANDLE_VALUE; + } + /* Close the handle of FindFirstFileW */ + if (t->d != INVALID_DIR_HANDLE) { + FindClose(t->d); + t->d = INVALID_DIR_HANDLE; + t->findData = NULL; + } + /* Release anything remaining in the stack. */ + while (t->stack != NULL) + tree_pop(t); +} + +/* + * Release any resources. + */ +static void +tree_free(struct tree *t) +{ + if (t == NULL) + return; + archive_wstring_free(&t->path); + archive_wstring_free(&t->full_path); + free(t->sparse_list); + free(t->filesystem_table); + free(t->entry_buff); + free(t); +} + + +/* + * Populate the archive_entry with metadata from the disk. + */ +int +archive_read_disk_entry_from_file(struct archive *_a, + struct archive_entry *entry, int fd, const struct stat *st) +{ + struct archive_read_disk *a = (struct archive_read_disk *)_a; + const wchar_t *path; + const wchar_t *wname; + const char *name; + HANDLE h; + BY_HANDLE_FILE_INFORMATION bhfi; + DWORD fileAttributes = 0; + int r; + + archive_clear_error(_a); + wname = archive_entry_sourcepath_w(entry); + if (wname == NULL) + wname = archive_entry_pathname_w(entry); + if (wname == NULL) { + archive_set_error(&a->archive, EINVAL, + "Can't get a wide character version of the path"); + return (ARCHIVE_FAILED); + } + path = __la_win_permissive_name_w(wname); + + if (st == NULL) { + /* + * Get metadata through GetFileInformationByHandle(). + */ + if (fd >= 0) { + h = (HANDLE)_get_osfhandle(fd); + r = GetFileInformationByHandle(h, &bhfi); + if (r == 0) { + archive_set_error(&a->archive, GetLastError(), + "Can't GetFileInformationByHandle"); + return (ARCHIVE_FAILED); + } + entry_copy_bhfi(entry, path, NULL, &bhfi); + } else { + WIN32_FIND_DATAW findData; + DWORD flag, desiredAccess; + + h = FindFirstFileW(path, &findData); + if (h == INVALID_DIR_HANDLE) { + archive_set_error(&a->archive, GetLastError(), + "Can't FindFirstFileW"); + return (ARCHIVE_FAILED); + } + FindClose(h); + + flag = FILE_FLAG_BACKUP_SEMANTICS; + if (!a->follow_symlinks && + (findData.dwFileAttributes + & FILE_ATTRIBUTE_REPARSE_POINT) && + (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { + flag |= FILE_FLAG_OPEN_REPARSE_POINT; + desiredAccess = 0; + } else if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + desiredAccess = 0; + } else + desiredAccess = GENERIC_READ; + + h = CreateFileW(path, desiredAccess, 0, NULL, + OPEN_EXISTING, flag, NULL); + if (h == INVALID_HANDLE_VALUE) { + archive_set_error(&a->archive, + GetLastError(), + "Can't CreateFileW"); + return (ARCHIVE_FAILED); + } + r = GetFileInformationByHandle(h, &bhfi); + if (r == 0) { + archive_set_error(&a->archive, + GetLastError(), + "Can't GetFileInformationByHandle"); + CloseHandle(h); + return (ARCHIVE_FAILED); + } + entry_copy_bhfi(entry, path, &findData, &bhfi); + } + fileAttributes = bhfi.dwFileAttributes; + } else { + archive_entry_copy_stat(entry, st); + h = INVALID_DIR_HANDLE; + } + + /* Lookup uname/gname */ + name = archive_read_disk_uname(_a, archive_entry_uid(entry)); + if (name != NULL) + archive_entry_copy_uname(entry, name); + name = archive_read_disk_gname(_a, archive_entry_gid(entry)); + if (name != NULL) + archive_entry_copy_gname(entry, name); + + /* + * Can this file be sparse file ? + */ + if (archive_entry_filetype(entry) != AE_IFREG + || archive_entry_size(entry) <= 0 + || archive_entry_hardlink(entry) != NULL) { + if (h != INVALID_HANDLE_VALUE && fd < 0) + CloseHandle(h); + return (ARCHIVE_OK); + } + + if (h == INVALID_HANDLE_VALUE) { + if (fd >= 0) { + h = (HANDLE)_get_osfhandle(fd); + } else { + h = CreateFileW(path, GENERIC_READ, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (h == INVALID_HANDLE_VALUE) { + archive_set_error(&a->archive, GetLastError(), + "Can't CreateFileW"); + return (ARCHIVE_FAILED); + } + } + r = GetFileInformationByHandle(h, &bhfi); + if (r == 0) { + archive_set_error(&a->archive, GetLastError(), + "Can't GetFileInformationByHandle"); + if (h != INVALID_HANDLE_VALUE && fd < 0) + CloseHandle(h); + return (ARCHIVE_FAILED); + } + fileAttributes = bhfi.dwFileAttributes; + } + + /* Sparse file must be set a mark, FILE_ATTRIBUTE_SPARSE_FILE */ + if ((fileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) == 0) { + if (fd < 0) + CloseHandle(h); + return (ARCHIVE_OK); + } + + r = setup_sparse_from_disk(a, entry, h); + if (fd < 0) + CloseHandle(h); + + return (r); +} + +/* + * Windows sparse interface. + */ +#if defined(__MINGW32__) && !defined(FSCTL_QUERY_ALLOCATED_RANGES) +#define FSCTL_QUERY_ALLOCATED_RANGES 0x940CF +typedef struct { + LARGE_INTEGER FileOffset; + LARGE_INTEGER Length; +} FILE_ALLOCATED_RANGE_BUFFER; +#endif + +static int +setup_sparse_from_disk(struct archive_read_disk *a, + struct archive_entry *entry, HANDLE handle) +{ + FILE_ALLOCATED_RANGE_BUFFER range, *outranges = NULL; + size_t outranges_size; + int64_t entry_size = archive_entry_size(entry); + int exit_sts = ARCHIVE_OK; + + range.FileOffset.QuadPart = 0; + range.Length.QuadPart = entry_size; + outranges_size = 2048; + outranges = (FILE_ALLOCATED_RANGE_BUFFER *)malloc(outranges_size); + if (outranges == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Couldn't allocate memory"); + exit_sts = ARCHIVE_FATAL; + goto exit_setup_sparse; + } + + for (;;) { + DWORD retbytes; + BOOL ret; + + for (;;) { + ret = DeviceIoControl(handle, + FSCTL_QUERY_ALLOCATED_RANGES, + &range, sizeof(range), outranges, + outranges_size, &retbytes, NULL); + if (ret == 0 && GetLastError() == ERROR_MORE_DATA) { + free(outranges); + outranges_size *= 2; + outranges = (FILE_ALLOCATED_RANGE_BUFFER *) + malloc(outranges_size); + if (outranges == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Couldn't allocate memory"); + exit_sts = ARCHIVE_FATAL; + goto exit_setup_sparse; + } + continue; + } else + break; + } + if (ret != 0) { + if (retbytes > 0) { + DWORD i, n; + + n = retbytes / sizeof(outranges[0]); + if (n == 1 && + outranges[0].FileOffset.QuadPart == 0 && + outranges[0].Length.QuadPart == entry_size) + break;/* This is not sparse. */ + for (i = 0; i < n; i++) + archive_entry_sparse_add_entry(entry, + outranges[i].FileOffset.QuadPart, + outranges[i].Length.QuadPart); + range.FileOffset.QuadPart = + outranges[n-1].FileOffset.QuadPart + + outranges[n-1].Length.QuadPart; + range.Length.QuadPart = + entry_size - range.FileOffset.QuadPart; + if (range.Length.QuadPart > 0) + continue; + } else { + /* The remaining data is hole. */ + archive_entry_sparse_add_entry(entry, + range.FileOffset.QuadPart, + range.Length.QuadPart); + } + break; + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "DeviceIoControl Failed: %lu", GetLastError()); + exit_sts = ARCHIVE_FAILED; + goto exit_setup_sparse; + } + } +exit_setup_sparse: + free(outranges); + + return (exit_sts); +} + +#endif diff --git a/libarchive/archive_read_extract.3 b/libarchive/archive_read_extract.3 new file mode 100644 index 0000000..950248e --- /dev/null +++ b/libarchive/archive_read_extract.3 @@ -0,0 +1,135 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 22, 2011 +.Dt archive_read_extract 3 +.Os +.Sh NAME +.Nm archive_read_extract , +.Nm archive_read_extract2 , +.Nm archive_read_extract_set_progress_callback +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fo archive_read_extract +.Fa "struct archive *" +.Fa "struct archive_entry *" +.Fa "int flags" +.Fc +.Ft int +.Fo archive_read_extract2 +.Fa "struct archive *src" +.Fa "struct archive_entry *" +.Fa "struct archive *dest" +.Fc +.Ft void +.Fo archive_read_extract_set_progress_callback +.Fa "struct archive *" +.Fa "void (*func)(void *)" +.Fa "void *user_data" +.Fc +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Fn archive_read_extract , Fn archive_read_extract_set_skip_file +A convenience function that wraps the corresponding +.Xr archive_write_disk 3 +interfaces. +The first call to +.Fn archive_read_extract +creates a restore object using +.Xr archive_write_disk_new 3 +and +.Xr archive_write_disk_set_standard_lookup 3 , +then transparently invokes +.Xr archive_write_disk_set_options 3 , +.Xr archive_write_header 3 , +.Xr archive_write_data 3 , +and +.Xr archive_write_finish_entry 3 +to create the entry on disk and copy data into it. +The +.Va flags +argument is passed unmodified to +.Xr archive_write_disk_set_options 3 . +.It Fn archive_read_extract2 +This is another version of +.Fn archive_read_extract +that allows you to provide your own restore object. +In particular, this allows you to override the standard lookup functions +using +.Xr archive_write_disk_set_group_lookup 3 , +and +.Xr archive_write_disk_set_user_lookup 3 . +Note that +.Fn archive_read_extract2 +does not accept a +.Va flags +argument; you should use +.Fn archive_write_disk_set_options +to set the restore options yourself. +.It Fn archive_read_extract_set_progress_callback +Sets a pointer to a user-defined callback that can be used +for updating progress displays during extraction. +The progress function will be invoked during the extraction of large +regular files. +The progress function will be invoked with the pointer provided to this call. +Generally, the data pointed to should include a reference to the archive +object and the archive_entry object so that various statistics +can be retrieved for the progress display. +.El +.\" +.Sh RETURN VALUES +Most functions return zero on success, non-zero on error. +The possible return codes include: +.Cm ARCHIVE_OK +(the operation succeeded), +.Cm ARCHIVE_WARN +(the operation succeeded but a non-critical error was encountered), +.Cm ARCHIVE_EOF +(end-of-archive was encountered), +.Cm ARCHIVE_RETRY +(the operation failed but can be retried), +and +.Cm ARCHIVE_FATAL +(there was a fatal error; the archive should be closed immediately). +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_data 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_open 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 diff --git a/libarchive/archive_read_extract.c b/libarchive/archive_read_extract.c new file mode 100644 index 0000000..aad8ac5 --- /dev/null +++ b/libarchive/archive_read_extract.c @@ -0,0 +1,183 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_extract.c,v 1.61 2008/05/26 17:00:22 kientzle Exp $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_write_disk_private.h" + +struct extract { + struct archive *ad; /* archive_write_disk object */ + + /* Progress function invoked during extract. */ + void (*extract_progress)(void *); + void *extract_progress_user_data; +}; + +static int archive_read_extract_cleanup(struct archive_read *); +static int copy_data(struct archive *ar, struct archive *aw); +static struct extract *get_extract(struct archive_read *); + +static struct extract * +get_extract(struct archive_read *a) +{ + /* If we haven't initialized, do it now. */ + /* This also sets up a lot of global state. */ + if (a->extract == NULL) { + a->extract = (struct extract *)malloc(sizeof(*a->extract)); + if (a->extract == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't extract"); + return (NULL); + } + memset(a->extract, 0, sizeof(*a->extract)); + a->extract->ad = archive_write_disk_new(); + if (a->extract->ad == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't extract"); + return (NULL); + } + archive_write_disk_set_standard_lookup(a->extract->ad); + a->cleanup_archive_extract = archive_read_extract_cleanup; + } + return (a->extract); +} + +int +archive_read_extract(struct archive *_a, struct archive_entry *entry, int flags) +{ + struct extract *extract; + + extract = get_extract((struct archive_read *)_a); + if (extract == NULL) + return (ARCHIVE_FATAL); + archive_write_disk_set_options(extract->ad, flags); + return (archive_read_extract2(_a, entry, extract->ad)); +} + +int +archive_read_extract2(struct archive *_a, struct archive_entry *entry, + struct archive *ad) +{ + struct archive_read *a = (struct archive_read *)_a; + int r, r2; + + /* Set up for this particular entry. */ + if (a->skip_file_set) + archive_write_disk_set_skip_file(ad, + a->skip_file_dev, a->skip_file_ino); + r = archive_write_header(ad, entry); + if (r < ARCHIVE_WARN) + r = ARCHIVE_WARN; + if (r != ARCHIVE_OK) + /* If _write_header failed, copy the error. */ + archive_copy_error(&a->archive, ad); + else if (!archive_entry_size_is_set(entry) || archive_entry_size(entry) > 0) + /* Otherwise, pour data into the entry. */ + r = copy_data(_a, ad); + r2 = archive_write_finish_entry(ad); + if (r2 < ARCHIVE_WARN) + r2 = ARCHIVE_WARN; + /* Use the first message. */ + if (r2 != ARCHIVE_OK && r == ARCHIVE_OK) + archive_copy_error(&a->archive, ad); + /* Use the worst error return. */ + if (r2 < r) + r = r2; + return (r); +} + +void +archive_read_extract_set_progress_callback(struct archive *_a, + void (*progress_func)(void *), void *user_data) +{ + struct archive_read *a = (struct archive_read *)_a; + struct extract *extract = get_extract(a); + if (extract != NULL) { + extract->extract_progress = progress_func; + extract->extract_progress_user_data = user_data; + } +} + +static int +copy_data(struct archive *ar, struct archive *aw) +{ + int64_t offset; + const void *buff; + struct extract *extract; + size_t size; + int r; + + extract = get_extract((struct archive_read *)ar); + if (extract == NULL) + return (ARCHIVE_FATAL); + for (;;) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); + if (r != ARCHIVE_OK) + return (r); + r = archive_write_data_block(aw, buff, size, offset); + if (r < ARCHIVE_WARN) + r = ARCHIVE_WARN; + if (r != ARCHIVE_OK) { + archive_set_error(ar, archive_errno(aw), + "%s", archive_error_string(aw)); + return (r); + } + if (extract->extract_progress) + (extract->extract_progress) + (extract->extract_progress_user_data); + } +} + +/* + * Cleanup function for archive_extract. + */ +static int +archive_read_extract_cleanup(struct archive_read *a) +{ + int ret = ARCHIVE_OK; + + ret = archive_write_free(a->extract->ad); + free(a->extract); + a->extract = NULL; + return (ret); +} diff --git a/libarchive/archive_read_filter.3 b/libarchive/archive_read_filter.3 new file mode 100644 index 0000000..7b506cc --- /dev/null +++ b/libarchive/archive_read_filter.3 @@ -0,0 +1,127 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 19, 2011 +.Dt archive_read_filter 3 +.Os +.Sh NAME +.Nm archive_read_support_filter_all , +.Nm archive_read_support_filter_bzip2 , +.Nm archive_read_support_filter_compress , +.Nm archive_read_support_filter_gzip , +.Nm archive_read_support_filter_lzma , +.Nm archive_read_support_filter_none , +.Nm archive_read_support_filter_xz , +.Nm archive_read_support_filter_program , +.Nm archive_read_support_filter_program_signature +.Nd functions for reading streaming archives +.\" +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_read_support_filter_all "struct archive *" +.Ft int +.Fn archive_read_support_filter_bzip2 "struct archive *" +.Ft int +.Fn archive_read_support_filter_compress "struct archive *" +.Ft int +.Fn archive_read_support_filter_gzip "struct archive *" +.Ft int +.Fn archive_read_support_filter_lzma "struct archive *" +.Ft int +.Fn archive_read_support_filter_none "struct archive *" +.Ft int +.Fn archive_read_support_filter_xz "struct archive *" +.Ft int +.Fo archive_read_support_filter_program +.Fa "struct archive *" +.Fa "const char *cmd" +.Fc +.Ft int +.Fo archive_read_support_filter_program_signature +.Fa "struct archive *" +.Fa "const char *cmd" +.Fa "const void *signature" +.Fa "size_t signature_length" +.Fc +.\" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Xo +.Fn archive_read_support_filter_bzip2 , +.Fn archive_read_support_filter_compress , +.Fn archive_read_support_filter_gzip , +.Fn archive_read_support_filter_lzma , +.Fn archive_read_support_filter_none , +.Fn archive_read_support_filter_xz +.Xc +Enables auto-detection code and decompression support for the +specified compression. +These functions may fall back on external programs if an appropriate +library was not available at build time. +Decompression using an external program is usually slower than +decompression through built-in libraries. +Note that +.Dq none +is always enabled by default. +.It Fn archive_read_support_filter_all +Enables all available decompression filters. +.It Fn archive_read_support_filter_program +Data is fed through the specified external program before being dearchived. +Note that this disables automatic detection of the compression format, +so it makes no sense to specify this in conjunction with any other +decompression option. +.It Fn archive_read_support_filter_program_signature +This feeds data through the specified external program +but only if the initial bytes of the data match the specified +signature value. +.El +.\" +.\". Sh EXAMPLE +.\" +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +if the compression is fully supported, +.Cm ARCHIVE_WARN +if the compression is supported only through an external program. +.Pp +.Fn archive_read_support_filter_none +always succeeds. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_data 3 , +.Xr archive_read_format 3 , +.Xr archive_read_format 3 diff --git a/libarchive/archive_read_format.3 b/libarchive/archive_read_format.3 new file mode 100644 index 0000000..3b5abf3 --- /dev/null +++ b/libarchive/archive_read_format.3 @@ -0,0 +1,175 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read.3 191595 2009-04-27 20:13:13Z kientzle $ +.\" +.Dd March 19, 2011 +.Dt archive_read_format 3 +.Os +.Sh NAME +.Nm archive_read_support_format_7zip , +.Nm archive_read_support_format_all , +.Nm archive_read_support_format_ar , +.Nm archive_read_support_format_by_code , +.Nm archive_read_support_format_cab , +.Nm archive_read_support_format_cpio , +.Nm archive_read_support_format_empty , +.Nm archive_read_support_format_iso9660 , +.Nm archive_read_support_format_lha , +.Nm archive_read_support_format_mtree, +.Nm archive_read_support_format_rar, +.Nm archive_read_support_format_raw, +.Nm archive_read_support_format_tar , +.Nm archive_read_support_format_xar , +.Nm archive_read_support_format_zip +.Nd functions for reading streaming archives +.\" +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_read_support_format_7zip "struct archive *" +.Ft int +.Fn archive_read_support_format_all "struct archive *" +.Ft int +.Fn archive_read_support_format_ar "struct archive *" +.Ft int +.Fn archive_read_support_format_by_code "struct archive *" "int" +.Ft int +.Fn archive_read_support_format_cab "struct archive *" +.Ft int +.Fn archive_read_support_format_cpio "struct archive *" +.Ft int +.Fn archive_read_support_format_empty "struct archive *" +.Ft int +.Fn archive_read_support_format_iso9660 "struct archive *" +.Ft int +.Fn archive_read_support_format_lha "struct archive *" +.Ft int +.Fn archive_read_support_format_mtree "struct archive *" +.Ft int +.Fn archive_read_support_format_rar "struct archive *" +.Ft int +.Fn archive_read_support_format_raw "struct archive *" +.Ft int +.Fn archive_read_support_format_tar "struct archive *" +.Ft int +.Fn archive_read_support_format_xar "struct archive *" +.Ft int +.Fn archive_read_support_format_zip "struct archive *" +.\" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Xo +.Fn archive_read_support_format_7zip , +.Fn archive_read_support_format_ar , +.Fn archive_read_support_format_cab , +.Fn archive_read_support_format_cpio , +.Fn archive_read_support_format_iso9660 , +.Fn archive_read_support_format_lha , +.Fn archive_read_support_format_mtree , +.Fn archive_read_support_format_rar , +.Fn archive_read_support_format_raw , +.Fn archive_read_support_format_tar , +.Fn archive_read_support_format_xar , +.Fn archive_read_support_format_zip +.Xc +Enables support---including auto-detection code---for the +specified archive format. +For example, +.Fn archive_read_support_format_tar +enables support for a variety of standard tar formats, old-style tar, +ustar, pax interchange format, and many common variants. +.It Fn archive_read_support_format_all +Enables support for all available formats except the +.Dq raw +format (see below). +.It Fn archive_read_support_format_by_code +Enables a single format specified by the format code. +This can be useful when reading a single archive twice; +use +.Fn archive_format +after reading the first time and pass the resulting code +to this function to selectively enable only the necessary +format support. +Note: In statically-linked executables, this will cause +your program to include support for every format. +If executable size is a concern, you may wish to avoid +using this function. +.It Fn archive_read_support_format_empty +Enables support for treating empty files as empty archives. +Because empty files are valid for several different formats, +it is not possible to accurately determine a format for +an empty file based purely on contents. +So empty files are treated by libarchive as a distinct +format. +.It Fn archive_read_support_format_raw +The +.Dq raw +format handler allows libarchive to be used to read arbitrary data. +It treats any data stream as an archive with a single entry. +The pathname of this entry is +.Dq data ; +all other entry fields are unset. +This is not enabled by +.Fn archive_read_support_format_all +in order to avoid erroneous handling of damaged archives. +.El +.\" .Sh EXAMPLE +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read_data 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 +.Sh BUGS +Many traditional archiver programs treat +empty files as valid empty archives. +For example, many implementations of +.Xr tar 1 +allow you to append entries to an empty file. +Of course, it is impossible to determine the format of an empty file +by inspecting the contents, so this library treats empty files as +having a special +.Dq empty +format. +.Pp +Using the +.Dq raw +handler together with any other handler will often work +but can produce surprising results. diff --git a/libarchive/archive_read_free.3 b/libarchive/archive_read_free.3 new file mode 100644 index 0000000..5838e20 --- /dev/null +++ b/libarchive/archive_read_free.3 @@ -0,0 +1,91 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read.3 191595 2009-04-27 20:13:13Z kientzle $ +.\" +.Dd March 20, 2011 +.Dt archive_read_free 3 +.Os +.Sh NAME +.Nm archive_read_close , +.Nm archive_read_finish , +.Nm archive_read_free +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_read_close "struct archive *" +.Ft int +.Fn archive_read_finish "struct archive *" +.Ft int +.Fn archive_read_free "struct archive *" +.\" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Fn archive_read_close +Complete the archive and invoke the close callback. +.It Fn archive_read_finish +This is a deprecated synonym for +.Fn archive_read_free . +The new name was introduced with libarchive 3.0. +Applications that need to compile with either libarchive 2 +or libarchive 3 should continue to use the +.Fn archive_read_finish +name. +Both names will be supported until libarchive 4.0 is +released, which is not expected to occur earlier +than 2013. +.It Fn archive_read_free +Invokes +.Fn archive_read_close +if it was not invoked manually, then release all resources. +Note: In libarchive 1.x, this function was declared to return +.Ft void , +which made it impossible to detect certain errors when +.Fn archive_read_close +was invoked implicitly from this function. +The declaration is corrected beginning with libarchive 2.0. +.El +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr libarchive 3 , +.Xr archive_read_new 3 , +.Xr archive_read_data 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_open 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 diff --git a/libarchive/archive_read_header.3 b/libarchive/archive_read_header.3 new file mode 100644 index 0000000..f8543f7 --- /dev/null +++ b/libarchive/archive_read_header.3 @@ -0,0 +1,89 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 22, 2011 +.Dt archive_read_header 3 +.Os +.Sh NAME +.Nm archive_read_next_header , +.Nm archive_read_next_header2 +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_read_next_header "struct archive *" "struct archive_entry **" +.Ft int +.Fn archive_read_next_header2 "struct archive *" "struct archive_entry *" +.\" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Fn archive_read_next_header +Read the header for the next entry and return a pointer to +a +.Tn struct archive_entry . +This is a convenience wrapper around +.Fn archive_read_next_header2 +that reuses an internal +.Tn struct archive_entry +object for each request. +.It Fn archive_read_next_header2 +Read the header for the next entry and populate the provided +.Tn struct archive_entry . +.El +.\" +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +(the operation succeeded), +.Cm ARCHIVE_WARN +(the operation succeeded but a non-critical error was encountered), +.Cm ARCHIVE_EOF +(end-of-archive was encountered), +.Cm ARCHIVE_RETRY +(the operation failed but can be retried), +and +.Cm ARCHIVE_FATAL +(there was a fatal error; the archive should be closed immediately). +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_data 3 , +.Xr archive_read_extract 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_open 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 diff --git a/libarchive/archive_read_new.3 b/libarchive/archive_read_new.3 new file mode 100644 index 0000000..d2d9862 --- /dev/null +++ b/libarchive/archive_read_new.3 @@ -0,0 +1,57 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read.3 191595 2009-04-27 20:13:13Z kientzle $ +.\" +.Dd March 20, 2011 +.Dt archive_read_new 3 +.Os +.Sh NAME +.Nm archive_read_new +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_read_new "void" +.Sh DESCRIPTION +Allocates and initializes a +.Tn struct archive +object suitable for reading from an archive. +.Dv NULL +is returned on error. +.Pp +A complete description of the +.Tn struct archive +object can be found in the overview manual page for +.Xr libarchive 3 . +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read_data 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 diff --git a/libarchive/archive_read_open.3 b/libarchive/archive_read_open.3 new file mode 100644 index 0000000..ff15641 --- /dev/null +++ b/libarchive/archive_read_open.3 @@ -0,0 +1,231 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_read.3 191595 2009-04-27 20:13:13Z kientzle $ +.\" +.Dd March 19, 2011 +.Dt archive_read_open 3 +.Os +.Sh NAME +.Nm archive_read_open , +.Nm archive_read_open2 , +.Nm archive_read_open_fd , +.Nm archive_read_open_FILE , +.Nm archive_read_open_filename , +.Nm archive_read_open_memory , +.Nd functions for reading streaming archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fo archive_read_open +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_read_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fo archive_read_open2 +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_read_callback *" +.Fa "archive_skip_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fn archive_read_open_FILE "struct archive *" "FILE *file" +.Ft int +.Fn archive_read_open_fd "struct archive *" "int fd" "size_t block_size" +.Ft int +.Fo archive_read_open_filename +.Fa "struct archive *" +.Fa "const char *filename" +.Fa "size_t block_size" +.Fc +.Ft int +.Fn archive_read_open_memory "struct archive *" "void *buff" "size_t size" +.Sh DESCRIPTION +.Bl -tag -compact -width indent +.It Fn archive_read_open +The same as +.Fn archive_read_open2 , +except that the skip callback is assumed to be +.Dv NULL . +.It Fn archive_read_open2 +Freeze the settings, open the archive, and prepare for reading entries. +This is the most generic version of this call, which accepts +four callback functions. +Most clients will want to use +.Fn archive_read_open_filename , +.Fn archive_read_open_FILE , +.Fn archive_read_open_fd , +or +.Fn archive_read_open_memory +instead. +The library invokes the client-provided functions to obtain +raw bytes from the archive. +.It Fn archive_read_open_FILE +Like +.Fn archive_read_open , +except that it accepts a +.Ft "FILE *" +pointer. +This function should not be used with tape drives or other devices +that require strict I/O blocking. +.It Fn archive_read_open_fd +Like +.Fn archive_read_open , +except that it accepts a file descriptor and block size rather than +a set of function pointers. +Note that the file descriptor will not be automatically closed at +end-of-archive. +This function is safe for use with tape drives or other blocked devices. +.It Fn archive_read_open_file +This is a deprecated synonym for +.Fn archive_read_open_filename . +.It Fn archive_read_open_filename +Like +.Fn archive_read_open , +except that it accepts a simple filename and a block size. +A NULL filename represents standard input. +This function is safe for use with tape drives or other blocked devices. +.It Fn archive_read_open_memory +Like +.Fn archive_read_open , +except that it accepts a pointer and size of a block of +memory containing the archive data. +.El +.Pp +A complete description of the +.Tn struct archive +and +.Tn struct archive_entry +objects can be found in the overview manual page for +.Xr libarchive 3 . +.Sh CLIENT CALLBACKS +The callback functions must match the following prototypes: +.Bl -item -offset indent +.It +.Ft typedef ssize_t +.Fo archive_read_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "const void **buffer" +.Fc +.It +.Ft typedef off_t +.Fo archive_skip_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "off_t request" +.Fc +.It +.Ft typedef int +.Fn archive_open_callback "struct archive *" "void *client_data" +.It +.Ft typedef int +.Fn archive_close_callback "struct archive *" "void *client_data" +.El +.Pp +The open callback is invoked by +.Fn archive_open . +It should return +.Cm ARCHIVE_OK +if the underlying file or data source is successfully +opened. +If the open fails, it should call +.Fn archive_set_error +to register an error code and message and return +.Cm ARCHIVE_FATAL . +.Pp +The read callback is invoked whenever the library +requires raw bytes from the archive. +The read callback should read data into a buffer, +set the +.Li const void **buffer +argument to point to the available data, and +return a count of the number of bytes available. +The library will invoke the read callback again +only after it has consumed this data. +The library imposes no constraints on the size +of the data blocks returned. +On end-of-file, the read callback should +return zero. +On error, the read callback should invoke +.Fn archive_set_error +to register an error code and message and +return -1. +.Pp +The skip callback is invoked when the +library wants to ignore a block of data. +The return value is the number of bytes actually +skipped, which may differ from the request. +If the callback cannot skip data, it should return +zero. +If the skip callback is not provided (the +function pointer is +.Dv NULL ), +the library will invoke the read function +instead and simply discard the result. +A skip callback can provide significant +performance gains when reading uncompressed +archives from slow disk drives or other media +that can skip quickly. +.Pp +The close callback is invoked by archive_close when +the archive processing is complete. +The callback should return +.Cm ARCHIVE_OK +on success. +On failure, the callback should invoke +.Fn archive_set_error +to register an error code and message and +return +.Cm ARCHIVE_FATAL. +.\" .Sh EXAMPLE +.\" +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_data 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_set_options 3 , +.Xr archive_util 3 , +.Xr tar 5 diff --git a/libarchive/archive_read_open_fd.c b/libarchive/archive_read_open_fd.c new file mode 100644 index 0000000..d8f6572 --- /dev/null +++ b/libarchive/archive_read_open_fd.c @@ -0,0 +1,181 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_fd.c 201103 2009-12-28 03:13:49Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" + +struct read_fd_data { + int fd; + size_t block_size; + char use_lseek; + void *buffer; +}; + +static int file_close(struct archive *, void *); +static ssize_t file_read(struct archive *, void *, const void **buff); +static int64_t file_skip(struct archive *, void *, int64_t request); + +int +archive_read_open_fd(struct archive *a, int fd, size_t block_size) +{ + struct stat st; + struct read_fd_data *mine; + void *b; + + archive_clear_error(a); + if (fstat(fd, &st) != 0) { + archive_set_error(a, errno, "Can't stat fd %d", fd); + return (ARCHIVE_FATAL); + } + + mine = (struct read_fd_data *)calloc(1, sizeof(*mine)); + b = malloc(block_size); + if (mine == NULL || b == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + free(mine); + free(b); + return (ARCHIVE_FATAL); + } + mine->block_size = block_size; + mine->buffer = b; + mine->fd = fd; + /* + * Skip support is a performance optimization for anything + * that supports lseek(). On FreeBSD, only regular files and + * raw disk devices support lseek() and there's no portable + * way to determine if a device is a raw disk device, so we + * only enable this optimization for regular files. + */ + if (S_ISREG(st.st_mode)) { + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + mine->use_lseek = 1; + } +#if defined(__CYGWIN__) || defined(_WIN32) + setmode(mine->fd, O_BINARY); +#endif + + archive_read_set_read_callback(a, file_read); + archive_read_set_skip_callback(a, file_skip); + archive_read_set_close_callback(a, file_close); + archive_read_set_callback_data(a, mine); + return (archive_read_open1(a)); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + ssize_t bytes_read; + + *buff = mine->buffer; + for (;;) { + bytes_read = read(mine->fd, mine->buffer, mine->block_size); + if (bytes_read < 0) { + if (errno == EINTR) + continue; + archive_set_error(a, errno, "Error reading fd %d", mine->fd); + } + return (bytes_read); + } +} + +static int64_t +file_skip(struct archive *a, void *client_data, int64_t request) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + off_t skip = (off_t)request; + off_t old_offset, new_offset; + int skip_bits = sizeof(skip) * 8 - 1; /* off_t is a signed type. */ + + if (!mine->use_lseek) + return (0); + + /* Reduce a request that would overflow the 'skip' variable. */ + if (sizeof(request) > sizeof(skip)) { + int64_t max_skip = + (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1; + if (request > max_skip) + skip = max_skip; + } + + /* Reduce request to the next smallest multiple of block_size */ + request = (request / mine->block_size) * mine->block_size; + if (request == 0) + return (0); + + if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) && + ((new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0)) + return (new_offset - old_offset); + + /* If seek failed once, it will probably fail again. */ + mine->use_lseek = 0; + + /* Let libarchive recover with read+discard. */ + if (errno == ESPIPE) + return (0); + + /* + * There's been an error other than ESPIPE. This is most + * likely caused by a programmer error (too large request) + * or a corrupted archive file. + */ + archive_set_error(a, errno, "Error seeking"); + return (-1); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_fd_data *mine = (struct read_fd_data *)client_data; + + (void)a; /* UNUSED */ + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c new file mode 100644 index 0000000..b1aac0a --- /dev/null +++ b/libarchive/archive_read_open_file.c @@ -0,0 +1,177 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_file.c 201093 2009-12-28 02:28:44Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" + +struct read_FILE_data { + FILE *f; + size_t block_size; + void *buffer; + char can_skip; +}; + +static int file_close(struct archive *, void *); +static ssize_t file_read(struct archive *, void *, const void **buff); +static int64_t file_skip(struct archive *, void *, int64_t request); + +int +archive_read_open_FILE(struct archive *a, FILE *f) +{ + struct stat st; + struct read_FILE_data *mine; + size_t block_size = 128 * 1024; + void *b; + + archive_clear_error(a); + mine = (struct read_FILE_data *)malloc(sizeof(*mine)); + b = malloc(block_size); + if (mine == NULL || b == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + free(mine); + free(b); + return (ARCHIVE_FATAL); + } + mine->block_size = block_size; + mine->buffer = b; + mine->f = f; + /* + * If we can't fstat() the file, it may just be that it's not + * a file. (FILE * objects can wrap many kinds of I/O + * streams, some of which don't support fileno()).) + */ + if (fstat(fileno(mine->f), &st) == 0 && S_ISREG(st.st_mode)) { + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + /* Enable the seek optimization only for regular files. */ + mine->can_skip = 1; + } else + mine->can_skip = 0; + +#if defined(__CYGWIN__) || defined(_WIN32) + setmode(fileno(mine->f), O_BINARY); +#endif + + archive_read_set_read_callback(a, file_read); + archive_read_set_skip_callback(a, file_skip); + archive_read_set_close_callback(a, file_close); + archive_read_set_callback_data(a, mine); + return (archive_read_open1(a)); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + ssize_t bytes_read; + + *buff = mine->buffer; + bytes_read = fread(mine->buffer, 1, mine->block_size, mine->f); + if (bytes_read < 0) { + archive_set_error(a, errno, "Error reading file"); + } + return (bytes_read); +} + +static int64_t +file_skip(struct archive *a, void *client_data, int64_t request) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; +#if HAVE_FSEEKO + off_t skip = (off_t)request; +#elif HAVE__FSEEKI64 + int64_t skip = request; +#else + long skip = (long)request; +#endif + int skip_bits = sizeof(skip) * 8 - 1; + + (void)a; /* UNUSED */ + + /* + * If we can't skip, return 0 as the amount we did step and + * the caller will work around by reading and discarding. + */ + if (!mine->can_skip) + return (0); + if (request == 0) + return (0); + + /* If request is too big for a long or an off_t, reduce it. */ + if (sizeof(request) > sizeof(skip)) { + int64_t max_skip = + (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1; + if (request > max_skip) + skip = max_skip; + } + +#if HAVE_FSEEKO + if (fseeko(mine->f, skip, SEEK_CUR) != 0) +#elif HAVE__FSEEKI64 + if (_fseeki64(mine->f, skip, SEEK_CUR) != 0) +#else + if (fseek(mine->f, skip, SEEK_CUR) != 0) +#endif + { + mine->can_skip = 0; + return (0); + } + return (request); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_FILE_data *mine = (struct read_FILE_data *)client_data; + + (void)a; /* UNUSED */ + if (mine->buffer != NULL) + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_filename.c b/libarchive/archive_read_open_filename.c new file mode 100644 index 0000000..bf52697 --- /dev/null +++ b/libarchive/archive_read_open_filename.c @@ -0,0 +1,509 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_open_filename.c 201093 2009-12-28 02:28:44Z kientzle $"); + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#include +#include +#elif defined(__DragonFly__) +#include +#endif + +#include "archive.h" +#include "archive_string.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct read_file_data { + int fd; + size_t block_size; + void *buffer; + mode_t st_mode; /* Mode bits for opened file. */ + char use_lseek; + enum fnt_e { FNT_STDIN, FNT_MBS, FNT_WCS } filename_type; + union { + char m[1];/* MBS filename. */ + wchar_t w[1];/* WCS filename. */ + } filename; /* Must be last! */ +}; + +static int file_close(struct archive *, void *); +static int file_open_filename(struct archive *, enum fnt_e, const void *, + size_t); +static ssize_t file_read(struct archive *, void *, const void **buff); +static int64_t file_seek(struct archive *, void *, int64_t request, int); +static int64_t file_skip(struct archive *, void *, int64_t request); +static int64_t file_skip_lseek(struct archive *, void *, int64_t request); + +int +archive_read_open_file(struct archive *a, const char *filename, + size_t block_size) +{ + return (archive_read_open_filename(a, filename, block_size)); +} + +int +archive_read_open_filename(struct archive *a, const char *filename, + size_t block_size) +{ + enum fnt_e filename_type; + + if (filename == NULL || filename[0] == '\0') { + filename_type = FNT_STDIN; + } else + filename_type = FNT_MBS; + return (file_open_filename(a, filename_type, filename, block_size)); +} + +int +archive_read_open_filename_w(struct archive *a, const wchar_t *wfilename, + size_t block_size) +{ + enum fnt_e filename_type; + + if (wfilename == NULL || wfilename[0] == L'\0') { + filename_type = FNT_STDIN; + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + filename_type = FNT_WCS; +#else + /* + * POSIX system does not support a wchar_t interface for + * open() system call, so we have to translate a whcar_t + * filename to multi-byte one and use it. + */ + struct archive_string fn; + int r; + + archive_string_init(&fn); + if (archive_string_append_from_wcs(&fn, wfilename, + wcslen(wfilename)) != 0) { + archive_set_error(a, EINVAL, + "Failed to convert a wide-character filename to" + " a multi-byte filename"); + archive_string_free(&fn); + return (ARCHIVE_FATAL); + } + r = file_open_filename(a, FNT_MBS, fn.s, block_size); + archive_string_free(&fn); + return (r); +#endif + } + return (file_open_filename(a, filename_type, wfilename, block_size)); +} + +static int +file_open_filename(struct archive *a, enum fnt_e filename_type, + const void *_filename, size_t block_size) +{ + struct stat st; + struct read_file_data *mine; + void *buffer; + const char *filename = NULL; + const wchar_t *wfilename = NULL; + int fd; + int is_disk_like = 0; +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + off_t mediasize = 0; /* FreeBSD-specific, so off_t okay here. */ +#elif defined(__NetBSD__) || defined(__OpenBSD__) + struct disklabel dl; +#elif defined(__DragonFly__) + struct partinfo pi; +#endif + + archive_clear_error(a); + if (filename_type == FNT_STDIN) { + /* We used to delegate stdin support by + * directly calling archive_read_open_fd(a,0,block_size) + * here, but that doesn't (and shouldn't) handle the + * end-of-file flush when reading stdout from a pipe. + * Basically, read_open_fd() is intended for folks who + * are willing to handle such details themselves. This + * API is intended to be a little smarter for folks who + * want easy handling of the common case. + */ + fd = 0; +#if defined(__CYGWIN__) || defined(_WIN32) + setmode(0, O_BINARY); +#endif + filename = ""; + } else if (filename_type == FNT_MBS) { + filename = (const char *)_filename; + fd = open(filename, O_RDONLY | O_BINARY); + if (fd < 0) { + archive_set_error(a, errno, + "Failed to open '%s'", filename); + return (ARCHIVE_FATAL); + } + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + wfilename = (const wchar_t *)_filename; + fd = _wopen(wfilename, O_RDONLY | O_BINARY); + if (fd < 0 && errno == ENOENT) { + wchar_t *fullpath; + fullpath = __la_win_permissive_name_w(wfilename); + if (fullpath != NULL) { + fd = _wopen(fullpath, O_RDONLY | O_BINARY); + free(fullpath); + } + } + if (fd < 0) { + archive_set_error(a, errno, + "Failed to open '%S'", wfilename); + return (ARCHIVE_FATAL); + } +#else + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Unexpedted operation in archive_read_open_filename"); + return (ARCHIVE_FATAL); +#endif + } + if (fstat(fd, &st) != 0) { + if (filename_type == FNT_WCS) + archive_set_error(a, errno, "Can't stat '%S'", + wfilename); + else + archive_set_error(a, errno, "Can't stat '%s'", + filename); + return (ARCHIVE_FATAL); + } + + /* + * Determine whether the input looks like a disk device or a + * tape device. The results are used below to select an I/O + * strategy: + * = "disk-like" devices support arbitrary lseek() and will + * support I/O requests of any size. So we get easy skipping + * and can cheat on block sizes to get better performance. + * = "tape-like" devices require strict blocking and use + * specialized ioctls for seeking. + * = "socket-like" devices cannot seek at all but can improve + * performance by using nonblocking I/O to read "whatever is + * available right now". + * + * Right now, we only specially recognize disk-like devices, + * but it should be straightforward to add probes and strategy + * here for tape-like and socket-like devices. + */ + if (S_ISREG(st.st_mode)) { + /* Safety: Tell the extractor not to overwrite the input. */ + archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino); + /* Regular files act like disks. */ + is_disk_like = 1; + } +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + /* FreeBSD: if it supports DIOCGMEDIASIZE ioctl, it's disk-like. */ + else if (S_ISCHR(st.st_mode) && + ioctl(fd, DIOCGMEDIASIZE, &mediasize) == 0 && + mediasize > 0) { + is_disk_like = 1; + } +#elif defined(__NetBSD__) || defined(__OpenBSD__) + /* Net/OpenBSD: if it supports DIOCGDINFO ioctl, it's disk-like. */ + else if ((S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) && + ioctl(fd, DIOCGDINFO, &dl) == 0 && + dl.d_partitions[DISKPART(st.st_rdev)].p_size > 0) { + is_disk_like = 1; + } +#elif defined(__DragonFly__) + /* DragonFly BSD: if it supports DIOCGPART ioctl, it's disk-like. */ + else if (S_ISCHR(st.st_mode) && + ioctl(fd, DIOCGPART, &pi) == 0 && + pi.media_size > 0) { + is_disk_like = 1; + } +#elif defined(__linux__) + /* Linux: All block devices are disk-like. */ + else if (S_ISBLK(st.st_mode) && + lseek(fd, 0, SEEK_CUR) == 0 && + lseek(fd, 0, SEEK_SET) == 0 && + lseek(fd, 0, SEEK_END) > 0 && + lseek(fd, 0, SEEK_SET) == 0) { + is_disk_like = 1; + } +#endif + /* TODO: Add an "is_tape_like" variable and appropriate tests. */ + + if (filename_type == FNT_WCS) + mine = (struct read_file_data *)calloc(1, + sizeof(*mine) + wcslen(wfilename) * sizeof(wchar_t)); + else + mine = (struct read_file_data *)calloc(1, + sizeof(*mine) + strlen(filename)); + /* Disk-like devices prefer power-of-two block sizes. */ + /* Use provided block_size as a guide so users have some control. */ + if (is_disk_like) { + size_t new_block_size = 64 * 1024; + while (new_block_size < block_size + && new_block_size < 64 * 1024 * 1024) + new_block_size *= 2; + block_size = new_block_size; + } + buffer = malloc(block_size); + if (mine == NULL || buffer == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + free(mine); + free(buffer); + return (ARCHIVE_FATAL); + } + if (filename_type == FNT_WCS) + wcscpy(mine->filename.w, wfilename); + else + strcpy(mine->filename.m, filename); + mine->filename_type = filename_type; + mine->block_size = block_size; + mine->buffer = buffer; + mine->fd = fd; + /* Remember mode so close can decide whether to flush. */ + mine->st_mode = st.st_mode; + + /* Disk-like inputs can use lseek(). */ + if (is_disk_like) { + archive_read_set_seek_callback(a, file_seek); + mine->use_lseek = 1; + } + + archive_read_set_read_callback(a, file_read); + archive_read_set_skip_callback(a, file_skip); + archive_read_set_close_callback(a, file_close); + archive_read_set_callback_data(a, mine); + return (archive_read_open1(a)); +} + +static ssize_t +file_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + ssize_t bytes_read; + + /* TODO: If a recent lseek() operation has left us + * mis-aligned, read and return a short block to try to get + * us back in alignment. */ + + /* TODO: Someday, try mmap() here; if that succeeds, give + * the entire file to libarchive as a single block. That + * could be a lot faster than block-by-block manual I/O. */ + + /* TODO: We might be able to improve performance on pipes and + * sockets by setting non-blocking I/O and just accepting + * whatever we get here instead of waiting for a full block + * worth of data. */ + + *buff = mine->buffer; + for (;;) { + bytes_read = read(mine->fd, mine->buffer, mine->block_size); + if (bytes_read < 0) { + if (errno == EINTR) + continue; + else if (mine->filename_type == FNT_STDIN) + archive_set_error(a, errno, + "Error reading stdin"); + else if (mine->filename_type == FNT_MBS) + archive_set_error(a, errno, + "Error reading '%s'", mine->filename.m); + else + archive_set_error(a, errno, + "Error reading '%S'", mine->filename.w); + } + return (bytes_read); + } +} + +/* + * Regular files and disk-like block devices can use simple lseek + * without needing to round the request to the block size. + * + * TODO: This can leave future reads mis-aligned. Since we know the + * offset here, we should store it and use it in file_read() above + * to determine whether we should perform a short read to get back + * into alignment. Long series of mis-aligned reads can negatively + * impact disk throughput. (Of course, the performance impact should + * be carefully tested; extra code complexity is only worthwhile if + * it does provide measurable improvement.) + * + * TODO: Be lazy about the actual seek. There are a few pathological + * cases where libarchive makes a bunch of seek requests in a row + * without any intervening reads. This isn't a huge performance + * problem, since the kernel handles seeks lazily already, but + * it would be very slightly faster if we simply remembered the + * seek request here and then actually performed the seek at the + * top of the read callback above. + */ +static int64_t +file_skip_lseek(struct archive *a, void *client_data, int64_t request) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; +#if defined(_WIN32) && !defined(__CYGWIN__) + /* We use _lseeki64() on Windows. */ + int64_t old_offset, new_offset; +#else + off_t old_offset, new_offset; +#endif + + /* We use off_t here because lseek() is declared that way. */ + + /* TODO: Deal with case where off_t isn't 64 bits. + * This shouldn't be a problem on Linux or other POSIX + * systems, since the configuration logic for libarchive + * tries to obtain a 64-bit off_t. It's still an issue + * on Windows, though, so it might suffice to just use + * _lseeki64() on Windows. + */ + if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0 && + (new_offset = lseek(mine->fd, request, SEEK_CUR)) >= 0) + return (new_offset - old_offset); + + /* If lseek() fails, don't bother trying again. */ + mine->use_lseek = 0; + + /* Let libarchive recover with read+discard */ + if (errno == ESPIPE) + return (0); + + /* If the input is corrupted or truncated, fail. */ + if (mine->filename_type == FNT_STDIN) + archive_set_error(a, errno, "Error seeking in stdin"); + else if (mine->filename_type == FNT_MBS) + archive_set_error(a, errno, "Error seeking in '%s'", + mine->filename.m); + else + archive_set_error(a, errno, "Error seeking in '%S'", + mine->filename.w); + return (-1); +} + + +/* + * TODO: Implement another file_skip_XXXX that uses MTIO ioctls to + * accelerate operation on tape drives. + */ + +static int64_t +file_skip(struct archive *a, void *client_data, int64_t request) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + + /* Delegate skip requests. */ + if (mine->use_lseek) + return (file_skip_lseek(a, client_data, request)); + + /* If we can't skip, return 0; libarchive will read+discard instead. */ + return (0); +} + +/* + * TODO: Store the offset and use it in the read callback. + */ +static int64_t +file_seek(struct archive *a, void *client_data, int64_t request, int whence) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + off_t r; + + /* We use off_t here because lseek() is declared that way. */ + /* See above for notes about when off_t is less than 64 bits. */ + r = lseek(mine->fd, request, whence); + if (r >= 0) + return r; + + /* If the input is corrupted or truncated, fail. */ + if (mine->filename_type == FNT_STDIN) + archive_set_error(a, errno, "Error seeking in stdin"); + else if (mine->filename_type == FNT_MBS) + archive_set_error(a, errno, "Error seeking in '%s'", + mine->filename.m); + else + archive_set_error(a, errno, "Error seeking in '%S'", + mine->filename.w); + return (ARCHIVE_FATAL); +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct read_file_data *mine = (struct read_file_data *)client_data; + + (void)a; /* UNUSED */ + + /* Only flush and close if open succeeded. */ + if (mine->fd >= 0) { + /* + * Sometimes, we should flush the input before closing. + * Regular files: faster to just close without flush. + * Disk-like devices: Ditto. + * Tapes: must not flush (user might need to + * read the "next" item on a non-rewind device). + * Pipes and sockets: must flush (otherwise, the + * program feeding the pipe or socket may complain). + * Here, I flush everything except for regular files and + * device nodes. + */ + if (!S_ISREG(mine->st_mode) + && !S_ISCHR(mine->st_mode) + && !S_ISBLK(mine->st_mode)) { + ssize_t bytesRead; + do { + bytesRead = read(mine->fd, mine->buffer, + mine->block_size); + } while (bytesRead > 0); + } + /* If a named file was opened, then it needs to be closed. */ + if (mine->filename_type != FNT_STDIN) + close(mine->fd); + } + free(mine->buffer); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_open_memory.c b/libarchive/archive_read_open_memory.c new file mode 100644 index 0000000..07940a2 --- /dev/null +++ b/libarchive/archive_read_open_memory.c @@ -0,0 +1,186 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_open_memory.c,v 1.6 2007/07/06 15:51:59 kientzle Exp $"); + +#include +#include +#include + +#include "archive.h" + +/* + * Glue to read an archive from a block of memory. + * + * This is mostly a huge help in building test harnesses; + * test programs can build archives in memory and read them + * back again without having to mess with files on disk. + */ + +struct read_memory_data { + unsigned char *start; + unsigned char *p; + unsigned char *end; + ssize_t read_size; +}; + +static int memory_read_close(struct archive *, void *); +static int memory_read_open(struct archive *, void *); +static int64_t memory_read_seek(struct archive *, void *, int64_t offset, int whence); +static int64_t memory_read_skip(struct archive *, void *, int64_t request); +static ssize_t memory_read(struct archive *, void *, const void **buff); + +int +archive_read_open_memory(struct archive *a, void *buff, size_t size) +{ + return archive_read_open_memory2(a, buff, size, size); +} + +/* + * Don't use _open_memory2() in production code; the archive_read_open_memory() + * version is the one you really want. This is just here so that + * test harnesses can exercise block operations inside the library. + */ +int +archive_read_open_memory2(struct archive *a, void *buff, + size_t size, size_t read_size) +{ + struct read_memory_data *mine; + + mine = (struct read_memory_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + memset(mine, 0, sizeof(*mine)); + mine->start = mine->p = (unsigned char *)buff; + mine->end = mine->start + size; + mine->read_size = read_size; + archive_read_set_open_callback(a, memory_read_open); + archive_read_set_read_callback(a, memory_read); + archive_read_set_seek_callback(a, memory_read_seek); + archive_read_set_skip_callback(a, memory_read_skip); + archive_read_set_close_callback(a, memory_read_close); + archive_read_set_callback_data(a, mine); + return (archive_read_open1(a)); +} + +/* + * There's nothing to open. + */ +static int +memory_read_open(struct archive *a, void *client_data) +{ + (void)a; /* UNUSED */ + (void)client_data; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* + * This is scary simple: Just advance a pointer. Limiting + * to read_size is not technically necessary, but it exercises + * more of the internal logic when used with a small block size + * in a test harness. Production use should not specify a block + * size; then this is much faster. + */ +static ssize_t +memory_read(struct archive *a, void *client_data, const void **buff) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + ssize_t size; + + (void)a; /* UNUSED */ + *buff = mine->p; + size = mine->end - mine->p; + if (size > mine->read_size) + size = mine->read_size; + mine->p += size; + return (size); +} + +/* + * Advancing is just as simple. Again, this is doing more than + * necessary in order to better exercise internal code when used + * as a test harness. + */ +static int64_t +memory_read_skip(struct archive *a, void *client_data, int64_t skip) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + + (void)a; /* UNUSED */ + if ((int64_t)skip > (int64_t)(mine->end - mine->p)) + skip = mine->end - mine->p; + /* Round down to block size. */ + skip /= mine->read_size; + skip *= mine->read_size; + mine->p += skip; + return (skip); +} + +/* + * Seeking. + */ +static int64_t +memory_read_seek(struct archive *a, void *client_data, int64_t offset, int whence) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + + switch (whence) { + case SEEK_SET: + mine->p = mine->start + offset; + break; + case SEEK_CUR: + mine->p += offset; + break; + case SEEK_END: + mine->p = mine->end + offset; + break; + default: + return ARCHIVE_FATAL; + } + if (mine->p < mine->start) { + mine->p = mine->start; + return ARCHIVE_FAILED; + } + if (mine->p > mine->end) { + mine->p = mine->end; + return ARCHIVE_FAILED; + } + return (mine->p - mine->start); +} + +/* + * Close is just cleaning up our one small bit of data. + */ +static int +memory_read_close(struct archive *a, void *client_data) +{ + struct read_memory_data *mine = (struct read_memory_data *)client_data; + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_private.h b/libarchive/archive_read_private.h new file mode 100644 index 0000000..76d0b91 --- /dev/null +++ b/libarchive/archive_read_private.h @@ -0,0 +1,210 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_read_private.h 201088 2009-12-28 02:18:55Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_READ_PRIVATE_H_INCLUDED +#define ARCHIVE_READ_PRIVATE_H_INCLUDED + +#include "archive.h" +#include "archive_string.h" +#include "archive_private.h" + +struct archive_read; +struct archive_read_filter_bidder; +struct archive_read_filter; + +/* + * How bidding works for filters: + * * The bid manager initializes the client-provided reader as the + * first filter. + * * It invokes the bidder for each registered filter with the + * current head filter. + * * The bidders can use archive_read_filter_ahead() to peek ahead + * at the incoming data to compose their bids. + * * The bid manager creates a new filter structure for the winning + * bidder and gives the winning bidder a chance to initialize it. + * * The new filter becomes the new top filter and we repeat the + * process. + * This ends only when no bidder provides a non-zero bid. Then + * we perform a similar dance with the registered format handlers. + */ +struct archive_read_filter_bidder { + /* Configuration data for the bidder. */ + void *data; + /* Taste the upstream filter to see if we handle this. */ + int (*bid)(struct archive_read_filter_bidder *, + struct archive_read_filter *); + /* Initialize a newly-created filter. */ + int (*init)(struct archive_read_filter *); + /* Set an option for the filter bidder. */ + int (*options)(struct archive_read_filter_bidder *, + const char *key, const char *value); + /* Release the bidder's configuration data. */ + int (*free)(struct archive_read_filter_bidder *); +}; + +/* + * This structure is allocated within the archive_read core + * and initialized by archive_read and the init() method of the + * corresponding bidder above. + */ +struct archive_read_filter { + int64_t position; + /* Essentially all filters will need these values, so + * just declare them here. */ + struct archive_read_filter_bidder *bidder; /* My bidder. */ + struct archive_read_filter *upstream; /* Who I read from. */ + struct archive_read *archive; /* Associated archive. */ + /* Return next block. */ + ssize_t (*read)(struct archive_read_filter *, const void **); + /* Skip forward this many bytes. */ + int64_t (*skip)(struct archive_read_filter *self, int64_t request); + /* Seek to an absolute location. */ + int64_t (*seek)(struct archive_read_filter *self, int64_t offset, int whence); + /* Close (just this filter) and free(self). */ + int (*close)(struct archive_read_filter *self); + /* My private data. */ + void *data; + + const char *name; + int code; + + /* Used by reblocking logic. */ + char *buffer; + size_t buffer_size; + char *next; /* Current read location. */ + size_t avail; /* Bytes in my buffer. */ + const void *client_buff; /* Client buffer information. */ + size_t client_total; + const char *client_next; + size_t client_avail; + char end_of_file; + char closed; + char fatal; +}; + +/* + * The client looks a lot like a filter, so we just wrap it here. + * + * TODO: Make archive_read_filter and archive_read_client identical so + * that users of the library can easily register their own + * transformation filters. This will probably break the API/ABI and + * so should be deferred at least until libarchive 3.0. + */ +struct archive_read_client { + archive_open_callback *opener; + archive_read_callback *reader; + archive_skip_callback *skipper; + archive_seek_callback *seeker; + archive_close_callback *closer; + void *data; +}; + +struct archive_read { + struct archive archive; + + struct archive_entry *entry; + + /* Dev/ino of the archive being read/written. */ + int skip_file_set; + dev_t skip_file_dev; + ino_t skip_file_ino; + + /* + * Used by archive_read_data() to track blocks and copy + * data to client buffers, filling gaps with zero bytes. + */ + const char *read_data_block; + int64_t read_data_offset; + int64_t read_data_output_offset; + size_t read_data_remaining; + + /* Callbacks to open/read/write/close client archive stream. */ + struct archive_read_client client; + + /* Registered filter bidders. */ + struct archive_read_filter_bidder bidders[9]; + + /* Last filter in chain */ + struct archive_read_filter *filter; + + /* File offset of beginning of most recently-read header. */ + int64_t header_position; + + /* + * Format detection is mostly the same as compression + * detection, with one significant difference: The bidders + * use the read_ahead calls above to examine the stream rather + * than having the supervisor hand them a block of data to + * examine. + */ + + struct archive_format_descriptor { + void *data; + const char *name; + int (*bid)(struct archive_read *, int best_bid); + int (*options)(struct archive_read *, const char *key, + const char *value); + int (*read_header)(struct archive_read *, struct archive_entry *); + int (*read_data)(struct archive_read *, const void **, size_t *, int64_t *); + int (*read_data_skip)(struct archive_read *); + int (*cleanup)(struct archive_read *); + } formats[16]; + struct archive_format_descriptor *format; /* Active format. */ + + /* + * Various information needed by archive_extract. + */ + struct extract *extract; + int (*cleanup_archive_extract)(struct archive_read *); +}; + +int __archive_read_register_format(struct archive_read *a, + void *format_data, + const char *name, + int (*bid)(struct archive_read *, int), + int (*options)(struct archive_read *, const char *, const char *), + int (*read_header)(struct archive_read *, struct archive_entry *), + int (*read_data)(struct archive_read *, const void **, size_t *, int64_t *), + int (*read_data_skip)(struct archive_read *), + int (*cleanup)(struct archive_read *)); + +int __archive_read_get_bidder(struct archive_read *a, + struct archive_read_filter_bidder **bidder); + +const void *__archive_read_ahead(struct archive_read *, size_t, ssize_t *); +const void *__archive_read_filter_ahead(struct archive_read_filter *, + size_t, ssize_t *); +int64_t __archive_read_seek(struct archive_read*, int64_t, int); +int64_t __archive_read_filter_seek(struct archive_read_filter *, int64_t, int); +int64_t __archive_read_consume(struct archive_read *, int64_t); +int64_t __archive_read_filter_consume(struct archive_read_filter *, int64_t); +int __archive_read_program(struct archive_read_filter *, const char *); +#endif diff --git a/libarchive/archive_read_set_options.3 b/libarchive/archive_read_set_options.3 new file mode 100644 index 0000000..2079d2e --- /dev/null +++ b/libarchive/archive_read_set_options.3 @@ -0,0 +1,207 @@ +.\" Copyright (c) 2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd April 13, 2009 +.Dt archive_read_options 3 +.Os +.Sh NAME +.Nm archive_read_set_filter_option , +.Nm archive_read_set_format_option , +.Nm archive_read_set_option , +.Nm archive_read_set_options +.Nd functions controlling options for reading archives +.\" +.Sh SYNOPSIS +.Ft int +.Fo archive_read_set_filter_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_read_set_format_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_read_set_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_read_set_options +.Fa "struct archive *" +.Fa "const char *options" +.Fc +.Sh DESCRIPTION +These functions provide a way for libarchive clients to configure +specific read modules. +.Bl -tag -width indent +.It Xo +.Fn archive_read_set_filter_option , +.Fn archive_read_set_format_option +.Xc +Specifies an option that will be passed to currently-registered +filters (including decompression filters) or format readers. +.Pp +If +.Ar option +and +.Ar value +are both +.Dv NULL , +these functions will do nothing and +.Cm ARCHIVE_OK +will be returned. +If +.Ar option +is +.Dv NULL +but +.Ar value +is not, these functions will do nothing and +.Cm ARCHIVE_FAILED +will be returned. +.Pp +If +.Ar module +is not +.Dv NULL , +.Ar option +and +.Ar value +will be provided to the filter or reader named +.Ar module . +The return value will be that of the module. +If there is no such module, +.Cm ARCHIVE_FAILED +will be returned. +.Pp +If +.Ar module +is +.Dv NULL , +.Ar option +and +.Ar value +will be provided to every registered module. +If any module returns +.Cm ARCHIVE_FATAL , +this value will be returned immediately. +Otherwise, +.Cm ARCHIVE_OK +will be returned if any module accepts the option, and +.Cm ARCHIVE_FAILED +in all other cases. +.\" +.It Xo +.Fn archive_read_set_option +.Xc +Calls +.Fn archive_read_set_format_option , +then +.Fn archive_read_set_filter_option . +If either function returns +.Cm ARCHIVE_FATAL , +.Cm ARCHIVE_FATAL +will be returned +immediately. +Otherwise, greater of the two values will be returned. +.\" +.It Xo +.Fn archive_read_set_options +.Xc +.Ar options +is a comma-separated list of options. +If +.Ar options +is +.Dv NULL +or empty, +.Cm ARCHIVE_OK +will be returned immediately. +.Pp +Calls +.Fn archive_read_set_option +with each option in turn. +If any +.Fn archive_read_set_option +call returns +.Cm ARCHIVE_FATAL , +.Cm ARCHIVE_FATAL +will be returned immediately. +.Pp +Individual options have one of the following forms: +.Bl -tag -compact -width indent +.It Ar option=value +The option/value pair will be provided to every module. +Modules that do not accept an option with this name will ignore it. +.It Ar option +The option will be provided to every module with a value of +.Dq 1 . +.It Ar !option +The option will be provided to every module with a NULL value. +.It Ar module:option=value , Ar module:option , Ar module:!option +As above, but the corresponding option and value will be provided +only to modules whose name matches +.Ar module . +.El +.El +.\" +.Sh OPTIONS +.Bl -tag -compact -width indent +.It Format iso9660 +.Bl -tag -compact -width indent +.It Cm joliet +Support Joliet extensions. +Defaults to enabled, use +.Cm !joliet +to disable. +.It Cm rockridge +Support RockRidge extensions. +Defaults to enabled, use +.Cm !rockridge +to disable. +.El +.El +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_set_options 3 , +.Xr archive_read 3 diff --git a/libarchive/archive_read_set_options.c b/libarchive/archive_read_set_options.c new file mode 100644 index 0000000..d6a5f45 --- /dev/null +++ b/libarchive/archive_read_set_options.c @@ -0,0 +1,148 @@ +/*- + * Copyright (c) 2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive_read_private.h" +#include "archive_options_private.h" + +static int archive_set_format_option(struct archive *a, + const char *m, const char *o, const char *v); +static int archive_set_filter_option(struct archive *a, + const char *m, const char *o, const char *v); +static int archive_set_option(struct archive *a, + const char *m, const char *o, const char *v); + +int +archive_read_set_format_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_READ_MAGIC, "archive_read_set_format_option", + archive_set_format_option); +} + +int +archive_read_set_filter_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_READ_MAGIC, "archive_read_set_filter_option", + archive_set_filter_option); +} + +int +archive_read_set_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_READ_MAGIC, "archive_read_set_option", + archive_set_option); +} + +int +archive_read_set_options(struct archive *a, const char *options) +{ + return _archive_set_options(a, options, + ARCHIVE_READ_MAGIC, "archive_read_set_options", + archive_set_option); +} + +static int +archive_set_format_option(struct archive *_a, const char *m, const char *o, + const char *v) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_format_descriptor *format; + size_t i; + int r, rv = ARCHIVE_FAILED; + + for (i = 0; i < sizeof(a->formats)/sizeof(a->formats[0]); i++) { + format = &a->formats[i]; + if (format == NULL || format->options == NULL || + format->name == NULL) + /* This format does not support option. */ + continue; + if (m != NULL && strcmp(format->name, m) != 0) + continue; + + a->format = format; + r = format->options(a, o, v); + a->format = NULL; + + if (r == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + if (m != NULL) + return (r); + + if (r == ARCHIVE_OK) + rv = ARCHIVE_OK; + } + return (rv); +} + +static int +archive_set_filter_option(struct archive *_a, const char *m, const char *o, + const char *v) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter *filter; + struct archive_read_filter_bidder *bidder; + int r, rv = ARCHIVE_FAILED; + + for (filter = a->filter; filter != NULL; filter = filter->upstream) { + bidder = filter->bidder; + if (bidder == NULL) + continue; + if (bidder->options == NULL) + /* This bidder does not support option */ + continue; + if (m != NULL && strcmp(filter->name, m) != 0) + continue; + + r = bidder->options(bidder, o, v); + + if (r == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + if (m != NULL) + return (r); + + if (r == ARCHIVE_OK) + rv = ARCHIVE_OK; + } + return (rv); +} + +static int +archive_set_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_either_option(a, m, o, v, + archive_set_format_option, + archive_set_filter_option); +} diff --git a/libarchive/archive_read_support_filter_all.c b/libarchive/archive_read_support_filter_all.c new file mode 100644 index 0000000..733d862 --- /dev/null +++ b/libarchive/archive_read_support_filter_all.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 2003-2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive.h" +#include "archive_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_all(struct archive *a) +{ + return archive_read_support_filter_all(a); +} +#endif + +int +archive_read_support_filter_all(struct archive *a) +{ + archive_check_magic(a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_all"); + + /* Bzip falls back to "bunzip2" command-line */ + archive_read_support_filter_bzip2(a); + /* The decompress code doesn't use an outside library. */ + archive_read_support_filter_compress(a); + /* Gzip decompress falls back to "gunzip" command-line. */ + archive_read_support_filter_gzip(a); + /* Lzip falls back to "unlzip" command-line program. */ + archive_read_support_filter_lzip(a); + /* The LZMA file format has a very weak signature, so it + * may not be feasible to keep this here, but we'll try. + * This will come back out if there are problems. */ + /* Lzma falls back to "unlzma" command-line program. */ + archive_read_support_filter_lzma(a); + /* Xz falls back to "unxz" command-line program. */ + archive_read_support_filter_xz(a); + /* The decode code doesn't use an outside library. */ + archive_read_support_filter_uu(a); + /* The decode code doesn't use an outside library. */ + archive_read_support_filter_rpm(a); + + /* Note: We always return ARCHIVE_OK here, even if some of the + * above return ARCHIVE_WARN. The intent here is to enable + * "as much as possible." Clients who need specific + * compression should enable those individually so they can + * verify the level of support. */ + /* Clear any warning messages set by the above functions. */ + archive_clear_error(a); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_filter_bzip2.c b/libarchive/archive_read_support_filter_bzip2.c new file mode 100644 index 0000000..8d5bd1c --- /dev/null +++ b/libarchive/archive_read_support_filter_bzip2.c @@ -0,0 +1,370 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_BZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) +struct private_data { + bz_stream stream; + char *out_block; + size_t out_block_size; + char valid; /* True = decompressor is initialized */ + char eof; /* True = found end of compressed data. */ +}; + +/* Bzip2 filter */ +static ssize_t bzip2_filter_read(struct archive_read_filter *, const void **); +static int bzip2_filter_close(struct archive_read_filter *); +#endif + +/* + * Note that we can detect bzip2 archives even if we can't decompress + * them. (In fact, we like detecting them because we can give better + * error messages.) So the bid framework here gets compiled even + * if bzlib is unavailable. + */ +static int bzip2_reader_bid(struct archive_read_filter_bidder *, struct archive_read_filter *); +static int bzip2_reader_init(struct archive_read_filter *); +static int bzip2_reader_free(struct archive_read_filter_bidder *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_bzip2(struct archive *a) +{ + return archive_read_support_filter_bzip2(a); +} +#endif + +int +archive_read_support_filter_bzip2(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *reader; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_bzip2"); + + if (__archive_read_get_bidder(a, &reader) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + reader->data = NULL; + reader->bid = bzip2_reader_bid; + reader->init = bzip2_reader_init; + reader->options = NULL; + reader->free = bzip2_reader_free; +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + return (ARCHIVE_OK); +#else + archive_set_error(_a, ARCHIVE_ERRNO_MISC, + "Using external bunzip2 program"); + return (ARCHIVE_WARN); +#endif +} + +static int +bzip2_reader_free(struct archive_read_filter_bidder *self){ + (void)self; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* + * Test whether we can handle this data. + * + * This logic returns zero if any part of the signature fails. It + * also tries to Do The Right Thing if a very short buffer prevents us + * from verifying as much as we would like. + */ +static int +bzip2_reader_bid(struct archive_read_filter_bidder *self, struct archive_read_filter *filter) +{ + const unsigned char *buffer; + ssize_t avail; + int bits_checked; + + (void)self; /* UNUSED */ + + /* Minimal bzip2 archive is 14 bytes. */ + buffer = __archive_read_filter_ahead(filter, 14, &avail); + if (buffer == NULL) + return (0); + + /* First three bytes must be "BZh" */ + bits_checked = 0; + if (memcmp(buffer, "BZh", 3) != 0) + return (0); + bits_checked += 24; + + /* Next follows a compression flag which must be an ASCII digit. */ + if (buffer[3] < '1' || buffer[3] > '9') + return (0); + bits_checked += 5; + + /* After BZh[1-9], there must be either a data block + * which begins with 0x314159265359 or an end-of-data + * marker of 0x177245385090. */ + if (memcmp(buffer + 4, "\x31\x41\x59\x26\x53\x59", 6) == 0) + bits_checked += 48; + else if (memcmp(buffer + 4, "\x17\x72\x45\x38\x50\x90", 6) == 0) + bits_checked += 48; + else + return (0); + + return (bits_checked); +} + +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) + +/* + * If we don't have the library on this system, we can't actually do the + * decompression. We can, however, still detect compressed archives + * and emit a useful message. + */ +static int +bzip2_reader_init(struct archive_read_filter *self) +{ + int r; + + r = __archive_read_program(self, "bunzip2"); + /* Note: We set the format here even if __archive_read_program() + * above fails. We do, after all, know what the format is + * even if we weren't able to read it. */ + self->code = ARCHIVE_COMPRESSION_BZIP2; + self->name = "bzip2"; + return (r); +} + + +#else + +/* + * Setup the callbacks. + */ +static int +bzip2_reader_init(struct archive_read_filter *self) +{ + static const size_t out_block_size = 64 * 1024; + void *out_block; + struct private_data *state; + + self->code = ARCHIVE_COMPRESSION_BZIP2; + self->name = "bzip2"; + + state = (struct private_data *)calloc(sizeof(*state), 1); + out_block = (unsigned char *)malloc(out_block_size); + if (state == NULL || out_block == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for bzip2 decompression"); + free(out_block); + free(state); + return (ARCHIVE_FATAL); + } + + self->data = state; + state->out_block_size = out_block_size; + state->out_block = out_block; + self->read = bzip2_filter_read; + self->skip = NULL; /* not supported */ + self->close = bzip2_filter_close; + + return (ARCHIVE_OK); +} + +/* + * Return the next block of decompressed data. + */ +static ssize_t +bzip2_filter_read(struct archive_read_filter *self, const void **p) +{ + struct private_data *state; + size_t decompressed; + const char *read_buf; + ssize_t ret; + + state = (struct private_data *)self->data; + + if (state->eof) { + *p = NULL; + return (0); + } + + /* Empty our output buffer. */ + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + /* Try to fill the output buffer. */ + for (;;) { + if (!state->valid) { + if (bzip2_reader_bid(self->bidder, self->upstream) == 0) { + state->eof = 1; + *p = state->out_block; + decompressed = state->stream.next_out + - state->out_block; + return (decompressed); + } + /* Initialize compression library. */ + ret = BZ2_bzDecompressInit(&(state->stream), + 0 /* library verbosity */, + 0 /* don't use low-mem algorithm */); + + /* If init fails, try low-memory algorithm instead. */ + if (ret == BZ_MEM_ERROR) + ret = BZ2_bzDecompressInit(&(state->stream), + 0 /* library verbosity */, + 1 /* do use low-mem algo */); + + if (ret != BZ_OK) { + const char *detail = NULL; + int err = ARCHIVE_ERRNO_MISC; + switch (ret) { + case BZ_PARAM_ERROR: + detail = "invalid setup parameter"; + break; + case BZ_MEM_ERROR: + err = ENOMEM; + detail = "out of memory"; + break; + case BZ_CONFIG_ERROR: + detail = "mis-compiled library"; + break; + } + archive_set_error(&self->archive->archive, err, + "Internal error initializing decompressor%s%s", + detail == NULL ? "" : ": ", + detail); + return (ARCHIVE_FATAL); + } + state->valid = 1; + } + + /* stream.next_in is really const, but bzlib + * doesn't declare it so. */ + read_buf = + __archive_read_filter_ahead(self->upstream, 1, &ret); + if (read_buf == NULL) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "truncated bzip2 input"); + return (ARCHIVE_FATAL); + } + state->stream.next_in = (char *)(uintptr_t)read_buf; + state->stream.avail_in = ret; + /* There is no more data, return whatever we have. */ + if (ret == 0) { + state->eof = 1; + *p = state->out_block; + decompressed = state->stream.next_out + - state->out_block; + return (decompressed); + } + + /* Decompress as much as we can in one pass. */ + ret = BZ2_bzDecompress(&(state->stream)); + __archive_read_filter_consume(self->upstream, + state->stream.next_in - read_buf); + + switch (ret) { + case BZ_STREAM_END: /* Found end of stream. */ + switch (BZ2_bzDecompressEnd(&(state->stream))) { + case BZ_OK: + break; + default: + archive_set_error(&(self->archive->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up decompressor"); + return (ARCHIVE_FATAL); + } + state->valid = 0; + /* FALLTHROUGH */ + case BZ_OK: /* Decompressor made some progress. */ + /* If we filled our buffer, update stats and return. */ + if (state->stream.avail_out == 0) { + *p = state->out_block; + decompressed = state->stream.next_out + - state->out_block; + return (decompressed); + } + break; + default: /* Return an error. */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, "bzip decompression failed"); + return (ARCHIVE_FATAL); + } + } +} + +/* + * Clean up the decompressor. + */ +static int +bzip2_filter_close(struct archive_read_filter *self) +{ + struct private_data *state; + int ret = ARCHIVE_OK; + + state = (struct private_data *)self->data; + + if (state->valid) { + switch (BZ2_bzDecompressEnd(&state->stream)) { + case BZ_OK: + break; + default: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up decompressor"); + ret = ARCHIVE_FATAL; + } + state->valid = 0; + } + + free(state->out_block); + free(state); + return (ret); +} + +#endif /* HAVE_BZLIB_H && BZ_CONFIG_ERROR */ diff --git a/libarchive/archive_read_support_filter_compress.c b/libarchive/archive_read_support_filter_compress.c new file mode 100644 index 0000000..1b85300 --- /dev/null +++ b/libarchive/archive_read_support_filter_compress.c @@ -0,0 +1,454 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code borrows heavily from "compress" source code, which is + * protected by the following copyright. (Clause 3 dropped by request + * of the Regents.) + */ + +/*- + * Copyright (c) 1985, 1986, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis and James A. Woods, derived from original + * work by Spencer Thomas and Joseph Orost. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +/* + * Because LZW decompression is pretty simple, I've just implemented + * the whole decompressor here (cribbing from "compress" source code, + * of course), rather than relying on an external library. I have + * made an effort to clarify and simplify the algorithm, so the + * names and structure here don't exactly match those used by compress. + */ + +struct private_data { + /* Input variables. */ + const unsigned char *next_in; + size_t avail_in; + size_t consume_unnotified; + int bit_buffer; + int bits_avail; + size_t bytes_in_section; + + /* Output variables. */ + size_t out_block_size; + void *out_block; + + /* Decompression status variables. */ + int use_reset_code; + int end_of_stream; /* EOF status. */ + int maxcode; /* Largest code. */ + int maxcode_bits; /* Length of largest code. */ + int section_end_code; /* When to increase bits. */ + int bits; /* Current code length. */ + int oldcode; /* Previous code. */ + int finbyte; /* Last byte of prev code. */ + + /* Dictionary. */ + int free_ent; /* Next dictionary entry. */ + unsigned char suffix[65536]; + uint16_t prefix[65536]; + + /* + * Scratch area for expanding dictionary entries. Note: + * "worst" case here comes from compressing /dev/zero: the + * last code in the dictionary will code a sequence of + * 65536-256 zero bytes. Thus, we need stack space to expand + * a 65280-byte dictionary entry. (Of course, 32640:1 + * compression could also be considered the "best" case. ;-) + */ + unsigned char *stackp; + unsigned char stack[65300]; +}; + +static int compress_bidder_bid(struct archive_read_filter_bidder *, struct archive_read_filter *); +static int compress_bidder_init(struct archive_read_filter *); +static int compress_bidder_free(struct archive_read_filter_bidder *); + +static ssize_t compress_filter_read(struct archive_read_filter *, const void **); +static int compress_filter_close(struct archive_read_filter *); + +static int getbits(struct archive_read_filter *, int n); +static int next_code(struct archive_read_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_compress(struct archive *a) +{ + return archive_read_support_filter_compress(a); +} +#endif + +int +archive_read_support_filter_compress(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_compress"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = compress_bidder_bid; + bidder->init = compress_bidder_init; + bidder->options = NULL; + bidder->free = compress_bidder_free; + return (ARCHIVE_OK); +} + +/* + * Test whether we can handle this data. + * This logic returns zero if any part of the signature fails. + */ +static int +compress_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *buffer; + ssize_t avail; + int bits_checked; + + (void)self; /* UNUSED */ + + buffer = __archive_read_filter_ahead(filter, 2, &avail); + + if (buffer == NULL) + return (0); + + bits_checked = 0; + if (buffer[0] != 0x1F || buffer[1] != 0x9D) + return (0); + bits_checked += 16; + + /* + * TODO: Verify more. + */ + + return (bits_checked); +} + +/* + * Setup the callbacks. + */ +static int +compress_bidder_init(struct archive_read_filter *self) +{ + struct private_data *state; + static const size_t out_block_size = 64 * 1024; + void *out_block; + int code; + + self->code = ARCHIVE_COMPRESSION_COMPRESS; + self->name = "compress (.Z)"; + + state = (struct private_data *)calloc(sizeof(*state), 1); + out_block = malloc(out_block_size); + if (state == NULL || out_block == NULL) { + free(out_block); + free(state); + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for %s decompression", + self->name); + return (ARCHIVE_FATAL); + } + + self->data = state; + state->out_block_size = out_block_size; + state->out_block = out_block; + self->read = compress_filter_read; + self->skip = NULL; /* not supported */ + self->close = compress_filter_close; + + /* XXX MOVE THE FOLLOWING OUT OF INIT() XXX */ + + (void)getbits(self, 8); /* Skip first signature byte. */ + (void)getbits(self, 8); /* Skip second signature byte. */ + + code = getbits(self, 8); + state->maxcode_bits = code & 0x1f; + state->maxcode = (1 << state->maxcode_bits); + state->use_reset_code = code & 0x80; + + /* Initialize decompressor. */ + state->free_ent = 256; + state->stackp = state->stack; + if (state->use_reset_code) + state->free_ent++; + state->bits = 9; + state->section_end_code = (1<bits) - 1; + state->oldcode = -1; + for (code = 255; code >= 0; code--) { + state->prefix[code] = 0; + state->suffix[code] = code; + } + next_code(self); + + return (ARCHIVE_OK); +} + +/* + * Return a block of data from the decompression buffer. Decompress more + * as necessary. + */ +static ssize_t +compress_filter_read(struct archive_read_filter *self, const void **pblock) +{ + struct private_data *state; + unsigned char *p, *start, *end; + int ret; + + state = (struct private_data *)self->data; + if (state->end_of_stream) { + *pblock = NULL; + return (0); + } + p = start = (unsigned char *)state->out_block; + end = start + state->out_block_size; + + while (p < end && !state->end_of_stream) { + if (state->stackp > state->stack) { + *p++ = *--state->stackp; + } else { + ret = next_code(self); + if (ret == -1) + state->end_of_stream = ret; + else if (ret != ARCHIVE_OK) + return (ret); + } + } + + *pblock = start; + return (p - start); +} + +/* + * Clean up the reader. + */ +static int +compress_bidder_free(struct archive_read_filter_bidder *self) +{ + self->data = NULL; + return (ARCHIVE_OK); +} + +/* + * Close and release the filter. + */ +static int +compress_filter_close(struct archive_read_filter *self) +{ + struct private_data *state = (struct private_data *)self->data; + + free(state->out_block); + free(state); + return (ARCHIVE_OK); +} + +/* + * Process the next code and fill the stack with the expansion + * of the code. Returns ARCHIVE_FATAL if there is a fatal I/O or + * format error, ARCHIVE_EOF if we hit end of data, ARCHIVE_OK otherwise. + */ +static int +next_code(struct archive_read_filter *self) +{ + struct private_data *state = (struct private_data *)self->data; + int code, newcode; + + static int debug_buff[1024]; + static unsigned debug_index; + + code = newcode = getbits(self, state->bits); + if (code < 0) + return (code); + + debug_buff[debug_index++] = code; + if (debug_index >= sizeof(debug_buff)/sizeof(debug_buff[0])) + debug_index = 0; + + /* If it's a reset code, reset the dictionary. */ + if ((code == 256) && state->use_reset_code) { + /* + * The original 'compress' implementation blocked its + * I/O in a manner that resulted in junk bytes being + * inserted after every reset. The next section skips + * this junk. (Yes, the number of *bytes* to skip is + * a function of the current *bit* length.) + */ + int skip_bytes = state->bits - + (state->bytes_in_section % state->bits); + skip_bytes %= state->bits; + state->bits_avail = 0; /* Discard rest of this byte. */ + while (skip_bytes-- > 0) { + code = getbits(self, 8); + if (code < 0) + return (code); + } + /* Now, actually do the reset. */ + state->bytes_in_section = 0; + state->bits = 9; + state->section_end_code = (1 << state->bits) - 1; + state->free_ent = 257; + state->oldcode = -1; + return (next_code(self)); + } + + if (code > state->free_ent) { + /* An invalid code is a fatal error. */ + archive_set_error(&(self->archive->archive), -1, + "Invalid compressed data"); + return (ARCHIVE_FATAL); + } + + /* Special case for KwKwK string. */ + if (code >= state->free_ent) { + *state->stackp++ = state->finbyte; + code = state->oldcode; + } + + /* Generate output characters in reverse order. */ + while (code >= 256) { + *state->stackp++ = state->suffix[code]; + code = state->prefix[code]; + } + *state->stackp++ = state->finbyte = code; + + /* Generate the new entry. */ + code = state->free_ent; + if (code < state->maxcode && state->oldcode >= 0) { + state->prefix[code] = state->oldcode; + state->suffix[code] = state->finbyte; + ++state->free_ent; + } + if (state->free_ent > state->section_end_code) { + state->bits++; + state->bytes_in_section = 0; + if (state->bits == state->maxcode_bits) + state->section_end_code = state->maxcode; + else + state->section_end_code = (1 << state->bits) - 1; + } + + /* Remember previous code. */ + state->oldcode = newcode; + return (ARCHIVE_OK); +} + +/* + * Return next 'n' bits from stream. + * + * -1 indicates end of available data. + */ +static int +getbits(struct archive_read_filter *self, int n) +{ + struct private_data *state = (struct private_data *)self->data; + int code; + ssize_t ret; + static const int mask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff, + 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff + }; + + while (state->bits_avail < n) { + if (state->avail_in <= 0) { + if (state->consume_unnotified) { + __archive_read_filter_consume(self->upstream, + state->consume_unnotified); + state->consume_unnotified = 0; + } + state->next_in + = __archive_read_filter_ahead(self->upstream, + 1, &ret); + if (ret == 0) + return (-1); + if (ret < 0 || state->next_in == NULL) + return (ARCHIVE_FATAL); + state->consume_unnotified = state->avail_in = ret; + } + state->bit_buffer |= *state->next_in++ << state->bits_avail; + state->avail_in--; + state->bits_avail += 8; + state->bytes_in_section++; + } + + code = state->bit_buffer; + state->bit_buffer >>= n; + state->bits_avail -= n; + + return (code & mask[n]); +} diff --git a/libarchive/archive_read_support_filter_gzip.c b/libarchive/archive_read_support_filter_gzip.c new file mode 100644 index 0000000..f6d5595 --- /dev/null +++ b/libarchive/archive_read_support_filter_gzip.c @@ -0,0 +1,476 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD$"); + + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#ifdef HAVE_ZLIB_H +struct private_data { + z_stream stream; + char in_stream; + unsigned char *out_block; + size_t out_block_size; + int64_t total_out; + unsigned long crc; + char eof; /* True = found end of compressed data. */ +}; + +/* Gzip Filter. */ +static ssize_t gzip_filter_read(struct archive_read_filter *, const void **); +static int gzip_filter_close(struct archive_read_filter *); +#endif + +/* + * Note that we can detect gzip archives even if we can't decompress + * them. (In fact, we like detecting them because we can give better + * error messages.) So the bid framework here gets compiled even + * if zlib is unavailable. + * + * TODO: If zlib is unavailable, gzip_bidder_init() should + * use the compress_program framework to try to fire up an external + * gunzip program. + */ +static int gzip_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int gzip_bidder_init(struct archive_read_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_gzip(struct archive *a) +{ + return archive_read_support_filter_gzip(a); +} +#endif + +int +archive_read_support_filter_gzip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_gzip"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = gzip_bidder_bid; + bidder->init = gzip_bidder_init; + bidder->options = NULL; + bidder->free = NULL; /* No data, so no cleanup necessary. */ + /* Signal the extent of gzip support with the return value here. */ +#if HAVE_ZLIB_H + return (ARCHIVE_OK); +#else + archive_set_error(_a, ARCHIVE_ERRNO_MISC, + "Using external gunzip program"); + return (ARCHIVE_WARN); +#endif +} + +/* + * Read and verify the header. + * + * Returns zero if the header couldn't be validated, else returns + * number of bytes in header. If pbits is non-NULL, it receives a + * count of bits verified, suitable for use by bidder. + */ +static int +peek_at_header(struct archive_read_filter *filter, int *pbits) +{ + const unsigned char *p; + ssize_t avail, len; + int bits = 0; + int header_flags; + + /* Start by looking at the first ten bytes of the header, which + * is all fixed layout. */ + len = 10; + p = __archive_read_filter_ahead(filter, len, &avail); + if (p == NULL || avail == 0) + return (0); + /* We only support deflation- third byte must be 0x08. */ + if (memcmp(p, "\x1F\x8B\x08", 3) != 0) + return (0); + bits += 24; + if ((p[3] & 0xE0)!= 0) /* No reserved flags set. */ + return (0); + bits += 3; + header_flags = p[3]; + /* Bytes 4-7 are mod time. */ + /* Byte 8 is deflate flags. */ + /* XXXX TODO: return deflate flags back to consume_header for use + in initializing the decompressor. */ + /* Byte 9 is OS. */ + + /* Optional extra data: 2 byte length plus variable body. */ + if (header_flags & 4) { + p = __archive_read_filter_ahead(filter, len + 2, &avail); + if (p == NULL) + return (0); + len += ((int)p[len + 1] << 8) | (int)p[len]; + len += 2; + } + + /* Null-terminated optional filename. */ + if (header_flags & 8) { + do { + ++len; + if (avail < len) + p = __archive_read_filter_ahead(filter, + len, &avail); + if (p == NULL) + return (0); + } while (p[len - 1] != 0); + } + + /* Null-terminated optional comment. */ + if (header_flags & 16) { + do { + ++len; + if (avail < len) + p = __archive_read_filter_ahead(filter, + len, &avail); + if (p == NULL) + return (0); + } while (p[len - 1] != 0); + } + + /* Optional header CRC */ + if ((header_flags & 2)) { + p = __archive_read_filter_ahead(filter, len + 2, &avail); + if (p == NULL) + return (0); +#if 0 + int hcrc = ((int)p[len + 1] << 8) | (int)p[len]; + int crc = /* XXX TODO: Compute header CRC. */; + if (crc != hcrc) + return (0); + bits += 16; +#endif + len += 2; + } + + if (pbits != NULL) + *pbits = bits; + return (len); +} + +/* + * Bidder just verifies the header and returns the number of verified bits. + */ +static int +gzip_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + int bits_checked; + + (void)self; /* UNUSED */ + + if (peek_at_header(filter, &bits_checked)) + return (bits_checked); + return (0); +} + + +#ifndef HAVE_ZLIB_H + +/* + * If we don't have the library on this system, we can't do the + * decompression directly. We can, however, try to run gunzip + * in case that's available. + */ +static int +gzip_bidder_init(struct archive_read_filter *self) +{ + int r; + + r = __archive_read_program(self, "gunzip"); + /* Note: We set the format here even if __archive_read_program() + * above fails. We do, after all, know what the format is + * even if we weren't able to read it. */ + self->code = ARCHIVE_COMPRESSION_GZIP; + self->name = "gzip"; + return (r); +} + +#else + +/* + * Initialize the filter object. + */ +static int +gzip_bidder_init(struct archive_read_filter *self) +{ + struct private_data *state; + static const size_t out_block_size = 64 * 1024; + void *out_block; + + self->code = ARCHIVE_COMPRESSION_GZIP; + self->name = "gzip"; + + state = (struct private_data *)calloc(sizeof(*state), 1); + out_block = (unsigned char *)malloc(out_block_size); + if (state == NULL || out_block == NULL) { + free(out_block); + free(state); + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for gzip decompression"); + return (ARCHIVE_FATAL); + } + + self->data = state; + state->out_block_size = out_block_size; + state->out_block = out_block; + self->read = gzip_filter_read; + self->skip = NULL; /* not supported */ + self->close = gzip_filter_close; + + state->in_stream = 0; /* We're not actually within a stream yet. */ + + return (ARCHIVE_OK); +} + +static int +consume_header(struct archive_read_filter *self) +{ + struct private_data *state; + ssize_t avail; + size_t len; + int ret; + + state = (struct private_data *)self->data; + + /* If this is a real header, consume it. */ + len = peek_at_header(self->upstream, NULL); + if (len == 0) + return (ARCHIVE_EOF); + __archive_read_filter_consume(self->upstream, len); + + /* Initialize CRC accumulator. */ + state->crc = crc32(0L, NULL, 0); + + /* Initialize compression library. */ + state->stream.next_in = (unsigned char *)(uintptr_t) + __archive_read_filter_ahead(self->upstream, 1, &avail); + state->stream.avail_in = avail; + ret = inflateInit2(&(state->stream), + -15 /* Don't check for zlib header */); + + /* Decipher the error code. */ + switch (ret) { + case Z_OK: + state->in_stream = 1; + return (ARCHIVE_OK); + case Z_STREAM_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid setup parameter"); + break; + case Z_MEM_ERROR: + archive_set_error(&self->archive->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + case Z_VERSION_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid library version"); + break; + default: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + " Zlib error %d", ret); + break; + } + return (ARCHIVE_FATAL); +} + +static int +consume_trailer(struct archive_read_filter *self) +{ + struct private_data *state; + const unsigned char *p; + ssize_t avail; + + state = (struct private_data *)self->data; + + state->in_stream = 0; + switch (inflateEnd(&(state->stream))) { + case Z_OK: + break; + default: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up gzip decompressor"); + return (ARCHIVE_FATAL); + } + + /* GZip trailer is a fixed 8 byte structure. */ + p = __archive_read_filter_ahead(self->upstream, 8, &avail); + if (p == NULL || avail == 0) + return (ARCHIVE_FATAL); + + /* XXX TODO: Verify the length and CRC. */ + + /* We've verified the trailer, so consume it now. */ + __archive_read_filter_consume(self->upstream, 8); + + return (ARCHIVE_OK); +} + +static ssize_t +gzip_filter_read(struct archive_read_filter *self, const void **p) +{ + struct private_data *state; + size_t decompressed; + ssize_t avail_in; + int ret; + + state = (struct private_data *)self->data; + + /* Empty our output buffer. */ + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + /* Try to fill the output buffer. */ + while (state->stream.avail_out > 0 && !state->eof) { + /* If we're not in a stream, read a header + * and initialize the decompression library. */ + if (!state->in_stream) { + ret = consume_header(self); + if (ret == ARCHIVE_EOF) { + state->eof = 1; + break; + } + if (ret < ARCHIVE_OK) + return (ret); + } + + /* Peek at the next available data. */ + /* ZLib treats stream.next_in as const but doesn't declare + * it so, hence this ugly cast. */ + state->stream.next_in = (unsigned char *)(uintptr_t) + __archive_read_filter_ahead(self->upstream, 1, &avail_in); + if (state->stream.next_in == NULL) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "truncated gzip input"); + return (ARCHIVE_FATAL); + } + state->stream.avail_in = avail_in; + + /* Decompress and consume some of that data. */ + ret = inflate(&(state->stream), 0); + switch (ret) { + case Z_OK: /* Decompressor made some progress. */ + __archive_read_filter_consume(self->upstream, + avail_in - state->stream.avail_in); + break; + case Z_STREAM_END: /* Found end of stream. */ + __archive_read_filter_consume(self->upstream, + avail_in - state->stream.avail_in); + /* Consume the stream trailer; release the + * decompression library. */ + ret = consume_trailer(self); + if (ret < ARCHIVE_OK) + return (ret); + break; + default: + /* Return an error. */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "gzip decompression failed"); + return (ARCHIVE_FATAL); + } + } + + /* We've read as much as we can. */ + decompressed = state->stream.next_out - state->out_block; + state->total_out += decompressed; + if (decompressed == 0) + *p = NULL; + else + *p = state->out_block; + return (decompressed); +} + +/* + * Clean up the decompressor. + */ +static int +gzip_filter_close(struct archive_read_filter *self) +{ + struct private_data *state; + int ret; + + state = (struct private_data *)self->data; + ret = ARCHIVE_OK; + + if (state->in_stream) { + switch (inflateEnd(&(state->stream))) { + case Z_OK: + break; + default: + archive_set_error(&(self->archive->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up gzip compressor"); + ret = ARCHIVE_FATAL; + } + } + + free(state->out_block); + free(state); + return (ret); +} + +#endif /* HAVE_ZLIB_H */ diff --git a/libarchive/archive_read_support_filter_none.c b/libarchive/archive_read_support_filter_none.c new file mode 100644 index 0000000..95e5cfd --- /dev/null +++ b/libarchive/archive_read_support_filter_none.c @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive.h" +#include "archive_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_none(struct archive *a) +{ + return archive_read_support_filter_none(a); +} +#endif + +/* + * Uncompressed streams are handled implicitly by the read core, + * so this is now a no-op. + */ +int +archive_read_support_filter_none(struct archive *a) +{ + archive_check_magic(a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_none"); + + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_filter_program.c b/libarchive/archive_read_support_filter_program.c new file mode 100644 index 0000000..b05eb03 --- /dev/null +++ b/libarchive/archive_read_support_filter_program.c @@ -0,0 +1,476 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_SYS_WAIT_H +# include +#endif +#ifdef HAVE_ERRNO_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_LIMITS_H +# include +#endif +#ifdef HAVE_SIGNAL_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_program(struct archive *a, const char *cmd) +{ + return archive_read_support_filter_program(a, cmd); +} + +int +archive_read_support_compression_program_signature(struct archive *a, + const char *cmd, const void *signature, size_t signature_len) +{ + return archive_read_support_filter_program_signature(a, + cmd, signature, signature_len); +} +#endif + +int +archive_read_support_filter_program(struct archive *a, const char *cmd) +{ + return (archive_read_support_filter_program_signature(a, cmd, NULL, 0)); +} + + +/* This capability is only available on POSIX systems. */ +#if (!defined(HAVE_PIPE) || !defined(HAVE_FCNTL) || \ + !(defined(HAVE_FORK) || defined(HAVE_VFORK))) && (!defined(_WIN32) || defined(__CYGWIN__)) + +/* + * On non-Posix systems, allow the program to build, but choke if + * this function is actually invoked. + */ +int +archive_read_support_filter_program_signature(struct archive *_a, + const char *cmd, const void *signature, size_t signature_len) +{ + (void)_a; /* UNUSED */ + (void)cmd; /* UNUSED */ + (void)signature; /* UNUSED */ + (void)signature_len; /* UNUSED */ + + archive_set_error(_a, -1, + "External compression programs not supported on this platform"); + return (ARCHIVE_FATAL); +} + +int +__archive_read_program(struct archive_read_filter *self, const char *cmd) +{ + (void)self; /* UNUSED */ + (void)cmd; /* UNUSED */ + + archive_set_error(&self->archive->archive, -1, + "External compression programs not supported on this platform"); + return (ARCHIVE_FATAL); +} + +#else + +#include "filter_fork.h" + +/* + * The bidder object stores the command and the signature to watch for. + * The 'inhibit' entry here is used to ensure that unchecked filters never + * bid twice in the same pipeline. + */ +struct program_bidder { + char *cmd; + void *signature; + size_t signature_len; + int inhibit; +}; + +static int program_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *upstream); +static int program_bidder_init(struct archive_read_filter *); +static int program_bidder_free(struct archive_read_filter_bidder *); + +/* + * The actual filter needs to track input and output data. + */ +struct program_filter { + char *description; + pid_t child; + int exit_status; + int waitpid_return; + int child_stdin, child_stdout; + + char *out_buf; + size_t out_buf_len; +}; + +static ssize_t program_filter_read(struct archive_read_filter *, + const void **); +static int program_filter_close(struct archive_read_filter *); + +int +archive_read_support_filter_program_signature(struct archive *_a, + const char *cmd, const void *signature, size_t signature_len) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + struct program_bidder *state; + + /* + * Get a bidder object from the read core. + */ + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* + * Allocate our private state. + */ + state = (struct program_bidder *)calloc(sizeof (*state), 1); + if (state == NULL) + return (ARCHIVE_FATAL); + state->cmd = strdup(cmd); + if (signature != NULL && signature_len > 0) { + state->signature_len = signature_len; + state->signature = malloc(signature_len); + memcpy(state->signature, signature, signature_len); + } + + /* + * Fill in the bidder object. + */ + bidder->data = state; + bidder->bid = program_bidder_bid; + bidder->init = program_bidder_init; + bidder->options = NULL; + bidder->free = program_bidder_free; + return (ARCHIVE_OK); +} + +static int +program_bidder_free(struct archive_read_filter_bidder *self) +{ + struct program_bidder *state = (struct program_bidder *)self->data; + free(state->cmd); + free(state->signature); + free(self->data); + return (ARCHIVE_OK); +} + +/* + * If we do have a signature, bid only if that matches. + * + * If there's no signature, we bid INT_MAX the first time + * we're called, then never bid again. + */ +static int +program_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *upstream) +{ + struct program_bidder *state = self->data; + const char *p; + + /* If we have a signature, use that to match. */ + if (state->signature_len > 0) { + p = __archive_read_filter_ahead(upstream, + state->signature_len, NULL); + if (p == NULL) + return (0); + /* No match, so don't bid. */ + if (memcmp(p, state->signature, state->signature_len) != 0) + return (0); + return ((int)state->signature_len * 8); + } + + /* Otherwise, bid once and then never bid again. */ + if (state->inhibit) + return (0); + state->inhibit = 1; + return (INT_MAX); +} + +/* + * Shut down the child, return ARCHIVE_OK if it exited normally. + * + * Note that the return value is sticky; if we're called again, + * we won't reap the child again, but we will return the same status + * (including error message if the child came to a bad end). + */ +static int +child_stop(struct archive_read_filter *self, struct program_filter *state) +{ + /* Close our side of the I/O with the child. */ + if (state->child_stdin != -1) { + close(state->child_stdin); + state->child_stdin = -1; + } + if (state->child_stdout != -1) { + close(state->child_stdout); + state->child_stdout = -1; + } + + if (state->child != 0) { + /* Reap the child. */ + do { + state->waitpid_return + = waitpid(state->child, &state->exit_status, 0); + } while (state->waitpid_return == -1 && errno == EINTR); + state->child = 0; + } + + if (state->waitpid_return < 0) { + /* waitpid() failed? This is ugly. */ + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Child process exited badly"); + return (ARCHIVE_WARN); + } + +#if !defined(_WIN32) || defined(__CYGWIN__) + if (WIFSIGNALED(state->exit_status)) { +#ifdef SIGPIPE + /* If the child died because we stopped reading before + * it was done, that's okay. Some archive formats + * have padding at the end that we routinely ignore. */ + /* The alternative to this would be to add a step + * before close(child_stdout) above to read from the + * child until the child has no more to write. */ + if (WTERMSIG(state->exit_status) == SIGPIPE) + return (ARCHIVE_OK); +#endif + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Child process exited with signal %d", + WTERMSIG(state->exit_status)); + return (ARCHIVE_WARN); + } +#endif /* !_WIN32 || __CYGWIN__ */ + + if (WIFEXITED(state->exit_status)) { + if (WEXITSTATUS(state->exit_status) == 0) + return (ARCHIVE_OK); + + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Child process exited with status %d", + WEXITSTATUS(state->exit_status)); + return (ARCHIVE_WARN); + } + + return (ARCHIVE_WARN); +} + +/* + * Use select() to decide whether the child is ready for read or write. + */ +static ssize_t +child_read(struct archive_read_filter *self, char *buf, size_t buf_len) +{ + struct program_filter *state = self->data; + ssize_t ret, requested, avail; + const char *p; + + requested = buf_len > SSIZE_MAX ? SSIZE_MAX : buf_len; + + for (;;) { + do { + ret = read(state->child_stdout, buf, requested); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) + return (ret); + if (ret == 0 || (ret == -1 && errno == EPIPE)) + /* Child has closed its output; reap the child + * and return the status. */ + return (child_stop(self, state)); + if (ret == -1 && errno != EAGAIN) + return (-1); + + if (state->child_stdin == -1) { + /* Block until child has some I/O ready. */ + __archive_check_child(state->child_stdin, + state->child_stdout); + continue; + } + + /* Get some more data from upstream. */ + p = __archive_read_filter_ahead(self->upstream, 1, &avail); + if (p == NULL) { + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + if (avail < 0) + return (avail); + continue; + } + + do { + ret = write(state->child_stdin, p, avail); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) { + /* Consume whatever we managed to write. */ + __archive_read_filter_consume(self->upstream, ret); + } else if (ret == -1 && errno == EAGAIN) { + /* Block until child has some I/O ready. */ + __archive_check_child(state->child_stdin, + state->child_stdout); + } else { + /* Write failed. */ + close(state->child_stdin); + state->child_stdin = -1; + fcntl(state->child_stdout, F_SETFL, 0); + /* If it was a bad error, we're done; otherwise + * it was EPIPE or EOF, and we can still read + * from the child. */ + if (ret == -1 && errno != EPIPE) + return (-1); + } + } +} + +int +__archive_read_program(struct archive_read_filter *self, const char *cmd) +{ + struct program_filter *state; + static const size_t out_buf_len = 65536; + char *out_buf; + char *description; + const char *prefix = "Program: "; + + state = (struct program_filter *)calloc(1, sizeof(*state)); + out_buf = (char *)malloc(out_buf_len); + description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1); + if (state == NULL || out_buf == NULL || description == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate input data"); + free(state); + free(out_buf); + free(description); + return (ARCHIVE_FATAL); + } + + self->code = ARCHIVE_COMPRESSION_PROGRAM; + state->description = description; + strcpy(state->description, prefix); + strcat(state->description, cmd); + self->name = state->description; + + state->out_buf = out_buf; + state->out_buf_len = out_buf_len; + + if ((state->child = __archive_create_child(cmd, + &state->child_stdin, &state->child_stdout)) == -1) { + free(state->out_buf); + free(state); + archive_set_error(&self->archive->archive, EINVAL, + "Can't initialize filter; unable to run program \"%s\"", cmd); + return (ARCHIVE_FATAL); + } + + self->data = state; + self->read = program_filter_read; + self->skip = NULL; + self->close = program_filter_close; + + /* XXX Check that we can read at least one byte? */ + return (ARCHIVE_OK); +} + +static int +program_bidder_init(struct archive_read_filter *self) +{ + struct program_bidder *bidder_state; + + bidder_state = (struct program_bidder *)self->bidder->data; + return (__archive_read_program(self, bidder_state->cmd)); +} + +static ssize_t +program_filter_read(struct archive_read_filter *self, const void **buff) +{ + struct program_filter *state; + ssize_t bytes; + size_t total; + char *p; + + state = (struct program_filter *)self->data; + + total = 0; + p = state->out_buf; + while (state->child_stdout != -1 && total < state->out_buf_len) { + bytes = child_read(self, p, state->out_buf_len - total); + if (bytes < 0) + /* No recovery is possible if we can no longer + * read from the child. */ + return (ARCHIVE_FATAL); + if (bytes == 0) + /* We got EOF from the child. */ + break; + total += bytes; + p += bytes; + } + + *buff = state->out_buf; + return (total); +} + +static int +program_filter_close(struct archive_read_filter *self) +{ + struct program_filter *state; + int e; + + state = (struct program_filter *)self->data; + e = child_stop(self, state); + + /* Release our private data. */ + free(state->out_buf); + free(state->description); + free(state); + + return (e); +} + +#endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */ diff --git a/libarchive/archive_read_support_filter_rpm.c b/libarchive/archive_read_support_filter_rpm.c new file mode 100644 index 0000000..7dbfc0e --- /dev/null +++ b/libarchive/archive_read_support_filter_rpm.c @@ -0,0 +1,288 @@ +/*- + * Copyright (c) 2009 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct rpm { + int64_t total_in; + size_t hpos; + size_t hlen; + unsigned char header[16]; + enum { + ST_LEAD, /* Skipping 'Lead' section. */ + ST_HEADER, /* Reading 'Header' section; + * first 16 bytes. */ + ST_HEADER_DATA, /* Skipping 'Header' section. */ + ST_PADDING, /* Skipping padding data after the + * 'Header' section. */ + ST_ARCHIVE /* Reading 'Archive' section. */ + } state; + int first_header; +}; +#define RPM_LEAD_SIZE 96 /* Size of 'Lead' section. */ + +static int rpm_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int rpm_bidder_init(struct archive_read_filter *); + +static ssize_t rpm_filter_read(struct archive_read_filter *, + const void **); +static int rpm_filter_close(struct archive_read_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_rpm(struct archive *a) +{ + return archive_read_support_filter_rpm(a); +} +#endif + +int +archive_read_support_filter_rpm(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_rpm"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = rpm_bidder_bid; + bidder->init = rpm_bidder_init; + bidder->options = NULL; + bidder->free = NULL; + return (ARCHIVE_OK); +} + +static int +rpm_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *b; + ssize_t avail; + int bits_checked; + + (void)self; /* UNUSED */ + + b = __archive_read_filter_ahead(filter, 8, &avail); + if (b == NULL) + return (0); + + bits_checked = 0; + /* + * Verify Header Magic Bytes : 0XED 0XAB 0XEE 0XDB + */ + if (memcmp(b, "\xED\xAB\xEE\xDB", 4) != 0) + return (0); + bits_checked += 32; + /* + * Check major version. + */ + if (b[4] != 3 && b[4] != 4) + return (0); + bits_checked += 8; + /* + * Check package type; binary or source. + */ + if (b[6] != 0) + return (0); + bits_checked += 8; + if (b[7] != 0 && b[7] != 1) + return (0); + bits_checked += 8; + + return (bits_checked); +} + +static int +rpm_bidder_init(struct archive_read_filter *self) +{ + struct rpm *rpm; + + self->code = ARCHIVE_COMPRESSION_RPM; + self->name = "rpm"; + self->read = rpm_filter_read; + self->skip = NULL; /* not supported */ + self->close = rpm_filter_close; + + rpm = (struct rpm *)calloc(sizeof(*rpm), 1); + if (rpm == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for rpm"); + return (ARCHIVE_FATAL); + } + + self->data = rpm; + rpm->state = ST_LEAD; + + return (ARCHIVE_OK); +} + +static ssize_t +rpm_filter_read(struct archive_read_filter *self, const void **buff) +{ + struct rpm *rpm; + const unsigned char *b; + ssize_t avail_in, total; + size_t used, n; + uint32_t section; + uint32_t bytes; + + rpm = (struct rpm *)self->data; + *buff = NULL; + total = avail_in = 0; + b = NULL; + used = 0; + do { + if (b == NULL) { + b = __archive_read_filter_ahead(self->upstream, 1, + &avail_in); + if (b == NULL) { + if (avail_in < 0) + return (ARCHIVE_FATAL); + else + break; + } + } + + switch (rpm->state) { + case ST_LEAD: + if (rpm->total_in + avail_in < RPM_LEAD_SIZE) + used += avail_in; + else { + n = RPM_LEAD_SIZE - rpm->total_in; + used += n; + b += n; + rpm->state = ST_HEADER; + rpm->hpos = 0; + rpm->hlen = 0; + rpm->first_header = 1; + } + break; + case ST_HEADER: + n = 16 - rpm->hpos; + if (n > avail_in - used) + n = avail_in - used; + memcpy(rpm->header+rpm->hpos, b, n); + b += n; + used += n; + rpm->hpos += n; + + if (rpm->hpos == 16) { + if (rpm->header[0] != 0x8e || + rpm->header[1] != 0xad || + rpm->header[2] != 0xe8 || + rpm->header[3] != 0x01) { + if (rpm->first_header) { + archive_set_error( + &self->archive->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecoginized rpm header"); + return (ARCHIVE_FATAL); + } + rpm->state = ST_ARCHIVE; + *buff = rpm->header; + total = rpm->hpos; + break; + } + /* Calculate 'Header' length. */ + section = archive_be32dec(rpm->header+8); + bytes = archive_be32dec(rpm->header+12); + rpm->hlen = 16 + section * 16 + bytes; + rpm->state = ST_HEADER_DATA; + rpm->first_header = 0; + } + break; + case ST_HEADER_DATA: + n = rpm->hlen - rpm->hpos; + if (n > avail_in - used) + n = avail_in - used; + b += n; + used += n; + rpm->hpos += n; + if (rpm->hpos == rpm->hlen) + rpm->state = ST_PADDING; + break; + case ST_PADDING: + while (used < (size_t)avail_in) { + if (*b != 0) { + /* Read next header. */ + rpm->state = ST_HEADER; + rpm->hpos = 0; + rpm->hlen = 0; + break; + } + b++; + used++; + } + break; + case ST_ARCHIVE: + *buff = b; + total = avail_in; + used = avail_in; + break; + } + if (used == (size_t)avail_in) { + rpm->total_in += used; + __archive_read_filter_consume(self->upstream, used); + b = NULL; + used = 0; + } + } while (total == 0 && avail_in > 0); + + if (used > 0 && b != NULL) { + rpm->total_in += used; + __archive_read_filter_consume(self->upstream, used); + } + return (total); +} + +static int +rpm_filter_close(struct archive_read_filter *self) +{ + struct rpm *rpm; + + rpm = (struct rpm *)self->data; + free(rpm); + + return (ARCHIVE_OK); +} + diff --git a/libarchive/archive_read_support_filter_uu.c b/libarchive/archive_read_support_filter_uu.c new file mode 100644 index 0000000..a75ef75 --- /dev/null +++ b/libarchive/archive_read_support_filter_uu.c @@ -0,0 +1,680 @@ +/*- + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" + +/* Maximum lookahead during bid phase */ +#define UUENCODE_BID_MAX_READ 128*1024 /* in bytes */ + +struct uudecode { + int64_t total; + unsigned char *in_buff; +#define IN_BUFF_SIZE (1024) + int in_cnt; + size_t in_allocated; + unsigned char *out_buff; +#define OUT_BUFF_SIZE (64 * 1024) + int state; +#define ST_FIND_HEAD 0 +#define ST_READ_UU 1 +#define ST_UUEND 2 +#define ST_READ_BASE64 3 +}; + +static int uudecode_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *filter); +static int uudecode_bidder_init(struct archive_read_filter *); + +static ssize_t uudecode_filter_read(struct archive_read_filter *, + const void **); +static int uudecode_filter_close(struct archive_read_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_uu(struct archive *a) +{ + return archive_read_support_filter_uu(a); +} +#endif + +int +archive_read_support_filter_uu(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_uu"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = uudecode_bidder_bid; + bidder->init = uudecode_bidder_init; + bidder->options = NULL; + bidder->free = NULL; + return (ARCHIVE_OK); +} + +static const unsigned char ascii[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, '\r', 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 30 - 3F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ +}; + +static const unsigned char uuchar[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 30 - 3F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ +}; + +static const unsigned char base64[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, /* 20 - 2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, /* 30 - 3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ +}; + +static const int base64num[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 62, 0, 0, 0, 63, /* 20 - 2F */ + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 0, 0, 0, 0, 0, 0, /* 30 - 3F */ + 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, /* 40 - 4F */ + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 0, 0, 0, 0, 0, /* 50 - 5F */ + 0, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, /* 60 - 6F */ + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 0, 0, 0, 0, 0, /* 70 - 7F */ +}; + +static ssize_t +get_line(const unsigned char *b, ssize_t avail, ssize_t *nlsize) +{ + ssize_t len; + + len = 0; + while (len < avail) { + switch (ascii[*b]) { + case 0: /* Non-ascii character or control character. */ + if (nlsize != NULL) + *nlsize = 0; + return (-1); + case '\r': + if (avail-len > 1 && b[1] == '\n') { + if (nlsize != NULL) + *nlsize = 2; + return (len+2); + } + /* FALL THROUGH */ + case '\n': + if (nlsize != NULL) + *nlsize = 1; + return (len+1); + case 1: + b++; + len++; + break; + } + } + if (nlsize != NULL) + *nlsize = 0; + return (avail); +} + +static ssize_t +bid_get_line(struct archive_read_filter *filter, + const unsigned char **b, ssize_t *avail, ssize_t *ravail, + ssize_t *nl, size_t* nbytes_read) +{ + ssize_t len; + int quit; + + quit = 0; + if (*avail == 0) { + *nl = 0; + len = 0; + } else + len = get_line(*b, *avail, nl); + + /* + * Read bytes more while it does not reach the end of line. + */ + while (*nl == 0 && len == *avail && !quit && + *nbytes_read < UUENCODE_BID_MAX_READ) { + ssize_t diff = *ravail - *avail; + size_t nbytes_req = (*ravail+1023) & ~1023U; + ssize_t tested; + + /* Increase reading bytes if it is not enough to at least + * new two lines. */ + if (nbytes_req < (size_t)*ravail + 160) + nbytes_req <<= 1; + + *b = __archive_read_filter_ahead(filter, nbytes_req, avail); + if (*b == NULL) { + if (*ravail >= *avail) + return (0); + /* Reading bytes reaches the end of a stream. */ + *b = __archive_read_filter_ahead(filter, *avail, avail); + quit = 1; + } + *nbytes_read = *avail; + *ravail = *avail; + *b += diff; + *avail -= diff; + tested = len;/* Skip some bytes we already determinated. */ + len = get_line(*b + tested, *avail - tested, nl); + if (len >= 0) + len += tested; + } + return (len); +} + +#define UUDECODE(c) (((c) - 0x20) & 0x3f) + +static int +uudecode_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *b; + ssize_t avail, ravail; + ssize_t len, nl; + int l; + int firstline; + size_t nbytes_read; + + (void)self; /* UNUSED */ + + b = __archive_read_filter_ahead(filter, 1, &avail); + if (b == NULL) + return (0); + + firstline = 20; + ravail = avail; + nbytes_read = avail; + for (;;) { + len = bid_get_line(filter, &b, &avail, &ravail, &nl, &nbytes_read); + if (len < 0 || nl == 0) + return (0); /* No match found. */ + if (len - nl >= 11 && memcmp(b, "begin ", 6) == 0) + l = 6; + else if (len -nl >= 18 && memcmp(b, "begin-base64 ", 13) == 0) + l = 13; + else + l = 0; + + if (l > 0 && (b[l] < '0' || b[l] > '7' || + b[l+1] < '0' || b[l+1] > '7' || + b[l+2] < '0' || b[l+2] > '7' || b[l+3] != ' ')) + l = 0; + + b += len; + avail -= len; + if (l) + break; + firstline = 0; + + /* Do not read more than UUENCODE_BID_MAX_READ bytes */ + if (nbytes_read >= UUENCODE_BID_MAX_READ) + return (0); + } + if (!avail) + return (0); + len = bid_get_line(filter, &b, &avail, &ravail, &nl, &nbytes_read); + if (len < 0 || nl == 0) + return (0);/* There are non-ascii characters. */ + avail -= len; + + if (l == 6) { + if (!uuchar[*b]) + return (0); + /* Get a length of decoded bytes. */ + l = UUDECODE(*b++); len--; + if (l > 45) + /* Normally, maximum length is 45(character 'M'). */ + return (0); + while (l && len-nl > 0) { + if (l > 0) { + if (!uuchar[*b++]) + return (0); + if (!uuchar[*b++]) + return (0); + len -= 2; + --l; + } + if (l > 0) { + if (!uuchar[*b++]) + return (0); + --len; + --l; + } + if (l > 0) { + if (!uuchar[*b++]) + return (0); + --len; + --l; + } + } + if (len-nl < 0) + return (0); + if (len-nl == 1 && + (uuchar[*b] || /* Check sum. */ + (*b >= 'a' && *b <= 'z'))) {/* Padding data(MINIX). */ + ++b; + --len; + } + b += nl; + if (avail && uuchar[*b]) + return (firstline+30); + } + if (l == 13) { + while (len-nl > 0) { + if (!base64[*b++]) + return (0); + --len; + } + b += nl; + + if (avail >= 5 && memcmp(b, "====\n", 5) == 0) + return (firstline+40); + if (avail >= 6 && memcmp(b, "====\r\n", 6) == 0) + return (firstline+40); + if (avail > 0 && base64[*b]) + return (firstline+30); + } + + return (0); +} + +static int +uudecode_bidder_init(struct archive_read_filter *self) +{ + struct uudecode *uudecode; + void *out_buff; + void *in_buff; + + self->code = ARCHIVE_COMPRESSION_UU; + self->name = "uu"; + self->read = uudecode_filter_read; + self->skip = NULL; /* not supported */ + self->close = uudecode_filter_close; + + uudecode = (struct uudecode *)calloc(sizeof(*uudecode), 1); + out_buff = malloc(OUT_BUFF_SIZE); + in_buff = malloc(IN_BUFF_SIZE); + if (uudecode == NULL || out_buff == NULL || in_buff == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for uudecode"); + free(uudecode); + free(out_buff); + free(in_buff); + return (ARCHIVE_FATAL); + } + + self->data = uudecode; + uudecode->in_buff = in_buff; + uudecode->in_cnt = 0; + uudecode->in_allocated = IN_BUFF_SIZE; + uudecode->out_buff = out_buff; + uudecode->state = ST_FIND_HEAD; + + return (ARCHIVE_OK); +} + +static int +ensure_in_buff_size(struct archive_read_filter *self, + struct uudecode *uudecode, size_t size) +{ + + if (size > uudecode->in_allocated) { + unsigned char *ptr; + size_t newsize; + + /* + * Calculate a new buffer size for in_buff. + * Increase its value until it has enough size we need. + */ + newsize = uudecode->in_allocated; + do { + if (newsize < IN_BUFF_SIZE*32) + newsize <<= 1; + else + newsize += IN_BUFF_SIZE; + } while (size > newsize); + /* Allocate the new buffer. */ + ptr = malloc(newsize); + if (ptr == NULL) { + free(ptr); + archive_set_error(&self->archive->archive, + ENOMEM, + "Can't allocate data for uudecode"); + return (ARCHIVE_FATAL); + } + /* Move the remaining data in in_buff into the new buffer. */ + if (uudecode->in_cnt) + memmove(ptr, uudecode->in_buff, uudecode->in_cnt); + /* Replace in_buff with the new buffer. */ + free(uudecode->in_buff); + uudecode->in_buff = ptr; + uudecode->in_allocated = newsize; + } + return (ARCHIVE_OK); +} + +static ssize_t +uudecode_filter_read(struct archive_read_filter *self, const void **buff) +{ + struct uudecode *uudecode; + const unsigned char *b, *d; + unsigned char *out; + ssize_t avail_in, ravail; + ssize_t used; + ssize_t total; + ssize_t len, llen, nl; + + uudecode = (struct uudecode *)self->data; + +read_more: + d = __archive_read_filter_ahead(self->upstream, 1, &avail_in); + if (d == NULL && avail_in < 0) + return (ARCHIVE_FATAL); + /* Quiet a code analyzer; make sure avail_in must be zero + * when d is NULL. */ + if (d == NULL) + avail_in = 0; + used = 0; + total = 0; + out = uudecode->out_buff; + ravail = avail_in; + if (uudecode->in_cnt) { + /* + * If there is remaining data which is saved by + * previous calling, use it first. + */ + if (ensure_in_buff_size(self, uudecode, + avail_in + uudecode->in_cnt) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + memcpy(uudecode->in_buff + uudecode->in_cnt, + d, avail_in); + d = uudecode->in_buff; + avail_in += uudecode->in_cnt; + uudecode->in_cnt = 0; + } + for (;used < avail_in; d += llen, used += llen) { + int l, body; + + b = d; + len = get_line(b, avail_in - used, &nl); + if (len < 0) { + /* Non-ascii character is found. */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + llen = len; + if (nl == 0) { + /* + * Save remaining data which does not contain + * NL('\n','\r'). + */ + if (ensure_in_buff_size(self, uudecode, len) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if (uudecode->in_buff != b) + memmove(uudecode->in_buff, b, len); + uudecode->in_cnt = len; + if (total == 0) { + /* Do not return 0; it means end-of-file. + * We should try to read bytes more. */ + __archive_read_filter_consume( + self->upstream, ravail); + goto read_more; + } + break; + } + switch (uudecode->state) { + default: + case ST_FIND_HEAD: + /* Do not read more than UUENCODE_BID_MAX_READ bytes */ + if (total + len >= UUENCODE_BID_MAX_READ) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid format data"); + return (ARCHIVE_FATAL); + } + if (len - nl >= 11 && memcmp(b, "begin ", 6) == 0) + l = 6; + else if (len - nl >= 18 && + memcmp(b, "begin-base64 ", 13) == 0) + l = 13; + else + l = 0; + if (l != 0 && b[l] >= '0' && b[l] <= '7' && + b[l+1] >= '0' && b[l+1] <= '7' && + b[l+2] >= '0' && b[l+2] <= '7' && b[l+3] == ' ') { + if (l == 6) + uudecode->state = ST_READ_UU; + else + uudecode->state = ST_READ_BASE64; + } + break; + case ST_READ_UU: + if (total + len * 2 > OUT_BUFF_SIZE) + break; + body = len - nl; + if (!uuchar[*b] || body <= 0) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + /* Get length of undecoded bytes of curent line. */ + l = UUDECODE(*b++); + body--; + if (l > body) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + if (l == 0) { + uudecode->state = ST_UUEND; + break; + } + while (l > 0) { + int n = 0; + + if (l > 0) { + if (!uuchar[b[0]] || !uuchar[b[1]]) + break; + n = UUDECODE(*b++) << 18; + n |= UUDECODE(*b++) << 12; + *out++ = n >> 16; total++; + --l; + } + if (l > 0) { + if (!uuchar[b[0]]) + break; + n |= UUDECODE(*b++) << 6; + *out++ = (n >> 8) & 0xFF; total++; + --l; + } + if (l > 0) { + if (!uuchar[b[0]]) + break; + n |= UUDECODE(*b++); + *out++ = n & 0xFF; total++; + --l; + } + } + if (l) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + break; + case ST_UUEND: + if (len - nl == 3 && memcmp(b, "end ", 3) == 0) + uudecode->state = ST_FIND_HEAD; + else { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + break; + case ST_READ_BASE64: + if (total + len * 2 > OUT_BUFF_SIZE) + break; + l = len - nl; + if (l >= 3 && b[0] == '=' && b[1] == '=' && + b[2] == '=') { + uudecode->state = ST_FIND_HEAD; + break; + } + while (l > 0) { + int n = 0; + + if (l > 0) { + if (!base64[b[0]] || !base64[b[1]]) + break; + n = base64num[*b++] << 18; + n |= base64num[*b++] << 12; + *out++ = n >> 16; total++; + l -= 2; + } + if (l > 0) { + if (*b == '=') + break; + if (!base64[*b]) + break; + n |= base64num[*b++] << 6; + *out++ = (n >> 8) & 0xFF; total++; + --l; + } + if (l > 0) { + if (*b == '=') + break; + if (!base64[*b]) + break; + n |= base64num[*b++]; + *out++ = n & 0xFF; total++; + --l; + } + } + if (l && *b != '=') { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + } + break; + } + } + + __archive_read_filter_consume(self->upstream, ravail); + + *buff = uudecode->out_buff; + uudecode->total += total; + return (total); +} + +static int +uudecode_filter_close(struct archive_read_filter *self) +{ + struct uudecode *uudecode; + + uudecode = (struct uudecode *)self->data; + free(uudecode->in_buff); + free(uudecode->out_buff); + free(uudecode); + + return (ARCHIVE_OK); +} + diff --git a/libarchive/archive_read_support_filter_xz.c b/libarchive/archive_read_support_filter_xz.c new file mode 100644 index 0000000..cf762a4 --- /dev/null +++ b/libarchive/archive_read_support_filter_xz.c @@ -0,0 +1,985 @@ +/*- + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * Copyright (c) 2003-2008 Tim Kientzle and Miklos Vajna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#if HAVE_LZMA_H +#include +#elif HAVE_LZMADEC_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#if HAVE_LZMA_H && HAVE_LIBLZMA + +struct private_data { + lzma_stream stream; + unsigned char *out_block; + size_t out_block_size; + int64_t total_out; + char eof; /* True = found end of compressed data. */ + char in_stream; + + /* Following variables are used for lzip only. */ + char lzip_ver; + uint32_t crc32; + int64_t member_in; + int64_t member_out; +}; + +#if LZMA_VERSION_MAJOR >= 5 +/* Effectively disable the limiter. */ +#define LZMA_MEMLIMIT UINT64_MAX +#else +/* NOTE: This needs to check memory size which running system has. */ +#define LZMA_MEMLIMIT (1U << 30) +#endif + +/* Combined lzip/lzma/xz filter */ +static ssize_t xz_filter_read(struct archive_read_filter *, const void **); +static int xz_filter_close(struct archive_read_filter *); +static int xz_lzma_bidder_init(struct archive_read_filter *); + +#elif HAVE_LZMADEC_H && HAVE_LIBLZMADEC + +struct private_data { + lzmadec_stream stream; + unsigned char *out_block; + size_t out_block_size; + int64_t total_out; + char eof; /* True = found end of compressed data. */ +}; + +/* Lzma-only filter */ +static ssize_t lzma_filter_read(struct archive_read_filter *, const void **); +static int lzma_filter_close(struct archive_read_filter *); +#endif + +/* + * Note that we can detect xz and lzma compressed files even if we + * can't decompress them. (In fact, we like detecting them because we + * can give better error messages.) So the bid framework here gets + * compiled even if no lzma library is available. + */ +static int xz_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int xz_bidder_init(struct archive_read_filter *); +static int lzma_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int lzma_bidder_init(struct archive_read_filter *); +static int lzip_has_member(struct archive_read_filter *); +static int lzip_bidder_bid(struct archive_read_filter_bidder *, + struct archive_read_filter *); +static int lzip_bidder_init(struct archive_read_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* Deprecated; remove in libarchive 4.0 */ +int +archive_read_support_compression_xz(struct archive *a) +{ + return archive_read_support_filter_xz(a); +} +#endif + +int +archive_read_support_filter_xz(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_xz"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = xz_bidder_bid; + bidder->init = xz_bidder_init; + bidder->options = NULL; + bidder->free = NULL; +#if HAVE_LZMA_H && HAVE_LIBLZMA + return (ARCHIVE_OK); +#else + archive_set_error(_a, ARCHIVE_ERRNO_MISC, + "Using external unxz program for xz decompression"); + return (ARCHIVE_WARN); +#endif +} + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_read_support_compression_lzma(struct archive *a) +{ + return archive_read_support_filter_lzma(a); +} +#endif + +int +archive_read_support_filter_lzma(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_lzma"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = lzma_bidder_bid; + bidder->init = lzma_bidder_init; + bidder->options = NULL; + bidder->free = NULL; +#if HAVE_LZMA_H && HAVE_LIBLZMA + return (ARCHIVE_OK); +#elif HAVE_LZMADEC_H && HAVE_LIBLZMADEC + return (ARCHIVE_OK); +#else + archive_set_error(_a, ARCHIVE_ERRNO_MISC, + "Using external unlzma program for lzma decompression"); + return (ARCHIVE_WARN); +#endif +} + + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_read_support_compression_lzip(struct archive *a) +{ + return archive_read_support_filter_lzip(a); +} +#endif + +int +archive_read_support_filter_lzip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct archive_read_filter_bidder *bidder; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_filter_lzip"); + + if (__archive_read_get_bidder(a, &bidder) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + bidder->data = NULL; + bidder->bid = lzip_bidder_bid; + bidder->init = lzip_bidder_init; + bidder->options = NULL; + bidder->free = NULL; +#if HAVE_LZMA_H && HAVE_LIBLZMA + return (ARCHIVE_OK); +#else + archive_set_error(_a, ARCHIVE_ERRNO_MISC, + "Using external lzip program for lzip decompression"); + return (ARCHIVE_WARN); +#endif +} + +/* + * Test whether we can handle this data. + */ +static int +xz_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *buffer; + ssize_t avail; + + (void)self; /* UNUSED */ + + buffer = __archive_read_filter_ahead(filter, 6, &avail); + if (buffer == NULL) + return (0); + + /* + * Verify Header Magic Bytes : FD 37 7A 58 5A 00 + */ + if (memcmp(buffer, "\xFD\x37\x7A\x58\x5A\x00", 6) != 0) + return (0); + + return (48); +} + +/* + * Test whether we can handle this data. + * + * LZMA has a rather poor file signature. Zeros do not + * make good signature bytes as a rule, and the only non-zero byte + * here is an ASCII character. For example, an uncompressed tar + * archive whose first file is ']' would satisfy this check. It may + * be necessary to exclude LZMA from compression_all() because of + * this. Clients of libarchive would then have to explicitly enable + * LZMA checking instead of (or in addition to) compression_all() when + * they have other evidence (file name, command-line option) to go on. + */ +static int +lzma_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + const unsigned char *buffer; + ssize_t avail; + uint32_t dicsize; + uint64_t uncompressed_size; + int bits_checked; + + (void)self; /* UNUSED */ + + buffer = __archive_read_filter_ahead(filter, 14, &avail); + if (buffer == NULL) + return (0); + + /* First byte of raw LZMA stream is commonly 0x5d. + * The first byte is a special number, which consists of + * three parameters of LZMA compression, a number of literal + * context bits(which is from 0 to 8, default is 3), a number + * of literal pos bits(which is from 0 to 4, default is 0), + * a number of pos bits(which is from 0 to 4, default is 2). + * The first byte is made by + * (pos bits * 5 + literal pos bit) * 9 + * literal contest bit, + * and so the default value in this field is + * (2 * 5 + 0) * 9 + 3 = 0x5d. + * lzma of LZMA SDK has options to change those parameters. + * It means a range of this field is from 0 to 224. And lzma of + * XZ Utils with option -e records 0x5e in this field. */ + /* NOTE: If this checking of the first byte increases false + * recognition, we should allow only 0x5d and 0x5e for the first + * byte of LZMA stream. */ + bits_checked = 0; + if (buffer[0] > (4 * 5 + 4) * 9 + 8) + return (0); + /* Most likely value in the first byte of LZMA stream. */ + if (buffer[0] == 0x5d || buffer[0] == 0x5e) + bits_checked += 8; + + /* Sixth through fourteenth bytes are uncompressed size, + * stored in little-endian order. `-1' means uncompressed + * size is unknown and lzma of XZ Utils always records `-1' + * in this field. */ + uncompressed_size = archive_le64dec(buffer+5); + if (uncompressed_size == (uint64_t)ARCHIVE_LITERAL_LL(-1)) + bits_checked += 64; + + /* Second through fifth bytes are dictionary size, stored in + * little-endian order. The minimum dictionary size is + * 1 << 12(4KiB) which the lzma of LZMA SDK uses with option + * -d12 and the maxinam dictionary size is 1 << 27(128MiB) + * which the one uses with option -d27. + * NOTE: A comment of LZMA SDK source code says this dictionary + * range is from 1 << 12 to 1 << 30. */ + dicsize = archive_le32dec(buffer+1); + switch (dicsize) { + case 0x00001000:/* lzma of LZMA SDK option -d12. */ + case 0x00002000:/* lzma of LZMA SDK option -d13. */ + case 0x00004000:/* lzma of LZMA SDK option -d14. */ + case 0x00008000:/* lzma of LZMA SDK option -d15. */ + case 0x00010000:/* lzma of XZ Utils option -0 and -1. + * lzma of LZMA SDK option -d16. */ + case 0x00020000:/* lzma of LZMA SDK option -d17. */ + case 0x00040000:/* lzma of LZMA SDK option -d18. */ + case 0x00080000:/* lzma of XZ Utils option -2. + * lzma of LZMA SDK option -d19. */ + case 0x00100000:/* lzma of XZ Utils option -3. + * lzma of LZMA SDK option -d20. */ + case 0x00200000:/* lzma of XZ Utils option -4. + * lzma of LZMA SDK option -d21. */ + case 0x00400000:/* lzma of XZ Utils option -5. + * lzma of LZMA SDK option -d22. */ + case 0x00800000:/* lzma of XZ Utils option -6. + * lzma of LZMA SDK option -d23. */ + case 0x01000000:/* lzma of XZ Utils option -7. + * lzma of LZMA SDK option -d24. */ + case 0x02000000:/* lzma of XZ Utils option -8. + * lzma of LZMA SDK option -d25. */ + case 0x04000000:/* lzma of XZ Utils option -9. + * lzma of LZMA SDK option -d26. */ + case 0x08000000:/* lzma of LZMA SDK option -d27. */ + bits_checked += 32; + break; + default: + /* If a memory usage for encoding was not enough on + * the platform where LZMA stream was made, lzma of + * XZ Utils automatically decreased the dictionary + * size to enough memory for encoding by 1Mi bytes + * (1 << 20).*/ + if (dicsize <= 0x03F00000 && dicsize >= 0x00300000 && + (dicsize & ((1 << 20)-1)) == 0 && + bits_checked == 8 + 64) { + bits_checked += 32; + break; + } + /* Otherwise dictionary size is unlikely. But it is + * possible that someone makes lzma stream with + * liblzma/LZMA SDK in one's dictionary size. */ + return (0); + } + + /* TODO: The above test is still very weak. It would be + * good to do better. */ + + return (bits_checked); +} + +static int +lzip_has_member(struct archive_read_filter *filter) +{ + const unsigned char *buffer; + ssize_t avail; + int bits_checked; + int log2dic; + + buffer = __archive_read_filter_ahead(filter, 6, &avail); + if (buffer == NULL) + return (0); + + /* + * Verify Header Magic Bytes : 4C 5A 49 50 (`LZIP') + */ + bits_checked = 0; + if (memcmp(buffer, "LZIP", 4) != 0) + return (0); + bits_checked += 32; + + /* A version number must be 0 or 1 */ + if (buffer[4] != 0 && buffer[4] != 1) + return (0); + bits_checked += 8; + + /* Dictionary size. */ + log2dic = buffer[5] & 0x1f; + if (log2dic < 12 || log2dic > 27) + return (0); + bits_checked += 8; + + return (bits_checked); +} + +static int +lzip_bidder_bid(struct archive_read_filter_bidder *self, + struct archive_read_filter *filter) +{ + + (void)self; /* UNUSED */ + return (lzip_has_member(filter)); +} + +#if HAVE_LZMA_H && HAVE_LIBLZMA + +/* + * liblzma 4.999.7 and later support both lzma and xz streams. + */ +static int +xz_bidder_init(struct archive_read_filter *self) +{ + self->code = ARCHIVE_COMPRESSION_XZ; + self->name = "xz"; + return (xz_lzma_bidder_init(self)); +} + +static int +lzma_bidder_init(struct archive_read_filter *self) +{ + self->code = ARCHIVE_COMPRESSION_LZMA; + self->name = "lzma"; + return (xz_lzma_bidder_init(self)); +} + +static int +lzip_bidder_init(struct archive_read_filter *self) +{ + self->code = ARCHIVE_COMPRESSION_LZIP; + self->name = "lzip"; + return (xz_lzma_bidder_init(self)); +} + +/* + * Set an error code and choose an error message + */ +static void +set_error(struct archive_read_filter *self, int ret) +{ + + switch (ret) { + case LZMA_STREAM_END: /* Found end of stream. */ + case LZMA_OK: /* Decompressor made some progress. */ + break; + case LZMA_MEM_ERROR: + archive_set_error(&self->archive->archive, ENOMEM, + "Lzma library error: Cannot allocate memory"); + break; + case LZMA_MEMLIMIT_ERROR: + archive_set_error(&self->archive->archive, ENOMEM, + "Lzma library error: Out of memory"); + break; + case LZMA_FORMAT_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: format not recognized"); + break; + case LZMA_OPTIONS_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Invalid options"); + break; + case LZMA_DATA_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Corrupted input data"); + break; + case LZMA_BUF_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: No progress is possible"); + break; + default: + /* Return an error. */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma decompression failed: Unknown error"); + break; + } +} + +/* + * Setup the callbacks. + */ +static int +xz_lzma_bidder_init(struct archive_read_filter *self) +{ + static const size_t out_block_size = 64 * 1024; + void *out_block; + struct private_data *state; + int ret; + + state = (struct private_data *)calloc(sizeof(*state), 1); + out_block = (unsigned char *)malloc(out_block_size); + if (state == NULL || out_block == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for xz decompression"); + free(out_block); + free(state); + return (ARCHIVE_FATAL); + } + + self->data = state; + state->out_block_size = out_block_size; + state->out_block = out_block; + self->read = xz_filter_read; + self->skip = NULL; /* not supported */ + self->close = xz_filter_close; + + state->stream.avail_in = 0; + + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + state->crc32 = 0; + if (self->code == ARCHIVE_COMPRESSION_LZIP) { + /* + * We have to read a lzip header and use it to initialize + * compression library, thus we cannot initialize the + * library for lzip here. + */ + state->in_stream = 0; + return (ARCHIVE_OK); + } else + state->in_stream = 1; + + /* Initialize compression library. */ + if (self->code == ARCHIVE_COMPRESSION_XZ) + ret = lzma_stream_decoder(&(state->stream), + LZMA_MEMLIMIT,/* memlimit */ + LZMA_CONCATENATED); + else + ret = lzma_alone_decoder(&(state->stream), + LZMA_MEMLIMIT);/* memlimit */ + + if (ret == LZMA_OK) + return (ARCHIVE_OK); + + /* Library setup failed: Choose an error message and clean up. */ + set_error(self, ret); + + free(state->out_block); + free(state); + self->data = NULL; + return (ARCHIVE_FATAL); +} + +static int +lzip_init(struct archive_read_filter *self) +{ + struct private_data *state; + const unsigned char *h; + lzma_filter filters[2]; + unsigned char props[5]; + ssize_t avail_in; + uint32_t dicsize; + int log2dic, ret; + + state = (struct private_data *)self->data; + h = __archive_read_filter_ahead(self->upstream, 6, &avail_in); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Get a version number. */ + state->lzip_ver = h[4]; + + /* + * Setup lzma property. + */ + props[0] = 0x5d; + + /* Get dictionary size. */ + log2dic = h[5] & 0x1f; + if (log2dic < 12 || log2dic > 27) + return (ARCHIVE_FATAL); + dicsize = 1U << log2dic; + if (log2dic > 12) + dicsize -= (dicsize / 16) * (h[5] >> 5); + archive_le32enc(props+1, dicsize); + + /* Consume lzip header. */ + __archive_read_filter_consume(self->upstream, 6); + state->member_in = 6; + + filters[0].id = LZMA_FILTER_LZMA1; + filters[0].options = NULL; + filters[1].id = LZMA_VLI_UNKNOWN; + filters[1].options = NULL; + + ret = lzma_properties_decode(&filters[0], NULL, props, sizeof(props)); + if (ret != LZMA_OK) { + set_error(self, ret); + return (ARCHIVE_FATAL); + } + ret = lzma_raw_decoder(&(state->stream), filters); +#if LZMA_VERSION < 50000030 + free(filters[0].options); +#endif + if (ret != LZMA_OK) { + set_error(self, ret); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static int +lzip_tail(struct archive_read_filter *self) +{ + struct private_data *state; + const unsigned char *f; + ssize_t avail_in; + int tail; + + state = (struct private_data *)self->data; + if (state->lzip_ver == 0) + tail = 12; + else + tail = 20; + f = __archive_read_filter_ahead(self->upstream, tail, &avail_in); + if (f == NULL && avail_in < 0) + return (ARCHIVE_FATAL); + if (avail_in < tail) { + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Lzip: Remaining data is less bytes"); + return (ARCHIVE_FAILED); + } + + /* Check the crc32 value of the uncompressed data of the current + * member */ + if (state->crc32 != archive_le32dec(f)) { + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Lzip: CRC32 error"); + return (ARCHIVE_FAILED); + } + + /* Check the uncompressed size of the current member */ + if ((uint64_t)state->member_out != archive_le64dec(f + 4)) { + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Lzip: Uncompressed size error"); + return (ARCHIVE_FAILED); + } + + /* Check the total size of the current member */ + if (state->lzip_ver == 1 && + (uint64_t)state->member_in + tail != archive_le64dec(f + 12)) { + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Lzip: Member size error"); + return (ARCHIVE_FAILED); + } + __archive_read_filter_consume(self->upstream, tail); + + /* If current lzip data consists of multi member, try decompressing + * a next member. */ + if (lzip_has_member(self->upstream) != 0) { + state->in_stream = 0; + state->crc32 = 0; + state->member_out = 0; + state->member_in = 0; + state->eof = 0; + } + return (ARCHIVE_OK); +} + +/* + * Return the next block of decompressed data. + */ +static ssize_t +xz_filter_read(struct archive_read_filter *self, const void **p) +{ + struct private_data *state; + size_t decompressed; + ssize_t avail_in; + int ret; + + state = (struct private_data *)self->data; + + /* Empty our output buffer. */ + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + /* Try to fill the output buffer. */ + while (state->stream.avail_out > 0 && !state->eof) { + if (!state->in_stream) { + /* + * Initialize liblzma for lzip + */ + ret = lzip_init(self); + if (ret != ARCHIVE_OK) + return (ret); + state->in_stream = 1; + } + state->stream.next_in = + __archive_read_filter_ahead(self->upstream, 1, &avail_in); + if (state->stream.next_in == NULL && avail_in < 0) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "truncated input"); + return (ARCHIVE_FATAL); + } + state->stream.avail_in = avail_in; + + /* Decompress as much as we can in one pass. */ + ret = lzma_code(&(state->stream), + (state->stream.avail_in == 0)? LZMA_FINISH: LZMA_RUN); + switch (ret) { + case LZMA_STREAM_END: /* Found end of stream. */ + state->eof = 1; + /* FALL THROUGH */ + case LZMA_OK: /* Decompressor made some progress. */ + __archive_read_filter_consume(self->upstream, + avail_in - state->stream.avail_in); + state->member_in += + avail_in - state->stream.avail_in; + break; + default: + set_error(self, ret); + return (ARCHIVE_FATAL); + } + } + + decompressed = state->stream.next_out - state->out_block; + state->total_out += decompressed; + state->member_out += decompressed; + if (decompressed == 0) + *p = NULL; + else { + *p = state->out_block; + if (self->code == ARCHIVE_COMPRESSION_LZIP) { + state->crc32 = lzma_crc32(state->out_block, + decompressed, state->crc32); + if (state->eof) { + ret = lzip_tail(self); + if (ret != ARCHIVE_OK) + return (ret); + } + } + } + return (decompressed); +} + +/* + * Clean up the decompressor. + */ +static int +xz_filter_close(struct archive_read_filter *self) +{ + struct private_data *state; + + state = (struct private_data *)self->data; + lzma_end(&(state->stream)); + free(state->out_block); + free(state); + return (ARCHIVE_OK); +} + +#else + +#if HAVE_LZMADEC_H && HAVE_LIBLZMADEC + +/* + * If we have the older liblzmadec library, then we can handle + * LZMA streams but not XZ streams. + */ + +/* + * Setup the callbacks. + */ +static int +lzma_bidder_init(struct archive_read_filter *self) +{ + static const size_t out_block_size = 64 * 1024; + void *out_block; + struct private_data *state; + ssize_t ret, avail_in; + + self->code = ARCHIVE_COMPRESSION_LZMA; + self->name = "lzma"; + + state = (struct private_data *)calloc(sizeof(*state), 1); + out_block = (unsigned char *)malloc(out_block_size); + if (state == NULL || out_block == NULL) { + archive_set_error(&self->archive->archive, ENOMEM, + "Can't allocate data for lzma decompression"); + free(out_block); + free(state); + return (ARCHIVE_FATAL); + } + + self->data = state; + state->out_block_size = out_block_size; + state->out_block = out_block; + self->read = lzma_filter_read; + self->skip = NULL; /* not supported */ + self->close = lzma_filter_close; + + /* Prime the lzma library with 18 bytes of input. */ + state->stream.next_in = (unsigned char *)(uintptr_t) + __archive_read_filter_ahead(self->upstream, 18, &avail_in); + if (state->stream.next_in == NULL) + return (ARCHIVE_FATAL); + state->stream.avail_in = avail_in; + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + /* Initialize compression library. */ + ret = lzmadec_init(&(state->stream)); + __archive_read_filter_consume(self->upstream, + avail_in - state->stream.avail_in); + if (ret == LZMADEC_OK) + return (ARCHIVE_OK); + + /* Library setup failed: Clean up. */ + archive_set_error(&self->archive->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing lzma library"); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case LZMADEC_HEADER_ERROR: + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid header"); + break; + case LZMADEC_MEM_ERROR: + archive_set_error(&self->archive->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + } + + free(state->out_block); + free(state); + self->data = NULL; + return (ARCHIVE_FATAL); +} + +/* + * Return the next block of decompressed data. + */ +static ssize_t +lzma_filter_read(struct archive_read_filter *self, const void **p) +{ + struct private_data *state; + size_t decompressed; + ssize_t avail_in, ret; + + state = (struct private_data *)self->data; + + /* Empty our output buffer. */ + state->stream.next_out = state->out_block; + state->stream.avail_out = state->out_block_size; + + /* Try to fill the output buffer. */ + while (state->stream.avail_out > 0 && !state->eof) { + state->stream.next_in = (unsigned char *)(uintptr_t) + __archive_read_filter_ahead(self->upstream, 1, &avail_in); + if (state->stream.next_in == NULL && avail_in < 0) { + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "truncated lzma input"); + return (ARCHIVE_FATAL); + } + state->stream.avail_in = avail_in; + + /* Decompress as much as we can in one pass. */ + ret = lzmadec_decode(&(state->stream), avail_in == 0); + switch (ret) { + case LZMADEC_STREAM_END: /* Found end of stream. */ + state->eof = 1; + /* FALL THROUGH */ + case LZMADEC_OK: /* Decompressor made some progress. */ + __archive_read_filter_consume(self->upstream, + avail_in - state->stream.avail_in); + break; + case LZMADEC_BUF_ERROR: /* Insufficient input data? */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Insufficient compressed data"); + return (ARCHIVE_FATAL); + default: + /* Return an error. */ + archive_set_error(&self->archive->archive, + ARCHIVE_ERRNO_MISC, + "Lzma decompression failed"); + return (ARCHIVE_FATAL); + } + } + + decompressed = state->stream.next_out - state->out_block; + state->total_out += decompressed; + if (decompressed == 0) + *p = NULL; + else + *p = state->out_block; + return (decompressed); +} + +/* + * Clean up the decompressor. + */ +static int +lzma_filter_close(struct archive_read_filter *self) +{ + struct private_data *state; + int ret; + + state = (struct private_data *)self->data; + ret = ARCHIVE_OK; + switch (lzmadec_end(&(state->stream))) { + case LZMADEC_OK: + break; + default: + archive_set_error(&(self->archive->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up %s compressor", + self->archive->archive.compression_name); + ret = ARCHIVE_FATAL; + } + + free(state->out_block); + free(state); + return (ret); +} + +#else + +/* + * + * If we have no suitable library on this system, we can't actually do + * the decompression. We can, however, still detect compressed + * archives and emit a useful message. + * + */ +static int +lzma_bidder_init(struct archive_read_filter *self) +{ + int r; + + r = __archive_read_program(self, "unlzma"); + /* Note: We set the format here even if __archive_read_program() + * above fails. We do, after all, know what the format is + * even if we weren't able to read it. */ + self->code = ARCHIVE_COMPRESSION_LZMA; + self->name = "lzma"; + return (r); +} + +#endif /* HAVE_LZMADEC_H */ + + +static int +xz_bidder_init(struct archive_read_filter *self) +{ + int r; + + r = __archive_read_program(self, "unxz"); + /* Note: We set the format here even if __archive_read_program() + * above fails. We do, after all, know what the format is + * even if we weren't able to read it. */ + self->code = ARCHIVE_COMPRESSION_XZ; + self->name = "xz"; + return (r); +} + +static int +lzip_bidder_init(struct archive_read_filter *self) +{ + int r; + + r = __archive_read_program(self, "unlzip"); + /* Note: We set the format here even if __archive_read_program() + * above fails. We do, after all, know what the format is + * even if we weren't able to read it. */ + self->code = ARCHIVE_COMPRESSION_LZIP; + self->name = "lzip"; + return (r); +} + + +#endif /* HAVE_LZMA_H */ diff --git a/libarchive/archive_read_support_format_7zip.c b/libarchive/archive_read_support_format_7zip.c new file mode 100644 index 0000000..f581ee5 --- /dev/null +++ b/libarchive/archive_read_support_format_7zip.c @@ -0,0 +1,3687 @@ +/*- + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_BZLIB_H +#include +#endif +#ifdef HAVE_LZMA_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_ppmd7_private.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif + +#define _7ZIP_SIGNATURE "7z\xBC\xAF\x27\x1C" +#define SFX_MIN_ADDR 0x27000 +#define SFX_MAX_ADDR 0x60000 + + +/* + * Codec ID + */ +#define _7Z_COPY 0 +#define _7Z_LZMA 0x030101 +#define _7Z_LZMA2 0x21 +#define _7Z_DEFLATE 0x040108 +#define _7Z_BZ2 0x040202 +#define _7Z_PPMD 0x030401 +#define _7Z_DELTA 0x03 +#define _7Z_CRYPTO 0x06F10701 +#define _7Z_X86 0x03030103 +#define _7Z_X86_BCJ2 0x0303011B +#define _7Z_POWERPC 0x03030205 +#define _7Z_IA64 0x03030401 +#define _7Z_ARM 0x03030501 +#define _7Z_ARMTHUMB 0x03030701 +#define _7Z_SPARC 0x03030805 + +/* + * 7-Zip header property IDs. + */ +#define kEnd 0x00 +#define kHeader 0x01 +#define kArchiveProperties 0x02 +#define kAdditionalStreamsInfo 0x03 +#define kMainStreamsInfo 0x04 +#define kFilesInfo 0x05 +#define kPackInfo 0x06 +#define kUnPackInfo 0x07 +#define kSubStreamsInfo 0x08 +#define kSize 0x09 +#define kCRC 0x0A +#define kFolder 0x0B +#define kCodersUnPackSize 0x0C +#define kNumUnPackStream 0x0D +#define kEmptyStream 0x0E +#define kEmptyFile 0x0F +#define kAnti 0x10 +#define kName 0x11 +#define kCTime 0x12 +#define kATime 0x13 +#define kMTime 0x14 +#define kAttributes 0x15 +#define kEncodedHeader 0x17 + +struct _7z_digests { + unsigned char *defineds; + uint32_t *digests; +}; + + +struct _7z_folder { + uint64_t numCoders; + struct _7z_coder { + unsigned long codec; + uint64_t numInStreams; + uint64_t numOutStreams; + uint64_t propertiesSize; + unsigned char *properties; + } *coders; + uint64_t numBindPairs; + struct { + uint64_t inIndex; + uint64_t outIndex; + } *bindPairs; + uint64_t numPackedStreams; + uint64_t *packedStreams; + uint64_t numInStreams; + uint64_t numOutStreams; + uint64_t *unPackSize; + unsigned char digest_defined; + uint32_t digest; + uint64_t numUnpackStreams; + uint32_t packIndex; + /* Unoperated bytes. */ + uint64_t skipped_bytes; +}; + +struct _7z_coders_info { + uint64_t numFolders; + struct _7z_folder *folders; + uint64_t dataStreamIndex; +}; + +struct _7z_pack_info { + uint64_t pos; + uint64_t numPackStreams; + uint64_t *sizes; + struct _7z_digests digest; + /* Calculated from pos and numPackStreams. */ + uint64_t *positions; +}; + +struct _7z_substream_info { + size_t unpack_streams; + uint64_t *unpackSizes; + unsigned char *digestsDefined; + uint32_t *digests; +}; + +struct _7z_stream_info { + struct _7z_pack_info pi; + struct _7z_coders_info ci; + struct _7z_substream_info ss; +}; + +struct _7z_header_info { + uint64_t dataIndex; + + unsigned char *emptyStreamBools; + unsigned char *emptyFileBools; + unsigned char *antiBools; + unsigned char *attrBools; +}; + +struct _7zip_entry { + size_t name_len; + unsigned char *utf16name; +#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) + const wchar_t *wname; +#endif + uint32_t folderIndex; + uint32_t ssIndex; + unsigned flg; +#define MTIME_IS_SET (1<<0) +#define ATIME_IS_SET (1<<1) +#define CTIME_IS_SET (1<<2) +#define CRC32_IS_SET (1<<3) +#define HAS_STREAM (1<<4) + + time_t mtime; + time_t atime; + time_t ctime; + long mtime_ns; + long atime_ns; + long ctime_ns; + uint32_t mode; + uint32_t attr; +}; + +struct _7zip { + /* Structural information about the archive. */ + struct _7z_stream_info si; + + /* Header offset to check that reading pointes of the file contens + * will not exceed the header. */ + uint64_t header_offset; + /* Base offset of the archive file for a seek in case reading SFX. */ + uint64_t seek_base; + + /* List of entries */ + size_t entries_remaining; + uint64_t numFiles; + struct _7zip_entry *entries; + struct _7zip_entry *entry; + unsigned char *entry_names; + + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_offset; + uint64_t entry_bytes_remaining; + + /* Running CRC32 of the decompressed data */ + unsigned long entry_crc32; + + /* Flags to mark progress of decompression. */ + char end_of_entry; + + /* Uncompressed buffer control. */ + unsigned char *uncompressed_buffer; + unsigned char *uncompressed_buffer_pointer; + size_t uncompressed_buffer_size; + size_t uncompressed_buffer_bytes_remaining; + + /* Offset of the compressed data. */ + int64_t stream_offset; + + /* + * Decompressing control data. + */ + unsigned folder_index; + uint64_t folder_outbytes_remaining; + unsigned pack_stream_index; + unsigned pack_stream_remaining; + uint64_t pack_stream_inbytes_remaining; + size_t pack_stream_bytes_unconsumed; + + /* The codec information of a folder. */ + unsigned long codec; + unsigned long codec2; + + /* + * Decompressor controllers. + */ + /* Decording LZMA1 and LZMA2 data. */ +#ifdef HAVE_LZMA_H + lzma_stream lzstream; + int lzstream_valid; +#endif + /* Decording bzip2 data. */ +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + bz_stream bzstream; + int bzstream_valid; +#endif + /* Decording deflate data. */ +#ifdef HAVE_ZLIB_H + z_stream stream; + int stream_valid; +#endif + /* Decording PPMd data. */ + int ppmd7_stat; + CPpmd7 ppmd7_context; + CPpmd7z_RangeDec range_dec; + IByteIn bytein; + struct { + const unsigned char *next_in; + int64_t avail_in; + int64_t total_in; + unsigned char *next_out; + int64_t avail_out; + int64_t total_out; + int overconsumed; + } ppstream; + int ppmd7_valid; + + /* Decoding BCJ and BCJ2 data. */ + uint32_t bcj_state; + size_t odd_bcj_size; + unsigned char odd_bcj[4]; + + /* Decoding BCJ2 data. */ + size_t main_stream_bytes_remaining; + unsigned char *sub_stream_buff[3]; + size_t sub_stream_size[3]; + size_t sub_stream_bytes_remaining[3]; + unsigned char *tmp_stream_buff; + size_t tmp_stream_buff_size; + size_t tmp_stream_bytes_avail; + size_t tmp_stream_bytes_remaining; +#ifdef _LZMA_PROB32 +#define CProb uint32_t +#else +#define CProb uint16_t +#endif + CProb bcj2_p[256 + 2]; + uint8_t bcj2_prevByte; + uint32_t bcj2_range; + uint32_t bcj2_code; + uint64_t bcj2_outPos; + + /* Filename character-set convertion data. */ + struct archive_string_conv *sconv; + + char format_name[64]; +}; + +static int archive_read_format_7zip_bid(struct archive_read *, int); +static int archive_read_format_7zip_cleanup(struct archive_read *); +static int archive_read_format_7zip_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_7zip_read_data_skip(struct archive_read *); +static int archive_read_format_7zip_read_header(struct archive_read *, + struct archive_entry *); +static int check_7zip_header_in_sfx(const char *); +static unsigned long decode_codec_id(const unsigned char *, size_t); +static ssize_t decode_header_image(struct archive_read *, struct _7zip *, + struct _7z_stream_info *, const unsigned char *, uint64_t, + const void **); +static int decompress(struct archive_read *, struct _7zip *, + void *, size_t *, const void *, size_t *); +static ssize_t extract_pack_stream(struct archive_read *); +static void fileTimeToUtc(uint64_t, time_t *, long *); +static uint64_t folder_uncompressed_size(struct _7z_folder *); +static void free_CodersInfo(struct _7z_coders_info *); +static void free_Digest(struct _7z_digests *); +static void free_Folder(struct _7z_folder *); +static void free_Header(struct _7z_header_info *); +static void free_PackInfo(struct _7z_pack_info *); +static void free_StreamsInfo(struct _7z_stream_info *); +static void free_SubStreamsInfo(struct _7z_substream_info *); +static int free_decompression(struct archive_read *, struct _7zip *); +static ssize_t get_uncompressed_data(struct archive_read *, const void **, + size_t); +static int init_decompression(struct archive_read *, struct _7zip *, + const struct _7z_coder *, const struct _7z_coder *); +static int parse_7zip_uint64(const unsigned char *, size_t, uint64_t *); +static int read_Bools(unsigned char *, size_t, const unsigned char *, + size_t); +static int read_CodersInfo(struct _7z_coders_info *, + const unsigned char *, size_t); +static int read_Digests(struct _7z_digests *, size_t, + const unsigned char *, size_t); +static int read_Folder(struct _7z_folder *, const unsigned char *, + size_t); +static int read_Header(struct _7zip *, struct _7z_header_info *, + const unsigned char *, size_t); +static int read_PackInfo(struct _7z_pack_info *, const unsigned char *, + size_t); +static int read_StreamsInfo(struct _7zip *, struct _7z_stream_info *, + const unsigned char *, size_t); +static int read_SubStreamsInfo(struct _7z_substream_info *, + struct _7z_folder *, size_t, const unsigned char *, + size_t); +static int read_Times(struct _7zip *, struct _7z_header_info *, int, + const unsigned char *, size_t); +static void read_consume(struct archive_read *); +static ssize_t read_stream(struct archive_read *, const void **, size_t); +static int64_t skip_stream(struct archive_read *, size_t); +static int skip_sfx(struct archive_read *, ssize_t); +static int slurp_central_directory(struct archive_read *, struct _7zip *, + struct _7z_header_info *); +static int setup_decode_folder(struct archive_read *, struct _7z_folder *, + int); +static size_t x86_Convert(uint8_t *, size_t, uint32_t, uint32_t *); +ssize_t Bcj2_Decode(struct _7zip *, uint8_t *, size_t); + + +int +archive_read_support_format_7zip(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct _7zip *zip; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_7zip"); + + zip = calloc(1, sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate 7zip data"); + return (ARCHIVE_FATAL); + } + + r = __archive_read_register_format(a, + zip, + "7zip", + archive_read_format_7zip_bid, + NULL, + archive_read_format_7zip_read_header, + archive_read_format_7zip_read_data, + archive_read_format_7zip_read_data_skip, + archive_read_format_7zip_cleanup); + + if (r != ARCHIVE_OK) + free(zip); + return (ARCHIVE_OK); +} + +static int +archive_read_format_7zip_bid(struct archive_read *a, int best_bid) +{ + const char *p; + + /* If someone has already bid more than 32, then avoid + trashing the look-ahead buffers with a seek. */ + if (best_bid > 32) + return (-1); + + if ((p = __archive_read_ahead(a, 6, NULL)) == NULL) + return (0); + + /* If first six bytes are the 7-Zip signature, + * return the bid right now. */ + if (memcmp(p, _7ZIP_SIGNATURE, 6) == 0) + return (48); + + /* + * It may a 7-Zip SFX archive file. If first two bytes are + * 'M' and 'Z' available on Windows or first four bytes are + * "\x7F\x45LF" available on posix like system, seek the 7-Zip + * signature. Although we will perform a seek when reading + * a header, what we do not use __archive_read_seek() here is + * due to a bidding performance. + */ + if ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0) { + ssize_t offset = SFX_MIN_ADDR; + ssize_t window = 4096; + ssize_t bytes_avail; + while (offset + window <= (SFX_MAX_ADDR)) { + const char *buff = __archive_read_ahead(a, + offset + window, &bytes_avail); + if (buff == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + return (0); + continue; + } + p = buff + offset; + while (p + 32 < buff + bytes_avail) { + int step = check_7zip_header_in_sfx(p); + if (step == 0) + return (48); + p += step; + } + offset = p - buff; + } + } + return (0); +} + +static int +check_7zip_header_in_sfx(const char *p) +{ + switch ((unsigned char)p[5]) { + case 0x1C: + if (memcmp(p, _7ZIP_SIGNATURE, 6) != 0) + return (6); + /* + * Test the CRC because its extraction code has 7-Zip + * Magic Code, so we should do this in order not to + * make a mis-detection. + */ + if (crc32(0, (unsigned char *)p + 12, 20) + != archive_le32dec(p + 8)) + return (6); + /* Hit the header! */ + return (0); + case 0x37: return (5); + case 0x7A: return (4); + case 0xBC: return (3); + case 0xAF: return (2); + case 0x27: return (1); + default: return (6); + } +} + +static int +skip_sfx(struct archive_read *a, ssize_t bytes_avail) +{ + const void *h; + const char *p, *q; + size_t skip, offset; + ssize_t bytes, window; + + /* + * If bytes_avail > SFX_MIN_ADDR we do not have to call + * __archive_read_seek() at this time since we have + * alredy had enough data. + */ + if (bytes_avail > SFX_MIN_ADDR) + __archive_read_consume(a, SFX_MIN_ADDR); + else if (__archive_read_seek(a, SFX_MIN_ADDR, SEEK_SET) < 0) + return (ARCHIVE_FATAL); + + offset = 0; + window = 1; + while (offset + window <= SFX_MAX_ADDR - SFX_MIN_ADDR) { + h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + goto fatal; + continue; + } + if (bytes < 6) { + /* This case might happen when window == 1. */ + window = 4096; + continue; + } + p = (const char *)h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the 7-Zip header. + */ + while (p + 32 < q) { + int step = check_7zip_header_in_sfx(p); + if (step == 0) { + struct _7zip *zip = + (struct _7zip *)a->format->data; + skip = p - (const char *)h; + __archive_read_consume(a, skip); + zip->seek_base = SFX_MIN_ADDR + offset + skip; + return (ARCHIVE_OK); + } + p += step; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + offset += skip; + if (window == 1) + window = 4096; + } +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out 7-Zip header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_7zip_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + struct _7zip_entry *zip_entry; + int r, ret = ARCHIVE_OK; + + a->archive.archive_format = ARCHIVE_FORMAT_7ZIP; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "7-Zip"; + + if (zip->entries == NULL) { + struct _7z_header_info header; + + memset(&header, 0, sizeof(header)); + r = slurp_central_directory(a, zip, &header); + free_Header(&header); + if (r != ARCHIVE_OK) + return (r); + zip->entries_remaining = zip->numFiles; + zip->entry = zip->entries; + } else { + ++zip->entry; + } + zip_entry = zip->entry; + + if (zip->entries_remaining <= 0) + return ARCHIVE_EOF; + --zip->entries_remaining; + + zip->entry_offset = 0; + zip->end_of_entry = 0; + zip->entry_crc32 = crc32(0, NULL, 0); + + /* Setup a string conversion for a filename. */ + if (zip->sconv == NULL) { + zip->sconv = archive_string_conversion_from_charset( + &a->archive, "UTF-16LE", 1); + if (zip->sconv == NULL) + return (ARCHIVE_FATAL); + } + + if (archive_entry_copy_pathname_l(entry, + (const char *)zip_entry->utf16name, + zip_entry->name_len, zip->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(zip->sconv)); + ret = ARCHIVE_WARN; + } + + /* Populate some additional entry fields: */ + archive_entry_set_mode(entry, zip_entry->mode); + if (zip_entry->flg & MTIME_IS_SET) + archive_entry_set_mtime(entry, zip_entry->mtime, + zip_entry->mtime_ns); + if (zip_entry->flg & CTIME_IS_SET) + archive_entry_set_ctime(entry, zip_entry->ctime, + zip_entry->ctime_ns); + if (zip_entry->flg & ATIME_IS_SET) + archive_entry_set_atime(entry, zip_entry->atime, + zip_entry->atime_ns); + if (zip_entry->ssIndex != -1) { + zip->entry_bytes_remaining = + zip->si.ss.unpackSizes[zip_entry->ssIndex]; + archive_entry_set_size(entry, zip->entry_bytes_remaining); + } else { + zip->entry_bytes_remaining = 0; + archive_entry_set_size(entry, 0); + } + + /* If there's no body, force read_data() to return EOF immediately. */ + if (zip->entry_bytes_remaining < 1) + zip->end_of_entry = 1; + + if ((zip_entry->mode & AE_IFMT) == AE_IFLNK) { + unsigned char *symname = NULL; + size_t symsize = 0; + int r; + + /* + * Symbolic-name is recorded as its contents. We have to read the + * contents at this time. + */ + while (zip->entry_bytes_remaining > 0) { + const void *buff; + size_t size; + int64_t offset; + + r = archive_read_format_7zip_read_data(a, &buff, &size, + &offset); + if (r < ARCHIVE_WARN) + return (r); + symname = realloc(symname, symsize + size + 1); + if (symname == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Symname"); + return (ARCHIVE_FATAL); + } + memcpy(symname+symsize, buff, size); + symsize += size; + } + if (symsize == 0) { + /* If there is no synname, handle it as a regular file. */ + zip_entry->mode &= ~AE_IFMT; + zip_entry->mode |= AE_IFREG; + archive_entry_set_mode(entry, zip_entry->mode); + } else { + symname[symsize] = '\0'; + archive_entry_copy_symlink(entry, (const char *)symname); + free(symname); + } + archive_entry_set_size(entry, 0); + } + + /* Set up a more descriptive format name. */ + sprintf(zip->format_name, "7-Zip"); + a->archive.archive_format_name = zip->format_name; + + return (ret); +} + +static int +archive_read_format_7zip_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct _7zip *zip; + ssize_t bytes; + int ret = ARCHIVE_OK; + + zip = (struct _7zip *)(a->format->data); + + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + + /* + * If we hit end-of-entry last time, clean up and return + * ARCHIVE_EOF this time. + */ + if (zip->end_of_entry) { + *offset = zip->entry_offset; + *size = 0; + *buff = NULL; + return (ARCHIVE_EOF); + } + + bytes = read_stream(a, buff, zip->entry_bytes_remaining); + if (bytes < 0) + return ((int)bytes); + if (bytes == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + zip->entry_bytes_remaining -= bytes; + if (zip->entry_bytes_remaining == 0) + zip->end_of_entry = 1; + + /* Update checksum */ + if ((zip->entry->flg & CRC32_IS_SET) && bytes) + zip->entry_crc32 = crc32(zip->entry_crc32, *buff, bytes); + + /* If we hit the end, swallow any end-of-data marker. */ + if (zip->end_of_entry) { + /* Check computed CRC against file contents. */ + if ((zip->entry->flg & CRC32_IS_SET) && + zip->si.ss.digests[zip->entry->ssIndex] != + zip->entry_crc32) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "7-Zip bad CRC: 0x%lx should be 0x%lx", + (unsigned long)zip->entry_crc32, + (unsigned long)zip->si.ss.digests[ + zip->entry->ssIndex]); + ret = ARCHIVE_WARN; + } + } + + *size = bytes; + *offset = zip->entry_offset; + zip->entry_offset += bytes; + + return (ret); +} + +static int +archive_read_format_7zip_read_data_skip(struct archive_read *a) +{ + struct _7zip *zip; + int64_t bytes_skipped; + + zip = (struct _7zip *)(a->format->data); + + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + + /* If we've already read to end of data, we're done. */ + if (zip->end_of_entry) + return (ARCHIVE_OK); + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = skip_stream(a, zip->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + zip->entry_bytes_remaining = 0; + + /* This entry is finished and done. */ + zip->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_7zip_cleanup(struct archive_read *a) +{ + struct _7zip *zip; + + zip = (struct _7zip *)(a->format->data); + free_StreamsInfo(&(zip->si)); + free(zip->entries); + free(zip->entry_names); + free_decompression(a, zip); + free(zip->uncompressed_buffer); + free(zip->sub_stream_buff[0]); + free(zip->sub_stream_buff[1]); + free(zip->sub_stream_buff[2]); + free(zip->tmp_stream_buff); + free(zip); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static void +read_consume(struct archive_read *a) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + + if (zip->pack_stream_bytes_unconsumed) { + __archive_read_consume(a, zip->pack_stream_bytes_unconsumed); + zip->stream_offset += zip->pack_stream_bytes_unconsumed; + zip->pack_stream_bytes_unconsumed = 0; + } +} + +#ifdef HAVE_LZMA_H + +/* + * Set an error code and choose an error message for liblzma. + */ +static void +set_error(struct archive_read *a, int ret) +{ + + switch (ret) { + case LZMA_STREAM_END: /* Found end of stream. */ + case LZMA_OK: /* Decompressor made some progress. */ + break; + case LZMA_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Lzma library error: Cannot allocate memory"); + break; + case LZMA_MEMLIMIT_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Lzma library error: Out of memory"); + break; + case LZMA_FORMAT_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: format not recognized"); + break; + case LZMA_OPTIONS_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Invalid options"); + break; + case LZMA_DATA_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: Corrupted input data"); + break; + case LZMA_BUF_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma library error: No progress is possible"); + break; + default: + /* Return an error. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Lzma decompression failed: Unknown error"); + break; + } +} + +#endif + +static unsigned long +decode_codec_id(const unsigned char *codecId, size_t id_size) +{ + unsigned i; + unsigned long id = 0; + + for (i = 0; i < id_size; i++) { + id <<= 8; + id += codecId[i]; + } + return (id); +} + +static void * +ppmd_alloc(void *p, size_t size) +{ + (void)p; + return malloc(size); +} +static void +ppmd_free(void *p, void *address) +{ + (void)p; + free(address); +} +static Byte +ppmd_read(void *p) +{ + struct archive_read *a = ((IByteIn*)p)->a; + struct _7zip *zip = (struct _7zip *)(a->format->data); + Byte b; + + if (zip->ppstream.avail_in == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + zip->ppstream.overconsumed = 1; + return (0); + } + b = *zip->ppstream.next_in++; + zip->ppstream.avail_in--; + zip->ppstream.total_in++; + return (b); +} + +static ISzAlloc g_szalloc = { ppmd_alloc, ppmd_free }; + +static int +init_decompression(struct archive_read *a, struct _7zip *zip, + const struct _7z_coder *coder1, const struct _7z_coder *coder2) +{ + int r; + + zip->codec = coder1->codec; + zip->codec2 = -1; + + switch (zip->codec) { + case _7Z_COPY: + case _7Z_BZ2: + case _7Z_DEFLATE: + case _7Z_PPMD: + if (coder2 != NULL) { + if (coder2->codec != _7Z_X86 && + coder2->codec != _7Z_X86_BCJ2) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unsupported filter %lx for %lx", + coder2->codec, coder1->codec); + return (ARCHIVE_FAILED); + } + zip->codec2 = coder2->codec; + zip->bcj_state = 0; + } + break; + default: + break; + } + + switch (zip->codec) { + case _7Z_COPY: + break; + + case _7Z_LZMA: case _7Z_LZMA2: +#ifdef HAVE_LZMA_H +#if LZMA_VERSION_MAJOR >= 5 +/* Effectively disable the limiter. */ +#define LZMA_MEMLIMIT UINT64_MAX +#else +/* NOTE: This needs to check memory size which running system has. */ +#define LZMA_MEMLIMIT (1U << 30) +#endif + { + lzma_options_delta delta_opt; + lzma_filter filters[LZMA_FILTERS_MAX]; +#if LZMA_VERSION < 50000030 + lzma_filter *ff; +#endif + int fi = 0; + + if (zip->lzstream_valid) { + lzma_end(&(zip->lzstream)); + zip->lzstream_valid = 0; + } + + /* + * NOTE: liblzma incompletely handle the BCJ+LZMA compressed + * data made by 7-Zip because 7-Zip does not add End-Of- + * Payload Marker(EOPM) at the end of LZMA compressed data, + * and so liblzma cannot know the end of the compressed data + * without EOPM. So consequently liblzma will not return last + * three or four bytes of uncompressed data because + * LZMA_FILTER_X86 filter does not handle input data if its + * data size is less than five bytes. If liblzma detect EOPM + * or know the uncompressed data size, liblzma will flush out + * the remaining that three or four bytes of uncompressed + * data. That is why we have to use our converting program + * for BCJ+LZMA. If we were able to tell the uncompressed + * size to liblzma when using lzma_raw_decoder() liblzma + * could correctly deal with BCJ+LZMA. But unfortunately + * there is no way to do that. + * Discussion about this can be found at XZ Utils forum. + */ + if (coder2 != NULL) { + zip->codec2 = coder2->codec; + + filters[fi].options = NULL; + switch (zip->codec2) { + case _7Z_X86: + if (zip->codec == _7Z_LZMA2) { + filters[fi].id = LZMA_FILTER_X86; + fi++; + } else + /* Use our filter. */ + zip->bcj_state = 0; + break; + case _7Z_X86_BCJ2: + /* Use our filter. */ + zip->bcj_state = 0; + break; + case _7Z_DELTA: + filters[fi].id = LZMA_FILTER_DELTA; + memset(&delta_opt, 0, sizeof(delta_opt)); + delta_opt.type = LZMA_DELTA_TYPE_BYTE; + delta_opt.dist = 1; + filters[fi].options = &delta_opt; + fi++; + break; + /* Following filters have not been tested yet. */ + case _7Z_POWERPC: + filters[fi].id = LZMA_FILTER_POWERPC; + fi++; + break; + case _7Z_IA64: + filters[fi].id = LZMA_FILTER_IA64; + fi++; + break; + case _7Z_ARM: + filters[fi].id = LZMA_FILTER_ARM; + fi++; + break; + case _7Z_ARMTHUMB: + filters[fi].id = LZMA_FILTER_ARMTHUMB; + fi++; + break; + case _7Z_SPARC: + filters[fi].id = LZMA_FILTER_SPARC; + fi++; + break; + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unexpected codec ID: %lX", zip->codec2); + return (ARCHIVE_FAILED); + } + } + + if (zip->codec == _7Z_LZMA2) + filters[fi].id = LZMA_FILTER_LZMA2; + else + filters[fi].id = LZMA_FILTER_LZMA1; + filters[fi].options = NULL; +#if LZMA_VERSION < 50000030 + ff = &filters[fi]; +#endif + r = lzma_properties_decode(&filters[fi], NULL, + coder1->properties, coder1->propertiesSize); + if (r != LZMA_OK) { + set_error(a, r); + return (ARCHIVE_FAILED); + } + fi++; + + filters[fi].id = LZMA_VLI_UNKNOWN; + filters[fi].options = NULL; + r = lzma_raw_decoder(&(zip->lzstream), filters); +#if LZMA_VERSION < 50000030 + free(ff->options); +#endif + if (r != LZMA_OK) { + set_error(a, r); + return (ARCHIVE_FAILED); + } + zip->lzstream_valid = 1; + zip->lzstream.total_in = 0; + zip->lzstream.total_out = 0; + break; + } +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LZMA codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_BZ2: +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + if (zip->bzstream_valid) { + BZ2_bzDecompressEnd(&(zip->bzstream)); + zip->bzstream_valid = 0; + } + r = BZ2_bzDecompressInit(&(zip->bzstream), 0, 0); + if (r == BZ_MEM_ERROR) + r = BZ2_bzDecompressInit(&(zip->bzstream), 0, 1); + if (r != BZ_OK) { + int err = ARCHIVE_ERRNO_MISC; + const char *detail = NULL; + switch (r) { + case BZ_PARAM_ERROR: + detail = "invalid setup parameter"; + break; + case BZ_MEM_ERROR: + err = ENOMEM; + detail = "out of memory"; + break; + case BZ_CONFIG_ERROR: + detail = "mis-compiled library"; + break; + } + archive_set_error(&a->archive, err, + "Internal error initializing decompressor: %s", + detail == NULL ? "??" : detail); + zip->bzstream_valid = 0; + return (ARCHIVE_FAILED); + } + zip->bzstream_valid = 1; + zip->bzstream.total_in_lo32 = 0; + zip->bzstream.total_in_hi32 = 0; + zip->bzstream.total_out_lo32 = 0; + zip->bzstream.total_out_hi32 = 0; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "BZ2 codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_DEFLATE: +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) + r = inflateReset(&(zip->stream)); + else + r = inflateInit2(&(zip->stream), + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Couldn't initialize zlib stream."); + return (ARCHIVE_FAILED); + } + zip->stream_valid = 1; + zip->stream.total_in = 0; + zip->stream.total_out = 0; + break; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "DEFLATE codec is unsupported"); + return (ARCHIVE_FAILED); +#endif + case _7Z_PPMD: + { + unsigned order; + uint32_t msize; + + if (zip->ppmd7_valid) { + __archive_ppmd7_functions.Ppmd7_Free( + &zip->ppmd7_context, &g_szalloc); + zip->ppmd7_valid = 0; + } + + if (coder1->propertiesSize < 5) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed PPMd parameter"); + return (ARCHIVE_FAILED); + } + order = coder1->properties[0]; + msize = archive_le32dec(&(coder1->properties[1])); + if (order < PPMD7_MIN_ORDER || order > PPMD7_MAX_ORDER || + msize < PPMD7_MIN_MEM_SIZE || msize > PPMD7_MAX_MEM_SIZE) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed PPMd parameter"); + return (ARCHIVE_FAILED); + } + __archive_ppmd7_functions.Ppmd7_Construct(&zip->ppmd7_context); + r = __archive_ppmd7_functions.Ppmd7_Alloc( + &zip->ppmd7_context, msize, &g_szalloc); + if (r == 0) { + archive_set_error(&a->archive, ENOMEM, + "Coludn't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + __archive_ppmd7_functions.Ppmd7_Init( + &zip->ppmd7_context, order); + __archive_ppmd7_functions.Ppmd7z_RangeDec_CreateVTable( + &zip->range_dec); + zip->ppmd7_valid = 1; + zip->ppmd7_stat = 0; + zip->ppstream.overconsumed = 0; + zip->ppstream.total_in = 0; + zip->ppstream.total_out = 0; + break; + } + case _7Z_X86: + case _7Z_X86_BCJ2: + case _7Z_POWERPC: + case _7Z_IA64: + case _7Z_ARM: + case _7Z_ARMTHUMB: + case _7Z_SPARC: + case _7Z_DELTA: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unexpected codec ID: %lX", zip->codec); + return (ARCHIVE_FAILED); + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Unknown codec ID: %lX", zip->codec); + return (ARCHIVE_FAILED); + } + + return (ARCHIVE_OK); +} + +static int +decompress(struct archive_read *a, struct _7zip *zip, + void *buff, size_t *outbytes, const void *b, size_t *used) +{ + const uint8_t *t_next_in; + uint8_t *t_next_out; + size_t o_avail_in, o_avail_out; + size_t t_avail_in, t_avail_out; + uint8_t *bcj2_next_out; + size_t bcj2_avail_out; + int r, ret = ARCHIVE_OK; + + t_avail_in = o_avail_in = *used; + t_avail_out = o_avail_out = *outbytes; + t_next_in = b; + t_next_out = buff; + + if (zip->codec != _7Z_LZMA2 && zip->codec2 == _7Z_X86) { + int i; + for (i = 0; zip->odd_bcj_size > 0 && t_avail_out; i++) { + *t_next_out++ = zip->odd_bcj[i]; + t_avail_out--; + zip->odd_bcj_size--; + } + if (o_avail_in == 0 || t_avail_out == 0) { + *used = o_avail_in - t_avail_in; + *outbytes = o_avail_out - t_avail_out; + if (o_avail_in == 0) + ret = ARCHIVE_EOF; + return (ret); + } + } + + bcj2_next_out = t_next_out; + bcj2_avail_out = t_avail_out; + if (zip->codec2 == _7Z_X86_BCJ2) { + /* + * Decord a remaining decompressed main stream for BCJ2. + */ + if (zip->tmp_stream_bytes_remaining > 0) { + ssize_t bytes; + size_t remaining = zip->tmp_stream_bytes_remaining; + bytes = Bcj2_Decode(zip, t_next_out, t_avail_out); + if (bytes < 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "BCJ2 conversion Failed"); + return (ARCHIVE_FAILED); + } + zip->main_stream_bytes_remaining -= + remaining - zip->tmp_stream_bytes_remaining; + t_avail_out -= bytes; + if (o_avail_in == 0 || t_avail_out == 0) { + *used = 0; + *outbytes = o_avail_out - t_avail_out; + if (o_avail_in == 0) + ret = ARCHIVE_EOF; + return (ret); + } + t_next_out += bytes; + bcj2_next_out = t_next_out; + bcj2_avail_out = t_avail_out; + } + t_next_out = zip->tmp_stream_buff; + t_avail_out = zip->tmp_stream_buff_size; + } + + switch (zip->codec) { + case _7Z_COPY: + { + size_t bytes = + (t_avail_in > t_avail_out)?t_avail_out:t_avail_in; + + memcpy(t_next_out, t_next_in, bytes); + t_avail_in -= bytes; + t_avail_out -= bytes; + if (o_avail_in == 0) + ret = ARCHIVE_EOF; + break; + } +#ifdef HAVE_LZMA_H + case _7Z_LZMA: case _7Z_LZMA2: + zip->lzstream.next_in = t_next_in; + zip->lzstream.avail_in = t_avail_in; + zip->lzstream.next_out = t_next_out; + zip->lzstream.avail_out = t_avail_out; + + r = lzma_code(&(zip->lzstream), LZMA_RUN); + switch (r) { + case LZMA_STREAM_END: /* Found end of stream. */ + lzma_end(&(zip->lzstream)); + zip->lzstream_valid = 0; + ret = ARCHIVE_EOF; + break; + case LZMA_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Decompression failed(%d)", + r); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->lzstream.avail_in; + t_avail_out = zip->lzstream.avail_out; + break; +#endif +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + case _7Z_BZ2: + zip->bzstream.next_in = (char *)(uintptr_t)t_next_in; + zip->bzstream.avail_in = t_avail_in; + zip->bzstream.next_out = (char *)(uintptr_t)t_next_out; + zip->bzstream.avail_out = t_avail_out; + r = BZ2_bzDecompress(&(zip->bzstream)); + switch (r) { + case BZ_STREAM_END: /* Found end of stream. */ + switch (BZ2_bzDecompressEnd(&(zip->bzstream))) { + case BZ_OK: + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up decompressor"); + return (ARCHIVE_FAILED); + } + zip->bzstream_valid = 0; + ret = ARCHIVE_EOF; + break; + case BZ_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "bzip decompression failed"); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->bzstream.avail_in; + t_avail_out = zip->bzstream.avail_out; + break; +#endif +#ifdef HAVE_ZLIB_H + case _7Z_DEFLATE: + zip->stream.next_in = (Bytef *)(uintptr_t)t_next_in; + zip->stream.avail_in = t_avail_in; + zip->stream.next_out = t_next_out; + zip->stream.avail_out = t_avail_out; + r = inflate(&(zip->stream), 0); + switch (r) { + case Z_STREAM_END: /* Found end of stream. */ + ret = ARCHIVE_EOF; + break; + case Z_OK: /* Decompressor made some progress.*/ + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File decompression failed (%d)", r); + return (ARCHIVE_FAILED); + } + t_avail_in = zip->stream.avail_in; + t_avail_out = zip->stream.avail_out; + break; +#endif + case _7Z_PPMD: + { + uint64_t flush_bytes; + + if (!zip->ppmd7_valid || zip->ppmd7_stat < 0 || + t_avail_in < 0 || t_avail_out <= 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Decompression internal error"); + return (ARCHIVE_FAILED); + } + zip->ppstream.next_in = t_next_in; + zip->ppstream.avail_in = t_avail_in; + zip->ppstream.next_out = t_next_out; + zip->ppstream.avail_out = t_avail_out; + if (zip->ppmd7_stat == 0) { + zip->bytein.a = a; + zip->bytein.Read = &ppmd_read; + zip->range_dec.Stream = &zip->bytein; + r = __archive_ppmd7_functions.Ppmd7z_RangeDec_Init( + &(zip->range_dec)); + if (r == 0) { + zip->ppmd7_stat = -1; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to initialize PPMd range decorder"); + return (ARCHIVE_FAILED); + } + if (zip->ppstream.overconsumed) { + zip->ppmd7_stat = -1; + return (ARCHIVE_FAILED); + } + zip->ppmd7_stat = 1; + } + + if (t_avail_in == 0) + /* XXX Flush out remaining decoded data XXX */ + flush_bytes = zip->folder_outbytes_remaining; + else + flush_bytes = 0; + + do { + int sym; + + sym = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &(zip->ppmd7_context), &(zip->range_dec.p)); + if (sym < 0) { + zip->ppmd7_stat = -1; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Failed to decode PPMd"); + return (ARCHIVE_FAILED); + } + if (zip->ppstream.overconsumed) { + zip->ppmd7_stat = -1; + return (ARCHIVE_FAILED); + } + *zip->ppstream.next_out++ = (unsigned char)sym; + zip->ppstream.avail_out--; + zip->ppstream.total_out++; + if (flush_bytes) + flush_bytes--; + } while (zip->ppstream.avail_out && + (zip->ppstream.avail_in || flush_bytes)); + + t_avail_in = zip->ppstream.avail_in; + t_avail_out = zip->ppstream.avail_out; + break; + } + default: + archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC, + "Decompression internal error"); + return (ARCHIVE_FAILED); + } + if (ret != ARCHIVE_OK && ret != ARCHIVE_EOF) + return (ret); + + *used = o_avail_in - t_avail_in; + *outbytes = o_avail_out - t_avail_out; + + /* + * Decord BCJ. + */ + if (zip->codec != _7Z_LZMA2 && zip->codec2 == _7Z_X86) { + size_t l = x86_Convert(buff, *outbytes, 0, &(zip->bcj_state)); + zip->odd_bcj_size = *outbytes - l; + if (zip->odd_bcj_size > 0 && zip->odd_bcj_size <= 4 && + o_avail_in && ret != ARCHIVE_EOF) { + memcpy(zip->odd_bcj, ((unsigned char *)buff) + l, + zip->odd_bcj_size); + *outbytes = l; + } else + zip->odd_bcj_size = 0; + } + + /* + * Decord BCJ2 with a decompressed main stream. + */ + if (zip->codec2 == _7Z_X86_BCJ2) { + ssize_t bytes; + + zip->tmp_stream_bytes_avail = + zip->tmp_stream_buff_size - t_avail_out; + if (zip->tmp_stream_bytes_avail > + zip->main_stream_bytes_remaining) + zip->tmp_stream_bytes_avail = + zip->main_stream_bytes_remaining; + zip->tmp_stream_bytes_remaining = zip->tmp_stream_bytes_avail; + bytes = Bcj2_Decode(zip, bcj2_next_out, bcj2_avail_out); + if (bytes < 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "BCJ2 conversion Failed"); + return (ARCHIVE_FAILED); + } + zip->main_stream_bytes_remaining -= + zip->tmp_stream_bytes_avail + - zip->tmp_stream_bytes_remaining; + bcj2_avail_out -= bytes; + *outbytes = o_avail_out - bcj2_avail_out; + } + + return (ret); +} + +static int +free_decompression(struct archive_read *a, struct _7zip *zip) +{ + int r = ARCHIVE_OK; + +#ifdef HAVE_LZMA_H + if (zip->lzstream_valid) + lzma_end(&(zip->lzstream)); +#endif +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + if (zip->bzstream_valid) { + if (BZ2_bzDecompressEnd(&(zip->bzstream)) != BZ_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up bzip2 decompressor"); + r = ARCHIVE_FATAL; + } + zip->bzstream_valid = 0; + } +#endif +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) { + if (inflateEnd(&(zip->stream)) != Z_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up zlib decompressor"); + r = ARCHIVE_FATAL; + } + zip->stream_valid = 0; + } +#endif + if (zip->ppmd7_valid) { + __archive_ppmd7_functions.Ppmd7_Free( + &zip->ppmd7_context, &g_szalloc); + zip->ppmd7_valid = 0; + } + return (r); +} + +static int +parse_7zip_uint64(const unsigned char *p, size_t len, uint64_t *val) +{ + const unsigned char *_p = p; + unsigned char avail, mask; + int i; + + if (len-- == 0) + return (-1); + avail = *p++; + mask = 0x80; + *val = 0; + for (i = 0; i < 8; i++) { + if (avail & mask) { + if (len-- == 0) + return (-1); + *val |= ((uint64_t)*p++) << (8 * i); + mask >>= 1; + continue; + } + *val += (avail & (mask -1)) << (8 * i); + break; + } + return (p - _p); +} + +static int +read_Bools(unsigned char *data, size_t num, const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + unsigned i, mask = 0, avail; + + for (i = 0; i < num; i++) { + if (mask == 0) { + if (len == 0) + return (-1); + avail = *p++; + len--; + mask = 0x80; + } + data[i] = (avail & mask)?1:0; + mask >>= 1; + } + return (p - _p); +} + +static void +free_Digest(struct _7z_digests *d) +{ + free(d->defineds); + free(d->digests); +} + +static int +read_Digests(struct _7z_digests *d, size_t num, const unsigned char *p, + size_t len) +{ + const unsigned char *_p = p; + unsigned i; + + memset(d, 0, sizeof(*d)); + + if (len == 0) + return (-1); + + d->defineds = malloc(num); + if (d->defineds == NULL) + return (-1); + /* + * Read Bools. + */ + len--; + if (*p++ == 0) { + int r = read_Bools(d->defineds, num, p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + } else + /* All are defined */ + memset(d->defineds, 1, num); + + d->digests = calloc(num, sizeof(*d->digests)); + if (d->digests == NULL) + return (-1); + if (len < 4 * num) + return (-1); + for (i = 0; i < num; i++) { + if (d->defineds[i]) { + d->digests[i] = archive_le32dec(p); + p += 4; + len -= 4; + } + } + + return (p - _p); +} + +static void +free_PackInfo(struct _7z_pack_info *pi) +{ + free(pi->sizes); + free(pi->positions); + free_Digest(&(pi->digest)); +} + +static int +read_PackInfo(struct _7z_pack_info *pi, const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + unsigned i; + int r; + + memset(pi, 0, sizeof(*pi)); + + if (len < 3 || *p++ != kPackInfo) + return (-1); + --len; + + /* + * Read PackPos. + */ + r = parse_7zip_uint64(p, len, &(pi->pos)); + if (r < 0) + return (r); + p += r; + len -= r; + + /* + * Read NumPackStreams. + */ + r = parse_7zip_uint64(p, len, &(pi->numPackStreams)); + if (r < 0 || pi->numPackStreams == 0) + return (r); + p += r; + len -= r; + + /* + * Read PackSizes[num] + */ + if (len >= 1 && *p == kEnd) + /* PackSizes[num] are not present. */ + return (p - _p + 1); + if (len < 1 + pi->numPackStreams || *p++ != kSize) + return (-1); + --len; + pi->sizes = calloc(pi->numPackStreams, sizeof(uint64_t)); + pi->positions = calloc(pi->numPackStreams, sizeof(uint64_t)); + if (pi->sizes == NULL || pi->positions == NULL) + return (-1); + + for (i = 0; i < pi->numPackStreams; i++) { + r = parse_7zip_uint64(p, len, &(pi->sizes[i])); + if (r < 0) + return (-1); + p += r; + len -= r; + } + + /* + * Read PackStreamDigests[num] + */ + if (len >= 1 && *p == kEnd) { + /* PackStreamDigests[num] are not present. */ + pi->digest.defineds = + calloc(pi->numPackStreams, sizeof(*pi->digest.defineds)); + pi->digest.digests = + calloc(pi->numPackStreams, sizeof(*pi->digest.digests)); + if (pi->digest.defineds == NULL || pi->digest.digests == NULL) + return (-1); + return (p - _p + 1); + } + + if (len < 1 + pi->numPackStreams || *p++ != kSize) + return (-1); + --len; + + r = read_Digests(&(pi->digest), pi->numPackStreams, p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + + /* + * Must be marked by kEnd. + */ + if (len == 0 || *p++ != kEnd) + return (-1); + return (p - _p); +} + +static void +free_Folder(struct _7z_folder *f) +{ + unsigned i; + + if (f->coders) { + for (i = 0; i< f->numCoders; i++) { + free(f->coders[i].properties); + } + free(f->coders); + } + free(f->bindPairs); + free(f->packedStreams); + free(f->unPackSize); +} + +static int +read_Folder(struct _7z_folder *f, const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + uint64_t numInStreamsTotal = 0; + uint64_t numOutStreamsTotal = 0; + int r; + unsigned i; + + memset(f, 0, sizeof(*f)); + + /* + * Read NumCoders. + */ + r = parse_7zip_uint64(p, len, &(f->numCoders)); + if (r < 0) + return (-1); + p += r; + len -= r; + + f->coders = calloc(f->numCoders, sizeof(*f->coders)); + if (f->coders == NULL) + return (-1); + for (i = 0; i< f->numCoders; i++) { + size_t codec_size; + int simple, attr; + + if (len == 0) + return (-1); + /* + * 0:3 CodecIdSize + * 4: 0 - IsSimple + * 1 - Is not Simple + * 5: 0 - No Attributes + * 1 - There are Attributes; + * 7: Must be zero. + */ + codec_size = *p & 0xf; + simple = (*p & 0x10)?0:1; + attr = *p & 0x20; + if (*p & 0x80) + return (-1);/* Not supported. */ + p++; + len--; + + /* + * Read Decompression Method IDs. + */ + if (len < codec_size) + return (-1); + + f->coders[i].codec = decode_codec_id(p, codec_size); + p += codec_size; + len -= codec_size; + + if (simple) { + f->coders[i].numInStreams = 1; + f->coders[i].numOutStreams = 1; + } else { + r = parse_7zip_uint64(p, len, + &(f->coders[i].numInStreams)); + if (r < 0) + return (-1); + p += r; + len -= r; + r = parse_7zip_uint64(p, len, + &(f->coders[i].numOutStreams)); + if (r < 0) + return (-1); + p += r; + len -= r; + } + + if (attr) { + r = parse_7zip_uint64(p, len, + &(f->coders[i].propertiesSize)); + if (r < 0) + return (-1); + p += r; + len -= r; + + if (len < f->coders[i].propertiesSize) + return (-1); + f->coders[i].properties = + malloc(f->coders[i].propertiesSize); + if (f->coders[i].properties == NULL) + return (-1); + memcpy(f->coders[i].properties, p, + f->coders[i].propertiesSize); + p += f->coders[i].propertiesSize; + len -= f->coders[i].propertiesSize; + } + + numInStreamsTotal += f->coders[i].numInStreams; + numOutStreamsTotal += f->coders[i].numOutStreams; + } + + if (numOutStreamsTotal == 0 || + numInStreamsTotal < numOutStreamsTotal-1) + return (-1); + + f->numBindPairs = numOutStreamsTotal - 1; + f->bindPairs = calloc(f->numBindPairs, sizeof(*f->bindPairs)); + if (f->bindPairs == NULL) + return (-1); + for (i = 0; i < f->numBindPairs; i++) { + r = parse_7zip_uint64(p, len, &(f->bindPairs[i].inIndex)); + if (r < 0) + return (-1); + p += r; + len -= r; + r = parse_7zip_uint64(p, len, &(f->bindPairs[i].outIndex)); + if (r < 0) + return (-1); + p += r; + len -= r; + } + + f->numPackedStreams = numInStreamsTotal - f->numBindPairs; + f->packedStreams = + calloc(f->numPackedStreams, sizeof(*f->packedStreams)); + if (f->packedStreams == NULL) + return (-1); + if (f->numPackedStreams == 1) { + for (i = 0; i < numInStreamsTotal; i++) { + unsigned j; + for (j = 0; j < f->numBindPairs; j++) { + if (f->bindPairs[j].inIndex == i) + break; + } + if (j == f->numBindPairs) + break; + } + if (i == numInStreamsTotal) + return (-1); + f->packedStreams[0] = i; + } else { + for (i = 0; i < f->numPackedStreams; i++) { + r = parse_7zip_uint64(p, len, &(f->packedStreams[i])); + if (r < 0) + return (-1); + p += r; + len -= r; + } + } + f->numInStreams = numInStreamsTotal; + f->numOutStreams = numOutStreamsTotal; + + return (p - _p); +} + +static void +free_CodersInfo(struct _7z_coders_info *ci) +{ + unsigned i; + + if (ci->folders) { + for (i = 0; i < ci->numFolders; i++) + free_Folder(&(ci->folders[i])); + free(ci->folders); + } +} + +static int +read_CodersInfo(struct _7z_coders_info *ci, const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + struct _7z_digests digest; + unsigned i, external; + int r; + + memset(ci, 0, sizeof(*ci)); + memset(&digest, 0, sizeof(digest)); + + if (len < 3 || *p++ != kUnPackInfo) + goto failed; + --len; + + if (len < 3 || *p++ != kFolder) + goto failed; + --len; + + /* + * Read NumFolders. + */ + r = parse_7zip_uint64(p, len, &(ci->numFolders)); + if (r < 0) + goto failed; + p += r; + len -= r; + + /* + * Read External. + */ + if (len == 0) + goto failed; + external = *p++; + len --; + switch (external) { + case 0: + ci->folders = calloc(ci->numFolders, sizeof(*ci->folders)); + if (ci->folders == NULL) + return (-1); + for (i = 0; i < ci->numFolders; i++) { + r = read_Folder(&(ci->folders[i]), p, len); + if (r < 0) + goto failed; + p += r; + len -= r; + } + break; + case 1: + r = parse_7zip_uint64(p, len, &(ci->dataStreamIndex)); + if (r < 0) + return (r); + p += r; + len -= r; + break; + } + + if (len < 1 + ci->numFolders || *p++ != kCodersUnPackSize) + goto failed; + --len; + + for (i = 0; i < ci->numFolders; i++) { + struct _7z_folder *folder = &(ci->folders[i]); + unsigned j; + + folder->unPackSize = + calloc(folder->numOutStreams, sizeof(*folder->unPackSize)); + if (folder->unPackSize == NULL) + goto failed; + for (j = 0; j < folder->numOutStreams; j++) { + r = parse_7zip_uint64(p, len, + &(folder->unPackSize[j])); + if (r < 0) + goto failed; + p += r; + len -= r; + } + } + + /* + * Read CRCs. + */ + if (len == 0) + goto failed; + if (*p == kEnd) + return (p - _p + 1); + if (len < 1 + ci->numFolders || *p++ != kCRC) + goto failed; + --len; + r = read_Digests(&digest, ci->numFolders, p, len); + if (r < 0) + goto failed; + p += r; + len -= r; + for (i = 0; i < ci->numFolders; i++) { + ci->folders[i].digest_defined = digest.defineds[i]; + ci->folders[i].digest = digest.digests[i]; + } + + /* + * Must be kEnd. + */ + if (len == 0 || *p++ != kEnd) + goto failed; + free_Digest(&digest); + return (p - _p); +failed: + free_Digest(&digest); + return (-1); +} + +static uint64_t +folder_uncompressed_size(struct _7z_folder *f) +{ + int n = f->numOutStreams; + unsigned pairs = f->numBindPairs; + + while (--n >= 0) { + unsigned i; + for (i = 0; i < pairs; i++) { + if (f->bindPairs[i].outIndex == n) + break; + } + if (i >= pairs) + return (f->unPackSize[n]); + } + return (0); +} + +static void +free_SubStreamsInfo(struct _7z_substream_info *ss) +{ + free(ss->unpackSizes); + free(ss->digestsDefined); + free(ss->digests); +} + +static int +read_SubStreamsInfo(struct _7z_substream_info *ss, struct _7z_folder *f, + size_t numFolders, const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + uint64_t *usizes; + size_t unpack_streams; + int r, type; + unsigned i; + uint32_t numDigests; + + memset(ss, 0, sizeof(*ss)); + + if (len < 2 || *p++ != kSubStreamsInfo) + return (-1); + --len; + + for (i = 0; i < numFolders; i++) + f[i].numUnpackStreams = 1; + + if (len < 1) + return (-1); + type = *p++; + --len; + + if (type == kNumUnPackStream) { + unpack_streams = 0; + for (i = 0; i < numFolders; i++) { + r = parse_7zip_uint64(p, len, &(f[i].numUnpackStreams)); + if (r < 0) + return (-1); + p += r; + len -= r; + unpack_streams += f[i].numUnpackStreams; + } + if (len < 1) + return (-1); + type = *p++; + --len; + } else + unpack_streams = numFolders; + + ss->unpack_streams = unpack_streams; + if (unpack_streams) { + ss->unpackSizes = calloc(unpack_streams, + sizeof(*ss->unpackSizes)); + ss->digestsDefined = calloc(unpack_streams, + sizeof(*ss->digestsDefined)); + ss->digests = calloc(unpack_streams, + sizeof(*ss->digests)); + if (ss->unpackSizes == NULL || ss->digestsDefined == NULL || + ss->digests == NULL) + return (-1); + } + + usizes = ss->unpackSizes; + for (i = 0; i < numFolders; i++) { + unsigned pack; + uint64_t sum; + + if (f[i].numUnpackStreams == 0) + continue; + + sum = 0; + if (type == kSize) { + for (pack = 1; pack < f[i].numUnpackStreams; pack++) { + r = parse_7zip_uint64(p, len, usizes); + if (r < 0) + return (-1); + p += r; + len -= r; + sum += *usizes++; + } + } + *usizes++ = folder_uncompressed_size(&f[i]) - sum; + } + + if (type == kSize) { + if (len < 1) + return (-1); + type = *p++; + --len; + } + + for (i = 0; i < unpack_streams; i++) { + ss->digestsDefined[i] = 0; + ss->digests[i] = 0; + } + + numDigests = 0; + for (i = 0; i < numFolders; i++) { + if (f[i].numUnpackStreams != 1 || + !f[i].digest_defined) + numDigests += f[i].numUnpackStreams; + } + + if (type == kCRC) { + struct _7z_digests tmpDigests; + unsigned char *digestsDefined = ss->digestsDefined; + uint32_t * digests = ss->digests; + int di = 0; + + memset(&tmpDigests, 0, sizeof(tmpDigests)); + r = read_Digests(&(tmpDigests), numDigests, p, len); + if (r < 0) { + free_Digest(&tmpDigests); + return (-1); + } + p += r; + len -= r; + for (i = 0; i < numFolders; i++) { + if (f[i].numUnpackStreams == 1 && f[i].digest_defined) { + *digestsDefined++ = 1; + *digests++ = f[i].digest; + } else { + unsigned j; + + for (j = 0; j < f[i].numUnpackStreams; + j++, di++) { + *digestsDefined++ = + tmpDigests.defineds[di]; + *digests++ = + tmpDigests.digests[di]; + } + } + } + free_Digest(&tmpDigests); + if (len < 1) + return (-1); + type = *p++; + --len; + } + + /* + * Must be kEnd. + */ + if (type != kEnd) + return (-1); + return (p - _p); +} + +static void +free_StreamsInfo(struct _7z_stream_info *si) +{ + free_PackInfo(&(si->pi)); + free_CodersInfo(&(si->ci)); + free_SubStreamsInfo(&(si->ss)); +} + +static int +read_StreamsInfo(struct _7zip *zip, struct _7z_stream_info *si, + const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + unsigned i; + int r; + + memset(si, 0, sizeof(*si)); + + if (len > 0 && *p == kPackInfo) { + uint64_t packPos; + + r = read_PackInfo(&(si->pi), p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + + /* + * Calculate packed stream positions. + */ + packPos = si->pi.pos; + for (i = 0; i < si->pi.numPackStreams; i++) { + si->pi.positions[i] = packPos; + packPos += si->pi.sizes[i]; + if (packPos > zip->header_offset) + return (-1); + } + } + if (len > 0 && *p == kUnPackInfo) { + uint32_t packIndex; + struct _7z_folder *f; + + r = read_CodersInfo(&(si->ci), p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + + /* + * Calculate packed stream indexes. + */ + packIndex = 0; + f = si->ci.folders; + for (i = 0; i < si->ci.numFolders; i++) { + f[i].packIndex = packIndex; + packIndex += f[i].numPackedStreams; + if (packIndex > si->pi.numPackStreams) + return (-1); + } + } + if (len > 0 && *p == kSubStreamsInfo) { + r = read_SubStreamsInfo(&(si->ss), + si->ci.folders, si->ci.numFolders, p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + } + + /* + * Must be kEnd. + */ + if (len == 0 || *p++ != kEnd) + return (-1); + return (p - _p); +} + +static void +free_Header(struct _7z_header_info *h) +{ + free(h->emptyStreamBools); + free(h->emptyFileBools); + free(h->antiBools); + free(h->attrBools); +} + +static int +read_Header(struct _7zip *zip, struct _7z_header_info *h, + const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + struct _7z_folder *folders; + struct _7z_stream_info *si = &(zip->si); + struct _7zip_entry *entries; + uint32_t folderIndex, indexInFolder; + unsigned i; + int eindex, empty_streams, r, sindex; + + if (len < 2 || *p++ != kHeader) + return (-1); + len--; + + /* + * Read ArchiveProperties. + */ + if (*p == kArchiveProperties) { + p++; + len--; + + for (;;) { + uint64_t size; + int atype = *p++; + len--; + if (atype == 0) + break; + r = parse_7zip_uint64(p, len, &size); + if (r < 0 || len < r + size) + return (-1); + p += r + size; + len -= r + size; + } + } + + /* + * Read MainStreamsInfo. + */ + if (*p == kMainStreamsInfo) { + p++; + len--; + r = read_StreamsInfo(zip, &(zip->si), p, len); + if (r < 0) + return (-1); + p += r; + len -= r; + } + if (len == 0) + return (-1); + if (*p == kEnd) + return (p - _p + 1); + + /* + * Read FilesInfo. + */ + if (len < 2 || *p++ != kFilesInfo) + return (-1); + len--; + + r = parse_7zip_uint64(p, len, &(zip->numFiles)); + if (r < 0) + return (-1); + p += r; + len -= r; + + zip->entries = calloc(zip->numFiles, sizeof(*zip->entries)); + if (zip->entries == NULL) + return (-1); + entries = zip->entries; + + empty_streams = 0; + for (;;) { + int type; + uint64_t size; + size_t ll; + + if (len < 1) + return (-1); + type = *p++; + len--; + if (type == kEnd) + break; + + r = parse_7zip_uint64(p, len, &size); + if (r < 0 || len < size) + return (-1); + p += r; + len -= r; + ll = (size_t)size; + len -= ll; + + switch (type) { + case kEmptyStream: + h->emptyStreamBools = calloc(zip->numFiles, + sizeof(*h->emptyStreamBools)); + if (h->emptyStreamBools == NULL) + return (-1); + r = read_Bools(h->emptyStreamBools, zip->numFiles, + p, ll); + if (r < 0) + return (-1); + p += r; + ll -= r; + empty_streams = 0; + for (i = 0; i < zip->numFiles; i++) { + if (h->emptyStreamBools[i]) + empty_streams++; + } + break; + case kEmptyFile: + h->emptyFileBools = calloc(empty_streams, + sizeof(*h->emptyFileBools)); + if (h->emptyFileBools == NULL) + return (-1); + r = read_Bools(h->emptyFileBools, empty_streams, + p, len); + if (r < 0) + return (-1); + p += r; + ll -= r; + break; + case kAnti: + h->antiBools = calloc(empty_streams, + sizeof(*h->antiBools)); + if (h->antiBools == NULL) + return (-1); + r = read_Bools(h->antiBools, empty_streams, p, len); + if (r < 0) + return (-1); + p += r; + ll -= r; + break; + case kCTime: + case kATime: + case kMTime: + r = read_Times(zip, h, type, p, ll); + if (r < 0) + return (-1); + p += r; + ll -= r; + break; + case kName: + { + unsigned char *np; + size_t nl; + + if (ll < 1) + return (-1); + p++; ll--;/* Skip one byte. */ + if ((ll & 1) || ll < zip->numFiles * 4) + return (-1); + + zip->entry_names = malloc(ll); + if (zip->entry_names == NULL) + return (-1); + memcpy(zip->entry_names, p, ll); + np = zip->entry_names; + nl = ll; + + for (i = 0; i < zip->numFiles; i++) { + entries[i].utf16name = np; +#if defined(_WIN32) && !defined(__CYGWIN__) && defined(_DEBUG) + entries[i].wname = (wchar_t *)np; +#endif + + /* Find a terminator. */ + while (nl >= 2 && (np[0] || np[1])) { + np += 2; + nl -= 2; + } + if (nl < 2) + return (-1);/* Terminator not found */ + entries[i].name_len = np - entries[i].utf16name; + np += 2; + nl -= 2; + } + break; + } + case kAttributes: + { + int allAreDefined; + + if (ll < 2) + return (-1); + allAreDefined = *p++; + --ll; + p++; --ll;/* Skip one byte. */ + h->attrBools = calloc(zip->numFiles, + sizeof(*h->attrBools)); + if (h->attrBools == NULL) + return (-1); + if (allAreDefined) + memset(h->attrBools, 1, zip->numFiles); + else { + r = read_Bools(h->attrBools, + zip->numFiles, p, ll); + if (r < 0) + return (-1); + p += r; + ll -= r; + } + for (i = 0; i < zip->numFiles; i++) { + if (h->attrBools[i]) { + if (ll < 4) + return (-1); + entries[i].attr = archive_le32dec(p); + p += 4; + ll -= 4; + } + } + break; + } + default: + break; + } + /* Skip remaining data. */ + p += ll; + } + + /* + * Set up entry's attributes. + */ + folders = si->ci.folders; + eindex = sindex = 0; + folderIndex = indexInFolder = 0; + for (i = 0; i < zip->numFiles; i++) { + if (h->emptyStreamBools == NULL || + h->emptyStreamBools[i] == 0) + entries[i].flg |= HAS_STREAM; + /* The high 16 bits of attributes is a posix file mode. */ + entries[i].mode = entries[i].attr >> 16; + if (entries[i].flg & HAS_STREAM) { + if ((size_t)sindex >= si->ss.unpack_streams) + return (-1); + if (entries[i].mode == 0) + entries[i].mode = AE_IFREG | 0777; + if (si->ss.digestsDefined[sindex]) + entries[i].flg |= CRC32_IS_SET; + entries[i].ssIndex = sindex; + sindex++; + } else { + int dir; + if (h->emptyFileBools == NULL) + dir = 1; + else { + if (h->emptyFileBools[eindex]) + dir = 0; + else + dir = 1; + eindex++; + } + if (entries[i].mode == 0) { + if (dir) + entries[i].mode = AE_IFDIR | 0777; + else + entries[i].mode = AE_IFREG | 0777; + } else if (dir && + (entries[i].mode & AE_IFMT) != AE_IFDIR) { + entries[i].mode &= ~AE_IFMT; + entries[i].mode |= AE_IFDIR; + } + if ((entries[i].mode & AE_IFMT) == AE_IFDIR && + entries[i].name_len >= 2 && + (entries[i].utf16name[entries[i].name_len-2] != '/' || + entries[i].utf16name[entries[i].name_len-1] != 0)) { + entries[i].utf16name[entries[i].name_len] = '/'; + entries[i].utf16name[entries[i].name_len+1] = 0; + entries[i].name_len += 2; + } + entries[i].ssIndex = -1; + } + if (entries[i].attr & 0x01) + entries[i].mode &= ~0222;/* Read only. */ + + if ((entries[i].flg & HAS_STREAM) == 0 && indexInFolder == 0) { + /* + * The entry is an empty file or a directory file, + * those both have no contents. + */ + entries[i].folderIndex = -1; + continue; + } + if (indexInFolder == 0) { + for (;;) { + if (folderIndex >= si->ci.numFolders) + return (-1); + if (folders[folderIndex].numUnpackStreams) + break; + folderIndex++; + } + } + entries[i].folderIndex = folderIndex; + if ((entries[i].flg & HAS_STREAM) == 0) + continue; + indexInFolder++; + if (indexInFolder >= folders[folderIndex].numUnpackStreams) { + folderIndex++; + indexInFolder = 0; + } + } + + return (p - _p); +} + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) +static void +fileTimeToUtc(uint64_t fileTime, time_t *time, long *ns) +{ + + if (fileTime >= EPOC_TIME) { + fileTime -= EPOC_TIME; + /* milli seconds base */ + *time = (time_t)(fileTime / 10000000); + /* nano seconds base */ + *ns = (long)(fileTime % 10000000) * 100; + } else { + *time = 0; + *ns = 0; + } +} + +static int +read_Times(struct _7zip *zip, struct _7z_header_info *h, int type, + const unsigned char *p, size_t len) +{ + const unsigned char *_p = p; + struct _7zip_entry *entries = zip->entries; + unsigned char *timeBools; + int r; + int allAreDefined, external; + unsigned i; + + timeBools = calloc(zip->numFiles, sizeof(*timeBools)); + if (timeBools == NULL) + return (-1); + + if (len < 1) + goto failed; + allAreDefined = *p++; + len--; + if (allAreDefined) + memset(timeBools, 1, zip->numFiles); + else { + r = read_Bools(timeBools, zip->numFiles, p, len); + if (r < 0) + goto failed; + p += r; + len -= r; + } + + if (len < 1) + goto failed; + external = *p++; + len--; + if (external) { + r = parse_7zip_uint64(p, len, &(h->dataIndex)); + if (r < 0) + goto failed; + p += r; + len -= r; + } + + for (i = 0; i < zip->numFiles; i++) { + if (!timeBools[i]) + continue; + if (len < 8) + goto failed; + switch (type) { + case kCTime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].ctime), + &(entries[i].ctime_ns)); + entries[i].flg |= CTIME_IS_SET; + break; + case kATime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].atime), + &(entries[i].atime_ns)); + entries[i].flg |= ATIME_IS_SET; + break; + case kMTime: + fileTimeToUtc(archive_le64dec(p), + &(entries[i].mtime), + &(entries[i].mtime_ns)); + entries[i].flg |= MTIME_IS_SET; + break; + } + p += 8; + len -= 8; + } + + free(timeBools); + return (p - _p); +failed: + free(timeBools); + return (-1); +} + +static ssize_t +decode_header_image(struct archive_read *a, struct _7zip *zip, + struct _7z_stream_info *si, const unsigned char *p, uint64_t len, + const void **image) +{ + const unsigned char *v; + size_t vsize; + int r; + + errno = 0; + r = read_StreamsInfo(zip, si, p, len); + if (r < 0) { + if (errno == ENOMEM) + archive_set_error(&a->archive, -1, + "Couldn't allocate memory"); + else + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + if (si->pi.numPackStreams == 0 || si->ci.numFolders == 0) { + archive_set_error(&a->archive, -1, "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + if (zip->header_offset < si->pi.pos + si->pi.sizes[0] || + (int64_t)(si->pi.pos + si->pi.sizes[0]) < 0 || + si->pi.sizes[0] == 0 || (int64_t)si->pi.pos < 0) { + archive_set_error(&a->archive, -1, "Malformed Header offset"); + return (ARCHIVE_FATAL); + } + + r = setup_decode_folder(a, si->ci.folders, 1); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Get an uncompressed header size. */ + vsize = (size_t)zip->folder_outbytes_remaining; + + /* + * Allocate an uncompressed buffer for the header image. + */ + zip->uncompressed_buffer_size = 64 * 1024; + if (vsize > zip->uncompressed_buffer_size) + zip->uncompressed_buffer_size = vsize; + zip->uncompressed_buffer = malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + + /* Get the bytes we can read to decode the header. */ + zip->pack_stream_inbytes_remaining = si->pi.sizes[0]; + + /* Seek the read point. */ + if (__archive_read_seek(a, si->pi.pos + zip->seek_base, SEEK_SET) < 0) + return (ARCHIVE_FATAL); + zip->header_offset = si->pi.pos; + + /* Extract a pack stream. */ + r = extract_pack_stream(a); + if (r < 0) + return (r); + for (;;) { + ssize_t bytes; + + bytes = get_uncompressed_data(a, image, vsize); + if (bytes < 0) + return (r); + if (bytes != vsize) { + if (*image != zip->uncompressed_buffer) { + /* This might happen if the coder was COPY. + * We have to make sure we read a full plain + * header image. */ + if (NULL==__archive_read_ahead(a, vsize, NULL)) + return (ARCHIVE_FATAL); + continue; + } else { + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive file"); + return (ARCHIVE_FATAL); + } + } + break; + } + v = *image; + + /* Clean up variables which will not be used for decoding the + * archive header */ + zip->pack_stream_remaining = 0; + zip->pack_stream_index = 0; + zip->folder_outbytes_remaining = 0; + zip->uncompressed_buffer_bytes_remaining = 0; + zip->pack_stream_bytes_unconsumed = 0; + + /* Check the header CRC. */ + if (si->ci.folders[0].digest_defined){ + uint32_t c = crc32(0, v, vsize); + if (c != si->ci.folders[0].digest) { + archive_set_error(&a->archive, -1, "Header CRC error"); + return (ARCHIVE_FATAL); + } + } + return ((ssize_t)vsize); +} + +static int +slurp_central_directory(struct archive_read *a, struct _7zip *zip, + struct _7z_header_info *header) +{ + const unsigned char *p; + const void *image; + uint64_t len; + uint64_t next_header_offset; + uint64_t next_header_size; + uint32_t next_header_crc; + ssize_t bytes_avail, image_bytes; + int r; + + if ((p = __archive_read_ahead(a, 32, &bytes_avail)) == NULL) + return (ARCHIVE_FATAL); + + if ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0) { + /* This is an executable ? Must be self-extracting... */ + r = skip_sfx(a, bytes_avail); + if (r < ARCHIVE_WARN) + return (r); + if ((p = __archive_read_ahead(a, 32, NULL)) == NULL) + return (ARCHIVE_FATAL); + } + zip->seek_base += 32; + + if (memcmp(p, _7ZIP_SIGNATURE, 6) != 0) { + archive_set_error(&a->archive, -1, "Not 7-Zip archive file"); + return (ARCHIVE_FATAL); + } + + /* CRC check. */ + if (crc32(0, (unsigned char *)p + 12, 20) != archive_le32dec(p + 8)) { + archive_set_error(&a->archive, -1, "Header CRC error"); + return (ARCHIVE_FATAL); + } + + next_header_offset = archive_le64dec(p + 12); + next_header_size = archive_le64dec(p + 20); + next_header_crc = archive_le32dec(p + 28); + + if (next_header_size == 0) + /* There is no entry in an archive file. */ + return (ARCHIVE_EOF); + + if (((int64_t)next_header_offset) < 0) { + archive_set_error(&a->archive, -1, "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + if (__archive_read_seek(a, next_header_offset + zip->seek_base, + SEEK_SET) < 0) + return (ARCHIVE_FATAL); + zip->header_offset = next_header_offset; + + if ((p = __archive_read_ahead(a, next_header_size, NULL)) == NULL) + return (ARCHIVE_FATAL); + + if (crc32(0, p, next_header_size) != next_header_crc) { + archive_set_error(&a->archive, -1, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + + len = next_header_size; + /* Parse ArchiveProperties. */ + switch (p[0]) { + case kEncodedHeader: + p++; + len--; + + /* + * The archive has an encoded header and we have to decode it + * in order to parse the header correctly. + */ + image_bytes = + decode_header_image(a, zip, &(zip->si), p, len, &image); + free_StreamsInfo(&(zip->si)); + memset(&(zip->si), 0, sizeof(zip->si)); + if (image_bytes < 0) + return (ARCHIVE_FATAL); + p = image; + len = image_bytes; + /* FALL THROUGH */ + case kHeader: + /* + * Parse the header. + */ + errno = 0; + r = read_Header(zip, header, p, len); + if (r < 0) { + if (errno == ENOMEM) + archive_set_error(&a->archive, -1, + "Couldn't allocate memory"); + else + archive_set_error(&a->archive, -1, + "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + if (len - r == 0 || p[r] != kEnd) { + archive_set_error(&a->archive, -1, + "Malformed 7-Zip archive"); + return (ARCHIVE_FATAL); + } + break; + default: + archive_set_error(&a->archive, -1, + "Unexpected Property ID = %X", p[0]); + return (ARCHIVE_FATAL); + } + zip->stream_offset = -1; + + /* + * If the uncompressed buffer was allocated more than 64K for + * the header image, release it. + */ + if (zip->uncompressed_buffer != NULL && + zip->uncompressed_buffer_size != 64 * 1024) { + free(zip->uncompressed_buffer); + zip->uncompressed_buffer = NULL; + zip->uncompressed_buffer_size = 0; + } + + return (ARCHIVE_OK); +} + +static ssize_t +get_uncompressed_data(struct archive_read *a, const void **buff, size_t size) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + ssize_t bytes_avail; + + if (zip->codec == _7Z_COPY && zip->codec2 == -1) { + /* Copy mode. */ + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + *buff = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file data"); + return (ARCHIVE_FATAL); + } + if ((size_t)bytes_avail > + zip->uncompressed_buffer_bytes_remaining) + bytes_avail = (ssize_t) + zip->uncompressed_buffer_bytes_remaining; + if ((size_t)bytes_avail > size) + bytes_avail = (ssize_t)size; + + zip->pack_stream_bytes_unconsumed = bytes_avail; + } else if (zip->uncompressed_buffer_pointer == NULL) { + /* Decompression has failed. */ + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } else { + /* Packed mode. */ + if (size > zip->uncompressed_buffer_bytes_remaining) + bytes_avail = (ssize_t) + zip->uncompressed_buffer_bytes_remaining; + else + bytes_avail = (ssize_t)size; + *buff = zip->uncompressed_buffer_pointer; + zip->uncompressed_buffer_pointer += bytes_avail; + } + zip->uncompressed_buffer_bytes_remaining -= bytes_avail; + return (bytes_avail); +} + +static ssize_t +extract_pack_stream(struct archive_read *a) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + ssize_t bytes_avail; + int r; + + if (zip->codec == _7Z_COPY && zip->codec2 == -1) { + if (__archive_read_ahead(a, 1, &bytes_avail) == NULL + || bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > zip->pack_stream_inbytes_remaining) + bytes_avail = zip->pack_stream_inbytes_remaining; + zip->pack_stream_inbytes_remaining -= bytes_avail; + if (bytes_avail > zip->folder_outbytes_remaining) + bytes_avail = zip->folder_outbytes_remaining; + zip->folder_outbytes_remaining -= bytes_avail; + zip->uncompressed_buffer_bytes_remaining = bytes_avail; + return (ARCHIVE_OK); + } + + /* If the buffer hasn't been allocated, allocate it now. */ + if (zip->uncompressed_buffer == NULL) { + zip->uncompressed_buffer_size = 64 * 1024; + zip->uncompressed_buffer = + malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + } + zip->uncompressed_buffer_bytes_remaining = 0; + zip->uncompressed_buffer_pointer = NULL; + for (;;) { + size_t bytes_in, bytes_out; + const void *buff_in; + unsigned char *buff_out; + int eof; + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + buff_in = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + + buff_out = zip->uncompressed_buffer + + zip->uncompressed_buffer_bytes_remaining; + bytes_out = zip->uncompressed_buffer_size + - zip->uncompressed_buffer_bytes_remaining; + bytes_in = bytes_avail; + if (bytes_in > zip->pack_stream_inbytes_remaining) + bytes_in = zip->pack_stream_inbytes_remaining; + /* Drive decompression. */ + r = decompress(a, zip, buff_out, &bytes_out, + buff_in, &bytes_in); + switch (r) { + case ARCHIVE_OK: + eof = 0; + break; + case ARCHIVE_EOF: + eof = 1; + break; + default: + return (ARCHIVE_FATAL); + } + zip->pack_stream_inbytes_remaining -= bytes_in; + if (bytes_out > zip->folder_outbytes_remaining) + bytes_out = zip->folder_outbytes_remaining; + zip->folder_outbytes_remaining -= bytes_out; + zip->uncompressed_buffer_bytes_remaining += bytes_out; + zip->pack_stream_bytes_unconsumed = bytes_in; + + /* + * Continue decompression until uncompressed_buffer is full. + */ + if (zip->uncompressed_buffer_bytes_remaining == + zip->uncompressed_buffer_size) + break; + if (zip->pack_stream_inbytes_remaining == 0 && + zip->folder_outbytes_remaining == 0) + break; + if (eof || (bytes_in == 0 && bytes_out == 0)) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + read_consume(a); + } + zip->uncompressed_buffer_pointer = zip->uncompressed_buffer; + return (ARCHIVE_OK); +} + +static int +seek_pack(struct archive_read *a) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + uint64_t pack_offset; + + if (zip->pack_stream_remaining <= 0) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, "Damaged 7-Zip archive"); + return (ARCHIVE_FATAL); + } + zip->pack_stream_inbytes_remaining = + zip->si.pi.sizes[zip->pack_stream_index]; + pack_offset = zip->si.pi.positions[zip->pack_stream_index]; + if (zip->stream_offset != pack_offset) { + if (0 > __archive_read_seek(a, pack_offset + zip->seek_base, + SEEK_SET)) + return (ARCHIVE_FATAL); + zip->stream_offset = pack_offset; + } + zip->pack_stream_index++; + zip->pack_stream_remaining--; + return (ARCHIVE_OK); +} + +static ssize_t +read_stream(struct archive_read *a, const void **buff, size_t size) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + uint64_t skip_bytes = 0; + int r; + + if (zip->uncompressed_buffer_bytes_remaining == 0) { + if (zip->pack_stream_inbytes_remaining > 0) { + r = extract_pack_stream(a); + if (r < 0) + return (r); + return (get_uncompressed_data(a, buff, size)); + } else if (zip->folder_outbytes_remaining > 0) { + /* Extract a remaining pack stream. */ + r = extract_pack_stream(a); + if (r < 0) + return (r); + return (get_uncompressed_data(a, buff, size)); + } + } else + return (get_uncompressed_data(a, buff, size)); + + /* + * Current pack stream has been consumed. + */ + if (zip->pack_stream_remaining == 0) { + /* + * All current folder's pack streams have been + * consumed. Switch to next folder. + */ + + if (zip->folder_index == 0 && + (zip->si.ci.folders[zip->entry->folderIndex].skipped_bytes + || zip->folder_index != zip->entry->folderIndex)) { + zip->folder_index = zip->entry->folderIndex; + skip_bytes = + zip->si.ci.folders[zip->folder_index].skipped_bytes; + } + + if (zip->folder_index >= zip->si.ci.numFolders) { + /* + * We have consumed all folders and its pack streams. + */ + *buff = NULL; + return (0); + } + r = setup_decode_folder(a, + &(zip->si.ci.folders[zip->folder_index]), 0); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + zip->folder_index++; + } + + /* + * Switch to next pack stream. + */ + r = seek_pack(a); + if (r < 0) + return (r); + + /* Extract a new pack stream. */ + r = extract_pack_stream(a); + if (r < 0) + return (r); + + /* + * Skip the bytes we alrady has skipped in skip_stream(). + */ + while (skip_bytes) { + ssize_t skipped; + + if (zip->uncompressed_buffer_bytes_remaining == 0) { + if (zip->pack_stream_inbytes_remaining > 0) { + r = extract_pack_stream(a); + if (r < 0) + return (r); + } else if (zip->folder_outbytes_remaining > 0) { + /* Extract a remaining pack stream. */ + r = extract_pack_stream(a); + if (r < 0) + return (r); + } else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + } + skipped = get_uncompressed_data(a, buff, skip_bytes); + if (skipped < 0) + return (skipped); + skip_bytes -= skipped; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + + return (get_uncompressed_data(a, buff, size)); +} + +static int +setup_decode_folder(struct archive_read *a, struct _7z_folder *folder, + int header) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const struct _7z_coder *coder1, *coder2; + const char *cname = (header)?"archive header":"file content"; + unsigned i; + int r, found_bcj2 = 0; + + /* + * Release the memory which the previous folder used for BCJ2. + */ + for (i = 0; i < 3; i++) { + if (zip->sub_stream_buff[i] != NULL) + free(zip->sub_stream_buff[i]); + zip->sub_stream_buff[i] = NULL; + } + + /* + * Initialize a stream reader. + */ + zip->pack_stream_remaining = (unsigned)folder->numPackedStreams; + zip->pack_stream_index = (unsigned)folder->packIndex; + zip->folder_outbytes_remaining = folder_uncompressed_size(folder); + zip->uncompressed_buffer_bytes_remaining = 0; + + /* + * Check coder types. + */ + for (i = 0; i < folder->numCoders; i++) { + if (folder->coders[i].codec == _7Z_CRYPTO) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "The %s is encrypted, " + "but currently not supported", cname); + return (ARCHIVE_FATAL); + } + if (folder->coders[i].codec == _7Z_X86_BCJ2) + found_bcj2++; + } + if ((folder->numCoders > 2 && !found_bcj2) || found_bcj2 > 1) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "The %s is encoded with many filters, " + "but currently not supported", cname); + return (ARCHIVE_FATAL); + } + coder1 = &(folder->coders[0]); + if (folder->numCoders == 2) + coder2 = &(folder->coders[1]); + else + coder2 = NULL; + + if (found_bcj2) { + /* + * Preparation to decode BCJ2. + * Decoding BCJ2 requires four sources. Those are at least, + * as far as I know, two types of the storage form. + */ + const struct _7z_coder *fc = folder->coders; + static const struct _7z_coder coder_copy = {0, 1, 1, 0, NULL}; + const struct _7z_coder *scoder[3] = + {&coder_copy, &coder_copy, &coder_copy}; + const void *buff; + ssize_t bytes; + unsigned char *b[3] = {NULL, NULL, NULL}; + uint64_t sunpack[3] ={-1, -1, -1}; + size_t s[3] = {0, 0, 0}; + int idx[3] = {0, 1, 2}; + + if (folder->numCoders == 4 && fc[3].codec == _7Z_X86_BCJ2 && + folder->numInStreams == 7 && folder->numOutStreams == 4 && + zip->pack_stream_remaining == 4) { + /* Source type 1 made by 7zr or 7z with -m options. */ + if (folder->bindPairs[0].inIndex == 5) { + /* The form made by 7zr */ + idx[0] = 1; idx[1] = 2; idx[2] = 0; + scoder[1] = &(fc[1]); + scoder[2] = &(fc[0]); + sunpack[1] = folder->unPackSize[1]; + sunpack[2] = folder->unPackSize[0]; + coder1 = &(fc[2]); + } else { + /* + * NOTE: Some patterns do not work. + * work: + * 7z a -m0=BCJ2 -m1=COPY -m2=COPY + * -m3=(any) + * 7z a -m0=BCJ2 -m1=COPY -m2=(any) + * -m3=COPY + * 7z a -m0=BCJ2 -m1=(any) -m2=COPY + * -m3=COPY + * not work: + * other patterns. + * + * We have to handle this like `pipe' or + * our libarchive7s filter frame work, + * decoding the BCJ2 main stream sequentially, + * m3 -> m2 -> m1 -> BCJ2. + * + */ + if (fc[0].codec == _7Z_COPY && + fc[1].codec == _7Z_COPY) + coder1 = &(folder->coders[2]); + else if (fc[0].codec == _7Z_COPY && + fc[2].codec == _7Z_COPY) + coder1 = &(folder->coders[1]); + else if (fc[1].codec == _7Z_COPY && + fc[2].codec == _7Z_COPY) + coder1 = &(folder->coders[0]); + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unsupported form of " + "BCJ2 streams"); + return (ARCHIVE_FATAL); + } + } + coder2 = &(fc[3]); + zip->main_stream_bytes_remaining = + folder->unPackSize[2]; + } else if (coder2 != NULL && coder2->codec == _7Z_X86_BCJ2 && + zip->pack_stream_remaining == 4 && + folder->numInStreams == 5 && folder->numOutStreams == 2) { + /* Source type 0 made by 7z */ + zip->main_stream_bytes_remaining = + folder->unPackSize[0]; + } else { + /* We got an unexpected form. */ + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unsupported form of BCJ2 streams"); + return (ARCHIVE_FATAL); + } + + /* Skip the main stream at this time. */ + if ((r = seek_pack(a)) < 0) + return (r); + zip->pack_stream_bytes_unconsumed = + zip->pack_stream_inbytes_remaining; + read_consume(a); + + /* Read following three sub streams. */ + for (i = 0; i < 3; i++) { + const struct _7z_coder *coder = scoder[i]; + + if ((r = seek_pack(a)) < 0) + return (r); + + if (sunpack[i] == -1) + zip->folder_outbytes_remaining = + zip->pack_stream_inbytes_remaining; + else + zip->folder_outbytes_remaining = sunpack[i]; + + r = init_decompression(a, zip, coder, NULL); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Allocate memory for the decorded data of a sub + * stream. */ + b[i] = malloc(zip->folder_outbytes_remaining); + if (b[i] == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + + /* Extract a sub stream. */ + while (zip->pack_stream_inbytes_remaining > 0) { + r = extract_pack_stream(a); + if (r < 0) + return (r); + bytes = get_uncompressed_data(a, &buff, + zip->uncompressed_buffer_bytes_remaining); + if (bytes < 0) + return ((int)bytes); + memcpy(b[i]+s[i], buff, bytes); + s[i] += bytes; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + } + + /* Set the sub streams to the right place. */ + for (i = 0; i < 3; i++) { + zip->sub_stream_buff[i] = b[idx[i]]; + zip->sub_stream_size[i] = s[idx[i]]; + zip->sub_stream_bytes_remaining[i] = s[idx[i]]; + } + + /* Allocate memory used for decoded main stream bytes. */ + if (zip->tmp_stream_buff == NULL) { + zip->tmp_stream_buff_size = 32 * 1024; + zip->tmp_stream_buff = + malloc(zip->tmp_stream_buff_size); + if (zip->tmp_stream_buff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for 7-Zip decompression"); + return (ARCHIVE_FATAL); + } + } + zip->tmp_stream_bytes_avail = 0; + zip->tmp_stream_bytes_remaining = 0; + zip->odd_bcj_size = 0; + zip->bcj2_outPos = 0; + + /* + * Reset a stream reader in order to read the main stream + * of BCJ2. + */ + zip->pack_stream_remaining = 1; + zip->pack_stream_index = (unsigned)folder->packIndex; + zip->folder_outbytes_remaining = + folder_uncompressed_size(folder); + zip->uncompressed_buffer_bytes_remaining = 0; + } + + /* + * Initialize the decompressor for the new folder's pack streams. + */ + r = init_decompression(a, zip, coder1, coder2); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + return (ARCHIVE_OK); +} + +static int64_t +skip_stream(struct archive_read *a, size_t skip_bytes) +{ + struct _7zip *zip = (struct _7zip *)a->format->data; + const void *p; + int64_t skipped_bytes; + size_t bytes = skip_bytes; + + if (zip->folder_index == 0) { + /* + * Optimization for a list mode. + * Avoid unncecessary decoding operations. + */ + zip->si.ci.folders[zip->entry->folderIndex].skipped_bytes + += skip_bytes; + return (skip_bytes); + } + + while (bytes) { + skipped_bytes = read_stream(a, &p, bytes); + if (skipped_bytes < 0) + return (skipped_bytes); + if (skipped_bytes == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated 7-Zip file body"); + return (ARCHIVE_FATAL); + } + bytes -= skipped_bytes; + if (zip->pack_stream_bytes_unconsumed) + read_consume(a); + } + return (skip_bytes); +} + +/* + * Brought from LZMA SDK. + * + * Bra86.c -- Converter for x86 code (BCJ) + * 2008-10-04 : Igor Pavlov : Public domain + * + */ + +#define Test86MSByte(b) ((b) == 0 || (b) == 0xFF) + +static const unsigned char kMaskToAllowedStatus[8] = {1, 1, 1, 0, 1, 0, 0, 0}; +static const unsigned char kMaskToBitNumber[8] = {0, 1, 2, 2, 3, 3, 3, 3}; + +static size_t +x86_Convert(uint8_t *data, size_t size, uint32_t ip, uint32_t *state) +{ + size_t bufferPos = 0, prevPosT; + uint32_t prevMask = *state & 0x7; + if (size < 5) + return 0; + ip += 5; + prevPosT = (size_t)0 - 1; + + for (;;) { + uint8_t *p = data + bufferPos; + uint8_t *limit = data + size - 4; + + for (; p < limit; p++) + if ((*p & 0xFE) == 0xE8) + break; + bufferPos = (size_t)(p - data); + if (p >= limit) + break; + prevPosT = bufferPos - prevPosT; + if (prevPosT > 3) + prevMask = 0; + else { + prevMask = (prevMask << ((int)prevPosT - 1)) & 0x7; + if (prevMask != 0) { + unsigned char b = + p[4 - kMaskToBitNumber[prevMask]]; + if (!kMaskToAllowedStatus[prevMask] || + Test86MSByte(b)) { + prevPosT = bufferPos; + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + continue; + } + } + } + prevPosT = bufferPos; + + if (Test86MSByte(p[4])) { + uint32_t src = ((uint32_t)p[4] << 24) | + ((uint32_t)p[3] << 16) | ((uint32_t)p[2] << 8) | + ((uint32_t)p[1]); + uint32_t dest; + for (;;) { + uint8_t b; + int index; + + dest = src - (ip + (uint32_t)bufferPos); + if (prevMask == 0) + break; + index = kMaskToBitNumber[prevMask] * 8; + b = (uint8_t)(dest >> (24 - index)); + if (!Test86MSByte(b)) + break; + src = dest ^ ((1 << (32 - index)) - 1); + } + p[4] = (uint8_t)(~(((dest >> 24) & 1) - 1)); + p[3] = (uint8_t)(dest >> 16); + p[2] = (uint8_t)(dest >> 8); + p[1] = (uint8_t)dest; + bufferPos += 5; + } else { + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + } + } + prevPosT = bufferPos - prevPosT; + *state = ((prevPosT > 3) ? + 0 : ((prevMask << ((int)prevPosT - 1)) & 0x7)); + return (bufferPos); +} + +/* + * Brought from LZMA SDK. + * + * Bcj2.c -- Converter for x86 code (BCJ2) + * 2008-10-04 : Igor Pavlov : Public domain + * + */ + +#define SZ_ERROR_DATA ARCHIVE_FAILED + +#define IsJcc(b0, b1) ((b0) == 0x0F && ((b1) & 0xF0) == 0x80) +#define IsJ(b0, b1) ((b1 & 0xFE) == 0xE8 || IsJcc(b0, b1)) + +#define kNumTopBits 24 +#define kTopValue ((uint32_t)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_READ_BYTE (*buffer++) +#define RC_TEST { if (buffer == bufferLim) return SZ_ERROR_DATA; } +#define RC_INIT2 zip->bcj2_code = 0; zip->bcj2_range = 0xFFFFFFFF; \ + { int i; for (i = 0; i < 5; i++) { RC_TEST; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; }} + +#define NORMALIZE if (zip->bcj2_range < kTopValue) { RC_TEST; zip->bcj2_range <<= 8; zip->bcj2_code = (zip->bcj2_code << 8) | RC_READ_BYTE; } + +#define IF_BIT_0(p) ttt = *(p); bound = (zip->bcj2_range >> kNumBitModelTotalBits) * ttt; if (zip->bcj2_code < bound) +#define UPDATE_0(p) zip->bcj2_range = bound; *(p) = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); NORMALIZE; +#define UPDATE_1(p) zip->bcj2_range -= bound; zip->bcj2_code -= bound; *(p) = (CProb)(ttt - (ttt >> kNumMoveBits)); NORMALIZE; + +ssize_t +Bcj2_Decode(struct _7zip *zip, uint8_t *outBuf, size_t outSize) +{ + size_t inPos = 0, outPos = 0; + const uint8_t *buf0, *buf1, *buf2, *buf3; + size_t size0, size1, size2, size3; + const uint8_t *buffer, *bufferLim; + unsigned int i, j; + + size0 = zip->tmp_stream_bytes_remaining; + buf0 = zip->tmp_stream_buff + zip->tmp_stream_bytes_avail - size0; + size1 = zip->sub_stream_bytes_remaining[0]; + buf1 = zip->sub_stream_buff[0] + zip->sub_stream_size[0] - size1; + size2 = zip->sub_stream_bytes_remaining[1]; + buf2 = zip->sub_stream_buff[1] + zip->sub_stream_size[1] - size2; + size3 = zip->sub_stream_bytes_remaining[2]; + buf3 = zip->sub_stream_buff[2] + zip->sub_stream_size[2] - size3; + + buffer = buf3; + bufferLim = buffer + size3; + + if (zip->bcj_state == 0) { + /* + * Initialize. + */ + zip->bcj2_prevByte = 0; + for (i = 0; + i < sizeof(zip->bcj2_p) / sizeof(zip->bcj2_p[0]); i++) + zip->bcj2_p[i] = kBitModelTotal >> 1; + RC_INIT2; + zip->bcj_state = 1; + } + + /* + * Gather the odd bytes of a previous call. + */ + for (i = 0; zip->odd_bcj_size > 0 && outPos < outSize; i++) { + outBuf[outPos++] = zip->odd_bcj[i]; + zip->odd_bcj_size--; + } + + if (outSize == 0) { + zip->bcj2_outPos += outPos; + return (outPos); + } + + for (;;) { + uint8_t b; + CProb *prob; + uint32_t bound; + uint32_t ttt; + + size_t limit = size0 - inPos; + if (outSize - outPos < limit) + limit = outSize - outPos; + + if (zip->bcj_state == 1) { + while (limit != 0) { + uint8_t b = buf0[inPos]; + outBuf[outPos++] = b; + if (IsJ(zip->bcj2_prevByte, b)) { + zip->bcj_state = 2; + break; + } + inPos++; + zip->bcj2_prevByte = b; + limit--; + } + } + + if (limit == 0 || outPos == outSize) + break; + zip->bcj_state = 1; + + b = buf0[inPos++]; + + if (b == 0xE8) + prob = zip->bcj2_p + zip->bcj2_prevByte; + else if (b == 0xE9) + prob = zip->bcj2_p + 256; + else + prob = zip->bcj2_p + 257; + + IF_BIT_0(prob) { + UPDATE_0(prob) + zip->bcj2_prevByte = b; + } else { + uint32_t dest; + const uint8_t *v; + uint8_t out[4]; + + UPDATE_1(prob) + if (b == 0xE8) { + v = buf1; + if (size1 < 4) + return SZ_ERROR_DATA; + buf1 += 4; + size1 -= 4; + } else { + v = buf2; + if (size2 < 4) + return SZ_ERROR_DATA; + buf2 += 4; + size2 -= 4; + } + dest = (((uint32_t)v[0] << 24) | + ((uint32_t)v[1] << 16) | + ((uint32_t)v[2] << 8) | + ((uint32_t)v[3])) - + ((uint32_t)zip->bcj2_outPos + outPos + 4); + out[0] = (uint8_t)dest; + out[1] = (uint8_t)(dest >> 8); + out[2] = (uint8_t)(dest >> 16); + out[3] = zip->bcj2_prevByte = (uint8_t)(dest >> 24); + + for (i = 0; i < 4 && outPos < outSize; i++) + outBuf[outPos++] = out[i]; + if (i < 4) { + /* + * Save odd bytes which we could not add into + * the output buffer because of out of space. + */ + zip->odd_bcj_size = 4 -i; + for (; i < 4; i++) { + j = i - 4 + zip->odd_bcj_size; + zip->odd_bcj[j] = out[i]; + } + break; + } + } + } + zip->tmp_stream_bytes_remaining -= inPos; + zip->sub_stream_bytes_remaining[0] = size1; + zip->sub_stream_bytes_remaining[1] = size2; + zip->sub_stream_bytes_remaining[2] = bufferLim - buffer; + zip->bcj2_outPos += outPos; + + return ((ssize_t)outPos); +} + diff --git a/libarchive/archive_read_support_format_all.c b/libarchive/archive_read_support_format_all.c new file mode 100644 index 0000000..53fe6fa --- /dev/null +++ b/libarchive/archive_read_support_format_all.c @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2003-2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_all.c 174991 2007-12-30 04:58:22Z kientzle $"); + +#include "archive.h" +#include "archive_private.h" + +int +archive_read_support_format_all(struct archive *a) +{ + archive_check_magic(a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_all"); + + /* TODO: It would be nice to compute the ordering + * here automatically so that people who enable just + * a few formats can still get the benefits. That + * may just require the format registration to include + * a "maximum read-ahead" value (anything that uses seek + * would be essentially infinite read-ahead). The core + * bid management can then sort the bidders before calling + * them. + * + * If you implement the above, please return the list below + * to alphabetic order. + */ + + /* + * These bidders are all pretty cheap; they just examine a + * small initial part of the archive. If one of these bids + * high, we can maybe avoid running any of the more expensive + * bidders below. + */ + archive_read_support_format_ar(a); + archive_read_support_format_cpio(a); + archive_read_support_format_empty(a); + archive_read_support_format_lha(a); + archive_read_support_format_mtree(a); + archive_read_support_format_tar(a); + archive_read_support_format_xar(a); + + /* + * Install expensive bidders last. By doing them last, we + * increase the chance that a high bid from someone else will + * make it unnecessary for these to do anything at all. + */ + /* These three have potentially large look-ahead. */ + archive_read_support_format_7zip(a); + archive_read_support_format_cab(a); + archive_read_support_format_rar(a); + archive_read_support_format_iso9660(a); + /* Seek is really bad, since it forces the read-ahead + * logic to discard buffered data. */ + archive_read_support_format_zip(a); + + /* Note: We always return ARCHIVE_OK here, even if some of the + * above return ARCHIVE_WARN. The intent here is to enable + * "as much as possible." Clients who need specific + * compression should enable those individually so they can + * verify the level of support. */ + /* Clear any warning messages set by the above functions. */ + archive_clear_error(a); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_ar.c b/libarchive/archive_read_support_format_ar.c new file mode 100644 index 0000000..9feb547 --- /dev/null +++ b/libarchive/archive_read_support_format_ar.c @@ -0,0 +1,625 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_ar.c 201101 2009-12-28 03:06:27Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct ar { + int64_t entry_bytes_remaining; + /* unconsumed is purely to track data we've gotten from readahead, + * but haven't yet marked as consumed. Must be paired with + * entry_bytes_remaining usage/modification. + */ + size_t entry_bytes_unconsumed; + int64_t entry_offset; + int64_t entry_padding; + char *strtab; + size_t strtab_size; + char read_global_header; +}; + +/* + * Define structure of the "ar" header. + */ +#define AR_name_offset 0 +#define AR_name_size 16 +#define AR_date_offset 16 +#define AR_date_size 12 +#define AR_uid_offset 28 +#define AR_uid_size 6 +#define AR_gid_offset 34 +#define AR_gid_size 6 +#define AR_mode_offset 40 +#define AR_mode_size 8 +#define AR_size_offset 48 +#define AR_size_size 10 +#define AR_fmag_offset 58 +#define AR_fmag_size 2 + +static int archive_read_format_ar_bid(struct archive_read *a, int); +static int archive_read_format_ar_cleanup(struct archive_read *a); +static int archive_read_format_ar_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset); +static int archive_read_format_ar_skip(struct archive_read *a); +static int archive_read_format_ar_read_header(struct archive_read *a, + struct archive_entry *e); +static uint64_t ar_atol8(const char *p, unsigned char_cnt); +static uint64_t ar_atol10(const char *p, unsigned char_cnt); +static int ar_parse_gnu_filename_table(struct archive_read *a); +static int ar_parse_common_header(struct ar *ar, struct archive_entry *, + const char *h); + +int +archive_read_support_format_ar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct ar *ar; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_ar"); + + ar = (struct ar *)malloc(sizeof(*ar)); + if (ar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate ar data"); + return (ARCHIVE_FATAL); + } + memset(ar, 0, sizeof(*ar)); + ar->strtab = NULL; + + r = __archive_read_register_format(a, + ar, + "ar", + archive_read_format_ar_bid, + NULL, + archive_read_format_ar_read_header, + archive_read_format_ar_read_data, + archive_read_format_ar_skip, + archive_read_format_ar_cleanup); + + if (r != ARCHIVE_OK) { + free(ar); + return (r); + } + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_cleanup(struct archive_read *a) +{ + struct ar *ar; + + ar = (struct ar *)(a->format->data); + if (ar->strtab) + free(ar->strtab); + free(ar); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_bid(struct archive_read *a, int best_bid) +{ + const void *h; + + (void)best_bid; /* UNUSED */ + + /* + * Verify the 8-byte file signature. + * TODO: Do we need to check more than this? + */ + if ((h = __archive_read_ahead(a, 8, NULL)) == NULL) + return (-1); + if (memcmp(h, "!\n", 8) == 0) { + return (64); + } + return (-1); +} + +static int +_ar_read_header(struct archive_read *a, struct archive_entry *entry, + struct ar *ar, const char *h, size_t *unconsumed) +{ + char filename[AR_name_size + 1]; + uint64_t number; /* Used to hold parsed numbers before validation. */ + size_t bsd_name_length, entry_size; + char *p, *st; + const void *b; + int r; + + /* Verify the magic signature on the file header. */ + if (strncmp(h + AR_fmag_offset, "`\n", 2) != 0) { + archive_set_error(&a->archive, EINVAL, + "Incorrect file header signature"); + return (ARCHIVE_WARN); + } + + /* Copy filename into work buffer. */ + strncpy(filename, h + AR_name_offset, AR_name_size); + filename[AR_name_size] = '\0'; + + /* + * Guess the format variant based on the filename. + */ + if (a->archive.archive_format == ARCHIVE_FORMAT_AR) { + /* We don't already know the variant, so let's guess. */ + /* + * Biggest clue is presence of '/': GNU starts special + * filenames with '/', appends '/' as terminator to + * non-special names, so anything with '/' should be + * GNU except for BSD long filenames. + */ + if (strncmp(filename, "#1/", 3) == 0) + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + else if (strchr(filename, '/') != NULL) + a->archive.archive_format = ARCHIVE_FORMAT_AR_GNU; + else if (strncmp(filename, "__.SYMDEF", 9) == 0) + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + /* + * XXX Do GNU/SVR4 'ar' programs ever omit trailing '/' + * if name exactly fills 16-byte field? If so, we + * can't assume entries without '/' are BSD. XXX + */ + } + + /* Update format name from the code. */ + if (a->archive.archive_format == ARCHIVE_FORMAT_AR_GNU) + a->archive.archive_format_name = "ar (GNU/SVR4)"; + else if (a->archive.archive_format == ARCHIVE_FORMAT_AR_BSD) + a->archive.archive_format_name = "ar (BSD)"; + else + a->archive.archive_format_name = "ar"; + + /* + * Remove trailing spaces from the filename. GNU and BSD + * variants both pad filename area out with spaces. + * This will only be wrong if GNU/SVR4 'ar' implementations + * omit trailing '/' for 16-char filenames and we have + * a 16-char filename that ends in ' '. + */ + p = filename + AR_name_size - 1; + while (p >= filename && *p == ' ') { + *p = '\0'; + p--; + } + + /* + * Remove trailing slash unless first character is '/'. + * (BSD entries never end in '/', so this will only trim + * GNU-format entries. GNU special entries start with '/' + * and are not terminated in '/', so we don't trim anything + * that starts with '/'.) + */ + if (filename[0] != '/' && *p == '/') + *p = '\0'; + + /* + * '//' is the GNU filename table. + * Later entries can refer to names in this table. + */ + if (strcmp(filename, "//") == 0) { + /* This must come before any call to _read_ahead. */ + ar_parse_common_header(ar, entry, h); + archive_entry_copy_pathname(entry, filename); + archive_entry_set_filetype(entry, AE_IFREG); + /* Get the size of the filename table. */ + number = ar_atol10(h + AR_size_offset, AR_size_size); + if (number > SIZE_MAX) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Filename table too large"); + return (ARCHIVE_FATAL); + } + entry_size = (size_t)number; + if (entry_size == 0) { + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + return (ARCHIVE_WARN); + } + if (ar->strtab != NULL) { + archive_set_error(&a->archive, EINVAL, + "More than one string tables exist"); + return (ARCHIVE_WARN); + } + + /* Read the filename table into memory. */ + st = malloc(entry_size); + if (st == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate filename table buffer"); + return (ARCHIVE_FATAL); + } + ar->strtab = st; + ar->strtab_size = entry_size; + + if (*unconsumed) { + __archive_read_consume(a, *unconsumed); + *unconsumed = 0; + } + + if ((b = __archive_read_ahead(a, entry_size, NULL)) == NULL) + return (ARCHIVE_FATAL); + memcpy(st, b, entry_size); + __archive_read_consume(a, entry_size); + /* All contents are consumed. */ + ar->entry_bytes_remaining = 0; + archive_entry_set_size(entry, ar->entry_bytes_remaining); + + /* Parse the filename table. */ + return (ar_parse_gnu_filename_table(a)); + } + + /* + * GNU variant handles long filenames by storing / + * to indicate a name stored in the filename table. + * XXX TODO: Verify that it's all digits... Don't be fooled + * by "/9xyz" XXX + */ + if (filename[0] == '/' && filename[1] >= '0' && filename[1] <= '9') { + number = ar_atol10(h + AR_name_offset + 1, AR_name_size - 1); + /* + * If we can't look up the real name, warn and return + * the entry with the wrong name. + */ + if (ar->strtab == NULL || number > ar->strtab_size) { + archive_set_error(&a->archive, EINVAL, + "Can't find long filename for entry"); + archive_entry_copy_pathname(entry, filename); + /* Parse the time, owner, mode, size fields. */ + ar_parse_common_header(ar, entry, h); + return (ARCHIVE_WARN); + } + + archive_entry_copy_pathname(entry, &ar->strtab[(size_t)number]); + /* Parse the time, owner, mode, size fields. */ + return (ar_parse_common_header(ar, entry, h)); + } + + /* + * BSD handles long filenames by storing "#1/" followed by the + * length of filename as a decimal number, then prepends the + * the filename to the file contents. + */ + if (strncmp(filename, "#1/", 3) == 0) { + /* Parse the time, owner, mode, size fields. */ + /* This must occur before _read_ahead is called again. */ + ar_parse_common_header(ar, entry, h); + + /* Parse the size of the name, adjust the file size. */ + number = ar_atol10(h + AR_name_offset + 3, AR_name_size - 3); + bsd_name_length = (size_t)number; + /* Guard against the filename + trailing NUL + * overflowing a size_t and against the filename size + * being larger than the entire entry. */ + if (number > (uint64_t)(bsd_name_length + 1) + || (int64_t)bsd_name_length > ar->entry_bytes_remaining) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Bad input file size"); + return (ARCHIVE_FATAL); + } + ar->entry_bytes_remaining -= bsd_name_length; + /* Adjust file size reported to client. */ + archive_entry_set_size(entry, ar->entry_bytes_remaining); + + if (*unconsumed) { + __archive_read_consume(a, *unconsumed); + *unconsumed = 0; + } + + /* Read the long name into memory. */ + if ((b = __archive_read_ahead(a, bsd_name_length, NULL)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file"); + return (ARCHIVE_FATAL); + } + /* Store it in the entry. */ + p = (char *)malloc(bsd_name_length + 1); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate fname buffer"); + return (ARCHIVE_FATAL); + } + strncpy(p, b, bsd_name_length); + p[bsd_name_length] = '\0'; + + __archive_read_consume(a, bsd_name_length); + + archive_entry_copy_pathname(entry, p); + free(p); + return (ARCHIVE_OK); + } + + /* + * "/" is the SVR4/GNU archive symbol table. + */ + if (strcmp(filename, "/") == 0) { + archive_entry_copy_pathname(entry, "/"); + /* Parse the time, owner, mode, size fields. */ + r = ar_parse_common_header(ar, entry, h); + /* Force the file type to a regular file. */ + archive_entry_set_filetype(entry, AE_IFREG); + return (r); + } + + /* + * "__.SYMDEF" is a BSD archive symbol table. + */ + if (strcmp(filename, "__.SYMDEF") == 0) { + archive_entry_copy_pathname(entry, filename); + /* Parse the time, owner, mode, size fields. */ + return (ar_parse_common_header(ar, entry, h)); + } + + /* + * Otherwise, this is a standard entry. The filename + * has already been trimmed as much as possible, based + * on our current knowledge of the format. + */ + archive_entry_copy_pathname(entry, filename); + return (ar_parse_common_header(ar, entry, h)); +} + +static int +archive_read_format_ar_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct ar *ar = (struct ar*)(a->format->data); + size_t unconsumed; + const void *header_data; + int ret; + + if (!ar->read_global_header) { + /* + * We are now at the beginning of the archive, + * so we need first consume the ar global header. + */ + __archive_read_consume(a, 8); + ar->read_global_header = 1; + /* Set a default format code for now. */ + a->archive.archive_format = ARCHIVE_FORMAT_AR; + } + + /* Read the header for the next file entry. */ + if ((header_data = __archive_read_ahead(a, 60, NULL)) == NULL) + /* Broken header. */ + return (ARCHIVE_EOF); + + unconsumed = 60; + + ret = _ar_read_header(a, entry, ar, (const char *)header_data, &unconsumed); + + if (unconsumed) + __archive_read_consume(a, unconsumed); + + return ret; +} + + +static int +ar_parse_common_header(struct ar *ar, struct archive_entry *entry, + const char *h) +{ + uint64_t n; + + /* Copy remaining header */ + archive_entry_set_mtime(entry, + (time_t)ar_atol10(h + AR_date_offset, AR_date_size), 0L); + archive_entry_set_uid(entry, + (uid_t)ar_atol10(h + AR_uid_offset, AR_uid_size)); + archive_entry_set_gid(entry, + (gid_t)ar_atol10(h + AR_gid_offset, AR_gid_size)); + archive_entry_set_mode(entry, + (mode_t)ar_atol8(h + AR_mode_offset, AR_mode_size)); + n = ar_atol10(h + AR_size_offset, AR_size_size); + + ar->entry_offset = 0; + ar->entry_padding = n % 2; + archive_entry_set_size(entry, n); + ar->entry_bytes_remaining = n; + return (ARCHIVE_OK); +} + +static int +archive_read_format_ar_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + ssize_t bytes_read; + struct ar *ar; + + ar = (struct ar *)(a->format->data); + + if (ar->entry_bytes_unconsumed) { + __archive_read_consume(a, ar->entry_bytes_unconsumed); + ar->entry_bytes_unconsumed = 0; + } + + if (ar->entry_bytes_remaining > 0) { + *buff = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated ar archive"); + return (ARCHIVE_FATAL); + } + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read > ar->entry_bytes_remaining) + bytes_read = (ssize_t)ar->entry_bytes_remaining; + *size = bytes_read; + ar->entry_bytes_unconsumed = bytes_read; + *offset = ar->entry_offset; + ar->entry_offset += bytes_read; + ar->entry_bytes_remaining -= bytes_read; + return (ARCHIVE_OK); + } else { + int64_t skipped = __archive_read_consume(a, ar->entry_padding); + if (skipped >= 0) { + ar->entry_padding -= skipped; + } + if (ar->entry_padding) { + if (skipped >= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated ar archive- failed consuming padding"); + } + return (ARCHIVE_FATAL); + } + *buff = NULL; + *size = 0; + *offset = ar->entry_offset; + return (ARCHIVE_EOF); + } +} + +static int +archive_read_format_ar_skip(struct archive_read *a) +{ + int64_t bytes_skipped; + struct ar* ar; + + ar = (struct ar *)(a->format->data); + + bytes_skipped = __archive_read_consume(a, + ar->entry_bytes_remaining + ar->entry_padding + + ar->entry_bytes_unconsumed); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + ar->entry_bytes_remaining = 0; + ar->entry_bytes_unconsumed = 0; + ar->entry_padding = 0; + + return (ARCHIVE_OK); +} + +static int +ar_parse_gnu_filename_table(struct archive_read *a) +{ + struct ar *ar; + char *p; + size_t size; + + ar = (struct ar*)(a->format->data); + size = ar->strtab_size; + + for (p = ar->strtab; p < ar->strtab + size - 1; ++p) { + if (*p == '/') { + *p++ = '\0'; + if (*p != '\n') + goto bad_string_table; + *p = '\0'; + } + } + /* + * GNU ar always pads the table to an even size. + * The pad character is either '\n' or '`'. + */ + if (p != ar->strtab + size && *p != '\n' && *p != '`') + goto bad_string_table; + + /* Enforce zero termination. */ + ar->strtab[size - 1] = '\0'; + + return (ARCHIVE_OK); + +bad_string_table: + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + free(ar->strtab); + ar->strtab = NULL; + return (ARCHIVE_WARN); +} + +static uint64_t +ar_atol8(const char *p, unsigned char_cnt) +{ + uint64_t l, limit, last_digit_limit; + unsigned int digit, base; + + base = 8; + limit = UINT64_MAX / base; + last_digit_limit = UINT64_MAX % base; + + while ((*p == ' ' || *p == '\t') && char_cnt-- > 0) + p++; + + l = 0; + digit = *p - '0'; + while (*p >= '0' && digit < base && char_cnt-- > 0) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (l); +} + +static uint64_t +ar_atol10(const char *p, unsigned char_cnt) +{ + uint64_t l, limit, last_digit_limit; + unsigned int base, digit; + + base = 10; + limit = UINT64_MAX / base; + last_digit_limit = UINT64_MAX % base; + + while ((*p == ' ' || *p == '\t') && char_cnt-- > 0) + p++; + l = 0; + digit = *p - '0'; + while (*p >= '0' && digit < base && char_cnt-- > 0) { + if (l > limit || (l == limit && digit > last_digit_limit)) { + l = UINT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (l); +} diff --git a/libarchive/archive_read_support_format_by_code.c b/libarchive/archive_read_support_format_by_code.c new file mode 100644 index 0000000..084563f --- /dev/null +++ b/libarchive/archive_read_support_format_by_code.c @@ -0,0 +1,74 @@ +/*- + * Copyright (c) 2003-2011 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive.h" +#include "archive_private.h" + +int +archive_read_support_format_by_code(struct archive *a, int format_code) +{ + archive_check_magic(a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_by_code"); + + switch (format_code & ARCHIVE_FORMAT_BASE_MASK) { + case ARCHIVE_FORMAT_7ZIP: + return archive_read_support_format_7zip(a); + break; + case ARCHIVE_FORMAT_AR: + return archive_read_support_format_ar(a); + break; + case ARCHIVE_FORMAT_CAB: + return archive_read_support_format_cab(a); + break; + case ARCHIVE_FORMAT_CPIO: + return archive_read_support_format_cpio(a); + break; + case ARCHIVE_FORMAT_ISO9660: + return archive_read_support_format_iso9660(a); + break; + case ARCHIVE_FORMAT_LHA: + return archive_read_support_format_lha(a); + break; + case ARCHIVE_FORMAT_MTREE: + return archive_read_support_format_mtree(a); + break; + case ARCHIVE_FORMAT_RAR: + return archive_read_support_format_rar(a); + break; + case ARCHIVE_FORMAT_TAR: + return archive_read_support_format_tar(a); + break; + case ARCHIVE_FORMAT_XAR: + return archive_read_support_format_xar(a); + break; + case ARCHIVE_FORMAT_ZIP: + return archive_read_support_format_zip(a); + break; + } + return (ARCHIVE_FATAL); +} diff --git a/libarchive/archive_read_support_format_cab.c b/libarchive/archive_read_support_format_cab.c new file mode 100644 index 0000000..1b81ee9 --- /dev/null +++ b/libarchive/archive_read_support_format_cab.c @@ -0,0 +1,3315 @@ +/*- + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + + +struct lzx_dec { + /* Decoding status. */ + int state; + + /* + * Window to see last decoded data, from 32KBi to 2MBi. + */ + int w_size; + int w_mask; + /* Window buffer, which is a loop buffer. */ + unsigned char *w_buff; + /* The insert position to the window. */ + int w_pos; + /* The position where we can copy decoded code from the window. */ + int copy_pos; + /* The length how many bytes we can copy decoded code from + * the window. */ + int copy_len; + /* Translation reversal for x86 proccessor CALL byte sequence(E8). + * This is used for LZX only. */ + uint32_t translation_size; + char translation; + char block_type; +#define VERBATIM_BLOCK 1 +#define ALIGNED_OFFSET_BLOCK 2 +#define UNCOMPRESSED_BLOCK 3 + size_t block_size; + size_t block_bytes_avail; + /* Repeated offset. */ + int r0, r1, r2; + unsigned char rbytes[4]; + int rbytes_avail; + int length_header; + int position_slot; + int offset_bits; + + struct lzx_pos_tbl { + int base; + int footer_bits; + } *pos_tbl; + /* + * Bit stream reader. + */ + struct lzx_br { +#define CACHE_TYPE uint64_t +#define CACHE_BITS (8 * sizeof(CACHE_TYPE)) + /* Cache buffer. */ + CACHE_TYPE cache_buffer; + /* Indicates how many bits avail in cache_buffer. */ + int cache_avail; + unsigned char odd; + char have_odd; + } br; + + /* + * Huffman coding. + */ + struct huffman { + int len_size; + int freq[17]; + unsigned char *bitlen; + + /* + * Use a index table. It's faster than searching a huffman + * coding tree, which is a binary tree. But a use of a large + * index table causes L1 cache read miss many times. + */ +#define HTBL_BITS 10 + int max_bits; + int shift_bits; + int tbl_bits; + int tree_used; + int tree_avail; + /* Direct access table. */ + uint16_t *tbl; + /* Binary tree table for extra bits over the direct access. */ + struct htree_t { + uint16_t left; + uint16_t right; + } *tree; + } at, lt, mt, pt; + + int loop; + int error; +}; + +static const int slots[] = { + 30, 32, 34, 36, 38, 42, 50, 66, 98, 162, 290 +}; +#define SLOT_BASE 15 +#define SLOT_MAX 21/*->25*/ + +struct lzx_stream { + const unsigned char *next_in; + int64_t avail_in; + int64_t total_in; + unsigned char *next_out; + int64_t avail_out; + int64_t total_out; + struct lzx_dec *ds; +}; + +/* + * Cabinet file definitions. + */ +/* CFHEADER offset */ +#define CFHEADER_signature 0 +#define CFHEADER_cbCabinet 8 +#define CFHEADER_coffFiles 16 +#define CFHEADER_versionMinor 24 +#define CFHEADER_versionMajor 25 +#define CFHEADER_cFolders 26 +#define CFHEADER_cFiles 28 +#define CFHEADER_flags 30 +#define CFHEADER_setID 32 +#define CFHEADER_iCabinet 34 +#define CFHEADER_cbCFHeader 36 +#define CFHEADER_cbCFFolder 38 +#define CFHEADER_cbCFData 39 + +/* CFFOLDER offset */ +#define CFFOLDER_coffCabStart 0 +#define CFFOLDER_cCFData 4 +#define CFFOLDER_typeCompress 6 +#define CFFOLDER_abReserve 8 + +/* CFFILE offset */ +#define CFFILE_cbFile 0 +#define CFFILE_uoffFolderStart 4 +#define CFFILE_iFolder 8 +#define CFFILE_date_time 10 +#define CFFILE_attribs 14 + +/* CFDATA offset */ +#define CFDATA_csum 0 +#define CFDATA_cbData 4 +#define CFDATA_cbUncomp 6 + +static const char *compression_name[] = { + "NONE", + "MSZIP", + "Quantum", + "LZX", +}; + +struct cfdata { + /* Sum value of this CFDATA. */ + uint32_t sum; + uint16_t compressed_size; + uint16_t compressed_bytes_remaining; + uint16_t uncompressed_size; + uint16_t uncompressed_bytes_remaining; + /* To know how many bytes we have decompressed. */ + uint16_t uncompressed_avail; + /* Offset from the beginning of compressed data of this CFDATA */ + uint16_t read_offset; + int64_t unconsumed; + /* To keep memory image of this CFDATA to compute the sum. */ + size_t memimage_size; + unsigned char *memimage; + /* Result of calculation of sum. */ + uint32_t sum_calculated; + unsigned char sum_extra[4]; + int sum_extra_avail; + const void *sum_ptr; +}; + +struct cffolder { + uint32_t cfdata_offset_in_cab; + uint16_t cfdata_count; + uint16_t comptype; +#define COMPTYPE_NONE 0x0000 +#define COMPTYPE_MSZIP 0x0001 +#define COMPTYPE_QUANTUM 0x0002 +#define COMPTYPE_LZX 0x0003 + uint16_t compdata; + const char *compname; + /* At the time reading CFDATA */ + struct cfdata cfdata; + int cfdata_index; + /* Flags to mark progress of decompression. */ + char decompress_init; +}; + +struct cffile { + uint32_t uncompressed_size; + uint32_t offset; + time_t mtime; + uint16_t folder; +#define iFoldCONTINUED_FROM_PREV 0xFFFD +#define iFoldCONTINUED_TO_NEXT 0xFFFE +#define iFoldCONTINUED_PREV_AND_NEXT 0xFFFF + unsigned char attr; +#define ATTR_RDONLY 0x01 +#define ATTR_NAME_IS_UTF 0x80 + struct archive_string pathname; +}; + +struct cfheader { + /* Total bytes of all file size in a Cabinet. */ + uint32_t total_bytes; + uint32_t files_offset; + uint16_t folder_count; + uint16_t file_count; + uint16_t flags; +#define PREV_CABINET 0x0001 +#define NEXT_CABINET 0x0002 +#define RESERVE_PRESENT 0x0004 + uint16_t setid; + uint16_t cabinet; + /* Version number. */ + unsigned char major; + unsigned char minor; + unsigned char cffolder; + unsigned char cfdata; + /* All folders in a cabinet. */ + struct cffolder *folder_array; + /* All files in a cabinet. */ + struct cffile *file_array; + int file_index; +}; + +struct cab { + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_offset; + int64_t entry_bytes_remaining; + int64_t entry_unconsumed; + int64_t entry_compressed_bytes_read; + int64_t entry_uncompressed_bytes_read; + struct cffolder *entry_cffolder; + struct cffile *entry_cffile; + struct cfdata *entry_cfdata; + + /* Offset from beginning of a cabinet file. */ + int64_t cab_offset; + struct cfheader cfheader; + struct archive_wstring ws; + + /* Flag to mark progress that an archive was read their first header.*/ + char found_header; + char end_of_archive; + char end_of_entry; + char end_of_entry_cleanup; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; + + int init_default_conversion; + struct archive_string_conv *sconv; + struct archive_string_conv *sconv_default; + struct archive_string_conv *sconv_utf8; + char format_name[64]; + +#ifdef HAVE_ZLIB_H + z_stream stream; + char stream_valid; +#endif + struct lzx_stream xstrm; +}; + +static int archive_read_format_cab_bid(struct archive_read *, int); +static int archive_read_format_cab_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_cab_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_cab_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_cab_read_data_skip(struct archive_read *); +static int archive_read_format_cab_cleanup(struct archive_read *); + +static int cab_skip_sfx(struct archive_read *); +static time_t cab_dos_time(const unsigned char *); +static int cab_read_data(struct archive_read *, const void **, + size_t *, int64_t *); +static int cab_read_header(struct archive_read *); +static uint32_t cab_checksum_cfdata_4(const void *, size_t bytes, uint32_t); +static uint32_t cab_checksum_cfdata(const void *, size_t bytes, uint32_t); +static void cab_checksum_update(struct archive_read *, size_t); +static int cab_checksum_finish(struct archive_read *); +static int cab_next_cfdata(struct archive_read *); +static const void *cab_read_ahead_cfdata(struct archive_read *, ssize_t *); +static const void *cab_read_ahead_cfdata_none(struct archive_read *, ssize_t *); +static const void *cab_read_ahead_cfdata_deflate(struct archive_read *, + ssize_t *); +static const void *cab_read_ahead_cfdata_lzx(struct archive_read *, + ssize_t *); +static int64_t cab_consume_cfdata(struct archive_read *, int64_t); +static int64_t cab_minimum_consume_cfdata(struct archive_read *, int64_t); +static int lzx_decode_init(struct lzx_stream *, int); +static int lzx_read_blocks(struct lzx_stream *, int); +static int lzx_decode_blocks(struct lzx_stream *, int); +static void lzx_decode_free(struct lzx_stream *); +static void lzx_translation(struct lzx_stream *, void *, size_t, uint32_t); +static void lzx_cleanup_bitstream(struct lzx_stream *); +static int lzx_decode(struct lzx_stream *, int); +static int lzx_read_pre_tree(struct lzx_stream *); +static int lzx_read_bitlen(struct lzx_stream *, struct huffman *, int); +static int lzx_huffman_init(struct huffman *, size_t, int); +static void lzx_huffman_free(struct huffman *); +static int lzx_make_huffman_table(struct huffman *); +static int inline lzx_decode_huffman(struct huffman *, unsigned); +static int lzx_decode_huffman_tree(struct huffman *, unsigned, int); + + +int +archive_read_support_format_cab(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct cab *cab; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_cab"); + + cab = (struct cab *)calloc(1, sizeof(*cab)); + if (cab == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate CAB data"); + return (ARCHIVE_FATAL); + } + archive_string_init(&cab->ws); + archive_wstring_ensure(&cab->ws, 256); + + r = __archive_read_register_format(a, + cab, + "cab", + archive_read_format_cab_bid, + archive_read_format_cab_options, + archive_read_format_cab_read_header, + archive_read_format_cab_read_data, + archive_read_format_cab_read_data_skip, + archive_read_format_cab_cleanup); + + if (r != ARCHIVE_OK) + free(cab); + return (ARCHIVE_OK); +} + +static int +find_cab_magic(const char *p) +{ + switch (p[4]) { + case 0: + /* + * Note: Self-Extraction program has 'MSCF' string in their + * program. If we were finding 'MSCF' string only, we got + * wrong place for Cabinet header, thus, we have to check + * following four bytes which are reserved and must be set + * to zero. + */ + if (memcmp(p, "MSCF\0\0\0\0", 8) == 0) + return 0; + return 5; + case 'F': return 1; + case 'C': return 2; + case 'S': return 3; + case 'M': return 4; + default: return 5; + } +} + +static int +archive_read_format_cab_bid(struct archive_read *a, int best_bid) +{ + const char *p; + ssize_t bytes_avail, offset, window; + + /* If there's already a better bid than we can ever + make, don't bother testing. */ + if (best_bid > 64) + return (-1); + + if ((p = __archive_read_ahead(a, 8, NULL)) == NULL) + return (-1); + + if (memcmp(p, "MSCF\0\0\0\0", 8) == 0) + return (64); + + /* + * Attempt to handle self-extracting archives + * by noting a PE header and searching forward + * up to 128k for a 'MSCF' marker. + */ + if (p[0] == 'M' && p[1] == 'Z') { + offset = 0; + window = 4096; + while (offset < (1024 * 128)) { + const char *h = __archive_read_ahead(a, offset + window, + &bytes_avail); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 128) + return (0); + continue; + } + p = h + offset; + while (p + 8 < h + bytes_avail) { + int next; + if ((next = find_cab_magic(p)) == 0) + return (64); + p += next; + } + offset = p - h; + } + } + return (0); +} + +static int +archive_read_format_cab_options(struct archive_read *a, + const char *key, const char *val) +{ + struct cab *cab; + int ret = ARCHIVE_FAILED; + + cab = (struct cab *)(a->format->data); + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "cab: hdrcharset option needs a character-set name"); + else { + cab->sconv = archive_string_conversion_from_charset( + &a->archive, val, 0); + if (cab->sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "cab: unknown keyword ``%s''", key); + + return (ret); +} + +static int +cab_skip_sfx(struct archive_read *a) +{ + const char *p, *q; + size_t skip; + ssize_t bytes, window; + + window = 4096; + for (;;) { + const char *h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining size are less than window. */ + window >>= 1; + if (window < 128) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out CAB header"); + return (ARCHIVE_FATAL); + } + continue; + } + p = h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the cab header. + */ + while (p + 8 < q) { + int next; + if ((next = find_cab_magic(p)) == 0) { + skip = p - h; + __archive_read_consume(a, skip); + return (ARCHIVE_OK); + } + p += next; + } + skip = p - h; + __archive_read_consume(a, skip); + } +} + +static int +truncated_error(struct archive_read *a) +{ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated CAB header"); + return (ARCHIVE_FATAL); +} + +static int +cab_strnlen(const unsigned char *p, size_t maxlen) +{ + size_t i; + + for (i = 0; i <= maxlen; i++) { + if (p[i] == 0) + break; + } + if (i > maxlen) + return (-1);/* invalid */ + return (i); +} + +/* Read bytes as much as remaining. */ +static const void * +cab_read_ahead_remaining(struct archive_read *a, size_t min, ssize_t *avail) +{ + const void *p; + + while (min > 0) { + p = __archive_read_ahead(a, min, avail); + if (p != NULL) + return (p); + min--; + } + return (NULL); +} + +/* Convert a path separator '\' -> '/' */ +static int +cab_convert_path_separator_1(struct archive_string *fn, unsigned char attr) +{ + size_t i; + int mb; + + /* Easy check if we have '\' in multi-byte string. */ + mb = 0; + for (i = 0; i < archive_strlen(fn); i++) { + if (fn->s[i] == '\\') { + if (mb) { + /* This may be second byte of multi-byte + * character. */ + break; + } + fn->s[i] = '/'; + mb = 0; + } else if ((fn->s[i] & 0x80) && !(attr & ATTR_NAME_IS_UTF)) + mb = 1; + else + mb = 0; + } + if (i == archive_strlen(fn)) + return (0); + return (-1); +} + +/* + * Replace a character '\' with '/' in wide character. + */ +static void +cab_convert_path_separator_2(struct cab *cab, struct archive_entry *entry) +{ + const wchar_t *wp; + size_t i; + + /* If a conversion to wide character failed, force the replacement. */ + if ((wp = archive_entry_pathname_w(entry)) != NULL) { + archive_wstrcpy(&(cab->ws), wp); + for (i = 0; i < archive_strlen(&(cab->ws)); i++) { + if (cab->ws.s[i] == L'\\') + cab->ws.s[i] = L'/'; + } + archive_entry_copy_pathname_w(entry, cab->ws.s); + } +} + +/* + * Read CFHEADER, CFFOLDER and CFFILE. + */ +static int +cab_read_header(struct archive_read *a) +{ + const unsigned char *p; + struct cab *cab; + struct cfheader *hd; + size_t bytes, used; + int64_t skip; + int err, i, len; + int cur_folder, prev_folder; + uint32_t offset32; + + a->archive.archive_format = ARCHIVE_FORMAT_CAB; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "CAB"; + + if ((p = __archive_read_ahead(a, 42, NULL)) == NULL) + return (truncated_error(a)); + + cab = (struct cab *)(a->format->data); + if (cab->found_header == 0 && + p[0] == 'M' && p[1] == 'Z') { + /* This is an executable? Must be self-extracting... */ + err = cab_skip_sfx(a); + if (err < ARCHIVE_WARN) + return (err); + + if ((p = __archive_read_ahead(a, sizeof(*p), NULL)) == NULL) + return (truncated_error(a)); + } + + cab->cab_offset = 0; + /* + * Read CFHEADER. + */ + hd = &cab->cfheader; + if (p[CFHEADER_signature+0] != 'M' || p[CFHEADER_signature+1] != 'S' || + p[CFHEADER_signature+2] != 'C' || p[CFHEADER_signature+3] != 'F') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out CAB header"); + return (ARCHIVE_FATAL); + } + hd->total_bytes = archive_le32dec(p + CFHEADER_cbCabinet); + hd->files_offset = archive_le32dec(p + CFHEADER_coffFiles); + hd->minor = p[CFHEADER_versionMinor]; + hd->major = p[CFHEADER_versionMajor]; + hd->folder_count = archive_le16dec(p + CFHEADER_cFolders); + if (hd->folder_count == 0) + goto invalid; + hd->file_count = archive_le16dec(p + CFHEADER_cFiles); + if (hd->file_count == 0) + goto invalid; + hd->flags = archive_le16dec(p + CFHEADER_flags); + hd->setid = archive_le16dec(p + CFHEADER_setID); + hd->cabinet = archive_le16dec(p + CFHEADER_iCabinet); + used = CFHEADER_iCabinet + 2; + if (hd->flags & RESERVE_PRESENT) { + uint16_t cfheader; + cfheader = archive_le16dec(p + CFHEADER_cbCFHeader); + if (cfheader > 60000U) + goto invalid; + hd->cffolder = p[CFHEADER_cbCFFolder]; + hd->cfdata = p[CFHEADER_cbCFData]; + used += 4;/* cbCFHeader, cbCFFolder and cbCFData */ + used += cfheader;/* abReserve */ + } else + hd->cffolder = 0;/* Avoid compiling warning. */ + if (hd->flags & PREV_CABINET) { + /* How many bytes are used for szCabinetPrev. */ + if ((p = __archive_read_ahead(a, used+256, NULL)) == NULL) + return (truncated_error(a)); + if ((len = cab_strnlen(p + used, 255)) <= 0) + goto invalid; + used += len + 1; + /* How many bytes are used for szDiskPrev. */ + if ((p = __archive_read_ahead(a, used+256, NULL)) == NULL) + return (truncated_error(a)); + if ((len = cab_strnlen(p + used, 255)) <= 0) + goto invalid; + used += len + 1; + } + if (hd->flags & NEXT_CABINET) { + /* How many bytes are used for szCabinetNext. */ + if ((p = __archive_read_ahead(a, used+256, NULL)) == NULL) + return (truncated_error(a)); + if ((len = cab_strnlen(p + used, 255)) <= 0) + goto invalid; + used += len + 1; + /* How many bytes are used for szDiskNext. */ + if ((p = __archive_read_ahead(a, used+256, NULL)) == NULL) + return (truncated_error(a)); + if ((len = cab_strnlen(p + used, 255)) <= 0) + goto invalid; + used += len + 1; + } + __archive_read_consume(a, used); + cab->cab_offset += used; + used = 0; + + /* + * Read CFFOLDER. + */ + hd->folder_array = (struct cffolder *)calloc( + hd->folder_count, sizeof(struct cffolder)); + if (hd->folder_array == NULL) + goto nomem; + + bytes = 8; + if (hd->flags & RESERVE_PRESENT) + bytes += hd->cffolder; + bytes *= hd->folder_count; + if ((p = __archive_read_ahead(a, bytes, NULL)) == NULL) + return (truncated_error(a)); + offset32 = 0; + for (i = 0; i < hd->folder_count; i++) { + struct cffolder *folder = &(hd->folder_array[i]); + folder->cfdata_offset_in_cab = + archive_le32dec(p + CFFOLDER_coffCabStart); + folder->cfdata_count = archive_le16dec(p+CFFOLDER_cCFData); + folder->comptype = + archive_le16dec(p+CFFOLDER_typeCompress) & 0x0F; + folder->compdata = + archive_le16dec(p+CFFOLDER_typeCompress) >> 8; + /* Get a compression name. */ + if (folder->comptype < + sizeof(compression_name) / sizeof(compression_name[0])) + folder->compname = compression_name[folder->comptype]; + else + folder->compname = "UNKNOWN"; + p += 8; + used += 8; + if (hd->flags & RESERVE_PRESENT) { + p += hd->cffolder;/* abReserve */ + used += hd->cffolder; + } + /* + * Sanity check if each data is acceptable. + */ + if (offset32 >= folder->cfdata_offset_in_cab) + goto invalid; + offset32 = folder->cfdata_offset_in_cab; + + /* Set a request to initialize zlib for the CFDATA of + * this folder. */ + folder->decompress_init = 0; + } + __archive_read_consume(a, used); + cab->cab_offset += used; + + /* + * Read CFFILE. + */ + /* Seek read pointer to the offset of CFFILE if needed. */ + skip = (int64_t)hd->files_offset - cab->cab_offset; + if (skip < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid offset of CFFILE %jd < %jd", + (intmax_t)hd->files_offset, (intmax_t)cab->cab_offset); + return (ARCHIVE_FATAL); + } + if (skip) { + __archive_read_consume(a, skip); + cab->cab_offset += skip; + } + /* Allocate memory for CFDATA */ + hd->file_array = (struct cffile *)calloc( + hd->file_count, sizeof(struct cffile)); + if (hd->file_array == NULL) + goto nomem; + + prev_folder = -1; + for (i = 0; i < hd->file_count; i++) { + struct cffile *file = &(hd->file_array[i]); + ssize_t avail; + + if ((p = __archive_read_ahead(a, 16, NULL)) == NULL) + return (truncated_error(a)); + file->uncompressed_size = archive_le32dec(p + CFFILE_cbFile); + file->offset = archive_le32dec(p + CFFILE_uoffFolderStart); + file->folder = archive_le16dec(p + CFFILE_iFolder); + file->mtime = cab_dos_time(p + CFFILE_date_time); + file->attr = archive_le16dec(p + CFFILE_attribs); + __archive_read_consume(a, 16); + + cab->cab_offset += 16; + if ((p = cab_read_ahead_remaining(a, 256, &avail)) == NULL) + return (truncated_error(a)); + if ((len = cab_strnlen(p, avail-1)) <= 0) + goto invalid; + + /* Copy a pathname. */ + archive_string_init(&(file->pathname)); + archive_strncpy(&(file->pathname), p, len); + __archive_read_consume(a, len + 1); + cab->cab_offset += len + 1; + + /* + * Sanity check if each data is acceptable. + */ + if (file->uncompressed_size > 0x7FFF8000) + goto invalid;/* Too large */ + if ((int64_t)file->offset + (int64_t)file->uncompressed_size + > ARCHIVE_LITERAL_LL(0x7FFF8000)) + goto invalid;/* Too large */ + switch (file->folder) { + case iFoldCONTINUED_TO_NEXT: + /* This must be last file in a folder. */ + if (i != hd->file_count -1) + goto invalid; + cur_folder = hd->folder_count -1; + break; + case iFoldCONTINUED_PREV_AND_NEXT: + /* This must be only one file in a folder. */ + if (hd->file_count != 1) + goto invalid; + /* FALL THROUGH */ + case iFoldCONTINUED_FROM_PREV: + /* This must be first file in a folder. */ + if (i != 0) + goto invalid; + prev_folder = cur_folder = 0; + offset32 = file->offset; + break; + default: + if (file->folder >= hd->folder_count) + goto invalid; + cur_folder = file->folder; + break; + } + /* Dot not back track. */ + if (cur_folder < prev_folder) + goto invalid; + if (cur_folder != prev_folder) + offset32 = 0; + prev_folder = cur_folder; + + /* Make sure there are not any blanks from last file + * contents. */ + if (offset32 != file->offset) + goto invalid; + offset32 += file->uncompressed_size; + + /* CFDATA is available for file contents. */ + if (file->uncompressed_size > 0 && + hd->folder_array[cur_folder].cfdata_count == 0) + goto invalid; + } + + if (hd->cabinet != 0 || hd->flags & (PREV_CABINET | NEXT_CABINET)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Multivolume cabinet file is unsupported"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid CAB header"); + return (ARCHIVE_FATAL); +nomem: + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for CAB data"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_cab_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct cab *cab; + struct cfheader *hd; + struct cffolder *prev_folder; + struct cffile *file; + struct archive_string_conv *sconv; + int err = ARCHIVE_OK, r; + + cab = (struct cab *)(a->format->data); + if (cab->found_header == 0) { + err = cab_read_header(a); + if (err < ARCHIVE_WARN) + return (err); + /* We've found the header. */ + cab->found_header = 1; + } + hd = &cab->cfheader; + + if (hd->file_index >= hd->file_count) { + cab->end_of_archive = 1; + return (ARCHIVE_EOF); + } + file = &hd->file_array[hd->file_index++]; + + cab->end_of_entry = 0; + cab->end_of_entry_cleanup = 0; + cab->entry_compressed_bytes_read = 0; + cab->entry_uncompressed_bytes_read = 0; + cab->entry_unconsumed = 0; + cab->entry_cffile = file; + + /* + * Choose a proper folder. + */ + prev_folder = cab->entry_cffolder; + switch (file->folder) { + case iFoldCONTINUED_FROM_PREV: + case iFoldCONTINUED_PREV_AND_NEXT: + cab->entry_cffolder = &hd->folder_array[0]; + break; + case iFoldCONTINUED_TO_NEXT: + cab->entry_cffolder = &hd->folder_array[hd->folder_count-1]; + break; + default: + cab->entry_cffolder = &hd->folder_array[file->folder]; + break; + } + /* If a cffolder of this file is changed, reset a cfdata to read + * file contents from next cfdata. */ + if (prev_folder != cab->entry_cffolder) + cab->entry_cfdata = NULL; + + /* If a pathname is UTF-8, prepare a string conversion object + * for UTF-8 and use it. */ + if (file->attr & ATTR_NAME_IS_UTF) { + if (cab->sconv_utf8 == NULL) { + cab->sconv_utf8 = + archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (cab->sconv_utf8 == NULL) + return (ARCHIVE_FATAL); + } + sconv = cab->sconv_utf8; + } else if (cab->sconv != NULL) { + /* Choose the conversion specified by the option. */ + sconv = cab->sconv; + } else { + /* Choose the default conversion. */ + if (!cab->init_default_conversion) { + cab->sconv_default = + archive_string_default_conversion_for_read( + &(a->archive)); + cab->init_default_conversion = 1; + } + sconv = cab->sconv_default; + } + + /* + * Set a default value and common data + */ + r = cab_convert_path_separator_1(&(file->pathname), file->attr); + if (archive_entry_copy_pathname_l(entry, file->pathname.s, + archive_strlen(&(file->pathname)), sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(sconv)); + err = ARCHIVE_WARN; + } + if (r < 0) { + /* Convert a path separator '\' -> '/' */ + cab_convert_path_separator_2(cab, entry); + } + + archive_entry_set_size(entry, file->uncompressed_size); + if (file->attr & ATTR_RDONLY) + archive_entry_set_mode(entry, AE_IFREG | 0555); + else + archive_entry_set_mode(entry, AE_IFREG | 0777); + archive_entry_set_mtime(entry, file->mtime, 0); + + cab->entry_bytes_remaining = file->uncompressed_size; + cab->entry_offset = 0; + /* We don't need compress data. */ + if (file->uncompressed_size == 0) + cab->end_of_entry_cleanup = cab->end_of_entry = 1; + + /* Set up a more descriptive format name. */ + sprintf(cab->format_name, "CAB %d.%d (%s)", + hd->major, hd->minor, cab->entry_cffolder->compname); + a->archive.archive_format_name = cab->format_name; + + return (err); +} + +static int +archive_read_format_cab_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct cab *cab = (struct cab *)(a->format->data); + int r; + + switch (cab->entry_cffile->folder) { + case iFoldCONTINUED_FROM_PREV: + case iFoldCONTINUED_TO_NEXT: + case iFoldCONTINUED_PREV_AND_NEXT: + *buff = NULL; + *size = 0; + *offset = 0; + archive_clear_error(&a->archive); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Cannot restore this file split in multivolume."); + return (ARCHIVE_FAILED); + default: + break; + } + if (cab->entry_unconsumed) { + /* Consume as much as the compressor actually used. */ + r = cab_consume_cfdata(a, cab->entry_unconsumed); + cab->entry_unconsumed = 0; + if (r < 0) + return (r); + } + if (cab->end_of_archive || cab->end_of_entry) { + if (!cab->end_of_entry_cleanup) { + /* End-of-entry cleanup done. */ + cab->end_of_entry_cleanup = 1; + } + *offset = cab->entry_offset; + *size = 0; + *buff = NULL; + return (ARCHIVE_EOF); + } + + return (cab_read_data(a, buff, size, offset)); +} + +static uint32_t +cab_checksum_cfdata_4(const void *p, size_t bytes, uint32_t seed) +{ + const unsigned char *b; + int u32num; + uint32_t sum; + + u32num = bytes / 4; + sum = seed; + b = p; + while (--u32num >= 0) { + sum ^= archive_le32dec(b); + b += 4; + } + return (sum); +} + +static uint32_t +cab_checksum_cfdata(const void *p, size_t bytes, uint32_t seed) +{ + const unsigned char *b; + uint32_t sum; + uint32_t t; + + sum = cab_checksum_cfdata_4(p, bytes, seed); + b = p; + b += bytes & ~3; + t = 0; + switch (bytes & 3) { + case 3: + t |= ((uint32_t)(*b++)) << 16; + /* FALL THROUGH */ + case 2: + t |= ((uint32_t)(*b++)) << 8; + /* FALL THROUGH */ + case 1: + t |= *b; + /* FALL THROUGH */ + default: + break; + } + sum ^= t; + + return (sum); +} + +static void +cab_checksum_update(struct archive_read *a, size_t bytes) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata = cab->entry_cfdata; + const unsigned char *p; + size_t sumbytes; + + if (cfdata->sum == 0 || cfdata->sum_ptr == NULL) + return; + /* + * Calculate the sum of this CFDATA. + * Make sure CFDATA must be calculated in four bytes. + */ + p = cfdata->sum_ptr; + sumbytes = bytes; + if (cfdata->sum_extra_avail) { + while (cfdata->sum_extra_avail < 4 && sumbytes > 0) { + cfdata->sum_extra[ + cfdata->sum_extra_avail++] = *p++; + sumbytes--; + } + if (cfdata->sum_extra_avail == 4) { + cfdata->sum_calculated = cab_checksum_cfdata_4( + cfdata->sum_extra, 4, cfdata->sum_calculated); + cfdata->sum_extra_avail = 0; + } + } + if (sumbytes) { + int odd = sumbytes & 3; + if (sumbytes - odd > 0) + cfdata->sum_calculated = cab_checksum_cfdata_4( + p, sumbytes - odd, cfdata->sum_calculated); + if (odd) + memcpy(cfdata->sum_extra, p + sumbytes - odd, odd); + cfdata->sum_extra_avail = odd; + } + cfdata->sum_ptr = NULL; +} + +static int +cab_checksum_finish(struct archive_read *a) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata = cab->entry_cfdata; + int l; + + /* Do not need to compute a sum. */ + if (cfdata->sum == 0) + return (ARCHIVE_OK); + + /* + * Calculate the sum of remaining CFDATA. + */ + if (cfdata->sum_extra_avail) { + cfdata->sum_calculated = + cab_checksum_cfdata(cfdata->sum_extra, + cfdata->sum_extra_avail, cfdata->sum_calculated); + cfdata->sum_extra_avail = 0; + } + + l = 4; + if (cab->cfheader.flags & RESERVE_PRESENT) + l += cab->cfheader.cfdata; + cfdata->sum_calculated = cab_checksum_cfdata( + cfdata->memimage + CFDATA_cbData, l, cfdata->sum_calculated); + if (cfdata->sum_calculated != cfdata->sum) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Checksum error CFDATA[%d] %x:%x in %d bytes", + cab->entry_cffolder->cfdata_index -1, + cfdata->sum, cfdata->sum_calculated, + cfdata->compressed_size); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); +} + +/* + * Read CFDATA if needed. + */ +static int +cab_next_cfdata(struct archive_read *a) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata = cab->entry_cfdata; + + /* There are remaining bytes in current CFDATA, use it first. */ + if (cfdata != NULL && cfdata->uncompressed_bytes_remaining > 0) + return (ARCHIVE_OK); + + if (cfdata == NULL) { + int64_t skip; + + cab->entry_cffolder->cfdata_index = 0; + + /* Seek read pointer to the offset of CFDATA if needed. */ + skip = cab->entry_cffolder->cfdata_offset_in_cab + - cab->cab_offset; + if (skip < 0) { + int folder_index; + switch (cab->entry_cffile->folder) { + case iFoldCONTINUED_FROM_PREV: + case iFoldCONTINUED_PREV_AND_NEXT: + folder_index = 0; + break; + case iFoldCONTINUED_TO_NEXT: + folder_index = cab->cfheader.folder_count-1; + break; + default: + folder_index = cab->entry_cffile->folder; + break; + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid offset of CFDATA in folder(%d) %jd < %jd", + folder_index, + (intmax_t)cab->entry_cffolder->cfdata_offset_in_cab, + (intmax_t)cab->cab_offset); + return (ARCHIVE_FATAL); + } + if (skip > 0) { + if (__archive_read_consume(a, skip) < 0) + return (ARCHIVE_FATAL); + cab->cab_offset = + cab->entry_cffolder->cfdata_offset_in_cab; + } + } + + /* + * Read a CFDATA. + */ + if (cab->entry_cffolder->cfdata_index < + cab->entry_cffolder->cfdata_count) { + const unsigned char *p; + int l; + + cfdata = &(cab->entry_cffolder->cfdata); + cab->entry_cffolder->cfdata_index++; + cab->entry_cfdata = cfdata; + cfdata->sum_calculated = 0; + cfdata->sum_extra_avail = 0; + cfdata->sum_ptr = NULL; + l = 8; + if (cab->cfheader.flags & RESERVE_PRESENT) + l += cab->cfheader.cfdata; + if ((p = __archive_read_ahead(a, l, NULL)) == NULL) + return (truncated_error(a)); + cfdata->sum = archive_le32dec(p + CFDATA_csum); + cfdata->compressed_size = archive_le16dec(p + CFDATA_cbData); + cfdata->compressed_bytes_remaining = cfdata->compressed_size; + cfdata->uncompressed_size = + archive_le16dec(p + CFDATA_cbUncomp); + cfdata->uncompressed_bytes_remaining = + cfdata->uncompressed_size; + cfdata->uncompressed_avail = 0; + cfdata->read_offset = 0; + cfdata->unconsumed = 0; + + /* + * Sanity check if data size is acceptable. + */ + if (cfdata->compressed_size == 0 || + cfdata->compressed_size > (0x8000+6144)) + goto invalid; + if (cfdata->uncompressed_size > 0x8000) + goto invalid; + if (cfdata->uncompressed_size == 0) { + switch (cab->entry_cffile->folder) { + case iFoldCONTINUED_PREV_AND_NEXT: + case iFoldCONTINUED_TO_NEXT: + break; + case iFoldCONTINUED_FROM_PREV: + default: + goto invalid; + } + } + /* If CFDATA is not last in a folder, an uncompressed + * size must be 0x8000(32KBi) */ + if ((cab->entry_cffolder->cfdata_index < + cab->entry_cffolder->cfdata_count) && + cfdata->uncompressed_size != 0x8000) + goto invalid; + + /* A compressed data size and an uncompressed data size must + * be the same in no compression mode. */ + if (cab->entry_cffolder->comptype == COMPTYPE_NONE && + cfdata->compressed_size != cfdata->uncompressed_size) + goto invalid; + + /* + * Save CFDATA image for sum check. + */ + if (cfdata->memimage_size < (size_t)l) { + free(cfdata->memimage); + cfdata->memimage = malloc(l); + if (cfdata->memimage == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for CAB data"); + return (ARCHIVE_FATAL); + } + cfdata->memimage_size = l; + } + memcpy(cfdata->memimage, p, l); + + /* Consume bytes as much as we used. */ + __archive_read_consume(a, l); + cab->cab_offset += l; + } else if (cab->entry_cffolder->cfdata_count > 0) { + /* Run out of all CFDATA in a folder. */ + cfdata->compressed_size = 0; + cfdata->uncompressed_size = 0; + cfdata->compressed_bytes_remaining = 0; + cfdata->uncompressed_bytes_remaining = 0; + } else { + /* Current folder does not have any CFDATA. */ + cfdata = &(cab->entry_cffolder->cfdata); + cab->entry_cfdata = cfdata; + memset(cfdata, 0, sizeof(*cfdata)); + } + return (ARCHIVE_OK); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid CFDATA"); + return (ARCHIVE_FATAL); +} + +/* + * Read ahead CFDATA. + */ +static const void * +cab_read_ahead_cfdata(struct archive_read *a, ssize_t *avail) +{ + struct cab *cab = (struct cab *)(a->format->data); + int err; + + err = cab_next_cfdata(a); + if (err < ARCHIVE_OK) { + *avail = err; + return (NULL); + } + + switch (cab->entry_cffolder->comptype) { + case COMPTYPE_NONE: + return (cab_read_ahead_cfdata_none(a, avail)); + case COMPTYPE_MSZIP: + return (cab_read_ahead_cfdata_deflate(a, avail)); + case COMPTYPE_LZX: + return (cab_read_ahead_cfdata_lzx(a, avail)); + default: /* Unsupported compression. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported CAB compression : %s", + cab->entry_cffolder->compname); + *avail = ARCHIVE_FAILED; + return (NULL); + } +} + +/* + * Read ahead CFDATA as uncompressed data. + */ +static const void * +cab_read_ahead_cfdata_none(struct archive_read *a, ssize_t *avail) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata; + const void *d; + int64_t skipped_bytes; + + cfdata = cab->entry_cfdata; + + if (cfdata->uncompressed_avail == 0 && + cfdata->read_offset > 0) { + /* we've already skipped some bytes before really read. */ + skipped_bytes = cfdata->read_offset; + cfdata->read_offset = 0; + cfdata->uncompressed_bytes_remaining += skipped_bytes; + } else + skipped_bytes = 0; + do { + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + d = __archive_read_ahead(a, 1, avail); + if (*avail <= 0) { + *avail = truncated_error(a); + return (NULL); + } + if (*avail > cfdata->uncompressed_bytes_remaining) + *avail = cfdata->uncompressed_bytes_remaining; + cfdata->uncompressed_avail = cfdata->uncompressed_size; + cfdata->unconsumed = *avail; + cfdata->sum_ptr = d; + if (skipped_bytes > 0) { + skipped_bytes = + cab_minimum_consume_cfdata(a, skipped_bytes); + if (skipped_bytes < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + continue; + } + } while (0); + + return (d); +} + +/* + * Read ahead CFDATA as deflate data. + */ +#ifdef HAVE_ZLIB_H +static const void * +cab_read_ahead_cfdata_deflate(struct archive_read *a, ssize_t *avail) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata; + const void *d; + int r, mszip; + uint16_t uavail; + char eod = 0; + + cfdata = cab->entry_cfdata; + /* If the buffer hasn't been allocated, allocate it now. */ + if (cab->uncompressed_buffer == NULL) { + cab->uncompressed_buffer_size = 0x8000; + cab->uncompressed_buffer + = (unsigned char *)malloc(cab->uncompressed_buffer_size); + if (cab->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for CAB reader"); + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + + uavail = cfdata->uncompressed_avail; + if (uavail == cfdata->uncompressed_size) { + d = cab->uncompressed_buffer + cfdata->read_offset; + *avail = uavail - cfdata->read_offset; + return (d); + } + + if (!cab->entry_cffolder->decompress_init) { + cab->stream.next_in = NULL; + cab->stream.avail_in = 0; + cab->stream.total_in = 0; + cab->stream.next_out = NULL; + cab->stream.avail_out = 0; + cab->stream.total_out = 0; + if (cab->stream_valid) + r = inflateReset(&cab->stream); + else + r = inflateInit2(&cab->stream, + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize deflate decompression."); + *avail = ARCHIVE_FATAL; + return (NULL); + } + /* Stream structure has been set up. */ + cab->stream_valid = 1; + /* We've initialized decompression for this stream. */ + cab->entry_cffolder->decompress_init = 1; + } + + if (cfdata->compressed_bytes_remaining == cfdata->compressed_size) + mszip = 2; + else + mszip = 0; + eod = 0; + cab->stream.total_out = uavail; + /* + * We always uncompress all data in current CFDATA. + */ + while (!eod && cab->stream.total_out < cfdata->uncompressed_size) { + ssize_t bytes_avail; + + cab->stream.next_out = + cab->uncompressed_buffer + cab->stream.total_out; + cab->stream.avail_out = + cfdata->uncompressed_size - cab->stream.total_out; + + d = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + *avail = truncated_error(a); + return (NULL); + } + if (bytes_avail > cfdata->compressed_bytes_remaining) + bytes_avail = cfdata->compressed_bytes_remaining; + /* + * A bug in zlib.h: stream.next_in should be marked 'const' + * but isn't (the library never alters data through the + * next_in pointer, only reads it). The result: this ugly + * cast to remove 'const'. + */ + cab->stream.next_in = (Bytef *)(uintptr_t)d; + cab->stream.avail_in = bytes_avail; + cab->stream.total_in = 0; + + /* Cut out a tow-byte MSZIP signature(0x43, 0x4b). */ + if (mszip > 0) { + if (bytes_avail <= mszip) { + if (mszip == 2) { + if (cab->stream.next_in[0] != 0x43) + goto nomszip; + if (bytes_avail > 1 && + cab->stream.next_in[1] != 0x4b) + goto nomszip; + } else if (cab->stream.next_in[0] != 0x4b) + goto nomszip; + cfdata->unconsumed = bytes_avail; + cfdata->sum_ptr = d; + if (cab_minimum_consume_cfdata( + a, cfdata->unconsumed) < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + mszip -= bytes_avail; + continue; + } + if (mszip == 1 && cab->stream.next_in[0] != 0x4b) + goto nomszip; + else if (cab->stream.next_in[0] != 0x43 || + cab->stream.next_in[1] != 0x4b) + goto nomszip; + cab->stream.next_in += mszip; + cab->stream.avail_in -= mszip; + cab->stream.total_in += mszip; + mszip = 0; + } + + r = inflate(&cab->stream, 0); + switch (r) { + case Z_OK: + break; + case Z_STREAM_END: + eod = 1; + break; + default: + goto zlibfailed; + } + cfdata->unconsumed = cab->stream.total_in; + cfdata->sum_ptr = d; + if (cab_minimum_consume_cfdata(a, cfdata->unconsumed) < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + uavail = cab->stream.total_out; + + if (uavail < cfdata->uncompressed_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid uncompressed size (%d < %d)", + uavail, cfdata->uncompressed_size); + *avail = ARCHIVE_FATAL; + return (NULL); + } + + /* + * Note: I suspect there is a bug in makecab.exe because, in rare + * case, compressed bytes are still remaining regardless we have + * gotten all uncompressed bytes, which size is recoded in CFDATA, + * as much as we need, and we have to use the garbage so as to + * correctly compute the sum of CFDATA accordingly. + */ + if (cfdata->compressed_bytes_remaining > 0) { + ssize_t bytes_avail; + + d = __archive_read_ahead(a, cfdata->compressed_bytes_remaining, + &bytes_avail); + if (bytes_avail <= 0) { + *avail = truncated_error(a); + return (NULL); + } + cfdata->unconsumed = cfdata->compressed_bytes_remaining; + cfdata->sum_ptr = d; + if (cab_minimum_consume_cfdata(a, cfdata->unconsumed) < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + + /* + * Set dictionary data for decompressing of next CFDATA, which + * in the same folder. This is why we always do decompress CFDATA + * even if beginning CFDATA or some of CFDATA are not used in + * skipping file data. + */ + if (cab->entry_cffolder->cfdata_index < + cab->entry_cffolder->cfdata_count) { + r = inflateReset(&cab->stream); + if (r != Z_OK) + goto zlibfailed; + r = inflateSetDictionary(&cab->stream, + cab->uncompressed_buffer, cfdata->uncompressed_size); + if (r != Z_OK) + goto zlibfailed; + } + + d = cab->uncompressed_buffer + cfdata->read_offset; + *avail = uavail - cfdata->read_offset; + cfdata->uncompressed_avail = uavail; + + return (d); + +zlibfailed: + switch (r) { + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Out of memory for deflate decompression"); + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Deflate decompression failed (%d)", r); + break; + } + *avail = ARCHIVE_FATAL; + return (NULL); +nomszip: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "CFDATA incorrect(no MSZIP signature)"); + *avail = ARCHIVE_FATAL; + return (NULL); +} + +#else /* HAVE_ZLIB_H */ + +static const void * +cab_read_ahead_cfdata_deflate(struct archive_read *a, ssize_t *avail) +{ + *avail = ARCHIVE_FATAL; + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "libarchive compiled without deflate support (no libz)"); + return (NULL); +} + +#endif /* HAVE_ZLIB_H */ + +static const void * +cab_read_ahead_cfdata_lzx(struct archive_read *a, ssize_t *avail) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata; + const void *d; + int r; + uint16_t uavail; + + cfdata = cab->entry_cfdata; + /* If the buffer hasn't been allocated, allocate it now. */ + if (cab->uncompressed_buffer == NULL) { + cab->uncompressed_buffer_size = 0x8000; + cab->uncompressed_buffer + = (unsigned char *)malloc(cab->uncompressed_buffer_size); + if (cab->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for CAB reader"); + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + + uavail = cfdata->uncompressed_avail; + if (uavail == cfdata->uncompressed_size) { + d = cab->uncompressed_buffer + cfdata->read_offset; + *avail = uavail - cfdata->read_offset; + return (d); + } + + if (!cab->entry_cffolder->decompress_init) { + r = lzx_decode_init(&cab->xstrm, + cab->entry_cffolder->compdata); + if (r != ARCHIVE_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize LZX decompression."); + *avail = ARCHIVE_FATAL; + return (NULL); + } + /* We've initialized decompression for this stream. */ + cab->entry_cffolder->decompress_init = 1; + } + + /* Clean up remaining bits of previous CFDATA. */ + lzx_cleanup_bitstream(&cab->xstrm); + cab->xstrm.total_out = uavail; + while (cab->xstrm.total_out < cfdata->uncompressed_size) { + ssize_t bytes_avail; + + cab->xstrm.next_out = + cab->uncompressed_buffer + cab->xstrm.total_out; + cab->xstrm.avail_out = + cfdata->uncompressed_size - cab->xstrm.total_out; + + d = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated CAB file data"); + *avail = ARCHIVE_FATAL; + return (NULL); + } + if (bytes_avail > cfdata->compressed_bytes_remaining) + bytes_avail = cfdata->compressed_bytes_remaining; + + cab->xstrm.next_in = d; + cab->xstrm.avail_in = bytes_avail; + cab->xstrm.total_in = 0; + r = lzx_decode(&cab->xstrm, + cfdata->compressed_bytes_remaining == bytes_avail); + switch (r) { + case ARCHIVE_OK: + case ARCHIVE_EOF: + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LZX decompression failed (%d)", r); + *avail = ARCHIVE_FATAL; + return (NULL); + } + cfdata->unconsumed = cab->xstrm.total_in; + cfdata->sum_ptr = d; + if (cab_minimum_consume_cfdata(a, cfdata->unconsumed) < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + + uavail = cab->xstrm.total_out; + /* + * Make sure a read pointer advances to next CFDATA. + */ + if (cfdata->compressed_bytes_remaining > 0) { + ssize_t bytes_avail; + + d = __archive_read_ahead(a, cfdata->compressed_bytes_remaining, + &bytes_avail); + if (bytes_avail <= 0) { + *avail = truncated_error(a); + return (NULL); + } + cfdata->unconsumed = cfdata->compressed_bytes_remaining; + cfdata->sum_ptr = d; + if (cab_minimum_consume_cfdata(a, cfdata->unconsumed) < 0) { + *avail = ARCHIVE_FATAL; + return (NULL); + } + } + + /* + * Translation reversal of x86 proccessor CALL byte sequence(E8). + */ + lzx_translation(&cab->xstrm, cab->uncompressed_buffer, + cfdata->uncompressed_size, + (cab->entry_cffolder->cfdata_index-1) * 0x8000); + + d = cab->uncompressed_buffer + cfdata->read_offset; + *avail = uavail - cfdata->read_offset; + cfdata->uncompressed_avail = uavail; + + return (d); +} + +/* + * Consume CFDATA. + * We always decompress CFDATA to consume CFDATA as much as we need + * in uncompressed bytes because all CFDATA in a folder are related + * so we do not skip any CFDATA without decompressing. + * Note: If the folder of a CFFILE is iFoldCONTINUED_PREV_AND_NEXT or + * iFoldCONTINUED_FROM_PREV, we won't decompress because a CFDATA for + * the CFFILE is remaining bytes of previous Multivolume CAB file. + */ +static int64_t +cab_consume_cfdata(struct archive_read *a, int64_t consumed_bytes) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata; + int64_t cbytes, rbytes; + int err; + + rbytes = cab_minimum_consume_cfdata(a, consumed_bytes); + if (rbytes < 0) + return (ARCHIVE_FATAL); + + cfdata = cab->entry_cfdata; + while (rbytes > 0) { + ssize_t avail; + + if (cfdata->compressed_size == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid CFDATA"); + return (ARCHIVE_FATAL); + } + cbytes = cfdata->uncompressed_bytes_remaining; + if (cbytes > rbytes) + cbytes = rbytes; + rbytes -= cbytes; + + if (cfdata->uncompressed_avail == 0 && + (cab->entry_cffolder->comptype == COMPTYPE_NONE || + cab->entry_cffile->folder == iFoldCONTINUED_PREV_AND_NEXT || + cab->entry_cffile->folder == iFoldCONTINUED_FROM_PREV)) { + /* We have not read any data yet. */ + if (cbytes == cfdata->uncompressed_bytes_remaining) { + /* Skip whole current CFDATA. */ + __archive_read_consume(a, + cfdata->compressed_size); + cab->cab_offset += cfdata->compressed_size; + cfdata->compressed_bytes_remaining = 0; + cfdata->uncompressed_bytes_remaining = 0; + err = cab_next_cfdata(a); + if (err < 0) + return (err); + cfdata = cab->entry_cfdata; + if (cfdata->uncompressed_size == 0) { + switch (cab->entry_cffile->folder) { + case iFoldCONTINUED_PREV_AND_NEXT: + case iFoldCONTINUED_TO_NEXT: + case iFoldCONTINUED_FROM_PREV: + rbytes = 0; + break; + default: + break; + } + } + continue; + } + cfdata->read_offset += cbytes; + cfdata->uncompressed_bytes_remaining -= cbytes; + break; + } else if (cbytes == 0) { + err = cab_next_cfdata(a); + if (err < 0) + return (err); + cfdata = cab->entry_cfdata; + if (cfdata->uncompressed_size == 0) { + switch (cab->entry_cffile->folder) { + case iFoldCONTINUED_PREV_AND_NEXT: + case iFoldCONTINUED_TO_NEXT: + case iFoldCONTINUED_FROM_PREV: + return (ARCHIVE_FATAL); + default: + break; + } + } + continue; + } + while (cbytes > 0) { + (void)cab_read_ahead_cfdata(a, &avail); + if (avail <= 0) + return (ARCHIVE_FATAL); + if (avail > cbytes) + avail = cbytes; + if (cab_minimum_consume_cfdata(a, avail) < 0) + return (ARCHIVE_FATAL); + cbytes -= avail; + } + } + return (consumed_bytes); +} + +/* + * Consume CFDATA as much as we have already gotten and + * compute the sum of CFDATA. + */ +static int64_t +cab_minimum_consume_cfdata(struct archive_read *a, int64_t consumed_bytes) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfdata *cfdata; + int64_t cbytes, rbytes; + int err; + + cfdata = cab->entry_cfdata; + rbytes = consumed_bytes; + if (cab->entry_cffolder->comptype == COMPTYPE_NONE) { + if (consumed_bytes < cfdata->unconsumed) + cbytes = consumed_bytes; + else + cbytes = cfdata->unconsumed; + rbytes -= cbytes; + cfdata->read_offset += cbytes; + cfdata->uncompressed_bytes_remaining -= cbytes; + cfdata->unconsumed -= cbytes; + } else { + cbytes = cfdata->uncompressed_avail - cfdata->read_offset; + if (cbytes > 0) { + if (consumed_bytes < cbytes) + cbytes = consumed_bytes; + rbytes -= cbytes; + cfdata->read_offset += cbytes; + cfdata->uncompressed_bytes_remaining -= cbytes; + } + + if (cfdata->unconsumed) { + cbytes = cfdata->unconsumed; + cfdata->unconsumed = 0; + } else + cbytes = 0; + } + if (cbytes) { + /* Compute the sum. */ + cab_checksum_update(a, cbytes); + + /* Consume as much as the compressor actually used. */ + __archive_read_consume(a, cbytes); + cab->cab_offset += cbytes; + cfdata->compressed_bytes_remaining -= cbytes; + if (cfdata->compressed_bytes_remaining == 0) { + err = cab_checksum_finish(a); + if (err < 0) + return (err); + } + } + return (rbytes); +} + +/* + * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets + * cab->end_of_entry if it consumes all of the data. + */ +static int +cab_read_data(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct cab *cab = (struct cab *)(a->format->data); + ssize_t bytes_avail; + + if (cab->entry_bytes_remaining == 0) { + *buff = NULL; + *size = 0; + *offset = cab->entry_offset; + cab->end_of_entry = 1; + return (ARCHIVE_OK); + } + + *buff = cab_read_ahead_cfdata(a, &bytes_avail); + if (bytes_avail <= 0) { + *buff = NULL; + *size = 0; + *offset = 0; + if (bytes_avail == 0 && + cab->entry_cfdata->uncompressed_size == 0) { + /* All of CFDATA in a folder has been handled. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, "Invalid CFDATA"); + return (ARCHIVE_FATAL); + } else + return (bytes_avail); + } + if (bytes_avail > cab->entry_bytes_remaining) + bytes_avail = cab->entry_bytes_remaining; + + *size = bytes_avail; + *offset = cab->entry_offset; + cab->entry_offset += bytes_avail; + cab->entry_bytes_remaining -= bytes_avail; + if (cab->entry_bytes_remaining == 0) + cab->end_of_entry = 1; + cab->entry_unconsumed = bytes_avail; + return (ARCHIVE_OK); +} + +static int +archive_read_format_cab_read_data_skip(struct archive_read *a) +{ + struct cab *cab; + int64_t bytes_skipped; + int r; + + cab = (struct cab *)(a->format->data); + + if (cab->end_of_archive) + return (ARCHIVE_EOF); + + if (cab->entry_unconsumed) { + /* Consume as much as the compressor actually used. */ + r = cab_consume_cfdata(a, cab->entry_unconsumed); + cab->entry_unconsumed = 0; + if (r < 0) + return (r); + } else if (cab->entry_cfdata == NULL) { + r = cab_next_cfdata(a); + if (r < 0) + return (r); + } + + /* if we've already read to end of data, we're done. */ + if (cab->end_of_entry_cleanup) + return (ARCHIVE_OK); + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = cab_consume_cfdata(a, cab->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + /* This entry is finished and done. */ + cab->end_of_entry_cleanup = cab->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_cab_cleanup(struct archive_read *a) +{ + struct cab *cab = (struct cab *)(a->format->data); + struct cfheader *hd = &cab->cfheader; + int i; + + if (hd->folder_array != NULL) { + for (i = 0; i < hd->folder_count; i++) + free(hd->folder_array[i].cfdata.memimage); + free(hd->folder_array); + } + if (hd->file_array != NULL) { + for (i = 0; i < cab->cfheader.file_count; i++) + archive_string_free(&(hd->file_array[i].pathname)); + free(hd->file_array); + } +#ifdef HAVE_ZLIB_H + if (cab->stream_valid) + inflateEnd(&cab->stream); +#endif + lzx_decode_free(&cab->xstrm); + archive_wstring_free(&cab->ws); + free(cab->uncompressed_buffer); + free(cab); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* Convert an MSDOS-style date/time into Unix-style time. */ +static time_t +cab_dos_time(const unsigned char *p) +{ + int msTime, msDate; + struct tm ts; + + msDate = archive_le16dec(p); + msTime = archive_le16dec(p+2); + + memset(&ts, 0, sizeof(ts)); + ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ + ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ + ts.tm_mday = msDate & 0x1f; /* Day of month. */ + ts.tm_hour = (msTime >> 11) & 0x1f; + ts.tm_min = (msTime >> 5) & 0x3f; + ts.tm_sec = (msTime << 1) & 0x3e; + ts.tm_isdst = -1; + return (mktime(&ts)); +} + +/***************************************************************** + * + * LZX decompression code. + * + *****************************************************************/ + +/* + * Initialize LZX decoder. + * + * Returns ARCHIVE_OK if initialization was successful. + * Returns ARCHIVE_FAILED if w_bits has unsupported value. + * Returns ARCHIVE_FATAL if initialization failed; memory allocation + * error occurred. + */ +static int +lzx_decode_init(struct lzx_stream *strm, int w_bits) +{ + struct lzx_dec *ds; + int slot, w_size, w_slot; + int base, footer; + + if (strm->ds == NULL) { + strm->ds = calloc(1, sizeof(*strm->ds)); + if (strm->ds == NULL) + return (ARCHIVE_FATAL); + } + ds = strm->ds; + ds->error = ARCHIVE_FAILED; + + /* Allow bits from 15(32KBi) up to 21(2MBi) */ + if (w_bits < SLOT_BASE || w_bits > SLOT_MAX) + return (ARCHIVE_FAILED); + + ds->error = ARCHIVE_FATAL; + + /* + * Alloc window + */ + w_size = ds->w_size; + w_slot = slots[w_bits - SLOT_BASE]; + ds->w_size = 1U << w_bits; + ds->w_mask = ds->w_size -1; + if (ds->w_buff == NULL || w_size != ds->w_size) { + free(ds->w_buff); + ds->w_buff = malloc(ds->w_size); + if (ds->w_buff == NULL) + return (ARCHIVE_FATAL); + free(ds->pos_tbl); + ds->pos_tbl = malloc(sizeof(ds->pos_tbl[0]) * w_slot); + if (ds->pos_tbl == NULL) + return (ARCHIVE_FATAL); + lzx_huffman_free(&(ds->mt)); + } + + base = footer = 0; + for (slot = 0; slot < w_slot; slot++) { + int n; + if (footer == 0) + base = slot; + else + base += 1 << footer; + if (footer < 17) { + footer = -2; + for (n = base; n; n >>= 1) + footer++; + if (footer <= 0) + footer = 0; + } + ds->pos_tbl[slot].base = base; + ds->pos_tbl[slot].footer_bits = footer; + } + + ds->w_pos = 0; + ds->state = 0; + ds->br.cache_buffer = 0; + ds->br.cache_avail = 0; + ds->r0 = ds->r1 = ds->r2 = 1; + + /* Initialize aligned offset tree. */ + if (lzx_huffman_init(&(ds->at), 8, 8) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Initialize pre-tree. */ + if (lzx_huffman_init(&(ds->pt), 20, 10) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Initialize Main tree. */ + if (lzx_huffman_init(&(ds->mt), 256+(w_slot<<3), 16) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Initialize Length tree. */ + if (lzx_huffman_init(&(ds->lt), 249, 16) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + ds->error = 0; + + return (ARCHIVE_OK); +} + +/* + * Release LZX decoder. + */ +static void +lzx_decode_free(struct lzx_stream *strm) +{ + + if (strm->ds == NULL) + return; + free(strm->ds->w_buff); + free(strm->ds->pos_tbl); + lzx_huffman_free(&(strm->ds->at)); + lzx_huffman_free(&(strm->ds->pt)); + lzx_huffman_free(&(strm->ds->mt)); + lzx_huffman_free(&(strm->ds->lt)); + free(strm->ds); + strm->ds = NULL; +} + +/* + * E8 Call Translation reversal. + */ +static void +lzx_translation(struct lzx_stream *strm, void *p, size_t size, uint32_t offset) +{ + struct lzx_dec *ds = strm->ds; + unsigned char *b, *end; + + if (!ds->translation || size <= 10) + return; + b = p; + end = b + size - 10; + while (b < end && (b = memchr(b, 0xE8, end - b)) != NULL) { + size_t i = b - (unsigned char *)p; + long cp, displacement, value; + + cp = offset + i; + value = archive_le32dec(&b[1]); + if (value >= -cp && value < (long)ds->translation_size) { + if (value >= 0) + displacement = value - cp; + else + displacement = value + ds->translation_size; + archive_le32enc(&b[1], (uint32_t)displacement); + } + b += 5; + } +} + +/* + * Bit stream reader. + */ +/* Check that the cache buffer has enough bits. */ +#define lzx_br_has(br, n) ((br)->cache_avail >= n) +/* Get compressed data by bit. */ +#define lzx_br_bits(br, n) \ + (((uint32_t)((br)->cache_buffer >> \ + ((br)->cache_avail - (n)))) & cache_masks[n]) +#define lzx_br_bits_forced(br, n) \ + (((uint32_t)((br)->cache_buffer << \ + ((n) - (br)->cache_avail))) & cache_masks[n]) +/* Read ahead to make sure the cache buffer has enough compressed data we + * will use. + * True : completed, there is enough data in the cache buffer. + * False : we met that strm->next_in is empty, we have to get following + * bytes. */ +#define lzx_br_read_ahead_0(strm, br, n) \ + (lzx_br_has((br), (n)) || lzx_br_fillup(strm, br)) +/* True : the cache buffer has some bits as much as we need. + * False : there are no enough bits in the cache buffer to be used, + * we have to get following bytes if we could. */ +#define lzx_br_read_ahead(strm, br, n) \ + (lzx_br_read_ahead_0((strm), (br), (n)) || lzx_br_has((br), (n))) + +/* Notify how many bits we consumed. */ +#define lzx_br_consume(br, n) ((br)->cache_avail -= (n)) +#define lzx_br_consume_unalined_bits(br) ((br)->cache_avail &= ~0x0f) + +static const uint32_t cache_masks[] = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, + 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, + 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, + 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, + 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, + 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, + 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, + 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/* + * Shift away used bits in the cache data and fill it up with following bits. + * Call this when cache buffer does not have enough bits you need. + * + * Returns 1 if the cache buffer is full. + * Returns 0 if the cache buffer is not full; input buffer is empty. + */ +static int +lzx_br_fillup(struct lzx_stream *strm, struct lzx_br *br) +{ +/* + * x86 proccessor family can read misaligned data without an access error. + */ + int n = CACHE_BITS - br->cache_avail; + + for (;;) { + switch (n >> 4) { + case 4: + if (strm->avail_in >= 8) { + br->cache_buffer = + ((uint64_t)strm->next_in[1]) << 56 | + ((uint64_t)strm->next_in[0]) << 48 | + ((uint64_t)strm->next_in[3]) << 40 | + ((uint64_t)strm->next_in[2]) << 32 | + ((uint32_t)strm->next_in[5]) << 24 | + ((uint32_t)strm->next_in[4]) << 16 | + ((uint32_t)strm->next_in[7]) << 8 | + (uint32_t)strm->next_in[6]; + strm->next_in += 8; + strm->avail_in -= 8; + br->cache_avail += 8 * 8; + return (1); + } + break; + case 3: + if (strm->avail_in >= 6) { + br->cache_buffer = + (br->cache_buffer << 48) | + ((uint64_t)strm->next_in[1]) << 40 | + ((uint64_t)strm->next_in[0]) << 32 | + ((uint32_t)strm->next_in[3]) << 24 | + ((uint32_t)strm->next_in[2]) << 16 | + ((uint32_t)strm->next_in[5]) << 8 | + (uint32_t)strm->next_in[4]; + strm->next_in += 6; + strm->avail_in -= 6; + br->cache_avail += 6 * 8; + return (1); + } + break; + case 0: + /* We have enough compressed data in + * the cache buffer.*/ + return (1); + default: + break; + } + if (strm->avail_in < 2) { + /* There is not enough compressed data to + * fill up the cache buffer. */ + if (strm->avail_in == 1) { + br->odd = *strm->next_in++; + strm->avail_in--; + br->have_odd = 1; + } + return (0); + } + br->cache_buffer = + (br->cache_buffer << 16) | + archive_le16dec(strm->next_in); + strm->next_in += 2; + strm->avail_in -= 2; + br->cache_avail += 16; + n -= 16; + } +} + +static void +lzx_br_fixup(struct lzx_stream *strm, struct lzx_br *br) +{ + int n = CACHE_BITS - br->cache_avail; + + if (br->have_odd && n >= 16 && strm->avail_in > 0) { + br->cache_buffer = + (br->cache_buffer << 16) | + ((uint16_t)(*strm->next_in)) << 8 | br->odd; + strm->next_in++; + strm->avail_in--; + br->cache_avail += 16; + br->have_odd = 0; + } +} + +static void +lzx_cleanup_bitstream(struct lzx_stream *strm) +{ + strm->ds->br.cache_avail = 0; + strm->ds->br.have_odd = 0; +} + +/* + * Decode LZX. + * + * 1. Returns ARCHIVE_OK if output buffer or input buffer are empty. + * Please set available buffer and call this function again. + * 2. Returns ARCHIVE_EOF if decompression has been completed. + * 3. Returns ARCHIVE_FAILED if an error occurred; compressed data + * is broken or you do not set 'last' flag properly. + */ +#define ST_RD_TRANSLATION 0 +#define ST_RD_TRANSLATION_SIZE 1 +#define ST_RD_BLOCK_TYPE 2 +#define ST_RD_BLOCK_SIZE 3 +#define ST_RD_R0 4 +#define ST_RD_R1 5 +#define ST_RD_R2 6 +#define ST_COPY_UNCOMP1 7 +#define ST_COPY_UNCOMP2 8 +#define ST_RD_ALIGNED_OFFSET 9 +#define ST_RD_VERBATIM 10 +#define ST_RD_PRE_MAIN_TREE_256 11 +#define ST_MAIN_TREE_256 12 +#define ST_RD_PRE_MAIN_TREE_REM 13 +#define ST_MAIN_TREE_REM 14 +#define ST_RD_PRE_LENGTH_TREE 15 +#define ST_LENGTH_TREE 16 +#define ST_MAIN 17 +#define ST_LENGTH 18 +#define ST_OFFSET 19 +#define ST_REAL_POS 20 +#define ST_COPY 21 + +static int +lzx_decode(struct lzx_stream *strm, int last) +{ + struct lzx_dec *ds = strm->ds; + int64_t avail_in; + int r; + + if (ds->error) + return (ds->error); + + avail_in = strm->avail_in; + lzx_br_fixup(strm, &(ds->br)); + do { + if (ds->state < ST_MAIN) + r = lzx_read_blocks(strm, last); + else { + int64_t bytes_written = strm->avail_out; + r = lzx_decode_blocks(strm, last); + bytes_written -= strm->avail_out; + strm->next_out += bytes_written; + strm->total_out += bytes_written; + } + } while (r == 100); + strm->total_in += avail_in - strm->avail_in; + return (r); +} + +static int +lzx_read_blocks(struct lzx_stream *strm, int last) +{ + struct lzx_dec *ds = strm->ds; + struct lzx_br *br = &(ds->br); + int i, r; + + for (;;) { + switch (ds->state) { + case ST_RD_TRANSLATION: + if (!lzx_br_read_ahead(strm, br, 1)) { + ds->state = ST_RD_TRANSLATION; + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->translation = lzx_br_bits(br, 1); + lzx_br_consume(br, 1); + /* FALL THROUGH */ + case ST_RD_TRANSLATION_SIZE: + if (ds->translation) { + if (!lzx_br_read_ahead(strm, br, 32)) { + ds->state = ST_RD_TRANSLATION_SIZE; + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->translation_size = lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + ds->translation_size <<= 16; + ds->translation_size |= lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + } + /* FALL THROUGH */ + case ST_RD_BLOCK_TYPE: + if (!lzx_br_read_ahead(strm, br, 3)) { + ds->state = ST_RD_BLOCK_TYPE; + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->block_type = lzx_br_bits(br, 3); + lzx_br_consume(br, 3); + /* Check a block type. */ + switch (ds->block_type) { + case VERBATIM_BLOCK: + case ALIGNED_OFFSET_BLOCK: + case UNCOMPRESSED_BLOCK: + break; + default: + goto failed;/* Invalid */ + } + /* FALL THROUGH */ + case ST_RD_BLOCK_SIZE: + if (!lzx_br_read_ahead(strm, br, 24)) { + ds->state = ST_RD_BLOCK_SIZE; + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->block_size = lzx_br_bits(br, 8); + lzx_br_consume(br, 8); + ds->block_size <<= 16; + ds->block_size |= lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + if (ds->block_size == 0) + goto failed; + ds->block_bytes_avail = ds->block_size; + if (ds->block_type != UNCOMPRESSED_BLOCK) { + if (ds->block_type == VERBATIM_BLOCK) + ds->state = ST_RD_VERBATIM; + else + ds->state = ST_RD_ALIGNED_OFFSET; + break; + } + /* + * Handle an Uncompressed Block. + */ + /* Skip padding to align following field on + * 16-bit boundary. */ + lzx_br_consume_unalined_bits(br); + /* Preparation to read repeated offsets R0,R1 and R2. */ + ds->rbytes_avail = 0; + ds->state = ST_RD_R0; + /* FALL THROUGH */ + case ST_RD_R0: + case ST_RD_R1: + case ST_RD_R2: + do { + uint16_t u16; + /* Drain bits in the cache buffer of + * bit-stream. */ + if (lzx_br_has(br, 32)) { + u16 = lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + archive_le16enc(ds->rbytes, u16); + u16 = lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + archive_le16enc(ds->rbytes+2, u16); + ds->rbytes_avail = 4; + } else if (lzx_br_has(br, 16)) { + u16 = lzx_br_bits(br, 16); + lzx_br_consume(br, 16); + archive_le16enc(ds->rbytes, u16); + ds->rbytes_avail = 2; + } else + ds->rbytes_avail = 0; + if (ds->rbytes_avail < 4 && ds->br.have_odd) { + ds->rbytes[ds->rbytes_avail++] = + ds->br.odd; + ds->br.have_odd = 0; + } + while (ds->rbytes_avail < 4) { + if (strm->avail_in <= 0) { + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->rbytes[ds->rbytes_avail++] = + *strm->next_in++; + strm->avail_in--; + } + if (ds->state == ST_RD_R0) { + ds->r0 = archive_le32dec(ds->rbytes); + if (ds->r0 < 0) + goto failed; + ds->state = ST_RD_R1; + } else if (ds->state == ST_RD_R1) { + ds->r1 = archive_le32dec(ds->rbytes); + if (ds->r1 < 0) + goto failed; + ds->state = ST_RD_R2; + } else if (ds->state == ST_RD_R2) { + ds->r2 = archive_le32dec(ds->rbytes); + if (ds->r2 < 0) + goto failed; + /* We've gotten all repeated offsets. */ + ds->state = ST_COPY_UNCOMP1; + } + } while (ds->state != ST_COPY_UNCOMP1); + /* FALL THROUGH */ + case ST_COPY_UNCOMP1: + /* + * Copy bytes form next_in to next_out directly. + */ + while (ds->block_bytes_avail) { + unsigned char *d; + int l,ll; + + if (strm->avail_out <= 0) + /* Output buffer is empty. */ + return (ARCHIVE_OK); + if (strm->avail_in <= 0) { + /* Input buffer is empty. */ + if (last) + goto failed; + return (ARCHIVE_OK); + } + l = ds->block_bytes_avail; + if (l > ds->w_size - ds->w_pos) + l = ds->w_size - ds->w_pos; + if (l > strm->avail_out) + l = (int)strm->avail_out; + if (l > strm->avail_in) + l = (int)strm->avail_in; + ll = l; + d = &(ds->w_buff[ds->w_pos]); + while (--l >= 0) { + *strm->next_out++ = *strm->next_in; + *d++ = *strm->next_in++; + } + strm->avail_out -= ll; + strm->total_out += ll; + strm->avail_in -= ll; + ds->w_pos = (ds->w_pos + ll) & ds->w_mask; + ds->block_bytes_avail -= ll; + } + /* FALL THROUGH */ + case ST_COPY_UNCOMP2: + /* Re-align; skip padding byte. */ + if (ds->block_size & 1) { + if (strm->avail_in <= 0) { + /* Input buffer is empty. */ + ds->state = ST_COPY_UNCOMP2; + if (last) + goto failed; + return (ARCHIVE_OK); + } + strm->next_in++; + strm->avail_in --; + } + /* This block ended. */ + ds->state = ST_RD_BLOCK_TYPE; + return (ARCHIVE_EOF); + /********************/ + case ST_RD_ALIGNED_OFFSET: + /* + * Read Aligned offset tree. + */ + if (!lzx_br_read_ahead(strm, br, 3 * ds->at.len_size)) { + ds->state = ST_RD_ALIGNED_OFFSET; + if (last) + goto failed; + return (ARCHIVE_OK); + } + memset(ds->at.freq, 0, sizeof(ds->at.freq)); + for (i = 0; i < ds->at.len_size; i++) { + ds->at.bitlen[i] = lzx_br_bits(br, 3); + ds->at.freq[ds->at.bitlen[i]]++; + lzx_br_consume(br, 3); + } + if (!lzx_make_huffman_table(&ds->at)) + goto failed; + /* FALL THROUGH */ + case ST_RD_VERBATIM: + ds->loop = 0; + /* FALL THROUGH */ + case ST_RD_PRE_MAIN_TREE_256: + /* + * Read Pre-tree for first 256 elements of main tree. + */ + if (!lzx_read_pre_tree(strm)) { + ds->state = ST_RD_PRE_MAIN_TREE_256; + if (last) + goto failed; + return (ARCHIVE_OK); + } + if (!lzx_make_huffman_table(&ds->pt)) + goto failed; + ds->loop = 0; + /* FALL THROUGH */ + case ST_MAIN_TREE_256: + /* + * Get path lengths of first 256 elements of main tree. + */ + r = lzx_read_bitlen(strm, &ds->mt, 256); + if (r < 0) + goto failed; + else if (!r) { + ds->state = ST_MAIN_TREE_256; + if (last) + goto failed; + return (ARCHIVE_OK); + } + ds->loop = 0; + /* FALL THROUGH */ + case ST_RD_PRE_MAIN_TREE_REM: + /* + * Read Pre-tree for remaining elements of main tree. + */ + if (!lzx_read_pre_tree(strm)) { + ds->state = ST_RD_PRE_MAIN_TREE_REM; + if (last) + goto failed; + return (ARCHIVE_OK); + } + if (!lzx_make_huffman_table(&ds->pt)) + goto failed; + ds->loop = 256; + /* FALL THROUGH */ + case ST_MAIN_TREE_REM: + /* + * Get path lengths of remaining elements of main tree. + */ + r = lzx_read_bitlen(strm, &ds->mt, -1); + if (r < 0) + goto failed; + else if (!r) { + ds->state = ST_MAIN_TREE_REM; + if (last) + goto failed; + return (ARCHIVE_OK); + } + if (!lzx_make_huffman_table(&ds->mt)) + goto failed; + ds->loop = 0; + /* FALL THROUGH */ + case ST_RD_PRE_LENGTH_TREE: + /* + * Read Pre-tree for remaining elements of main tree. + */ + if (!lzx_read_pre_tree(strm)) { + ds->state = ST_RD_PRE_LENGTH_TREE; + if (last) + goto failed; + return (ARCHIVE_OK); + } + if (!lzx_make_huffman_table(&ds->pt)) + goto failed; + ds->loop = 0; + /* FALL THROUGH */ + case ST_LENGTH_TREE: + /* + * Get path lengths of remaining elements of main tree. + */ + r = lzx_read_bitlen(strm, &ds->lt, -1); + if (r < 0) + goto failed; + else if (!r) { + ds->state = ST_LENGTH_TREE; + if (last) + goto failed; + return (ARCHIVE_OK); + } + if (!lzx_make_huffman_table(&ds->lt)) + goto failed; + ds->state = ST_MAIN; + return (100); + } + } +failed: + return (ds->error = ARCHIVE_FAILED); +} + +static int +lzx_decode_blocks(struct lzx_stream *strm, int last) +{ + struct lzx_dec *ds = strm->ds; + struct lzx_br bre = ds->br; + struct huffman *at = &(ds->at), *lt = &(ds->lt), *mt = &(ds->mt); + const struct lzx_pos_tbl *pos_tbl = ds->pos_tbl; + unsigned char *outp = strm->next_out; + unsigned char *endp = outp + strm->avail_out; + unsigned char *w_buff = ds->w_buff; + unsigned char *at_bitlen = at->bitlen; + unsigned char *lt_bitlen = lt->bitlen; + unsigned char *mt_bitlen = mt->bitlen; + size_t block_bytes_avail = ds->block_bytes_avail; + int at_max_bits = at->max_bits; + int lt_max_bits = lt->max_bits; + int mt_max_bits = mt->max_bits; + int c, copy_len = ds->copy_len, copy_pos = ds->copy_pos; + int w_pos = ds->w_pos, w_mask = ds->w_mask, w_size = ds->w_size; + int length_header = ds->length_header; + int offset_bits = ds->offset_bits; + int position_slot = ds->position_slot; + int r0 = ds->r0, r1 = ds->r1, r2 = ds->r2; + int state = ds->state; + char block_type = ds->block_type; + + for (;;) { + switch (state) { + case ST_MAIN: + for (;;) { + if (block_bytes_avail == 0) { + /* This block ended. */ + ds->state = ST_RD_BLOCK_TYPE; + ds->br = bre; + ds->block_bytes_avail = + block_bytes_avail; + ds->copy_len = copy_len; + ds->copy_pos = copy_pos; + ds->length_header = length_header; + ds->position_slot = position_slot; + ds->r0 = r0; ds->r1 = r1; ds->r2 = r2; + ds->w_pos = w_pos; + strm->avail_out = endp - outp; + return (ARCHIVE_EOF); + } + if (outp >= endp) + /* Output buffer is empty. */ + goto next_data; + + if (!lzx_br_read_ahead(strm, &bre, + mt_max_bits)) { + if (!last) + goto next_data; + /* Remaining bits are less than + * maximum bits(mt.max_bits) but maybe + * it still remains as much as we need, + * so we should try to use it with + * dummy bits. */ + c = lzx_decode_huffman(mt, + lzx_br_bits_forced( + &bre, mt_max_bits)); + lzx_br_consume(&bre, mt_bitlen[c]); + if (!lzx_br_has(&bre, 0)) + goto failed;/* Over read. */ + } else { + c = lzx_decode_huffman(mt, + lzx_br_bits(&bre, mt_max_bits)); + lzx_br_consume(&bre, mt_bitlen[c]); + } + if (c > UCHAR_MAX) + break; + /* + * 'c' is exactly literal code. + */ + /* Save a decoded code to reference it + * afterward. */ + w_buff[w_pos] = c; + w_pos = (w_pos + 1) & w_mask; + /* Store the decoded code to output buffer. */ + *outp++ = c; + block_bytes_avail--; + } + /* + * Get a match code, its length and offset. + */ + c -= UCHAR_MAX + 1; + length_header = c & 7; + position_slot = c >> 3; + /* FALL THROUGH */ + case ST_LENGTH: + /* + * Get a length. + */ + if (length_header == 7) { + if (!lzx_br_read_ahead(strm, &bre, + lt_max_bits)) { + if (!last) { + state = ST_LENGTH; + goto next_data; + } + c = lzx_decode_huffman(lt, + lzx_br_bits_forced( + &bre, lt_max_bits)); + lzx_br_consume(&bre, lt_bitlen[c]); + if (!lzx_br_has(&bre, 0)) + goto failed;/* Over read. */ + } else { + c = lzx_decode_huffman(lt, + lzx_br_bits(&bre, lt_max_bits)); + lzx_br_consume(&bre, lt_bitlen[c]); + } + copy_len = c + 7 + 2; + } else + copy_len = length_header + 2; + if ((size_t)copy_len > block_bytes_avail) + goto failed; + /* + * Get an offset. + */ + switch (position_slot) { + case 0: /* Use repeated offset 0. */ + copy_pos = r0; + state = ST_REAL_POS; + continue; + case 1: /* Use repeated offset 1. */ + copy_pos = r1; + /* Swap repeated offset. */ + r1 = r0; + r0 = copy_pos; + state = ST_REAL_POS; + continue; + case 2: /* Use repeated offset 2. */ + copy_pos = r2; + /* Swap repeated offset. */ + r2 = r0; + r0 = copy_pos; + state = ST_REAL_POS; + continue; + default: + offset_bits = + pos_tbl[position_slot].footer_bits; + break; + } + /* FALL THROUGH */ + case ST_OFFSET: + /* + * Get the offset, which is a distance from + * current window position. + */ + if (block_type == ALIGNED_OFFSET_BLOCK && + offset_bits >= 3) { + int offbits = offset_bits - 3; + + if (!lzx_br_read_ahead(strm, &bre, offbits)) { + state = ST_OFFSET; + if (last) + goto failed; + goto next_data; + } + copy_pos = lzx_br_bits(&bre, offbits) << 3; + + /* Get an aligned number. */ + if (!lzx_br_read_ahead(strm, &bre, + offbits + at_max_bits)) { + if (!last) { + state = ST_OFFSET; + goto next_data; + } + lzx_br_consume(&bre, offbits); + c = lzx_decode_huffman(at, + lzx_br_bits_forced(&bre, + at_max_bits)); + lzx_br_consume(&bre, at_bitlen[c]); + if (!lzx_br_has(&bre, 0)) + goto failed;/* Over read. */ + } else { + lzx_br_consume(&bre, offbits); + c = lzx_decode_huffman(at, + lzx_br_bits(&bre, at_max_bits)); + lzx_br_consume(&bre, at_bitlen[c]); + } + /* Add an aligned number. */ + copy_pos += c; + } else { + if (!lzx_br_read_ahead(strm, &bre, + offset_bits)) { + state = ST_OFFSET; + if (last) + goto failed; + goto next_data; + } + copy_pos = lzx_br_bits(&bre, offset_bits); + lzx_br_consume(&bre, offset_bits); + } + copy_pos += pos_tbl[position_slot].base -2; + + /* Update repeated offset LRU queue. */ + r2 = r1; + r1 = r0; + r0 = copy_pos; + /* FALL THROUGH */ + case ST_REAL_POS: + /* + * Compute a real position in window. + */ + copy_pos = (w_pos - copy_pos) & w_mask; + /* FALL THROUGH */ + case ST_COPY: + /* + * Copy several bytes as extracted data from the window + * into the output buffer. + */ + for (;;) { + const unsigned char *s; + int l; + + l = copy_len; + if (copy_pos > w_pos) { + if (l > w_size - copy_pos) + l = w_size - copy_pos; + } else { + if (l > w_size - w_pos) + l = w_size - w_pos; + } + if (outp + l >= endp) + l = endp - outp; + s = w_buff + copy_pos; + if (l >= 8 && ((copy_pos + l < w_pos) + || (w_pos + l < copy_pos))) { + memcpy(w_buff + w_pos, s, l); + memcpy(outp, s, l); + } else { + unsigned char *d; + int li; + + d = w_buff + w_pos; + for (li = 0; li < l; li++) + outp[li] = d[li] = s[li]; + } + outp += l; + copy_pos = (copy_pos + l) & w_mask; + w_pos = (w_pos + l) & w_mask; + block_bytes_avail -= l; + if (copy_len <= l) + /* A copy of current pattern ended. */ + break; + copy_len -= l; + if (outp >= endp) { + /* Output buffer is empty. */ + state = ST_COPY; + goto next_data; + } + } + state = ST_MAIN; + break; + } + } +failed: + return (ds->error = ARCHIVE_FAILED); +next_data: + ds->br = bre; + ds->block_bytes_avail = block_bytes_avail; + ds->copy_len = copy_len; + ds->copy_pos = copy_pos; + ds->length_header = length_header; + ds->offset_bits = offset_bits; + ds->position_slot = position_slot; + ds->r0 = r0; ds->r1 = r1; ds->r2 = r2; + ds->state = state; + ds->w_pos = w_pos; + strm->avail_out = endp - outp; + return (ARCHIVE_OK); +} + +static int +lzx_read_pre_tree(struct lzx_stream *strm) +{ + struct lzx_dec *ds = strm->ds; + struct lzx_br *br = &(ds->br); + int i; + + if (ds->loop == 0) + memset(ds->pt.freq, 0, sizeof(ds->pt.freq)); + for (i = ds->loop; i < ds->pt.len_size; i++) { + if (!lzx_br_read_ahead(strm, br, 4)) { + ds->loop = i; + return (0); + } + ds->pt.bitlen[i] = lzx_br_bits(br, 4); + ds->pt.freq[ds->pt.bitlen[i]]++; + lzx_br_consume(br, 4); + } + ds->loop = i; + return (1); +} + +/* + * Read a bunch of bit-lengths from pre-tree. + */ +static int +lzx_read_bitlen(struct lzx_stream *strm, struct huffman *d, int end) +{ + struct lzx_dec *ds = strm->ds; + struct lzx_br *br = &(ds->br); + int c, i, j, ret, same; + unsigned rbits; + + i = ds->loop; + if (i == 0) + memset(d->freq, 0, sizeof(d->freq)); + ret = 0; + if (end < 0) + end = d->len_size; + while (i < end) { + ds->loop = i; + if (!lzx_br_read_ahead(strm, br, ds->pt.max_bits)) + goto getdata; + rbits = lzx_br_bits(br, ds->pt.max_bits); + c = lzx_decode_huffman(&(ds->pt), rbits); + switch (c) { + case 17:/* several zero lengths, from 4 to 19. */ + if (!lzx_br_read_ahead(strm, br, ds->pt.bitlen[c]+4)) + goto getdata; + lzx_br_consume(br, ds->pt.bitlen[c]); + same = lzx_br_bits(br, 4) + 4; + if (i + same > end) + return (-1);/* Invalid */ + lzx_br_consume(br, 4); + for (j = 0; j < same; j++) + d->bitlen[i++] = 0; + break; + case 18:/* many zero lengths, from 20 to 51. */ + if (!lzx_br_read_ahead(strm, br, ds->pt.bitlen[c]+5)) + goto getdata; + lzx_br_consume(br, ds->pt.bitlen[c]); + same = lzx_br_bits(br, 5) + 20; + if (i + same > end) + return (-1);/* Invalid */ + lzx_br_consume(br, 5); + memset(d->bitlen + i, 0, same); + i += same; + break; + case 19:/* a few same lengths. */ + if (!lzx_br_read_ahead(strm, br, + ds->pt.bitlen[c]+1+ds->pt.max_bits)) + goto getdata; + lzx_br_consume(br, ds->pt.bitlen[c]); + same = lzx_br_bits(br, 1) + 4; + if (i + same > end) + return (-1); + lzx_br_consume(br, 1); + rbits = lzx_br_bits(br, ds->pt.max_bits); + c = lzx_decode_huffman(&(ds->pt), rbits); + lzx_br_consume(br, ds->pt.bitlen[c]); + c = (d->bitlen[i] - c + 17) % 17; + if (c < 0) + return (-1);/* Invalid */ + for (j = 0; j < same; j++) + d->bitlen[i++] = c; + d->freq[c] += same; + break; + default: + lzx_br_consume(br, ds->pt.bitlen[c]); + c = (d->bitlen[i] - c + 17) % 17; + if (c < 0) + return (-1);/* Invalid */ + d->freq[c]++; + d->bitlen[i++] = c; + break; + } + } + ret = 1; +getdata: + ds->loop = i; + return (ret); +} + +static int +lzx_huffman_init(struct huffman *hf, size_t len_size, int tbl_bits) +{ + int bits; + + if (hf->bitlen == NULL || hf->len_size != (int)len_size) { + free(hf->bitlen); + hf->bitlen = calloc(len_size, sizeof(hf->bitlen[0])); + if (hf->bitlen == NULL) + return (ARCHIVE_FATAL); + hf->len_size = len_size; + } else + memset(hf->bitlen, 0, len_size * sizeof(hf->bitlen[0])); + if (hf->tbl == NULL) { + if (tbl_bits < HTBL_BITS) + bits = tbl_bits; + else + bits = HTBL_BITS; + hf->tbl = malloc((1 << bits) * sizeof(hf->tbl[0])); + if (hf->tbl == NULL) + return (ARCHIVE_FATAL); + hf->tbl_bits = tbl_bits; + } + if (hf->tree == NULL && tbl_bits > HTBL_BITS) { + hf->tree_avail = 1 << (tbl_bits - HTBL_BITS + 4); + hf->tree = malloc(hf->tree_avail * sizeof(hf->tree[0])); + if (hf->tree == NULL) + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static void +lzx_huffman_free(struct huffman *hf) +{ + free(hf->bitlen); + free(hf->tbl); + free(hf->tree); +} + +/* + * Make a huffman coding table. + */ +static int +lzx_make_huffman_table(struct huffman *hf) +{ + uint16_t *tbl; + const unsigned char *bitlen; + int bitptn[17], weight[17]; + int i, maxbits = 0, ptn, tbl_size, w; + int diffbits, len_avail; + + /* + * Initialize bit patterns. + */ + ptn = 0; + for (i = 1, w = 1 << 15; i <= 16; i++, w >>= 1) { + bitptn[i] = ptn; + weight[i] = w; + if (hf->freq[i]) { + ptn += hf->freq[i] * w; + maxbits = i; + } + } + if ((ptn & 0xffff) != 0 || maxbits > hf->tbl_bits) + return (0);/* Invalid */ + + hf->max_bits = maxbits; + + /* + * Cut out extra bits which we won't house in the table. + * This preparation reduces the same calculation in the for-loop + * making the table. + */ + if (maxbits < 16) { + int ebits = 16 - maxbits; + for (i = 1; i <= maxbits; i++) { + bitptn[i] >>= ebits; + weight[i] >>= ebits; + } + } + if (maxbits > HTBL_BITS) { + int htbl_max; + uint16_t *p; + + diffbits = maxbits - HTBL_BITS; + for (i = 1; i <= HTBL_BITS; i++) { + bitptn[i] >>= diffbits; + weight[i] >>= diffbits; + } + htbl_max = bitptn[HTBL_BITS] + + weight[HTBL_BITS] * hf->freq[HTBL_BITS]; + p = &(hf->tbl[htbl_max]); + while (p < &hf->tbl[1U<shift_bits = diffbits; + + /* + * Make the table. + */ + tbl_size = 1 << HTBL_BITS; + tbl = hf->tbl; + bitlen = hf->bitlen; + len_avail = hf->len_size; + hf->tree_used = 0; + for (i = 0; i < len_avail; i++) { + uint16_t *p; + int len, cnt; + uint16_t bit; + int extlen; + struct htree_t *ht; + + if (bitlen[i] == 0) + continue; + /* Get a bit pattern */ + len = bitlen[i]; + ptn = bitptn[len]; + cnt = weight[len]; + if (len <= HTBL_BITS) { + /* Calculate next bit pattern */ + if ((bitptn[len] = ptn + cnt) > tbl_size) + return (0);/* Invalid */ + /* Update the table */ + p = &(tbl[ptn]); + while (--cnt >= 0) + p[cnt] = (uint16_t)i; + continue; + } + + /* + * A bit length is too big to be housed to a direct table, + * so we use a tree model for its extra bits. + */ + bitptn[len] = ptn + cnt; + bit = 1U << (diffbits -1); + extlen = len - HTBL_BITS; + + p = &(tbl[ptn >> diffbits]); + if (*p == 0) { + *p = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + if (*p < len_avail || + *p >= (len_avail + hf->tree_used)) + return (0);/* Invalid */ + ht = &(hf->tree[*p - len_avail]); + } + while (--extlen > 0) { + if (ptn & bit) { + if (ht->left < len_avail) { + ht->left = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + ht = &(hf->tree[ht->left - len_avail]); + } + } else { + if (ht->right < len_avail) { + ht->right = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + ht = &(hf->tree[ht->right - len_avail]); + } + } + bit >>= 1; + } + if (ptn & bit) { + if (ht->left != 0) + return (0);/* Invalid */ + ht->left = (uint16_t)i; + } else { + if (ht->right != 0) + return (0);/* Invalid */ + ht->right = (uint16_t)i; + } + } + return (1); +} + +static int +lzx_decode_huffman_tree(struct huffman *hf, unsigned rbits, int c) +{ + struct htree_t *ht; + int extlen; + + ht = hf->tree; + extlen = hf->shift_bits; + while (c >= hf->len_size) { + c -= hf->len_size; + if (extlen-- <= 0 || c >= hf->tree_used) + return (0); + if (rbits & (1U << extlen)) + c = ht[c].left; + else + c = ht[c].right; + } + return (c); +} + +static inline int +lzx_decode_huffman(struct huffman *hf, unsigned rbits) +{ + int c; + /* + * At first search an index table for a bit pattern. + * If it fails, search a huffman tree for. + */ + c = hf->tbl[rbits >> hf->shift_bits]; + if (c < hf->len_size) + return (c); + /* This bit pattern needs to be found out at a huffman tree. */ + return (lzx_decode_huffman_tree(hf, rbits, c)); +} + diff --git a/libarchive/archive_read_support_format_cpio.c b/libarchive/archive_read_support_format_cpio.c new file mode 100644 index 0000000..5ae73d7 --- /dev/null +++ b/libarchive/archive_read_support_format_cpio.c @@ -0,0 +1,1048 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_cpio.c 201163 2009-12-29 05:50:34Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +/* #include */ /* See archive_platform.h */ +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#define bin_magic_offset 0 +#define bin_magic_size 2 +#define bin_dev_offset 2 +#define bin_dev_size 2 +#define bin_ino_offset 4 +#define bin_ino_size 2 +#define bin_mode_offset 6 +#define bin_mode_size 2 +#define bin_uid_offset 8 +#define bin_uid_size 2 +#define bin_gid_offset 10 +#define bin_gid_size 2 +#define bin_nlink_offset 12 +#define bin_nlink_size 2 +#define bin_rdev_offset 14 +#define bin_rdev_size 2 +#define bin_mtime_offset 16 +#define bin_mtime_size 4 +#define bin_namesize_offset 20 +#define bin_namesize_size 2 +#define bin_filesize_offset 22 +#define bin_filesize_size 4 +#define bin_header_size 26 + +#define odc_magic_offset 0 +#define odc_magic_size 6 +#define odc_dev_offset 6 +#define odc_dev_size 6 +#define odc_ino_offset 12 +#define odc_ino_size 6 +#define odc_mode_offset 18 +#define odc_mode_size 6 +#define odc_uid_offset 24 +#define odc_uid_size 6 +#define odc_gid_offset 30 +#define odc_gid_size 6 +#define odc_nlink_offset 36 +#define odc_nlink_size 6 +#define odc_rdev_offset 42 +#define odc_rdev_size 6 +#define odc_mtime_offset 48 +#define odc_mtime_size 11 +#define odc_namesize_offset 59 +#define odc_namesize_size 6 +#define odc_filesize_offset 65 +#define odc_filesize_size 11 +#define odc_header_size 76 + +#define newc_magic_offset 0 +#define newc_magic_size 6 +#define newc_ino_offset 6 +#define newc_ino_size 8 +#define newc_mode_offset 14 +#define newc_mode_size 8 +#define newc_uid_offset 22 +#define newc_uid_size 8 +#define newc_gid_offset 30 +#define newc_gid_size 8 +#define newc_nlink_offset 38 +#define newc_nlink_size 8 +#define newc_mtime_offset 46 +#define newc_mtime_size 8 +#define newc_filesize_offset 54 +#define newc_filesize_size 8 +#define newc_devmajor_offset 62 +#define newc_devmajor_size 8 +#define newc_devminor_offset 70 +#define newc_devminor_size 8 +#define newc_rdevmajor_offset 78 +#define newc_rdevmajor_size 8 +#define newc_rdevminor_offset 86 +#define newc_rdevminor_size 8 +#define newc_namesize_offset 94 +#define newc_namesize_size 8 +#define newc_checksum_offset 102 +#define newc_checksum_size 8 +#define newc_header_size 110 + +/* + * An afio large ASCII header, which they named itself. + * afio utility uses this header, if a file size is larger than 2G bytes + * or inode/uid/gid is bigger than 65535(0xFFFF) or mtime is bigger than + * 0x7fffffff, which we cannot record to odc header because of its limit. + * If not, uses odc header. + */ +#define afiol_magic_offset 0 +#define afiol_magic_size 6 +#define afiol_dev_offset 6 +#define afiol_dev_size 8 /* hex */ +#define afiol_ino_offset 14 +#define afiol_ino_size 16 /* hex */ +#define afiol_ino_m_offset 30 /* 'm' */ +#define afiol_mode_offset 31 +#define afiol_mode_size 6 /* oct */ +#define afiol_uid_offset 37 +#define afiol_uid_size 8 /* hex */ +#define afiol_gid_offset 45 +#define afiol_gid_size 8 /* hex */ +#define afiol_nlink_offset 53 +#define afiol_nlink_size 8 /* hex */ +#define afiol_rdev_offset 61 +#define afiol_rdev_size 8 /* hex */ +#define afiol_mtime_offset 69 +#define afiol_mtime_size 16 /* hex */ +#define afiol_mtime_n_offset 85 /* 'n' */ +#define afiol_namesize_offset 86 +#define afiol_namesize_size 4 /* hex */ +#define afiol_flag_offset 90 +#define afiol_flag_size 4 /* hex */ +#define afiol_xsize_offset 94 +#define afiol_xsize_size 4 /* hex */ +#define afiol_xsize_s_offset 98 /* 's' */ +#define afiol_filesize_offset 99 +#define afiol_filesize_size 16 /* hex */ +#define afiol_filesize_c_offset 115 /* ':' */ +#define afiol_header_size 116 + + +struct links_entry { + struct links_entry *next; + struct links_entry *previous; + int links; + dev_t dev; + int64_t ino; + char *name; +}; + +#define CPIO_MAGIC 0x13141516 +struct cpio { + int magic; + int (*read_header)(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); + struct links_entry *links_head; + int64_t entry_bytes_remaining; + int64_t entry_bytes_unconsumed; + int64_t entry_offset; + int64_t entry_padding; + + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; +}; + +static int64_t atol16(const char *, unsigned); +static int64_t atol8(const char *, unsigned); +static int archive_read_format_cpio_bid(struct archive_read *, int); +static int archive_read_format_cpio_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_cpio_cleanup(struct archive_read *); +static int archive_read_format_cpio_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_cpio_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_cpio_skip(struct archive_read *); +static int be4(const unsigned char *); +static int find_odc_header(struct archive_read *); +static int find_newc_header(struct archive_read *); +static int header_bin_be(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_bin_le(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_newc(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_odc(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int header_afiol(struct archive_read *, struct cpio *, + struct archive_entry *, size_t *, size_t *); +static int is_octal(const char *, size_t); +static int is_hex(const char *, size_t); +static int le4(const unsigned char *); +static int record_hardlink(struct archive_read *a, + struct cpio *cpio, struct archive_entry *entry); + +int +archive_read_support_format_cpio(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct cpio *cpio; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_cpio"); + + cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + cpio->magic = CPIO_MAGIC; + + r = __archive_read_register_format(a, + cpio, + "cpio", + archive_read_format_cpio_bid, + archive_read_format_cpio_options, + archive_read_format_cpio_read_header, + archive_read_format_cpio_read_data, + archive_read_format_cpio_skip, + archive_read_format_cpio_cleanup); + + if (r != ARCHIVE_OK) + free(cpio); + return (ARCHIVE_OK); +} + + +static int +archive_read_format_cpio_bid(struct archive_read *a, int best_bid) +{ + const unsigned char *p; + struct cpio *cpio; + int bid; + + (void)best_bid; /* UNUSED */ + + cpio = (struct cpio *)(a->format->data); + + if ((p = __archive_read_ahead(a, 6, NULL)) == NULL) + return (-1); + + bid = 0; + if (memcmp(p, "070707", 6) == 0) { + /* ASCII cpio archive (odc, POSIX.1) */ + cpio->read_header = header_odc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only octal + * digits appear in appropriate header locations. XXX + */ + } else if (memcmp(p, "070727", 6) == 0) { + /* afio large ASCII cpio archive */ + cpio->read_header = header_odc; + bid += 48; + /* + * XXX TODO: More verification; Could check that almost hex + * digits appear in appropriate header locations. XXX + */ + } else if (memcmp(p, "070701", 6) == 0) { + /* ASCII cpio archive (SVR4 without CRC) */ + cpio->read_header = header_newc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only hex + * digits appear in appropriate header locations. XXX + */ + } else if (memcmp(p, "070702", 6) == 0) { + /* ASCII cpio archive (SVR4 with CRC) */ + /* XXX TODO: Flag that we should check the CRC. XXX */ + cpio->read_header = header_newc; + bid += 48; + /* + * XXX TODO: More verification; Could check that only hex + * digits appear in appropriate header locations. XXX + */ + } else if (p[0] * 256 + p[1] == 070707) { + /* big-endian binary cpio archives */ + cpio->read_header = header_bin_be; + bid += 16; + /* Is more verification possible here? */ + } else if (p[0] + p[1] * 256 == 070707) { + /* little-endian binary cpio archives */ + cpio->read_header = header_bin_le; + bid += 16; + /* Is more verification possible here? */ + } else + return (ARCHIVE_WARN); + + return (bid); +} + +static int +archive_read_format_cpio_options(struct archive_read *a, + const char *key, const char *val) +{ + struct cpio *cpio; + int ret = ARCHIVE_FAILED; + + cpio = (struct cpio *)(a->format->data); + if (strcmp(key, "compat-2x") == 0) { + /* Handle filnames as libarchive 2.x */ + cpio->init_default_conversion = (val != NULL)?1:0; + ret = ARCHIVE_OK; + } else if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "cpio: hdrcharset option needs a character-set name"); + else { + cpio->opt_sconv = + archive_string_conversion_from_charset( + &a->archive, val, 0); + if (cpio->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "cpio: unknown keyword ``%s''", key); + + return (ret); +} + +static int +archive_read_format_cpio_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct cpio *cpio; + const void *h; + struct archive_string_conv *sconv; + size_t namelength; + size_t name_pad; + int r; + + cpio = (struct cpio *)(a->format->data); + sconv = cpio->opt_sconv; + if (sconv == NULL) { + if (!cpio->init_default_conversion) { + cpio->sconv_default = + archive_string_default_conversion_for_read( + &(a->archive)); + cpio->init_default_conversion = 1; + } + sconv = cpio->sconv_default; + } + + r = (cpio->read_header(a, cpio, entry, &namelength, &name_pad)); + + if (r < ARCHIVE_WARN) + return (r); + + /* Read name from buffer. */ + h = __archive_read_ahead(a, namelength + name_pad, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + if (archive_entry_copy_pathname_l(entry, + (const char *)h, namelength, sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname can't be converted from %s to current locale.", + archive_string_conversion_charset_name(sconv)); + r = ARCHIVE_WARN; + } + cpio->entry_offset = 0; + + __archive_read_consume(a, namelength + name_pad); + + /* If this is a symlink, read the link contents. */ + if (archive_entry_filetype(entry) == AE_IFLNK) { + h = __archive_read_ahead(a, cpio->entry_bytes_remaining, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + if (archive_entry_copy_symlink_l(entry, (const char *)h, + cpio->entry_bytes_remaining, sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Linkname can't be converted from %s to " + "current locale.", + archive_string_conversion_charset_name(sconv)); + r = ARCHIVE_WARN; + } + __archive_read_consume(a, cpio->entry_bytes_remaining); + cpio->entry_bytes_remaining = 0; + } + + /* XXX TODO: If the full mode is 0160200, then this is a Solaris + * ACL description for the following entry. Read this body + * and parse it as a Solaris-style ACL, then read the next + * header. XXX */ + + /* Compare name to "TRAILER!!!" to test for end-of-archive. */ + if (namelength == 11 && strcmp((const char *)h, "TRAILER!!!") == 0) { + /* TODO: Store file location of start of block. */ + archive_clear_error(&a->archive); + return (ARCHIVE_EOF); + } + + /* Detect and record hardlinks to previously-extracted entries. */ + if (record_hardlink(a, cpio, entry) != ARCHIVE_OK) { + return (ARCHIVE_FATAL); + } + + return (r); +} + +static int +archive_read_format_cpio_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + ssize_t bytes_read; + struct cpio *cpio; + + cpio = (struct cpio *)(a->format->data); + + if (cpio->entry_bytes_unconsumed) { + __archive_read_consume(a, cpio->entry_bytes_unconsumed); + cpio->entry_bytes_unconsumed = 0; + } + + if (cpio->entry_bytes_remaining > 0) { + *buff = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + if (bytes_read > cpio->entry_bytes_remaining) + bytes_read = cpio->entry_bytes_remaining; + *size = bytes_read; + cpio->entry_bytes_unconsumed = bytes_read; + *offset = cpio->entry_offset; + cpio->entry_offset += bytes_read; + cpio->entry_bytes_remaining -= bytes_read; + return (ARCHIVE_OK); + } else { + if (cpio->entry_padding != + __archive_read_consume(a, cpio->entry_padding)) { + return (ARCHIVE_FATAL); + } + cpio->entry_padding = 0; + *buff = NULL; + *size = 0; + *offset = cpio->entry_offset; + return (ARCHIVE_EOF); + } +} + +static int +archive_read_format_cpio_skip(struct archive_read *a) +{ + struct cpio *cpio = (struct cpio *)(a->format->data); + int64_t to_skip = cpio->entry_bytes_remaining + cpio->entry_padding + + cpio->entry_bytes_unconsumed; + + if (to_skip != __archive_read_consume(a, to_skip)) { + return (ARCHIVE_FATAL); + } + cpio->entry_bytes_remaining = 0; + cpio->entry_padding = 0; + cpio->entry_bytes_unconsumed = 0; + return (ARCHIVE_OK); +} + +/* + * Skip forward to the next cpio newc header by searching for the + * 07070[12] string. This should be generalized and merged with + * find_odc_header below. + */ +static int +is_hex(const char *p, size_t len) +{ + while (len-- > 0) { + if ((*p >= '0' && *p <= '9') + || (*p >= 'a' && *p <= 'f') + || (*p >= 'A' && *p <= 'F')) + ++p; + else + return (0); + } + return (1); +} + +static int +find_newc_header(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip, skipped = 0; + ssize_t bytes; + + for (;;) { + h = __archive_read_ahead(a, newc_header_size, &bytes); + if (h == NULL) + return (ARCHIVE_FATAL); + p = h; + q = p + bytes; + + /* Try the typical case first, then go into the slow search.*/ + if (memcmp("07070", p, 5) == 0 + && (p[5] == '1' || p[5] == '2') + && is_hex(p, newc_header_size)) + return (ARCHIVE_OK); + + /* + * Scan ahead until we find something that looks + * like a newc header. + */ + while (p + newc_header_size <= q) { + switch (p[5]) { + case '1': + case '2': + if (memcmp("07070", p, 5) == 0 + && is_hex(p, newc_header_size)) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + skipped += skip; + if (skipped > 0) { + archive_set_error(&a->archive, + 0, + "Skipped %d bytes before " + "finding valid header", + (int)skipped); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } + p += 2; + break; + case '0': + p++; + break; + default: + p += 6; + break; + } + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + skipped += skip; + } +} + +static int +header_newc(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const char *header; + int r; + + r = find_newc_header(a); + if (r < ARCHIVE_WARN) + return (r); + + /* Read fixed-size portion of header. */ + h = __archive_read_ahead(a, newc_header_size, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Parse out hex fields. */ + header = (const char *)h; + + if (memcmp(header + newc_magic_offset, "070701", 6) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; + a->archive.archive_format_name = "ASCII cpio (SVR4 with no CRC)"; + } else if (memcmp(header + newc_magic_offset, "070702", 6) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_CRC; + a->archive.archive_format_name = "ASCII cpio (SVR4 with CRC)"; + } else { + /* TODO: Abort here? */ + } + + archive_entry_set_devmajor(entry, atol16(header + newc_devmajor_offset, newc_devmajor_size)); + archive_entry_set_devminor(entry, atol16(header + newc_devminor_offset, newc_devminor_size)); + archive_entry_set_ino(entry, atol16(header + newc_ino_offset, newc_ino_size)); + archive_entry_set_mode(entry, atol16(header + newc_mode_offset, newc_mode_size)); + archive_entry_set_uid(entry, atol16(header + newc_uid_offset, newc_uid_size)); + archive_entry_set_gid(entry, atol16(header + newc_gid_offset, newc_gid_size)); + archive_entry_set_nlink(entry, atol16(header + newc_nlink_offset, newc_nlink_size)); + archive_entry_set_rdevmajor(entry, atol16(header + newc_rdevmajor_offset, newc_rdevmajor_size)); + archive_entry_set_rdevminor(entry, atol16(header + newc_rdevminor_offset, newc_rdevminor_size)); + archive_entry_set_mtime(entry, atol16(header + newc_mtime_offset, newc_mtime_size), 0); + *namelength = atol16(header + newc_namesize_offset, newc_namesize_size); + /* Pad name to 2 more than a multiple of 4. */ + *name_pad = (2 - *namelength) & 3; + + /* + * Note: entry_bytes_remaining is at least 64 bits and + * therefore guaranteed to be big enough for a 33-bit file + * size. + */ + cpio->entry_bytes_remaining = + atol16(header + newc_filesize_offset, newc_filesize_size); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + /* Pad file contents to a multiple of 4. */ + cpio->entry_padding = 3 & -cpio->entry_bytes_remaining; + __archive_read_consume(a, newc_header_size); + return (r); +} + +/* + * Skip forward to the next cpio odc header by searching for the + * 070707 string. This is a hand-optimized search that could + * probably be easily generalized to handle all character-based + * cpio variants. + */ +static int +is_octal(const char *p, size_t len) +{ + while (len-- > 0) { + if (*p < '0' || *p > '7') + return (0); + ++p; + } + return (1); +} + +static int +is_afio_large(const char *h, size_t len) +{ + if (len < afiol_header_size) + return (0); + if (h[afiol_ino_m_offset] != 'm' + || h[afiol_mtime_n_offset] != 'n' + || h[afiol_xsize_s_offset] != 's' + || h[afiol_filesize_c_offset] != ':') + return (0); + if (!is_hex(h + afiol_dev_offset, afiol_ino_m_offset - afiol_dev_offset)) + return (0); + if (!is_hex(h + afiol_mode_offset, afiol_mtime_n_offset - afiol_mode_offset)) + return (0); + if (!is_hex(h + afiol_namesize_offset, afiol_xsize_s_offset - afiol_namesize_offset)) + return (0); + if (!is_hex(h + afiol_filesize_offset, afiol_filesize_size)) + return (0); + return (1); +} + +static int +find_odc_header(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip, skipped = 0; + ssize_t bytes; + + for (;;) { + h = __archive_read_ahead(a, odc_header_size, &bytes); + if (h == NULL) + return (ARCHIVE_FATAL); + p = h; + q = p + bytes; + + /* Try the typical case first, then go into the slow search.*/ + if (memcmp("070707", p, 6) == 0 && is_octal(p, odc_header_size)) + return (ARCHIVE_OK); + if (memcmp("070727", p, 6) == 0 && is_afio_large(p, bytes)) { + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_AFIO_LARGE; + return (ARCHIVE_OK); + } + + /* + * Scan ahead until we find something that looks + * like an odc header. + */ + while (p + odc_header_size <= q) { + switch (p[5]) { + case '7': + if ((memcmp("070707", p, 6) == 0 + && is_octal(p, odc_header_size)) + || (memcmp("070727", p, 6) == 0 + && is_afio_large(p, q - p))) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + skipped += skip; + if (p[4] == '2') + a->archive.archive_format = + ARCHIVE_FORMAT_CPIO_AFIO_LARGE; + if (skipped > 0) { + archive_set_error(&a->archive, + 0, + "Skipped %d bytes before " + "finding valid header", + (int)skipped); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); + } + p += 2; + break; + case '0': + p++; + break; + default: + p += 6; + break; + } + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + skipped += skip; + } +} + +static int +header_odc(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + int r; + const char *header; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_POSIX; + a->archive.archive_format_name = "POSIX octet-oriented cpio"; + + /* Find the start of the next header. */ + r = find_odc_header(a); + if (r < ARCHIVE_WARN) + return (r); + + if (a->archive.archive_format == ARCHIVE_FORMAT_CPIO_AFIO_LARGE) { + int r2 = (header_afiol(a, cpio, entry, namelength, name_pad)); + if (r2 == ARCHIVE_OK) + return (r); + else + return (r2); + } + + /* Read fixed-size portion of header. */ + h = __archive_read_ahead(a, odc_header_size, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Parse out octal fields. */ + header = (const char *)h; + + archive_entry_set_dev(entry, atol8(header + odc_dev_offset, odc_dev_size)); + archive_entry_set_ino(entry, atol8(header + odc_ino_offset, odc_ino_size)); + archive_entry_set_mode(entry, atol8(header + odc_mode_offset, odc_mode_size)); + archive_entry_set_uid(entry, atol8(header + odc_uid_offset, odc_uid_size)); + archive_entry_set_gid(entry, atol8(header + odc_gid_offset, odc_gid_size)); + archive_entry_set_nlink(entry, atol8(header + odc_nlink_offset, odc_nlink_size)); + archive_entry_set_rdev(entry, atol8(header + odc_rdev_offset, odc_rdev_size)); + archive_entry_set_mtime(entry, atol8(header + odc_mtime_offset, odc_mtime_size), 0); + *namelength = atol8(header + odc_namesize_offset, odc_namesize_size); + *name_pad = 0; /* No padding of filename. */ + + /* + * Note: entry_bytes_remaining is at least 64 bits and + * therefore guaranteed to be big enough for a 33-bit file + * size. + */ + cpio->entry_bytes_remaining = + atol8(header + odc_filesize_offset, odc_filesize_size); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = 0; + __archive_read_consume(a, odc_header_size); + return (r); +} + +/* + * NOTE: if a filename suffix is ".z", it is the file gziped by afio. + * it would be nice that we can show uncompressed file size and we can + * uncompressed file contents automatically, unfortunately we have nothing + * to get a uncompressed file size while reading each header. it means + * we also cannot uncompressed file contens under the our framework. + */ +static int +header_afiol(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const char *header; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_AFIO_LARGE; + a->archive.archive_format_name = "afio large ASCII"; + + /* Read fixed-size portion of header. */ + h = __archive_read_ahead(a, afiol_header_size, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Parse out octal fields. */ + header = (const char *)h; + + archive_entry_set_dev(entry, atol16(header + afiol_dev_offset, afiol_dev_size)); + archive_entry_set_ino(entry, atol16(header + afiol_ino_offset, afiol_ino_size)); + archive_entry_set_mode(entry, atol8(header + afiol_mode_offset, afiol_mode_size)); + archive_entry_set_uid(entry, atol16(header + afiol_uid_offset, afiol_uid_size)); + archive_entry_set_gid(entry, atol16(header + afiol_gid_offset, afiol_gid_size)); + archive_entry_set_nlink(entry, atol16(header + afiol_nlink_offset, afiol_nlink_size)); + archive_entry_set_rdev(entry, atol16(header + afiol_rdev_offset, afiol_rdev_size)); + archive_entry_set_mtime(entry, atol16(header + afiol_mtime_offset, afiol_mtime_size), 0); + *namelength = atol16(header + afiol_namesize_offset, afiol_namesize_size); + *name_pad = 0; /* No padding of filename. */ + + cpio->entry_bytes_remaining = + atol16(header + afiol_filesize_offset, afiol_filesize_size); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = 0; + __archive_read_consume(a, afiol_header_size); + return (ARCHIVE_OK); +} + + +static int +header_bin_le(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const unsigned char *header; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_BIN_LE; + a->archive.archive_format_name = "cpio (little-endian binary)"; + + /* Read fixed-size portion of header. */ + h = __archive_read_ahead(a, bin_header_size, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Parse out binary fields. */ + header = (const unsigned char *)h; + + archive_entry_set_dev(entry, header[bin_dev_offset] + header[bin_dev_offset + 1] * 256); + archive_entry_set_ino(entry, header[bin_ino_offset] + header[bin_ino_offset + 1] * 256); + archive_entry_set_mode(entry, header[bin_mode_offset] + header[bin_mode_offset + 1] * 256); + archive_entry_set_uid(entry, header[bin_uid_offset] + header[bin_uid_offset + 1] * 256); + archive_entry_set_gid(entry, header[bin_gid_offset] + header[bin_gid_offset + 1] * 256); + archive_entry_set_nlink(entry, header[bin_nlink_offset] + header[bin_nlink_offset + 1] * 256); + archive_entry_set_rdev(entry, header[bin_rdev_offset] + header[bin_rdev_offset + 1] * 256); + archive_entry_set_mtime(entry, le4(header + bin_mtime_offset), 0); + *namelength = header[bin_namesize_offset] + header[bin_namesize_offset + 1] * 256; + *name_pad = *namelength & 1; /* Pad to even. */ + + cpio->entry_bytes_remaining = le4(header + bin_filesize_offset); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = cpio->entry_bytes_remaining & 1; /* Pad to even. */ + __archive_read_consume(a, bin_header_size); + return (ARCHIVE_OK); +} + +static int +header_bin_be(struct archive_read *a, struct cpio *cpio, + struct archive_entry *entry, size_t *namelength, size_t *name_pad) +{ + const void *h; + const unsigned char *header; + + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_BIN_BE; + a->archive.archive_format_name = "cpio (big-endian binary)"; + + /* Read fixed-size portion of header. */ + h = __archive_read_ahead(a, bin_header_size, NULL); + if (h == NULL) + return (ARCHIVE_FATAL); + + /* Parse out binary fields. */ + header = (const unsigned char *)h; + + archive_entry_set_dev(entry, header[bin_dev_offset] * 256 + header[bin_dev_offset + 1]); + archive_entry_set_ino(entry, header[bin_ino_offset] * 256 + header[bin_ino_offset + 1]); + archive_entry_set_mode(entry, header[bin_mode_offset] * 256 + header[bin_mode_offset + 1]); + archive_entry_set_uid(entry, header[bin_uid_offset] * 256 + header[bin_uid_offset + 1]); + archive_entry_set_gid(entry, header[bin_gid_offset] * 256 + header[bin_gid_offset + 1]); + archive_entry_set_nlink(entry, header[bin_nlink_offset] * 256 + header[bin_nlink_offset + 1]); + archive_entry_set_rdev(entry, header[bin_rdev_offset] * 256 + header[bin_rdev_offset + 1]); + archive_entry_set_mtime(entry, be4(header + bin_mtime_offset), 0); + *namelength = header[bin_namesize_offset] * 256 + header[bin_namesize_offset + 1]; + *name_pad = *namelength & 1; /* Pad to even. */ + + cpio->entry_bytes_remaining = be4(header + bin_filesize_offset); + archive_entry_set_size(entry, cpio->entry_bytes_remaining); + cpio->entry_padding = cpio->entry_bytes_remaining & 1; /* Pad to even. */ + __archive_read_consume(a, bin_header_size); + return (ARCHIVE_OK); +} + +static int +archive_read_format_cpio_cleanup(struct archive_read *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)(a->format->data); + /* Free inode->name map */ + while (cpio->links_head != NULL) { + struct links_entry *lp = cpio->links_head->next; + + if (cpio->links_head->name) + free(cpio->links_head->name); + free(cpio->links_head); + cpio->links_head = lp; + } + free(cpio); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static int +le4(const unsigned char *p) +{ + return ((p[0]<<16) + (p[1]<<24) + (p[2]<<0) + (p[3]<<8)); +} + + +static int +be4(const unsigned char *p) +{ + return ((p[0]<<24) + (p[1]<<16) + (p[2]<<8) + (p[3])); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +atol8(const char *p, unsigned char_cnt) +{ + int64_t l; + int digit; + + l = 0; + while (char_cnt-- > 0) { + if (*p >= '0' && *p <= '7') + digit = *p - '0'; + else + return (l); + p++; + l <<= 3; + l |= digit; + } + return (l); +} + +static int64_t +atol16(const char *p, unsigned char_cnt) +{ + int64_t l; + int digit; + + l = 0; + while (char_cnt-- > 0) { + if (*p >= 'a' && *p <= 'f') + digit = *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + digit = *p - 'A' + 10; + else if (*p >= '0' && *p <= '9') + digit = *p - '0'; + else + return (l); + p++; + l <<= 4; + l |= digit; + } + return (l); +} + +static int +record_hardlink(struct archive_read *a, + struct cpio *cpio, struct archive_entry *entry) +{ + struct links_entry *le; + dev_t dev; + int64_t ino; + + if (archive_entry_nlink(entry) <= 1) + return (ARCHIVE_OK); + + dev = archive_entry_dev(entry); + ino = archive_entry_ino64(entry); + + /* + * First look in the list of multiply-linked files. If we've + * already dumped it, convert this entry to a hard link entry. + */ + for (le = cpio->links_head; le; le = le->next) { + if (le->dev == dev && le->ino == ino) { + archive_entry_copy_hardlink(entry, le->name); + + if (--le->links <= 0) { + if (le->previous != NULL) + le->previous->next = le->next; + if (le->next != NULL) + le->next->previous = le->previous; + if (cpio->links_head == le) + cpio->links_head = le->next; + free(le->name); + free(le); + } + + return (ARCHIVE_OK); + } + } + + le = (struct links_entry *)malloc(sizeof(struct links_entry)); + if (le == NULL) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory adding file to list"); + return (ARCHIVE_FATAL); + } + if (cpio->links_head != NULL) + cpio->links_head->previous = le; + le->next = cpio->links_head; + le->previous = NULL; + cpio->links_head = le; + le->dev = dev; + le->ino = ino; + le->links = archive_entry_nlink(entry) - 1; + le->name = strdup(archive_entry_pathname(entry)); + if (le->name == NULL) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory adding file to list"); + return (ARCHIVE_FATAL); + } + + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_empty.c b/libarchive/archive_read_support_format_empty.c new file mode 100644 index 0000000..3dc2196 --- /dev/null +++ b/libarchive/archive_read_support_format_empty.c @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_empty.c 191524 2009-04-26 18:24:14Z kientzle $"); + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +static int archive_read_format_empty_bid(struct archive_read *, int); +static int archive_read_format_empty_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_empty_read_header(struct archive_read *, + struct archive_entry *); +int +archive_read_support_format_empty(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_empty"); + + r = __archive_read_register_format(a, + NULL, + NULL, + archive_read_format_empty_bid, + NULL, + archive_read_format_empty_read_header, + archive_read_format_empty_read_data, + NULL, + NULL); + + return (r); +} + + +static int +archive_read_format_empty_bid(struct archive_read *a, int best_bid) +{ + if (best_bid < 1 && __archive_read_ahead(a, 1, NULL) == NULL) + return (1); + return (-1); +} + +static int +archive_read_format_empty_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + (void)a; /* UNUSED */ + (void)entry; /* UNUSED */ + + a->archive.archive_format = ARCHIVE_FORMAT_EMPTY; + a->archive.archive_format_name = "Empty file"; + + return (ARCHIVE_EOF); +} + +static int +archive_read_format_empty_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + (void)a; /* UNUSED */ + (void)buff; /* UNUSED */ + (void)size; /* UNUSED */ + (void)offset; /* UNUSED */ + + return (ARCHIVE_EOF); +} diff --git a/libarchive/archive_read_support_format_iso9660.c b/libarchive/archive_read_support_format_iso9660.c new file mode 100644 index 0000000..a1bed4f --- /dev/null +++ b/libarchive/archive_read_support_format_iso9660.c @@ -0,0 +1,3213 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2009 Andreas Henriksson + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_iso9660.c 201246 2009-12-30 05:30:35Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +/* #include */ /* See archive_platform.h */ +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_string.h" + +/* + * An overview of ISO 9660 format: + * + * Each disk is laid out as follows: + * * 32k reserved for private use + * * Volume descriptor table. Each volume descriptor + * is 2k and specifies basic format information. + * The "Primary Volume Descriptor" (PVD) is defined by the + * standard and should always be present; other volume + * descriptors include various vendor-specific extensions. + * * Files and directories. Each file/dir is specified by + * an "extent" (starting sector and length in bytes). + * Dirs are just files with directory records packed one + * after another. The PVD contains a single dir entry + * specifying the location of the root directory. Everything + * else follows from there. + * + * This module works by first reading the volume descriptors, then + * building a list of directory entries, sorted by starting + * sector. At each step, I look for the earliest dir entry that + * hasn't yet been read, seek forward to that location and read + * that entry. If it's a dir, I slurp in the new dir entries and + * add them to the heap; if it's a regular file, I return the + * corresponding archive_entry and wait for the client to request + * the file body. This strategy allows us to read most compliant + * CDs with a single pass through the data, as required by libarchive. + */ +#define LOGICAL_BLOCK_SIZE 2048 +#define SYSTEM_AREA_BLOCK 16 + +/* Structure of on-disk primary volume descriptor. */ +#define PVD_type_offset 0 +#define PVD_type_size 1 +#define PVD_id_offset (PVD_type_offset + PVD_type_size) +#define PVD_id_size 5 +#define PVD_version_offset (PVD_id_offset + PVD_id_size) +#define PVD_version_size 1 +#define PVD_reserved1_offset (PVD_version_offset + PVD_version_size) +#define PVD_reserved1_size 1 +#define PVD_system_id_offset (PVD_reserved1_offset + PVD_reserved1_size) +#define PVD_system_id_size 32 +#define PVD_volume_id_offset (PVD_system_id_offset + PVD_system_id_size) +#define PVD_volume_id_size 32 +#define PVD_reserved2_offset (PVD_volume_id_offset + PVD_volume_id_size) +#define PVD_reserved2_size 8 +#define PVD_volume_space_size_offset (PVD_reserved2_offset + PVD_reserved2_size) +#define PVD_volume_space_size_size 8 +#define PVD_reserved3_offset (PVD_volume_space_size_offset + PVD_volume_space_size_size) +#define PVD_reserved3_size 32 +#define PVD_volume_set_size_offset (PVD_reserved3_offset + PVD_reserved3_size) +#define PVD_volume_set_size_size 4 +#define PVD_volume_sequence_number_offset (PVD_volume_set_size_offset + PVD_volume_set_size_size) +#define PVD_volume_sequence_number_size 4 +#define PVD_logical_block_size_offset (PVD_volume_sequence_number_offset + PVD_volume_sequence_number_size) +#define PVD_logical_block_size_size 4 +#define PVD_path_table_size_offset (PVD_logical_block_size_offset + PVD_logical_block_size_size) +#define PVD_path_table_size_size 8 +#define PVD_type_1_path_table_offset (PVD_path_table_size_offset + PVD_path_table_size_size) +#define PVD_type_1_path_table_size 4 +#define PVD_opt_type_1_path_table_offset (PVD_type_1_path_table_offset + PVD_type_1_path_table_size) +#define PVD_opt_type_1_path_table_size 4 +#define PVD_type_m_path_table_offset (PVD_opt_type_1_path_table_offset + PVD_opt_type_1_path_table_size) +#define PVD_type_m_path_table_size 4 +#define PVD_opt_type_m_path_table_offset (PVD_type_m_path_table_offset + PVD_type_m_path_table_size) +#define PVD_opt_type_m_path_table_size 4 +#define PVD_root_directory_record_offset (PVD_opt_type_m_path_table_offset + PVD_opt_type_m_path_table_size) +#define PVD_root_directory_record_size 34 +#define PVD_volume_set_id_offset (PVD_root_directory_record_offset + PVD_root_directory_record_size) +#define PVD_volume_set_id_size 128 +#define PVD_publisher_id_offset (PVD_volume_set_id_offset + PVD_volume_set_id_size) +#define PVD_publisher_id_size 128 +#define PVD_preparer_id_offset (PVD_publisher_id_offset + PVD_publisher_id_size) +#define PVD_preparer_id_size 128 +#define PVD_application_id_offset (PVD_preparer_id_offset + PVD_preparer_id_size) +#define PVD_application_id_size 128 +#define PVD_copyright_file_id_offset (PVD_application_id_offset + PVD_application_id_size) +#define PVD_copyright_file_id_size 37 +#define PVD_abstract_file_id_offset (PVD_copyright_file_id_offset + PVD_copyright_file_id_size) +#define PVD_abstract_file_id_size 37 +#define PVD_bibliographic_file_id_offset (PVD_abstract_file_id_offset + PVD_abstract_file_id_size) +#define PVD_bibliographic_file_id_size 37 +#define PVD_creation_date_offset (PVD_bibliographic_file_id_offset + PVD_bibliographic_file_id_size) +#define PVD_creation_date_size 17 +#define PVD_modification_date_offset (PVD_creation_date_offset + PVD_creation_date_size) +#define PVD_modification_date_size 17 +#define PVD_expiration_date_offset (PVD_modification_date_offset + PVD_modification_date_size) +#define PVD_expiration_date_size 17 +#define PVD_effective_date_offset (PVD_expiration_date_offset + PVD_expiration_date_size) +#define PVD_effective_date_size 17 +#define PVD_file_structure_version_offset (PVD_effective_date_offset + PVD_effective_date_size) +#define PVD_file_structure_version_size 1 +#define PVD_reserved4_offset (PVD_file_structure_version_offset + PVD_file_structure_version_size) +#define PVD_reserved4_size 1 +#define PVD_application_data_offset (PVD_reserved4_offset + PVD_reserved4_size) +#define PVD_application_data_size 512 +#define PVD_reserved5_offset (PVD_application_data_offset + PVD_application_data_size) +#define PVD_reserved5_size (2048 - PVD_reserved5_offset) + +/* TODO: It would make future maintenance easier to just hardcode the + * above values. In particular, ECMA119 states the offsets as part of + * the standard. That would eliminate the need for the following check.*/ +#if PVD_reserved5_offset != 1395 +#error PVD offset and size definitions are wrong. +#endif + + +/* Structure of optional on-disk supplementary volume descriptor. */ +#define SVD_type_offset 0 +#define SVD_type_size 1 +#define SVD_id_offset (SVD_type_offset + SVD_type_size) +#define SVD_id_size 5 +#define SVD_version_offset (SVD_id_offset + SVD_id_size) +#define SVD_version_size 1 +/* ... */ +#define SVD_reserved1_offset 72 +#define SVD_reserved1_size 8 +#define SVD_volume_space_size_offset 80 +#define SVD_volume_space_size_size 8 +#define SVD_escape_sequences_offset (SVD_volume_space_size_offset + SVD_volume_space_size_size) +#define SVD_escape_sequences_size 32 +/* ... */ +#define SVD_logical_block_size_offset 128 +#define SVD_logical_block_size_size 4 +#define SVD_type_L_path_table_offset 140 +#define SVD_type_M_path_table_offset 148 +/* ... */ +#define SVD_root_directory_record_offset 156 +#define SVD_root_directory_record_size 34 +#define SVD_file_structure_version_offset 881 +#define SVD_reserved2_offset 882 +#define SVD_reserved2_size 1 +#define SVD_reserved3_offset 1395 +#define SVD_reserved3_size 653 +/* ... */ +/* FIXME: validate correctness of last SVD entry offset. */ + +/* Structure of an on-disk directory record. */ +/* Note: ISO9660 stores each multi-byte integer twice, once in + * each byte order. The sizes here are the size of just one + * of the two integers. (This is why the offset of a field isn't + * the same as the offset+size of the previous field.) */ +#define DR_length_offset 0 +#define DR_length_size 1 +#define DR_ext_attr_length_offset 1 +#define DR_ext_attr_length_size 1 +#define DR_extent_offset 2 +#define DR_extent_size 4 +#define DR_size_offset 10 +#define DR_size_size 4 +#define DR_date_offset 18 +#define DR_date_size 7 +#define DR_flags_offset 25 +#define DR_flags_size 1 +#define DR_file_unit_size_offset 26 +#define DR_file_unit_size_size 1 +#define DR_interleave_offset 27 +#define DR_interleave_size 1 +#define DR_volume_sequence_number_offset 28 +#define DR_volume_sequence_number_size 2 +#define DR_name_len_offset 32 +#define DR_name_len_size 1 +#define DR_name_offset 33 + +#ifdef HAVE_ZLIB_H +static const unsigned char zisofs_magic[8] = { + 0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07 +}; + +struct zisofs { + /* Set 1 if this file compressed by paged zlib */ + int pz; + int pz_log2_bs; /* Log2 of block size */ + uint64_t pz_uncompressed_size; + + int initialized; + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; + + uint32_t pz_offset; + unsigned char header[16]; + size_t header_avail; + int header_passed; + unsigned char *block_pointers; + size_t block_pointers_alloc; + size_t block_pointers_size; + size_t block_pointers_avail; + size_t block_off; + uint32_t block_avail; + + z_stream stream; + int stream_valid; +}; +#else +struct zisofs { + /* Set 1 if this file compressed by paged zlib */ + int pz; +}; +#endif + +struct content { + uint64_t offset;/* Offset on disk. */ + uint64_t size; /* File size in bytes. */ + struct content *next; +}; + +/* In-memory storage for a directory record. */ +struct file_info { + struct file_info *use_next; + struct file_info *parent; + struct file_info *next; + struct file_info *re_next; + int subdirs; + uint64_t key; /* Heap Key. */ + uint64_t offset; /* Offset on disk. */ + uint64_t size; /* File size in bytes. */ + uint32_t ce_offset; /* Offset of CE. */ + uint32_t ce_size; /* Size of CE. */ + char rr_moved; /* Flag to rr_moved. */ + char rr_moved_has_re_only; + char re; /* Having RRIP "RE" extension. */ + char re_descendant; + uint64_t cl_offset; /* Having RRIP "CL" extension. */ + int birthtime_is_set; + time_t birthtime; /* File created time. */ + time_t mtime; /* File last modified time. */ + time_t atime; /* File last accessed time. */ + time_t ctime; /* File attribute change time. */ + uint64_t rdev; /* Device number. */ + mode_t mode; + uid_t uid; + gid_t gid; + int64_t number; + int nlinks; + struct archive_string name; /* Pathname */ + unsigned char *utf16be_name; + size_t utf16be_bytes; + char name_continues; /* Non-zero if name continues */ + struct archive_string symlink; + char symlink_continues; /* Non-zero if link continues */ + /* Set 1 if this file compressed by paged zlib(zisofs) */ + int pz; + int pz_log2_bs; /* Log2 of block size */ + uint64_t pz_uncompressed_size; + /* Set 1 if this file is multi extent. */ + int multi_extent; + struct { + struct content *first; + struct content **last; + } contents; + struct { + struct file_info *first; + struct file_info **last; + } rede_files; +}; + +struct heap_queue { + struct file_info **files; + int allocated; + int used; +}; + +struct iso9660 { + int magic; +#define ISO9660_MAGIC 0x96609660 + + int opt_support_joliet; + int opt_support_rockridge; + + struct archive_string pathname; + char seenRockridge; /* Set true if RR extensions are used. */ + char seenSUSP; /* Set true if SUSP is beging used. */ + char seenJoliet; + + unsigned char suspOffset; + struct file_info *rr_moved; + struct read_ce_queue { + struct read_ce_req { + uint64_t offset;/* Offset of CE on disk. */ + struct file_info *file; + } *reqs; + int cnt; + int allocated; + } read_ce_req; + + int64_t previous_number; + struct archive_string previous_pathname; + + struct file_info *use_files; + struct heap_queue pending_files; + struct { + struct file_info *first; + struct file_info **last; + } cache_files; + struct { + struct file_info *first; + struct file_info **last; + } re_files; + + uint64_t current_position; + ssize_t logical_block_size; + uint64_t volume_size; /* Total size of volume in bytes. */ + int32_t volume_block;/* Total size of volume in logical blocks. */ + + struct vd { + int location; /* Location of Extent. */ + uint32_t size; + } primary, joliet; + + int64_t entry_sparse_offset; + int64_t entry_bytes_remaining; + size_t entry_bytes_unconsumed; + struct zisofs entry_zisofs; + struct content *entry_content; + struct archive_string_conv *sconv_utf16be; + /* + * Buffers for a full pathname in UTF-16BE in Joliet extensions. + */ +#define UTF16_NAME_MAX 1024 + unsigned char *utf16be_path; + size_t utf16be_path_len; + unsigned char *utf16be_previous_path; + size_t utf16be_previous_path_len; +}; + +static int archive_read_format_iso9660_bid(struct archive_read *, int); +static int archive_read_format_iso9660_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_iso9660_cleanup(struct archive_read *); +static int archive_read_format_iso9660_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_iso9660_read_data_skip(struct archive_read *); +static int archive_read_format_iso9660_read_header(struct archive_read *, + struct archive_entry *); +static const char *build_pathname(struct archive_string *, struct file_info *); +static int build_pathname_utf16be(unsigned char *, size_t, size_t *, + struct file_info *); +#if DEBUG +static void dump_isodirrec(FILE *, const unsigned char *isodirrec); +#endif +static time_t time_from_tm(struct tm *); +static time_t isodate17(const unsigned char *); +static time_t isodate7(const unsigned char *); +static int isBootRecord(struct iso9660 *, const unsigned char *); +static int isVolumePartition(struct iso9660 *, const unsigned char *); +static int isVDSetTerminator(struct iso9660 *, const unsigned char *); +static int isJolietSVD(struct iso9660 *, const unsigned char *); +static int isSVD(struct iso9660 *, const unsigned char *); +static int isEVD(struct iso9660 *, const unsigned char *); +static int isPVD(struct iso9660 *, const unsigned char *); +static int next_cache_entry(struct archive_read *, struct iso9660 *, + struct file_info **); +static int next_entry_seek(struct archive_read *, struct iso9660 *, + struct file_info **); +static struct file_info * + parse_file_info(struct archive_read *a, + struct file_info *parent, const unsigned char *isodirrec); +static int parse_rockridge(struct archive_read *a, + struct file_info *file, const unsigned char *start, + const unsigned char *end); +static int register_CE(struct archive_read *a, int32_t location, + struct file_info *file); +static int read_CE(struct archive_read *a, struct iso9660 *iso9660); +static void parse_rockridge_NM1(struct file_info *, + const unsigned char *, int); +static void parse_rockridge_SL1(struct file_info *, + const unsigned char *, int); +static void parse_rockridge_TF1(struct file_info *, + const unsigned char *, int); +static void parse_rockridge_ZF1(struct file_info *, + const unsigned char *, int); +static void register_file(struct iso9660 *, struct file_info *); +static void release_files(struct iso9660 *); +static unsigned toi(const void *p, int n); +static inline void re_add_entry(struct iso9660 *, struct file_info *); +static inline struct file_info * re_get_entry(struct iso9660 *); +static inline int rede_add_entry(struct file_info *); +static inline struct file_info * rede_get_entry(struct file_info *); +static inline void cache_add_entry(struct iso9660 *iso9660, + struct file_info *file); +static inline struct file_info *cache_get_entry(struct iso9660 *iso9660); +static int heap_add_entry(struct archive_read *a, struct heap_queue *heap, + struct file_info *file, uint64_t key); +static struct file_info *heap_get_entry(struct heap_queue *heap); + +#define add_entry(arch, iso9660, file) \ + heap_add_entry(arch, &((iso9660)->pending_files), file, file->offset) +#define next_entry(iso9660) \ + heap_get_entry(&((iso9660)->pending_files)) + +int +archive_read_support_format_iso9660(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct iso9660 *iso9660; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_iso9660"); + + iso9660 = (struct iso9660 *)calloc(1, sizeof(*iso9660)); + if (iso9660 == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate iso9660 data"); + return (ARCHIVE_FATAL); + } + iso9660->magic = ISO9660_MAGIC; + iso9660->cache_files.first = NULL; + iso9660->cache_files.last = &(iso9660->cache_files.first); + iso9660->re_files.first = NULL; + iso9660->re_files.last = &(iso9660->re_files.first); + /* Enable to support Joliet extensions by default. */ + iso9660->opt_support_joliet = 1; + /* Enable to support Rock Ridge extensions by default. */ + iso9660->opt_support_rockridge = 1; + + r = __archive_read_register_format(a, + iso9660, + "iso9660", + archive_read_format_iso9660_bid, + archive_read_format_iso9660_options, + archive_read_format_iso9660_read_header, + archive_read_format_iso9660_read_data, + archive_read_format_iso9660_read_data_skip, + archive_read_format_iso9660_cleanup); + + if (r != ARCHIVE_OK) { + free(iso9660); + return (r); + } + return (ARCHIVE_OK); +} + + +static int +archive_read_format_iso9660_bid(struct archive_read *a, int best_bid) +{ + struct iso9660 *iso9660; + ssize_t bytes_read; + const unsigned char *p; + int seenTerminator; + + /* If there's already a better bid than we can ever + make, don't bother testing. */ + if (best_bid > 48) + return (-1); + + iso9660 = (struct iso9660 *)(a->format->data); + + /* + * Skip the first 32k (reserved area) and get the first + * 8 sectors of the volume descriptor table. Of course, + * if the I/O layer gives us more, we'll take it. + */ +#define RESERVED_AREA (SYSTEM_AREA_BLOCK * LOGICAL_BLOCK_SIZE) + p = __archive_read_ahead(a, + RESERVED_AREA + 8 * LOGICAL_BLOCK_SIZE, + &bytes_read); + if (p == NULL) + return (-1); + + /* Skip the reserved area. */ + bytes_read -= RESERVED_AREA; + p += RESERVED_AREA; + + /* Check each volume descriptor. */ + seenTerminator = 0; + for (; bytes_read > LOGICAL_BLOCK_SIZE; + bytes_read -= LOGICAL_BLOCK_SIZE, p += LOGICAL_BLOCK_SIZE) { + /* Do not handle undefined Volume Descriptor Type. */ + if (p[0] >= 4 && p[0] <= 254) + return (0); + /* Standard Identifier must be "CD001" */ + if (memcmp(p + 1, "CD001", 5) != 0) + return (0); + if (isPVD(iso9660, p)) + continue; + if (!iso9660->joliet.location) { + if (isJolietSVD(iso9660, p)) + continue; + } + if (isBootRecord(iso9660, p)) + continue; + if (isEVD(iso9660, p)) + continue; + if (isSVD(iso9660, p)) + continue; + if (isVolumePartition(iso9660, p)) + continue; + if (isVDSetTerminator(iso9660, p)) { + seenTerminator = 1; + break; + } + return (0); + } + /* + * ISO 9660 format must have Primary Volume Descriptor and + * Volume Descriptor Set Terminator. + */ + if (seenTerminator && iso9660->primary.location > 16) + return (48); + + /* We didn't find a valid PVD; return a bid of zero. */ + return (0); +} + +static int +archive_read_format_iso9660_options(struct archive_read *a, + const char *key, const char *val) +{ + struct iso9660 *iso9660; + + iso9660 = (struct iso9660 *)(a->format->data); + + if (strcmp(key, "joliet") == 0) { + if (val == NULL || strcmp(val, "off") == 0 || + strcmp(val, "ignore") == 0 || + strcmp(val, "disable") == 0 || + strcmp(val, "0") == 0) + iso9660->opt_support_joliet = 0; + else + iso9660->opt_support_joliet = 1; + return (ARCHIVE_OK); + } + if (strcmp(key, "rockridge") == 0 || + strcmp(key, "Rockridge") == 0) { + iso9660->opt_support_rockridge = val != NULL; + return (ARCHIVE_OK); + } + + /* Note: The "warn" return is just to inform the options + * supervisor that we didn't handle it. It will generate + * a suitable error if no one used this option. */ + return (ARCHIVE_WARN); +} + +static int +isBootRecord(struct iso9660 *iso9660, const unsigned char *h) +{ + (void)iso9660; /* UNUSED */ + + /* Type of the Volume Descriptor Boot Record must be 0. */ + if (h[0] != 0) + return (0); + + /* Volume Descriptor Version must be 1. */ + if (h[6] != 1) + return (0); + + return (1); +} + +static int +isVolumePartition(struct iso9660 *iso9660, const unsigned char *h) +{ + int32_t location; + + /* Type of the Volume Partition Descriptor must be 3. */ + if (h[0] != 3) + return (0); + + /* Volume Descriptor Version must be 1. */ + if (h[6] != 1) + return (0); + /* Unused Field */ + if (h[7] != 0) + return (0); + + location = archive_le32dec(h + 72); + if (location <= SYSTEM_AREA_BLOCK || + location >= iso9660->volume_block) + return (0); + if ((uint32_t)location != archive_be32dec(h + 76)) + return (0); + + return (1); +} + +static int +isVDSetTerminator(struct iso9660 *iso9660, const unsigned char *h) +{ + int i; + + (void)iso9660; /* UNUSED */ + + /* Type of the Volume Descriptor Set Terminator must be 255. */ + if (h[0] != 255) + return (0); + + /* Volume Descriptor Version must be 1. */ + if (h[6] != 1) + return (0); + + /* Reserved field must be 0. */ + for (i = 7; i < 2048; ++i) + if (h[i] != 0) + return (0); + + return (1); +} + +static int +isJolietSVD(struct iso9660 *iso9660, const unsigned char *h) +{ + const unsigned char *p; + ssize_t logical_block_size; + int32_t volume_block; + + /* Check if current sector is a kind of Supplementary Volume + * Descriptor. */ + if (!isSVD(iso9660, h)) + return (0); + + /* FIXME: do more validations according to joliet spec. */ + + /* check if this SVD contains joliet extension! */ + p = h + SVD_escape_sequences_offset; + /* N.B. Joliet spec says p[1] == '\\', but.... */ + if (p[0] == '%' && p[1] == '/') { + int level = 0; + + if (p[2] == '@') + level = 1; + else if (p[2] == 'C') + level = 2; + else if (p[2] == 'E') + level = 3; + else /* not joliet */ + return (0); + + iso9660->seenJoliet = level; + + } else /* not joliet */ + return (0); + + logical_block_size = + archive_le16dec(h + SVD_logical_block_size_offset); + volume_block = archive_le32dec(h + SVD_volume_space_size_offset); + + iso9660->logical_block_size = logical_block_size; + iso9660->volume_block = volume_block; + iso9660->volume_size = logical_block_size * (uint64_t)volume_block; + /* Read Root Directory Record in Volume Descriptor. */ + p = h + SVD_root_directory_record_offset; + iso9660->joliet.location = archive_le32dec(p + DR_extent_offset); + iso9660->joliet.size = archive_le32dec(p + DR_size_offset); + + return (48); +} + +static int +isSVD(struct iso9660 *iso9660, const unsigned char *h) +{ + const unsigned char *p; + ssize_t logical_block_size; + int32_t volume_block; + int32_t location; + int i; + + (void)iso9660; /* UNUSED */ + + /* Type 2 means it's a SVD. */ + if (h[SVD_type_offset] != 2) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < SVD_reserved1_size; ++i) + if (h[SVD_reserved1_offset + i] != 0) + return (0); + for (i = 0; i < SVD_reserved2_size; ++i) + if (h[SVD_reserved2_offset + i] != 0) + return (0); + for (i = 0; i < SVD_reserved3_size; ++i) + if (h[SVD_reserved3_offset + i] != 0) + return (0); + + /* File structure version must be 1 for ISO9660/ECMA119. */ + if (h[SVD_file_structure_version_offset] != 1) + return (0); + + logical_block_size = + archive_le16dec(h + SVD_logical_block_size_offset); + if (logical_block_size <= 0) + return (0); + + volume_block = archive_le32dec(h + SVD_volume_space_size_offset); + if (volume_block <= SYSTEM_AREA_BLOCK+4) + return (0); + + /* Location of Occurrence of Type L Path Table must be + * available location, + * >= SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_le32dec(h+SVD_type_L_path_table_offset); + if (location < SYSTEM_AREA_BLOCK+2 || location >= volume_block) + return (0); + + /* The Type M Path Table must be at a valid location (WinISO + * and probably other programs omit this, so we allow zero) + * + * >= SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_be32dec(h+SVD_type_M_path_table_offset); + if ((location > 0 && location < SYSTEM_AREA_BLOCK+2) + || location >= volume_block) + return (0); + + /* Read Root Directory Record in Volume Descriptor. */ + p = h + SVD_root_directory_record_offset; + if (p[DR_length_offset] != 34) + return (0); + + return (48); +} + +static int +isEVD(struct iso9660 *iso9660, const unsigned char *h) +{ + const unsigned char *p; + ssize_t logical_block_size; + int32_t volume_block; + int32_t location; + int i; + + (void)iso9660; /* UNUSED */ + + /* Type of the Enhanced Volume Descriptor must be 2. */ + if (h[PVD_type_offset] != 2) + return (0); + + /* EVD version must be 2. */ + if (h[PVD_version_offset] != 2) + return (0); + + /* Reserved field must be 0. */ + if (h[PVD_reserved1_offset] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved2_size; ++i) + if (h[PVD_reserved2_offset + i] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved3_size; ++i) + if (h[PVD_reserved3_offset + i] != 0) + return (0); + + /* Logical block size must be > 0. */ + /* I've looked at Ecma 119 and can't find any stronger + * restriction on this field. */ + logical_block_size = + archive_le16dec(h + PVD_logical_block_size_offset); + if (logical_block_size <= 0) + return (0); + + volume_block = + archive_le32dec(h + PVD_volume_space_size_offset); + if (volume_block <= SYSTEM_AREA_BLOCK+4) + return (0); + + /* File structure version must be 2 for ISO9660:1999. */ + if (h[PVD_file_structure_version_offset] != 2) + return (0); + + /* Location of Occurrence of Type L Path Table must be + * available location, + * >= SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_le32dec(h+PVD_type_1_path_table_offset); + if (location < SYSTEM_AREA_BLOCK+2 || location >= volume_block) + return (0); + + /* Location of Occurrence of Type M Path Table must be + * available location, + * >= SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_be32dec(h+PVD_type_m_path_table_offset); + if ((location > 0 && location < SYSTEM_AREA_BLOCK+2) + || location >= volume_block) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved4_size; ++i) + if (h[PVD_reserved4_offset + i] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved5_size; ++i) + if (h[PVD_reserved5_offset + i] != 0) + return (0); + + /* Read Root Directory Record in Volume Descriptor. */ + p = h + PVD_root_directory_record_offset; + if (p[DR_length_offset] != 34) + return (0); + + return (48); +} + +static int +isPVD(struct iso9660 *iso9660, const unsigned char *h) +{ + const unsigned char *p; + ssize_t logical_block_size; + int32_t volume_block; + int32_t location; + int i; + + /* Type of the Primary Volume Descriptor must be 1. */ + if (h[PVD_type_offset] != 1) + return (0); + + /* PVD version must be 1. */ + if (h[PVD_version_offset] != 1) + return (0); + + /* Reserved field must be 0. */ + if (h[PVD_reserved1_offset] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved2_size; ++i) + if (h[PVD_reserved2_offset + i] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved3_size; ++i) + if (h[PVD_reserved3_offset + i] != 0) + return (0); + + /* Logical block size must be > 0. */ + /* I've looked at Ecma 119 and can't find any stronger + * restriction on this field. */ + logical_block_size = + archive_le16dec(h + PVD_logical_block_size_offset); + if (logical_block_size <= 0) + return (0); + + volume_block = archive_le32dec(h + PVD_volume_space_size_offset); + if (volume_block <= SYSTEM_AREA_BLOCK+4) + return (0); + + /* File structure version must be 1 for ISO9660/ECMA119. */ + if (h[PVD_file_structure_version_offset] != 1) + return (0); + + /* Location of Occurrence of Type L Path Table must be + * available location, + * > SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_le32dec(h+PVD_type_1_path_table_offset); + if (location < SYSTEM_AREA_BLOCK+2 || location >= volume_block) + return (0); + + /* The Type M Path Table must also be at a valid location + * (although ECMA 119 requires a Type M Path Table, WinISO and + * probably other programs omit it, so we permit a zero here) + * + * >= SYSTEM_AREA_BLOCK(16) + 2 and < Volume Space Size. */ + location = archive_be32dec(h+PVD_type_m_path_table_offset); + if ((location > 0 && location < SYSTEM_AREA_BLOCK+2) + || location >= volume_block) + return (0); + + /* Reserved field must be 0. */ + /* But accept NetBSD/FreeBSD "makefs" images with 0x20 here. */ + for (i = 0; i < PVD_reserved4_size; ++i) + if (h[PVD_reserved4_offset + i] != 0 + && h[PVD_reserved4_offset + i] != 0x20) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved5_size; ++i) + if (h[PVD_reserved5_offset + i] != 0) + return (0); + + /* XXX TODO: Check other values for sanity; reject more + * malformed PVDs. XXX */ + + /* Read Root Directory Record in Volume Descriptor. */ + p = h + PVD_root_directory_record_offset; + if (p[DR_length_offset] != 34) + return (0); + + if (!iso9660->primary.location) { + iso9660->logical_block_size = logical_block_size; + iso9660->volume_block = volume_block; + iso9660->volume_size = logical_block_size * (uint64_t)volume_block; + iso9660->primary.location = archive_le32dec(p + DR_extent_offset); + iso9660->primary.size = archive_le32dec(p + DR_size_offset); + } + + return (48); +} + +static int +read_children(struct archive_read *a, struct file_info *parent) +{ + struct iso9660 *iso9660; + const unsigned char *b, *p; + struct file_info *multi; + size_t step, skip_size; + + iso9660 = (struct iso9660 *)(a->format->data); + if (iso9660->current_position > parent->offset) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring out-of-order directory (%s) %jd > %jd", + parent->name.s, + (intmax_t)iso9660->current_position, + (intmax_t)parent->offset); + return (ARCHIVE_WARN); + } + if (parent->offset + parent->size > iso9660->volume_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Directory is beyond end-of-media: %s", + parent->name.s); + return (ARCHIVE_WARN); + } + if (iso9660->current_position < parent->offset) { + int64_t skipsize; + + skipsize = parent->offset - iso9660->current_position; + skipsize = __archive_read_consume(a, skipsize); + if (skipsize < 0) + return ((int)skipsize); + iso9660->current_position = parent->offset; + } + + step = ((parent->size + iso9660->logical_block_size -1) / + iso9660->logical_block_size) * iso9660->logical_block_size; + b = __archive_read_ahead(a, step, NULL); + if (b == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to read full block when scanning " + "ISO9660 directory list"); + return (ARCHIVE_FATAL); + } + iso9660->current_position += step; + multi = NULL; + skip_size = step; + while (step) { + p = b; + b += iso9660->logical_block_size; + step -= iso9660->logical_block_size; + for (; *p != 0 && p < b && p + *p <= b; p += *p) { + struct file_info *child; + + /* N.B.: these special directory identifiers + * are 8 bit "values" even on a + * Joliet CD with UCS-2 (16bit) encoding. + */ + + /* Skip '.' entry. */ + if (*(p + DR_name_len_offset) == 1 + && *(p + DR_name_offset) == '\0') + continue; + /* Skip '..' entry. */ + if (*(p + DR_name_len_offset) == 1 + && *(p + DR_name_offset) == '\001') + continue; + child = parse_file_info(a, parent, p); + if (child == NULL) { + __archive_read_consume(a, skip_size); + return (ARCHIVE_FATAL); + } + if (child->cl_offset == 0 && + (child->multi_extent || multi != NULL)) { + struct content *con; + + if (multi == NULL) { + multi = child; + multi->contents.first = NULL; + multi->contents.last = + &(multi->contents.first); + } + con = malloc(sizeof(struct content)); + if (con == NULL) { + archive_set_error( + &a->archive, ENOMEM, + "No memory for multi extent"); + __archive_read_consume(a, skip_size); + return (ARCHIVE_FATAL); + } + con->offset = child->offset; + con->size = child->size; + con->next = NULL; + *multi->contents.last = con; + multi->contents.last = &(con->next); + if (multi == child) { + if (add_entry(a, iso9660, child) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + multi->size += child->size; + if (!child->multi_extent) + multi = NULL; + } + } else + if (add_entry(a, iso9660, child) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + } + + __archive_read_consume(a, skip_size); + + /* Read data which recorded by RRIP "CE" extension. */ + if (read_CE(a, iso9660) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct iso9660 *iso9660; + struct file_info *file; + int r, rd_r = ARCHIVE_OK; + + iso9660 = (struct iso9660 *)(a->format->data); + + if (!a->archive.archive_format) { + a->archive.archive_format = ARCHIVE_FORMAT_ISO9660; + a->archive.archive_format_name = "ISO9660"; + } + + if (iso9660->current_position == 0) { + int64_t skipsize; + struct vd *vd; + const void *block; + char seenJoliet; + + vd = &(iso9660->primary); + if (!iso9660->opt_support_joliet) + iso9660->seenJoliet = 0; + if (iso9660->seenJoliet && + vd->location > iso9660->joliet.location) + /* This condition is unlikely; by way of caution. */ + vd = &(iso9660->joliet); + + skipsize = LOGICAL_BLOCK_SIZE * vd->location; + skipsize = __archive_read_consume(a, skipsize); + if (skipsize < 0) + return ((int)skipsize); + iso9660->current_position = skipsize; + + block = __archive_read_ahead(a, vd->size, NULL); + if (block == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to read full block when scanning " + "ISO9660 directory list"); + return (ARCHIVE_FATAL); + } + + /* + * While reading Root Directory, flag seenJoliet + * must be zero to avoid converting special name + * 0x00(Current Directory) and next byte to UCS2. + */ + seenJoliet = iso9660->seenJoliet;/* Save flag. */ + iso9660->seenJoliet = 0; + file = parse_file_info(a, NULL, block); + if (file == NULL) + return (ARCHIVE_FATAL); + iso9660->seenJoliet = seenJoliet; + if (vd == &(iso9660->primary) && iso9660->seenRockridge + && iso9660->seenJoliet) + /* + * If iso image has RockRidge and Joliet, + * we use RockRidge Extensions. + */ + iso9660->seenJoliet = 0; + if (vd == &(iso9660->primary) && !iso9660->seenRockridge + && iso9660->seenJoliet) { + /* Switch reading data from primary to joliet. */ + vd = &(iso9660->joliet); + skipsize = LOGICAL_BLOCK_SIZE * vd->location; + skipsize -= iso9660->current_position; + skipsize = __archive_read_consume(a, skipsize); + if (skipsize < 0) + return ((int)skipsize); + iso9660->current_position += skipsize; + + block = __archive_read_ahead(a, vd->size, NULL); + if (block == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to read full block when scanning " + "ISO9660 directory list"); + return (ARCHIVE_FATAL); + } + iso9660->seenJoliet = 0; + file = parse_file_info(a, NULL, block); + if (file == NULL) + return (ARCHIVE_FATAL); + iso9660->seenJoliet = seenJoliet; + } + /* Store the root directory in the pending list. */ + if (add_entry(a, iso9660, file) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if (iso9660->seenRockridge) { + a->archive.archive_format = + ARCHIVE_FORMAT_ISO9660_ROCKRIDGE; + a->archive.archive_format_name = + "ISO9660 with Rockridge extensions"; + } + } + + file = NULL;/* Eliminate a warning. */ + /* Get the next entry that appears after the current offset. */ + r = next_entry_seek(a, iso9660, &file); + if (r != ARCHIVE_OK) + return (r); + + if (iso9660->seenJoliet) { + /* + * Convert UTF-16BE of a filename to local locale MBS + * and store the result into a filename field. + */ + if (iso9660->sconv_utf16be == NULL) { + iso9660->sconv_utf16be = + archive_string_conversion_from_charset( + &(a->archive), "UTF-16BE", 1); + if (iso9660->sconv_utf16be == NULL) + /* Coundn't allocate memory */ + return (ARCHIVE_FATAL); + } + if (iso9660->utf16be_path == NULL) { + iso9660->utf16be_path = malloc(UTF16_NAME_MAX); + if (iso9660->utf16be_path == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory"); + return (ARCHIVE_FATAL); + } + } + if (iso9660->utf16be_previous_path == NULL) { + iso9660->utf16be_previous_path = malloc(UTF16_NAME_MAX); + if (iso9660->utf16be_previous_path == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory"); + return (ARCHIVE_FATAL); + } + } + + iso9660->utf16be_path_len = 0; + if (build_pathname_utf16be(iso9660->utf16be_path, + UTF16_NAME_MAX, &(iso9660->utf16be_path_len), file) != 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname is too long"); + } + + r = archive_entry_copy_pathname_l(entry, + (const char *)iso9660->utf16be_path, + iso9660->utf16be_path_len, + iso9660->sconv_utf16be); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "No memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name( + iso9660->sconv_utf16be)); + + rd_r = ARCHIVE_WARN; + } + } else { + archive_string_empty(&iso9660->pathname); + archive_entry_set_pathname(entry, + build_pathname(&iso9660->pathname, file)); + } + + iso9660->entry_bytes_remaining = file->size; + iso9660->entry_sparse_offset = 0; /* Offset for sparse-file-aware clients. */ + + if (file->offset + file->size > iso9660->volume_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File is beyond end-of-media: %s", + archive_entry_pathname(entry)); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + return (ARCHIVE_WARN); + } + + /* Set up the entry structure with information about this entry. */ + archive_entry_set_mode(entry, file->mode); + archive_entry_set_uid(entry, file->uid); + archive_entry_set_gid(entry, file->gid); + archive_entry_set_nlink(entry, file->nlinks); + if (file->birthtime_is_set) + archive_entry_set_birthtime(entry, file->birthtime, 0); + else + archive_entry_unset_birthtime(entry); + archive_entry_set_mtime(entry, file->mtime, 0); + archive_entry_set_ctime(entry, file->ctime, 0); + archive_entry_set_atime(entry, file->atime, 0); + /* N.B.: Rock Ridge supports 64-bit device numbers. */ + archive_entry_set_rdev(entry, (dev_t)file->rdev); + archive_entry_set_size(entry, iso9660->entry_bytes_remaining); + if (file->symlink.s != NULL) + archive_entry_copy_symlink(entry, file->symlink.s); + + /* Note: If the input isn't seekable, we can't rewind to + * return the same body again, so if the next entry refers to + * the same data, we have to return it as a hardlink to the + * original entry. */ + if (file->number != -1 && + file->number == iso9660->previous_number) { + if (iso9660->seenJoliet) { + r = archive_entry_copy_hardlink_l(entry, + (const char *)iso9660->utf16be_previous_path, + iso9660->utf16be_previous_path_len, + iso9660->sconv_utf16be); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "No memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Linkname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name( + iso9660->sconv_utf16be)); + rd_r = ARCHIVE_WARN; + } + } else + archive_entry_set_hardlink(entry, + iso9660->previous_pathname.s); + archive_entry_unset_size(entry); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + return (rd_r); + } + + /* Except for the hardlink case above, if the offset of the + * next entry is before our current position, we can't seek + * backwards to extract it, so issue a warning. Note that + * this can only happen if this entry was added to the heap + * after we passed this offset, that is, only if the directory + * mentioning this entry is later than the body of the entry. + * Such layouts are very unusual; most ISO9660 writers lay out + * and record all directory information first, then store + * all file bodies. */ + /* TODO: Someday, libarchive's I/O core will support optional + * seeking. When that day comes, this code should attempt to + * seek and only return the error if the seek fails. That + * will give us support for whacky ISO images that require + * seeking while retaining the ability to read almost all ISO + * images in a streaming fashion. */ + if ((file->mode & AE_IFMT) != AE_IFDIR && + file->offset < iso9660->current_position) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring out-of-order file @%jx (%s) %jd < %jd", + (intmax_t)file->number, + iso9660->pathname.s, + (intmax_t)file->offset, + (intmax_t)iso9660->current_position); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + return (ARCHIVE_WARN); + } + + /* Initialize zisofs variables. */ + iso9660->entry_zisofs.pz = file->pz; + if (file->pz) { +#ifdef HAVE_ZLIB_H + struct zisofs *zisofs; + + zisofs = &iso9660->entry_zisofs; + zisofs->initialized = 0; + zisofs->pz_log2_bs = file->pz_log2_bs; + zisofs->pz_uncompressed_size = file->pz_uncompressed_size; + zisofs->pz_offset = 0; + zisofs->header_avail = 0; + zisofs->header_passed = 0; + zisofs->block_pointers_avail = 0; +#endif + archive_entry_set_size(entry, file->pz_uncompressed_size); + } + + iso9660->previous_number = file->number; + if (iso9660->seenJoliet) { + memcpy(iso9660->utf16be_previous_path, iso9660->utf16be_path, + iso9660->utf16be_path_len); + iso9660->utf16be_previous_path_len = iso9660->utf16be_path_len; + } else + archive_strcpy( + &iso9660->previous_pathname, iso9660->pathname.s); + + /* Reset entry_bytes_remaining if the file is multi extent. */ + iso9660->entry_content = file->contents.first; + if (iso9660->entry_content != NULL) + iso9660->entry_bytes_remaining = iso9660->entry_content->size; + + if (archive_entry_filetype(entry) == AE_IFDIR) { + /* Overwrite nlinks by proper link number which is + * calculated from number of sub directories. */ + archive_entry_set_nlink(entry, 2 + file->subdirs); + /* Directory data has been read completely. */ + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + } + + if (rd_r != ARCHIVE_OK) + return (rd_r); + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_read_data_skip(struct archive_read *a) +{ + /* Because read_next_header always does an explicit skip + * to the next entry, we don't need to do anything here. */ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +#ifdef HAVE_ZLIB_H + +static int +zisofs_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct iso9660 *iso9660; + struct zisofs *zisofs; + const unsigned char *p; + size_t avail; + ssize_t bytes_read; + size_t uncompressed_size; + int r; + + iso9660 = (struct iso9660 *)(a->format->data); + zisofs = &iso9660->entry_zisofs; + + p = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated zisofs file body"); + return (ARCHIVE_FATAL); + } + if (bytes_read > iso9660->entry_bytes_remaining) + bytes_read = iso9660->entry_bytes_remaining; + avail = bytes_read; + uncompressed_size = 0; + + if (!zisofs->initialized) { + size_t ceil, xsize; + + /* Allocate block pointers buffer. */ + ceil = (zisofs->pz_uncompressed_size + + (1LL << zisofs->pz_log2_bs) - 1) + >> zisofs->pz_log2_bs; + xsize = (ceil + 1) * 4; + if (zisofs->block_pointers_alloc < xsize) { + size_t alloc; + + if (zisofs->block_pointers != NULL) + free(zisofs->block_pointers); + alloc = ((xsize >> 10) + 1) << 10; + zisofs->block_pointers = malloc(alloc); + if (zisofs->block_pointers == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for zisofs decompression"); + return (ARCHIVE_FATAL); + } + zisofs->block_pointers_alloc = alloc; + } + zisofs->block_pointers_size = xsize; + + /* Allocate uncompressed data buffer. */ + xsize = 1UL << zisofs->pz_log2_bs; + if (zisofs->uncompressed_buffer_size < xsize) { + if (zisofs->uncompressed_buffer != NULL) + free(zisofs->uncompressed_buffer); + zisofs->uncompressed_buffer = malloc(xsize); + if (zisofs->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for zisofs decompression"); + return (ARCHIVE_FATAL); + } + } + zisofs->uncompressed_buffer_size = xsize; + + /* + * Read the file header, and check the magic code of zisofs. + */ + if (zisofs->header_avail < sizeof(zisofs->header)) { + xsize = sizeof(zisofs->header) - zisofs->header_avail; + if (avail < xsize) + xsize = avail; + memcpy(zisofs->header + zisofs->header_avail, p, xsize); + zisofs->header_avail += xsize; + avail -= xsize; + p += xsize; + } + if (!zisofs->header_passed && + zisofs->header_avail == sizeof(zisofs->header)) { + int err = 0; + + if (memcmp(zisofs->header, zisofs_magic, + sizeof(zisofs_magic)) != 0) + err = 1; + if (archive_le32dec(zisofs->header + 8) + != zisofs->pz_uncompressed_size) + err = 1; + if (zisofs->header[12] != 4) + err = 1; + if (zisofs->header[13] != zisofs->pz_log2_bs) + err = 1; + if (err) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs file body"); + return (ARCHIVE_FATAL); + } + zisofs->header_passed = 1; + } + /* + * Read block pointers. + */ + if (zisofs->header_passed && + zisofs->block_pointers_avail < zisofs->block_pointers_size) { + xsize = zisofs->block_pointers_size + - zisofs->block_pointers_avail; + if (avail < xsize) + xsize = avail; + memcpy(zisofs->block_pointers + + zisofs->block_pointers_avail, p, xsize); + zisofs->block_pointers_avail += xsize; + avail -= xsize; + p += xsize; + if (zisofs->block_pointers_avail + == zisofs->block_pointers_size) { + /* We've got all block pointers and initialize + * related variables. */ + zisofs->block_off = 0; + zisofs->block_avail = 0; + /* Complete a initialization */ + zisofs->initialized = 1; + } + } + + if (!zisofs->initialized) + goto next_data; /* We need more data. */ + } + + /* + * Get block offsets from block pointers. + */ + if (zisofs->block_avail == 0) { + uint32_t bst, bed; + + if (zisofs->block_off + 4 >= zisofs->block_pointers_size) { + /* There isn't a pair of offsets. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers"); + return (ARCHIVE_FATAL); + } + bst = archive_le32dec( + zisofs->block_pointers + zisofs->block_off); + if (bst != zisofs->pz_offset + (bytes_read - avail)) { + /* TODO: Should we seek offset of current file + * by bst ? */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers(cannot seek)"); + return (ARCHIVE_FATAL); + } + bed = archive_le32dec( + zisofs->block_pointers + zisofs->block_off + 4); + if (bed < bst) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers"); + return (ARCHIVE_FATAL); + } + zisofs->block_avail = bed - bst; + zisofs->block_off += 4; + + /* Initialize compression library for new block. */ + if (zisofs->stream_valid) + r = inflateReset(&zisofs->stream); + else + r = inflateInit(&zisofs->stream); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize zisofs decompression."); + return (ARCHIVE_FATAL); + } + zisofs->stream_valid = 1; + zisofs->stream.total_in = 0; + zisofs->stream.total_out = 0; + } + + /* + * Make uncompressed data. + */ + if (zisofs->block_avail == 0) { + memset(zisofs->uncompressed_buffer, 0, + zisofs->uncompressed_buffer_size); + uncompressed_size = zisofs->uncompressed_buffer_size; + } else { + zisofs->stream.next_in = (Bytef *)(uintptr_t)(const void *)p; + if (avail > zisofs->block_avail) + zisofs->stream.avail_in = zisofs->block_avail; + else + zisofs->stream.avail_in = avail; + zisofs->stream.next_out = zisofs->uncompressed_buffer; + zisofs->stream.avail_out = zisofs->uncompressed_buffer_size; + + r = inflate(&zisofs->stream, 0); + switch (r) { + case Z_OK: /* Decompressor made some progress.*/ + case Z_STREAM_END: /* Found end of stream. */ + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "zisofs decompression failed (%d)", r); + return (ARCHIVE_FATAL); + } + uncompressed_size = + zisofs->uncompressed_buffer_size - zisofs->stream.avail_out; + avail -= zisofs->stream.next_in - p; + zisofs->block_avail -= zisofs->stream.next_in - p; + } +next_data: + bytes_read -= avail; + *buff = zisofs->uncompressed_buffer; + *size = uncompressed_size; + *offset = iso9660->entry_sparse_offset; + iso9660->entry_sparse_offset += uncompressed_size; + iso9660->entry_bytes_remaining -= bytes_read; + iso9660->current_position += bytes_read; + zisofs->pz_offset += bytes_read; + iso9660->entry_bytes_unconsumed += bytes_read; + + return (ARCHIVE_OK); +} + +#else /* HAVE_ZLIB_H */ + +static int +zisofs_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + + (void)buff;/* UNUSED */ + (void)size;/* UNUSED */ + (void)offset;/* UNUSED */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "zisofs is not supported on this platform."); + return (ARCHIVE_FAILED); +} + +#endif /* HAVE_ZLIB_H */ + +static int +archive_read_format_iso9660_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + ssize_t bytes_read; + struct iso9660 *iso9660; + + iso9660 = (struct iso9660 *)(a->format->data); + + if (iso9660->entry_bytes_unconsumed) { + __archive_read_consume(a, iso9660->entry_bytes_unconsumed); + iso9660->entry_bytes_unconsumed = 0; + } + + if (iso9660->entry_bytes_remaining <= 0) { + if (iso9660->entry_content != NULL) + iso9660->entry_content = iso9660->entry_content->next; + if (iso9660->entry_content == NULL) { + *buff = NULL; + *size = 0; + *offset = iso9660->entry_sparse_offset; + return (ARCHIVE_EOF); + } + /* Seek forward to the start of the entry. */ + if (iso9660->current_position < iso9660->entry_content->offset) { + int64_t step; + + step = iso9660->entry_content->offset - + iso9660->current_position; + step = __archive_read_consume(a, step); + if (step < 0) + return ((int)step); + iso9660->current_position = + iso9660->entry_content->offset; + } + if (iso9660->entry_content->offset < iso9660->current_position) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring out-of-order file (%s) %jd < %jd", + iso9660->pathname.s, + (intmax_t)iso9660->entry_content->offset, + (intmax_t)iso9660->current_position); + *buff = NULL; + *size = 0; + *offset = iso9660->entry_sparse_offset; + return (ARCHIVE_WARN); + } + iso9660->entry_bytes_remaining = iso9660->entry_content->size; + } + if (iso9660->entry_zisofs.pz) + return (zisofs_read_data(a, buff, size, offset)); + + *buff = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated input file"); + if (*buff == NULL) + return (ARCHIVE_FATAL); + if (bytes_read > iso9660->entry_bytes_remaining) + bytes_read = iso9660->entry_bytes_remaining; + *size = bytes_read; + *offset = iso9660->entry_sparse_offset; + iso9660->entry_sparse_offset += bytes_read; + iso9660->entry_bytes_remaining -= bytes_read; + iso9660->entry_bytes_unconsumed = bytes_read; + iso9660->current_position += bytes_read; + return (ARCHIVE_OK); +} + +static int +archive_read_format_iso9660_cleanup(struct archive_read *a) +{ + struct iso9660 *iso9660; + int r = ARCHIVE_OK; + + iso9660 = (struct iso9660 *)(a->format->data); + release_files(iso9660); + free(iso9660->read_ce_req.reqs); + archive_string_free(&iso9660->pathname); + archive_string_free(&iso9660->previous_pathname); + if (iso9660->pending_files.files) + free(iso9660->pending_files.files); +#ifdef HAVE_ZLIB_H + free(iso9660->entry_zisofs.uncompressed_buffer); + free(iso9660->entry_zisofs.block_pointers); + if (iso9660->entry_zisofs.stream_valid) { + if (inflateEnd(&iso9660->entry_zisofs.stream) != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up zlib decompressor"); + r = ARCHIVE_FATAL; + } + } +#endif + free(iso9660->utf16be_path); + free(iso9660->utf16be_previous_path); + free(iso9660); + (a->format->data) = NULL; + return (r); +} + +/* + * This routine parses a single ISO directory record, makes sense + * of any extensions, and stores the result in memory. + */ +static struct file_info * +parse_file_info(struct archive_read *a, struct file_info *parent, + const unsigned char *isodirrec) +{ + struct iso9660 *iso9660; + struct file_info *file; + size_t name_len; + const unsigned char *rr_start, *rr_end; + const unsigned char *p; + size_t dr_len; + uint64_t fsize; + int32_t location; + int flags; + + iso9660 = (struct iso9660 *)(a->format->data); + + dr_len = (size_t)isodirrec[DR_length_offset]; + name_len = (size_t)isodirrec[DR_name_len_offset]; + location = archive_le32dec(isodirrec + DR_extent_offset); + fsize = toi(isodirrec + DR_size_offset, DR_size_size); + /* Sanity check that dr_len needs at least 34. */ + if (dr_len < 34) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid length of directory record"); + return (NULL); + } + /* Sanity check that name_len doesn't exceed dr_len. */ + if (dr_len - 33 < name_len || name_len == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid length of file identifier"); + return (NULL); + } + /* Sanity check that location doesn't exceed volume block. + * Don't check lower limit of location; it's possibility + * the location has negative value when file type is symbolic + * link or file size is zero. As far as I know latest mkisofs + * do that. + */ + if (location > 0 && + (location + ((fsize + iso9660->logical_block_size -1) + / iso9660->logical_block_size)) > iso9660->volume_block) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid location of extent of file"); + return (NULL); + } + /* Sanity check that location doesn't have a negative value + * when the file is not empty. it's too large. */ + if (fsize != 0 && location < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid location of extent of file"); + return (NULL); + } + + /* Create a new file entry and copy data from the ISO dir record. */ + file = (struct file_info *)calloc(1, sizeof(*file)); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for file entry"); + return (NULL); + } + file->parent = parent; + file->offset = iso9660->logical_block_size * (uint64_t)location; + file->size = fsize; + file->mtime = isodate7(isodirrec + DR_date_offset); + file->ctime = file->atime = file->mtime; + file->rede_files.first = NULL; + file->rede_files.last = &(file->rede_files.first); + + p = isodirrec + DR_name_offset; + /* Rockridge extensions (if any) follow name. Compute this + * before fidgeting the name_len below. */ + rr_start = p + name_len + (name_len & 1 ? 0 : 1); + rr_end = isodirrec + dr_len; + + if (iso9660->seenJoliet) { + /* Joliet names are max 64 chars (128 bytes) according to spec, + * but genisoimage/mkisofs allows recording longer Joliet + * names which are 103 UCS2 characters(206 bytes) by their + * option '-joliet-long'. + */ + if (name_len > 206) + name_len = 206; + name_len &= ~1; + + /* trim trailing first version and dot from filename. + * + * Remember we were in UTF-16BE land! + * SEPARATOR 1 (.) and SEPARATOR 2 (;) are both + * 16 bits big endian characters on Joliet. + * + * TODO: sanitize filename? + * Joliet allows any UCS-2 char except: + * *, /, :, ;, ? and \. + */ + /* Chop off trailing ';1' from files. */ + if (name_len > 4 && p[name_len-4] == 0 && p[name_len-3] == ';' + && p[name_len-2] == 0 && p[name_len-1] == '1') + name_len -= 4; +#if 0 /* XXX: this somehow manages to strip of single-character file extensions, like '.c'. */ + /* Chop off trailing '.' from filenames. */ + if (name_len > 2 && p[name_len-2] == 0 && p[name_len-1] == '.') + name_len -= 2; +#endif + if ((file->utf16be_name = malloc(name_len)) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for file name"); + return (NULL); + } + memcpy(file->utf16be_name, p, name_len); + file->utf16be_bytes = name_len; + } else { + /* Chop off trailing ';1' from files. */ + if (name_len > 2 && p[name_len - 2] == ';' && + p[name_len - 1] == '1') + name_len -= 2; + /* Chop off trailing '.' from filenames. */ + if (name_len > 1 && p[name_len - 1] == '.') + --name_len; + + archive_strncpy(&file->name, (const char *)p, name_len); + } + + flags = isodirrec[DR_flags_offset]; + if (flags & 0x02) + file->mode = AE_IFDIR | 0700; + else + file->mode = AE_IFREG | 0400; + if (flags & 0x80) + file->multi_extent = 1; + else + file->multi_extent = 0; + /* + * Use a location for the file number, which is treated as an inode + * number to find out hardlink target. If Rockridge extensions is + * being used, the file number will be overwritten by FILE SERIAL + * NUMBER of RRIP "PX" extension. + * Note: Old mkisofs did not record that FILE SERIAL NUMBER + * in ISO images. + * Note2: xorriso set 0 to the location of a symlink file. + */ + if (file->size == 0 && location >= 0) { + /* If file->size is zero, its location points wrong place, + * and so we should not use it for the file number. + * When the location has negative value, it can be used + * for the file number. + */ + file->number = -1; + /* Do not appear before any directory entries. */ + file->offset = -1; + } else + file->number = (int64_t)(uint32_t)location; + + /* Rockridge extensions overwrite information from above. */ + if (iso9660->opt_support_rockridge) { + if (parent == NULL && rr_end - rr_start >= 7) { + p = rr_start; + if (p[0] == 'S' && p[1] == 'P' + && p[2] == 7 && p[3] == 1 + && p[4] == 0xBE && p[5] == 0xEF) { + /* + * SP extension stores the suspOffset + * (Number of bytes to skip between + * filename and SUSP records.) + * It is mandatory by the SUSP standard + * (IEEE 1281). + * + * It allows SUSP to coexist with + * non-SUSP uses of the System + * Use Area by placing non-SUSP data + * before SUSP data. + * + * SP extension must be in the root + * directory entry, disable all SUSP + * processing if not found. + */ + iso9660->suspOffset = p[6]; + iso9660->seenSUSP = 1; + rr_start += 7; + } + } + if (iso9660->seenSUSP) { + int r; + + file->name_continues = 0; + file->symlink_continues = 0; + rr_start += iso9660->suspOffset; + r = parse_rockridge(a, file, rr_start, rr_end); + if (r != ARCHIVE_OK) { + free(file); + return (NULL); + } + } else + /* If there isn't SUSP, disable parsing + * rock ridge extensions. */ + iso9660->opt_support_rockridge = 0; + } + + file->nlinks = 1;/* Reset nlink. we'll calculate it later. */ + /* Tell file's parent how many children that parent has. */ + if (parent != NULL && (flags & 0x02)) + parent->subdirs++; + + if (iso9660->seenRockridge) { + if (parent != NULL && parent->parent == NULL && + (flags & 0x02) && iso9660->rr_moved == NULL && + (strcmp(file->name.s, "rr_moved") == 0 || + strcmp(file->name.s, ".rr_moved") == 0)) { + iso9660->rr_moved = file; + file->rr_moved = 1; + file->rr_moved_has_re_only = 1; + file->re = 0; + parent->subdirs--; + } else if (file->re) { + /* + * Sanity check: file's parent is rr_moved. + */ + if (parent == NULL || parent->rr_moved == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge RE"); + return (NULL); + } + /* + * Sanity check: file does not have "CL" extension. + */ + if (file->cl_offset) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge RE and CL"); + return (NULL); + } + /* + * Sanity check: The file type must be a directory. + */ + if ((flags & 0x02) == 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge RE"); + return (NULL); + } + } else if (parent != NULL && parent->rr_moved) + file->rr_moved_has_re_only = 0; + else if (parent != NULL && (flags & 0x02) && + (parent->re || parent->re_descendant)) + file->re_descendant = 1; + if (file->cl_offset) { + struct file_info *p; + + if (parent == NULL || parent->parent == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge CL"); + return (NULL); + } + /* + * Sanity check: The file type must be a regular file. + */ + if ((flags & 0x02) != 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge CL"); + return (NULL); + } + parent->subdirs++; + /* Overwrite an offset and a number of this "CL" entry + * to appear before other dirs. "+1" to those is to + * make sure to appear after "RE" entry which this + * "CL" entry should be connected with. */ + file->offset = file->number = file->cl_offset + 1; + + /* + * Sanity check: cl_offset does not point at its + * the parents or itself. + */ + for (p = parent; p; p = p->parent) { + if (p->offset == file->cl_offset) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge CL"); + return (NULL); + } + } + if (file->cl_offset == file->offset || + parent->rr_moved) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid Rockridge CL"); + return (NULL); + } + } + } + +#if DEBUG + /* DEBUGGING: Warn about attributes I don't yet fully support. */ + if ((flags & ~0x02) != 0) { + fprintf(stderr, "\n ** Unrecognized flag: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (toi(isodirrec + DR_volume_sequence_number_offset, 2) != 1) { + fprintf(stderr, "\n ** Unrecognized sequence number: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_file_unit_size_offset) != 0) { + fprintf(stderr, "\n ** Unexpected file unit size: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_interleave_offset) != 0) { + fprintf(stderr, "\n ** Unexpected interleave: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } else if (*(isodirrec + DR_ext_attr_length_offset) != 0) { + fprintf(stderr, "\n ** Unexpected extended attribute length: "); + dump_isodirrec(stderr, isodirrec); + fprintf(stderr, "\n"); + } +#endif + register_file(iso9660, file); + return (file); +} + +static int +parse_rockridge(struct archive_read *a, struct file_info *file, + const unsigned char *p, const unsigned char *end) +{ + struct iso9660 *iso9660; + + iso9660 = (struct iso9660 *)(a->format->data); + + while (p + 4 <= end /* Enough space for another entry. */ + && p[0] >= 'A' && p[0] <= 'Z' /* Sanity-check 1st char of name. */ + && p[1] >= 'A' && p[1] <= 'Z' /* Sanity-check 2nd char of name. */ + && p[2] >= 4 /* Sanity-check length. */ + && p + p[2] <= end) { /* Sanity-check length. */ + const unsigned char *data = p + 4; + int data_length = p[2] - 4; + int version = p[3]; + + /* + * Yes, each 'if' here does test p[0] again. + * Otherwise, the fall-through handling to catch + * unsupported extensions doesn't work. + */ + switch(p[0]) { + case 'C': + if (p[0] == 'C' && p[1] == 'E') { + if (version == 1 && data_length == 24) { + /* + * CE extension comprises: + * 8 byte sector containing extension + * 8 byte offset w/in above sector + * 8 byte length of continuation + */ + int32_t location = + archive_le32dec(data); + file->ce_offset = + archive_le32dec(data+8); + file->ce_size = + archive_le32dec(data+16); + if (register_CE(a, location, file) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + break; + } + if (p[0] == 'C' && p[1] == 'L') { + if (version == 1 && data_length == 8) { + file->cl_offset = (uint64_t) + iso9660->logical_block_size * + (uint64_t)archive_le32dec(data); + iso9660->seenRockridge = 1; + } + break; + } + /* FALLTHROUGH */ + case 'N': + if (p[0] == 'N' && p[1] == 'M') { + if (version == 1) { + parse_rockridge_NM1(file, + data, data_length); + iso9660->seenRockridge = 1; + } + break; + } + /* FALLTHROUGH */ + case 'P': + if (p[0] == 'P' && p[1] == 'D') { + /* + * PD extension is padding; + * contents are always ignored. + */ + break; + } + if (p[0] == 'P' && p[1] == 'L') { + /* + * PL extension won't appear; + * contents are always ignored. + */ + break; + } + if (p[0] == 'P' && p[1] == 'N') { + if (version == 1 && data_length == 16) { + file->rdev = toi(data,4); + file->rdev <<= 32; + file->rdev |= toi(data + 8, 4); + iso9660->seenRockridge = 1; + } + break; + } + if (p[0] == 'P' && p[1] == 'X') { + /* + * PX extension comprises: + * 8 bytes for mode, + * 8 bytes for nlinks, + * 8 bytes for uid, + * 8 bytes for gid, + * 8 bytes for inode. + */ + if (version == 1) { + if (data_length >= 8) + file->mode + = toi(data, 4); + if (data_length >= 16) + file->nlinks + = toi(data + 8, 4); + if (data_length >= 24) + file->uid + = toi(data + 16, 4); + if (data_length >= 32) + file->gid + = toi(data + 24, 4); + if (data_length >= 40) + file->number + = toi(data + 32, 4); + iso9660->seenRockridge = 1; + } + break; + } + /* FALLTHROUGH */ + case 'R': + if (p[0] == 'R' && p[1] == 'E' && version == 1) { + file->re = 1; + iso9660->seenRockridge = 1; + break; + } + if (p[0] == 'R' && p[1] == 'R' && version == 1) { + /* + * RR extension comprises: + * one byte flag value + * This extension is obsolete, + * so contents are always ignored. + */ + break; + } + /* FALLTHROUGH */ + case 'S': + if (p[0] == 'S' && p[1] == 'L') { + if (version == 1) { + parse_rockridge_SL1(file, + data, data_length); + iso9660->seenRockridge = 1; + } + break; + } + if (p[0] == 'S' && p[1] == 'T' + && data_length == 0 && version == 1) { + /* + * ST extension marks end of this + * block of SUSP entries. + * + * It allows SUSP to coexist with + * non-SUSP uses of the System + * Use Area by placing non-SUSP data + * after SUSP data. + */ + iso9660->seenSUSP = 0; + iso9660->seenRockridge = 0; + return (ARCHIVE_OK); + } + case 'T': + if (p[0] == 'T' && p[1] == 'F') { + if (version == 1) { + parse_rockridge_TF1(file, + data, data_length); + iso9660->seenRockridge = 1; + } + break; + } + /* FALLTHROUGH */ + case 'Z': + if (p[0] == 'Z' && p[1] == 'F') { + if (version == 1) + parse_rockridge_ZF1(file, + data, data_length); + break; + } + /* FALLTHROUGH */ + default: + /* The FALLTHROUGHs above leave us here for + * any unsupported extension. */ + break; + } + + + + p += p[2]; + } + return (ARCHIVE_OK); +} + +static int +register_CE(struct archive_read *a, int32_t location, + struct file_info *file) +{ + struct iso9660 *iso9660; + struct read_ce_queue *heap; + struct read_ce_req *p; + uint64_t offset, parent_offset; + int hole, parent; + + iso9660 = (struct iso9660 *)(a->format->data); + offset = ((uint64_t)location) * (uint64_t)iso9660->logical_block_size; + if (((file->mode & AE_IFMT) == AE_IFREG && + offset >= file->offset) || + offset < iso9660->current_position || + (((uint64_t)file->ce_offset) + file->ce_size) + > iso9660->logical_block_size || + offset + file->ce_offset + file->ce_size + > iso9660->volume_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid parameter in SUSP \"CE\" extension"); + return (ARCHIVE_FATAL); + } + + /* Expand our CE list as necessary. */ + heap = &(iso9660->read_ce_req); + if (heap->cnt >= heap->allocated) { + int new_size; + + if (heap->allocated < 16) + new_size = 16; + else + new_size = heap->allocated * 2; + /* Overflow might keep us from growing the list. */ + if (new_size <= heap->allocated) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + p = malloc(new_size * sizeof(p[0])); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + if (heap->reqs != NULL) { + memcpy(p, heap->reqs, heap->cnt * sizeof(*p)); + free(heap->reqs); + } + heap->reqs = p; + heap->allocated = new_size; + } + + /* + * Start with hole at end, walk it up tree to find insertion point. + */ + hole = heap->cnt++; + while (hole > 0) { + parent = (hole - 1)/2; + parent_offset = heap->reqs[parent].offset; + if (offset >= parent_offset) { + heap->reqs[hole].offset = offset; + heap->reqs[hole].file = file; + return (ARCHIVE_OK); + } + /* Move parent into hole <==> move hole up tree. */ + heap->reqs[hole] = heap->reqs[parent]; + hole = parent; + } + heap->reqs[0].offset = offset; + heap->reqs[0].file = file; + return (ARCHIVE_OK); +} + +static void +next_CE(struct read_ce_queue *heap) +{ + uint64_t a_offset, b_offset, c_offset; + int a, b, c; + struct read_ce_req tmp; + + if (heap->cnt < 1) + return; + + /* + * Move the last item in the heap to the root of the tree + */ + heap->reqs[0] = heap->reqs[--(heap->cnt)]; + + /* + * Rebalance the heap. + */ + a = 0; /* Starting element and its offset */ + a_offset = heap->reqs[a].offset; + for (;;) { + b = a + a + 1; /* First child */ + if (b >= heap->cnt) + return; + b_offset = heap->reqs[b].offset; + c = b + 1; /* Use second child if it is smaller. */ + if (c < heap->cnt) { + c_offset = heap->reqs[c].offset; + if (c_offset < b_offset) { + b = c; + b_offset = c_offset; + } + } + if (a_offset <= b_offset) + return; + tmp = heap->reqs[a]; + heap->reqs[a] = heap->reqs[b]; + heap->reqs[b] = tmp; + a = b; + } +} + + +static int +read_CE(struct archive_read *a, struct iso9660 *iso9660) +{ + struct read_ce_queue *heap; + const unsigned char *b, *p, *end; + struct file_info *file; + size_t step; + int r; + + /* Read data which RRIP "CE" extension points. */ + heap = &(iso9660->read_ce_req); + step = iso9660->logical_block_size; + while (heap->cnt && + heap->reqs[0].offset == iso9660->current_position) { + b = __archive_read_ahead(a, step, NULL); + if (b == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to read full block when scanning " + "ISO9660 directory list"); + return (ARCHIVE_FATAL); + } + do { + file = heap->reqs[0].file; + if (file->ce_offset + file->ce_size > step) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Malformed CE information"); + return (ARCHIVE_FATAL); + } + p = b + file->ce_offset; + end = p + file->ce_size; + next_CE(heap); + r = parse_rockridge(a, file, p, end); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } while (heap->cnt && + heap->reqs[0].offset == iso9660->current_position); + /* NOTE: Do not move this consume's code to fron of + * do-while loop. Registration of nested CE extension + * might cause error because of current position. */ + __archive_read_consume(a, step); + iso9660->current_position += step; + } + return (ARCHIVE_OK); +} + +static void +parse_rockridge_NM1(struct file_info *file, + const unsigned char *data, int data_length) +{ + if (!file->name_continues) + archive_string_empty(&file->name); + file->name_continues = 0; + if (data_length < 1) + return; + /* + * NM version 1 extension comprises: + * 1 byte flag, value is one of: + * = 0: remainder is name + * = 1: remainder is name, next NM entry continues name + * = 2: "." + * = 4: ".." + * = 32: Implementation specific + * All other values are reserved. + */ + switch(data[0]) { + case 0: + if (data_length < 2) + return; + archive_strncat(&file->name, + (const char *)data + 1, data_length - 1); + break; + case 1: + if (data_length < 2) + return; + archive_strncat(&file->name, + (const char *)data + 1, data_length - 1); + file->name_continues = 1; + break; + case 2: + archive_strcat(&file->name, "."); + break; + case 4: + archive_strcat(&file->name, ".."); + break; + default: + return; + } + +} + +static void +parse_rockridge_TF1(struct file_info *file, const unsigned char *data, + int data_length) +{ + char flag; + /* + * TF extension comprises: + * one byte flag + * create time (optional) + * modify time (optional) + * access time (optional) + * attribute time (optional) + * Time format and presence of fields + * is controlled by flag bits. + */ + if (data_length < 1) + return; + flag = data[0]; + ++data; + --data_length; + if (flag & 0x80) { + /* Use 17-byte time format. */ + if ((flag & 1) && data_length >= 17) { + /* Create time. */ + file->birthtime_is_set = 1; + file->birthtime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 2) && data_length >= 17) { + /* Modify time. */ + file->mtime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 4) && data_length >= 17) { + /* Access time. */ + file->atime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 8) && data_length >= 17) { + /* Attribute change time. */ + file->ctime = isodate17(data); + } + } else { + /* Use 7-byte time format. */ + if ((flag & 1) && data_length >= 7) { + /* Create time. */ + file->birthtime_is_set = 1; + file->birthtime = isodate7(data); + data += 7; + data_length -= 7; + } + if ((flag & 2) && data_length >= 7) { + /* Modify time. */ + file->mtime = isodate7(data); + data += 7; + data_length -= 7; + } + if ((flag & 4) && data_length >= 7) { + /* Access time. */ + file->atime = isodate7(data); + data += 7; + data_length -= 7; + } + if ((flag & 8) && data_length >= 7) { + /* Attribute change time. */ + file->ctime = isodate7(data); + } + } +} + +static void +parse_rockridge_SL1(struct file_info *file, const unsigned char *data, + int data_length) +{ + const char *separator = ""; + + if (!file->symlink_continues || file->symlink.length < 1) + archive_string_empty(&file->symlink); + else if (!file->symlink_continues && + file->symlink.s[file->symlink.length - 1] != '/') + separator = "/"; + file->symlink_continues = 0; + + /* + * Defined flag values: + * 0: This is the last SL record for this symbolic link + * 1: this symbolic link field continues in next SL entry + * All other values are reserved. + */ + if (data_length < 1) + return; + switch(*data) { + case 0: + break; + case 1: + file->symlink_continues = 1; + break; + default: + return; + } + ++data; /* Skip flag byte. */ + --data_length; + + /* + * SL extension body stores "components". + * Basically, this is a complicated way of storing + * a POSIX path. It also interferes with using + * symlinks for storing non-path data. + * + * Each component is 2 bytes (flag and length) + * possibly followed by name data. + */ + while (data_length >= 2) { + unsigned char flag = *data++; + unsigned char nlen = *data++; + data_length -= 2; + + archive_strcat(&file->symlink, separator); + separator = "/"; + + switch(flag) { + case 0: /* Usual case, this is text. */ + if (data_length < nlen) + return; + archive_strncat(&file->symlink, + (const char *)data, nlen); + break; + case 0x01: /* Text continues in next component. */ + if (data_length < nlen) + return; + archive_strncat(&file->symlink, + (const char *)data, nlen); + separator = ""; + break; + case 0x02: /* Current dir. */ + archive_strcat(&file->symlink, "."); + break; + case 0x04: /* Parent dir. */ + archive_strcat(&file->symlink, ".."); + break; + case 0x08: /* Root of filesystem. */ + archive_strcat(&file->symlink, "/"); + separator = ""; + break; + case 0x10: /* Undefined (historically "volume root" */ + archive_string_empty(&file->symlink); + archive_strcat(&file->symlink, "ROOT"); + break; + case 0x20: /* Undefined (historically "hostname") */ + archive_strcat(&file->symlink, "hostname"); + break; + default: + /* TODO: issue a warning ? */ + return; + } + data += nlen; + data_length -= nlen; + } +} + +static void +parse_rockridge_ZF1(struct file_info *file, const unsigned char *data, + int data_length) +{ + + if (data[0] == 0x70 && data[1] == 0x7a && data_length == 12) { + /* paged zlib */ + file->pz = 1; + file->pz_log2_bs = data[3]; + file->pz_uncompressed_size = archive_le32dec(&data[4]); + } +} + +static void +register_file(struct iso9660 *iso9660, struct file_info *file) +{ + + file->use_next = iso9660->use_files; + iso9660->use_files = file; +} + +static void +release_files(struct iso9660 *iso9660) +{ + struct content *con, *connext; + struct file_info *file; + + file = iso9660->use_files; + while (file != NULL) { + struct file_info *next = file->use_next; + + archive_string_free(&file->name); + archive_string_free(&file->symlink); + free(file->utf16be_name); + con = file->contents.first; + while (con != NULL) { + connext = con->next; + free(con); + con = connext; + } + free(file); + file = next; + } +} + +static int +next_entry_seek(struct archive_read *a, struct iso9660 *iso9660, + struct file_info **pfile) +{ + struct file_info *file; + int r; + + r = next_cache_entry(a, iso9660, pfile); + if (r != ARCHIVE_OK) + return (r); + file = *pfile; + + /* Don't waste time seeking for zero-length bodies. */ + if (file->size == 0) + file->offset = iso9660->current_position; + + /* flush any remaining bytes from the last round to ensure + * we're positioned */ + if (iso9660->entry_bytes_unconsumed) { + __archive_read_consume(a, iso9660->entry_bytes_unconsumed); + iso9660->entry_bytes_unconsumed = 0; + } + + /* Seek forward to the start of the entry. */ + if (iso9660->current_position < file->offset) { + int64_t step; + + step = file->offset - iso9660->current_position; + step = __archive_read_consume(a, step); + if (step < 0) + return ((int)step); + iso9660->current_position = file->offset; + } + + /* We found body of file; handle it now. */ + return (ARCHIVE_OK); +} + +static int +next_cache_entry(struct archive_read *a, struct iso9660 *iso9660, + struct file_info **pfile) +{ + struct file_info *file; + struct { + struct file_info *first; + struct file_info **last; + } empty_files; + int64_t number; + int count; + + file = cache_get_entry(iso9660); + if (file != NULL) { + *pfile = file; + return (ARCHIVE_OK); + } + + for (;;) { + struct file_info *re, *d; + + *pfile = file = next_entry(iso9660); + if (file == NULL) { + /* + * If directory entries all which are descendant of + * rr_moved are stil remaning, expose their. + */ + if (iso9660->re_files.first != NULL && + iso9660->rr_moved != NULL && + iso9660->rr_moved->rr_moved_has_re_only) + /* Expose "rr_moved" entry. */ + cache_add_entry(iso9660, iso9660->rr_moved); + while ((re = re_get_entry(iso9660)) != NULL) { + /* Expose its descendant dirs. */ + while ((d = rede_get_entry(re)) != NULL) + cache_add_entry(iso9660, d); + } + if (iso9660->cache_files.first != NULL) + return (next_cache_entry(a, iso9660, pfile)); + return (ARCHIVE_EOF); + } + + if (file->cl_offset) { + struct file_info *first_re = NULL; + int nexted_re = 0; + + /* + * Find "RE" dir for the current file, which + * has "CL" flag. + */ + while ((re = re_get_entry(iso9660)) + != first_re) { + if (first_re == NULL) + first_re = re; + if (re->offset == file->cl_offset) { + re->parent->subdirs--; + re->parent = file->parent; + re->re = 0; + if (re->parent->re_descendant) { + nexted_re = 1; + re->re_descendant = 1; + if (rede_add_entry(re) < 0) + goto fatal_rr; + /* Move a list of descendants + * to a new ancestor. */ + while ((d = rede_get_entry( + re)) != NULL) + if (rede_add_entry(d) + < 0) + goto fatal_rr; + break; + } + /* Replace the current file + * with "RE" dir */ + *pfile = file = re; + /* Expose its descendant */ + while ((d = rede_get_entry( + file)) != NULL) + cache_add_entry( + iso9660, d); + break; + } else + re_add_entry(iso9660, re); + } + if (nexted_re) { + /* + * Do not expose this at this time + * because we have not gotten its full-path + * name yet. + */ + continue; + } + } else if ((file->mode & AE_IFMT) == AE_IFDIR) { + int r; + + /* Read file entries in this dir. */ + r = read_children(a, file); + if (r != ARCHIVE_OK) + return (r); + + /* + * Handle a special dir of Rockridge extensions, + * "rr_moved". + */ + if (file->rr_moved) { + /* + * If this has only the subdirectories which + * have "RE" flags, do not expose at this time. + */ + if (file->rr_moved_has_re_only) + continue; + /* Otherwise expose "rr_moved" entry. */ + } else if (file->re) { + /* + * Do not expose this at this time + * because we have not gotten its full-path + * name yet. + */ + re_add_entry(iso9660, file); + continue; + } else if (file->re_descendant) { + /* + * If the top level "RE" entry of this entry + * is not exposed, we, accordingly, should not + * expose this entry at this time because + * we cannot make its proper full-path name. + */ + if (rede_add_entry(file) == 0) + continue; + /* Otherwise we can expose this entry because + * it seems its top level "RE" has already been + * exposed. */ + } + } + break; + } + + if ((file->mode & AE_IFMT) != AE_IFREG || file->number == -1) + return (ARCHIVE_OK); + + count = 0; + number = file->number; + iso9660->cache_files.first = NULL; + iso9660->cache_files.last = &(iso9660->cache_files.first); + empty_files.first = NULL; + empty_files.last = &empty_files.first; + /* Collect files which has the same file serial number. + * Peek pending_files so that file which number is different + * is not put bak. */ + while (iso9660->pending_files.used > 0 && + (iso9660->pending_files.files[0]->number == -1 || + iso9660->pending_files.files[0]->number == number)) { + if (file->number == -1) { + /* This file has the same offset + * but it's wrong offset which empty files + * and symlink files have. + * NOTE: This wrong offse was recorded by + * old mkisofs utility. If ISO images is + * created by latest mkisofs, this does not + * happen. + */ + file->next = NULL; + *empty_files.last = file; + empty_files.last = &(file->next); + } else { + count++; + cache_add_entry(iso9660, file); + } + file = next_entry(iso9660); + } + + if (count == 0) { + *pfile = file; + return ((file == NULL)?ARCHIVE_EOF:ARCHIVE_OK); + } + if (file->number == -1) { + file->next = NULL; + *empty_files.last = file; + empty_files.last = &(file->next); + } else { + count++; + cache_add_entry(iso9660, file); + } + + if (count > 1) { + /* The count is the same as number of hardlink, + * so much so that each nlinks of files in cache_file + * is overwritten by value of the count. + */ + for (file = iso9660->cache_files.first; + file != NULL; file = file->next) + file->nlinks = count; + } + /* If there are empty files, that files are added + * to the tail of the cache_files. */ + if (empty_files.first != NULL) { + *iso9660->cache_files.last = empty_files.first; + iso9660->cache_files.last = empty_files.last; + } + *pfile = cache_get_entry(iso9660); + return ((*pfile == NULL)?ARCHIVE_EOF:ARCHIVE_OK); + +fatal_rr: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to connect 'CL' pointer to 'RE' rr_moved pointer of" + "Rockridge extensions"); + return (ARCHIVE_FATAL); +} + +static inline void +re_add_entry(struct iso9660 *iso9660, struct file_info *file) +{ + file->re_next = NULL; + *iso9660->re_files.last = file; + iso9660->re_files.last = &(file->re_next); +} + +static inline struct file_info * +re_get_entry(struct iso9660 *iso9660) +{ + struct file_info *file; + + if ((file = iso9660->re_files.first) != NULL) { + iso9660->re_files.first = file->re_next; + if (iso9660->re_files.first == NULL) + iso9660->re_files.last = + &(iso9660->re_files.first); + } + return (file); +} + +static inline int +rede_add_entry(struct file_info *file) +{ + struct file_info *re; + + /* + * Find "RE" entry. + */ + re = file->parent; + while (re != NULL && !re->re) + re = re->parent; + if (re == NULL) + return (-1); + + file->re_next = NULL; + *re->rede_files.last = file; + re->rede_files.last = &(file->re_next); + return (0); +} + +static inline struct file_info * +rede_get_entry(struct file_info *re) +{ + struct file_info *file; + + if ((file = re->rede_files.first) != NULL) { + re->rede_files.first = file->re_next; + if (re->rede_files.first == NULL) + re->rede_files.last = + &(re->rede_files.first); + } + return (file); +} + +static inline void +cache_add_entry(struct iso9660 *iso9660, struct file_info *file) +{ + file->next = NULL; + *iso9660->cache_files.last = file; + iso9660->cache_files.last = &(file->next); +} + +static inline struct file_info * +cache_get_entry(struct iso9660 *iso9660) +{ + struct file_info *file; + + if ((file = iso9660->cache_files.first) != NULL) { + iso9660->cache_files.first = file->next; + if (iso9660->cache_files.first == NULL) + iso9660->cache_files.last = + &(iso9660->cache_files.first); + } + return (file); +} + +static int +heap_add_entry(struct archive_read *a, struct heap_queue *heap, + struct file_info *file, uint64_t key) +{ + uint64_t file_key, parent_key; + int hole, parent; + + /* Expand our pending files list as necessary. */ + if (heap->used >= heap->allocated) { + struct file_info **new_pending_files; + int new_size = heap->allocated * 2; + + if (heap->allocated < 1024) + new_size = 1024; + /* Overflow might keep us from growing the list. */ + if (new_size <= heap->allocated) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + new_pending_files = (struct file_info **) + malloc(new_size * sizeof(new_pending_files[0])); + if (new_pending_files == NULL) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + memcpy(new_pending_files, heap->files, + heap->allocated * sizeof(new_pending_files[0])); + if (heap->files != NULL) + free(heap->files); + heap->files = new_pending_files; + heap->allocated = new_size; + } + + file_key = file->key = key; + + /* + * Start with hole at end, walk it up tree to find insertion point. + */ + hole = heap->used++; + while (hole > 0) { + parent = (hole - 1)/2; + parent_key = heap->files[parent]->key; + if (file_key >= parent_key) { + heap->files[hole] = file; + return (ARCHIVE_OK); + } + /* Move parent into hole <==> move hole up tree. */ + heap->files[hole] = heap->files[parent]; + hole = parent; + } + heap->files[0] = file; + + return (ARCHIVE_OK); +} + +static struct file_info * +heap_get_entry(struct heap_queue *heap) +{ + uint64_t a_key, b_key, c_key; + int a, b, c; + struct file_info *r, *tmp; + + if (heap->used < 1) + return (NULL); + + /* + * The first file in the list is the earliest; we'll return this. + */ + r = heap->files[0]; + + /* + * Move the last item in the heap to the root of the tree + */ + heap->files[0] = heap->files[--(heap->used)]; + + /* + * Rebalance the heap. + */ + a = 0; /* Starting element and its heap key */ + a_key = heap->files[a]->key; + for (;;) { + b = a + a + 1; /* First child */ + if (b >= heap->used) + return (r); + b_key = heap->files[b]->key; + c = b + 1; /* Use second child if it is smaller. */ + if (c < heap->used) { + c_key = heap->files[c]->key; + if (c_key < b_key) { + b = c; + b_key = c_key; + } + } + if (a_key <= b_key) + return (r); + tmp = heap->files[a]; + heap->files[a] = heap->files[b]; + heap->files[b] = tmp; + a = b; + } +} + +static unsigned int +toi(const void *p, int n) +{ + const unsigned char *v = (const unsigned char *)p; + if (n > 1) + return v[0] + 256 * toi(v + 1, n - 1); + if (n == 1) + return v[0]; + return (0); +} + +static time_t +isodate7(const unsigned char *v) +{ + struct tm tm; + int offset; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = v[0]; + tm.tm_mon = v[1] - 1; + tm.tm_mday = v[2]; + tm.tm_hour = v[3]; + tm.tm_min = v[4]; + tm.tm_sec = v[5]; + /* v[6] is the signed timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)v)[6]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (time_from_tm(&tm)); +} + +static time_t +isodate17(const unsigned char *v) +{ + struct tm tm; + int offset; + memset(&tm, 0, sizeof(tm)); + tm.tm_year = (v[0] - '0') * 1000 + (v[1] - '0') * 100 + + (v[2] - '0') * 10 + (v[3] - '0') + - 1900; + tm.tm_mon = (v[4] - '0') * 10 + (v[5] - '0'); + tm.tm_mday = (v[6] - '0') * 10 + (v[7] - '0'); + tm.tm_hour = (v[8] - '0') * 10 + (v[9] - '0'); + tm.tm_min = (v[10] - '0') * 10 + (v[11] - '0'); + tm.tm_sec = (v[12] - '0') * 10 + (v[13] - '0'); + /* v[16] is the signed timezone offset, in 1/4-hour increments. */ + offset = ((const signed char *)v)[16]; + if (offset > -48 && offset < 52) { + tm.tm_hour -= offset / 4; + tm.tm_min -= (offset % 4) * 15; + } + return (time_from_tm(&tm)); +} + +static time_t +time_from_tm(struct tm *t) +{ +#if HAVE_TIMEGM + /* Use platform timegm() if available. */ + return (timegm(t)); +#elif HAVE__MKGMTIME64 + return (_mkgmtime64(t)); +#else + /* Else use direct calculation using POSIX assumptions. */ + /* First, fix up tm_yday based on the year/month/day. */ + mktime(t); + /* Then we can compute timegm() from first principles. */ + return (t->tm_sec + t->tm_min * 60 + t->tm_hour * 3600 + + t->tm_yday * 86400 + (t->tm_year - 70) * 31536000 + + ((t->tm_year - 69) / 4) * 86400 - + ((t->tm_year - 1) / 100) * 86400 + + ((t->tm_year + 299) / 400) * 86400); +#endif +} + +static const char * +build_pathname(struct archive_string *as, struct file_info *file) +{ + if (file->parent != NULL && archive_strlen(&file->parent->name) > 0) { + build_pathname(as, file->parent); + archive_strcat(as, "/"); + } + if (archive_strlen(&file->name) == 0) + archive_strcat(as, "."); + else + archive_string_concat(as, &file->name); + return (as->s); +} + +static int +build_pathname_utf16be(unsigned char *p, size_t max, size_t *len, + struct file_info *file) +{ + if (file->parent != NULL && file->parent->utf16be_bytes > 0) { + if (build_pathname_utf16be(p, max, len, file->parent) != 0) + return (-1); + p[*len] = 0; + p[*len + 1] = '/'; + *len += 2; + } + if (file->utf16be_bytes == 0) { + if (*len + 2 > max) + return (-1);/* Path is too long! */ + p[*len] = 0; + p[*len + 1] = '.'; + *len += 2; + } else { + if (*len + file->utf16be_bytes > max) + return (-1);/* Path is too long! */ + memcpy(p + *len, file->utf16be_name, file->utf16be_bytes); + *len += file->utf16be_bytes; + } + return (0); +} + +#if DEBUG +static void +dump_isodirrec(FILE *out, const unsigned char *isodirrec) +{ + fprintf(out, " l %d,", + toi(isodirrec + DR_length_offset, DR_length_size)); + fprintf(out, " a %d,", + toi(isodirrec + DR_ext_attr_length_offset, DR_ext_attr_length_size)); + fprintf(out, " ext 0x%x,", + toi(isodirrec + DR_extent_offset, DR_extent_size)); + fprintf(out, " s %d,", + toi(isodirrec + DR_size_offset, DR_extent_size)); + fprintf(out, " f 0x%x,", + toi(isodirrec + DR_flags_offset, DR_flags_size)); + fprintf(out, " u %d,", + toi(isodirrec + DR_file_unit_size_offset, DR_file_unit_size_size)); + fprintf(out, " ilv %d,", + toi(isodirrec + DR_interleave_offset, DR_interleave_size)); + fprintf(out, " seq %d,", + toi(isodirrec + DR_volume_sequence_number_offset, DR_volume_sequence_number_size)); + fprintf(out, " nl %d:", + toi(isodirrec + DR_name_len_offset, DR_name_len_size)); + fprintf(out, " `%.*s'", + toi(isodirrec + DR_name_len_offset, DR_name_len_size), isodirrec + DR_name_offset); +} +#endif diff --git a/libarchive/archive_read_support_format_lha.c b/libarchive/archive_read_support_format_lha.c new file mode 100644 index 0000000..50256b1 --- /dev/null +++ b/libarchive/archive_read_support_format_lha.c @@ -0,0 +1,2745 @@ +/*- + * Copyright (c) 2008-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + + +#define MAXMATCH 256 /* Maximum match length. */ +#define MINMATCH 3 /* Minimum match length. */ +/* + * Literal table format: + * +0 +256 +510 + * +---------------+-------------------------+ + * | literal code | match length | + * | 0 ... 255 | MINMATCH ... MAXMATCH | + * +---------------+-------------------------+ + * <--- LT_BITLEN_SIZE ---> + */ +/* Literal table size. */ +#define LT_BITLEN_SIZE (UCHAR_MAX + 1 + MAXMATCH - MINMATCH + 1) +/* Position table size. + * Note: this used for both position table and pre literal table.*/ +#define PT_BITLEN_SIZE (3 + 16) + +struct lzh_dec { + /* Decoding status. */ + int state; + + /* + * Window to see last 8Ki(lh5),32Ki(lh6),64Ki(lh7) bytes of decoded + * data. + */ + int w_size; + int w_mask; + /* Window buffer, which is a loop buffer. */ + unsigned char *w_buff; + /* The insert position to the window. */ + int w_pos; + /* The position where we can copy decoded code from the window. */ + int copy_pos; + /* The length how many bytes we can copy decoded code from + * the window. */ + int copy_len; + /* The remaining bytes that we have not copied decoded data from + * the window to an output buffer. */ + int w_remaining; + + /* + * Bit stream reader. + */ + struct lzh_br { +#define CACHE_TYPE uint64_t +#define CACHE_BITS (8 * sizeof(CACHE_TYPE)) + /* Cache buffer. */ + CACHE_TYPE cache_buffer; + /* Indicates how many bits avail in cache_buffer. */ + int cache_avail; + } br; + + /* + * Huffman coding. + */ + struct huffman { + int len_size; + int len_avail; + int len_bits; + int freq[17]; + unsigned char *bitlen; + + /* + * Use a index table. It's faster than searching a huffman + * coding tree, which is a binary tree. But a use of a large + * index table causes L1 cache read miss many times. + */ +#define HTBL_BITS 10 + int max_bits; + int shift_bits; + int tbl_bits; + int tree_used; + int tree_avail; + /* Direct access table. */ + uint16_t *tbl; + /* Binary tree table for extra bits over the direct access. */ + struct htree_t { + uint16_t left; + uint16_t right; + } *tree; + } lt, pt; + + int blocks_avail; + int pos_pt_len_size; + int pos_pt_len_bits; + int literal_pt_len_size; + int literal_pt_len_bits; + int reading_position; + int loop; + int error; +}; + +struct lzh_stream { + const unsigned char *next_in; + int64_t avail_in; + int64_t total_in; + unsigned char *next_out; + int64_t avail_out; + int64_t total_out; + struct lzh_dec *ds; +}; + +struct lha { + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_offset; + int64_t entry_bytes_remaining; + int64_t entry_unconsumed; + uint16_t entry_crc_calculated; + + size_t header_size; /* header size */ + unsigned char level; /* header level */ + char method[3]; /* compress type */ + int64_t compsize; /* compressed data size */ + int64_t origsize; /* original file size */ + int setflag; +#define BIRTHTIME_IS_SET 1 +#define ATIME_IS_SET 2 +#define UNIX_MODE_IS_SET 4 +#define CRC_IS_SET 8 + time_t birthtime; + long birthtime_tv_nsec; + time_t mtime; + long mtime_tv_nsec; + time_t atime; + long atime_tv_nsec; + mode_t mode; + int64_t uid; + int64_t gid; + struct archive_string uname; + struct archive_string gname; + uint16_t header_crc; + uint16_t crc; + struct archive_string_conv *sconv; + struct archive_string_conv *opt_sconv; + + struct archive_string dirname; + struct archive_string filename; + struct archive_wstring ws; + + unsigned char dos_attr; + + /* Flag to mark progress that an archive was read their first header.*/ + char found_first_header; + /* Flag to mark that indicates an empty directory. */ + char directory; + + /* Flags to mark progress of decompression. */ + char decompress_init; + char end_of_entry; + char end_of_entry_cleanup; + char entry_is_compressed; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; + + char format_name[64]; + + struct lzh_stream strm; +}; + +/* + * LHA header common member offset. + */ +#define H_METHOD_OFFSET 2 /* Compress type. */ +#define H_ATTR_OFFSET 19 /* DOS attribute. */ +#define H_LEVEL_OFFSET 20 /* Header Level. */ +#define H_SIZE 22 /* Minimum header size. */ + +static const uint16_t crc16tbl[256] = { + 0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241, + 0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440, + 0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40, + 0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841, + 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40, + 0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41, + 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641, + 0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040, + 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240, + 0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441, + 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41, + 0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840, + 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41, + 0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40, + 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640, + 0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041, + 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240, + 0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441, + 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41, + 0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840, + 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41, + 0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40, + 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640, + 0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041, + 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241, + 0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440, + 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40, + 0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841, + 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40, + 0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41, + 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641, + 0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040 +}; + +static int archive_read_format_lha_bid(struct archive_read *, int); +static int archive_read_format_lha_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_lha_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_lha_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_lha_read_data_skip(struct archive_read *); +static int archive_read_format_lha_cleanup(struct archive_read *); + +static void lha_replace_path_separator(struct lha *, + struct archive_entry *); +static int lha_read_file_header_0(struct archive_read *, struct lha *); +static int lha_read_file_header_1(struct archive_read *, struct lha *); +static int lha_read_file_header_2(struct archive_read *, struct lha *); +static int lha_read_file_header_3(struct archive_read *, struct lha *); +static int lha_read_file_extended_header(struct archive_read *, + struct lha *, uint16_t *, int, size_t, size_t *); +static size_t lha_check_header_format(const void *); +static int lha_skip_sfx(struct archive_read *); +static time_t lha_dos_time(const unsigned char *); +static time_t lha_win_time(uint64_t, long *); +static unsigned char lha_calcsum(unsigned char, const void *, + int, int); +static int lha_parse_linkname(struct archive_string *, + struct archive_string *); +static int lha_read_data_none(struct archive_read *, const void **, + size_t *, int64_t *); +static int lha_read_data_lzh(struct archive_read *, const void **, + size_t *, int64_t *); +static uint16_t lha_crc16(uint16_t, const void *, size_t); +static int lzh_decode_init(struct lzh_stream *, const char *); +static void lzh_decode_free(struct lzh_stream *); +static int lzh_decode(struct lzh_stream *, int); +static int lzh_br_fillup(struct lzh_stream *, struct lzh_br *); +static int lzh_huffman_init(struct huffman *, size_t, int); +static void lzh_huffman_free(struct huffman *); +static int lzh_read_pt_bitlen(struct lzh_stream *, int start, int end); +static int lzh_make_fake_table(struct huffman *, uint16_t); +static int lzh_make_huffman_table(struct huffman *); +static int inline lzh_decode_huffman(struct huffman *, unsigned); +static int lzh_decode_huffman_tree(struct huffman *, unsigned, int); + + +int +archive_read_support_format_lha(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct lha *lha; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_lha"); + + lha = (struct lha *)calloc(1, sizeof(*lha)); + if (lha == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate lha data"); + return (ARCHIVE_FATAL); + } + archive_string_init(&lha->ws); + + r = __archive_read_register_format(a, + lha, + "lha", + archive_read_format_lha_bid, + archive_read_format_lha_options, + archive_read_format_lha_read_header, + archive_read_format_lha_read_data, + archive_read_format_lha_read_data_skip, + archive_read_format_lha_cleanup); + + if (r != ARCHIVE_OK) + free(lha); + return (ARCHIVE_OK); +} + +static size_t +lha_check_header_format(const void *h) +{ + const unsigned char *p = h; + size_t next_skip_bytes; + + switch (p[H_METHOD_OFFSET+3]) { + /* + * "-lh0-" ... "-lh7-" "-lhd-" + * "-lzs-" "-lz5-" + */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case 'd': + case 's': + next_skip_bytes = 4; + + /* b0 == 0 means the end of an LHa archive file. */ + if (p[0] == 0) + break; + if (p[H_METHOD_OFFSET] != '-' || p[H_METHOD_OFFSET+1] != 'l' + || p[H_METHOD_OFFSET+4] != '-') + break; + + if (p[H_METHOD_OFFSET+2] == 'h') { + /* "-lh?-" */ + if (p[H_METHOD_OFFSET+3] == 's') + break; + if (p[H_LEVEL_OFFSET] == 0) + return (0); + if (p[H_LEVEL_OFFSET] <= 3 && p[H_ATTR_OFFSET] == 0x20) + return (0); + } + if (p[H_METHOD_OFFSET+2] == 'z') { + /* LArc extensions: -lzs-,-lz4- and -lz5- */ + if (p[H_LEVEL_OFFSET] != 0) + break; + if (p[H_METHOD_OFFSET+3] == 's' + || p[H_METHOD_OFFSET+3] == '4' + || p[H_METHOD_OFFSET+3] == '5') + return (0); + } + break; + case 'h': next_skip_bytes = 1; break; + case 'z': next_skip_bytes = 1; break; + case 'l': next_skip_bytes = 2; break; + case '-': next_skip_bytes = 3; break; + default : next_skip_bytes = 4; break; + } + + return (next_skip_bytes); +} + +static int +archive_read_format_lha_bid(struct archive_read *a, int best_bid) +{ + const char *p; + const void *buff; + ssize_t bytes_avail, offset, window; + size_t next; + + /* If there's already a better bid than we can ever + make, don't bother testing. */ + if (best_bid > 30) + return (-1); + + if ((p = __archive_read_ahead(a, H_SIZE, NULL)) == NULL) + return (-1); + + if (lha_check_header_format(p) == 0) + return (30); + + if (p[0] == 'M' && p[1] == 'Z') { + /* PE file */ + offset = 0; + window = 4096; + while (offset < (1024 * 20)) { + buff = __archive_read_ahead(a, offset + window, + &bytes_avail); + if (buff == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < (H_SIZE + 3)) + return (0); + continue; + } + p = (const char *)buff + offset; + while (p + H_SIZE < (const char *)buff + bytes_avail) { + if ((next = lha_check_header_format(p)) == 0) + return (30); + p += next; + } + offset = p - (const char *)buff; + } + } + return (0); +} + +static int +archive_read_format_lha_options(struct archive_read *a, + const char *key, const char *val) +{ + struct lha *lha; + int ret = ARCHIVE_FAILED; + + lha = (struct lha *)(a->format->data); + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "lha: hdrcharset option needs a character-set name"); + else { + lha->opt_sconv = + archive_string_conversion_from_charset( + &a->archive, val, 0); + if (lha->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "lha: unknown keyword ``%s''", key); + + return (ret); +} + +static int +lha_skip_sfx(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t next, skip; + ssize_t bytes, window; + + window = 4096; + for (;;) { + h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < (H_SIZE + 3)) + goto fatal; + continue; + } + if (bytes < H_SIZE) + goto fatal; + p = h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the lha header. + */ + while (p + H_SIZE < q) { + if ((next = lha_check_header_format(p)) == 0) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + return (ARCHIVE_OK); + } + p += next; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + } +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out LHa header"); + return (ARCHIVE_FATAL); +} + +static int +truncated_error(struct archive_read *a) +{ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated LHa header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_lha_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct archive_string linkname; + struct archive_string pathname; + struct lha *lha; + const unsigned char *p; + const char *signature; + int err; + + a->archive.archive_format = ARCHIVE_FORMAT_LHA; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "lha"; + + lha = (struct lha *)(a->format->data); + lha->decompress_init = 0; + lha->end_of_entry = 0; + lha->end_of_entry_cleanup = 0; + lha->entry_unconsumed = 0; + + if ((p = __archive_read_ahead(a, H_SIZE, NULL)) == NULL) { + /* + * LHa archiver added 0 to the tail of its archive file as + * the mark of the end of the archive. + */ + signature = __archive_read_ahead(a, sizeof(signature[0]), NULL); + if (signature == NULL || signature[0] == 0) + return (ARCHIVE_EOF); + return (truncated_error(a)); + } + + signature = (const char *)p; + if (lha->found_first_header == 0 && + signature[0] == 'M' && signature[1] == 'Z') { + /* This is an executable? Must be self-extracting... */ + err = lha_skip_sfx(a); + if (err < ARCHIVE_WARN) + return (err); + + if ((p = __archive_read_ahead(a, sizeof(*p), NULL)) == NULL) + return (truncated_error(a)); + signature = (const char *)p; + } + /* signature[0] == 0 means the end of an LHa archive file. */ + if (signature[0] == 0) + return (ARCHIVE_EOF); + + /* + * Check the header format and method type. + */ + if (lha_check_header_format(p) != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad LHa file"); + return (ARCHIVE_FATAL); + } + + /* We've found the first header. */ + lha->found_first_header = 1; + /* Set a default value and common data */ + lha->header_size = 0; + lha->level = p[H_LEVEL_OFFSET]; + lha->method[0] = p[H_METHOD_OFFSET+1]; + lha->method[1] = p[H_METHOD_OFFSET+2]; + lha->method[2] = p[H_METHOD_OFFSET+3]; + if (memcmp(lha->method, "lhd", 3) == 0) + lha->directory = 1; + else + lha->directory = 0; + if (memcmp(lha->method, "lh0", 3) == 0 || + memcmp(lha->method, "lz4", 3) == 0) + lha->entry_is_compressed = 0; + else + lha->entry_is_compressed = 1; + + lha->compsize = 0; + lha->origsize = 0; + lha->setflag = 0; + lha->birthtime = 0; + lha->birthtime_tv_nsec = 0; + lha->mtime = 0; + lha->mtime_tv_nsec = 0; + lha->atime = 0; + lha->atime_tv_nsec = 0; + lha->mode = (lha->directory)? 0777 : 0666; + lha->uid = 0; + lha->gid = 0; + archive_string_empty(&lha->dirname); + archive_string_empty(&lha->filename); + lha->dos_attr = 0; + if (lha->opt_sconv != NULL) + lha->sconv = lha->opt_sconv; + else + lha->sconv = NULL; + + switch (p[H_LEVEL_OFFSET]) { + case 0: + err = lha_read_file_header_0(a, lha); + break; + case 1: + err = lha_read_file_header_1(a, lha); + break; + case 2: + err = lha_read_file_header_2(a, lha); + break; + case 3: + err = lha_read_file_header_3(a, lha); + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported LHa header level %d", p[H_LEVEL_OFFSET]); + err = ARCHIVE_FATAL; + break; + } + if (err < ARCHIVE_WARN) + return (err); + + + if (!lha->directory && archive_strlen(&lha->filename) == 0) + /* The filename has not been set */ + return (truncated_error(a)); + + /* + * Make a pathname from a dirname and a filename. + */ + archive_string_concat(&lha->dirname, &lha->filename); + archive_string_init(&pathname); + archive_string_init(&linkname); + archive_string_copy(&pathname, &lha->dirname); + + if ((lha->mode & AE_IFMT) == AE_IFLNK) { + /* + * Extract the symlink-name if it's included in the pathname. + */ + if (!lha_parse_linkname(&linkname, &pathname)) { + /* We couldn't get the symlink-name. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unknown symlink-name"); + archive_string_free(&pathname); + archive_string_free(&linkname); + return (ARCHIVE_FAILED); + } + } else { + /* + * Make sure a file-type is set. + * The mode has been overridden if it is in the extended data. + */ + lha->mode = (lha->mode & ~AE_IFMT) | + ((lha->directory)? AE_IFDIR: AE_IFREG); + } + if ((lha->setflag & UNIX_MODE_IS_SET) == 0 && + (lha->dos_attr & 1) != 0) + lha->mode &= ~(0222);/* read only. */ + + /* + * Set basic file parameters. + */ + if (archive_entry_copy_pathname_l(entry, pathname.s, + pathname.length, lha->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(lha->sconv)); + err = ARCHIVE_WARN; + } + archive_string_free(&pathname); + if (archive_strlen(&linkname) > 0) { + if (archive_entry_copy_symlink_l(entry, linkname.s, + linkname.length, lha->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Linkname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(lha->sconv)); + err = ARCHIVE_WARN; + } + } else + archive_entry_set_symlink(entry, NULL); + archive_string_free(&linkname); + /* + * When a header level is 0, there is a possibilty that + * a pathname and a symlink has '\' character, a directory + * separator in DOS/Windows. So we should convert it to '/'. + */ + if (p[H_LEVEL_OFFSET] == 0) + lha_replace_path_separator(lha, entry); + + archive_entry_set_mode(entry, lha->mode); + archive_entry_set_uid(entry, lha->uid); + archive_entry_set_gid(entry, lha->gid); + if (archive_strlen(&lha->uname) > 0) + archive_entry_set_uname(entry, lha->uname.s); + if (archive_strlen(&lha->gname) > 0) + archive_entry_set_gname(entry, lha->gname.s); + if (lha->setflag & BIRTHTIME_IS_SET) { + archive_entry_set_birthtime(entry, lha->birthtime, + lha->birthtime_tv_nsec); + archive_entry_set_ctime(entry, lha->birthtime, + lha->birthtime_tv_nsec); + } else { + archive_entry_unset_birthtime(entry); + archive_entry_unset_ctime(entry); + } + archive_entry_set_mtime(entry, lha->mtime, lha->mtime_tv_nsec); + if (lha->setflag & ATIME_IS_SET) + archive_entry_set_atime(entry, lha->atime, + lha->atime_tv_nsec); + else + archive_entry_unset_atime(entry); + if (lha->directory || archive_entry_symlink(entry) != NULL) + archive_entry_unset_size(entry); + else + archive_entry_set_size(entry, lha->origsize); + + /* + * Prepare variables used to read a file content. + */ + lha->entry_bytes_remaining = lha->compsize; + lha->entry_offset = 0; + lha->entry_crc_calculated = 0; + + /* + * This file does not have a content. + */ + if (lha->directory || lha->compsize == 0) + lha->end_of_entry = 1; + + sprintf(lha->format_name, "lha -%c%c%c-", + lha->method[0], lha->method[1], lha->method[2]); + a->archive.archive_format_name = lha->format_name; + + return (err); +} + +/* + * Replace a DOS path separator '\' by a character '/'. + * Some multi-byte character set have a character '\' in its second byte. + */ +static void +lha_replace_path_separator(struct lha *lha, struct archive_entry *entry) +{ + const wchar_t *wp; + size_t i; + + if ((wp = archive_entry_pathname_w(entry)) != NULL) { + archive_wstrcpy(&(lha->ws), wp); + for (i = 0; i < archive_strlen(&(lha->ws)); i++) { + if (lha->ws.s[i] == L'\\') + lha->ws.s[i] = L'/'; + } + archive_entry_copy_pathname_w(entry, lha->ws.s); + } + + if ((wp = archive_entry_symlink_w(entry)) != NULL) { + archive_wstrcpy(&(lha->ws), wp); + for (i = 0; i < archive_strlen(&(lha->ws)); i++) { + if (lha->ws.s[i] == L'\\') + lha->ws.s[i] = L'/'; + } + archive_entry_copy_symlink_w(entry, lha->ws.s); + } +} + +/* + * Header 0 format + * + * +0 +1 +2 +7 +11 + * +---------------+----------+----------------+-------------------+ + * |header size(*1)|header sum|compression type|compressed size(*2)| + * +---------------+----------+----------------+-------------------+ + * <---------------------(*1)----------* + * + * +11 +15 +17 +19 +20 +21 + * +-----------------+---------+---------+--------------+----------------+ + * |uncompressed size|time(DOS)|date(DOS)|attribute(DOS)|header level(=0)| + * +-----------------+---------+---------+--------------+----------------+ + * *--------------------------------(*1)---------------------------------* + * + * +21 +22 +22+(*3) +22+(*3)+2 +22+(*3)+2+(*4) + * +---------------+---------+----------+----------------+------------------+ + * |name length(*3)|file name|file CRC16|extra header(*4)| compressed data | + * +---------------+---------+----------+----------------+------------------+ + * <--(*3)-> <------(*2)------> + * *----------------------(*1)--------------------------> + * + */ +#define H0_HEADER_SIZE_OFFSET 0 +#define H0_HEADER_SUM_OFFSET 1 +#define H0_COMP_SIZE_OFFSET 7 +#define H0_ORIG_SIZE_OFFSET 11 +#define H0_DOS_TIME_OFFSET 15 +#define H0_NAME_LEN_OFFSET 21 +#define H0_FILE_NAME_OFFSET 22 +#define H0_FIXED_SIZE 24 +static int +lha_read_file_header_0(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + int extdsize, namelen; + unsigned char headersum, sum_calculated; + + if ((p = __archive_read_ahead(a, H0_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + lha->header_size = p[H0_HEADER_SIZE_OFFSET] + 2; + headersum = p[H0_HEADER_SUM_OFFSET]; + lha->compsize = archive_le32dec(p + H0_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H0_ORIG_SIZE_OFFSET); + lha->mtime = lha_dos_time(p + H0_DOS_TIME_OFFSET); + namelen = p[H0_NAME_LEN_OFFSET]; + extdsize = (int)lha->header_size - H0_FIXED_SIZE - namelen; + if ((namelen > 221 || extdsize < 0) && extdsize != -2) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); + } + if ((p = __archive_read_ahead(a, lha->header_size, NULL)) == NULL) + return (truncated_error(a)); + + archive_strncpy(&lha->filename, p + H0_FILE_NAME_OFFSET, namelen); + /* When extdsize == -2, A CRC16 value is not present in the header. */ + if (extdsize >= 0) { + lha->crc = archive_le16dec(p + H0_FILE_NAME_OFFSET + namelen); + lha->setflag |= CRC_IS_SET; + } + sum_calculated = lha_calcsum(0, p, 2, lha->header_size - 2); + + /* Read an extended header */ + if (extdsize > 0) { + /* This extended data is set by 'LHa for UNIX' only. + * Maybe fixed size. + */ + p += H0_FILE_NAME_OFFSET + namelen + 2; + if (p[0] == 'U' && extdsize == 12) { + /* p[1] is a minor version. */ + lha->mtime = archive_le32dec(&p[2]); + lha->mode = archive_le16dec(&p[6]); + lha->uid = archive_le16dec(&p[8]); + lha->gid = archive_le16dec(&p[10]); + lha->setflag |= UNIX_MODE_IS_SET; + } + } + __archive_read_consume(a, lha->header_size); + + if (sum_calculated != headersum) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LHa header sum error"); + return (ARCHIVE_FATAL); + } + + return (ARCHIVE_OK); +} + +/* + * Header 1 format + * + * +0 +1 +2 +7 +11 + * +---------------+----------+----------------+-------------+ + * |header size(*1)|header sum|compression type|skip size(*2)| + * +---------------+----------+----------------+-------------+ + * <---------------(*1)----------* + * + * +11 +15 +17 +19 +20 +21 + * +-----------------+---------+---------+--------------+----------------+ + * |uncompressed size|time(DOS)|date(DOS)|attribute(DOS)|header level(=1)| + * +-----------------+---------+---------+--------------+----------------+ + * *-------------------------------(*1)----------------------------------* + * + * +21 +22 +22+(*3) +22+(*3)+2 +22+(*3)+3 +22+(*3)+3+(*4) + * +---------------+---------+----------+-----------+-----------+ + * |name length(*3)|file name|file CRC16| creator |padding(*4)| + * +---------------+---------+----------+-----------+-----------+ + * <--(*3)-> + * *----------------------------(*1)----------------------------* + * + * +22+(*3)+3+(*4) +22+(*3)+3+(*4)+2 +22+(*3)+3+(*4)+2+(*5) + * +----------------+---------------------+------------------------+ + * |next header size| extended header(*5) | compressed data | + * +----------------+---------------------+------------------------+ + * *------(*1)-----> <--------------------(*2)--------------------> + */ +#define H1_HEADER_SIZE_OFFSET 0 +#define H1_HEADER_SUM_OFFSET 1 +#define H1_COMP_SIZE_OFFSET 7 +#define H1_ORIG_SIZE_OFFSET 11 +#define H1_DOS_TIME_OFFSET 15 +#define H1_NAME_LEN_OFFSET 21 +#define H1_FILE_NAME_OFFSET 22 +#define H1_FIXED_SIZE 27 +static int +lha_read_file_header_1(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int i, err, err2; + int namelen, padding; + unsigned char headersum, sum_calculated; + + err = ARCHIVE_OK; + + if ((p = __archive_read_ahead(a, H1_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + lha->header_size = p[H1_HEADER_SIZE_OFFSET] + 2; + headersum = p[H1_HEADER_SUM_OFFSET]; + /* Note: An extended header size is included in a compsize. */ + lha->compsize = archive_le32dec(p + H1_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H1_ORIG_SIZE_OFFSET); + lha->mtime = lha_dos_time(p + H1_DOS_TIME_OFFSET); + namelen = p[H1_NAME_LEN_OFFSET]; + /* Calculate a padding size. The result will be normally 0 only(?) */ + padding = ((int)lha->header_size) - H1_FIXED_SIZE - namelen; + + if (namelen > 230 || padding < 0) + goto invalid; + + if ((p = __archive_read_ahead(a, lha->header_size, NULL)) == NULL) + return (truncated_error(a)); + + for (i = 0; i < namelen; i++) { + if (p[i + H1_FILE_NAME_OFFSET] == 0xff) + goto invalid;/* Invalid filename. */ + } + archive_strncpy(&lha->filename, p + H1_FILE_NAME_OFFSET, namelen); + lha->crc = archive_le16dec(p + H1_FILE_NAME_OFFSET + namelen); + lha->setflag |= CRC_IS_SET; + + sum_calculated = lha_calcsum(0, p, 2, lha->header_size - 2); + /* Consume used bytes but not include `next header size' data + * since it will be consumed in lha_read_file_extended_header(). */ + __archive_read_consume(a, lha->header_size - 2); + + /* Read extended headers */ + err2 = lha_read_file_extended_header(a, lha, NULL, 2, + lha->compsize + 2, &extdsize); + if (err2 < ARCHIVE_WARN) + return (err2); + if (err2 < err) + err = err2; + /* Get a real compressed file size. */ + lha->compsize -= extdsize - 2; + + if (sum_calculated != headersum) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "LHa header sum error"); + return (ARCHIVE_FATAL); + } + return (err); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); +} + +/* + * Header 2 format + * + * +0 +2 +7 +11 +15 + * +---------------+----------------+-------------------+-----------------+ + * |header size(*1)|compression type|compressed size(*2)|uncompressed size| + * +---------------+----------------+-------------------+-----------------+ + * <--------------------------------(*1)---------------------------------* + * + * +15 +19 +20 +21 +23 +24 + * +-----------------+------------+----------------+----------+-----------+ + * |data/time(time_t)| 0x20 fixed |header level(=2)|file CRC16| creator | + * +-----------------+------------+----------------+----------+-----------+ + * *---------------------------------(*1)---------------------------------* + * + * +24 +26 +26+(*3) +26+(*3)+(*4) + * +----------------+-------------------+-------------+-------------------+ + * |next header size|extended header(*3)| padding(*4) | compressed data | + * +----------------+-------------------+-------------+-------------------+ + * *--------------------------(*1)-------------------> <------(*2)-------> + * + */ +#define H2_HEADER_SIZE_OFFSET 0 +#define H2_COMP_SIZE_OFFSET 7 +#define H2_ORIG_SIZE_OFFSET 11 +#define H2_TIME_OFFSET 15 +#define H2_CRC_OFFSET 21 +#define H2_FIXED_SIZE 24 +static int +lha_read_file_header_2(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int err, padding; + uint16_t header_crc; + + if ((p = __archive_read_ahead(a, H2_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + lha->header_size =archive_le16dec(p + H2_HEADER_SIZE_OFFSET); + lha->compsize = archive_le32dec(p + H2_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H2_ORIG_SIZE_OFFSET); + lha->mtime = archive_le32dec(p + H2_TIME_OFFSET); + lha->crc = archive_le16dec(p + H2_CRC_OFFSET); + lha->setflag |= CRC_IS_SET; + + if (lha->header_size < H2_FIXED_SIZE) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header size"); + return (ARCHIVE_FATAL); + } + + header_crc = lha_crc16(0, p, H2_FIXED_SIZE); + __archive_read_consume(a, H2_FIXED_SIZE); + + /* Read extended headers */ + err = lha_read_file_extended_header(a, lha, &header_crc, 2, + lha->header_size - H2_FIXED_SIZE, &extdsize); + if (err < ARCHIVE_WARN) + return (err); + + /* Calculate a padding size. The result will be normally 0 or 1. */ + padding = (int)lha->header_size - (int)(H2_FIXED_SIZE + extdsize); + if (padding > 0) { + if ((p = __archive_read_ahead(a, padding, NULL)) == NULL) + return (truncated_error(a)); + header_crc = lha_crc16(header_crc, p, padding); + __archive_read_consume(a, padding); + } + + if (header_crc != lha->header_crc) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "LHa header CRC error"); + return (ARCHIVE_FATAL); + } + return (err); +} + +/* + * Header 3 format + * + * +0 +2 +7 +11 +15 + * +------------+----------------+-------------------+-----------------+ + * | 0x04 fixed |compression type|compressed size(*2)|uncompressed size| + * +------------+----------------+-------------------+-----------------+ + * <-------------------------------(*1)-------------------------------* + * + * +15 +19 +20 +21 +23 +24 + * +-----------------+------------+----------------+----------+-----------+ + * |date/time(time_t)| 0x20 fixed |header level(=3)|file CRC16| creator | + * +-----------------+------------+----------------+----------+-----------+ + * *--------------------------------(*1)----------------------------------* + * + * +24 +28 +32 +32+(*3) + * +---------------+----------------+-------------------+-----------------+ + * |header size(*1)|next header size|extended header(*3)| compressed data | + * +---------------+----------------+-------------------+-----------------+ + * *------------------------(*1)-----------------------> <------(*2)-----> + * + */ +#define H3_FIELD_LEN_OFFSET 0 +#define H3_COMP_SIZE_OFFSET 7 +#define H3_ORIG_SIZE_OFFSET 11 +#define H3_TIME_OFFSET 15 +#define H3_CRC_OFFSET 21 +#define H3_HEADER_SIZE_OFFSET 24 +#define H3_FIXED_SIZE 28 +static int +lha_read_file_header_3(struct archive_read *a, struct lha *lha) +{ + const unsigned char *p; + size_t extdsize; + int err; + uint16_t header_crc; + + if ((p = __archive_read_ahead(a, H3_FIXED_SIZE, NULL)) == NULL) + return (truncated_error(a)); + + if (archive_le16dec(p + H3_FIELD_LEN_OFFSET) != 4) + goto invalid; + lha->header_size =archive_le32dec(p + H3_HEADER_SIZE_OFFSET); + lha->compsize = archive_le32dec(p + H3_COMP_SIZE_OFFSET); + lha->origsize = archive_le32dec(p + H3_ORIG_SIZE_OFFSET); + lha->mtime = archive_le32dec(p + H3_TIME_OFFSET); + lha->crc = archive_le16dec(p + H3_CRC_OFFSET); + lha->setflag |= CRC_IS_SET; + + if (lha->header_size < H3_FIXED_SIZE + 4) + goto invalid; + header_crc = lha_crc16(0, p, H3_FIXED_SIZE); + __archive_read_consume(a, H3_FIXED_SIZE); + + /* Read extended headers */ + err = lha_read_file_extended_header(a, lha, &header_crc, 4, + lha->header_size - H3_FIXED_SIZE, &extdsize); + if (err < ARCHIVE_WARN) + return (err); + + if (header_crc != lha->header_crc) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "LHa header CRC error"); + return (ARCHIVE_FATAL); + } + return (err); +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid LHa header"); + return (ARCHIVE_FATAL); +} + +/* + * Extended header format + * + * +0 +2 +3 -- used in header 1 and 2 + * +0 +4 +5 -- used in header 3 + * +--------------+---------+-------------------+--------------+-- + * |ex-header size|header id| data |ex-header size| ....... + * +--------------+---------+-------------------+--------------+-- + * <-------------( ex-header size)------------> <-- next extended header --* + * + * If the ex-header size is zero, it is the make of the end of extended + * headers. + * + */ +static int +lha_read_file_extended_header(struct archive_read *a, struct lha *lha, + uint16_t *crc, int sizefield_length, size_t limitsize, size_t *total_size) +{ + const void *h; + const unsigned char *extdheader; + size_t extdsize; + size_t datasize; + unsigned int i; + unsigned char extdtype; + +#define EXT_HEADER_CRC 0x00 /* Header CRC and information*/ +#define EXT_FILENAME 0x01 /* Filename */ +#define EXT_DIRECTORY 0x02 /* Directory name */ +#define EXT_DOS_ATTR 0x40 /* MS-DOS attribute */ +#define EXT_TIMESTAMP 0x41 /* Windows time stamp */ +#define EXT_FILESIZE 0x42 /* Large file size */ +#define EXT_TIMEZONE 0x43 /* Time zone */ +#define EXT_UTF16_FILENAME 0x44 /* UTF-16 filename */ +#define EXT_UTF16_DIRECTORY 0x45 /* UTF-16 directory name */ +#define EXT_CODEPAGE 0x46 /* Codepage */ +#define EXT_UNIX_MODE 0x50 /* File permission */ +#define EXT_UNIX_GID_UID 0x51 /* gid,uid */ +#define EXT_UNIX_GNAME 0x52 /* Group name */ +#define EXT_UNIX_UNAME 0x53 /* User name */ +#define EXT_UNIX_MTIME 0x54 /* Modified time */ +#define EXT_OS2_NEW_ATTR 0x7f /* new attribute(OS/2 only) */ +#define EXT_NEW_ATTR 0xff /* new attribute */ + + *total_size = sizefield_length; + + for (;;) { + /* Read an extended header size. */ + if ((h = + __archive_read_ahead(a, sizefield_length, NULL)) == NULL) + return (truncated_error(a)); + /* Check if the size is the zero indicates the end of the + * extended header. */ + if (sizefield_length == sizeof(uint16_t)) + extdsize = archive_le16dec(h); + else + extdsize = archive_le32dec(h); + if (extdsize == 0) { + /* End of extended header */ + if (crc != NULL) + *crc = lha_crc16(*crc, h, sizefield_length); + __archive_read_consume(a, sizefield_length); + return (ARCHIVE_OK); + } + + /* Sanity check to the extended header size. */ + if (((uint64_t)*total_size + extdsize) > + (uint64_t)limitsize || + extdsize <= (size_t)sizefield_length) + goto invalid; + + /* Read the extended header. */ + if ((h = __archive_read_ahead(a, extdsize, NULL)) == NULL) + return (truncated_error(a)); + *total_size += extdsize; + + extdheader = (const unsigned char *)h; + /* Get the extended header type. */ + extdtype = extdheader[sizefield_length]; + /* Calculate an extended data size. */ + datasize = extdsize - (1 + sizefield_length); + /* Skip an extended header size field and type field. */ + extdheader += sizefield_length + 1; + + if (crc != NULL && extdtype != EXT_HEADER_CRC) + *crc = lha_crc16(*crc, h, extdsize); + switch (extdtype) { + case EXT_HEADER_CRC: + /* We only use a header CRC. Following data will not + * be used. */ + if (datasize >= 2) { + lha->header_crc = archive_le16dec(extdheader); + if (crc != NULL) { + static const char zeros[2] = {0, 0}; + *crc = lha_crc16(*crc, h, + extdsize - datasize); + /* CRC value itself as zero */ + *crc = lha_crc16(*crc, zeros, 2); + *crc = lha_crc16(*crc, + extdheader+2, datasize - 2); + } + } + break; + case EXT_FILENAME: + if (datasize == 0) { + /* maybe directory header */ + archive_string_empty(&lha->filename); + break; + } + archive_strncpy(&lha->filename, + (const char *)extdheader, datasize); + break; + case EXT_DIRECTORY: + if (datasize == 0) + /* no directory name data. exit this case. */ + break; + + archive_strncpy(&lha->dirname, + (const char *)extdheader, datasize); + /* + * Convert directory delimiter from 0xFF + * to '/' for local system. + */ + for (i = 0; i < lha->dirname.length; i++) { + if ((unsigned char)lha->dirname.s[i] == 0xFF) + lha->dirname.s[i] = '/'; + } + /* Is last character directory separator? */ + if (lha->dirname.s[lha->dirname.length-1] != '/') + /* invalid directory data */ + goto invalid; + break; + case EXT_DOS_ATTR: + if (datasize == 2) + lha->dos_attr = (unsigned char) + (archive_le16dec(extdheader) & 0xff); + break; + case EXT_TIMESTAMP: + if (datasize == (sizeof(uint64_t) * 3)) { + lha->birthtime = lha_win_time( + archive_le64dec(extdheader), + &lha->birthtime_tv_nsec); + extdheader += sizeof(uint64_t); + lha->mtime = lha_win_time( + archive_le64dec(extdheader), + &lha->mtime_tv_nsec); + extdheader += sizeof(uint64_t); + lha->atime = lha_win_time( + archive_le64dec(extdheader), + &lha->atime_tv_nsec); + lha->setflag |= BIRTHTIME_IS_SET | + ATIME_IS_SET; + } + break; + case EXT_FILESIZE: + if (datasize == sizeof(uint64_t) * 2) { + lha->compsize = archive_le64dec(extdheader); + extdheader += sizeof(uint64_t); + lha->origsize = archive_le64dec(extdheader); + } + break; + case EXT_CODEPAGE: + /* Get an archived filename charset from codepage. + * This overwrites the charset specified by + * hdrcharset option. */ + if (datasize == sizeof(uint32_t)) { + struct archive_string cp; + const char *charset; + + archive_string_init(&cp); + switch (archive_le32dec(extdheader)) { + case 65001: /* UTF-8 */ + charset = "UTF-8"; + break; + default: + archive_string_sprintf(&cp, "CP%d", + (int)archive_le32dec(extdheader)); + charset = cp.s; + break; + } + lha->sconv = + archive_string_conversion_from_charset( + &(a->archive), charset, 1); + archive_string_free(&cp); + if (lha->sconv == NULL) + return (ARCHIVE_FATAL); + } + break; + case EXT_UNIX_MODE: + if (datasize == sizeof(uint16_t)) { + lha->mode = archive_le16dec(extdheader); + lha->setflag |= UNIX_MODE_IS_SET; + } + break; + case EXT_UNIX_GID_UID: + if (datasize == (sizeof(uint16_t) * 2)) { + lha->gid = archive_le16dec(extdheader); + lha->uid = archive_le16dec(extdheader+2); + } + break; + case EXT_UNIX_GNAME: + if (datasize > 0) + archive_strncpy(&lha->gname, + (const char *)extdheader, datasize); + break; + case EXT_UNIX_UNAME: + if (datasize > 0) + archive_strncpy(&lha->uname, + (const char *)extdheader, datasize); + break; + case EXT_UNIX_MTIME: + if (datasize == sizeof(uint32_t)) + lha->mtime = archive_le32dec(extdheader); + break; + case EXT_OS2_NEW_ATTR: + /* This extended header is OS/2 depend. */ + if (datasize == 16) { + lha->dos_attr = (unsigned char) + (archive_le16dec(extdheader) & 0xff); + lha->mode = archive_le16dec(extdheader+2); + lha->gid = archive_le16dec(extdheader+4); + lha->uid = archive_le16dec(extdheader+6); + lha->birthtime = archive_le32dec(extdheader+8); + lha->atime = archive_le32dec(extdheader+12); + lha->setflag |= UNIX_MODE_IS_SET + | BIRTHTIME_IS_SET | ATIME_IS_SET; + } + break; + case EXT_NEW_ATTR: + if (datasize == 20) { + lha->mode = (mode_t)archive_le32dec(extdheader); + lha->gid = archive_le32dec(extdheader+4); + lha->uid = archive_le32dec(extdheader+8); + lha->birthtime = archive_le32dec(extdheader+12); + lha->atime = archive_le32dec(extdheader+16); + lha->setflag |= UNIX_MODE_IS_SET + | BIRTHTIME_IS_SET | ATIME_IS_SET; + } + break; + case EXT_TIMEZONE: /* Not supported */ + case EXT_UTF16_FILENAME: /* Not supported */ + case EXT_UTF16_DIRECTORY: /* Not supported */ + default: + break; + } + + __archive_read_consume(a, extdsize); + } +invalid: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid extended LHa header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_lha_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + int r; + + if (lha->entry_unconsumed) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, lha->entry_unconsumed); + lha->entry_unconsumed = 0; + } + if (lha->end_of_entry) { + if (!lha->end_of_entry_cleanup) { + if ((lha->setflag & CRC_IS_SET) && + lha->crc != lha->entry_crc_calculated) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "LHa data CRC error"); + return (ARCHIVE_WARN); + } + + /* End-of-entry cleanup done. */ + lha->end_of_entry_cleanup = 1; + } + *offset = lha->entry_offset; + *size = 0; + *buff = NULL; + return (ARCHIVE_EOF); + } + + if (lha->entry_is_compressed) + r = lha_read_data_lzh(a, buff, size, offset); + else + /* No compression. */ + r = lha_read_data_none(a, buff, size, offset); + return (r); +} + +/* + * Read a file content in no compression. + * + * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets + * lha->end_of_entry if it consumes all of the data. + */ +static int +lha_read_data_none(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + ssize_t bytes_avail; + + if (lha->entry_bytes_remaining == 0) { + *buff = NULL; + *size = 0; + *offset = lha->entry_offset; + lha->end_of_entry = 1; + return (ARCHIVE_OK); + } + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + *buff = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated LHa file data"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > lha->entry_bytes_remaining) + bytes_avail = lha->entry_bytes_remaining; + lha->entry_crc_calculated = + lha_crc16(lha->entry_crc_calculated, *buff, bytes_avail); + *size = bytes_avail; + *offset = lha->entry_offset; + lha->entry_offset += bytes_avail; + lha->entry_bytes_remaining -= bytes_avail; + if (lha->entry_bytes_remaining == 0) + lha->end_of_entry = 1; + lha->entry_unconsumed = bytes_avail; + return (ARCHIVE_OK); +} + +/* + * Read a file content in LZHUFF encoding. + * + * Returns ARCHIVE_OK if successful, returns ARCHIVE_WARN if compression is + * unsupported, ARCHIVE_FATAL otherwise, sets lha->end_of_entry if it consumes + * all of the data. + */ +static int +lha_read_data_lzh(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct lha *lha = (struct lha *)(a->format->data); + ssize_t bytes_avail; + int r; + + /* If the buffer hasn't been allocated, allocate it now. */ + if (lha->uncompressed_buffer == NULL) { + lha->uncompressed_buffer_size = 64 * 1024; + lha->uncompressed_buffer + = (unsigned char *)malloc(lha->uncompressed_buffer_size); + if (lha->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for lzh decompression"); + return (ARCHIVE_FATAL); + } + } + + /* If we haven't yet read any data, initialize the decompressor. */ + if (!lha->decompress_init) { + r = lzh_decode_init(&(lha->strm), lha->method); + switch (r) { + case ARCHIVE_OK: + break; + case ARCHIVE_FAILED: + /* Unsupported compression. */ + *buff = NULL; + *size = 0; + *offset = 0; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported lzh compression method -%c%c%c-", + lha->method[0], lha->method[1], lha->method[2]); + /* We know compressed size; just skip it. */ + archive_read_format_lha_read_data_skip(a); + return (ARCHIVE_WARN); + default: + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory " + "for lzh decompression"); + return (ARCHIVE_FATAL); + } + /* We've initialized decompression for this stream. */ + lha->decompress_init = 1; + lha->strm.avail_out = 0; + lha->strm.total_out = 0; + } + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + lha->strm.next_in = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated LHa file body"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > lha->entry_bytes_remaining) + bytes_avail = lha->entry_bytes_remaining; + + lha->strm.avail_in = bytes_avail; + lha->strm.total_in = 0; + if (lha->strm.avail_out == 0) { + lha->strm.next_out = lha->uncompressed_buffer; + lha->strm.avail_out = lha->uncompressed_buffer_size; + } + + r = lzh_decode(&(lha->strm), bytes_avail == lha->entry_bytes_remaining); + switch (r) { + case ARCHIVE_OK: + break; + case ARCHIVE_EOF: + lha->end_of_entry = 1; + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Bad lzh data"); + return (ARCHIVE_FAILED); + } + lha->entry_unconsumed = lha->strm.total_in; + lha->entry_bytes_remaining -= lha->strm.total_in; + + if (lha->strm.avail_out == 0 || lha->end_of_entry) { + *offset = lha->entry_offset; + *size = lha->strm.next_out - lha->uncompressed_buffer; + *buff = lha->uncompressed_buffer; + lha->entry_crc_calculated = + lha_crc16(lha->entry_crc_calculated, *buff, *size); + lha->entry_offset += *size; + } else { + *offset = lha->entry_offset; + *size = 0; + *buff = NULL; + } + return (ARCHIVE_OK); +} + +/* + * Skip a file content. + */ +static int +archive_read_format_lha_read_data_skip(struct archive_read *a) +{ + struct lha *lha; + off_t bytes_skipped; + + lha = (struct lha *)(a->format->data); + + if (lha->entry_unconsumed) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, lha->entry_unconsumed); + lha->entry_unconsumed = 0; + } + + /* if we've already read to end of data, we're done. */ + if (lha->end_of_entry_cleanup) + return (ARCHIVE_OK); + + /* + * If the length is at the beginning, we can skip the + * compressed data much more quickly. + */ + bytes_skipped = __archive_read_consume(a, lha->entry_bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + /* This entry is finished and done. */ + lha->end_of_entry_cleanup = lha->end_of_entry = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_lha_cleanup(struct archive_read *a) +{ + struct lha *lha = (struct lha *)(a->format->data); + + lzh_decode_free(&(lha->strm)); + free(lha->uncompressed_buffer); + archive_string_free(&(lha->dirname)); + archive_string_free(&(lha->filename)); + archive_string_free(&(lha->uname)); + archive_string_free(&(lha->gname)); + archive_wstring_free(&(lha->ws)); + free(lha); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* + * 'LHa for UNIX' utility has archived a symbolic-link name after + * a pathname with '|' character. + * This function extracts the symbolic-link name from the pathname. + * + * example. + * 1. a symbolic-name is 'aaa/bb/cc' + * 2. a filename is 'xxx/bbb' + * then a archived pathname is 'xxx/bbb|aaa/bb/cc' + */ +static int +lha_parse_linkname(struct archive_string *linkname, + struct archive_string *pathname) +{ + char * linkptr; + int symlen; + + linkptr = strchr(pathname->s, '|'); + if (linkptr != NULL) { + symlen = strlen(linkptr + 1); + archive_strncpy(linkname, linkptr+1, symlen); + + *linkptr = 0; + pathname->length = strlen(pathname->s); + + return (1); + } + return (0); +} + +/* Convert an MSDOS-style date/time into Unix-style time. */ +static time_t +lha_dos_time(const unsigned char *p) +{ + int msTime, msDate; + struct tm ts; + + msTime = archive_le16dec(p); + msDate = archive_le16dec(p+2); + + memset(&ts, 0, sizeof(ts)); + ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ + ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ + ts.tm_mday = msDate & 0x1f; /* Day of month. */ + ts.tm_hour = (msTime >> 11) & 0x1f; + ts.tm_min = (msTime >> 5) & 0x3f; + ts.tm_sec = (msTime << 1) & 0x3e; + ts.tm_isdst = -1; + return (mktime(&ts)); +} + +/* Convert an MS-Windows-style date/time into Unix-style time. */ +static time_t +lha_win_time(uint64_t wintime, long *ns) +{ +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) + + if (wintime >= EPOC_TIME) { + wintime -= EPOC_TIME; /* 1970-01-01 00:00:00 (UTC) */ + if (ns != NULL) + *ns = (long)(wintime % 10000000) * 100; + return (wintime / 10000000); + } else { + if (ns != NULL) + *ns = 0; + return (0); + } +} + +static unsigned char +lha_calcsum(unsigned char sum, const void *pp, int offset, int size) +{ + unsigned char const *p = (unsigned char const *)pp; + + p += offset; + while (--size >= 0) + sum += *p++; + return (sum); +} + +#define CRC16(crc, v) do { \ + (crc) = crc16tbl[((crc) ^ v) & 0xFF] ^ ((crc) >> 8); \ +} while (0) + +static uint16_t +lha_crc16(uint16_t crc, const void *pp, size_t len) +{ + const unsigned char *buff = (const unsigned char *)pp; + + while (len >= 8) { + CRC16(crc, *buff++); CRC16(crc, *buff++); + CRC16(crc, *buff++); CRC16(crc, *buff++); + CRC16(crc, *buff++); CRC16(crc, *buff++); + CRC16(crc, *buff++); CRC16(crc, *buff++); + len -= 8; + } + switch (len) { + case 7: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 6: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 5: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 4: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 3: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 2: + CRC16(crc, *buff++); + /* FALL THROUGH */ + case 1: + CRC16(crc, *buff); + /* FALL THROUGH */ + case 0: + break; + } + return (crc); +} + + +/* + * Initialize LZHUF decoder. + * + * Returns ARCHIVE_OK if initialization was successful. + * Returns ARCHIVE_FAILED if method is unsupported. + * Returns ARCHIVE_FATAL if initialization failed; memory allocation + * error occurred. + */ +static int +lzh_decode_init(struct lzh_stream *strm, const char *method) +{ + struct lzh_dec *ds; + int w_bits, w_size; + + if (strm->ds == NULL) { + strm->ds = calloc(1, sizeof(*strm->ds)); + if (strm->ds == NULL) + return (ARCHIVE_FATAL); + } + ds = strm->ds; + ds->error = ARCHIVE_FAILED; + if (method == NULL || method[0] != 'l' || method[1] != 'h') + return (ARCHIVE_FAILED); + switch (method[2]) { + case '5': + w_bits = 13;/* 8KiB for window */ + break; + case '6': + w_bits = 15;/* 32KiB for window */ + break; + case '7': + w_bits = 16;/* 64KiB for window */ + break; + default: + return (ARCHIVE_FAILED);/* Not supported. */ + } + ds->error = ARCHIVE_FATAL; + w_size = ds->w_size; + ds->w_size = 1U << w_bits; + ds->w_mask = ds->w_size -1; + if (ds->w_buff == NULL || w_size != ds->w_size) { + free(ds->w_buff); + ds->w_buff = malloc(ds->w_size); + if (ds->w_buff == NULL) + return (ARCHIVE_FATAL); + } + memset(ds->w_buff, 0x20, ds->w_size); + ds->w_pos = 0; + ds->w_remaining = 0; + ds->state = 0; + ds->pos_pt_len_size = w_bits + 1; + ds->pos_pt_len_bits = (w_bits == 15 || w_bits == 16)? 5: 4; + ds->literal_pt_len_size = PT_BITLEN_SIZE; + ds->literal_pt_len_bits = 5; + ds->br.cache_buffer = 0; + ds->br.cache_avail = 0; + + if (lzh_huffman_init(&(ds->lt), LT_BITLEN_SIZE, 16) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + ds->lt.len_bits = 9; + if (lzh_huffman_init(&(ds->pt), PT_BITLEN_SIZE, 16) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + ds->error = 0; + + return (ARCHIVE_OK); +} + +/* + * Release LZHUF decoder. + */ +static void +lzh_decode_free(struct lzh_stream *strm) +{ + + if (strm->ds == NULL) + return; + free(strm->ds->w_buff); + lzh_huffman_free(&(strm->ds->lt)); + lzh_huffman_free(&(strm->ds->pt)); + free(strm->ds); + strm->ds = NULL; +} + +/* + * Bit stream reader. + */ +/* Check that the cache buffer has enough bits. */ +#define lzh_br_has(br, n) ((br)->cache_avail >= n) +/* Get compressed data by bit. */ +#define lzh_br_bits(br, n) \ + (((uint16_t)((br)->cache_buffer >> \ + ((br)->cache_avail - (n)))) & cache_masks[n]) +#define lzh_br_bits_forced(br, n) \ + (((uint16_t)((br)->cache_buffer << \ + ((n) - (br)->cache_avail))) & cache_masks[n]) +/* Read ahead to make sure the cache buffer has enough compressed data we + * will use. + * True : completed, there is enough data in the cache buffer. + * False : we met that strm->next_in is empty, we have to get following + * bytes. */ +#define lzh_br_read_ahead_0(strm, br, n) \ + (lzh_br_has(br, (n)) || lzh_br_fillup(strm, br)) +/* True : the cache buffer has some bits as much as we need. + * False : there are no enough bits in the cache buffer to be used, + * we have to get following bytes if we could. */ +#define lzh_br_read_ahead(strm, br, n) \ + (lzh_br_read_ahead_0((strm), (br), (n)) || lzh_br_has((br), (n))) + +/* Notify how many bits we consumed. */ +#define lzh_br_consume(br, n) ((br)->cache_avail -= (n)) +#define lzh_br_unconsume(br, n) ((br)->cache_avail += (n)) + +static const uint16_t cache_masks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000F, 0x001F, 0x003F, 0x007F, + 0x00FF, 0x01FF, 0x03FF, 0x07FF, + 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF +}; + +/* + * Shift away used bits in the cache data and fill it up with following bits. + * Call this when cache buffer does not have enough bits you need. + * + * Returns 1 if the cache buffer is full. + * Returns 0 if the cache buffer is not full; input buffer is empty. + */ +static int +lzh_br_fillup(struct lzh_stream *strm, struct lzh_br *br) +{ + int n = CACHE_BITS - br->cache_avail; + + for (;;) { + switch (n >> 3) { + case 8: + if (strm->avail_in >= 8) { + br->cache_buffer = + ((uint64_t)strm->next_in[0]) << 56 | + ((uint64_t)strm->next_in[1]) << 48 | + ((uint64_t)strm->next_in[2]) << 40 | + ((uint64_t)strm->next_in[3]) << 32 | + ((uint32_t)strm->next_in[4]) << 24 | + ((uint32_t)strm->next_in[5]) << 16 | + ((uint32_t)strm->next_in[6]) << 8 | + (uint32_t)strm->next_in[7]; + strm->next_in += 8; + strm->avail_in -= 8; + br->cache_avail += 8 * 8; + return (1); + } + break; + case 7: + if (strm->avail_in >= 7) { + br->cache_buffer = + (br->cache_buffer << 56) | + ((uint64_t)strm->next_in[0]) << 48 | + ((uint64_t)strm->next_in[1]) << 40 | + ((uint64_t)strm->next_in[2]) << 32 | + ((uint32_t)strm->next_in[3]) << 24 | + ((uint32_t)strm->next_in[4]) << 16 | + ((uint32_t)strm->next_in[5]) << 8 | + (uint32_t)strm->next_in[6]; + strm->next_in += 7; + strm->avail_in -= 7; + br->cache_avail += 7 * 8; + return (1); + } + break; + case 6: + if (strm->avail_in >= 6) { + br->cache_buffer = + (br->cache_buffer << 48) | + ((uint64_t)strm->next_in[0]) << 40 | + ((uint64_t)strm->next_in[1]) << 32 | + ((uint32_t)strm->next_in[2]) << 24 | + ((uint32_t)strm->next_in[3]) << 16 | + ((uint32_t)strm->next_in[4]) << 8 | + (uint32_t)strm->next_in[5]; + strm->next_in += 6; + strm->avail_in -= 6; + br->cache_avail += 6 * 8; + return (1); + } + break; + case 0: + /* We have enough compressed data in + * the cache buffer.*/ + return (1); + default: + break; + } + if (strm->avail_in == 0) { + /* There is not enough compressed data to fill up the + * cache buffer. */ + return (0); + } + br->cache_buffer = + (br->cache_buffer << 8) | *strm->next_in++; + strm->avail_in--; + br->cache_avail += 8; + n -= 8; + } +} + +/* + * Decode LZHUF. + * + * 1. Returns ARCHIVE_OK if output buffer or input buffer are empty. + * Please set available buffer and call this function again. + * 2. Returns ARCHIVE_EOF if decompression has been completed. + * 3. Returns ARCHIVE_FAILED if an error occurred; compressed data + * is broken or you do not set 'last' flag properly. + * 4. 'last' flag is very important, you must set 1 to the flag if there + * is no input data. The lha compressed data format does not provide how + * to know the compressed data is really finished. + * Note: lha command utility check if the total size of output bytes is + * reached the uncompressed size recorded in its header. it does not mind + * that the decoding process is properly finished. + * GNU ZIP can decompress another compressed file made by SCO LZH compress. + * it handles EOF as null to fill read buffer with zero until the decoding + * process meet 2 bytes of zeros at reading a size of a next chunk, so the + * zeros are treated as the mark of the end of the data although the zeros + * is dummy, not the file data. + */ +static int lzh_read_blocks(struct lzh_stream *, int); +static int lzh_decode_blocks(struct lzh_stream *, int); +#define ST_RD_BLOCK 0 +#define ST_RD_PT_1 1 +#define ST_RD_PT_2 2 +#define ST_RD_PT_3 3 +#define ST_RD_PT_4 4 +#define ST_RD_LITERAL_1 5 +#define ST_RD_LITERAL_2 6 +#define ST_RD_LITERAL_3 7 +#define ST_RD_POS_DATA_1 8 +#define ST_GET_LITERAL 9 +#define ST_GET_POS_1 10 +#define ST_GET_POS_2 11 +#define ST_COPY_DATA 12 + +static int +lzh_decode(struct lzh_stream *strm, int last) +{ + struct lzh_dec *ds = strm->ds; + int64_t avail_in; + int r; + + if (ds->error) + return (ds->error); + + avail_in = strm->avail_in; + do { + if (ds->state < ST_GET_LITERAL) + r = lzh_read_blocks(strm, last); + else + r = lzh_decode_blocks(strm, last); + } while (r == 100); + strm->total_in += avail_in - strm->avail_in; + return (r); +} + +static int +lzh_copy_from_window(struct lzh_stream *strm, struct lzh_dec *ds) +{ + size_t copy_bytes; + + if (ds->w_remaining == 0 && ds->w_pos > 0) { + if (ds->w_pos - ds->copy_pos <= strm->avail_out) + copy_bytes = ds->w_pos - ds->copy_pos; + else + copy_bytes = strm->avail_out; + memcpy(strm->next_out, + ds->w_buff + ds->copy_pos, copy_bytes); + ds->copy_pos += copy_bytes; + } else { + if (ds->w_remaining <= strm->avail_out) + copy_bytes = ds->w_remaining; + else + copy_bytes = strm->avail_out; + memcpy(strm->next_out, + ds->w_buff + ds->w_size - ds->w_remaining, copy_bytes); + ds->w_remaining -= copy_bytes; + } + strm->next_out += copy_bytes; + strm->avail_out -= copy_bytes; + strm->total_out += copy_bytes; + if (strm->avail_out == 0) + return (0); + else + return (1); +} + +static int +lzh_read_blocks(struct lzh_stream *strm, int last) +{ + struct lzh_dec *ds = strm->ds; + struct lzh_br *br = &(ds->br); + int c = 0, i; + unsigned rbits; + + for (;;) { + switch (ds->state) { + case ST_RD_BLOCK: + /* + * Read a block number indicates how many blocks + * we will handle. The block is composed of a + * literal and a match, sometimes a literal only + * in particular, there are no reference data at + * the beginning of the decompression. + */ + if (!lzh_br_read_ahead_0(strm, br, 16)) { + if (!last) + /* We need following data. */ + return (ARCHIVE_OK); + if (lzh_br_has(br, 8)) { + /* + * It seems there are extra bits. + * 1. Compressed data is broken. + * 2. `last' flag does not properly + * set. + */ + goto failed; + } + if (ds->w_pos > 0) { + if (!lzh_copy_from_window(strm, ds)) + return (ARCHIVE_OK); + } + /* End of compressed data; we have completely + * handled all compressed data. */ + return (ARCHIVE_EOF); + } + ds->blocks_avail = lzh_br_bits(br, 16); + if (ds->blocks_avail == 0) + goto failed; + lzh_br_consume(br, 16); + /* + * Read a literal table compressed in huffman + * coding. + */ + ds->pt.len_size = ds->literal_pt_len_size; + ds->pt.len_bits = ds->literal_pt_len_bits; + ds->reading_position = 0; + /* FALL THROUGH */ + case ST_RD_PT_1: + /* Note: ST_RD_PT_1, ST_RD_PT_2 and ST_RD_PT_4 are + * used in reading both a literal table and a + * position table. */ + if (!lzh_br_read_ahead(strm, br, ds->pt.len_bits)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_PT_1; + return (ARCHIVE_OK); + } + ds->pt.len_avail = lzh_br_bits(br, ds->pt.len_bits); + lzh_br_consume(br, ds->pt.len_bits); + /* FALL THROUGH */ + case ST_RD_PT_2: + if (ds->pt.len_avail == 0) { + /* There is no bitlen. */ + if (!lzh_br_read_ahead(strm, br, + ds->pt.len_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->state = ST_RD_PT_2; + return (ARCHIVE_OK); + } + if (!lzh_make_fake_table(&(ds->pt), + lzh_br_bits(br, ds->pt.len_bits))) + goto failed;/* Invalid data. */ + lzh_br_consume(br, ds->pt.len_bits); + if (ds->reading_position) + ds->state = ST_GET_LITERAL; + else + ds->state = ST_RD_LITERAL_1; + break; + } else if (ds->pt.len_avail > ds->pt.len_size) + goto failed;/* Invalid data. */ + ds->loop = 0; + memset(ds->pt.freq, 0, sizeof(ds->pt.freq)); + if (ds->pt.len_avail < 3 || + ds->pt.len_size == ds->pos_pt_len_size) { + ds->state = ST_RD_PT_4; + break; + } + /* FALL THROUGH */ + case ST_RD_PT_3: + ds->loop = lzh_read_pt_bitlen(strm, ds->loop, 3); + if (ds->loop < 3) { + if (ds->loop < 0 || last) + goto failed;/* Invalid data. */ + /* Not completed, get following data. */ + ds->state = ST_RD_PT_3; + return (ARCHIVE_OK); + } + /* There are some null in bitlen of the literal. */ + if (!lzh_br_read_ahead(strm, br, 2)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_PT_3; + return (ARCHIVE_OK); + } + c = lzh_br_bits(br, 2); + lzh_br_consume(br, 2); + if (c > ds->pt.len_avail - 3) + goto failed;/* Invalid data. */ + for (i = 3; c-- > 0 ;) + ds->pt.bitlen[i++] = 0; + ds->loop = i; + /* FALL THROUGH */ + case ST_RD_PT_4: + ds->loop = lzh_read_pt_bitlen(strm, ds->loop, + ds->pt.len_avail); + if (ds->loop < ds->pt.len_avail) { + if (ds->loop < 0 || last) + goto failed;/* Invalid data. */ + /* Not completed, get following data. */ + ds->state = ST_RD_PT_4; + return (ARCHIVE_OK); + } + if (!lzh_make_huffman_table(&(ds->pt))) + goto failed;/* Invalid data */ + if (ds->reading_position) { + ds->state = ST_GET_LITERAL; + break; + } + /* FALL THROUGH */ + case ST_RD_LITERAL_1: + if (!lzh_br_read_ahead(strm, br, ds->lt.len_bits)) { + if (last) + goto failed;/* Truncated data. */ + ds->state = ST_RD_LITERAL_1; + return (ARCHIVE_OK); + } + ds->lt.len_avail = lzh_br_bits(br, ds->lt.len_bits); + lzh_br_consume(br, ds->lt.len_bits); + /* FALL THROUGH */ + case ST_RD_LITERAL_2: + if (ds->lt.len_avail == 0) { + /* There is no bitlen. */ + if (!lzh_br_read_ahead(strm, br, + ds->lt.len_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->state = ST_RD_LITERAL_2; + return (ARCHIVE_OK); + } + if (!lzh_make_fake_table(&(ds->lt), + lzh_br_bits(br, ds->lt.len_bits))) + goto failed;/* Invalid data */ + lzh_br_consume(br, ds->lt.len_bits); + ds->state = ST_RD_POS_DATA_1; + break; + } else if (ds->lt.len_avail > ds->lt.len_size) + goto failed;/* Invalid data */ + ds->loop = 0; + memset(ds->lt.freq, 0, sizeof(ds->lt.freq)); + /* FALL THROUGH */ + case ST_RD_LITERAL_3: + i = ds->loop; + while (i < ds->lt.len_avail) { + if (!lzh_br_read_ahead(strm, br, + ds->pt.max_bits)) { + if (last) + goto failed;/* Truncated data.*/ + ds->loop = i; + ds->state = ST_RD_LITERAL_3; + return (ARCHIVE_OK); + } + rbits = lzh_br_bits(br, ds->pt.max_bits); + c = lzh_decode_huffman(&(ds->pt), rbits); + if (c > 2) { + /* Note: 'c' will never be more than + * eighteen since it's limited by + * PT_BITLEN_SIZE, which is being set + * to ds->pt.len_size through + * ds->literal_pt_len_size. */ + lzh_br_consume(br, ds->pt.bitlen[c]); + c -= 2; + ds->lt.freq[c]++; + ds->lt.bitlen[i++] = c; + } else if (c == 0) { + lzh_br_consume(br, ds->pt.bitlen[c]); + ds->lt.bitlen[i++] = 0; + } else { + /* c == 1 or c == 2 */ + int n = (c == 1)?4:9; + if (!lzh_br_read_ahead(strm, br, + ds->pt.bitlen[c] + n)) { + if (last) /* Truncated data. */ + goto failed; + ds->loop = i; + ds->state = ST_RD_LITERAL_3; + return (ARCHIVE_OK); + } + lzh_br_consume(br, ds->pt.bitlen[c]); + c = lzh_br_bits(br, n); + lzh_br_consume(br, n); + c += (n == 4)?3:20; + if (i + c > ds->lt.len_avail) + goto failed;/* Invalid data */ + memset(&(ds->lt.bitlen[i]), 0, c); + i += c; + } + } + if (i > ds->lt.len_avail || + !lzh_make_huffman_table(&(ds->lt))) + goto failed;/* Invalid data */ + /* FALL THROUGH */ + case ST_RD_POS_DATA_1: + /* + * Read a position table compressed in huffman + * coding. + */ + ds->pt.len_size = ds->pos_pt_len_size; + ds->pt.len_bits = ds->pos_pt_len_bits; + ds->reading_position = 1; + ds->state = ST_RD_PT_1; + break; + case ST_GET_LITERAL: + return (100); + } + } +failed: + return (ds->error = ARCHIVE_FAILED); +} + +static int +lzh_decode_blocks(struct lzh_stream *strm, int last) +{ + struct lzh_dec *ds = strm->ds; + struct lzh_br bre = ds->br; + struct huffman *lt = &(ds->lt); + struct huffman *pt = &(ds->pt); + unsigned char *w_buff = ds->w_buff; + unsigned char *lt_bitlen = lt->bitlen; + unsigned char *pt_bitlen = pt->bitlen; + int blocks_avail = ds->blocks_avail, c = 0; + int copy_len = ds->copy_len, copy_pos = ds->copy_pos; + int w_pos = ds->w_pos, w_mask = ds->w_mask, w_size = ds->w_size; + int lt_max_bits = lt->max_bits, pt_max_bits = pt->max_bits; + int state = ds->state; + + if (ds->w_remaining > 0) { + if (!lzh_copy_from_window(strm, ds)) + goto next_data; + } + for (;;) { + switch (state) { + case ST_GET_LITERAL: + for (;;) { + if (blocks_avail == 0) { + /* We have decoded all blocks. + * Let's handle next blocks. */ + ds->state = ST_RD_BLOCK; + ds->br = bre; + ds->blocks_avail = 0; + ds->w_pos = w_pos; + ds->copy_pos = 0; + return (100); + } + + /* lzh_br_read_ahead() always try to fill the + * cache buffer up. In specific situation we + * are close to the end of the data, the cache + * buffer will not be full and thus we have to + * determine if the cache buffer has some bits + * as much as we need after lzh_br_read_ahead() + * failed. */ + if (!lzh_br_read_ahead(strm, &bre, + lt_max_bits)) { + if (!last) + goto next_data; + /* Remaining bits are less than + * maximum bits(lt.max_bits) but maybe + * it still remains as much as we need, + * so we should try to use it with + * dummy bits. */ + c = lzh_decode_huffman(lt, + lzh_br_bits_forced(&bre, + lt_max_bits)); + lzh_br_consume(&bre, lt_bitlen[c]); + if (!lzh_br_has(&bre, 0)) + goto failed;/* Over read. */ + } else { + c = lzh_decode_huffman(lt, + lzh_br_bits(&bre, lt_max_bits)); + lzh_br_consume(&bre, lt_bitlen[c]); + } + blocks_avail--; + if (c > UCHAR_MAX) + /* Current block is a match data. */ + break; + /* + * 'c' is exactly a literal code. + */ + /* Save a decoded code to reference it + * afterward. */ + w_buff[w_pos] = c; + if (++w_pos >= w_size) { + w_pos = 0; + ds->w_remaining = w_size; + if (!lzh_copy_from_window(strm, ds)) + goto next_data; + } + } + /* 'c' is the length of a match pattern we have + * already extracted, which has be stored in + * window(ds->w_buff). */ + copy_len = c - (UCHAR_MAX + 1) + MINMATCH; + /* FALL THROUGH */ + case ST_GET_POS_1: + /* + * Get a reference position. + */ + if (!lzh_br_read_ahead(strm, &bre, pt_max_bits)) { + if (!last) { + state = ST_GET_POS_1; + ds->copy_len = copy_len; + goto next_data; + } + copy_pos = lzh_decode_huffman(pt, + lzh_br_bits_forced(&bre, pt_max_bits)); + lzh_br_consume(&bre, pt_bitlen[copy_pos]); + if (!lzh_br_has(&bre, 0)) + goto failed;/* Over read. */ + } else { + copy_pos = lzh_decode_huffman(pt, + lzh_br_bits(&bre, pt_max_bits)); + lzh_br_consume(&bre, pt_bitlen[copy_pos]); + } + /* FALL THROUGH */ + case ST_GET_POS_2: + if (copy_pos > 1) { + /* We need an additional adjustment number to + * the position. */ + int p = copy_pos - 1; + if (!lzh_br_read_ahead(strm, &bre, p)) { + if (last) + goto failed;/* Truncated data.*/ + state = ST_GET_POS_2; + ds->copy_len = copy_len; + ds->copy_pos = copy_pos; + goto next_data; + } + copy_pos = (1 << p) + lzh_br_bits(&bre, p); + lzh_br_consume(&bre, p); + } + /* The position is actually a distance from the last + * code we had extracted and thus we have to convert + * it to a position of the window. */ + copy_pos = (w_pos - copy_pos - 1) & w_mask; + /* FALL THROUGH */ + case ST_COPY_DATA: + /* + * Copy `copy_len' bytes as extracted data from + * the window into the output buffer. + */ + for (;;) { + int l; + + l = copy_len; + if (copy_pos > w_pos) { + if (l > w_size - copy_pos) + l = w_size - copy_pos; + } else { + if (l > w_size - w_pos) + l = w_size - w_pos; + } + if ((copy_pos + l < w_pos) + || (w_pos + l < copy_pos)) { + /* No overlap. */ + memcpy(w_buff + w_pos, + w_buff + copy_pos, l); + } else { + const unsigned char *s; + unsigned char *d; + int li; + + d = w_buff + w_pos; + s = w_buff + copy_pos; + for (li = 0; li < l; li++) + d[li] = s[li]; + } + w_pos = (w_pos + l) & w_mask; + if (w_pos == 0) { + ds->w_remaining = w_size; + if (!lzh_copy_from_window(strm, ds)) { + if (copy_len <= l) + state = ST_GET_LITERAL; + else { + state = ST_COPY_DATA; + ds->copy_len = + copy_len - l; + ds->copy_pos = + (copy_pos + l) + & w_mask; + } + goto next_data; + } + } + if (copy_len <= l) + /* A copy of current pattern ended. */ + break; + copy_len -= l; + copy_pos = (copy_pos + l) & w_mask; + } + state = ST_GET_LITERAL; + break; + } + } +failed: + return (ds->error = ARCHIVE_FAILED); +next_data: + ds->br = bre; + ds->blocks_avail = blocks_avail; + ds->state = state; + ds->w_pos = w_pos; + return (ARCHIVE_OK); +} + +static int +lzh_huffman_init(struct huffman *hf, size_t len_size, int tbl_bits) +{ + int bits; + + if (hf->bitlen == NULL) { + hf->bitlen = malloc(len_size * sizeof(hf->bitlen[0])); + if (hf->bitlen == NULL) + return (ARCHIVE_FATAL); + } + if (hf->tbl == NULL) { + if (tbl_bits < HTBL_BITS) + bits = tbl_bits; + else + bits = HTBL_BITS; + hf->tbl = malloc((1 << bits) * sizeof(hf->tbl[0])); + if (hf->tbl == NULL) + return (ARCHIVE_FATAL); + } + if (hf->tree == NULL && tbl_bits > HTBL_BITS) { + hf->tree_avail = 1 << (tbl_bits - HTBL_BITS + 4); + hf->tree = malloc(hf->tree_avail * sizeof(hf->tree[0])); + if (hf->tree == NULL) + return (ARCHIVE_FATAL); + } + hf->len_size = len_size; + hf->tbl_bits = tbl_bits; + return (ARCHIVE_OK); +} + +static void +lzh_huffman_free(struct huffman *hf) +{ + free(hf->bitlen); + free(hf->tbl); + free(hf->tree); +} + +static int +lzh_read_pt_bitlen(struct lzh_stream *strm, int start, int end) +{ + struct lzh_dec *ds = strm->ds; + struct lzh_br * br = &(ds->br); + int c, i; + + for (i = start; i < end;) { + /* + * bit pattern the number we need + * 000 -> 0 + * 001 -> 1 + * 010 -> 2 + * ... + * 110 -> 6 + * 1110 -> 7 + * 11110 -> 8 + * ... + * 1111111111110 -> 16 + */ + if (!lzh_br_read_ahead(strm, br, 3)) + return (i); + if ((c = lzh_br_bits(br, 3)) == 7) { + int d; + if (!lzh_br_read_ahead(strm, br, 13)) + return (i); + d = lzh_br_bits(br, 13); + while (d & 0x200) { + c++; + d <<= 1; + } + if (c > 16) + return (-1);/* Invalid data. */ + lzh_br_consume(br, c - 3); + } else + lzh_br_consume(br, 3); + ds->pt.bitlen[i++] = c; + ds->pt.freq[c]++; + } + return (i); +} + +static int +lzh_make_fake_table(struct huffman *hf, uint16_t c) +{ + if (c >= hf->len_size) + return (0); + hf->tbl[0] = c; + hf->max_bits = 0; + hf->shift_bits = 0; + hf->bitlen[hf->tbl[0]] = 0; + return (1); +} + +/* + * Make a huffman coding table. + */ +static int +lzh_make_huffman_table(struct huffman *hf) +{ + uint16_t *tbl; + const unsigned char *bitlen; + int bitptn[17], weight[17]; + int i, maxbits = 0, ptn, tbl_size, w; + int diffbits, len_avail; + + /* + * Initialize bit patterns. + */ + ptn = 0; + for (i = 1, w = 1 << 15; i <= 16; i++, w >>= 1) { + bitptn[i] = ptn; + weight[i] = w; + if (hf->freq[i]) { + ptn += hf->freq[i] * w; + maxbits = i; + } + } + if (ptn != 0x10000 || maxbits > hf->tbl_bits) + return (0);/* Invalid */ + + hf->max_bits = maxbits; + + /* + * Cut out extra bits which we won't house in the table. + * This preparation reduces the same calculation in the for-loop + * making the table. + */ + if (maxbits < 16) { + int ebits = 16 - maxbits; + for (i = 1; i <= maxbits; i++) { + bitptn[i] >>= ebits; + weight[i] >>= ebits; + } + } + if (maxbits > HTBL_BITS) { + int htbl_max; + uint16_t *p; + + diffbits = maxbits - HTBL_BITS; + for (i = 1; i <= HTBL_BITS; i++) { + bitptn[i] >>= diffbits; + weight[i] >>= diffbits; + } + htbl_max = bitptn[HTBL_BITS] + + weight[HTBL_BITS] * hf->freq[HTBL_BITS]; + p = &(hf->tbl[htbl_max]); + while (p < &hf->tbl[1U<shift_bits = diffbits; + + /* + * Make the table. + */ + tbl_size = 1 << HTBL_BITS; + tbl = hf->tbl; + bitlen = hf->bitlen; + len_avail = hf->len_avail; + hf->tree_used = 0; + for (i = 0; i < len_avail; i++) { + uint16_t *p; + int len, cnt; + uint16_t bit; + int extlen; + struct htree_t *ht; + + if (bitlen[i] == 0) + continue; + /* Get a bit pattern */ + len = bitlen[i]; + ptn = bitptn[len]; + cnt = weight[len]; + if (len <= HTBL_BITS) { + /* Calculate next bit pattern */ + if ((bitptn[len] = ptn + cnt) > tbl_size) + return (0);/* Invalid */ + /* Update the table */ + p = &(tbl[ptn]); + while (--cnt >= 0) + p[cnt] = (uint16_t)i; + continue; + } + + /* + * A bit length is too big to be housed to a direct table, + * so we use a tree model for its extra bits. + */ + bitptn[len] = ptn + cnt; + bit = 1U << (diffbits -1); + extlen = len - HTBL_BITS; + + p = &(tbl[ptn >> diffbits]); + if (*p == 0) { + *p = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + if (*p < len_avail || + *p >= (len_avail + hf->tree_used)) + return (0);/* Invalid */ + ht = &(hf->tree[*p - len_avail]); + } + while (--extlen > 0) { + if (ptn & bit) { + if (ht->left < len_avail) { + ht->left = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + ht = &(hf->tree[ht->left - len_avail]); + } + } else { + if (ht->right < len_avail) { + ht->right = len_avail + hf->tree_used; + ht = &(hf->tree[hf->tree_used++]); + if (hf->tree_used > hf->tree_avail) + return (0);/* Invalid */ + ht->left = 0; + ht->right = 0; + } else { + ht = &(hf->tree[ht->right - len_avail]); + } + } + bit >>= 1; + } + if (ptn & bit) { + if (ht->left != 0) + return (0);/* Invalid */ + ht->left = (uint16_t)i; + } else { + if (ht->right != 0) + return (0);/* Invalid */ + ht->right = (uint16_t)i; + } + } + return (1); +} + +static int +lzh_decode_huffman_tree(struct huffman *hf, unsigned rbits, int c) +{ + struct htree_t *ht; + int extlen; + + ht = hf->tree; + extlen = hf->shift_bits; + while (c >= hf->len_avail) { + c -= hf->len_avail; + if (extlen-- <= 0 || c >= hf->tree_used) + return (0); + if (rbits & (1U << extlen)) + c = ht[c].left; + else + c = ht[c].right; + } + return (c); +} + +static inline int +lzh_decode_huffman(struct huffman *hf, unsigned rbits) +{ + int c; + /* + * At first search an index table for a bit pattern. + * If it fails, search a huffman tree for. + */ + c = hf->tbl[rbits >> hf->shift_bits]; + if (c < hf->len_avail) + return (c); + /* This bit pattern needs to be found out at a huffman tree. */ + return (lzh_decode_huffman_tree(hf, rbits, c)); +} + diff --git a/libarchive/archive_read_support_format_mtree.c b/libarchive/archive_read_support_format_mtree.c new file mode 100644 index 0000000..6ac7db8 --- /dev/null +++ b/libarchive/archive_read_support_format_mtree.c @@ -0,0 +1,1744 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2008 Joerg Sonnenberger + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_mtree.c 201165 2009-12-29 05:52:13Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#include +/* #include */ /* See archive_platform.h */ +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_string.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define MTREE_HAS_DEVICE 0x0001 +#define MTREE_HAS_FFLAGS 0x0002 +#define MTREE_HAS_GID 0x0004 +#define MTREE_HAS_GNAME 0x0008 +#define MTREE_HAS_MTIME 0x0010 +#define MTREE_HAS_NLINK 0x0020 +#define MTREE_HAS_PERM 0x0040 +#define MTREE_HAS_SIZE 0x0080 +#define MTREE_HAS_TYPE 0x0100 +#define MTREE_HAS_UID 0x0200 +#define MTREE_HAS_UNAME 0x0400 + +#define MTREE_HAS_OPTIONAL 0x0800 + +struct mtree_option { + struct mtree_option *next; + char *value; +}; + +struct mtree_entry { + struct mtree_entry *next; + struct mtree_option *options; + char *name; + char full; + char used; +}; + +struct mtree { + struct archive_string line; + size_t buffsize; + char *buff; + int64_t offset; + int fd; + int archive_format; + const char *archive_format_name; + struct mtree_entry *entries; + struct mtree_entry *this_entry; + struct archive_string current_dir; + struct archive_string contents_name; + + struct archive_entry_linkresolver *resolver; + + int64_t cur_size; +}; + +static int cleanup(struct archive_read *); +static int mtree_bid(struct archive_read *, int); +static int parse_file(struct archive_read *, struct archive_entry *, + struct mtree *, struct mtree_entry *, int *); +static void parse_escapes(char *, struct mtree_entry *); +static int parse_line(struct archive_read *, struct archive_entry *, + struct mtree *, struct mtree_entry *, int *); +static int parse_keyword(struct archive_read *, struct mtree *, + struct archive_entry *, struct mtree_option *, int *); +static int read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset); +static ssize_t readline(struct archive_read *, struct mtree *, char **, ssize_t); +static int skip(struct archive_read *a); +static int read_header(struct archive_read *, + struct archive_entry *); +static int64_t mtree_atol10(char **); +static int64_t mtree_atol8(char **); +static int64_t mtree_atol(char **); + +/* + * There's no standard for TIME_T_MAX/TIME_T_MIN. So we compute them + * here. TODO: Move this to configure time, but be careful + * about cross-compile environments. + */ +static int64_t +get_time_t_max(void) +{ +#if defined(TIME_T_MAX) + return TIME_T_MAX; +#else + static time_t t; + time_t a; + if (t == 0) { + a = 1; + while (a > t) { + t = a; + a = a * 2 + 1; + } + } + return t; +#endif +} + +static int64_t +get_time_t_min(void) +{ +#if defined(TIME_T_MIN) + return TIME_T_MIN; +#else + /* 't' will hold the minimum value, which will be zero (if + * time_t is unsigned) or -2^n (if time_t is signed). */ + static int computed; + static time_t t; + time_t a; + if (computed == 0) { + a = (time_t)-1; + while (a < t) { + t = a; + a = a * 2; + } + computed = 1; + } + return t; +#endif +} + +static void +free_options(struct mtree_option *head) +{ + struct mtree_option *next; + + for (; head != NULL; head = next) { + next = head->next; + free(head->value); + free(head); + } +} + +int +archive_read_support_format_mtree(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct mtree *mtree; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_mtree"); + + mtree = (struct mtree *)malloc(sizeof(*mtree)); + if (mtree == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate mtree data"); + return (ARCHIVE_FATAL); + } + memset(mtree, 0, sizeof(*mtree)); + mtree->fd = -1; + + r = __archive_read_register_format(a, mtree, "mtree", + mtree_bid, NULL, read_header, read_data, skip, cleanup); + + if (r != ARCHIVE_OK) + free(mtree); + return (ARCHIVE_OK); +} + +static int +cleanup(struct archive_read *a) +{ + struct mtree *mtree; + struct mtree_entry *p, *q; + + mtree = (struct mtree *)(a->format->data); + + p = mtree->entries; + while (p != NULL) { + q = p->next; + free(p->name); + free_options(p->options); + free(p); + p = q; + } + archive_string_free(&mtree->line); + archive_string_free(&mtree->current_dir); + archive_string_free(&mtree->contents_name); + archive_entry_linkresolver_free(mtree->resolver); + + free(mtree->buff); + free(mtree); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static ssize_t +get_line_size(const char *b, ssize_t avail, ssize_t *nlsize) +{ + ssize_t len; + + len = 0; + while (len < avail) { + switch (*b) { + case '\0':/* Non-ascii character or control character. */ + if (nlsize != NULL) + *nlsize = 0; + return (-1); + case '\r': + if (avail-len > 1 && b[1] == '\n') { + if (nlsize != NULL) + *nlsize = 2; + return (len+2); + } + /* FALL THROUGH */ + case '\n': + if (nlsize != NULL) + *nlsize = 1; + return (len+1); + default: + b++; + len++; + break; + } + } + if (nlsize != NULL) + *nlsize = 0; + return (avail); +} + +static ssize_t +next_line(struct archive_read *a, + const char **b, ssize_t *avail, ssize_t *ravail, ssize_t *nl) +{ + ssize_t len; + int quit; + + quit = 0; + if (*avail == 0) { + *nl = 0; + len = 0; + } else + len = get_line_size(*b, *avail, nl); + /* + * Read bytes more while it does not reach the end of line. + */ + while (*nl == 0 && len == *avail && !quit) { + ssize_t diff = *ravail - *avail; + size_t nbytes_req = (*ravail+1023) & ~1023U; + ssize_t tested; + + /* Increase reading bytes if it is not enough to at least + * new two lines. */ + if (nbytes_req < (size_t)*ravail + 160) + nbytes_req <<= 1; + + *b = __archive_read_ahead(a, nbytes_req, avail); + if (*b == NULL) { + if (*ravail >= *avail) + return (0); + /* Reading bytes reaches the end of file. */ + *b = __archive_read_ahead(a, *avail, avail); + quit = 1; + } + *ravail = *avail; + *b += diff; + *avail -= diff; + tested = len;/* Skip some bytes we already determinated. */ + len = get_line_size(*b, *avail, nl); + if (len >= 0) + len += tested; + } + return (len); +} + +/* + * Compare characters with a mtree keyword. + * Returns the length of a mtree keyword if matched. + * Returns 0 if not matched. + */ +int +bid_keycmp(const char *p, const char *key, ssize_t len) +{ + int match_len = 0; + + while (len > 0 && *p && *key) { + if (*p == *key) { + --len; + ++p; + ++key; + ++match_len; + continue; + } + return (0);/* Not match */ + } + if (*key != '\0') + return (0);/* Not match */ + + /* A following character should be specified characters */ + if (p[0] == '=' || p[0] == ' ' || p[0] == '\t' || + p[0] == '\n' || p[0] == '\r' || + (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r'))) + return (match_len); + return (0);/* Not match */ +} + +/* + * Test whether the characters 'p' has is mtree keyword. + * Returns the length of a detected keyword. + * Returns 0 if any keywords were not found. + */ +static ssize_t +bid_keyword(const char *p, ssize_t len) +{ + static const char *keys_c[] = { + "content", "contents", "cksum", NULL + }; + static const char *keys_df[] = { + "device", "flags", NULL + }; + static const char *keys_g[] = { + "gid", "gname", NULL + }; + static const char *keys_il[] = { + "ignore", "link", NULL + }; + static const char *keys_m[] = { + "md5", "md5digest", "mode", NULL + }; + static const char *keys_no[] = { + "nlink", "optional", NULL + }; + static const char *keys_r[] = { + "rmd160", "rmd160digest", NULL + }; + static const char *keys_s[] = { + "sha1", "sha1digest", + "sha256", "sha256digest", + "sha384", "sha384digest", + "sha512", "sha512digest", + "size", NULL + }; + static const char *keys_t[] = { + "tags", "time", "type", NULL + }; + static const char *keys_u[] = { + "uid", "uname", NULL + }; + const char **keys; + int i; + + switch (*p) { + case 'c': keys = keys_c; break; + case 'd': case 'f': keys = keys_df; break; + case 'g': keys = keys_g; break; + case 'i': case 'l': keys = keys_il; break; + case 'm': keys = keys_m; break; + case 'n': case 'o': keys = keys_no; break; + case 'r': keys = keys_r; break; + case 's': keys = keys_s; break; + case 't': keys = keys_t; break; + case 'u': keys = keys_u; break; + default: return (0);/* Unknown key */ + } + + for (i = 0; keys[i] != NULL; i++) { + int l = bid_keycmp(p, keys[i], len); + if (l > 0) + return (l); + } + return (0);/* Unkown key */ +} + +/* + * Test whether there is a set of mtree keywords. + * Returns the number of keyword. + * Returns -1 if we got incorrect sequence. + * This function expects a set of "keyword=value". + * When "unset" is specified, expects a set of "keyword". + */ +static int +bid_keyword_list(const char *p, ssize_t len, int unset) +{ + int l; + int keycnt = 0; + + while (len > 0 && *p) { + int blank = 0; + + /* Test whether there are blank characters in the line. */ + while (len >0 && (*p == ' ' || *p == '\t')) { + ++p; + --len; + blank = 1; + } + if (*p == '\n' || *p == '\r') + break; + if (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r')) + break; + if (!blank) /* No blank character. */ + return (-1); + + if (unset) { + l = bid_keycmp(p, "all", len); + if (l > 0) + return (1); + } + /* Test whether there is a correct key in the line. */ + l = bid_keyword(p, len); + if (l == 0) + return (-1);/* Unknown keyword was found. */ + p += l; + len -= l; + keycnt++; + + /* Skip value */ + if (*p == '=') { + int value = 0; + ++p; + --len; + while (len > 0 && *p != ' ' && *p != '\t') { + ++p; + --len; + value = 1; + } + /* A keyword should have a its value unless + * "/unset" operation. */ + if (!unset && value == 0) + return (-1); + } + } + return (keycnt); +} + +static int +bid_entry(const char *p, ssize_t len) +{ + int f = 0; + static const unsigned char safe_char[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */ + /* !"$%&'()*+,-./ EXCLUSION:( )(#) */ + 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */ + /* 0123456789:;<>? EXCLUSION:(=) */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, /* 30 - 3F */ + /* @ABCDEFGHIJKLMNO */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */ + /* PQRSTUVWXYZ[\]^_ */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */ + /* `abcdefghijklmno */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */ + /* pqrstuvwxyz{|}~ */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ + }; + + /* + * Skip the path-name which is quoted. + */ + while (len > 0 && *p != ' ' && *p != '\t') { + if (!safe_char[*(const unsigned char *)p]) + return (-1); + ++p; + --len; + ++f; + } + /* If a path-name was not found, returns error. */ + if (f == 0) + return (-1); + + return (bid_keyword_list(p, len, 0)); +} + +#define MAX_BID_ENTRY 3 + +static int +mtree_bid(struct archive_read *a, int best_bid) +{ + const char *signature = "#mtree"; + const char *p; + ssize_t avail, ravail; + ssize_t len, nl; + int detected_bytes = 0, entry_cnt = 0, multiline = 0; + + (void)best_bid; /* UNUSED */ + + /* Now let's look at the actual header and see if it matches. */ + p = __archive_read_ahead(a, strlen(signature), &avail); + if (p == NULL) + return (-1); + + if (memcmp(p, signature, strlen(signature)) == 0) + return (8 * (int)strlen(signature)); + + /* + * There is not a mtree signature. Let's try to detect mtree format. + */ + ravail = avail; + for (;;) { + len = next_line(a, &p, &avail, &ravail, &nl); + /* The terminal character of the line should be + * a new line character, '\r\n' or '\n'. */ + if (len <= 0 || nl == 0) + break; + if (!multiline) { + /* Leading whitespace is never significant, + * ignore it. */ + while (len > 0 && (*p == ' ' || *p == '\t')) { + ++p; + --avail; + --len; + } + /* Skip comment or empty line. */ + if (p[0] == '#' || p[0] == '\n' || p[0] == '\r') { + p += len; + avail -= len; + continue; + } + } else { + /* A continuance line; the terminal + * character of previous line was '\' character. */ + if (bid_keyword_list(p, len, 0) <= 0) + break; + if (multiline == 1) + detected_bytes += len; + if (p[len-nl-1] != '\\') { + if (multiline == 1 && + ++entry_cnt >= MAX_BID_ENTRY) + break; + multiline = 0; + } + p += len; + avail -= len; + continue; + } + if (p[0] != '/') { + if (bid_entry(p, len) >= 0) { + detected_bytes += len; + if (p[len-nl-1] == '\\') + /* This line continues. */ + multiline = 1; + else { + /* We've got plenty of correct lines + * to assume that this file is a mtree + * format. */ + if (++entry_cnt >= MAX_BID_ENTRY) + break; + } + } else + break; + } else if (strncmp(p, "/set", 4) == 0) { + if (bid_keyword_list(p+4, len-4, 0) <= 0) + break; + /* This line continues. */ + if (p[len-nl-1] == '\\') + multiline = 2; + } else if (strncmp(p, "/unset", 6) == 0) { + if (bid_keyword_list(p+6, len-6, 1) <= 0) + break; + /* This line continues. */ + if (p[len-nl-1] == '\\') + multiline = 2; + } else + break; + + /* Test next line. */ + p += len; + avail -= len; + } + if (entry_cnt >= MAX_BID_ENTRY || (entry_cnt > 0 && len == 0)) + return (32); + + return (0); +} + +/* + * The extended mtree format permits multiple lines specifying + * attributes for each file. For those entries, only the last line + * is actually used. Practically speaking, that means we have + * to read the entire mtree file into memory up front. + * + * The parsing is done in two steps. First, it is decided if a line + * changes the global defaults and if it is, processed accordingly. + * Otherwise, the options of the line are merged with the current + * global options. + */ +static int +add_option(struct archive_read *a, struct mtree_option **global, + const char *value, size_t len) +{ + struct mtree_option *opt; + + if ((opt = malloc(sizeof(*opt))) == NULL) { + archive_set_error(&a->archive, errno, "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + if ((opt->value = malloc(len + 1)) == NULL) { + free(opt); + archive_set_error(&a->archive, errno, "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + memcpy(opt->value, value, len); + opt->value[len] = '\0'; + opt->next = *global; + *global = opt; + return (ARCHIVE_OK); +} + +static void +remove_option(struct mtree_option **global, const char *value, size_t len) +{ + struct mtree_option *iter, *last; + + last = NULL; + for (iter = *global; iter != NULL; last = iter, iter = iter->next) { + if (strncmp(iter->value, value, len) == 0 && + (iter->value[len] == '\0' || + iter->value[len] == '=')) + break; + } + if (iter == NULL) + return; + if (last == NULL) + *global = iter->next; + else + last->next = iter->next; + + free(iter->value); + free(iter); +} + +static int +process_global_set(struct archive_read *a, + struct mtree_option **global, const char *line) +{ + const char *next, *eq; + size_t len; + int r; + + line += 4; + for (;;) { + next = line + strspn(line, " \t\r\n"); + if (*next == '\0') + return (ARCHIVE_OK); + line = next; + next = line + strcspn(line, " \t\r\n"); + eq = strchr(line, '='); + if (eq > next) + len = next - line; + else + len = eq - line; + + remove_option(global, line, len); + r = add_option(a, global, line, next - line); + if (r != ARCHIVE_OK) + return (r); + line = next; + } +} + +static int +process_global_unset(struct archive_read *a, + struct mtree_option **global, const char *line) +{ + const char *next; + size_t len; + + line += 6; + if (strchr(line, '=') != NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "/unset shall not contain `='"); + return ARCHIVE_FATAL; + } + + for (;;) { + next = line + strspn(line, " \t\r\n"); + if (*next == '\0') + return (ARCHIVE_OK); + line = next; + len = strcspn(line, " \t\r\n"); + + if (len == 3 && strncmp(line, "all", 3) == 0) { + free_options(*global); + *global = NULL; + } else { + remove_option(global, line, len); + } + + line += len; + } +} + +static int +process_add_entry(struct archive_read *a, struct mtree *mtree, + struct mtree_option **global, const char *line, + struct mtree_entry **last_entry) +{ + struct mtree_entry *entry; + struct mtree_option *iter; + const char *next, *eq; + size_t len; + int r; + + if ((entry = malloc(sizeof(*entry))) == NULL) { + archive_set_error(&a->archive, errno, "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + entry->next = NULL; + entry->options = NULL; + entry->name = NULL; + entry->used = 0; + entry->full = 0; + + /* Add this entry to list. */ + if (*last_entry == NULL) + mtree->entries = entry; + else + (*last_entry)->next = entry; + *last_entry = entry; + + len = strcspn(line, " \t\r\n"); + if ((entry->name = malloc(len + 1)) == NULL) { + archive_set_error(&a->archive, errno, "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + + memcpy(entry->name, line, len); + entry->name[len] = '\0'; + parse_escapes(entry->name, entry); + + line += len; + for (iter = *global; iter != NULL; iter = iter->next) { + r = add_option(a, &entry->options, iter->value, + strlen(iter->value)); + if (r != ARCHIVE_OK) + return (r); + } + + for (;;) { + next = line + strspn(line, " \t\r\n"); + if (*next == '\0') + return (ARCHIVE_OK); + line = next; + next = line + strcspn(line, " \t\r\n"); + eq = strchr(line, '='); + if (eq == NULL || eq > next) + len = next - line; + else + len = eq - line; + + remove_option(&entry->options, line, len); + r = add_option(a, &entry->options, line, next - line); + if (r != ARCHIVE_OK) + return (r); + line = next; + } +} + +static int +read_mtree(struct archive_read *a, struct mtree *mtree) +{ + ssize_t len; + uintmax_t counter; + char *p; + struct mtree_option *global; + struct mtree_entry *last_entry; + int r; + + mtree->archive_format = ARCHIVE_FORMAT_MTREE; + mtree->archive_format_name = "mtree"; + + global = NULL; + last_entry = NULL; + + for (counter = 1; ; ++counter) { + len = readline(a, mtree, &p, 65536); + if (len == 0) { + mtree->this_entry = mtree->entries; + free_options(global); + return (ARCHIVE_OK); + } + if (len < 0) { + free_options(global); + return (len); + } + /* Leading whitespace is never significant, ignore it. */ + while (*p == ' ' || *p == '\t') { + ++p; + --len; + } + /* Skip content lines and blank lines. */ + if (*p == '#') + continue; + if (*p == '\r' || *p == '\n' || *p == '\0') + continue; + if (*p != '/') { + r = process_add_entry(a, mtree, &global, p, + &last_entry); + } else if (strncmp(p, "/set", 4) == 0) { + if (p[4] != ' ' && p[4] != '\t') + break; + r = process_global_set(a, &global, p); + } else if (strncmp(p, "/unset", 6) == 0) { + if (p[6] != ' ' && p[6] != '\t') + break; + r = process_global_unset(a, &global, p); + } else + break; + + if (r != ARCHIVE_OK) { + free_options(global); + return r; + } + } + + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't parse line %ju", counter); + free_options(global); + return (ARCHIVE_FATAL); +} + +/* + * Read in the entire mtree file into memory on the first request. + * Then use the next unused file to satisfy each header request. + */ +static int +read_header(struct archive_read *a, struct archive_entry *entry) +{ + struct mtree *mtree; + char *p; + int r, use_next; + + mtree = (struct mtree *)(a->format->data); + + if (mtree->fd >= 0) { + close(mtree->fd); + mtree->fd = -1; + } + + if (mtree->entries == NULL) { + mtree->resolver = archive_entry_linkresolver_new(); + if (mtree->resolver == NULL) + return ARCHIVE_FATAL; + archive_entry_linkresolver_set_strategy(mtree->resolver, + ARCHIVE_FORMAT_MTREE); + r = read_mtree(a, mtree); + if (r != ARCHIVE_OK) + return (r); + } + + a->archive.archive_format = mtree->archive_format; + a->archive.archive_format_name = mtree->archive_format_name; + + for (;;) { + if (mtree->this_entry == NULL) + return (ARCHIVE_EOF); + if (strcmp(mtree->this_entry->name, "..") == 0) { + mtree->this_entry->used = 1; + if (archive_strlen(&mtree->current_dir) > 0) { + /* Roll back current path. */ + p = mtree->current_dir.s + + mtree->current_dir.length - 1; + while (p >= mtree->current_dir.s && *p != '/') + --p; + if (p >= mtree->current_dir.s) + --p; + mtree->current_dir.length + = p - mtree->current_dir.s + 1; + } + } + if (!mtree->this_entry->used) { + use_next = 0; + r = parse_file(a, entry, mtree, mtree->this_entry, &use_next); + if (use_next == 0) + return (r); + } + mtree->this_entry = mtree->this_entry->next; + } +} + +/* + * A single file can have multiple lines contribute specifications. + * Parse as many lines as necessary, then pull additional information + * from a backing file on disk as necessary. + */ +static int +parse_file(struct archive_read *a, struct archive_entry *entry, + struct mtree *mtree, struct mtree_entry *mentry, int *use_next) +{ + const char *path; + struct stat st_storage, *st; + struct mtree_entry *mp; + struct archive_entry *sparse_entry; + int r = ARCHIVE_OK, r1, parsed_kws; + + mentry->used = 1; + + /* Initialize reasonable defaults. */ + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_size(entry, 0); + archive_string_empty(&mtree->contents_name); + + /* Parse options from this line. */ + parsed_kws = 0; + r = parse_line(a, entry, mtree, mentry, &parsed_kws); + + if (mentry->full) { + archive_entry_copy_pathname(entry, mentry->name); + /* + * "Full" entries are allowed to have multiple lines + * and those lines aren't required to be adjacent. We + * don't support multiple lines for "relative" entries + * nor do we make any attempt to merge data from + * separate "relative" and "full" entries. (Merging + * "relative" and "full" entries would require dealing + * with pathname canonicalization, which is a very + * tricky subject.) + */ + for (mp = mentry->next; mp != NULL; mp = mp->next) { + if (mp->full && !mp->used + && strcmp(mentry->name, mp->name) == 0) { + /* Later lines override earlier ones. */ + mp->used = 1; + r1 = parse_line(a, entry, mtree, mp, + &parsed_kws); + if (r1 < r) + r = r1; + } + } + } else { + /* + * Relative entries require us to construct + * the full path and possibly update the + * current directory. + */ + size_t n = archive_strlen(&mtree->current_dir); + if (n > 0) + archive_strcat(&mtree->current_dir, "/"); + archive_strcat(&mtree->current_dir, mentry->name); + archive_entry_copy_pathname(entry, mtree->current_dir.s); + if (archive_entry_filetype(entry) != AE_IFDIR) + mtree->current_dir.length = n; + } + + /* + * Try to open and stat the file to get the real size + * and other file info. It would be nice to avoid + * this here so that getting a listing of an mtree + * wouldn't require opening every referenced contents + * file. But then we wouldn't know the actual + * contents size, so I don't see a really viable way + * around this. (Also, we may want to someday pull + * other unspecified info from the contents file on + * disk.) + */ + mtree->fd = -1; + if (archive_strlen(&mtree->contents_name) > 0) + path = mtree->contents_name.s; + else + path = archive_entry_pathname(entry); + + if (archive_entry_filetype(entry) == AE_IFREG || + archive_entry_filetype(entry) == AE_IFDIR) { + mtree->fd = open(path, O_RDONLY | O_BINARY); + if (mtree->fd == -1 && + (errno != ENOENT || + archive_strlen(&mtree->contents_name) > 0)) { + archive_set_error(&a->archive, errno, + "Can't open %s", path); + r = ARCHIVE_WARN; + } + } + + st = &st_storage; + if (mtree->fd >= 0) { + if (fstat(mtree->fd, st) == -1) { + archive_set_error(&a->archive, errno, + "Could not fstat %s", path); + r = ARCHIVE_WARN; + /* If we can't stat it, don't keep it open. */ + close(mtree->fd); + mtree->fd = -1; + st = NULL; + } + } else if (lstat(path, st) == -1) { + st = NULL; + } + + /* + * Check for a mismatch between the type in the specification and + * the type of the contents object on disk. + */ + if (st != NULL) { + if ( + ((st->st_mode & S_IFMT) == S_IFREG && + archive_entry_filetype(entry) == AE_IFREG) +#ifdef S_IFLNK + || ((st->st_mode & S_IFMT) == S_IFLNK && + archive_entry_filetype(entry) == AE_IFLNK) +#endif +#ifdef S_IFSOCK + || ((st->st_mode & S_IFSOCK) == S_IFSOCK && + archive_entry_filetype(entry) == AE_IFSOCK) +#endif +#ifdef S_IFCHR + || ((st->st_mode & S_IFMT) == S_IFCHR && + archive_entry_filetype(entry) == AE_IFCHR) +#endif +#ifdef S_IFBLK + || ((st->st_mode & S_IFMT) == S_IFBLK && + archive_entry_filetype(entry) == AE_IFBLK) +#endif + || ((st->st_mode & S_IFMT) == S_IFDIR && + archive_entry_filetype(entry) == AE_IFDIR) +#ifdef S_IFIFO + || ((st->st_mode & S_IFMT) == S_IFIFO && + archive_entry_filetype(entry) == AE_IFIFO) +#endif + ) { + /* Types match. */ + } else { + /* Types don't match; bail out gracefully. */ + if (mtree->fd >= 0) + close(mtree->fd); + mtree->fd = -1; + if (parsed_kws & MTREE_HAS_OPTIONAL) { + /* It's not an error for an optional entry + to not match disk. */ + *use_next = 1; + } else if (r == ARCHIVE_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "mtree specification has different type for %s", + archive_entry_pathname(entry)); + r = ARCHIVE_WARN; + } + return r; + } + } + + /* + * If there is a contents file on disk, pick some of the metadata + * from that file. For most of these, we only set it from the contents + * if it wasn't already parsed from the specification. + */ + if (st != NULL) { + if ((parsed_kws & MTREE_HAS_DEVICE) == 0 && + (archive_entry_filetype(entry) == AE_IFCHR || + archive_entry_filetype(entry) == AE_IFBLK)) + archive_entry_set_rdev(entry, st->st_rdev); + if ((parsed_kws & (MTREE_HAS_GID | MTREE_HAS_GNAME)) == 0) + archive_entry_set_gid(entry, st->st_gid); + if ((parsed_kws & (MTREE_HAS_UID | MTREE_HAS_UNAME)) == 0) + archive_entry_set_uid(entry, st->st_uid); + if ((parsed_kws & MTREE_HAS_MTIME) == 0) { +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + archive_entry_set_mtime(entry, st->st_mtime, + st->st_mtimespec.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + archive_entry_set_mtime(entry, st->st_mtime, + st->st_mtim.tv_nsec); +#elif HAVE_STRUCT_STAT_ST_MTIME_N + archive_entry_set_mtime(entry, st->st_mtime, + st->st_mtime_n); +#elif HAVE_STRUCT_STAT_ST_UMTIME + archive_entry_set_mtime(entry, st->st_mtime, + st->st_umtime*1000); +#elif HAVE_STRUCT_STAT_ST_MTIME_USEC + archive_entry_set_mtime(entry, st->st_mtime, + st->st_mtime_usec*1000); +#else + archive_entry_set_mtime(entry, st->st_mtime, 0); +#endif + } + if ((parsed_kws & MTREE_HAS_NLINK) == 0) + archive_entry_set_nlink(entry, st->st_nlink); + if ((parsed_kws & MTREE_HAS_PERM) == 0) + archive_entry_set_perm(entry, st->st_mode); + if ((parsed_kws & MTREE_HAS_SIZE) == 0) + archive_entry_set_size(entry, st->st_size); + archive_entry_set_ino(entry, st->st_ino); + archive_entry_set_dev(entry, st->st_dev); + + archive_entry_linkify(mtree->resolver, &entry, &sparse_entry); + } else if (parsed_kws & MTREE_HAS_OPTIONAL) { + /* + * Couldn't open the entry, stat it or the on-disk type + * didn't match. If this entry is optional, just ignore it + * and read the next header entry. + */ + *use_next = 1; + return ARCHIVE_OK; + } + + mtree->cur_size = archive_entry_size(entry); + mtree->offset = 0; + + return r; +} + +/* + * Each line contains a sequence of keywords. + */ +static int +parse_line(struct archive_read *a, struct archive_entry *entry, + struct mtree *mtree, struct mtree_entry *mp, int *parsed_kws) +{ + struct mtree_option *iter; + int r = ARCHIVE_OK, r1; + + for (iter = mp->options; iter != NULL; iter = iter->next) { + r1 = parse_keyword(a, mtree, entry, iter, parsed_kws); + if (r1 < r) + r = r1; + } + if (r == ARCHIVE_OK && (*parsed_kws & MTREE_HAS_TYPE) == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Missing type keyword in mtree specification"); + return (ARCHIVE_WARN); + } + return (r); +} + +/* + * Device entries have one of the following forms: + * raw dev_t + * format,major,minor[,subdevice] + * + * Just use major and minor, no translation etc is done + * between formats. + */ +static int +parse_device(struct archive *a, struct archive_entry *entry, char *val) +{ + char *comma1, *comma2; + + comma1 = strchr(val, ','); + if (comma1 == NULL) { + archive_entry_set_dev(entry, mtree_atol10(&val)); + return (ARCHIVE_OK); + } + ++comma1; + comma2 = strchr(comma1, ','); + if (comma2 == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_FILE_FORMAT, + "Malformed device attribute"); + return (ARCHIVE_WARN); + } + ++comma2; + archive_entry_set_rdevmajor(entry, mtree_atol(&comma1)); + archive_entry_set_rdevminor(entry, mtree_atol(&comma2)); + return (ARCHIVE_OK); +} + +/* + * Parse a single keyword and its value. + */ +static int +parse_keyword(struct archive_read *a, struct mtree *mtree, + struct archive_entry *entry, struct mtree_option *opt, int *parsed_kws) +{ + char *val, *key; + + key = opt->value; + + if (*key == '\0') + return (ARCHIVE_OK); + + if (strcmp(key, "optional") == 0) { + *parsed_kws |= MTREE_HAS_OPTIONAL; + return (ARCHIVE_OK); + } + if (strcmp(key, "ignore") == 0) { + /* + * The mtree processing is not recursive, so + * recursion will only happen for explicitly listed + * entries. + */ + return (ARCHIVE_OK); + } + + val = strchr(key, '='); + if (val == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Malformed attribute \"%s\" (%d)", key, key[0]); + return (ARCHIVE_WARN); + } + + *val = '\0'; + ++val; + + switch (key[0]) { + case 'c': + if (strcmp(key, "content") == 0 + || strcmp(key, "contents") == 0) { + parse_escapes(val, NULL); + archive_strcpy(&mtree->contents_name, val); + break; + } + if (strcmp(key, "cksum") == 0) + break; + case 'd': + if (strcmp(key, "device") == 0) { + *parsed_kws |= MTREE_HAS_DEVICE; + return parse_device(&a->archive, entry, val); + } + case 'f': + if (strcmp(key, "flags") == 0) { + *parsed_kws |= MTREE_HAS_FFLAGS; + archive_entry_copy_fflags_text(entry, val); + break; + } + case 'g': + if (strcmp(key, "gid") == 0) { + *parsed_kws |= MTREE_HAS_GID; + archive_entry_set_gid(entry, mtree_atol10(&val)); + break; + } + if (strcmp(key, "gname") == 0) { + *parsed_kws |= MTREE_HAS_GNAME; + archive_entry_copy_gname(entry, val); + break; + } + case 'l': + if (strcmp(key, "link") == 0) { + archive_entry_copy_symlink(entry, val); + break; + } + case 'm': + if (strcmp(key, "md5") == 0 || strcmp(key, "md5digest") == 0) + break; + if (strcmp(key, "mode") == 0) { + if (val[0] >= '0' && val[0] <= '9') { + *parsed_kws |= MTREE_HAS_PERM; + archive_entry_set_perm(entry, + mtree_atol8(&val)); + } else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Symbolic mode \"%s\" unsupported", val); + return ARCHIVE_WARN; + } + break; + } + case 'n': + if (strcmp(key, "nlink") == 0) { + *parsed_kws |= MTREE_HAS_NLINK; + archive_entry_set_nlink(entry, mtree_atol10(&val)); + break; + } + case 'r': + if (strcmp(key, "rmd160") == 0 || + strcmp(key, "rmd160digest") == 0) + break; + case 's': + if (strcmp(key, "sha1") == 0 || strcmp(key, "sha1digest") == 0) + break; + if (strcmp(key, "sha256") == 0 || + strcmp(key, "sha256digest") == 0) + break; + if (strcmp(key, "sha384") == 0 || + strcmp(key, "sha384digest") == 0) + break; + if (strcmp(key, "sha512") == 0 || + strcmp(key, "sha512digest") == 0) + break; + if (strcmp(key, "size") == 0) { + archive_entry_set_size(entry, mtree_atol10(&val)); + break; + } + case 't': + if (strcmp(key, "tags") == 0) { + /* + * Comma delimited list of tags. + * Ignore the tags for now, but the interface + * should be extended to allow inclusion/exclusion. + */ + break; + } + if (strcmp(key, "time") == 0) { + int64_t m; + int64_t my_time_t_max = get_time_t_max(); + int64_t my_time_t_min = get_time_t_min(); + long ns; + + *parsed_kws |= MTREE_HAS_MTIME; + m = mtree_atol10(&val); + /* Replicate an old mtree bug: + * 123456789.1 represents 123456789 + * seconds and 1 nanosecond. */ + if (*val == '.') { + ++val; + ns = (long)mtree_atol10(&val); + } else + ns = 0; + if (m > my_time_t_max) + m = my_time_t_max; + else if (m < my_time_t_min) + m = my_time_t_min; + archive_entry_set_mtime(entry, (time_t)m, ns); + break; + } + if (strcmp(key, "type") == 0) { + switch (val[0]) { + case 'b': + if (strcmp(val, "block") == 0) { + archive_entry_set_filetype(entry, AE_IFBLK); + break; + } + case 'c': + if (strcmp(val, "char") == 0) { + archive_entry_set_filetype(entry, AE_IFCHR); + break; + } + case 'd': + if (strcmp(val, "dir") == 0) { + archive_entry_set_filetype(entry, AE_IFDIR); + break; + } + case 'f': + if (strcmp(val, "fifo") == 0) { + archive_entry_set_filetype(entry, AE_IFIFO); + break; + } + if (strcmp(val, "file") == 0) { + archive_entry_set_filetype(entry, AE_IFREG); + break; + } + case 'l': + if (strcmp(val, "link") == 0) { + archive_entry_set_filetype(entry, AE_IFLNK); + break; + } + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized file type \"%s\"; assuming \"file\"", val); + archive_entry_set_filetype(entry, AE_IFREG); + return (ARCHIVE_WARN); + } + *parsed_kws |= MTREE_HAS_TYPE; + break; + } + case 'u': + if (strcmp(key, "uid") == 0) { + *parsed_kws |= MTREE_HAS_UID; + archive_entry_set_uid(entry, mtree_atol10(&val)); + break; + } + if (strcmp(key, "uname") == 0) { + *parsed_kws |= MTREE_HAS_UNAME; + archive_entry_copy_uname(entry, val); + break; + } + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unrecognized key %s=%s", key, val); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +read_data(struct archive_read *a, const void **buff, size_t *size, int64_t *offset) +{ + size_t bytes_to_read; + ssize_t bytes_read; + struct mtree *mtree; + + mtree = (struct mtree *)(a->format->data); + if (mtree->fd < 0) { + *buff = NULL; + *offset = 0; + *size = 0; + return (ARCHIVE_EOF); + } + if (mtree->buff == NULL) { + mtree->buffsize = 64 * 1024; + mtree->buff = malloc(mtree->buffsize); + if (mtree->buff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + } + + *buff = mtree->buff; + *offset = mtree->offset; + if ((int64_t)mtree->buffsize > mtree->cur_size - mtree->offset) + bytes_to_read = mtree->cur_size - mtree->offset; + else + bytes_to_read = mtree->buffsize; + bytes_read = read(mtree->fd, mtree->buff, bytes_to_read); + if (bytes_read < 0) { + archive_set_error(&a->archive, errno, "Can't read"); + return (ARCHIVE_WARN); + } + if (bytes_read == 0) { + *size = 0; + return (ARCHIVE_EOF); + } + mtree->offset += bytes_read; + *size = bytes_read; + return (ARCHIVE_OK); +} + +/* Skip does nothing except possibly close the contents file. */ +static int +skip(struct archive_read *a) +{ + struct mtree *mtree; + + mtree = (struct mtree *)(a->format->data); + if (mtree->fd >= 0) { + close(mtree->fd); + mtree->fd = -1; + } + return (ARCHIVE_OK); +} + +/* + * Since parsing backslash sequences always makes strings shorter, + * we can always do this conversion in-place. + */ +static void +parse_escapes(char *src, struct mtree_entry *mentry) +{ + char *dest = src; + char c; + + if (mentry != NULL && strcmp(src, ".") == 0) + mentry->full = 1; + + while (*src != '\0') { + c = *src++; + if (c == '/' && mentry != NULL) + mentry->full = 1; + if (c == '\\') { + switch (src[0]) { + case '0': + if (src[1] < '0' || src[1] > '7') { + c = 0; + ++src; + break; + } + /* FALLTHROUGH */ + case '1': + case '2': + case '3': + if (src[1] >= '0' && src[1] <= '7' && + src[2] >= '0' && src[2] <= '7') { + c = (src[0] - '0') << 6; + c |= (src[1] - '0') << 3; + c |= (src[2] - '0'); + src += 3; + } + break; + case 'a': + c = '\a'; + ++src; + break; + case 'b': + c = '\b'; + ++src; + break; + case 'f': + c = '\f'; + ++src; + break; + case 'n': + c = '\n'; + ++src; + break; + case 'r': + c = '\r'; + ++src; + break; + case 's': + c = ' '; + ++src; + break; + case 't': + c = '\t'; + ++src; + break; + case 'v': + c = '\v'; + ++src; + break; + } + } + *dest++ = c; + } + *dest = '\0'; +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +mtree_atol8(char **p) +{ + int64_t l, limit, last_digit_limit; + int digit, base; + + base = 8; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + l = 0; + digit = **p - '0'; + while (digit >= 0 && digit < base) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = INT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++(*p) - '0'; + } + return (l); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +mtree_atol10(char **p) +{ + int64_t l, limit, last_digit_limit; + int base, digit, sign; + + base = 10; + + if (**p == '-') { + sign = -1; + limit = ((uint64_t)(INT64_MAX) + 1) / base; + last_digit_limit = ((uint64_t)(INT64_MAX) + 1) % base; + ++(*p); + } else { + sign = 1; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + } + + l = 0; + digit = **p - '0'; + while (digit >= 0 && digit < base) { + if (l > limit || (l == limit && digit > last_digit_limit)) + return (sign < 0) ? INT64_MIN : INT64_MAX; + l = (l * base) + digit; + digit = *++(*p) - '0'; + } + return (sign < 0) ? -l : l; +} + +/* Parse a hex digit. */ +static int +parsehex(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a'; + else if (c >= 'A' && c <= 'F') + return c - 'A'; + else + return -1; +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +mtree_atol16(char **p) +{ + int64_t l, limit, last_digit_limit; + int base, digit, sign; + + base = 16; + + if (**p == '-') { + sign = -1; + limit = ((uint64_t)(INT64_MAX) + 1) / base; + last_digit_limit = ((uint64_t)(INT64_MAX) + 1) % base; + ++(*p); + } else { + sign = 1; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + } + + l = 0; + digit = parsehex(**p); + while (digit >= 0 && digit < base) { + if (l > limit || (l == limit && digit > last_digit_limit)) + return (sign < 0) ? INT64_MIN : INT64_MAX; + l = (l * base) + digit; + digit = parsehex(*++(*p)); + } + return (sign < 0) ? -l : l; +} + +static int64_t +mtree_atol(char **p) +{ + if (**p != '0') + return mtree_atol10(p); + if ((*p)[1] == 'x' || (*p)[1] == 'X') { + *p += 2; + return mtree_atol16(p); + } + return mtree_atol8(p); +} + +/* + * Returns length of line (including trailing newline) + * or negative on error. 'start' argument is updated to + * point to first character of line. + */ +static ssize_t +readline(struct archive_read *a, struct mtree *mtree, char **start, ssize_t limit) +{ + ssize_t bytes_read; + ssize_t total_size = 0; + ssize_t find_off = 0; + const void *t; + const char *s; + void *p; + char *u; + + /* Accumulate line in a line buffer. */ + for (;;) { + /* Read some more. */ + t = __archive_read_ahead(a, 1, &bytes_read); + if (t == NULL) + return (0); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n', trim the read. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + } + if (total_size + bytes_read + 1 > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + if (archive_string_ensure(&mtree->line, + total_size + bytes_read + 1) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate working buffer"); + return (ARCHIVE_FATAL); + } + memcpy(mtree->line.s + total_size, t, bytes_read); + __archive_read_consume(a, bytes_read); + total_size += bytes_read; + /* Null terminate. */ + mtree->line.s[total_size] = '\0'; + /* If we found an unescaped '\n', clean up and return. */ + for (u = mtree->line.s + find_off; *u; ++u) { + if (u[0] == '\n') { + *start = mtree->line.s; + return total_size; + } + if (u[0] == '#') { + if (p == NULL) + break; + *start = mtree->line.s; + return total_size; + } + if (u[0] != '\\') + continue; + if (u[1] == '\\') { + ++u; + continue; + } + if (u[1] == '\n') { + memmove(u, u + 1, + total_size - (u - mtree->line.s) + 1); + --total_size; + ++u; + break; + } + if (u[1] == '\0') + break; + } + find_off = u - mtree->line.s; + } +} diff --git a/libarchive/archive_read_support_format_rar.c b/libarchive/archive_read_support_format_rar.c new file mode 100644 index 0000000..a5f2041 --- /dev/null +++ b/libarchive/archive_read_support_format_rar.c @@ -0,0 +1,2574 @@ +/*- +* Copyright (c) 2003-2007 Tim Kientzle +* Copyright (c) 2011 Andres Mejia +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "archive_platform.h" + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#include +#ifdef HAVE_ZLIB_H +#include /* crc32 */ +#endif + +#include "archive.h" +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_ppmd7_private.h" +#include "archive_private.h" +#include "archive_read_private.h" + +/* RAR signature, also known as the mark header */ +#define RAR_SIGNATURE "\x52\x61\x72\x21\x1A\x07\x00" + +/* Header types */ +#define MARK_HEAD 0x72 +#define MAIN_HEAD 0x73 +#define FILE_HEAD 0x74 +#define COMM_HEAD 0x75 +#define AV_HEAD 0x76 +#define SUB_HEAD 0x77 +#define PROTECT_HEAD 0x78 +#define SIGN_HEAD 0x79 +#define NEWSUB_HEAD 0x7a +#define ENDARC_HEAD 0x7b + +/* Main Header Flags */ +#define MHD_VOLUME 0x0001 +#define MHD_COMMENT 0x0002 +#define MHD_LOCK 0x0004 +#define MHD_SOLID 0x0008 +#define MHD_NEWNUMBERING 0x0010 +#define MHD_AV 0x0020 +#define MHD_PROTECT 0x0040 +#define MHD_PASSWORD 0x0080 +#define MHD_FIRSTVOLUME 0x0100 +#define MHD_ENCRYPTVER 0x0200 + +/* Flags common to all headers */ +#define HD_MARKDELETION 0x4000 +#define HD_ADD_SIZE_PRESENT 0x8000 + +/* File Header Flags */ +#define FHD_SPLIT_BEFORE 0x0001 +#define FHD_SPLIT_AFTER 0x0002 +#define FHD_PASSWORD 0x0004 +#define FHD_COMMENT 0x0008 +#define FHD_SOLID 0x0010 +#define FHD_LARGE 0x0100 +#define FHD_UNICODE 0x0200 +#define FHD_SALT 0x0400 +#define FHD_VERSION 0x0800 +#define FHD_EXTTIME 0x1000 +#define FHD_EXTFLAGS 0x2000 + +/* File dictionary sizes */ +#define DICTIONARY_SIZE_64 0x00 +#define DICTIONARY_SIZE_128 0x20 +#define DICTIONARY_SIZE_256 0x40 +#define DICTIONARY_SIZE_512 0x60 +#define DICTIONARY_SIZE_1024 0x80 +#define DICTIONARY_SIZE_2048 0xA0 +#define DICTIONARY_SIZE_4096 0xC0 +#define FILE_IS_DIRECTORY 0xE0 +#define DICTIONARY_MASK FILE_IS_DIRECTORY + +/* OS Flags */ +#define OS_MSDOS 0 +#define OS_OS2 1 +#define OS_WIN32 2 +#define OS_UNIX 3 +#define OS_MAC_OS 4 +#define OS_BEOS 5 + +/* Compression Methods */ +#define COMPRESS_METHOD_STORE 0x30 +/* LZSS */ +#define COMPRESS_METHOD_FASTEST 0x31 +#define COMPRESS_METHOD_FAST 0x32 +#define COMPRESS_METHOD_NORMAL 0x33 +/* PPMd Variant H */ +#define COMPRESS_METHOD_GOOD 0x34 +#define COMPRESS_METHOD_BEST 0x35 + +#define CRC_POLYNOMIAL 0xEDB88320 + +#define NS_UNIT 10000000 + +#define DICTIONARY_MAX_SIZE 0x400000 + +#define MAINCODE_SIZE 299 +#define OFFSETCODE_SIZE 60 +#define LOWOFFSETCODE_SIZE 17 +#define LENGTHCODE_SIZE 28 +#define HUFFMAN_TABLE_SIZE \ + MAINCODE_SIZE + OFFSETCODE_SIZE + LOWOFFSETCODE_SIZE + LENGTHCODE_SIZE + +#define MAX_SYMBOL_LENGTH 0xF +#define MAX_SYMBOLS 20 + +/* + * Considering L1,L2 cache miss and a calling of write sytem-call, + * the best size of the output buffer(uncompressed buffer) is 128K. + * If the structure of extracting process is changed, this value + * might be researched again. + */ +#define UNP_BUFFER_SIZE (128 * 1024) + +/* Define this here for non-Windows platforms */ +#if !((defined(__WIN32__) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__)) +#define FILE_ATTRIBUTE_DIRECTORY 0x10 +#endif + +/* Fields common to all headers */ +struct rar_header +{ + char crc[2]; + char type; + char flags[2]; + char size[2]; +}; + +/* Fields common to all file headers */ +struct rar_file_header +{ + char pack_size[4]; + char unp_size[4]; + char host_os; + char file_crc[4]; + char file_time[4]; + char unp_ver; + char method; + char name_size[2]; + char file_attr[4]; +}; + +struct huffman_tree_node +{ + int branches[2]; +}; + +struct huffman_table_entry +{ + unsigned int length; + int value; +}; + +struct huffman_code +{ + struct huffman_tree_node *tree; + int numentries; + int minlength; + int maxlength; + int tablesize; + struct huffman_table_entry *table; +}; + +struct lzss +{ + unsigned char *window; + int mask; + int64_t position; +}; + +struct rar +{ + /* Entries from main RAR header */ + unsigned main_flags; + unsigned long file_crc; + char reserved1[2]; + char reserved2[4]; + char encryptver; + + /* File header entries */ + char compression_method; + unsigned file_flags; + int64_t packed_size; + int64_t unp_size; + time_t mtime; + long mnsec; + mode_t mode; + char *filename; + size_t filename_allocated; + + /* File header optional entries */ + char salt[8]; + time_t atime; + long ansec; + time_t ctime; + long cnsec; + time_t arctime; + long arcnsec; + + /* Fields to help with tracking decompression of files. */ + int64_t bytes_unconsumed; + int64_t bytes_remaining; + int64_t bytes_uncopied; + int64_t offset; + int64_t offset_outgoing; + char valid; + unsigned int unp_offset; + unsigned int unp_buffer_size; + unsigned char *unp_buffer; + unsigned int dictionary_size; + char start_new_block; + char entry_eof; + unsigned long crc_calculated; + int found_first_header; + + /* LZSS members */ + struct huffman_code maincode; + struct huffman_code offsetcode; + struct huffman_code lowoffsetcode; + struct huffman_code lengthcode; + unsigned char lengthtable[HUFFMAN_TABLE_SIZE]; + struct lzss lzss; + char output_last_match; + unsigned int lastlength; + unsigned int lastoffset; + unsigned int oldoffset[4]; + unsigned int lastlowoffset; + unsigned int numlowoffsetrepeats; + int64_t filterstart; + char start_new_table; + + /* PPMd Variant H members */ + char ppmd_valid; + char ppmd_eod; + char is_ppmd_block; + int ppmd_escape; + CPpmd7 ppmd7_context; + CPpmd7z_RangeDec range_dec; + IByteIn bytein; + + /* + * String conversion object. + */ + int init_default_conversion; + struct archive_string_conv *sconv_default; + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_utf8; + struct archive_string_conv *sconv_utf16be; + + /* + * Bit stream reader. + */ + struct rar_br { +#define CACHE_TYPE uint64_t +#define CACHE_BITS (8 * sizeof(CACHE_TYPE)) + /* Cache buffer. */ + CACHE_TYPE cache_buffer; + /* Indicates how many bits avail in cache_buffer. */ + int cache_avail; + ssize_t avail_in; + const unsigned char *next_in; + } br; +}; + +static int archive_read_format_rar_bid(struct archive_read *, int); +static int archive_read_format_rar_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_rar_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_rar_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_rar_read_data_skip(struct archive_read *a); +static int archive_read_format_rar_cleanup(struct archive_read *); + +/* Support functions */ +static int read_header(struct archive_read *, struct archive_entry *, char); +static time_t get_time(int time); +static int read_exttime(const char *, struct rar *, const char *); +static int read_symlink_stored(struct archive_read *, struct archive_entry *, + struct archive_string_conv *); +static int read_data_stored(struct archive_read *, const void **, size_t *, + int64_t *); +static int read_data_compressed(struct archive_read *, const void **, size_t *, + int64_t *); +static int rar_br_preparation(struct archive_read *, struct rar_br *); +static int parse_codes(struct archive_read *); +static void free_codes(struct archive_read *); +static int read_next_symbol(struct archive_read *, struct huffman_code *); +static int create_code(struct archive_read *, struct huffman_code *, + unsigned char *, int, char); +static int add_value(struct archive_read *, struct huffman_code *, int, int, + int); +static int new_node(struct huffman_code *); +static int make_table(struct archive_read *, struct huffman_code *); +static int make_table_recurse(struct archive_read *, struct huffman_code *, int, + struct huffman_table_entry *, int, int); +static int64_t expand(struct archive_read *, int64_t); +static int copy_from_lzss_window(struct archive_read *, const void **, + int64_t, int); + +/* + * Bit stream reader. + */ +/* Check that the cache buffer has enough bits. */ +#define rar_br_has(br, n) ((br)->cache_avail >= n) +/* Get compressed data by bit. */ +#define rar_br_bits(br, n) \ + (((uint32_t)((br)->cache_buffer >> \ + ((br)->cache_avail - (n)))) & cache_masks[n]) +#define rar_br_bits_forced(br, n) \ + (((uint32_t)((br)->cache_buffer << \ + ((n) - (br)->cache_avail))) & cache_masks[n]) +/* Read ahead to make sure the cache buffer has enough compressed data we + * will use. + * True : completed, there is enough data in the cache buffer. + * False : there is no data in the stream. */ +#define rar_br_read_ahead(a, br, n) \ + ((rar_br_has(br, (n)) || rar_br_fillup(a, br)) || rar_br_has(br, (n))) +/* Notify how many bits we consumed. */ +#define rar_br_consume(br, n) ((br)->cache_avail -= (n)) +#define rar_br_consume_unalined_bits(br) ((br)->cache_avail &= ~7) + +static const uint32_t cache_masks[] = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, + 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, + 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, + 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, + 0x0000FFFF, 0x0001FFFF, 0x0003FFFF, 0x0007FFFF, + 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, + 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, + 0x0FFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF +}; + +/* + * Shift away used bits in the cache data and fill it up with following bits. + * Call this when cache buffer does not have enough bits you need. + * + * Returns 1 if the cache buffer is full. + * Returns 0 if the cache buffer is not full; input buffer is empty. + */ +static int +rar_br_fillup(struct archive_read *a, struct rar_br *br) +{ + struct rar *rar = (struct rar *)(a->format->data); + int n = CACHE_BITS - br->cache_avail; + + for (;;) { + switch (n >> 3) { + case 8: + if (br->avail_in >= 8) { + br->cache_buffer = + ((uint64_t)br->next_in[0]) << 56 | + ((uint64_t)br->next_in[1]) << 48 | + ((uint64_t)br->next_in[2]) << 40 | + ((uint64_t)br->next_in[3]) << 32 | + ((uint32_t)br->next_in[4]) << 24 | + ((uint32_t)br->next_in[5]) << 16 | + ((uint32_t)br->next_in[6]) << 8 | + (uint32_t)br->next_in[7]; + br->next_in += 8; + br->avail_in -= 8; + br->cache_avail += 8 * 8; + rar->bytes_unconsumed += 8; + rar->bytes_remaining -= 8; + return (1); + } + break; + case 7: + if (br->avail_in >= 7) { + br->cache_buffer = + (br->cache_buffer << 56) | + ((uint64_t)br->next_in[0]) << 48 | + ((uint64_t)br->next_in[1]) << 40 | + ((uint64_t)br->next_in[2]) << 32 | + ((uint32_t)br->next_in[3]) << 24 | + ((uint32_t)br->next_in[4]) << 16 | + ((uint32_t)br->next_in[5]) << 8 | + (uint32_t)br->next_in[6]; + br->next_in += 7; + br->avail_in -= 7; + br->cache_avail += 7 * 8; + rar->bytes_unconsumed += 7; + rar->bytes_remaining -= 7; + return (1); + } + break; + case 6: + if (br->avail_in >= 6) { + br->cache_buffer = + (br->cache_buffer << 48) | + ((uint64_t)br->next_in[0]) << 40 | + ((uint64_t)br->next_in[1]) << 32 | + ((uint32_t)br->next_in[2]) << 24 | + ((uint32_t)br->next_in[3]) << 16 | + ((uint32_t)br->next_in[4]) << 8 | + (uint32_t)br->next_in[5]; + br->next_in += 6; + br->avail_in -= 6; + br->cache_avail += 6 * 8; + rar->bytes_unconsumed += 6; + rar->bytes_remaining -= 6; + return (1); + } + break; + case 0: + /* We have enough compressed data in + * the cache buffer.*/ + return (1); + default: + break; + } + if (br->avail_in <= 0) { + + if (rar->bytes_unconsumed > 0) { + /* Consume as much as the decompressor + * actually used. */ + __archive_read_consume(a, rar->bytes_unconsumed); + rar->bytes_unconsumed = 0; + } + br->next_in = __archive_read_ahead(a, 1, &(br->avail_in)); + if (br->next_in == NULL) + return (0); + if (br->avail_in > rar->bytes_remaining) + br->avail_in = rar->bytes_remaining; + if (br->avail_in == 0) + return (0); + } + br->cache_buffer = + (br->cache_buffer << 8) | *br->next_in++; + br->avail_in--; + br->cache_avail += 8; + n -= 8; + rar->bytes_unconsumed++; + rar->bytes_remaining--; + } +} + +static int +rar_br_preparation(struct archive_read *a, struct rar_br *br) +{ + struct rar *rar = (struct rar *)(a->format->data); + + if (rar->bytes_remaining > 0) { + br->next_in = __archive_read_ahead(a, 1, &(br->avail_in)); + if (br->next_in == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + return (ARCHIVE_FATAL); + } + if (br->avail_in > rar->bytes_remaining) + br->avail_in = rar->bytes_remaining; + if (br->cache_avail == 0) + (void)rar_br_fillup(a, br); + } + return (ARCHIVE_OK); +} + +/* Find last bit set */ +static inline int +rar_fls(unsigned int word) +{ + word |= (word >> 1); + word |= (word >> 2); + word |= (word >> 4); + word |= (word >> 8); + word |= (word >> 16); + return word - (word >> 1); +} + +/* LZSS functions */ +static inline int64_t +lzss_position(struct lzss *lzss) +{ + return lzss->position; +} + +static inline int +lzss_mask(struct lzss *lzss) +{ + return lzss->mask; +} + +static inline int +lzss_size(struct lzss *lzss) +{ + return lzss->mask + 1; +} + +static inline int +lzss_offset_for_position(struct lzss *lzss, int64_t pos) +{ + return pos & lzss->mask; +} + +static inline unsigned char * +lzss_pointer_for_position(struct lzss *lzss, int64_t pos) +{ + return &lzss->window[lzss_offset_for_position(lzss, pos)]; +} + +static inline int +lzss_current_offset(struct lzss *lzss) +{ + return lzss_offset_for_position(lzss, lzss->position); +} + +static inline uint8_t * +lzss_current_pointer(struct lzss *lzss) +{ + return lzss_pointer_for_position(lzss, lzss->position); +} + +static inline void +lzss_emit_literal(struct rar *rar, uint8_t literal) +{ + *lzss_current_pointer(&rar->lzss) = literal; + rar->lzss.position++; +} + +static inline void +lzss_emit_match(struct rar *rar, int offset, int length) +{ + int dstoffs = lzss_current_offset(&rar->lzss); + int srcoffs = (dstoffs - offset) & lzss_mask(&rar->lzss); + int l, li, remaining; + unsigned char *d, *s; + + remaining = length; + while (remaining > 0) { + l = remaining; + if (dstoffs > srcoffs) { + if (l > lzss_size(&rar->lzss) - dstoffs) + l = lzss_size(&rar->lzss) - dstoffs; + } else { + if (l > lzss_size(&rar->lzss) - srcoffs) + l = lzss_size(&rar->lzss) - srcoffs; + } + d = &(rar->lzss.window[dstoffs]); + s = &(rar->lzss.window[srcoffs]); + if ((dstoffs + l < srcoffs) || (srcoffs + l < dstoffs)) + memcpy(d, s, l); + else { + for (li = 0; li < l; li++) + d[li] = s[li]; + } + remaining -= l; + dstoffs = (dstoffs + l) & lzss_mask(&(rar->lzss)); + srcoffs = (srcoffs + l) & lzss_mask(&(rar->lzss)); + } + rar->lzss.position += length; +} + +static void * +ppmd_alloc(void *p, size_t size) +{ + (void)p; + return malloc(size); +} +static void +ppmd_free(void *p, void *address) +{ + (void)p; + free(address); +} +static ISzAlloc g_szalloc = { ppmd_alloc, ppmd_free }; + +static Byte +ppmd_read(void *p) +{ + struct archive_read *a = ((IByteIn*)p)->a; + struct rar *rar = (struct rar *)(a->format->data); + struct rar_br *br = &(rar->br); + Byte b; + if (!rar_br_read_ahead(a, br, 8)) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + rar->valid = 0; + return 0; + } + b = rar_br_bits(br, 8); + rar_br_consume(br, 8); + return b; +} + +int +archive_read_support_format_rar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct rar *rar; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, ARCHIVE_STATE_NEW, + "archive_read_support_format_rar"); + + rar = (struct rar *)malloc(sizeof(*rar)); + if (rar == NULL) + { + archive_set_error(&a->archive, ENOMEM, "Can't allocate rar data"); + return (ARCHIVE_FATAL); + } + memset(rar, 0, sizeof(*rar)); + + r = __archive_read_register_format(a, + rar, + "rar", + archive_read_format_rar_bid, + archive_read_format_rar_options, + archive_read_format_rar_read_header, + archive_read_format_rar_read_data, + archive_read_format_rar_read_data_skip, + archive_read_format_rar_cleanup); + + if (r != ARCHIVE_OK) + free(rar); + return (r); +} + +static int +archive_read_format_rar_bid(struct archive_read *a, int best_bid) +{ + const char *p; + + /* If there's already a bid > 30, we'll never win. */ + if (best_bid > 30) + return (-1); + + if ((p = __archive_read_ahead(a, 7, NULL)) == NULL) + return (-1); + + if (memcmp(p, RAR_SIGNATURE, 7) == 0) + return (30); + + if ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0) { + /* This is a PE file */ + ssize_t offset = 0x10000; + ssize_t window = 4096; + ssize_t bytes_avail; + while (offset + window <= (1024 * 128)) { + const char *buff = __archive_read_ahead(a, offset + window, &bytes_avail); + if (buff == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + return (0); + continue; + } + p = buff + offset; + while (p + 7 < buff + bytes_avail) { + if (memcmp(p, RAR_SIGNATURE, 7) == 0) + return (30); + p += 0x10; + } + offset = p - buff; + } + } + return (0); +} + +static int +skip_sfx(struct archive_read *a) +{ + const void *h; + const char *p, *q; + size_t skip, total; + ssize_t bytes, window; + + total = 0; + window = 4096; + while (total + window <= (1024 * 128)) { + h = __archive_read_ahead(a, window, &bytes); + if (h == NULL) { + /* Remaining bytes are less than window. */ + window >>= 1; + if (window < 0x40) + goto fatal; + continue; + } + if (bytes < 0x40) + goto fatal; + p = h; + q = p + bytes; + + /* + * Scan ahead until we find something that looks + * like the RAR header. + */ + while (p + 7 < q) { + if (memcmp(p, RAR_SIGNATURE, 7) == 0) { + skip = p - (const char *)h; + __archive_read_consume(a, skip); + return (ARCHIVE_OK); + } + p += 0x10; + } + skip = p - (const char *)h; + __archive_read_consume(a, skip); + total += skip; + } +fatal: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Couldn't find out RAR header"); + return (ARCHIVE_FATAL); +} + +static int +archive_read_format_rar_options(struct archive_read *a, + const char *key, const char *val) +{ + struct rar *rar; + int ret = ARCHIVE_FAILED; + + rar = (struct rar *)(a->format->data); + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "rar: hdrcharset option needs a character-set name"); + else { + rar->opt_sconv = + archive_string_conversion_from_charset( + &a->archive, val, 0); + if (rar->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "rar: unknown keyword ``%s''", key); + + return (ret); +} + +static int +archive_read_format_rar_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + const void *h; + const char *p; + struct rar *rar; + size_t skip; + char head_type; + int ret; + unsigned flags; + + a->archive.archive_format = ARCHIVE_FORMAT_RAR; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "RAR"; + + rar = (struct rar *)(a->format->data); + + /* RAR files can be generated without EOF headers, so return ARCHIVE_EOF if + * this fails. + */ + if ((h = __archive_read_ahead(a, 7, NULL)) == NULL) + return (ARCHIVE_EOF); + + p = h; + if (rar->found_first_header == 0 && + ((p[0] == 'M' && p[1] == 'Z') || memcmp(p, "\x7F\x45LF", 4) == 0)) { + /* This is an executable ? Must be self-extracting... */ + ret = skip_sfx(a); + if (ret < ARCHIVE_WARN) + return (ret); + } + rar->found_first_header = 1; + + while (1) + { + unsigned long crc32_val; + + if ((h = __archive_read_ahead(a, 7, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + + head_type = p[2]; + switch(head_type) + { + case MARK_HEAD: + if (memcmp(p, RAR_SIGNATURE, 7) != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid marker header"); + return (ARCHIVE_FATAL); + } + __archive_read_consume(a, 7); + break; + + case MAIN_HEAD: + rar->main_flags = archive_le16dec(p + 3); + skip = archive_le16dec(p + 5); + if (skip < 7 + sizeof(rar->reserved1) + sizeof(rar->reserved2)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + if ((h = __archive_read_ahead(a, skip, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + memcpy(rar->reserved1, p + 7, sizeof(rar->reserved1)); + memcpy(rar->reserved2, p + 7 + sizeof(rar->reserved1), + sizeof(rar->reserved2)); + if (rar->main_flags & MHD_ENCRYPTVER) { + if (skip < 7 + sizeof(rar->reserved1) + sizeof(rar->reserved2)+1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + rar->encryptver = *(p + 7 + sizeof(rar->reserved1) + + sizeof(rar->reserved2)); + } + + if (rar->main_flags & MHD_VOLUME || + rar->main_flags & MHD_FIRSTVOLUME) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "RAR volume support unavailable."); + return (ARCHIVE_FATAL); + } + if (rar->main_flags & MHD_PASSWORD) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "RAR encryption support unavailable."); + return (ARCHIVE_FATAL); + } + + crc32_val = crc32(0, (const unsigned char *)p + 2, skip - 2); + if ((crc32_val & 0xffff) != archive_le16dec(p)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Header CRC error"); + return (ARCHIVE_FATAL); + } + __archive_read_consume(a, skip); + break; + + case FILE_HEAD: + return read_header(a, entry, head_type); + + case COMM_HEAD: + case AV_HEAD: + case SUB_HEAD: + case PROTECT_HEAD: + case SIGN_HEAD: + flags = archive_le16dec(p + 3); + skip = archive_le16dec(p + 5); + if (skip < 7) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + if (skip > 7) { + if ((h = __archive_read_ahead(a, skip, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + } + if (flags & HD_ADD_SIZE_PRESENT) + { + if (skip < 7 + 4) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + skip += archive_le32dec(p + 7); + if ((h = __archive_read_ahead(a, skip, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + } + + crc32_val = crc32(0, (const unsigned char *)p + 2, skip - 2); + if ((crc32_val & 0xffff) != archive_le16dec(p)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Header CRC error"); + return (ARCHIVE_FATAL); + } + __archive_read_consume(a, skip); + break; + + case NEWSUB_HEAD: + if ((ret = read_header(a, entry, head_type)) < ARCHIVE_WARN) + return ret; + break; + + case ENDARC_HEAD: + return (ARCHIVE_EOF); + + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad RAR file"); + return (ARCHIVE_FATAL); + } + } +} + +static int +archive_read_format_rar_read_data(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct rar *rar = (struct rar *)(a->format->data); + int ret; + + if (rar->bytes_unconsumed > 0) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, rar->bytes_unconsumed); + rar->bytes_unconsumed = 0; + } + + if (rar->entry_eof) { + *buff = NULL; + *size = 0; + *offset = rar->offset; + return (ARCHIVE_EOF); + } + + switch (rar->compression_method) + { + case COMPRESS_METHOD_STORE: + ret = read_data_stored(a, buff, size, offset); + break; + + case COMPRESS_METHOD_FASTEST: + case COMPRESS_METHOD_FAST: + case COMPRESS_METHOD_NORMAL: + case COMPRESS_METHOD_GOOD: + case COMPRESS_METHOD_BEST: + ret = read_data_compressed(a, buff, size, offset); + if (ret != ARCHIVE_OK && ret != ARCHIVE_WARN) + __archive_ppmd7_functions.Ppmd7_Free(&rar->ppmd7_context, &g_szalloc); + break; + + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported compression method for RAR file."); + ret = ARCHIVE_FATAL; + break; + } + return (ret); +} + +static int +archive_read_format_rar_read_data_skip(struct archive_read *a) +{ + struct rar *rar; + int64_t bytes_skipped; + + rar = (struct rar *)(a->format->data); + + if (rar->bytes_unconsumed > 0) { + /* Consume as much as the decompressor actually used. */ + __archive_read_consume(a, rar->bytes_unconsumed); + rar->bytes_unconsumed = 0; + } + + if (rar->bytes_remaining > 0) { + bytes_skipped = __archive_read_consume(a, rar->bytes_remaining); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static int +archive_read_format_rar_cleanup(struct archive_read *a) +{ + struct rar *rar; + + rar = (struct rar *)(a->format->data); + free_codes(a); + free(rar->filename); + free(rar->unp_buffer); + free(rar->lzss.window); + __archive_ppmd7_functions.Ppmd7_Free(&rar->ppmd7_context, &g_szalloc); + free(rar); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +static int +read_header(struct archive_read *a, struct archive_entry *entry, + char head_type) +{ + const void *h; + const char *p, *endp; + struct rar *rar; + struct rar_header rar_header; + struct rar_file_header file_header; + int64_t header_size; + unsigned filename_size, end; + char *filename; + char *strp; + char packed_size[8]; + char unp_size[8]; + int time; + struct archive_string_conv *sconv, *fn_sconv; + unsigned long crc32_val; + int ret = (ARCHIVE_OK), ret2; + + rar = (struct rar *)(a->format->data); + + /* Setup a string conversion object for non-rar-unicode filenames. */ + sconv = rar->opt_sconv; + if (sconv == NULL) { + if (!rar->init_default_conversion) { + rar->sconv_default = + archive_string_default_conversion_for_read( + &(a->archive)); + rar->init_default_conversion = 1; + } + sconv = rar->sconv_default; + } + + + if ((h = __archive_read_ahead(a, 7, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + memcpy(&rar_header, p, sizeof(rar_header)); + rar->file_flags = archive_le16dec(rar_header.flags); + header_size = archive_le16dec(rar_header.size); + if (header_size < sizeof(file_header) + 7) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + crc32_val = crc32(0, (const unsigned char *)p + 2, 7 - 2); + __archive_read_consume(a, 7); + + if (!(rar->file_flags & FHD_SOLID)) + { + rar->compression_method = 0; + rar->packed_size = 0; + rar->unp_size = 0; + rar->mtime = 0; + rar->ctime = 0; + rar->atime = 0; + rar->arctime = 0; + rar->mode = 0; + memset(&rar->salt, 0, sizeof(rar->salt)); + rar->atime = 0; + rar->ansec = 0; + rar->ctime = 0; + rar->cnsec = 0; + rar->mtime = 0; + rar->mnsec = 0; + rar->arctime = 0; + rar->arcnsec = 0; + } + else + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "RAR solid archive support unavailable."); + return (ARCHIVE_FATAL); + } + + if ((h = __archive_read_ahead(a, header_size - 7, NULL)) == NULL) + return (ARCHIVE_FATAL); + + /* File Header CRC check. */ + crc32_val = crc32(crc32_val, h, header_size - 7); + if ((crc32_val & 0xffff) != archive_le16dec(rar_header.crc)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Header CRC error"); + return (ARCHIVE_FATAL); + } + /* If no CRC error, Go on parsing File Header. */ + p = h; + endp = p + header_size - 7; + memcpy(&file_header, p, sizeof(file_header)); + p += sizeof(file_header); + + rar->compression_method = file_header.method; + + time = archive_le32dec(file_header.file_time); + rar->mtime = get_time(time); + + rar->file_crc = archive_le32dec(file_header.file_crc); + + if (rar->file_flags & FHD_PASSWORD) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "RAR encryption support unavailable."); + return (ARCHIVE_FATAL); + } + + if (rar->file_flags & FHD_LARGE) + { + memcpy(packed_size, file_header.pack_size, 4); + memcpy(packed_size + 4, p, 4); /* High pack size */ + p += 4; + memcpy(unp_size, file_header.unp_size, 4); + memcpy(unp_size + 4, p, 4); /* High unpack size */ + p += 4; + rar->packed_size = archive_le64dec(&packed_size); + rar->unp_size = archive_le64dec(&unp_size); + } + else + { + rar->packed_size = archive_le32dec(file_header.pack_size); + rar->unp_size = archive_le32dec(file_header.unp_size); + } + + /* TODO: Need to use CRC check for these kind of cases. + * For now, check if sizes are not < 0. + */ + if (rar->packed_size < 0 || rar->unp_size < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid sizes specified."); + return (ARCHIVE_FATAL); + } + + /* TODO: RARv3 subblocks contain comments. For now the complete block is + * consumed at the end. + */ + if (head_type == NEWSUB_HEAD) { + size_t distance = p - (const char *)h; + header_size += rar->packed_size; + /* Make sure we have the extended data. */ + if ((h = __archive_read_ahead(a, header_size - 7, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + endp = p + header_size - 7; + p += distance; + } + + filename_size = archive_le16dec(file_header.name_size); + if (p + filename_size > endp) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid filename size"); + return (ARCHIVE_FATAL); + } + if (rar->filename_allocated < filename_size+2) { + rar->filename = realloc(rar->filename, filename_size+2); + if (rar->filename == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory."); + return (ARCHIVE_FATAL); + } + } + filename = rar->filename; + memcpy(filename, p, filename_size); + filename[filename_size] = '\0'; + if (rar->file_flags & FHD_UNICODE) + { + if (filename_size != strlen(filename)) + { + unsigned char highbyte, flagbits, flagbyte, length, offset; + + end = filename_size; + filename_size = 0; + offset = strlen(filename) + 1; + highbyte = *(p + offset++); + flagbits = 0; + flagbyte = 0; + while (offset < end && filename_size < end) + { + if (!flagbits) + { + flagbyte = *(p + offset++); + flagbits = 8; + } + + flagbits -= 2; + switch((flagbyte >> flagbits) & 3) + { + case 0: + filename[filename_size++] = '\0'; + filename[filename_size++] = *(p + offset++); + break; + case 1: + filename[filename_size++] = highbyte; + filename[filename_size++] = *(p + offset++); + break; + case 2: + filename[filename_size++] = *(p + offset + 1); + filename[filename_size++] = *(p + offset); + offset += 2; + break; + case 3: + { + length = *(p + offset++); + while (length) + { + if (filename_size >= end) + break; + filename[filename_size++] = *(p + offset); + length--; + } + } + break; + } + } + if (filename_size >= end) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid filename"); + return (ARCHIVE_FATAL); + } + filename[filename_size++] = '\0'; + filename[filename_size++] = '\0'; + + /* Decoded unicode form is UTF-16BE, so we have to update a string + * conversion object for it. */ + if (rar->sconv_utf16be == NULL) { + rar->sconv_utf16be = archive_string_conversion_from_charset( + &a->archive, "UTF-16BE", 1); + if (rar->sconv_utf16be == NULL) + return (ARCHIVE_FATAL); + } + fn_sconv = rar->sconv_utf16be; + + strp = filename; + while (memcmp(strp, "\x00\x00", 2)) + { + if (!memcmp(strp, "\x00\\", 2)) + *(strp + 1) = '/'; + strp += 2; + } + p += offset; + } else { + /* + * If FHD_UNICODE is set but no unicode data, this file name form + * is UTF-8, so we have to update a string conversion object for + * it accordingly. + */ + if (rar->sconv_utf8 == NULL) { + rar->sconv_utf8 = archive_string_conversion_from_charset( + &a->archive, "UTF-8", 1); + if (rar->sconv_utf8 == NULL) + return (ARCHIVE_FATAL); + } + fn_sconv = rar->sconv_utf8; + while ((strp = strchr(filename, '\\')) != NULL) + *strp = '/'; + p += filename_size; + } + } + else + { + fn_sconv = sconv; + while ((strp = strchr(filename, '\\')) != NULL) + *strp = '/'; + p += filename_size; + } + + if (rar->file_flags & FHD_SALT) + { + if (p + 8 > endp) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + memcpy(rar->salt, p, 8); + p += 8; + } + + if (rar->file_flags & FHD_EXTTIME) { + if (read_exttime(p, rar, endp) < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header size"); + return (ARCHIVE_FATAL); + } + } + + __archive_read_consume(a, header_size - 7); + + switch(file_header.host_os) + { + case OS_MSDOS: + case OS_OS2: + case OS_WIN32: + rar->mode = archive_le32dec(file_header.file_attr); + if (rar->mode & FILE_ATTRIBUTE_DIRECTORY) + rar->mode = AE_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + else + rar->mode = AE_IFREG; + rar->mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + break; + + case OS_UNIX: + case OS_MAC_OS: + case OS_BEOS: + rar->mode = archive_le32dec(file_header.file_attr); + break; + + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unknown file attributes from RAR file's host OS"); + return (ARCHIVE_FATAL); + } + + rar->bytes_remaining = rar->packed_size; + rar->bytes_uncopied = rar->bytes_unconsumed = 0; + rar->lzss.position = rar->dictionary_size = rar->offset = 0; + rar->offset_outgoing = 0; + rar->br.cache_avail = 0; + rar->br.avail_in = 0; + rar->crc_calculated = 0; + rar->entry_eof = 0; + rar->valid = 1; + rar->is_ppmd_block = 0; + rar->start_new_table = 1; + free(rar->unp_buffer); + rar->unp_buffer = NULL; + rar->unp_offset = 0; + rar->unp_buffer_size = UNP_BUFFER_SIZE; + memset(rar->lengthtable, 0, sizeof(rar->lengthtable)); + __archive_ppmd7_functions.Ppmd7_Free(&rar->ppmd7_context, &g_szalloc); + rar->ppmd_valid = rar->ppmd_eod = 0; + + /* Don't set any archive entries for non-file header types */ + if (head_type == NEWSUB_HEAD) + return ret; + + archive_entry_set_mtime(entry, rar->mtime, rar->mnsec); + archive_entry_set_ctime(entry, rar->ctime, rar->cnsec); + archive_entry_set_atime(entry, rar->atime, rar->ansec); + archive_entry_set_size(entry, rar->unp_size); + archive_entry_set_mode(entry, rar->mode); + + if (archive_entry_copy_pathname_l(entry, filename, filename_size, fn_sconv)) + { + if (errno == ENOMEM) + { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(fn_sconv)); + ret = (ARCHIVE_WARN); + } + + if (((rar->mode) & AE_IFMT) == AE_IFLNK) + { + /* Make sure a symbolic-link file does not have its body. */ + rar->bytes_remaining = 0; + archive_entry_set_size(entry, 0); + + /* Read a symbolic-link name. */ + if ((ret2 = read_symlink_stored(a, entry, sconv)) < (ARCHIVE_WARN)) + return ret2; + if (ret > ret2) + ret = ret2; + } + + if (rar->bytes_remaining == 0) + rar->entry_eof = 1; + + return ret; +} + +static time_t +get_time(int time) +{ + struct tm tm; + tm.tm_sec = 2 * (time & 0x1f); + tm.tm_min = (time >> 5) & 0x3f; + tm.tm_hour = (time >> 11) & 0x1f; + tm.tm_mday = (time >> 16) & 0x1f; + tm.tm_mon = ((time >> 21) & 0x0f) - 1; + tm.tm_year = ((time >> 25) & 0x7f) + 80; + tm.tm_isdst = -1; + return mktime(&tm); +} + +static int +read_exttime(const char *p, struct rar *rar, const char *endp) +{ + unsigned rmode, flags, rem, j, count; + int time, i; + struct tm *tm; + time_t t; + long nsec; + + if (p + 2 > endp) + return (-1); + flags = archive_le16dec(p); + p += 2; + + for (i = 3; i >= 0; i--) + { + t = 0; + if (i == 3) + t = rar->mtime; + rmode = flags >> i * 4; + if (rmode & 8) + { + if (!t) + { + if (p + 4 > endp) + return (-1); + time = archive_le32dec(p); + t = get_time(time); + p += 4; + } + rem = 0; + count = rmode & 3; + if (p + count > endp) + return (-1); + for (j = 0; j < count; j++) + { + rem = ((*p) << 16) | (rem >> 8); + p++; + } + tm = localtime(&t); + nsec = tm->tm_sec + rem / NS_UNIT; + if (rmode & 4) + { + tm->tm_sec++; + t = mktime(tm); + } + if (i == 3) + { + rar->mtime = t; + rar->mnsec = nsec; + } + else if (i == 2) + { + rar->ctime = t; + rar->cnsec = nsec; + } + else if (i == 1) + { + rar->atime = t; + rar->ansec = nsec; + } + else + { + rar->arctime = t; + rar->arcnsec = nsec; + } + } + } + return (0); +} + +static int +read_symlink_stored(struct archive_read *a, struct archive_entry *entry, + struct archive_string_conv *sconv) +{ + const void *h; + const char *p; + struct rar *rar; + int ret = (ARCHIVE_OK); + + rar = (struct rar *)(a->format->data); + if ((h = __archive_read_ahead(a, rar->packed_size, NULL)) == NULL) + return (ARCHIVE_FATAL); + p = h; + + if (archive_entry_copy_symlink_l(entry, p, rar->packed_size, sconv)) + { + if (errno == ENOMEM) + { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for link"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "link cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(sconv)); + ret = (ARCHIVE_WARN); + } + __archive_read_consume(a, rar->packed_size); + return ret; +} + +static int +read_data_stored(struct archive_read *a, const void **buff, size_t *size, + int64_t *offset) +{ + struct rar *rar; + ssize_t bytes_avail; + + rar = (struct rar *)(a->format->data); + if (rar->bytes_remaining == 0) + { + *buff = NULL; + *size = 0; + *offset = rar->offset; + if (rar->file_crc != rar->crc_calculated) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "File CRC error"); + return (ARCHIVE_FATAL); + } + rar->entry_eof = 1; + return (ARCHIVE_EOF); + } + + *buff = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > rar->bytes_remaining) + bytes_avail = rar->bytes_remaining; + + *size = bytes_avail; + *offset = rar->offset; + rar->offset += bytes_avail; + rar->bytes_remaining -= bytes_avail; + rar->bytes_unconsumed = bytes_avail; + /* Calculate File CRC. */ + rar->crc_calculated = crc32(rar->crc_calculated, *buff, bytes_avail); + return (ARCHIVE_OK); +} + +static int +read_data_compressed(struct archive_read *a, const void **buff, size_t *size, + int64_t *offset) +{ + struct rar *rar; + int64_t start, end, actualend; + size_t bs; + int ret = (ARCHIVE_OK), sym, code, lzss_offset, length, i; + + rar = (struct rar *)(a->format->data); + + do { + if (!rar->valid) + return (ARCHIVE_FATAL); + if (rar->ppmd_eod || + (rar->dictionary_size && rar->offset >= rar->unp_size)) + { + if (rar->unp_offset > 0) { + /* + * We have unprocessed extracted data. write it out. + */ + *buff = rar->unp_buffer; + *size = rar->unp_offset; + *offset = rar->offset_outgoing; + rar->offset_outgoing += *size; + /* Calculate File CRC. */ + rar->crc_calculated = crc32(rar->crc_calculated, *buff, *size); + rar->unp_offset = 0; + return (ARCHIVE_OK); + } + *buff = NULL; + *size = 0; + *offset = rar->offset; + if (rar->file_crc != rar->crc_calculated) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "File CRC error"); + return (ARCHIVE_FATAL); + } + rar->entry_eof = 1; + return (ARCHIVE_EOF); + } + + if (!rar->is_ppmd_block && rar->dictionary_size && rar->bytes_uncopied > 0) + { + if (rar->bytes_uncopied > (rar->unp_buffer_size - rar->unp_offset)) + bs = rar->unp_buffer_size - rar->unp_offset; + else + bs = rar->bytes_uncopied; + ret = copy_from_lzss_window(a, buff, rar->offset, bs); + if (ret != ARCHIVE_OK) + return (ret); + rar->offset += bs; + rar->bytes_uncopied -= bs; + if (*buff != NULL) { + rar->unp_offset = 0; + *size = rar->unp_buffer_size; + *offset = rar->offset_outgoing; + rar->offset_outgoing += *size; + /* Calculate File CRC. */ + rar->crc_calculated = crc32(rar->crc_calculated, *buff, *size); + return (ret); + } + continue; + } + + if (!rar->br.next_in && + (ret = rar_br_preparation(a, &(rar->br))) < ARCHIVE_WARN) + return (ret); + if (rar->start_new_table && ((ret = parse_codes(a)) < (ARCHIVE_WARN))) + return (ret); + + if (rar->is_ppmd_block) + { + if ((sym = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &rar->ppmd7_context, &rar->range_dec.p)) < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid symbol"); + return (ARCHIVE_FATAL); + } + if(sym != rar->ppmd_escape) + { + lzss_emit_literal(rar, sym); + rar->bytes_uncopied++; + } + else + { + if ((code = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &rar->ppmd7_context, &rar->range_dec.p)) < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid symbol"); + return (ARCHIVE_FATAL); + } + + switch(code) + { + case 0: + rar->start_new_table = 1; + return read_data_compressed(a, buff, size, offset); + + case 2: + rar->ppmd_eod = 1;/* End Of ppmd Data. */ + continue; + + case 3: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Parsing filters is unsupported."); + return (ARCHIVE_FAILED); + + case 4: + lzss_offset = 0; + for (i = 2; i >= 0; i--) + { + if ((code = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &rar->ppmd7_context, &rar->range_dec.p)) < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid symbol"); + return (ARCHIVE_FATAL); + } + lzss_offset |= code << (i * 8); + } + if ((length = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &rar->ppmd7_context, &rar->range_dec.p)) < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid symbol"); + return (ARCHIVE_FATAL); + } + lzss_emit_match(rar, lzss_offset + 2, length + 32); + rar->bytes_uncopied += length + 32; + break; + + case 5: + if ((length = __archive_ppmd7_functions.Ppmd7_DecodeSymbol( + &rar->ppmd7_context, &rar->range_dec.p)) < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid symbol"); + return (ARCHIVE_FATAL); + } + lzss_emit_match(rar, 1, length + 4); + rar->bytes_uncopied += length + 4; + break; + + default: + lzss_emit_literal(rar, sym); + rar->bytes_uncopied++; + } + } + } + else + { + start = rar->offset; + end = start + rar->dictionary_size; + rar->filterstart = INT64_MAX; + + if ((actualend = expand(a, end)) < 0) + return ((int)actualend); + + rar->bytes_uncopied = actualend - start; + if (rar->bytes_uncopied == 0) { + /* Broken RAR files cause this case. + * NOTE: If this case were possible on a normal RAR file + * we would find out where it was actually bad and + * what we would do to solve it. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Internal error extracting RAR file"); + return (ARCHIVE_FATAL); + } + } + if (rar->bytes_uncopied > (rar->unp_buffer_size - rar->unp_offset)) + bs = rar->unp_buffer_size - rar->unp_offset; + else + bs = rar->bytes_uncopied; + ret = copy_from_lzss_window(a, buff, rar->offset, bs); + if (ret != ARCHIVE_OK) + return (ret); + rar->offset += bs; + rar->bytes_uncopied -= bs; + /* + * If *buff is NULL, it means unp_buffer is not full. + * So we have to continue extracting a RAR file. + */ + } while (*buff == NULL); + + rar->unp_offset = 0; + *size = rar->unp_buffer_size; + *offset = rar->offset_outgoing; + rar->offset_outgoing += *size; + /* Calculate File CRC. */ + rar->crc_calculated = crc32(rar->crc_calculated, *buff, *size); + return ret; +} + +static int +parse_codes(struct archive_read *a) +{ + int i, j, val, n, r; + unsigned char bitlengths[MAX_SYMBOLS], zerocount, ppmd_flags; + unsigned int maxorder; + struct huffman_code precode; + struct rar *rar = (struct rar *)(a->format->data); + struct rar_br *br = &(rar->br); + + free_codes(a); + + /* Skip to the next byte */ + rar_br_consume_unalined_bits(br); + + /* PPMd block flag */ + if (!rar_br_read_ahead(a, br, 1)) + goto truncated_data; + if ((rar->is_ppmd_block = rar_br_bits(br, 1)) != 0) + { + rar_br_consume(br, 1); + if (!rar_br_read_ahead(a, br, 7)) + goto truncated_data; + ppmd_flags = rar_br_bits(br, 7); + rar_br_consume(br, 7); + + /* Memory is allocated in MB */ + if (ppmd_flags & 0x20) + { + if (!rar_br_read_ahead(a, br, 8)) + goto truncated_data; + rar->dictionary_size = (rar_br_bits(br, 8) + 1) << 20; + rar_br_consume(br, 8); + } + + if (ppmd_flags & 0x40) + { + if (!rar_br_read_ahead(a, br, 8)) + goto truncated_data; + rar->ppmd_escape = rar->ppmd7_context.InitEsc = rar_br_bits(br, 8); + rar_br_consume(br, 8); + } + else + rar->ppmd_escape = 2; + + if (ppmd_flags & 0x20) + { + maxorder = (ppmd_flags & 0x1F) + 1; + if(maxorder > 16) + maxorder = 16 + (maxorder - 16) * 3; + + if (maxorder == 1) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + return (ARCHIVE_FATAL); + } + + /* Make sure ppmd7_contest is freed before Ppmd7_Construct + * because reading a broken file cause this abnormal sequence. */ + __archive_ppmd7_functions.Ppmd7_Free(&rar->ppmd7_context, &g_szalloc); + + rar->bytein.a = a; + rar->bytein.Read = &ppmd_read; + __archive_ppmd7_functions.PpmdRAR_RangeDec_CreateVTable(&rar->range_dec); + rar->range_dec.Stream = &rar->bytein; + __archive_ppmd7_functions.Ppmd7_Construct(&rar->ppmd7_context); + + if (!__archive_ppmd7_functions.Ppmd7_Alloc(&rar->ppmd7_context, + rar->dictionary_size, &g_szalloc)) + { + archive_set_error(&a->archive, ENOMEM, + "Out of memory"); + return (ARCHIVE_FATAL); + } + if (!__archive_ppmd7_functions.PpmdRAR_RangeDec_Init(&rar->range_dec)) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unable to initialize PPMd range decoder"); + return (ARCHIVE_FATAL); + } + __archive_ppmd7_functions.Ppmd7_Init(&rar->ppmd7_context, maxorder); + rar->ppmd_valid = 1; + } + else + { + if (!rar->ppmd_valid) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid PPMd sequence"); + return (ARCHIVE_FATAL); + } + if (!__archive_ppmd7_functions.PpmdRAR_RangeDec_Init(&rar->range_dec)) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unable to initialize PPMd range decoder"); + return (ARCHIVE_FATAL); + } + } + } + else + { + rar_br_consume(br, 1); + + /* Keep existing table flag */ + if (!rar_br_read_ahead(a, br, 1)) + goto truncated_data; + if (!rar_br_bits(br, 1)) + memset(rar->lengthtable, 0, sizeof(rar->lengthtable)); + rar_br_consume(br, 1); + + memset(&bitlengths, 0, sizeof(bitlengths)); + for (i = 0; i < MAX_SYMBOLS;) + { + if (!rar_br_read_ahead(a, br, 4)) + goto truncated_data; + bitlengths[i++] = rar_br_bits(br, 4); + rar_br_consume(br, 4); + if (bitlengths[i-1] == 0xF) + { + if (!rar_br_read_ahead(a, br, 4)) + goto truncated_data; + zerocount = rar_br_bits(br, 4); + rar_br_consume(br, 4); + if (zerocount) + { + i--; + for (j = 0; j < zerocount + 2 && i < MAX_SYMBOLS; j++) + bitlengths[i++] = 0; + } + } + } + + memset(&precode, 0, sizeof(precode)); + r = create_code(a, &precode, bitlengths, MAX_SYMBOLS, MAX_SYMBOL_LENGTH); + if (r != ARCHIVE_OK) { + free(precode.tree); + free(precode.table); + return (r); + } + + for (i = 0; i < HUFFMAN_TABLE_SIZE;) + { + if ((val = read_next_symbol(a, &precode)) < 0) { + free(precode.tree); + free(precode.table); + return (ARCHIVE_FATAL); + } + if (val < 16) + { + rar->lengthtable[i] = (rar->lengthtable[i] + val) & 0xF; + i++; + } + else if (val < 18) + { + if (i == 0) + { + free(precode.tree); + free(precode.table); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Internal error extracting RAR file."); + return (ARCHIVE_FATAL); + } + + if(val == 16) { + if (!rar_br_read_ahead(a, br, 3)) { + free(precode.tree); + free(precode.table); + goto truncated_data; + } + n = rar_br_bits(br, 3) + 3; + rar_br_consume(br, 3); + } else { + if (!rar_br_read_ahead(a, br, 7)) { + free(precode.tree); + free(precode.table); + goto truncated_data; + } + n = rar_br_bits(br, 7) + 11; + rar_br_consume(br, 7); + } + + for (j = 0; j < n && i < HUFFMAN_TABLE_SIZE; j++) + { + rar->lengthtable[i] = rar->lengthtable[i-1]; + i++; + } + } + else + { + if(val == 18) { + if (!rar_br_read_ahead(a, br, 3)) { + free(precode.tree); + free(precode.table); + goto truncated_data; + } + n = rar_br_bits(br, 3) + 3; + rar_br_consume(br, 3); + } else { + if (!rar_br_read_ahead(a, br, 7)) { + free(precode.tree); + free(precode.table); + goto truncated_data; + } + n = rar_br_bits(br, 7) + 11; + rar_br_consume(br, 7); + } + + for(j = 0; j < n && i < HUFFMAN_TABLE_SIZE; j++) + rar->lengthtable[i++] = 0; + } + } + free(precode.tree); + free(precode.table); + + r = create_code(a, &rar->maincode, &rar->lengthtable[0], MAINCODE_SIZE, + MAX_SYMBOL_LENGTH); + if (r != ARCHIVE_OK) + return (r); + r = create_code(a, &rar->offsetcode, &rar->lengthtable[MAINCODE_SIZE], + OFFSETCODE_SIZE, MAX_SYMBOL_LENGTH); + if (r != ARCHIVE_OK) + return (r); + r = create_code(a, &rar->lowoffsetcode, + &rar->lengthtable[MAINCODE_SIZE + OFFSETCODE_SIZE], + LOWOFFSETCODE_SIZE, MAX_SYMBOL_LENGTH); + if (r != ARCHIVE_OK) + return (r); + r = create_code(a, &rar->lengthcode, + &rar->lengthtable[MAINCODE_SIZE + OFFSETCODE_SIZE + + LOWOFFSETCODE_SIZE], LENGTHCODE_SIZE, MAX_SYMBOL_LENGTH); + if (r != ARCHIVE_OK) + return (r); + } + + if (!rar->dictionary_size || !rar->lzss.window) + { + /* Seems as though dictionary sizes are not used. Even so, minimize + * memory usage as much as possible. + */ + if (rar->unp_size >= DICTIONARY_MAX_SIZE) + rar->dictionary_size = DICTIONARY_MAX_SIZE; + else + rar->dictionary_size = rar_fls(rar->unp_size) << 1; + rar->lzss.window = (unsigned char *)realloc(rar->lzss.window, + rar->dictionary_size); + if (rar->lzss.window == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for uncompressed data."); + return (ARCHIVE_FATAL); + } + memset(rar->lzss.window, 0, rar->dictionary_size); + rar->lzss.mask = rar->dictionary_size - 1; + } + + rar->start_new_table = 0; + return (ARCHIVE_OK); +truncated_data: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + rar->valid = 0; + return (ARCHIVE_FATAL); +} + +static void +free_codes(struct archive_read *a) +{ + struct rar *rar = (struct rar *)(a->format->data); + free(rar->maincode.tree); + free(rar->offsetcode.tree); + free(rar->lowoffsetcode.tree); + free(rar->lengthcode.tree); + free(rar->maincode.table); + free(rar->offsetcode.table); + free(rar->lowoffsetcode.table); + free(rar->lengthcode.table); + memset(&rar->maincode, 0, sizeof(rar->maincode)); + memset(&rar->offsetcode, 0, sizeof(rar->offsetcode)); + memset(&rar->lowoffsetcode, 0, sizeof(rar->lowoffsetcode)); + memset(&rar->lengthcode, 0, sizeof(rar->lengthcode)); +} + + +static int +read_next_symbol(struct archive_read *a, struct huffman_code *code) +{ + unsigned char bit; + unsigned int bits; + int length, value, node; + struct rar *rar; + struct rar_br *br; + + if (!code->table) + { + if (make_table(a, code) != (ARCHIVE_OK)) + return -1; + } + + rar = (struct rar *)(a->format->data); + br = &(rar->br); + + /* Look ahead (peek) at bits */ + if (!rar_br_read_ahead(a, br, code->tablesize)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + rar->valid = 0; + return -1; + } + bits = rar_br_bits(br, code->tablesize); + + length = code->table[bits].length; + value = code->table[bits].value; + + if (length < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid prefix code in bitstream"); + return -1; + } + + if (length <= code->tablesize) + { + /* Skip length bits */ + rar_br_consume(br, length); + return value; + } + + /* Skip tablesize bits */ + rar_br_consume(br, code->tablesize); + + node = value; + while (!(code->tree[node].branches[0] == + code->tree[node].branches[1])) + { + if (!rar_br_read_ahead(a, br, 1)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + rar->valid = 0; + return -1; + } + bit = rar_br_bits(br, 1); + rar_br_consume(br, 1); + + if (code->tree[node].branches[bit] < 0) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid prefix code in bitstream"); + return -1; + } + node = code->tree[node].branches[bit]; + } + + return code->tree[node].branches[0]; +} + +static int +create_code(struct archive_read *a, struct huffman_code *code, + unsigned char *lengths, int numsymbols, char maxlength) +{ + int i, j, codebits = 0, symbolsleft = numsymbols; + + if (new_node(code) < 0) { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for node data."); + return (ARCHIVE_FATAL); + } + code->numentries = 1; + code->minlength = INT_MAX; + code->maxlength = INT_MIN; + codebits = 0; + for(i = 1; i <= maxlength; i++) + { + for(j = 0; j < numsymbols; j++) + { + if (lengths[j] != i) continue; + if (add_value(a, code, j, codebits, i) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + codebits++; + if (--symbolsleft <= 0) { break; break; } + } + codebits <<= 1; + } + return (ARCHIVE_OK); +} + +static int +add_value(struct archive_read *a, struct huffman_code *code, int value, + int codebits, int length) +{ + int repeatpos, lastnode, bitpos, bit, repeatnode, nextnode; + + free(code->table); + code->table = NULL; + + if(length > code->maxlength) + code->maxlength = length; + if(length < code->minlength) + code->minlength = length; + + repeatpos = -1; + if (repeatpos == 0 || (repeatpos >= 0 + && (((codebits >> (repeatpos - 1)) & 3) == 0 + || ((codebits >> (repeatpos - 1)) & 3) == 3))) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid repeat position"); + return (ARCHIVE_FATAL); + } + + lastnode = 0; + for (bitpos = length - 1; bitpos >= 0; bitpos--) + { + bit = (codebits >> bitpos) & 1; + + /* Leaf node check */ + if (code->tree[lastnode].branches[0] == + code->tree[lastnode].branches[1]) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Prefix found"); + return (ARCHIVE_FATAL); + } + + if (bitpos == repeatpos) + { + /* Open branch check */ + if (!(code->tree[lastnode].branches[bit] < 0)) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid repeating code"); + return (ARCHIVE_FATAL); + } + + if ((repeatnode = new_node(code)) < 0) { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for node data."); + return (ARCHIVE_FATAL); + } + if ((nextnode = new_node(code)) < 0) { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for node data."); + return (ARCHIVE_FATAL); + } + + /* Set branches */ + code->tree[lastnode].branches[bit] = repeatnode; + code->tree[repeatnode].branches[bit] = repeatnode; + code->tree[repeatnode].branches[bit^1] = nextnode; + lastnode = nextnode; + + bitpos++; /* terminating bit already handled, skip it */ + } + else + { + /* Open branch check */ + if (code->tree[lastnode].branches[bit] < 0) + { + if (new_node(code) < 0) { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for node data."); + return (ARCHIVE_FATAL); + } + code->tree[lastnode].branches[bit] = code->numentries++; + } + + /* set to branch */ + lastnode = code->tree[lastnode].branches[bit]; + } + } + + if (!(code->tree[lastnode].branches[0] == -1 + && code->tree[lastnode].branches[1] == -2)) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Prefix found"); + return (ARCHIVE_FATAL); + } + + /* Set leaf value */ + code->tree[lastnode].branches[0] = value; + code->tree[lastnode].branches[1] = value; + + return (ARCHIVE_OK); +} + +static int +new_node(struct huffman_code *code) +{ + code->tree = (struct huffman_tree_node *)realloc(code->tree, + (code->numentries + 1) * sizeof(*code->tree)); + if (code->tree == NULL) + return (-1); + code->tree[code->numentries].branches[0] = -1; + code->tree[code->numentries].branches[1] = -2; + return 1; +} + +static int +make_table(struct archive_read *a, struct huffman_code *code) +{ + if (code->maxlength < code->minlength || code->maxlength > 10) + code->tablesize = 10; + else + code->tablesize = code->maxlength; + + code->table = + (struct huffman_table_entry *)malloc(sizeof(*code->table) + * (1 << code->tablesize)); + + return make_table_recurse(a, code, 0, code->table, 0, code->tablesize); +} + +static int +make_table_recurse(struct archive_read *a, struct huffman_code *code, int node, + struct huffman_table_entry *table, int depth, + int maxdepth) +{ + int currtablesize, i, ret = (ARCHIVE_OK); + + if (!code->tree) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Huffman tree was not created."); + return (ARCHIVE_FATAL); + } + if (node < 0 || node >= code->numentries) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid location to Huffman tree specified."); + return (ARCHIVE_FATAL); + } + + currtablesize = 1 << (maxdepth - depth); + + if (code->tree[node].branches[0] == + code->tree[node].branches[1]) + { + for(i = 0; i < currtablesize; i++) + { + table[i].length = depth; + table[i].value = code->tree[node].branches[0]; + } + } + else if (node < 0) + { + for(i = 0; i < currtablesize; i++) + table[i].length = -1; + } + else + { + if(depth == maxdepth) + { + table[0].length = maxdepth + 1; + table[0].value = node; + } + else + { + ret |= make_table_recurse(a, code, code->tree[node].branches[0], table, + depth + 1, maxdepth); + ret |= make_table_recurse(a, code, code->tree[node].branches[1], + table + currtablesize / 2, depth + 1, maxdepth); + } + } + return ret; +} + +static int64_t +expand(struct archive_read *a, int64_t end) +{ + static const unsigned char lengthbases[] = + { 0, 1, 2, 3, 4, 5, 6, + 7, 8, 10, 12, 14, 16, 20, + 24, 28, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 160, 192, 224 }; + static const unsigned char lengthbits[] = + { 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 2, 2, + 2, 2, 3, 3, 3, 3, 4, + 4, 4, 4, 5, 5, 5, 5 }; + static const unsigned int offsetbases[] = + { 0, 1, 2, 3, 4, 6, + 8, 12, 16, 24, 32, 48, + 64, 96, 128, 192, 256, 384, + 512, 768, 1024, 1536, 2048, 3072, + 4096, 6144, 8192, 12288, 16384, 24576, + 32768, 49152, 65536, 98304, 131072, 196608, + 262144, 327680, 393216, 458752, 524288, 589824, + 655360, 720896, 786432, 851968, 917504, 983040, + 1048576, 1310720, 1572864, 1835008, 2097152, 2359296, + 2621440, 2883584, 3145728, 3407872, 3670016, 3932160 }; + static const unsigned char offsetbits[] = + { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18 }; + static const unsigned char shortbases[] = + { 0, 4, 8, 16, 32, 64, 128, 192 }; + static const unsigned char shortbits[] = + { 2, 2, 3, 4, 5, 6, 6, 6 }; + + int symbol, offs, len, offsindex, lensymbol, i, offssymbol, lowoffsetsymbol; + unsigned char newfile; + struct rar *rar = (struct rar *)(a->format->data); + struct rar_br *br = &(rar->br); + + if (rar->filterstart < end) + end = rar->filterstart; + + while (1) + { + if (rar->output_last_match && + lzss_position(&rar->lzss) + rar->lastlength <= end) + { + lzss_emit_match(rar, rar->lastoffset, rar->lastlength); + rar->output_last_match = 0; + } + + if(rar->is_ppmd_block || rar->output_last_match || + lzss_position(&rar->lzss) >= end) + return lzss_position(&rar->lzss); + + if ((symbol = read_next_symbol(a, &rar->maincode)) < 0) + return (ARCHIVE_FATAL); + rar->output_last_match = 0; + + if (symbol < 256) + { + lzss_emit_literal(rar, symbol); + continue; + } + else if (symbol == 256) + { + if (!rar_br_read_ahead(a, br, 1)) + goto truncated_data; + newfile = !rar_br_bits(br, 1); + rar_br_consume(br, 1); + + if(newfile) + { + rar->start_new_block = 1; + if (!rar_br_read_ahead(a, br, 1)) + goto truncated_data; + rar->start_new_table = rar_br_bits(br, 1); + rar_br_consume(br, 1); + return lzss_position(&rar->lzss); + } + else + { + if (parse_codes(a) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + continue; + } + } + else if(symbol==257) + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Parsing filters is unsupported."); + return (ARCHIVE_FAILED); + } + else if(symbol==258) + { + if(rar->lastlength == 0) + continue; + + offs = rar->lastoffset; + len = rar->lastlength; + } + else if (symbol <= 262) + { + offsindex = symbol - 259; + offs = rar->oldoffset[offsindex]; + + if ((lensymbol = read_next_symbol(a, &rar->lengthcode)) < 0) + goto bad_data; + if (lensymbol > sizeof(lengthbases)/sizeof(lengthbases[0])) + goto bad_data; + if (lensymbol > sizeof(lengthbits)/sizeof(lengthbits[0])) + goto bad_data; + len = lengthbases[lensymbol] + 2; + if (lengthbits[lensymbol] > 0) { + if (!rar_br_read_ahead(a, br, lengthbits[lensymbol])) + goto truncated_data; + len += rar_br_bits(br, lengthbits[lensymbol]); + rar_br_consume(br, lengthbits[lensymbol]); + } + + for (i = offsindex; i > 0; i--) + rar->oldoffset[i] = rar->oldoffset[i-1]; + rar->oldoffset[0] = offs; + } + else if(symbol<=270) + { + offs = shortbases[symbol-263] + 1; + if(shortbits[symbol-263] > 0) { + if (!rar_br_read_ahead(a, br, shortbits[symbol-263])) + goto truncated_data; + offs += rar_br_bits(br, shortbits[symbol-263]); + rar_br_consume(br, shortbits[symbol-263]); + } + + len = 2; + + for(i = 3; i > 0; i--) + rar->oldoffset[i] = rar->oldoffset[i-1]; + rar->oldoffset[0] = offs; + } + else + { + if (symbol-271 > sizeof(lengthbases)/sizeof(lengthbases[0])) + goto bad_data; + if (symbol-271 > sizeof(lengthbits)/sizeof(lengthbits[0])) + goto bad_data; + len = lengthbases[symbol-271]+3; + if(lengthbits[symbol-271] > 0) { + if (!rar_br_read_ahead(a, br, lengthbits[symbol-271])) + goto truncated_data; + len += rar_br_bits(br, lengthbits[symbol-271]); + rar_br_consume(br, lengthbits[symbol-271]); + } + + if ((offssymbol = read_next_symbol(a, &rar->offsetcode)) < 0) + goto bad_data; + if (offssymbol > sizeof(offsetbases)/sizeof(offsetbases[0])) + goto bad_data; + if (offssymbol > sizeof(offsetbits)/sizeof(offsetbits[0])) + goto bad_data; + offs = offsetbases[offssymbol]+1; + if(offsetbits[offssymbol] > 0) + { + if(offssymbol > 9) + { + if(offsetbits[offssymbol] > 4) { + if (!rar_br_read_ahead(a, br, offsetbits[offssymbol] - 4)) + goto truncated_data; + offs += rar_br_bits(br, offsetbits[offssymbol] - 4) << 4; + rar_br_consume(br, offsetbits[offssymbol] - 4); + } + + if(rar->numlowoffsetrepeats > 0) + { + rar->numlowoffsetrepeats--; + offs += rar->lastlowoffset; + } + else + { + if ((lowoffsetsymbol = + read_next_symbol(a, &rar->lowoffsetcode)) < 0) + return (ARCHIVE_FATAL); + if(lowoffsetsymbol == 16) + { + rar->numlowoffsetrepeats = 15; + offs += rar->lastlowoffset; + } + else + { + offs += lowoffsetsymbol; + rar->lastlowoffset = lowoffsetsymbol; + } + } + } + else { + if (!rar_br_read_ahead(a, br, offsetbits[offssymbol])) + goto truncated_data; + offs += rar_br_bits(br, offsetbits[offssymbol]); + rar_br_consume(br, offsetbits[offssymbol]); + } + } + + if (offs >= 0x40000) + len++; + if (offs >= 0x2000) + len++; + + for(i = 3; i > 0; i--) + rar->oldoffset[i] = rar->oldoffset[i-1]; + rar->oldoffset[0] = offs; + } + + rar->lastoffset = offs; + rar->lastlength = len; + rar->output_last_match = 1; + } +truncated_data: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated RAR file data"); + rar->valid = 0; + return (ARCHIVE_FATAL); +bad_data: + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad RAR file data"); + return (ARCHIVE_FATAL); +} + +static int +copy_from_lzss_window(struct archive_read *a, const void **buffer, + int64_t startpos, int length) +{ + int windowoffs, firstpart; + struct rar *rar = (struct rar *)(a->format->data); + + if (!rar->unp_buffer) + { + if ((rar->unp_buffer = malloc(rar->unp_buffer_size)) == NULL) + { + archive_set_error(&a->archive, ENOMEM, + "Unable to allocate memory for uncompressed data."); + return (ARCHIVE_FATAL); + } + } + + windowoffs = lzss_offset_for_position(&rar->lzss, startpos); + if(windowoffs + length <= lzss_size(&rar->lzss)) + memcpy(&rar->unp_buffer[rar->unp_offset], &rar->lzss.window[windowoffs], + length); + else + { + firstpart = lzss_size(&rar->lzss) - windowoffs; + if (firstpart < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Bad RAR file data"); + return (ARCHIVE_FATAL); + } + if (firstpart < length) { + memcpy(&rar->unp_buffer[rar->unp_offset], + &rar->lzss.window[windowoffs], firstpart); + memcpy(&rar->unp_buffer[rar->unp_offset + firstpart], + &rar->lzss.window[0], length - firstpart); + } else + memcpy(&rar->unp_buffer[rar->unp_offset], + &rar->lzss.window[windowoffs], length); + } + rar->unp_offset += length; + if (rar->unp_offset >= rar->unp_buffer_size) + *buffer = rar->unp_buffer; + else + *buffer = NULL; + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_raw.c b/libarchive/archive_read_support_format_raw.c new file mode 100644 index 0000000..df2c00c --- /dev/null +++ b/libarchive/archive_read_support_format_raw.c @@ -0,0 +1,187 @@ +/*- + * Copyright (c) 2003-2009 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_raw.c 201107 2009-12-28 03:25:33Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_read_private.h" + +struct raw_info { + int64_t offset; /* Current position in the file. */ + int64_t unconsumed; + int end_of_file; +}; + +static int archive_read_format_raw_bid(struct archive_read *, int); +static int archive_read_format_raw_cleanup(struct archive_read *); +static int archive_read_format_raw_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_raw_read_data_skip(struct archive_read *); +static int archive_read_format_raw_read_header(struct archive_read *, + struct archive_entry *); + +int +archive_read_support_format_raw(struct archive *_a) +{ + struct raw_info *info; + struct archive_read *a = (struct archive_read *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_raw"); + + info = (struct raw_info *)calloc(1, sizeof(*info)); + if (info == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate raw_info data"); + return (ARCHIVE_FATAL); + } + + r = __archive_read_register_format(a, + info, + "raw", + archive_read_format_raw_bid, + NULL, + archive_read_format_raw_read_header, + archive_read_format_raw_read_data, + archive_read_format_raw_read_data_skip, + archive_read_format_raw_cleanup); + if (r != ARCHIVE_OK) + free(info); + return (r); +} + +/* + * Bid 1 if this is a non-empty file. Anyone who can really support + * this should outbid us, so it should generally be safe to use "raw" + * in conjunction with other formats. But, this could really confuse + * folks if there are bid errors or minor file damage, so we don't + * include "raw" as part of support_format_all(). + */ +static int +archive_read_format_raw_bid(struct archive_read *a, int best_bid) +{ + if (best_bid < 1 && __archive_read_ahead(a, 1, NULL) != NULL) + return (1); + return (-1); +} + +/* + * Mock up a fake header. + */ +static int +archive_read_format_raw_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct raw_info *info; + + info = (struct raw_info *)(a->format->data); + if (info->end_of_file) + return (ARCHIVE_EOF); + + a->archive.archive_format = ARCHIVE_FORMAT_RAW; + a->archive.archive_format_name = "raw"; + archive_entry_set_pathname(entry, "data"); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + /* I'm deliberately leaving most fields unset here. */ + return (ARCHIVE_OK); +} + +static int +archive_read_format_raw_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct raw_info *info; + ssize_t avail; + + info = (struct raw_info *)(a->format->data); + + /* Consume the bytes we read last time. */ + if (info->unconsumed) { + __archive_read_consume(a, info->unconsumed); + info->unconsumed = 0; + } + + if (info->end_of_file) + return (ARCHIVE_EOF); + + /* Get whatever bytes are immediately available. */ + *buff = __archive_read_ahead(a, 1, &avail); + if (avail > 0) { + /* Return the bytes we just read */ + *size = avail; + *offset = info->offset; + info->offset += *size; + info->unconsumed = avail; + return (ARCHIVE_OK); + } else if (0 == avail) { + /* Record and return end-of-file. */ + info->end_of_file = 1; + *size = 0; + *offset = info->offset; + return (ARCHIVE_EOF); + } else { + /* Record and return an error. */ + *size = 0; + *offset = info->offset; + return (avail); + } +} + +static int +archive_read_format_raw_read_data_skip(struct archive_read *a) +{ + struct raw_info *info = (struct raw_info *)(a->format->data); + + /* Consume the bytes we read last time. */ + if (info->unconsumed) { + __archive_read_consume(a, info->unconsumed); + info->unconsumed = 0; + } + info->end_of_file = 1; + return (ARCHIVE_OK); +} + +static int +archive_read_format_raw_cleanup(struct archive_read *a) +{ + struct raw_info *info; + + info = (struct raw_info *)(a->format->data); + free(info); + a->format->data = NULL; + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c new file mode 100644 index 0000000..89a1d4f --- /dev/null +++ b/libarchive/archive_read_support_format_tar.c @@ -0,0 +1,2692 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_tar.c 201161 2009-12-29 05:44:39Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_acl_private.h" /* For ACL parsing routines. */ +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#define tar_min(a,b) ((a) < (b) ? (a) : (b)) + +/* + * Layout of POSIX 'ustar' tar header. + */ +struct archive_entry_header_ustar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; /* "old format" header ends here */ + char magic[6]; /* For POSIX: "ustar\0" */ + char version[2]; /* For POSIX: "00" */ + char uname[32]; + char gname[32]; + char rdevmajor[8]; + char rdevminor[8]; + char prefix[155]; +}; + +/* + * Structure of GNU tar header + */ +struct gnu_sparse { + char offset[12]; + char numbytes[12]; +}; + +struct archive_entry_header_gnutar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[8]; /* "ustar \0" (note blank/blank/null at end) */ + char uname[32]; + char gname[32]; + char rdevmajor[8]; + char rdevminor[8]; + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + char unused[1]; + struct gnu_sparse sparse[4]; + char isextended[1]; + char realsize[12]; + /* + * Old GNU format doesn't use POSIX 'prefix' field; they use + * the 'L' (longname) entry instead. + */ +}; + +/* + * Data specific to this format. + */ +struct sparse_block { + struct sparse_block *next; + int64_t offset; + int64_t remaining; + int hole; +}; + +struct tar { + struct archive_string acl_text; + struct archive_string entry_pathname; + /* For "GNU.sparse.name" and other similar path extensions. */ + struct archive_string entry_pathname_override; + struct archive_string entry_linkpath; + struct archive_string entry_uname; + struct archive_string entry_gname; + struct archive_string longlink; + struct archive_string longname; + struct archive_string pax_header; + struct archive_string pax_global; + struct archive_string line; + int pax_hdrcharset_binary; + int header_recursion_depth; + int64_t entry_bytes_remaining; + int64_t entry_offset; + int64_t entry_padding; + int64_t entry_bytes_unconsumed; + int64_t realsize; + struct sparse_block *sparse_list; + struct sparse_block *sparse_last; + int64_t sparse_offset; + int64_t sparse_numbytes; + int sparse_gnu_major; + int sparse_gnu_minor; + char sparse_gnu_pending; + + struct archive_string localname; + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv; + struct archive_string_conv *sconv_acl; + struct archive_string_conv *sconv_default; + int init_default_conversion; + int compat_2x; +}; + +static int archive_block_is_null(const char *p); +static char *base64_decode(const char *, size_t, size_t *); +static int gnu_add_sparse_entry(struct archive_read *, struct tar *, + int64_t offset, int64_t remaining); + +static void gnu_clear_sparse_list(struct tar *); +static int gnu_sparse_old_read(struct archive_read *, struct tar *, + const struct archive_entry_header_gnutar *header, size_t *); +static int gnu_sparse_old_parse(struct archive_read *, struct tar *, + const struct gnu_sparse *sparse, int length); +static int gnu_sparse_01_parse(struct archive_read *, struct tar *, + const char *); +static ssize_t gnu_sparse_10_read(struct archive_read *, struct tar *, + size_t *); +static int header_Solaris_ACL(struct archive_read *, struct tar *, + struct archive_entry *, const void *, size_t *); +static int header_common(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_old_tar(struct archive_read *, struct tar *, + struct archive_entry *, const void *); +static int header_pax_extensions(struct archive_read *, struct tar *, + struct archive_entry *, const void *, size_t *); +static int header_pax_global(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int header_longlink(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int header_longname(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int read_mac_metadata_blob(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int header_volume(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int header_ustar(struct archive_read *, struct tar *, + struct archive_entry *, const void *h); +static int header_gnutar(struct archive_read *, struct tar *, + struct archive_entry *, const void *h, size_t *); +static int archive_read_format_tar_bid(struct archive_read *, int); +static int archive_read_format_tar_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_tar_cleanup(struct archive_read *); +static int archive_read_format_tar_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset); +static int archive_read_format_tar_skip(struct archive_read *a); +static int archive_read_format_tar_read_header(struct archive_read *, + struct archive_entry *); +static int checksum(struct archive_read *, const void *); +static int pax_attribute(struct archive_read *, struct tar *, + struct archive_entry *, char *key, char *value); +static int pax_header(struct archive_read *, struct tar *, + struct archive_entry *, char *attr); +static void pax_time(const char *, int64_t *sec, long *nanos); +static ssize_t readline(struct archive_read *, struct tar *, const char **, + ssize_t limit, size_t *); +static int read_body_to_string(struct archive_read *, struct tar *, + struct archive_string *, const void *h, size_t *); +static int solaris_sparse_parse(struct archive_read *, struct tar *, + struct archive_entry *, const char *); +static int64_t tar_atol(const char *, unsigned); +static int64_t tar_atol10(const char *, unsigned); +static int64_t tar_atol256(const char *, unsigned); +static int64_t tar_atol8(const char *, unsigned); +static int tar_read_header(struct archive_read *, struct tar *, + struct archive_entry *, size_t *); +static int tohex(int c); +static char *url_decode(const char *); +static void tar_flush_unconsumed(struct archive_read *, size_t *); + + +int +archive_read_support_format_gnutar(struct archive *a) +{ + archive_check_magic(a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_gnutar"); + return (archive_read_support_format_tar(a)); +} + + +int +archive_read_support_format_tar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct tar *tar; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_tar"); + + tar = (struct tar *)calloc(1, sizeof(*tar)); + if (tar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate tar data"); + return (ARCHIVE_FATAL); + } + + r = __archive_read_register_format(a, tar, "tar", + archive_read_format_tar_bid, + archive_read_format_tar_options, + archive_read_format_tar_read_header, + archive_read_format_tar_read_data, + archive_read_format_tar_skip, + archive_read_format_tar_cleanup); + + if (r != ARCHIVE_OK) + free(tar); + return (ARCHIVE_OK); +} + +static int +archive_read_format_tar_cleanup(struct archive_read *a) +{ + struct tar *tar; + + tar = (struct tar *)(a->format->data); + gnu_clear_sparse_list(tar); + archive_string_free(&tar->acl_text); + archive_string_free(&tar->entry_pathname); + archive_string_free(&tar->entry_pathname_override); + archive_string_free(&tar->entry_linkpath); + archive_string_free(&tar->entry_uname); + archive_string_free(&tar->entry_gname); + archive_string_free(&tar->line); + archive_string_free(&tar->pax_global); + archive_string_free(&tar->pax_header); + archive_string_free(&tar->longname); + archive_string_free(&tar->longlink); + archive_string_free(&tar->localname); + free(tar); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + + +static int +archive_read_format_tar_bid(struct archive_read *a, int best_bid) +{ + int bid; + const char *h; + const struct archive_entry_header_ustar *header; + + (void)best_bid; /* UNUSED */ + + bid = 0; + + /* Now let's look at the actual header and see if it matches. */ + h = __archive_read_ahead(a, 512, NULL); + if (h == NULL) + return (-1); + + /* If it's an end-of-archive mark, we can handle it. */ + if (h[0] == 0 && archive_block_is_null(h)) { + /* + * Usually, I bid the number of bits verified, but + * in this case, 4096 seems excessive so I picked 10 as + * an arbitrary but reasonable-seeming value. + */ + return (10); + } + + /* If it's not an end-of-archive mark, it must have a valid checksum.*/ + if (!checksum(a, h)) + return (0); + bid += 48; /* Checksum is usually 6 octal digits. */ + + header = (const struct archive_entry_header_ustar *)h; + + /* Recognize POSIX formats. */ + if ((memcmp(header->magic, "ustar\0", 6) == 0) + && (memcmp(header->version, "00", 2) == 0)) + bid += 56; + + /* Recognize GNU tar format. */ + if ((memcmp(header->magic, "ustar ", 6) == 0) + && (memcmp(header->version, " \0", 2) == 0)) + bid += 56; + + /* Type flag must be null, digit or A-Z, a-z. */ + if (header->typeflag[0] != 0 && + !( header->typeflag[0] >= '0' && header->typeflag[0] <= '9') && + !( header->typeflag[0] >= 'A' && header->typeflag[0] <= 'Z') && + !( header->typeflag[0] >= 'a' && header->typeflag[0] <= 'z') ) + return (0); + bid += 2; /* 6 bits of variation in an 8-bit field leaves 2 bits. */ + + /* Sanity check: Look at first byte of mode field. */ + switch (255 & (unsigned)header->mode[0]) { + case 0: case 255: + /* Base-256 value: No further verification possible! */ + break; + case ' ': /* Not recommended, but not illegal, either. */ + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + /* Octal Value. */ + /* TODO: Check format of remainder of this field. */ + break; + default: + /* Not a valid mode; bail out here. */ + return (0); + } + /* TODO: Sanity test uid/gid/size/mtime/rdevmajor/rdevminor fields. */ + + return (bid); +} + +static int +archive_read_format_tar_options(struct archive_read *a, + const char *key, const char *val) +{ + struct tar *tar; + int ret = ARCHIVE_FAILED; + + tar = (struct tar *)(a->format->data); + if (strcmp(key, "compat-2x") == 0) { + /* Handle UTF-8 filnames as libarchive 2.x */ + tar->compat_2x = (val != NULL)?1:0; + tar->init_default_conversion = tar->compat_2x; + ret = ARCHIVE_OK; + } else if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "tar: hdrcharset option needs a character-set name"); + else { + tar->opt_sconv = + archive_string_conversion_from_charset( + &a->archive, val, 0); + if (tar->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "tar: unknown keyword ``%s''", key); + + return (ret); +} + +/* utility function- this exists to centralize the logic of tracking + * how much unconsumed data we have floating around, and to consume + * anything outstanding since we're going to do read_aheads + */ +static void +tar_flush_unconsumed(struct archive_read *a, size_t *unconsumed) +{ + if (*unconsumed) { +/* + void *data = (void *)__archive_read_ahead(a, *unconsumed, NULL); + * this block of code is to poison claimed unconsumed space, ensuring + * things break if it is in use still. + * currently it WILL break things, so enable it only for debugging this issue + if (data) { + memset(data, 0xff, *unconsumed); + } +*/ + __archive_read_consume(a, *unconsumed); + *unconsumed = 0; + } +} + +/* + * The function invoked by archive_read_next_header(). This + * just sets up a few things and then calls the internal + * tar_read_header() function below. + */ +static int +archive_read_format_tar_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + /* + * When converting tar archives to cpio archives, it is + * essential that each distinct file have a distinct inode + * number. To simplify this, we keep a static count here to + * assign fake dev/inode numbers to each tar entry. Note that + * pax format archives may overwrite this with something more + * useful. + * + * Ideally, we would track every file read from the archive so + * that we could assign the same dev/ino pair to hardlinks, + * but the memory required to store a complete lookup table is + * probably not worthwhile just to support the relatively + * obscure tar->cpio conversion case. + */ + static int default_inode; + static int default_dev; + struct tar *tar; + const char *p; + int r; + size_t l, unconsumed = 0; + + /* Assign default device/inode values. */ + archive_entry_set_dev(entry, 1 + default_dev); /* Don't use zero. */ + archive_entry_set_ino(entry, ++default_inode); /* Don't use zero. */ + /* Limit generated st_ino number to 16 bits. */ + if (default_inode >= 0xffff) { + ++default_dev; + default_inode = 0; + } + + tar = (struct tar *)(a->format->data); + tar->entry_offset = 0; + gnu_clear_sparse_list(tar); + tar->realsize = -1; /* Mark this as "unset" */ + + /* Setup default string conversion. */ + tar->sconv = tar->opt_sconv; + if (tar->sconv == NULL) { + if (!tar->init_default_conversion) { + tar->sconv_default = + archive_string_default_conversion_for_read(&(a->archive)); + tar->init_default_conversion = 1; + } + tar->sconv = tar->sconv_default; + } + + r = tar_read_header(a, tar, entry, &unconsumed); + + tar_flush_unconsumed(a, &unconsumed); + + /* + * "non-sparse" files are really just sparse files with + * a single block. + */ + if (tar->sparse_list == NULL) { + if (gnu_add_sparse_entry(a, tar, 0, tar->entry_bytes_remaining) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + struct sparse_block *sb; + + for (sb = tar->sparse_list; sb != NULL; sb = sb->next) { + if (!sb->hole) + archive_entry_sparse_add_entry(entry, + sb->offset, sb->remaining); + } + } + + if (r == ARCHIVE_OK) { + /* + * "Regular" entry with trailing '/' is really + * directory: This is needed for certain old tar + * variants and even for some broken newer ones. + */ + const wchar_t *wp; + wp = archive_entry_pathname_w(entry); + if (wp != NULL) { + l = wcslen(wp); + if (archive_entry_filetype(entry) == AE_IFREG + && wp[l-1] == L'/') + archive_entry_set_filetype(entry, AE_IFDIR); + } else { + p = archive_entry_pathname(entry); + if (p == NULL) + return (ARCHIVE_FAILED); + l = strlen(p); + if (archive_entry_filetype(entry) == AE_IFREG + && p[l-1] == '/') + archive_entry_set_filetype(entry, AE_IFDIR); + } + } + return (r); +} + +static int +archive_read_format_tar_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + ssize_t bytes_read; + struct tar *tar; + struct sparse_block *p; + + tar = (struct tar *)(a->format->data); + +skip_hole: + /* Remove exhausted entries from sparse list. */ + while (tar->sparse_list != NULL && + tar->sparse_list->remaining == 0) { + p = tar->sparse_list; + tar->sparse_list = p->next; + free(p); + } + + if (tar->entry_bytes_unconsumed) { + __archive_read_consume(a, tar->entry_bytes_unconsumed); + tar->entry_bytes_unconsumed = 0; + } + + /* If we're at end of file, return EOF. */ + if (tar->sparse_list == NULL || tar->entry_bytes_remaining == 0) { + if (__archive_read_consume(a, tar->entry_padding) < 0) + return (ARCHIVE_FATAL); + tar->entry_padding = 0; + *buff = NULL; + *size = 0; + *offset = tar->realsize; + return (ARCHIVE_EOF); + } + + *buff = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (*buff == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated tar archive"); + return (ARCHIVE_FATAL); + } + if (bytes_read > tar->entry_bytes_remaining) + bytes_read = tar->entry_bytes_remaining; + /* Don't read more than is available in the + * current sparse block. */ + if (tar->sparse_list->remaining < bytes_read) + bytes_read = tar->sparse_list->remaining; + *size = bytes_read; + *offset = tar->sparse_list->offset; + tar->sparse_list->remaining -= bytes_read; + tar->sparse_list->offset += bytes_read; + tar->entry_bytes_remaining -= bytes_read; + tar->entry_bytes_unconsumed = bytes_read; + + if (tar->sparse_list->hole) + goto skip_hole; + + return (ARCHIVE_OK); +} + +static int +archive_read_format_tar_skip(struct archive_read *a) +{ + int64_t bytes_skipped; + struct tar* tar; + + tar = (struct tar *)(a->format->data); + + bytes_skipped = __archive_read_consume(a, + tar->entry_bytes_remaining + tar->entry_padding + + tar->entry_bytes_unconsumed); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + + tar->entry_bytes_remaining = 0; + tar->entry_bytes_unconsumed = 0; + tar->entry_padding = 0; + + /* Free the sparse list. */ + gnu_clear_sparse_list(tar); + + return (ARCHIVE_OK); +} + +/* + * This function recursively interprets all of the headers associated + * with a single entry. + */ +static int +tar_read_header(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, size_t *unconsumed) +{ + ssize_t bytes; + int err; + const char *h; + const struct archive_entry_header_ustar *header; + + tar_flush_unconsumed(a, unconsumed); + + /* Read 512-byte header record */ + h = __archive_read_ahead(a, 512, &bytes); + if (bytes < 0) + return (bytes); + if (bytes == 0) { /* EOF at a block boundary. */ + /* Some writers do omit the block of nulls. */ + return (ARCHIVE_EOF); + } + if (bytes < 512) { /* Short block at EOF; this is bad. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive"); + return (ARCHIVE_FATAL); + } + *unconsumed = 512; + + /* Check for end-of-archive mark. */ + if (h[0] == 0 && archive_block_is_null(h)) { + /* Try to consume a second all-null record, as well. */ + tar_flush_unconsumed(a, unconsumed); + h = __archive_read_ahead(a, 512, NULL); + if (h != NULL) + __archive_read_consume(a, 512); + archive_clear_error(&a->archive); + if (a->archive.archive_format_name == NULL) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar"; + } + return (ARCHIVE_EOF); + } + + /* + * Note: If the checksum fails and we return ARCHIVE_RETRY, + * then the client is likely to just retry. This is a very + * crude way to search for the next valid header! + * + * TODO: Improve this by implementing a real header scan. + */ + if (!checksum(a, h)) { + tar_flush_unconsumed(a, unconsumed); + archive_set_error(&a->archive, EINVAL, "Damaged tar archive"); + return (ARCHIVE_RETRY); /* Retryable: Invalid header */ + } + + if (++tar->header_recursion_depth > 32) { + tar_flush_unconsumed(a, unconsumed); + archive_set_error(&a->archive, EINVAL, "Too many special headers"); + return (ARCHIVE_WARN); + } + + /* Determine the format variant. */ + header = (const struct archive_entry_header_ustar *)h; + + switch(header->typeflag[0]) { + case 'A': /* Solaris tar ACL */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "Solaris tar"; + err = header_Solaris_ACL(a, tar, entry, h, unconsumed); + break; + case 'g': /* POSIX-standard 'g' header. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err = header_pax_global(a, tar, entry, h, unconsumed); + break; + case 'K': /* Long link name (GNU tar, others) */ + err = header_longlink(a, tar, entry, h, unconsumed); + break; + case 'L': /* Long filename (GNU tar, others) */ + err = header_longname(a, tar, entry, h, unconsumed); + break; + case 'V': /* GNU volume header */ + err = header_volume(a, tar, entry, h, unconsumed); + break; + case 'X': /* Used by SUN tar; same as 'x'. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = + "POSIX pax interchange format (Sun variant)"; + err = header_pax_extensions(a, tar, entry, h, unconsumed); + break; + case 'x': /* POSIX-standard 'x' header. */ + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange format"; + err = header_pax_extensions(a, tar, entry, h, unconsumed); + break; + default: + if (memcmp(header->magic, "ustar \0", 8) == 0) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; + a->archive.archive_format_name = "GNU tar format"; + err = header_gnutar(a, tar, entry, h, unconsumed); + } else if (memcmp(header->magic, "ustar", 5) == 0) { + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { + a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive.archive_format_name = "POSIX ustar format"; + } + err = header_ustar(a, tar, entry, h); + } else { + a->archive.archive_format = ARCHIVE_FORMAT_TAR; + a->archive.archive_format_name = "tar (non-POSIX)"; + err = header_old_tar(a, tar, entry, h); + } + } + if (err == ARCHIVE_FATAL) + return (err); + + tar_flush_unconsumed(a, unconsumed); + + h = NULL; + header = NULL; + + --tar->header_recursion_depth; + /* Yuck. Apple's design here ends up storing long pathname + * extensions for both the AppleDouble extension entry and the + * regular entry. + */ + /* TODO: Should this be disabled on non-Mac platforms? */ + if ((err == ARCHIVE_WARN || err == ARCHIVE_OK) && + tar->header_recursion_depth == 0) { + int err2 = read_mac_metadata_blob(a, tar, entry, h, unconsumed); + if (err2 < err) + err = err2; + } + + /* We return warnings or success as-is. Anything else is fatal. */ + if (err == ARCHIVE_WARN || err == ARCHIVE_OK) { + if (tar->sparse_gnu_pending) { + if (tar->sparse_gnu_major == 1 && + tar->sparse_gnu_minor == 0) { + ssize_t bytes_read; + + tar->sparse_gnu_pending = 0; + /* Read initial sparse map. */ + bytes_read = gnu_sparse_10_read(a, tar, unconsumed); + tar->entry_bytes_remaining -= bytes_read; + if (bytes_read < 0) + return (bytes_read); + } else { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Unrecognized GNU sparse file format"); + return (ARCHIVE_WARN); + } + tar->sparse_gnu_pending = 0; + } + return (err); + } + if (err == ARCHIVE_EOF) + /* EOF when recursively reading a header is bad. */ + archive_set_error(&a->archive, EINVAL, "Damaged tar archive"); + return (ARCHIVE_FATAL); +} + +/* + * Return true if block checksum is correct. + */ +static int +checksum(struct archive_read *a, const void *h) +{ + const unsigned char *bytes; + const struct archive_entry_header_ustar *header; + int check, i, sum; + + (void)a; /* UNUSED */ + bytes = (const unsigned char *)h; + header = (const struct archive_entry_header_ustar *)h; + + /* + * Test the checksum. Note that POSIX specifies _unsigned_ + * bytes for this calculation. + */ + sum = tar_atol(header->checksum, sizeof(header->checksum)); + check = 0; + for (i = 0; i < 148; i++) + check += (unsigned char)bytes[i]; + for (; i < 156; i++) + check += 32; + for (; i < 512; i++) + check += (unsigned char)bytes[i]; + if (sum == check) + return (1); + + /* + * Repeat test with _signed_ bytes, just in case this archive + * was created by an old BSD, Solaris, or HP-UX tar with a + * broken checksum calculation. + */ + check = 0; + for (i = 0; i < 148; i++) + check += (signed char)bytes[i]; + for (; i < 156; i++) + check += 32; + for (; i < 512; i++) + check += (signed char)bytes[i]; + if (sum == check) + return (1); + + return (0); +} + +/* + * Return true if this block contains only nulls. + */ +static int +archive_block_is_null(const char *p) +{ + unsigned i; + + for (i = 0; i < 512; i++) + if (*p++) + return (0); + return (1); +} + +/* + * Interpret 'A' Solaris ACL header + */ +static int +header_Solaris_ACL(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + const struct archive_entry_header_ustar *header; + size_t size; + int err; + int64_t type; + char *acl, *p; + + /* + * read_body_to_string adds a NUL terminator, but we need a little + * more to make sure that we don't overrun acl_text later. + */ + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + err = read_body_to_string(a, tar, &(tar->acl_text), h, unconsumed); + if (err != ARCHIVE_OK) + return (err); + + /* Recursively read next header */ + err = tar_read_header(a, tar, entry, unconsumed); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + + /* TODO: Examine the first characters to see if this + * is an AIX ACL descriptor. We'll likely never support + * them, but it would be polite to recognize and warn when + * we do see them. */ + + /* Leading octal number indicates ACL type and number of entries. */ + p = acl = tar->acl_text.s; + type = 0; + while (*p != '\0' && p < acl + size) { + if (*p < '0' || *p > '7') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute (invalid digit)"); + return(ARCHIVE_WARN); + } + type <<= 3; + type += *p - '0'; + if (type > 077777777) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute (count too large)"); + return (ARCHIVE_WARN); + } + p++; + } + switch ((int)type & ~0777777) { + case 01000000: + /* POSIX.1e ACL */ + break; + case 03000000: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Solaris NFSv4 ACLs not supported"); + return (ARCHIVE_WARN); + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute (unsupported type %o)", + (int)type); + return (ARCHIVE_WARN); + } + p++; + + if (p >= acl + size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute (body overflow)"); + return(ARCHIVE_WARN); + } + + /* ACL text is null-terminated; find the end. */ + size -= (p - acl); + acl = p; + + while (*p != '\0' && p < acl + size) + p++; + + if (tar->sconv_acl == NULL) { + tar->sconv_acl = archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (tar->sconv_acl == NULL) + return (ARCHIVE_FATAL); + } + archive_strncpy(&(tar->localname), acl, p - acl); + err = archive_acl_parse_l(archive_entry_acl(entry), + tar->localname.s, ARCHIVE_ENTRY_ACL_TYPE_ACCESS, tar->sconv_acl); + if (err != ARCHIVE_OK) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for ACL"); + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Malformed Solaris ACL attribute (unparsable)"); + } + return (err); +} + +/* + * Interpret 'K' long linkname header. + */ +static int +header_longlink(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->longlink), h, unconsumed); + if (err != ARCHIVE_OK) + return (err); + err = tar_read_header(a, tar, entry, unconsumed); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + /* Set symlink if symlink already set, else hardlink. */ + archive_entry_copy_link(entry, tar->longlink.s); + return (ARCHIVE_OK); +} + +static int +set_conversion_failed_error(struct archive_read *a, + struct archive_string_conv *sconv, const char *name) +{ + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for %s", name); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "%s can't be converted from %s to current locale.", + name, archive_string_conversion_charset_name(sconv)); + return (ARCHIVE_WARN); +} + +/* + * Interpret 'L' long filename header. + */ +static int +header_longname(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->longname), h, unconsumed); + if (err != ARCHIVE_OK) + return (err); + /* Read and parse "real" header, then override name. */ + err = tar_read_header(a, tar, entry, unconsumed); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + if (archive_entry_copy_pathname_l(entry, tar->longname.s, + archive_strlen(&(tar->longname)), tar->sconv) != 0) + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + return (err); +} + + +/* + * Interpret 'V' GNU tar volume header. + */ +static int +header_volume(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + (void)h; + + /* Just skip this and read the next header. */ + return (tar_read_header(a, tar, entry, unconsumed)); +} + +/* + * Read body of an archive entry into an archive_string object. + */ +static int +read_body_to_string(struct archive_read *a, struct tar *tar, + struct archive_string *as, const void *h, size_t *unconsumed) +{ + int64_t size; + const struct archive_entry_header_ustar *header; + const void *src; + + (void)tar; /* UNUSED */ + header = (const struct archive_entry_header_ustar *)h; + size = tar_atol(header->size, sizeof(header->size)); + if ((size > 1048576) || (size < 0)) { + archive_set_error(&a->archive, EINVAL, + "Special header too large"); + return (ARCHIVE_FATAL); + } + + /* Fail if we can't make our buffer big enough. */ + if (archive_string_ensure(as, size+1) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory"); + return (ARCHIVE_FATAL); + } + + tar_flush_unconsumed(a, unconsumed); + + /* Read the body into the string. */ + *unconsumed = (size + 511) & ~ 511; + src = __archive_read_ahead(a, *unconsumed, NULL); + if (src == NULL) { + *unconsumed = 0; + return (ARCHIVE_FATAL); + } + memcpy(as->s, src, size); + as->s[size] = '\0'; + as->length = size; + return (ARCHIVE_OK); +} + +/* + * Parse out common header elements. + * + * This would be the same as header_old_tar, except that the + * filename is handled slightly differently for old and POSIX + * entries (POSIX entries support a 'prefix'). This factoring + * allows header_old_tar and header_ustar + * to handle filenames differently, while still putting most of the + * common parsing into one place. + */ +static int +header_common(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + char tartype; + int err = ARCHIVE_OK; + + header = (const struct archive_entry_header_ustar *)h; + if (header->linkname[0]) + archive_strncpy(&(tar->entry_linkpath), + header->linkname, sizeof(header->linkname)); + else + archive_string_empty(&(tar->entry_linkpath)); + + /* Parse out the numeric fields (all are octal) */ + archive_entry_set_mode(entry, tar_atol(header->mode, sizeof(header->mode))); + archive_entry_set_uid(entry, tar_atol(header->uid, sizeof(header->uid))); + archive_entry_set_gid(entry, tar_atol(header->gid, sizeof(header->gid))); + tar->entry_bytes_remaining = tar_atol(header->size, sizeof(header->size)); + if (tar->entry_bytes_remaining < 0) { + tar->entry_bytes_remaining = 0; + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Tar entry has negative size?"); + err = ARCHIVE_WARN; + } + tar->realsize = tar->entry_bytes_remaining; + archive_entry_set_size(entry, tar->entry_bytes_remaining); + archive_entry_set_mtime(entry, tar_atol(header->mtime, sizeof(header->mtime)), 0); + + /* Handle the tar type flag appropriately. */ + tartype = header->typeflag[0]; + + switch (tartype) { + case '1': /* Hard link */ + if (archive_entry_copy_hardlink_l(entry, tar->entry_linkpath.s, + archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, + "Linkname"); + if (err == ARCHIVE_FATAL) + return (err); + } + /* + * The following may seem odd, but: Technically, tar + * does not store the file type for a "hard link" + * entry, only the fact that it is a hard link. So, I + * leave the type zero normally. But, pax interchange + * format allows hard links to have data, which + * implies that the underlying entry is a regular + * file. + */ + if (archive_entry_size(entry) > 0) + archive_entry_set_filetype(entry, AE_IFREG); + + /* + * A tricky point: Traditionally, tar readers have + * ignored the size field when reading hardlink + * entries, and some writers put non-zero sizes even + * though the body is empty. POSIX blessed this + * convention in the 1988 standard, but broke with + * this tradition in 2001 by permitting hardlink + * entries to store valid bodies in pax interchange + * format, but not in ustar format. Since there is no + * hard and fast way to distinguish pax interchange + * from earlier archives (the 'x' and 'g' entries are + * optional, after all), we need a heuristic. + */ + if (archive_entry_size(entry) == 0) { + /* If the size is already zero, we're done. */ + } else if (a->archive.archive_format + == ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE) { + /* Definitely pax extended; must obey hardlink size. */ + } else if (a->archive.archive_format == ARCHIVE_FORMAT_TAR + || a->archive.archive_format == ARCHIVE_FORMAT_TAR_GNUTAR) + { + /* Old-style or GNU tar: we must ignore the size. */ + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + } else if (archive_read_format_tar_bid(a, 50) > 50) { + /* + * We don't know if it's pax: If the bid + * function sees a valid ustar header + * immediately following, then let's ignore + * the hardlink size. + */ + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + } + /* + * TODO: There are still two cases I'd like to handle: + * = a ustar non-pax archive with a hardlink entry at + * end-of-archive. (Look for block of nulls following?) + * = a pax archive that has not seen any pax headers + * and has an entry which is a hardlink entry storing + * a body containing an uncompressed tar archive. + * The first is worth addressing; I don't see any reliable + * way to deal with the second possibility. + */ + break; + case '2': /* Symlink */ + archive_entry_set_filetype(entry, AE_IFLNK); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + if (archive_entry_copy_symlink_l(entry, tar->entry_linkpath.s, + archive_strlen(&(tar->entry_linkpath)), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, + "Linkname"); + if (err == ARCHIVE_FATAL) + return (err); + } + break; + case '3': /* Character device */ + archive_entry_set_filetype(entry, AE_IFCHR); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '4': /* Block device */ + archive_entry_set_filetype(entry, AE_IFBLK); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '5': /* Dir */ + archive_entry_set_filetype(entry, AE_IFDIR); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case '6': /* FIFO device */ + archive_entry_set_filetype(entry, AE_IFIFO); + archive_entry_set_size(entry, 0); + tar->entry_bytes_remaining = 0; + break; + case 'D': /* GNU incremental directory type */ + /* + * No special handling is actually required here. + * It might be nice someday to preprocess the file list and + * provide it to the client, though. + */ + archive_entry_set_filetype(entry, AE_IFDIR); + break; + case 'M': /* GNU "Multi-volume" (remainder of file from last archive)*/ + /* + * As far as I can tell, this is just like a regular file + * entry, except that the contents should be _appended_ to + * the indicated file at the indicated offset. This may + * require some API work to fully support. + */ + break; + case 'N': /* Old GNU "long filename" entry. */ + /* The body of this entry is a script for renaming + * previously-extracted entries. Ugh. It will never + * be supported by libarchive. */ + archive_entry_set_filetype(entry, AE_IFREG); + break; + case 'S': /* GNU sparse files */ + /* + * Sparse files are really just regular files with + * sparse information in the extended area. + */ + /* FALLTHROUGH */ + default: /* Regular file and non-standard types */ + /* + * Per POSIX: non-recognized types should always be + * treated as regular files. + */ + archive_entry_set_filetype(entry, AE_IFREG); + break; + } + return (err); +} + +/* + * Parse out header elements for "old-style" tar archives. + */ +static int +header_old_tar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + int err = ARCHIVE_OK, err2; + + /* Copy filename over (to ensure null termination). */ + header = (const struct archive_entry_header_ustar *)h; + if (archive_entry_copy_pathname_l(entry, + header->name, sizeof(header->name), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + /* Grab rest of common fields */ + err2 = header_common(a, tar, entry, h); + if (err > err2) + err = err2; + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + return (err); +} + +/* + * Read a Mac AppleDouble-encoded blob of file metadata, + * if there is one. + */ +static int +read_mac_metadata_blob(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + int64_t size; + const void *data; + const char *p, *name; + const wchar_t *wp, *wname; + + (void)h; /* UNUSED */ + + wname = wp = archive_entry_pathname_w(entry); + if (wp != NULL) { + /* Find the last path element. */ + for (; *wp != L'\0'; ++wp) { + if (wp[0] == '/' && wp[1] != L'\0') + wname = wp + 1; + } + /* + * If last path element starts with "._", then + * this is a Mac extension. + */ + if (wname[0] != L'.' || wname[1] != L'_' || wname[2] == L'\0') + return ARCHIVE_OK; + } else { + /* Find the last path element. */ + name = p = archive_entry_pathname(entry); + if (p == NULL) + return (ARCHIVE_FAILED); + for (; *p != '\0'; ++p) { + if (p[0] == '/' && p[1] != '\0') + name = p + 1; + } + /* + * If last path element starts with "._", then + * this is a Mac extension. + */ + if (name[0] != '.' || name[1] != '_' || name[2] == '\0') + return ARCHIVE_OK; + } + + /* Read the body as a Mac OS metadata blob. */ + size = archive_entry_size(entry); + + /* + * TODO: Look beyond the body here to peek at the next header. + * If it's a regular header (not an extension header) + * that has the wrong name, just return the current + * entry as-is, without consuming the body here. + * That would reduce the risk of us mis-identifying + * an ordinary file that just happened to have + * a name starting with "._". + * + * Q: Is the above idea really possible? Even + * when there are GNU or pax extension entries? + */ + data = __archive_read_ahead(a, size, NULL); + if (data == NULL) { + *unconsumed = 0; + return (ARCHIVE_FATAL); + } + archive_entry_copy_mac_metadata(entry, data, size); + *unconsumed = (size + 511) & ~ 511; + tar_flush_unconsumed(a, unconsumed); + return (tar_read_header(a, tar, entry, unconsumed)); +} + +/* + * Parse a file header for a pax extended archive entry. + */ +static int +header_pax_global(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + int err; + + err = read_body_to_string(a, tar, &(tar->pax_global), h, unconsumed); + if (err != ARCHIVE_OK) + return (err); + err = tar_read_header(a, tar, entry, unconsumed); + return (err); +} + +static int +header_pax_extensions(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + int err, err2; + + err = read_body_to_string(a, tar, &(tar->pax_header), h, unconsumed); + if (err != ARCHIVE_OK) + return (err); + + /* Parse the next header. */ + err = tar_read_header(a, tar, entry, unconsumed); + if ((err != ARCHIVE_OK) && (err != ARCHIVE_WARN)) + return (err); + + /* + * TODO: Parse global/default options into 'entry' struct here + * before handling file-specific options. + * + * This design (parse standard header, then overwrite with pax + * extended attribute data) usually works well, but isn't ideal; + * it would be better to parse the pax extended attributes first + * and then skip any fields in the standard header that were + * defined in the pax header. + */ + err2 = pax_header(a, tar, entry, tar->pax_header.s); + err = err_combine(err, err2); + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + return (err); +} + + +/* + * Parse a file header for a Posix "ustar" archive entry. This also + * handles "pax" or "extended ustar" entries. + */ +static int +header_ustar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h) +{ + const struct archive_entry_header_ustar *header; + struct archive_string *as; + int err = ARCHIVE_OK, r; + + header = (const struct archive_entry_header_ustar *)h; + + /* Copy name into an internal buffer to ensure null-termination. */ + as = &(tar->entry_pathname); + if (header->prefix[0]) { + archive_strncpy(as, header->prefix, sizeof(header->prefix)); + if (as->s[archive_strlen(as) - 1] != '/') + archive_strappend_char(as, '/'); + archive_strncat(as, header->name, sizeof(header->name)); + } else { + archive_strncpy(as, header->name, sizeof(header->name)); + } + if (archive_entry_copy_pathname_l(entry, as->s, archive_strlen(as), + tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + /* Handle rest of common fields. */ + r = header_common(a, tar, entry, h); + if (r == ARCHIVE_FATAL) + return (r); + if (r < err) + err = r; + + /* Handle POSIX ustar fields. */ + if (archive_entry_copy_uname_l(entry, + header->uname, sizeof(header->uname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + if (archive_entry_copy_gname_l(entry, + header->gname, sizeof(header->gname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Gname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + /* Parse out device numbers only for char and block specials. */ + if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { + archive_entry_set_rdevmajor(entry, + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + + return (err); +} + + +/* + * Parse the pax extended attributes record. + * + * Returns non-zero if there's an error in the data. + */ +static int +pax_header(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, char *attr) +{ + size_t attr_length, l, line_length; + char *p; + char *key, *value; + struct archive_string *as; + struct archive_string_conv *sconv; + int err, err2; + + attr_length = strlen(attr); + tar->pax_hdrcharset_binary = 0; + archive_string_empty(&(tar->entry_gname)); + archive_string_empty(&(tar->entry_linkpath)); + archive_string_empty(&(tar->entry_pathname)); + archive_string_empty(&(tar->entry_pathname_override)); + archive_string_empty(&(tar->entry_uname)); + err = ARCHIVE_OK; + while (attr_length > 0) { + /* Parse decimal length field at start of line. */ + line_length = 0; + l = attr_length; + p = attr; /* Record start of line. */ + while (l>0) { + if (*p == ' ') { + p++; + l--; + break; + } + if (*p < '0' || *p > '9') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax extended attributes"); + return (ARCHIVE_WARN); + } + line_length *= 10; + line_length += *p - '0'; + if (line_length > 999999) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Rejecting pax extended attribute > 1MB"); + return (ARCHIVE_WARN); + } + p++; + l--; + } + + /* + * Parsed length must be no bigger than available data, + * at least 1, and the last character of the line must + * be '\n'. + */ + if (line_length > attr_length + || line_length < 1 + || attr[line_length - 1] != '\n') + { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignoring malformed pax extended attribute"); + return (ARCHIVE_WARN); + } + + /* Null-terminate the line. */ + attr[line_length - 1] = '\0'; + + /* Find end of key and null terminate it. */ + key = p; + if (key[0] == '=') + return (-1); + while (*p && *p != '=') + ++p; + if (*p == '\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid pax extended attributes"); + return (ARCHIVE_WARN); + } + *p = '\0'; + + /* Identify null-terminated 'value' portion. */ + value = p + 1; + + /* Identify this attribute and set it in the entry. */ + err2 = pax_attribute(a, tar, entry, key, value); + if (err2 == ARCHIVE_FATAL) + return (err2); + err = err_combine(err, err2); + + /* Skip to next line */ + attr += line_length; + attr_length -= line_length; + } + + /* + * PAX format uses UTF-8 as default charset for its metadata + * unless hdrcharset=BINARY is present in its header. + * We apply the charset specified by the hdrcharset option only + * when the hdrcharset attribute(in PAX header) is BINARY because + * we respect the charset described in PAX header and BINARY also + * means that metadata(filename,uname and gname) character-set + * is unknown. + */ + if (tar->pax_hdrcharset_binary) + sconv = tar->opt_sconv; + else { + sconv = archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (sconv == NULL) + return (ARCHIVE_FATAL); + if (tar->compat_2x) + archive_string_conversion_set_opt(sconv, + SCONV_SET_OPT_UTF8_LIBARCHIVE2X); + } + + if (archive_strlen(&(tar->entry_gname)) > 0) { + if (archive_entry_copy_gname_l(entry, tar->entry_gname.s, + archive_strlen(&(tar->entry_gname)), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Gname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use a converted an original name. */ + archive_entry_copy_gname(entry, tar->entry_gname.s); + } + } + if (archive_strlen(&(tar->entry_linkpath)) > 0) { + if (archive_entry_copy_link_l(entry, tar->entry_linkpath.s, + archive_strlen(&(tar->entry_linkpath)), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Linkname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use a converted an original name. */ + archive_entry_copy_link(entry, tar->entry_linkpath.s); + } + } + /* + * Some extensions (such as the GNU sparse file extensions) + * deliberately store a synthetic name under the regular 'path' + * attribute and the real file name under a different attribute. + * Since we're supposed to not care about the order, we + * have no choice but to store all of the various filenames + * we find and figure it all out afterwards. This is the + * figuring out part. + */ + as = NULL; + if (archive_strlen(&(tar->entry_pathname_override)) > 0) + as = &(tar->entry_pathname_override); + else if (archive_strlen(&(tar->entry_pathname)) > 0) + as = &(tar->entry_pathname); + if (as != NULL) { + if (archive_entry_copy_pathname_l(entry, as->s, + archive_strlen(as), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use a converted an original name. */ + archive_entry_copy_pathname(entry, as->s); + } + } + if (archive_strlen(&(tar->entry_uname)) > 0) { + if (archive_entry_copy_uname_l(entry, tar->entry_uname.s, + archive_strlen(&(tar->entry_uname)), sconv) != 0) { + err = set_conversion_failed_error(a, sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + /* Use a converted an original name. */ + archive_entry_copy_uname(entry, tar->entry_uname.s); + } + } + return (err); +} + +static int +pax_attribute_xattr(struct archive_entry *entry, + char *name, char *value) +{ + char *name_decoded; + void *value_decoded; + size_t value_len; + + if (strlen(name) < 18 || (memcmp(name, "LIBARCHIVE.xattr.", 17)) != 0) + return 3; + + name += 17; + + /* URL-decode name */ + name_decoded = url_decode(name); + if (name_decoded == NULL) + return 2; + + /* Base-64 decode value */ + value_decoded = base64_decode(value, strlen(value), &value_len); + if (value_decoded == NULL) { + free(name_decoded); + return 1; + } + + archive_entry_xattr_add_entry(entry, name_decoded, + value_decoded, value_len); + + free(name_decoded); + free(value_decoded); + return 0; +} + +/* + * Parse a single key=value attribute. key/value pointers are + * assumed to point into reasonably long-lived storage. + * + * Note that POSIX reserves all-lowercase keywords. Vendor-specific + * extensions should always have keywords of the form "VENDOR.attribute" + * In particular, it's quite feasible to support many different + * vendor extensions here. I'm using "LIBARCHIVE" for extensions + * unique to this library. + * + * Investigate other vendor-specific extensions and see if + * any of them look useful. + */ +static int +pax_attribute(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, char *key, char *value) +{ + int64_t s; + long n; + int err = ARCHIVE_OK, r; + + switch (key[0]) { + case 'G': + /* GNU "0.0" sparse pax format. */ + if (strcmp(key, "GNU.sparse.numblocks") == 0) { + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 0; + } + if (strcmp(key, "GNU.sparse.offset") == 0) { + tar->sparse_offset = tar_atol10(value, strlen(value)); + if (tar->sparse_numbytes != -1) { + if (gnu_add_sparse_entry(a, tar, + tar->sparse_offset, tar->sparse_numbytes) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + if (strcmp(key, "GNU.sparse.numbytes") == 0) { + tar->sparse_numbytes = tar_atol10(value, strlen(value)); + if (tar->sparse_numbytes != -1) { + if (gnu_add_sparse_entry(a, tar, + tar->sparse_offset, tar->sparse_numbytes) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + tar->sparse_offset = -1; + tar->sparse_numbytes = -1; + } + } + if (strcmp(key, "GNU.sparse.size") == 0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } + + /* GNU "0.1" sparse pax format. */ + if (strcmp(key, "GNU.sparse.map") == 0) { + tar->sparse_gnu_major = 0; + tar->sparse_gnu_minor = 1; + if (gnu_sparse_01_parse(a, tar, value) != ARCHIVE_OK) + return (ARCHIVE_WARN); + } + + /* GNU "1.0" sparse pax format */ + if (strcmp(key, "GNU.sparse.major") == 0) { + tar->sparse_gnu_major = tar_atol10(value, strlen(value)); + tar->sparse_gnu_pending = 1; + } + if (strcmp(key, "GNU.sparse.minor") == 0) { + tar->sparse_gnu_minor = tar_atol10(value, strlen(value)); + tar->sparse_gnu_pending = 1; + } + if (strcmp(key, "GNU.sparse.name") == 0) { + /* + * The real filename; when storing sparse + * files, GNU tar puts a synthesized name into + * the regular 'path' attribute in an attempt + * to limit confusion. ;-) + */ + archive_strcpy(&(tar->entry_pathname_override), value); + } + if (strcmp(key, "GNU.sparse.realsize") == 0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } + break; + case 'L': + /* Our extensions */ +/* TODO: Handle arbitrary extended attributes... */ +/* + if (strcmp(key, "LIBARCHIVE.xxxxxxx") == 0) + archive_entry_set_xxxxxx(entry, value); +*/ + if (strcmp(key, "LIBARCHIVE.creationtime") == 0) { + pax_time(value, &s, &n); + archive_entry_set_birthtime(entry, s, n); + } + if (memcmp(key, "LIBARCHIVE.xattr.", 17) == 0) + pax_attribute_xattr(entry, key, value); + break; + case 'S': + /* We support some keys used by the "star" archiver */ + if (strcmp(key, "SCHILY.acl.access") == 0) { + if (tar->sconv_acl == NULL) { + tar->sconv_acl = + archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (tar->sconv_acl == NULL) + return (ARCHIVE_FATAL); + } + + r = archive_acl_parse_l(archive_entry_acl(entry), + value, ARCHIVE_ENTRY_ACL_TYPE_ACCESS, + tar->sconv_acl); + if (r != ARCHIVE_OK) { + err = r; + if (err == ARCHIVE_FATAL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for " + "SCHILY.acl.access"); + return (err); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Parse error: SCHILY.acl.access"); + } + } else if (strcmp(key, "SCHILY.acl.default") == 0) { + if (tar->sconv_acl == NULL) { + tar->sconv_acl = + archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (tar->sconv_acl == NULL) + return (ARCHIVE_FATAL); + } + + r = archive_acl_parse_l(archive_entry_acl(entry), + value, ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, + tar->sconv_acl); + if (r != ARCHIVE_OK) { + err = r; + if (err == ARCHIVE_FATAL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for " + "SCHILY.acl.default"); + return (err); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Parse error: SCHILY.acl.default"); + } + } else if (strcmp(key, "SCHILY.devmajor") == 0) { + archive_entry_set_rdevmajor(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.devminor") == 0) { + archive_entry_set_rdevminor(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.fflags") == 0) { + archive_entry_copy_fflags_text(entry, value); + } else if (strcmp(key, "SCHILY.dev") == 0) { + archive_entry_set_dev(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.ino") == 0) { + archive_entry_set_ino(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.nlink") == 0) { + archive_entry_set_nlink(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "SCHILY.realsize") == 0) { + tar->realsize = tar_atol10(value, strlen(value)); + archive_entry_set_size(entry, tar->realsize); + } else if (strcmp(key, "SUN.holesdata") == 0) { + /* A Solaris extension for sparse. */ + r = solaris_sparse_parse(a, tar, entry, value); + if (r < err) { + if (r == ARCHIVE_FATAL) + return (r); + err = r; + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Parse error: SUN.holesdata"); + } + } + break; + case 'a': + if (strcmp(key, "atime") == 0) { + pax_time(value, &s, &n); + archive_entry_set_atime(entry, s, n); + } + break; + case 'c': + if (strcmp(key, "ctime") == 0) { + pax_time(value, &s, &n); + archive_entry_set_ctime(entry, s, n); + } else if (strcmp(key, "charset") == 0) { + /* TODO: Publish charset information in entry. */ + } else if (strcmp(key, "comment") == 0) { + /* TODO: Publish comment in entry. */ + } + break; + case 'g': + if (strcmp(key, "gid") == 0) { + archive_entry_set_gid(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "gname") == 0) { + archive_strcpy(&(tar->entry_gname), value); + } + break; + case 'h': + if (strcmp(key, "hdrcharset") == 0) { + if (strcmp(value, "BINARY") == 0) + /* Binary mode. */ + tar->pax_hdrcharset_binary = 1; + else if (strcmp(value, "ISO-IR 10646 2000 UTF-8") == 0) + tar->pax_hdrcharset_binary = 0; + } + break; + case 'l': + /* pax interchange doesn't distinguish hardlink vs. symlink. */ + if (strcmp(key, "linkpath") == 0) { + archive_strcpy(&(tar->entry_linkpath), value); + } + break; + case 'm': + if (strcmp(key, "mtime") == 0) { + pax_time(value, &s, &n); + archive_entry_set_mtime(entry, s, n); + } + break; + case 'p': + if (strcmp(key, "path") == 0) { + archive_strcpy(&(tar->entry_pathname), value); + } + break; + case 'r': + /* POSIX has reserved 'realtime.*' */ + break; + case 's': + /* POSIX has reserved 'security.*' */ + /* Someday: if (strcmp(key, "security.acl") == 0) { ... } */ + if (strcmp(key, "size") == 0) { + /* "size" is the size of the data in the entry. */ + tar->entry_bytes_remaining + = tar_atol10(value, strlen(value)); + /* + * But, "size" is not necessarily the size of + * the file on disk; if this is a sparse file, + * the disk size may have already been set from + * GNU.sparse.realsize or GNU.sparse.size or + * an old GNU header field or SCHILY.realsize + * or .... + */ + if (tar->realsize < 0) { + archive_entry_set_size(entry, + tar->entry_bytes_remaining); + tar->realsize + = tar->entry_bytes_remaining; + } + } + break; + case 'u': + if (strcmp(key, "uid") == 0) { + archive_entry_set_uid(entry, + tar_atol10(value, strlen(value))); + } else if (strcmp(key, "uname") == 0) { + archive_strcpy(&(tar->entry_uname), value); + } + break; + } + return (err); +} + + + +/* + * parse a decimal time value, which may include a fractional portion + */ +static void +pax_time(const char *p, int64_t *ps, long *pn) +{ + char digit; + int64_t s; + unsigned long l; + int sign; + int64_t limit, last_digit_limit; + + limit = INT64_MAX / 10; + last_digit_limit = INT64_MAX % 10; + + s = 0; + sign = 1; + if (*p == '-') { + sign = -1; + p++; + } + while (*p >= '0' && *p <= '9') { + digit = *p - '0'; + if (s > limit || + (s == limit && digit > last_digit_limit)) { + s = INT64_MAX; + break; + } + s = (s * 10) + digit; + ++p; + } + + *ps = s * sign; + + /* Calculate nanoseconds. */ + *pn = 0; + + if (*p != '.') + return; + + l = 100000000UL; + do { + ++p; + if (*p >= '0' && *p <= '9') + *pn += (*p - '0') * l; + else + break; + } while (l /= 10); +} + +/* + * Parse GNU tar header + */ +static int +header_gnutar(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const void *h, size_t *unconsumed) +{ + const struct archive_entry_header_gnutar *header; + int64_t t; + int err = ARCHIVE_OK; + + /* + * GNU header is like POSIX ustar, except 'prefix' is + * replaced with some other fields. This also means the + * filename is stored as in old-style archives. + */ + + /* Grab fields common to all tar variants. */ + err = header_common(a, tar, entry, h); + if (err == ARCHIVE_FATAL) + return (err); + + /* Copy filename over (to ensure null termination). */ + header = (const struct archive_entry_header_gnutar *)h; + if (archive_entry_copy_pathname_l(entry, + header->name, sizeof(header->name), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Pathname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + /* Fields common to ustar and GNU */ + /* XXX Can the following be factored out since it's common + * to ustar and gnu tar? Is it okay to move it down into + * header_common, perhaps? */ + if (archive_entry_copy_uname_l(entry, + header->uname, sizeof(header->uname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Uname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + if (archive_entry_copy_gname_l(entry, + header->gname, sizeof(header->gname), tar->sconv) != 0) { + err = set_conversion_failed_error(a, tar->sconv, "Gname"); + if (err == ARCHIVE_FATAL) + return (err); + } + + /* Parse out device numbers only for char and block specials */ + if (header->typeflag[0] == '3' || header->typeflag[0] == '4') { + archive_entry_set_rdevmajor(entry, + tar_atol(header->rdevmajor, sizeof(header->rdevmajor))); + archive_entry_set_rdevminor(entry, + tar_atol(header->rdevminor, sizeof(header->rdevminor))); + } else + archive_entry_set_rdev(entry, 0); + + tar->entry_padding = 0x1ff & (-tar->entry_bytes_remaining); + + /* Grab GNU-specific fields. */ + t = tar_atol(header->atime, sizeof(header->atime)); + if (t > 0) + archive_entry_set_atime(entry, t, 0); + t = tar_atol(header->ctime, sizeof(header->ctime)); + if (t > 0) + archive_entry_set_ctime(entry, t, 0); + + if (header->realsize[0] != 0) { + tar->realsize + = tar_atol(header->realsize, sizeof(header->realsize)); + archive_entry_set_size(entry, tar->realsize); + } + + if (header->sparse[0].offset[0] != 0) { + if (gnu_sparse_old_read(a, tar, header, unconsumed) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + if (header->isextended[0] != 0) { + /* XXX WTF? XXX */ + } + } + + return (err); +} + +static int +gnu_add_sparse_entry(struct archive_read *a, struct tar *tar, + int64_t offset, int64_t remaining) +{ + struct sparse_block *p; + + p = (struct sparse_block *)malloc(sizeof(*p)); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + memset(p, 0, sizeof(*p)); + if (tar->sparse_last != NULL) + tar->sparse_last->next = p; + else + tar->sparse_list = p; + tar->sparse_last = p; + p->offset = offset; + p->remaining = remaining; + return (ARCHIVE_OK); +} + +static void +gnu_clear_sparse_list(struct tar *tar) +{ + struct sparse_block *p; + + while (tar->sparse_list != NULL) { + p = tar->sparse_list; + tar->sparse_list = p->next; + free(p); + } + tar->sparse_last = NULL; +} + +/* + * GNU tar old-format sparse data. + * + * GNU old-format sparse data is stored in a fixed-field + * format. Offset/size values are 11-byte octal fields (same + * format as 'size' field in ustart header). These are + * stored in the header, allocating subsequent header blocks + * as needed. Extending the header in this way is a pretty + * severe POSIX violation; this design has earned GNU tar a + * lot of criticism. + */ + +static int +gnu_sparse_old_read(struct archive_read *a, struct tar *tar, + const struct archive_entry_header_gnutar *header, size_t *unconsumed) +{ + ssize_t bytes_read; + const void *data; + struct extended { + struct gnu_sparse sparse[21]; + char isextended[1]; + char padding[7]; + }; + const struct extended *ext; + + if (gnu_sparse_old_parse(a, tar, header->sparse, 4) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if (header->isextended[0] == 0) + return (ARCHIVE_OK); + + do { + tar_flush_unconsumed(a, unconsumed); + data = __archive_read_ahead(a, 512, &bytes_read); + if (bytes_read < 0) + return (ARCHIVE_FATAL); + if (bytes_read < 512) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated tar archive " + "detected while reading sparse file data"); + return (ARCHIVE_FATAL); + } + *unconsumed = 512; + ext = (const struct extended *)data; + if (gnu_sparse_old_parse(a, tar, ext->sparse, 21) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } while (ext->isextended[0] != 0); + if (tar->sparse_list != NULL) + tar->entry_offset = tar->sparse_list->offset; + return (ARCHIVE_OK); +} + +static int +gnu_sparse_old_parse(struct archive_read *a, struct tar *tar, + const struct gnu_sparse *sparse, int length) +{ + while (length > 0 && sparse->offset[0] != 0) { + if (gnu_add_sparse_entry(a, tar, + tar_atol(sparse->offset, sizeof(sparse->offset)), + tar_atol(sparse->numbytes, sizeof(sparse->numbytes))) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + sparse++; + length--; + } + return (ARCHIVE_OK); +} + +/* + * GNU tar sparse format 0.0 + * + * Beginning with GNU tar 1.15, sparse files are stored using + * information in the pax extended header. The GNU tar maintainers + * have gone through a number of variations in the process of working + * out this scheme; fortunately, they're all numbered. + * + * Sparse format 0.0 uses attribute GNU.sparse.numblocks to store the + * number of blocks, and GNU.sparse.offset/GNU.sparse.numbytes to + * store offset/size for each block. The repeated instances of these + * latter fields violate the pax specification (which frowns on + * duplicate keys), so this format was quickly replaced. + */ + +/* + * GNU tar sparse format 0.1 + * + * This version replaced the offset/numbytes attributes with + * a single "map" attribute that stored a list of integers. This + * format had two problems: First, the "map" attribute could be very + * long, which caused problems for some implementations. More + * importantly, the sparse data was lost when extracted by archivers + * that didn't recognize this extension. + */ + +static int +gnu_sparse_01_parse(struct archive_read *a, struct tar *tar, const char *p) +{ + const char *e; + int64_t offset = -1, size = -1; + + for (;;) { + e = p; + while (*e != '\0' && *e != ',') { + if (*e < '0' || *e > '9') + return (ARCHIVE_WARN); + e++; + } + if (offset < 0) { + offset = tar_atol10(p, e - p); + if (offset < 0) + return (ARCHIVE_WARN); + } else { + size = tar_atol10(p, e - p); + if (size < 0) + return (ARCHIVE_WARN); + if (gnu_add_sparse_entry(a, tar, offset, size) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + offset = -1; + } + if (*e == '\0') + return (ARCHIVE_OK); + p = e + 1; + } +} + +/* + * GNU tar sparse format 1.0 + * + * The idea: The offset/size data is stored as a series of base-10 + * ASCII numbers prepended to the file data, so that dearchivers that + * don't support this format will extract the block map along with the + * data and a separate post-process can restore the sparseness. + * + * Unfortunately, GNU tar 1.16 had a bug that added unnecessary + * padding to the body of the file when using this format. GNU tar + * 1.17 corrected this bug without bumping the version number, so + * it's not possible to support both variants. This code supports + * the later variant at the expense of not supporting the former. + * + * This variant also replaced GNU.sparse.size with GNU.sparse.realsize + * and introduced the GNU.sparse.major/GNU.sparse.minor attributes. + */ + +/* + * Read the next line from the input, and parse it as a decimal + * integer followed by '\n'. Returns positive integer value or + * negative on error. + */ +static int64_t +gnu_sparse_10_atol(struct archive_read *a, struct tar *tar, + int64_t *remaining, size_t *unconsumed) +{ + int64_t l, limit, last_digit_limit; + const char *p; + ssize_t bytes_read; + int base, digit; + + base = 10; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + /* + * Skip any lines starting with '#'; GNU tar specs + * don't require this, but they should. + */ + do { + bytes_read = readline(a, tar, &p, tar_min(*remaining, 100), unconsumed); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + *remaining -= bytes_read; + } while (p[0] == '#'); + + l = 0; + while (bytes_read > 0) { + if (*p == '\n') + return (l); + if (*p < '0' || *p >= '0' + base) + return (ARCHIVE_WARN); + digit = *p - '0'; + if (l > limit || (l == limit && digit > last_digit_limit)) + l = INT64_MAX; /* Truncate on overflow. */ + else + l = (l * base) + digit; + p++; + bytes_read--; + } + /* TODO: Error message. */ + return (ARCHIVE_WARN); +} + +/* + * Returns length (in bytes) of the sparse data description + * that was read. + */ +static ssize_t +gnu_sparse_10_read(struct archive_read *a, struct tar *tar, size_t *unconsumed) +{ + ssize_t bytes_read; + int entries; + int64_t offset, size, to_skip, remaining; + + /* Clear out the existing sparse list. */ + gnu_clear_sparse_list(tar); + + remaining = tar->entry_bytes_remaining; + + /* Parse entries. */ + entries = gnu_sparse_10_atol(a, tar, &remaining, unconsumed); + if (entries < 0) + return (ARCHIVE_FATAL); + /* Parse the individual entries. */ + while (entries-- > 0) { + /* Parse offset/size */ + offset = gnu_sparse_10_atol(a, tar, &remaining, unconsumed); + if (offset < 0) + return (ARCHIVE_FATAL); + size = gnu_sparse_10_atol(a, tar, &remaining, unconsumed); + if (size < 0) + return (ARCHIVE_FATAL); + /* Add a new sparse entry. */ + if (gnu_add_sparse_entry(a, tar, offset, size) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + /* Skip rest of block... */ + tar_flush_unconsumed(a, unconsumed); + bytes_read = tar->entry_bytes_remaining - remaining; + to_skip = 0x1ff & -bytes_read; + if (to_skip != __archive_read_consume(a, to_skip)) + return (ARCHIVE_FATAL); + return (bytes_read + to_skip); +} + +/* + * Solaris pax extension for a sparse file. This is recorded with the + * data and hole pairs. The way recording sparse infomation by Solaris' + * pax simply indicates where data and sparse are, so the stored contents + * consist of both data and hole. + */ +static int +solaris_sparse_parse(struct archive_read *a, struct tar *tar, + struct archive_entry *entry, const char *p) +{ + const char *e; + int64_t start, end; + int hole = 1; + + end = 0; + if (*p == ' ') + p++; + else + return (ARCHIVE_WARN); + for (;;) { + e = p; + while (*e != '\0' && *e != ' ') { + if (*e < '0' || *e > '9') + return (ARCHIVE_WARN); + e++; + } + start = end; + end = tar_atol10(p, e - p); + if (end < 0) + return (ARCHIVE_WARN); + if (start < end) { + if (gnu_add_sparse_entry(a, tar, start, + end - start) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + tar->sparse_last->hole = hole; + } + if (*e == '\0') + return (ARCHIVE_OK); + p = e + 1; + hole = hole == 0; + } +} + +/*- + * Convert text->integer. + * + * Traditional tar formats (including POSIX) specify base-8 for + * all of the standard numeric fields. This is a significant limitation + * in practice: + * = file size is limited to 8GB + * = rdevmajor and rdevminor are limited to 21 bits + * = uid/gid are limited to 21 bits + * + * There are two workarounds for this: + * = pax extended headers, which use variable-length string fields + * = GNU tar and STAR both allow either base-8 or base-256 in + * most fields. The high bit is set to indicate base-256. + * + * On read, this implementation supports both extensions. + */ +static int64_t +tar_atol(const char *p, unsigned char_cnt) +{ + /* + * Technically, GNU tar considers a field to be in base-256 + * only if the first byte is 0xff or 0x80. + */ + if (*p & 0x80) + return (tar_atol256(p, char_cnt)); + return (tar_atol8(p, char_cnt)); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +tar_atol8(const char *p, unsigned char_cnt) +{ + int64_t l, limit, last_digit_limit; + int digit, sign, base; + + base = 8; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '-') { + sign = -1; + p++; + } else + sign = 1; + + l = 0; + digit = *p - '0'; + while (digit >= 0 && digit < base && char_cnt-- > 0) { + if (l>limit || (l == limit && digit > last_digit_limit)) { + l = INT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (sign < 0) ? -l : l; +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ +static int64_t +tar_atol10(const char *p, unsigned char_cnt) +{ + int64_t l, limit, last_digit_limit; + int base, digit, sign; + + base = 10; + limit = INT64_MAX / base; + last_digit_limit = INT64_MAX % base; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '-') { + sign = -1; + p++; + } else + sign = 1; + + l = 0; + digit = *p - '0'; + while (digit >= 0 && digit < base && char_cnt-- > 0) { + if (l > limit || (l == limit && digit > last_digit_limit)) { + l = INT64_MAX; /* Truncate on overflow. */ + break; + } + l = (l * base) + digit; + digit = *++p - '0'; + } + return (sign < 0) ? -l : l; +} + +/* + * Parse a base-256 integer. This is just a straight signed binary + * value in big-endian order, except that the high-order bit is + * ignored. + */ +static int64_t +tar_atol256(const char *_p, unsigned char_cnt) +{ + int64_t l, upper_limit, lower_limit; + const unsigned char *p = (const unsigned char *)_p; + + upper_limit = INT64_MAX / 256; + lower_limit = INT64_MIN / 256; + + /* Pad with 1 or 0 bits, depending on sign. */ + if ((0x40 & *p) == 0x40) + l = (int64_t)-1; + else + l = 0; + l = (l << 6) | (0x3f & *p++); + while (--char_cnt > 0) { + if (l > upper_limit) { + l = INT64_MAX; /* Truncate on overflow */ + break; + } else if (l < lower_limit) { + l = INT64_MIN; + break; + } + l = (l << 8) | (0xff & (int64_t)*p++); + } + return (l); +} + +/* + * Returns length of line (including trailing newline) + * or negative on error. 'start' argument is updated to + * point to first character of line. This avoids copying + * when possible. + */ +static ssize_t +readline(struct archive_read *a, struct tar *tar, const char **start, + ssize_t limit, size_t *unconsumed) +{ + ssize_t bytes_read; + ssize_t total_size = 0; + const void *t; + const char *s; + void *p; + + tar_flush_unconsumed(a, unconsumed); + + t = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n' in the read buffer, return pointer to that. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + if (bytes_read > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + *unconsumed = bytes_read; + *start = s; + return (bytes_read); + } + *unconsumed = bytes_read; + /* Otherwise, we need to accumulate in a line buffer. */ + for (;;) { + if (total_size + bytes_read > limit) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Line too long"); + return (ARCHIVE_FATAL); + } + if (archive_string_ensure(&tar->line, total_size + bytes_read) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate working buffer"); + return (ARCHIVE_FATAL); + } + memcpy(tar->line.s + total_size, t, bytes_read); + tar_flush_unconsumed(a, unconsumed); + total_size += bytes_read; + /* If we found '\n', clean up and return. */ + if (p != NULL) { + *start = tar->line.s; + return (total_size); + } + /* Read some more. */ + t = __archive_read_ahead(a, 1, &bytes_read); + if (bytes_read <= 0) + return (ARCHIVE_FATAL); + s = t; /* Start of line? */ + p = memchr(t, '\n', bytes_read); + /* If we found '\n', trim the read. */ + if (p != NULL) { + bytes_read = 1 + ((const char *)p) - s; + } + *unconsumed = bytes_read; + } +} + +/* + * base64_decode - Base64 decode + * + * This accepts most variations of base-64 encoding, including: + * * with or without line breaks + * * with or without the final group padded with '=' or '_' characters + * (The most economical Base-64 variant does not pad the last group and + * omits line breaks; RFC1341 used for MIME requires both.) + */ +static char * +base64_decode(const char *s, size_t len, size_t *out_len) +{ + static const unsigned char digits[64] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M','N', + 'O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b', + 'c','d','e','f','g','h','i','j','k','l','m','n','o','p', + 'q','r','s','t','u','v','w','x','y','z','0','1','2','3', + '4','5','6','7','8','9','+','/' }; + static unsigned char decode_table[128]; + char *out, *d; + const unsigned char *src = (const unsigned char *)s; + + /* If the decode table is not yet initialized, prepare it. */ + if (decode_table[digits[1]] != 1) { + unsigned i; + memset(decode_table, 0xff, sizeof(decode_table)); + for (i = 0; i < sizeof(digits); i++) + decode_table[digits[i]] = i; + } + + /* Allocate enough space to hold the entire output. */ + /* Note that we may not use all of this... */ + out = (char *)malloc(len - len / 4 + 1); + if (out == NULL) { + *out_len = 0; + return (NULL); + } + d = out; + + while (len > 0) { + /* Collect the next group of (up to) four characters. */ + int v = 0; + int group_size = 0; + while (group_size < 4 && len > 0) { + /* '=' or '_' padding indicates final group. */ + if (*src == '=' || *src == '_') { + len = 0; + break; + } + /* Skip illegal characters (including line breaks) */ + if (*src > 127 || *src < 32 + || decode_table[*src] == 0xff) { + len--; + src++; + continue; + } + v <<= 6; + v |= decode_table[*src++]; + len --; + group_size++; + } + /* Align a short group properly. */ + v <<= 6 * (4 - group_size); + /* Unpack the group we just collected. */ + switch (group_size) { + case 4: d[2] = v & 0xff; + /* FALLTHROUGH */ + case 3: d[1] = (v >> 8) & 0xff; + /* FALLTHROUGH */ + case 2: d[0] = (v >> 16) & 0xff; + break; + case 1: /* this is invalid! */ + break; + } + d += group_size * 3 / 4; + } + + *out_len = d - out; + return (out); +} + +static char * +url_decode(const char *in) +{ + char *out, *d; + const char *s; + + out = (char *)malloc(strlen(in) + 1); + if (out == NULL) + return (NULL); + for (s = in, d = out; *s != '\0'; ) { + if (s[0] == '%' && s[1] != '\0' && s[2] != '\0') { + /* Try to convert % escape */ + int digit1 = tohex(s[1]); + int digit2 = tohex(s[2]); + if (digit1 >= 0 && digit2 >= 0) { + /* Looks good, consume three chars */ + s += 3; + /* Convert output */ + *d++ = ((digit1 << 4) | digit2); + continue; + } + /* Else fall through and treat '%' as normal char */ + } + *d++ = *s++; + } + *d = '\0'; + return (out); +} + +static int +tohex(int c) +{ + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (-1); +} diff --git a/libarchive/archive_read_support_format_xar.c b/libarchive/archive_read_support_format_xar.c new file mode 100644 index 0000000..3c01cbe --- /dev/null +++ b/libarchive/archive_read_support_format_xar.c @@ -0,0 +1,3324 @@ +/*- + * Copyright (c) 2009 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#if HAVE_LIBXML_XMLREADER_H +#include +#elif HAVE_BSDXML_H +#include +#elif HAVE_EXPAT_H +#include +#endif +#ifdef HAVE_BZLIB_H +#include +#endif +#if HAVE_LZMA_H +#include +#elif HAVE_LZMADEC_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_crypto_private.h" +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" + +#if (!defined(HAVE_LIBXML_XMLREADER_H) && \ + !defined(HAVE_BSDXML_H) && !defined(HAVE_EXPAT_H)) ||\ + !defined(HAVE_ZLIB_H) || \ + !defined(ARCHIVE_HAS_MD5) || !defined(ARCHIVE_HAS_SHA1) +/* + * xar needs several external libraries. + * o libxml2 or expat --- XML parser + * o openssl or MD5/SHA1 hash function + * o zlib + * o bzlib2 (option) + * o liblzma (option) + */ +int +archive_read_support_format_xar(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_xar"); + + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Xar not supported on this platform"); + return (ARCHIVE_WARN); +} + +#else /* Support xar format */ + +/* #define DEBUG 1 */ +/* #define DEBUG_PRINT_TOC 1 */ +#if DEBUG_PRINT_TOC +#define PRINT_TOC(d, outbytes) do { \ + unsigned char *x = (unsigned char *)(uintptr_t)d; \ + unsigned char c = x[outbytes-1]; \ + x[outbytes - 1] = 0; \ + fprintf(stderr, "%s", x); \ + fprintf(stderr, "%c", c); \ + x[outbytes - 1] = c; \ +} while (0) +#else +#define PRINT_TOC(d, outbytes) +#endif + +#define HEADER_MAGIC 0x78617221 +#define HEADER_SIZE 28 +#define HEADER_VERSION 1 +#define CKSUM_NONE 0 +#define CKSUM_SHA1 1 +#define CKSUM_MD5 2 + +#define MD5_SIZE 16 +#define SHA1_SIZE 20 +#define MAX_SUM_SIZE 20 + +enum enctype { + NONE, + GZIP, + BZIP2, + LZMA, + XZ, +}; + +struct chksumval { + int alg; + size_t len; + unsigned char val[MAX_SUM_SIZE]; +}; + +struct chksumwork { + int alg; +#ifdef ARCHIVE_HAS_MD5 + archive_md5_ctx md5ctx; +#endif +#ifdef ARCHIVE_HAS_SHA1 + archive_sha1_ctx sha1ctx; +#endif +}; + +struct xattr { + struct xattr *next; + struct archive_string name; + uint64_t id; + uint64_t length; + uint64_t offset; + uint64_t size; + enum enctype encoding; + struct chksumval a_sum; + struct chksumval e_sum; + struct archive_string fstype; +}; + +struct xar_file { + struct xar_file *next; + struct xar_file *hdnext; + struct xar_file *parent; + int subdirs; + + unsigned int has; +#define HAS_DATA 0x00001 +#define HAS_PATHNAME 0x00002 +#define HAS_SYMLINK 0x00004 +#define HAS_TIME 0x00008 +#define HAS_UID 0x00010 +#define HAS_GID 0x00020 +#define HAS_MODE 0x00040 +#define HAS_TYPE 0x00080 +#define HAS_DEV 0x00100 +#define HAS_DEVMAJOR 0x00200 +#define HAS_DEVMINOR 0x00400 +#define HAS_INO 0x00800 +#define HAS_FFLAGS 0x01000 +#define HAS_XATTR 0x02000 +#define HAS_ACL 0x04000 + + uint64_t id; + uint64_t length; + uint64_t offset; + uint64_t size; + enum enctype encoding; + struct chksumval a_sum; + struct chksumval e_sum; + struct archive_string pathname; + struct archive_string symlink; + time_t ctime; + time_t mtime; + time_t atime; + struct archive_string uname; + uid_t uid; + struct archive_string gname; + gid_t gid; + mode_t mode; + dev_t dev; + dev_t devmajor; + dev_t devminor; + int64_t ino64; + struct archive_string fflags_text; + unsigned int link; + unsigned int nlink; + struct archive_string hardlink; + struct xattr *xattr_list; +}; + +struct hdlink { + struct hdlink *next; + + unsigned int id; + int cnt; + struct xar_file *files; +}; + +struct heap_queue { + struct xar_file **files; + int allocated; + int used; +}; + +enum xmlstatus { + INIT, + XAR, + TOC, + TOC_CREATION_TIME, + TOC_CHECKSUM, + TOC_CHECKSUM_OFFSET, + TOC_CHECKSUM_SIZE, + TOC_FILE, + FILE_DATA, + FILE_DATA_LENGTH, + FILE_DATA_OFFSET, + FILE_DATA_SIZE, + FILE_DATA_ENCODING, + FILE_DATA_A_CHECKSUM, + FILE_DATA_E_CHECKSUM, + FILE_DATA_CONTENT, + FILE_EA, + FILE_EA_LENGTH, + FILE_EA_OFFSET, + FILE_EA_SIZE, + FILE_EA_ENCODING, + FILE_EA_A_CHECKSUM, + FILE_EA_E_CHECKSUM, + FILE_EA_NAME, + FILE_EA_FSTYPE, + FILE_CTIME, + FILE_MTIME, + FILE_ATIME, + FILE_GROUP, + FILE_GID, + FILE_USER, + FILE_UID, + FILE_MODE, + FILE_DEVICE, + FILE_DEVICE_MAJOR, + FILE_DEVICE_MINOR, + FILE_DEVICENO, + FILE_INODE, + FILE_LINK, + FILE_TYPE, + FILE_NAME, + FILE_ACL, + FILE_ACL_DEFAULT, + FILE_ACL_ACCESS, + FILE_ACL_APPLEEXTENDED, + /* BSD file flags. */ + FILE_FLAGS, + FILE_FLAGS_USER_NODUMP, + FILE_FLAGS_USER_IMMUTABLE, + FILE_FLAGS_USER_APPEND, + FILE_FLAGS_USER_OPAQUE, + FILE_FLAGS_USER_NOUNLINK, + FILE_FLAGS_SYS_ARCHIVED, + FILE_FLAGS_SYS_IMMUTABLE, + FILE_FLAGS_SYS_APPEND, + FILE_FLAGS_SYS_NOUNLINK, + FILE_FLAGS_SYS_SNAPSHOT, + /* Linux file flags. */ + FILE_EXT2, + FILE_EXT2_SecureDeletion, + FILE_EXT2_Undelete, + FILE_EXT2_Compress, + FILE_EXT2_Synchronous, + FILE_EXT2_Immutable, + FILE_EXT2_AppendOnly, + FILE_EXT2_NoDump, + FILE_EXT2_NoAtime, + FILE_EXT2_CompDirty, + FILE_EXT2_CompBlock, + FILE_EXT2_NoCompBlock, + FILE_EXT2_CompError, + FILE_EXT2_BTree, + FILE_EXT2_HashIndexed, + FILE_EXT2_iMagic, + FILE_EXT2_Journaled, + FILE_EXT2_NoTail, + FILE_EXT2_DirSync, + FILE_EXT2_TopDir, + FILE_EXT2_Reserved, + UNKNOWN, +}; + +struct unknown_tag { + struct unknown_tag *next; + struct archive_string name; +}; + +struct xar { + uint64_t offset; /* Current position in the file. */ + int64_t total; + uint64_t h_base; + int end_of_file; +#define OUTBUFF_SIZE (1024 * 64) + unsigned char *outbuff; + + enum xmlstatus xmlsts; + enum xmlstatus xmlsts_unknown; + struct unknown_tag *unknowntags; + int base64text; + + /* + * TOC + */ + uint64_t toc_remaining; + uint64_t toc_total; + uint64_t toc_chksum_offset; + uint64_t toc_chksum_size; + + /* + * For Decoding data. + */ + enum enctype rd_encoding; + z_stream stream; + int stream_valid; +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + bz_stream bzstream; + int bzstream_valid; +#endif +#if HAVE_LZMA_H && HAVE_LIBLZMA + lzma_stream lzstream; + int lzstream_valid; +#elif HAVE_LZMADEC_H && HAVE_LIBLZMADEC + lzmadec_stream lzstream; + int lzstream_valid; +#endif + /* + * For Checksum data. + */ + struct chksumwork a_sumwrk; + struct chksumwork e_sumwrk; + + struct xar_file *file; /* current reading file. */ + struct xattr *xattr; /* current reading extended attribute. */ + struct heap_queue file_queue; + struct xar_file *hdlink_orgs; + struct hdlink *hdlink_list; + + int entry_init; + uint64_t entry_total; + uint64_t entry_remaining; + size_t entry_unconsumed; + uint64_t entry_size; + enum enctype entry_encoding; + struct chksumval entry_a_sum; + struct chksumval entry_e_sum; + + struct archive_string_conv *sconv; +}; + +struct xmlattr { + struct xmlattr *next; + char *name; + char *value; +}; + +struct xmlattr_list { + struct xmlattr *first; + struct xmlattr **last; +}; + +static int xar_bid(struct archive_read *, int); +static int xar_read_header(struct archive_read *, + struct archive_entry *); +static int xar_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int xar_read_data_skip(struct archive_read *); +static int xar_cleanup(struct archive_read *); +static int move_reading_point(struct archive_read *, uint64_t); +static int rd_contents_init(struct archive_read *, + enum enctype, int, int); +static int rd_contents(struct archive_read *, const void **, + size_t *, size_t *, uint64_t); +static uint64_t atol10(const char *, size_t); +static int64_t atol8(const char *, size_t); +static size_t atohex(unsigned char *, size_t, const char *, size_t); +static time_t parse_time(const char *p, size_t n); +static int heap_add_entry(struct archive_read *a, + struct heap_queue *, struct xar_file *); +static struct xar_file *heap_get_entry(struct heap_queue *); +static int add_link(struct archive_read *, + struct xar *, struct xar_file *); +static void checksum_init(struct archive_read *, int, int); +static void checksum_update(struct archive_read *, const void *, + size_t, const void *, size_t); +static int checksum_final(struct archive_read *, const void *, + size_t, const void *, size_t); +static int decompression_init(struct archive_read *, enum enctype); +static int decompress(struct archive_read *, const void **, + size_t *, const void *, size_t *); +static int decompression_cleanup(struct archive_read *); +static void xmlattr_cleanup(struct xmlattr_list *); +static int file_new(struct archive_read *, + struct xar *, struct xmlattr_list *); +static void file_free(struct xar_file *); +static int xattr_new(struct archive_read *, + struct xar *, struct xmlattr_list *); +static void xattr_free(struct xattr *); +static int getencoding(struct xmlattr_list *); +static int getsumalgorithm(struct xmlattr_list *); +static int unknowntag_start(struct archive_read *, + struct xar *, const char *); +static void unknowntag_end(struct xar *, const char *); +static int xml_start(struct archive_read *, + const char *, struct xmlattr_list *); +static void xml_end(void *, const char *); +static void xml_data(void *, const char *, int); +static int xml_parse_file_flags(struct xar *, const char *); +static int xml_parse_file_ext2(struct xar *, const char *); +#if defined(HAVE_LIBXML_XMLREADER_H) +static int xml2_xmlattr_setup(struct archive_read *, + struct xmlattr_list *, xmlTextReaderPtr); +static int xml2_read_cb(void *, char *, int); +static int xml2_close_cb(void *); +static void xml2_error_hdr(void *, const char *, xmlParserSeverities, + xmlTextReaderLocatorPtr); +static int xml2_read_toc(struct archive_read *); +#elif defined(HAVE_BSDXML_H) || defined(HAVE_EXPAT_H) +struct expat_userData { + int state; + struct archive_read *archive; +}; +static int expat_xmlattr_setup(struct archive_read *, + struct xmlattr_list *, const XML_Char **); +static void expat_start_cb(void *, const XML_Char *, const XML_Char **); +static void expat_end_cb(void *, const XML_Char *); +static void expat_data_cb(void *, const XML_Char *, int); +static int expat_read_toc(struct archive_read *); +#endif + +int +archive_read_support_format_xar(struct archive *_a) +{ + struct xar *xar; + struct archive_read *a = (struct archive_read *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_xar"); + + xar = (struct xar *)calloc(1, sizeof(*xar)); + if (xar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate xar data"); + return (ARCHIVE_FATAL); + } + + r = __archive_read_register_format(a, + xar, + "xar", + xar_bid, + NULL, + xar_read_header, + xar_read_data, + xar_read_data_skip, + xar_cleanup); + if (r != ARCHIVE_OK) + free(xar); + return (r); +} + +static int +xar_bid(struct archive_read *a, int best_bid) +{ + const unsigned char *b; + int bid; + + (void)best_bid; /* UNUSED */ + + b = __archive_read_ahead(a, HEADER_SIZE, NULL); + if (b == NULL) + return (-1); + + bid = 0; + /* + * Verify magic code + */ + if (archive_be32dec(b) != HEADER_MAGIC) + return (0); + bid += 32; + /* + * Verify header size + */ + if (archive_be16dec(b+4) != HEADER_SIZE) + return (0); + bid += 16; + /* + * Verify header version + */ + if (archive_be16dec(b+6) != HEADER_VERSION) + return (0); + bid += 16; + /* + * Verify type of checksum + */ + switch (archive_be32dec(b+24)) { + case CKSUM_NONE: + case CKSUM_SHA1: + case CKSUM_MD5: + bid += 32; + break; + default: + return (0); + } + + return (bid); +} + +static int +read_toc(struct archive_read *a) +{ + struct xar *xar; + struct xar_file *file; + const unsigned char *b; + uint64_t toc_compressed_size; + uint64_t toc_uncompressed_size; + uint32_t toc_chksum_alg; + ssize_t bytes; + int r; + + xar = (struct xar *)(a->format->data); + + /* + * Read xar header. + */ + b = __archive_read_ahead(a, HEADER_SIZE, &bytes); + if (bytes < 0) + return ((int)bytes); + if (bytes < HEADER_SIZE) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated archive header"); + return (ARCHIVE_FATAL); + } + + if (archive_be32dec(b) != HEADER_MAGIC) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid header magic"); + return (ARCHIVE_FATAL); + } + if (archive_be16dec(b+6) != HEADER_VERSION) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported header version(%d)", + archive_be16dec(b+6)); + return (ARCHIVE_FATAL); + } + toc_compressed_size = archive_be64dec(b+8); + xar->toc_remaining = toc_compressed_size; + toc_uncompressed_size = archive_be64dec(b+16); + toc_chksum_alg = archive_be32dec(b+24); + __archive_read_consume(a, HEADER_SIZE); + xar->offset += HEADER_SIZE; + xar->toc_total = 0; + + /* + * Read TOC(Table of Contents). + */ + /* Initialize reading contents. */ + r = move_reading_point(a, HEADER_SIZE); + if (r != ARCHIVE_OK) + return (r); + r = rd_contents_init(a, GZIP, toc_chksum_alg, CKSUM_NONE); + if (r != ARCHIVE_OK) + return (r); + +#ifdef HAVE_LIBXML_XMLREADER_H + r = xml2_read_toc(a); +#elif defined(HAVE_BSDXML_H) || defined(HAVE_EXPAT_H) + r = expat_read_toc(a); +#endif + if (r != ARCHIVE_OK) + return (r); + + /* Set 'The HEAP' base. */ + xar->h_base = xar->offset; + if (xar->toc_total != toc_uncompressed_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "TOC uncompressed size error"); + return (ARCHIVE_FATAL); + } + + /* + * Checksum TOC + */ + if (toc_chksum_alg != CKSUM_NONE) { + r = move_reading_point(a, xar->toc_chksum_offset); + if (r != ARCHIVE_OK) + return (r); + b = __archive_read_ahead(a, xar->toc_chksum_size, &bytes); + if (bytes < 0) + return ((int)bytes); + if ((uint64_t)bytes < xar->toc_chksum_size) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated archive file"); + return (ARCHIVE_FATAL); + } + r = checksum_final(a, b, xar->toc_chksum_size, NULL, 0); + __archive_read_consume(a, xar->toc_chksum_size); + xar->offset += xar->toc_chksum_size; + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* + * Connect hardlinked files. + */ + for (file = xar->hdlink_orgs; file != NULL; file = file->hdnext) { + struct hdlink **hdlink; + + for (hdlink = &(xar->hdlink_list); *hdlink != NULL; + hdlink = &((*hdlink)->next)) { + if ((*hdlink)->id == file->id) { + struct hdlink *hltmp; + struct xar_file *f2; + int nlink = (*hdlink)->cnt + 1; + + file->nlink = nlink; + for (f2 = (*hdlink)->files; f2 != NULL; + f2 = f2->hdnext) { + f2->nlink = nlink; + archive_string_copy( + &(f2->hardlink), &(file->pathname)); + } + /* Remove resolved files from hdlist_list. */ + hltmp = *hdlink; + *hdlink = hltmp->next; + free(hltmp); + break; + } + } + } + a->archive.archive_format = ARCHIVE_FORMAT_XAR; + a->archive.archive_format_name = "xar"; + + return (ARCHIVE_OK); +} + +static int +xar_read_header(struct archive_read *a, struct archive_entry *entry) +{ + struct xar *xar; + struct xar_file *file; + struct xattr *xattr; + int r; + + xar = (struct xar *)(a->format->data); + r = ARCHIVE_OK; + + if (xar->offset == 0) { + /* Create a character conversion object. */ + if (xar->sconv == NULL) { + xar->sconv = archive_string_conversion_from_charset( + &(a->archive), "UTF-8", 1); + if (xar->sconv == NULL) + return (ARCHIVE_FATAL); + } + + /* Read TOC. */ + r = read_toc(a); + if (r != ARCHIVE_OK) + return (r); + } + + for (;;) { + file = xar->file = heap_get_entry(&(xar->file_queue)); + if (file == NULL) { + xar->end_of_file = 1; + return (ARCHIVE_EOF); + } + if ((file->mode & AE_IFMT) != AE_IFDIR) + break; + if (file->has != (HAS_PATHNAME | HAS_TYPE)) + break; + /* + * If a file type is a directory and it does not have + * any metadata, do not export. + */ + file_free(file); + } + archive_entry_set_atime(entry, file->atime, 0); + archive_entry_set_ctime(entry, file->ctime, 0); + archive_entry_set_mtime(entry, file->mtime, 0); + archive_entry_set_gid(entry, file->gid); + if (file->gname.length > 0 && + archive_entry_copy_gname_l(entry, file->gname.s, + archive_strlen(&(file->gname)), xar->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Gname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Gname cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(xar->sconv)); + r = ARCHIVE_WARN; + } + archive_entry_set_uid(entry, file->uid); + if (file->uname.length > 0 && + archive_entry_copy_uname_l(entry, file->uname.s, + archive_strlen(&(file->uname)), xar->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Uname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Uname cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(xar->sconv)); + r = ARCHIVE_WARN; + } + archive_entry_set_mode(entry, file->mode); + if (archive_entry_copy_pathname_l(entry, file->pathname.s, + archive_strlen(&(file->pathname)), xar->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(xar->sconv)); + r = ARCHIVE_WARN; + } + + + if (file->symlink.length > 0 && + archive_entry_copy_symlink_l(entry, file->symlink.s, + archive_strlen(&(file->symlink)), xar->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Linkname cannot be converted from %s to current locale.", + archive_string_conversion_charset_name(xar->sconv)); + r = ARCHIVE_WARN; + } + /* Set proper nlink. */ + if ((file->mode & AE_IFMT) == AE_IFDIR) + archive_entry_set_nlink(entry, file->subdirs + 2); + else + archive_entry_set_nlink(entry, file->nlink); + archive_entry_set_size(entry, file->size); + if (archive_strlen(&(file->hardlink)) > 0) + archive_entry_set_hardlink(entry, file->hardlink.s); + archive_entry_set_ino64(entry, file->ino64); + if (file->has & HAS_DEV) + archive_entry_set_dev(entry, file->dev); + if (file->has & HAS_DEVMAJOR) + archive_entry_set_devmajor(entry, file->devmajor); + if (file->has & HAS_DEVMINOR) + archive_entry_set_devminor(entry, file->devminor); + if (archive_strlen(&(file->fflags_text)) > 0) + archive_entry_copy_fflags_text(entry, file->fflags_text.s); + + xar->entry_init = 1; + xar->entry_total = 0; + xar->entry_remaining = file->length; + xar->entry_size = file->size; + xar->entry_encoding = file->encoding; + xar->entry_a_sum = file->a_sum; + xar->entry_e_sum = file->e_sum; + /* + * Read extended attributes. + */ + xattr = file->xattr_list; + while (xattr != NULL) { + const void *d; + size_t outbytes, used; + + r = move_reading_point(a, xattr->offset); + if (r != ARCHIVE_OK) + break; + r = rd_contents_init(a, xattr->encoding, + xattr->a_sum.alg, xattr->e_sum.alg); + if (r != ARCHIVE_OK) + break; + d = NULL; + r = rd_contents(a, &d, &outbytes, &used, xattr->length); + if (r != ARCHIVE_OK) + break; + if (outbytes != xattr->size) { + archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC, + "Decompressed size error"); + r = ARCHIVE_FATAL; + break; + } + r = checksum_final(a, + xattr->a_sum.val, xattr->a_sum.len, + xattr->e_sum.val, xattr->e_sum.len); + if (r != ARCHIVE_OK) + break; + archive_entry_xattr_add_entry(entry, + xattr->name.s, d, outbytes); + xattr = xattr->next; + } + if (r != ARCHIVE_OK) { + file_free(file); + return (r); + } + + if (xar->entry_remaining > 0) + /* Move reading point to the beginning of current + * file contents. */ + r = move_reading_point(a, file->offset); + else + r = ARCHIVE_OK; + + file_free(file); + return (r); +} + +static int +xar_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + struct xar *xar; + size_t used; + int r; + + xar = (struct xar *)(a->format->data); + + if (xar->entry_unconsumed) { + __archive_read_consume(a, xar->entry_unconsumed); + xar->entry_unconsumed = 0; + } + + if (xar->end_of_file || xar->entry_remaining <= 0) { + r = ARCHIVE_EOF; + goto abort_read_data; + } + + if (xar->entry_init) { + r = rd_contents_init(a, xar->entry_encoding, + xar->entry_a_sum.alg, xar->entry_e_sum.alg); + if (r != ARCHIVE_OK) { + xar->entry_remaining = 0; + return (r); + } + xar->entry_init = 0; + } + + *buff = NULL; + r = rd_contents(a, buff, size, &used, xar->entry_remaining); + if (r != ARCHIVE_OK) + goto abort_read_data; + + *offset = xar->entry_total; + xar->entry_total += *size; + xar->total += *size; + xar->offset += used; + xar->entry_remaining -= used; + xar->entry_unconsumed = used; + + if (xar->entry_remaining == 0) { + if (xar->entry_total != xar->entry_size) { + archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC, + "Decompressed size error"); + r = ARCHIVE_FATAL; + goto abort_read_data; + } + r = checksum_final(a, + xar->entry_a_sum.val, xar->entry_a_sum.len, + xar->entry_e_sum.val, xar->entry_e_sum.len); + if (r != ARCHIVE_OK) + goto abort_read_data; + } + + return (ARCHIVE_OK); +abort_read_data: + *buff = NULL; + *size = 0; + *offset = xar->total; + return (r); +} + +static int +xar_read_data_skip(struct archive_read *a) +{ + struct xar *xar; + int64_t bytes_skipped; + + xar = (struct xar *)(a->format->data); + if (xar->end_of_file) + return (ARCHIVE_EOF); + bytes_skipped = __archive_read_consume(a, xar->entry_remaining + + xar->entry_unconsumed); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + xar->offset += bytes_skipped; + xar->entry_unconsumed = 0; + return (ARCHIVE_OK); +} + +static int +xar_cleanup(struct archive_read *a) +{ + struct xar *xar; + struct hdlink *hdlink; + int i; + int r; + + xar = (struct xar *)(a->format->data); + r = decompression_cleanup(a); + hdlink = xar->hdlink_list; + while (hdlink != NULL) { + struct hdlink *next = hdlink->next; + + free(hdlink); + hdlink = next; + } + for (i = 0; i < xar->file_queue.used; i++) + file_free(xar->file_queue.files[i]); + while (xar->unknowntags != NULL) { + struct unknown_tag *tag; + + tag = xar->unknowntags; + xar->unknowntags = tag->next; + archive_string_free(&(tag->name)); + free(tag); + } + free(xar->outbuff); + free(xar); + a->format->data = NULL; + return (r); +} + +static int +move_reading_point(struct archive_read *a, uint64_t offset) +{ + struct xar *xar; + + xar = (struct xar *)(a->format->data); + if (xar->offset - xar->h_base != offset) { + /* Seek forward to the start of file contents. */ + int64_t step; + + step = offset - (xar->offset - xar->h_base); + if (step > 0) { + step = __archive_read_consume(a, step); + if (step < 0) + return ((int)step); + xar->offset += step; + } else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Cannot seek."); + return (ARCHIVE_FAILED); + } + } + return (ARCHIVE_OK); +} + +static int +rd_contents_init(struct archive_read *a, enum enctype encoding, + int a_sum_alg, int e_sum_alg) +{ + int r; + + /* Init decompress library. */ + if ((r = decompression_init(a, encoding)) != ARCHIVE_OK) + return (r); + /* Init checksum library. */ + checksum_init(a, a_sum_alg, e_sum_alg); + return (ARCHIVE_OK); +} + +static int +rd_contents(struct archive_read *a, const void **buff, size_t *size, + size_t *used, uint64_t remaining) +{ + const unsigned char *b; + ssize_t bytes; + + /* Get whatever bytes are immediately available. */ + b = __archive_read_ahead(a, 1, &bytes); + if (bytes < 0) + return ((int)bytes); + if (bytes == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Truncated archive file"); + return (ARCHIVE_FATAL); + } + if ((uint64_t)bytes > remaining) + bytes = (ssize_t)remaining; + + /* + * Decompress contents of file. + */ + *used = bytes; + if (decompress(a, buff, size, b, used) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* + * Update checksum of a compressed data and a extracted data. + */ + checksum_update(a, b, *used, *buff, *size); + + return (ARCHIVE_OK); +} + +/* + * Note that this implementation does not (and should not!) obey + * locale settings; you cannot simply substitute strtol here, since + * it does obey locale. + */ + +static uint64_t +atol10(const char *p, size_t char_cnt) +{ + uint64_t l; + int digit; + + l = 0; + digit = *p - '0'; + while (digit >= 0 && digit < 10 && char_cnt-- > 0) { + l = (l * 10) + digit; + digit = *++p - '0'; + } + return (l); +} + +static int64_t +atol8(const char *p, size_t char_cnt) +{ + int64_t l; + int digit; + + l = 0; + while (char_cnt-- > 0) { + if (*p >= '0' && *p <= '7') + digit = *p - '0'; + else + break; + p++; + l <<= 3; + l |= digit; + } + return (l); +} + +static size_t +atohex(unsigned char *b, size_t bsize, const char *p, size_t psize) +{ + size_t fbsize = bsize; + + while (bsize && psize > 1) { + unsigned char x; + + if (p[0] >= 'a' && p[0] <= 'z') + x = (p[0] - 'a' + 0x0a) << 4; + else if (p[0] >= 'A' && p[0] <= 'Z') + x = (p[0] - 'A' + 0x0a) << 4; + else if (p[0] >= '0' && p[0] <= '9') + x = (p[0] - '0') << 4; + else + return (-1); + if (p[1] >= 'a' && p[1] <= 'z') + x |= p[1] - 'a' + 0x0a; + else if (p[1] >= 'A' && p[1] <= 'Z') + x |= p[1] - 'A' + 0x0a; + else if (p[1] >= '0' && p[1] <= '9') + x |= p[1] - '0'; + else + return (-1); + + *b++ = x; + bsize--; + p += 2; + psize -= 2; + } + return (fbsize - bsize); +} + +static time_t +time_from_tm(struct tm *t) +{ +#if HAVE_TIMEGM + /* Use platform timegm() if available. */ + return (timegm(t)); +#elif HAVE__MKGMTIME64 + return (_mkgmtime64(t)); +#else + /* Else use direct calculation using POSIX assumptions. */ + /* First, fix up tm_yday based on the year/month/day. */ + mktime(t); + /* Then we can compute timegm() from first principles. */ + return (t->tm_sec + t->tm_min * 60 + t->tm_hour * 3600 + + t->tm_yday * 86400 + (t->tm_year - 70) * 31536000 + + ((t->tm_year - 69) / 4) * 86400 - + ((t->tm_year - 1) / 100) * 86400 + + ((t->tm_year + 299) / 400) * 86400); +#endif +} + +static time_t +parse_time(const char *p, size_t n) +{ + struct tm tm; + time_t t = 0; + int64_t data; + + memset(&tm, 0, sizeof(tm)); + if (n != 20) + return (t); + data = atol10(p, 4); + if (data < 1900) + return (t); + tm.tm_year = (int)data - 1900; + p += 4; + if (*p++ != '-') + return (t); + data = atol10(p, 2); + if (data < 1 || data > 12) + return (t); + tm.tm_mon = (int)data -1; + p += 2; + if (*p++ != '-') + return (t); + data = atol10(p, 2); + if (data < 1 || data > 31) + return (t); + tm.tm_mday = (int)data; + p += 2; + if (*p++ != 'T') + return (t); + data = atol10(p, 2); + if (data < 0 || data > 23) + return (t); + tm.tm_hour = (int)data; + p += 2; + if (*p++ != ':') + return (t); + data = atol10(p, 2); + if (data < 0 || data > 59) + return (t); + tm.tm_min = (int)data; + p += 2; + if (*p++ != ':') + return (t); + data = atol10(p, 2); + if (data < 0 || data > 60) + return (t); + tm.tm_sec = (int)data; +#if 0 + p += 2; + if (*p != 'Z') + return (t); +#endif + + t = time_from_tm(&tm); + + return (t); +} + +static int +heap_add_entry(struct archive_read *a, + struct heap_queue *heap, struct xar_file *file) +{ + uint64_t file_id, parent_id; + int hole, parent; + + /* Expand our pending files list as necessary. */ + if (heap->used >= heap->allocated) { + struct xar_file **new_pending_files; + int new_size = heap->allocated * 2; + + if (heap->allocated < 1024) + new_size = 1024; + /* Overflow might keep us from growing the list. */ + if (new_size <= heap->allocated) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + new_pending_files = (struct xar_file **) + malloc(new_size * sizeof(new_pending_files[0])); + if (new_pending_files == NULL) { + archive_set_error(&a->archive, + ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + memcpy(new_pending_files, heap->files, + heap->allocated * sizeof(new_pending_files[0])); + if (heap->files != NULL) + free(heap->files); + heap->files = new_pending_files; + heap->allocated = new_size; + } + + file_id = file->id; + + /* + * Start with hole at end, walk it up tree to find insertion point. + */ + hole = heap->used++; + while (hole > 0) { + parent = (hole - 1)/2; + parent_id = heap->files[parent]->id; + if (file_id >= parent_id) { + heap->files[hole] = file; + return (ARCHIVE_OK); + } + /* Move parent into hole <==> move hole up tree. */ + heap->files[hole] = heap->files[parent]; + hole = parent; + } + heap->files[0] = file; + + return (ARCHIVE_OK); +} + +static struct xar_file * +heap_get_entry(struct heap_queue *heap) +{ + uint64_t a_id, b_id, c_id; + int a, b, c; + struct xar_file *r, *tmp; + + if (heap->used < 1) + return (NULL); + + /* + * The first file in the list is the earliest; we'll return this. + */ + r = heap->files[0]; + + /* + * Move the last item in the heap to the root of the tree + */ + heap->files[0] = heap->files[--(heap->used)]; + + /* + * Rebalance the heap. + */ + a = 0; /* Starting element and its heap key */ + a_id = heap->files[a]->id; + for (;;) { + b = a + a + 1; /* First child */ + if (b >= heap->used) + return (r); + b_id = heap->files[b]->id; + c = b + 1; /* Use second child if it is smaller. */ + if (c < heap->used) { + c_id = heap->files[c]->id; + if (c_id < b_id) { + b = c; + b_id = c_id; + } + } + if (a_id <= b_id) + return (r); + tmp = heap->files[a]; + heap->files[a] = heap->files[b]; + heap->files[b] = tmp; + a = b; + } +} + +static int +add_link(struct archive_read *a, struct xar *xar, struct xar_file *file) +{ + struct hdlink *hdlink; + + for (hdlink = xar->hdlink_list; hdlink != NULL; hdlink = hdlink->next) { + if (hdlink->id == file->link) { + file->hdnext = hdlink->files; + hdlink->cnt++; + hdlink->files = file; + return (ARCHIVE_OK); + } + } + hdlink = malloc(sizeof(*hdlink)); + if (hdlink == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + file->hdnext = NULL; + hdlink->id = file->link; + hdlink->cnt = 1; + hdlink->files = file; + hdlink->next = xar->hdlink_list; + xar->hdlink_list = hdlink; + return (ARCHIVE_OK); +} + +static void +_checksum_init(struct chksumwork *sumwrk, int sum_alg) +{ + sumwrk->alg = sum_alg; + switch (sum_alg) { + case CKSUM_NONE: + break; + case CKSUM_SHA1: + archive_sha1_init(&(sumwrk->sha1ctx)); + break; + case CKSUM_MD5: + archive_md5_init(&(sumwrk->md5ctx)); + break; + } +} + +static void +_checksum_update(struct chksumwork *sumwrk, const void *buff, size_t size) +{ + + switch (sumwrk->alg) { + case CKSUM_NONE: + break; + case CKSUM_SHA1: + archive_sha1_update(&(sumwrk->sha1ctx), buff, size); + break; + case CKSUM_MD5: + archive_md5_update(&(sumwrk->md5ctx), buff, size); + break; + } +} + +static int +_checksum_final(struct chksumwork *sumwrk, const void *val, size_t len) +{ + unsigned char sum[MAX_SUM_SIZE]; + int r = ARCHIVE_OK; + + switch (sumwrk->alg) { + case CKSUM_NONE: + break; + case CKSUM_SHA1: + archive_sha1_final(&(sumwrk->sha1ctx), sum); + if (len != SHA1_SIZE || + memcmp(val, sum, SHA1_SIZE) != 0) + r = ARCHIVE_FAILED; + break; + case CKSUM_MD5: + archive_md5_final(&(sumwrk->md5ctx), sum); + if (len != MD5_SIZE || + memcmp(val, sum, MD5_SIZE) != 0) + r = ARCHIVE_FAILED; + break; + } + return (r); +} + +static void +checksum_init(struct archive_read *a, int a_sum_alg, int e_sum_alg) +{ + struct xar *xar; + + xar = (struct xar *)(a->format->data); + _checksum_init(&(xar->a_sumwrk), a_sum_alg); + _checksum_init(&(xar->e_sumwrk), e_sum_alg); +} + +static void +checksum_update(struct archive_read *a, const void *abuff, size_t asize, + const void *ebuff, size_t esize) +{ + struct xar *xar; + + xar = (struct xar *)(a->format->data); + _checksum_update(&(xar->a_sumwrk), abuff, asize); + _checksum_update(&(xar->e_sumwrk), ebuff, esize); +} + +static int +checksum_final(struct archive_read *a, const void *a_sum_val, + size_t a_sum_len, const void *e_sum_val, size_t e_sum_len) +{ + struct xar *xar; + int r; + + xar = (struct xar *)(a->format->data); + r = _checksum_final(&(xar->a_sumwrk), a_sum_val, a_sum_len); + if (r == ARCHIVE_OK) + r = _checksum_final(&(xar->e_sumwrk), e_sum_val, e_sum_len); + if (r != ARCHIVE_OK) + archive_set_error(&(a->archive), ARCHIVE_ERRNO_MISC, + "Sumcheck error"); + return (r); +} + +static int +decompression_init(struct archive_read *a, enum enctype encoding) +{ + struct xar *xar; + const char *detail; + int r; + + xar = (struct xar *)(a->format->data); + xar->rd_encoding = encoding; + switch (encoding) { + case NONE: + break; + case GZIP: + if (xar->stream_valid) + r = inflateReset(&(xar->stream)); + else + r = inflateInit(&(xar->stream)); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Couldn't initialize zlib stream."); + return (ARCHIVE_FATAL); + } + xar->stream_valid = 1; + xar->stream.total_in = 0; + xar->stream.total_out = 0; + break; +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + case BZIP2: + if (xar->bzstream_valid) { + BZ2_bzDecompressEnd(&(xar->bzstream)); + xar->bzstream_valid = 0; + } + r = BZ2_bzDecompressInit(&(xar->bzstream), 0, 0); + if (r == BZ_MEM_ERROR) + r = BZ2_bzDecompressInit(&(xar->bzstream), 0, 1); + if (r != BZ_OK) { + int err = ARCHIVE_ERRNO_MISC; + detail = NULL; + switch (r) { + case BZ_PARAM_ERROR: + detail = "invalid setup parameter"; + break; + case BZ_MEM_ERROR: + err = ENOMEM; + detail = "out of memory"; + break; + case BZ_CONFIG_ERROR: + detail = "mis-compiled library"; + break; + } + archive_set_error(&a->archive, err, + "Internal error initializing decompressor: %s", + detail == NULL ? "??" : detail); + xar->bzstream_valid = 0; + return (ARCHIVE_FATAL); + } + xar->bzstream_valid = 1; + xar->bzstream.total_in_lo32 = 0; + xar->bzstream.total_in_hi32 = 0; + xar->bzstream.total_out_lo32 = 0; + xar->bzstream.total_out_hi32 = 0; + break; +#endif +#if defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) +#if LZMA_VERSION_MAJOR >= 5 +/* Effectively disable the limiter. */ +#define LZMA_MEMLIMIT UINT64_MAX +#else +/* NOTE: This needs to check memory size which running system has. */ +#define LZMA_MEMLIMIT (1U << 30) +#endif + case XZ: + case LZMA: + if (xar->lzstream_valid) { + lzma_end(&(xar->lzstream)); + xar->lzstream_valid = 0; + } + if (xar->entry_encoding == XZ) + r = lzma_stream_decoder(&(xar->lzstream), + LZMA_MEMLIMIT,/* memlimit */ + LZMA_CONCATENATED); + else + r = lzma_alone_decoder(&(xar->lzstream), + LZMA_MEMLIMIT);/* memlimit */ + if (r != LZMA_OK) { + switch (r) { + case LZMA_MEM_ERROR: + archive_set_error(&a->archive, + ENOMEM, + "Internal error initializing " + "compression library: " + "Cannot allocate memory"); + break; + case LZMA_OPTIONS_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: " + "Invalid or unsupported options"); + break; + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "lzma library"); + break; + } + return (ARCHIVE_FATAL); + } + xar->lzstream_valid = 1; + xar->lzstream.total_in = 0; + xar->lzstream.total_out = 0; + break; +#elif defined(HAVE_LZMADEC_H) && defined(HAVE_LIBLZMADEC) + case LZMA: + if (xar->lzstream_valid) + lzmadec_end(&(xar->lzstream)); + r = lzmadec_init(&(xar->lzstream)); + if (r != LZMADEC_OK) { + switch (r) { + case LZMADEC_HEADER_ERROR: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: " + "invalid header"); + break; + case LZMADEC_MEM_ERROR: + archive_set_error(&a->archive, + ENOMEM, + "Internal error initializing " + "compression library: " + "out of memory"); + break; + } + return (ARCHIVE_FATAL); + } + xar->lzstream_valid = 1; + xar->lzstream.total_in = 0; + xar->lzstream.total_out = 0; + break; +#endif + /* + * Unsupported compression. + */ + default: +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) + case BZIP2: +#endif +#if !defined(HAVE_LZMA_H) || !defined(HAVE_LIBLZMA) +#if !defined(HAVE_LZMADEC_H) || !defined(HAVE_LIBLZMADEC) + case LZMA: +#endif + case XZ: +#endif + switch (xar->entry_encoding) { + case BZIP2: detail = "bzip2"; break; + case LZMA: detail = "lzma"; break; + case XZ: detail = "xz"; break; + default: detail = "??"; break; + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s compression not supported on this platform", + detail); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); +} + +static int +decompress(struct archive_read *a, const void **buff, size_t *outbytes, + const void *b, size_t *used) +{ + struct xar *xar; + void *outbuff; + size_t avail_in, avail_out; + int r; + + xar = (struct xar *)(a->format->data); + avail_in = *used; + outbuff = (void *)(uintptr_t)*buff; + if (outbuff == NULL) { + if (xar->outbuff == NULL) { + xar->outbuff = malloc(OUTBUFF_SIZE); + if (xar->outbuff == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory for out buffer"); + return (ARCHIVE_FATAL); + } + } + outbuff = xar->outbuff; + *buff = outbuff; + avail_out = OUTBUFF_SIZE; + } else + avail_out = *outbytes; + switch (xar->rd_encoding) { + case GZIP: + xar->stream.next_in = (Bytef *)(uintptr_t)b; + xar->stream.avail_in = avail_in; + xar->stream.next_out = (unsigned char *)outbuff; + xar->stream.avail_out = avail_out; + r = inflate(&(xar->stream), 0); + switch (r) { + case Z_OK: /* Decompressor made some progress.*/ + case Z_STREAM_END: /* Found end of stream. */ + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File decompression failed (%d)", r); + return (ARCHIVE_FATAL); + } + *used = avail_in - xar->stream.avail_in; + *outbytes = avail_out - xar->stream.avail_out; + break; +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + case BZIP2: + xar->bzstream.next_in = (char *)(uintptr_t)b; + xar->bzstream.avail_in = avail_in; + xar->bzstream.next_out = (char *)outbuff; + xar->bzstream.avail_out = avail_out; + r = BZ2_bzDecompress(&(xar->bzstream)); + switch (r) { + case BZ_STREAM_END: /* Found end of stream. */ + switch (BZ2_bzDecompressEnd(&(xar->bzstream))) { + case BZ_OK: + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up decompressor"); + return (ARCHIVE_FATAL); + } + xar->bzstream_valid = 0; + /* FALLTHROUGH */ + case BZ_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "bzip decompression failed"); + return (ARCHIVE_FATAL); + } + *used = avail_in - xar->bzstream.avail_in; + *outbytes = avail_out - xar->bzstream.avail_out; + break; +#endif +#if defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) + case LZMA: + case XZ: + xar->lzstream.next_in = b; + xar->lzstream.avail_in = avail_in; + xar->lzstream.next_out = (unsigned char *)outbuff; + xar->lzstream.avail_out = avail_out; + r = lzma_code(&(xar->lzstream), LZMA_RUN); + switch (r) { + case LZMA_STREAM_END: /* Found end of stream. */ + lzma_end(&(xar->lzstream)); + xar->lzstream_valid = 0; + /* FALLTHROUGH */ + case LZMA_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "%s decompression failed(%d)", + (xar->entry_encoding == XZ)?"xz":"lzma", + r); + return (ARCHIVE_FATAL); + } + *used = avail_in - xar->lzstream.avail_in; + *outbytes = avail_out - xar->lzstream.avail_out; + break; +#elif defined(HAVE_LZMADEC_H) && defined(HAVE_LIBLZMADEC) + case LZMA: + xar->lzstream.next_in = (unsigned char *)(uintptr_t)b; + xar->lzstream.avail_in = avail_in; + xar->lzstream.next_out = (unsigned char *)outbuff; + xar->lzstream.avail_out = avail_out; + r = lzmadec_decode(&(xar->lzstream), 0); + switch (r) { + case LZMADEC_STREAM_END: /* Found end of stream. */ + switch (lzmadec_end(&(xar->lzstream))) { + case LZMADEC_OK: + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Failed to clean up lzmadec decompressor"); + return (ARCHIVE_FATAL); + } + xar->lzstream_valid = 0; + /* FALLTHROUGH */ + case LZMADEC_OK: /* Decompressor made some progress. */ + break; + default: + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "lzmadec decompression failed(%d)", + r); + return (ARCHIVE_FATAL); + } + *used = avail_in - xar->lzstream.avail_in; + *outbytes = avail_out - xar->lzstream.avail_out; + break; +#endif +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) + case BZIP2: +#endif +#if !defined(HAVE_LZMA_H) || !defined(HAVE_LIBLZMA) +#if !defined(HAVE_LZMADEC_H) || !defined(HAVE_LIBLZMADEC) + case LZMA: +#endif + case XZ: +#endif + case NONE: + default: + if (outbuff == xar->outbuff) { + *buff = b; + *used = avail_in; + *outbytes = avail_in; + } else { + if (avail_out > avail_in) + avail_out = avail_in; + memcpy(outbuff, b, avail_out); + *used = avail_out; + *outbytes = avail_out; + } + break; + } + return (ARCHIVE_OK); +} + +static int +decompression_cleanup(struct archive_read *a) +{ + struct xar *xar; + int r; + + xar = (struct xar *)(a->format->data); + r = ARCHIVE_OK; + if (xar->stream_valid) { + if (inflateEnd(&(xar->stream)) != Z_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up zlib decompressor"); + r = ARCHIVE_FATAL; + } + } +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + if (xar->bzstream_valid) { + if (BZ2_bzDecompressEnd(&(xar->bzstream)) != BZ_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up bzip2 decompressor"); + r = ARCHIVE_FATAL; + } + } +#endif +#if defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) + if (xar->lzstream_valid) + lzma_end(&(xar->lzstream)); +#elif defined(HAVE_LZMA_H) && defined(HAVE_LIBLZMA) + if (xar->lzstream_valid) { + if (lzmadec_end(&(xar->lzstream)) != LZMADEC_OK) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Failed to clean up lzmadec decompressor"); + r = ARCHIVE_FATAL; + } + } +#endif + return (r); +} + +static void +xmlattr_cleanup(struct xmlattr_list *list) +{ + struct xmlattr *attr, *next; + + attr = list->first; + while (attr != NULL) { + next = attr->next; + free(attr->name); + free(attr->value); + free(attr); + attr = next; + } + list->first = NULL; + list->last = &(list->first); +} + +static int +file_new(struct archive_read *a, struct xar *xar, struct xmlattr_list *list) +{ + struct xar_file *file; + struct xmlattr *attr; + + file = calloc(1, sizeof(*file)); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + file->parent = xar->file; + file->mode = 0777 | AE_IFREG; + file->atime = time(NULL); + file->mtime = time(NULL); + xar->file = file; + xar->xattr = NULL; + for (attr = list->first; attr != NULL; attr = attr->next) { + if (strcmp(attr->name, "id") == 0) + file->id = atol10(attr->value, strlen(attr->value)); + } + file->nlink = 1; + if (heap_add_entry(a, &(xar->file_queue), file) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + return (ARCHIVE_OK); +} + +static void +file_free(struct xar_file *file) +{ + struct xattr *xattr; + + archive_string_free(&(file->pathname)); + archive_string_free(&(file->symlink)); + archive_string_free(&(file->uname)); + archive_string_free(&(file->gname)); + archive_string_free(&(file->hardlink)); + xattr = file->xattr_list; + while (xattr != NULL) { + struct xattr *next; + + next = xattr->next; + xattr_free(xattr); + xattr = next; + } + + free(file); +} + +static int +xattr_new(struct archive_read *a, struct xar *xar, struct xmlattr_list *list) +{ + struct xattr *xattr, **nx; + struct xmlattr *attr; + + xattr = calloc(1, sizeof(*xattr)); + if (xattr == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + xar->xattr = xattr; + for (attr = list->first; attr != NULL; attr = attr->next) { + if (strcmp(attr->name, "id") == 0) + xattr->id = atol10(attr->value, strlen(attr->value)); + } + /* Chain to xattr list. */ + for (nx = &(xar->file->xattr_list); + *nx != NULL; nx = &((*nx)->next)) { + if (xattr->id < (*nx)->id) + break; + } + xattr->next = *nx; + *nx = xattr; + + return (ARCHIVE_OK); +} + +static void +xattr_free(struct xattr *xattr) +{ + archive_string_free(&(xattr->name)); + free(xattr); +} + +static int +getencoding(struct xmlattr_list *list) +{ + struct xmlattr *attr; + enum enctype encoding = NONE; + + for (attr = list->first; attr != NULL; attr = attr->next) { + if (strcmp(attr->name, "style") == 0) { + if (strcmp(attr->value, "application/octet-stream") == 0) + encoding = NONE; + else if (strcmp(attr->value, "application/x-gzip") == 0) + encoding = GZIP; + else if (strcmp(attr->value, "application/x-bzip2") == 0) + encoding = BZIP2; + else if (strcmp(attr->value, "application/x-lzma") == 0) + encoding = LZMA; + else if (strcmp(attr->value, "application/x-xz") == 0) + encoding = XZ; + } + } + return (encoding); +} + +static int +getsumalgorithm(struct xmlattr_list *list) +{ + struct xmlattr *attr; + int alg = CKSUM_NONE; + + for (attr = list->first; attr != NULL; attr = attr->next) { + if (strcmp(attr->name, "style") == 0) { + const char *v = attr->value; + if ((v[0] == 'S' || v[0] == 's') && + (v[1] == 'H' || v[1] == 'h') && + (v[2] == 'A' || v[2] == 'a') && + v[3] == '1' && v[4] == '\0') + alg = CKSUM_SHA1; + if ((v[0] == 'M' || v[0] == 'm') && + (v[1] == 'D' || v[1] == 'd') && + v[2] == '5' && v[3] == '\0') + alg = CKSUM_MD5; + } + } + return (alg); +} + +static int +unknowntag_start(struct archive_read *a, struct xar *xar, const char *name) +{ + struct unknown_tag *tag; + +#if DEBUG + fprintf(stderr, "unknowntag_start:%s\n", name); +#endif + tag = malloc(sizeof(*tag)); + if (tag == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + tag->next = xar->unknowntags; + archive_string_init(&(tag->name)); + archive_strcpy(&(tag->name), name); + if (xar->unknowntags == NULL) { + xar->xmlsts_unknown = xar->xmlsts; + xar->xmlsts = UNKNOWN; + } + xar->unknowntags = tag; + return (ARCHIVE_OK); +} + +static void +unknowntag_end(struct xar *xar, const char *name) +{ + struct unknown_tag *tag; + +#if DEBUG + fprintf(stderr, "unknowntag_end:%s\n", name); +#endif + tag = xar->unknowntags; + if (tag == NULL || name == NULL) + return; + if (strcmp(tag->name.s, name) == 0) { + xar->unknowntags = tag->next; + archive_string_free(&(tag->name)); + free(tag); + if (xar->unknowntags == NULL) + xar->xmlsts = xar->xmlsts_unknown; + } +} + +static int +xml_start(struct archive_read *a, const char *name, struct xmlattr_list *list) +{ + struct xar *xar; + struct xmlattr *attr; + + xar = (struct xar *)(a->format->data); + +#if DEBUG + fprintf(stderr, "xml_sta:[%s]\n", name); + for (attr = list->first; attr != NULL; attr = attr->next) + fprintf(stderr, " attr:\"%s\"=\"%s\"\n", + attr->name, attr->value); +#endif + xar->base64text = 0; + switch (xar->xmlsts) { + case INIT: + if (strcmp(name, "xar") == 0) + xar->xmlsts = XAR; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case XAR: + if (strcmp(name, "toc") == 0) + xar->xmlsts = TOC; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case TOC: + if (strcmp(name, "creation-time") == 0) + xar->xmlsts = TOC_CREATION_TIME; + else if (strcmp(name, "checksum") == 0) + xar->xmlsts = TOC_CHECKSUM; + else if (strcmp(name, "file") == 0) { + if (file_new(a, xar, list) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + xar->xmlsts = TOC_FILE; + } + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case TOC_CHECKSUM: + if (strcmp(name, "offset") == 0) + xar->xmlsts = TOC_CHECKSUM_OFFSET; + else if (strcmp(name, "size") == 0) + xar->xmlsts = TOC_CHECKSUM_SIZE; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case TOC_FILE: + if (strcmp(name, "file") == 0) { + if (file_new(a, xar, list) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + else if (strcmp(name, "data") == 0) + xar->xmlsts = FILE_DATA; + else if (strcmp(name, "ea") == 0) { + if (xattr_new(a, xar, list) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + xar->xmlsts = FILE_EA; + } + else if (strcmp(name, "ctime") == 0) + xar->xmlsts = FILE_CTIME; + else if (strcmp(name, "mtime") == 0) + xar->xmlsts = FILE_MTIME; + else if (strcmp(name, "atime") == 0) + xar->xmlsts = FILE_ATIME; + else if (strcmp(name, "group") == 0) + xar->xmlsts = FILE_GROUP; + else if (strcmp(name, "gid") == 0) + xar->xmlsts = FILE_GID; + else if (strcmp(name, "user") == 0) + xar->xmlsts = FILE_USER; + else if (strcmp(name, "uid") == 0) + xar->xmlsts = FILE_UID; + else if (strcmp(name, "mode") == 0) + xar->xmlsts = FILE_MODE; + else if (strcmp(name, "device") == 0) + xar->xmlsts = FILE_DEVICE; + else if (strcmp(name, "deviceno") == 0) + xar->xmlsts = FILE_DEVICENO; + else if (strcmp(name, "inode") == 0) + xar->xmlsts = FILE_INODE; + else if (strcmp(name, "link") == 0) + xar->xmlsts = FILE_LINK; + else if (strcmp(name, "type") == 0) { + xar->xmlsts = FILE_TYPE; + for (attr = list->first; attr != NULL; + attr = attr->next) { + if (strcmp(attr->name, "link") != 0) + continue; + if (strcmp(attr->value, "original") == 0) { + xar->file->hdnext = xar->hdlink_orgs; + xar->hdlink_orgs = xar->file; + } else { + xar->file->link = atol10(attr->value, + strlen(attr->value)); + if (xar->file->link > 0) + if (add_link(a, xar, xar->file) != ARCHIVE_OK) { + return (ARCHIVE_FATAL); + }; + } + } + } + else if (strcmp(name, "name") == 0) { + xar->xmlsts = FILE_NAME; + for (attr = list->first; attr != NULL; + attr = attr->next) { + if (strcmp(attr->name, "enctype") == 0 && + strcmp(attr->value, "base64") == 0) + xar->base64text = 1; + } + } + else if (strcmp(name, "acl") == 0) + xar->xmlsts = FILE_ACL; + else if (strcmp(name, "flags") == 0) + xar->xmlsts = FILE_FLAGS; + else if (strcmp(name, "ext2") == 0) + xar->xmlsts = FILE_EXT2; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_DATA: + if (strcmp(name, "length") == 0) + xar->xmlsts = FILE_DATA_LENGTH; + else if (strcmp(name, "offset") == 0) + xar->xmlsts = FILE_DATA_OFFSET; + else if (strcmp(name, "size") == 0) + xar->xmlsts = FILE_DATA_SIZE; + else if (strcmp(name, "encoding") == 0) { + xar->xmlsts = FILE_DATA_ENCODING; + xar->file->encoding = getencoding(list); + } + else if (strcmp(name, "archived-checksum") == 0) { + xar->xmlsts = FILE_DATA_A_CHECKSUM; + xar->file->a_sum.alg = getsumalgorithm(list); + } + else if (strcmp(name, "extracted-checksum") == 0) { + xar->xmlsts = FILE_DATA_E_CHECKSUM; + xar->file->e_sum.alg = getsumalgorithm(list); + } + else if (strcmp(name, "content") == 0) + xar->xmlsts = FILE_DATA_CONTENT; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_DEVICE: + if (strcmp(name, "major") == 0) + xar->xmlsts = FILE_DEVICE_MAJOR; + else if (strcmp(name, "minor") == 0) + xar->xmlsts = FILE_DEVICE_MINOR; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_DATA_CONTENT: + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_EA: + if (strcmp(name, "length") == 0) + xar->xmlsts = FILE_EA_LENGTH; + else if (strcmp(name, "offset") == 0) + xar->xmlsts = FILE_EA_OFFSET; + else if (strcmp(name, "size") == 0) + xar->xmlsts = FILE_EA_SIZE; + else if (strcmp(name, "encoding") == 0) { + xar->xmlsts = FILE_EA_ENCODING; + xar->xattr->encoding = getencoding(list); + } else if (strcmp(name, "archived-checksum") == 0) + xar->xmlsts = FILE_EA_A_CHECKSUM; + else if (strcmp(name, "extracted-checksum") == 0) + xar->xmlsts = FILE_EA_E_CHECKSUM; + else if (strcmp(name, "name") == 0) + xar->xmlsts = FILE_EA_NAME; + else if (strcmp(name, "fstype") == 0) + xar->xmlsts = FILE_EA_FSTYPE; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_ACL: + if (strcmp(name, "appleextended") == 0) + xar->xmlsts = FILE_ACL_APPLEEXTENDED; + if (strcmp(name, "default") == 0) + xar->xmlsts = FILE_ACL_DEFAULT; + else if (strcmp(name, "access") == 0) + xar->xmlsts = FILE_ACL_ACCESS; + else + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_FLAGS: + if (!xml_parse_file_flags(xar, name)) + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case FILE_EXT2: + if (!xml_parse_file_ext2(xar, name)) + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + case TOC_CREATION_TIME: + case TOC_CHECKSUM_OFFSET: + case TOC_CHECKSUM_SIZE: + case FILE_DATA_LENGTH: + case FILE_DATA_OFFSET: + case FILE_DATA_SIZE: + case FILE_DATA_ENCODING: + case FILE_DATA_A_CHECKSUM: + case FILE_DATA_E_CHECKSUM: + case FILE_EA_LENGTH: + case FILE_EA_OFFSET: + case FILE_EA_SIZE: + case FILE_EA_ENCODING: + case FILE_EA_A_CHECKSUM: + case FILE_EA_E_CHECKSUM: + case FILE_EA_NAME: + case FILE_EA_FSTYPE: + case FILE_CTIME: + case FILE_MTIME: + case FILE_ATIME: + case FILE_GROUP: + case FILE_GID: + case FILE_USER: + case FILE_UID: + case FILE_INODE: + case FILE_DEVICE_MAJOR: + case FILE_DEVICE_MINOR: + case FILE_DEVICENO: + case FILE_MODE: + case FILE_TYPE: + case FILE_LINK: + case FILE_NAME: + case FILE_ACL_DEFAULT: + case FILE_ACL_ACCESS: + case FILE_ACL_APPLEEXTENDED: + case FILE_FLAGS_USER_NODUMP: + case FILE_FLAGS_USER_IMMUTABLE: + case FILE_FLAGS_USER_APPEND: + case FILE_FLAGS_USER_OPAQUE: + case FILE_FLAGS_USER_NOUNLINK: + case FILE_FLAGS_SYS_ARCHIVED: + case FILE_FLAGS_SYS_IMMUTABLE: + case FILE_FLAGS_SYS_APPEND: + case FILE_FLAGS_SYS_NOUNLINK: + case FILE_FLAGS_SYS_SNAPSHOT: + case FILE_EXT2_SecureDeletion: + case FILE_EXT2_Undelete: + case FILE_EXT2_Compress: + case FILE_EXT2_Synchronous: + case FILE_EXT2_Immutable: + case FILE_EXT2_AppendOnly: + case FILE_EXT2_NoDump: + case FILE_EXT2_NoAtime: + case FILE_EXT2_CompDirty: + case FILE_EXT2_CompBlock: + case FILE_EXT2_NoCompBlock: + case FILE_EXT2_CompError: + case FILE_EXT2_BTree: + case FILE_EXT2_HashIndexed: + case FILE_EXT2_iMagic: + case FILE_EXT2_Journaled: + case FILE_EXT2_NoTail: + case FILE_EXT2_DirSync: + case FILE_EXT2_TopDir: + case FILE_EXT2_Reserved: + case UNKNOWN: + if (unknowntag_start(a, xar, name) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + break; + } + return (ARCHIVE_OK); +} + +static void +xml_end(void *userData, const char *name) +{ + struct archive_read *a; + struct xar *xar; + + a = (struct archive_read *)userData; + xar = (struct xar *)(a->format->data); + +#if DEBUG + fprintf(stderr, "xml_end:[%s]\n", name); +#endif + switch (xar->xmlsts) { + case INIT: + break; + case XAR: + if (strcmp(name, "xar") == 0) + xar->xmlsts = INIT; + break; + case TOC: + if (strcmp(name, "toc") == 0) + xar->xmlsts = XAR; + break; + case TOC_CREATION_TIME: + if (strcmp(name, "creation-time") == 0) + xar->xmlsts = TOC; + break; + case TOC_CHECKSUM: + if (strcmp(name, "checksum") == 0) + xar->xmlsts = TOC; + break; + case TOC_CHECKSUM_OFFSET: + if (strcmp(name, "offset") == 0) + xar->xmlsts = TOC_CHECKSUM; + break; + case TOC_CHECKSUM_SIZE: + if (strcmp(name, "size") == 0) + xar->xmlsts = TOC_CHECKSUM; + break; + case TOC_FILE: + if (strcmp(name, "file") == 0) { + if (xar->file->parent != NULL && + ((xar->file->mode & AE_IFMT) == AE_IFDIR)) + xar->file->parent->subdirs++; + xar->file = xar->file->parent; + if (xar->file == NULL) + xar->xmlsts = TOC; + } + break; + case FILE_DATA: + if (strcmp(name, "data") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_DATA_LENGTH: + if (strcmp(name, "length") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_OFFSET: + if (strcmp(name, "offset") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_SIZE: + if (strcmp(name, "size") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_ENCODING: + if (strcmp(name, "encoding") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_A_CHECKSUM: + if (strcmp(name, "archived-checksum") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_E_CHECKSUM: + if (strcmp(name, "extracted-checksum") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_DATA_CONTENT: + if (strcmp(name, "content") == 0) + xar->xmlsts = FILE_DATA; + break; + case FILE_EA: + if (strcmp(name, "ea") == 0) { + xar->xmlsts = TOC_FILE; + xar->xattr = NULL; + } + break; + case FILE_EA_LENGTH: + if (strcmp(name, "length") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_OFFSET: + if (strcmp(name, "offset") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_SIZE: + if (strcmp(name, "size") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_ENCODING: + if (strcmp(name, "encoding") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_A_CHECKSUM: + if (strcmp(name, "archived-checksum") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_E_CHECKSUM: + if (strcmp(name, "extracted-checksum") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_NAME: + if (strcmp(name, "name") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_EA_FSTYPE: + if (strcmp(name, "fstype") == 0) + xar->xmlsts = FILE_EA; + break; + case FILE_CTIME: + if (strcmp(name, "ctime") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_MTIME: + if (strcmp(name, "mtime") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_ATIME: + if (strcmp(name, "atime") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_GROUP: + if (strcmp(name, "group") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_GID: + if (strcmp(name, "gid") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_USER: + if (strcmp(name, "user") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_UID: + if (strcmp(name, "uid") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_MODE: + if (strcmp(name, "mode") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_DEVICE: + if (strcmp(name, "device") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_DEVICE_MAJOR: + if (strcmp(name, "major") == 0) + xar->xmlsts = FILE_DEVICE; + break; + case FILE_DEVICE_MINOR: + if (strcmp(name, "minor") == 0) + xar->xmlsts = FILE_DEVICE; + break; + case FILE_DEVICENO: + if (strcmp(name, "deviceno") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_INODE: + if (strcmp(name, "inode") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_LINK: + if (strcmp(name, "link") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_TYPE: + if (strcmp(name, "type") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_NAME: + if (strcmp(name, "name") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_ACL: + if (strcmp(name, "acl") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_ACL_DEFAULT: + if (strcmp(name, "default") == 0) + xar->xmlsts = FILE_ACL; + break; + case FILE_ACL_ACCESS: + if (strcmp(name, "access") == 0) + xar->xmlsts = FILE_ACL; + break; + case FILE_ACL_APPLEEXTENDED: + if (strcmp(name, "appleextended") == 0) + xar->xmlsts = FILE_ACL; + break; + case FILE_FLAGS: + if (strcmp(name, "flags") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_FLAGS_USER_NODUMP: + if (strcmp(name, "UserNoDump") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_USER_IMMUTABLE: + if (strcmp(name, "UserImmutable") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_USER_APPEND: + if (strcmp(name, "UserAppend") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_USER_OPAQUE: + if (strcmp(name, "UserOpaque") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_USER_NOUNLINK: + if (strcmp(name, "UserNoUnlink") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_SYS_ARCHIVED: + if (strcmp(name, "SystemArchived") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_SYS_IMMUTABLE: + if (strcmp(name, "SystemImmutable") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_SYS_APPEND: + if (strcmp(name, "SystemAppend") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_SYS_NOUNLINK: + if (strcmp(name, "SystemNoUnlink") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_FLAGS_SYS_SNAPSHOT: + if (strcmp(name, "SystemSnapshot") == 0) + xar->xmlsts = FILE_FLAGS; + break; + case FILE_EXT2: + if (strcmp(name, "ext2") == 0) + xar->xmlsts = TOC_FILE; + break; + case FILE_EXT2_SecureDeletion: + if (strcmp(name, "SecureDeletion") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Undelete: + if (strcmp(name, "Undelete") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Compress: + if (strcmp(name, "Compress") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Synchronous: + if (strcmp(name, "Synchronous") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Immutable: + if (strcmp(name, "Immutable") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_AppendOnly: + if (strcmp(name, "AppendOnly") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_NoDump: + if (strcmp(name, "NoDump") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_NoAtime: + if (strcmp(name, "NoAtime") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_CompDirty: + if (strcmp(name, "CompDirty") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_CompBlock: + if (strcmp(name, "CompBlock") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_NoCompBlock: + if (strcmp(name, "NoCompBlock") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_CompError: + if (strcmp(name, "CompError") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_BTree: + if (strcmp(name, "BTree") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_HashIndexed: + if (strcmp(name, "HashIndexed") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_iMagic: + if (strcmp(name, "iMagic") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Journaled: + if (strcmp(name, "Journaled") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_NoTail: + if (strcmp(name, "NoTail") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_DirSync: + if (strcmp(name, "DirSync") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_TopDir: + if (strcmp(name, "TopDir") == 0) + xar->xmlsts = FILE_EXT2; + break; + case FILE_EXT2_Reserved: + if (strcmp(name, "Reserved") == 0) + xar->xmlsts = FILE_EXT2; + break; + case UNKNOWN: + unknowntag_end(xar, name); + break; + } +} + +static const int base64[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* 00 - 0F */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* 10 - 1F */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 62, -1, -1, -1, 63, /* 20 - 2F */ + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, -1, -1, -1, -1, -1, -1, /* 30 - 3F */ + -1, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, /* 40 - 4F */ + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, -1, -1, -1, -1, -1, /* 50 - 5F */ + -1, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, /* 60 - 6F */ + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, -1, -1, -1, -1, -1, /* 70 - 7F */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* 80 - 8F */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* 90 - 9F */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* A0 - AF */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* B0 - BF */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* C0 - CF */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* D0 - DF */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* E0 - EF */ + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, /* F0 - FF */ +}; + +static void +strappend_base64(struct xar *xar, + struct archive_string *as, const char *s, size_t l) +{ + unsigned char buff[256]; + unsigned char *out; + const unsigned char *b; + size_t len; + + len = 0; + out = buff; + b = (const unsigned char *)s; + while (l > 0) { + int n = 0; + + if (l > 0) { + if (base64[b[0]] < 0 || base64[b[1]] < 0) + break; + n = base64[*b++] << 18; + n |= base64[*b++] << 12; + *out++ = n >> 16; + len++; + l -= 2; + } + if (l > 0) { + if (base64[*b] < 0) + break; + n |= base64[*b++] << 6; + *out++ = (n >> 8) & 0xFF; + len++; + --l; + } + if (l > 0) { + if (base64[*b] < 0) + break; + n |= base64[*b++]; + *out++ = n & 0xFF; + len++; + --l; + } + if (len+3 >= sizeof(buff)) { + archive_strncat(as, (const char *)buff, len); + len = 0; + out = buff; + } + } + if (len > 0) + archive_strncat(as, (const char *)buff, len); +} + +static void +xml_data(void *userData, const char *s, int len) +{ + struct archive_read *a; + struct xar *xar; + + a = (struct archive_read *)userData; + xar = (struct xar *)(a->format->data); + +#if DEBUG + { + char buff[1024]; + if (len > sizeof(buff)-1) + len = sizeof(buff)-1; + memcpy(buff, s, len); + buff[len] = 0; + fprintf(stderr, "\tlen=%d:\"%s\"\n", len, buff); + } +#endif + switch (xar->xmlsts) { + case TOC_CHECKSUM_OFFSET: + xar->toc_chksum_offset = atol10(s, len); + break; + case TOC_CHECKSUM_SIZE: + xar->toc_chksum_size = atol10(s, len); + break; + default: + break; + } + if (xar->file == NULL) + return; + + switch (xar->xmlsts) { + case FILE_NAME: + if (xar->file->parent != NULL) { + archive_string_concat(&(xar->file->pathname), + &(xar->file->parent->pathname)); + archive_strappend_char(&(xar->file->pathname), '/'); + } + xar->file->has |= HAS_PATHNAME; + if (xar->base64text) { + strappend_base64(xar, + &(xar->file->pathname), s, len); + } else + archive_strncat(&(xar->file->pathname), s, len); + break; + case FILE_LINK: + xar->file->has |= HAS_SYMLINK; + archive_strncpy(&(xar->file->symlink), s, len); + break; + case FILE_TYPE: + if (strncmp("file", s, len) == 0 || + strncmp("hardlink", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFREG; + if (strncmp("directory", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFDIR; + if (strncmp("symlink", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFLNK; + if (strncmp("character special", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFCHR; + if (strncmp("block special", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFBLK; + if (strncmp("socket", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFSOCK; + if (strncmp("fifo", s, len) == 0) + xar->file->mode = + (xar->file->mode & ~AE_IFMT) | AE_IFIFO; + xar->file->has |= HAS_TYPE; + break; + case FILE_INODE: + xar->file->has |= HAS_INO; + xar->file->ino64 = atol10(s, len); + break; + case FILE_DEVICE_MAJOR: + xar->file->has |= HAS_DEVMAJOR; + xar->file->devmajor = (dev_t)atol10(s, len); + break; + case FILE_DEVICE_MINOR: + xar->file->has |= HAS_DEVMINOR; + xar->file->devminor = (dev_t)atol10(s, len); + break; + case FILE_DEVICENO: + xar->file->has |= HAS_DEV; + xar->file->dev = (dev_t)atol10(s, len); + break; + case FILE_MODE: + xar->file->has |= HAS_MODE; + xar->file->mode = + (xar->file->mode & AE_IFMT) | + (atol8(s, len) & ~AE_IFMT); + break; + case FILE_GROUP: + xar->file->has |= HAS_GID; + archive_strncpy(&(xar->file->gname), s, len); + break; + case FILE_GID: + xar->file->has |= HAS_GID; + xar->file->gid = atol10(s, len); + break; + case FILE_USER: + xar->file->has |= HAS_UID; + archive_strncpy(&(xar->file->uname), s, len); + break; + case FILE_UID: + xar->file->has |= HAS_UID; + xar->file->uid = atol10(s, len); + break; + case FILE_CTIME: + xar->file->has |= HAS_TIME; + xar->file->ctime = parse_time(s, len); + break; + case FILE_MTIME: + xar->file->has |= HAS_TIME; + xar->file->mtime = parse_time(s, len); + break; + case FILE_ATIME: + xar->file->has |= HAS_TIME; + xar->file->atime = parse_time(s, len); + break; + case FILE_DATA_LENGTH: + xar->file->has |= HAS_DATA; + xar->file->length = atol10(s, len); + break; + case FILE_DATA_OFFSET: + xar->file->has |= HAS_DATA; + xar->file->offset = atol10(s, len); + break; + case FILE_DATA_SIZE: + xar->file->has |= HAS_DATA; + xar->file->size = atol10(s, len); + break; + case FILE_DATA_A_CHECKSUM: + xar->file->a_sum.len = atohex(xar->file->a_sum.val, + sizeof(xar->file->a_sum.val), s, len); + break; + case FILE_DATA_E_CHECKSUM: + xar->file->e_sum.len = atohex(xar->file->e_sum.val, + sizeof(xar->file->e_sum.val), s, len); + break; + case FILE_EA_LENGTH: + xar->file->has |= HAS_XATTR; + xar->xattr->length = atol10(s, len); + break; + case FILE_EA_OFFSET: + xar->file->has |= HAS_XATTR; + xar->xattr->offset = atol10(s, len); + break; + case FILE_EA_SIZE: + xar->file->has |= HAS_XATTR; + xar->xattr->size = atol10(s, len); + break; + case FILE_EA_A_CHECKSUM: + xar->file->has |= HAS_XATTR; + xar->xattr->a_sum.len = atohex(xar->xattr->a_sum.val, + sizeof(xar->xattr->a_sum.val), s, len); + break; + case FILE_EA_E_CHECKSUM: + xar->file->has |= HAS_XATTR; + xar->xattr->e_sum.len = atohex(xar->xattr->e_sum.val, + sizeof(xar->xattr->e_sum.val), s, len); + break; + case FILE_EA_NAME: + xar->file->has |= HAS_XATTR; + archive_strncpy(&(xar->xattr->name), s, len); + break; + case FILE_EA_FSTYPE: + xar->file->has |= HAS_XATTR; + archive_strncpy(&(xar->xattr->fstype), s, len); + break; + break; + case FILE_ACL_DEFAULT: + case FILE_ACL_ACCESS: + case FILE_ACL_APPLEEXTENDED: + xar->file->has |= HAS_ACL; + /* TODO */ + break; + case INIT: + case XAR: + case TOC: + case TOC_CREATION_TIME: + case TOC_CHECKSUM: + case TOC_CHECKSUM_OFFSET: + case TOC_CHECKSUM_SIZE: + case TOC_FILE: + case FILE_DATA: + case FILE_DATA_ENCODING: + case FILE_DATA_CONTENT: + case FILE_DEVICE: + case FILE_EA: + case FILE_EA_ENCODING: + case FILE_ACL: + case FILE_FLAGS: + case FILE_FLAGS_USER_NODUMP: + case FILE_FLAGS_USER_IMMUTABLE: + case FILE_FLAGS_USER_APPEND: + case FILE_FLAGS_USER_OPAQUE: + case FILE_FLAGS_USER_NOUNLINK: + case FILE_FLAGS_SYS_ARCHIVED: + case FILE_FLAGS_SYS_IMMUTABLE: + case FILE_FLAGS_SYS_APPEND: + case FILE_FLAGS_SYS_NOUNLINK: + case FILE_FLAGS_SYS_SNAPSHOT: + case FILE_EXT2: + case FILE_EXT2_SecureDeletion: + case FILE_EXT2_Undelete: + case FILE_EXT2_Compress: + case FILE_EXT2_Synchronous: + case FILE_EXT2_Immutable: + case FILE_EXT2_AppendOnly: + case FILE_EXT2_NoDump: + case FILE_EXT2_NoAtime: + case FILE_EXT2_CompDirty: + case FILE_EXT2_CompBlock: + case FILE_EXT2_NoCompBlock: + case FILE_EXT2_CompError: + case FILE_EXT2_BTree: + case FILE_EXT2_HashIndexed: + case FILE_EXT2_iMagic: + case FILE_EXT2_Journaled: + case FILE_EXT2_NoTail: + case FILE_EXT2_DirSync: + case FILE_EXT2_TopDir: + case FILE_EXT2_Reserved: + case UNKNOWN: + break; + } +} + +/* + * BSD file flags. + */ +static int +xml_parse_file_flags(struct xar *xar, const char *name) +{ + const char *flag = NULL; + + if (strcmp(name, "UserNoDump") == 0) { + xar->xmlsts = FILE_FLAGS_USER_NODUMP; + flag = "nodump"; + } + else if (strcmp(name, "UserImmutable") == 0) { + xar->xmlsts = FILE_FLAGS_USER_IMMUTABLE; + flag = "uimmutable"; + } + else if (strcmp(name, "UserAppend") == 0) { + xar->xmlsts = FILE_FLAGS_USER_APPEND; + flag = "uappend"; + } + else if (strcmp(name, "UserOpaque") == 0) { + xar->xmlsts = FILE_FLAGS_USER_OPAQUE; + flag = "opaque"; + } + else if (strcmp(name, "UserNoUnlink") == 0) { + xar->xmlsts = FILE_FLAGS_USER_NOUNLINK; + flag = "nouunlink"; + } + else if (strcmp(name, "SystemArchived") == 0) { + xar->xmlsts = FILE_FLAGS_SYS_ARCHIVED; + flag = "archived"; + } + else if (strcmp(name, "SystemImmutable") == 0) { + xar->xmlsts = FILE_FLAGS_SYS_IMMUTABLE; + flag = "simmutable"; + } + else if (strcmp(name, "SystemAppend") == 0) { + xar->xmlsts = FILE_FLAGS_SYS_APPEND; + flag = "sappend"; + } + else if (strcmp(name, "SystemNoUnlink") == 0) { + xar->xmlsts = FILE_FLAGS_SYS_NOUNLINK; + flag = "nosunlink"; + } + else if (strcmp(name, "SystemSnapshot") == 0) { + xar->xmlsts = FILE_FLAGS_SYS_SNAPSHOT; + flag = "snapshot"; + } + + if (flag == NULL) + return (0); + xar->file->has |= HAS_FFLAGS; + if (archive_strlen(&(xar->file->fflags_text)) > 0) + archive_strappend_char(&(xar->file->fflags_text), ','); + archive_strcat(&(xar->file->fflags_text), flag); + return (1); +} + +/* + * Linux file flags. + */ +static int +xml_parse_file_ext2(struct xar *xar, const char *name) +{ + const char *flag = NULL; + + if (strcmp(name, "SecureDeletion") == 0) { + xar->xmlsts = FILE_EXT2_SecureDeletion; + flag = "securedeletion"; + } + else if (strcmp(name, "Undelete") == 0) { + xar->xmlsts = FILE_EXT2_Undelete; + flag = "nouunlink"; + } + else if (strcmp(name, "Compress") == 0) { + xar->xmlsts = FILE_EXT2_Compress; + flag = "compress"; + } + else if (strcmp(name, "Synchronous") == 0) { + xar->xmlsts = FILE_EXT2_Synchronous; + flag = "sync"; + } + else if (strcmp(name, "Immutable") == 0) { + xar->xmlsts = FILE_EXT2_Immutable; + flag = "simmutable"; + } + else if (strcmp(name, "AppendOnly") == 0) { + xar->xmlsts = FILE_EXT2_AppendOnly; + flag = "sappend"; + } + else if (strcmp(name, "NoDump") == 0) { + xar->xmlsts = FILE_EXT2_NoDump; + flag = "nodump"; + } + else if (strcmp(name, "NoAtime") == 0) { + xar->xmlsts = FILE_EXT2_NoAtime; + flag = "noatime"; + } + else if (strcmp(name, "CompDirty") == 0) { + xar->xmlsts = FILE_EXT2_CompDirty; + flag = "compdirty"; + } + else if (strcmp(name, "CompBlock") == 0) { + xar->xmlsts = FILE_EXT2_CompBlock; + flag = "comprblk"; + } + else if (strcmp(name, "NoCompBlock") == 0) { + xar->xmlsts = FILE_EXT2_NoCompBlock; + flag = "nocomprblk"; + } + else if (strcmp(name, "CompError") == 0) { + xar->xmlsts = FILE_EXT2_CompError; + flag = "comperr"; + } + else if (strcmp(name, "BTree") == 0) { + xar->xmlsts = FILE_EXT2_BTree; + flag = "btree"; + } + else if (strcmp(name, "HashIndexed") == 0) { + xar->xmlsts = FILE_EXT2_HashIndexed; + flag = "hashidx"; + } + else if (strcmp(name, "iMagic") == 0) { + xar->xmlsts = FILE_EXT2_iMagic; + flag = "imagic"; + } + else if (strcmp(name, "Journaled") == 0) { + xar->xmlsts = FILE_EXT2_Journaled; + flag = "journal"; + } + else if (strcmp(name, "NoTail") == 0) { + xar->xmlsts = FILE_EXT2_NoTail; + flag = "notail"; + } + else if (strcmp(name, "DirSync") == 0) { + xar->xmlsts = FILE_EXT2_DirSync; + flag = "dirsync"; + } + else if (strcmp(name, "TopDir") == 0) { + xar->xmlsts = FILE_EXT2_TopDir; + flag = "topdir"; + } + else if (strcmp(name, "Reserved") == 0) { + xar->xmlsts = FILE_EXT2_Reserved; + flag = "reserved"; + } + + if (flag == NULL) + return (0); + if (archive_strlen(&(xar->file->fflags_text)) > 0) + archive_strappend_char(&(xar->file->fflags_text), ','); + archive_strcat(&(xar->file->fflags_text), flag); + return (1); +} + +#ifdef HAVE_LIBXML_XMLREADER_H + +static int +xml2_xmlattr_setup(struct archive_read *a, + struct xmlattr_list *list, xmlTextReaderPtr reader) +{ + struct xmlattr *attr; + int r; + + list->first = NULL; + list->last = &(list->first); + r = xmlTextReaderMoveToFirstAttribute(reader); + while (r == 1) { + attr = malloc(sizeof*(attr)); + if (attr == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + attr->name = strdup( + (const char *)xmlTextReaderConstLocalName(reader)); + if (attr->name == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + attr->value = strdup( + (const char *)xmlTextReaderConstValue(reader)); + if (attr->value == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + attr->next = NULL; + *list->last = attr; + list->last = &(attr->next); + r = xmlTextReaderMoveToNextAttribute(reader); + } + return (r); +} + +static int +xml2_read_cb(void *context, char *buffer, int len) +{ + struct archive_read *a; + struct xar *xar; + const void *d; + size_t outbytes; + size_t used; + int r; + + a = (struct archive_read *)context; + xar = (struct xar *)(a->format->data); + + if (xar->toc_remaining <= 0) + return (0); + d = buffer; + outbytes = len; + r = rd_contents(a, &d, &outbytes, &used, xar->toc_remaining); + if (r != ARCHIVE_OK) + return (r); + __archive_read_consume(a, used); + xar->toc_remaining -= used; + xar->offset += used; + xar->toc_total += outbytes; + PRINT_TOC(buffer, len); + + return ((int)outbytes); +} + +static int +xml2_close_cb(void *context) +{ + + (void)context; /* UNUSED */ + return (0); +} + +static void +xml2_error_hdr(void *arg, const char *msg, xmlParserSeverities severity, + xmlTextReaderLocatorPtr locator) +{ + struct archive_read *a; + + (void)locator; /* UNUSED */ + a = (struct archive_read *)arg; + switch (severity) { + case XML_PARSER_SEVERITY_VALIDITY_WARNING: + case XML_PARSER_SEVERITY_WARNING: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "XML Parsing error: %s", msg); + break; + case XML_PARSER_SEVERITY_VALIDITY_ERROR: + case XML_PARSER_SEVERITY_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "XML Parsing error: %s", msg); + break; + } +} + +static int +xml2_read_toc(struct archive_read *a) +{ + xmlTextReaderPtr reader; + struct xmlattr_list list; + int r; + + reader = xmlReaderForIO(xml2_read_cb, xml2_close_cb, a, NULL, NULL, 0); + if (reader == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory for xml parser"); + return (ARCHIVE_FATAL); + } + xmlTextReaderSetErrorHandler(reader, xml2_error_hdr, a); + + while ((r = xmlTextReaderRead(reader)) == 1) { + const char *name, *value; + int type, empty; + + type = xmlTextReaderNodeType(reader); + name = (const char *)xmlTextReaderConstLocalName(reader); + switch (type) { + case XML_READER_TYPE_ELEMENT: + empty = xmlTextReaderIsEmptyElement(reader); + r = xml2_xmlattr_setup(a, &list, reader); + if (r != ARCHIVE_OK) + return (r); + r = xml_start(a, name, &list); + xmlattr_cleanup(&list); + if (r != ARCHIVE_OK) + return (r); + if (empty) + xml_end(a, name); + break; + case XML_READER_TYPE_END_ELEMENT: + xml_end(a, name); + break; + case XML_READER_TYPE_TEXT: + value = (const char *)xmlTextReaderConstValue(reader); + xml_data(a, value, strlen(value)); + break; + case XML_READER_TYPE_SIGNIFICANT_WHITESPACE: + default: + break; + } + if (r < 0) + break; + } + xmlFreeTextReader(reader); + xmlCleanupParser(); + + return ((r == 0)?ARCHIVE_OK:ARCHIVE_FATAL); +} + +#elif defined(HAVE_BSDXML_H) || defined(HAVE_EXPAT_H) + +static int +expat_xmlattr_setup(struct archive_read *a, + struct xmlattr_list *list, const XML_Char **atts) +{ + struct xmlattr *attr; + char *name, *value; + + list->first = NULL; + list->last = &(list->first); + if (atts == NULL) + return (ARCHIVE_OK); + while (atts[0] != NULL && atts[1] != NULL) { + attr = malloc(sizeof*(attr)); + name = strdup(atts[0]); + value = strdup(atts[1]); + if (attr == NULL || name == NULL || value == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + attr->name = name; + attr->value = value; + attr->next = NULL; + *list->last = attr; + list->last = &(attr->next); + atts += 2; + } + return (ARCHIVE_OK); +} + +static void +expat_start_cb(void *userData, const XML_Char *name, const XML_Char **atts) +{ + struct expat_userData *ud = (struct expat_userData *)userData; + struct archive_read *a = ud->archive; + struct xmlattr_list list; + int r; + + r = expat_xmlattr_setup(a, &list, atts); + if (r == ARCHIVE_OK) + r = xml_start(a, (const char *)name, &list); + xmlattr_cleanup(&list); + ud->state = r; +} + +static void +expat_end_cb(void *userData, const XML_Char *name) +{ + struct expat_userData *ud = (struct expat_userData *)userData; + + xml_end(ud->archive, (const char *)name); +} + +static void +expat_data_cb(void *userData, const XML_Char *s, int len) +{ + struct expat_userData *ud = (struct expat_userData *)userData; + + xml_data(ud->archive, s, len); +} + +static int +expat_read_toc(struct archive_read *a) +{ + struct xar *xar; + XML_Parser parser; + struct expat_userData ud; + + ud.state = ARCHIVE_OK; + ud.archive = a; + + xar = (struct xar *)(a->format->data); + + /* Initialize XML Parser library. */ + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Couldn't allocate memory for xml parser"); + return (ARCHIVE_FATAL); + } + XML_SetUserData(parser, &ud); + XML_SetElementHandler(parser, expat_start_cb, expat_end_cb); + XML_SetCharacterDataHandler(parser, expat_data_cb); + xar->xmlsts = INIT; + + while (xar->toc_remaining && ud.state == ARCHIVE_OK) { + enum XML_Status xr; + const void *d; + size_t outbytes; + size_t used; + int r; + + d = NULL; + r = rd_contents(a, &d, &outbytes, &used, xar->toc_remaining); + if (r != ARCHIVE_OK) + return (r); + xar->toc_remaining -= used; + xar->offset += used; + xar->toc_total += outbytes; + PRINT_TOC(d, outbytes); + + xr = XML_Parse(parser, d, outbytes, xar->toc_remaining == 0); + __archive_read_consume(a, used); + if (xr == XML_STATUS_ERROR) { + XML_ParserFree(parser); + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "XML Parsing failed"); + return (ARCHIVE_FATAL); + } + } + XML_ParserFree(parser); + return (ud.state); +} +#endif /* defined(HAVE_BSDXML_H) || defined(HAVE_EXPAT_H) */ + +#endif /* Support xar format */ diff --git a/libarchive/archive_read_support_format_zip.c b/libarchive/archive_read_support_format_zip.c new file mode 100644 index 0000000..de0e434 --- /dev/null +++ b/libarchive/archive_read_support_format_zip.c @@ -0,0 +1,1233 @@ +/*- + * Copyright (c) 2004 Tim Kientzle + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_read_support_format_zip.c 201102 2009-12-28 03:11:36Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_endian.h" + +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif + +struct zip_entry { + int64_t local_header_offset; + int64_t compressed_size; + int64_t uncompressed_size; + int64_t gid; + int64_t uid; + struct archive_entry *entry; + time_t mtime; + time_t atime; + time_t ctime; + uint32_t crc32; + uint16_t mode; + uint16_t flags; + char compression; + char system; +}; + +struct zip { + /* Structural information about the archive. */ + int64_t central_directory_offset; + size_t central_directory_size; + size_t central_directory_entries; + char have_central_directory; + + /* List of entries (seekable Zip only) */ + size_t entries_remaining; + struct zip_entry *zip_entries; + struct zip_entry *entry; + + size_t unconsumed; + + /* entry_bytes_remaining is the number of bytes we expect. */ + int64_t entry_bytes_remaining; + + /* These count the number of bytes actually read for the entry. */ + int64_t entry_compressed_bytes_read; + int64_t entry_uncompressed_bytes_read; + + /* Running CRC32 of the decompressed data */ + unsigned long entry_crc32; + + /* Flags to mark progress of decompression. */ + char decompress_init; + char end_of_entry; + + ssize_t filename_length; + ssize_t extra_length; + + unsigned char *uncompressed_buffer; + size_t uncompressed_buffer_size; +#ifdef HAVE_ZLIB_H + z_stream stream; + char stream_valid; +#endif + + struct archive_string extra; + struct archive_string_conv *sconv; + struct archive_string_conv *sconv_default; + struct archive_string_conv *sconv_utf8; + int init_default_conversion; + char format_name[64]; +}; + +#define ZIP_LENGTH_AT_END 8 +#define ZIP_UTF8_NAME (1<<11) + +static int archive_read_format_zip_streamable_bid(struct archive_read *, int); +static int archive_read_format_zip_seekable_bid(struct archive_read *, int); +static int archive_read_format_zip_options(struct archive_read *, + const char *, const char *); +static int archive_read_format_zip_cleanup(struct archive_read *); +static int archive_read_format_zip_read_data(struct archive_read *, + const void **, size_t *, int64_t *); +static int archive_read_format_zip_read_data_skip(struct archive_read *a); +static int archive_read_format_zip_seekable_read_header(struct archive_read *, + struct archive_entry *); +static int archive_read_format_zip_streamable_read_header(struct archive_read *, + struct archive_entry *); +#ifdef HAVE_ZLIB_H +static int zip_read_data_deflate(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset); +#endif +static int zip_read_data_none(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset); +static int zip_read_local_file_header(struct archive_read *a, + struct archive_entry *entry, struct zip *); +static time_t zip_time(const char *); +static const char *compression_name(int compression); +static void process_extra(const char *, size_t, struct zip_entry *); + +int +archive_read_support_format_zip_streamable(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct zip *zip; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_zip"); + + zip = (struct zip *)malloc(sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate zip data"); + return (ARCHIVE_FATAL); + } + memset(zip, 0, sizeof(*zip)); + + r = __archive_read_register_format(a, + zip, + "zip", + archive_read_format_zip_streamable_bid, + archive_read_format_zip_options, + archive_read_format_zip_streamable_read_header, + archive_read_format_zip_read_data, + archive_read_format_zip_read_data_skip, + archive_read_format_zip_cleanup); + + if (r != ARCHIVE_OK) + free(zip); + return (ARCHIVE_OK); +} + +int +archive_read_support_format_zip_seekable(struct archive *_a) +{ + struct archive_read *a = (struct archive_read *)_a; + struct zip *zip; + int r; + + archive_check_magic(_a, ARCHIVE_READ_MAGIC, + ARCHIVE_STATE_NEW, "archive_read_support_format_zip_seekable"); + + zip = (struct zip *)malloc(sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate zip data"); + return (ARCHIVE_FATAL); + } + memset(zip, 0, sizeof(*zip)); + + r = __archive_read_register_format(a, + zip, + "zip", + archive_read_format_zip_seekable_bid, + archive_read_format_zip_options, + archive_read_format_zip_seekable_read_header, + archive_read_format_zip_read_data, + archive_read_format_zip_read_data_skip, + archive_read_format_zip_cleanup); + + if (r != ARCHIVE_OK) + free(zip); + return (ARCHIVE_OK); +} + +int +archive_read_support_format_zip(struct archive *a) +{ + int r; + r = archive_read_support_format_zip_streamable(a); + if (r != ARCHIVE_OK) + return r; + return (archive_read_support_format_zip_seekable(a)); +} + +/* + * TODO: This is a performance sink because it forces + * the read core to drop buffered data from the start + * of file, which will then have to be re-read again + * if this bidder loses. + * + * Consider passing in the winning bid value to subsequent + * bidders so that this bidder in particular can avoid + * seeking if it knows it's going to lose anyway. + */ +static int +archive_read_format_zip_seekable_bid(struct archive_read *a, int best_bid) +{ + struct zip *zip = (struct zip *)a->format->data; + int64_t filesize; + const char *p; + + /* If someone has already bid more than 32, then avoid + trashing the look-ahead buffers with a seek. */ + if (best_bid > 32) + return (-1); + + filesize = __archive_read_seek(a, -22, SEEK_END); + /* If we can't seek, then we can't bid. */ + if (filesize <= 0) + return 0; + + /* TODO: More robust search for end of central directory record. */ + if ((p = __archive_read_ahead(a, 22, NULL)) == NULL) + return 0; + /* First four bytes are signature for end of central directory + record. Four zero bytes ensure this isn't a multi-volume + Zip file (which we don't yet support). */ + if (memcmp(p, "PK\005\006\000\000\000\000", 8) != 0) + return 0; + + /* Since we've already done the hard work of finding the + end of central directory record, let's save the important + information. */ + zip->central_directory_entries = archive_le16dec(p + 10); + zip->central_directory_size = archive_le32dec(p + 12); + zip->central_directory_offset = archive_le32dec(p + 16); + + /* Just one volume, so central dir must all be on this volume. */ + if (zip->central_directory_entries != archive_le16dec(p + 8)) + return 0; + /* Central directory can't extend beyond end of this file. */ + if (zip->central_directory_offset + zip->central_directory_size > filesize) + return 0; + + /* This is just a tiny bit higher than the maximum returned by + the streaming Zip bidder. This ensures that the more accurate + seeking Zip parser wins whenever seek is available. */ + return 32; +} + +static int +slurp_central_directory(struct archive_read *a, struct zip *zip) +{ + unsigned i; + + __archive_read_seek(a, zip->central_directory_offset, SEEK_SET); + + zip->zip_entries = calloc(zip->central_directory_entries, sizeof(struct zip_entry)); + for (i = 0; i < zip->central_directory_entries; ++i) { + struct zip_entry *zip_entry = &zip->zip_entries[i]; + size_t filename_length, extra_length, comment_length; + uint32_t external_attributes; + const char *p; + + if ((p = __archive_read_ahead(a, 46, NULL)) == NULL) + return ARCHIVE_FATAL; + if (memcmp(p, "PK\001\002", 4) != 0) { + archive_set_error(&a->archive, + -1, "Invalid central directory signature"); + return ARCHIVE_FATAL; + } + zip->have_central_directory = 1; + /* version = p[4]; */ + zip_entry->system = p[5]; + /* version_required = archive_le16dec(p + 6); */ + zip_entry->flags = archive_le16dec(p + 8); + zip_entry->compression = archive_le16dec(p + 10); + zip_entry->mtime = zip_time(p + 12); + zip_entry->crc32 = archive_le32dec(p + 16); + zip_entry->compressed_size = archive_le32dec(p + 20); + zip_entry->uncompressed_size = archive_le32dec(p + 24); + filename_length = archive_le16dec(p + 28); + extra_length = archive_le16dec(p + 30); + comment_length = archive_le16dec(p + 32); + /* disk_start = archive_le16dec(p + 34); */ /* Better be zero. */ + /* internal_attributes = archive_le16dec(p + 36); */ /* text bit */ + external_attributes = archive_le32dec(p + 38); + zip_entry->local_header_offset = archive_le32dec(p + 42); + + if (zip_entry->system == 3) { + zip_entry->mode = external_attributes >> 16; + } else { + zip_entry->mode = AE_IFREG | 0777; + } + + /* Do we need to parse filename here? */ + /* Or can we wait until we read the local header? */ + __archive_read_consume(a, + 46 + filename_length + extra_length + comment_length); + } + + /* TODO: Sort zip entries. */ + + return ARCHIVE_OK; +} + +static int +archive_read_format_zip_seekable_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct zip *zip = (struct zip *)a->format->data; + int r; + + a->archive.archive_format = ARCHIVE_FORMAT_ZIP; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "ZIP"; + + if (zip->zip_entries == NULL) { + r = slurp_central_directory(a, zip); + zip->entries_remaining = zip->central_directory_entries; + if (r != ARCHIVE_OK) + return r; + zip->entry = zip->zip_entries; + } else { + ++zip->entry; + } + + if (zip->entries_remaining <= 0) + return ARCHIVE_EOF; + --zip->entries_remaining; + + /* TODO: If entries are sorted by offset within the file, we + should be able to skip here instead of seeking. Skipping is + typically faster (easier for I/O layer to optimize). */ + __archive_read_seek(a, zip->entry->local_header_offset, SEEK_SET); + zip->unconsumed = 0; + return zip_read_local_file_header(a, entry, zip); +} + +static int +archive_read_format_zip_streamable_bid(struct archive_read *a, int best_bid) +{ + const char *p; + + (void)best_bid; /* UNUSED */ + + if ((p = __archive_read_ahead(a, 4, NULL)) == NULL) + return (-1); + + /* + * Bid of 30 here is: 16 bits for "PK", + * next 16-bit field has four options (-2 bits). + * 16 + 16-2 = 30. + */ + if (p[0] == 'P' && p[1] == 'K') { + if ((p[2] == '\001' && p[3] == '\002') + || (p[2] == '\003' && p[3] == '\004') + || (p[2] == '\005' && p[3] == '\006') + || (p[2] == '\007' && p[3] == '\010') + || (p[2] == '0' && p[3] == '0')) + return (30); + } + + return (0); +} + +static int +archive_read_format_zip_options(struct archive_read *a, + const char *key, const char *val) +{ + struct zip *zip; + int ret = ARCHIVE_FAILED; + + zip = (struct zip *)(a->format->data); + if (strcmp(key, "compat-2x") == 0) { + /* Handle filnames as libarchive 2.x */ + zip->init_default_conversion = (val != NULL) ? 1 : 0; + ret = ARCHIVE_OK; + } else if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "zip: hdrcharset option needs a character-set name"); + else { + zip->sconv = archive_string_conversion_from_charset( + &a->archive, val, 0); + if (zip->sconv != NULL) { + if (strcmp(val, "UTF-8") == 0) + zip->sconv_utf8 = zip->sconv; + ret = ARCHIVE_OK; + } else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "zip: unknown keyword ``%s''", key); + + return (ret); +} + +static int +archive_read_format_zip_streamable_read_header(struct archive_read *a, + struct archive_entry *entry) +{ + struct zip *zip; + + a->archive.archive_format = ARCHIVE_FORMAT_ZIP; + if (a->archive.archive_format_name == NULL) + a->archive.archive_format_name = "ZIP"; + + zip = (struct zip *)(a->format->data); + + /* Make sure we have a zip_entry structure to use. */ + if (zip->zip_entries == NULL) { + zip->zip_entries = malloc(sizeof(struct zip_entry)); + if (zip->zip_entries == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return ARCHIVE_FATAL; + } + } + zip->entry = zip->zip_entries; + memset(zip->entry, 0, sizeof(struct zip_entry)); + + /* Search ahead for the next local file header. */ + __archive_read_consume(a, zip->unconsumed); + zip->unconsumed = 0; + for (;;) { + int64_t skipped = 0; + const char *p, *end; + ssize_t bytes; + + p = __archive_read_ahead(a, 4, &bytes); + if (p == NULL) + return (ARCHIVE_FATAL); + end = p + bytes; + + while (p + 4 <= end) { + if (p[0] == 'P' && p[1] == 'K') { + if (p[2] == '\001' && p[3] == '\002') + /* Beginning of central directory. */ + return (ARCHIVE_EOF); + + if (p[2] == '\003' && p[3] == '\004') { + /* Regular file entry. */ + __archive_read_consume(a, skipped); + return zip_read_local_file_header(a, entry, zip); + } + + if (p[2] == '\005' && p[3] == '\006') + /* End of central directory. */ + return (ARCHIVE_EOF); + } + ++p; + ++skipped; + } + __archive_read_consume(a, skipped); + } +} + +/* + * Assumes file pointer is at beginning of local file header. + */ +static int +zip_read_local_file_header(struct archive_read *a, struct archive_entry *entry, + struct zip *zip) +{ + const char *p; + const void *h; + const wchar_t *wp; + const char *cp; + size_t len, filename_length, extra_length; + struct archive_string_conv *sconv; + struct zip_entry *zip_entry = zip->entry; + uint32_t local_crc32; + int64_t compressed_size, uncompressed_size; + int ret = ARCHIVE_OK; + char version; + + zip->decompress_init = 0; + zip->end_of_entry = 0; + zip->entry_uncompressed_bytes_read = 0; + zip->entry_compressed_bytes_read = 0; + zip->entry_crc32 = crc32(0, NULL, 0); + + /* Setup default conversion. */ + if (zip->sconv == NULL && !zip->init_default_conversion) { + zip->sconv_default = + archive_string_default_conversion_for_read(&(a->archive)); + zip->init_default_conversion = 1; + } + + if ((p = __archive_read_ahead(a, 30, NULL)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + + if (memcmp(p, "PK\003\004", 4) != 0) { + archive_set_error(&a->archive, -1, "Damaged Zip archive"); + return ARCHIVE_FATAL; + } + version = p[4]; + zip_entry->system = p[5]; + zip_entry->flags = archive_le16dec(p + 6); + zip_entry->compression = archive_le16dec(p + 8); + zip_entry->mtime = zip_time(p + 10); + local_crc32 = archive_le32dec(p + 14); + compressed_size = archive_le32dec(p + 18); + uncompressed_size = archive_le32dec(p + 22); + filename_length = archive_le16dec(p + 26); + extra_length = archive_le16dec(p + 28); + + __archive_read_consume(a, 30); + + if (zip->have_central_directory) { + /* If we read the central dir entry, we must have size information + as well, so ignore the length-at-end flag. */ + zip_entry->flags &= ~ZIP_LENGTH_AT_END; + /* If we have values from both the local file header + and the central directory, warn about mismatches + which might indicate a damaged file. But some + writers always put zero in the local header; don't + bother warning about that. */ + if (local_crc32 != 0 && local_crc32 != zip_entry->crc32) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Inconsistent CRC32 values"); + ret = ARCHIVE_WARN; + } + if (compressed_size != 0 + && compressed_size != zip_entry->compressed_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Inconsistent compressed size"); + ret = ARCHIVE_WARN; + } + if (uncompressed_size != 0 + && uncompressed_size != zip_entry->uncompressed_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Inconsistent uncompressed size"); + ret = ARCHIVE_WARN; + } + } else { + /* If we don't have the CD info, use whatever we do have. */ + zip_entry->crc32 = local_crc32; + zip_entry->compressed_size = compressed_size; + zip_entry->uncompressed_size = uncompressed_size; + } + + /* Read the filename. */ + if ((h = __archive_read_ahead(a, filename_length, NULL)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + if (zip_entry->flags & ZIP_UTF8_NAME) { + /* The filename is stored to be UTF-8. */ + if (zip->sconv_utf8 == NULL) { + zip->sconv_utf8 = + archive_string_conversion_from_charset( + &a->archive, "UTF-8", 1); + if (zip->sconv_utf8 == NULL) + return (ARCHIVE_FATAL); + } + sconv = zip->sconv_utf8; + } else if (zip->sconv != NULL) + sconv = zip->sconv; + else + sconv = zip->sconv_default; + + if (archive_entry_copy_pathname_l(entry, + h, filename_length, sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Pathname cannot be converted " + "from %s to current locale.", + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + __archive_read_consume(a, filename_length); + + if (zip_entry->mode == 0) { + /* Especially in streaming mode, we can end up + here without having seen any mode information. + Guess from the filename. */ + wp = archive_entry_pathname_w(entry); + if (wp != NULL) { + len = wcslen(wp); + if (len > 0 && wp[len - 1] == L'/') + zip_entry->mode = AE_IFDIR | 0777; + else + zip_entry->mode = AE_IFREG | 0777; + } else { + cp = archive_entry_pathname(entry); + len = (cp != NULL)?strlen(cp):0; + if (len > 0 && cp[len - 1] == '/') + zip_entry->mode = AE_IFDIR | 0777; + else + zip_entry->mode = AE_IFREG | 0777; + } + } + + /* Read the extra data. */ + if ((h = __archive_read_ahead(a, extra_length, NULL)) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file header"); + return (ARCHIVE_FATAL); + } + process_extra(h, extra_length, zip_entry); + __archive_read_consume(a, extra_length); + + /* Populate some additional entry fields: */ + archive_entry_set_mode(entry, zip_entry->mode); + archive_entry_set_uid(entry, zip_entry->uid); + archive_entry_set_gid(entry, zip_entry->gid); + archive_entry_set_mtime(entry, zip_entry->mtime, 0); + archive_entry_set_ctime(entry, zip_entry->ctime, 0); + archive_entry_set_atime(entry, zip_entry->atime, 0); + /* Set the size only if it's meaningful. */ + if (0 == (zip_entry->flags & ZIP_LENGTH_AT_END)) + archive_entry_set_size(entry, zip_entry->uncompressed_size); + + zip->entry_bytes_remaining = zip_entry->compressed_size; + + /* If there's no body, force read_data() to return EOF immediately. */ + if (0 == (zip_entry->flags & ZIP_LENGTH_AT_END) + && zip->entry_bytes_remaining < 1) + zip->end_of_entry = 1; + + /* Set up a more descriptive format name. */ + sprintf(zip->format_name, "ZIP %d.%d (%s)", + version / 10, version % 10, + compression_name(zip->entry->compression)); + a->archive.archive_format_name = zip->format_name; + + return (ret); +} + +static const char * +compression_name(int compression) +{ + static const char *compression_names[] = { + "uncompressed", + "shrinking", + "reduced-1", + "reduced-2", + "reduced-3", + "reduced-4", + "imploded", + "reserved", + "deflation" + }; + + if (compression < + sizeof(compression_names)/sizeof(compression_names[0])) + return compression_names[compression]; + else + return "??"; +} + +/* Convert an MSDOS-style date/time into Unix-style time. */ +static time_t +zip_time(const char *p) +{ + int msTime, msDate; + struct tm ts; + + msTime = (0xff & (unsigned)p[0]) + 256 * (0xff & (unsigned)p[1]); + msDate = (0xff & (unsigned)p[2]) + 256 * (0xff & (unsigned)p[3]); + + memset(&ts, 0, sizeof(ts)); + ts.tm_year = ((msDate >> 9) & 0x7f) + 80; /* Years since 1900. */ + ts.tm_mon = ((msDate >> 5) & 0x0f) - 1; /* Month number. */ + ts.tm_mday = msDate & 0x1f; /* Day of month. */ + ts.tm_hour = (msTime >> 11) & 0x1f; + ts.tm_min = (msTime >> 5) & 0x3f; + ts.tm_sec = (msTime << 1) & 0x3e; + ts.tm_isdst = -1; + return mktime(&ts); +} + +static int +archive_read_format_zip_read_data(struct archive_read *a, + const void **buff, size_t *size, int64_t *offset) +{ + int r; + struct zip *zip = (struct zip *)(a->format->data); + + *offset = zip->entry_uncompressed_bytes_read; + *size = 0; + *buff = NULL; + + /* If we hit end-of-entry last time, return ARCHIVE_EOF. */ + if (zip->end_of_entry) + return (ARCHIVE_EOF); + + /* Return EOF immediately if this is a non-regular file. */ + if (AE_IFREG != (zip->entry->mode & AE_IFMT)) + return (ARCHIVE_EOF); + + __archive_read_consume(a, zip->unconsumed); + zip->unconsumed = 0; + + switch(zip->entry->compression) { + case 0: /* No compression. */ + r = zip_read_data_none(a, buff, size, offset); + break; +#ifdef HAVE_ZLIB_H + case 8: /* Deflate compression. */ + r = zip_read_data_deflate(a, buff, size, offset); + break; +#endif + default: /* Unsupported compression. */ + /* Return a warning. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Unsupported ZIP compression method (%s)", + compression_name(zip->entry->compression)); + /* We can't decompress this entry, but we will + * be able to skip() it and try the next entry. */ + return (ARCHIVE_FAILED); + break; + } + if (r != ARCHIVE_OK) + return (r); + /* Update checksum */ + if (*size) + zip->entry_crc32 = crc32(zip->entry_crc32, *buff, *size); + /* If we hit the end, swallow any end-of-data marker. */ + if (zip->end_of_entry) { + /* Check file size, CRC against these values. */ + if (zip->entry->compressed_size != zip->entry_compressed_bytes_read) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP compressed data is wrong size (read %jd, expected %jd)", + (intmax_t)zip->entry_compressed_bytes_read, + (intmax_t)zip->entry->compressed_size); + return (ARCHIVE_WARN); + } + /* Size field only stores the lower 32 bits of the actual + * size. */ + if ((zip->entry->uncompressed_size & UINT32_MAX) + != (zip->entry_uncompressed_bytes_read & UINT32_MAX)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP uncompressed data is wrong size (read %jd, expected %jd)", + (intmax_t)zip->entry_uncompressed_bytes_read, + (intmax_t)zip->entry->uncompressed_size); + return (ARCHIVE_WARN); + } + /* Check computed CRC against header */ + if (zip->entry->crc32 != zip->entry_crc32) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP bad CRC: 0x%lx should be 0x%lx", + (unsigned long)zip->entry_crc32, + (unsigned long)zip->entry->crc32); + return (ARCHIVE_WARN); + } + } + + return (ARCHIVE_OK); +} + +/* + * Read "uncompressed" data. There are three cases: + * 1) We know the size of the data. This is always true for the + * seeking reader (we've examined the Central Directory already). + * 2) ZIP_LENGTH_AT_END was set, but only the CRC was deferred. + * Info-ZIP seems to do this; we know the size but have to grab + * the CRC from the data descriptor afterwards. + * 3) We're streaming and ZIP_LENGTH_AT_END was specified and + * we have no size information. In this case, we can do pretty + * well by watching for the data descriptor record. The data + * descriptor is 16 bytes and includes a computed CRC that should + * provide a strong check. + * + * TODO: Technically, the PK\007\010 signature is optional. + * In the original spec, the data descriptor contained CRC + * and size fields but had no leading signature. In practice, + * newer writers seem to provide the signature pretty consistently, + * but we might need to do something more complex here if + * we want to handle older archives that lack that signature. + * + * Returns ARCHIVE_OK if successful, ARCHIVE_FATAL otherwise, sets + * zip->end_of_entry if it consumes all of the data. + */ +static int +zip_read_data_none(struct archive_read *a, const void **_buff, + size_t *size, int64_t *offset) +{ + struct zip *zip; + const char *buff; + ssize_t bytes_avail; + + zip = (struct zip *)(a->format->data); + + if (zip->entry->flags & ZIP_LENGTH_AT_END) { + const char *p; + + /* Grab at least 16 bytes. */ + buff = __archive_read_ahead(a, 16, &bytes_avail); + if (bytes_avail < 16) { + /* Zip archives have end-of-archive markers + that are longer than this, so a failure to get at + least 16 bytes really does indicate a truncated + file. */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file data"); + return (ARCHIVE_FATAL); + } + /* Check for a complete PK\007\010 signature. */ + p = buff; + if (p[0] == 'P' && p[1] == 'K' + && p[2] == '\007' && p[3] == '\010' + && archive_le32dec(p + 4) == zip->entry_crc32 + && archive_le32dec(p + 8) == zip->entry_compressed_bytes_read + && archive_le32dec(p + 12) == zip->entry_uncompressed_bytes_read) { + zip->entry->crc32 = archive_le32dec(p + 4); + zip->entry->compressed_size = archive_le32dec(p + 8); + zip->entry->uncompressed_size = archive_le32dec(p + 12); + zip->end_of_entry = 1; + zip->unconsumed = 16; + return (ARCHIVE_OK); + } + /* If not at EOF, ensure we consume at least one byte. */ + ++p; + + /* Scan forward until we see where a PK\007\010 signature might be. */ + /* Return bytes up until that point. On the next call, the code + above will verify the data descriptor. */ + while (p < buff + bytes_avail - 4) { + if (p[3] == 'P') { p += 3; } + else if (p[3] == 'K') { p += 2; } + else if (p[3] == '\007') { p += 1; } + else if (p[3] == '\010' && p[2] == '\007' + && p[1] == 'K' && p[0] == 'P') { + break; + } else { p += 4; } + } + bytes_avail = p - buff; + } else { + if (zip->entry_bytes_remaining == 0) { + zip->end_of_entry = 1; + return (ARCHIVE_OK); + } + /* Grab a bunch of bytes. */ + buff = __archive_read_ahead(a, 1, &bytes_avail); + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file data"); + return (ARCHIVE_FATAL); + } + if (bytes_avail > zip->entry_bytes_remaining) + bytes_avail = zip->entry_bytes_remaining; + } + *size = bytes_avail; + zip->entry_bytes_remaining -= bytes_avail; + zip->entry_uncompressed_bytes_read += bytes_avail; + zip->entry_compressed_bytes_read += bytes_avail; + zip->unconsumed += bytes_avail; + *_buff = buff; + return (ARCHIVE_OK); +} + +#ifdef HAVE_ZLIB_H +static int +zip_read_data_deflate(struct archive_read *a, const void **buff, + size_t *size, int64_t *offset) +{ + struct zip *zip; + ssize_t bytes_avail; + const void *compressed_buff; + int r; + + zip = (struct zip *)(a->format->data); + + /* If the buffer hasn't been allocated, allocate it now. */ + if (zip->uncompressed_buffer == NULL) { + zip->uncompressed_buffer_size = 256 * 1024; + zip->uncompressed_buffer + = (unsigned char *)malloc(zip->uncompressed_buffer_size); + if (zip->uncompressed_buffer == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for ZIP decompression"); + return (ARCHIVE_FATAL); + } + } + + /* If we haven't yet read any data, initialize the decompressor. */ + if (!zip->decompress_init) { + if (zip->stream_valid) + r = inflateReset(&zip->stream); + else + r = inflateInit2(&zip->stream, + -15 /* Don't check for zlib header */); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize ZIP decompression."); + return (ARCHIVE_FATAL); + } + /* Stream structure has been set up. */ + zip->stream_valid = 1; + /* We've initialized decompression for this stream. */ + zip->decompress_init = 1; + } + + /* + * Note: '1' here is a performance optimization. + * Recall that the decompression layer returns a count of + * available bytes; asking for more than that forces the + * decompressor to combine reads by copying data. + */ + compressed_buff = __archive_read_ahead(a, 1, &bytes_avail); + if (0 == (zip->entry->flags & ZIP_LENGTH_AT_END) + && bytes_avail > zip->entry_bytes_remaining) { + bytes_avail = zip->entry_bytes_remaining; + } + if (bytes_avail <= 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file body"); + return (ARCHIVE_FATAL); + } + + /* + * A bug in zlib.h: stream.next_in should be marked 'const' + * but isn't (the library never alters data through the + * next_in pointer, only reads it). The result: this ugly + * cast to remove 'const'. + */ + zip->stream.next_in = (Bytef *)(uintptr_t)(const void *)compressed_buff; + zip->stream.avail_in = bytes_avail; + zip->stream.total_in = 0; + zip->stream.next_out = zip->uncompressed_buffer; + zip->stream.avail_out = zip->uncompressed_buffer_size; + zip->stream.total_out = 0; + + r = inflate(&zip->stream, 0); + switch (r) { + case Z_OK: + break; + case Z_STREAM_END: + zip->end_of_entry = 1; + break; + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Out of memory for ZIP decompression"); + return (ARCHIVE_FATAL); + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "ZIP decompression failed (%d)", r); + return (ARCHIVE_FATAL); + } + + /* Consume as much as the compressor actually used. */ + bytes_avail = zip->stream.total_in; + __archive_read_consume(a, bytes_avail); + zip->entry_bytes_remaining -= bytes_avail; + zip->entry_compressed_bytes_read += bytes_avail; + + *size = zip->stream.total_out; + zip->entry_uncompressed_bytes_read += zip->stream.total_out; + *buff = zip->uncompressed_buffer; + + if (zip->end_of_entry && (zip->entry->flags & ZIP_LENGTH_AT_END)) { + const char *p; + + if (NULL == (p = __archive_read_ahead(a, 16, NULL))) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP end-of-file record"); + return (ARCHIVE_FATAL); + } + /* Consume the optional PK\007\010 marker. */ + if (p[0] == 'P' && p[1] == 'K' && p[2] == '\007' && p[3] == '\010') { + zip->entry->crc32 = archive_le32dec(p + 4); + zip->entry->compressed_size = archive_le32dec(p + 8); + zip->entry->uncompressed_size = archive_le32dec(p + 12); + zip->unconsumed = 16; + } + } + + return (ARCHIVE_OK); +} +#endif + +static int +archive_read_format_zip_read_data_skip(struct archive_read *a) +{ + struct zip *zip; + + zip = (struct zip *)(a->format->data); + + /* If we've already read to end of data, we're done. */ + if (zip->end_of_entry) + return (ARCHIVE_OK); + /* If we're seeking, we're done. */ + if (zip->have_central_directory) + return (ARCHIVE_OK); + + /* So we know we're streaming... */ + if (0 == (zip->entry->flags & ZIP_LENGTH_AT_END)) { + /* We know the compressed length, so we can just skip. */ + int64_t bytes_skipped = __archive_read_consume(a, + zip->entry_bytes_remaining + zip->unconsumed); + if (bytes_skipped < 0) + return (ARCHIVE_FATAL); + zip->unconsumed = 0; + return (ARCHIVE_OK); + } + + /* We're streaming and we don't know the length. */ + /* If the body is compressed and we know the format, we can + * find an exact end-of-entry by decompressing it. */ + switch (zip->entry->compression) { +#ifdef HAVE_ZLIB_H + case 8: /* Deflate compression. */ + while (!zip->end_of_entry) { + int64_t offset = 0; + const void *buff = NULL; + size_t size = 0; + int r; + r = zip_read_data_deflate(a, &buff, &size, &offset); + if (r != ARCHIVE_OK) + return (r); + } + break; +#endif + default: /* Uncompressed or unknown. */ + /* Scan for a PK\007\010 signature. */ + __archive_read_consume(a, zip->unconsumed); + zip->unconsumed = 0; + for (;;) { + const char *p, *buff; + ssize_t bytes_avail; + buff = __archive_read_ahead(a, 16, &bytes_avail); + if (bytes_avail < 16) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Truncated ZIP file data"); + return (ARCHIVE_FATAL); + } + p = buff; + while (p < buff + bytes_avail - 16) { + if (p[3] == 'P') { p += 3; } + else if (p[3] == 'K') { p += 2; } + else if (p[3] == '\007') { p += 1; } + else if (p[3] == '\010' && p[2] == '\007' + && p[1] == 'K' && p[0] == 'P') { + __archive_read_consume(a, p - buff + 16); + return ARCHIVE_OK; + } else { p += 4; } + } + __archive_read_consume(a, p - buff); + } + } + return ARCHIVE_OK; +} + +static int +archive_read_format_zip_cleanup(struct archive_read *a) +{ + struct zip *zip; + + zip = (struct zip *)(a->format->data); +#ifdef HAVE_ZLIB_H + if (zip->stream_valid) + inflateEnd(&zip->stream); +#endif + free(zip->zip_entries); + free(zip->uncompressed_buffer); + archive_string_free(&(zip->extra)); + free(zip); + (a->format->data) = NULL; + return (ARCHIVE_OK); +} + +/* + * The extra data is stored as a list of + * id1+size1+data1 + id2+size2+data2 ... + * triplets. id and size are 2 bytes each. + */ +static void +process_extra(const char *p, size_t extra_length, struct zip_entry* zip_entry) +{ + unsigned offset = 0; + + while (offset < extra_length - 4) + { + unsigned short headerid = archive_le16dec(p + offset); + unsigned short datasize = archive_le16dec(p + offset + 2); + offset += 4; + if (offset + datasize > extra_length) + break; +#ifdef DEBUG + fprintf(stderr, "Header id 0x%x, length %d\n", + headerid, datasize); +#endif + switch (headerid) { + case 0x0001: + /* Zip64 extended information extra field. */ + if (datasize >= 8) + zip_entry->uncompressed_size = + archive_le64dec(p + offset); + if (datasize >= 16) + zip_entry->compressed_size = + archive_le64dec(p + offset + 8); + break; + case 0x5455: + { + /* Extended time field "UT". */ + int flags = p[offset]; + offset++; + datasize--; + /* Flag bits indicate which dates are present. */ + if (flags & 0x01) + { +#ifdef DEBUG + fprintf(stderr, "mtime: %lld -> %d\n", + (long long)zip_entry->mtime, + archive_le32dec(p + offset)); +#endif + if (datasize < 4) + break; + zip_entry->mtime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x02) + { + if (datasize < 4) + break; + zip_entry->atime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + if (flags & 0x04) + { + if (datasize < 4) + break; + zip_entry->ctime = archive_le32dec(p + offset); + offset += 4; + datasize -= 4; + } + break; + } + case 0x5855: + { + /* Info-ZIP Unix Extra Field (old version) "UX". */ + if (datasize >= 8) { + zip_entry->atime = archive_le32dec(p + offset); + zip_entry->mtime = archive_le32dec(p + offset + 4); + } + if (datasize >= 12) { + zip_entry->uid = archive_le16dec(p + offset + 8); + zip_entry->gid = archive_le16dec(p + offset + 10); + } + break; + } + case 0x7855: + /* Info-ZIP Unix Extra Field (type 2) "Ux". */ +#ifdef DEBUG + fprintf(stderr, "uid %d gid %d\n", + archive_le16dec(p + offset), + archive_le16dec(p + offset + 2)); +#endif + if (datasize >= 2) + zip_entry->uid = archive_le16dec(p + offset); + if (datasize >= 4) + zip_entry->gid = archive_le16dec(p + offset + 2); + break; + case 0x7875: + { + /* Info-Zip Unix Extra Field (type 3) "ux". */ + int uidsize = 0, gidsize = 0; + + if (datasize >= 1 && p[offset] == 1) {/* version=1 */ + if (datasize >= 4) { + /* get a uid size. */ + uidsize = p[offset+1]; + if (uidsize == 2) + zip_entry->uid = archive_le16dec( + p + offset + 2); + else if (uidsize == 4 && datasize >= 6) + zip_entry->uid = archive_le32dec( + p + offset + 2); + } + if (datasize >= (2 + uidsize + 3)) { + /* get a gid size. */ + gidsize = p[offset+2+uidsize]; + if (gidsize == 2) + zip_entry->gid = archive_le16dec( + p+offset+2+uidsize+1); + else if (gidsize == 4 && + datasize >= (2 + uidsize + 5)) + zip_entry->gid = archive_le32dec( + p+offset+2+uidsize+1); + } + } + break; + } + default: + break; + } + offset += datasize; + } +#ifdef DEBUG + if (offset != extra_length) + { + fprintf(stderr, + "Extra data field contents do not match reported size!\n"); + } +#endif +} diff --git a/libarchive/archive_string.c b/libarchive/archive_string.c new file mode 100644 index 0000000..5e95dd7 --- /dev/null +++ b/libarchive/archive_string.c @@ -0,0 +1,4228 @@ +/*- + * Copyright (c) 2003-2011 Tim Kientzle + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_string.c 201095 2009-12-28 02:33:22Z kientzle $"); + +/* + * Basic resizable string support, to simplify manipulating arbitrary-sized + * strings while minimizing heap activity. + * + * In particular, the buffer used by a string object is only grown, it + * never shrinks, so you can clear and reuse the same string object + * without incurring additional memory allocations. + */ + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_ICONV_H +#include +#endif +#ifdef HAVE_LANGINFO_H +#include +#endif +#ifdef HAVE_LOCALCHARSET_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif +#if defined(_WIN32) && !defined(__CYGWIN__) +#include +#include +#endif +#if defined(__APPLE__) +#include +#endif + +#include "archive_endian.h" +#include "archive_private.h" +#include "archive_string.h" +#include "archive_string_composition.h" + +#if !defined(HAVE_WMEMCPY) && !defined(wmemcpy) +#define wmemcpy(a,b,i) (wchar_t *)memcpy((a), (b), (i) * sizeof(wchar_t)) +#endif + +struct archive_string_conv { + struct archive_string_conv *next; + char *from_charset; + char *to_charset; + unsigned from_cp; + unsigned to_cp; + /* Set 1 if from_charset and to_charset are the same. */ + int same; + int flag; +#define SCONV_TO_CHARSET 1 /* MBS is being converted to specified + * charset. */ +#define SCONV_FROM_CHARSET (1<<1) /* MBS is being converted from + * specified charset. */ +#define SCONV_BEST_EFFORT (1<<2) /* Copy at least ASCII code. */ +#define SCONV_WIN_CP (1<<3) /* Use Windows API for converting + * MBS. */ +#define SCONV_UTF8_LIBARCHIVE_2 (1<<4) /* Incorrect UTF-8 made by libarchive + * 2.x in the wrong assumption. */ +#define SCONV_NORMALIZATION_C (1<<6) /* Need normalization to be Form C. + * Before UTF-8 characters are actually + * processed. */ +#define SCONV_NORMALIZATION_D (1<<7) /* Need normalization to be Form D. + * Before UTF-8 characters are actually + * processed. + * Currently this only for MAC OS X. */ +#define SCONV_TO_UTF8 (1<<8) /* "to charset" side is UTF-8. */ +#define SCONV_FROM_UTF8 (1<<9) /* "from charset" side is UTF-8. */ +#define SCONV_TO_UTF16BE (1<<10) /* "to charset" side is UTF-16BE. */ +#define SCONV_FROM_UTF16BE (1<<11) /* "from charset" side is UTF-16BE. */ +#define SCONV_TO_UTF16LE (1<<12) /* "to charset" side is UTF-16LE. */ +#define SCONV_FROM_UTF16LE (1<<13) /* "from charset" side is UTF-16LE. */ +#define SCONV_TO_UTF16 (SCONV_TO_UTF16BE | SCONV_TO_UTF16LE) +#define SCONV_FROM_UTF16 (SCONV_FROM_UTF16BE | SCONV_FROM_UTF16LE) + +#if HAVE_ICONV + iconv_t cd; + iconv_t cd_w;/* Use at archive_mstring on + * Windows. */ +#endif + /* A temporary buffer for normalization. */ + struct archive_string utftmp; +#if defined(__APPLE__) + UnicodeToTextInfo uniInfo; + struct archive_string utf16nfc; + struct archive_string utf16nfd; +#endif + int (*converter[2])(struct archive_string *, const void *, size_t, + struct archive_string_conv *); + int nconverter; +}; + +#define CP_C_LOCALE 0 /* "C" locale only for this file. */ +#define CP_UTF16LE 1200 +#define CP_UTF16BE 1201 + +#define IS_HIGH_SURROGATE_LA(uc) ((uc) >= 0xD800 && (uc) <= 0xDBFF) +#define IS_LOW_SURROGATE_LA(uc) ((uc) >= 0xDC00 && (uc) <= 0xDFFF) +#define IS_SURROGATE_PAIR_LA(uc) ((uc) >= 0xD800 && (uc) <= 0xDFFF) +#define UNICODE_MAX 0x10FFFF +#define UNICODE_R_CHAR 0xFFFD /* Replacement character. */ +/* Set U+FFFD(Replacement character) in UTF-8. */ +#define UTF8_SET_R_CHAR(outp) do { \ + (outp)[0] = 0xef; \ + (outp)[1] = 0xbf; \ + (outp)[2] = 0xbd; \ +} while (0) +#define UTF8_R_CHAR_SIZE 3 + +static struct archive_string_conv *find_sconv_object(struct archive *, + const char *, const char *); +static void add_sconv_object(struct archive *, struct archive_string_conv *); +static struct archive_string_conv *create_sconv_object(const char *, + const char *, unsigned, int); +static void free_sconv_object(struct archive_string_conv *); +static struct archive_string_conv *get_sconv_object(struct archive *, + const char *, const char *, int); +static unsigned make_codepage_from_charset(const char *); +static unsigned get_current_codepage(void); +static unsigned get_current_oemcp(void); +static size_t mbsnbytes(const void *, size_t); +static size_t utf16nbytes(const void *, size_t); +#if defined(_WIN32) && !defined(__CYGWIN__) +static int archive_wstring_append_from_mbs_in_codepage( + struct archive_wstring *, const char *, size_t, + struct archive_string_conv *); +static int archive_string_append_from_wcs_in_codepage(struct archive_string *, + const wchar_t *, size_t, struct archive_string_conv *); +static int is_big_endian(void); +static int strncat_in_codepage(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int win_strncat_from_utf16be(struct archive_string *, const void *, size_t, + struct archive_string_conv *); +static int win_strncat_from_utf16le(struct archive_string *, const void *, size_t, + struct archive_string_conv *); +static int win_strncat_to_utf16be(struct archive_string *, const void *, size_t, + struct archive_string_conv *); +static int win_strncat_to_utf16le(struct archive_string *, const void *, size_t, + struct archive_string_conv *); +#endif +static int best_effort_strncat_from_utf16be(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int best_effort_strncat_from_utf16le(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int best_effort_strncat_to_utf16be(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int best_effort_strncat_to_utf16le(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +#if defined(HAVE_ICONV) +static int iconv_strncat_in_locale(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +#endif +static int best_effort_strncat_in_locale(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int _utf8_to_unicode(uint32_t *, const char *, size_t); +static int utf8_to_unicode(uint32_t *, const char *, size_t); +static inline uint32_t combine_surrogate_pair(uint32_t, uint32_t); +static int cesu8_to_unicode(uint32_t *, const char *, size_t); +static size_t unicode_to_utf8(char *, size_t, uint32_t); +static int utf16_to_unicode(uint32_t *, const char *, size_t, int); +static size_t unicode_to_utf16be(char *, size_t, uint32_t); +static size_t unicode_to_utf16le(char *, size_t, uint32_t); +static int strncat_from_utf8_libarchive2(struct archive_string *, + const void *, size_t, struct archive_string_conv *); +static int strncat_from_utf8_to_utf8(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +static int archive_string_normalize_C(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +#if defined(__APPLE__) +static int archive_string_normalize_D(struct archive_string *, const void *, + size_t, struct archive_string_conv *); +#endif +static int archive_string_append_unicode(struct archive_string *, + const void *, size_t, struct archive_string_conv *); + +static struct archive_string * +archive_string_append(struct archive_string *as, const char *p, size_t s) +{ + if (archive_string_ensure(as, as->length + s + 1) == NULL) + __archive_errx(1, "Out of memory"); + memcpy(as->s + as->length, p, s); + as->length += s; + as->s[as->length] = 0; + return (as); +} + +static struct archive_wstring * +archive_wstring_append(struct archive_wstring *as, const wchar_t *p, size_t s) +{ + if (archive_wstring_ensure(as, as->length + s + 1) == NULL) + __archive_errx(1, "Out of memory"); + wmemcpy(as->s + as->length, p, s); + as->length += s; + as->s[as->length] = 0; + return (as); +} + +void +archive_string_concat(struct archive_string *dest, struct archive_string *src) +{ + archive_string_append(dest, src->s, src->length); +} + +void +archive_wstring_concat(struct archive_wstring *dest, struct archive_wstring *src) +{ + archive_wstring_append(dest, src->s, src->length); +} + +void +archive_string_free(struct archive_string *as) +{ + as->length = 0; + as->buffer_length = 0; + free(as->s); + as->s = NULL; +} + +void +archive_wstring_free(struct archive_wstring *as) +{ + as->length = 0; + as->buffer_length = 0; + free(as->s); + as->s = NULL; +} + +struct archive_wstring * +archive_wstring_ensure(struct archive_wstring *as, size_t s) +{ + return (struct archive_wstring *) + archive_string_ensure((struct archive_string *)as, + s * sizeof(wchar_t)); +} + +/* Returns NULL on any allocation failure. */ +struct archive_string * +archive_string_ensure(struct archive_string *as, size_t s) +{ + char *p; + size_t new_length; + + /* If buffer is already big enough, don't reallocate. */ + if (as->s && (s <= as->buffer_length)) + return (as); + + /* + * Growing the buffer at least exponentially ensures that + * append operations are always linear in the number of + * characters appended. Using a smaller growth rate for + * larger buffers reduces memory waste somewhat at the cost of + * a larger constant factor. + */ + if (as->buffer_length < 32) + /* Start with a minimum 32-character buffer. */ + new_length = 32; + else if (as->buffer_length < 8192) + /* Buffers under 8k are doubled for speed. */ + new_length = as->buffer_length + as->buffer_length; + else { + /* Buffers 8k and over grow by at least 25% each time. */ + new_length = as->buffer_length + as->buffer_length / 4; + /* Be safe: If size wraps, fail. */ + if (new_length < as->buffer_length) { + /* On failure, wipe the string and return NULL. */ + archive_string_free(as); + errno = ENOMEM;/* Make sure errno has ENOMEM. */ + return (NULL); + } + } + /* + * The computation above is a lower limit to how much we'll + * grow the buffer. In any case, we have to grow it enough to + * hold the request. + */ + if (new_length < s) + new_length = s; + /* Now we can reallocate the buffer. */ + p = (char *)realloc(as->s, new_length); + if (p == NULL) { + /* On failure, wipe the string and return NULL. */ + archive_string_free(as); + errno = ENOMEM;/* Make sure errno has ENOMEM. */ + return (NULL); + } + + as->s = p; + as->buffer_length = new_length; + return (as); +} + +/* + * TODO: See if there's a way to avoid scanning + * the source string twice. Then test to see + * if it actually helps (remember that we're almost + * always called with pretty short arguments, so + * such an optimization might not help). + */ +struct archive_string * +archive_strncat(struct archive_string *as, const void *_p, size_t n) +{ + size_t s; + const char *p, *pp; + + p = (const char *)_p; + + /* Like strlen(p), except won't examine positions beyond p[n]. */ + s = 0; + pp = p; + while (s < n && *pp) { + pp++; + s++; + } + return (archive_string_append(as, p, s)); +} + +struct archive_wstring * +archive_wstrncat(struct archive_wstring *as, const wchar_t *p, size_t n) +{ + size_t s; + const wchar_t *pp; + + /* Like strlen(p), except won't examine positions beyond p[n]. */ + s = 0; + pp = p; + while (s < n && *pp) { + pp++; + s++; + } + return (archive_wstring_append(as, p, s)); +} + +struct archive_string * +archive_strcat(struct archive_string *as, const void *p) +{ + /* strcat is just strncat without an effective limit. + * Assert that we'll never get called with a source + * string over 16MB. + * TODO: Review all uses of strcat in the source + * and try to replace them with strncat(). + */ + return archive_strncat(as, p, 0x1000000); +} + +struct archive_wstring * +archive_wstrcat(struct archive_wstring *as, const wchar_t *p) +{ + /* Ditto. */ + return archive_wstrncat(as, p, 0x1000000); +} + +struct archive_string * +archive_strappend_char(struct archive_string *as, char c) +{ + return (archive_string_append(as, &c, 1)); +} + +struct archive_wstring * +archive_wstrappend_wchar(struct archive_wstring *as, wchar_t c) +{ + return (archive_wstring_append(as, &c, 1)); +} + +/* + * Get the "current character set" name to use with iconv. + * On FreeBSD, the empty character set name "" chooses + * the correct character encoding for the current locale, + * so this isn't necessary. + * But iconv on Mac OS 10.6 doesn't seem to handle this correctly; + * on that system, we have to explicitly call nl_langinfo() + * to get the right name. Not sure about other platforms. + * + * NOTE: GNU libiconv does not recognize the character-set name + * which some platform nl_langinfo(CODESET) returns, so we should + * use locale_charset() instead of nl_langinfo(CODESET) for GNU libiconv. + */ +static const char * +default_iconv_charset(const char *charset) { + if (charset != NULL && charset[0] != '\0') + return charset; +#if HAVE_LOCALE_CHARSET && !defined(__APPLE__) + /* locale_charset() is broken on Mac OS */ + return locale_charset(); +#elif HAVE_NL_LANGINFO + return nl_langinfo(CODESET); +#else + return ""; +#endif +} + +#if defined(_WIN32) && !defined(__CYGWIN__) + +/* + * Convert MBS to WCS. + * Note: returns -1 if conversion fails. + */ +int +archive_wstring_append_from_mbs(struct archive_wstring *dest, + const char *p, size_t len) +{ + int r = archive_wstring_append_from_mbs_in_codepage(dest, p, len, NULL); + if (r != 0 && errno == ENOMEM) + __archive_errx(1, "No memory"); + return (r); +} + +static int +archive_wstring_append_from_mbs_in_codepage(struct archive_wstring *dest, + const char *s, size_t length, struct archive_string_conv *sc) +{ + int count, ret = 0; + UINT from_cp; + + if (sc != NULL) + from_cp = sc->from_cp; + else + from_cp = get_current_codepage(); + + if (from_cp == CP_C_LOCALE) { + /* + * "C" locale special process. + */ + wchar_t *ws; + const unsigned char *mp; + + if (NULL == archive_wstring_ensure(dest, + dest->length + length + 1)) + return (-1); + + ws = dest->s + dest->length; + mp = (const unsigned char *)s; + count = 0; + while (count < (int)length && *mp) { + *ws++ = (wchar_t)*mp++; + count++; + } + } else if (sc != NULL && (sc->flag & SCONV_NORMALIZATION_C)) { + /* + * Normalize UTF-8 and UTF-16BE and convert it directly + * to UTF-16 as wchar_t. + */ + struct archive_string u16; + int saved_flag = sc->flag;/* save current flag. */ + + if (is_big_endian()) + sc->flag |= SCONV_TO_UTF16BE; + else + sc->flag |= SCONV_TO_UTF16LE; + + if (sc->flag & SCONV_FROM_UTF16) { + /* + * UTF-16BE/LE NFD ===> UTF-16 NFC + */ + count = utf16nbytes(s, length); + } else { + /* + * UTF-8 NFD ===> UTF-16 NFC + */ + count = mbsnbytes(s, length); + } + u16.s = (char *)dest->s; + u16.length = dest->length << 1;; + u16.buffer_length = dest->buffer_length; + ret = archive_string_normalize_C(&u16, s, count, sc); + dest->s = (wchar_t *)u16.s; + dest->length = u16.length >> 1; + dest->buffer_length = u16.buffer_length; + sc->flag = saved_flag;/* restore the saved flag. */ + return (ret); + } else if (sc != NULL && (sc->flag & SCONV_FROM_UTF16)) { + count = utf16nbytes(s, length); + count >>= 1; /* to be WCS length */ + /* Allocate memory for WCS. */ + if (NULL == archive_wstring_ensure(dest, + dest->length + count + 1)) + return (-1); + wmemcpy(dest->s + dest->length, (wchar_t *)s, count); + if ((sc->flag & SCONV_FROM_UTF16BE) && !is_big_endian()) { + uint16_t *u16 = (uint16_t *)(dest->s + dest->length); + int b; + for (b = 0; b < count; b++) { + uint16_t val = archive_le16dec(u16+b); + archive_be16enc(u16+b, val); + } + } else if ((sc->flag & SCONV_FROM_UTF16LE) && is_big_endian()) { + uint16_t *u16 = (uint16_t *)(dest->s + dest->length); + int b; + for (b = 0; b < count; b++) { + uint16_t val = archive_be16dec(u16+b); + archive_le16enc(u16+b, val); + } + } + } else { + DWORD mbflag; + + if (sc == NULL) + mbflag = 0; + else if (sc->flag & SCONV_FROM_CHARSET) { + /* Do not trust the length which comes from + * an archive file. */ + length = mbsnbytes(s, length); + mbflag = 0; + } else + mbflag = MB_PRECOMPOSED; + + if (length == 0) { + /* + * We do not need to convert any characters but make + * sure `dest' has a valid buffer(no NULL pointer). + */ + if (NULL == archive_wstring_ensure(dest, + dest->length + 1)) + return (-1); + dest->s[dest->length] = L'\0'; + return (0); + } + + /* + * Count how many bytes are needed for WCS. + */ + count = MultiByteToWideChar(from_cp, + mbflag, s, length, NULL, 0); + if (count == 0) { + if (dest->s == NULL) { + if (NULL == archive_wstring_ensure(dest, + dest->length + 1)) + return (-1); + } + dest->s[dest->length] = L'\0'; + return (-1); + } + /* Allocate memory for WCS. */ + if (NULL == archive_wstring_ensure(dest, + dest->length + count + 1)) + return (-1); + /* Convert MBS to WCS. */ + count = MultiByteToWideChar(from_cp, + mbflag, s, length, dest->s + dest->length, count); + if (count == 0) + ret = -1; + } + dest->length += count; + dest->s[dest->length] = L'\0'; + return (ret); +} + +#elif defined(HAVE_MBSNRTOWCS) + +/* + * Convert MBS to WCS. + * Note: returns -1 if conversion fails. + */ +int +archive_wstring_append_from_mbs(struct archive_wstring *dest, + const char *p, size_t len) +{ + size_t r; + /* + * No single byte will be more than one wide character, + * so this length estimate will always be big enough. + */ + size_t wcs_length = len; + size_t mbs_length = len; + const char *mbs = p; + wchar_t *wcs; + mbstate_t shift_state; + + memset(&shift_state, 0, sizeof(shift_state)); + if (NULL == archive_wstring_ensure(dest, dest->length + wcs_length + 1)) + __archive_errx(1, + "No memory for archive_wstring_append_from_mbs()"); + wcs = dest->s + dest->length; + r = mbsnrtowcs(wcs, &mbs, mbs_length, wcs_length, &shift_state); + if (r != (size_t)-1) { + dest->length += r; + dest->s[dest->length] = L'\0'; + return (0); + } + dest->s[dest->length] = L'\0'; + return (-1); +} + +#else + +/* + * Convert MBS to WCS. + * Note: returns -1 if conversion fails. + */ +int +archive_wstring_append_from_mbs(struct archive_wstring *dest, + const char *p, size_t len) +{ + size_t r; + /* + * No single byte will be more than one wide character, + * so this length estimate will always be big enough. + */ + size_t wcs_length = len; + size_t mbs_length = len; + const char *mbs = p; + wchar_t *wcs; +#if HAVE_MBRTOWC + mbstate_t shift_state; + + memset(&shift_state, 0, sizeof(shift_state)); +#endif + if (NULL == archive_wstring_ensure(dest, dest->length + wcs_length + 1)) + __archive_errx(1, + "No memory for archive_wstring_append_from_mbs()"); + wcs = dest->s + dest->length; + /* + * We cannot use mbsrtowcs/mbstowcs here because those may convert + * extra MBS when strlen(p) > len and one wide character consis of + * multi bytes. + */ + while (wcs_length > 0 && *mbs && mbs_length > 0) { +#if HAVE_MBRTOWC + r = mbrtowc(wcs, mbs, wcs_length, &shift_state); +#else + r = mbtowc(wcs, mbs, wcs_length); +#endif + if (r == (size_t)-1 || r == (size_t)-2) { + dest->s[dest->length] = L'\0'; + return (-1); + } + if (r == 0 || r > mbs_length) + break; + wcs++; + wcs_length--; + mbs += r; + mbs_length -= r; + } + dest->length = wcs - dest->s; + dest->s[dest->length] = L'\0'; + return (0); +} + +#endif + +#if defined(_WIN32) && !defined(__CYGWIN__) + +/* + * WCS ==> MBS. + * Note: returns -1 if conversion fails. + * + * Win32 builds use WideCharToMultiByte from the Windows API. + * (Maybe Cygwin should too? WideCharToMultiByte will know a + * lot more about local character encodings than the wcrtomb() + * wrapper is going to know.) + */ +int +archive_string_append_from_wcs(struct archive_string *as, + const wchar_t *w, size_t len) +{ + int r = archive_string_append_from_wcs_in_codepage(as, w, len, NULL); + if (r != 0 && errno == ENOMEM) + __archive_errx(1, "No memory"); + return (r); +} + +static int +archive_string_append_from_wcs_in_codepage(struct archive_string *as, + const wchar_t *ws, size_t len, struct archive_string_conv *sc) +{ + BOOL defchar_used, *dp; + int count, ret = 0; + UINT to_cp; + int wslen = (int)len; + + if (sc != NULL) + to_cp = sc->to_cp; + else + to_cp = get_current_codepage(); + + if (to_cp == CP_C_LOCALE) { + /* + * "C" locale special process. + */ + const wchar_t *wp = ws; + char *p; + + if (NULL == archive_string_ensure(as, + as->length + wslen +1)) + return (-1); + p = as->s + as->length; + count = 0; + defchar_used = 0; + while (count < wslen && *wp) { + if (*wp > 255) { + *p++ = '?'; + wp++; + defchar_used = 1; + } else + *p++ = (char)*wp++; + count++; + } + } else if (sc != NULL && (sc->flag & SCONV_TO_UTF16)) { + uint16_t *u16; + + if (NULL == + archive_string_ensure(as, as->length + len * 2 + 2)) + return (-1); + u16 = (uint16_t *)(as->s + as->length); + count = 0; + defchar_used = 0; + if (sc->flag & SCONV_TO_UTF16BE) { + while (count < (int)len && *ws) { + archive_be16enc(u16+count, *ws); + ws++; + count++; + } + } else { + while (count < (int)len && *ws) { + archive_le16enc(u16+count, *ws); + ws++; + count++; + } + } + count <<= 1; /* to be byte size */ + } else { + /* Make sure the MBS buffer has plenty to set. */ + if (NULL == + archive_string_ensure(as, as->length + len * 2 + 1)) + return (-1); + do { + defchar_used = 0; + if (to_cp == CP_UTF8 || sc == NULL) + dp = NULL; + else + dp = &defchar_used; + count = WideCharToMultiByte(to_cp, 0, ws, wslen, + as->s + as->length, as->buffer_length-1, NULL, dp); + if (count == 0 && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + /* Expand the MBS buffer and retry. */ + if (NULL == archive_string_ensure(as, + as->buffer_length + len)) + return (-1); + continue; + } + if (count == 0) + ret = -1; + } while (0); + } + as->length += count; + as->s[as->length] = '\0'; + return (defchar_used?-1:ret); +} + +#elif defined(HAVE_WCSNRTOMBS) + +/* + * Translates a wide character string into current locale character set + * and appends to the archive_string. Note: returns -1 if conversion + * fails. + */ +int +archive_string_append_from_wcs(struct archive_string *as, + const wchar_t *w, size_t len) +{ + mbstate_t shift_state; + size_t r, ndest, nwc; + char *dest; + const wchar_t *wp, *wpp; + int ret_val = 0; + + wp = w; + nwc = len; + ndest = len * 2; + /* Initialize the shift state. */ + memset(&shift_state, 0, sizeof(shift_state)); + while (nwc > 0) { + /* Allocate buffer for MBS. */ + if (archive_string_ensure(as, as->length + ndest + 1) == NULL) + __archive_errx(1, "Out of memory"); + + dest = as->s + as->length; + wpp = wp; + r = wcsnrtombs(dest, &wp, nwc, + as->buffer_length - as->length -1, + &shift_state); + if (r == (size_t)-1) { + if (errno == EILSEQ) { + /* Retry conversion just for safe WCS. */ + size_t xwc = wp - wpp; + wp = wpp; + r = wcsnrtombs(dest, &wp, xwc, + as->buffer_length - as->length -1, + &shift_state); + if (r == (size_t)-1) + /* This would not happen. */ + return (-1); + as->length += r; + nwc -= wp - wpp; + /* Skip an illegal wide char. */ + as->s[as->length++] = '?'; + wp++; + nwc--; + ret_val = -1; + continue; + } else { + ret_val = -1; + break; + } + } + as->length += r; + if (wp == NULL || (wp - wpp) >= nwc) + break; + /* Get a remaining WCS lenth. */ + nwc -= wp - wpp; + } + /* All wide characters are translated to MBS. */ + as->s[as->length] = '\0'; + return (ret_val); +} + +#elif defined(HAVE_WCTOMB) || defined(HAVE_WCRTOMB) + +/* + * Translates a wide character string into current locale character set + * and appends to the archive_string. Note: returns -1 if conversion + * fails. + */ +int +archive_string_append_from_wcs(struct archive_string *as, + const wchar_t *w, size_t len) +{ + /* We cannot use the standard wcstombs() here because it + * cannot tell us how big the output buffer should be. So + * I've built a loop around wcrtomb() or wctomb() that + * converts a character at a time and resizes the string as + * needed. We prefer wcrtomb() when it's available because + * it's thread-safe. */ + int n, ret_val = 0; + char *p; + char *end; +#if HAVE_WCRTOMB + mbstate_t shift_state; + + memset(&shift_state, 0, sizeof(shift_state)); +#else + /* Clear the shift state before starting. */ + wctomb(NULL, L'\0'); +#endif + /* + * Allocate buffer for MBS. + * We need this allocation here since it is possible that + * as->s is still NULL. + */ + if (archive_string_ensure(as, as->length + len + 1) == NULL) + __archive_errx(1, "Out of memory"); + + p = as->s + as->length; + end = as->s + as->buffer_length - MB_CUR_MAX -1; + while (*w != L'\0' && len > 0) { + if (p >= end) { + as->length = p - as->s; + as->s[as->length] = '\0'; + /* Re-allocate buffer for MBS. */ + if (archive_string_ensure(as, + as->length + len * 2 + 1) == NULL) + __archive_errx(1, "Out of memory"); + p = as->s + as->length; + end = as->s + as->buffer_length - MB_CUR_MAX -1; + } +#if HAVE_WCRTOMB + n = wcrtomb(p, *w++, &shift_state); +#else + n = wctomb(p, *w++); +#endif + if (n == -1) { + if (errno == EILSEQ) { + /* Skip an illegal wide char. */ + *p++ = '?'; + ret_val = -1; + } else { + ret_val = -1; + break; + } + } else + p += n; + len--; + } + as->length = p - as->s; + as->s[as->length] = '\0'; + return (ret_val); +} + +#else /* HAVE_WCTOMB || HAVE_WCRTOMB */ + +/* + * TODO: Test if __STDC_ISO_10646__ is defined. + * Non-Windows uses ISO C wcrtomb() or wctomb() to perform the conversion + * one character at a time. If a non-Windows platform doesn't have + * either of these, fall back to the built-in UTF8 conversion. + */ +int +archive_string_append_from_wcs(struct archive_string *as, + const wchar_t *w, size_t len) +{ + (void)as;/* UNUSED */ + (void)w;/* UNUSED */ + (void)len;/* UNUSED */ + return (-1); +} + +#endif /* HAVE_WCTOMB || HAVE_WCRTOMB */ + +/* + * Find a string conversion object by a pair of 'from' charset name + * and 'to' charset name from an archive object. + * Return NULL if not found. + */ +static struct archive_string_conv * +find_sconv_object(struct archive *a, const char *fc, const char *tc) +{ + struct archive_string_conv *sc; + + if (a == NULL) + return (NULL); + + for (sc = a->sconv; sc != NULL; sc = sc->next) { + if (strcmp(sc->from_charset, fc) == 0 && + strcmp(sc->to_charset, tc) == 0) + break; + } + return (sc); +} + +/* + * Register a string object to an archive object. + */ +static void +add_sconv_object(struct archive *a, struct archive_string_conv *sc) +{ + struct archive_string_conv **psc; + + /* Add a new sconv to sconv list. */ + psc = &(a->sconv); + while (*psc != NULL) + psc = &((*psc)->next); + *psc = sc; +} + +#if defined(__APPLE__) + +static int +createUniInfo(struct archive_string_conv *sconv) +{ + UnicodeMapping map; + OSStatus err; + + map.unicodeEncoding = CreateTextEncoding(kTextEncodingUnicodeDefault, + kUnicodeNoSubset, kUnicode16BitFormat); + map.otherEncoding = CreateTextEncoding(kTextEncodingUnicodeDefault, + kUnicodeHFSPlusDecompVariant, kUnicode16BitFormat); + map.mappingVersion = kUnicodeUseLatestMapping; + + sconv->uniInfo = NULL; + err = CreateUnicodeToTextInfo(&map, &(sconv->uniInfo)); + return ((err == noErr)? 0: -1); +} + +#endif /* __APPLE__ */ + +static void +add_converter(struct archive_string_conv *sc, int (*converter) + (struct archive_string *, const void *, size_t, + struct archive_string_conv *)) +{ + if (sc == NULL || sc->nconverter >= 2) + __archive_errx(1, "Programing error"); + sc->converter[sc->nconverter++] = converter; +} + +static void +setup_converter(struct archive_string_conv *sc) +{ + + /* Reset. */ + sc->nconverter = 0; + + /* + * Perform special sequence for the incorrect UTF-8 filenames + * made by libarchive2.x. + */ + if (sc->flag & SCONV_UTF8_LIBARCHIVE_2) { + add_converter(sc, strncat_from_utf8_libarchive2); + return; + } + + /* + * Convert a string to UTF-16BE/LE. + */ + if (sc->flag & SCONV_TO_UTF16) { + /* + * If the current locale is UTF-8, we can translate + * a UTF-8 string into a UTF-16BE string. + */ + if (sc->flag & SCONV_FROM_UTF8) { + add_converter(sc, archive_string_append_unicode); + return; + } + +#if defined(_WIN32) && !defined(__CYGWIN__) + if (sc->flag & SCONV_WIN_CP) { + if (sc->flag & SCONV_TO_UTF16BE) + add_converter(sc, win_strncat_to_utf16be); + else + add_converter(sc, win_strncat_to_utf16le); + return; + } +#endif + +#if defined(HAVE_ICONV) + if (sc->cd != (iconv_t)-1) { + add_converter(sc, iconv_strncat_in_locale); + return; + } +#endif + + if (sc->flag & SCONV_BEST_EFFORT) { + if (sc->flag & SCONV_TO_UTF16BE) + add_converter(sc, best_effort_strncat_to_utf16be); + else + add_converter(sc, best_effort_strncat_to_utf16le); + } else + /* Make sure we have no converter. */ + sc->nconverter = 0; + return; + } + + /* + * Convert a string from UTF-16BE/LE. + */ + if (sc->flag & SCONV_FROM_UTF16) { + /* + * At least we should normalize a UTF-16BE string. + */ +#if defined(__APPLE__) + if (sc->flag & SCONV_NORMALIZATION_D) + add_converter(sc,archive_string_normalize_D); + else +#endif + if (sc->flag & SCONV_NORMALIZATION_C) + add_converter(sc, archive_string_normalize_C); + + if (sc->flag & SCONV_TO_UTF8) { + /* + * If the current locale is UTF-8, we can translate + * a UTF-16BE/LE string into a UTF-8 string directly. + */ + if (!(sc->flag & + (SCONV_NORMALIZATION_D |SCONV_NORMALIZATION_C))) + add_converter(sc, + archive_string_append_unicode); + return; + } + +#if defined(_WIN32) && !defined(__CYGWIN__) + if (sc->flag & SCONV_WIN_CP) { + if (sc->flag & SCONV_FROM_UTF16BE) + add_converter(sc, win_strncat_from_utf16be); + else + add_converter(sc, win_strncat_from_utf16le); + return; + } +#endif + +#if defined(HAVE_ICONV) + if (sc->cd != (iconv_t)-1) { + add_converter(sc, iconv_strncat_in_locale); + return; + } +#endif + + if ((sc->flag & (SCONV_BEST_EFFORT | SCONV_FROM_UTF16BE)) + == (SCONV_BEST_EFFORT | SCONV_FROM_UTF16BE)) + add_converter(sc, best_effort_strncat_from_utf16be); + else if ((sc->flag & (SCONV_BEST_EFFORT | SCONV_FROM_UTF16LE)) + == (SCONV_BEST_EFFORT | SCONV_FROM_UTF16LE)) + add_converter(sc, best_effort_strncat_from_utf16le); + else + /* Make sure we have no converter. */ + sc->nconverter = 0; + return; + } + + if (sc->flag & SCONV_FROM_UTF8) { + /* + * At least we should normalize a UTF-8 string. + */ +#if defined(__APPLE__) + if (sc->flag & SCONV_NORMALIZATION_D) + add_converter(sc,archive_string_normalize_D); + else +#endif + if (sc->flag & SCONV_NORMALIZATION_C) + add_converter(sc, archive_string_normalize_C); + + /* + * Copy UTF-8 string with a check of CESU-8. + * Apparently, iconv does not check surrogate pairs in UTF-8 + * when both from-charset and to-charset are UTF-8, and then + * we use our UTF-8 copy code. + */ + if (sc->flag & SCONV_TO_UTF8) { + /* + * If the current locale is UTF-8, we can translate + * a UTF-16BE string into a UTF-8 string directly. + */ + if (!(sc->flag & + (SCONV_NORMALIZATION_D |SCONV_NORMALIZATION_C))) + add_converter(sc, strncat_from_utf8_to_utf8); + return; + } + } + +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * On Windows we can use Windows API for a string conversion. + */ + if (sc->flag & SCONV_WIN_CP) { + add_converter(sc, strncat_in_codepage); + return; + } +#endif + +#if HAVE_ICONV + if (sc->cd != (iconv_t)-1) { + add_converter(sc, iconv_strncat_in_locale); + return; + } +#endif + + /* + * Try conversion in the best effort or no conversion. + */ + if ((sc->flag & SCONV_BEST_EFFORT) || sc->same) + add_converter(sc, best_effort_strncat_in_locale); + else + /* Make sure we have no converter. */ + sc->nconverter = 0; +} + +/* + * Return canonicalized charset-name but this supports just UTF-8, UTF-16BE + * and CP932 which are referenced in create_sconv_object(). + */ +static const char * +canonical_charset_name(const char *charset) +{ + char cs[16]; + char *p; + const char *s; + + if (charset == NULL || charset[0] == '\0' + || strlen(charset) > 15) + return (charset); + + /* Copy name to uppercase. */ + p = cs; + s = charset; + while (*s) { + char c = *s++; + if (c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + *p++ = c; + } + *p++ = '\0'; + + if (strcmp(cs, "UTF-8") == 0 || + strcmp(cs, "UTF8") == 0) + return ("UTF-8"); + if (strcmp(cs, "UTF-16BE") == 0 || + strcmp(cs, "UTF16BE") == 0) + return ("UTF-16BE"); + if (strcmp(cs, "UTF-16LE") == 0 || + strcmp(cs, "UTF16LE") == 0) + return ("UTF-16LE"); + if (strcmp(cs, "CP932") == 0) + return ("CP932"); + return (charset); +} + +/* + * Create a string conversion object. + */ +static struct archive_string_conv * +create_sconv_object(const char *fc, const char *tc, + unsigned current_codepage, int flag) +{ + struct archive_string_conv *sc; + + sc = calloc(1, sizeof(*sc)); + if (sc == NULL) + return (NULL); + sc->next = NULL; + sc->from_charset = strdup(fc); + if (sc->from_charset == NULL) { + free(sc); + return (NULL); + } + sc->to_charset = strdup(tc); + if (sc->to_charset == NULL) { + free(sc); + free(sc->from_charset); + return (NULL); + } + archive_string_init(&sc->utftmp); +#if defined(__APPLE__) + archive_string_init(&sc->utf16nfc); + archive_string_init(&sc->utf16nfd); +#endif + + if (flag & SCONV_TO_CHARSET) { + /* + * Convert characters from the current locale charset to + * a specified charset. + */ + sc->from_cp = current_codepage; + sc->to_cp = make_codepage_from_charset(tc); +#if defined(_WIN32) && !defined(__CYGWIN__) + if (IsValidCodePage(sc->to_cp)) + flag |= SCONV_WIN_CP; +#endif + } else if (flag & SCONV_FROM_CHARSET) { + /* + * Convert characters from a specified charset to + * the current locale charset. + */ + sc->to_cp = current_codepage; + sc->from_cp = make_codepage_from_charset(fc); +#if defined(_WIN32) && !defined(__CYGWIN__) + if (IsValidCodePage(sc->from_cp)) + flag |= SCONV_WIN_CP; +#endif + } + + /* + * Check if "from charset" and "to charset" are the same. + */ + if (strcmp(fc, tc) == 0 || + (sc->from_cp != -1 && sc->from_cp == sc->to_cp)) + sc->same = 1; + else + sc->same = 0; + + /* + * Mark if "from charset" or "to charset" are UTF-8 or UTF-16BE/LE. + */ + if (strcmp(tc, "UTF-8") == 0) + flag |= SCONV_TO_UTF8; + else if (strcmp(tc, "UTF-16BE") == 0) + flag |= SCONV_TO_UTF16BE; + else if (strcmp(tc, "UTF-16LE") == 0) + flag |= SCONV_TO_UTF16LE; + if (strcmp(fc, "UTF-8") == 0) + flag |= SCONV_FROM_UTF8; + else if (strcmp(fc, "UTF-16BE") == 0) + flag |= SCONV_FROM_UTF16BE; + else if (strcmp(fc, "UTF-16LE") == 0) + flag |= SCONV_FROM_UTF16LE; +#if defined(_WIN32) && !defined(__CYGWIN__) + if (sc->to_cp == CP_UTF8) + flag |= SCONV_TO_UTF8; + else if (sc->to_cp == CP_UTF16BE) + flag |= SCONV_TO_UTF16BE | SCONV_WIN_CP; + else if (sc->to_cp == CP_UTF16LE) + flag |= SCONV_TO_UTF16LE | SCONV_WIN_CP; + if (sc->from_cp == CP_UTF8) + flag |= SCONV_FROM_UTF8; + else if (sc->from_cp == CP_UTF16BE) + flag |= SCONV_FROM_UTF16BE | SCONV_WIN_CP; + else if (sc->from_cp == CP_UTF16LE) + flag |= SCONV_FROM_UTF16LE | SCONV_WIN_CP; +#endif + + /* + * Set a flag for Unicode NFD. Usually iconv cannot correctly + * handle it. So we have to translate NFD characters to NFC ones + * ourselves before iconv handles. Another reason is to prevent + * that the same sight of two filenames, one is NFC and other + * is NFD, would be in its directory. + * On Mac OS X, although its filesystem layer automatically + * convert filenames to NFD, it would be useful for filename + * comparing to find out the same filenames that we normalize + * that to be NFD ourselves. + */ + if ((flag & SCONV_FROM_CHARSET) && + (flag & (SCONV_FROM_UTF16 | SCONV_FROM_UTF8))) { +#if defined(__APPLE__) + if (flag & SCONV_TO_UTF8) { + if (createUniInfo(sc) == 0) + flag |= SCONV_NORMALIZATION_D; + } else +#endif + flag |= SCONV_NORMALIZATION_C; + } + +#if defined(HAVE_ICONV) + sc->cd_w = (iconv_t)-1; + /* + * Create an iconv object. + */ + if (((flag & (SCONV_TO_UTF8 | SCONV_TO_UTF16)) && + (flag & (SCONV_FROM_UTF8 | SCONV_FROM_UTF16))) || + (flag & SCONV_WIN_CP)) { + /* This case we won't use iconv. */ + sc->cd = (iconv_t)-1; +#if defined(__APPLE__) + } else if ((flag & SCONV_FROM_CHARSET) && (flag & SCONV_TO_UTF8)) { + /* + * In case reading an archive file. + * Translate non-Unicode filenames in an archive file to + * UTF-8-MAC filenames. + */ + sc->cd = iconv_open("UTF-8-MAC", fc); + if (sc->cd == (iconv_t)-1) { + if ((sc->flag & SCONV_BEST_EFFORT) && + strcmp(fc, "CP932") == 0) { + sc->cd = iconv_open("UTF-8-MAC", "SJIS"); + if (sc->cd == (iconv_t)-1) { + sc->cd = iconv_open(tc, fc); + if (sc->cd == (iconv_t)-1) + sc->cd = iconv_open(tc, "SJIS"); + } + } else + sc->cd = iconv_open(tc, fc); + } + } else if ((flag & SCONV_TO_CHARSET) && (flag & SCONV_FROM_UTF8)) { + /* + * In case writing an archive file. + * Translate UTF-8-MAC filenames in HFS Plus to non-Unicode + * filenames. + */ + sc->cd = iconv_open(tc, "UTF-8-MAC"); + if (sc->cd == (iconv_t)-1) { + if ((sc->flag & SCONV_BEST_EFFORT) && + strcmp(tc, "CP932") == 0) { + sc->cd = iconv_open("SJIS", "UTF-8-MAC"); + if (sc->cd == (iconv_t)-1) { + sc->cd = iconv_open(tc, fc); + if (sc->cd == (iconv_t)-1) + sc->cd = iconv_open("SJIS", fc); + } + } else + sc->cd = iconv_open(tc, fc); + } +#endif + } else { + sc->cd = iconv_open(tc, fc); + if (sc->cd == (iconv_t)-1 && (sc->flag & SCONV_BEST_EFFORT)) { + /* + * Unfortunaly, all of iconv implements do support + * "CP932" character-set, so we should use "SJIS" + * instead if iconv_open failed. + */ + if (strcmp(tc, "CP932") == 0) + sc->cd = iconv_open("SJIS", fc); + else if (strcmp(fc, "CP932") == 0) + sc->cd = iconv_open(tc, "SJIS"); + } +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * archive_mstring on Windows directly convert multi-bytes + * into archive_wstring in order not to depend on locale + * so that you can do a I18N programing. This will be + * used only in archive_mstring_copy_mbs_len_l so far. + */ + if (flag & SCONV_FROM_CHARSET) { + sc->cd_w = iconv_open("UTF-8", fc); + if (sc->cd_w == (iconv_t)-1 && + (sc->flag & SCONV_BEST_EFFORT)) { + if (strcmp(fc, "CP932") == 0) + sc->cd_w = iconv_open("UTF-8", "SJIS"); + } + } +#endif /* _WIN32 && !__CYGWIN__ */ + } +#endif /* HAVE_ICONV */ + + sc->flag = flag; + + /* + * Setup converters. + */ + setup_converter(sc); + + return (sc); +} + +/* + * Free a string conversion object. + */ +static void +free_sconv_object(struct archive_string_conv *sc) +{ + free(sc->from_charset); + free(sc->to_charset); + archive_string_free(&sc->utftmp); +#if HAVE_ICONV + if (sc->cd != (iconv_t)-1) + iconv_close(sc->cd); + if (sc->cd_w != (iconv_t)-1) + iconv_close(sc->cd_w); +#endif +#if defined(__APPLE__) + archive_string_free(&sc->utf16nfc); + archive_string_free(&sc->utf16nfd); + if (sc->uniInfo != NULL) + DisposeUnicodeToTextInfo(&(sc->uniInfo)); +#endif + free(sc); +} + +#if defined(_WIN32) && !defined(__CYGWIN__) +static unsigned +my_atoi(const char *p) +{ + unsigned cp; + + cp = 0; + while (*p) { + if (*p >= '0' && *p <= '9') + cp = cp * 10 + (*p - '0'); + else + return (-1); + p++; + } + return (cp); +} + +/* + * Translate Charset name (as used by iconv) into CodePage (as used by Windows) + * Return -1 if failed. + * + * Note: This translation code may be insufficient. + */ +static struct charset { + const char *name; + unsigned cp; +} charsets[] = { + /* MUST BE SORTED! */ + {"ASCII", 1252}, + {"ASMO-708", 708}, + {"BIG5", 950}, + {"CHINESE", 936}, + {"CP367", 1252}, + {"CP819", 1252}, + {"CP1025", 21025}, + {"DOS-720", 720}, + {"DOS-862", 862}, + {"EUC-CN", 51936}, + {"EUC-JP", 51932}, + {"EUC-KR", 949}, + {"EUCCN", 51936}, + {"EUCJP", 51932}, + {"EUCKR", 949}, + {"GB18030", 54936}, + {"GB2312", 936}, + {"HEBREW", 1255}, + {"HZ-GB-2312", 52936}, + {"IBM273", 20273}, + {"IBM277", 20277}, + {"IBM278", 20278}, + {"IBM280", 20280}, + {"IBM284", 20284}, + {"IBM285", 20285}, + {"IBM290", 20290}, + {"IBM297", 20297}, + {"IBM367", 1252}, + {"IBM420", 20420}, + {"IBM423", 20423}, + {"IBM424", 20424}, + {"IBM819", 1252}, + {"IBM871", 20871}, + {"IBM880", 20880}, + {"IBM905", 20905}, + {"IBM924", 20924}, + {"ISO-8859-1", 28591}, + {"ISO-8859-13", 28603}, + {"ISO-8859-15", 28605}, + {"ISO-8859-2", 28592}, + {"ISO-8859-3", 28593}, + {"ISO-8859-4", 28594}, + {"ISO-8859-5", 28595}, + {"ISO-8859-6", 28596}, + {"ISO-8859-7", 28597}, + {"ISO-8859-8", 28598}, + {"ISO-8859-9", 28599}, + {"ISO8859-1", 28591}, + {"ISO8859-13", 28603}, + {"ISO8859-15", 28605}, + {"ISO8859-2", 28592}, + {"ISO8859-3", 28593}, + {"ISO8859-4", 28594}, + {"ISO8859-5", 28595}, + {"ISO8859-6", 28596}, + {"ISO8859-7", 28597}, + {"ISO8859-8", 28598}, + {"ISO8859-9", 28599}, + {"JOHAB", 1361}, + {"KOI8-R", 20866}, + {"KOI8-U", 21866}, + {"KS_C_5601-1987", 949}, + {"LATIN1", 1252}, + {"LATIN2", 28592}, + {"MACINTOSH", 10000}, + {"SHIFT-JIS", 932}, + {"SHIFT_JIS", 932}, + {"SJIS", 932}, + {"US", 1252}, + {"US-ASCII", 1252}, + {"UTF-16", 1200}, + {"UTF-16BE", 1201}, + {"UTF-16LE", 1200}, + {"UTF-8", CP_UTF8}, + {"X-EUROPA", 29001}, + {"X-MAC-ARABIC", 10004}, + {"X-MAC-CE", 10029}, + {"X-MAC-CHINESEIMP", 10008}, + {"X-MAC-CHINESETRAD", 10002}, + {"X-MAC-CROATIAN", 10082}, + {"X-MAC-CYRILLIC", 10007}, + {"X-MAC-GREEK", 10006}, + {"X-MAC-HEBREW", 10005}, + {"X-MAC-ICELANDIC", 10079}, + {"X-MAC-JAPANESE", 10001}, + {"X-MAC-KOREAN", 10003}, + {"X-MAC-ROMANIAN", 10010}, + {"X-MAC-THAI", 10021}, + {"X-MAC-TURKISH", 10081}, + {"X-MAC-UKRAINIAN", 10017}, +}; +static unsigned +make_codepage_from_charset(const char *charset) +{ + char cs[16]; + char *p; + unsigned cp; + int a, b; + + if (charset == NULL || strlen(charset) > 15) + return -1; + + /* Copy name to uppercase. */ + p = cs; + while (*charset) { + char c = *charset++; + if (c >= 'a' && c <= 'z') + c -= 'a' - 'A'; + *p++ = c; + } + *p++ = '\0'; + cp = -1; + + /* Look it up in the table first, so that we can easily + * override CP367, which we map to 1252 instead of 367. */ + a = 0; + b = sizeof(charsets)/sizeof(charsets[0]); + while (b > a) { + int c = (b + a) / 2; + int r = strcmp(charsets[c].name, cs); + if (r < 0) + a = c + 1; + else if (r > 0) + b = c; + else + return charsets[c].cp; + } + + /* If it's not in the table, try to parse it. */ + switch (*cs) { + case 'C': + if (cs[1] == 'P' && cs[2] >= '0' && cs[2] <= '9') { + cp = my_atoi(cs + 2); + } else if (strcmp(cs, "CP_ACP") == 0) + cp = get_current_codepage(); + else if (strcmp(cs, "CP_OEMCP") == 0) + cp = get_current_oemcp(); + break; + case 'I': + if (cs[1] == 'B' && cs[2] == 'M' && + cs[3] >= '0' && cs[3] <= '9') { + cp = my_atoi(cs + 3); + } + break; + case 'W': + if (strncmp(cs, "WINDOWS-", 8) == 0) { + cp = my_atoi(cs + 8); + if (cp != 874 && (cp < 1250 || cp > 1258)) + cp = -1;/* This may invalid code. */ + } + break; + } + return (cp); +} + +/* + * Return ANSI Code Page of current locale set by setlocale(). + */ +static unsigned +get_current_codepage() +{ + char *locale, *p; + unsigned cp; + + locale = setlocale(LC_CTYPE, NULL); + if (locale == NULL) + return (GetACP()); + if (locale[0] == 'C' && locale[1] == '\0') + return (CP_C_LOCALE); + p = strrchr(locale, '.'); + if (p == NULL) + return (GetACP()); + cp = my_atoi(p+1); + if (cp <= 0) + return (GetACP()); + return (cp); +} + +/* + * Translation table between Locale Name and ACP/OEMCP. + */ +static struct { + unsigned acp; + unsigned ocp; + const char *locale; +} acp_ocp_map[] = { + { 950, 950, "Chinese_Taiwan" }, + { 936, 936, "Chinese_People's Republic of China" }, + { 950, 950, "Chinese_Taiwan" }, + { 1250, 852, "Czech_Czech Republic" }, + { 1252, 850, "Danish_Denmark" }, + { 1252, 850, "Dutch_Netherlands" }, + { 1252, 850, "Dutch_Belgium" }, + { 1252, 437, "English_United States" }, + { 1252, 850, "English_Australia" }, + { 1252, 850, "English_Canada" }, + { 1252, 850, "English_New Zealand" }, + { 1252, 850, "English_United Kingdom" }, + { 1252, 437, "English_United States" }, + { 1252, 850, "Finnish_Finland" }, + { 1252, 850, "French_France" }, + { 1252, 850, "French_Belgium" }, + { 1252, 850, "French_Canada" }, + { 1252, 850, "French_Switzerland" }, + { 1252, 850, "German_Germany" }, + { 1252, 850, "German_Austria" }, + { 1252, 850, "German_Switzerland" }, + { 1253, 737, "Greek_Greece" }, + { 1250, 852, "Hungarian_Hungary" }, + { 1252, 850, "Icelandic_Iceland" }, + { 1252, 850, "Italian_Italy" }, + { 1252, 850, "Italian_Switzerland" }, + { 932, 932, "Japanese_Japan" }, + { 949, 949, "Korean_Korea" }, + { 1252, 850, "Norwegian (BokmOl)_Norway" }, + { 1252, 850, "Norwegian (BokmOl)_Norway" }, + { 1252, 850, "Norwegian-Nynorsk_Norway" }, + { 1250, 852, "Polish_Poland" }, + { 1252, 850, "Portuguese_Portugal" }, + { 1252, 850, "Portuguese_Brazil" }, + { 1251, 866, "Russian_Russia" }, + { 1250, 852, "Slovak_Slovakia" }, + { 1252, 850, "Spanish_Spain" }, + { 1252, 850, "Spanish_Mexico" }, + { 1252, 850, "Spanish_Spain" }, + { 1252, 850, "Swedish_Sweden" }, + { 1254, 857, "Turkish_Turkey" }, + { 0, 0, NULL} +}; + +/* + * Return OEM Code Page of current locale set by setlocale(). + */ +static unsigned +get_current_oemcp() +{ + int i; + char *locale, *p; + size_t len; + + locale = setlocale(LC_CTYPE, NULL); + if (locale == NULL) + return (GetOEMCP()); + if (locale[0] == 'C' && locale[1] == '\0') + return (CP_C_LOCALE); + + p = strrchr(locale, '.'); + if (p == NULL) + return (GetOEMCP()); + len = p - locale; + for (i = 0; acp_ocp_map[i].acp; i++) { + if (strncmp(acp_ocp_map[i].locale, locale, len) == 0) + return (acp_ocp_map[i].ocp); + } + return (GetOEMCP()); +} +#else + +/* + * POSIX platform does not use CodePage. + */ + +static unsigned +get_current_codepage() +{ + return (-1);/* Unknown */ +} +static unsigned +make_codepage_from_charset(const char *charset) +{ + (void)charset; /* UNUSED */ + return (-1);/* Unknown */ +} +static unsigned +get_current_oemcp() +{ + return (-1);/* Unknown */ +} + +#endif /* defined(_WIN32) && !defined(__CYGWIN__) */ + +/* + * Return a string conversion object. + */ +static struct archive_string_conv * +get_sconv_object(struct archive *a, const char *fc, const char *tc, int flag) +{ + struct archive_string_conv *sc; + unsigned current_codepage; + + /* Check if we have made the sconv object. */ + sc = find_sconv_object(a, fc, tc); + if (sc != NULL) + return (sc); + + if (a == NULL) + current_codepage = get_current_codepage(); + else + current_codepage = a->current_codepage; + + sc = create_sconv_object(canonical_charset_name(fc), + canonical_charset_name(tc), current_codepage, flag); + if (sc == NULL) { + if (a != NULL) + archive_set_error(a, ENOMEM, + "Could not allocate memory for " + "a string conversion object"); + return (NULL); + } + + /* + * If there is no converter for current string conversion object, + * we cannot handle this conversion. + */ + if (sc->nconverter == 0) { + if (a != NULL) { +#if HAVE_ICONV + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "iconv_open failed : Cannot handle ``%s''", + (flag & SCONV_TO_CHARSET)?tc:fc); +#else + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "A character-set conversion not fully supported " + "on this platform"); +#endif + } + /* Failed; free a sconv object. */ + free_sconv_object(sc); + return (NULL); + } + + /* + * Success! + */ + if (a != NULL) + add_sconv_object(a, sc); + return (sc); +} + +static const char * +get_current_charset(struct archive *a) +{ + const char *cur_charset; + + if (a == NULL) + cur_charset = default_iconv_charset(""); + else { + cur_charset = default_iconv_charset(a->current_code); + if (a->current_code == NULL) { + a->current_code = strdup(cur_charset); + a->current_codepage = get_current_codepage(); + a->current_oemcp = get_current_oemcp(); + } + } + return (cur_charset); +} + +/* + * Make and Return a string conversion object. + * Return NULL if the platform does not support the specified conversion + * and best_effort is 0. + * If best_effort is set, A string conversion object must be returned + * unless memory allocation for the object fails, but the conversion + * might fail when non-ASCII code is found. + */ +struct archive_string_conv * +archive_string_conversion_to_charset(struct archive *a, const char *charset, + int best_effort) +{ + int flag = SCONV_TO_CHARSET; + + if (best_effort) + flag |= SCONV_BEST_EFFORT; + return (get_sconv_object(a, get_current_charset(a), charset, flag)); +} + +struct archive_string_conv * +archive_string_conversion_from_charset(struct archive *a, const char *charset, + int best_effort) +{ + int flag = SCONV_FROM_CHARSET; + + if (best_effort) + flag |= SCONV_BEST_EFFORT; + return (get_sconv_object(a, charset, get_current_charset(a), flag)); +} + +/* + * archive_string_default_conversion_*_archive() are provided for Windows + * platform because other archiver application use CP_OEMCP for + * MultiByteToWideChar() and WideCharToMultiByte() for the filenames + * in tar or zip files. But mbstowcs/wcstombs(CRT) usually use CP_ACP + * unless you use setlocale(LC_ALL, ".OCP")(specify CP_OEMCP). + * So we should make a string conversion between CP_ACP and CP_OEMCP + * for compatibillty. + */ +#if defined(_WIN32) && !defined(__CYGWIN__) +struct archive_string_conv * +archive_string_default_conversion_for_read(struct archive *a) +{ + const char *cur_charset = get_current_charset(a); + char oemcp[16]; + + /* NOTE: a check of cur_charset is unneeded but we need + * that get_current_charset() has been surely called at + * this time whatever C compiler optimized. */ + if (cur_charset != NULL && + (a->current_codepage == CP_C_LOCALE || + a->current_codepage == a->current_oemcp)) + return (NULL);/* no conversion. */ + + _snprintf(oemcp, sizeof(oemcp)-1, "CP%d", a->current_oemcp); + /* Make sure a null termination must be set. */ + oemcp[sizeof(oemcp)-1] = '\0'; + return (get_sconv_object(a, oemcp, cur_charset, + SCONV_FROM_CHARSET)); +} + +struct archive_string_conv * +archive_string_default_conversion_for_write(struct archive *a) +{ + const char *cur_charset = get_current_charset(a); + char oemcp[16]; + + /* NOTE: a check of cur_charset is unneeded but we need + * that get_current_charset() has been surely called at + * this time whatever C compiler optimized. */ + if (cur_charset != NULL && + (a->current_codepage == CP_C_LOCALE || + a->current_codepage == a->current_oemcp)) + return (NULL);/* no conversion. */ + + _snprintf(oemcp, sizeof(oemcp)-1, "CP%d", a->current_oemcp); + /* Make sure a null termination must be set. */ + oemcp[sizeof(oemcp)-1] = '\0'; + return (get_sconv_object(a, cur_charset, oemcp, + SCONV_TO_CHARSET)); +} +#else +struct archive_string_conv * +archive_string_default_conversion_for_read(struct archive *a) +{ + (void)a; /* UNUSED */ + return (NULL); +} + +struct archive_string_conv * +archive_string_default_conversion_for_write(struct archive *a) +{ + (void)a; /* UNUSED */ + return (NULL); +} +#endif + +/* + * Dispose of all character conversion objects in the archive object. + */ +void +archive_string_conversion_free(struct archive *a) +{ + struct archive_string_conv *sc; + struct archive_string_conv *sc_next; + + for (sc = a->sconv; sc != NULL; sc = sc_next) { + sc_next = sc->next; + free_sconv_object(sc); + } + a->sconv = NULL; + free(a->current_code); + a->current_code = NULL; +} + +/* + * Return a conversion charset name. + */ +const char * +archive_string_conversion_charset_name(struct archive_string_conv *sc) +{ + if (sc->flag & SCONV_TO_CHARSET) + return (sc->to_charset); + else + return (sc->from_charset); +} + +/* + * Change the behavior of a string conversion. + */ +void +archive_string_conversion_set_opt(struct archive_string_conv *sc, int opt) +{ + switch (opt) { + /* + * A filename in UTF-8 was made with libarchive 2.x in a wrong + * assumption that wchar_t was Unicode. + * This option enables simulating the assumption in order to read + * that filname correctly. + */ + case SCONV_SET_OPT_UTF8_LIBARCHIVE2X: +#if (defined(_WIN32) && !defined(__CYGWIN__)) \ + || defined(__STDC_ISO_10646__) || defined(__APPLE__) + /* + * Nothing to do for it since wchar_t on these platforms + * is really Unicode. + */ + (void)sc; /* UNUSED */ +#else + if ((sc->flag & SCONV_UTF8_LIBARCHIVE_2) == 0) { + sc->flag |= SCONV_UTF8_LIBARCHIVE_2; + /* Re-setup string converters. */ + setup_converter(sc); + } +#endif + break; + default: + break; + } +} + +/* + * + * Copy one archive_string to another in locale conversion. + * + * archive_strncpy_in_locale(); + * archive_strcpy_in_locale(); + * + */ + +static size_t +mbsnbytes(const void *_p, size_t n) +{ + size_t s; + const char *p, *pp; + + if (_p == NULL) + return (0); + p = (const char *)_p; + + /* Like strlen(p), except won't examine positions beyond p[n]. */ + s = 0; + pp = p; + while (s < n && *pp) { + pp++; + s++; + } + return (s); +} + +static size_t +utf16nbytes(const void *_p, size_t n) +{ + size_t s; + const char *p, *pp; + + if (_p == NULL) + return (0); + p = (const char *)_p; + + /* Like strlen(p), except won't examine positions beyond p[n]. */ + s = 0; + pp = p; + n >>= 1; + while (s < n && (pp[0] || pp[1])) { + pp += 2; + s++; + } + return (s<<1); +} + +int +archive_strncpy_in_locale(struct archive_string *as, const void *_p, size_t n, + struct archive_string_conv *sc) +{ + as->length = 0; + return (archive_strncat_in_locale(as, _p, n, sc)); +} + +int +archive_strncat_in_locale(struct archive_string *as, const void *_p, size_t n, + struct archive_string_conv *sc) +{ + const void *s; + size_t length; + int i, r = 0, r2; + + /* We must allocate memory even if there is no data for conversion + * or copy. This simulates archive_string_append behavior. */ + if (_p == NULL || n == 0) { + int tn = 1; + if (sc != NULL && (sc->flag & SCONV_TO_UTF16)) + tn = 2; + if (archive_string_ensure(as, as->length + tn) == NULL) + return (-1); + as->s[as->length] = 0; + if (tn == 2) + as->s[as->length+1] = 0; + return (0); + } + + /* + * If sc is NULL, we just make a copy. + */ + if (sc == NULL) { + length = mbsnbytes(_p, n); + /* + * archive_string_append() will call archive_string_ensure() + * but we cannot know if that call is failed or not. so + * we call archive_string_ensure() here. + */ + if (archive_string_ensure(as, as->length + length + 1) == NULL) + return (-1); + archive_string_append(as, _p, length); + return (0); + } + + if (sc->flag & SCONV_FROM_UTF16) + length = utf16nbytes(_p, n); + else + length = mbsnbytes(_p, n); + s = _p; + i = 0; + if (sc->nconverter > 1) { + sc->utftmp.length = 0; + r2 = sc->converter[0](&(sc->utftmp), s, length, sc); + if (r2 != 0 && errno == ENOMEM) + return (r2); + if (r > r2) + r = r2; + s = sc->utftmp.s; + length = sc->utftmp.length; + ++i; + } + r2 = sc->converter[i](as, s, length, sc); + if (r > r2) + r = r2; + return (r); +} + +#if HAVE_ICONV + +/* + * Return -1 if conversion failes. + */ +static int +iconv_strncat_in_locale(struct archive_string *as, const void *_p, + size_t length, struct archive_string_conv *sc) +{ + ICONV_CONST char *inp; + size_t remaining; + iconv_t cd; + char *outp; + size_t avail, bs; + int return_value = 0; /* success */ + int to_size, from_size; + + if (sc->flag & SCONV_TO_UTF16) + to_size = 2; + else + to_size = 1; + if (sc->flag & SCONV_FROM_UTF16) + from_size = 2; + else + from_size = 1; + + if (archive_string_ensure(as, as->length + length*2+to_size) == NULL) + return (-1); + + cd = sc->cd; + inp = (char *)(uintptr_t)_p; + remaining = length; + outp = as->s + as->length; + avail = as->buffer_length - as->length - to_size; + while (remaining >= (size_t)from_size) { + size_t result = iconv(cd, &inp, &remaining, &outp, &avail); + + if (result != (size_t)-1) + break; /* Conversion completed. */ + + if (errno == EILSEQ || errno == EINVAL) { + /* + * If an output charset is UTF-8 or UTF-16BE/LE, + * unknown character should be U+FFFD + * (replacement character). + */ + if (sc->flag & (SCONV_TO_UTF8 | SCONV_TO_UTF16)) { + size_t rbytes; + if (sc->flag & SCONV_TO_UTF8) + rbytes = UTF8_R_CHAR_SIZE; + else + rbytes = 2; + + if (avail < rbytes) { + as->length = outp - as->s; + bs = as->buffer_length + + (remaining * to_size) + rbytes; + if (NULL == + archive_string_ensure(as, bs)) + return (-1); + outp = as->s + as->length; + avail = as->buffer_length + - as->length - to_size; + } + if (sc->flag & SCONV_TO_UTF8) + UTF8_SET_R_CHAR(outp); + else if (sc->flag & SCONV_TO_UTF16BE) + archive_be16enc(outp, UNICODE_R_CHAR); + else + archive_le16enc(outp, UNICODE_R_CHAR); + outp += rbytes; + avail -= rbytes; + } else { + /* Skip the illegal input bytes. */ + *outp++ = '?'; + avail--; + } + inp += from_size; + remaining -= from_size; + return_value = -1; /* failure */ + } else { + /* E2BIG no output buffer, + * Increase an output buffer. */ + as->length = outp - as->s; + bs = as->buffer_length + remaining * 2; + if (NULL == archive_string_ensure(as, bs)) + return (-1); + outp = as->s + as->length; + avail = as->buffer_length - as->length - to_size; + } + } + as->length = outp - as->s; + as->s[as->length] = 0; + if (to_size == 2) + as->s[as->length+1] = 0; + return (return_value); +} + +#endif /* HAVE_ICONV */ + + +#if defined(_WIN32) && !defined(__CYGWIN__) + +/* + * Translate a string from a some CodePage to an another CodePage by + * Windows APIs, and copy the result. Return -1 if conversion failes. + */ +static int +strncat_in_codepage(struct archive_string *as, + const void *_p, size_t length, struct archive_string_conv *sc) +{ + const char *s = (const char *)_p; + struct archive_wstring aws; + size_t l; + int r, saved_flag; + + archive_string_init(&aws); + saved_flag = sc->flag; + sc->flag &= ~(SCONV_NORMALIZATION_D | SCONV_NORMALIZATION_C); + r = archive_wstring_append_from_mbs_in_codepage(&aws, s, length, sc); + sc->flag = saved_flag; + if (r != 0) { + archive_wstring_free(&aws); + if (errno != ENOMEM) + archive_string_append(as, s, length); + return (-1); + } + + l = as->length; + r = archive_string_append_from_wcs_in_codepage( + as, aws.s, aws.length, sc); + if (r != 0 && errno != ENOMEM && l == as->length) + archive_string_append(as, s, length); + archive_wstring_free(&aws); + return (r); +} + +/* + * Test whether MBS ==> WCS is okay. + */ +static int +invalid_mbs(const void *_p, size_t n, struct archive_string_conv *sc) +{ + const char *p = (const char *)_p; + unsigned codepage; + DWORD mbflag = MB_ERR_INVALID_CHARS; + + if (sc->flag & SCONV_FROM_CHARSET) + codepage = sc->to_cp; + else + codepage = sc->from_cp; + + if (codepage == CP_C_LOCALE) + return (0); + if (codepage != CP_UTF8) + mbflag |= MB_PRECOMPOSED; + + if (MultiByteToWideChar(codepage, mbflag, p, n, NULL, 0) == 0) + return (-1); /* Invalid */ + return (0); /* Okay */ +} + +#else + +/* + * Test whether MBS ==> WCS is okay. + */ +static int +invalid_mbs(const void *_p, size_t n, struct archive_string_conv *sc) +{ + const char *p = (const char *)_p; + size_t r; + + (void)sc; /* UNUSED */ +#if HAVE_MBRTOWC + mbstate_t shift_state; + + memset(&shift_state, 0, sizeof(shift_state)); +#else + /* Clear the shift state before starting. */ + mbtowc(NULL, NULL, 0); +#endif + while (n) { + wchar_t wc; + +#if HAVE_MBRTOWC + r = mbrtowc(&wc, p, n, &shift_state); +#else + r = mbtowc(&wc, p, n); +#endif + if (r == (size_t)-1 || r == (size_t)-2) + return (-1);/* Invalid. */ + if (r == 0) + break; + p += r; + n -= r; + } + return (0); /* All Okey. */ +} + +#endif /* defined(_WIN32) && !defined(__CYGWIN__) */ + +/* + * Basically returns -1 because we cannot make a conversion of charset + * without iconv but in some cases this would return 0. + * Returns 0 if all copied characters are ASCII. + * Returns 0 if both from-locale and to-locale are the same and those + * can be WCS with no error. + */ +static int +best_effort_strncat_in_locale(struct archive_string *as, const void *_p, + size_t length, struct archive_string_conv *sc) +{ + size_t remaining; + char *outp; + const char *inp; + size_t avail; + int return_value = 0; /* success */ + + /* + * If both from-locale and to-locale is the same, this makes a copy. + * And then this checks all copied MBS can be WCS if so returns 0. + */ + if (sc->same) { + archive_string_append(as, _p, length); + return (invalid_mbs(_p, length, sc)); + } + + /* + * If a character is ASCII, this just copies it. If not, this + * assigns '?' charater instead but in UTF-8 locale this assigns + * byte sequence 0xEF 0xBD 0xBD, which are code point U+FFFD, + * a Replacement Character in Unicode. + */ + if (archive_string_ensure(as, as->length + length + 1) == NULL) + return (-1); + + remaining = length; + inp = (const char *)_p; + outp = as->s + as->length; + avail = as->buffer_length - as->length -1; + while (*inp && remaining > 0) { + if (*inp < 0 && (sc->flag & SCONV_TO_UTF8)) { + if (avail < UTF8_R_CHAR_SIZE) { + as->length = outp - as->s; + if (NULL == archive_string_ensure(as, + as->buffer_length + remaining + + UTF8_R_CHAR_SIZE)) + return (-1); + outp = as->s + as->length; + avail = as->buffer_length - as->length -1; + } + /* + * When coping a string in UTF-8, unknown character + * should be U+FFFD (replacement character). + */ + UTF8_SET_R_CHAR(outp); + outp += UTF8_R_CHAR_SIZE; + avail -= UTF8_R_CHAR_SIZE; + inp++; + remaining--; + return_value = -1; + } else if (*inp < 0) { + *outp++ = '?'; + inp++; + remaining--; + return_value = -1; + } else { + *outp++ = *inp++; + remaining--; + } + } + as->length = outp - as->s; + as->s[as->length] = '\0'; + return (return_value); +} + + +/* + * Unicode conversion functions. + * - UTF-8 <===> UTF-8 in removing surrogate pairs. + * - UTF-8 NFD ===> UTF-8 NFC in removing surrogate pairs. + * - UTF-8 made by libarchive 2.x ===> UTF-8. + * - UTF-16BE <===> UTF-8. + * + */ + +/* + * Utility to convert a single UTF-8 sequence. + * + * Usually return used bytes, return used byte in negative value when + * a unicode character is replaced with U+FFFD. + * See also http://unicode.org/review/pr-121.html Public Review Issue #121 + * Recommended Practice for Replacement Characters. + */ +static int +_utf8_to_unicode(uint32_t *pwc, const char *s, size_t n) +{ + static const char utf8_count[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 00 - 0F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 10 - 1F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 20 - 2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 30 - 3F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40 - 4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 50 - 5F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 60 - 6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 70 - 7F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 80 - 8F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 90 - 9F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* A0 - AF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* B0 - BF */ + 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,/* C0 - CF */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,/* D0 - DF */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,/* E0 - EF */ + 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* F0 - FF */ + }; + int ch, i; + int cnt; + uint32_t wc; + + /* Sanity check. */ + if (n == 0) + return (0); + /* + * Decode 1-4 bytes depending on the value of the first byte. + */ + ch = (unsigned char)*s; + if (ch == 0) + return (0); /* Standard: return 0 for end-of-string. */ + cnt = utf8_count[ch]; + + /* Invalide sequence or there are not plenty bytes. */ + if ((int)n < cnt) { + cnt = n; + for (i = 1; i < cnt; i++) { + if ((s[i] & 0xc0) != 0x80) { + cnt = i; + break; + } + } + goto invalid_sequence; + } + + /* Make a Unicode code point from a single UTF-8 sequence. */ + switch (cnt) { + case 1: /* 1 byte sequence. */ + *pwc = ch & 0x7f; + return (cnt); + case 2: /* 2 bytes sequence. */ + if ((s[1] & 0xc0) != 0x80) { + cnt = 1; + goto invalid_sequence; + } + *pwc = ((ch & 0x1f) << 6) | (s[1] & 0x3f); + return (cnt); + case 3: /* 3 bytes sequence. */ + if ((s[1] & 0xc0) != 0x80) { + cnt = 1; + goto invalid_sequence; + } + if ((s[2] & 0xc0) != 0x80) { + cnt = 2; + goto invalid_sequence; + } + wc = ((ch & 0x0f) << 12) + | ((s[1] & 0x3f) << 6) + | (s[2] & 0x3f); + if (wc < 0x800) + goto invalid_sequence;/* Overlong sequence. */ + break; + case 4: /* 4 bytes sequence. */ + if ((s[1] & 0xc0) != 0x80) { + cnt = 1; + goto invalid_sequence; + } + if ((s[2] & 0xc0) != 0x80) { + cnt = 2; + goto invalid_sequence; + } + if ((s[3] & 0xc0) != 0x80) { + cnt = 3; + goto invalid_sequence; + } + wc = ((ch & 0x07) << 18) + | ((s[1] & 0x3f) << 12) + | ((s[2] & 0x3f) << 6) + | (s[3] & 0x3f); + if (wc < 0x10000) + goto invalid_sequence;/* Overlong sequence. */ + break; + default: /* Others are all invalid sequence. */ + if (ch == 0xc0 || ch == 0xc1) + cnt = 2; + else if (ch >= 0xf5 && ch <= 0xf7) + cnt = 4; + else if (ch >= 0xf8 && ch <= 0xfb) + cnt = 5; + else if (ch == 0xfc || ch == 0xfd) + cnt = 6; + else + cnt = 1; + if ((int)n < cnt) + cnt = n; + for (i = 1; i < cnt; i++) { + if ((s[i] & 0xc0) != 0x80) { + cnt = i; + break; + } + } + goto invalid_sequence; + } + + /* The code point larger than 0x10FFFF is not leagal + * Unicode values. */ + if (wc > UNICODE_MAX) + goto invalid_sequence; + /* Correctly gets a Unicode, returns used bytes. */ + *pwc = wc; + return (cnt); +invalid_sequence: + *pwc = UNICODE_R_CHAR;/* set the Replacement Character instead. */ + return (cnt * -1); +} + +static int +utf8_to_unicode(uint32_t *pwc, const char *s, size_t n) +{ + int cnt; + + cnt = _utf8_to_unicode(pwc, s, n); + /* Any of Surrogate pair is not leagal Unicode values. */ + if (cnt == 3 && IS_SURROGATE_PAIR_LA(*pwc)) + return (-3); + return (cnt); +} + +static inline uint32_t +combine_surrogate_pair(uint32_t uc, uint32_t uc2) +{ + uc -= 0xD800; + uc *= 0x400; + uc += uc2 - 0xDC00; + uc += 0x10000; + return (uc); +} + +/* + * Convert a single UTF-8/CESU-8 sequence to a Unicode code point in + * removing surrogate pairs. + * + * CESU-8: The Compatibility Encoding Scheme for UTF-16. + * + * Usually return used bytes, return used byte in negative value when + * a unicode character is replaced with U+FFFD. + */ +static int +cesu8_to_unicode(uint32_t *pwc, const char *s, size_t n) +{ + uint32_t wc, wc2; + int cnt; + + cnt = _utf8_to_unicode(&wc, s, n); + if (cnt == 3 && IS_HIGH_SURROGATE_LA(wc)) { + if (n - 3 < 3) { + /* Invalid byte sequence. */ + goto invalid_sequence; + } + cnt = _utf8_to_unicode(&wc2, s+3, n-3); + if (cnt != 3 || !IS_LOW_SURROGATE_LA(wc2)) { + /* Invalid byte sequence. */ + goto invalid_sequence; + } + wc = combine_surrogate_pair(wc, wc2); + cnt = 6; + } else if (cnt == 3 && IS_LOW_SURROGATE_LA(wc)) { + /* Invalid byte sequence. */ + goto invalid_sequence; + } + *pwc = wc; + return (cnt); +invalid_sequence: + *pwc = UNICODE_R_CHAR;/* set the Replacement Character instead. */ + if (cnt > 0) + cnt *= -1; + return (cnt); +} + +/* + * Convert a Unicode code point to a single UTF-8 sequence. + * + * NOTE:This function does not check if the Unicode is leagal or not. + * Please you definitely check it before calling this. + */ +static size_t +unicode_to_utf8(char *p, size_t remaining, uint32_t uc) +{ + char *_p = p; + + /* Translate code point to UTF8 */ + if (uc <= 0x7f) { + if (remaining == 0) + return (0); + *p++ = (char)uc; + } else if (uc <= 0x7ff) { + if (remaining < 2) + return (0); + *p++ = 0xc0 | ((uc >> 6) & 0x1f); + *p++ = 0x80 | (uc & 0x3f); + } else if (uc <= 0xffff) { + if (remaining < 3) + return (0); + *p++ = 0xe0 | ((uc >> 12) & 0x0f); + *p++ = 0x80 | ((uc >> 6) & 0x3f); + *p++ = 0x80 | (uc & 0x3f); + } else if (uc <= UNICODE_MAX) { + if (remaining < 4) + return (0); + *p++ = 0xf0 | ((uc >> 18) & 0x07); + *p++ = 0x80 | ((uc >> 12) & 0x3f); + *p++ = 0x80 | ((uc >> 6) & 0x3f); + *p++ = 0x80 | (uc & 0x3f); + } else { + /* + * Undescribed code point should be U+FFFD + * (replacement character). + */ + if (remaining < UTF8_R_CHAR_SIZE) + return (0); + UTF8_SET_R_CHAR(p); + p += UTF8_R_CHAR_SIZE; + } + return (p - _p); +} + +static int +utf16be_to_unicode(uint32_t *pwc, const char *s, size_t n) +{ + return (utf16_to_unicode(pwc, s, n, 1)); +} + +static int +utf16le_to_unicode(uint32_t *pwc, const char *s, size_t n) +{ + return (utf16_to_unicode(pwc, s, n, 0)); +} + +static int +utf16_to_unicode(uint32_t *pwc, const char *s, size_t n, int be) +{ + const char *utf16 = s; + unsigned uc; + + if (n == 0) + return (0); + if (n == 1) { + /* set the Replacement Character instead. */ + *pwc = UNICODE_R_CHAR; + return (-1); + } + + if (be) + uc = archive_be16dec(utf16); + else + uc = archive_le16dec(utf16); + utf16 += 2; + + /* If this is a surrogate pair, assemble the full code point.*/ + if (IS_HIGH_SURROGATE_LA(uc)) { + unsigned uc2; + + if (n >= 4) { + if (be) + uc2 = archive_be16dec(utf16); + else + uc2 = archive_le16dec(utf16); + } else + uc2 = 0; + if (IS_LOW_SURROGATE_LA(uc2)) { + uc = combine_surrogate_pair(uc, uc2); + utf16 += 2; + } else { + /* Undescribed code point should be U+FFFD + * (replacement character). */ + *pwc = UNICODE_R_CHAR; + return (-2); + } + } + + /* + * Surrogate pair values(0xd800 through 0xdfff) are only + * used by UTF-16, so, after above culculation, the code + * must not be surrogate values, and Unicode has no codes + * larger than 0x10ffff. Thus, those are not leagal Unicode + * values. + */ + if (IS_SURROGATE_PAIR_LA(uc) || uc > UNICODE_MAX) { + /* Undescribed code point should be U+FFFD + * (replacement character). */ + *pwc = UNICODE_R_CHAR; + return (((int)(utf16 - s)) * -1); + } + *pwc = uc; + return ((int)(utf16 - s)); +} + +static size_t +unicode_to_utf16be(char *p, size_t remaining, uint32_t uc) +{ + char *utf16 = p; + + if (uc > 0xffff) { + /* We have a code point that won't fit into a + * wchar_t; convert it to a surrogate pair. */ + if (remaining < 4) + return (0); + uc -= 0x10000; + archive_be16enc(utf16, ((uc >> 10) & 0x3ff) + 0xD800); + archive_be16enc(utf16+2, (uc & 0x3ff) + 0xDC00); + return (4); + } else { + if (remaining < 2) + return (0); + archive_be16enc(utf16, uc); + return (2); + } +} + +static size_t +unicode_to_utf16le(char *p, size_t remaining, uint32_t uc) +{ + char *utf16 = p; + + if (uc > 0xffff) { + /* We have a code point that won't fit into a + * wchar_t; convert it to a surrogate pair. */ + if (remaining < 4) + return (0); + uc -= 0x10000; + archive_le16enc(utf16, ((uc >> 10) & 0x3ff) + 0xD800); + archive_le16enc(utf16+2, (uc & 0x3ff) + 0xDC00); + return (4); + } else { + if (remaining < 2) + return (0); + archive_le16enc(utf16, uc); + return (2); + } +} + +/* + * Copy UTF-8 string in checking surrogate pair. + * If any surrogate pair are found, it would be canonicalized. + */ +static int +strncat_from_utf8_to_utf8(struct archive_string *as, const void *_p, size_t len, + struct archive_string_conv *sc) +{ + const char *s; + char *p, *endp; + int n, ret = 0; + + (void)sc; /* UNUSED */ + + if (archive_string_ensure(as, as->length + len + 1) == NULL) + return (-1); + + s = (const char *)_p; + p = as->s + as->length; + endp = as->s + as->buffer_length -1; + do { + uint32_t uc; + const char *ss = s; + size_t w; + + /* + * Forward byte sequence until a conversion of that is needed. + */ + while ((n = utf8_to_unicode(&uc, s, len)) > 0) { + s += n; + len -= n; + } + if (ss < s) { + if (p + (s - ss) > endp) { + as->length = p - as->s; + if (archive_string_ensure(as, + as->buffer_length + len + 1) == NULL) + return (-1); + p = as->s + as->length; + endp = as->s + as->buffer_length -1; + } + + memcpy(p, ss, s - ss); + p += s - ss; + } + + /* + * If n is negative, current byte sequence needs a replacement. + */ + if (n < 0) { + if (n == -3 && IS_SURROGATE_PAIR_LA(uc)) { + /* Current byte sequence may be CESU-8. */ + n = cesu8_to_unicode(&uc, s, len); + } + if (n < 0) { + ret = -1; + n *= -1;/* Use a replaced unicode character. */ + } + + /* Rebuild UTF-8 byte sequence. */ + while ((w = unicode_to_utf8(p, endp - p, uc)) == 0) { + as->length = p - as->s; + if (archive_string_ensure(as, + as->buffer_length + len + 1) == NULL) + return (-1); + p = as->s + as->length; + endp = as->s + as->buffer_length -1; + } + p += w; + s += n; + len -= n; + } + } while (n > 0); + as->length = p - as->s; + as->s[as->length] = '\0'; + return (ret); +} + +static int +archive_string_append_unicode(struct archive_string *as, const void *_p, + size_t len, struct archive_string_conv *sc) +{ + const char *s; + char *p, *endp; + uint32_t uc; + size_t w; + int n, ret = 0, ts, tm; + int (*parse)(uint32_t *, const char *, size_t); + size_t (*unparse)(char *, size_t, uint32_t); + + if (sc->flag & SCONV_TO_UTF16BE) { + unparse = unicode_to_utf16be; + ts = 2; + } else if (sc->flag & SCONV_TO_UTF16LE) { + unparse = unicode_to_utf16le; + ts = 2; + } else if (sc->flag & SCONV_TO_UTF8) { + unparse = unicode_to_utf8; + ts = 1; + } else { + /* + * This case is going to be converted to another + * character-set through iconv. + */ + if (sc->flag & SCONV_FROM_UTF16BE) { + unparse = unicode_to_utf16be; + ts = 2; + } else if (sc->flag & SCONV_FROM_UTF16LE) { + unparse = unicode_to_utf16le; + ts = 2; + } else { + unparse = unicode_to_utf8; + ts = 1; + } + } + + if (sc->flag & SCONV_FROM_UTF16BE) { + parse = utf16be_to_unicode; + tm = 1; + } else if (sc->flag & SCONV_FROM_UTF16LE) { + parse = utf16le_to_unicode; + tm = 1; + } else { + parse = cesu8_to_unicode; + tm = ts; + } + + if (archive_string_ensure(as, as->length + len * tm + ts) == NULL) + return (-1); + + s = (const char *)_p; + p = as->s + as->length; + endp = as->s + as->buffer_length - ts; + while ((n = parse(&uc, s, len)) != 0) { + if (n < 0) { + /* Use a replaced unicode character. */ + n *= -1; + ret = -1; + } + s += n; + len -= n; + while ((w = unparse(p, endp - p, uc)) == 0) { + /* There is not enough output buffer so + * we have to expand it. */ + as->length = p - as->s; + if (archive_string_ensure(as, + as->buffer_length + len * tm + ts) == NULL) + return (-1); + p = as->s + as->length; + endp = as->s + as->buffer_length - ts; + } + p += w; + } + as->length = p - as->s; + as->s[as->length] = '\0'; + if (ts == 2) + as->s[as->length+1] = '\0'; + return (ret); +} + +/* + * Following Constants for Hangul compositions this information comes from + * Unicode Standard Annex #15 http://unicode.org/reports/tr15/ + */ +#define HC_SBASE 0xAC00 +#define HC_LBASE 0x1100 +#define HC_VBASE 0x1161 +#define HC_TBASE 0x11A7 +#define HC_LCOUNT 19 +#define HC_VCOUNT 21 +#define HC_TCOUNT 28 +#define HC_NCOUNT (HC_VCOUNT * HC_TCOUNT) +#define HC_SCOUNT (HC_LCOUNT * HC_NCOUNT) + +static uint32_t +get_nfc(uint32_t uc, uint32_t uc2) +{ + int t, b; + + t = 0; + b = sizeof(u_composition_table)/sizeof(u_composition_table[0]) -1; + while (b >= t) { + int m = (t + b) / 2; + if (u_composition_table[m].cp1 < uc) + t = m + 1; + else if (u_composition_table[m].cp1 > uc) + b = m - 1; + else if (u_composition_table[m].cp2 < uc2) + t = m + 1; + else if (u_composition_table[m].cp2 > uc2) + b = m - 1; + else + return (u_composition_table[m].nfc); + } + return (0); +} + +#define FDC_MAX 10 /* The maximum number of Following Decomposable + * Characters. */ + +/* + * Update first code point. + */ +#define UPDATE_UC(new_uc) do { \ + uc = new_uc; \ + ucptr = NULL; \ +} while (0) + +/* + * Replace first code point with second code point. + */ +#define REPLACE_UC_WITH_UC2() do { \ + uc = uc2; \ + ucptr = uc2ptr; \ + n = n2; \ +} while (0) + +#define EXPAND_BUFFER() do { \ + as->length = p - as->s; \ + if (archive_string_ensure(as, \ + as->buffer_length + len * tm + ts) == NULL)\ + return (-1); \ + p = as->s + as->length; \ + endp = as->s + as->buffer_length - ts; \ +} while (0) + +#define UNPARSE(p, endp, uc) do { \ + while ((w = unparse(p, (endp) - (p), uc)) == 0) {\ + EXPAND_BUFFER(); \ + } \ + p += w; \ +} while (0) + +/* + * Write first code point. + * If the code point has not be changed from its original code, + * this just copies it from its original buffer pointer. + * If not, this converts it to UTF-8 byte sequence and copies it. + */ +#define WRITE_UC() do { \ + if (ucptr) { \ + if (p + n > endp) \ + EXPAND_BUFFER(); \ + switch (n) { \ + case 4: \ + *p++ = *ucptr++; \ + /* FALL THROUGH */ \ + case 3: \ + *p++ = *ucptr++; \ + /* FALL THROUGH */ \ + case 2: \ + *p++ = *ucptr++; \ + /* FALL THROUGH */ \ + case 1: \ + *p++ = *ucptr; \ + break; \ + } \ + ucptr = NULL; \ + } else { \ + UNPARSE(p, endp, uc); \ + } \ +} while (0) + +/* + * Collect following decomposable code points. + */ +#define COLLECT_CPS(start) do { \ + int _i; \ + for (_i = start; _i < FDC_MAX ; _i++) { \ + nx = parse(&ucx[_i], s, len); \ + if (nx <= 0) \ + break; \ + cx = CCC(ucx[_i]); \ + if (cl >= cx && cl != 228 && cx != 228)\ + break; \ + s += nx; \ + len -= nx; \ + cl = cx; \ + ccx[_i] = cx; \ + } \ + if (_i >= FDC_MAX) { \ + ret = -1; \ + ucx_size = FDC_MAX; \ + } else \ + ucx_size = _i; \ +} while (0) + +/* + * Normalize UTF-8/UTF-16BE characters to Form C and copy the result. + * + * TODO: Convert composition exclusions,which are never converted + * from NFC,NFD,NFKC and NFKD, to Form C. + */ +static int +archive_string_normalize_C(struct archive_string *as, const void *_p, + size_t len, struct archive_string_conv *sc) +{ + const char *s = (const char *)_p; + char *p, *endp; + uint32_t uc, uc2; + size_t w; + int always_replace, n, n2, ret = 0, spair, ts, tm; + int (*parse)(uint32_t *, const char *, size_t); + size_t (*unparse)(char *, size_t, uint32_t); + + always_replace = 1; + ts = 1;/* text size. */ + if (sc->flag & SCONV_TO_UTF16BE) { + unparse = unicode_to_utf16be; + ts = 2; + if (sc->flag & SCONV_FROM_UTF16BE) + always_replace = 0; + } else if (sc->flag & SCONV_TO_UTF16LE) { + unparse = unicode_to_utf16le; + ts = 2; + if (sc->flag & SCONV_FROM_UTF16LE) + always_replace = 0; + } else if (sc->flag & SCONV_TO_UTF8) { + unparse = unicode_to_utf8; + if (sc->flag & SCONV_FROM_UTF8) + always_replace = 0; + } else { + /* + * This case is going to be converted to another + * character-set through iconv. + */ + always_replace = 0; + if (sc->flag & SCONV_FROM_UTF16BE) { + unparse = unicode_to_utf16be; + ts = 2; + } else if (sc->flag & SCONV_FROM_UTF16LE) { + unparse = unicode_to_utf16le; + ts = 2; + } else { + unparse = unicode_to_utf8; + } + } + + if (sc->flag & SCONV_FROM_UTF16BE) { + parse = utf16be_to_unicode; + tm = 1; + spair = 4;/* surrogate pair size in UTF-16. */ + } else if (sc->flag & SCONV_FROM_UTF16LE) { + parse = utf16le_to_unicode; + tm = 1; + spair = 4;/* surrogate pair size in UTF-16. */ + } else { + parse = cesu8_to_unicode; + tm = ts; + spair = 6;/* surrogate pair size in UTF-8. */ + } + + if (archive_string_ensure(as, as->length + len * tm + ts) == NULL) + return (-1); + + p = as->s + as->length; + endp = as->s + as->buffer_length - ts; + while ((n = parse(&uc, s, len)) != 0) { + const char *ucptr, *uc2ptr; + + if (n < 0) { + /* Use a replaced unicode character. */ + UNPARSE(p, endp, uc); + s += n*-1; + len -= n*-1; + ret = -1; + continue; + } else if (n == spair || always_replace) + /* uc is converted from a surrogate pair. + * this should be treated as a changed code. */ + ucptr = NULL; + else + ucptr = s; + s += n; + len -= n; + + /* Read second code point. */ + while ((n2 = parse(&uc2, s, len)) > 0) { + uint32_t ucx[FDC_MAX]; + int ccx[FDC_MAX]; + int cl, cx, i, nx, ucx_size; + int LIndex,SIndex; + uint32_t nfc; + + if (n2 == spair || always_replace) + /* uc2 is converted from a surrogate pair. + * this should be treated as a changed code. */ + uc2ptr = NULL; + else + uc2ptr = s; + s += n2; + len -= n2; + + /* + * If current second code point is out of decomposable + * code points, finding compositions is unneeded. + */ + if (!IS_DECOMPOSABLE_BLOCK(uc2)) { + WRITE_UC(); + REPLACE_UC_WITH_UC2(); + continue; + } + + /* + * Try to combine current code points. + */ + /* + * We have to combine Hangul characters according to + * http://uniicode.org/reports/tr15/#Hangul + */ + if (0 <= (LIndex = uc - HC_LBASE) && + LIndex < HC_LCOUNT) { + /* + * Hangul Composition. + * 1. Two current code points are L and V. + */ + int VIndex = uc2 - HC_VBASE; + if (0 <= VIndex && VIndex < HC_VCOUNT) { + /* Make syllable of form LV. */ + UPDATE_UC(HC_SBASE + + (LIndex * HC_VCOUNT + VIndex) * + HC_TCOUNT); + } else { + WRITE_UC(); + REPLACE_UC_WITH_UC2(); + } + continue; + } else if (0 <= (SIndex = uc - HC_SBASE) && + SIndex < HC_SCOUNT && (SIndex % HC_TCOUNT) == 0) { + /* + * Hangul Composition. + * 2. Two current code points are LV and T. + */ + int TIndex = uc2 - HC_TBASE; + if (0 < TIndex && TIndex < HC_TCOUNT) { + /* Make syllable of form LVT. */ + UPDATE_UC(uc + TIndex); + } else { + WRITE_UC(); + REPLACE_UC_WITH_UC2(); + } + continue; + } else if ((nfc = get_nfc(uc, uc2)) != 0) { + /* A composition to current code points + * is found. */ + UPDATE_UC(nfc); + continue; + } else if ((cl = CCC(uc2)) == 0) { + /* Clearly 'uc2' the second code point is not + * a decomposable code. */ + WRITE_UC(); + REPLACE_UC_WITH_UC2(); + continue; + } + + /* + * Collect following decomposable code points. + */ + cx = 0; + ucx[0] = uc2; + ccx[0] = cl; + COLLECT_CPS(1); + + /* + * Find a composed code in the collected code points. + */ + i = 1; + while (i < ucx_size) { + int j; + + if ((nfc = get_nfc(uc, ucx[i])) == 0) { + i++; + continue; + } + + /* + * nfc is composed of uc and ucx[i]. + */ + UPDATE_UC(nfc); + + /* + * Remove ucx[i] by shifting + * follwoing code points. + */ + for (j = i; j+1 < ucx_size; j++) { + ucx[j] = ucx[j+1]; + ccx[j] = ccx[j+1]; + } + ucx_size --; + + /* + * Collect following code points blocked + * by ucx[i] the removed code point. + */ + if (ucx_size > 0 && i == ucx_size && + nx > 0 && cx == cl) { + cl = ccx[ucx_size-1]; + COLLECT_CPS(ucx_size); + } + /* + * Restart finding a composed code with + * the updated uc from the top of the + * collected code points. + */ + i = 0; + } + + /* + * Apparently the current code points are not + * decomposed characters or already composed. + */ + WRITE_UC(); + for (i = 0; i < ucx_size; i++) + UNPARSE(p, endp, ucx[i]); + + /* + * Flush out remaining canonical combining characters. + */ + if (nx > 0 && cx == cl && len > 0) { + while ((nx = parse(&ucx[0], s, len)) + > 0) { + cx = CCC(ucx[0]); + if (cl > cx) + break; + s += nx; + len -= nx; + cl = cx; + UNPARSE(p, endp, ucx[0]); + } + } + break; + } + if (n2 < 0) { + WRITE_UC(); + /* Use a replaced unicode character. */ + UNPARSE(p, endp, uc2); + s += n2*-1; + len -= n2*-1; + ret = -1; + continue; + } else if (n2 == 0) { + WRITE_UC(); + break; + } + } + as->length = p - as->s; + as->s[as->length] = '\0'; + if (ts == 2) + as->s[as->length+1] = '\0'; + return (ret); +} + +#if defined(__APPLE__) + +/* + * Normalize UTF-8 characters to Form D and copy the result. + */ +static int +archive_string_normalize_D(struct archive_string *as, const void *_p, + size_t len, struct archive_string_conv *sc) +{ + const UniChar *inp; + char *outp; + size_t newsize; + ByteCount inCount, outCount; + ByteCount inAvail, outAvail; + OSStatus err; + int ret, saved_flag; + + /* + * Convert the current string to UTF-16LE for normalization. + * The character-set of the current string must be UTF-16BE or + * UTF-8. + */ + archive_string_empty(&(sc->utf16nfc)); + saved_flag = sc->flag;/* save a flag. */ + sc->flag &= ~(SCONV_TO_UTF16BE | SCONV_TO_UTF8); + sc->flag |= SCONV_TO_UTF16LE; + ret = archive_string_append_unicode(&(sc->utf16nfc), _p, len, sc); + sc->flag = saved_flag;/* restore the saved flag */ + if (archive_strlen(&(sc->utf16nfc)) == 0) { + if (archive_string_ensure(as, as->length + 1) == NULL) + return (-1); + return (ret); + } + + /* + * Normalize an NFC string to be an NFD(HFS Plus version). + */ + newsize = sc->utf16nfc.length + 2; + if (archive_string_ensure(&(sc->utf16nfd), newsize) == NULL) + return (-1); + + inp = (UniChar *)sc->utf16nfc.s; + inAvail = archive_strlen(&(sc->utf16nfc)); + sc->utf16nfd.length = 0; + outp = sc->utf16nfd.s; + outAvail = sc->utf16nfd.buffer_length -2; + + do { + /* Reinitialize all state information. */ + if (ResetUnicodeToTextInfo(sc->uniInfo) != noErr) + goto return_no_changed_data; + + inCount = outCount = 0; + err = ConvertFromUnicodeToText(sc->uniInfo, + inAvail, inp, + kUnicodeDefaultDirectionMask, 0, NULL, NULL, NULL, + outAvail, &inCount, &outCount, outp); + + if (err == noErr) { + sc->utf16nfd.length = outCount; + sc->utf16nfd.s[sc->utf16nfd.length] = 0; + sc->utf16nfd.s[sc->utf16nfd.length+1] = 0; + } else if (err == kTECOutputBufferFullStatus) { + newsize = inAvail - inCount; + if (newsize > inAvail) + newsize = inAvail; + newsize += sc->utf16nfd.buffer_length + 2; + if (archive_string_ensure(&(sc->utf16nfd), newsize) + == NULL) + return (-1); + outp = sc->utf16nfd.s; + outAvail = sc->utf16nfd.buffer_length -2; + } else + goto return_no_changed_data; + } while (err == kTECOutputBufferFullStatus); + + /* + * If there is a next-step conversion, we should convert + * a UTF-16LE(NFD) string back to the original Unicode type. + */ + saved_flag = sc->flag;/* save a flag. */ + if (!(sc->flag & + (SCONV_TO_UTF16BE | SCONV_TO_UTF16LE | SCONV_TO_UTF8))) { + /* + * This case is going to be converted to another + * character-set through iconv. + */ + if (sc->flag & SCONV_FROM_UTF16BE) + sc->flag |= SCONV_TO_UTF16BE; + else if (sc->flag & SCONV_FROM_UTF16LE) + sc->flag |= SCONV_TO_UTF16LE; + else + sc->flag |= SCONV_TO_UTF8; + } + sc->flag &= ~(SCONV_FROM_UTF16BE | SCONV_FROM_UTF8); + sc->flag |= SCONV_FROM_UTF16LE; + if (archive_string_append_unicode(as, sc->utf16nfd.s, + sc->utf16nfd.length, sc) != 0) + ret = -1; + sc->flag = saved_flag;/* restore the saved flag */ + return (ret); + +return_no_changed_data: + /* + * Something conversion error happend, so we return a no normalized + * string with an error. + */ + (void)archive_string_append_unicode(as, _p, len, sc); + return (-1); +} + +#endif /* __APPLE__ */ + +/* + * libarchive 2.x made incorrect UTF-8 strings in the wrong assumuption + * that WCS is Unicode. it is true for servel platforms but some are false. + * And then people who did not use UTF-8 locale on the non Unicode WCS + * platform and made a tar file with libarchive(mostly bsdtar) 2.x. Those + * now cannot get right filename from libarchive 3.x and later since we + * fixed the wrong assumption and it is incompatible to older its versions. + * So we provide special option, "compat-2x.x", for resolving it. + * That option enable the string conversion of libarchive 2.x. + * + * Translates the wrong UTF-8 string made by libarchive 2.x into current + * locale character set and appends to the archive_string. + * Note: returns -1 if conversion fails. + */ +static int +strncat_from_utf8_libarchive2(struct archive_string *as, + const void *_p, size_t len, struct archive_string_conv *sc) +{ + const char *s; + int n; + char *p; + char *end; + uint32_t unicode; +#if HAVE_WCRTOMB + mbstate_t shift_state; + + memset(&shift_state, 0, sizeof(shift_state)); +#else + /* Clear the shift state before starting. */ + wctomb(NULL, L'\0'); +#endif + (void)sc; /* UNUSED */ + /* + * Allocate buffer for MBS. + * We need this allocation here since it is possible that + * as->s is still NULL. + */ + if (archive_string_ensure(as, as->length + len + 1) == NULL) + return (-1); + + s = (const char *)_p; + p = as->s + as->length; + end = as->s + as->buffer_length - MB_CUR_MAX -1; + while ((n = _utf8_to_unicode(&unicode, s, len)) != 0) { + wchar_t wc; + + if (p >= end) { + as->length = p - as->s; + /* Re-allocate buffer for MBS. */ + if (archive_string_ensure(as, + as->length + len * 2 + 1) == NULL) + return (-1); + p = as->s + as->length; + end = as->s + as->buffer_length - MB_CUR_MAX -1; + } + + /* + * As libarchie 2.x, translates the UTF-8 characters into + * wide-characters in the assumption that WCS is Unicode. + */ + if (n < 0) { + n *= -1; + wc = L'?'; + } else + wc = (wchar_t)unicode; + + s += n; + len -= n; + /* + * Translates the wide-character into the current locale MBS. + */ +#if HAVE_WCRTOMB + n = wcrtomb(p, wc, &shift_state); +#else + n = wctomb(p, wc); +#endif + if (n == -1) + return (-1); + p += n; + } + as->length = p - as->s; + as->s[as->length] = '\0'; + return (0); +} + + +/* + * Conversion functions between current locale dependent MBS and UTF-16BE. + * strncat_from_utf16be() : UTF-16BE --> MBS + * strncat_to_utf16be() : MBS --> UTF16BE + */ + +#if defined(_WIN32) && !defined(__CYGWIN__) + +/* + * Convert a UTF-16BE/LE string to current locale and copy the result. + * Return -1 if conversion failes. + */ +static int +win_strncat_from_utf16(struct archive_string *as, const void *_p, size_t bytes, + struct archive_string_conv *sc, int be) +{ + struct archive_string tmp; + const char *u16; + int ll; + BOOL defchar; + char *mbs; + size_t mbs_size, b; + int ret = 0; + + bytes &= ~1; + if (archive_string_ensure(as, as->length + bytes +1) == NULL) + return (-1); + + mbs = as->s + as->length; + mbs_size = as->buffer_length - as->length -1; + + if (sc->to_cp == CP_C_LOCALE) { + /* + * "C" locale special process. + */ + u16 = _p; + ll = 0; + for (b = 0; b < bytes; b += 2) { + uint16_t val; + if (be) + val = archive_be16dec(u16+b); + else + val = archive_le16dec(u16+b); + if (val > 255) { + *mbs++ = '?'; + ret = -1; + } else + *mbs++ = (char)(val&0xff); + ll++; + } + as->length += ll; + as->s[as->length] = '\0'; + return (ret); + } + + archive_string_init(&tmp); + if (be) { + if (is_big_endian()) { + u16 = _p; + } else { + if (archive_string_ensure(&tmp, bytes+2) == NULL) + return (-1); + memcpy(tmp.s, _p, bytes); + for (b = 0; b < bytes; b += 2) { + uint16_t val = archive_be16dec(tmp.s+b); + archive_le16enc(tmp.s+b, val); + } + u16 = tmp.s; + } + } else { + if (!is_big_endian()) { + u16 = _p; + } else { + if (archive_string_ensure(&tmp, bytes+2) == NULL) + return (-1); + memcpy(tmp.s, _p, bytes); + for (b = 0; b < bytes; b += 2) { + uint16_t val = archive_le16dec(tmp.s+b); + archive_be16enc(tmp.s+b, val); + } + u16 = tmp.s; + } + } + + do { + defchar = 0; + ll = WideCharToMultiByte(sc->to_cp, 0, + (LPCWSTR)u16, bytes>>1, mbs, mbs_size, + NULL, &defchar); + if (ll == 0 && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + /* Need more buffer for MBS. */ + ll = WideCharToMultiByte(sc->to_cp, 0, + (LPCWSTR)u16, bytes, NULL, 0, NULL, NULL); + if (archive_string_ensure(as, ll +1) == NULL) + return (-1); + mbs = as->s + as->length; + mbs_size = as->buffer_length - as->length -1; + continue; + } + } while (0); + archive_string_free(&tmp); + as->length += ll; + as->s[as->length] = '\0'; + if (ll == 0 || defchar) + ret = -1; + return (ret); +} + +static int +win_strncat_from_utf16be(struct archive_string *as, const void *_p, size_t bytes, + struct archive_string_conv *sc) +{ + return (win_strncat_from_utf16(as, _p, bytes, sc, 1)); +} + +static int +win_strncat_from_utf16le(struct archive_string *as, const void *_p, size_t bytes, + struct archive_string_conv *sc) +{ + return (win_strncat_from_utf16(as, _p, bytes, sc, 0)); +} + +static int +is_big_endian(void) +{ + uint16_t d = 1; + + return (archive_be16dec(&d) == 1); +} + +/* + * Convert a current locale string to UTF-16BE/LE and copy the result. + * Return -1 if conversion failes. + */ +static int +win_strncat_to_utf16(struct archive_string *as16, const void *_p, size_t length, + struct archive_string_conv *sc, int bigendian) +{ + const char *s = (const char *)_p; + char *u16; + size_t count, avail; + + if (archive_string_ensure(as16, + as16->length + (length + 1) * 2) == NULL) + return (-1); + + u16 = as16->s + as16->length; + avail = as16->buffer_length - 2; + if (sc->from_cp == CP_C_LOCALE) { + /* + * "C" locale special process. + */ + count = 0; + while (count < length && *s) { + if (bigendian) + archive_be16enc(u16, *s); + else + archive_le16enc(u16, *s); + u16 += 2; + s++; + count++; + } + as16->length += count << 1; + as16->s[as16->length] = 0; + as16->s[as16->length+1] = 0; + return (0); + } + do { + count = MultiByteToWideChar(sc->from_cp, + MB_PRECOMPOSED, s, length, (LPWSTR)u16, (int)avail>>1); + if (count == 0 && + GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + /* Need more buffer for UTF-16 string */ + count = MultiByteToWideChar(sc->from_cp, + MB_PRECOMPOSED, s, length, NULL, 0); + if (archive_string_ensure(as16, (count +1) * 2) + == NULL) + return (-1); + u16 = as16->s + as16->length; + avail = as16->buffer_length - 2; + continue; + } + } while (0); + as16->length += count * 2; + as16->s[as16->length] = 0; + as16->s[as16->length+1] = 0; + if (count == 0) + return (-1); + + if (is_big_endian()) { + if (!bigendian) { + while (count > 0) { + uint16_t v = archive_be16dec(u16); + archive_le16enc(u16, v); + u16 += 2; + count--; + } + } + } else { + if (bigendian) { + while (count > 0) { + uint16_t v = archive_le16dec(u16); + archive_be16enc(u16, v); + u16 += 2; + count--; + } + } + } + return (0); +} + +static int +win_strncat_to_utf16be(struct archive_string *as16, const void *_p, size_t length, + struct archive_string_conv *sc) +{ + return (win_strncat_to_utf16(as16, _p, length, sc, 1)); +} + +static int +win_strncat_to_utf16le(struct archive_string *as16, const void *_p, size_t length, + struct archive_string_conv *sc) +{ + return (win_strncat_to_utf16(as16, _p, length, sc, 0)); +} + +#endif /* _WIN32 && !__CYGWIN__ */ + +/* + * Do the best effort for conversions. + * We cannot handle UTF-16BE character-set without such iconv, + * but there is a chance if a string consists just ASCII code or + * a current locale is UTF-8. + */ + +/* + * Convert a UTF-16BE string to current locale and copy the result. + * Return -1 if conversion failes. + */ +static int +best_effort_strncat_from_utf16(struct archive_string *as, const void *_p, + size_t bytes, struct archive_string_conv *sc, int be) +{ + const char *utf16 = (const char *)_p; + char *mbs; + uint32_t uc; + int n, ret; + + (void)sc; /* UNUSED */ + /* + * Other case, we should do the best effort. + * If all character are ASCII(<0x7f), we can convert it. + * if not , we set a alternative character and return -1. + */ + ret = 0; + if (archive_string_ensure(as, as->length + bytes +1) == NULL) + return (-1); + mbs = as->s + as->length; + + while ((n = utf16_to_unicode(&uc, utf16, bytes, be)) != 0) { + if (n < 0) { + n *= -1; + ret = -1; + } + bytes -= n; + utf16 += n; + + if (uc > 127) { + /* We cannot handle it. */ + *mbs++ = '?'; + ret = -1; + } else + *mbs++ = (char)uc; + } + as->length = mbs - as->s; + as->s[as->length] = '\0'; + return (ret); +} + +static int +best_effort_strncat_from_utf16be(struct archive_string *as, const void *_p, + size_t bytes, struct archive_string_conv *sc) +{ + return (best_effort_strncat_from_utf16(as, _p, bytes, sc, 1)); +} + +static int +best_effort_strncat_from_utf16le(struct archive_string *as, const void *_p, + size_t bytes, struct archive_string_conv *sc) +{ + return (best_effort_strncat_from_utf16(as, _p, bytes, sc, 0)); +} + +/* + * Convert a current locale string to UTF-16BE/LE and copy the result. + * Return -1 if conversion failes. + */ +static int +best_effort_strncat_to_utf16(struct archive_string *as16, const void *_p, + size_t length, struct archive_string_conv *sc, int bigendian) +{ + const char *s = (const char *)_p; + char *utf16; + size_t remaining; + int ret; + + (void)sc; /* UNUSED */ + /* + * Other case, we should do the best effort. + * If all character are ASCII(<0x7f), we can convert it. + * if not , we set a alternative character and return -1. + */ + ret = 0; + remaining = length; + + if (archive_string_ensure(as16, + as16->length + (length + 1) * 2) == NULL) + return (-1); + + utf16 = as16->s + as16->length; + while (remaining--) { + unsigned c = *s++; + if (c > 127) { + /* We cannot handle it. */ + c = UNICODE_R_CHAR; + ret = -1; + } + if (bigendian) + archive_be16enc(utf16, c); + else + archive_le16enc(utf16, c); + utf16 += 2; + } + as16->length = utf16 - as16->s; + as16->s[as16->length] = 0; + as16->s[as16->length+1] = 0; + return (ret); +} + +static int +best_effort_strncat_to_utf16be(struct archive_string *as16, const void *_p, + size_t length, struct archive_string_conv *sc) +{ + return (best_effort_strncat_to_utf16(as16, _p, length, sc, 1)); +} + +static int +best_effort_strncat_to_utf16le(struct archive_string *as16, const void *_p, + size_t length, struct archive_string_conv *sc) +{ + return (best_effort_strncat_to_utf16(as16, _p, length, sc, 0)); +} + + +/* + * Multistring operations. + */ + +void +archive_mstring_clean(struct archive_mstring *aes) +{ + archive_wstring_free(&(aes->aes_wcs)); + archive_string_free(&(aes->aes_mbs)); + archive_string_free(&(aes->aes_utf8)); + archive_string_free(&(aes->aes_mbs_in_locale)); + aes->aes_set = 0; +} + +void +archive_mstring_copy(struct archive_mstring *dest, struct archive_mstring *src) +{ + dest->aes_set = src->aes_set; + archive_string_copy(&(dest->aes_mbs), &(src->aes_mbs)); + archive_string_copy(&(dest->aes_utf8), &(src->aes_utf8)); + archive_wstring_copy(&(dest->aes_wcs), &(src->aes_wcs)); +} + +int +archive_mstring_get_utf8(struct archive *a, struct archive_mstring *aes, + const char **p) +{ + struct archive_string_conv *sc; + int r; + + /* If we already have a UTF8 form, return that immediately. */ + if (aes->aes_set & AES_SET_UTF8) { + *p = aes->aes_utf8.s; + return (0); + } + + *p = NULL; + if (aes->aes_set & AES_SET_MBS) { + sc = archive_string_conversion_to_charset(a, "UTF-8", 1); + if (sc == NULL) + return (-1);/* Couldn't allocate memory for sc. */ + r = archive_strncpy_in_locale(&(aes->aes_mbs), aes->aes_mbs.s, + aes->aes_mbs.length, sc); + if (a == NULL) + free_sconv_object(sc); + if (r == 0) { + aes->aes_set |= AES_SET_UTF8; + *p = aes->aes_utf8.s; + return (0);/* success. */ + } else + return (-1);/* failure. */ + } + return (0);/* success. */ +} + +int +archive_mstring_get_mbs(struct archive *a, struct archive_mstring *aes, + const char **p) +{ + int r, ret = 0; + + (void)a; /* UNUSED */ + /* If we already have an MBS form, return that immediately. */ + if (aes->aes_set & AES_SET_MBS) { + *p = aes->aes_mbs.s; + return (ret); + } + + *p = NULL; + /* If there's a WCS form, try converting with the native locale. */ + if (aes->aes_set & AES_SET_WCS) { + archive_string_empty(&(aes->aes_mbs)); + r = archive_string_append_from_wcs(&(aes->aes_mbs), + aes->aes_wcs.s, aes->aes_wcs.length); + *p = aes->aes_mbs.s; + if (r == 0) { + aes->aes_set |= AES_SET_MBS; + return (ret); + } else + ret = -1; + } + + /* + * Only a UTF-8 form cannot avail because its conversion already + * failed at archive_mstring_update_utf8(). + */ + return (ret); +} + +int +archive_mstring_get_wcs(struct archive *a, struct archive_mstring *aes, + const wchar_t **wp) +{ + int r, ret = 0; + + (void)a;/* UNUSED */ + /* Return WCS form if we already have it. */ + if (aes->aes_set & AES_SET_WCS) { + *wp = aes->aes_wcs.s; + return (ret); + } + + *wp = NULL; + /* Try converting MBS to WCS using native locale. */ + if (aes->aes_set & AES_SET_MBS) { + archive_wstring_empty(&(aes->aes_wcs)); + r = archive_wstring_append_from_mbs(&(aes->aes_wcs), + aes->aes_mbs.s, aes->aes_mbs.length); + if (r == 0) { + aes->aes_set |= AES_SET_WCS; + *wp = aes->aes_wcs.s; + } else + ret = -1;/* failure. */ + } + return (ret); +} + +int +archive_mstring_get_mbs_l(struct archive_mstring *aes, + const char **p, size_t *length, struct archive_string_conv *sc) +{ + int r, ret = 0; + +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * Internationalization programing on Windows must use Wide + * characters because Windows platform cannot make locale UTF-8. + */ + if (sc != NULL && (aes->aes_set & AES_SET_WCS) != 0) { + archive_string_empty(&(aes->aes_mbs_in_locale)); + r = archive_string_append_from_wcs_in_codepage( + &(aes->aes_mbs_in_locale), aes->aes_wcs.s, + aes->aes_wcs.length, sc); + if (r == 0) { + *p = aes->aes_mbs_in_locale.s; + if (length != NULL) + *length = aes->aes_mbs_in_locale.length; + return (0); + } else if (errno == ENOMEM) + return (-1); + else + ret = -1; + } +#endif + + /* If there is not an MBS form but is a WCS form, try converting + * with the native locale to be used for translating it to specified + * character-set. */ + if ((aes->aes_set & AES_SET_MBS) == 0 && + (aes->aes_set & AES_SET_WCS) != 0) { + archive_string_empty(&(aes->aes_mbs)); + r = archive_string_append_from_wcs(&(aes->aes_mbs), + aes->aes_wcs.s, aes->aes_wcs.length); + if (r == 0) + aes->aes_set |= AES_SET_MBS; + else if (errno == ENOMEM) + return (-1); + else + ret = -1; + } + /* If we already have an MBS form, use it to be translated to + * specified character-set. */ + if (aes->aes_set & AES_SET_MBS) { + if (sc == NULL) { + /* Conversion is unneeded. */ + *p = aes->aes_mbs.s; + if (length != NULL) + *length = aes->aes_mbs.length; + return (0); + } + ret = archive_strncpy_in_locale(&(aes->aes_mbs_in_locale), + aes->aes_mbs.s, aes->aes_mbs.length, sc); + *p = aes->aes_mbs_in_locale.s; + if (length != NULL) + *length = aes->aes_mbs_in_locale.length; + } else { + *p = NULL; + if (length != NULL) + *length = 0; + } + return (ret); +} + +int +archive_mstring_copy_mbs(struct archive_mstring *aes, const char *mbs) +{ + if (mbs == NULL) { + aes->aes_set = 0; + return (0); + } + return (archive_mstring_copy_mbs_len(aes, mbs, strlen(mbs))); +} + +int +archive_mstring_copy_mbs_len(struct archive_mstring *aes, const char *mbs, + size_t len) +{ + if (mbs == NULL) { + aes->aes_set = 0; + return (0); + } + aes->aes_set = AES_SET_MBS; /* Only MBS form is set now. */ + archive_strncpy(&(aes->aes_mbs), mbs, len); + archive_string_empty(&(aes->aes_utf8)); + archive_wstring_empty(&(aes->aes_wcs)); + return (0); +} + +int +archive_mstring_copy_wcs(struct archive_mstring *aes, const wchar_t *wcs) +{ + return archive_mstring_copy_wcs_len(aes, wcs, wcs == NULL ? 0 : wcslen(wcs)); +} + +int +archive_mstring_copy_wcs_len(struct archive_mstring *aes, const wchar_t *wcs, + size_t len) +{ + if (wcs == NULL) { + aes->aes_set = 0; + } + aes->aes_set = AES_SET_WCS; /* Only WCS form set. */ + archive_string_empty(&(aes->aes_mbs)); + archive_string_empty(&(aes->aes_utf8)); + archive_wstrncpy(&(aes->aes_wcs), wcs, len); + return (0); +} + +int +archive_mstring_copy_mbs_len_l(struct archive_mstring *aes, + const char *mbs, size_t len, struct archive_string_conv *sc) +{ + int r; + + if (mbs == NULL) { + aes->aes_set = 0; + return (0); + } + archive_string_empty(&(aes->aes_mbs)); + archive_wstring_empty(&(aes->aes_wcs)); + archive_string_empty(&(aes->aes_utf8)); +#if defined(_WIN32) && !defined(__CYGWIN__) + /* + * Internationalization programing on Windows must use Wide + * characters because Windows platform cannot make locale UTF-8. + */ + if (sc == NULL) { + archive_string_append(&(aes->aes_mbs), + mbs, mbsnbytes(mbs, len)); + aes->aes_set = AES_SET_MBS; + r = 0; +#if defined(HAVE_ICONV) + } else if (sc != NULL && sc->cd_w != (iconv_t)-1) { + /* + * This case happens only when MultiByteToWideChar() cannot + * handle sc->from_cp, and we have to iconv in order to + * translate character-set to wchar_t,UTF-16. + */ + iconv_t cd = sc->cd; + unsigned from_cp; + int flag; + + /* + * Translate multi-bytes from some character-set to UTF-8. + */ + sc->cd = sc->cd_w; + r = archive_strncpy_in_locale(&(aes->aes_utf8), mbs, len, sc); + sc->cd = cd; + if (r != 0) { + aes->aes_set = 0; + return (r); + } + aes->aes_set = AES_SET_UTF8; + + /* + * Append the UTF-8 string into wstring. + */ + flag = sc->flag; + sc->flag &= ~(SCONV_NORMALIZATION_C + | SCONV_TO_UTF16| SCONV_FROM_UTF16); + from_cp = sc->from_cp; + sc->from_cp = CP_UTF8; + r = archive_wstring_append_from_mbs_in_codepage(&(aes->aes_wcs), + aes->aes_utf8.s, aes->aes_utf8.length, sc); + sc->flag = flag; + sc->from_cp = from_cp; + if (r == 0) + aes->aes_set |= AES_SET_WCS; +#endif + } else { + r = archive_wstring_append_from_mbs_in_codepage( + &(aes->aes_wcs), mbs, len, sc); + if (r == 0) + aes->aes_set = AES_SET_WCS; + else + aes->aes_set = 0; + } +#else + r = archive_strncpy_in_locale(&(aes->aes_mbs), mbs, len, sc); + if (r == 0) + aes->aes_set = AES_SET_MBS; /* Only MBS form is set now. */ + else + aes->aes_set = 0; +#endif + return (r); +} + +/* + * The 'update' form tries to proactively update all forms of + * this string (WCS and MBS) and returns an error if any of + * them fail. This is used by the 'pax' handler, for instance, + * to detect and report character-conversion failures early while + * still allowing clients to get potentially useful values from + * the more tolerant lazy conversions. (get_mbs and get_wcs will + * strive to give the user something useful, so you can get hopefully + * usable values even if some of the character conversions are failing.) + */ +int +archive_mstring_update_utf8(struct archive *a, struct archive_mstring *aes, + const char *utf8) +{ + struct archive_string_conv *sc; + int r; + + if (utf8 == NULL) { + aes->aes_set = 0; + return (0); /* Succeeded in clearing everything. */ + } + + /* Save the UTF8 string. */ + archive_strcpy(&(aes->aes_utf8), utf8); + + /* Empty the mbs and wcs strings. */ + archive_string_empty(&(aes->aes_mbs)); + archive_wstring_empty(&(aes->aes_wcs)); + + aes->aes_set = AES_SET_UTF8; /* Only UTF8 is set now. */ + + /* Try converting UTF-8 to MBS, return false on failure. */ + sc = archive_string_conversion_from_charset(a, "UTF-8", 1); + if (sc == NULL) + return (-1);/* Couldn't allocate memory for sc. */ + r = archive_strcpy_in_locale(&(aes->aes_mbs), utf8, sc); + if (a == NULL) + free_sconv_object(sc); + if (r != 0) + return (-1); + aes->aes_set = AES_SET_UTF8 | AES_SET_MBS; /* Both UTF8 and MBS set. */ + + /* Try converting MBS to WCS, return false on failure. */ + if (archive_wstring_append_from_mbs(&(aes->aes_wcs), aes->aes_mbs.s, + aes->aes_mbs.length)) + return (-1); + aes->aes_set = AES_SET_UTF8 | AES_SET_WCS | AES_SET_MBS; + + /* All conversions succeeded. */ + return (0); +} diff --git a/libarchive/archive_string.h b/libarchive/archive_string.h new file mode 100644 index 0000000..0e4c9b9 --- /dev/null +++ b/libarchive/archive_string.h @@ -0,0 +1,237 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_string.h 201092 2009-12-28 02:26:06Z kientzle $ + * + */ + +#ifndef __LIBARCHIVE_BUILD +#ifndef __LIBARCHIVE_TEST +#error This header is only to be used internally to libarchive. +#endif +#endif + +#ifndef ARCHIVE_STRING_H_INCLUDED +#define ARCHIVE_STRING_H_INCLUDED + +#include +#ifdef HAVE_STDLIB_H +#include /* required for wchar_t on some systems */ +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_WCHAR_H +#include +#endif + +#include "archive.h" + +/* + * Basic resizable/reusable string support similar to Java's "StringBuffer." + * + * Unlike sbuf(9), the buffers here are fully reusable and track the + * length throughout. + */ + +struct archive_string { + char *s; /* Pointer to the storage */ + size_t length; /* Length of 's' in characters */ + size_t buffer_length; /* Length of malloc-ed storage in bytes. */ +}; + +struct archive_wstring { + wchar_t *s; /* Pointer to the storage */ + size_t length; /* Length of 's' in characters */ + size_t buffer_length; /* Length of malloc-ed storage in bytes. */ +}; + +struct archive_string_conv; + +/* Initialize an archive_string object on the stack or elsewhere. */ +#define archive_string_init(a) \ + do { (a)->s = NULL; (a)->length = 0; (a)->buffer_length = 0; } while(0) + +/* Append a C char to an archive_string, resizing as necessary. */ +struct archive_string * +archive_strappend_char(struct archive_string *, char); + +/* Ditto for a wchar_t and an archive_wstring. */ +struct archive_wstring * +archive_wstrappend_wchar(struct archive_wstring *, wchar_t); + +/* Convert a Unicode string to current locale and append the result. */ +/* Returns -1 if conversion fails. */ +int +archive_string_append_from_wcs(struct archive_string *, const wchar_t *, size_t); + + +/* Create a string conversion object. + * Return NULL and set a error message if the conversion is not supported + * on the platform. */ +struct archive_string_conv * +archive_string_conversion_to_charset(struct archive *, const char *, int); +struct archive_string_conv * +archive_string_conversion_from_charset(struct archive *, const char *, int); +/* Create the default string conversion object for reading/writing an archive. + * Return NULL if the conversion is unneeded. + * Note: On non Windows platform this always returns NULL. + */ +struct archive_string_conv * +archive_string_default_conversion_for_read(struct archive *); +struct archive_string_conv * +archive_string_default_conversion_for_write(struct archive *); +/* Dispose of a string conversion object. */ +void +archive_string_conversion_free(struct archive *); +const char * +archive_string_conversion_charset_name(struct archive_string_conv *); +void +archive_string_conversion_set_opt(struct archive_string_conv *, int); +#define SCONV_SET_OPT_UTF8_LIBARCHIVE2X 1 + + +/* Copy one archive_string to another in locale conversion. + * Return -1 if conversion failes. */ +int +archive_strncpy_in_locale(struct archive_string *, const void *, size_t, + struct archive_string_conv *); + +/* Copy one archive_string to another in locale conversion. + * Return -1 if conversion failes. */ +int +archive_strncat_in_locale(struct archive_string *, const void *, size_t, + struct archive_string_conv *); + + +/* Copy one archive_string to another */ +#define archive_string_copy(dest, src) \ + ((dest)->length = 0, archive_string_concat((dest), (src))) +#define archive_wstring_copy(dest, src) \ + ((dest)->length = 0, archive_wstring_concat((dest), (src))) + +/* Concatenate one archive_string to another */ +void archive_string_concat(struct archive_string *dest, struct archive_string *src); +void archive_wstring_concat(struct archive_wstring *dest, struct archive_wstring *src); + +/* Ensure that the underlying buffer is at least as large as the request. */ +struct archive_string * +archive_string_ensure(struct archive_string *, size_t); +struct archive_wstring * +archive_wstring_ensure(struct archive_wstring *, size_t); + +/* Append C string, which may lack trailing \0. */ +/* The source is declared void * here because this gets used with + * "signed char *", "unsigned char *" and "char *" arguments. + * Declaring it "char *" as with some of the other functions just + * leads to a lot of extra casts. */ +struct archive_string * +archive_strncat(struct archive_string *, const void *, size_t); +struct archive_wstring * +archive_wstrncat(struct archive_wstring *, const wchar_t *, size_t); + +/* Append a C string to an archive_string, resizing as necessary. */ +struct archive_string * +archive_strcat(struct archive_string *, const void *); +struct archive_wstring * +archive_wstrcat(struct archive_wstring *, const wchar_t *); + +/* Copy a C string to an archive_string, resizing as necessary. */ +#define archive_strcpy(as,p) \ + archive_strncpy((as), (p), ((p) == NULL ? 0 : strlen(p))) +#define archive_wstrcpy(as,p) \ + archive_wstrncpy((as), (p), ((p) == NULL ? 0 : wcslen(p))) +#define archive_strcpy_in_locale(as,p,lo) \ + archive_strncpy_in_locale((as), (p), ((p) == NULL ? 0 : strlen(p)), (lo)) + +/* Copy a C string to an archive_string with limit, resizing as necessary. */ +#define archive_strncpy(as,p,l) \ + ((as)->length=0, archive_strncat((as), (p), (l))) +#define archive_wstrncpy(as,p,l) \ + ((as)->length = 0, archive_wstrncat((as), (p), (l))) + +/* Return length of string. */ +#define archive_strlen(a) ((a)->length) + +/* Set string length to zero. */ +#define archive_string_empty(a) ((a)->length = 0) +#define archive_wstring_empty(a) ((a)->length = 0) + +/* Release any allocated storage resources. */ +void archive_string_free(struct archive_string *); +void archive_wstring_free(struct archive_wstring *); + +/* Like 'vsprintf', but resizes the underlying string as necessary. */ +/* Note: This only implements a small subset of standard printf functionality. */ +void archive_string_vsprintf(struct archive_string *, const char *, + va_list) __LA_PRINTF(2, 0); +void archive_string_sprintf(struct archive_string *, const char *, ...) + __LA_PRINTF(2, 3); + +/* Translates from MBS to Unicode. */ +/* Returns non-zero if conversion failed in any way. */ +int archive_wstring_append_from_mbs(struct archive_wstring *dest, + const char *, size_t); + + +/* A "multistring" can hold Unicode, UTF8, or MBS versions of + * the string. If you set and read the same version, no translation + * is done. If you set and read different versions, the library + * will attempt to transparently convert. + */ +struct archive_mstring { + struct archive_string aes_mbs; + struct archive_string aes_utf8; + struct archive_wstring aes_wcs; + struct archive_string aes_mbs_in_locale; + /* Bitmap of which of the above are valid. Because we're lazy + * about malloc-ing and reusing the underlying storage, we + * can't rely on NULL pointers to indicate whether a string + * has been set. */ + int aes_set; +#define AES_SET_MBS 1 +#define AES_SET_UTF8 2 +#define AES_SET_WCS 4 +}; + +void archive_mstring_clean(struct archive_mstring *); +void archive_mstring_copy(struct archive_mstring *dest, struct archive_mstring *src); +int archive_mstring_get_mbs(struct archive *, struct archive_mstring *, const char **); +int archive_mstring_get_utf8(struct archive *, struct archive_mstring *, const char **); +int archive_mstring_get_wcs(struct archive *, struct archive_mstring *, const wchar_t **); +int archive_mstring_get_mbs_l(struct archive_mstring *, const char **, + size_t *, struct archive_string_conv *); +int archive_mstring_copy_mbs(struct archive_mstring *, const char *mbs); +int archive_mstring_copy_mbs_len(struct archive_mstring *, const char *mbs, + size_t); +int archive_mstring_copy_utf8(struct archive_mstring *, const char *utf8); +int archive_mstring_copy_wcs(struct archive_mstring *, const wchar_t *wcs); +int archive_mstring_copy_wcs_len(struct archive_mstring *, + const wchar_t *wcs, size_t); +int archive_mstring_copy_mbs_len_l(struct archive_mstring *, + const char *mbs, size_t, struct archive_string_conv *); +int archive_mstring_update_utf8(struct archive *, struct archive_mstring *aes, const char *utf8); + + +#endif diff --git a/libarchive/archive_string_composition.h b/libarchive/archive_string_composition.h new file mode 100644 index 0000000..cc4bf46 --- /dev/null +++ b/libarchive/archive_string_composition.h @@ -0,0 +1,1351 @@ +/*- + * Copyright (c) 2011 libarchive Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) 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(S) 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. + * + * $FreeBSD$ + * + */ + +/* + * ATTENTION! + * This file is generated by build/utils/gen_archive_string_composition_h.sh + * from http://unicode.org/Public/UNIDATA/UnicodeData.txt + * + * See also http://unicode.org/report/tr15/ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_STRING_COMPOSITION_H_INCLUDED +#define ARCHIVE_STRING_COMPOSITION_H_INCLUDED + +struct unicode_composition_table { + uint32_t cp1; + uint32_t cp2; + uint32_t nfc; +}; + +static const struct unicode_composition_table u_composition_table[] = { + { 0x0003C , 0x00338 , 0x0226E }, + { 0x0003D , 0x00338 , 0x02260 }, + { 0x0003E , 0x00338 , 0x0226F }, + { 0x00041 , 0x00300 , 0x000C0 }, + { 0x00041 , 0x00301 , 0x000C1 }, + { 0x00041 , 0x00302 , 0x000C2 }, + { 0x00041 , 0x00303 , 0x000C3 }, + { 0x00041 , 0x00304 , 0x00100 }, + { 0x00041 , 0x00306 , 0x00102 }, + { 0x00041 , 0x00307 , 0x00226 }, + { 0x00041 , 0x00308 , 0x000C4 }, + { 0x00041 , 0x00309 , 0x01EA2 }, + { 0x00041 , 0x0030A , 0x000C5 }, + { 0x00041 , 0x0030C , 0x001CD }, + { 0x00041 , 0x0030F , 0x00200 }, + { 0x00041 , 0x00311 , 0x00202 }, + { 0x00041 , 0x00323 , 0x01EA0 }, + { 0x00041 , 0x00325 , 0x01E00 }, + { 0x00041 , 0x00328 , 0x00104 }, + { 0x00042 , 0x00307 , 0x01E02 }, + { 0x00042 , 0x00323 , 0x01E04 }, + { 0x00042 , 0x00331 , 0x01E06 }, + { 0x00043 , 0x00301 , 0x00106 }, + { 0x00043 , 0x00302 , 0x00108 }, + { 0x00043 , 0x00307 , 0x0010A }, + { 0x00043 , 0x0030C , 0x0010C }, + { 0x00043 , 0x00327 , 0x000C7 }, + { 0x00044 , 0x00307 , 0x01E0A }, + { 0x00044 , 0x0030C , 0x0010E }, + { 0x00044 , 0x00323 , 0x01E0C }, + { 0x00044 , 0x00327 , 0x01E10 }, + { 0x00044 , 0x0032D , 0x01E12 }, + { 0x00044 , 0x00331 , 0x01E0E }, + { 0x00045 , 0x00300 , 0x000C8 }, + { 0x00045 , 0x00301 , 0x000C9 }, + { 0x00045 , 0x00302 , 0x000CA }, + { 0x00045 , 0x00303 , 0x01EBC }, + { 0x00045 , 0x00304 , 0x00112 }, + { 0x00045 , 0x00306 , 0x00114 }, + { 0x00045 , 0x00307 , 0x00116 }, + { 0x00045 , 0x00308 , 0x000CB }, + { 0x00045 , 0x00309 , 0x01EBA }, + { 0x00045 , 0x0030C , 0x0011A }, + { 0x00045 , 0x0030F , 0x00204 }, + { 0x00045 , 0x00311 , 0x00206 }, + { 0x00045 , 0x00323 , 0x01EB8 }, + { 0x00045 , 0x00327 , 0x00228 }, + { 0x00045 , 0x00328 , 0x00118 }, + { 0x00045 , 0x0032D , 0x01E18 }, + { 0x00045 , 0x00330 , 0x01E1A }, + { 0x00046 , 0x00307 , 0x01E1E }, + { 0x00047 , 0x00301 , 0x001F4 }, + { 0x00047 , 0x00302 , 0x0011C }, + { 0x00047 , 0x00304 , 0x01E20 }, + { 0x00047 , 0x00306 , 0x0011E }, + { 0x00047 , 0x00307 , 0x00120 }, + { 0x00047 , 0x0030C , 0x001E6 }, + { 0x00047 , 0x00327 , 0x00122 }, + { 0x00048 , 0x00302 , 0x00124 }, + { 0x00048 , 0x00307 , 0x01E22 }, + { 0x00048 , 0x00308 , 0x01E26 }, + { 0x00048 , 0x0030C , 0x0021E }, + { 0x00048 , 0x00323 , 0x01E24 }, + { 0x00048 , 0x00327 , 0x01E28 }, + { 0x00048 , 0x0032E , 0x01E2A }, + { 0x00049 , 0x00300 , 0x000CC }, + { 0x00049 , 0x00301 , 0x000CD }, + { 0x00049 , 0x00302 , 0x000CE }, + { 0x00049 , 0x00303 , 0x00128 }, + { 0x00049 , 0x00304 , 0x0012A }, + { 0x00049 , 0x00306 , 0x0012C }, + { 0x00049 , 0x00307 , 0x00130 }, + { 0x00049 , 0x00308 , 0x000CF }, + { 0x00049 , 0x00309 , 0x01EC8 }, + { 0x00049 , 0x0030C , 0x001CF }, + { 0x00049 , 0x0030F , 0x00208 }, + { 0x00049 , 0x00311 , 0x0020A }, + { 0x00049 , 0x00323 , 0x01ECA }, + { 0x00049 , 0x00328 , 0x0012E }, + { 0x00049 , 0x00330 , 0x01E2C }, + { 0x0004A , 0x00302 , 0x00134 }, + { 0x0004B , 0x00301 , 0x01E30 }, + { 0x0004B , 0x0030C , 0x001E8 }, + { 0x0004B , 0x00323 , 0x01E32 }, + { 0x0004B , 0x00327 , 0x00136 }, + { 0x0004B , 0x00331 , 0x01E34 }, + { 0x0004C , 0x00301 , 0x00139 }, + { 0x0004C , 0x0030C , 0x0013D }, + { 0x0004C , 0x00323 , 0x01E36 }, + { 0x0004C , 0x00327 , 0x0013B }, + { 0x0004C , 0x0032D , 0x01E3C }, + { 0x0004C , 0x00331 , 0x01E3A }, + { 0x0004D , 0x00301 , 0x01E3E }, + { 0x0004D , 0x00307 , 0x01E40 }, + { 0x0004D , 0x00323 , 0x01E42 }, + { 0x0004E , 0x00300 , 0x001F8 }, + { 0x0004E , 0x00301 , 0x00143 }, + { 0x0004E , 0x00303 , 0x000D1 }, + { 0x0004E , 0x00307 , 0x01E44 }, + { 0x0004E , 0x0030C , 0x00147 }, + { 0x0004E , 0x00323 , 0x01E46 }, + { 0x0004E , 0x00327 , 0x00145 }, + { 0x0004E , 0x0032D , 0x01E4A }, + { 0x0004E , 0x00331 , 0x01E48 }, + { 0x0004F , 0x00300 , 0x000D2 }, + { 0x0004F , 0x00301 , 0x000D3 }, + { 0x0004F , 0x00302 , 0x000D4 }, + { 0x0004F , 0x00303 , 0x000D5 }, + { 0x0004F , 0x00304 , 0x0014C }, + { 0x0004F , 0x00306 , 0x0014E }, + { 0x0004F , 0x00307 , 0x0022E }, + { 0x0004F , 0x00308 , 0x000D6 }, + { 0x0004F , 0x00309 , 0x01ECE }, + { 0x0004F , 0x0030B , 0x00150 }, + { 0x0004F , 0x0030C , 0x001D1 }, + { 0x0004F , 0x0030F , 0x0020C }, + { 0x0004F , 0x00311 , 0x0020E }, + { 0x0004F , 0x0031B , 0x001A0 }, + { 0x0004F , 0x00323 , 0x01ECC }, + { 0x0004F , 0x00328 , 0x001EA }, + { 0x00050 , 0x00301 , 0x01E54 }, + { 0x00050 , 0x00307 , 0x01E56 }, + { 0x00052 , 0x00301 , 0x00154 }, + { 0x00052 , 0x00307 , 0x01E58 }, + { 0x00052 , 0x0030C , 0x00158 }, + { 0x00052 , 0x0030F , 0x00210 }, + { 0x00052 , 0x00311 , 0x00212 }, + { 0x00052 , 0x00323 , 0x01E5A }, + { 0x00052 , 0x00327 , 0x00156 }, + { 0x00052 , 0x00331 , 0x01E5E }, + { 0x00053 , 0x00301 , 0x0015A }, + { 0x00053 , 0x00302 , 0x0015C }, + { 0x00053 , 0x00307 , 0x01E60 }, + { 0x00053 , 0x0030C , 0x00160 }, + { 0x00053 , 0x00323 , 0x01E62 }, + { 0x00053 , 0x00326 , 0x00218 }, + { 0x00053 , 0x00327 , 0x0015E }, + { 0x00054 , 0x00307 , 0x01E6A }, + { 0x00054 , 0x0030C , 0x00164 }, + { 0x00054 , 0x00323 , 0x01E6C }, + { 0x00054 , 0x00326 , 0x0021A }, + { 0x00054 , 0x00327 , 0x00162 }, + { 0x00054 , 0x0032D , 0x01E70 }, + { 0x00054 , 0x00331 , 0x01E6E }, + { 0x00055 , 0x00300 , 0x000D9 }, + { 0x00055 , 0x00301 , 0x000DA }, + { 0x00055 , 0x00302 , 0x000DB }, + { 0x00055 , 0x00303 , 0x00168 }, + { 0x00055 , 0x00304 , 0x0016A }, + { 0x00055 , 0x00306 , 0x0016C }, + { 0x00055 , 0x00308 , 0x000DC }, + { 0x00055 , 0x00309 , 0x01EE6 }, + { 0x00055 , 0x0030A , 0x0016E }, + { 0x00055 , 0x0030B , 0x00170 }, + { 0x00055 , 0x0030C , 0x001D3 }, + { 0x00055 , 0x0030F , 0x00214 }, + { 0x00055 , 0x00311 , 0x00216 }, + { 0x00055 , 0x0031B , 0x001AF }, + { 0x00055 , 0x00323 , 0x01EE4 }, + { 0x00055 , 0x00324 , 0x01E72 }, + { 0x00055 , 0x00328 , 0x00172 }, + { 0x00055 , 0x0032D , 0x01E76 }, + { 0x00055 , 0x00330 , 0x01E74 }, + { 0x00056 , 0x00303 , 0x01E7C }, + { 0x00056 , 0x00323 , 0x01E7E }, + { 0x00057 , 0x00300 , 0x01E80 }, + { 0x00057 , 0x00301 , 0x01E82 }, + { 0x00057 , 0x00302 , 0x00174 }, + { 0x00057 , 0x00307 , 0x01E86 }, + { 0x00057 , 0x00308 , 0x01E84 }, + { 0x00057 , 0x00323 , 0x01E88 }, + { 0x00058 , 0x00307 , 0x01E8A }, + { 0x00058 , 0x00308 , 0x01E8C }, + { 0x00059 , 0x00300 , 0x01EF2 }, + { 0x00059 , 0x00301 , 0x000DD }, + { 0x00059 , 0x00302 , 0x00176 }, + { 0x00059 , 0x00303 , 0x01EF8 }, + { 0x00059 , 0x00304 , 0x00232 }, + { 0x00059 , 0x00307 , 0x01E8E }, + { 0x00059 , 0x00308 , 0x00178 }, + { 0x00059 , 0x00309 , 0x01EF6 }, + { 0x00059 , 0x00323 , 0x01EF4 }, + { 0x0005A , 0x00301 , 0x00179 }, + { 0x0005A , 0x00302 , 0x01E90 }, + { 0x0005A , 0x00307 , 0x0017B }, + { 0x0005A , 0x0030C , 0x0017D }, + { 0x0005A , 0x00323 , 0x01E92 }, + { 0x0005A , 0x00331 , 0x01E94 }, + { 0x00061 , 0x00300 , 0x000E0 }, + { 0x00061 , 0x00301 , 0x000E1 }, + { 0x00061 , 0x00302 , 0x000E2 }, + { 0x00061 , 0x00303 , 0x000E3 }, + { 0x00061 , 0x00304 , 0x00101 }, + { 0x00061 , 0x00306 , 0x00103 }, + { 0x00061 , 0x00307 , 0x00227 }, + { 0x00061 , 0x00308 , 0x000E4 }, + { 0x00061 , 0x00309 , 0x01EA3 }, + { 0x00061 , 0x0030A , 0x000E5 }, + { 0x00061 , 0x0030C , 0x001CE }, + { 0x00061 , 0x0030F , 0x00201 }, + { 0x00061 , 0x00311 , 0x00203 }, + { 0x00061 , 0x00323 , 0x01EA1 }, + { 0x00061 , 0x00325 , 0x01E01 }, + { 0x00061 , 0x00328 , 0x00105 }, + { 0x00062 , 0x00307 , 0x01E03 }, + { 0x00062 , 0x00323 , 0x01E05 }, + { 0x00062 , 0x00331 , 0x01E07 }, + { 0x00063 , 0x00301 , 0x00107 }, + { 0x00063 , 0x00302 , 0x00109 }, + { 0x00063 , 0x00307 , 0x0010B }, + { 0x00063 , 0x0030C , 0x0010D }, + { 0x00063 , 0x00327 , 0x000E7 }, + { 0x00064 , 0x00307 , 0x01E0B }, + { 0x00064 , 0x0030C , 0x0010F }, + { 0x00064 , 0x00323 , 0x01E0D }, + { 0x00064 , 0x00327 , 0x01E11 }, + { 0x00064 , 0x0032D , 0x01E13 }, + { 0x00064 , 0x00331 , 0x01E0F }, + { 0x00065 , 0x00300 , 0x000E8 }, + { 0x00065 , 0x00301 , 0x000E9 }, + { 0x00065 , 0x00302 , 0x000EA }, + { 0x00065 , 0x00303 , 0x01EBD }, + { 0x00065 , 0x00304 , 0x00113 }, + { 0x00065 , 0x00306 , 0x00115 }, + { 0x00065 , 0x00307 , 0x00117 }, + { 0x00065 , 0x00308 , 0x000EB }, + { 0x00065 , 0x00309 , 0x01EBB }, + { 0x00065 , 0x0030C , 0x0011B }, + { 0x00065 , 0x0030F , 0x00205 }, + { 0x00065 , 0x00311 , 0x00207 }, + { 0x00065 , 0x00323 , 0x01EB9 }, + { 0x00065 , 0x00327 , 0x00229 }, + { 0x00065 , 0x00328 , 0x00119 }, + { 0x00065 , 0x0032D , 0x01E19 }, + { 0x00065 , 0x00330 , 0x01E1B }, + { 0x00066 , 0x00307 , 0x01E1F }, + { 0x00067 , 0x00301 , 0x001F5 }, + { 0x00067 , 0x00302 , 0x0011D }, + { 0x00067 , 0x00304 , 0x01E21 }, + { 0x00067 , 0x00306 , 0x0011F }, + { 0x00067 , 0x00307 , 0x00121 }, + { 0x00067 , 0x0030C , 0x001E7 }, + { 0x00067 , 0x00327 , 0x00123 }, + { 0x00068 , 0x00302 , 0x00125 }, + { 0x00068 , 0x00307 , 0x01E23 }, + { 0x00068 , 0x00308 , 0x01E27 }, + { 0x00068 , 0x0030C , 0x0021F }, + { 0x00068 , 0x00323 , 0x01E25 }, + { 0x00068 , 0x00327 , 0x01E29 }, + { 0x00068 , 0x0032E , 0x01E2B }, + { 0x00068 , 0x00331 , 0x01E96 }, + { 0x00069 , 0x00300 , 0x000EC }, + { 0x00069 , 0x00301 , 0x000ED }, + { 0x00069 , 0x00302 , 0x000EE }, + { 0x00069 , 0x00303 , 0x00129 }, + { 0x00069 , 0x00304 , 0x0012B }, + { 0x00069 , 0x00306 , 0x0012D }, + { 0x00069 , 0x00308 , 0x000EF }, + { 0x00069 , 0x00309 , 0x01EC9 }, + { 0x00069 , 0x0030C , 0x001D0 }, + { 0x00069 , 0x0030F , 0x00209 }, + { 0x00069 , 0x00311 , 0x0020B }, + { 0x00069 , 0x00323 , 0x01ECB }, + { 0x00069 , 0x00328 , 0x0012F }, + { 0x00069 , 0x00330 , 0x01E2D }, + { 0x0006A , 0x00302 , 0x00135 }, + { 0x0006A , 0x0030C , 0x001F0 }, + { 0x0006B , 0x00301 , 0x01E31 }, + { 0x0006B , 0x0030C , 0x001E9 }, + { 0x0006B , 0x00323 , 0x01E33 }, + { 0x0006B , 0x00327 , 0x00137 }, + { 0x0006B , 0x00331 , 0x01E35 }, + { 0x0006C , 0x00301 , 0x0013A }, + { 0x0006C , 0x0030C , 0x0013E }, + { 0x0006C , 0x00323 , 0x01E37 }, + { 0x0006C , 0x00327 , 0x0013C }, + { 0x0006C , 0x0032D , 0x01E3D }, + { 0x0006C , 0x00331 , 0x01E3B }, + { 0x0006D , 0x00301 , 0x01E3F }, + { 0x0006D , 0x00307 , 0x01E41 }, + { 0x0006D , 0x00323 , 0x01E43 }, + { 0x0006E , 0x00300 , 0x001F9 }, + { 0x0006E , 0x00301 , 0x00144 }, + { 0x0006E , 0x00303 , 0x000F1 }, + { 0x0006E , 0x00307 , 0x01E45 }, + { 0x0006E , 0x0030C , 0x00148 }, + { 0x0006E , 0x00323 , 0x01E47 }, + { 0x0006E , 0x00327 , 0x00146 }, + { 0x0006E , 0x0032D , 0x01E4B }, + { 0x0006E , 0x00331 , 0x01E49 }, + { 0x0006F , 0x00300 , 0x000F2 }, + { 0x0006F , 0x00301 , 0x000F3 }, + { 0x0006F , 0x00302 , 0x000F4 }, + { 0x0006F , 0x00303 , 0x000F5 }, + { 0x0006F , 0x00304 , 0x0014D }, + { 0x0006F , 0x00306 , 0x0014F }, + { 0x0006F , 0x00307 , 0x0022F }, + { 0x0006F , 0x00308 , 0x000F6 }, + { 0x0006F , 0x00309 , 0x01ECF }, + { 0x0006F , 0x0030B , 0x00151 }, + { 0x0006F , 0x0030C , 0x001D2 }, + { 0x0006F , 0x0030F , 0x0020D }, + { 0x0006F , 0x00311 , 0x0020F }, + { 0x0006F , 0x0031B , 0x001A1 }, + { 0x0006F , 0x00323 , 0x01ECD }, + { 0x0006F , 0x00328 , 0x001EB }, + { 0x00070 , 0x00301 , 0x01E55 }, + { 0x00070 , 0x00307 , 0x01E57 }, + { 0x00072 , 0x00301 , 0x00155 }, + { 0x00072 , 0x00307 , 0x01E59 }, + { 0x00072 , 0x0030C , 0x00159 }, + { 0x00072 , 0x0030F , 0x00211 }, + { 0x00072 , 0x00311 , 0x00213 }, + { 0x00072 , 0x00323 , 0x01E5B }, + { 0x00072 , 0x00327 , 0x00157 }, + { 0x00072 , 0x00331 , 0x01E5F }, + { 0x00073 , 0x00301 , 0x0015B }, + { 0x00073 , 0x00302 , 0x0015D }, + { 0x00073 , 0x00307 , 0x01E61 }, + { 0x00073 , 0x0030C , 0x00161 }, + { 0x00073 , 0x00323 , 0x01E63 }, + { 0x00073 , 0x00326 , 0x00219 }, + { 0x00073 , 0x00327 , 0x0015F }, + { 0x00074 , 0x00307 , 0x01E6B }, + { 0x00074 , 0x00308 , 0x01E97 }, + { 0x00074 , 0x0030C , 0x00165 }, + { 0x00074 , 0x00323 , 0x01E6D }, + { 0x00074 , 0x00326 , 0x0021B }, + { 0x00074 , 0x00327 , 0x00163 }, + { 0x00074 , 0x0032D , 0x01E71 }, + { 0x00074 , 0x00331 , 0x01E6F }, + { 0x00075 , 0x00300 , 0x000F9 }, + { 0x00075 , 0x00301 , 0x000FA }, + { 0x00075 , 0x00302 , 0x000FB }, + { 0x00075 , 0x00303 , 0x00169 }, + { 0x00075 , 0x00304 , 0x0016B }, + { 0x00075 , 0x00306 , 0x0016D }, + { 0x00075 , 0x00308 , 0x000FC }, + { 0x00075 , 0x00309 , 0x01EE7 }, + { 0x00075 , 0x0030A , 0x0016F }, + { 0x00075 , 0x0030B , 0x00171 }, + { 0x00075 , 0x0030C , 0x001D4 }, + { 0x00075 , 0x0030F , 0x00215 }, + { 0x00075 , 0x00311 , 0x00217 }, + { 0x00075 , 0x0031B , 0x001B0 }, + { 0x00075 , 0x00323 , 0x01EE5 }, + { 0x00075 , 0x00324 , 0x01E73 }, + { 0x00075 , 0x00328 , 0x00173 }, + { 0x00075 , 0x0032D , 0x01E77 }, + { 0x00075 , 0x00330 , 0x01E75 }, + { 0x00076 , 0x00303 , 0x01E7D }, + { 0x00076 , 0x00323 , 0x01E7F }, + { 0x00077 , 0x00300 , 0x01E81 }, + { 0x00077 , 0x00301 , 0x01E83 }, + { 0x00077 , 0x00302 , 0x00175 }, + { 0x00077 , 0x00307 , 0x01E87 }, + { 0x00077 , 0x00308 , 0x01E85 }, + { 0x00077 , 0x0030A , 0x01E98 }, + { 0x00077 , 0x00323 , 0x01E89 }, + { 0x00078 , 0x00307 , 0x01E8B }, + { 0x00078 , 0x00308 , 0x01E8D }, + { 0x00079 , 0x00300 , 0x01EF3 }, + { 0x00079 , 0x00301 , 0x000FD }, + { 0x00079 , 0x00302 , 0x00177 }, + { 0x00079 , 0x00303 , 0x01EF9 }, + { 0x00079 , 0x00304 , 0x00233 }, + { 0x00079 , 0x00307 , 0x01E8F }, + { 0x00079 , 0x00308 , 0x000FF }, + { 0x00079 , 0x00309 , 0x01EF7 }, + { 0x00079 , 0x0030A , 0x01E99 }, + { 0x00079 , 0x00323 , 0x01EF5 }, + { 0x0007A , 0x00301 , 0x0017A }, + { 0x0007A , 0x00302 , 0x01E91 }, + { 0x0007A , 0x00307 , 0x0017C }, + { 0x0007A , 0x0030C , 0x0017E }, + { 0x0007A , 0x00323 , 0x01E93 }, + { 0x0007A , 0x00331 , 0x01E95 }, + { 0x000A8 , 0x00300 , 0x01FED }, + { 0x000A8 , 0x00301 , 0x00385 }, + { 0x000A8 , 0x00342 , 0x01FC1 }, + { 0x000C2 , 0x00300 , 0x01EA6 }, + { 0x000C2 , 0x00301 , 0x01EA4 }, + { 0x000C2 , 0x00303 , 0x01EAA }, + { 0x000C2 , 0x00309 , 0x01EA8 }, + { 0x000C4 , 0x00304 , 0x001DE }, + { 0x000C5 , 0x00301 , 0x001FA }, + { 0x000C6 , 0x00301 , 0x001FC }, + { 0x000C6 , 0x00304 , 0x001E2 }, + { 0x000C7 , 0x00301 , 0x01E08 }, + { 0x000CA , 0x00300 , 0x01EC0 }, + { 0x000CA , 0x00301 , 0x01EBE }, + { 0x000CA , 0x00303 , 0x01EC4 }, + { 0x000CA , 0x00309 , 0x01EC2 }, + { 0x000CF , 0x00301 , 0x01E2E }, + { 0x000D4 , 0x00300 , 0x01ED2 }, + { 0x000D4 , 0x00301 , 0x01ED0 }, + { 0x000D4 , 0x00303 , 0x01ED6 }, + { 0x000D4 , 0x00309 , 0x01ED4 }, + { 0x000D5 , 0x00301 , 0x01E4C }, + { 0x000D5 , 0x00304 , 0x0022C }, + { 0x000D5 , 0x00308 , 0x01E4E }, + { 0x000D6 , 0x00304 , 0x0022A }, + { 0x000D8 , 0x00301 , 0x001FE }, + { 0x000DC , 0x00300 , 0x001DB }, + { 0x000DC , 0x00301 , 0x001D7 }, + { 0x000DC , 0x00304 , 0x001D5 }, + { 0x000DC , 0x0030C , 0x001D9 }, + { 0x000E2 , 0x00300 , 0x01EA7 }, + { 0x000E2 , 0x00301 , 0x01EA5 }, + { 0x000E2 , 0x00303 , 0x01EAB }, + { 0x000E2 , 0x00309 , 0x01EA9 }, + { 0x000E4 , 0x00304 , 0x001DF }, + { 0x000E5 , 0x00301 , 0x001FB }, + { 0x000E6 , 0x00301 , 0x001FD }, + { 0x000E6 , 0x00304 , 0x001E3 }, + { 0x000E7 , 0x00301 , 0x01E09 }, + { 0x000EA , 0x00300 , 0x01EC1 }, + { 0x000EA , 0x00301 , 0x01EBF }, + { 0x000EA , 0x00303 , 0x01EC5 }, + { 0x000EA , 0x00309 , 0x01EC3 }, + { 0x000EF , 0x00301 , 0x01E2F }, + { 0x000F4 , 0x00300 , 0x01ED3 }, + { 0x000F4 , 0x00301 , 0x01ED1 }, + { 0x000F4 , 0x00303 , 0x01ED7 }, + { 0x000F4 , 0x00309 , 0x01ED5 }, + { 0x000F5 , 0x00301 , 0x01E4D }, + { 0x000F5 , 0x00304 , 0x0022D }, + { 0x000F5 , 0x00308 , 0x01E4F }, + { 0x000F6 , 0x00304 , 0x0022B }, + { 0x000F8 , 0x00301 , 0x001FF }, + { 0x000FC , 0x00300 , 0x001DC }, + { 0x000FC , 0x00301 , 0x001D8 }, + { 0x000FC , 0x00304 , 0x001D6 }, + { 0x000FC , 0x0030C , 0x001DA }, + { 0x00102 , 0x00300 , 0x01EB0 }, + { 0x00102 , 0x00301 , 0x01EAE }, + { 0x00102 , 0x00303 , 0x01EB4 }, + { 0x00102 , 0x00309 , 0x01EB2 }, + { 0x00103 , 0x00300 , 0x01EB1 }, + { 0x00103 , 0x00301 , 0x01EAF }, + { 0x00103 , 0x00303 , 0x01EB5 }, + { 0x00103 , 0x00309 , 0x01EB3 }, + { 0x00112 , 0x00300 , 0x01E14 }, + { 0x00112 , 0x00301 , 0x01E16 }, + { 0x00113 , 0x00300 , 0x01E15 }, + { 0x00113 , 0x00301 , 0x01E17 }, + { 0x0014C , 0x00300 , 0x01E50 }, + { 0x0014C , 0x00301 , 0x01E52 }, + { 0x0014D , 0x00300 , 0x01E51 }, + { 0x0014D , 0x00301 , 0x01E53 }, + { 0x0015A , 0x00307 , 0x01E64 }, + { 0x0015B , 0x00307 , 0x01E65 }, + { 0x00160 , 0x00307 , 0x01E66 }, + { 0x00161 , 0x00307 , 0x01E67 }, + { 0x00168 , 0x00301 , 0x01E78 }, + { 0x00169 , 0x00301 , 0x01E79 }, + { 0x0016A , 0x00308 , 0x01E7A }, + { 0x0016B , 0x00308 , 0x01E7B }, + { 0x0017F , 0x00307 , 0x01E9B }, + { 0x001A0 , 0x00300 , 0x01EDC }, + { 0x001A0 , 0x00301 , 0x01EDA }, + { 0x001A0 , 0x00303 , 0x01EE0 }, + { 0x001A0 , 0x00309 , 0x01EDE }, + { 0x001A0 , 0x00323 , 0x01EE2 }, + { 0x001A1 , 0x00300 , 0x01EDD }, + { 0x001A1 , 0x00301 , 0x01EDB }, + { 0x001A1 , 0x00303 , 0x01EE1 }, + { 0x001A1 , 0x00309 , 0x01EDF }, + { 0x001A1 , 0x00323 , 0x01EE3 }, + { 0x001AF , 0x00300 , 0x01EEA }, + { 0x001AF , 0x00301 , 0x01EE8 }, + { 0x001AF , 0x00303 , 0x01EEE }, + { 0x001AF , 0x00309 , 0x01EEC }, + { 0x001AF , 0x00323 , 0x01EF0 }, + { 0x001B0 , 0x00300 , 0x01EEB }, + { 0x001B0 , 0x00301 , 0x01EE9 }, + { 0x001B0 , 0x00303 , 0x01EEF }, + { 0x001B0 , 0x00309 , 0x01EED }, + { 0x001B0 , 0x00323 , 0x01EF1 }, + { 0x001B7 , 0x0030C , 0x001EE }, + { 0x001EA , 0x00304 , 0x001EC }, + { 0x001EB , 0x00304 , 0x001ED }, + { 0x00226 , 0x00304 , 0x001E0 }, + { 0x00227 , 0x00304 , 0x001E1 }, + { 0x00228 , 0x00306 , 0x01E1C }, + { 0x00229 , 0x00306 , 0x01E1D }, + { 0x0022E , 0x00304 , 0x00230 }, + { 0x0022F , 0x00304 , 0x00231 }, + { 0x00292 , 0x0030C , 0x001EF }, + { 0x00391 , 0x00300 , 0x01FBA }, + { 0x00391 , 0x00301 , 0x00386 }, + { 0x00391 , 0x00304 , 0x01FB9 }, + { 0x00391 , 0x00306 , 0x01FB8 }, + { 0x00391 , 0x00313 , 0x01F08 }, + { 0x00391 , 0x00314 , 0x01F09 }, + { 0x00391 , 0x00345 , 0x01FBC }, + { 0x00395 , 0x00300 , 0x01FC8 }, + { 0x00395 , 0x00301 , 0x00388 }, + { 0x00395 , 0x00313 , 0x01F18 }, + { 0x00395 , 0x00314 , 0x01F19 }, + { 0x00397 , 0x00300 , 0x01FCA }, + { 0x00397 , 0x00301 , 0x00389 }, + { 0x00397 , 0x00313 , 0x01F28 }, + { 0x00397 , 0x00314 , 0x01F29 }, + { 0x00397 , 0x00345 , 0x01FCC }, + { 0x00399 , 0x00300 , 0x01FDA }, + { 0x00399 , 0x00301 , 0x0038A }, + { 0x00399 , 0x00304 , 0x01FD9 }, + { 0x00399 , 0x00306 , 0x01FD8 }, + { 0x00399 , 0x00308 , 0x003AA }, + { 0x00399 , 0x00313 , 0x01F38 }, + { 0x00399 , 0x00314 , 0x01F39 }, + { 0x0039F , 0x00300 , 0x01FF8 }, + { 0x0039F , 0x00301 , 0x0038C }, + { 0x0039F , 0x00313 , 0x01F48 }, + { 0x0039F , 0x00314 , 0x01F49 }, + { 0x003A1 , 0x00314 , 0x01FEC }, + { 0x003A5 , 0x00300 , 0x01FEA }, + { 0x003A5 , 0x00301 , 0x0038E }, + { 0x003A5 , 0x00304 , 0x01FE9 }, + { 0x003A5 , 0x00306 , 0x01FE8 }, + { 0x003A5 , 0x00308 , 0x003AB }, + { 0x003A5 , 0x00314 , 0x01F59 }, + { 0x003A9 , 0x00300 , 0x01FFA }, + { 0x003A9 , 0x00301 , 0x0038F }, + { 0x003A9 , 0x00313 , 0x01F68 }, + { 0x003A9 , 0x00314 , 0x01F69 }, + { 0x003A9 , 0x00345 , 0x01FFC }, + { 0x003AC , 0x00345 , 0x01FB4 }, + { 0x003AE , 0x00345 , 0x01FC4 }, + { 0x003B1 , 0x00300 , 0x01F70 }, + { 0x003B1 , 0x00301 , 0x003AC }, + { 0x003B1 , 0x00304 , 0x01FB1 }, + { 0x003B1 , 0x00306 , 0x01FB0 }, + { 0x003B1 , 0x00313 , 0x01F00 }, + { 0x003B1 , 0x00314 , 0x01F01 }, + { 0x003B1 , 0x00342 , 0x01FB6 }, + { 0x003B1 , 0x00345 , 0x01FB3 }, + { 0x003B5 , 0x00300 , 0x01F72 }, + { 0x003B5 , 0x00301 , 0x003AD }, + { 0x003B5 , 0x00313 , 0x01F10 }, + { 0x003B5 , 0x00314 , 0x01F11 }, + { 0x003B7 , 0x00300 , 0x01F74 }, + { 0x003B7 , 0x00301 , 0x003AE }, + { 0x003B7 , 0x00313 , 0x01F20 }, + { 0x003B7 , 0x00314 , 0x01F21 }, + { 0x003B7 , 0x00342 , 0x01FC6 }, + { 0x003B7 , 0x00345 , 0x01FC3 }, + { 0x003B9 , 0x00300 , 0x01F76 }, + { 0x003B9 , 0x00301 , 0x003AF }, + { 0x003B9 , 0x00304 , 0x01FD1 }, + { 0x003B9 , 0x00306 , 0x01FD0 }, + { 0x003B9 , 0x00308 , 0x003CA }, + { 0x003B9 , 0x00313 , 0x01F30 }, + { 0x003B9 , 0x00314 , 0x01F31 }, + { 0x003B9 , 0x00342 , 0x01FD6 }, + { 0x003BF , 0x00300 , 0x01F78 }, + { 0x003BF , 0x00301 , 0x003CC }, + { 0x003BF , 0x00313 , 0x01F40 }, + { 0x003BF , 0x00314 , 0x01F41 }, + { 0x003C1 , 0x00313 , 0x01FE4 }, + { 0x003C1 , 0x00314 , 0x01FE5 }, + { 0x003C5 , 0x00300 , 0x01F7A }, + { 0x003C5 , 0x00301 , 0x003CD }, + { 0x003C5 , 0x00304 , 0x01FE1 }, + { 0x003C5 , 0x00306 , 0x01FE0 }, + { 0x003C5 , 0x00308 , 0x003CB }, + { 0x003C5 , 0x00313 , 0x01F50 }, + { 0x003C5 , 0x00314 , 0x01F51 }, + { 0x003C5 , 0x00342 , 0x01FE6 }, + { 0x003C9 , 0x00300 , 0x01F7C }, + { 0x003C9 , 0x00301 , 0x003CE }, + { 0x003C9 , 0x00313 , 0x01F60 }, + { 0x003C9 , 0x00314 , 0x01F61 }, + { 0x003C9 , 0x00342 , 0x01FF6 }, + { 0x003C9 , 0x00345 , 0x01FF3 }, + { 0x003CA , 0x00300 , 0x01FD2 }, + { 0x003CA , 0x00301 , 0x00390 }, + { 0x003CA , 0x00342 , 0x01FD7 }, + { 0x003CB , 0x00300 , 0x01FE2 }, + { 0x003CB , 0x00301 , 0x003B0 }, + { 0x003CB , 0x00342 , 0x01FE7 }, + { 0x003CE , 0x00345 , 0x01FF4 }, + { 0x003D2 , 0x00301 , 0x003D3 }, + { 0x003D2 , 0x00308 , 0x003D4 }, + { 0x00406 , 0x00308 , 0x00407 }, + { 0x00410 , 0x00306 , 0x004D0 }, + { 0x00410 , 0x00308 , 0x004D2 }, + { 0x00413 , 0x00301 , 0x00403 }, + { 0x00415 , 0x00300 , 0x00400 }, + { 0x00415 , 0x00306 , 0x004D6 }, + { 0x00415 , 0x00308 , 0x00401 }, + { 0x00416 , 0x00306 , 0x004C1 }, + { 0x00416 , 0x00308 , 0x004DC }, + { 0x00417 , 0x00308 , 0x004DE }, + { 0x00418 , 0x00300 , 0x0040D }, + { 0x00418 , 0x00304 , 0x004E2 }, + { 0x00418 , 0x00306 , 0x00419 }, + { 0x00418 , 0x00308 , 0x004E4 }, + { 0x0041A , 0x00301 , 0x0040C }, + { 0x0041E , 0x00308 , 0x004E6 }, + { 0x00423 , 0x00304 , 0x004EE }, + { 0x00423 , 0x00306 , 0x0040E }, + { 0x00423 , 0x00308 , 0x004F0 }, + { 0x00423 , 0x0030B , 0x004F2 }, + { 0x00427 , 0x00308 , 0x004F4 }, + { 0x0042B , 0x00308 , 0x004F8 }, + { 0x0042D , 0x00308 , 0x004EC }, + { 0x00430 , 0x00306 , 0x004D1 }, + { 0x00430 , 0x00308 , 0x004D3 }, + { 0x00433 , 0x00301 , 0x00453 }, + { 0x00435 , 0x00300 , 0x00450 }, + { 0x00435 , 0x00306 , 0x004D7 }, + { 0x00435 , 0x00308 , 0x00451 }, + { 0x00436 , 0x00306 , 0x004C2 }, + { 0x00436 , 0x00308 , 0x004DD }, + { 0x00437 , 0x00308 , 0x004DF }, + { 0x00438 , 0x00300 , 0x0045D }, + { 0x00438 , 0x00304 , 0x004E3 }, + { 0x00438 , 0x00306 , 0x00439 }, + { 0x00438 , 0x00308 , 0x004E5 }, + { 0x0043A , 0x00301 , 0x0045C }, + { 0x0043E , 0x00308 , 0x004E7 }, + { 0x00443 , 0x00304 , 0x004EF }, + { 0x00443 , 0x00306 , 0x0045E }, + { 0x00443 , 0x00308 , 0x004F1 }, + { 0x00443 , 0x0030B , 0x004F3 }, + { 0x00447 , 0x00308 , 0x004F5 }, + { 0x0044B , 0x00308 , 0x004F9 }, + { 0x0044D , 0x00308 , 0x004ED }, + { 0x00456 , 0x00308 , 0x00457 }, + { 0x00474 , 0x0030F , 0x00476 }, + { 0x00475 , 0x0030F , 0x00477 }, + { 0x004D8 , 0x00308 , 0x004DA }, + { 0x004D9 , 0x00308 , 0x004DB }, + { 0x004E8 , 0x00308 , 0x004EA }, + { 0x004E9 , 0x00308 , 0x004EB }, + { 0x00627 , 0x00653 , 0x00622 }, + { 0x00627 , 0x00654 , 0x00623 }, + { 0x00627 , 0x00655 , 0x00625 }, + { 0x00648 , 0x00654 , 0x00624 }, + { 0x0064A , 0x00654 , 0x00626 }, + { 0x006C1 , 0x00654 , 0x006C2 }, + { 0x006D2 , 0x00654 , 0x006D3 }, + { 0x006D5 , 0x00654 , 0x006C0 }, + { 0x00928 , 0x0093C , 0x00929 }, + { 0x00930 , 0x0093C , 0x00931 }, + { 0x00933 , 0x0093C , 0x00934 }, + { 0x009C7 , 0x009BE , 0x009CB }, + { 0x009C7 , 0x009D7 , 0x009CC }, + { 0x00B47 , 0x00B3E , 0x00B4B }, + { 0x00B47 , 0x00B56 , 0x00B48 }, + { 0x00B47 , 0x00B57 , 0x00B4C }, + { 0x00B92 , 0x00BD7 , 0x00B94 }, + { 0x00BC6 , 0x00BBE , 0x00BCA }, + { 0x00BC6 , 0x00BD7 , 0x00BCC }, + { 0x00BC7 , 0x00BBE , 0x00BCB }, + { 0x00C46 , 0x00C56 , 0x00C48 }, + { 0x00CBF , 0x00CD5 , 0x00CC0 }, + { 0x00CC6 , 0x00CC2 , 0x00CCA }, + { 0x00CC6 , 0x00CD5 , 0x00CC7 }, + { 0x00CC6 , 0x00CD6 , 0x00CC8 }, + { 0x00CCA , 0x00CD5 , 0x00CCB }, + { 0x00D46 , 0x00D3E , 0x00D4A }, + { 0x00D46 , 0x00D57 , 0x00D4C }, + { 0x00D47 , 0x00D3E , 0x00D4B }, + { 0x00DD9 , 0x00DCA , 0x00DDA }, + { 0x00DD9 , 0x00DCF , 0x00DDC }, + { 0x00DD9 , 0x00DDF , 0x00DDE }, + { 0x00DDC , 0x00DCA , 0x00DDD }, + { 0x01025 , 0x0102E , 0x01026 }, + { 0x01B05 , 0x01B35 , 0x01B06 }, + { 0x01B07 , 0x01B35 , 0x01B08 }, + { 0x01B09 , 0x01B35 , 0x01B0A }, + { 0x01B0B , 0x01B35 , 0x01B0C }, + { 0x01B0D , 0x01B35 , 0x01B0E }, + { 0x01B11 , 0x01B35 , 0x01B12 }, + { 0x01B3A , 0x01B35 , 0x01B3B }, + { 0x01B3C , 0x01B35 , 0x01B3D }, + { 0x01B3E , 0x01B35 , 0x01B40 }, + { 0x01B3F , 0x01B35 , 0x01B41 }, + { 0x01B42 , 0x01B35 , 0x01B43 }, + { 0x01E36 , 0x00304 , 0x01E38 }, + { 0x01E37 , 0x00304 , 0x01E39 }, + { 0x01E5A , 0x00304 , 0x01E5C }, + { 0x01E5B , 0x00304 , 0x01E5D }, + { 0x01E62 , 0x00307 , 0x01E68 }, + { 0x01E63 , 0x00307 , 0x01E69 }, + { 0x01EA0 , 0x00302 , 0x01EAC }, + { 0x01EA0 , 0x00306 , 0x01EB6 }, + { 0x01EA1 , 0x00302 , 0x01EAD }, + { 0x01EA1 , 0x00306 , 0x01EB7 }, + { 0x01EB8 , 0x00302 , 0x01EC6 }, + { 0x01EB9 , 0x00302 , 0x01EC7 }, + { 0x01ECC , 0x00302 , 0x01ED8 }, + { 0x01ECD , 0x00302 , 0x01ED9 }, + { 0x01F00 , 0x00300 , 0x01F02 }, + { 0x01F00 , 0x00301 , 0x01F04 }, + { 0x01F00 , 0x00342 , 0x01F06 }, + { 0x01F00 , 0x00345 , 0x01F80 }, + { 0x01F01 , 0x00300 , 0x01F03 }, + { 0x01F01 , 0x00301 , 0x01F05 }, + { 0x01F01 , 0x00342 , 0x01F07 }, + { 0x01F01 , 0x00345 , 0x01F81 }, + { 0x01F02 , 0x00345 , 0x01F82 }, + { 0x01F03 , 0x00345 , 0x01F83 }, + { 0x01F04 , 0x00345 , 0x01F84 }, + { 0x01F05 , 0x00345 , 0x01F85 }, + { 0x01F06 , 0x00345 , 0x01F86 }, + { 0x01F07 , 0x00345 , 0x01F87 }, + { 0x01F08 , 0x00300 , 0x01F0A }, + { 0x01F08 , 0x00301 , 0x01F0C }, + { 0x01F08 , 0x00342 , 0x01F0E }, + { 0x01F08 , 0x00345 , 0x01F88 }, + { 0x01F09 , 0x00300 , 0x01F0B }, + { 0x01F09 , 0x00301 , 0x01F0D }, + { 0x01F09 , 0x00342 , 0x01F0F }, + { 0x01F09 , 0x00345 , 0x01F89 }, + { 0x01F0A , 0x00345 , 0x01F8A }, + { 0x01F0B , 0x00345 , 0x01F8B }, + { 0x01F0C , 0x00345 , 0x01F8C }, + { 0x01F0D , 0x00345 , 0x01F8D }, + { 0x01F0E , 0x00345 , 0x01F8E }, + { 0x01F0F , 0x00345 , 0x01F8F }, + { 0x01F10 , 0x00300 , 0x01F12 }, + { 0x01F10 , 0x00301 , 0x01F14 }, + { 0x01F11 , 0x00300 , 0x01F13 }, + { 0x01F11 , 0x00301 , 0x01F15 }, + { 0x01F18 , 0x00300 , 0x01F1A }, + { 0x01F18 , 0x00301 , 0x01F1C }, + { 0x01F19 , 0x00300 , 0x01F1B }, + { 0x01F19 , 0x00301 , 0x01F1D }, + { 0x01F20 , 0x00300 , 0x01F22 }, + { 0x01F20 , 0x00301 , 0x01F24 }, + { 0x01F20 , 0x00342 , 0x01F26 }, + { 0x01F20 , 0x00345 , 0x01F90 }, + { 0x01F21 , 0x00300 , 0x01F23 }, + { 0x01F21 , 0x00301 , 0x01F25 }, + { 0x01F21 , 0x00342 , 0x01F27 }, + { 0x01F21 , 0x00345 , 0x01F91 }, + { 0x01F22 , 0x00345 , 0x01F92 }, + { 0x01F23 , 0x00345 , 0x01F93 }, + { 0x01F24 , 0x00345 , 0x01F94 }, + { 0x01F25 , 0x00345 , 0x01F95 }, + { 0x01F26 , 0x00345 , 0x01F96 }, + { 0x01F27 , 0x00345 , 0x01F97 }, + { 0x01F28 , 0x00300 , 0x01F2A }, + { 0x01F28 , 0x00301 , 0x01F2C }, + { 0x01F28 , 0x00342 , 0x01F2E }, + { 0x01F28 , 0x00345 , 0x01F98 }, + { 0x01F29 , 0x00300 , 0x01F2B }, + { 0x01F29 , 0x00301 , 0x01F2D }, + { 0x01F29 , 0x00342 , 0x01F2F }, + { 0x01F29 , 0x00345 , 0x01F99 }, + { 0x01F2A , 0x00345 , 0x01F9A }, + { 0x01F2B , 0x00345 , 0x01F9B }, + { 0x01F2C , 0x00345 , 0x01F9C }, + { 0x01F2D , 0x00345 , 0x01F9D }, + { 0x01F2E , 0x00345 , 0x01F9E }, + { 0x01F2F , 0x00345 , 0x01F9F }, + { 0x01F30 , 0x00300 , 0x01F32 }, + { 0x01F30 , 0x00301 , 0x01F34 }, + { 0x01F30 , 0x00342 , 0x01F36 }, + { 0x01F31 , 0x00300 , 0x01F33 }, + { 0x01F31 , 0x00301 , 0x01F35 }, + { 0x01F31 , 0x00342 , 0x01F37 }, + { 0x01F38 , 0x00300 , 0x01F3A }, + { 0x01F38 , 0x00301 , 0x01F3C }, + { 0x01F38 , 0x00342 , 0x01F3E }, + { 0x01F39 , 0x00300 , 0x01F3B }, + { 0x01F39 , 0x00301 , 0x01F3D }, + { 0x01F39 , 0x00342 , 0x01F3F }, + { 0x01F40 , 0x00300 , 0x01F42 }, + { 0x01F40 , 0x00301 , 0x01F44 }, + { 0x01F41 , 0x00300 , 0x01F43 }, + { 0x01F41 , 0x00301 , 0x01F45 }, + { 0x01F48 , 0x00300 , 0x01F4A }, + { 0x01F48 , 0x00301 , 0x01F4C }, + { 0x01F49 , 0x00300 , 0x01F4B }, + { 0x01F49 , 0x00301 , 0x01F4D }, + { 0x01F50 , 0x00300 , 0x01F52 }, + { 0x01F50 , 0x00301 , 0x01F54 }, + { 0x01F50 , 0x00342 , 0x01F56 }, + { 0x01F51 , 0x00300 , 0x01F53 }, + { 0x01F51 , 0x00301 , 0x01F55 }, + { 0x01F51 , 0x00342 , 0x01F57 }, + { 0x01F59 , 0x00300 , 0x01F5B }, + { 0x01F59 , 0x00301 , 0x01F5D }, + { 0x01F59 , 0x00342 , 0x01F5F }, + { 0x01F60 , 0x00300 , 0x01F62 }, + { 0x01F60 , 0x00301 , 0x01F64 }, + { 0x01F60 , 0x00342 , 0x01F66 }, + { 0x01F60 , 0x00345 , 0x01FA0 }, + { 0x01F61 , 0x00300 , 0x01F63 }, + { 0x01F61 , 0x00301 , 0x01F65 }, + { 0x01F61 , 0x00342 , 0x01F67 }, + { 0x01F61 , 0x00345 , 0x01FA1 }, + { 0x01F62 , 0x00345 , 0x01FA2 }, + { 0x01F63 , 0x00345 , 0x01FA3 }, + { 0x01F64 , 0x00345 , 0x01FA4 }, + { 0x01F65 , 0x00345 , 0x01FA5 }, + { 0x01F66 , 0x00345 , 0x01FA6 }, + { 0x01F67 , 0x00345 , 0x01FA7 }, + { 0x01F68 , 0x00300 , 0x01F6A }, + { 0x01F68 , 0x00301 , 0x01F6C }, + { 0x01F68 , 0x00342 , 0x01F6E }, + { 0x01F68 , 0x00345 , 0x01FA8 }, + { 0x01F69 , 0x00300 , 0x01F6B }, + { 0x01F69 , 0x00301 , 0x01F6D }, + { 0x01F69 , 0x00342 , 0x01F6F }, + { 0x01F69 , 0x00345 , 0x01FA9 }, + { 0x01F6A , 0x00345 , 0x01FAA }, + { 0x01F6B , 0x00345 , 0x01FAB }, + { 0x01F6C , 0x00345 , 0x01FAC }, + { 0x01F6D , 0x00345 , 0x01FAD }, + { 0x01F6E , 0x00345 , 0x01FAE }, + { 0x01F6F , 0x00345 , 0x01FAF }, + { 0x01F70 , 0x00345 , 0x01FB2 }, + { 0x01F74 , 0x00345 , 0x01FC2 }, + { 0x01F7C , 0x00345 , 0x01FF2 }, + { 0x01FB6 , 0x00345 , 0x01FB7 }, + { 0x01FBF , 0x00300 , 0x01FCD }, + { 0x01FBF , 0x00301 , 0x01FCE }, + { 0x01FBF , 0x00342 , 0x01FCF }, + { 0x01FC6 , 0x00345 , 0x01FC7 }, + { 0x01FF6 , 0x00345 , 0x01FF7 }, + { 0x01FFE , 0x00300 , 0x01FDD }, + { 0x01FFE , 0x00301 , 0x01FDE }, + { 0x01FFE , 0x00342 , 0x01FDF }, + { 0x02190 , 0x00338 , 0x0219A }, + { 0x02192 , 0x00338 , 0x0219B }, + { 0x02194 , 0x00338 , 0x021AE }, + { 0x021D0 , 0x00338 , 0x021CD }, + { 0x021D2 , 0x00338 , 0x021CF }, + { 0x021D4 , 0x00338 , 0x021CE }, + { 0x02203 , 0x00338 , 0x02204 }, + { 0x02208 , 0x00338 , 0x02209 }, + { 0x0220B , 0x00338 , 0x0220C }, + { 0x02223 , 0x00338 , 0x02224 }, + { 0x02225 , 0x00338 , 0x02226 }, + { 0x0223C , 0x00338 , 0x02241 }, + { 0x02243 , 0x00338 , 0x02244 }, + { 0x02245 , 0x00338 , 0x02247 }, + { 0x02248 , 0x00338 , 0x02249 }, + { 0x0224D , 0x00338 , 0x0226D }, + { 0x02261 , 0x00338 , 0x02262 }, + { 0x02264 , 0x00338 , 0x02270 }, + { 0x02265 , 0x00338 , 0x02271 }, + { 0x02272 , 0x00338 , 0x02274 }, + { 0x02273 , 0x00338 , 0x02275 }, + { 0x02276 , 0x00338 , 0x02278 }, + { 0x02277 , 0x00338 , 0x02279 }, + { 0x0227A , 0x00338 , 0x02280 }, + { 0x0227B , 0x00338 , 0x02281 }, + { 0x0227C , 0x00338 , 0x022E0 }, + { 0x0227D , 0x00338 , 0x022E1 }, + { 0x02282 , 0x00338 , 0x02284 }, + { 0x02283 , 0x00338 , 0x02285 }, + { 0x02286 , 0x00338 , 0x02288 }, + { 0x02287 , 0x00338 , 0x02289 }, + { 0x02291 , 0x00338 , 0x022E2 }, + { 0x02292 , 0x00338 , 0x022E3 }, + { 0x022A2 , 0x00338 , 0x022AC }, + { 0x022A8 , 0x00338 , 0x022AD }, + { 0x022A9 , 0x00338 , 0x022AE }, + { 0x022AB , 0x00338 , 0x022AF }, + { 0x022B2 , 0x00338 , 0x022EA }, + { 0x022B3 , 0x00338 , 0x022EB }, + { 0x022B4 , 0x00338 , 0x022EC }, + { 0x022B5 , 0x00338 , 0x022ED }, + { 0x03046 , 0x03099 , 0x03094 }, + { 0x0304B , 0x03099 , 0x0304C }, + { 0x0304D , 0x03099 , 0x0304E }, + { 0x0304F , 0x03099 , 0x03050 }, + { 0x03051 , 0x03099 , 0x03052 }, + { 0x03053 , 0x03099 , 0x03054 }, + { 0x03055 , 0x03099 , 0x03056 }, + { 0x03057 , 0x03099 , 0x03058 }, + { 0x03059 , 0x03099 , 0x0305A }, + { 0x0305B , 0x03099 , 0x0305C }, + { 0x0305D , 0x03099 , 0x0305E }, + { 0x0305F , 0x03099 , 0x03060 }, + { 0x03061 , 0x03099 , 0x03062 }, + { 0x03064 , 0x03099 , 0x03065 }, + { 0x03066 , 0x03099 , 0x03067 }, + { 0x03068 , 0x03099 , 0x03069 }, + { 0x0306F , 0x03099 , 0x03070 }, + { 0x0306F , 0x0309A , 0x03071 }, + { 0x03072 , 0x03099 , 0x03073 }, + { 0x03072 , 0x0309A , 0x03074 }, + { 0x03075 , 0x03099 , 0x03076 }, + { 0x03075 , 0x0309A , 0x03077 }, + { 0x03078 , 0x03099 , 0x03079 }, + { 0x03078 , 0x0309A , 0x0307A }, + { 0x0307B , 0x03099 , 0x0307C }, + { 0x0307B , 0x0309A , 0x0307D }, + { 0x0309D , 0x03099 , 0x0309E }, + { 0x030A6 , 0x03099 , 0x030F4 }, + { 0x030AB , 0x03099 , 0x030AC }, + { 0x030AD , 0x03099 , 0x030AE }, + { 0x030AF , 0x03099 , 0x030B0 }, + { 0x030B1 , 0x03099 , 0x030B2 }, + { 0x030B3 , 0x03099 , 0x030B4 }, + { 0x030B5 , 0x03099 , 0x030B6 }, + { 0x030B7 , 0x03099 , 0x030B8 }, + { 0x030B9 , 0x03099 , 0x030BA }, + { 0x030BB , 0x03099 , 0x030BC }, + { 0x030BD , 0x03099 , 0x030BE }, + { 0x030BF , 0x03099 , 0x030C0 }, + { 0x030C1 , 0x03099 , 0x030C2 }, + { 0x030C4 , 0x03099 , 0x030C5 }, + { 0x030C6 , 0x03099 , 0x030C7 }, + { 0x030C8 , 0x03099 , 0x030C9 }, + { 0x030CF , 0x03099 , 0x030D0 }, + { 0x030CF , 0x0309A , 0x030D1 }, + { 0x030D2 , 0x03099 , 0x030D3 }, + { 0x030D2 , 0x0309A , 0x030D4 }, + { 0x030D5 , 0x03099 , 0x030D6 }, + { 0x030D5 , 0x0309A , 0x030D7 }, + { 0x030D8 , 0x03099 , 0x030D9 }, + { 0x030D8 , 0x0309A , 0x030DA }, + { 0x030DB , 0x03099 , 0x030DC }, + { 0x030DB , 0x0309A , 0x030DD }, + { 0x030EF , 0x03099 , 0x030F7 }, + { 0x030F0 , 0x03099 , 0x030F8 }, + { 0x030F1 , 0x03099 , 0x030F9 }, + { 0x030F2 , 0x03099 , 0x030FA }, + { 0x030FD , 0x03099 , 0x030FE }, + { 0x11099 , 0x110BA , 0x1109A }, + { 0x1109B , 0x110BA , 0x1109C }, + { 0x110A5 , 0x110BA , 0x110AB }, +}; + +#define CANONICAL_CLASS_MIN 0x0300 +#define CANONICAL_CLASS_MAX 0x1D244 + +#define IS_DECOMPOSABLE_BLOCK(uc) \ + (((uc)>>8) <= 0x1D2 && u_decomposable_blocks[(uc)>>8]) +static const char u_decomposable_blocks[0x1D2+1] = { + 0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,1,1,1,1,1,1,0,0, + 1,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0, + 0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, +}; + +/* Get Canonical Combining Class(CCC). */ +#define CCC(uc) \ + (((uc) > 0x1D244)?0:\ + ccc_val[ccc_val_index[ccc_index[(uc)>>8]][((uc)>>4)&0x0F]][(uc)&0x0F]) + +/* The table of the value of Canonical Cimbining Class */ +static const unsigned char ccc_val[][16] = { + /* idx=0: XXXX0 - XXXXF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=1: 00300 - 0030F */ + {230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=2: 00310 - 0031F */ + {230, 230, 230, 230, 230, 232, 220, 220, 220, 220, 232, 216, 220, 220, 220, 220 }, + /* idx=3: 00320 - 0032F */ + {220, 202, 202, 220, 220, 220, 220, 202, 202, 220, 220, 220, 220, 220, 220, 220 }, + /* idx=4: 00330 - 0033F */ + {220, 220, 220, 220, 1, 1, 1, 1, 1, 220, 220, 220, 220, 230, 230, 230 }, + /* idx=5: 00340 - 0034F */ + {230, 230, 230, 230, 230, 240, 230, 220, 220, 220, 230, 230, 230, 220, 220, 0 }, + /* idx=6: 00350 - 0035F */ + {230, 230, 230, 220, 220, 220, 220, 230, 232, 220, 220, 230, 233, 234, 234, 233 }, + /* idx=7: 00360 - 0036F */ + {234, 234, 233, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=8: 00480 - 0048F */ + {0, 0, 0, 230, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=9: 00590 - 0059F */ + {0, 220, 230, 230, 230, 230, 220, 230, 230, 230, 222, 220, 230, 230, 230, 230 }, + /* idx=10: 005A0 - 005AF */ + {230, 230, 220, 220, 220, 220, 220, 220, 230, 230, 220, 230, 230, 222, 228, 230 }, + /* idx=11: 005B0 - 005BF */ + {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 0, 23 }, + /* idx=12: 005C0 - 005CF */ + {0, 24, 25, 0, 230, 220, 0, 18, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=13: 00610 - 0061F */ + {230, 230, 230, 230, 230, 230, 230, 230, 30, 31, 32, 0, 0, 0, 0, 0 }, + /* idx=14: 00640 - 0064F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 30, 31 }, + /* idx=15: 00650 - 0065F */ + {32, 33, 34, 230, 230, 220, 220, 230, 230, 230, 230, 230, 220, 230, 230, 220 }, + /* idx=16: 00670 - 0067F */ + {35, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=17: 006D0 - 006DF */ + {0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 230, 230, 230, 0, 0, 230 }, + /* idx=18: 006E0 - 006EF */ + {230, 230, 230, 220, 230, 0, 0, 230, 230, 0, 220, 230, 230, 220, 0, 0 }, + /* idx=19: 00710 - 0071F */ + {0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=20: 00730 - 0073F */ + {230, 220, 230, 230, 220, 230, 230, 220, 220, 220, 230, 220, 220, 230, 220, 230 }, + /* idx=21: 00740 - 0074F */ + {230, 230, 220, 230, 220, 230, 220, 230, 220, 230, 230, 0, 0, 0, 0, 0 }, + /* idx=22: 007E0 - 007EF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 230 }, + /* idx=23: 007F0 - 007FF */ + {230, 230, 220, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=24: 00810 - 0081F */ + {0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 0, 230, 230, 230, 230, 230 }, + /* idx=25: 00820 - 0082F */ + {230, 230, 230, 230, 0, 230, 230, 230, 0, 230, 230, 230, 230, 230, 0, 0 }, + /* idx=26: 00850 - 0085F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220, 0, 0, 0, 0 }, + /* idx=27: 00930 - 0093F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=28: 00940 - 0094F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=29: 00950 - 0095F */ + {0, 230, 220, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=30: 009B0 - 009BF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=31: 009C0 - 009CF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=32: 00A30 - 00A3F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=33: 00A40 - 00A4F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=34: 00AB0 - 00ABF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=35: 00AC0 - 00ACF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=36: 00B30 - 00B3F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=37: 00B40 - 00B4F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=38: 00BC0 - 00BCF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=39: 00C40 - 00C4F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=40: 00C50 - 00C5F */ + {0, 0, 0, 0, 0, 84, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=41: 00CB0 - 00CBF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0 }, + /* idx=42: 00CC0 - 00CCF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=43: 00D40 - 00D4F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=44: 00DC0 - 00DCF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0 }, + /* idx=45: 00E30 - 00E3F */ + {0, 0, 0, 0, 0, 0, 0, 0, 103, 103, 9, 0, 0, 0, 0, 0 }, + /* idx=46: 00E40 - 00E4F */ + {0, 0, 0, 0, 0, 0, 0, 0, 107, 107, 107, 107, 0, 0, 0, 0 }, + /* idx=47: 00EB0 - 00EBF */ + {0, 0, 0, 0, 0, 0, 0, 0, 118, 118, 0, 0, 0, 0, 0, 0 }, + /* idx=48: 00EC0 - 00ECF */ + {0, 0, 0, 0, 0, 0, 0, 0, 122, 122, 122, 122, 0, 0, 0, 0 }, + /* idx=49: 00F10 - 00F1F */ + {0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 0, 0, 0, 0, 0, 0 }, + /* idx=50: 00F30 - 00F3F */ + {0, 0, 0, 0, 0, 220, 0, 220, 0, 216, 0, 0, 0, 0, 0, 0 }, + /* idx=51: 00F70 - 00F7F */ + {0, 129, 130, 0, 132, 0, 0, 0, 0, 0, 130, 130, 130, 130, 0, 0 }, + /* idx=52: 00F80 - 00F8F */ + {130, 0, 230, 230, 9, 0, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=53: 00FC0 - 00FCF */ + {0, 0, 0, 0, 0, 0, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=54: 01030 - 0103F */ + {0, 0, 0, 0, 0, 0, 0, 7, 0, 9, 9, 0, 0, 0, 0, 0 }, + /* idx=55: 01080 - 0108F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0 }, + /* idx=56: 01350 - 0135F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230 }, + /* idx=57: 01710 - 0171F */ + {0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=58: 01730 - 0173F */ + {0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=59: 017D0 - 017DF */ + {0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 0, 0 }, + /* idx=60: 018A0 - 018AF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 228, 0, 0, 0, 0, 0, 0 }, + /* idx=61: 01930 - 0193F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 222, 230, 220, 0, 0, 0, 0 }, + /* idx=62: 01A10 - 01A1F */ + {0, 0, 0, 0, 0, 0, 0, 230, 220, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=63: 01A60 - 01A6F */ + {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=64: 01A70 - 01A7F */ + {0, 0, 0, 0, 0, 230, 230, 230, 230, 230, 230, 230, 230, 0, 0, 220 }, + /* idx=65: 01B30 - 01B3F */ + {0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=66: 01B40 - 01B4F */ + {0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=67: 01B60 - 01B6F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 220, 230, 230, 230 }, + /* idx=68: 01B70 - 01B7F */ + {230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=69: 01BA0 - 01BAF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0 }, + /* idx=70: 01BE0 - 01BEF */ + {0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=71: 01BF0 - 01BFF */ + {0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=72: 01C30 - 01C3F */ + {0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=73: 01CD0 - 01CDF */ + {230, 230, 230, 0, 1, 220, 220, 220, 220, 220, 230, 230, 220, 220, 220, 220 }, + /* idx=74: 01CE0 - 01CEF */ + {230, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 220, 0, 0 }, + /* idx=75: 01DC0 - 01DCF */ + {230, 230, 220, 230, 230, 230, 230, 230, 230, 230, 220, 230, 230, 234, 214, 220 }, + /* idx=76: 01DD0 - 01DDF */ + {202, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=77: 01DE0 - 01DEF */ + {230, 230, 230, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=78: 01DF0 - 01DFF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233, 220, 230, 220 }, + /* idx=79: 020D0 - 020DF */ + {230, 230, 1, 1, 230, 230, 230, 230, 1, 1, 1, 230, 230, 0, 0, 0 }, + /* idx=80: 020E0 - 020EF */ + {0, 230, 0, 0, 0, 1, 1, 230, 220, 230, 1, 1, 220, 220, 220, 220 }, + /* idx=81: 020F0 - 020FF */ + {230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=82: 02CE0 - 02CEF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230 }, + /* idx=83: 02CF0 - 02CFF */ + {230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=84: 02D70 - 02D7F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9 }, + /* idx=85: 02DE0 - 02DEF */ + {230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=86: 02DF0 - 02DFF */ + {230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=87: 03020 - 0302F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 218, 228, 232, 222, 224, 224 }, + /* idx=88: 03090 - 0309F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0 }, + /* idx=89: 0A660 - 0A66F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230 }, + /* idx=90: 0A670 - 0A67F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 0, 0 }, + /* idx=91: 0A6F0 - 0A6FF */ + {230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=92: 0A800 - 0A80F */ + {0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=93: 0A8C0 - 0A8CF */ + {0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=94: 0A8E0 - 0A8EF */ + {230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230 }, + /* idx=95: 0A8F0 - 0A8FF */ + {230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=96: 0A920 - 0A92F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220, 0, 0 }, + /* idx=97: 0A950 - 0A95F */ + {0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=98: 0A9B0 - 0A9BF */ + {0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=99: 0A9C0 - 0A9CF */ + {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=100: 0AAB0 - 0AABF */ + {230, 0, 230, 230, 220, 0, 0, 230, 230, 0, 0, 0, 0, 0, 230, 230 }, + /* idx=101: 0AAC0 - 0AACF */ + {0, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=102: 0ABE0 - 0ABEF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0 }, + /* idx=103: 0FB10 - 0FB1F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 0 }, + /* idx=104: 0FE20 - 0FE2F */ + {230, 230, 230, 230, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=105: 101F0 - 101FF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 0 }, + /* idx=106: 10A00 - 10A0F */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 220, 0, 230 }, + /* idx=107: 10A30 - 10A3F */ + {0, 0, 0, 0, 0, 0, 0, 0, 230, 1, 220, 0, 0, 0, 0, 9 }, + /* idx=108: 11040 - 1104F */ + {0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=109: 110B0 - 110BF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 7, 0, 0, 0, 0, 0 }, + /* idx=110: 1D160 - 1D16F */ + {0, 0, 0, 0, 0, 216, 216, 1, 1, 1, 0, 0, 0, 226, 216, 216 }, + /* idx=111: 1D170 - 1D17F */ + {216, 216, 216, 0, 0, 0, 0, 0, 0, 0, 0, 220, 220, 220, 220, 220 }, + /* idx=112: 1D180 - 1D18F */ + {220, 220, 220, 0, 0, 230, 230, 230, 230, 230, 220, 220, 0, 0, 0, 0 }, + /* idx=113: 1D1A0 - 1D1AF */ + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 230, 230, 230, 230, 0, 0 }, + /* idx=114: 1D240 - 1D24F */ + {0, 0, 230, 230, 230, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +/* The index table to ccc_val[*][16] */ +static const unsigned char ccc_val_index[][16] = { + /* idx=0: XXX00 - XXXFF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=1: 00300 - 003FF */ + { 1, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=2: 00400 - 004FF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=3: 00500 - 005FF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 9,10,11,12, 0, 0, 0 }, + /* idx=4: 00600 - 006FF */ + { 0,13, 0, 0,14,15, 0,16, 0, 0, 0, 0, 0,17,18, 0 }, + /* idx=5: 00700 - 007FF */ + { 0,19, 0,20,21, 0, 0, 0, 0, 0, 0, 0, 0, 0,22,23 }, + /* idx=6: 00800 - 008FF */ + { 0,24,25, 0, 0,26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=7: 00900 - 009FF */ + { 0, 0, 0,27,28,29, 0, 0, 0, 0, 0,30,31, 0, 0, 0 }, + /* idx=8: 00A00 - 00AFF */ + { 0, 0, 0,32,33, 0, 0, 0, 0, 0, 0,34,35, 0, 0, 0 }, + /* idx=9: 00B00 - 00BFF */ + { 0, 0, 0,36,37, 0, 0, 0, 0, 0, 0, 0,38, 0, 0, 0 }, + /* idx=10: 00C00 - 00CFF */ + { 0, 0, 0, 0,39,40, 0, 0, 0, 0, 0,41,42, 0, 0, 0 }, + /* idx=11: 00D00 - 00DFF */ + { 0, 0, 0, 0,43, 0, 0, 0, 0, 0, 0, 0,44, 0, 0, 0 }, + /* idx=12: 00E00 - 00EFF */ + { 0, 0, 0,45,46, 0, 0, 0, 0, 0, 0,47,48, 0, 0, 0 }, + /* idx=13: 00F00 - 00FFF */ + { 0,49, 0,50, 0, 0, 0,51,52, 0, 0, 0,53, 0, 0, 0 }, + /* idx=14: 01000 - 010FF */ + { 0, 0, 0,54, 0, 0, 0, 0,55, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=15: 01300 - 013FF */ + { 0, 0, 0, 0, 0,56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=16: 01700 - 017FF */ + { 0,57, 0,58, 0, 0, 0, 0, 0, 0, 0, 0, 0,59, 0, 0 }, + /* idx=17: 01800 - 018FF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,60, 0, 0, 0, 0, 0 }, + /* idx=18: 01900 - 019FF */ + { 0, 0, 0,61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=19: 01A00 - 01AFF */ + { 0,62, 0, 0, 0, 0,63,64, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=20: 01B00 - 01BFF */ + { 0, 0, 0,65,66, 0,67,68, 0, 0,69, 0, 0, 0,70,71 }, + /* idx=21: 01C00 - 01CFF */ + { 0, 0, 0,72, 0, 0, 0, 0, 0, 0, 0, 0, 0,73,74, 0 }, + /* idx=22: 01D00 - 01DFF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,75,76,77,78 }, + /* idx=23: 02000 - 020FF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,79,80,81 }, + /* idx=24: 02C00 - 02CFF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,82,83 }, + /* idx=25: 02D00 - 02DFF */ + { 0, 0, 0, 0, 0, 0, 0,84, 0, 0, 0, 0, 0, 0,85,86 }, + /* idx=26: 03000 - 030FF */ + { 0, 0,87, 0, 0, 0, 0, 0, 0,88, 0, 0, 0, 0, 0, 0 }, + /* idx=27: 0A600 - 0A6FF */ + { 0, 0, 0, 0, 0, 0,89,90, 0, 0, 0, 0, 0, 0, 0,91 }, + /* idx=28: 0A800 - 0A8FF */ + {92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,93, 0,94,95 }, + /* idx=29: 0A900 - 0A9FF */ + { 0, 0,96, 0, 0,97, 0, 0, 0, 0, 0,98,99, 0, 0, 0 }, + /* idx=30: 0AA00 - 0AAFF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,100,101, 0, 0, 0 }, + /* idx=31: 0AB00 - 0ABFF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,102, 0 }, + /* idx=32: 0FB00 - 0FBFF */ + { 0,103, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=33: 0FE00 - 0FEFF */ + { 0, 0,104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=34: 10100 - 101FF */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,105 }, + /* idx=35: 10A00 - 10AFF */ + {106, 0, 0,107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + /* idx=36: 11000 - 110FF */ + { 0, 0, 0, 0,108, 0, 0, 0, 0, 0, 0,109, 0, 0, 0, 0 }, + /* idx=37: 1D100 - 1D1FF */ + { 0, 0, 0, 0, 0, 0,110,111,112, 0,113, 0, 0, 0, 0, 0 }, + /* idx=38: 1D200 - 1D2FF */ + { 0, 0, 0, 0,114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, +}; + +/* The index table to ccc_val_index[*][16] */ +static const unsigned char ccc_index[] = { + 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, 0, 0,15, 0, 0, 0,16, + 17,18,19,20,21,22, 0, 0,23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,24,25, 0, 0, + 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,27, 0, + 28,29,30,31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,32, 0, 0,33, 0, 0,34, 0, 0, 0, 0, 0, 0, + 0, 0,35, 0, 0, 0, 0, 0,36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,37,38,}; + +#endif /* ARCHIVE_STRING_COMPOSITION_H_INCLUDED */ diff --git a/libarchive/archive_string_sprintf.c b/libarchive/archive_string_sprintf.c new file mode 100644 index 0000000..7d7d971 --- /dev/null +++ b/libarchive/archive_string_sprintf.c @@ -0,0 +1,186 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_string_sprintf.c 189435 2009-03-06 05:14:55Z kientzle $"); + +/* + * The use of printf()-family functions can be troublesome + * for space-constrained applications. In addition, correctly + * implementing this function in terms of vsnprintf() requires + * two calls (one to determine the size, another to format the + * result), which in turn requires duplicating the argument list + * using va_copy, which isn't yet universally available. + * + * So, I've implemented a bare minimum of printf()-like capability + * here. This is only used to format error messages, so doesn't + * require any floating-point support or field-width handling. + */ + +#include + +#include "archive_string.h" +#include "archive_private.h" + +/* + * Utility functions to format signed/unsigned integers and append + * them to an archive_string. + */ +static void +append_uint(struct archive_string *as, uintmax_t d, unsigned base) +{ + static const char *digits = "0123456789abcdef"; + if (d >= base) + append_uint(as, d/base, base); + archive_strappend_char(as, digits[d % base]); +} + +static void +append_int(struct archive_string *as, intmax_t d, unsigned base) +{ + uintmax_t ud; + + if (d < 0) { + archive_strappend_char(as, '-'); + ud = (d == INTMAX_MIN) ? (uintmax_t)(INTMAX_MAX) + 1 : (uintmax_t)(-d); + } else + ud = d; + append_uint(as, ud, base); +} + + +void +archive_string_sprintf(struct archive_string *as, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + archive_string_vsprintf(as, fmt, ap); + va_end(ap); +} + +/* + * Like 'vsprintf', but ensures the target is big enough, resizing if + * necessary. + */ +void +archive_string_vsprintf(struct archive_string *as, const char *fmt, + va_list ap) +{ + char long_flag; + intmax_t s; /* Signed integer temp. */ + uintmax_t u; /* Unsigned integer temp. */ + const char *p, *p2; + const wchar_t *pw; + + if (archive_string_ensure(as, 64) == NULL) + __archive_errx(1, "Out of memory"); + + if (fmt == NULL) { + as->s[0] = 0; + return; + } + + for (p = fmt; *p != '\0'; p++) { + const char *saved_p = p; + + if (*p != '%') { + archive_strappend_char(as, *p); + continue; + } + + p++; + + long_flag = '\0'; + switch(*p) { + case 'j': + case 'l': + case 'z': + long_flag = *p; + p++; + break; + } + + switch (*p) { + case '%': + archive_strappend_char(as, '%'); + break; + case 'c': + s = va_arg(ap, int); + archive_strappend_char(as, s); + break; + case 'd': + switch(long_flag) { + case 'j': s = va_arg(ap, intmax_t); break; + case 'l': s = va_arg(ap, long); break; + case 'z': s = va_arg(ap, ssize_t); break; + default: s = va_arg(ap, int); break; + } + append_int(as, s, 10); + break; + case 's': + switch(long_flag) { + case 'l': + pw = va_arg(ap, wchar_t *); + if (pw == NULL) + pw = L"(null)"; + archive_string_append_from_wcs(as, pw, wcslen(pw)); + break; + default: + p2 = va_arg(ap, char *); + if (p2 == NULL) + p2 = "(null)"; + archive_strcat(as, p2); + break; + } + break; + case 'S': + pw = va_arg(ap, wchar_t *); + if (pw == NULL) + pw = L"(null)"; + archive_string_append_from_wcs(as, pw, wcslen(pw)); + break; + case 'o': case 'u': case 'x': case 'X': + /* Common handling for unsigned integer formats. */ + switch(long_flag) { + case 'j': u = va_arg(ap, uintmax_t); break; + case 'l': u = va_arg(ap, unsigned long); break; + case 'z': u = va_arg(ap, size_t); break; + default: u = va_arg(ap, unsigned int); break; + } + /* Format it in the correct base. */ + switch (*p) { + case 'o': append_uint(as, u, 8); break; + case 'u': append_uint(as, u, 10); break; + default: append_uint(as, u, 16); break; + } + break; + default: + /* Rewind and print the initial '%' literally. */ + p = saved_p; + archive_strappend_char(as, *p); + } + } +} diff --git a/libarchive/archive_util.3 b/libarchive/archive_util.3 new file mode 100644 index 0000000..e07ac95 --- /dev/null +++ b/libarchive/archive_util.3 @@ -0,0 +1,222 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_util.3 201098 2009-12-28 02:58:14Z kientzle $ +.\" +.Dd January 8, 2005 +.Dt archive_util 3 +.Os +.Sh NAME +.Nm archive_clear_error , +.Nm archive_compression , +.Nm archive_compression_name , +.Nm archive_copy_error , +.Nm archive_errno , +.Nm archive_error_string , +.Nm archive_file_count , +.Nm archive_filter_code , +.Nm archive_filter_count , +.Nm archive_filter_name , +.Nm archive_format , +.Nm archive_format_name , +.Nm archive_position , +.Nm archive_set_error +.Nd libarchive utility functions +.Sh SYNOPSIS +.In archive.h +.Ft void +.Fn archive_clear_error "struct archive *" +.Ft int +.Fn archive_compression "struct archive *" +.Ft const char * +.Fn archive_compression_name "struct archive *" +.Ft void +.Fn archive_copy_error "struct archive *" "struct archive *" +.Ft int +.Fn archive_errno "struct archive *" +.Ft const char * +.Fn archive_error_string "struct archive *" +.Ft int +.Fn archive_file_count "struct archive *" +.Ft int +.Fn archive_filter_code "struct archive *" "int" +.Ft int +.Fn archive_filter_count "struct archive *" "int" +.Ft const char * +.Fn archive_filter_name "struct archive *" "int" +.Ft int +.Fn archive_format "struct archive *" +.Ft const char * +.Fn archive_format_name "struct archive *" +.Ft int64_t +.Fn archive_position "struct archive *" "int" +.Ft void +.Fo archive_set_error +.Fa "struct archive *" +.Fa "int error_code" +.Fa "const char *fmt" +.Fa "..." +.Fc +.Sh DESCRIPTION +These functions provide access to various information about the +.Tn struct archive +object used in the +.Xr libarchive 3 +library. +.Bl -tag -compact -width indent +.It Fn archive_clear_error +Clears any error information left over from a previous call. +Not generally used in client code. +.It Fn archive_compression +Synonym for +.Fn archive_filter_code(a, 0) . +.It Fn archive_compression_name +Synonym for +.Fn archive_filter_name(a, 0) . +.It Fn archive_copy_error +Copies error information from one archive to another. +.It Fn archive_errno +Returns a numeric error code (see +.Xr errno 2 ) +indicating the reason for the most recent error return. +Note that this can not be reliably used to detect whether an +error has occurred. +It should be used only after another libarchive function +has returned an error status. +.It Fn archive_error_string +Returns a textual error message suitable for display. +The error message here is usually more specific than that +obtained from passing the result of +.Fn archive_errno +to +.Xr strerror 3 . +.It Fn archive_file_count +Returns a count of the number of files processed by this archive object. +The count is incremented by calls to +.Xr archive_write_header 3 +or +.Xr archive_read_next_header 3 . +.It Fn archive_filter_code +Returns a numeric code identifying the indicated filter. +See +.Fn archive_filter_count +for details of the numbering. +.It Fn archive_filter_count +Returns the number of filters in the current pipeline. +For read archive handles, these filters are added automatically +by the automatic format detection. +For write archive handles, these filters are added by calls to the various +.Fn archive_write_add_filter_XXX +functions. +Filters in the resulting pipeline are numbered so that filter 0 +is the filter closest to the format handler. +As a convenience, functions that expect a filter number will +accept -1 as a synonym for the highest-numbered filter. +.Pp +For example, when reading a uuencoded gzipped tar archive, there +are three filters: +filter 0 is the gunzip filter, +filter 1 is the uudecode filter, +and filter 2 is the pseudo-filter that wraps the archive read functions. +In this case, requesting +.Fn archive_position(a, -1) +would be a synonym for +.Fn archive_position(a, 2) +which would return the number of bytes currently read from the archive, while +.Fn archive_position(a, 1) +would return the number of bytes after uudecoding, and +.Fn archive_position(a, 0) +would return the number of bytes after decompression. +.It Fn archive_filter_name +Returns a textual name identifying the indicated filter. +See +.Fn archive_filter_count +for details of the numbering. +.It Fn archive_format +Returns a numeric code indicating the format of the current +archive entry. +This value is set by a successful call to +.Fn archive_read_next_header . +Note that it is common for this value to change from +entry to entry. +For example, a tar archive might have several entries that +utilize GNU tar extensions and several entries that do not. +These entries will have different format codes. +.It Fn archive_format_name +A textual description of the format of the current entry. +.It Fn archive_position +Returns the number of bytes read from or written to the indicated filter. +In particular, +.Fn archive_position(a, 0) +returns the number of bytes read or written by the format handler, while +.Fn archive_position(a, -1) +returns the number of bytes read or written to the archive. +See +.Fn archive_filter_count +for details of the numbering here. +.It Fn archive_set_error +Sets the numeric error code and error description that will be returned +by +.Fn archive_errno +and +.Fn archive_error_string . +This function should be used within I/O callbacks to set system-specific +error codes and error descriptions. +This function accepts a printf-like format string and arguments. +However, you should be careful to use only the following printf +format specifiers: +.Dq %c , +.Dq %d , +.Dq %jd , +.Dq %jo , +.Dq %ju , +.Dq %jx , +.Dq %ld , +.Dq %lo , +.Dq %lu , +.Dq %lx , +.Dq %o , +.Dq %u , +.Dq %s , +.Dq %x , +.Dq %% . +Field-width specifiers and other printf features are +not uniformly supported and should not be used. +.El +.Sh SEE ALSO +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr libarchive 3 , +.Xr printf 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . diff --git a/libarchive/archive_util.c b/libarchive/archive_util.c new file mode 100644 index 0000000..e0852a3 --- /dev/null +++ b/libarchive/archive_util.c @@ -0,0 +1,465 @@ +/*- + * Copyright (c) 2009,2010 Michihiro NAKAJIMA + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_util.c 201098 2009-12-28 02:58:14Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#if defined(HAVE_WINCRYPT_H) && !defined(__CYGWIN__) +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_string.h" + +/* Generic initialization of 'struct archive' objects. */ +int +__archive_clean(struct archive *a) +{ + archive_string_conversion_free(a); + return (ARCHIVE_OK); +} + +int +archive_version_number(void) +{ + return (ARCHIVE_VERSION_NUMBER); +} + +const char * +archive_version_string(void) +{ + return (ARCHIVE_VERSION_STRING); +} + +int +archive_errno(struct archive *a) +{ + return (a->archive_error_number); +} + +const char * +archive_error_string(struct archive *a) +{ + + if (a->error != NULL && *a->error != '\0') + return (a->error); + else + return (NULL); +} + +int +archive_file_count(struct archive *a) +{ + return (a->file_count); +} + +int +archive_format(struct archive *a) +{ + return (a->archive_format); +} + +const char * +archive_format_name(struct archive *a) +{ + return (a->archive_format_name); +} + + +int +archive_compression(struct archive *a) +{ + return archive_filter_code(a, 0); +} + +const char * +archive_compression_name(struct archive *a) +{ + return archive_filter_name(a, 0); +} + + +/* + * Return a count of the number of compressed bytes processed. + */ +int64_t +archive_position_compressed(struct archive *a) +{ + return archive_filter_bytes(a, -1); +} + +/* + * Return a count of the number of uncompressed bytes processed. + */ +int64_t +archive_position_uncompressed(struct archive *a) +{ + return archive_filter_bytes(a, 0); +} + +void +archive_clear_error(struct archive *a) +{ + archive_string_empty(&a->error_string); + a->error = NULL; + a->archive_error_number = 0; +} + +void +archive_set_error(struct archive *a, int error_number, const char *fmt, ...) +{ + va_list ap; + + a->archive_error_number = error_number; + if (fmt == NULL) { + a->error = NULL; + return; + } + + archive_string_empty(&(a->error_string)); + va_start(ap, fmt); + archive_string_vsprintf(&(a->error_string), fmt, ap); + va_end(ap); + a->error = a->error_string.s; +} + +void +archive_copy_error(struct archive *dest, struct archive *src) +{ + dest->archive_error_number = src->archive_error_number; + + archive_string_copy(&dest->error_string, &src->error_string); + dest->error = dest->error_string.s; +} + +void +__archive_errx(int retvalue, const char *msg) +{ + static const char *msg1 = "Fatal Internal Error in libarchive: "; + size_t s; + + s = write(2, msg1, strlen(msg1)); + (void)s; /* UNUSED */ + s = write(2, msg, strlen(msg)); + (void)s; /* UNUSED */ + s = write(2, "\n", 1); + (void)s; /* UNUSED */ + exit(retvalue); +} + +/* + * Create a temporary file + */ +#if defined(_WIN32) && !defined(__CYGWIN__) + +/* + * Do not use Windows tmpfile() function. + * It will make a temporary file under the root directory + * and it'll cause permission error if a user who is + * non-Administrator creates temporary files. + * Also Windows version of mktemp family including _mktemp_s + * are not secure. + */ +int +__archive_mktemp(const char *tmpdir) +{ + static const wchar_t num[] = { + L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', + L'8', L'9', L'A', L'B', L'C', L'D', L'E', L'F', + L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', + L'O', L'P', L'Q', L'R', L'S', L'T', L'U', L'V', + L'W', L'X', L'Y', L'Z', L'a', L'b', L'c', L'd', + L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l', + L'm', L'n', L'o', L'p', L'q', L'r', L's', L't', + L'u', L'v', L'w', L'x', L'y', L'z' + }; + HCRYPTPROV hProv; + struct archive_wstring temp_name; + wchar_t *ws; + DWORD attr; + wchar_t *xp, *ep; + int fd; + + hProv = (HCRYPTPROV)NULL; + fd = -1; + ws = NULL; + archive_string_init(&temp_name); + + /* Get a temporary directory. */ + if (tmpdir == NULL) { + size_t l; + wchar_t *tmp; + + l = GetTempPathW(0, NULL); + if (l == 0) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + tmp = malloc(l*sizeof(wchar_t)); + if (tmp == NULL) { + errno = ENOMEM; + goto exit_tmpfile; + } + GetTempPathW(l, tmp); + archive_wstrcpy(&temp_name, tmp); + free(tmp); + } else { + archive_wstring_append_from_mbs(&temp_name, tmpdir, + strlen(tmpdir)); + if (temp_name.s[temp_name.length-1] != L'/') + archive_wstrappend_wchar(&temp_name, L'/'); + } + + /* Check if temp_name is a directory. */ + attr = GetFileAttributesW(temp_name.s); + if (attr == (DWORD)-1) { + if (GetLastError() != ERROR_FILE_NOT_FOUND) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + ws = __la_win_permissive_name_w(temp_name.s); + if (ws == NULL) { + errno = EINVAL; + goto exit_tmpfile; + } + attr = GetFileAttributesW(ws); + if (attr == (DWORD)-1) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + } + if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { + errno = ENOTDIR; + goto exit_tmpfile; + } + + /* + * Create a temporary file. + */ + archive_wstrcat(&temp_name, L"libarchive_"); + xp = temp_name.s + archive_strlen(&temp_name); + archive_wstrcat(&temp_name, L"XXXXXXXXXX"); + ep = temp_name.s + archive_strlen(&temp_name); + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + + for (;;) { + wchar_t *p; + HANDLE h; + + /* Generate a random file name through CryptGenRandom(). */ + p = xp; + if (!CryptGenRandom(hProv, (ep - p)*sizeof(wchar_t), (BYTE*)p)) { + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + for (; p < ep; p++) + *p = num[((DWORD)*p) % (sizeof(num)/sizeof(num[0]))]; + + free(ws); + ws = __la_win_permissive_name_w(temp_name.s); + if (ws == NULL) { + errno = EINVAL; + goto exit_tmpfile; + } + /* Specifies FILE_FLAG_DELETE_ON_CLOSE flag is to + * delete this temporary file immediately when this + * file closed. */ + h = CreateFileW(ws, + GENERIC_READ | GENERIC_WRITE | DELETE, + 0,/* Not share */ + NULL, + CREATE_NEW,/* Create a new file only */ + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + NULL); + if (h == INVALID_HANDLE_VALUE) { + /* The same file already exists. retry with + * a new filename. */ + if (GetLastError() == ERROR_FILE_EXISTS) + continue; + /* Otherwise, fail creation temporary file. */ + la_dosmaperr(GetLastError()); + goto exit_tmpfile; + } + fd = _open_osfhandle((intptr_t)h, _O_BINARY | _O_RDWR); + if (fd == -1) { + CloseHandle(h); + goto exit_tmpfile; + } else + break;/* success! */ + } +exit_tmpfile: + if (hProv != (HCRYPTPROV)NULL) + CryptReleaseContext(hProv, 0); + free(ws); + archive_wstring_free(&temp_name); + return (fd); +} + +#else + +static int +get_tempdir(struct archive_string *temppath) +{ + const char *tmp; + + tmp = getenv("TMPDIR"); + if (tmp == NULL) +#ifdef _PATH_TMP + tmp = _PATH_TMP; +#else + tmp = "/tmp"; +#endif + archive_strcpy(temppath, tmp); + if (temppath->s[temppath->length-1] != '/') + archive_strappend_char(temppath, '/'); + return (ARCHIVE_OK); +} + +#if defined(HAVE_MKSTEMP) + +/* + * We can use mkstemp(). + */ + +int +__archive_mktemp(const char *tmpdir) +{ + struct archive_string temp_name; + int fd = -1; + + archive_string_init(&temp_name); + if (tmpdir == NULL) { + if (get_tempdir(&temp_name) != ARCHIVE_OK) + goto exit_tmpfile; + } else { + archive_strcpy(&temp_name, tmpdir); + if (temp_name.s[temp_name.length-1] != '/') + archive_strappend_char(&temp_name, '/'); + } + archive_strcat(&temp_name, "libarchive_XXXXXX"); + fd = mkstemp(temp_name.s); + if (fd < 0) + goto exit_tmpfile; + unlink(temp_name.s); +exit_tmpfile: + archive_string_free(&temp_name); + return (fd); +} + +#else + +/* + * We use a private routine. + */ + +int +__archive_mktemp(const char *tmpdir) +{ + static const char num[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + struct archive_string temp_name; + struct stat st; + int fd; + char *tp, *ep; + unsigned seed; + + fd = -1; + archive_string_init(&temp_name); + if (tmpdir == NULL) { + if (get_tempdir(&temp_name) != ARCHIVE_OK) + goto exit_tmpfile; + } else + archive_strcpy(&temp_name, tmpdir); + if (temp_name.s[temp_name.length-1] == '/') { + temp_name.s[temp_name.length-1] = '\0'; + temp_name.length --; + } + if (stat(temp_name.s, &st) < 0) + goto exit_tmpfile; + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + goto exit_tmpfile; + } + archive_strcat(&temp_name, "/libarchive_"); + tp = temp_name.s + archive_strlen(&temp_name); + archive_strcat(&temp_name, "XXXXXXXXXX"); + ep = temp_name.s + archive_strlen(&temp_name); + + fd = open("/dev/random", O_RDONLY); + if (fd < 0) + seed = time(NULL); + else { + if (read(fd, &seed, sizeof(seed)) < 0) + seed = time(NULL); + close(fd); + } + do { + char *p; + + p = tp; + while (p < ep) + *p++ = num[((unsigned)rand_r(&seed)) % sizeof(num)]; + fd = open(temp_name.s, O_CREAT | O_EXCL | O_RDWR, 0600); + } while (fd < 0 && errno == EEXIST); + if (fd < 0) + goto exit_tmpfile; + unlink(temp_name.s); +exit_tmpfile: + archive_string_free(&temp_name); + return (fd); +} + +#endif /* HAVE_MKSTEMP */ +#endif /* !_WIN32 || __CYGWIN__ */ diff --git a/libarchive/archive_virtual.c b/libarchive/archive_virtual.c new file mode 100644 index 0000000..752dc17 --- /dev/null +++ b/libarchive/archive_virtual.c @@ -0,0 +1,147 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_virtual.c 201098 2009-12-28 02:58:14Z kientzle $"); + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" + +int +archive_filter_code(struct archive *a, int n) +{ + return ((a->vtable->archive_filter_code)(a, n)); +} + +int +archive_filter_count(struct archive *a) +{ + return ((a->vtable->archive_filter_count)(a)); +} + +const char * +archive_filter_name(struct archive *a, int n) +{ + return ((a->vtable->archive_filter_name)(a, n)); +} + +int64_t +archive_filter_bytes(struct archive *a, int n) +{ + return ((a->vtable->archive_filter_bytes)(a, n)); +} + +int +archive_write_close(struct archive *a) +{ + return ((a->vtable->archive_close)(a)); +} + +int +archive_read_close(struct archive *a) +{ + return ((a->vtable->archive_close)(a)); +} + +int +archive_write_free(struct archive *a) +{ + return ((a->vtable->archive_free)(a)); +} + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* For backwards compatibility; will be removed with libarchive 4.0. */ +int +archive_write_finish(struct archive *a) +{ + return ((a->vtable->archive_free)(a)); +} +#endif + +int +archive_read_free(struct archive *a) +{ + return ((a->vtable->archive_free)(a)); +} + +#if ARCHIVE_VERSION_NUMBER < 4000000 +/* For backwards compatibility; will be removed with libarchive 4.0. */ +int +archive_read_finish(struct archive *a) +{ + return ((a->vtable->archive_free)(a)); +} +#endif + +int +archive_write_header(struct archive *a, struct archive_entry *entry) +{ + ++a->file_count; + return ((a->vtable->archive_write_header)(a, entry)); +} + +int +archive_write_finish_entry(struct archive *a) +{ + return ((a->vtable->archive_write_finish_entry)(a)); +} + +ssize_t +archive_write_data(struct archive *a, const void *buff, size_t s) +{ + return ((a->vtable->archive_write_data)(a, buff, s)); +} + +ssize_t +archive_write_data_block(struct archive *a, const void *buff, size_t s, int64_t o) +{ + if (a->vtable->archive_write_data_block == NULL) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "archive_write_data_block not supported"); + a->state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + return ((a->vtable->archive_write_data_block)(a, buff, s, o)); +} + +int +archive_read_next_header(struct archive *a, struct archive_entry **entry) +{ + return ((a->vtable->archive_read_next_header)(a, entry)); +} + +int +archive_read_next_header2(struct archive *a, struct archive_entry *entry) +{ + return ((a->vtable->archive_read_next_header2)(a, entry)); +} + +int +archive_read_data_block(struct archive *a, + const void **buff, size_t *s, int64_t *o) +{ + return ((a->vtable->archive_read_data_block)(a, buff, s, o)); +} diff --git a/libarchive/archive_windows.c b/libarchive/archive_windows.c new file mode 100644 index 0000000..af104b8 --- /dev/null +++ b/libarchive/archive_windows.c @@ -0,0 +1,794 @@ +/*- + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * Copyright (c) 2003-2007 Kees Zeelenberg + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD$ + */ + +/* + * A set of compatibility glue for building libarchive on Windows platforms. + * + * Originally created as "libarchive-nonposix.c" by Kees Zeelenberg + * for the GnuWin32 project, trimmed significantly by Tim Kientzle. + * + * Much of the original file was unnecessary for libarchive, because + * many of the features it emulated were not strictly necessary for + * libarchive. I hope for this to shrink further as libarchive + * internals are gradually reworked to sit more naturally on both + * POSIX and Windows. Any ideas for this are greatly appreciated. + * + * The biggest remaining issue is the dev/ino emulation; libarchive + * has a couple of public APIs that rely on dev/ino uniquely + * identifying a file. This doesn't match well with Windows. I'm + * considering alternative APIs. + */ + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#include "archive_platform.h" +#include "archive_private.h" +#include +#include +#include +#ifdef HAVE_SYS_UTIME_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) + +struct ustat { + int64_t st_atime; + uint32_t st_atime_nsec; + int64_t st_ctime; + uint32_t st_ctime_nsec; + int64_t st_mtime; + uint32_t st_mtime_nsec; + gid_t st_gid; + /* 64bits ino */ + int64_t st_ino; + mode_t st_mode; + uint32_t st_nlink; + uint64_t st_size; + uid_t st_uid; + dev_t st_dev; + dev_t st_rdev; +}; + +/* Transform 64-bits ino into 32-bits by hashing. + * You do not forget that really unique number size is 64-bits. + */ +#define INOSIZE (8*sizeof(ino_t)) /* 32 */ +static __inline ino_t +getino(struct ustat *ub) +{ + ULARGE_INTEGER ino64; + ino64.QuadPart = ub->st_ino; + /* I don't know this hashing is correct way */ + return (ino64.LowPart ^ (ino64.LowPart >> INOSIZE)); +} + +/* + * Prepend "\\?\" to the path name and convert it to unicode to permit + * an extended-length path for a maximum total path length of 32767 + * characters. + * see also http://msdn.microsoft.com/en-us/library/aa365247.aspx + */ +wchar_t * +__la_win_permissive_name(const char *name) +{ + wchar_t *wn; + wchar_t *ws; + size_t ll; + + ll = strlen(name); + wn = malloc((ll + 1) * sizeof(wchar_t)); + if (wn == NULL) + return (NULL); + ll = mbstowcs(wn, name, ll); + if (ll == (size_t)-1) { + free(wn); + return (NULL); + } + wn[ll] = L'\0'; + ws = __la_win_permissive_name_w(wn); + free(wn); + return (ws); +} + +wchar_t * +__la_win_permissive_name_w(const wchar_t *wname) +{ + wchar_t *wn, *wnp; + wchar_t *ws, *wsp; + DWORD l, len, slen; + int unc; + + /* Get a full-pathname. */ + l = GetFullPathNameW(wname, 0, NULL, NULL); + if (l == 0) + return (NULL); + wnp = malloc(l * sizeof(wchar_t)); + if (wnp == NULL) + return (NULL); + len = GetFullPathNameW(wname, l, wnp, NULL); + wn = wnp; + + if (wnp[0] == L'\\' && wnp[1] == L'\\' && + wnp[2] == L'?' && wnp[3] == L'\\') + /* We have already a permissive name. */ + return (wn); + + if (wnp[0] == L'\\' && wnp[1] == L'\\' && + wnp[2] == L'.' && wnp[3] == L'\\') { + /* This is a device name */ + if (((wnp[4] >= L'a' && wnp[4] <= L'z') || + (wnp[4] >= L'A' && wnp[4] <= L'Z')) && + wnp[5] == L':' && wnp[6] == L'\\') + wnp[2] = L'?';/* Not device name. */ + return (wn); + } + + unc = 0; + if (wnp[0] == L'\\' && wnp[1] == L'\\' && wnp[2] != L'\\') { + wchar_t *p = &wnp[2]; + + /* Skip server-name letters. */ + while (*p != L'\\' && *p != L'\0') + ++p; + if (*p == L'\\') { + wchar_t *rp = ++p; + /* Skip share-name letters. */ + while (*p != L'\\' && *p != L'\0') + ++p; + if (*p == L'\\' && p != rp) { + /* Now, match patterns such as + * "\\server-name\share-name\" */ + wnp += 2; + len -= 2; + unc = 1; + } + } + } + + slen = 4 + (unc * 4) + len + 1; + ws = wsp = malloc(slen * sizeof(wchar_t)); + if (ws == NULL) { + free(wn); + return (NULL); + } + /* prepend "\\?\" */ + wcsncpy(wsp, L"\\\\?\\", 4); + wsp += 4; + slen -= 4; + if (unc) { + /* append "UNC\" ---> "\\?\UNC\" */ + wcsncpy(wsp, L"UNC\\", 4); + wsp += 4; + slen -= 4; + } + wcsncpy(wsp, wnp, slen); + wsp[slen - 1] = L'\0'; /* Ensure null termination. */ + free(wn); + return (ws); +} + +/* + * Create a file handle. + * This can exceed MAX_PATH limitation. + */ +static HANDLE +la_CreateFile(const char *path, DWORD dwDesiredAccess, DWORD dwShareMode, + LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, + DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) +{ + wchar_t *wpath; + HANDLE handle; + + handle = CreateFileA(path, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + if (handle != INVALID_HANDLE_VALUE) + return (handle); + if (GetLastError() != ERROR_PATH_NOT_FOUND) + return (handle); + wpath = __la_win_permissive_name(path); + if (wpath == NULL) + return (handle); + handle = CreateFileW(wpath, dwDesiredAccess, dwShareMode, + lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, + hTemplateFile); + free(wpath); + return (handle); +} + +/* + * This fcntl is limited implementation. + */ +int +__la_fcntl(int fd, int cmd, int val) +{ + HANDLE handle; + + handle = (HANDLE)_get_osfhandle(fd); + if (GetFileType(handle) == FILE_TYPE_PIPE) { + if (cmd == F_SETFL && val == 0) { + DWORD mode = PIPE_WAIT; + if (SetNamedPipeHandleState( + handle, &mode, NULL, NULL) != 0) + return (0); + } + } + errno = EINVAL; + return (-1); +} + +/* This can exceed MAX_PATH limitation. */ +int +__la_open(const char *path, int flags, ...) +{ + va_list ap; + wchar_t *ws; + int r, pmode; + DWORD attr; + + va_start(ap, flags); + pmode = va_arg(ap, int); + va_end(ap); + ws = NULL; + if ((flags & ~O_BINARY) == O_RDONLY) { + /* + * When we open a directory, _open function returns + * "Permission denied" error. + */ + attr = GetFileAttributesA(path); + if (attr == (DWORD)-1 && GetLastError() == ERROR_PATH_NOT_FOUND) { + ws = __la_win_permissive_name(path); + if (ws == NULL) { + errno = EINVAL; + return (-1); + } + attr = GetFileAttributesW(ws); + } + if (attr == (DWORD)-1) { + la_dosmaperr(GetLastError()); + free(ws); + return (-1); + } + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + HANDLE handle; + + if (ws != NULL) + handle = CreateFileW(ws, 0, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_ATTRIBUTE_READONLY, + NULL); + else + handle = CreateFileA(path, 0, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_ATTRIBUTE_READONLY, + NULL); + free(ws); + if (handle == INVALID_HANDLE_VALUE) { + la_dosmaperr(GetLastError()); + return (-1); + } + r = _open_osfhandle((intptr_t)handle, _O_RDONLY); + return (r); + } + } + if (ws == NULL) { +#if defined(__BORLANDC__) + /* Borland has no mode argument. + TODO: Fix mode of new file. */ + r = _open(path, flags); +#else + r = _open(path, flags, pmode); +#endif + if (r < 0 && errno == EACCES && (flags & O_CREAT) != 0) { + /* Simulate other POSIX system action to pass our test suite. */ + attr = GetFileAttributesA(path); + if (attr == (DWORD)-1) + la_dosmaperr(GetLastError()); + else if (attr & FILE_ATTRIBUTE_DIRECTORY) + errno = EISDIR; + else + errno = EACCES; + return (-1); + } + if (r >= 0 || errno != ENOENT) + return (r); + ws = __la_win_permissive_name(path); + if (ws == NULL) { + errno = EINVAL; + return (-1); + } + } + r = _wopen(ws, flags, pmode); + if (r < 0 && errno == EACCES && (flags & O_CREAT) != 0) { + /* Simulate other POSIX system action to pass our test suite. */ + attr = GetFileAttributesW(ws); + if (attr == (DWORD)-1) + la_dosmaperr(GetLastError()); + else if (attr & FILE_ATTRIBUTE_DIRECTORY) + errno = EISDIR; + else + errno = EACCES; + } + free(ws); + return (r); +} + +ssize_t +__la_read(int fd, void *buf, size_t nbytes) +{ + HANDLE handle; + DWORD bytes_read, lasterr; + int r; + +#ifdef _WIN64 + if (nbytes > UINT32_MAX) + nbytes = UINT32_MAX; +#endif + if (fd < 0) { + errno = EBADF; + return (-1); + } + /* Do not pass 0 to third parameter of ReadFile(), read bytes. + * This will not return to application side. */ + if (nbytes == 0) + return (0); + handle = (HANDLE)_get_osfhandle(fd); + if (GetFileType(handle) == FILE_TYPE_PIPE) { + DWORD sta; + if (GetNamedPipeHandleState( + handle, &sta, NULL, NULL, NULL, NULL, 0) != 0 && + (sta & PIPE_NOWAIT) == 0) { + DWORD avail = -1; + int cnt = 3; + + while (PeekNamedPipe( + handle, NULL, 0, NULL, &avail, NULL) != 0 && + avail == 0 && --cnt) + Sleep(100); + if (avail == 0) + return (0); + } + } + r = ReadFile(handle, buf, (uint32_t)nbytes, + &bytes_read, NULL); + if (r == 0) { + lasterr = GetLastError(); + if (lasterr == ERROR_NO_DATA) { + errno = EAGAIN; + return (-1); + } + if (lasterr == ERROR_BROKEN_PIPE) + return (0); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + return (-1); + } + return ((ssize_t)bytes_read); +} + +/* Convert Windows FILETIME to UTC */ +__inline static void +fileTimeToUTC(const FILETIME *filetime, time_t *time, long *ns) +{ + ULARGE_INTEGER utc; + + utc.HighPart = filetime->dwHighDateTime; + utc.LowPart = filetime->dwLowDateTime; + if (utc.QuadPart >= EPOC_TIME) { + utc.QuadPart -= EPOC_TIME; + *time = (time_t)(utc.QuadPart / 10000000); /* milli seconds base */ + *ns = (long)(utc.QuadPart % 10000000) * 100;/* nano seconds base */ + } else { + *time = 0; + *ns = 0; + } +} + +/* Stat by handle + * Windows' stat() does not accept the path added "\\?\" especially "?" + * character. + * It means we cannot access the long name path longer than MAX_PATH. + * So I've implemented simular Windows' stat() to access the long name path. + * And I've added some feature. + * 1. set st_ino by nFileIndexHigh and nFileIndexLow of + * BY_HANDLE_FILE_INFORMATION. + * 2. set st_nlink by nNumberOfLinks of BY_HANDLE_FILE_INFORMATION. + * 3. set st_dev by dwVolumeSerialNumber by BY_HANDLE_FILE_INFORMATION. + */ +static int +__hstat(HANDLE handle, struct ustat *st) +{ + BY_HANDLE_FILE_INFORMATION info; + ULARGE_INTEGER ino64; + DWORD ftype; + mode_t mode; + time_t time; + long ns; + + switch (ftype = GetFileType(handle)) { + case FILE_TYPE_UNKNOWN: + errno = EBADF; + return (-1); + case FILE_TYPE_CHAR: + case FILE_TYPE_PIPE: + if (ftype == FILE_TYPE_CHAR) { + st->st_mode = S_IFCHR; + st->st_size = 0; + } else { + DWORD avail; + + st->st_mode = S_IFIFO; + if (PeekNamedPipe(handle, NULL, 0, NULL, &avail, NULL)) + st->st_size = avail; + else + st->st_size = 0; + } + st->st_atime = 0; + st->st_atime_nsec = 0; + st->st_mtime = 0; + st->st_mtime_nsec = 0; + st->st_ctime = 0; + st->st_ctime_nsec = 0; + st->st_ino = 0; + st->st_nlink = 1; + st->st_uid = 0; + st->st_gid = 0; + st->st_rdev = 0; + st->st_dev = 0; + return (0); + case FILE_TYPE_DISK: + break; + default: + /* This ftype is undocumented type. */ + la_dosmaperr(GetLastError()); + return (-1); + } + + ZeroMemory(&info, sizeof(info)); + if (!GetFileInformationByHandle (handle, &info)) { + la_dosmaperr(GetLastError()); + return (-1); + } + + mode = S_IRUSR | S_IRGRP | S_IROTH; + if ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + mode |= S_IWUSR | S_IWGRP | S_IWOTH; + if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + else + mode |= S_IFREG; + st->st_mode = mode; + + fileTimeToUTC(&info.ftLastAccessTime, &time, &ns); + st->st_atime = time; + st->st_atime_nsec = ns; + fileTimeToUTC(&info.ftLastWriteTime, &time, &ns); + st->st_mtime = time; + st->st_mtime_nsec = ns; + fileTimeToUTC(&info.ftCreationTime, &time, &ns); + st->st_ctime = time; + st->st_ctime_nsec = ns; + st->st_size = + ((int64_t)(info.nFileSizeHigh) * ((int64_t)MAXDWORD + 1)) + + (int64_t)(info.nFileSizeLow); +#ifdef SIMULATE_WIN_STAT + st->st_ino = 0; + st->st_nlink = 1; + st->st_dev = 0; +#else + /* Getting FileIndex as i-node. We should remove a sequence which + * is high-16-bits of nFileIndexHigh. */ + ino64.HighPart = info.nFileIndexHigh & 0x0000FFFFUL; + ino64.LowPart = info.nFileIndexLow; + st->st_ino = ino64.QuadPart; + st->st_nlink = info.nNumberOfLinks; + if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ++st->st_nlink;/* Add parent directory. */ + st->st_dev = info.dwVolumeSerialNumber; +#endif + st->st_uid = 0; + st->st_gid = 0; + st->st_rdev = 0; + return (0); +} + +static void +copy_stat(struct stat *st, struct ustat *us) +{ + st->st_atime = us->st_atime; + st->st_ctime = us->st_ctime; + st->st_mtime = us->st_mtime; + st->st_gid = us->st_gid; + st->st_ino = getino(us); + st->st_mode = us->st_mode; + st->st_nlink = us->st_nlink; + st->st_size = us->st_size; + st->st_uid = us->st_uid; + st->st_dev = us->st_dev; + st->st_rdev = us->st_rdev; +} + +/* + * TODO: Remove a use of __la_fstat and __la_stat. + * We should use GetFileInformationByHandle in place + * where We still use the *stat functions. + */ +int +__la_fstat(int fd, struct stat *st) +{ + struct ustat u; + int ret; + + if (fd < 0) { + errno = EBADF; + return (-1); + } + ret = __hstat((HANDLE)_get_osfhandle(fd), &u); + if (ret >= 0) { + copy_stat(st, &u); + if (u.st_mode & (S_IFCHR | S_IFIFO)) { + st->st_dev = fd; + st->st_rdev = fd; + } + } + return (ret); +} + +/* This can exceed MAX_PATH limitation. */ +int +__la_stat(const char *path, struct stat *st) +{ + HANDLE handle; + struct ustat u; + int ret; + + handle = la_CreateFile(path, 0, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (handle == INVALID_HANDLE_VALUE) { + la_dosmaperr(GetLastError()); + return (-1); + } + ret = __hstat(handle, &u); + CloseHandle(handle); + if (ret >= 0) { + char *p; + + copy_stat(st, &u); + p = strrchr(path, '.'); + if (p != NULL && strlen(p) == 4) { + char exttype[4]; + + ++ p; + exttype[0] = toupper(*p++); + exttype[1] = toupper(*p++); + exttype[2] = toupper(*p++); + exttype[3] = '\0'; + if (!strcmp(exttype, "EXE") || !strcmp(exttype, "CMD") || + !strcmp(exttype, "BAT") || !strcmp(exttype, "COM")) + st->st_mode |= S_IXUSR | S_IXGRP | S_IXOTH; + } + } + return (ret); +} + +/* + * This waitpid is limited implementation. + */ +pid_t +__la_waitpid(pid_t wpid, int *status, int option) +{ + HANDLE child; + DWORD cs, ret; + + (void)option;/* UNUSED */ + *status = 0; + child = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, wpid); + if (child == NULL) { + la_dosmaperr(GetLastError()); + return (-1); + } + ret = WaitForSingleObject(child, INFINITE); + if (ret == WAIT_FAILED) { + CloseHandle(child); + la_dosmaperr(GetLastError()); + return (-1); + } + if (GetExitCodeProcess(child, &cs) == 0) { + CloseHandle(child); + la_dosmaperr(GetLastError()); + return (-1); + } + if (cs == STILL_ACTIVE) + *status = 0x100; + else + *status = (int)(cs & 0xff); + CloseHandle(child); + return (wpid); +} + +ssize_t +__la_write(int fd, const void *buf, size_t nbytes) +{ + DWORD bytes_written; + +#ifdef _WIN64 + if (nbytes > UINT32_MAX) + nbytes = UINT32_MAX; +#endif + if (fd < 0) { + errno = EBADF; + return (-1); + } + if (!WriteFile((HANDLE)_get_osfhandle(fd), buf, (uint32_t)nbytes, + &bytes_written, NULL)) { + DWORD lasterr; + + lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + return (-1); + } + return (bytes_written); +} + +/* + * The following function was modified from PostgreSQL sources and is + * subject to the copyright below. + */ +/*------------------------------------------------------------------------- + * + * win32error.c + * Map win32 error codes to errno values + * + * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/port/win32error.c,v 1.4 2008/01/01 19:46:00 momjian Exp $ + * + *------------------------------------------------------------------------- + */ +/* +PostgreSQL Database Management System +(formerly known as Postgres, then as Postgres95) + +Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group + +Portions Copyright (c) 1994, The Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this +paragraph and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING +LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS +ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO +PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +*/ + +static const struct { + DWORD winerr; + int doserr; +} doserrors[] = +{ + { ERROR_INVALID_FUNCTION, EINVAL }, + { ERROR_FILE_NOT_FOUND, ENOENT }, + { ERROR_PATH_NOT_FOUND, ENOENT }, + { ERROR_TOO_MANY_OPEN_FILES, EMFILE }, + { ERROR_ACCESS_DENIED, EACCES }, + { ERROR_INVALID_HANDLE, EBADF }, + { ERROR_ARENA_TRASHED, ENOMEM }, + { ERROR_NOT_ENOUGH_MEMORY, ENOMEM }, + { ERROR_INVALID_BLOCK, ENOMEM }, + { ERROR_BAD_ENVIRONMENT, E2BIG }, + { ERROR_BAD_FORMAT, ENOEXEC }, + { ERROR_INVALID_ACCESS, EINVAL }, + { ERROR_INVALID_DATA, EINVAL }, + { ERROR_INVALID_DRIVE, ENOENT }, + { ERROR_CURRENT_DIRECTORY, EACCES }, + { ERROR_NOT_SAME_DEVICE, EXDEV }, + { ERROR_NO_MORE_FILES, ENOENT }, + { ERROR_LOCK_VIOLATION, EACCES }, + { ERROR_SHARING_VIOLATION, EACCES }, + { ERROR_BAD_NETPATH, ENOENT }, + { ERROR_NETWORK_ACCESS_DENIED, EACCES }, + { ERROR_BAD_NET_NAME, ENOENT }, + { ERROR_FILE_EXISTS, EEXIST }, + { ERROR_CANNOT_MAKE, EACCES }, + { ERROR_FAIL_I24, EACCES }, + { ERROR_INVALID_PARAMETER, EINVAL }, + { ERROR_NO_PROC_SLOTS, EAGAIN }, + { ERROR_DRIVE_LOCKED, EACCES }, + { ERROR_BROKEN_PIPE, EPIPE }, + { ERROR_DISK_FULL, ENOSPC }, + { ERROR_INVALID_TARGET_HANDLE, EBADF }, + { ERROR_INVALID_HANDLE, EINVAL }, + { ERROR_WAIT_NO_CHILDREN, ECHILD }, + { ERROR_CHILD_NOT_COMPLETE, ECHILD }, + { ERROR_DIRECT_ACCESS_HANDLE, EBADF }, + { ERROR_NEGATIVE_SEEK, EINVAL }, + { ERROR_SEEK_ON_DEVICE, EACCES }, + { ERROR_DIR_NOT_EMPTY, ENOTEMPTY }, + { ERROR_NOT_LOCKED, EACCES }, + { ERROR_BAD_PATHNAME, ENOENT }, + { ERROR_MAX_THRDS_REACHED, EAGAIN }, + { ERROR_LOCK_FAILED, EACCES }, + { ERROR_ALREADY_EXISTS, EEXIST }, + { ERROR_FILENAME_EXCED_RANGE, ENOENT }, + { ERROR_NESTING_NOT_ALLOWED, EAGAIN }, + { ERROR_NOT_ENOUGH_QUOTA, ENOMEM } +}; + +void +__la_dosmaperr(unsigned long e) +{ + int i; + + if (e == 0) + { + errno = 0; + return; + } + + for (i = 0; i < sizeof(doserrors); i++) + { + if (doserrors[i].winerr == e) + { + errno = doserrors[i].doserr; + return; + } + } + + /* fprintf(stderr, "unrecognized win32 error code: %lu", e); */ + errno = EINVAL; + return; +} + +#endif /* _WIN32 && !__CYGWIN__ */ diff --git a/libarchive/archive_windows.h b/libarchive/archive_windows.h new file mode 100644 index 0000000..b26811e --- /dev/null +++ b/libarchive/archive_windows.h @@ -0,0 +1,267 @@ +/*- + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * Copyright (c) 2003-2006 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD$ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +/* + * TODO: A lot of stuff in here isn't actually used by libarchive and + * can be trimmed out. Note that this file is used by libarchive and + * libarchive_test but nowhere else. (But note that it gets compiled + * with many different Windows environments, including MinGW, Visual + * Studio, and Cygwin. Significant changes should be tested in all three.) + */ + +/* + * TODO: Don't use off_t in here. Use __int64 instead. Note that + * Visual Studio and the Windows SDK define off_t as 32 bits; Win32's + * more modern file handling APIs all use __int64 instead of off_t. + */ + +#ifndef LIBARCHIVE_ARCHIVE_WINDOWS_H_INCLUDED +#define LIBARCHIVE_ARCHIVE_WINDOWS_H_INCLUDED + +/* Start of configuration for native Win32 */ + +#include +#define set_errno(val) ((errno)=val) +#include +#include //brings in NULL +#if defined(HAVE_STDINT_H) +#include +#endif +#include +#include +#include +#include +#include +#if defined(__MINGW32__) && defined(HAVE_UNISTD_H) +/* Prevent build error from a type mismatch of ftruncate(). + * This unistd.h defines it as ftruncate(int, off_t). */ +#include +#endif +#define NOCRYPT +#include +//#define EFTYPE 7 + +#if defined(_MSC_VER) +/* TODO: Fix the code, don't suppress the warnings. */ +#pragma warning(disable:4244) /* 'conversion' conversion from 'type1' to 'type2', possible loss of data */ +#endif +#if defined(__BORLANDC__) +#pragma warn -8068 /* Constant out of range in comparison. */ +#pragma warn -8072 /* Suspicious pointer arithmetic. */ +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +/* Alias the Windows _function to the POSIX equivalent. */ +#define close _close +#define fcntl __la_fcntl +#ifndef fileno +#define fileno _fileno +#endif +#define fstat __la_fstat +#define lseek _lseeki64 +#define lstat __la_stat +#define open __la_open +#define read __la_read +#if !defined(__BORLANDC__) +#define setmode _setmode +#endif +#define stat(path,stref) __la_stat(path,stref) +#if !defined(__BORLANDC__) +#define strdup _strdup +#endif +#define tzset _tzset +#if !defined(__BORLANDC__) +#define umask _umask +#endif +#define waitpid __la_waitpid +#define write __la_write + +#ifndef O_RDONLY +#define O_RDONLY _O_RDONLY +#define O_WRONLY _O_WRONLY +#define O_TRUNC _O_TRUNC +#define O_CREAT _O_CREAT +#define O_EXCL _O_EXCL +#define O_BINARY _O_BINARY +#endif + +#ifndef _S_IFIFO + #define _S_IFIFO 0010000 /* pipe */ +#endif +#ifndef _S_IFCHR + #define _S_IFCHR 0020000 /* character special */ +#endif +#ifndef _S_IFDIR + #define _S_IFDIR 0040000 /* directory */ +#endif +#ifndef _S_IFBLK + #define _S_IFBLK 0060000 /* block special */ +#endif +#ifndef _S_IFLNK + #define _S_IFLNK 0120000 /* symbolic link */ +#endif +#ifndef _S_IFSOCK + #define _S_IFSOCK 0140000 /* socket */ +#endif +#ifndef _S_IFREG + #define _S_IFREG 0100000 /* regular */ +#endif +#ifndef _S_IFMT + #define _S_IFMT 0170000 /* file type mask */ +#endif + +#ifndef S_IFIFO +#define S_IFIFO _S_IFIFO +#endif +//#define S_IFCHR _S_IFCHR +//#define S_IFDIR _S_IFDIR +#ifndef S_IFBLK +#define S_IFBLK _S_IFBLK +#endif +#ifndef S_IFLNK +#define S_IFLNK _S_IFLNK +#endif +#ifndef S_IFSOCK +#define S_IFSOCK _S_IFSOCK +#endif +//#define S_IFREG _S_IFREG +//#define S_IFMT _S_IFMT + +#ifndef S_ISBLK +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */ +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */ +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */ +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) /* directory */ +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) /* regular file */ +#endif +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* Symbolic link */ +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* Socket */ + +#define _S_ISUID 0004000 /* set user id on execution */ +#define _S_ISGID 0002000 /* set group id on execution */ +#define _S_ISVTX 0001000 /* save swapped text even after use */ + +#define S_ISUID _S_ISUID +#define S_ISGID _S_ISGID +#define S_ISVTX _S_ISVTX + +#define _S_IRWXU (_S_IREAD | _S_IWRITE | _S_IEXEC) +#define _S_IXUSR _S_IEXEC /* read permission, user */ +#define _S_IWUSR _S_IWRITE /* write permission, user */ +#define _S_IRUSR _S_IREAD /* execute/search permission, user */ +#define _S_IRWXG (_S_IRWXU >> 3) +#define _S_IXGRP (_S_IXUSR >> 3) /* read permission, group */ +#define _S_IWGRP (_S_IWUSR >> 3) /* write permission, group */ +#define _S_IRGRP (_S_IRUSR >> 3) /* execute/search permission, group */ +#define _S_IRWXO (_S_IRWXG >> 3) +#define _S_IXOTH (_S_IXGRP >> 3) /* read permission, other */ +#define _S_IWOTH (_S_IWGRP >> 3) /* write permission, other */ +#define _S_IROTH (_S_IRGRP >> 3) /* execute/search permission, other */ + +#ifndef S_IRWXU +#define S_IRWXU _S_IRWXU +#define S_IXUSR _S_IXUSR +#define S_IWUSR _S_IWUSR +#define S_IRUSR _S_IRUSR +#endif +#define S_IRWXG _S_IRWXG +#define S_IXGRP _S_IXGRP +#define S_IWGRP _S_IWGRP +#define S_IRGRP _S_IRGRP +#define S_IRWXO _S_IRWXO +#define S_IXOTH _S_IXOTH +#define S_IWOTH _S_IWOTH +#define S_IROTH _S_IROTH + +#define F_DUPFD 0 /* Duplicate file descriptor. */ +#define F_GETFD 1 /* Get file descriptor flags. */ +#define F_SETFD 2 /* Set file descriptor flags. */ +#define F_GETFL 3 /* Get file status flags. */ +#define F_SETFL 4 /* Set file status flags. */ +#define F_GETOWN 5 /* Get owner (receiver of SIGIO). */ +#define F_SETOWN 6 /* Set owner (receiver of SIGIO). */ +#define F_GETLK 7 /* Get record locking info. */ +#define F_SETLK 8 /* Set record locking info (non-blocking). */ +#define F_SETLKW 9 /* Set record locking info (blocking). */ + +/* XXX missing */ +#define F_GETLK64 7 /* Get record locking info. */ +#define F_SETLK64 8 /* Set record locking info (non-blocking). */ +#define F_SETLKW64 9 /* Set record locking info (blocking). */ + +/* File descriptor flags used with F_GETFD and F_SETFD. */ +#define FD_CLOEXEC 1 /* Close on exec. */ + +//NOT SURE IF O_NONBLOCK is OK here but at least the 0x0004 flag is not used by anything else... +#define O_NONBLOCK 0x0004 /* Non-blocking I/O. */ +//#define O_NDELAY O_NONBLOCK + +/* Symbolic constants for the access() function */ +#if !defined(F_OK) + #define R_OK 4 /* Test for read permission */ + #define W_OK 2 /* Test for write permission */ + #define X_OK 1 /* Test for execute permission */ + #define F_OK 0 /* Test for existence of file */ +#endif + + +/* Replacement POSIX function */ +extern int __la_fcntl(int fd, int cmd, int val); +extern int __la_fstat(int fd, struct stat *st); +extern int __la_lstat(const char *path, struct stat *st); +extern int __la_open(const char *path, int flags, ...); +extern ssize_t __la_read(int fd, void *buf, size_t nbytes); +extern int __la_stat(const char *path, struct stat *st); +extern pid_t __la_waitpid(pid_t wpid, int *status, int option); +extern ssize_t __la_write(int fd, const void *buf, size_t nbytes); + +#define _stat64i32(path, st) __la_stat(path, st) +#define _stat64(path, st) __la_stat(path, st) +/* for status returned by la_waitpid */ +#define WIFEXITED(sts) ((sts & 0x100) == 0) +#define WEXITSTATUS(sts) (sts & 0x0FF) + +extern wchar_t *__la_win_permissive_name(const char *name); +extern wchar_t *__la_win_permissive_name_w(const wchar_t *wname); +extern void __la_dosmaperr(unsigned long e); +#define la_dosmaperr(e) __la_dosmaperr(e) + + +#endif /* LIBARCHIVE_ARCHIVE_WINDOWS_H_INCLUDED */ diff --git a/libarchive/archive_write.3 b/libarchive/archive_write.3 new file mode 100644 index 0000000..1fa4245 --- /dev/null +++ b/libarchive/archive_write.3 @@ -0,0 +1,261 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_write.3 201110 2009-12-28 03:31:29Z kientzle $ +.\" +.Dd March 23, 2011 +.Dt archive_write 3 +.Os +.Sh NAME +.Nm archive_write +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Sh DESCRIPTION +These functions provide a complete API for creating streaming +archive files. +The general process is to first create the +.Tn struct archive +object, set any desired options, initialize the archive, append entries, then +close the archive and release all resources. +.\" +.Ss Create archive object +See +.Xr archive_write_new 3 . +.Pp +To write an archive, you must first obtain an initialized +.Tn struct archive +object from +.Fn archive_write_new . +.\" +.Ss Enable filters and formats, configure block size and padding +See +.Xr archive_write_filter 3 , +.Xr archive_write_format 3 +and +.Xr archive_write_blocksize 3 . +.Pp +You can then modify this object for the desired operations with the +various +.Fn archive_write_set_XXX +functions. +In particular, you will need to invoke appropriate +.Fn archive_write_add_XXX +and +.Fn archive_write_set_XXX +functions to enable the corresponding compression and format +support. +.\" +.Ss Set options +See +.Xr archive_read_set_options 3 . +.\" +.Ss Open archive +See +.Xr archive_write_open 3 . +.Pp +Once you have prepared the +.Tn struct archive +object, you call +.Fn archive_write_open +to actually open the archive and prepare it for writing. +There are several variants of this function; +the most basic expects you to provide pointers to several +functions that can provide blocks of bytes from the archive. +There are convenience forms that allow you to +specify a filename, file descriptor, +.Ft "FILE *" +object, or a block of memory from which to write the archive data. +.\" +.Ss Produce archive +See +.Xr archive_write_header 3 +and +.Xr archive_write_data 3 . +.Pp +Individual archive entries are written in a three-step +process: +You first initialize a +.Tn struct archive_entry +structure with information about the new entry. +At a minimum, you should set the pathname of the +entry and provide a +.Va struct stat +with a valid +.Va st_mode +field, which specifies the type of object and +.Va st_size +field, which specifies the size of the data portion of the object. +.\" +.Ss Release resources +See +.Xr archive_write_free 3 . +.Pp +After all entries have been written, use the +.Fn archive_write_free +function to release all resources. +.\" +.Sh EXAMPLE +The following sketch illustrates basic usage of the library. +In this example, +the callback functions are simply wrappers around the standard +.Xr open 2 , +.Xr write 2 , +and +.Xr close 2 +system calls. +.Bd -literal -offset indent +#ifdef __linux__ +#define _FILE_OFFSET_BITS 64 +#endif +#include +#include +#include +#include +#include +#include + +struct mydata { + const char *name; + int fd; +}; + +int +myopen(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + mydata->fd = open(mydata->name, O_WRONLY | O_CREAT, 0644); + if (mydata->fd >= 0) + return (ARCHIVE_OK); + else + return (ARCHIVE_FATAL); +} + +ssize_t +mywrite(struct archive *a, void *client_data, const void *buff, size_t n) +{ + struct mydata *mydata = client_data; + + return (write(mydata->fd, buff, n)); +} + +int +myclose(struct archive *a, void *client_data) +{ + struct mydata *mydata = client_data; + + if (mydata->fd > 0) + close(mydata->fd); + return (0); +} + +void +write_archive(const char *outname, const char **filename) +{ + struct mydata *mydata = malloc(sizeof(struct mydata)); + struct archive *a; + struct archive_entry *entry; + struct stat st; + char buff[8192]; + int len; + int fd; + + a = archive_write_new(); + mydata->name = outname; + archive_write_add_filter_gzip(a); + archive_write_set_format_ustar(a); + archive_write_open(a, mydata, myopen, mywrite, myclose); + while (*filename) { + stat(*filename, &st); + entry = archive_entry_new(); + archive_entry_copy_stat(entry, &st); + archive_entry_set_pathname(entry, *filename); + archive_write_header(a, entry); + if ((fd = open(*filename, O_RDONLY)) != -1) { + len = read(fd, buff, sizeof(buff)); + while ( len > 0 ) { + archive_write_data(a, buff, len); + len = read(fd, buff, sizeof(buff)); + } + close(fd); + } + archive_entry_free(entry); + filename++; + } + archive_write_free(a); +} + +int main(int argc, const char **argv) +{ + const char *outname; + argv++; + outname = argv++; + write_archive(outname, argv); + return 0; +} +.Ed +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +There are many peculiar bugs in historic tar implementations that may cause +certain programs to reject archives written by this library. +For example, several historic implementations calculated header checksums +incorrectly and will thus reject valid archives; GNU tar does not fully support +pax interchange format; some old tar implementations required specific +field terminations. +.Pp +The default pax interchange format eliminates most of the historic +tar limitations and provides a generic key/value attribute facility +for vendor-defined extensions. +One oversight in POSIX is the failure to provide a standard attribute +for large device numbers. +This library uses +.Dq SCHILY.devminor +and +.Dq SCHILY.devmajor +for device numbers that exceed the range supported by the backwards-compatible +ustar header. +These keys are compatible with Joerg Schilling's +.Nm star +archiver. +Other implementations may not recognize these keys and will thus be unable +to correctly restore device nodes with large device numbers from archives +created by this library. diff --git a/libarchive/archive_write.c b/libarchive/archive_write.c new file mode 100644 index 0000000..c742d60 --- /dev/null +++ b/libarchive/archive_write.c @@ -0,0 +1,709 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write.c 201099 2009-12-28 03:03:00Z kientzle $"); + +/* + * This file contains the "essential" portions of the write API, that + * is, stuff that will essentially always be used by any client that + * actually needs to write an archive. Optional pieces have been, as + * far as possible, separated out into separate files to reduce + * needlessly bloating statically-linked clients. + */ + +#ifdef HAVE_SYS_WAIT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static struct archive_vtable *archive_write_vtable(void); + +static int _archive_filter_code(struct archive *, int); +static const char *_archive_filter_name(struct archive *, int); +static int64_t _archive_filter_bytes(struct archive *, int); +static int _archive_write_filter_count(struct archive *); +static int _archive_write_close(struct archive *); +static int _archive_write_free(struct archive *); +static int _archive_write_header(struct archive *, struct archive_entry *); +static int _archive_write_finish_entry(struct archive *); +static ssize_t _archive_write_data(struct archive *, const void *, size_t); + +struct archive_none { + size_t buffer_size; + size_t avail; + char *buffer; + char *next; +}; + +static struct archive_vtable * +archive_write_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_close = _archive_write_close; + av.archive_filter_bytes = _archive_filter_bytes; + av.archive_filter_code = _archive_filter_code; + av.archive_filter_name = _archive_filter_name; + av.archive_filter_count = _archive_write_filter_count; + av.archive_free = _archive_write_free; + av.archive_write_header = _archive_write_header; + av.archive_write_finish_entry = _archive_write_finish_entry; + av.archive_write_data = _archive_write_data; + inited = 1; + } + return (&av); +} + +/* + * Allocate, initialize and return an archive object. + */ +struct archive * +archive_write_new(void) +{ + struct archive_write *a; + unsigned char *nulls; + + a = (struct archive_write *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_WRITE_MAGIC; + a->archive.state = ARCHIVE_STATE_NEW; + a->archive.vtable = archive_write_vtable(); + /* + * The value 10240 here matches the traditional tar default, + * but is otherwise arbitrary. + * TODO: Set the default block size from the format selected. + */ + a->bytes_per_block = 10240; + a->bytes_in_last_block = -1; /* Default */ + + /* Initialize a block of nulls for padding purposes. */ + a->null_length = 1024; + nulls = (unsigned char *)malloc(a->null_length); + if (nulls == NULL) { + free(a); + return (NULL); + } + memset(nulls, 0, a->null_length); + a->nulls = nulls; + return (&a->archive); +} + +/* + * Set the block size. Returns 0 if successful. + */ +int +archive_write_set_bytes_per_block(struct archive *_a, int bytes_per_block) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_bytes_per_block"); + a->bytes_per_block = bytes_per_block; + return (ARCHIVE_OK); +} + +/* + * Get the current block size. -1 if it has never been set. + */ +int +archive_write_get_bytes_per_block(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_get_bytes_per_block"); + return (a->bytes_per_block); +} + +/* + * Set the size for the last block. + * Returns 0 if successful. + */ +int +archive_write_set_bytes_in_last_block(struct archive *_a, int bytes) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_set_bytes_in_last_block"); + a->bytes_in_last_block = bytes; + return (ARCHIVE_OK); +} + +/* + * Return the value set above. -1 indicates it has not been set. + */ +int +archive_write_get_bytes_in_last_block(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_get_bytes_in_last_block"); + return (a->bytes_in_last_block); +} + +/* + * dev/ino of a file to be rejected. Used to prevent adding + * an archive to itself recursively. + */ +int +archive_write_set_skip_file(struct archive *_a, int64_t d, int64_t i) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_set_skip_file"); + a->skip_file_set = 1; + a->skip_file_dev = d; + a->skip_file_ino = i; + return (ARCHIVE_OK); +} + +/* + * Allocate and return the next filter structure. + */ +struct archive_write_filter * +__archive_write_allocate_filter(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *f; + + f = calloc(1, sizeof(*f)); + f->archive = _a; + if (a->filter_first == NULL) + a->filter_first = f; + else + a->filter_last->next_filter = f; + a->filter_last = f; + return f; +} + +/* + * Write data to a particular filter. + */ +int +__archive_write_filter(struct archive_write_filter *f, + const void *buff, size_t length) +{ + int r; + if (length == 0) + return(ARCHIVE_OK); + r = (f->write)(f, buff, length); + f->bytes_written += length; + return (r); +} + +/* + * Open a filter. + */ +int +__archive_write_open_filter(struct archive_write_filter *f) +{ + if (f->open == NULL) + return (ARCHIVE_OK); + return (f->open)(f); +} + +/* + * Close a filter. + */ +int +__archive_write_close_filter(struct archive_write_filter *f) +{ + if (f->close != NULL) + return (f->close)(f); + if (f->next_filter != NULL) + return (__archive_write_close_filter(f->next_filter)); + return (ARCHIVE_OK); +} + +int +__archive_write_output(struct archive_write *a, const void *buff, size_t length) +{ + return (__archive_write_filter(a->filter_first, buff, length)); +} + +int +__archive_write_nulls(struct archive_write *a, size_t length) +{ + if (length == 0) + return (ARCHIVE_OK); + + while (length > 0) { + size_t to_write = length < a->null_length ? length : a->null_length; + int r = __archive_write_output(a, a->nulls, to_write); + if (r < ARCHIVE_OK) + return (r); + length -= to_write; + } + return (ARCHIVE_OK); +} + +static int +archive_write_client_open(struct archive_write_filter *f) +{ + struct archive_write *a = (struct archive_write *)f->archive; + struct archive_none *state; + void *buffer; + size_t buffer_size; + + f->bytes_per_block = archive_write_get_bytes_per_block(f->archive); + f->bytes_in_last_block = + archive_write_get_bytes_in_last_block(f->archive); + buffer_size = f->bytes_per_block; + + state = (struct archive_none *)calloc(1, sizeof(*state)); + buffer = (char *)malloc(buffer_size); + if (state == NULL || buffer == NULL) { + free(state); + free(buffer); + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for output buffering"); + return (ARCHIVE_FATAL); + } + + state->buffer_size = buffer_size; + state->buffer = buffer; + state->next = state->buffer; + state->avail = state->buffer_size; + f->data = state; + + if (a->client_opener == NULL) + return (ARCHIVE_OK); + return (a->client_opener(f->archive, a->client_data)); +} + +static int +archive_write_client_write(struct archive_write_filter *f, + const void *_buff, size_t length) +{ + struct archive_write *a = (struct archive_write *)f->archive; + struct archive_none *state = (struct archive_none *)f->data; + const char *buff = (const char *)_buff; + ssize_t remaining, to_copy; + ssize_t bytes_written; + + remaining = length; + + /* + * If there is no buffer for blocking, just pass the data + * straight through to the client write callback. In + * particular, this supports "no write delay" operation for + * special applications. Just set the block size to zero. + */ + if (state->buffer_size == 0) { + while (remaining > 0) { + bytes_written = (a->client_writer)(&a->archive, + a->client_data, buff, remaining); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + remaining -= bytes_written; + buff += bytes_written; + } + return (ARCHIVE_OK); + } + + /* If the copy buffer isn't empty, try to fill it. */ + if (state->avail < state->buffer_size) { + /* If buffer is not empty... */ + /* ... copy data into buffer ... */ + to_copy = ((size_t)remaining > state->avail) ? + state->avail : (size_t)remaining; + memcpy(state->next, buff, to_copy); + state->next += to_copy; + state->avail -= to_copy; + buff += to_copy; + remaining -= to_copy; + /* ... if it's full, write it out. */ + if (state->avail == 0) { + char *p = state->buffer; + size_t to_write = state->buffer_size; + while (to_write > 0) { + bytes_written = (a->client_writer)(&a->archive, + a->client_data, p, to_write); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + if ((size_t)bytes_written > to_write) { + archive_set_error(&(a->archive), + -1, "write overrun"); + return (ARCHIVE_FATAL); + } + p += bytes_written; + to_write -= bytes_written; + } + state->next = state->buffer; + state->avail = state->buffer_size; + } + } + + while ((size_t)remaining > state->buffer_size) { + /* Write out full blocks directly to client. */ + bytes_written = (a->client_writer)(&a->archive, + a->client_data, buff, state->buffer_size); + if (bytes_written <= 0) + return (ARCHIVE_FATAL); + buff += bytes_written; + remaining -= bytes_written; + } + + if (remaining > 0) { + /* Copy last bit into copy buffer. */ + memcpy(state->next, buff, remaining); + state->next += remaining; + state->avail -= remaining; + } + return (ARCHIVE_OK); +} + +static int +archive_write_client_close(struct archive_write_filter *f) +{ + struct archive_write *a = (struct archive_write *)f->archive; + struct archive_none *state = (struct archive_none *)f->data; + ssize_t block_length; + ssize_t target_block_length; + ssize_t bytes_written; + int ret = ARCHIVE_OK; + + /* If there's pending data, pad and write the last block */ + if (state->next != state->buffer) { + block_length = state->buffer_size - state->avail; + + /* Tricky calculation to determine size of last block */ + if (a->bytes_in_last_block <= 0) + /* Default or Zero: pad to full block */ + target_block_length = a->bytes_per_block; + else + /* Round to next multiple of bytes_in_last_block. */ + target_block_length = a->bytes_in_last_block * + ( (block_length + a->bytes_in_last_block - 1) / + a->bytes_in_last_block); + if (target_block_length > a->bytes_per_block) + target_block_length = a->bytes_per_block; + if (block_length < target_block_length) { + memset(state->next, 0, + target_block_length - block_length); + block_length = target_block_length; + } + bytes_written = (a->client_writer)(&a->archive, + a->client_data, state->buffer, block_length); + ret = bytes_written <= 0 ? ARCHIVE_FATAL : ARCHIVE_OK; + } + if (a->client_closer) + (*a->client_closer)(&a->archive, a->client_data); + free(state->buffer); + free(state); + a->client_data = NULL; + return (ret); +} + +/* + * Open the archive using the current settings. + */ +int +archive_write_open(struct archive *_a, void *client_data, + archive_open_callback *opener, archive_write_callback *writer, + archive_close_callback *closer) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *client_filter; + int ret, r1; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_open"); + archive_clear_error(&a->archive); + + a->client_writer = writer; + a->client_opener = opener; + a->client_closer = closer; + a->client_data = client_data; + + client_filter = __archive_write_allocate_filter(_a); + client_filter->open = archive_write_client_open; + client_filter->write = archive_write_client_write; + client_filter->close = archive_write_client_close; + + ret = __archive_write_open_filter(a->filter_first); + if (ret < ARCHIVE_WARN) { + r1 = __archive_write_close_filter(a->filter_first); + return (r1 < ret ? r1 : ret); + } + + a->archive.state = ARCHIVE_STATE_HEADER; + if (a->format_init) + ret = (a->format_init)(a); + return (ret); +} + +/* + * Close out the archive. + */ +static int +_archive_write_close(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = ARCHIVE_OK, r1 = ARCHIVE_OK; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, + "archive_write_close"); + if (a->archive.state == ARCHIVE_STATE_NEW + || a->archive.state == ARCHIVE_STATE_CLOSED) + return (ARCHIVE_OK); /* Okay to close() when not open. */ + + archive_clear_error(&a->archive); + + /* Finish the last entry. */ + if (a->archive.state == ARCHIVE_STATE_DATA) + r = ((a->format_finish_entry)(a)); + + /* Finish off the archive. */ + /* TODO: have format closers invoke compression close. */ + if (a->format_close != NULL) { + r1 = (a->format_close)(a); + if (r1 < r) + r = r1; + } + + /* Finish the compression and close the stream. */ + r1 = __archive_write_close_filter(a->filter_first); + if (r1 < r) + r = r1; + + if (a->archive.state != ARCHIVE_STATE_FATAL) + a->archive.state = ARCHIVE_STATE_CLOSED; + return (r); +} + +static int +_archive_write_filter_count(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *p = a->filter_first; + int count = 0; + while(p) { + count++; + p = p->next_filter; + } + return count; +} + +void +__archive_write_filters_free(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = ARCHIVE_OK, r1; + + while (a->filter_first != NULL) { + struct archive_write_filter *next + = a->filter_first->next_filter; + if (a->filter_first->free != NULL) { + r1 = (*a->filter_first->free)(a->filter_first); + if (r > r1) + r = r1; + } + free(a->filter_first); + a->filter_first = next; + } + a->filter_last = NULL; +} + +/* + * Destroy the archive structure. + * + * Be careful: user might just call write_new and then write_free. + * Don't assume we actually wrote anything or performed any non-trivial + * initialization. + */ +static int +_archive_write_free(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r = ARCHIVE_OK, r1; + + if (_a == NULL) + return (ARCHIVE_OK); + /* It is okay to call free() in state FATAL. */ + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_write_free"); + if (a->archive.state != ARCHIVE_STATE_FATAL) + r = archive_write_close(&a->archive); + + /* Release format resources. */ + if (a->format_free != NULL) { + r1 = (a->format_free)(a); + if (r1 < r) + r = r1; + } + + __archive_write_filters_free(_a); + + /* Release various dynamic buffers. */ + free((void *)(uintptr_t)(const void *)a->nulls); + archive_string_free(&a->archive.error_string); + a->archive.magic = 0; + __archive_clean(&a->archive); + free(a); + return (r); +} + +/* + * Write the appropriate header. + */ +static int +_archive_write_header(struct archive *_a, struct archive_entry *entry) +{ + struct archive_write *a = (struct archive_write *)_a; + int ret, r2; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_DATA | ARCHIVE_STATE_HEADER, "archive_write_header"); + archive_clear_error(&a->archive); + + if (a->format_write_header == NULL) { + archive_set_error(&(a->archive), -1, + "Format must be set before you can write to an archive."); + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + + /* In particular, "retry" and "fatal" get returned immediately. */ + ret = archive_write_finish_entry(&a->archive); + if (ret == ARCHIVE_FATAL) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + if (ret < ARCHIVE_OK && ret != ARCHIVE_WARN) + return (ret); + + if (a->skip_file_set && + archive_entry_dev_is_set(entry) && + archive_entry_ino_is_set(entry) && + archive_entry_dev(entry) == a->skip_file_dev && + archive_entry_ino64(entry) == a->skip_file_ino) { + archive_set_error(&a->archive, 0, + "Can't add archive to itself"); + return (ARCHIVE_FAILED); + } + + /* Format and write header. */ + r2 = ((a->format_write_header)(a, entry)); + if (r2 == ARCHIVE_FATAL) { + a->archive.state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); + } + if (r2 < ret) + ret = r2; + + a->archive.state = ARCHIVE_STATE_DATA; + return (ret); +} + +static int +_archive_write_finish_entry(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int ret = ARCHIVE_OK; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_finish_entry"); + if (a->archive.state & ARCHIVE_STATE_DATA) + ret = (a->format_finish_entry)(a); + a->archive.state = ARCHIVE_STATE_HEADER; + return (ret); +} + +/* + * Note that the compressor is responsible for blocking. + */ +static ssize_t +_archive_write_data(struct archive *_a, const void *buff, size_t s) +{ + struct archive_write *a = (struct archive_write *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data"); + archive_clear_error(&a->archive); + return ((a->format_write_data)(a, buff, s)); +} + +static struct archive_write_filter * +filter_lookup(struct archive *_a, int n) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *f = a->filter_first; + if (n == -1) + return a->filter_last; + if (n < 0) + return NULL; + while (n > 0 && f != NULL) { + f = f->next_filter; + --n; + } + return f; +} + +static int +_archive_filter_code(struct archive *_a, int n) +{ + struct archive_write_filter *f = filter_lookup(_a, n); + return f == NULL ? -1 : f->code; +} + +static const char * +_archive_filter_name(struct archive *_a, int n) +{ + struct archive_write_filter *f = filter_lookup(_a, n); + return f == NULL ? NULL : f->name; +} + +static int64_t +_archive_filter_bytes(struct archive *_a, int n) +{ + struct archive_write_filter *f = filter_lookup(_a, n); + return f == NULL ? -1 : f->bytes_written; +} diff --git a/libarchive/archive_write_add_filter_bzip2.c b/libarchive/archive_write_add_filter_bzip2.c new file mode 100644 index 0000000..e0d07a9 --- /dev/null +++ b/libarchive/archive_write_add_filter_bzip2.c @@ -0,0 +1,335 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_bzip2.c 201091 2009-12-28 02:22:41Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_BZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_write_set_compression_bzip2(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_bzip2(a)); +} +#endif + +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) +int +archive_write_add_filter_bzip2(struct archive *a) +{ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "bzip2 compression not supported on this platform"); + return (ARCHIVE_FATAL); +} +#else +/* Don't compile this if we don't have bzlib. */ + +struct private_data { + int compression_level; + bz_stream stream; + int64_t total_in; + char *compressed; + size_t compressed_buffer_size; +}; + +/* + * Yuck. bzlib.h is not const-correct, so I need this one bit + * of ugly hackery to convert a const * pointer to a non-const pointer. + */ +#define SET_NEXT_IN(st,src) \ + (st)->stream.next_in = (char *)(uintptr_t)(const void *)(src) + +static int archive_compressor_bzip2_close(struct archive_write_filter *); +static int archive_compressor_bzip2_free(struct archive_write_filter *); +static int archive_compressor_bzip2_open(struct archive_write_filter *); +static int archive_compressor_bzip2_options(struct archive_write_filter *, + const char *, const char *); +static int archive_compressor_bzip2_write(struct archive_write_filter *, + const void *, size_t); +static int drive_compressor(struct archive_write_filter *, + struct private_data *, int finishing); + +/* + * Add a bzip2 compression filter to this write handle. + */ +int +archive_write_add_filter_bzip2(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *f = __archive_write_allocate_filter(_a); + struct private_data *data; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_bzip2"); + + data = calloc(1, sizeof(*data)); + if (data == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + data->compression_level = 9; /* default */ + + f->data = data; + f->options = &archive_compressor_bzip2_options; + f->close = &archive_compressor_bzip2_close; + f->free = &archive_compressor_bzip2_free; + f->open = &archive_compressor_bzip2_open; + f->code = ARCHIVE_COMPRESSION_BZIP2; + f->name = "bzip2"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_bzip2_open(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret; + + ret = __archive_write_open_filter(f->next_filter); + if (ret != 0) + return (ret); + + /* TODO: Find a better way to size this. (Maybe look at the */ + /* block size expected by the following filter?) */ + if (data->compressed == NULL) { + data->compressed_buffer_size = 65536; + data->compressed + = (char *)malloc(data->compressed_buffer_size); + if (data->compressed == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for compression buffer"); + return (ARCHIVE_FATAL); + } + } + + memset(&data->stream, 0, sizeof(data->stream)); + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + f->write = archive_compressor_bzip2_write; + + /* Initialize compression library */ + ret = BZ2_bzCompressInit(&(data->stream), + data->compression_level, 0, 30); + if (ret == BZ_OK) { + f->data = data; + return (ARCHIVE_OK); + } + + /* Library setup failed: clean up. */ + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case BZ_PARAM_ERROR: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "invalid setup parameter"); + break; + case BZ_MEM_ERROR: + archive_set_error(f->archive, ENOMEM, + "Internal error initializing compression library: " + "out of memory"); + break; + case BZ_CONFIG_ERROR: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "mis-compiled library"); + break; + } + + return (ARCHIVE_FATAL); + +} + +/* + * Set write options. + */ +static int +archive_compressor_bzip2_options(struct archive_write_filter *f, + const char *key, const char *value) +{ + struct private_data *data = (struct private_data *)f->data; + + if (strcmp(key, "compression-level") == 0) { + if (value == NULL || !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') + return (ARCHIVE_WARN); + data->compression_level = value[0] - '0'; + /* Make '0' be a synonym for '1'. */ + /* This way, bzip2 compressor supports the same 0..9 + * range of levels as gzip. */ + if (data->compression_level < 1) + data->compression_level = 1; + return (ARCHIVE_OK); + } + + return (ARCHIVE_WARN); +} + +/* + * Write data to the compressed stream. + * + * Returns ARCHIVE_OK if all data written, error otherwise. + */ +static int +archive_compressor_bzip2_write(struct archive_write_filter *f, + const void *buff, size_t length) +{ + struct private_data *data = (struct private_data *)f->data; + + /* Update statistics */ + data->total_in += length; + + /* Compress input data to output buffer */ + SET_NEXT_IN(data, buff); + data->stream.avail_in = length; + if (drive_compressor(f, data, 0)) + return (ARCHIVE_FATAL); + return (ARCHIVE_OK); +} + + +/* + * Finish the compression. + */ +static int +archive_compressor_bzip2_close(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret, r1; + + /* Finish compression cycle. */ + ret = drive_compressor(f, data, 1); + if (ret == ARCHIVE_OK) { + /* Write the last block */ + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size - data->stream.avail_out); + } + + switch (BZ2_bzCompressEnd(&(data->stream))) { + case BZ_OK: + break; + default: + archive_set_error(f->archive, ARCHIVE_ERRNO_PROGRAMMER, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + + r1 = __archive_write_close_filter(f->next_filter); + return (r1 < ret ? r1 : ret); +} + +static int +archive_compressor_bzip2_free(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + free(data->compressed); + free(data); + f->data = NULL; + return (ARCHIVE_OK); +} + +/* + * Utility function to push input data through compressor, writing + * full output blocks as necessary. + * + * Note that this handles both the regular write case (finishing == + * false) and the end-of-archive case (finishing == true). + */ +static int +drive_compressor(struct archive_write_filter *f, + struct private_data *data, int finishing) +{ + int ret; + + for (;;) { + if (data->stream.avail_out == 0) { + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size); + if (ret != ARCHIVE_OK) { + /* TODO: Handle this write failure */ + return (ARCHIVE_FATAL); + } + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + } + + /* If there's nothing to do, we're done. */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + + ret = BZ2_bzCompress(&(data->stream), + finishing ? BZ_FINISH : BZ_RUN); + + switch (ret) { + case BZ_RUN_OK: + /* In non-finishing case, did compressor + * consume everything? */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + break; + case BZ_FINISH_OK: /* Finishing: There's more work to do */ + break; + case BZ_STREAM_END: /* Finishing: all done */ + /* Only occurs in finishing case */ + return (ARCHIVE_OK); + default: + /* Any other return value indicates an error */ + archive_set_error(f->archive, + ARCHIVE_ERRNO_PROGRAMMER, + "Bzip2 compression failed;" + " BZ2_bzCompress() returned %d", + ret); + return (ARCHIVE_FATAL); + } + } +} + +#endif /* HAVE_BZLIB_H && BZ_CONFIG_ERROR */ diff --git a/libarchive/archive_write_add_filter_compress.c b/libarchive/archive_write_add_filter_compress.c new file mode 100644 index 0000000..465ff0e --- /dev/null +++ b/libarchive/archive_write_add_filter_compress.c @@ -0,0 +1,445 @@ +/*- + * Copyright (c) 2008 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + */ + +/*- + * Copyright (c) 1985, 1986, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis and James A. Woods, derived from original + * work by Spencer Thomas and Joseph Orost. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_compress.c 201111 2009-12-28 03:33:05Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#define HSIZE 69001 /* 95% occupancy */ +#define HSHIFT 8 /* 8 - trunc(log2(HSIZE / 65536)) */ +#define CHECK_GAP 10000 /* Ratio check interval. */ + +#define MAXCODE(bits) ((1 << (bits)) - 1) + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define FIRST 257 /* First free entry. */ +#define CLEAR 256 /* Table clear output code. */ + +struct private_data { + int64_t in_count, out_count, checkpoint; + + int code_len; /* Number of bits/code. */ + int cur_maxcode; /* Maximum code, given n_bits. */ + int max_maxcode; /* Should NEVER generate this code. */ + int hashtab [HSIZE]; + unsigned short codetab [HSIZE]; + int first_free; /* First unused entry. */ + int compress_ratio; + + int cur_code, cur_fcode; + + int bit_offset; + unsigned char bit_buf; + + unsigned char *compressed; + size_t compressed_buffer_size; + size_t compressed_offset; +}; + +static int archive_compressor_compress_open(struct archive_write_filter *); +static int archive_compressor_compress_write(struct archive_write_filter *, + const void *, size_t); +static int archive_compressor_compress_close(struct archive_write_filter *); +static int archive_compressor_compress_free(struct archive_write_filter *); + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_write_set_compression_compress(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_compress(a)); +} +#endif + +/* + * Add a compress filter to this write handle. + */ +int +archive_write_add_filter_compress(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *f = __archive_write_allocate_filter(_a); + + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_compress"); + f->open = &archive_compressor_compress_open; + f->code = ARCHIVE_COMPRESSION_COMPRESS; + f->name = "compress"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_compress_open(struct archive_write_filter *f) +{ + int ret; + struct private_data *state; + + f->code = ARCHIVE_COMPRESSION_COMPRESS; + f->name = "compress"; + + ret = __archive_write_open_filter(f->next_filter); + if (ret != ARCHIVE_OK) + return (ret); + + state = (struct private_data *)calloc(1, sizeof(*state)); + if (state == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for compression"); + return (ARCHIVE_FATAL); + } + + state->compressed_buffer_size = 65536; + state->compressed = malloc(state->compressed_buffer_size); + + if (state->compressed == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for compression buffer"); + free(state); + return (ARCHIVE_FATAL); + } + + f->write = archive_compressor_compress_write; + f->close = archive_compressor_compress_close; + f->free = archive_compressor_compress_free; + + state->max_maxcode = 0x10000; /* Should NEVER generate this code. */ + state->in_count = 0; /* Length of input. */ + state->bit_buf = 0; + state->bit_offset = 0; + state->out_count = 3; /* Includes 3-byte header mojo. */ + state->compress_ratio = 0; + state->checkpoint = CHECK_GAP; + state->code_len = 9; + state->cur_maxcode = MAXCODE(state->code_len); + state->first_free = FIRST; + + memset(state->hashtab, 0xff, sizeof(state->hashtab)); + + /* Prime output buffer with a gzip header. */ + state->compressed[0] = 0x1f; /* Compress */ + state->compressed[1] = 0x9d; + state->compressed[2] = 0x90; /* Block mode, 16bit max */ + state->compressed_offset = 3; + + f->data = state; + return (0); +} + +/*- + * Output the given code. + * Inputs: + * code: A n_bits-bit integer. If == -1, then EOF. This assumes + * that n_bits <= (long)wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a BITS character long buffer (so that 8 codes will + * fit in it exactly). Use the VAX insv instruction to insert each + * code in turn. When the buffer fills up empty it and start over. + */ + +static const unsigned char rmask[9] = + {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + +static int +output_byte(struct archive_write_filter *f, unsigned char c) +{ + struct private_data *state = f->data; + + state->compressed[state->compressed_offset++] = c; + ++state->out_count; + + if (state->compressed_buffer_size == state->compressed_offset) { + int ret = __archive_write_filter(f->next_filter, + state->compressed, state->compressed_buffer_size); + if (ret != ARCHIVE_OK) + return ARCHIVE_FATAL; + state->compressed_offset = 0; + } + + return ARCHIVE_OK; +} + +static int +output_code(struct archive_write_filter *f, int ocode) +{ + struct private_data *state = f->data; + int bits, ret, clear_flg, bit_offset; + + clear_flg = ocode == CLEAR; + + /* + * Since ocode is always >= 8 bits, only need to mask the first + * hunk on the left. + */ + bit_offset = state->bit_offset % 8; + state->bit_buf |= (ocode << bit_offset) & 0xff; + output_byte(f, state->bit_buf); + + bits = state->code_len - (8 - bit_offset); + ocode >>= 8 - bit_offset; + /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */ + if (bits >= 8) { + output_byte(f, ocode & 0xff); + ocode >>= 8; + bits -= 8; + } + /* Last bits. */ + state->bit_offset += state->code_len; + state->bit_buf = ocode & rmask[bits]; + if (state->bit_offset == state->code_len * 8) + state->bit_offset = 0; + + /* + * If the next entry is going to be too big for the ocode size, + * then increase it, if possible. + */ + if (clear_flg || state->first_free > state->cur_maxcode) { + /* + * Write the whole buffer, because the input side won't + * discover the size increase until after it has read it. + */ + if (state->bit_offset > 0) { + while (state->bit_offset < state->code_len * 8) { + ret = output_byte(f, state->bit_buf); + if (ret != ARCHIVE_OK) + return ret; + state->bit_offset += 8; + state->bit_buf = 0; + } + } + state->bit_buf = 0; + state->bit_offset = 0; + + if (clear_flg) { + state->code_len = 9; + state->cur_maxcode = MAXCODE(state->code_len); + } else { + state->code_len++; + if (state->code_len == 16) + state->cur_maxcode = state->max_maxcode; + else + state->cur_maxcode = MAXCODE(state->code_len); + } + } + + return (ARCHIVE_OK); +} + +static int +output_flush(struct archive_write_filter *f) +{ + struct private_data *state = f->data; + int ret; + + /* At EOF, write the rest of the buffer. */ + if (state->bit_offset % 8) { + state->code_len = (state->bit_offset % 8 + 7) / 8; + ret = output_byte(f, state->bit_buf); + if (ret != ARCHIVE_OK) + return ret; + } + + return (ARCHIVE_OK); +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_compress_write(struct archive_write_filter *f, + const void *buff, size_t length) +{ + struct private_data *state = (struct private_data *)f->data; + int i; + int ratio; + int c, disp, ret; + const unsigned char *bp; + + if (length == 0) + return ARCHIVE_OK; + + bp = buff; + + if (state->in_count == 0) { + state->cur_code = *bp++; + ++state->in_count; + --length; + } + + while (length--) { + c = *bp++; + state->in_count++; + state->cur_fcode = (c << 16) + state->cur_code; + i = ((c << HSHIFT) ^ state->cur_code); /* Xor hashing. */ + + if (state->hashtab[i] == state->cur_fcode) { + state->cur_code = state->codetab[i]; + continue; + } + if (state->hashtab[i] < 0) /* Empty slot. */ + goto nomatch; + /* Secondary hash (after G. Knott). */ + if (i == 0) + disp = 1; + else + disp = HSIZE - i; + probe: + if ((i -= disp) < 0) + i += HSIZE; + + if (state->hashtab[i] == state->cur_fcode) { + state->cur_code = state->codetab[i]; + continue; + } + if (state->hashtab[i] >= 0) + goto probe; + nomatch: + ret = output_code(f, state->cur_code); + if (ret != ARCHIVE_OK) + return ret; + state->cur_code = c; + if (state->first_free < state->max_maxcode) { + state->codetab[i] = state->first_free++; /* code -> hashtable */ + state->hashtab[i] = state->cur_fcode; + continue; + } + if (state->in_count < state->checkpoint) + continue; + + state->checkpoint = state->in_count + CHECK_GAP; + + if (state->in_count <= 0x007fffff) + ratio = state->in_count * 256 / state->out_count; + else if ((ratio = state->out_count / 256) == 0) + ratio = 0x7fffffff; + else + ratio = state->in_count / ratio; + + if (ratio > state->compress_ratio) + state->compress_ratio = ratio; + else { + state->compress_ratio = 0; + memset(state->hashtab, 0xff, sizeof(state->hashtab)); + state->first_free = FIRST; + ret = output_code(f, CLEAR); + if (ret != ARCHIVE_OK) + return ret; + } + } + + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_compress_close(struct archive_write_filter *f) +{ + struct private_data *state = (struct private_data *)f->data; + int ret, ret2; + + ret = output_code(f, state->cur_code); + if (ret != ARCHIVE_OK) + goto cleanup; + ret = output_flush(f); + if (ret != ARCHIVE_OK) + goto cleanup; + + /* Write the last block */ + ret = __archive_write_filter(f->next_filter, + state->compressed, state->compressed_offset); +cleanup: + ret2 = __archive_write_close_filter(f->next_filter); + if (ret > ret2) + ret = ret2; + free(state->compressed); + free(state); + return (ret); +} + +static int +archive_compressor_compress_free(struct archive_write_filter *f) +{ + (void)f; /* UNUSED */ + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_add_filter_gzip.c b/libarchive/archive_write_add_filter_gzip.c new file mode 100644 index 0000000..786ae98 --- /dev/null +++ b/libarchive/archive_write_add_filter_gzip.c @@ -0,0 +1,356 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_gzip.c 201081 2009-12-28 02:04:42Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_write_set_compression_gzip(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_gzip(a)); +} +#endif + +#ifndef HAVE_ZLIB_H +int +archive_write_add_filter_gzip(struct archive *a) +{ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "gzip compression not supported on this platform"); + return (ARCHIVE_FATAL); +} +#else +/* Don't compile this if we don't have zlib. */ + +struct private_data { + int compression_level; + z_stream stream; + int64_t total_in; + unsigned char *compressed; + size_t compressed_buffer_size; + unsigned long crc; +}; + +/* + * Yuck. zlib.h is not const-correct, so I need this one bit + * of ugly hackery to convert a const * pointer to a non-const pointer. + */ +#define SET_NEXT_IN(st,src) \ + (st)->stream.next_in = (Bytef *)(uintptr_t)(const void *)(src) + +static int archive_compressor_gzip_options(struct archive_write_filter *, + const char *, const char *); +static int archive_compressor_gzip_open(struct archive_write_filter *); +static int archive_compressor_gzip_write(struct archive_write_filter *, + const void *, size_t); +static int archive_compressor_gzip_close(struct archive_write_filter *); +static int archive_compressor_gzip_free(struct archive_write_filter *); +static int drive_compressor(struct archive_write_filter *, + struct private_data *, int finishing); + + +/* + * Add a gzip compression filter to this write handle. + */ +int +archive_write_add_filter_gzip(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *f = __archive_write_allocate_filter(_a); + struct private_data *data; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_gzip"); + + data = calloc(1, sizeof(*data)); + if (data == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + f->data = data; + data->compression_level = Z_DEFAULT_COMPRESSION; + f->open = &archive_compressor_gzip_open; + f->options = &archive_compressor_gzip_options; + f->close = &archive_compressor_gzip_close; + f->free = &archive_compressor_gzip_free; + f->code = ARCHIVE_COMPRESSION_GZIP; + f->name = "gzip"; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_gzip_open(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret; + time_t t; + + ret = __archive_write_open_filter(f->next_filter); + if (ret != ARCHIVE_OK) + return (ret); + + if (data->compressed == NULL) { + data->compressed_buffer_size = 65536; + data->compressed + = (unsigned char *)malloc(data->compressed_buffer_size); + if (data->compressed == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for compression buffer"); + return (ARCHIVE_FATAL); + } + } + + data->crc = crc32(0L, NULL, 0); + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + + /* Prime output buffer with a gzip header. */ + t = time(NULL); + data->compressed[0] = 0x1f; /* GZip signature bytes */ + data->compressed[1] = 0x8b; + data->compressed[2] = 0x08; /* "Deflate" compression */ + data->compressed[3] = 0; /* No options */ + data->compressed[4] = (t)&0xff; /* Timestamp */ + data->compressed[5] = (t>>8)&0xff; + data->compressed[6] = (t>>16)&0xff; + data->compressed[7] = (t>>24)&0xff; + data->compressed[8] = 0; /* No deflate options */ + data->compressed[9] = 3; /* OS=Unix */ + data->stream.next_out += 10; + data->stream.avail_out -= 10; + + f->write = archive_compressor_gzip_write; + + /* Initialize compression library. */ + ret = deflateInit2(&(data->stream), + data->compression_level, + Z_DEFLATED, + -15 /* < 0 to suppress zlib header */, + 8, + Z_DEFAULT_STRATEGY); + + if (ret == Z_OK) { + f->data = data; + return (ARCHIVE_OK); + } + + /* Library setup failed: clean up. */ + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, "Internal error " + "initializing compression library"); + + /* Override the error message if we know what really went wrong. */ + switch (ret) { + case Z_STREAM_ERROR: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid setup parameter"); + break; + case Z_MEM_ERROR: + archive_set_error(f->archive, ENOMEM, "Internal error initializing " + "compression library"); + break; + case Z_VERSION_ERROR: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid library version"); + break; + } + + return (ARCHIVE_FATAL); +} + +/* + * Set write options. + */ +static int +archive_compressor_gzip_options(struct archive_write_filter *f, const char *key, + const char *value) +{ + struct private_data *data = (struct private_data *)f->data; + + if (strcmp(key, "compression-level") == 0) { + if (value == NULL || !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') + return (ARCHIVE_WARN); + data->compression_level = value[0] - '0'; + return (ARCHIVE_OK); + } + return (ARCHIVE_WARN); +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_gzip_write(struct archive_write_filter *f, const void *buff, + size_t length) +{ + struct private_data *data = (struct private_data *)f->data; + int ret; + + /* Update statistics */ + data->crc = crc32(data->crc, (const Bytef *)buff, length); + data->total_in += length; + + /* Compress input data to output buffer */ + SET_NEXT_IN(data, buff); + data->stream.avail_in = length; + if ((ret = drive_compressor(f, data, 0)) != ARCHIVE_OK) + return (ret); + + return (ARCHIVE_OK); +} + +/* + * Finish the compression... + */ +static int +archive_compressor_gzip_close(struct archive_write_filter *f) +{ + unsigned char trailer[8]; + struct private_data *data = (struct private_data *)f->data; + int ret, r1; + + /* Finish compression cycle */ + ret = drive_compressor(f, data, 1); + if (ret == ARCHIVE_OK) { + /* Write the last compressed data. */ + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size - data->stream.avail_out); + } + if (ret == ARCHIVE_OK) { + /* Build and write out 8-byte trailer. */ + trailer[0] = (data->crc)&0xff; + trailer[1] = (data->crc >> 8)&0xff; + trailer[2] = (data->crc >> 16)&0xff; + trailer[3] = (data->crc >> 24)&0xff; + trailer[4] = (data->total_in)&0xff; + trailer[5] = (data->total_in >> 8)&0xff; + trailer[6] = (data->total_in >> 16)&0xff; + trailer[7] = (data->total_in >> 24)&0xff; + ret = __archive_write_filter(f->next_filter, trailer, 8); + } + + switch (deflateEnd(&(data->stream))) { + case Z_OK: + break; + default: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + r1 = __archive_write_close_filter(f->next_filter); + return (r1 < ret ? r1 : ret); +} + +static int +archive_compressor_gzip_free(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + free(data->compressed); + free(data); + f->data = NULL; + return (ARCHIVE_OK); +} + +/* + * Utility function to push input data through compressor, + * writing full output blocks as necessary. + * + * Note that this handles both the regular write case (finishing == + * false) and the end-of-archive case (finishing == true). + */ +static int +drive_compressor(struct archive_write_filter *f, + struct private_data *data, int finishing) +{ + int ret; + + for (;;) { + if (data->stream.avail_out == 0) { + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + } + + /* If there's nothing to do, we're done. */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + + ret = deflate(&(data->stream), + finishing ? Z_FINISH : Z_NO_FLUSH ); + + switch (ret) { + case Z_OK: + /* In non-finishing case, check if compressor + * consumed everything */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + /* In finishing case, this return always means + * there's more work */ + break; + case Z_STREAM_END: + /* This return can only occur in finishing case. */ + return (ARCHIVE_OK); + default: + /* Any other return value indicates an error. */ + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "GZip compression failed:" + " deflate() call returned status %d", + ret); + return (ARCHIVE_FATAL); + } + } +} + +#endif /* HAVE_ZLIB_H */ diff --git a/libarchive/archive_write_add_filter_none.c b/libarchive/archive_write_add_filter_none.c new file mode 100644 index 0000000..3c06c64 --- /dev/null +++ b/libarchive/archive_write_add_filter_none.c @@ -0,0 +1,43 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_none.c 201080 2009-12-28 02:03:54Z kientzle $"); + +#include "archive.h" + +int +archive_write_set_compression_none(struct archive *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +int +archive_write_add_filter_none(struct archive *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_add_filter_program.c b/libarchive/archive_write_add_filter_program.c new file mode 100644 index 0000000..3dcc9df --- /dev/null +++ b/libarchive/archive_write_add_filter_program.c @@ -0,0 +1,327 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_program.c 201104 2009-12-28 03:14:30Z kientzle $"); + +#ifdef HAVE_SYS_WAIT_H +# include +#endif +#ifdef HAVE_ERRNO_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_write_set_compression_program(struct archive *a, const char *cmd) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_program(a, cmd)); +} +#endif + +/* This capability is only available on POSIX systems. */ +#if (!defined(HAVE_PIPE) || !defined(HAVE_FCNTL) || \ + !(defined(HAVE_FORK) || defined(HAVE_VFORK))) && (!defined(_WIN32) || defined(__CYGWIN__)) + +/* + * On non-Posix systems, allow the program to build, but choke if + * this function is actually invoked. + */ +int +archive_write_add_filter_program(struct archive *_a, const char *cmd) +{ + archive_set_error(_a, -1, + "External compression programs not supported on this platform"); + return (ARCHIVE_FATAL); +} + +#else + +#include "filter_fork.h" + +struct private_data { + char *cmd; + char *description; + pid_t child; + int child_stdin, child_stdout; + + char *child_buf; + size_t child_buf_len, child_buf_avail; +}; + +static int archive_compressor_program_open(struct archive_write_filter *); +static int archive_compressor_program_write(struct archive_write_filter *, + const void *, size_t); +static int archive_compressor_program_close(struct archive_write_filter *); +static int archive_compressor_program_free(struct archive_write_filter *); + +/* + * Add a filter to this write handle that passes all data through an + * external program. + */ +int +archive_write_add_filter_program(struct archive *_a, const char *cmd) +{ + struct archive_write_filter *f = __archive_write_allocate_filter(_a); + struct archive_write *a = (struct archive_write *)_a; + struct private_data *data; + static const char *prefix = "Program: "; + archive_check_magic(&a->archive, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_program"); + data = calloc(1, sizeof(*data)); + if (data == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + data->cmd = strdup(cmd); + data->description = (char *)malloc(strlen(prefix) + strlen(cmd) + 1); + strcpy(data->description, prefix); + strcat(data->description, cmd); + + f->name = data->description; + f->data = data; + f->open = &archive_compressor_program_open; + f->code = ARCHIVE_COMPRESSION_PROGRAM; + return (ARCHIVE_OK); +} + +/* + * Setup callback. + */ +static int +archive_compressor_program_open(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret; + + ret = __archive_write_open_filter(f->next_filter); + if (ret != ARCHIVE_OK) + return (ret); + + if (data->child_buf == NULL) { + data->child_buf_len = 65536; + data->child_buf_avail = 0; + data->child_buf = malloc(data->child_buf_len); + + if (data->child_buf == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate compression buffer"); + return (ARCHIVE_FATAL); + } + } + + if ((data->child = __archive_create_child(data->cmd, + &data->child_stdin, &data->child_stdout)) == -1) { + archive_set_error(f->archive, EINVAL, + "Can't initialise filter"); + return (ARCHIVE_FATAL); + } + + f->write = archive_compressor_program_write; + f->close = archive_compressor_program_close; + f->free = archive_compressor_program_free; + return (0); +} + +static ssize_t +child_write(struct archive_write_filter *f, const char *buf, size_t buf_len) +{ + struct private_data *data = f->data; + ssize_t ret; + + if (data->child_stdin == -1) + return (-1); + + if (buf_len == 0) + return (-1); + +restart_write: + do { + ret = write(data->child_stdin, buf, buf_len); + } while (ret == -1 && errno == EINTR); + + if (ret > 0) + return (ret); + if (ret == 0) { + close(data->child_stdin); + data->child_stdin = -1; + fcntl(data->child_stdout, F_SETFL, 0); + return (0); + } + if (ret == -1 && errno != EAGAIN) + return (-1); + + if (data->child_stdout == -1) { + fcntl(data->child_stdin, F_SETFL, 0); + __archive_check_child(data->child_stdin, data->child_stdout); + goto restart_write; + } + + do { + ret = read(data->child_stdout, + data->child_buf + data->child_buf_avail, + data->child_buf_len - data->child_buf_avail); + } while (ret == -1 && errno == EINTR); + + if (ret == 0 || (ret == -1 && errno == EPIPE)) { + close(data->child_stdout); + data->child_stdout = -1; + fcntl(data->child_stdin, F_SETFL, 0); + goto restart_write; + } + if (ret == -1 && errno == EAGAIN) { + __archive_check_child(data->child_stdin, data->child_stdout); + goto restart_write; + } + if (ret == -1) + return (-1); + + data->child_buf_avail += ret; + + ret = __archive_write_filter(f->next_filter, + data->child_buf, data->child_buf_avail); + if (ret <= 0) + return (-1); + + if ((size_t)ret < data->child_buf_avail) { + memmove(data->child_buf, data->child_buf + ret, + data->child_buf_avail - ret); + } + data->child_buf_avail -= ret; + goto restart_write; +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_program_write(struct archive_write_filter *f, + const void *buff, size_t length) +{ + ssize_t ret; + const char *buf; + + buf = buff; + while (length > 0) { + ret = child_write(f, buf, length); + if (ret == -1 || ret == 0) { + archive_set_error(f->archive, EIO, + "Can't write to filter"); + return (ARCHIVE_FATAL); + } + length -= ret; + buf += ret; + } + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_program_close(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret, r1, status; + ssize_t bytes_read; + + ret = 0; + close(data->child_stdin); + data->child_stdin = -1; + fcntl(data->child_stdout, F_SETFL, 0); + + for (;;) { + do { + bytes_read = read(data->child_stdout, + data->child_buf + data->child_buf_avail, + data->child_buf_len - data->child_buf_avail); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0 || (bytes_read == -1 && errno == EPIPE)) + break; + + if (bytes_read == -1) { + archive_set_error(f->archive, errno, + "Read from filter failed unexpectedly."); + ret = ARCHIVE_FATAL; + goto cleanup; + } + data->child_buf_avail += bytes_read; + + ret = __archive_write_filter(f->next_filter, + data->child_buf, data->child_buf_avail); + if (ret != ARCHIVE_OK) { + ret = ARCHIVE_FATAL; + goto cleanup; + } + data->child_buf_avail = 0; + } + +cleanup: + /* Shut down the child. */ + if (data->child_stdin != -1) + close(data->child_stdin); + if (data->child_stdout != -1) + close(data->child_stdout); + while (waitpid(data->child, &status, 0) == -1 && errno == EINTR) + continue; + + if (status != 0) { + archive_set_error(f->archive, EIO, + "Filter exited with failure."); + ret = ARCHIVE_FATAL; + } + r1 = __archive_write_close_filter(f->next_filter); + return (r1 < ret ? r1 : ret); +} + +static int +archive_compressor_program_free(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + free(data->cmd); + free(data->description); + free(data->child_buf); + free(data); + f->data = NULL; + return (ARCHIVE_OK); +} + +#endif /* !defined(HAVE_PIPE) || !defined(HAVE_VFORK) || !defined(HAVE_FCNTL) */ diff --git a/libarchive/archive_write_add_filter_xz.c b/libarchive/archive_write_add_filter_xz.c new file mode 100644 index 0000000..b067752 --- /dev/null +++ b/libarchive/archive_write_add_filter_xz.c @@ -0,0 +1,502 @@ +/*- + * Copyright (c) 2009,2010 Michihiro NAKAJIMA + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_compression_xz.c 201108 2009-12-28 03:28:21Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include +#ifdef HAVE_LZMA_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#if ARCHIVE_VERSION_NUMBER < 4000000 +int +archive_write_set_compression_lzip(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_lzip(a)); +} + +int +archive_write_set_compression_lzma(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_lzma(a)); +} + +int +archive_write_set_compression_xz(struct archive *a) +{ + __archive_write_filters_free(a); + return (archive_write_add_filter_xz(a)); +} + +#endif + +#ifndef HAVE_LZMA_H +int +archive_write_add_filter_xz(struct archive *a) +{ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "xz compression not supported on this platform"); + return (ARCHIVE_FATAL); +} + +int +archive_write_add_filter_lzma(struct archive *a) +{ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma compression not supported on this platform"); + return (ARCHIVE_FATAL); +} + +int +archive_write_add_filter_lzip(struct archive *a) +{ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma compression not supported on this platform"); + return (ARCHIVE_FATAL); +} +#else +/* Don't compile this if we don't have liblzma. */ + +struct private_data { + int compression_level; + lzma_stream stream; + lzma_filter lzmafilters[2]; + lzma_options_lzma lzma_opt; + int64_t total_in; + unsigned char *compressed; + size_t compressed_buffer_size; + int64_t total_out; + /* the CRC32 value of uncompressed data for lzip */ + uint32_t crc32; +}; + +static int archive_compressor_xz_options(struct archive_write_filter *, + const char *, const char *); +static int archive_compressor_xz_open(struct archive_write_filter *); +static int archive_compressor_xz_write(struct archive_write_filter *, + const void *, size_t); +static int archive_compressor_xz_close(struct archive_write_filter *); +static int archive_compressor_xz_free(struct archive_write_filter *); +static int drive_compressor(struct archive_write_filter *, + struct private_data *, int finishing); + +struct option_value { + uint32_t dict_size; + uint32_t nice_len; + lzma_match_finder mf; +}; +static const struct option_value option_values[] = { + { 1 << 16, 32, LZMA_MF_HC3}, + { 1 << 20, 32, LZMA_MF_HC3}, + { 3 << 19, 32, LZMA_MF_HC4}, + { 1 << 21, 32, LZMA_MF_BT4}, + { 3 << 20, 32, LZMA_MF_BT4}, + { 1 << 22, 32, LZMA_MF_BT4}, + { 1 << 23, 64, LZMA_MF_BT4}, + { 1 << 24, 64, LZMA_MF_BT4}, + { 3 << 23, 64, LZMA_MF_BT4}, + { 1 << 25, 64, LZMA_MF_BT4} +}; + +static int +common_setup(struct archive_write_filter *f) +{ + struct private_data *data; + struct archive_write *a = (struct archive_write *)f->archive; + data = calloc(1, sizeof(*data)); + if (data == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + f->data = data; + data->compression_level = LZMA_PRESET_DEFAULT; + f->open = &archive_compressor_xz_open; + f->close = archive_compressor_xz_close; + f->free = archive_compressor_xz_free; + f->options = &archive_compressor_xz_options; + return (ARCHIVE_OK); +} + +/* + * Add an xz compression filter to this write handle. + */ +int +archive_write_add_filter_xz(struct archive *_a) +{ + struct archive_write_filter *f; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_xz"); + f = __archive_write_allocate_filter(_a); + r = common_setup(f); + if (r == ARCHIVE_OK) { + f->code = ARCHIVE_COMPRESSION_XZ; + f->name = "xz"; + } + return (r); +} + +/* LZMA is handled identically, we just need a different compression + * code set. (The liblzma setup looks at the code to determine + * the one place that XZ and LZMA require different handling.) */ +int +archive_write_add_filter_lzma(struct archive *_a) +{ + struct archive_write_filter *f; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_lzma"); + f = __archive_write_allocate_filter(_a); + r = common_setup(f); + if (r == ARCHIVE_OK) { + f->code = ARCHIVE_COMPRESSION_LZMA; + f->name = "lzma"; + } + return (r); +} + +int +archive_write_add_filter_lzip(struct archive *_a) +{ + struct archive_write_filter *f; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_add_filter_lzip"); + f = __archive_write_allocate_filter(_a); + r = common_setup(f); + if (r == ARCHIVE_OK) { + f->code = ARCHIVE_COMPRESSION_LZIP; + f->name = "lzip"; + } + return (r); +} + +static int +archive_compressor_xz_init_stream(struct archive_write_filter *f, + struct private_data *data) +{ + static const lzma_stream lzma_stream_init_data = LZMA_STREAM_INIT; + int ret; + + data->stream = lzma_stream_init_data; + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + if (f->code == ARCHIVE_COMPRESSION_XZ) + ret = lzma_stream_encoder(&(data->stream), + data->lzmafilters, LZMA_CHECK_CRC64); + else if (f->code == ARCHIVE_COMPRESSION_LZMA) + ret = lzma_alone_encoder(&(data->stream), &data->lzma_opt); + else { /* ARCHIVE_COMPRESSION_LZIP */ + int dict_size = data->lzma_opt.dict_size; + int ds, log2dic, wedges; + + /* Calculate a coded dictionary size */ + if (dict_size < (1 << 12) || dict_size > (1 << 27)) { + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Unacceptable dictionary dize for lzip: %d", + dict_size); + return (ARCHIVE_FATAL); + } + for (log2dic = 27; log2dic >= 12; log2dic--) { + if (dict_size & (1 << log2dic)) + break; + } + if (dict_size > (1 << log2dic)) { + log2dic++; + wedges = + ((1 << log2dic) - dict_size) / (1 << (log2dic - 4)); + } else + wedges = 0; + ds = ((wedges << 5) & 0xe0) | (log2dic & 0x1f); + + data->crc32 = 0; + /* Make a header */ + data->compressed[0] = 0x4C; + data->compressed[1] = 0x5A; + data->compressed[2] = 0x49; + data->compressed[3] = 0x50; + data->compressed[4] = 1;/* Version */ + data->compressed[5] = (unsigned char)ds; + data->stream.next_out += 6; + data->stream.avail_out -= 6; + + ret = lzma_raw_encoder(&(data->stream), data->lzmafilters); + } + if (ret == LZMA_OK) + return (ARCHIVE_OK); + + switch (ret) { + case LZMA_MEM_ERROR: + archive_set_error(f->archive, ENOMEM, + "Internal error initializing compression library: " + "Cannot allocate memory"); + break; + default: + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "It's a bug in liblzma"); + break; + } + return (ARCHIVE_FATAL); +} + +/* + * Setup callback. + */ +static int +archive_compressor_xz_open(struct archive_write_filter *f) +{ + struct private_data *data = f->data; + int ret; + + ret = __archive_write_open_filter(f->next_filter); + if (ret != ARCHIVE_OK) + return (ret); + + if (data->compressed == NULL) { + data->compressed_buffer_size = 65536; + data->compressed + = (unsigned char *)malloc(data->compressed_buffer_size); + if (data->compressed == NULL) { + archive_set_error(f->archive, ENOMEM, + "Can't allocate data for compression buffer"); + return (ARCHIVE_FATAL); + } + } + + f->write = archive_compressor_xz_write; + + /* Initialize compression library. */ + if (f->code == ARCHIVE_COMPRESSION_LZIP) { + const struct option_value *val = + &option_values[data->compression_level]; + + data->lzma_opt.dict_size = val->dict_size; + data->lzma_opt.preset_dict = NULL; + data->lzma_opt.preset_dict_size = 0; + data->lzma_opt.lc = LZMA_LC_DEFAULT; + data->lzma_opt.lp = LZMA_LP_DEFAULT; + data->lzma_opt.pb = LZMA_PB_DEFAULT; + data->lzma_opt.mode = + data->compression_level<= 2? LZMA_MODE_FAST:LZMA_MODE_NORMAL; + data->lzma_opt.nice_len = val->nice_len; + data->lzma_opt.mf = val->mf; + data->lzma_opt.depth = 0; + data->lzmafilters[0].id = LZMA_FILTER_LZMA1; + data->lzmafilters[0].options = &data->lzma_opt; + data->lzmafilters[1].id = LZMA_VLI_UNKNOWN;/* Terminate */ + } else { + if (lzma_lzma_preset(&data->lzma_opt, data->compression_level)) { + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + } + data->lzmafilters[0].id = LZMA_FILTER_LZMA2; + data->lzmafilters[0].options = &data->lzma_opt; + data->lzmafilters[1].id = LZMA_VLI_UNKNOWN;/* Terminate */ + } + ret = archive_compressor_xz_init_stream(f, data); + if (ret == LZMA_OK) { + f->data = data; + return (0); + } + return (ARCHIVE_FATAL); +} + +/* + * Set write options. + */ +static int +archive_compressor_xz_options(struct archive_write_filter *f, + const char *key, const char *value) +{ + struct private_data *data = (struct private_data *)f->data; + + if (strcmp(key, "compression-level") == 0) { + if (value == NULL || !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') + return (ARCHIVE_WARN); + data->compression_level = value[0] - '0'; + if (data->compression_level > 6) + data->compression_level = 6; + return (ARCHIVE_OK); + } + + return (ARCHIVE_WARN); +} + +/* + * Write data to the compressed stream. + */ +static int +archive_compressor_xz_write(struct archive_write_filter *f, + const void *buff, size_t length) +{ + struct private_data *data = (struct private_data *)f->data; + int ret; + + /* Update statistics */ + data->total_in += length; + if (f->code == ARCHIVE_COMPRESSION_LZIP) + data->crc32 = lzma_crc32(buff, length, data->crc32); + + /* Compress input data to output buffer */ + data->stream.next_in = buff; + data->stream.avail_in = length; + if ((ret = drive_compressor(f, data, 0)) != ARCHIVE_OK) + return (ret); + + return (ARCHIVE_OK); +} + + +/* + * Finish the compression... + */ +static int +archive_compressor_xz_close(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + int ret, r1; + + ret = drive_compressor(f, data, 1); + if (ret == ARCHIVE_OK) { + data->total_out += + data->compressed_buffer_size - data->stream.avail_out; + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size - data->stream.avail_out); + if (f->code == ARCHIVE_COMPRESSION_LZIP && ret == ARCHIVE_OK) { + archive_le32enc(data->compressed, data->crc32); + archive_le64enc(data->compressed+4, data->total_in); + archive_le64enc(data->compressed+12, data->total_out + 20); + ret = __archive_write_filter(f->next_filter, + data->compressed, 20); + } + } + lzma_end(&(data->stream)); + r1 = __archive_write_close_filter(f->next_filter); + return (r1 < ret ? r1 : ret); +} + +static int +archive_compressor_xz_free(struct archive_write_filter *f) +{ + struct private_data *data = (struct private_data *)f->data; + free(data->compressed); + free(data); + f->data = NULL; + return (ARCHIVE_OK); +} + +/* + * Utility function to push input data through compressor, + * writing full output blocks as necessary. + * + * Note that this handles both the regular write case (finishing == + * false) and the end-of-archive case (finishing == true). + */ +static int +drive_compressor(struct archive_write_filter *f, + struct private_data *data, int finishing) +{ + int ret; + + for (;;) { + if (data->stream.avail_out == 0) { + data->total_out += data->compressed_buffer_size; + ret = __archive_write_filter(f->next_filter, + data->compressed, + data->compressed_buffer_size); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + data->stream.next_out = data->compressed; + data->stream.avail_out = data->compressed_buffer_size; + } + + /* If there's nothing to do, we're done. */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + + ret = lzma_code(&(data->stream), + finishing ? LZMA_FINISH : LZMA_RUN ); + + switch (ret) { + case LZMA_OK: + /* In non-finishing case, check if compressor + * consumed everything */ + if (!finishing && data->stream.avail_in == 0) + return (ARCHIVE_OK); + /* In finishing case, this return always means + * there's more work */ + break; + case LZMA_STREAM_END: + /* This return can only occur in finishing case. */ + if (finishing) + return (ARCHIVE_OK); + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "lzma compression data error"); + return (ARCHIVE_FATAL); + case LZMA_MEMLIMIT_ERROR: + archive_set_error(f->archive, ENOMEM, + "lzma compression error: " + "%ju MiB would have been needed", + (uintmax_t)((lzma_memusage(&(data->stream)) + + 1024 * 1024 -1) + / (1024 * 1024))); + return (ARCHIVE_FATAL); + default: + /* Any other return value indicates an error. */ + archive_set_error(f->archive, ARCHIVE_ERRNO_MISC, + "lzma compression failed:" + " lzma_code() call returned status %d", + ret); + return (ARCHIVE_FATAL); + } + } +} + +#endif /* HAVE_LZMA_H */ diff --git a/libarchive/archive_write_blocksize.3 b/libarchive/archive_write_blocksize.3 new file mode 100644 index 0000000..239fa92 --- /dev/null +++ b/libarchive/archive_write_blocksize.3 @@ -0,0 +1,112 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_blocksize 3 +.Os +.Sh NAME +.Nm archive_write_get_bytes_per_block , +.Nm archive_write_set_bytes_per_block , +.Nm archive_write_get_bytes_in_last_block , +.Nm archive_write_set_bytes_in_last_block +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_get_bytes_per_block "struct archive *" +.Ft int +.Fn archive_write_set_bytes_per_block "struct archive *" "int bytes_per_block" +.Ft int +.Fn archive_write_get_bytes_in_last_block "struct archive *" +.Ft int +.Fn archive_write_set_bytes_in_last_block "struct archive *" "int" +.Sh DESCRIPTION +.Bl -tag -width indent +.It Fn archive_write_set_bytes_per_block +Sets the block size used for writing the archive data. +Every call to the write callback function, except possibly the last one, will +use this value for the length. +The default is to use a block size of 10240 bytes. +Note that a block size of zero will suppress internal blocking +and cause writes to be sent directly to the write callback as they occur. +.It Fn archive_write_get_bytes_per_block +Retrieve the block size to be used for writing. +A value of -1 here indicates that the library should use default values. +A value of zero indicates that internal blocking is suppressed. +.It Fn archive_write_set_bytes_in_last_block +Sets the block size used for writing the last block. +If this value is zero, the last block will be padded to the same size +as the other blocks. +Otherwise, the final block will be padded to a multiple of this size. +In particular, setting it to 1 will cause the final block to not be padded. +For compressed output, any padding generated by this option +is applied only after the compression. +The uncompressed data is always unpadded. +The default is to pad the last block to the full block size (note that +.Fn archive_write_open_filename +will set this based on the file type). +Unlike the other +.Dq set +functions, this function can be called after the archive is opened. +.It Fn archive_write_get_bytes_in_last_block +Retrieve the currently-set value for last block size. +A value of -1 here indicates that the library should use default values. +.El +.\" .Sh EXAMPLE +.Sh RETURN VALUES +.Fn archive_write_set_bytes_per_block +and +.Fn archive_write_set_bytes_in_last_block +return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.Pp +.Fn archive_write_get_bytes_per_block +and +.Fn archive_write_get_bytes_in_last_block +return currently configured block size +.Po +.Li -1 +indicates the default block size +.Pc , +or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_data.3 b/libarchive/archive_write_data.3 new file mode 100644 index 0000000..2c98b3e --- /dev/null +++ b/libarchive/archive_write_data.3 @@ -0,0 +1,60 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_write.3 201110 2009-12-28 03:31:29Z kientzle $ +.\" +.Dd March 23, 2011 +.Dt archive_write 3 +.Os +.Sh NAME +.Nm archive_write_data +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft ssize_t +.Fn archive_write_data "struct archive *" "const void *" "size_t" +.Sh DESCRIPTION +Write data corresponding to the header just written. +.\" .Sh EXAMPLE +.\" +.Sh RETURN VALUES +This function returns the number of bytes actually written, or +.Li -1 +on error. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_finish_entry 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_disk.3 b/libarchive/archive_write_disk.3 new file mode 100644 index 0000000..90bbdcf --- /dev/null +++ b/libarchive/archive_write_disk.3 @@ -0,0 +1,401 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/archive_write_disk.3,v 1.4 2008/09/04 05:22:00 kientzle Exp $ +.\" +.Dd August 5, 2008 +.Dt archive_write_disk 3 +.Os +.Sh NAME +.Nm archive_write_disk_new , +.Nm archive_write_disk_set_options , +.Nm archive_write_disk_set_skip_file , +.Nm archive_write_disk_set_group_lookup , +.Nm archive_write_disk_set_standard_lookup , +.Nm archive_write_disk_set_user_lookup , +.Nm archive_write_header , +.Nm archive_write_data , +.Nm archive_write_data_block , +.Nm archive_write_finish_entry , +.Nm archive_write_close , +.Nm archive_write_finish +.Nm archive_write_free +.Nd functions for creating objects on disk +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_write_disk_new "void" +.Ft int +.Fn archive_write_disk_set_options "struct archive *" "int flags" +.Ft int +.Fn archive_write_disk_set_skip_file "struct archive *" "dev_t" "ino_t" +.Ft int +.Fo archive_write_disk_set_group_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "gid_t (*)(void *, const char *gname, gid_t gid)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fn archive_write_disk_set_standard_lookup "struct archive *" +.Ft int +.Fo archive_write_disk_set_user_lookup +.Fa "struct archive *" +.Fa "void *" +.Fa "uid_t (*)(void *, const char *uname, uid_t uid)" +.Fa "void (*cleanup)(void *)" +.Fc +.Ft int +.Fn archive_write_header "struct archive *" "struct archive_entry *" +.Ft ssize_t +.Fn archive_write_data "struct archive *" "const void *" "size_t" +.Ft ssize_t +.Fn archive_write_data_block "struct archive *" "const void *" "size_t size" "int64_t offset" +.Ft int +.Fn archive_write_finish_entry "struct archive *" +.Ft int +.Fn archive_write_close "struct archive *" +.Ft int +.Fn archive_write_finish "struct archive *" +.Ft int +.Fn archive_write_free "struct archive *" +.Sh DESCRIPTION +These functions provide a complete API for creating objects on +disk from +.Tn struct archive_entry +descriptions. +They are most naturally used when extracting objects from an archive +using the +.Fn archive_read +interface. +The general process is to read +.Tn struct archive_entry +objects from an archive, then write those objects to a +.Tn struct archive +object created using the +.Fn archive_write_disk +family functions. +This interface is deliberately very similar to the +.Fn archive_write +interface used to write objects to a streaming archive. +.Bl -tag -width indent +.It Fn archive_write_disk_new +Allocates and initializes a +.Tn struct archive +object suitable for writing objects to disk. +.It Fn archive_write_disk_set_skip_file +Records the device and inode numbers of a file that should not be +overwritten. +This is typically used to ensure that an extraction process does not +overwrite the archive from which objects are being read. +This capability is technically unnecessary but can be a significant +performance optimization in practice. +.It Fn archive_write_disk_set_options +The options field consists of a bitwise OR of one or more of the +following values: +.Bl -tag -compact -width "indent" +.It Cm ARCHIVE_EXTRACT_OWNER +The user and group IDs should be set on the restored file. +By default, the user and group IDs are not restored. +.It Cm ARCHIVE_EXTRACT_PERM +Full permissions (including SGID, SUID, and sticky bits) should +be restored exactly as specified, without obeying the +current umask. +Note that SUID and SGID bits can only be restored if the +user and group ID of the object on disk are correct. +If +.Cm ARCHIVE_EXTRACT_OWNER +is not specified, then SUID and SGID bits will only be restored +if the default user and group IDs of newly-created objects on disk +happen to match those specified in the archive entry. +By default, only basic permissions are restored, and umask is obeyed. +.It Cm ARCHIVE_EXTRACT_TIME +The timestamps (mtime, ctime, and atime) should be restored. +By default, they are ignored. +Note that restoring of atime is not currently supported. +.It Cm ARCHIVE_EXTRACT_NO_OVERWRITE +Existing files on disk will not be overwritten. +By default, existing regular files are truncated and overwritten; +existing directories will have their permissions updated; +other pre-existing objects are unlinked and recreated from scratch. +.It Cm ARCHIVE_EXTRACT_UNLINK +Existing files on disk will be unlinked before any attempt to +create them. +In some cases, this can prove to be a significant performance improvement. +By default, existing files are truncated and rewritten, but +the file is not recreated. +In particular, the default behavior does not break existing hard links. +.It Cm ARCHIVE_EXTRACT_ACL +Attempt to restore ACLs. +By default, extended ACLs are ignored. +.It Cm ARCHIVE_EXTRACT_FFLAGS +Attempt to restore extended file flags. +By default, file flags are ignored. +.It Cm ARCHIVE_EXTRACT_XATTR +Attempt to restore POSIX.1e extended attributes. +By default, they are ignored. +.It Cm ARCHIVE_EXTRACT_SECURE_SYMLINKS +Refuse to extract any object whose final location would be altered +by a symlink on disk. +This is intended to help guard against a variety of mischief +caused by archives that (deliberately or otherwise) extract +files outside of the current directory. +The default is not to perform this check. +If +.Cm ARCHIVE_EXTRACT_UNLINK +is specified together with this option, the library will +remove any intermediate symlinks it finds and return an +error only if such symlink could not be removed. +.It Cm ARCHIVE_EXTRACT_SECURE_NODOTDOT +Refuse to extract a path that contains a +.Pa .. +element anywhere within it. +The default is to not refuse such paths. +Note that paths ending in +.Pa .. +always cause an error, regardless of this flag. +.It Cm ARCHIVE_EXTRACT_SPARSE +Scan data for blocks of NUL bytes and try to recreate them with holes. +This results in sparse files, independent of whether the archive format +supports or uses them. +.El +.It Xo +.Fn archive_write_disk_set_group_lookup , +.Fn archive_write_disk_set_user_lookup +.Xc +The +.Tn struct archive_entry +objects contain both names and ids that can be used to identify users +and groups. +These names and ids describe the ownership of the file itself and +also appear in ACL lists. +By default, the library uses the ids and ignores the names, but +this can be overridden by registering user and group lookup functions. +To register, you must provide a lookup function which +accepts both a name and id and returns a suitable id. +You may also provide a +.Tn void * +pointer to a private data structure and a cleanup function for +that data. +The cleanup function will be invoked when the +.Tn struct archive +object is destroyed. +.It Fn archive_write_disk_set_standard_lookup +This convenience function installs a standard set of user +and group lookup functions. +These functions use +.Xr getpwnam 3 +and +.Xr getgrnam 3 +to convert names to ids, defaulting to the ids if the names cannot +be looked up. +These functions also implement a simple memory cache to reduce +the number of calls to +.Xr getpwnam 3 +and +.Xr getgrnam 3 . +.It Fn archive_write_header +Build and write a header using the data in the provided +.Tn struct archive_entry +structure. +See +.Xr archive_entry 3 +for information on creating and populating +.Tn struct archive_entry +objects. +.It Fn archive_write_data +Write data corresponding to the header just written. +Returns number of bytes written or -1 on error. +.It Fn archive_write_data_block +Write data corresponding to the header just written. +This is like +.Fn archive_write_data +except that it performs a seek on the file being +written to the specified offset before writing the data. +This is useful when restoring sparse files from archive +formats that support sparse files. +Returns number of bytes written or -1 on error. +(Note: This is currently not supported for +.Tn archive_write +handles, only for +.Tn archive_write_disk +handles.) +.It Fn archive_write_finish_entry +Close out the entry just written. +Ordinarily, clients never need to call this, as it +is called automatically by +.Fn archive_write_next_header +and +.Fn archive_write_close +as needed. +However, some file attributes are written to disk only +after the file is closed, so this can be necessary +if you need to work with the file on disk right away. +.It Fn archive_write_close +Set any attributes that could not be set during the initial restore. +For example, directory timestamps are not restored initially because +restoring a subsequent file would alter that timestamp. +Similarly, non-writable directories are initially created with +write permissions (so that their contents can be restored). +The +.Nm +library maintains a list of all such deferred attributes and +sets them when this function is invoked. +.It Fn archive_write_finish +This is a deprecated synonym for +.Fn archive_write_free . +.It Fn archive_write_free +Invokes +.Fn archive_write_close +if it was not invoked manually, then releases all resources. +.El +More information about the +.Va struct archive +object and the overall design of the library can be found in the +.Xr libarchive 3 +overview. +Many of these functions are also documented under +.Xr archive_write 3 . +.Sh RETURN VALUES +Most functions return +.Cm ARCHIVE_OK +(zero) on success, or one of several non-zero +error codes for errors. +Specific error codes include: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +.Pp +.Fn archive_write_disk_new +returns a pointer to a newly-allocated +.Tn struct archive +object. +.Pp +.Fn archive_write_data +returns a count of the number of bytes actually written, +or +.Li -1 +on error. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr tar 1 , +.Xr libarchive 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +The +.Nm archive_write_disk +interface was added to +.Nm libarchive 2.0 +and first appeared in +.Fx 6.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Directories are actually extracted in two distinct phases. +Directories are created during +.Fn archive_write_header , +but final permissions are not set until +.Fn archive_write_close . +This separation is necessary to correctly handle borderline +cases such as a non-writable directory containing +files, but can cause unexpected results. +In particular, directory permissions are not fully +restored until the archive is closed. +If you use +.Xr chdir 2 +to change the current directory between calls to +.Fn archive_read_extract +or before calling +.Fn archive_read_close , +you may confuse the permission-setting logic with +the result that directory permissions are restored +incorrectly. +.Pp +The library attempts to create objects with filenames longer than +.Cm PATH_MAX +by creating prefixes of the full path and changing the current directory. +Currently, this logic is limited in scope; the fixup pass does +not work correctly for such objects and the symlink security check +option disables the support for very long pathnames. +.Pp +Restoring the path +.Pa aa/../bb +does create each intermediate directory. +In particular, the directory +.Pa aa +is created as well as the final object +.Pa bb . +In theory, this can be exploited to create an entire directory hierarchy +with a single request. +Of course, this does not work if the +.Cm ARCHIVE_EXTRACT_NODOTDOT +option is specified. +.Pp +Implicit directories are always created obeying the current umask. +Explicit objects are created obeying the current umask unless +.Cm ARCHIVE_EXTRACT_PERM +is specified, in which case they current umask is ignored. +.Pp +SGID and SUID bits are restored only if the correct user and +group could be set. +If +.Cm ARCHIVE_EXTRACT_OWNER +is not specified, then no attempt is made to set the ownership. +In this case, SGID and SUID bits are restored only if the +user and group of the final object happen to match those specified +in the entry. +.Pp +The +.Dq standard +user-id and group-id lookup functions are not the defaults because +.Xr getgrnam 3 +and +.Xr getpwnam 3 +are sometimes too large for particular applications. +The current design allows the application author to use a more +compact implementation when appropriate. +.Pp +There should be a corresponding +.Nm archive_read_disk +interface that walks a directory hierarchy and returns archive +entry objects. diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c new file mode 100644 index 0000000..99afab2 --- /dev/null +++ b/libarchive/archive_write_disk_posix.c @@ -0,0 +1,2844 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#if !defined(_WIN32) || defined(__CYGWIN__) + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_ACL_H +#include +#endif +#ifdef HAVE_SYS_EXTATTR_H +#include +#endif +#ifdef HAVE_SYS_XATTR_H +#include +#endif +#ifdef HAVE_SYS_EA_H +#include +#endif +#ifdef HAVE_ATTR_XATTR_H +#include +#endif +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_SYS_UTIME_H +#include +#endif +#ifdef HAVE_COPYFILE_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#ifdef HAVE_LANGINFO_H +#include +#endif +#ifdef HAVE_LINUX_FS_H +#include /* for Linux file flags */ +#endif +/* + * Some Linux distributions have both linux/ext2_fs.h and ext2fs/ext2_fs.h. + * As the include guards don't agree, the order of include is important. + */ +#ifdef HAVE_LINUX_EXT2_FS_H +#include /* for Linux file flags */ +#endif +#if defined(HAVE_EXT2FS_EXT2_FS_H) && !defined(__CYGWIN__) +#include /* Linux file flags, broken on Cygwin */ +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#ifdef HAVE_PWD_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_UTIME_H +#include +#endif +#ifdef F_GETTIMES /* Tru64 specific */ +#include +#endif + +#if __APPLE__ +#include +#if TARGET_OS_MAC && !TARGET_OS_EMBEDDED && HAVE_QUARANTINE_H +#include +#define HAVE_QUARANTINE 1 +#endif +#endif + +/* TODO: Support Mac OS 'quarantine' feature. This is really just a + * standard tag to mark files that have been downloaded as "tainted". + * On Mac OS, we should mark the extracted files as tainted if the + * archive being read was tainted. Windows has a similar feature; we + * should investigate ways to support this generically. */ + +#include "archive.h" +#include "archive_acl_private.h" +#include "archive_string.h" +#include "archive_entry.h" +#include "archive_private.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct fixup_entry { + struct fixup_entry *next; + struct archive_acl acl; + mode_t mode; + int64_t atime; + int64_t birthtime; + int64_t mtime; + int64_t ctime; + unsigned long atime_nanos; + unsigned long birthtime_nanos; + unsigned long mtime_nanos; + unsigned long ctime_nanos; + unsigned long fflags_set; + size_t mac_metadata_size; + void *mac_metadata; + int fixup; /* bitmask of what needs fixing */ + char *name; +}; + +/* + * We use a bitmask to track which operations remain to be done for + * this file. In particular, this helps us avoid unnecessary + * operations when it's possible to take care of one step as a + * side-effect of another. For example, mkdir() can specify the mode + * for the newly-created object but symlink() cannot. This means we + * can skip chmod() if mkdir() succeeded, but we must explicitly + * chmod() if we're trying to create a directory that already exists + * (mkdir() failed) or if we're restoring a symlink. Similarly, we + * need to verify UID/GID before trying to restore SUID/SGID bits; + * that verification can occur explicitly through a stat() call or + * implicitly because of a successful chown() call. + */ +#define TODO_MODE_FORCE 0x40000000 +#define TODO_MODE_BASE 0x20000000 +#define TODO_SUID 0x10000000 +#define TODO_SUID_CHECK 0x08000000 +#define TODO_SGID 0x04000000 +#define TODO_SGID_CHECK 0x02000000 +#define TODO_MODE (TODO_MODE_BASE|TODO_SUID|TODO_SGID) +#define TODO_TIMES ARCHIVE_EXTRACT_TIME +#define TODO_OWNER ARCHIVE_EXTRACT_OWNER +#define TODO_FFLAGS ARCHIVE_EXTRACT_FFLAGS +#define TODO_ACLS ARCHIVE_EXTRACT_ACL +#define TODO_XATTR ARCHIVE_EXTRACT_XATTR +#define TODO_MAC_METADATA ARCHIVE_EXTRACT_MAC_METADATA + +struct archive_write_disk { + struct archive archive; + + mode_t user_umask; + struct fixup_entry *fixup_list; + struct fixup_entry *current_fixup; + int64_t user_uid; + int skip_file_set; + dev_t skip_file_dev; + ino_t skip_file_ino; + time_t start_time; + + int64_t (*lookup_gid)(void *private, const char *gname, int64_t gid); + void (*cleanup_gid)(void *private); + void *lookup_gid_data; + int64_t (*lookup_uid)(void *private, const char *uname, int64_t uid); + void (*cleanup_uid)(void *private); + void *lookup_uid_data; + + /* + * Full path of last file to satisfy symlink checks. + */ + struct archive_string path_safe; + + /* + * Cached stat data from disk for the current entry. + * If this is valid, pst points to st. Otherwise, + * pst is null. + */ + struct stat st; + struct stat *pst; + + /* Information about the object being restored right now. */ + struct archive_entry *entry; /* Entry being extracted. */ + char *name; /* Name of entry, possibly edited. */ + struct archive_string _name_data; /* backing store for 'name' */ + /* Tasks remaining for this object. */ + int todo; + /* Tasks deferred until end-of-archive. */ + int deferred; + /* Options requested by the client. */ + int flags; + /* Handle for the file we're restoring. */ + int fd; + /* Current offset for writing data to the file. */ + int64_t offset; + /* Last offset actually written to disk. */ + int64_t fd_offset; + /* Total bytes actually written to files. */ + int64_t total_bytes_written; + /* Maximum size of file, -1 if unknown. */ + int64_t filesize; + /* Dir we were in before this restore; only for deep paths. */ + int restore_pwd; + /* Mode we should use for this entry; affected by _PERM and umask. */ + mode_t mode; + /* UID/GID to use in restoring this entry. */ + int64_t uid; + int64_t gid; +}; + +/* + * Default mode for dirs created automatically (will be modified by umask). + * Note that POSIX specifies 0777 for implicitly-created dirs, "modified + * by the process' file creation mask." + */ +#define DEFAULT_DIR_MODE 0777 +/* + * Dir modes are restored in two steps: During the extraction, the permissions + * in the archive are modified to match the following limits. During + * the post-extract fixup pass, the permissions from the archive are + * applied. + */ +#define MINIMUM_DIR_MODE 0700 +#define MAXIMUM_DIR_MODE 0775 + +static int check_symlinks(struct archive_write_disk *); +static int create_filesystem_object(struct archive_write_disk *); +static struct fixup_entry *current_fixup(struct archive_write_disk *, const char *pathname); +#if defined(HAVE_FCHDIR) && defined(PATH_MAX) +static void edit_deep_directories(struct archive_write_disk *ad); +#endif +static int cleanup_pathname(struct archive_write_disk *); +static int create_dir(struct archive_write_disk *, char *); +static int create_parent_dir(struct archive_write_disk *, char *); +static int older(struct stat *, struct archive_entry *); +static int restore_entry(struct archive_write_disk *); +#ifdef HAVE_POSIX_ACL +static int set_acl(struct archive_write_disk *, int fd, const char *, struct archive_acl *, + acl_type_t, int archive_entry_acl_type, const char *tn); +#endif +static int set_acls(struct archive_write_disk *, int fd, const char *, struct archive_acl *); +static int set_mac_metadata(struct archive_write_disk *, const char *, + const void *, size_t); +static int set_xattrs(struct archive_write_disk *); +static int set_fflags(struct archive_write_disk *); +static int set_fflags_platform(struct archive_write_disk *, int fd, + const char *name, mode_t mode, + unsigned long fflags_set, unsigned long fflags_clear); +static int set_ownership(struct archive_write_disk *); +static int set_mode(struct archive_write_disk *, int mode); +static int set_time(int, int, const char *, time_t, long, time_t, long); +static int set_times(struct archive_write_disk *, int, int, const char *, + time_t, long, time_t, long, time_t, long, time_t, long); +static int set_times_from_entry(struct archive_write_disk *); +static struct fixup_entry *sort_dir_list(struct fixup_entry *p); +static ssize_t write_data_block(struct archive_write_disk *, + const char *, size_t); + +static struct archive_vtable *archive_write_disk_vtable(void); + +static int _archive_write_disk_close(struct archive *); +static int _archive_write_disk_free(struct archive *); +static int _archive_write_disk_header(struct archive *, struct archive_entry *); +static int64_t _archive_write_disk_filter_bytes(struct archive *, int); +static int _archive_write_disk_finish_entry(struct archive *); +static ssize_t _archive_write_disk_data(struct archive *, const void *, size_t); +static ssize_t _archive_write_disk_data_block(struct archive *, const void *, size_t, int64_t); + +static int +lazy_stat(struct archive_write_disk *a) +{ + if (a->pst != NULL) { + /* Already have stat() data available. */ + return (ARCHIVE_OK); + } +#ifdef HAVE_FSTAT + if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) { + a->pst = &a->st; + return (ARCHIVE_OK); + } +#endif + /* + * XXX At this point, symlinks should not be hit, otherwise + * XXX a race occurred. Do we want to check explicitly for that? + */ + if (lstat(a->name, &a->st) == 0) { + a->pst = &a->st; + return (ARCHIVE_OK); + } + archive_set_error(&a->archive, errno, "Couldn't stat file"); + return (ARCHIVE_WARN); +} + +static struct archive_vtable * +archive_write_disk_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_close = _archive_write_disk_close; + av.archive_filter_bytes = _archive_write_disk_filter_bytes; + av.archive_free = _archive_write_disk_free; + av.archive_write_header = _archive_write_disk_header; + av.archive_write_finish_entry + = _archive_write_disk_finish_entry; + av.archive_write_data = _archive_write_disk_data; + av.archive_write_data_block = _archive_write_disk_data_block; + inited = 1; + } + return (&av); +} + +static int64_t +_archive_write_disk_filter_bytes(struct archive *_a, int n) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + (void)n; /* UNUSED */ + if (n == -1 || n == 0) + return (a->total_bytes_written); + return (-1); +} + + +int +archive_write_disk_set_options(struct archive *_a, int flags) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + + a->flags = flags; + return (ARCHIVE_OK); +} + + +/* + * Extract this entry to disk. + * + * TODO: Validate hardlinks. According to the standards, we're + * supposed to check each extracted hardlink and squawk if it refers + * to a file that we didn't restore. I'm not entirely convinced this + * is a good idea, but more importantly: Is there any way to validate + * hardlinks without keeping a complete list of filenames from the + * entire archive?? Ugh. + * + */ +static int +_archive_write_disk_header(struct archive *_a, struct archive_entry *entry) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *fe; + int ret, r; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_header"); + archive_clear_error(&a->archive); + if (a->archive.state & ARCHIVE_STATE_DATA) { + r = _archive_write_disk_finish_entry(&a->archive); + if (r == ARCHIVE_FATAL) + return (r); + } + + /* Set up for this particular entry. */ + a->pst = NULL; + a->current_fixup = NULL; + a->deferred = 0; + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->entry = archive_entry_clone(entry); + a->fd = -1; + a->fd_offset = 0; + a->offset = 0; + a->restore_pwd = -1; + a->uid = a->user_uid; + a->mode = archive_entry_mode(a->entry); + if (archive_entry_size_is_set(a->entry)) + a->filesize = archive_entry_size(a->entry); + else + a->filesize = -1; + archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry)); + a->name = a->_name_data.s; + archive_clear_error(&a->archive); + + /* + * Clean up the requested path. This is necessary for correct + * dir restores; the dir restore logic otherwise gets messed + * up by nonsense like "dir/.". + */ + ret = cleanup_pathname(a); + if (ret != ARCHIVE_OK) + return (ret); + + /* + * Query the umask so we get predictable mode settings. + * This gets done on every call to _write_header in case the + * user edits their umask during the extraction for some + * reason. + */ + umask(a->user_umask = umask(0)); + + /* Figure out what we need to do for this entry. */ + a->todo = TODO_MODE_BASE; + if (a->flags & ARCHIVE_EXTRACT_PERM) { + a->todo |= TODO_MODE_FORCE; /* Be pushy about permissions. */ + /* + * SGID requires an extra "check" step because we + * cannot easily predict the GID that the system will + * assign. (Different systems assign GIDs to files + * based on a variety of criteria, including process + * credentials and the gid of the enclosing + * directory.) We can only restore the SGID bit if + * the file has the right GID, and we only know the + * GID if we either set it (see set_ownership) or if + * we've actually called stat() on the file after it + * was restored. Since there are several places at + * which we might verify the GID, we need a TODO bit + * to keep track. + */ + if (a->mode & S_ISGID) + a->todo |= TODO_SGID | TODO_SGID_CHECK; + /* + * Verifying the SUID is simpler, but can still be + * done in multiple ways, hence the separate "check" bit. + */ + if (a->mode & S_ISUID) + a->todo |= TODO_SUID | TODO_SUID_CHECK; + } else { + /* + * User didn't request full permissions, so don't + * restore SUID, SGID bits and obey umask. + */ + a->mode &= ~S_ISUID; + a->mode &= ~S_ISGID; + a->mode &= ~S_ISVTX; + a->mode &= ~a->user_umask; + } + if (a->flags & ARCHIVE_EXTRACT_OWNER) + a->todo |= TODO_OWNER; + if (a->flags & ARCHIVE_EXTRACT_TIME) + a->todo |= TODO_TIMES; + if (a->flags & ARCHIVE_EXTRACT_ACL) { + if (archive_entry_filetype(a->entry) == AE_IFDIR) + a->deferred |= TODO_ACLS; + else + a->todo |= TODO_ACLS; + } + if (a->flags & ARCHIVE_EXTRACT_MAC_METADATA) { + if (archive_entry_filetype(a->entry) == AE_IFDIR) + a->deferred |= TODO_MAC_METADATA; + else + a->todo |= TODO_MAC_METADATA; + } + if (a->flags & ARCHIVE_EXTRACT_XATTR) + a->todo |= TODO_XATTR; + if (a->flags & ARCHIVE_EXTRACT_FFLAGS) + a->todo |= TODO_FFLAGS; + if (a->flags & ARCHIVE_EXTRACT_SECURE_SYMLINKS) { + ret = check_symlinks(a); + if (ret != ARCHIVE_OK) + return (ret); + } +#if defined(HAVE_FCHDIR) && defined(PATH_MAX) + /* If path exceeds PATH_MAX, shorten the path. */ + edit_deep_directories(a); +#endif + + ret = restore_entry(a); + + /* + * TODO: There are rumours that some extended attributes must + * be restored before file data is written. If this is true, + * then we either need to write all extended attributes both + * before and after restoring the data, or find some rule for + * determining which must go first and which last. Due to the + * many ways people are using xattrs, this may prove to be an + * intractable problem. + */ + +#ifdef HAVE_FCHDIR + /* If we changed directory above, restore it here. */ + if (a->restore_pwd >= 0) { + r = fchdir(a->restore_pwd); + if (r != 0) { + archive_set_error(&a->archive, errno, "chdir() failure"); + ret = ARCHIVE_FATAL; + } + close(a->restore_pwd); + a->restore_pwd = -1; + } +#endif + + /* + * Fixup uses the unedited pathname from archive_entry_pathname(), + * because it is relative to the base dir and the edited path + * might be relative to some intermediate dir as a result of the + * deep restore logic. + */ + if (a->deferred & TODO_MODE) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->fixup |= TODO_MODE_BASE; + fe->mode = a->mode; + } + + if ((a->deferred & TODO_TIMES) + && (archive_entry_mtime_is_set(entry) + || archive_entry_atime_is_set(entry))) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->mode = a->mode; + fe->fixup |= TODO_TIMES; + if (archive_entry_atime_is_set(entry)) { + fe->atime = archive_entry_atime(entry); + fe->atime_nanos = archive_entry_atime_nsec(entry); + } else { + /* If atime is unset, use start time. */ + fe->atime = a->start_time; + fe->atime_nanos = 0; + } + if (archive_entry_mtime_is_set(entry)) { + fe->mtime = archive_entry_mtime(entry); + fe->mtime_nanos = archive_entry_mtime_nsec(entry); + } else { + /* If mtime is unset, use start time. */ + fe->mtime = a->start_time; + fe->mtime_nanos = 0; + } + if (archive_entry_birthtime_is_set(entry)) { + fe->birthtime = archive_entry_birthtime(entry); + fe->birthtime_nanos = archive_entry_birthtime_nsec(entry); + } else { + /* If birthtime is unset, use mtime. */ + fe->birthtime = fe->mtime; + fe->birthtime_nanos = fe->mtime_nanos; + } + } + + if (a->deferred & TODO_ACLS) { + fe = current_fixup(a, archive_entry_pathname(entry)); + archive_acl_copy(&fe->acl, archive_entry_acl(entry)); + } + + if (a->deferred & TODO_MAC_METADATA) { + const void *metadata; + size_t metadata_size; + metadata = archive_entry_mac_metadata(a->entry, &metadata_size); + if (metadata != NULL && metadata_size > 0) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->mac_metadata = malloc(metadata_size); + if (fe->mac_metadata != NULL) { + memcpy(fe->mac_metadata, metadata, metadata_size); + fe->mac_metadata_size = metadata_size; + fe->fixup |= TODO_MAC_METADATA; + } + } + } + + if (a->deferred & TODO_FFLAGS) { + fe = current_fixup(a, archive_entry_pathname(entry)); + fe->fixup |= TODO_FFLAGS; + /* TODO: Complete this.. defer fflags from below. */ + } + + /* We've created the object and are ready to pour data into it. */ + if (ret >= ARCHIVE_WARN) + a->archive.state = ARCHIVE_STATE_DATA; + /* + * If it's not open, tell our client not to try writing. + * In particular, dirs, links, etc, don't get written to. + */ + if (a->fd < 0) { + archive_entry_set_size(entry, 0); + a->filesize = 0; + } + + return (ret); +} + +int +archive_write_disk_set_skip_file(struct archive *_a, int64_t d, int64_t i) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_skip_file"); + a->skip_file_set = 1; + a->skip_file_dev = d; + a->skip_file_ino = i; + return (ARCHIVE_OK); +} + +static ssize_t +write_data_block(struct archive_write_disk *a, const char *buff, size_t size) +{ + uint64_t start_size = size; + ssize_t bytes_written = 0; + ssize_t block_size = 0, bytes_to_write; + + if (size == 0) + return (ARCHIVE_OK); + + if (a->filesize == 0 || a->fd < 0) { + archive_set_error(&a->archive, 0, + "Attempt to write to an empty file"); + return (ARCHIVE_WARN); + } + + if (a->flags & ARCHIVE_EXTRACT_SPARSE) { +#if HAVE_STRUCT_STAT_ST_BLKSIZE + int r; + if ((r = lazy_stat(a)) != ARCHIVE_OK) + return (r); + block_size = a->pst->st_blksize; +#else + /* XXX TODO XXX Is there a more appropriate choice here ? */ + /* This needn't match the filesystem allocation size. */ + block_size = 16*1024; +#endif + } + + /* If this write would run beyond the file size, truncate it. */ + if (a->filesize >= 0 && (int64_t)(a->offset + size) > a->filesize) + start_size = size = (size_t)(a->filesize - a->offset); + + /* Write the data. */ + while (size > 0) { + if (block_size == 0) { + bytes_to_write = size; + } else { + /* We're sparsifying the file. */ + const char *p, *end; + int64_t block_end; + + /* Skip leading zero bytes. */ + for (p = buff, end = buff + size; p < end; ++p) { + if (*p != '\0') + break; + } + a->offset += p - buff; + size -= p - buff; + buff = p; + if (size == 0) + break; + + /* Calculate next block boundary after offset. */ + block_end + = (a->offset / block_size + 1) * block_size; + + /* If the adjusted write would cross block boundary, + * truncate it to the block boundary. */ + bytes_to_write = size; + if (a->offset + bytes_to_write > block_end) + bytes_to_write = block_end - a->offset; + } + /* Seek if necessary to the specified offset. */ + if (a->offset != a->fd_offset) { + if (lseek(a->fd, a->offset, SEEK_SET) < 0) { + archive_set_error(&a->archive, errno, + "Seek failed"); + return (ARCHIVE_FATAL); + } + a->fd_offset = a->offset; + } + bytes_written = write(a->fd, buff, bytes_to_write); + if (bytes_written < 0) { + archive_set_error(&a->archive, errno, "Write failed"); + return (ARCHIVE_WARN); + } + buff += bytes_written; + size -= bytes_written; + a->total_bytes_written += bytes_written; + a->offset += bytes_written; + a->fd_offset = a->offset; + } + return (start_size - size); +} + +static ssize_t +_archive_write_disk_data_block(struct archive *_a, + const void *buff, size_t size, int64_t offset) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + ssize_t r; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data_block"); + + a->offset = offset; + r = write_data_block(a, buff, size); + if (r < ARCHIVE_OK) + return (r); + if ((size_t)r < size) { + archive_set_error(&a->archive, 0, + "Write request too large"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static ssize_t +_archive_write_disk_data(struct archive *_a, const void *buff, size_t size) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data"); + + return (write_data_block(a, buff, size)); +} + +static int +_archive_write_disk_finish_entry(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + int ret = ARCHIVE_OK; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_finish_entry"); + if (a->archive.state & ARCHIVE_STATE_HEADER) + return (ARCHIVE_OK); + archive_clear_error(&a->archive); + + /* Pad or truncate file to the right size. */ + if (a->fd < 0) { + /* There's no file. */ + } else if (a->filesize < 0) { + /* File size is unknown, so we can't set the size. */ + } else if (a->fd_offset == a->filesize) { + /* Last write ended at exactly the filesize; we're done. */ + /* Hopefully, this is the common case. */ + } else { +#if HAVE_FTRUNCATE + if (ftruncate(a->fd, a->filesize) == -1 && + a->filesize == 0) { + archive_set_error(&a->archive, errno, + "File size could not be restored"); + return (ARCHIVE_FAILED); + } +#endif + /* + * Not all platforms implement the XSI option to + * extend files via ftruncate. Stat() the file again + * to see what happened. + */ + a->pst = NULL; + if ((ret = lazy_stat(a)) != ARCHIVE_OK) + return (ret); + /* We can use lseek()/write() to extend the file if + * ftruncate didn't work or isn't available. */ + if (a->st.st_size < a->filesize) { + const char nul = '\0'; + if (lseek(a->fd, a->filesize - 1, SEEK_SET) < 0) { + archive_set_error(&a->archive, errno, + "Seek failed"); + return (ARCHIVE_FATAL); + } + if (write(a->fd, &nul, 1) < 0) { + archive_set_error(&a->archive, errno, + "Write to restore size failed"); + return (ARCHIVE_FATAL); + } + a->pst = NULL; + } + } + + /* Restore metadata. */ + + /* + * Look up the "real" UID only if we're going to need it. + * TODO: the TODO_SGID condition can be dropped here, can't it? + */ + if (a->todo & (TODO_OWNER | TODO_SUID | TODO_SGID)) { + a->uid = archive_write_disk_uid(&a->archive, + archive_entry_uname(a->entry), + archive_entry_uid(a->entry)); + } + /* Look up the "real" GID only if we're going to need it. */ + /* TODO: the TODO_SUID condition can be dropped here, can't it? */ + if (a->todo & (TODO_OWNER | TODO_SGID | TODO_SUID)) { + a->gid = archive_write_disk_gid(&a->archive, + archive_entry_gname(a->entry), + archive_entry_gid(a->entry)); + } + + /* + * Restore ownership before set_mode tries to restore suid/sgid + * bits. If we set the owner, we know what it is and can skip + * a stat() call to examine the ownership of the file on disk. + */ + if (a->todo & TODO_OWNER) + ret = set_ownership(a); + + /* + * set_mode must precede ACLs on systems such as Solaris and + * FreeBSD where setting the mode implicitly clears extended ACLs + */ + if (a->todo & TODO_MODE) { + int r2 = set_mode(a, a->mode); + if (r2 < ret) ret = r2; + } + + /* + * Security-related extended attributes (such as + * security.capability on Linux) have to be restored last, + * since they're implicitly removed by other file changes. + */ + if (a->todo & TODO_XATTR) { + int r2 = set_xattrs(a); + if (r2 < ret) ret = r2; + } + + /* + * Some flags prevent file modification; they must be restored after + * file contents are written. + */ + if (a->todo & TODO_FFLAGS) { + int r2 = set_fflags(a); + if (r2 < ret) ret = r2; + } + + /* + * Time must follow most other metadata; + * otherwise atime will get changed. + */ + if (a->todo & TODO_TIMES) { + int r2 = set_times_from_entry(a); + if (r2 < ret) ret = r2; + } + + /* + * Mac extended metadata includes ACLs. + */ + if (a->todo & TODO_MAC_METADATA) { + const void *metadata; + size_t metadata_size; + metadata = archive_entry_mac_metadata(a->entry, &metadata_size); + if (metadata != NULL && metadata_size > 0) { + int r2 = set_mac_metadata(a, archive_entry_pathname(a->entry), metadata, metadata_size); + if (r2 < ret) ret = r2; + } + } + + /* + * ACLs must be restored after timestamps because there are + * ACLs that prevent attribute changes (including time). + */ + if (a->todo & TODO_ACLS) { + int r2 = set_acls(a, a->fd, + archive_entry_pathname(a->entry), + archive_entry_acl(a->entry)); + if (r2 < ret) ret = r2; + } + + /* If there's an fd, we can close it now. */ + if (a->fd >= 0) { + close(a->fd); + a->fd = -1; + } + /* If there's an entry, we can release it now. */ + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->archive.state = ARCHIVE_STATE_HEADER; + return (ret); +} + +int +archive_write_disk_set_group_lookup(struct archive *_a, + void *private_data, + int64_t (*lookup_gid)(void *private, const char *gname, int64_t gid), + void (*cleanup_gid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_group_lookup"); + + if (a->cleanup_gid != NULL && a->lookup_gid_data != NULL) + (a->cleanup_gid)(a->lookup_gid_data); + + a->lookup_gid = lookup_gid; + a->cleanup_gid = cleanup_gid; + a->lookup_gid_data = private_data; + return (ARCHIVE_OK); +} + +int +archive_write_disk_set_user_lookup(struct archive *_a, + void *private_data, + int64_t (*lookup_uid)(void *private, const char *uname, int64_t uid), + void (*cleanup_uid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_user_lookup"); + + if (a->cleanup_uid != NULL && a->lookup_uid_data != NULL) + (a->cleanup_uid)(a->lookup_uid_data); + + a->lookup_uid = lookup_uid; + a->cleanup_uid = cleanup_uid; + a->lookup_uid_data = private_data; + return (ARCHIVE_OK); +} + +int64_t +archive_write_disk_gid(struct archive *_a, const char *name, int64_t id) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_gid"); + if (a->lookup_gid) + return (a->lookup_gid)(a->lookup_gid_data, name, id); + return (id); +} + +int64_t +archive_write_disk_uid(struct archive *_a, const char *name, int64_t id) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_uid"); + if (a->lookup_uid) + return (a->lookup_uid)(a->lookup_uid_data, name, id); + return (id); +} + +/* + * Create a new archive_write_disk object and initialize it with global state. + */ +struct archive * +archive_write_disk_new(void) +{ + struct archive_write_disk *a; + + a = (struct archive_write_disk *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC; + /* We're ready to write a header immediately. */ + a->archive.state = ARCHIVE_STATE_HEADER; + a->archive.vtable = archive_write_disk_vtable(); + a->start_time = time(NULL); + /* Query and restore the umask. */ + umask(a->user_umask = umask(0)); +#ifdef HAVE_GETEUID + a->user_uid = geteuid(); +#endif /* HAVE_GETEUID */ + if (archive_string_ensure(&a->path_safe, 512) == NULL) { + free(a); + return (NULL); + } + return (&a->archive); +} + + +/* + * If pathname is longer than PATH_MAX, chdir to a suitable + * intermediate dir and edit the path down to a shorter suffix. Note + * that this routine never returns an error; if the chdir() attempt + * fails for any reason, we just go ahead with the long pathname. The + * object creation is likely to fail, but any error will get handled + * at that time. + */ +#if defined(HAVE_FCHDIR) && defined(PATH_MAX) +static void +edit_deep_directories(struct archive_write_disk *a) +{ + int ret; + char *tail = a->name; + + /* If path is short, avoid the open() below. */ + if (strlen(tail) <= PATH_MAX) + return; + + /* Try to record our starting dir. */ + a->restore_pwd = open(".", O_RDONLY | O_BINARY); + if (a->restore_pwd < 0) + return; + + /* As long as the path is too long... */ + while (strlen(tail) > PATH_MAX) { + /* Locate a dir prefix shorter than PATH_MAX. */ + tail += PATH_MAX - 8; + while (tail > a->name && *tail != '/') + tail--; + /* Exit if we find a too-long path component. */ + if (tail <= a->name) + return; + /* Create the intermediate dir and chdir to it. */ + *tail = '\0'; /* Terminate dir portion */ + ret = create_dir(a, a->name); + if (ret == ARCHIVE_OK && chdir(a->name) != 0) + ret = ARCHIVE_FAILED; + *tail = '/'; /* Restore the / we removed. */ + if (ret != ARCHIVE_OK) + return; + tail++; + /* The chdir() succeeded; we've now shortened the path. */ + a->name = tail; + } + return; +} +#endif + +/* + * The main restore function. + */ +static int +restore_entry(struct archive_write_disk *a) +{ + int ret = ARCHIVE_OK, en; + + if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) { + /* + * TODO: Fix this. Apparently, there are platforms + * that still allow root to hose the entire filesystem + * by unlinking a dir. The S_ISDIR() test above + * prevents us from using unlink() here if the new + * object is a dir, but that doesn't mean the old + * object isn't a dir. + */ + if (unlink(a->name) == 0) { + /* We removed it, reset cached stat. */ + a->pst = NULL; + } else if (errno == ENOENT) { + /* File didn't exist, that's just as good. */ + } else if (rmdir(a->name) == 0) { + /* It was a dir, but now it's gone. */ + a->pst = NULL; + } else { + /* We tried, but couldn't get rid of it. */ + archive_set_error(&a->archive, errno, + "Could not unlink"); + return(ARCHIVE_FAILED); + } + } + + /* Try creating it first; if this fails, we'll try to recover. */ + en = create_filesystem_object(a); + + if ((en == ENOTDIR || en == ENOENT) + && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) { + /* If the parent dir doesn't exist, try creating it. */ + create_parent_dir(a, a->name); + /* Now try to create the object again. */ + en = create_filesystem_object(a); + } + + if ((en == EISDIR || en == EEXIST) + && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + /* If we're not overwriting, we're done. */ + archive_entry_unset_size(a->entry); + return (ARCHIVE_OK); + } + + /* + * Some platforms return EISDIR if you call + * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some + * return EEXIST. POSIX is ambiguous, requiring EISDIR + * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT) + * on an existing item. + */ + if (en == EISDIR) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* Try again. */ + en = create_filesystem_object(a); + } else if (en == EEXIST) { + /* + * We know something is in the way, but we don't know what; + * we need to find out before we go any further. + */ + int r = 0; + /* + * The SECURE_SYMLINKS logic has already removed a + * symlink to a dir if the client wants that. So + * follow the symlink if we're creating a dir. + */ + if (S_ISDIR(a->mode)) + r = stat(a->name, &a->st); + /* + * If it's not a dir (or it's a broken symlink), + * then don't follow it. + */ + if (r != 0 || !S_ISDIR(a->mode)) + r = lstat(a->name, &a->st); + if (r != 0) { + archive_set_error(&a->archive, errno, + "Can't stat existing object"); + return (ARCHIVE_FAILED); + } + + /* + * NO_OVERWRITE_NEWER doesn't apply to directories. + */ + if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER) + && !S_ISDIR(a->st.st_mode)) { + if (!older(&(a->st), a->entry)) { + archive_entry_unset_size(a->entry); + return (ARCHIVE_OK); + } + } + + /* If it's our archive, we're done. */ + if (a->skip_file_set && + a->st.st_dev == a->skip_file_dev && + a->st.st_ino == a->skip_file_ino) { + archive_set_error(&a->archive, 0, "Refusing to overwrite archive"); + return (ARCHIVE_FAILED); + } + + if (!S_ISDIR(a->st.st_mode)) { + /* A non-dir is in the way, unlink it. */ + if (unlink(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't unlink already-existing object"); + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* Try again. */ + en = create_filesystem_object(a); + } else if (!S_ISDIR(a->mode)) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_FAILED); + } + /* Try again. */ + en = create_filesystem_object(a); + } else { + /* + * There's a dir in the way of a dir. Don't + * waste time with rmdir()/mkdir(), just fix + * up the permissions on the existing dir. + * Note that we don't change perms on existing + * dirs unless _EXTRACT_PERM is specified. + */ + if ((a->mode != a->st.st_mode) + && (a->todo & TODO_MODE_FORCE)) + a->deferred |= (a->todo & TODO_MODE); + /* Ownership doesn't need deferred fixup. */ + en = 0; /* Forget the EEXIST. */ + } + } + + if (en) { + /* Everything failed; give up here. */ + archive_set_error(&a->archive, en, "Can't create '%s'", + a->name); + return (ARCHIVE_FAILED); + } + + a->pst = NULL; /* Cached stat data no longer valid. */ + return (ret); +} + +/* + * Returns 0 if creation succeeds, or else returns errno value from + * the failed system call. Note: This function should only ever perform + * a single system call. + */ +static int +create_filesystem_object(struct archive_write_disk *a) +{ + /* Create the entry. */ + const char *linkname; + mode_t final_mode, mode; + int r; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ + linkname = archive_entry_hardlink(a->entry); + if (linkname != NULL) { +#if !HAVE_LINK + return (EPERM); +#else + r = link(linkname, a->name) ? errno : 0; + /* + * New cpio and pax formats allow hardlink entries + * to carry data, so we may have to open the file + * for hardlink entries. + * + * If the hardlink was successfully created and + * the archive doesn't have carry data for it, + * consider it to be non-authoritive for meta data. + * This is consistent with GNU tar and BSD pax. + * If the hardlink does carry data, let the last + * archive entry decide ownership. + */ + if (r == 0 && a->filesize <= 0) { + a->todo = 0; + a->deferred = 0; + } else if (r == 0 && a->filesize > 0) { + a->fd = open(a->name, O_WRONLY | O_TRUNC | O_BINARY); + if (a->fd < 0) + r = errno; + } + return (r); +#endif + } + linkname = archive_entry_symlink(a->entry); + if (linkname != NULL) { +#if HAVE_SYMLINK + return symlink(linkname, a->name) ? errno : 0; +#else + return (EPERM); +#endif + } + + /* + * The remaining system calls all set permissions, so let's + * try to take advantage of that to avoid an extra chmod() + * call. (Recall that umask is set to zero right now!) + */ + + /* Mode we want for the final restored object (w/o file type bits). */ + final_mode = a->mode & 07777; + /* + * The mode that will actually be restored in this step. Note + * that SUID, SGID, etc, require additional work to ensure + * security, so we never restore them at this point. + */ + mode = final_mode & 0777 & a->user_umask; + + switch (a->mode & AE_IFMT) { + default: + /* POSIX requires that we fall through here. */ + /* FALLTHROUGH */ + case AE_IFREG: + a->fd = open(a->name, + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, mode); + r = (a->fd < 0); + break; + case AE_IFCHR: +#ifdef HAVE_MKNOD + /* Note: we use AE_IFCHR for the case label, and + * S_IFCHR for the mknod() call. This is correct. */ + r = mknod(a->name, mode | S_IFCHR, + archive_entry_rdev(a->entry)); + break; +#else + /* TODO: Find a better way to warn about our inability + * to restore a char device node. */ + return (EINVAL); +#endif /* HAVE_MKNOD */ + case AE_IFBLK: +#ifdef HAVE_MKNOD + r = mknod(a->name, mode | S_IFBLK, + archive_entry_rdev(a->entry)); + break; +#else + /* TODO: Find a better way to warn about our inability + * to restore a block device node. */ + return (EINVAL); +#endif /* HAVE_MKNOD */ + case AE_IFDIR: + mode = (mode | MINIMUM_DIR_MODE) & MAXIMUM_DIR_MODE; + r = mkdir(a->name, mode); + if (r == 0) { + /* Defer setting dir times. */ + a->deferred |= (a->todo & TODO_TIMES); + a->todo &= ~TODO_TIMES; + /* Never use an immediate chmod(). */ + /* We can't avoid the chmod() entirely if EXTRACT_PERM + * because of SysV SGID inheritance. */ + if ((mode != final_mode) + || (a->flags & ARCHIVE_EXTRACT_PERM)) + a->deferred |= (a->todo & TODO_MODE); + a->todo &= ~TODO_MODE; + } + break; + case AE_IFIFO: +#ifdef HAVE_MKFIFO + r = mkfifo(a->name, mode); + break; +#else + /* TODO: Find a better way to warn about our inability + * to restore a fifo. */ + return (EINVAL); +#endif /* HAVE_MKFIFO */ + } + + /* All the system calls above set errno on failure. */ + if (r) + return (errno); + + /* If we managed to set the final mode, we've avoided a chmod(). */ + if (mode == final_mode) + a->todo &= ~TODO_MODE; + return (0); +} + +/* + * Cleanup function for archive_extract. Mostly, this involves processing + * the fixup list, which is used to address a number of problems: + * * Dir permissions might prevent us from restoring a file in that + * dir, so we restore the dir with minimum 0700 permissions first, + * then correct the mode at the end. + * * Similarly, the act of restoring a file touches the directory + * and changes the timestamp on the dir, so we have to touch-up dir + * timestamps at the end as well. + * * Some file flags can interfere with the restore by, for example, + * preventing the creation of hardlinks to those files. + * * Mac OS extended metadata includes ACLs, so must be deferred on dirs. + * + * Note that tar/cpio do not require that archives be in a particular + * order; there is no way to know when the last file has been restored + * within a directory, so there's no way to optimize the memory usage + * here by fixing up the directory any earlier than the + * end-of-archive. + * + * XXX TODO: Directory ACLs should be restored here, for the same + * reason we set directory perms here. XXX + */ +static int +_archive_write_disk_close(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *next, *p; + int ret; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_close"); + ret = _archive_write_disk_finish_entry(&a->archive); + + /* Sort dir list so directories are fixed up in depth-first order. */ + p = sort_dir_list(a->fixup_list); + + while (p != NULL) { + a->pst = NULL; /* Mark stat cache as out-of-date. */ + if (p->fixup & TODO_TIMES) { + set_times(a, -1, p->mode, p->name, + p->atime, p->atime_nanos, + p->birthtime, p->birthtime_nanos, + p->mtime, p->mtime_nanos, + p->ctime, p->ctime_nanos); + } + if (p->fixup & TODO_MODE_BASE) + chmod(p->name, p->mode); + if (p->fixup & TODO_ACLS) + set_acls(a, -1, p->name, &p->acl); + if (p->fixup & TODO_FFLAGS) + set_fflags_platform(a, -1, p->name, + p->mode, p->fflags_set, 0); + if (p->fixup & TODO_MAC_METADATA) + set_mac_metadata(a, p->name, p->mac_metadata, + p->mac_metadata_size); + next = p->next; + archive_acl_clear(&p->acl); + free(p->mac_metadata); + free(p->name); + free(p); + p = next; + } + a->fixup_list = NULL; + return (ret); +} + +static int +_archive_write_disk_free(struct archive *_a) +{ + struct archive_write_disk *a; + int ret; + if (_a == NULL) + return (ARCHIVE_OK); + archive_check_magic(_a, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_write_disk_free"); + a = (struct archive_write_disk *)_a; + ret = _archive_write_disk_close(&a->archive); + archive_write_disk_set_group_lookup(&a->archive, NULL, NULL, NULL); + archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL); + if (a->entry) + archive_entry_free(a->entry); + archive_string_free(&a->_name_data); + archive_string_free(&a->archive.error_string); + archive_string_free(&a->path_safe); + a->archive.magic = 0; + __archive_clean(&a->archive); + free(a); + return (ret); +} + +/* + * Simple O(n log n) merge sort to order the fixup list. In + * particular, we want to restore dir timestamps depth-first. + */ +static struct fixup_entry * +sort_dir_list(struct fixup_entry *p) +{ + struct fixup_entry *a, *b, *t; + + if (p == NULL) + return (NULL); + /* A one-item list is already sorted. */ + if (p->next == NULL) + return (p); + + /* Step 1: split the list. */ + t = p; + a = p->next->next; + while (a != NULL) { + /* Step a twice, t once. */ + a = a->next; + if (a != NULL) + a = a->next; + t = t->next; + } + /* Now, t is at the mid-point, so break the list here. */ + b = t->next; + t->next = NULL; + a = p; + + /* Step 2: Recursively sort the two sub-lists. */ + a = sort_dir_list(a); + b = sort_dir_list(b); + + /* Step 3: Merge the returned lists. */ + /* Pick the first element for the merged list. */ + if (strcmp(a->name, b->name) > 0) { + t = p = a; + a = a->next; + } else { + t = p = b; + b = b->next; + } + + /* Always put the later element on the list first. */ + while (a != NULL && b != NULL) { + if (strcmp(a->name, b->name) > 0) { + t->next = a; + a = a->next; + } else { + t->next = b; + b = b->next; + } + t = t->next; + } + + /* Only one list is non-empty, so just splice it on. */ + if (a != NULL) + t->next = a; + if (b != NULL) + t->next = b; + + return (p); +} + +/* + * Returns a new, initialized fixup entry. + * + * TODO: Reduce the memory requirements for this list by using a tree + * structure rather than a simple list of names. + */ +static struct fixup_entry * +new_fixup(struct archive_write_disk *a, const char *pathname) +{ + struct fixup_entry *fe; + + fe = (struct fixup_entry *)calloc(1, sizeof(struct fixup_entry)); + if (fe == NULL) + return (NULL); + fe->next = a->fixup_list; + a->fixup_list = fe; + fe->fixup = 0; + fe->name = strdup(pathname); + return (fe); +} + +/* + * Returns a fixup structure for the current entry. + */ +static struct fixup_entry * +current_fixup(struct archive_write_disk *a, const char *pathname) +{ + if (a->current_fixup == NULL) + a->current_fixup = new_fixup(a, pathname); + return (a->current_fixup); +} + +/* TODO: Make this work. */ +/* + * TODO: The deep-directory support bypasses this; disable deep directory + * support if we're doing symlink checks. + */ +/* + * TODO: Someday, integrate this with the deep dir support; they both + * scan the path and both can be optimized by comparing against other + * recent paths. + */ +/* TODO: Extend this to support symlinks on Windows Vista and later. */ +static int +check_symlinks(struct archive_write_disk *a) +{ +#if !defined(HAVE_LSTAT) + /* Platform doesn't have lstat, so we can't look for symlinks. */ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +#else + char *pn; + char c; + int r; + struct stat st; + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. + */ + /* Whatever we checked last time doesn't need to be re-checked. */ + pn = a->name; + if (archive_strlen(&(a->path_safe)) > 0) { + char *p = a->path_safe.s; + while ((*pn != '\0') && (*p == *pn)) + ++p, ++pn; + } + c = pn[0]; + /* Keep going until we've checked the entire name. */ + while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) { + /* Skip the next path element. */ + while (*pn != '\0' && *pn != '/') + ++pn; + c = pn[0]; + pn[0] = '\0'; + /* Check that we haven't hit a symlink. */ + r = lstat(a->name, &st); + if (r != 0) { + /* We've hit a dir that doesn't exist; stop now. */ + if (errno == ENOENT) + break; + } else if (S_ISLNK(st.st_mode)) { + if (c == '\0') { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ + if (unlink(a->name)) { + archive_set_error(&a->archive, errno, + "Could not remove symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ + if (!S_ISLNK(a->mode)) { + archive_set_error(&a->archive, 0, + "Removing symlink %s", + a->name); + } + /* Symlink gone. No more problem! */ + pn[0] = c; + return (0); + } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ + if (unlink(a->name) != 0) { + archive_set_error(&a->archive, 0, + "Cannot remove intervening symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + a->pst = NULL; + } else { + archive_set_error(&a->archive, 0, + "Cannot extract through symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + } + } + pn[0] = c; + /* We've checked and/or cleaned the whole path, so remember it. */ + archive_strcpy(&a->path_safe, a->name); + return (ARCHIVE_OK); +#endif +} + +#if defined(__CYGWIN__) +/* + * 1. Convert a path separator from '\' to '/' . + * We shouldn't check multibyte character directly because some + * character-set have been using the '\' character for a part of + * its multibyte character code. + * 2. Replace unusable characters in Windows with underscore('_'). + * See also : http://msdn.microsoft.com/en-us/library/aa365247.aspx + */ +static void +cleanup_pathname_win(struct archive_write_disk *a) +{ + wchar_t wc; + char *p; + size_t alen, l; + int mb, complete, utf8; + + alen = 0; + mb = 0; + complete = 1; + utf8 = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0)? 1: 0; + for (p = a->name; *p != '\0'; p++) { + ++alen; + if (*p == '\\') { + /* If previous byte is smaller than 128, + * this is not second byte of multibyte characters, + * so we can replace '\' with '/'. */ + if (utf8 || !mb) + *p = '/'; + else + complete = 0;/* uncompleted. */ + } else if (*(unsigned char *)p > 127) + mb = 1; + else + mb = 0; + /* Rewrite the path name if its next character is unusable. */ + if (*p == ':' || *p == '*' || *p == '?' || *p == '"' || + *p == '<' || *p == '>' || *p == '|') + *p = '_'; + } + if (complete) + return; + + /* + * Convert path separator in wide-character. + */ + p = a->name; + while (*p != '\0' && alen) { + l = mbtowc(&wc, p, alen); + if (l == -1) { + while (*p != '\0') { + if (*p == '\\') + *p = '/'; + ++p; + } + break; + } + if (l == 1 && wc == L'\\') + *p = '/'; + p += l; + alen -= l; + } +} +#endif + +/* + * Canonicalize the pathname. In particular, this strips duplicate + * '/' characters, '.' elements, and trailing '/'. It also raises an + * error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is + * set) any '..' in the path. + */ +static int +cleanup_pathname(struct archive_write_disk *a) +{ + char *dest, *src; + char separator = '\0'; + + dest = src = a->name; + if (*src == '\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid empty pathname"); + return (ARCHIVE_FAILED); + } + +#if defined(__CYGWIN__) + cleanup_pathname_win(a); +#endif + /* Skip leading '/'. */ + if (*src == '/') + separator = *src++; + + /* Scan the pathname one element at a time. */ + for (;;) { + /* src points to first char after '/' */ + if (src[0] == '\0') { + break; + } else if (src[0] == '/') { + /* Found '//', ignore second one. */ + src++; + continue; + } else if (src[0] == '.') { + if (src[1] == '\0') { + /* Ignore trailing '.' */ + break; + } else if (src[1] == '/') { + /* Skip './'. */ + src += 2; + continue; + } else if (src[1] == '.') { + if (src[2] == '/' || src[2] == '\0') { + /* Conditionally warn about '..' */ + if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Path contains '..'"); + return (ARCHIVE_FAILED); + } + } + /* + * Note: Under no circumstances do we + * remove '..' elements. In + * particular, restoring + * '/foo/../bar/' should create the + * 'foo' dir as a side-effect. + */ + } + } + + /* Copy current element, including leading '/'. */ + if (separator) + *dest++ = '/'; + while (*src != '\0' && *src != '/') { + *dest++ = *src++; + } + + if (*src == '\0') + break; + + /* Skip '/' separator. */ + separator = *src++; + } + /* + * We've just copied zero or more path elements, not including the + * final '/'. + */ + if (dest == a->name) { + /* + * Nothing got copied. The path must have been something + * like '.' or '/' or './' or '/././././/./'. + */ + if (separator) + *dest++ = '/'; + else + *dest++ = '.'; + } + /* Terminate the result. */ + *dest = '\0'; + return (ARCHIVE_OK); +} + +/* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. + */ +static int +create_parent_dir(struct archive_write_disk *a, char *path) +{ + char *slash; + int r; + + /* Remove tail element to obtain parent name. */ + slash = strrchr(path, '/'); + if (slash == NULL) + return (ARCHIVE_OK); + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + return (r); +} + +/* + * Create the specified dir, recursing to create parents as necessary. + * + * Returns ARCHIVE_OK if the path exists when we're done here. + * Otherwise, returns ARCHIVE_FAILED. + * Assumes path is in mutable storage; path is unchanged on exit. + */ +static int +create_dir(struct archive_write_disk *a, char *path) +{ + struct stat st; + struct fixup_entry *le; + char *slash, *base; + mode_t mode_final, mode; + int r; + + /* Check for special names and just skip them. */ + slash = strrchr(path, '/'); + if (slash == NULL) + base = path; + else + base = slash + 1; + + if (base[0] == '\0' || + (base[0] == '.' && base[1] == '\0') || + (base[0] == '.' && base[1] == '.' && base[2] == '\0')) { + /* Don't bother trying to create null path, '.', or '..'. */ + if (slash != NULL) { + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + return (r); + } + return (ARCHIVE_OK); + } + + /* + * Yes, this should be stat() and not lstat(). Using lstat() + * here loses the ability to extract through symlinks. Also note + * that this should not use the a->st cache. + */ + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) + return (ARCHIVE_OK); + if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + archive_set_error(&a->archive, EEXIST, + "Can't create directory '%s'", path); + return (ARCHIVE_FAILED); + } + if (unlink(path) != 0) { + archive_set_error(&a->archive, errno, + "Can't create directory '%s': " + "Conflicting file cannot be removed", + path); + return (ARCHIVE_FAILED); + } + } else if (errno != ENOENT && errno != ENOTDIR) { + /* Stat failed? */ + archive_set_error(&a->archive, errno, "Can't test directory '%s'", path); + return (ARCHIVE_FAILED); + } else if (slash != NULL) { + *slash = '\0'; + r = create_dir(a, path); + *slash = '/'; + if (r != ARCHIVE_OK) + return (r); + } + + /* + * Mode we want for the final restored directory. Per POSIX, + * implicitly-created dirs must be created obeying the umask. + * There's no mention whether this is different for privileged + * restores (which the rest of this code handles by pretending + * umask=0). I've chosen here to always obey the user's umask for + * implicit dirs, even if _EXTRACT_PERM was specified. + */ + mode_final = DEFAULT_DIR_MODE & ~a->user_umask; + /* Mode we want on disk during the restore process. */ + mode = mode_final; + mode |= MINIMUM_DIR_MODE; + mode &= MAXIMUM_DIR_MODE; + if (mkdir(path, mode) == 0) { + if (mode != mode_final) { + le = new_fixup(a, path); + le->fixup |=TODO_MODE_BASE; + le->mode = mode_final; + } + return (ARCHIVE_OK); + } + + /* + * Without the following check, a/b/../b/c/d fails at the + * second visit to 'b', so 'd' can't be created. Note that we + * don't add it to the fixup list here, as it's already been + * added. + */ + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + return (ARCHIVE_OK); + + archive_set_error(&a->archive, errno, "Failed to create dir '%s'", + path); + return (ARCHIVE_FAILED); +} + +/* + * Note: Although we can skip setting the user id if the desired user + * id matches the current user, we cannot skip setting the group, as + * many systems set the gid based on the containing directory. So + * we have to perform a chown syscall if we want to set the SGID + * bit. (The alternative is to stat() and then possibly chown(); it's + * more efficient to skip the stat() and just always chown().) Note + * that a successful chown() here clears the TODO_SGID_CHECK bit, which + * allows set_mode to skip the stat() check for the GID. + */ +static int +set_ownership(struct archive_write_disk *a) +{ +#ifndef __CYGWIN__ +/* unfortunately, on win32 there is no 'root' user with uid 0, + so we just have to try the chown and see if it works */ + + /* If we know we can't change it, don't bother trying. */ + if (a->user_uid != 0 && a->user_uid != a->uid) { + archive_set_error(&a->archive, errno, + "Can't set UID=%jd", (intmax_t)a->uid); + return (ARCHIVE_WARN); + } +#endif + +#ifdef HAVE_FCHOWN + /* If we have an fd, we can avoid a race. */ + if (a->fd >= 0 && fchown(a->fd, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#endif + + /* We prefer lchown() but will use chown() if that's all we have. */ + /* Of course, if we have neither, this will always fail. */ +#ifdef HAVE_LCHOWN + if (lchown(a->name, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#elif HAVE_CHOWN + if (!S_ISLNK(a->mode) && chown(a->name, a->uid, a->gid) == 0) { + /* We've set owner and know uid/gid are correct. */ + a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK); + return (ARCHIVE_OK); + } +#endif + + archive_set_error(&a->archive, errno, + "Can't set user=%jd/group=%jd for %s", + (intmax_t)a->uid, (intmax_t)a->gid, a->name); + return (ARCHIVE_WARN); +} + +/* + * Note: Returns 0 on success, non-zero on failure. + */ +static int +set_time(int fd, int mode, const char *name, + time_t atime, long atime_nsec, + time_t mtime, long mtime_nsec) +{ + /* Select the best implementation for this platform. */ +#if defined(HAVE_UTIMENSAT) && defined(HAVE_FUTIMENS) + /* + * utimensat() and futimens() are defined in + * POSIX.1-2008. They support ns resolution and setting times + * on fds and symlinks. + */ + struct timespec ts[2]; + ts[0].tv_sec = atime; + ts[0].tv_nsec = atime_nsec; + ts[1].tv_sec = mtime; + ts[1].tv_nsec = mtime_nsec; + if (fd >= 0) + return futimens(fd, ts); + return utimensat(AT_FDCWD, name, ts, AT_SYMLINK_NOFOLLOW); + +#elif HAVE_UTIMES + /* + * The utimes()-family functions support µs-resolution and + * setting times fds and symlinks. utimes() is documented as + * LEGACY by POSIX, futimes() and lutimes() are not described + * in POSIX. + */ + struct timeval times[2]; + + times[0].tv_sec = atime; + times[0].tv_usec = atime_nsec / 1000; + times[1].tv_sec = mtime; + times[1].tv_usec = mtime_nsec / 1000; + +#ifdef HAVE_FUTIMES + if (fd >= 0) + return (futimes(fd, times)); +#else + (void)fd; /* UNUSED */ +#endif +#ifdef HAVE_LUTIMES + (void)mode; /* UNUSED */ + return (lutimes(name, times)); +#else + if (S_ISLNK(mode)) + return (0); + return (utimes(name, times)); +#endif + +#elif defined(HAVE_UTIME) + /* + * utime() is POSIX-standard but only supports 1s resolution and + * does not support fds or symlinks. + */ + struct utimbuf times; + (void)fd; /* UNUSED */ + (void)name; /* UNUSED */ + (void)atime_nsec; /* UNUSED */ + (void)mtime_nsec; /* UNUSED */ + times.actime = atime; + times.modtime = mtime; + if (S_ISLNK(mode)) + return (ARCHIVE_OK); + return (utime(name, ×)); + +#else + /* + * We don't know how to set the time on this platform. + */ + return (ARCHIVE_WARN); +#endif +} + +#ifdef F_SETTIMES /* Tru64 */ +static int +set_time_tru64(int fd, int mode, const char *name, + time_t atime, long atime_nsec, + time_t mtime, long mtime_nsec, + time_t ctime, long ctime_nsec) +{ + struct attr_timbuf tstamp; + struct timeval times[3]; + times[0].tv_sec = atime; + times[0].tv_usec = atime_nsec / 1000; + times[1].tv_sec = mtime; + times[1].tv_usec = mtime_nsec / 1000; + times[2].tv_sec = ctime; + times[2].tv_usec = ctime_nsec / 1000; + tstamp.atime = times[0]; + tstamp.mtime = times[1]; + tstamp.ctime = times[2]; + return (fcntl(fd,F_SETTIMES,&tstamp)); +} +#endif /* Tru64 */ + +static int +set_times(struct archive_write_disk *a, + int fd, int mode, const char *name, + time_t atime, long atime_nanos, + time_t birthtime, long birthtime_nanos, + time_t mtime, long mtime_nanos, + time_t ctime, long ctime_nanos) +{ + /* Note: set_time doesn't use libarchive return conventions! + * It uses syscall conventions. So 0 here instead of ARCHIVE_OK. */ + int r1 = 0, r2 = 0; + +#ifdef F_SETTIMES + /* + * on Tru64 try own fcntl first which can restore even the + * ctime, fall back to default code path below if it fails + * or if we are not running as root + */ + if (a->user_uid == 0 && + set_time_tru64(fd, mode, name, + atime, atime_nanos, mtime, + mtime_nanos, ctime, ctime_nanos) == 0) { + return (ARCHIVE_OK); + } +#endif /* Tru64 */ + +#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME + /* + * If you have struct stat.st_birthtime, we assume BSD + * birthtime semantics, in which {f,l,}utimes() updates + * birthtime to earliest mtime. So we set the time twice, + * first using the birthtime, then using the mtime. If + * birthtime == mtime, this isn't necessary, so we skip it. + * If birthtime > mtime, then this won't work, so we skip it. + */ + if (birthtime < mtime + || (birthtime == mtime && birthtime_nanos < mtime_nanos)) + r1 = set_time(fd, mode, name, + atime, atime_nanos, + birthtime, birthtime_nanos); +#endif + r2 = set_time(fd, mode, name, + atime, atime_nanos, + mtime, mtime_nanos); + if (r1 != 0 || r2 != 0) { + archive_set_error(&a->archive, errno, + "Can't restore time"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +set_times_from_entry(struct archive_write_disk *a) +{ + time_t atime, birthtime, mtime, ctime; + long atime_nsec, birthtime_nsec, mtime_nsec, ctime_nsec; + + /* Suitable defaults. */ + atime = birthtime = mtime = ctime = a->start_time; + atime_nsec = birthtime_nsec = mtime_nsec = ctime_nsec = 0; + + /* If no time was provided, we're done. */ + if (!archive_entry_atime_is_set(a->entry) +#if HAVE_STRUCT_STAT_ST_BIRTHTIME + && !archive_entry_birthtime_is_set(a->entry) +#endif + && !archive_entry_mtime_is_set(a->entry)) + return (ARCHIVE_OK); + + if (archive_entry_atime_is_set(a->entry)) { + atime = archive_entry_atime(a->entry); + atime_nsec = archive_entry_atime_nsec(a->entry); + } + if (archive_entry_birthtime_is_set(a->entry)) { + birthtime = archive_entry_birthtime(a->entry); + birthtime_nsec = archive_entry_birthtime_nsec(a->entry); + } + if (archive_entry_mtime_is_set(a->entry)) { + mtime = archive_entry_mtime(a->entry); + mtime_nsec = archive_entry_mtime_nsec(a->entry); + } + if (archive_entry_ctime_is_set(a->entry)) { + ctime = archive_entry_ctime(a->entry); + ctime_nsec = archive_entry_ctime_nsec(a->entry); + } + + return set_times(a, a->fd, a->mode, a->name, + atime, atime_nsec, + birthtime, birthtime_nsec, + mtime, mtime_nsec, + ctime, ctime_nsec); +} + +static int +set_mode(struct archive_write_disk *a, int mode) +{ + int r = ARCHIVE_OK; + mode &= 07777; /* Strip off file type bits. */ + + if (a->todo & TODO_SGID_CHECK) { + /* + * If we don't know the GID is right, we must stat() + * to verify it. We can't just check the GID of this + * process, since systems sometimes set GID from + * the enclosing dir or based on ACLs. + */ + if ((r = lazy_stat(a)) != ARCHIVE_OK) + return (r); + if (a->pst->st_gid != a->gid) { + mode &= ~ S_ISGID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + /* + * This is only an error if you + * requested owner restore. If you + * didn't, we'll try to restore + * sgid/suid, but won't consider it a + * problem if we can't. + */ + archive_set_error(&a->archive, -1, + "Can't restore SGID bit"); + r = ARCHIVE_WARN; + } + } + /* While we're here, double-check the UID. */ + if (a->pst->st_uid != a->uid + && (a->todo & TODO_SUID)) { + mode &= ~ S_ISUID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + archive_set_error(&a->archive, -1, + "Can't restore SUID bit"); + r = ARCHIVE_WARN; + } + } + a->todo &= ~TODO_SGID_CHECK; + a->todo &= ~TODO_SUID_CHECK; + } else if (a->todo & TODO_SUID_CHECK) { + /* + * If we don't know the UID is right, we can just check + * the user, since all systems set the file UID from + * the process UID. + */ + if (a->user_uid != a->uid) { + mode &= ~ S_ISUID; + if (a->flags & ARCHIVE_EXTRACT_OWNER) { + archive_set_error(&a->archive, -1, + "Can't make file SUID"); + r = ARCHIVE_WARN; + } + } + a->todo &= ~TODO_SUID_CHECK; + } + + if (S_ISLNK(a->mode)) { +#ifdef HAVE_LCHMOD + /* + * If this is a symlink, use lchmod(). If the + * platform doesn't support lchmod(), just skip it. A + * platform that doesn't provide a way to set + * permissions on symlinks probably ignores + * permissions on symlinks, so a failure here has no + * impact. + */ + if (lchmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } +#endif + } else if (!S_ISDIR(a->mode)) { + /* + * If it's not a symlink and not a dir, then use + * fchmod() or chmod(), depending on whether we have + * an fd. Dirs get their perms set during the + * post-extract fixup, which is handled elsewhere. + */ +#ifdef HAVE_FCHMOD + if (a->fd >= 0) { + if (fchmod(a->fd, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } else +#endif + /* If this platform lacks fchmod(), then + * we'll just use chmod(). */ + if (chmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } + return (r); +} + +static int +set_fflags(struct archive_write_disk *a) +{ + struct fixup_entry *le; + unsigned long set, clear; + int r; + int critical_flags; + mode_t mode = archive_entry_mode(a->entry); + + /* + * Make 'critical_flags' hold all file flags that can't be + * immediately restored. For example, on BSD systems, + * SF_IMMUTABLE prevents hardlinks from being created, so + * should not be set until after any hardlinks are created. To + * preserve some semblance of portability, this uses #ifdef + * extensively. Ugly, but it works. + * + * Yes, Virginia, this does create a security race. It's mitigated + * somewhat by the practice of creating dirs 0700 until the extract + * is done, but it would be nice if we could do more than that. + * People restoring critical file systems should be wary of + * other programs that might try to muck with files as they're + * being restored. + */ + /* Hopefully, the compiler will optimize this mess into a constant. */ + critical_flags = 0; +#ifdef SF_IMMUTABLE + critical_flags |= SF_IMMUTABLE; +#endif +#ifdef UF_IMMUTABLE + critical_flags |= UF_IMMUTABLE; +#endif +#ifdef SF_APPEND + critical_flags |= SF_APPEND; +#endif +#ifdef UF_APPEND + critical_flags |= UF_APPEND; +#endif +#ifdef EXT2_APPEND_FL + critical_flags |= EXT2_APPEND_FL; +#endif +#ifdef EXT2_IMMUTABLE_FL + critical_flags |= EXT2_IMMUTABLE_FL; +#endif + + if (a->todo & TODO_FFLAGS) { + archive_entry_fflags(a->entry, &set, &clear); + + /* + * The first test encourages the compiler to eliminate + * all of this if it's not necessary. + */ + if ((critical_flags != 0) && (set & critical_flags)) { + le = current_fixup(a, a->name); + le->fixup |= TODO_FFLAGS; + le->fflags_set = set; + /* Store the mode if it's not already there. */ + if ((le->fixup & TODO_MODE) == 0) + le->mode = mode; + } else { + r = set_fflags_platform(a, a->fd, + a->name, mode, set, clear); + if (r != ARCHIVE_OK) + return (r); + } + } + return (ARCHIVE_OK); +} + + +#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && defined(HAVE_STRUCT_STAT_ST_FLAGS) +/* + * BSD reads flags using stat() and sets them with one of {f,l,}chflags() + */ +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + int r; + + (void)mode; /* UNUSED */ + if (set == 0 && clear == 0) + return (ARCHIVE_OK); + + /* + * XXX Is the stat here really necessary? Or can I just use + * the 'set' flags directly? In particular, I'm not sure + * about the correct approach if we're overwriting an existing + * file that already has flags on it. XXX + */ + if ((r = lazy_stat(a)) != ARCHIVE_OK) + return (r); + + a->st.st_flags &= ~clear; + a->st.st_flags |= set; +#ifdef HAVE_FCHFLAGS + /* If platform has fchflags() and we were given an fd, use it. */ + if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#endif + /* + * If we can't use the fd to set the flags, we'll use the + * pathname to set flags. We prefer lchflags() but will use + * chflags() if we must. + */ +#ifdef HAVE_LCHFLAGS + if (lchflags(name, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#elif defined(HAVE_CHFLAGS) + if (S_ISLNK(a->st.st_mode)) { + archive_set_error(&a->archive, errno, + "Can't set file flags on symlink."); + return (ARCHIVE_WARN); + } + if (chflags(name, a->st.st_flags) == 0) + return (ARCHIVE_OK); +#endif + archive_set_error(&a->archive, errno, + "Failed to set file flags"); + return (ARCHIVE_WARN); +} + +#elif defined(EXT2_IOC_GETFLAGS) && defined(EXT2_IOC_SETFLAGS) && defined(HAVE_WORKING_EXT2_IOC_GETFLAGS) +/* + * Linux uses ioctl() to read and write file flags. + */ +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + int ret; + int myfd = fd; + unsigned long newflags, oldflags; + unsigned long sf_mask = 0; + + if (set == 0 && clear == 0) + return (ARCHIVE_OK); + /* Only regular files and dirs can have flags. */ + if (!S_ISREG(mode) && !S_ISDIR(mode)) + return (ARCHIVE_OK); + + /* If we weren't given an fd, open it ourselves. */ + if (myfd < 0) + myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY); + if (myfd < 0) + return (ARCHIVE_OK); + + /* + * Linux has no define for the flags that are only settable by + * the root user. This code may seem a little complex, but + * there seem to be some Linux systems that lack these + * defines. (?) The code below degrades reasonably gracefully + * if sf_mask is incomplete. + */ +#ifdef EXT2_IMMUTABLE_FL + sf_mask |= EXT2_IMMUTABLE_FL; +#endif +#ifdef EXT2_APPEND_FL + sf_mask |= EXT2_APPEND_FL; +#endif + /* + * XXX As above, this would be way simpler if we didn't have + * to read the current flags from disk. XXX + */ + ret = ARCHIVE_OK; + + /* Read the current file flags. */ + if (ioctl(myfd, EXT2_IOC_GETFLAGS, &oldflags) < 0) + goto fail; + + /* Try setting the flags as given. */ + newflags = (oldflags & ~clear) | set; + if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0) + goto cleanup; + if (errno != EPERM) + goto fail; + + /* If we couldn't set all the flags, try again with a subset. */ + newflags &= ~sf_mask; + oldflags &= sf_mask; + newflags |= oldflags; + if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0) + goto cleanup; + + /* We couldn't set the flags, so report the failure. */ +fail: + archive_set_error(&a->archive, errno, + "Failed to set file flags"); + ret = ARCHIVE_WARN; +cleanup: + if (fd < 0) + close(myfd); + return (ret); +} + +#else + +/* + * Of course, some systems have neither BSD chflags() nor Linux' flags + * support through ioctl(). + */ +static int +set_fflags_platform(struct archive_write_disk *a, int fd, const char *name, + mode_t mode, unsigned long set, unsigned long clear) +{ + (void)a; /* UNUSED */ + (void)fd; /* UNUSED */ + (void)name; /* UNUSED */ + (void)mode; /* UNUSED */ + (void)set; /* UNUSED */ + (void)clear; /* UNUSED */ + return (ARCHIVE_OK); +} + +#endif /* __linux */ + +#ifndef HAVE_COPYFILE_H +/* Default is to simply drop Mac extended metadata. */ +static int +set_mac_metadata(struct archive_write_disk *a, const char *pathname, + const void *metadata, size_t metadata_size) +{ + (void)a; /* UNUSED */ + (void)pathname; /* UNUSED */ + (void)metadata; /* UNUSED */ + (void)metadata_size; /* UNUSED */ + return (ARCHIVE_OK); +} +#else + +/* + * On Mac OS, we use copyfile() to unpack the metadata and + * apply it to the target file. + */ +static int +set_mac_metadata(struct archive_write_disk *a, const char *pathname, + const void *metadata, size_t metadata_size) +{ + struct archive_string tmp; + ssize_t written; + int fd; + int ret = ARCHIVE_OK; + + /* This would be simpler if copyfile() could just accept the + * metadata as a block of memory; then we could sidestep this + * silly dance of writing the data to disk just so that + * copyfile() can read it back in again. */ + archive_string_init(&tmp); + archive_strcpy(&tmp, pathname); + archive_strcat(&tmp, ".XXXXXX"); + fd = mkstemp(tmp.s); + + if (fd < 0) { + archive_set_error(&a->archive, errno, + "Failed to restore metadata"); + return (ARCHIVE_WARN); + } + written = write(fd, metadata, metadata_size); + close(fd); + if (written != metadata_size + || copyfile(tmp.s, pathname, 0, + COPYFILE_UNPACK | COPYFILE_NOFOLLOW + | COPYFILE_ACL | COPYFILE_XATTR)) { + archive_set_error(&a->archive, errno, + "Failed to restore metadata"); + ret = ARCHIVE_WARN; + } + unlink(tmp.s); + return (ret); +} +#endif + +#ifndef HAVE_POSIX_ACL +/* Default empty function body to satisfy mainline code. */ +static int +set_acls(struct archive_write_disk *a, int fd, const char *name, + struct archive_acl *acl) +{ + (void)a; /* UNUSED */ + (void)fd; /* UNUSED */ + (void)name; /* UNUSED */ + (void)acl; /* UNUSED */ + return (ARCHIVE_OK); +} + +#else + +/* + * XXX TODO: What about ACL types other than ACCESS and DEFAULT? + */ +static int +set_acls(struct archive_write_disk *a, int fd, const char *name, + struct archive_acl *abstract_acl) +{ + int ret; + + ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_ACCESS, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access"); + if (ret != ARCHIVE_OK) + return (ret); + ret = set_acl(a, fd, name, abstract_acl, ACL_TYPE_DEFAULT, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default"); + return (ret); +} + + +static int +set_acl(struct archive_write_disk *a, int fd, const char *name, + struct archive_acl *abstract_acl, + acl_type_t acl_type, int ae_requested_type, const char *tname) +{ + acl_t acl; + acl_entry_t acl_entry; + acl_permset_t acl_permset; + int ret; + int ae_type, ae_permset, ae_tag, ae_id; + uid_t ae_uid; + gid_t ae_gid; + const char *ae_name; + int entries; + + ret = ARCHIVE_OK; + entries = archive_acl_reset(abstract_acl, ae_requested_type); + if (entries == 0) + return (ARCHIVE_OK); + acl = acl_init(entries); + while (archive_acl_next(&a->archive, abstract_acl, + ae_requested_type, &ae_type, &ae_permset, &ae_tag, &ae_id, + &ae_name) == ARCHIVE_OK) { + acl_create_entry(&acl, &acl_entry); + + switch (ae_tag) { + case ARCHIVE_ENTRY_ACL_USER: + acl_set_tag_type(acl_entry, ACL_USER); + ae_uid = archive_write_disk_uid(&a->archive, + ae_name, ae_id); + acl_set_qualifier(acl_entry, &ae_uid); + break; + case ARCHIVE_ENTRY_ACL_GROUP: + acl_set_tag_type(acl_entry, ACL_GROUP); + ae_gid = archive_write_disk_gid(&a->archive, + ae_name, ae_id); + acl_set_qualifier(acl_entry, &ae_gid); + break; + case ARCHIVE_ENTRY_ACL_USER_OBJ: + acl_set_tag_type(acl_entry, ACL_USER_OBJ); + break; + case ARCHIVE_ENTRY_ACL_GROUP_OBJ: + acl_set_tag_type(acl_entry, ACL_GROUP_OBJ); + break; + case ARCHIVE_ENTRY_ACL_MASK: + acl_set_tag_type(acl_entry, ACL_MASK); + break; + case ARCHIVE_ENTRY_ACL_OTHER: + acl_set_tag_type(acl_entry, ACL_OTHER); + break; + default: + /* XXX */ + break; + } + + acl_get_permset(acl_entry, &acl_permset); + acl_clear_perms(acl_permset); + if (ae_permset & ARCHIVE_ENTRY_ACL_EXECUTE) + acl_add_perm(acl_permset, ACL_EXECUTE); + if (ae_permset & ARCHIVE_ENTRY_ACL_WRITE) + acl_add_perm(acl_permset, ACL_WRITE); + if (ae_permset & ARCHIVE_ENTRY_ACL_READ) + acl_add_perm(acl_permset, ACL_READ); + } + + /* Try restoring the ACL through 'fd' if we can. */ +#if HAVE_ACL_SET_FD + if (fd >= 0 && acl_type == ACL_TYPE_ACCESS && acl_set_fd(fd, acl) == 0) + ret = ARCHIVE_OK; + else +#else +#if HAVE_ACL_SET_FD_NP + if (fd >= 0 && acl_set_fd_np(fd, acl, acl_type) == 0) + ret = ARCHIVE_OK; + else +#endif +#endif + if (acl_set_file(name, acl_type, acl) != 0) { + archive_set_error(&a->archive, errno, "Failed to set %s acl", tname); + ret = ARCHIVE_WARN; + } + acl_free(acl); + return (ret); +} +#endif + +#if HAVE_LSETXATTR || HAVE_LSETEA +/* + * Restore extended attributes - Linux and AIX implementations: + * AIX' ea interface is syntaxwise identical to the Linux xattr interface. + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + struct archive_entry *entry = a->entry; + static int warning_done = 0; + int ret = ARCHIVE_OK; + int i = archive_entry_xattr_reset(entry); + + while (i--) { + const char *name; + const void *value; + size_t size; + archive_entry_xattr_next(entry, &name, &value, &size); + if (name != NULL && + strncmp(name, "xfsroot.", 8) != 0 && + strncmp(name, "system.", 7) != 0) { + int e; +#if HAVE_FSETXATTR + if (a->fd >= 0) + e = fsetxattr(a->fd, name, value, size, 0); + else +#elif HAVE_FSETEA + if (a->fd >= 0) + e = fsetea(a->fd, name, value, size, 0); + else +#endif + { +#if HAVE_LSETXATTR + e = lsetxattr(archive_entry_pathname(entry), + name, value, size, 0); +#elif HAVE_LSETEA + e = lsetea(archive_entry_pathname(entry), + name, value, size, 0); +#endif + } + if (e == -1) { + if (errno == ENOTSUP || errno == ENOSYS) { + if (!warning_done) { + warning_done = 1; + archive_set_error(&a->archive, errno, + "Cannot restore extended " + "attributes on this file " + "system"); + } + } else + archive_set_error(&a->archive, errno, + "Failed to set extended attribute"); + ret = ARCHIVE_WARN; + } + } else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Invalid extended attribute encountered"); + ret = ARCHIVE_WARN; + } + } + return (ret); +} +#elif HAVE_EXTATTR_SET_FILE && HAVE_DECL_EXTATTR_NAMESPACE_USER +/* + * Restore extended attributes - FreeBSD implementation + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + struct archive_entry *entry = a->entry; + static int warning_done = 0; + int ret = ARCHIVE_OK; + int i = archive_entry_xattr_reset(entry); + + while (i--) { + const char *name; + const void *value; + size_t size; + archive_entry_xattr_next(entry, &name, &value, &size); + if (name != NULL) { + int e; + int namespace; + + if (strncmp(name, "user.", 5) == 0) { + /* "user." attributes go to user namespace */ + name += 5; + namespace = EXTATTR_NAMESPACE_USER; + } else { + /* Warn about other extended attributes. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't restore extended attribute ``%s''", + name); + ret = ARCHIVE_WARN; + continue; + } + errno = 0; +#if HAVE_EXTATTR_SET_FD + if (a->fd >= 0) + e = extattr_set_fd(a->fd, namespace, name, value, size); + else +#endif + /* TODO: should we use extattr_set_link() instead? */ + { + e = extattr_set_file(archive_entry_pathname(entry), + namespace, name, value, size); + } + if (e != (int)size) { + if (errno == ENOTSUP || errno == ENOSYS) { + if (!warning_done) { + warning_done = 1; + archive_set_error(&a->archive, errno, + "Cannot restore extended " + "attributes on this file " + "system"); + } + } else { + archive_set_error(&a->archive, errno, + "Failed to set extended attribute"); + } + + ret = ARCHIVE_WARN; + } + } + } + return (ret); +} +#else +/* + * Restore extended attributes - stub implementation for unsupported systems + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + static int warning_done = 0; + + /* If there aren't any extended attributes, then it's okay not + * to extract them, otherwise, issue a single warning. */ + if (archive_entry_xattr_count(a->entry) != 0 && !warning_done) { + warning_done = 1; + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Cannot restore extended attributes on this system"); + return (ARCHIVE_WARN); + } + /* Warning was already emitted; suppress further warnings. */ + return (ARCHIVE_OK); +} +#endif + +/* + * Test if file on disk is older than entry. + */ +static int +older(struct stat *st, struct archive_entry *entry) +{ + /* First, test the seconds and return if we have a definite answer. */ + /* Definitely older. */ + if (st->st_mtime < archive_entry_mtime(entry)) + return (1); + /* Definitely younger. */ + if (st->st_mtime > archive_entry_mtime(entry)) + return (0); + /* If this platform supports fractional seconds, try those. */ +#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC + /* Definitely older. */ + if (st->st_mtimespec.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); +#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC + /* Definitely older. */ + if (st->st_mtim.tv_nsec < archive_entry_mtime_nsec(entry)) + return (1); +#elif HAVE_STRUCT_STAT_ST_MTIME_N + /* older. */ + if (st->st_mtime_n < archive_entry_mtime_nsec(entry)) + return (1); +#elif HAVE_STRUCT_STAT_ST_UMTIME + /* older. */ + if (st->st_umtime * 1000 < archive_entry_mtime_nsec(entry)) + return (1); +#elif HAVE_STRUCT_STAT_ST_MTIME_USEC + /* older. */ + if (st->st_mtime_usec * 1000 < archive_entry_mtime_nsec(entry)) + return (1); +#else + /* This system doesn't have high-res timestamps. */ +#endif + /* Same age or newer, so not older. */ + return (0); +} + +#endif /* !_WIN32 || __CYGWIN__ */ + diff --git a/libarchive/archive_write_disk_private.h b/libarchive/archive_write_disk_private.h new file mode 100644 index 0000000..707c0cf --- /dev/null +++ b/libarchive/archive_write_disk_private.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_write_disk_private.h 201086 2009-12-28 02:17:53Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_WRITE_DISK_PRIVATE_H_INCLUDED +#define ARCHIVE_WRITE_DISK_PRIVATE_H_INCLUDED + +struct archive_write_disk; + +#endif diff --git a/libarchive/archive_write_disk_set_standard_lookup.c b/libarchive/archive_write_disk_set_standard_lookup.c new file mode 100644 index 0000000..5ee89a7 --- /dev/null +++ b/libarchive/archive_write_disk_set_standard_lookup.c @@ -0,0 +1,262 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_disk_set_standard_lookup.c 201083 2009-12-28 02:09:57Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_GRP_H +#include +#endif +#ifdef HAVE_PWD_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" +#include "archive_read_private.h" +#include "archive_write_disk_private.h" + +struct bucket { + char *name; + int hash; + id_t id; +}; + +static const size_t cache_size = 127; +static unsigned int hash(const char *); +static int64_t lookup_gid(void *, const char *uname, int64_t); +static int64_t lookup_uid(void *, const char *uname, int64_t); +static void cleanup(void *); + +/* + * Installs functions that use getpwnam()/getgrnam()---along with + * a simple cache to accelerate such lookups---into the archive_write_disk + * object. This is in a separate file because getpwnam()/getgrnam() + * can pull in a LOT of library code (including NIS/LDAP functions, which + * pull in DNS resolveers, etc). This can easily top 500kB, which makes + * it inappropriate for some space-constrained applications. + * + * Applications that are size-sensitive may want to just use the + * real default functions (defined in archive_write_disk.c) that just + * use the uid/gid without the lookup. Or define your own custom functions + * if you prefer. + * + * TODO: Replace these hash tables with simpler move-to-front LRU + * lists with a bounded size (128 items?). The hash is a bit faster, + * but has a bad pathology in which it thrashes a single bucket. Even + * walking a list of 128 items is a lot faster than calling + * getpwnam()! + */ +int +archive_write_disk_set_standard_lookup(struct archive *a) +{ + struct bucket *ucache = malloc(cache_size * sizeof(struct bucket)); + struct bucket *gcache = malloc(cache_size * sizeof(struct bucket)); + memset(ucache, 0, cache_size * sizeof(struct bucket)); + memset(gcache, 0, cache_size * sizeof(struct bucket)); + archive_write_disk_set_group_lookup(a, gcache, lookup_gid, cleanup); + archive_write_disk_set_user_lookup(a, ucache, lookup_uid, cleanup); + return (ARCHIVE_OK); +} + +static int64_t +lookup_gid(void *private_data, const char *gname, int64_t gid) +{ + int h; + struct bucket *b; + struct bucket *gcache = (struct bucket *)private_data; + + /* If no gname, just use the gid provided. */ + if (gname == NULL || *gname == '\0') + return (gid); + + /* Try to find gname in the cache. */ + h = hash(gname); + b = &gcache[h % cache_size ]; + if (b->name != NULL && b->hash == h && strcmp(gname, b->name) == 0) + return ((gid_t)b->id); + + /* Free the cache slot for a new entry. */ + if (b->name != NULL) + free(b->name); + b->name = strdup(gname); + /* Note: If strdup fails, that's okay; we just won't cache. */ + b->hash = h; +#if HAVE_GRP_H +# if HAVE_GETGRNAM_R + { + char _buffer[128]; + size_t bufsize = 128; + char *buffer = _buffer; + struct group grent, *result; + int r; + + for (;;) { + result = &grent; /* Old getgrnam_r ignores last arg. */ + r = getgrnam_r(gname, &grent, buffer, bufsize, &result); + if (r == 0) + break; + if (r != ERANGE) + break; + bufsize *= 2; + if (buffer != _buffer) + free(buffer); + buffer = malloc(bufsize); + if (buffer == NULL) + break; + } + if (result != NULL) + gid = result->gr_gid; + if (buffer != _buffer) + free(buffer); + } +# else /* HAVE_GETGRNAM_R */ + { + struct group *result; + + result = getgrnam(gname); + if (result != NULL) + gid = result->gr_gid; + } +# endif /* HAVE_GETGRNAM_R */ +#elif defined(_WIN32) && !defined(__CYGWIN__) + /* TODO: do a gname->gid lookup for Windows. */ +#else + #error No way to perform gid lookups on this platform +#endif + b->id = gid; + + return (gid); +} + +static int64_t +lookup_uid(void *private_data, const char *uname, int64_t uid) +{ + int h; + struct bucket *b; + struct bucket *ucache = (struct bucket *)private_data; + + /* If no uname, just use the uid provided. */ + if (uname == NULL || *uname == '\0') + return (uid); + + /* Try to find uname in the cache. */ + h = hash(uname); + b = &ucache[h % cache_size ]; + if (b->name != NULL && b->hash == h && strcmp(uname, b->name) == 0) + return ((uid_t)b->id); + + /* Free the cache slot for a new entry. */ + if (b->name != NULL) + free(b->name); + b->name = strdup(uname); + /* Note: If strdup fails, that's okay; we just won't cache. */ + b->hash = h; +#if HAVE_PWD_H +# if HAVE_GETPWNAM_R + { + char _buffer[128]; + size_t bufsize = 128; + char *buffer = _buffer; + struct passwd pwent, *result; + int r; + + for (;;) { + result = &pwent; /* Old getpwnam_r ignores last arg. */ + r = getpwnam_r(uname, &pwent, buffer, bufsize, &result); + if (r == 0) + break; + if (r != ERANGE) + break; + bufsize *= 2; + if (buffer != _buffer) + free(buffer); + buffer = malloc(bufsize); + if (buffer == NULL) + break; + } + if (result != NULL) + uid = result->pw_uid; + if (buffer != _buffer) + free(buffer); + } +# else /* HAVE_GETPWNAM_R */ + { + struct passwd *result; + + result = getpwnam(uname); + if (result != NULL) + uid = result->pw_uid; + } +#endif /* HAVE_GETPWNAM_R */ +#elif defined(_WIN32) && !defined(__CYGWIN__) + /* TODO: do a uname->uid lookup for Windows. */ +#else + #error No way to look up uids on this platform +#endif + b->id = uid; + + return (uid); +} + +static void +cleanup(void *private) +{ + size_t i; + struct bucket *cache = (struct bucket *)private; + + for (i = 0; i < cache_size; i++) + free(cache[i].name); + free(cache); +} + + +static unsigned int +hash(const char *p) +{ + /* A 32-bit version of Peter Weinberger's (PJW) hash algorithm, + as used by ELF for hashing function names. */ + unsigned g, h = 0; + while (*p != '\0') { + h = (h << 4) + *p++; + if ((g = h & 0xF0000000) != 0) { + h ^= g >> 24; + h &= 0x0FFFFFFF; + } + } + return h; +} diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c new file mode 100644 index 0000000..9df9ddc --- /dev/null +++ b/libarchive/archive_write_disk_windows.c @@ -0,0 +1,2525 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_UTIME_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#include + +/* TODO: Support Mac OS 'quarantine' feature. This is really just a + * standard tag to mark files that have been downloaded as "tainted". + * On Mac OS, we should mark the extracted files as tainted if the + * archive being read was tainted. Windows has a similar feature; we + * should investigate ways to support this generically. */ + +#include "archive.h" +#include "archive_acl_private.h" +#include "archive_string.h" +#include "archive_entry.h" +#include "archive_private.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#ifndef IO_REPARSE_TAG_SYMLINK +/* Old SDKs do not provide IO_REPARSE_TAG_SYMLINK */ +#define IO_REPARSE_TAG_SYMLINK 0xA000000CL +#endif + +static BOOL SetFilePointerEx_perso(HANDLE hFile, + LARGE_INTEGER liDistanceToMove, + PLARGE_INTEGER lpNewFilePointer, + DWORD dwMoveMethod) +{ + LARGE_INTEGER li; + li.QuadPart = liDistanceToMove.QuadPart; + li.LowPart = SetFilePointer( + hFile, li.LowPart, &li.HighPart, dwMoveMethod); + if(lpNewFilePointer) { + lpNewFilePointer->QuadPart = li.QuadPart; + } + return li.LowPart != -1 || GetLastError() == NO_ERROR; +} + +struct fixup_entry { + struct fixup_entry *next; + struct archive_acl acl; + mode_t mode; + int64_t atime; + int64_t birthtime; + int64_t mtime; + int64_t ctime; + unsigned long atime_nanos; + unsigned long birthtime_nanos; + unsigned long mtime_nanos; + unsigned long ctime_nanos; + unsigned long fflags_set; + int fixup; /* bitmask of what needs fixing */ + wchar_t *name; +}; + +/* + * We use a bitmask to track which operations remain to be done for + * this file. In particular, this helps us avoid unnecessary + * operations when it's possible to take care of one step as a + * side-effect of another. For example, mkdir() can specify the mode + * for the newly-created object but symlink() cannot. This means we + * can skip chmod() if mkdir() succeeded, but we must explicitly + * chmod() if we're trying to create a directory that already exists + * (mkdir() failed) or if we're restoring a symlink. Similarly, we + * need to verify UID/GID before trying to restore SUID/SGID bits; + * that verification can occur explicitly through a stat() call or + * implicitly because of a successful chown() call. + */ +#define TODO_MODE_FORCE 0x40000000 +#define TODO_MODE_BASE 0x20000000 +#define TODO_SUID 0x10000000 +#define TODO_SUID_CHECK 0x08000000 +#define TODO_SGID 0x04000000 +#define TODO_SGID_CHECK 0x02000000 +#define TODO_MODE (TODO_MODE_BASE|TODO_SUID|TODO_SGID) +#define TODO_TIMES ARCHIVE_EXTRACT_TIME +#define TODO_OWNER ARCHIVE_EXTRACT_OWNER +#define TODO_FFLAGS ARCHIVE_EXTRACT_FFLAGS +#define TODO_ACLS ARCHIVE_EXTRACT_ACL +#define TODO_XATTR ARCHIVE_EXTRACT_XATTR +#define TODO_MAC_METADATA ARCHIVE_EXTRACT_MAC_METADATA + +struct archive_write_disk { + struct archive archive; + + mode_t user_umask; + struct fixup_entry *fixup_list; + struct fixup_entry *current_fixup; + int64_t user_uid; + int skip_file_set; + dev_t skip_file_dev; + ino_t skip_file_ino; + time_t start_time; + + int64_t (*lookup_gid)(void *private, const char *gname, int64_t gid); + void (*cleanup_gid)(void *private); + void *lookup_gid_data; + int64_t (*lookup_uid)(void *private, const char *uname, int64_t uid); + void (*cleanup_uid)(void *private); + void *lookup_uid_data; + + /* + * Full path of last file to satisfy symlink checks. + */ + struct archive_wstring path_safe; + + /* + * Cached stat data from disk for the current entry. + * If this is valid, pst points to st. Otherwise, + * pst is null. + */ + BY_HANDLE_FILE_INFORMATION st; + BY_HANDLE_FILE_INFORMATION *pst; + + /* Information about the object being restored right now. */ + struct archive_entry *entry; /* Entry being extracted. */ + wchar_t *name; /* Name of entry, possibly edited. */ + struct archive_wstring _name_data; /* backing store for 'name' */ + /* Tasks remaining for this object. */ + int todo; + /* Tasks deferred until end-of-archive. */ + int deferred; + /* Options requested by the client. */ + int flags; + /* Handle for the file we're restoring. */ + HANDLE fh; + /* Current offset for writing data to the file. */ + int64_t offset; + /* Last offset actually written to disk. */ + int64_t fd_offset; + /* Total bytes actually written to files. */ + int64_t total_bytes_written; + /* Maximum size of file, -1 if unknown. */ + int64_t filesize; + /* Dir we were in before this restore; only for deep paths. */ + int restore_pwd; + /* Mode we should use for this entry; affected by _PERM and umask. */ + mode_t mode; + /* UID/GID to use in restoring this entry. */ + int64_t uid; + int64_t gid; +}; + +/* + * Default mode for dirs created automatically (will be modified by umask). + * Note that POSIX specifies 0777 for implicity-created dirs, "modified + * by the process' file creation mask." + */ +#define DEFAULT_DIR_MODE 0777 +/* + * Dir modes are restored in two steps: During the extraction, the permissions + * in the archive are modified to match the following limits. During + * the post-extract fixup pass, the permissions from the archive are + * applied. + */ +#define MINIMUM_DIR_MODE 0700 +#define MAXIMUM_DIR_MODE 0775 + +static int check_symlinks(struct archive_write_disk *); +static int create_filesystem_object(struct archive_write_disk *); +static struct fixup_entry *current_fixup(struct archive_write_disk *, const wchar_t *pathname); +#if defined(HAVE_FCHDIR) && defined(PATH_MAX) +static void edit_deep_directories(struct archive_write_disk *ad); +#endif +static int cleanup_pathname(struct archive_write_disk *); +static int create_dir(struct archive_write_disk *, wchar_t *); +static int create_parent_dir(struct archive_write_disk *, wchar_t *); +static int older(BY_HANDLE_FILE_INFORMATION *, struct archive_entry *); +static int restore_entry(struct archive_write_disk *); +#ifdef HAVE_POSIX_ACL +static int set_acl(struct archive_write_disk *, int fd, const char *, struct archive_acl *, + acl_type_t, int archive_entry_acl_type, const char *tn); +#endif +static int set_acls(struct archive_write_disk *, HANDLE h, const wchar_t *, struct archive_acl *); +static int set_xattrs(struct archive_write_disk *); +static int set_fflags(struct archive_write_disk *); +static int set_ownership(struct archive_write_disk *); +static int set_mode(struct archive_write_disk *, int mode); +static int set_times(struct archive_write_disk *, HANDLE, int, const wchar_t *, + time_t, long, time_t, long, time_t, long, time_t, long); +static int set_times_from_entry(struct archive_write_disk *); +static struct fixup_entry *sort_dir_list(struct fixup_entry *p); +static ssize_t write_data_block(struct archive_write_disk *, + const char *, size_t); + +static struct archive_vtable *archive_write_disk_vtable(void); + +static int _archive_write_disk_close(struct archive *); +static int _archive_write_disk_free(struct archive *); +static int _archive_write_disk_header(struct archive *, struct archive_entry *); +static int64_t _archive_write_disk_filter_bytes(struct archive *, int); +static int _archive_write_disk_finish_entry(struct archive *); +static ssize_t _archive_write_disk_data(struct archive *, const void *, size_t); +static ssize_t _archive_write_disk_data_block(struct archive *, const void *, size_t, int64_t); + +#define bhfi_dev(bhfi) ((bhfi)->dwVolumeSerialNumber) +/* Treat FileIndex as i-node. We should remove a sequence number + * which is high-16-bits of nFileIndexHigh. */ +#define bhfi_ino(bhfi) \ + ((((int64_t)((bhfi)->nFileIndexHigh & 0x0000FFFFUL)) << 32) \ + + (bhfi)->nFileIndexLow) +#define bhfi_size(bhfi) \ + ((((int64_t)(bhfi)->nFileSizeHigh) << 32) + (bhfi)->nFileSizeLow) + +static int +file_information(struct archive_write_disk *a, wchar_t *path, + BY_HANDLE_FILE_INFORMATION *st, mode_t *mode, int sim_lstat) +{ + HANDLE h; + int r; + DWORD flag = FILE_FLAG_BACKUP_SEMANTICS; + WIN32_FIND_DATAW findData; + + if (sim_lstat || mode != NULL) { + h = FindFirstFileW(path, &findData); + if (h == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_INVALID_NAME) { + wchar_t *full; + full = __la_win_permissive_name_w(path); + h = FindFirstFileW(full, &findData); + free(full); + } + if (h == INVALID_HANDLE_VALUE) { + la_dosmaperr(GetLastError()); + return (-1); + } + FindClose(h); + } + + /* Is symlink file ? */ + if (sim_lstat && + ((findData.dwFileAttributes + & FILE_ATTRIBUTE_REPARSE_POINT) && + (findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK))) + flag |= FILE_FLAG_OPEN_REPARSE_POINT; + + h = CreateFileW(a->name, 0, 0, NULL, + OPEN_EXISTING, flag, NULL); + if (h == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_INVALID_NAME) { + wchar_t *full; + full = __la_win_permissive_name_w(path); + h = CreateFileW(full, 0, 0, NULL, + OPEN_EXISTING, flag, NULL); + free(full); + } + if (h == INVALID_HANDLE_VALUE) { + la_dosmaperr(GetLastError()); + return (-1); + } + r = GetFileInformationByHandle(h, st); + CloseHandle(h); + if (r == 0) { + la_dosmaperr(GetLastError()); + return (-1); + } + + if (mode == NULL) + return (0); + + *mode = S_IRUSR | S_IRGRP | S_IROTH; + if ((st->dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0) + *mode |= S_IWUSR | S_IWGRP | S_IWOTH; + if ((st->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK) + *mode |= S_IFLNK; + else if (st->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + *mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH; + else { + const wchar_t *p; + + *mode |= S_IFREG; + p = wcsrchr(path, L'.'); + if (p != NULL && wcslen(p) == 4) { + switch (p[1]) { + case L'B': case L'b': + if ((p[2] == L'A' || p[2] == L'a' ) && + (p[3] == L'T' || p[3] == L't' )) + *mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + case L'C': case L'c': + if (((p[2] == L'M' || p[2] == L'm' ) && + (p[3] == L'D' || p[3] == L'd' )) || + ((p[2] == L'M' || p[2] == L'm' ) && + (p[3] == L'D' || p[3] == L'd' ))) + *mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + case L'E': case L'e': + if ((p[2] == L'X' || p[2] == L'x' ) && + (p[3] == L'E' || p[3] == L'e' )) + *mode |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + default: + break; + } + } + } + return (0); +} + +/* + * Note: The path, for example, "aa/a/../b../c" will be converted to "aa/c" + * by GetFullPathNameW() W32 API, which __la_win_permissive_name_w uses. + * It means we cannot handle multiple dirs in one archive_entry. + * So we have to make the full-pathname in another way, which does not + * break "../" path string. + */ +int +permissive_name_w(struct archive_write_disk *a) +{ + wchar_t *wn, *wnp; + wchar_t *ws, *wsp; + DWORD l; + + wnp = a->name; + if (wnp[0] == L'\\' && wnp[1] == L'\\' && + wnp[2] == L'?' && wnp[3] == L'\\') + /* We have already a permissive name. */ + return (0); + + if (wnp[0] == L'\\' && wnp[1] == L'\\' && + wnp[2] == L'.' && wnp[3] == L'\\') { + /* This is a device name */ + if (((wnp[4] >= L'a' && wnp[4] <= L'z') || + (wnp[4] >= L'A' && wnp[4] <= L'Z')) && + wnp[5] == L':' && wnp[6] == L'\\') { + wnp[2] = L'?';/* Not device name. */ + return (0); + } + } + + /* + * A full-pathname starting with a drive name like "C:\abc". + */ + if (((wnp[0] >= L'a' && wnp[0] <= L'z') || + (wnp[0] >= L'A' && wnp[0] <= L'Z')) && + wnp[1] == L':' && wnp[2] == L'\\') { + wn = _wcsdup(wnp); + if (wn == NULL) + return (-1); + archive_wstring_ensure(&(a->_name_data), 4 + wcslen(wn) + 1); + a->name = a->_name_data.s; + /* Prepend "\\?\" */ + archive_wstrncpy(&(a->_name_data), L"\\\\?\\", 4); + archive_wstrcat(&(a->_name_data), wn); + free(wn); + return (0); + } + + /* + * A full-pathname pointig a network drive + * like "\\\\file". + */ + if (wnp[0] == L'\\' && wnp[1] == L'\\' && wnp[2] != L'\\') { + const wchar_t *p = &wnp[2]; + + /* Skip server-name letters. */ + while (*p != L'\\' && *p != L'\0') + ++p; + if (*p == L'\\') { + const wchar_t *rp = ++p; + /* Skip share-name letters. */ + while (*p != L'\\' && *p != L'\0') + ++p; + if (*p == L'\\' && p != rp) { + /* Now, match patterns such as + * "\\server-name\share-name\" */ + wn = _wcsdup(wnp); + if (wn == NULL) + return (-1); + archive_wstring_ensure(&(a->_name_data), 8 + wcslen(wn) + 1); + a->name = a->_name_data.s; + /* Prepend "\\?\UNC\" */ + archive_wstrncpy(&(a->_name_data), L"\\\\?\\UNC\\", 8); + archive_wstrcat(&(a->_name_data), wn+2); + free(wn); + return (0); + } + } + return (0); + } + + /* + * Get current working directory. + */ + l = GetCurrentDirectoryW(0, NULL); + if (l == 0) + return (-1); + ws = malloc(l * sizeof(wchar_t)); + l = GetCurrentDirectoryW(l, ws); + if (l == 0) { + free(ws); + return (-1); + } + wsp = ws; + + /* + * A full-pathname starting without a drive name like "\abc". + */ + if (wnp[0] == L'\\') { + wn = _wcsdup(wnp); + if (wn == NULL) + return (-1); + archive_wstring_ensure(&(a->_name_data), 4 + 2 + wcslen(wn) + 1); + a->name = a->_name_data.s; + /* Prepend "\\?\" and drive name. */ + archive_wstrncpy(&(a->_name_data), L"\\\\?\\", 4); + archive_wstrncat(&(a->_name_data), wsp, 2); + archive_wstrcat(&(a->_name_data), wn); + free(wsp); + free(wn); + return (0); + } + + wn = _wcsdup(wnp); + if (wn == NULL) + return (-1); + archive_wstring_ensure(&(a->_name_data), 4 + l + 1 + wcslen(wn) + 1); + a->name = a->_name_data.s; + /* Prepend "\\?\" and drive name. */ + archive_wstrncpy(&(a->_name_data), L"\\\\?\\", 4); + archive_wstrncat(&(a->_name_data), wsp, l); + archive_wstrncat(&(a->_name_data), L"\\", 1); + archive_wstrcat(&(a->_name_data), wn); + a->name = a->_name_data.s; + free(wsp); + free(wn); + return (0); +} + +int +la_chmod(const wchar_t *path, mode_t mode) +{ + DWORD attr; + BOOL r; + wchar_t *fullname; + int ret = 0; + + fullname = NULL; + attr = GetFileAttributesW(path); + if (attr == (DWORD)-1 && + GetLastError() == ERROR_INVALID_NAME) { + fullname = __la_win_permissive_name_w(path); + attr = GetFileAttributesW(fullname); + } + if (attr == (DWORD)-1) { + la_dosmaperr(GetLastError()); + ret = -1; + goto exit_chmode; + } + if (mode & _S_IWRITE) + attr &= ~FILE_ATTRIBUTE_READONLY; + else + attr |= FILE_ATTRIBUTE_READONLY; + if (fullname != NULL) + r = SetFileAttributesW(fullname, attr); + else + r = SetFileAttributesW(path, attr); + if (r == 0) { + la_dosmaperr(GetLastError()); + ret = -1; + } +exit_chmode: + free(fullname); + return (ret); +} + +static void * +la_GetFunctionKernel32(const char *name) +{ + static HINSTANCE lib; + static int set; + if (!set) { + set = 1; + lib = LoadLibrary("kernel32.dll"); + } + if (lib == NULL) { + fprintf(stderr, "Can't load kernel32.dll?!\n"); + exit(1); + } + return (void *)GetProcAddress(lib, name); +} + +static int +la_CreateHardLinkW(wchar_t *linkname, wchar_t *target) +{ + static BOOLEAN (WINAPI *f)(LPWSTR, LPWSTR, LPSECURITY_ATTRIBUTES); + static int set; + if (!set) { + set = 1; + f = la_GetFunctionKernel32("CreateHardLinkW"); + } + return f == NULL ? 0 : (*f)(linkname, target, NULL); +} + +static int +la_ftruncate(HANDLE handle, int64_t length) +{ + LARGE_INTEGER distance; + + if (GetFileType(handle) != FILE_TYPE_DISK) { + errno = EBADF; + return (-1); + } + distance.QuadPart = length; + if (!SetFilePointerEx_perso(handle, distance, NULL, FILE_BEGIN)) { + la_dosmaperr(GetLastError()); + return (-1); + } + if (!SetEndOfFile(handle)) { + la_dosmaperr(GetLastError()); + return (-1); + } + return (0); +} + +static int +lazy_stat(struct archive_write_disk *a) +{ + if (a->pst != NULL) { + /* Already have stat() data available. */ + return (ARCHIVE_OK); + } + if (a->fh != INVALID_HANDLE_VALUE && + GetFileInformationByHandle(a->fh, &a->st) == 0) { + a->pst = &a->st; + return (ARCHIVE_OK); + } + + /* + * XXX At this point, symlinks should not be hit, otherwise + * XXX a race occured. Do we want to check explicitly for that? + */ + if (file_information(a, a->name, &a->st, NULL, 1) == 0) { + a->pst = &a->st; + return (ARCHIVE_OK); + } + archive_set_error(&a->archive, errno, "Couldn't stat file"); + return (ARCHIVE_WARN); +} + +static struct archive_vtable * +archive_write_disk_vtable(void) +{ + static struct archive_vtable av; + static int inited = 0; + + if (!inited) { + av.archive_close = _archive_write_disk_close; + av.archive_filter_bytes = _archive_write_disk_filter_bytes; + av.archive_free = _archive_write_disk_free; + av.archive_write_header = _archive_write_disk_header; + av.archive_write_finish_entry + = _archive_write_disk_finish_entry; + av.archive_write_data = _archive_write_disk_data; + av.archive_write_data_block = _archive_write_disk_data_block; + inited = 1; + } + return (&av); +} + +static int64_t +_archive_write_disk_filter_bytes(struct archive *_a, int n) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + (void)n; /* UNUSED */ + if (n == -1 || n == 0) + return (a->total_bytes_written); + return (-1); +} + + +int +archive_write_disk_set_options(struct archive *_a, int flags) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + + a->flags = flags; + return (ARCHIVE_OK); +} + + +/* + * Extract this entry to disk. + * + * TODO: Validate hardlinks. According to the standards, we're + * supposed to check each extracted hardlink and squawk if it refers + * to a file that we didn't restore. I'm not entirely convinced this + * is a good idea, but more importantly: Is there any way to validate + * hardlinks without keeping a complete list of filenames from the + * entire archive?? Ugh. + * + */ +static int +_archive_write_disk_header(struct archive *_a, struct archive_entry *entry) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *fe; + int ret, r; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_header"); + archive_clear_error(&a->archive); + if (a->archive.state & ARCHIVE_STATE_DATA) { + r = _archive_write_disk_finish_entry(&a->archive); + if (r == ARCHIVE_FATAL) + return (r); + } + + /* Set up for this particular entry. */ + a->pst = NULL; + a->current_fixup = NULL; + a->deferred = 0; + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->entry = archive_entry_clone(entry); + a->fh = INVALID_HANDLE_VALUE; + a->fd_offset = 0; + a->offset = 0; + a->restore_pwd = -1; + a->uid = a->user_uid; + a->mode = archive_entry_mode(a->entry); + if (archive_entry_size_is_set(a->entry)) + a->filesize = archive_entry_size(a->entry); + else + a->filesize = -1; + archive_wstrcpy(&(a->_name_data), archive_entry_pathname_w(a->entry)); + a->name = a->_name_data.s; + archive_clear_error(&a->archive); + + /* + * Clean up the requested path. This is necessary for correct + * dir restores; the dir restore logic otherwise gets messed + * up by nonsense like "dir/.". + */ + ret = cleanup_pathname(a); + if (ret != ARCHIVE_OK) + return (ret); + + /* + * Generate a full-pathname and use it from here. + */ + if (permissive_name_w(a) < 0) { + errno = EINVAL; + return (ARCHIVE_FAILED); + } + + /* + * Query the umask so we get predictable mode settings. + * This gets done on every call to _write_header in case the + * user edits their umask during the extraction for some + * reason. + */ + umask(a->user_umask = umask(0)); + + /* Figure out what we need to do for this entry. */ + a->todo = TODO_MODE_BASE; + if (a->flags & ARCHIVE_EXTRACT_PERM) { + a->todo |= TODO_MODE_FORCE; /* Be pushy about permissions. */ + /* + * SGID requires an extra "check" step because we + * cannot easily predict the GID that the system will + * assign. (Different systems assign GIDs to files + * based on a variety of criteria, including process + * credentials and the gid of the enclosing + * directory.) We can only restore the SGID bit if + * the file has the right GID, and we only know the + * GID if we either set it (see set_ownership) or if + * we've actually called stat() on the file after it + * was restored. Since there are several places at + * which we might verify the GID, we need a TODO bit + * to keep track. + */ + if (a->mode & S_ISGID) + a->todo |= TODO_SGID | TODO_SGID_CHECK; + /* + * Verifying the SUID is simpler, but can still be + * done in multiple ways, hence the separate "check" bit. + */ + if (a->mode & S_ISUID) + a->todo |= TODO_SUID | TODO_SUID_CHECK; + } else { + /* + * User didn't request full permissions, so don't + * restore SUID, SGID bits and obey umask. + */ + a->mode &= ~S_ISUID; + a->mode &= ~S_ISGID; + a->mode &= ~S_ISVTX; + a->mode &= ~a->user_umask; + } +#if 0 + if (a->flags & ARCHIVE_EXTRACT_OWNER) + a->todo |= TODO_OWNER; +#endif + if (a->flags & ARCHIVE_EXTRACT_TIME) + a->todo |= TODO_TIMES; + if (a->flags & ARCHIVE_EXTRACT_ACL) { + if (archive_entry_filetype(a->entry) == AE_IFDIR) + a->deferred |= TODO_ACLS; + else + a->todo |= TODO_ACLS; + } + if (a->flags & ARCHIVE_EXTRACT_XATTR) + a->todo |= TODO_XATTR; + if (a->flags & ARCHIVE_EXTRACT_FFLAGS) + a->todo |= TODO_FFLAGS; + if (a->flags & ARCHIVE_EXTRACT_SECURE_SYMLINKS) { + ret = check_symlinks(a); + if (ret != ARCHIVE_OK) + return (ret); + } + + ret = restore_entry(a); + + /* + * TODO: There are rumours that some extended attributes must + * be restored before file data is written. If this is true, + * then we either need to write all extended attributes both + * before and after restoring the data, or find some rule for + * determining which must go first and which last. Due to the + * many ways people are using xattrs, this may prove to be an + * intractable problem. + */ + + /* + * Fixup uses the unedited pathname from archive_entry_pathname(), + * because it is relative to the base dir and the edited path + * might be relative to some intermediate dir as a result of the + * deep restore logic. + */ + if (a->deferred & TODO_MODE) { + fe = current_fixup(a, archive_entry_pathname_w(entry)); + fe->fixup |= TODO_MODE_BASE; + fe->mode = a->mode; + } + + if ((a->deferred & TODO_TIMES) + && (archive_entry_mtime_is_set(entry) + || archive_entry_atime_is_set(entry))) { + fe = current_fixup(a, archive_entry_pathname_w(entry)); + fe->mode = a->mode; + fe->fixup |= TODO_TIMES; + if (archive_entry_atime_is_set(entry)) { + fe->atime = archive_entry_atime(entry); + fe->atime_nanos = archive_entry_atime_nsec(entry); + } else { + /* If atime is unset, use start time. */ + fe->atime = a->start_time; + fe->atime_nanos = 0; + } + if (archive_entry_mtime_is_set(entry)) { + fe->mtime = archive_entry_mtime(entry); + fe->mtime_nanos = archive_entry_mtime_nsec(entry); + } else { + /* If mtime is unset, use start time. */ + fe->mtime = a->start_time; + fe->mtime_nanos = 0; + } + if (archive_entry_birthtime_is_set(entry)) { + fe->birthtime = archive_entry_birthtime(entry); + fe->birthtime_nanos = archive_entry_birthtime_nsec(entry); + } else { + /* If birthtime is unset, use mtime. */ + fe->birthtime = fe->mtime; + fe->birthtime_nanos = fe->mtime_nanos; + } + } + + if (a->deferred & TODO_ACLS) { + fe = current_fixup(a, archive_entry_pathname_w(entry)); + archive_acl_copy(&fe->acl, archive_entry_acl(entry)); + } + + if (a->deferred & TODO_FFLAGS) { + fe = current_fixup(a, archive_entry_pathname_w(entry)); + fe->fixup |= TODO_FFLAGS; + /* TODO: Complete this.. defer fflags from below. */ + } + + /* + * On Windows, A creating sparse file requires a special mark. + */ + if (a->fh != INVALID_HANDLE_VALUE && + archive_entry_sparse_count(entry) > 0) { + int64_t base = 0, offset, length; + int i, cnt = archive_entry_sparse_reset(entry); + int sparse = 0; + + for (i = 0; i < cnt; i++) { + archive_entry_sparse_next(entry, &offset, &length); + if (offset - base >= 4096) { + sparse = 1;/* we have a hole. */ + break; + } + base = offset + length; + } + if (sparse) { + DWORD dmy; + /* Mark this file as sparse. */ + DeviceIoControl(a->fh, FSCTL_SET_SPARSE, + NULL, 0, NULL, 0, &dmy, NULL); + } + } + + /* We've created the object and are ready to pour data into it. */ + if (ret >= ARCHIVE_WARN) + a->archive.state = ARCHIVE_STATE_DATA; + /* + * If it's not open, tell our client not to try writing. + * In particular, dirs, links, etc, don't get written to. + */ + if (a->fh == INVALID_HANDLE_VALUE) { + archive_entry_set_size(entry, 0); + a->filesize = 0; + } + + return (ret); +} + +int +archive_write_disk_set_skip_file(struct archive *_a, int64_t d, int64_t i) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_skip_file"); + a->skip_file_set = 1; + a->skip_file_dev = d; + a->skip_file_ino = i; + return (ARCHIVE_OK); +} + +static ssize_t +write_data_block(struct archive_write_disk *a, const char *buff, size_t size) +{ + uint64_t start_size = size; + DWORD bytes_written = 0; + ssize_t block_size = 0, bytes_to_write; + + if (size == 0) + return (ARCHIVE_OK); + + if (a->filesize == 0 || a->fh == INVALID_HANDLE_VALUE) { + archive_set_error(&a->archive, 0, + "Attempt to write to an empty file"); + return (ARCHIVE_WARN); + } + + if (a->flags & ARCHIVE_EXTRACT_SPARSE) { + /* XXX TODO XXX Is there a more appropriate choice here ? */ + /* This needn't match the filesystem allocation size. */ + block_size = 16*1024; + } + + /* If this write would run beyond the file size, truncate it. */ + if (a->filesize >= 0 && (int64_t)(a->offset + size) > a->filesize) + start_size = size = (size_t)(a->filesize - a->offset); + + /* Write the data. */ + while (size > 0) { + if (block_size == 0) { + bytes_to_write = size; + } else { + /* We're sparsifying the file. */ + const char *p, *end; + int64_t block_end; + + /* Skip leading zero bytes. */ + for (p = buff, end = buff + size; p < end; ++p) { + if (*p != '\0') + break; + } + a->offset += p - buff; + size -= p - buff; + buff = p; + if (size == 0) + break; + + /* Calculate next block boundary after offset. */ + block_end + = (a->offset / block_size + 1) * block_size; + + /* If the adjusted write would cross block boundary, + * truncate it to the block boundary. */ + bytes_to_write = size; + if (a->offset + bytes_to_write > block_end) + bytes_to_write = block_end - a->offset; + } + /* Seek if necessary to the specified offset. */ + if (a->offset != a->fd_offset) { + LARGE_INTEGER distance; + distance.QuadPart = a->offset; + if (SetFilePointerEx_perso(a->fh, distance, NULL, FILE_BEGIN) == 0) { + DWORD lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, + "Seek failed"); + return (ARCHIVE_FATAL); + } + a->fd_offset = a->offset; + } + if (!WriteFile(a->fh, buff, (uint32_t)bytes_to_write, + &bytes_written, NULL)) { + DWORD lasterr; + + lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, "Write failed"); + return (ARCHIVE_WARN); + } + buff += bytes_written; + size -= bytes_written; + a->total_bytes_written += bytes_written; + a->offset += bytes_written; + a->fd_offset = a->offset; + } + return (start_size - size); +} + +static ssize_t +_archive_write_disk_data_block(struct archive *_a, + const void *buff, size_t size, int64_t offset) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + ssize_t r; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data_block"); + + a->offset = offset; + r = write_data_block(a, buff, size); + if (r < ARCHIVE_OK) + return (r); + if ((size_t)r < size) { + archive_set_error(&a->archive, 0, + "Write request too large"); + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static ssize_t +_archive_write_disk_data(struct archive *_a, const void *buff, size_t size) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_DATA, "archive_write_data"); + + return (write_data_block(a, buff, size)); +} + +static int +_archive_write_disk_finish_entry(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + int ret = ARCHIVE_OK; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_finish_entry"); + if (a->archive.state & ARCHIVE_STATE_HEADER) + return (ARCHIVE_OK); + archive_clear_error(&a->archive); + + /* Pad or truncate file to the right size. */ + if (a->fh == INVALID_HANDLE_VALUE) { + /* There's no file. */ + } else if (a->filesize < 0) { + /* File size is unknown, so we can't set the size. */ + } else if (a->fd_offset == a->filesize) { + /* Last write ended at exactly the filesize; we're done. */ + /* Hopefully, this is the common case. */ + } else { + if (la_ftruncate(a->fh, a->filesize) == -1 && + a->filesize == 0) { + archive_set_error(&a->archive, errno, + "File size could not be restored"); + return (ARCHIVE_FAILED); + } + /* + * Not all platforms implement the XSI option to + * extend files via ftruncate. Stat() the file again + * to see what happened. + */ + a->pst = NULL; + if ((ret = lazy_stat(a)) != ARCHIVE_OK) + return (ret); + /* We can use lseek()/write() to extend the file if + * ftruncate didn't work or isn't available. */ + if (bhfi_size(&(a->st)) < a->filesize) { + const char nul = '\0'; + LARGE_INTEGER distance; + distance.QuadPart = a->filesize - 1; + if (!SetFilePointerEx_perso(a->fh, distance, NULL, FILE_BEGIN)) { + DWORD lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, + "Seek failed"); + return (ARCHIVE_FATAL); + } + if (!WriteFile(a->fh, &nul, 1, NULL, NULL)) { + DWORD lasterr = GetLastError(); + if (lasterr == ERROR_ACCESS_DENIED) + errno = EBADF; + else + la_dosmaperr(lasterr); + archive_set_error(&a->archive, errno, + "Write to restore size failed"); + return (ARCHIVE_FATAL); + } + a->pst = NULL; + } + } + + /* Restore metadata. */ + + /* + * Look up the "real" UID only if we're going to need it. + * TODO: the TODO_SGID condition can be dropped here, can't it? + */ + if (a->todo & (TODO_OWNER | TODO_SUID | TODO_SGID)) { + a->uid = archive_write_disk_uid(&a->archive, + archive_entry_uname(a->entry), + archive_entry_uid(a->entry)); + } + /* Look up the "real" GID only if we're going to need it. */ + /* TODO: the TODO_SUID condition can be dropped here, can't it? */ + if (a->todo & (TODO_OWNER | TODO_SGID | TODO_SUID)) { + a->gid = archive_write_disk_gid(&a->archive, + archive_entry_gname(a->entry), + archive_entry_gid(a->entry)); + } + + /* + * Restore ownership before set_mode tries to restore suid/sgid + * bits. If we set the owner, we know what it is and can skip + * a stat() call to examine the ownership of the file on disk. + */ + if (a->todo & TODO_OWNER) + ret = set_ownership(a); + + /* + * set_mode must precede ACLs on systems such as Solaris and + * FreeBSD where setting the mode implicitly clears extended ACLs + */ + if (a->todo & TODO_MODE) { + int r2 = set_mode(a, a->mode); + if (r2 < ret) ret = r2; + } + + /* + * Security-related extended attributes (such as + * security.capability on Linux) have to be restored last, + * since they're implicitly removed by other file changes. + */ + if (a->todo & TODO_XATTR) { + int r2 = set_xattrs(a); + if (r2 < ret) ret = r2; + } + + /* + * Some flags prevent file modification; they must be restored after + * file contents are written. + */ + if (a->todo & TODO_FFLAGS) { + int r2 = set_fflags(a); + if (r2 < ret) ret = r2; + } + + /* + * Time must follow most other metadata; + * otherwise atime will get changed. + */ + if (a->todo & TODO_TIMES) { + int r2 = set_times_from_entry(a); + if (r2 < ret) ret = r2; + } + + /* + * ACLs must be restored after timestamps because there are + * ACLs that prevent attribute changes (including time). + */ + if (a->todo & TODO_ACLS) { + int r2 = set_acls(a, a->fh, + archive_entry_pathname_w(a->entry), + archive_entry_acl(a->entry)); + if (r2 < ret) ret = r2; + } + + /* If there's an fd, we can close it now. */ + if (a->fh != INVALID_HANDLE_VALUE) { + CloseHandle(a->fh); + a->fh = INVALID_HANDLE_VALUE; + } + /* If there's an entry, we can release it now. */ + if (a->entry) { + archive_entry_free(a->entry); + a->entry = NULL; + } + a->archive.state = ARCHIVE_STATE_HEADER; + return (ret); +} + +int +archive_write_disk_set_group_lookup(struct archive *_a, + void *private_data, + int64_t (*lookup_gid)(void *private, const char *gname, int64_t gid), + void (*cleanup_gid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_group_lookup"); + + if (a->cleanup_gid != NULL && a->lookup_gid_data != NULL) + (a->cleanup_gid)(a->lookup_gid_data); + + a->lookup_gid = lookup_gid; + a->cleanup_gid = cleanup_gid; + a->lookup_gid_data = private_data; + return (ARCHIVE_OK); +} + +int +archive_write_disk_set_user_lookup(struct archive *_a, + void *private_data, + int64_t (*lookup_uid)(void *private, const char *uname, int64_t uid), + void (*cleanup_uid)(void *private)) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_set_user_lookup"); + + if (a->cleanup_uid != NULL && a->lookup_uid_data != NULL) + (a->cleanup_uid)(a->lookup_uid_data); + + a->lookup_uid = lookup_uid; + a->cleanup_uid = cleanup_uid; + a->lookup_uid_data = private_data; + return (ARCHIVE_OK); +} + +int64_t +archive_write_disk_gid(struct archive *_a, const char *name, int64_t id) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_gid"); + if (a->lookup_gid) + return (a->lookup_gid)(a->lookup_gid_data, name, id); + return (id); +} + +int64_t +archive_write_disk_uid(struct archive *_a, const char *name, int64_t id) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY, "archive_write_disk_uid"); + if (a->lookup_uid) + return (a->lookup_uid)(a->lookup_uid_data, name, id); + return (id); +} + +/* + * Create a new archive_write_disk object and initialize it with global state. + */ +struct archive * +archive_write_disk_new(void) +{ + struct archive_write_disk *a; + + a = (struct archive_write_disk *)malloc(sizeof(*a)); + if (a == NULL) + return (NULL); + memset(a, 0, sizeof(*a)); + a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC; + /* We're ready to write a header immediately. */ + a->archive.state = ARCHIVE_STATE_HEADER; + a->archive.vtable = archive_write_disk_vtable(); + a->start_time = time(NULL); + /* Query and restore the umask. */ + umask(a->user_umask = umask(0)); + if (archive_wstring_ensure(&a->path_safe, 512) == NULL) { + free(a); + return (NULL); + } + return (&a->archive); +} + +static int +disk_unlink(wchar_t *path) +{ + wchar_t *fullname; + int r; + + r = _wunlink(path); + if (r != 0 && GetLastError() == ERROR_INVALID_NAME) { + fullname = __la_win_permissive_name_w(path); + r = _wunlink(fullname); + free(fullname); + } + return (r); +} + +static int +disk_rmdir(wchar_t *path) +{ + wchar_t *fullname; + int r; + + r = _wrmdir(path); + if (r != 0 && GetLastError() == ERROR_INVALID_NAME) { + fullname = __la_win_permissive_name_w(path); + r = _wrmdir(fullname); + free(fullname); + } + return (r); +} + +/* + * The main restore function. + */ +static int +restore_entry(struct archive_write_disk *a) +{ + int ret = ARCHIVE_OK, en; + + if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) { + /* + * TODO: Fix this. Apparently, there are platforms + * that still allow root to hose the entire filesystem + * by unlinking a dir. The S_ISDIR() test above + * prevents us from using unlink() here if the new + * object is a dir, but that doesn't mean the old + * object isn't a dir. + */ + if (disk_unlink(a->name) == 0) { + /* We removed it, reset cached stat. */ + a->pst = NULL; + } else if (errno == ENOENT) { + /* File didn't exist, that's just as good. */ + } else if (disk_rmdir(a->name) == 0) { + /* It was a dir, but now it's gone. */ + a->pst = NULL; + } else { + /* We tried, but couldn't get rid of it. */ + archive_set_error(&a->archive, errno, + "Could not unlink"); + return(ARCHIVE_FAILED); + } + } + + /* Try creating it first; if this fails, we'll try to recover. */ + en = create_filesystem_object(a); + + if ((en == ENOTDIR || en == ENOENT) + && !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) { + wchar_t *full; + /* If the parent dir doesn't exist, try creating it. */ + create_parent_dir(a, a->name); + /* Now try to create the object again. */ + full = __la_win_permissive_name_w(a->name); + if (full == NULL) { + en = EINVAL; + } else { + /* Remove multiple directories such as "a/../b../c" */ + archive_wstrcpy(&(a->_name_data), full); + a->name = a->_name_data.s; + free(full); + en = create_filesystem_object(a); + } + } + + if ((en == EISDIR || en == EEXIST) + && (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + /* If we're not overwriting, we're done. */ + archive_entry_unset_size(a->entry); + return (ARCHIVE_OK); + } + + /* + * Some platforms return EISDIR if you call + * open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some + * return EEXIST. POSIX is ambiguous, requiring EISDIR + * for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT) + * on an existing item. + */ + if (en == EISDIR) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (disk_rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* Try again. */ + en = create_filesystem_object(a); + } else if (en == EEXIST) { + mode_t st_mode; + /* + * We know something is in the way, but we don't know what; + * we need to find out before we go any further. + */ + int r = 0; + /* + * The SECURE_SYMLINK logic has already removed a + * symlink to a dir if the client wants that. So + * follow the symlink if we're creating a dir. + */ + if (S_ISDIR(a->mode)) + r = file_information(a, a->name, &a->st, &st_mode, 0); + /* + * If it's not a dir (or it's a broken symlink), + * then don't follow it. + */ + if (r != 0 || !S_ISDIR(a->mode)) + r = file_information(a, a->name, &a->st, &st_mode, 1); + if (r != 0) { + archive_set_error(&a->archive, errno, + "Can't stat existing object"); + return (ARCHIVE_FAILED); + } + + /* + * NO_OVERWRITE_NEWER doesn't apply to directories. + */ + if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER) + && !S_ISDIR(st_mode)) { + if (!older(&(a->st), a->entry)) { + archive_entry_unset_size(a->entry); + return (ARCHIVE_OK); + } + } + + /* If it's our archive, we're done. */ + if (a->skip_file_set && + bhfi_dev(&a->st) == a->skip_file_dev && + bhfi_ino(&a->st) == a->skip_file_ino) { + archive_set_error(&a->archive, 0, "Refusing to overwrite archive"); + return (ARCHIVE_FAILED); + } + + if (!S_ISDIR(st_mode)) { + /* A non-dir is in the way, unlink it. */ + if (disk_unlink(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't unlink already-existing object"); + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* Try again. */ + en = create_filesystem_object(a); + } else if (!S_ISDIR(a->mode)) { + /* A dir is in the way of a non-dir, rmdir it. */ + if (disk_rmdir(a->name) != 0) { + archive_set_error(&a->archive, errno, + "Can't remove already-existing dir"); + return (ARCHIVE_FAILED); + } + /* Try again. */ + en = create_filesystem_object(a); + } else { + /* + * There's a dir in the way of a dir. Don't + * waste time with rmdir()/mkdir(), just fix + * up the permissions on the existing dir. + * Note that we don't change perms on existing + * dirs unless _EXTRACT_PERM is specified. + */ + if ((a->mode != st_mode) + && (a->todo & TODO_MODE_FORCE)) + a->deferred |= (a->todo & TODO_MODE); + /* Ownership doesn't need deferred fixup. */ + en = 0; /* Forget the EEXIST. */ + } + } + + if (en) { + /* Everything failed; give up here. */ + archive_set_error(&a->archive, en, "Can't create '%s'", + a->name); + return (ARCHIVE_FAILED); + } + + a->pst = NULL; /* Cached stat data no longer valid. */ + return (ret); +} + +/* + * Returns 0 if creation succeeds, or else returns errno value from + * the failed system call. Note: This function should only ever perform + * a single system call. + */ +static int +create_filesystem_object(struct archive_write_disk *a) +{ + /* Create the entry. */ + const wchar_t *linkname; + wchar_t *fullname; + mode_t final_mode, mode; + int r; + + /* We identify hard/symlinks according to the link names. */ + /* Since link(2) and symlink(2) don't handle modes, we're done here. */ + linkname = archive_entry_hardlink_w(a->entry); + if (linkname != NULL) { + wchar_t *linkfull, *namefull; + + linkfull = __la_win_permissive_name_w(linkname); + namefull = __la_win_permissive_name_w(a->name); + if (linkfull == NULL || namefull == NULL) { + errno = EINVAL; + r = -1; + } else { + r = la_CreateHardLinkW(namefull, linkfull); + if (r == 0) { + la_dosmaperr(GetLastError()); + r = errno; + } else + r = 0; + } + /* + * New cpio and pax formats allow hardlink entries + * to carry data, so we may have to open the file + * for hardlink entries. + * + * If the hardlink was successfully created and + * the archive doesn't have carry data for it, + * consider it to be non-authoritive for meta data. + * This is consistent with GNU tar and BSD pax. + * If the hardlink does carry data, let the last + * archive entry decide ownership. + */ + if (r == 0 && a->filesize <= 0) { + a->todo = 0; + a->deferred = 0; + } else if (r == 0 && a->filesize > 0) { + a->fh = CreateFileW(namefull, GENERIC_WRITE, 0, NULL, + TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (a->fh == INVALID_HANDLE_VALUE) { + la_dosmaperr(GetLastError()); + r = errno; + } + } + free(linkfull); + free(namefull); + return (r); + } + linkname = archive_entry_symlink_w(a->entry); + if (linkname != NULL) { +#if HAVE_SYMLINK + return symlink(linkname, a->name) ? errno : 0; +#else + return (EPERM); +#endif + } + + /* + * The remaining system calls all set permissions, so let's + * try to take advantage of that to avoid an extra chmod() + * call. (Recall that umask is set to zero right now!) + */ + + /* Mode we want for the final restored object (w/o file type bits). */ + final_mode = a->mode & 07777; + /* + * The mode that will actually be restored in this step. Note + * that SUID, SGID, etc, require additional work to ensure + * security, so we never restore them at this point. + */ + mode = final_mode & 0777 & a->user_umask; + + switch (a->mode & AE_IFMT) { + default: + /* POSIX requires that we fall through here. */ + /* FALLTHROUGH */ + case AE_IFREG: + fullname = a->name; + /* O_WRONLY | O_CREAT | O_EXCL */ + a->fh = CreateFileW(fullname, GENERIC_WRITE, 0, NULL, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (a->fh == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_INVALID_NAME && + fullname == a->name) { + fullname = __la_win_permissive_name_w(a->name); + a->fh = CreateFileW(fullname, GENERIC_WRITE, 0, NULL, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + } + if (a->fh == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_ACCESS_DENIED) { + DWORD attr; + /* Simulate an errno of POSIX system. */ + attr = GetFileAttributesW(fullname); + if (attr == (DWORD)-1) + la_dosmaperr(GetLastError()); + else if (attr & FILE_ATTRIBUTE_DIRECTORY) + errno = EISDIR; + else + errno = EACCES; + } else + la_dosmaperr(GetLastError()); + r = 1; + } else + r = 0; + if (fullname != a->name) + free(fullname); + break; + case AE_IFCHR: + case AE_IFBLK: + /* TODO: Find a better way to warn about our inability + * to restore a block device node. */ + return (EINVAL); + case AE_IFDIR: + mode = (mode | MINIMUM_DIR_MODE) & MAXIMUM_DIR_MODE; + fullname = a->name; + r = CreateDirectoryW(fullname, NULL); + if (r == 0 && GetLastError() == ERROR_INVALID_NAME && + fullname == a->name) { + fullname = __la_win_permissive_name_w(a->name); + r = CreateDirectoryW(fullname, NULL); + } + if (r != 0) { + r = 0; + /* Defer setting dir times. */ + a->deferred |= (a->todo & TODO_TIMES); + a->todo &= ~TODO_TIMES; + /* Never use an immediate chmod(). */ + /* We can't avoid the chmod() entirely if EXTRACT_PERM + * because of SysV SGID inheritance. */ + if ((mode != final_mode) + || (a->flags & ARCHIVE_EXTRACT_PERM)) + a->deferred |= (a->todo & TODO_MODE); + a->todo &= ~TODO_MODE; + } else { + la_dosmaperr(GetLastError()); + r = -1; + } + if (fullname != a->name) + free(fullname); + break; + case AE_IFIFO: + /* TODO: Find a better way to warn about our inability + * to restore a fifo. */ + return (EINVAL); + } + + /* All the system calls above set errno on failure. */ + if (r) + return (errno); + + /* If we managed to set the final mode, we've avoided a chmod(). */ + if (mode == final_mode) + a->todo &= ~TODO_MODE; + return (0); +} + +/* + * Cleanup function for archive_extract. Mostly, this involves processing + * the fixup list, which is used to address a number of problems: + * * Dir permissions might prevent us from restoring a file in that + * dir, so we restore the dir with minimum 0700 permissions first, + * then correct the mode at the end. + * * Similarly, the act of restoring a file touches the directory + * and changes the timestamp on the dir, so we have to touch-up dir + * timestamps at the end as well. + * * Some file flags can interfere with the restore by, for example, + * preventing the creation of hardlinks to those files. + * * Mac OS extended metadata includes ACLs, so must be deferred on dirs. + * + * Note that tar/cpio do not require that archives be in a particular + * order; there is no way to know when the last file has been restored + * within a directory, so there's no way to optimize the memory usage + * here by fixing up the directory any earlier than the + * end-of-archive. + * + * XXX TODO: Directory ACLs should be restored here, for the same + * reason we set directory perms here. XXX + */ +static int +_archive_write_disk_close(struct archive *_a) +{ + struct archive_write_disk *a = (struct archive_write_disk *)_a; + struct fixup_entry *next, *p; + int ret; + + archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA, + "archive_write_disk_close"); + ret = _archive_write_disk_finish_entry(&a->archive); + + /* Sort dir list so directories are fixed up in depth-first order. */ + p = sort_dir_list(a->fixup_list); + + while (p != NULL) { + a->pst = NULL; /* Mark stat cache as out-of-date. */ + if (p->fixup & TODO_TIMES) { + set_times(a, INVALID_HANDLE_VALUE, p->mode, p->name, + p->atime, p->atime_nanos, + p->birthtime, p->birthtime_nanos, + p->mtime, p->mtime_nanos, + p->ctime, p->ctime_nanos); + } + if (p->fixup & TODO_MODE_BASE) + la_chmod(p->name, p->mode); + if (p->fixup & TODO_ACLS) + set_acls(a, INVALID_HANDLE_VALUE, p->name, &p->acl); + next = p->next; + archive_acl_clear(&p->acl); + free(p->name); + free(p); + p = next; + } + a->fixup_list = NULL; + return (ret); +} + +static int +_archive_write_disk_free(struct archive *_a) +{ + struct archive_write_disk *a; + int ret; + if (_a == NULL) + return (ARCHIVE_OK); + archive_check_magic(_a, ARCHIVE_WRITE_DISK_MAGIC, + ARCHIVE_STATE_ANY | ARCHIVE_STATE_FATAL, "archive_write_disk_free"); + a = (struct archive_write_disk *)_a; + ret = _archive_write_disk_close(&a->archive); + archive_write_disk_set_group_lookup(&a->archive, NULL, NULL, NULL); + archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL); + if (a->entry) + archive_entry_free(a->entry); + archive_wstring_free(&a->_name_data); + archive_string_free(&a->archive.error_string); + archive_wstring_free(&a->path_safe); + a->archive.magic = 0; + __archive_clean(&a->archive); + free(a); + return (ret); +} + +/* + * Simple O(n log n) merge sort to order the fixup list. In + * particular, we want to restore dir timestamps depth-first. + */ +static struct fixup_entry * +sort_dir_list(struct fixup_entry *p) +{ + struct fixup_entry *a, *b, *t; + + if (p == NULL) + return (NULL); + /* A one-item list is already sorted. */ + if (p->next == NULL) + return (p); + + /* Step 1: split the list. */ + t = p; + a = p->next->next; + while (a != NULL) { + /* Step a twice, t once. */ + a = a->next; + if (a != NULL) + a = a->next; + t = t->next; + } + /* Now, t is at the mid-point, so break the list here. */ + b = t->next; + t->next = NULL; + a = p; + + /* Step 2: Recursively sort the two sub-lists. */ + a = sort_dir_list(a); + b = sort_dir_list(b); + + /* Step 3: Merge the returned lists. */ + /* Pick the first element for the merged list. */ + if (wcscmp(a->name, b->name) > 0) { + t = p = a; + a = a->next; + } else { + t = p = b; + b = b->next; + } + + /* Always put the later element on the list first. */ + while (a != NULL && b != NULL) { + if (wcscmp(a->name, b->name) > 0) { + t->next = a; + a = a->next; + } else { + t->next = b; + b = b->next; + } + t = t->next; + } + + /* Only one list is non-empty, so just splice it on. */ + if (a != NULL) + t->next = a; + if (b != NULL) + t->next = b; + + return (p); +} + +/* + * Returns a new, initialized fixup entry. + * + * TODO: Reduce the memory requirements for this list by using a tree + * structure rather than a simple list of names. + */ +static struct fixup_entry * +new_fixup(struct archive_write_disk *a, const wchar_t *pathname) +{ + struct fixup_entry *fe; + + fe = (struct fixup_entry *)calloc(1, sizeof(struct fixup_entry)); + if (fe == NULL) + return (NULL); + fe->next = a->fixup_list; + a->fixup_list = fe; + fe->fixup = 0; + fe->name = _wcsdup(pathname); + return (fe); +} + +/* + * Returns a fixup structure for the current entry. + */ +static struct fixup_entry * +current_fixup(struct archive_write_disk *a, const wchar_t *pathname) +{ + if (a->current_fixup == NULL) + a->current_fixup = new_fixup(a, pathname); + return (a->current_fixup); +} + +/* TODO: Make this work. */ +/* + * TODO: The deep-directory support bypasses this; disable deep directory + * support if we're doing symlink checks. + */ +/* + * TODO: Someday, integrate this with the deep dir support; they both + * scan the path and both can be optimized by comparing against other + * recent paths. + */ +/* TODO: Extend this to support symlinks on Windows Vista and later. */ +static int +check_symlinks(struct archive_write_disk *a) +{ + wchar_t *pn, *p; + wchar_t c; + int r; + BY_HANDLE_FILE_INFORMATION st; + mode_t st_mode; + + /* + * Guard against symlink tricks. Reject any archive entry whose + * destination would be altered by a symlink. + */ + /* Whatever we checked last time doesn't need to be re-checked. */ + pn = a->name; + p = a->path_safe.s; + while ((*pn != '\0') && (*p == *pn)) + ++p, ++pn; + c = pn[0]; + /* Keep going until we've checked the entire name. */ + while (pn[0] != '\0' && (pn[0] != '\\' || pn[1] != '\0')) { + /* Skip the next path element. */ + while (*pn != '\0' && *pn != '\\') + ++pn; + c = pn[0]; + pn[0] = '\0'; + /* Check that we haven't hit a symlink. */ + r = file_information(a, a->name, &st, &st_mode, 1); + if (r != 0) { + /* We've hit a dir that doesn't exist; stop now. */ + if (errno == ENOENT) + break; + } else if (S_ISLNK(st_mode)) { + if (c == '\0') { + /* + * Last element is symlink; remove it + * so we can overwrite it with the + * item being extracted. + */ + if (disk_unlink(a->name)) { + archive_set_error(&a->archive, errno, + "Could not remove symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + a->pst = NULL; + /* + * Even if we did remove it, a warning + * is in order. The warning is silly, + * though, if we're just replacing one + * symlink with another symlink. + */ + if (!S_ISLNK(a->mode)) { + archive_set_error(&a->archive, 0, + "Removing symlink %s", + a->name); + } + /* Symlink gone. No more problem! */ + pn[0] = c; + return (0); + } else if (a->flags & ARCHIVE_EXTRACT_UNLINK) { + /* User asked us to remove problems. */ + if (disk_unlink(a->name) != 0) { + archive_set_error(&a->archive, 0, + "Cannot remove intervening symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + a->pst = NULL; + } else { + archive_set_error(&a->archive, 0, + "Cannot extract through symlink %s", + a->name); + pn[0] = c; + return (ARCHIVE_FAILED); + } + } + } + pn[0] = c; + /* We've checked and/or cleaned the whole path, so remember it. */ + archive_wstrcpy(&a->path_safe, a->name); + return (ARCHIVE_OK); +} + +static int +guidword(wchar_t *p, int n) +{ + int i; + + for (i = 0; i < n; i++) { + if ((*p >= L'0' && *p <= L'9') || + (*p >= L'a' && *p <= L'f') || + (*p >= L'A' && *p <= L'F')) + p++; + else + return (-1); + } + return (0); +} + +/* + * Canonicalize the pathname. In particular, this strips duplicate + * '\' characters, '.' elements, and trailing '\'. It also raises an + * error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is + * set) any '..' in the path. + */ +static int +cleanup_pathname(struct archive_write_disk *a) +{ + wchar_t *dest, *src, *p, *top; + wchar_t separator = L'\0'; + + p = a->name; + if (*p == L'\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid empty pathname"); + return (ARCHIVE_FAILED); + } + + /* Replace '/' by '\' */ + for (; *p != L'\0'; p++) { + if (*p == L'/') + *p = L'\\'; + } + p = a->name; + + /* Skip leading "\\.\" or "\\?\" or "\\?\UNC\" or + * "\\?\Volume{GUID}\" + * (absolute path prefixes used by Windows API) */ + if (p[0] == L'\\' && p[1] == L'\\' && + (p[2] == L'.' || p[2] == L'?') && p[3] == L'\\') + { + /* A path begin with "\\?\UNC\" */ + if (p[2] == L'?' && + (p[4] == L'U' || p[4] == L'u') && + (p[5] == L'N' || p[5] == L'n') && + (p[6] == L'C' || p[6] == L'c') && + p[7] == L'\\') + p += 8; + /* A path begin with "\\?\Volume{GUID}\" */ + else if (p[2] == L'?' && + (p[4] == L'V' || p[4] == L'v') && + (p[5] == L'O' || p[5] == L'o') && + (p[6] == L'L' || p[6] == L'l') && + (p[7] == L'U' || p[7] == L'u') && + (p[8] == L'M' || p[8] == L'm') && + (p[9] == L'E' || p[9] == L'e') && + p[10] == L'{') { + if (guidword(p+11, 8) == 0 && p[19] == L'-' && + guidword(p+20, 4) == 0 && p[24] == L'-' && + guidword(p+25, 4) == 0 && p[29] == L'-' && + guidword(p+30, 4) == 0 && p[34] == L'-' && + guidword(p+35, 12) == 0 && p[47] == L'}' && + p[48] == L'\\') + p += 49; + else + p += 4; + /* A path begin with "\\.\PhysicalDriveX" */ + } else if (p[2] == L'.' && + (p[4] == L'P' || p[4] == L'p') && + (p[5] == L'H' || p[5] == L'h') && + (p[6] == L'Y' || p[6] == L'y') && + (p[7] == L'S' || p[7] == L's') && + (p[8] == L'I' || p[8] == L'i') && + (p[9] == L'C' || p[9] == L'c') && + (p[9] == L'A' || p[9] == L'a') && + (p[9] == L'L' || p[9] == L'l') && + (p[9] == L'D' || p[9] == L'd') && + (p[9] == L'R' || p[9] == L'r') && + (p[9] == L'I' || p[9] == L'i') && + (p[9] == L'V' || p[9] == L'v') && + (p[9] == L'E' || p[9] == L'e') && + (p[10] >= L'0' && p[10] <= L'9') && + p[11] == L'\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Path is a physical drive name"); + return (ARCHIVE_FAILED); + } else + p += 4; + } + + /* Skip leading drive letter from archives created + * on Windows. */ + if (((p[0] >= L'a' && p[0] <= L'z') || + (p[0] >= L'A' && p[0] <= L'Z')) && + p[1] == L':') { + if (p[2] == L'\0') { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Path is a drive name"); + return (ARCHIVE_FAILED); + } + if (p[2] == L'\\') + p += 2; + } + + top = dest = src = p; + /* Rewrite the path name if its character is a unusable. */ + for (; *p != L'\0'; p++) { + if (*p == L':' || *p == L'*' || *p == L'?' || *p == L'"' || + *p == L'<' || *p == L'>' || *p == L'|') + *p = L'_'; + } + /* Skip leading '\'. */ + if (*src == L'\\') + separator = *src++; + + /* Scan the pathname one element at a time. */ + for (;;) { + /* src points to first char after '\' */ + if (src[0] == L'\0') { + break; + } else if (src[0] == L'\\') { + /* Found '\\'('//'), ignore second one. */ + src++; + continue; + } else if (src[0] == L'.') { + if (src[1] == L'\0') { + /* Ignore trailing '.' */ + break; + } else if (src[1] == L'\\') { + /* Skip '.\'. */ + src += 2; + continue; + } else if (src[1] == L'.') { + if (src[2] == L'\\' || src[2] == L'\0') { + /* Conditionally warn about '..' */ + if (a->flags & + ARCHIVE_EXTRACT_SECURE_NODOTDOT) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Path contains '..'"); + return (ARCHIVE_FAILED); + } + } + /* + * Note: Under no circumstances do we + * remove '..' elements. In + * particular, restoring + * '\foo\..\bar\' should create the + * 'foo' dir as a side-effect. + */ + } + } + + /* Copy current element, including leading '\'. */ + if (separator) + *dest++ = L'\\'; + while (*src != L'\0' && *src != L'\\') { + *dest++ = *src++; + } + + if (*src == L'\0') + break; + + /* Skip '\' separator. */ + separator = *src++; + } + /* + * We've just copied zero or more path elements, not including the + * final '\'. + */ + if (dest == top) { + /* + * Nothing got copied. The path must have been something + * like '.' or '\' or './' or '/././././/./'. + */ + if (separator) + *dest++ = L'\\'; + else + *dest++ = L'.'; + } + /* Terminate the result. */ + *dest = L'\0'; + return (ARCHIVE_OK); +} + +/* + * Create the parent directory of the specified path, assuming path + * is already in mutable storage. + */ +static int +create_parent_dir(struct archive_write_disk *a, wchar_t *path) +{ + wchar_t *slash; + int r; + + /* Remove tail element to obtain parent name. */ + slash = wcsrchr(path, L'\\'); + if (slash == NULL) + return (ARCHIVE_OK); + *slash = L'\0'; + r = create_dir(a, path); + *slash = L'\\'; + return (r); +} + +/* + * Create the specified dir, recursing to create parents as necessary. + * + * Returns ARCHIVE_OK if the path exists when we're done here. + * Otherwise, returns ARCHIVE_FAILED. + * Assumes path is in mutable storage; path is unchanged on exit. + */ +static int +create_dir(struct archive_write_disk *a, wchar_t *path) +{ + BY_HANDLE_FILE_INFORMATION st; + struct fixup_entry *le; + wchar_t *slash, *base, *full; + mode_t mode_final, mode, st_mode; + int r; + + /* Check for special names and just skip them. */ + slash = wcsrchr(path, L'\\'); + if (slash == NULL) + base = path; + else + base = slash + 1; + + if (base[0] == L'\0' || + (base[0] == L'.' && base[1] == L'\0') || + (base[0] == L'.' && base[1] == L'.' && base[2] == L'\0')) { + /* Don't bother trying to create null path, '.', or '..'. */ + if (slash != NULL) { + *slash = L'\0'; + r = create_dir(a, path); + *slash = L'\\'; + return (r); + } + return (ARCHIVE_OK); + } + + /* + * Yes, this should be stat() and not lstat(). Using lstat() + * here loses the ability to extract through symlinks. Also note + * that this should not use the a->st cache. + */ + if (file_information(a, path, &st, &st_mode, 0) == 0) { + if (S_ISDIR(st_mode)) + return (ARCHIVE_OK); + if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) { + archive_set_error(&a->archive, EEXIST, + "Can't create directory '%s'", path); + return (ARCHIVE_FAILED); + } + if (disk_unlink(path) != 0) { + archive_set_error(&a->archive, errno, + "Can't create directory '%s': " + "Conflicting file cannot be removed", + path); + return (ARCHIVE_FAILED); + } + } else if (errno != ENOENT && errno != ENOTDIR) { + /* Stat failed? */ + archive_set_error(&a->archive, errno, "Can't test directory '%s'", path); + return (ARCHIVE_FAILED); + } else if (slash != NULL) { + *slash = '\0'; + r = create_dir(a, path); + *slash = '\\'; + if (r != ARCHIVE_OK) + return (r); + } + + /* + * Mode we want for the final restored directory. Per POSIX, + * implicitly-created dirs must be created obeying the umask. + * There's no mention whether this is different for privileged + * restores (which the rest of this code handles by pretending + * umask=0). I've chosen here to always obey the user's umask for + * implicit dirs, even if _EXTRACT_PERM was specified. + */ + mode_final = DEFAULT_DIR_MODE & ~a->user_umask; + /* Mode we want on disk during the restore process. */ + mode = mode_final; + mode |= MINIMUM_DIR_MODE; + mode &= MAXIMUM_DIR_MODE; + /* + * Apply __la_win_permissive_name_w to path in order to + * remove '../' path string. + */ + full = __la_win_permissive_name_w(path); + if (full == NULL) + errno = EINVAL; + else if (CreateDirectoryW(full, NULL) != 0) { + if (mode != mode_final) { + le = new_fixup(a, path); + le->fixup |=TODO_MODE_BASE; + le->mode = mode_final; + } + return (ARCHIVE_OK); + } else { + la_dosmaperr(GetLastError()); + } + free(full); + + /* + * Without the following check, a/b/../b/c/d fails at the + * second visit to 'b', so 'd' can't be created. Note that we + * don't add it to the fixup list here, as it's already been + * added. + */ + if (file_information(a, path, &st, &st_mode, 0) == 0 && S_ISDIR(st_mode)) + return (ARCHIVE_OK); + + archive_set_error(&a->archive, errno, "Failed to create dir '%s'", + path); + return (ARCHIVE_FAILED); +} + +/* + * Note: Although we can skip setting the user id if the desired user + * id matches the current user, we cannot skip setting the group, as + * many systems set the gid based on the containing directory. So + * we have to perform a chown syscall if we want to set the SGID + * bit. (The alternative is to stat() and then possibly chown(); it's + * more efficient to skip the stat() and just always chown().) Note + * that a successful chown() here clears the TODO_SGID_CHECK bit, which + * allows set_mode to skip the stat() check for the GID. + */ +static int +set_ownership(struct archive_write_disk *a) +{ +/* unfortunately, on win32 there is no 'root' user with uid 0, + so we just have to try the chown and see if it works */ + + /* If we know we can't change it, don't bother trying. */ + if (a->user_uid != 0 && a->user_uid != a->uid) { + archive_set_error(&a->archive, errno, + "Can't set UID=%jd", (intmax_t)a->uid); + return (ARCHIVE_WARN); + } + + archive_set_error(&a->archive, errno, + "Can't set user=%jd/group=%jd for %s", + (intmax_t)a->uid, (intmax_t)a->gid, a->name); + return (ARCHIVE_WARN); +} + +static int +set_times(struct archive_write_disk *a, + HANDLE h, int mode, const wchar_t *name, + time_t atime, long atime_nanos, + time_t birthtime, long birthtime_nanos, + time_t mtime, long mtime_nanos, + time_t ctime, long ctime_nanos) +{ +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) +#define WINTIME(sec, nsec) ((Int32x32To64(sec, 10000000) + EPOC_TIME)\ + + (((nsec)/1000)*10)) + + HANDLE hw = 0; + ULARGE_INTEGER wintm; + FILETIME *pfbtime; + FILETIME fatime, fbtime, fmtime; + + (void)ctime; /* UNUSED */ + (void)ctime_nanos; /* UNUSED */ + + if (h != INVALID_HANDLE_VALUE) { + hw = NULL; + } else { + wchar_t *ws; + + if (S_ISLNK(mode)) + return (ARCHIVE_OK); + ws = __la_win_permissive_name_w(name); + if (ws == NULL) + goto settimes_failed; + hw = CreateFileW(ws, FILE_WRITE_ATTRIBUTES, + 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + free(ws); + if (hw == INVALID_HANDLE_VALUE) + goto settimes_failed; + h = hw; + } + + wintm.QuadPart = WINTIME(atime, atime_nanos); + fatime.dwLowDateTime = wintm.LowPart; + fatime.dwHighDateTime = wintm.HighPart; + wintm.QuadPart = WINTIME(mtime, mtime_nanos); + fmtime.dwLowDateTime = wintm.LowPart; + fmtime.dwHighDateTime = wintm.HighPart; + /* + * SetFileTime() supports birthtime. + */ + if (birthtime > 0 || birthtime_nanos > 0) { + wintm.QuadPart = WINTIME(birthtime, birthtime_nanos); + fbtime.dwLowDateTime = wintm.LowPart; + fbtime.dwHighDateTime = wintm.HighPart; + pfbtime = &fbtime; + } else + pfbtime = NULL; + if (SetFileTime(h, pfbtime, &fatime, &fmtime) == 0) + goto settimes_failed; + CloseHandle(hw); + return (ARCHIVE_OK); + +settimes_failed: + CloseHandle(hw); + archive_set_error(&a->archive, EINVAL, "Can't restore time"); + return (ARCHIVE_WARN); +} + +static int +set_times_from_entry(struct archive_write_disk *a) +{ + time_t atime, birthtime, mtime, ctime; + long atime_nsec, birthtime_nsec, mtime_nsec, ctime_nsec; + + /* Suitable defaults. */ + atime = birthtime = mtime = ctime = a->start_time; + atime_nsec = birthtime_nsec = mtime_nsec = ctime_nsec = 0; + + /* If no time was provided, we're done. */ + if (!archive_entry_atime_is_set(a->entry) + && !archive_entry_birthtime_is_set(a->entry) + && !archive_entry_mtime_is_set(a->entry)) + return (ARCHIVE_OK); + + if (archive_entry_atime_is_set(a->entry)) { + atime = archive_entry_atime(a->entry); + atime_nsec = archive_entry_atime_nsec(a->entry); + } + if (archive_entry_birthtime_is_set(a->entry)) { + birthtime = archive_entry_birthtime(a->entry); + birthtime_nsec = archive_entry_birthtime_nsec(a->entry); + } + if (archive_entry_mtime_is_set(a->entry)) { + mtime = archive_entry_mtime(a->entry); + mtime_nsec = archive_entry_mtime_nsec(a->entry); + } + if (archive_entry_ctime_is_set(a->entry)) { + ctime = archive_entry_ctime(a->entry); + ctime_nsec = archive_entry_ctime_nsec(a->entry); + } + + return set_times(a, a->fh, a->mode, a->name, + atime, atime_nsec, + birthtime, birthtime_nsec, + mtime, mtime_nsec, + ctime, ctime_nsec); +} + +static int +set_mode(struct archive_write_disk *a, int mode) +{ + int r = ARCHIVE_OK; + mode &= 07777; /* Strip off file type bits. */ + + if (a->todo & TODO_SGID_CHECK) { + /* + * If we don't know the GID is right, we must stat() + * to verify it. We can't just check the GID of this + * process, since systems sometimes set GID from + * the enclosing dir or based on ACLs. + */ + if ((r = lazy_stat(a)) != ARCHIVE_OK) + return (r); + if (0 != a->gid) { + mode &= ~ S_ISGID; + } + /* While we're here, double-check the UID. */ + if (0 != a->uid + && (a->todo & TODO_SUID)) { + mode &= ~ S_ISUID; + } + a->todo &= ~TODO_SGID_CHECK; + a->todo &= ~TODO_SUID_CHECK; + } else if (a->todo & TODO_SUID_CHECK) { + /* + * If we don't know the UID is right, we can just check + * the user, since all systems set the file UID from + * the process UID. + */ + if (a->user_uid != a->uid) { + mode &= ~ S_ISUID; + } + a->todo &= ~TODO_SUID_CHECK; + } + + if (S_ISLNK(a->mode)) { +#ifdef HAVE_LCHMOD + /* + * If this is a symlink, use lchmod(). If the + * platform doesn't support lchmod(), just skip it. A + * platform that doesn't provide a way to set + * permissions on symlinks probably ignores + * permissions on symlinks, so a failure here has no + * impact. + */ + if (lchmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } +#endif + } else if (!S_ISDIR(a->mode)) { + /* + * If it's not a symlink and not a dir, then use + * fchmod() or chmod(), depending on whether we have + * an fd. Dirs get their perms set during the + * post-extract fixup, which is handled elsewhere. + */ +#ifdef HAVE_FCHMOD + if (a->fd >= 0) { + if (fchmod(a->fd, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } else +#endif + /* If this platform lacks fchmod(), then + * we'll just use chmod(). */ + if (la_chmod(a->name, mode) != 0) { + archive_set_error(&a->archive, errno, + "Can't set permissions to 0%o", (int)mode); + r = ARCHIVE_WARN; + } + } + return (r); +} + +static int +set_fflags(struct archive_write_disk *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* Default empty function body to satisfy mainline code. */ +static int +set_acls(struct archive_write_disk *a, HANDLE h, const wchar_t *name, + struct archive_acl *acl) +{ + (void)a; /* UNUSED */ + (void)h; /* UNUSED */ + (void)name; /* UNUSED */ + (void)acl; /* UNUSED */ + return (ARCHIVE_OK); +} + +/* + * Restore extended attributes - stub implementation for unsupported systems + */ +static int +set_xattrs(struct archive_write_disk *a) +{ + static int warning_done = 0; + + /* If there aren't any extended attributes, then it's okay not + * to extract them, otherwise, issue a single warning. */ + if (archive_entry_xattr_count(a->entry) != 0 && !warning_done) { + warning_done = 1; + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Cannot restore extended attributes on this system"); + return (ARCHIVE_WARN); + } + /* Warning was already emitted; suppress further warnings. */ + return (ARCHIVE_OK); +} + +static void +fileTimeToUtc(const FILETIME *filetime, time_t *time, long *ns) +{ + ULARGE_INTEGER utc; + + utc.HighPart = filetime->dwHighDateTime; + utc.LowPart = filetime->dwLowDateTime; + if (utc.QuadPart >= EPOC_TIME) { + utc.QuadPart -= EPOC_TIME; + /* milli seconds base */ + *time = (time_t)(utc.QuadPart / 10000000); + /* nano seconds base */ + *ns = (long)(utc.QuadPart % 10000000) * 100; + } else { + *time = 0; + *ns = 0; + } +} +/* + * Test if file on disk is older than entry. + */ +static int +older(BY_HANDLE_FILE_INFORMATION *st, struct archive_entry *entry) +{ + time_t sec; + long nsec; + + fileTimeToUtc(&st->ftLastWriteTime, &sec, &nsec); + /* First, test the seconds and return if we have a definite answer. */ + /* Definitely older. */ + if (sec < archive_entry_mtime(entry)) + return (1); + /* Definitely younger. */ + if (sec > archive_entry_mtime(entry)) + return (0); + if (nsec < archive_entry_mtime_nsec(entry)) + return (1); + /* Same age or newer, so not older. */ + return (0); +} + +#endif /* _WIN32 && !__CYGWIN__ */ + diff --git a/libarchive/archive_write_filter.3 b/libarchive/archive_write_filter.3 new file mode 100644 index 0000000..f084a51 --- /dev/null +++ b/libarchive/archive_write_filter.3 @@ -0,0 +1,98 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_filter 3 +.Os +.Sh NAME +.Nm archive_write_add_filter_bzip2 , +.Nm archive_write_add_filter_compress , +.Nm archive_write_add_filter_gzip , +.Nm archive_write_add_filter_lzip , +.Nm archive_write_add_filter_lzma , +.Nm archive_write_add_filter_none , +.Nm archive_write_add_filter_program , +.Nm archive_write_add_filter_xz +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_add_filter_bzip2 "struct archive *" +.Ft int +.Fn archive_write_add_filter_compress "struct archive *" +.Ft int +.Fn archive_write_add_filter_gzip "struct archive *" +.Ft int +.Fn archive_write_add_filter_lzip "struct archive *" +.Ft int +.Fn archive_write_add_filter_lzma "struct archive *" +.Ft int +.Fn archive_write_add_filter_none "struct archive *" +.Ft int +.Fn archive_write_add_filter_program "struct archive *" "const char * cmd" +.Ft int +.Fn archive_write_add_filter_xz "struct archive *" +.Sh DESCRIPTION +.Bl -tag -width indent +.It Xo +.Fn archive_write_add_filter_bzip2 , +.Fn archive_write_add_filter_compress , +.Fn archive_write_add_filter_gzip , +.Fn archive_write_add_filter_lzip , +.Fn archive_write_add_filter_lzma , +.Fn archive_write_add_filter_xz , +.Xc +The resulting archive will be compressed as specified. +Note that the compressed output is always properly blocked. +.It Fn archive_write_add_filter_none +This is never necessary. +It is provided only for backwards compatibility. +.It Fn archive_write_add_filter_program +The archive will be fed into the specified compression program. +The output of that program is blocked and written to the client +write callbacks. +.El +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write 3 , +.Xr archive_write_format 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_finish_entry.3 b/libarchive/archive_write_finish_entry.3 new file mode 100644 index 0000000..06c002e --- /dev/null +++ b/libarchive/archive_write_finish_entry.3 @@ -0,0 +1,74 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_write.3 201110 2009-12-28 03:31:29Z kientzle $ +.\" +.Dd March 23, 2011 +.Dt archive_write_finish_entry 3 +.Os +.Sh NAME +.Nm archive_write_finish_entry +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_finish_entry "struct archive *" +.Sh DESCRIPTION +Close out the entry just written. +In particular, this writes out the final padding required by some formats. +Ordinarily, clients never need to call this, as it +is called automatically by +.Fn archive_write_next_header +and +.Fn archive_write_close +as needed. +.\" .Sh EXAMPLE +.Sh RETURN VALUES +This function returns +.Cm ARCHIVE_OK +on success, or one of several non-zero +error codes for errors. +Specific error codes include: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_data 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_format.3 b/libarchive/archive_write_format.3 new file mode 100644 index 0000000..b3e7e35 --- /dev/null +++ b/libarchive/archive_write_format.3 @@ -0,0 +1,98 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_format 3 +.Os +.Sh NAME +.Nm archive_write_set_format_cpio , +.Nm archive_write_set_format_pax , +.Nm archive_write_set_format_pax_restricted , +.Nm archive_write_set_format_shar , +.Nm archive_write_set_format_shar_dump , +.Nm archive_write_set_format_ustar +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_set_format_cpio "struct archive *" +.Ft int +.Fn archive_write_set_format_pax "struct archive *" +.Ft int +.Fn archive_write_set_format_pax_restricted "struct archive *" +.Ft int +.Fn archive_write_set_format_shar "struct archive *" +.Ft int +.Fn archive_write_set_format_shar_dump "struct archive *" +.Ft int +.Fn archive_write_set_format_ustar "struct archive *" +.Sh DESCRIPTION +These functions set the format that will be used for the archive. +.Pp +The library can write +POSIX octet-oriented cpio format archives, +POSIX-standard +.Dq pax interchange +format archives, +traditional +.Dq shar +archives, +enhanced +.Dq dump +shar archives that store a variety of file attributes and handle binary files, +and +POSIX-standard +.Dq ustar +archives. +The pax interchange format is a backwards-compatible tar format that +adds key/value attributes to each entry and supports arbitrary +filenames, linknames, uids, sizes, etc. +.Dq Restricted pax interchange format +is the library default; this is the same as pax format, but suppresses +the pax extended header for most normal files. +In most cases, this will result in ordinary ustar archives. +.\" +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_free.3 b/libarchive/archive_write_free.3 new file mode 100644 index 0000000..d939780 --- /dev/null +++ b/libarchive/archive_write_free.3 @@ -0,0 +1,81 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_free 3 +.Os +.Sh NAME +.Nm archive_write_close , +.Nm archive_write_finish , +.Nm archive_write_free +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_close "struct archive *" +.Ft int +.Fn archive_write_finish "struct archive *" +.Ft int +.Fn archive_write_free "struct archive *" +.Sh DESCRIPTION +.Bl -tag -width indent +.It Fn archive_write_close +Complete the archive and invoke the close callback. +.It Fn archive_write_finish +This is a deprecated synonym for +.Fn archive_write_free . +.It Fn archive_write_free +Invokes +.Fn archive_write_close +if necessary, then releases all resources. +If you need detailed information about +.Fn archive_write_close +failures, you should be careful to call it separately, as +you cannot obtain error information after +.Fn archive_write_free +returns. +.El +.\" .Sh EXAMPLE +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_header.3 b/libarchive/archive_write_header.3 new file mode 100644 index 0000000..f76175b --- /dev/null +++ b/libarchive/archive_write_header.3 @@ -0,0 +1,71 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_header 3 +.Os +.Sh NAME +.Nm archive_write_header +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fn archive_write_header "struct archive *" "struct archive_entry *" +.Sh DESCRIPTION +Build and write a header using the data in the provided +.Tn struct archive_entry +structure. +See +.Xr archive_entry 3 +for information on creating and populating +.Tn struct archive_entry +objects. +.\" .Sh EXAMPLE +.Sh RETURN VALUES +This function returns +.Cm ARCHIVE_OK +on success, or one of the following on error: +.Cm ARCHIVE_RETRY +for operations that might succeed if retried, +.Cm ARCHIVE_WARN +for unusual conditions that do not prevent further operations, and +.Cm ARCHIVE_FATAL +for serious errors that make remaining operations impossible. +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_new.3 b/libarchive/archive_write_new.3 new file mode 100644 index 0000000..76515bb --- /dev/null +++ b/libarchive/archive_write_new.3 @@ -0,0 +1,56 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 23, 2011 +.Dt archive_write_new 3 +.Os +.Sh NAME +.Nm archive_write_new +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft struct archive * +.Fn archive_write_new "void" +.Sh DESCRIPTION +Allocates and initializes a +.Tn struct archive +object suitable for writing a tar archive. +.Dv NULL +is returned on error. +.Pp +A complete description of the +.Tn struct archive +object can be found in the overview manual page for +.Xr libarchive 3 . +.\" .Sh ERRORS +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_open.3 b/libarchive/archive_write_open.3 new file mode 100644 index 0000000..ab2d484 --- /dev/null +++ b/libarchive/archive_write_open.3 @@ -0,0 +1,233 @@ +.\" Copyright (c) 2003-2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_write.3 201110 2009-12-28 03:31:29Z kientzle $ +.\" +.Dd March 23, 2011 +.Dt archive_write 3 +.Os +.Sh NAME +.Nm archive_write_open , +.Nm archive_write_open_fd , +.Nm archive_write_open_FILE , +.Nm archive_write_open_filename , +.Nm archive_write_open_memory +.Nd functions for creating archives +.Sh SYNOPSIS +.In archive.h +.Ft int +.Fo archive_write_open +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "archive_open_callback *" +.Fa "archive_write_callback *" +.Fa "archive_close_callback *" +.Fc +.Ft int +.Fn archive_write_open_fd "struct archive *" "int fd" +.Ft int +.Fn archive_write_open_FILE "struct archive *" "FILE *file" +.Ft int +.Fn archive_write_open_filename "struct archive *" "const char *filename" +.Ft int +.Fo archive_write_open_memory +.Fa "struct archive *" +.Fa "void *buffer" +.Fa "size_t bufferSize" +.Fa "size_t *outUsed" +.Fc +.Sh DESCRIPTION +.Bl -tag -width indent +.It Fn archive_write_open +Freeze the settings, open the archive, and prepare for writing entries. +This is the most generic form of this function, which accepts +pointers to three callback functions which will be invoked by +the compression layer to write the constructed archive. +.It Fn archive_write_open_fd +A convenience form of +.Fn archive_write_open +that accepts a file descriptor. +The +.Fn archive_write_open_fd +function is safe for use with tape drives or other +block-oriented devices. +.It Fn archive_write_open_FILE +A convenience form of +.Fn archive_write_open +that accepts a +.Ft "FILE *" +pointer. +Note that +.Fn archive_write_open_FILE +is not safe for writing to tape drives or other devices +that require correct blocking. +.It Fn archive_write_open_file +A deprecated synonym for +.Fn archive_write_open_filename . +.It Fn archive_write_open_filename +A convenience form of +.Fn archive_write_open +that accepts a filename. +A NULL argument indicates that the output should be written to standard output; +an argument of +.Dq - +will open a file with that name. +If you have not invoked +.Fn archive_write_set_bytes_in_last_block , +then +.Fn archive_write_open_filename +will adjust the last-block padding depending on the file: +it will enable padding when writing to standard output or +to a character or block device node, it will disable padding otherwise. +You can override this by manually invoking +.Fn archive_write_set_bytes_in_last_block +before calling +.Fn archive_write_open . +The +.Fn archive_write_open_filename +function is safe for use with tape drives or other +block-oriented devices. +.It Fn archive_write_open_memory +A convenience form of +.Fn archive_write_open +that accepts a pointer to a block of memory that will receive +the archive. +The final +.Ft "size_t *" +argument points to a variable that will be updated +after each write to reflect how much of the buffer +is currently in use. +You should be careful to ensure that this variable +remains allocated until after the archive is +closed. +.El +More information about the +.Va struct archive +object and the overall design of the library can be found in the +.Xr libarchive 3 +overview. +.\" +.Sh CLIENT CALLBACKS +To use this library, you will need to define and register +callback functions that will be invoked to write data to the +resulting archive. +These functions are registered by calling +.Fn archive_write_open : +.Bl -item -offset indent +.It +.Ft typedef int +.Fn archive_open_callback "struct archive *" "void *client_data" +.El +.Pp +The open callback is invoked by +.Fn archive_write_open . +It should return +.Cm ARCHIVE_OK +if the underlying file or data source is successfully +opened. +If the open fails, it should call +.Fn archive_set_error +to register an error code and message and return +.Cm ARCHIVE_FATAL . +.Bl -item -offset indent +.It +.Ft typedef ssize_t +.Fo archive_write_callback +.Fa "struct archive *" +.Fa "void *client_data" +.Fa "const void *buffer" +.Fa "size_t length" +.Fc +.El +.Pp +The write callback is invoked whenever the library +needs to write raw bytes to the archive. +For correct blocking, each call to the write callback function +should translate into a single +.Xr write 2 +system call. +This is especially critical when writing archives to tape drives. +On success, the write callback should return the +number of bytes actually written. +On error, the callback should invoke +.Fn archive_set_error +to register an error code and message and return -1. +.Bl -item -offset indent +.It +.Ft typedef int +.Fn archive_close_callback "struct archive *" "void *client_data" +.El +.Pp +The close callback is invoked by archive_close when +the archive processing is complete. +The callback should return +.Cm ARCHIVE_OK +on success. +On failure, the callback should invoke +.Fn archive_set_error +to register an error code and message and +return +.Cm ARCHIVE_FATAL. +.Pp +Note that if the client-provided write callback function +returns a non-zero value, that error will be propagated back to the caller +through whatever API function resulted in that call, which +may include +.Fn archive_write_header , +.Fn archive_write_data , +.Fn archive_write_close , +.Fn archive_write_finish , +or +.Fn archive_write_free . +The client callback can call +.Fn archive_set_error +to provide values that can then be retrieved by +.Fn archive_errno +and +.Fn archive_error_string . +.\" .Sh EXAMPLE +.Sh RETURN VALUES +These functions return +.Cm ARCHIVE_OK +on success, or +.Cm ARCHIVE_FATAL . +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_write 3 , +.Xr archive_write_filter 3 , +.Xr archive_write_format 3 , +.Xr archive_write_new 3 , +.Xr archive_write_set_options 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/archive_write_open_fd.c b/libarchive/archive_write_open_fd.c new file mode 100644 index 0000000..d5c426c --- /dev/null +++ b/libarchive/archive_write_open_fd.c @@ -0,0 +1,144 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_fd.c 201093 2009-12-28 02:28:44Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" + +struct write_fd_data { + int fd; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_fd(struct archive *a, int fd) +{ + struct write_fd_data *mine; + + mine = (struct write_fd_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->fd = fd; +#if defined(__CYGWIN__) || defined(_WIN32) + setmode(mine->fd, O_BINARY); +#endif + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + struct write_fd_data *mine; + struct stat st; + + mine = (struct write_fd_data *)client_data; + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat fd %d", mine->fd); + return (ARCHIVE_FATAL); + } + + /* + * If this is a regular file, don't add it to itself. + */ + if (S_ISREG(st.st_mode)) + archive_write_set_skip_file(a, st.st_dev, st.st_ino); + + /* + * If client hasn't explicitly set the last block handling, + * then set it here. + */ + if (archive_write_get_bytes_in_last_block(a) < 0) { + /* If the output is a block or character device, fifo, + * or stdout, pad the last block, otherwise leave it + * unpadded. */ + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) || + S_ISFIFO(st.st_mode) || (mine->fd == 1)) + /* Last block will be fully padded. */ + archive_write_set_bytes_in_last_block(a, 0); + else + archive_write_set_bytes_in_last_block(a, 1); + } + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_fd_data *mine; + ssize_t bytesWritten; + + mine = (struct write_fd_data *)client_data; + for (;;) { + bytesWritten = write(mine->fd, buff, length); + if (bytesWritten <= 0) { + if (errno == EINTR) + continue; + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); + } +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_fd_data *mine = (struct write_fd_data *)client_data; + + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_file.c b/libarchive/archive_write_open_file.c new file mode 100644 index 0000000..f6b1412 --- /dev/null +++ b/libarchive/archive_write_open_file.c @@ -0,0 +1,109 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_file.c,v 1.19 2007/01/09 08:05:56 kientzle Exp $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" + +struct write_FILE_data { + FILE *f; +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_FILE(struct archive *a, FILE *f) +{ + struct write_FILE_data *mine; + + mine = (struct write_FILE_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + mine->f = f; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +static int +file_open(struct archive *a, void *client_data) +{ + (void)a; /* UNUSED */ + (void)client_data; /* UNUSED */ + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_FILE_data *mine; + size_t bytesWritten; + + mine = client_data; + for (;;) { + bytesWritten = fwrite(buff, 1, length, mine->f); + if (bytesWritten <= 0) { + if (errno == EINTR) + continue; + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); + } +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_FILE_data *mine = client_data; + + (void)a; /* UNUSED */ + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_filename.c b/libarchive/archive_write_open_filename.c new file mode 100644 index 0000000..8689866 --- /dev/null +++ b/libarchive/archive_write_open_filename.c @@ -0,0 +1,248 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_open_filename.c 191165 2009-04-17 00:39:35Z kientzle $"); + +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_FCNTL_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "archive.h" +#include "archive_string.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +struct write_file_data { + int fd; + char mbs_filename; + union { + char m[1]; + wchar_t w[1]; + } filename; /* Must be last! */ +}; + +static int file_close(struct archive *, void *); +static int file_open(struct archive *, void *); +static ssize_t file_write(struct archive *, void *, const void *buff, size_t); + +int +archive_write_open_file(struct archive *a, const char *filename) +{ + return (archive_write_open_filename(a, filename)); +} + +int +archive_write_open_filename(struct archive *a, const char *filename) +{ + struct write_file_data *mine; + + if (filename == NULL || filename[0] == '\0') + return (archive_write_open_fd(a, 1)); + + mine = (struct write_file_data *)malloc(sizeof(*mine) + strlen(filename)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + strcpy(mine->filename.m, filename); + mine->mbs_filename = 1; + mine->fd = -1; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +} + +int +archive_write_open_filename_w(struct archive *a, const wchar_t *filename) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + struct write_file_data *mine; + + if (filename == NULL || filename[0] == L'\0') + return (archive_write_open_fd(a, 1)); + + mine = malloc(sizeof(*mine) + wcslen(filename) * sizeof(wchar_t)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + wcscpy(mine->filename.w, filename); + mine->mbs_filename = 0; + mine->fd = -1; + return (archive_write_open(a, mine, + file_open, file_write, file_close)); +#else + /* + * POSIX system does not support a wchar_t interface for + * open() system call, so we have to translate a wchar_t + * filename to multi-byte one and use it. + */ + struct archive_string fn; + int r; + + if (filename == NULL || filename[0] == L'\0') + return (archive_write_open_fd(a, 1)); + + archive_string_init(&fn); + if (archive_string_append_from_wcs(&fn, filename, + wcslen(filename)) != 0) { + archive_set_error(a, EINVAL, + "Failed to convert a wide-character filename to" + " a multi-byte filename"); + archive_string_free(&fn); + return (ARCHIVE_FATAL); + } + r = archive_write_open_filename(a, fn.s); + archive_string_free(&fn); + return (r); +#endif +} + + +static int +file_open(struct archive *a, void *client_data) +{ + int flags; + struct write_file_data *mine; + struct stat st; + + mine = (struct write_file_data *)client_data; + flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY; + + /* + * Open the file. + */ + if (mine->mbs_filename) { + mine->fd = open(mine->filename.m, flags, 0666); + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%s'", + mine->filename.m); + return (ARCHIVE_FATAL); + } + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat '%s'", + mine->filename.m); + return (ARCHIVE_FATAL); + } + } else { +#if defined(_WIN32) && !defined(__CYGWIN__) + mine->fd = _wopen(mine->filename.w, flags, 0666); + if (mine->fd < 0 && errno == ENOENT) { + wchar_t *fullpath; + fullpath = __la_win_permissive_name_w(mine->filename.w); + if (fullpath != NULL) { + mine->fd = _wopen(fullpath, flags, 0666); + free(fullpath); + } + } + if (mine->fd < 0) { + archive_set_error(a, errno, "Failed to open '%S'", + mine->filename.w); + return (ARCHIVE_FATAL); + } + + if (fstat(mine->fd, &st) != 0) { + archive_set_error(a, errno, "Couldn't stat '%S'", + mine->filename.w); + return (ARCHIVE_FATAL); + } +#else + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Unexpedted operation in archive_write_open_filename"); + return (ARCHIVE_FATAL); +#endif + } + + /* + * Set up default last block handling. + */ + if (archive_write_get_bytes_in_last_block(a) < 0) { + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) || + S_ISFIFO(st.st_mode)) + /* Pad last block when writing to device or FIFO. */ + archive_write_set_bytes_in_last_block(a, 0); + else + /* Don't pad last block otherwise. */ + archive_write_set_bytes_in_last_block(a, 1); + } + + /* + * If the output file is a regular file, don't add it to + * itself. If it's a device file, it's okay to add the device + * entry to the output archive. + */ + if (S_ISREG(st.st_mode)) + archive_write_set_skip_file(a, st.st_dev, st.st_ino); + + return (ARCHIVE_OK); +} + +static ssize_t +file_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_file_data *mine; + ssize_t bytesWritten; + + mine = (struct write_file_data *)client_data; + for (;;) { + bytesWritten = write(mine->fd, buff, length); + if (bytesWritten <= 0) { + if (errno == EINTR) + continue; + archive_set_error(a, errno, "Write error"); + return (-1); + } + return (bytesWritten); + } +} + +static int +file_close(struct archive *a, void *client_data) +{ + struct write_file_data *mine = (struct write_file_data *)client_data; + + (void)a; /* UNUSED */ + close(mine->fd); + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_open_memory.c b/libarchive/archive_write_open_memory.c new file mode 100644 index 0000000..4f8d679 --- /dev/null +++ b/libarchive/archive_write_open_memory.c @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: src/lib/libarchive/archive_write_open_memory.c,v 1.3 2007/01/09 08:05:56 kientzle Exp $"); + +#include +#include +#include + +#include "archive.h" + +struct write_memory_data { + size_t used; + size_t size; + size_t * client_size; + unsigned char * buff; +}; + +static int memory_write_close(struct archive *, void *); +static int memory_write_open(struct archive *, void *); +static ssize_t memory_write(struct archive *, void *, const void *buff, size_t); + +/* + * Client provides a pointer to a block of memory to receive + * the data. The 'size' param both tells us the size of the + * client buffer and lets us tell the client the final size. + */ +int +archive_write_open_memory(struct archive *a, void *buff, size_t buffSize, size_t *used) +{ + struct write_memory_data *mine; + + mine = (struct write_memory_data *)malloc(sizeof(*mine)); + if (mine == NULL) { + archive_set_error(a, ENOMEM, "No memory"); + return (ARCHIVE_FATAL); + } + memset(mine, 0, sizeof(*mine)); + mine->buff = buff; + mine->size = buffSize; + mine->client_size = used; + return (archive_write_open(a, mine, + memory_write_open, memory_write, memory_write_close)); +} + +static int +memory_write_open(struct archive *a, void *client_data) +{ + struct write_memory_data *mine; + mine = client_data; + mine->used = 0; + if (mine->client_size != NULL) + *mine->client_size = mine->used; + /* Disable padding if it hasn't been set explicitly. */ + if (-1 == archive_write_get_bytes_in_last_block(a)) + archive_write_set_bytes_in_last_block(a, 1); + return (ARCHIVE_OK); +} + +/* + * Copy the data into the client buffer. + * Note that we update mine->client_size on every write. + * In particular, this means the client can follow exactly + * how much has been written into their buffer at any time. + */ +static ssize_t +memory_write(struct archive *a, void *client_data, const void *buff, size_t length) +{ + struct write_memory_data *mine; + mine = client_data; + + if (mine->used + length > mine->size) { + archive_set_error(a, ENOMEM, "Buffer exhausted"); + return (ARCHIVE_FATAL); + } + memcpy(mine->buff + mine->used, buff, length); + mine->used += length; + if (mine->client_size != NULL) + *mine->client_size = mine->used; + return (length); +} + +static int +memory_write_close(struct archive *a, void *client_data) +{ + struct write_memory_data *mine; + (void)a; /* UNUSED */ + mine = client_data; + free(mine); + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_private.h b/libarchive/archive_write_private.h new file mode 100644 index 0000000..91284cf --- /dev/null +++ b/libarchive/archive_write_private.h @@ -0,0 +1,136 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/archive_write_private.h 201155 2009-12-29 05:20:12Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef ARCHIVE_WRITE_PRIVATE_H_INCLUDED +#define ARCHIVE_WRITE_PRIVATE_H_INCLUDED + +#include "archive.h" +#include "archive_string.h" +#include "archive_private.h" + +struct archive_write; + +struct archive_write_filter { + int64_t bytes_written; + struct archive *archive; /* Associated archive. */ + struct archive_write_filter *next_filter; /* Who I write to. */ + int (*options)(struct archive_write_filter *, + const char *key, const char *value); + int (*open)(struct archive_write_filter *); + int (*write)(struct archive_write_filter *, const void *, size_t); + int (*close)(struct archive_write_filter *); + int (*free)(struct archive_write_filter *); + void *data; + const char *name; + int code; + int bytes_per_block; + int bytes_in_last_block; +}; + +#if ARCHIVE_VERSION < 4000000 +void __archive_write_filters_free(struct archive *); +#endif + +struct archive_write_filter *__archive_write_allocate_filter(struct archive *); + +int __archive_write_output(struct archive_write *, const void *, size_t); +int __archive_write_nulls(struct archive_write *, size_t); +int __archive_write_filter(struct archive_write_filter *, const void *, size_t); +int __archive_write_open_filter(struct archive_write_filter *); +int __archive_write_close_filter(struct archive_write_filter *); + +struct archive_write { + struct archive archive; + + /* Dev/ino of the archive being written. */ + int skip_file_set; + dev_t skip_file_dev; + int64_t skip_file_ino; + + /* Utility: Pointer to a block of nulls. */ + const unsigned char *nulls; + size_t null_length; + + /* Callbacks to open/read/write/close archive stream. */ + archive_open_callback *client_opener; + archive_write_callback *client_writer; + archive_close_callback *client_closer; + void *client_data; + + /* + * Blocking information. Note that bytes_in_last_block is + * misleadingly named; I should find a better name. These + * control the final output from all compressors, including + * compression_none. + */ + int bytes_per_block; + int bytes_in_last_block; + + /* + * First and last write filters in the pipeline. + */ + struct archive_write_filter *filter_first; + struct archive_write_filter *filter_last; + + /* + * Pointers to format-specific functions for writing. They're + * initialized by archive_write_set_format_XXX() calls. + */ + void *format_data; + const char *format_name; + int (*format_init)(struct archive_write *); + int (*format_options)(struct archive_write *, + const char *key, const char *value); + int (*format_finish_entry)(struct archive_write *); + int (*format_write_header)(struct archive_write *, + struct archive_entry *); + ssize_t (*format_write_data)(struct archive_write *, + const void *buff, size_t); + int (*format_close)(struct archive_write *); + int (*format_free)(struct archive_write *); +}; + +/* + * Utility function to format a USTAR header into a buffer. If + * "strict" is set, this tries to create the absolutely most portable + * version of a ustar header. If "strict" is set to 0, then it will + * relax certain requirements. + * + * Generally, format-specific declarations don't belong in this + * header; this is a rare example of a function that is shared by + * two very similar formats (ustar and pax). + */ +int +__archive_write_format_header_ustar(struct archive_write *, char buff[512], + struct archive_entry *, int tartype, int strict, + struct archive_string_conv *); + +#endif diff --git a/libarchive/archive_write_set_format.c b/libarchive/archive_write_set_format.c new file mode 100644 index 0000000..641d56f --- /dev/null +++ b/libarchive/archive_write_set_format.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format.c 201168 2009-12-29 06:15:32Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" + +/* A table that maps format codes to functions. */ +static +struct { int code; int (*setter)(struct archive *); } codes[] = +{ + { ARCHIVE_FORMAT_7ZIP, archive_write_set_format_7zip }, + { ARCHIVE_FORMAT_CPIO, archive_write_set_format_cpio }, + { ARCHIVE_FORMAT_CPIO_POSIX, archive_write_set_format_cpio }, + { ARCHIVE_FORMAT_CPIO_SVR4_NOCRC, archive_write_set_format_cpio_newc }, + { ARCHIVE_FORMAT_ISO9660, archive_write_set_format_iso9660 }, + { ARCHIVE_FORMAT_MTREE, archive_write_set_format_mtree }, + { ARCHIVE_FORMAT_SHAR, archive_write_set_format_shar }, + { ARCHIVE_FORMAT_SHAR_BASE, archive_write_set_format_shar }, + { ARCHIVE_FORMAT_SHAR_DUMP, archive_write_set_format_shar_dump }, + { ARCHIVE_FORMAT_TAR, archive_write_set_format_pax_restricted }, + { ARCHIVE_FORMAT_TAR_GNUTAR, archive_write_set_format_gnutar }, + { ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, archive_write_set_format_pax }, + { ARCHIVE_FORMAT_TAR_PAX_RESTRICTED, + archive_write_set_format_pax_restricted }, + { ARCHIVE_FORMAT_TAR_USTAR, archive_write_set_format_ustar }, + { ARCHIVE_FORMAT_XAR, archive_write_set_format_xar }, + { ARCHIVE_FORMAT_ZIP, archive_write_set_format_zip }, + { 0, NULL } +}; + +int +archive_write_set_format(struct archive *a, int code) +{ + int i; + + for (i = 0; codes[i].code != 0; i++) { + if (code == codes[i].code) + return ((codes[i].setter)(a)); + } + + archive_set_error(a, EINVAL, "No such format"); + return (ARCHIVE_FATAL); +} diff --git a/libarchive/archive_write_set_format_7zip.c b/libarchive/archive_write_set_format_7zip.c new file mode 100644 index 0000000..f405d1f --- /dev/null +++ b/libarchive/archive_write_set_format_7zip.c @@ -0,0 +1,2275 @@ +/*- + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_BZLIB_H +#include +#endif +#if HAVE_LZMA_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_ppmd7_private.h" +#include "archive_private.h" +#include "archive_rb.h" +#include "archive_string.h" +#include "archive_write_private.h" + +/* + * Codec ID + */ +#define _7Z_COPY 0 +#define _7Z_LZMA1 0x030101 +#define _7Z_LZMA2 0x21 +#define _7Z_DEFLATE 0x040108 +#define _7Z_BZIP2 0x040202 +#define _7Z_PPMD 0x030401 + +/* + * 7-Zip header property IDs. + */ +#define kEnd 0x00 +#define kHeader 0x01 +#define kArchiveProperties 0x02 +#define kAdditionalStreamsInfo 0x03 +#define kMainStreamsInfo 0x04 +#define kFilesInfo 0x05 +#define kPackInfo 0x06 +#define kUnPackInfo 0x07 +#define kSubStreamsInfo 0x08 +#define kSize 0x09 +#define kCRC 0x0A +#define kFolder 0x0B +#define kCodersUnPackSize 0x0C +#define kNumUnPackStream 0x0D +#define kEmptyStream 0x0E +#define kEmptyFile 0x0F +#define kAnti 0x10 +#define kName 0x11 +#define kCTime 0x12 +#define kATime 0x13 +#define kMTime 0x14 +#define kAttributes 0x15 +#define kEncodedHeader 0x17 + +enum la_zaction { + ARCHIVE_Z_FINISH, + ARCHIVE_Z_RUN +}; + +/* + * Universal zstream. + */ +struct la_zstream { + const uint8_t *next_in; + size_t avail_in; + uint64_t total_in; + + uint8_t *next_out; + size_t avail_out; + uint64_t total_out; + + uint32_t prop_size; + uint8_t *props; + + int valid; + void *real_stream; + int (*code) (struct archive *a, + struct la_zstream *lastrm, + enum la_zaction action); + int (*end)(struct archive *a, + struct la_zstream *lastrm); +}; + +#define PPMD7_DEFAULT_ORDER 6 +#define PPMD7_DEFAULT_MEM_SIZE (1 << 24) + +struct ppmd_stream { + int stat; + CPpmd7 ppmd7_context; + CPpmd7z_RangeEnc range_enc; + IByteOut byteout; + uint8_t *buff; + uint8_t *buff_ptr; + uint8_t *buff_end; + size_t buff_bytes; +}; + +struct coder { + unsigned codec; + size_t prop_size; + uint8_t *props; +}; + +struct file { + struct archive_rb_node rbnode; + + struct file *next; + unsigned name_len; + uint8_t *utf16name;/* UTF16-LE name. */ + uint64_t size; + unsigned flg; +#define MTIME_IS_SET (1<<0) +#define ATIME_IS_SET (1<<1) +#define CTIME_IS_SET (1<<2) +#define CRC32_IS_SET (1<<3) +#define HAS_STREAM (1<<4) + + struct { + time_t time; + long time_ns; + } times[3]; +#define MTIME 0 +#define ATIME 1 +#define CTIME 2 + + mode_t mode; + uint32_t crc32; + + int dir:1; +}; + +struct _7zip { + int temp_fd; + uint64_t temp_offset; + + struct file *cur_file; + size_t total_number_entry; + size_t total_number_nonempty_entry; + size_t total_number_empty_entry; + size_t total_number_dir_entry; + size_t total_bytes_entry_name; + size_t total_number_time_defined[3]; + uint64_t total_bytes_compressed; + uint64_t total_bytes_uncompressed; + uint64_t entry_bytes_remaining; + uint32_t entry_crc32; + uint32_t precode_crc32; + uint32_t encoded_crc32; + int crc32flg; +#define PRECODE_CRC32 1 +#define ENCODED_CRC32 2 + + unsigned opt_compression; + int opt_compression_level; + + struct la_zstream stream; + struct coder coder; + + struct archive_string_conv *sconv; + + /* + * Compressed data buffer. + */ + unsigned char wbuff[1024 * 64]; + size_t wbuff_remaining; + + /* + * The list of the file entries which has its contents is used to + * manage struct file objects. + * We use 'next' a menber of struct file to chain. + */ + struct { + struct file *first; + struct file **last; + } file_list, empty_list; + struct archive_rb_tree rbtree;/* for empty files */ +}; + +static int _7z_options(struct archive_write *, + const char *, const char *); +static int _7z_write_header(struct archive_write *, + struct archive_entry *); +static ssize_t _7z_write_data(struct archive_write *, + const void *, size_t); +static int _7z_finish_entry(struct archive_write *); +static int _7z_close(struct archive_write *); +static int _7z_free(struct archive_write *); +static int file_cmp_node(const struct archive_rb_node *, + const struct archive_rb_node *); +static int file_cmp_key(const struct archive_rb_node *, const void *); +static int file_new(struct archive_write *a, struct archive_entry *, + struct file **); +static void file_free(struct file *); +static void file_register(struct _7zip *, struct file *); +static void file_register_empty(struct _7zip *, struct file *); +static void file_init_register(struct _7zip *); +static void file_init_register_empty(struct _7zip *); +static void file_free_register(struct _7zip *); +static ssize_t compress_out(struct archive_write *, const void *, size_t , + enum la_zaction); +static int compression_init_encoder_copy(struct archive *, + struct la_zstream *); +static int compression_code_copy(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_copy(struct archive *, struct la_zstream *); +static int compression_init_encoder_deflate(struct archive *, + struct la_zstream *, int, int); +#ifdef HAVE_ZLIB_H +static int compression_code_deflate(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_deflate(struct archive *, struct la_zstream *); +#endif +static int compression_init_encoder_bzip2(struct archive *, + struct la_zstream *, int); +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) +static int compression_code_bzip2(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_bzip2(struct archive *, struct la_zstream *); +#endif +static int compression_init_encoder_lzma1(struct archive *, + struct la_zstream *, int); +static int compression_init_encoder_lzma2(struct archive *, + struct la_zstream *, int); +#if defined(HAVE_LZMA_H) +static int compression_code_lzma(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_lzma(struct archive *, struct la_zstream *); +#endif +static int compression_init_encoder_ppmd(struct archive *, + struct la_zstream *, unsigned, uint32_t); +static int compression_code_ppmd(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_ppmd(struct archive *, struct la_zstream *); +static int _7z_compression_init_encoder(struct archive_write *, unsigned, + int); +static int compression_code(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end(struct archive *, + struct la_zstream *); +static int enc_uint64(struct archive_write *, uint64_t); +static int make_header(struct archive_write *, uint64_t, uint64_t, + uint64_t, int, struct coder *); +static int make_streamsInfo(struct archive_write *, uint64_t, uint64_t, + uint64_t, int, struct coder *, int, uint32_t); + +int +archive_write_set_format_7zip(struct archive *_a) +{ + static const struct archive_rb_tree_ops rb_ops = { + file_cmp_node, file_cmp_key + }; + struct archive_write *a = (struct archive_write *)_a; + struct _7zip *zip; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_7zip"); + + /* If another format was already registered, unregister it. */ + if (a->format_free != NULL) + (a->format_free)(a); + + zip = calloc(1, sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate 7-Zip data"); + return (ARCHIVE_FATAL); + } + zip->temp_fd = -1; + __archive_rb_tree_init(&(zip->rbtree), &rb_ops); + file_init_register(zip); + file_init_register_empty(zip); + + /* Set default compression type and its level. */ +#if HAVE_LZMA_H + zip->opt_compression = _7Z_LZMA1; +#elif defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + zip->opt_compression = _7Z_BZIP2; +#elif defined(HAVE_ZLIB_H) + zip->opt_compression = _7Z_DEFLATE; +#else + zip->opt_compression = _7Z_COPY; +#endif + zip->opt_compression_level = 6; + + a->format_data = zip; + + a->format_name = "7zip"; + a->format_options = _7z_options; + a->format_write_header = _7z_write_header; + a->format_write_data = _7z_write_data; + a->format_finish_entry = _7z_finish_entry; + a->format_close = _7z_close; + a->format_free = _7z_free; + a->archive.archive_format = ARCHIVE_FORMAT_7ZIP; + a->archive.archive_format_name = "7zip"; + + return (ARCHIVE_OK); +} + +static int +_7z_options(struct archive_write *a, const char *key, const char *value) +{ + struct _7zip *zip; + + zip = (struct _7zip *)a->format_data; + + if (strcmp(key, "compression") == 0) { + const char *name = NULL; + + if (value == NULL || strcmp(value, "copy") == 0 || + strcmp(value, "COPY") == 0 || + strcmp(value, "store") == 0 || + strcmp(value, "STORE") == 0) + zip->opt_compression = _7Z_COPY; + else if (strcmp(value, "deflate") == 0 || + strcmp(value, "DEFLATE") == 0) +#if HAVE_ZLIB_H + zip->opt_compression = _7Z_DEFLATE; +#else + name = "deflate"; +#endif + else if (strcmp(value, "bzip2") == 0 || + strcmp(value, "BZIP2") == 0) +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + zip->opt_compression = _7Z_BZIP2; +#else + name = "bzip2"; +#endif + else if (strcmp(value, "lzma1") == 0 || + strcmp(value, "LZMA1") == 0) +#if HAVE_LZMA_H + zip->opt_compression = _7Z_LZMA1; +#else + name = "lzma1"; +#endif + else if (strcmp(value, "lzma2") == 0 || + strcmp(value, "LZMA2") == 0) +#if HAVE_LZMA_H + zip->opt_compression = _7Z_LZMA2; +#else + name = "lzma2"; +#endif + else if (strcmp(value, "ppmd") == 0 || + strcmp(value, "PPMD") == 0 || + strcmp(value, "PPMd") == 0) + zip->opt_compression = _7Z_PPMD; + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unkonwn compression name: `%s'", + value); + return (ARCHIVE_FAILED); + } + if (name != NULL) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "`%s' compression not supported " + "on this platform", + name); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); + } + if (strcmp(key, "compression-level") == 0) { + if (value == NULL || + !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Illeagal value `%s'", + value); + return (ARCHIVE_FAILED); + } + zip->opt_compression_level = value[0] - '0'; + return (ARCHIVE_OK); + } + + return (ARCHIVE_FAILED); +} + +static int +_7z_write_header(struct archive_write *a, struct archive_entry *entry) +{ + struct _7zip *zip; + struct file *file; + int r; + + zip = (struct _7zip *)a->format_data; + zip->cur_file = NULL; + zip->entry_bytes_remaining = 0; + + if (zip->sconv == NULL) { + zip->sconv = archive_string_conversion_to_charset( + &a->archive, "UTF-16LE", 1); + if (zip->sconv == NULL) + return (ARCHIVE_FATAL); + } + + r = file_new(a, entry, &file); + if (r < ARCHIVE_WARN) { + file_free(file); + return (r); + } + + if (file->flg & MTIME_IS_SET) + zip->total_number_time_defined[MTIME]++; + if (file->flg & CTIME_IS_SET) + zip->total_number_time_defined[CTIME]++; + if (file->flg & ATIME_IS_SET) + zip->total_number_time_defined[ATIME]++; + + if (file->size == 0 && file->dir) { + if (!__archive_rb_tree_insert_node(&(zip->rbtree), + (struct archive_rb_node *)file)) + file_free(file); + } + zip->total_number_entry++; + zip->total_bytes_entry_name += file->name_len + 2; + if (file->size == 0) { + zip->total_number_empty_entry++; + if (file->dir) + zip->total_number_dir_entry++; + else + file_register_empty(zip, file); + return (r); + } + + /* + * Init compression. + */ + if ((zip->total_number_entry - zip->total_number_empty_entry) == 1) { + r = _7z_compression_init_encoder(a, zip->opt_compression, + zip->opt_compression_level); + if (r < 0) { + file_free(file); + return (ARCHIVE_FATAL); + } + } + + /* Register a non-empty file. */ + file_register(zip, file); + + /* + * Set the current file to cur_file to read its contents. + */ + zip->cur_file = file; + + + /* Save a offset of current file in temporary file. */ + zip->entry_bytes_remaining = file->size; + zip->entry_crc32 = 0; + + if (archive_entry_filetype(entry) == AE_IFLNK) { + ssize_t bytes; + const void *p = (const void *)archive_entry_symlink(entry); + bytes = compress_out(a, p, file->size, ARCHIVE_Z_RUN); + if (bytes < 0) + return ((int)bytes); + zip->entry_crc32 = crc32(zip->entry_crc32, p, bytes); + zip->entry_bytes_remaining -= bytes; + } + + return (r); +} + +static int +write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + struct _7zip *zip; + unsigned char *p; + ssize_t ws; + + zip = (struct _7zip *)a->format_data; + + /* + * Open a temporary file. + */ + if (zip->temp_fd == -1) { + zip->temp_offset = 0; + zip->temp_fd = __archive_mktemp(NULL); + if (zip->temp_fd < 0) { + archive_set_error(&a->archive, errno, + "Couldn't create temporary file"); + return (ARCHIVE_FATAL); + } + } + + p = (unsigned char *)buff; + while (s) { + ws = write(zip->temp_fd, p, s); + if (ws < 0) { + archive_set_error(&(a->archive), errno, + "fwrite function failed"); + return (ARCHIVE_FATAL); + } + s -= ws; + p += ws; + zip->temp_offset += ws; + } + return (ARCHIVE_OK); +} + +static ssize_t +compress_out(struct archive_write *a, const void *buff, size_t s, + enum la_zaction run) +{ + struct _7zip *zip = (struct _7zip *)a->format_data; + int r; + + if (run == ARCHIVE_Z_FINISH && zip->stream.total_in == 0 && s == 0) + return (0); + + if ((zip->crc32flg & PRECODE_CRC32) && s) + zip->precode_crc32 = crc32(zip->precode_crc32, buff, s); + zip->stream.next_in = (const unsigned char *)buff; + zip->stream.avail_in = s; + do { + /* Compress file data. */ + r = compression_code(&(a->archive), &(zip->stream), run); + if (r != ARCHIVE_OK && r != ARCHIVE_EOF) + return (ARCHIVE_FATAL); + if (zip->stream.avail_out == 0) { + if (write_to_temp(a, zip->wbuff, sizeof(zip->wbuff)) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->stream.next_out = zip->wbuff; + zip->stream.avail_out = sizeof(zip->wbuff); + if (zip->crc32flg & ENCODED_CRC32) + zip->encoded_crc32 = crc32(zip->encoded_crc32, + zip->wbuff, sizeof(zip->wbuff)); + } + } while (zip->stream.avail_in); + if (run == ARCHIVE_Z_FINISH) { + uint64_t bytes = sizeof(zip->wbuff) - zip->stream.avail_out; + if (write_to_temp(a, zip->wbuff, bytes) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if ((zip->crc32flg & ENCODED_CRC32) && bytes) + zip->encoded_crc32 = crc32(zip->encoded_crc32, + zip->wbuff, bytes); + } + + return (s); +} + +static ssize_t +_7z_write_data(struct archive_write *a, const void *buff, size_t s) +{ + struct _7zip *zip; + ssize_t bytes; + + zip = (struct _7zip *)a->format_data; + + if (s > zip->entry_bytes_remaining) + s = zip->entry_bytes_remaining; + if (s == 0 || zip->cur_file == NULL) + return (0); + bytes = compress_out(a, buff, s, ARCHIVE_Z_RUN); + if (bytes < 0) + return (bytes); + zip->entry_crc32 = crc32(zip->entry_crc32, buff, bytes); + zip->entry_bytes_remaining -= bytes; + return (bytes); +} + +static int +_7z_finish_entry(struct archive_write *a) +{ + struct _7zip *zip; + size_t s; + ssize_t r; + + zip = (struct _7zip *)a->format_data; + if (zip->cur_file == NULL) + return (ARCHIVE_OK); + + while (zip->entry_bytes_remaining > 0) { + s = zip->entry_bytes_remaining; + if (s > a->null_length) + s = a->null_length; + r = _7z_write_data(a, a->nulls, s); + if (r < 0) + return (r); + } + zip->total_bytes_compressed += zip->stream.total_in; + zip->total_bytes_uncompressed += zip->stream.total_out; + zip->cur_file->crc32 = zip->entry_crc32; + zip->cur_file = NULL; + + return (ARCHIVE_OK); +} + +static int +flush_wbuff(struct archive_write *a) +{ + struct _7zip *zip; + int r; + size_t s; + + zip = (struct _7zip *)a->format_data; + s = sizeof(zip->wbuff) - zip->wbuff_remaining; + r = __archive_write_output(a, zip->wbuff, s); + if (r != ARCHIVE_OK) + return (r); + zip->wbuff_remaining = sizeof(zip->wbuff); + return (r); +} + +static int +copy_out(struct archive_write *a, uint64_t offset, uint64_t length) +{ + struct _7zip *zip; + int r; + + zip = (struct _7zip *)a->format_data; + if (zip->temp_offset > 0 && + lseek(zip->temp_fd, offset, SEEK_SET) < 0) { + archive_set_error(&(a->archive), errno, "lseek failed"); + return (ARCHIVE_FATAL); + } + while (length) { + size_t rsize; + ssize_t rs; + unsigned char *wb; + + if (length > zip->wbuff_remaining) + rsize = zip->wbuff_remaining; + else + rsize = (size_t)length; + wb = zip->wbuff + (sizeof(zip->wbuff) - zip->wbuff_remaining); + rs = read(zip->temp_fd, wb, rsize); + if (rs < 0) { + archive_set_error(&(a->archive), errno, + "Can't read temporary file(%jd)", + (intmax_t)rs); + return (ARCHIVE_FATAL); + } + if (rs == 0) { + archive_set_error(&(a->archive), 0, + "Truncated 7-Zip archive"); + return (ARCHIVE_FATAL); + } + zip->wbuff_remaining -= rs; + length -= rs; + if (zip->wbuff_remaining == 0) { + r = flush_wbuff(a); + if (r != ARCHIVE_OK) + return (r); + } + } + return (ARCHIVE_OK); +} + +static int +_7z_close(struct archive_write *a) +{ + struct _7zip *zip; + unsigned char *wb; + uint64_t header_offset, header_size, header_unpacksize; + uint64_t length; + uint32_t header_crc32; + int r; + + zip = (struct _7zip *)a->format_data; + + if (zip->total_number_entry > 0) { + struct archive_rb_node *n; + uint64_t data_offset, data_size, data_unpacksize; + unsigned header_compression; + + r = (int)compress_out(a, NULL, 0, ARCHIVE_Z_FINISH); + if (r < 0) + return (r); + data_offset = 0; + data_size = zip->stream.total_out; + data_unpacksize = zip->stream.total_in; + zip->coder.codec = zip->opt_compression; + zip->coder.prop_size = zip->stream.prop_size; + zip->coder.props = zip->stream.props; + zip->stream.prop_size = 0; + zip->stream.props = NULL; + zip->total_number_nonempty_entry = + zip->total_number_entry - zip->total_number_empty_entry; + + *zip->file_list.last = zip->empty_list.first; + zip->file_list.last = zip->empty_list.last; + ARCHIVE_RB_TREE_FOREACH(n, &(zip->rbtree)) { + file_register(zip, (struct file *)n); + } + + /* + * NOTE: 7z command supports just LZMA1, LZMA2 and COPY for + * the header. + */ +#if HAVE_LZMA_H + header_compression = _7Z_LZMA1; + /* If the stored file is only one, do not encode the header. */ + if (zip->total_number_entry == 1) + header_compression = _7Z_COPY; +#else + header_compression = _7Z_COPY; +#endif + r = _7z_compression_init_encoder(a, header_compression, 6); + if (r < 0) + return (r); + zip->crc32flg = PRECODE_CRC32; + zip->precode_crc32 = 0; + r = make_header(a, data_offset, data_size, data_unpacksize, + 1, &(zip->coder)); + if (r < 0) + return (r); + r = (int)compress_out(a, NULL, 0, ARCHIVE_Z_FINISH); + if (r < 0) + return (r); + header_offset = data_offset + data_size; + header_size = zip->stream.total_out; + header_crc32 = zip->precode_crc32; + header_unpacksize = zip->stream.total_in; + + if (header_compression != _7Z_COPY) { + /* + * Encode the header in order to reduce the size + * of the archive. + */ + free(zip->coder.props); + zip->coder.codec = header_compression; + zip->coder.prop_size = zip->stream.prop_size; + zip->coder.props = zip->stream.props; + zip->stream.prop_size = 0; + zip->stream.props = NULL; + + r = _7z_compression_init_encoder(a, _7Z_COPY, 0); + if (r < 0) + return (r); + zip->crc32flg = ENCODED_CRC32; + zip->encoded_crc32 = 0; + + /* + * Make EncodedHeader. + */ + r = enc_uint64(a, kEncodedHeader); + if (r < 0) + return (r); + r = make_streamsInfo(a, header_offset, header_size, + header_unpacksize, 1, &(zip->coder), 0, + header_crc32); + if (r < 0) + return (r); + r = (int)compress_out(a, NULL, 0, ARCHIVE_Z_FINISH); + if (r < 0) + return (r); + header_offset = header_offset + header_size; + header_size = zip->stream.total_out; + header_crc32 = zip->encoded_crc32; + } + zip->crc32flg = 0; + } else { + header_offset = header_size = 0; + header_crc32 = 0; + } + + length = zip->temp_offset; + + /* + * Make the zip header on wbuff(write buffer). + */ + wb = zip->wbuff; + zip->wbuff_remaining = sizeof(zip->wbuff); + memcpy(&wb[0], "7z\xBC\xAF\x27\x1C", 6); + wb[6] = 0;/* Major version. */ + wb[7] = 3;/* Minor version. */ + archive_le64enc(&wb[12], header_offset);/* Next Header Offset */ + archive_le64enc(&wb[20], header_size);/* Next Header Size */ + archive_le32enc(&wb[28], header_crc32);/* Next Header CRC */ + archive_le32enc(&wb[8], crc32(0, &wb[12], 20));/* Start Header CRC */ + zip->wbuff_remaining -= 32; + + /* + * Read all file contents and an encoded header from the temporary + * file and write out it. + */ + r = copy_out(a, 0, length); + if (r != ARCHIVE_OK) + return (r); + r = flush_wbuff(a); + return (r); +} + +static int +enc_uint64(struct archive_write *a, uint64_t val) +{ + unsigned mask = 0x80; + uint8_t numdata[9]; + int i; + + numdata[0] = 0; + for (i = 1; i < sizeof(numdata); i++) { + if (val < mask) { + numdata[0] |= (uint8_t)val; + break; + } + numdata[i] = (uint8_t)val; + val >>= 8; + numdata[0] |= mask; + mask >>= 1; + } + return (compress_out(a, numdata, i, ARCHIVE_Z_RUN)); +} + +static int +make_substreamsInfo(struct archive_write *a, struct coder *coders) +{ + struct _7zip *zip = (struct _7zip *)a->format_data; + struct file *file; + int r; + + /* + * Make SubStreamsInfo. + */ + r = enc_uint64(a, kSubStreamsInfo); + if (r < 0) + return (r); + + if (zip->total_number_nonempty_entry > 1 && coders->codec != _7Z_COPY) { + /* + * Make NumUnPackStream. + */ + r = enc_uint64(a, kNumUnPackStream); + if (r < 0) + return (r); + + /* Write numUnpackStreams */ + r = enc_uint64(a, zip->total_number_nonempty_entry); + if (r < 0) + return (r); + + /* + * Make kSize. + */ + r = enc_uint64(a, kSize); + if (r < 0) + return (r); + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->next == NULL || + file->next->size == 0) + break; + r = enc_uint64(a, file->size); + if (r < 0) + return (r); + } + } + + /* + * Make CRC. + */ + r = enc_uint64(a, kCRC); + if (r < 0) + return (r); + + + /* All are defined */ + r = enc_uint64(a, 1); + if (r < 0) + return (r); + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + uint8_t crc[4]; + if (file->size == 0) + break; + archive_le32enc(crc, file->crc32); + r = compress_out(a, crc, 4, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + + /* Write End. */ + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + return (ARCHIVE_OK); +} + +static int +make_streamsInfo(struct archive_write *a, uint64_t offset, uint64_t pack_size, + uint64_t unpack_size, int num_coder, struct coder *coders, int substrm, + uint32_t header_crc) +{ + struct _7zip *zip = (struct _7zip *)a->format_data; + uint8_t codec_buff[8]; + int numFolders, fi; + int codec_size; + int i, r; + + if (coders->codec == _7Z_COPY) + numFolders = zip->total_number_nonempty_entry; + else + numFolders = 1; + + /* + * Make PackInfo. + */ + r = enc_uint64(a, kPackInfo); + if (r < 0) + return (r); + + /* Write PackPos. */ + r = enc_uint64(a, offset); + if (r < 0) + return (r); + + /* Write NumPackStreams. */ + r = enc_uint64(a, numFolders); + if (r < 0) + return (r); + + /* Make Size. */ + r = enc_uint64(a, kSize); + if (r < 0) + return (r); + + if (numFolders > 1) { + struct file *file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->size == 0) + break; + r = enc_uint64(a, file->size); + if (r < 0) + return (r); + } + } else { + /* Write size. */ + r = enc_uint64(a, pack_size); + if (r < 0) + return (r); + } + + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + + /* + * Make UnPackInfo. + */ + r = enc_uint64(a, kUnPackInfo); + if (r < 0) + return (r); + + /* + * Make Folder. + */ + r = enc_uint64(a, kFolder); + if (r < 0) + return (r); + + /* Write NumFolders. */ + r = enc_uint64(a, numFolders); + if (r < 0) + return (r); + + /* Write External. */ + r = enc_uint64(a, 0); + if (r < 0) + return (r); + + for (fi = 0; fi < numFolders; fi++) { + /* Write NumCoders. */ + r = enc_uint64(a, num_coder); + if (r < 0) + return (r); + + for (i = 0; i < num_coder; i++) { + unsigned codec_id = coders[i].codec; + + /* Write Codec flag. */ + archive_be64enc(codec_buff, codec_id); + for (codec_size = 8; codec_size > 0; codec_size--) { + if (codec_buff[8 - codec_size]) + break; + } + if (codec_size == 0) + codec_size = 1; + if (coders[i].prop_size) + r = enc_uint64(a, codec_size | 0x20); + else + r = enc_uint64(a, codec_size); + if (r < 0) + return (r); + + /* Write Codec ID. */ + codec_size &= 0x0f; + r = compress_out(a, &codec_buff[8-codec_size], + codec_size, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + + if (coders[i].prop_size) { + /* Write Codec property size. */ + r = enc_uint64(a, coders[i].prop_size); + if (r < 0) + return (r); + + /* Write Codec properties. */ + r = compress_out(a, coders[i].props, + coders[i].prop_size, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + } + } + + /* + * Make CodersUnPackSize. + */ + r = enc_uint64(a, kCodersUnPackSize); + if (r < 0) + return (r); + + if (numFolders > 1) { + struct file *file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->size == 0) + break; + r = enc_uint64(a, file->size); + if (r < 0) + return (r); + } + + } else { + /* Write UnPackSize. */ + r = enc_uint64(a, unpack_size); + if (r < 0) + return (r); + } + + if (!substrm) { + uint8_t crc[4]; + /* + * Make CRC. + */ + r = enc_uint64(a, kCRC); + if (r < 0) + return (r); + + /* All are defined */ + r = enc_uint64(a, 1); + if (r < 0) + return (r); + archive_le32enc(crc, header_crc); + r = compress_out(a, crc, 4, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + + /* Write End. */ + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + + if (substrm) { + /* + * Make SubStreamsInfo. + */ + r = make_substreamsInfo(a, coders); + if (r < 0) + return (r); + } + + + /* Write End. */ + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + + return (ARCHIVE_OK); +} + + +#define EPOC_TIME ARCHIVE_LITERAL_ULL(116444736000000000) +static uint64_t +utcToFiletime(time_t time, long ns) +{ + uint64_t fileTime; + + fileTime = time; + fileTime *= 10000000; + fileTime += ns / 100; + fileTime += EPOC_TIME; + return (fileTime); +} + +static int +make_time(struct archive_write *a, uint8_t type, unsigned flg, int ti) +{ + uint8_t filetime[8]; + struct _7zip *zip = (struct _7zip *)a->format_data; + struct file *file; + int r; + uint8_t mask, byte; + + /* + * Make Time Bools. + */ + if (zip->total_number_time_defined[ti] == zip->total_number_entry) { + /* Write Time Type. */ + r = enc_uint64(a, type); + if (r < 0) + return (r); + /* Write EmptyStream Size. */ + r = enc_uint64(a, 2 + zip->total_number_entry * 8); + if (r < 0) + return (r); + /* All are defined. */ + r = enc_uint64(a, 1); + if (r < 0) + return (r); + } else { + if (zip->total_number_time_defined[ti] == 0) + return (ARCHIVE_OK); + + /* Write Time Type. */ + r = enc_uint64(a, type); + if (r < 0) + return (r); + /* Write EmptyStream Size. */ + r = enc_uint64(a, 2 + ((zip->total_number_entry + 7) >> 3) + + zip->total_number_time_defined[ti] * 8); + if (r < 0) + return (r); + + /* All are not defined. */ + r = enc_uint64(a, 0); + if (r < 0) + return (r); + + byte = 0; + mask = 0x80; + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->flg & flg) + byte |= mask; + mask >>= 1; + if (mask == 0) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + mask = 0x80; + byte = 0; + } + } + if (mask != 0x80) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + } + + /* External. */ + r = enc_uint64(a, 0); + if (r < 0) + return (r); + + + /* + * Make Times. + */ + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if ((file->flg & flg) == 0) + continue; + archive_le64enc(filetime, utcToFiletime(file->times[ti].time, + file->times[ti].time_ns)); + r = compress_out(a, filetime, 8, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + + return (ARCHIVE_OK); +} + +static int +make_header(struct archive_write *a, uint64_t offset, uint64_t pack_size, + uint64_t unpack_size, int codernum, struct coder *coders) +{ + struct _7zip *zip = (struct _7zip *)a->format_data; + struct file *file; + int r; + uint8_t mask, byte; + + /* + * Make FilesInfo. + */ + r = enc_uint64(a, kHeader); + if (r < 0) + return (r); + + /* + * If there are empty files only, do not write MainStreamInfo. + */ + if (zip->total_number_nonempty_entry) { + /* + * Make MainStreamInfo. + */ + r = enc_uint64(a, kMainStreamsInfo); + if (r < 0) + return (r); + r = make_streamsInfo(a, offset, pack_size, unpack_size, + codernum, coders, 1, 0); + if (r < 0) + return (r); + } + + /* + * Make FilesInfo. + */ + r = enc_uint64(a, kFilesInfo); + if (r < 0) + return (r); + + /* Write numFiles. */ + r = enc_uint64(a, zip->total_number_entry); + if (r < 0) + return (r); + + if (zip->total_number_empty_entry > 0) { + /* Make EmptyStream. */ + r = enc_uint64(a, kEmptyStream); + if (r < 0) + return (r); + + /* Write EmptyStream Size. */ + r = enc_uint64(a, (zip->total_number_entry+7)>>3); + if (r < 0) + return (r); + + byte = 0; + mask = 0x80; + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->size == 0) + byte |= mask; + mask >>= 1; + if (mask == 0) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + mask = 0x80; + byte = 0; + } + } + if (mask != 0x80) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + } + + if (zip->total_number_empty_entry > zip->total_number_dir_entry) { + /* Make EmptyFile. */ + r = enc_uint64(a, kEmptyFile); + if (r < 0) + return (r); + + /* Write EmptyFile Size. */ + r = enc_uint64(a, (zip->total_number_empty_entry + 7) >> 3); + if (r < 0) + return (r); + + byte = 0; + mask = 0x80; + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + if (file->size) + continue; + if (!file->dir) + byte |= mask; + mask >>= 1; + if (mask == 0) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + mask = 0x80; + byte = 0; + } + } + if (mask != 0x80) { + r = compress_out(a, &byte, 1, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + } + + /* Make Name. */ + r = enc_uint64(a, kName); + if (r < 0) + return (r); + + /* Write Nume size. */ + r = enc_uint64(a, zip->total_bytes_entry_name+1); + if (r < 0) + return (r); + + /* Write dmy byte. */ + r = enc_uint64(a, 0); + if (r < 0) + return (r); + + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + r = compress_out(a, file->utf16name, file->name_len+2, + ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + + /* Make MTime. */ + r = make_time(a, kMTime, MTIME_IS_SET, MTIME); + if (r < 0) + return (r); + + /* Make CTime. */ + r = make_time(a, kCTime, CTIME_IS_SET, CTIME); + if (r < 0) + return (r); + + /* Make ATime. */ + r = make_time(a, kATime, ATIME_IS_SET, ATIME); + if (r < 0) + return (r); + + /* Make Attributes. */ + r = enc_uint64(a, kAttributes); + if (r < 0) + return (r); + + /* Write Attributes size. */ + r = enc_uint64(a, 2 + zip->total_number_entry * 4); + if (r < 0) + return (r); + + /* Write "All Are Defined". */ + r = enc_uint64(a, 1); + if (r < 0) + return (r); + + /* Write dmy byte. */ + r = enc_uint64(a, 0); + if (r < 0) + return (r); + + file = zip->file_list.first; + for (;file != NULL; file = file->next) { + /* + * High 16bits is unix mode. + * Low 16bits is Windows attributes. + */ + uint32_t encattr, attr; + if (file->dir) + attr = 0x8010; + else + attr = 0x8020; + if ((file->mode & 0222) == 0) + attr |= 1;/* Read Only. */ + attr |= ((uint32_t)file->mode) << 16; + archive_le32enc(&encattr, attr); + r = compress_out(a, &encattr, 4, ARCHIVE_Z_RUN); + if (r < 0) + return (r); + } + + /* Write End. */ + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + + /* Write End. */ + r = enc_uint64(a, kEnd); + if (r < 0) + return (r); + + return (ARCHIVE_OK); +} + + +static int +_7z_free(struct archive_write *a) +{ + struct _7zip *zip = (struct _7zip *)a->format_data; + + file_free_register(zip); + compression_end(&(a->archive), &(zip->stream)); + free(zip->coder.props); + free(zip); + + return (ARCHIVE_OK); +} + +static int +file_cmp_node(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + struct file *f1 = (struct file *)n1; + struct file *f2 = (struct file *)n2; + + if (f1->name_len == f2->name_len) + return (memcmp(f1->utf16name, f2->utf16name, f1->name_len)); + return (f1->name_len > f2->name_len)?1:-1; +} + +static int +file_cmp_key(const struct archive_rb_node *n, const void *key) +{ + struct file *f = (struct file *)n; + + return (f->name_len - *(const char *)key); +} + +static int +file_new(struct archive_write *a, struct archive_entry *entry, + struct file **newfile) +{ + struct _7zip *zip; + struct file *file; + const char *u16; + size_t u16len; + int ret = ARCHIVE_OK; + + zip = (struct _7zip *)a->format_data; + *newfile = NULL; + + file = calloc(1, sizeof(*file)); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + + if (0 > archive_entry_pathname_l(entry, &u16, &u16len, zip->sconv)) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for UTF-16LE"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "A filename cannot be converted to UTF-16LE;" + "You should disable making Joliet extension"); + ret = ARCHIVE_WARN; + } + file->utf16name = malloc(u16len + 2); + if (file->utf16name == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Name"); + return (ARCHIVE_FATAL); + } + memcpy(file->utf16name, u16, u16len); + file->utf16name[u16len+0] = 0; + file->utf16name[u16len+1] = 0; + file->name_len = u16len; + file->mode = archive_entry_mode(entry); + if (archive_entry_filetype(entry) == AE_IFREG) + file->size = archive_entry_size(entry); + else + archive_entry_set_size(entry, 0); + if (archive_entry_filetype(entry) == AE_IFDIR) + file->dir = 1; + else if (archive_entry_filetype(entry) == AE_IFLNK) + file->size = strlen(archive_entry_symlink(entry)); + if (archive_entry_mtime_is_set(entry)) { + file->flg |= MTIME_IS_SET; + file->times[MTIME].time = archive_entry_mtime(entry); + file->times[MTIME].time_ns = archive_entry_mtime_nsec(entry); + } + if (archive_entry_atime_is_set(entry)) { + file->flg |= ATIME_IS_SET; + file->times[ATIME].time = archive_entry_atime(entry); + file->times[ATIME].time_ns = archive_entry_atime_nsec(entry); + } + if (archive_entry_ctime_is_set(entry)) { + file->flg |= CTIME_IS_SET; + file->times[CTIME].time = archive_entry_ctime(entry); + file->times[CTIME].time_ns = archive_entry_ctime_nsec(entry); + } + + *newfile = file; + return (ret); +} + +static void +file_free(struct file *file) +{ + free(file->utf16name); + free(file); +} + +static void +file_register(struct _7zip *zip, struct file *file) +{ + file->next = NULL; + *zip->file_list.last = file; + zip->file_list.last = &(file->next); +} + +static void +file_init_register(struct _7zip *zip) +{ + zip->file_list.first = NULL; + zip->file_list.last = &(zip->file_list.first); +} + +static void +file_free_register(struct _7zip *zip) +{ + struct file *file, *file_next; + + file = zip->file_list.first; + while (file != NULL) { + file_next = file->next; + file_free(file); + file = file_next; + } +} + +static void +file_register_empty(struct _7zip *zip, struct file *file) +{ + file->next = NULL; + *zip->empty_list.last = file; + zip->empty_list.last = &(file->next); +} + +static void +file_init_register_empty(struct _7zip *zip) +{ + zip->empty_list.first = NULL; + zip->empty_list.last = &(zip->empty_list.first); +} + +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) || !defined(HAVE_LZMA_H) +static int +compression_unsupported_encoder(struct archive *a, + struct la_zstream *lastrm, const char *name) +{ + + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "%s compression not supported on this platform", name); + lastrm->valid = 0; + lastrm->real_stream = NULL; + return (ARCHIVE_FAILED); +} +#endif + +static int +compression_init_encoder_copy(struct archive *a, struct la_zstream *lastrm) +{ + + if (lastrm->valid) + compression_end(a, lastrm); + lastrm->valid = 1; + lastrm->code = compression_code_copy; + lastrm->end = compression_end_copy; + return (ARCHIVE_OK); +} + +static int +compression_code_copy(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + size_t bytes; + + (void)a; /* UNUSED */ + if (lastrm->avail_out > lastrm->avail_in) + bytes = lastrm->avail_in; + else + bytes = lastrm->avail_out; + if (bytes) { + memcpy(lastrm->next_out, lastrm->next_in, bytes); + lastrm->next_in += bytes; + lastrm->avail_in -= bytes; + lastrm->total_in += bytes; + lastrm->next_out += bytes; + lastrm->avail_out -= bytes; + lastrm->total_out += bytes; + } + if (action == ARCHIVE_Z_FINISH && lastrm->avail_in == 0) + return (ARCHIVE_EOF); + return (ARCHIVE_OK); +} + +static int +compression_end_copy(struct archive *a, struct la_zstream *lastrm) +{ + (void)a; /* UNUSED */ + lastrm->valid = 0; + return (ARCHIVE_OK); +} + +#ifdef HAVE_ZLIB_H +static int +compression_init_encoder_deflate(struct archive *a, + struct la_zstream *lastrm, int level, int withheader) +{ + z_stream *strm; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for gzip stream"); + return (ARCHIVE_FATAL); + } + /* zlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + if (deflateInit2(strm, level, Z_DEFLATED, + (withheader)?15:-15, + 8, Z_DEFAULT_STRATEGY) != Z_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_deflate; + lastrm->end = compression_end_deflate; + return (ARCHIVE_OK); +} + +static int +compression_code_deflate(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + z_stream *strm; + int r; + + strm = (z_stream *)lastrm->real_stream; + /* zlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + r = deflate(strm, + (action == ARCHIVE_Z_FINISH)? Z_FINISH: Z_NO_FLUSH); + lastrm->next_in = strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = strm->total_in; + lastrm->next_out = strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = strm->total_out; + switch (r) { + case Z_OK: + return (ARCHIVE_OK); + case Z_STREAM_END: + return (ARCHIVE_EOF); + default: + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "GZip compression failed:" + " deflate() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_deflate(struct archive *a, struct la_zstream *lastrm) +{ + z_stream *strm; + int r; + + strm = (z_stream *)lastrm->real_stream; + r = deflateEnd(strm); + free(strm); + lastrm->real_stream = NULL; + lastrm->valid = 0; + if (r != Z_OK) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} +#else +static int +compression_init_encoder_deflate(struct archive *a, + struct la_zstream *lastrm, int level, int withheader) +{ + + (void) level; /* UNUSED */ + (void) withheader; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "deflate")); +} +#endif + +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) +static int +compression_init_encoder_bzip2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + bz_stream *strm; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for bzip2 stream"); + return (ARCHIVE_FATAL); + } + /* bzlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); + strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); + strm->next_out = (char *)lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); + strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); + if (BZ2_bzCompressInit(strm, level, 0, 30) != BZ_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_bzip2; + lastrm->end = compression_end_bzip2; + return (ARCHIVE_OK); +} + +static int +compression_code_bzip2(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + bz_stream *strm; + int r; + + strm = (bz_stream *)lastrm->real_stream; + /* bzlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); + strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); + strm->next_out = (char *)lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); + strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); + r = BZ2_bzCompress(strm, + (action == ARCHIVE_Z_FINISH)? BZ_FINISH: BZ_RUN); + lastrm->next_in = (const unsigned char *)strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = + (((uint64_t)(uint32_t)strm->total_in_hi32) << 32) + + (uint64_t)(uint32_t)strm->total_in_lo32; + lastrm->next_out = (unsigned char *)strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = + (((uint64_t)(uint32_t)strm->total_out_hi32) << 32) + + (uint64_t)(uint32_t)strm->total_out_lo32; + switch (r) { + case BZ_RUN_OK: /* Non-finishing */ + case BZ_FINISH_OK: /* Finishing: There's more work to do */ + return (ARCHIVE_OK); + case BZ_STREAM_END: /* Finishing: all done */ + /* Only occurs in finishing case */ + return (ARCHIVE_EOF); + default: + /* Any other return value indicates an error */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Bzip2 compression failed:" + " BZ2_bzCompress() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_bzip2(struct archive *a, struct la_zstream *lastrm) +{ + bz_stream *strm; + int r; + + strm = (bz_stream *)lastrm->real_stream; + r = BZ2_bzCompressEnd(strm); + free(strm); + lastrm->real_stream = NULL; + lastrm->valid = 0; + if (r != BZ_OK) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +#else +static int +compression_init_encoder_bzip2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "bzip2")); +} +#endif + +#if defined(HAVE_LZMA_H) +static int +compression_init_encoder_lzma(struct archive *a, + struct la_zstream *lastrm, int level, uint64_t filter_id) +{ + static const lzma_stream lzma_init_data = LZMA_STREAM_INIT; + lzma_stream *strm; + lzma_filter *lzmafilters; + lzma_options_lzma lzma_opt; + int r; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm) + sizeof(*lzmafilters) * 2); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for lzma stream"); + return (ARCHIVE_FATAL); + } + lzmafilters = (lzma_filter *)(strm+1); + if (level > 6) + level = 6; + if (lzma_lzma_preset(&lzma_opt, level)) { + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lzmafilters[0].id = filter_id; + lzmafilters[0].options = &lzma_opt; + lzmafilters[1].id = LZMA_VLI_UNKNOWN;/* Terminate */ + + r = lzma_properties_size(&(lastrm->prop_size), lzmafilters); + if (r != LZMA_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma_properties_size failed"); + return (ARCHIVE_FATAL); + } + if (lastrm->prop_size) { + lastrm->props = malloc(lastrm->prop_size); + if (lastrm->props == NULL) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Cannot allocate memory"); + return (ARCHIVE_FATAL); + } + r = lzma_properties_encode(lzmafilters, lastrm->props); + if (r != LZMA_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma_properties_encode failed"); + return (ARCHIVE_FATAL); + } + } + + *strm = lzma_init_data; + r = lzma_raw_encoder(strm, lzmafilters); + switch (r) { + case LZMA_OK: + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_lzma; + lastrm->end = compression_end_lzma; + r = ARCHIVE_OK; + break; + case LZMA_MEM_ERROR: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library: " + "Cannot allocate memory"); + r = ARCHIVE_FATAL; + break; + default: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "It's a bug in liblzma"); + r = ARCHIVE_FATAL; + break; + } + return (r); +} + +static int +compression_init_encoder_lzma1(struct archive *a, + struct la_zstream *lastrm, int level) +{ + return compression_init_encoder_lzma(a, lastrm, level, + LZMA_FILTER_LZMA1); +} + +static int +compression_init_encoder_lzma2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + return compression_init_encoder_lzma(a, lastrm, level, + LZMA_FILTER_LZMA2); +} + +static int +compression_code_lzma(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + lzma_stream *strm; + int r; + + strm = (lzma_stream *)lastrm->real_stream; + strm->next_in = lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + r = lzma_code(strm, + (action == ARCHIVE_Z_FINISH)? LZMA_FINISH: LZMA_RUN); + lastrm->next_in = strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = strm->total_in; + lastrm->next_out = strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = strm->total_out; + switch (r) { + case LZMA_OK: + /* Non-finishing case */ + return (ARCHIVE_OK); + case LZMA_STREAM_END: + /* This return can only occur in finishing case. */ + return (ARCHIVE_EOF); + case LZMA_MEMLIMIT_ERROR: + archive_set_error(a, ENOMEM, + "lzma compression error:" + " %ju MiB would have been needed", + (uintmax_t)((lzma_memusage(strm) + 1024 * 1024 -1) + / (1024 * 1024))); + return (ARCHIVE_FATAL); + default: + /* Any other return value indicates an error */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma compression failed:" + " lzma_code() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_lzma(struct archive *a, struct la_zstream *lastrm) +{ + lzma_stream *strm; + + (void)a; /* UNUSED */ + strm = (lzma_stream *)lastrm->real_stream; + lzma_end(strm); + free(strm); + lastrm->valid = 0; + lastrm->real_stream = NULL; + return (ARCHIVE_OK); +} +#else +static int +compression_init_encoder_lzma1(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "lzma")); +} +static int +compression_init_encoder_lzma2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "lzma")); +} +#endif + +/* + * PPMd compression. + */ +static void * +ppmd_alloc(void *p, size_t size) +{ + (void)p; + return malloc(size); +} +static void +ppmd_free(void *p, void *address) +{ + (void)p; + free(address); +} +static ISzAlloc g_szalloc = { ppmd_alloc, ppmd_free }; +static void +ppmd_write(void *p, Byte b) +{ + struct archive_write *a = ((IByteOut *)p)->a; + struct _7zip *zip = (struct _7zip *)(a->format_data); + struct la_zstream *lastrm = &(zip->stream); + struct ppmd_stream *strm; + + if (lastrm->avail_out) { + *lastrm->next_out++ = b; + lastrm->avail_out--; + lastrm->total_out++; + return; + } + strm = (struct ppmd_stream *)lastrm->real_stream; + if (strm->buff_ptr < strm->buff_end) { + *strm->buff_ptr++ = b; + strm->buff_bytes++; + } +} + +static int +compression_init_encoder_ppmd(struct archive *a, + struct la_zstream *lastrm, unsigned maxOrder, uint32_t msize) +{ + struct ppmd_stream *strm; + uint8_t *props; + int r; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + strm->buff = malloc(32); + if (strm->buff == NULL) { + free(strm); + archive_set_error(a, ENOMEM, + "Can't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + strm->buff_ptr = strm->buff; + strm->buff_end = strm->buff + 32; + + props = malloc(1+4); + if (props == NULL) { + free(strm->buff); + free(strm); + archive_set_error(a, ENOMEM, + "Coludn't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + props[0] = maxOrder; + archive_le32enc(props+1, msize); + __archive_ppmd7_functions.Ppmd7_Construct(&strm->ppmd7_context); + r = __archive_ppmd7_functions.Ppmd7_Alloc( + &strm->ppmd7_context, msize, &g_szalloc); + if (r == 0) { + free(strm->buff); + free(strm); + free(props); + archive_set_error(a, ENOMEM, + "Coludn't allocate memory for PPMd"); + return (ARCHIVE_FATAL); + } + __archive_ppmd7_functions.Ppmd7_Init(&(strm->ppmd7_context), maxOrder); + strm->byteout.a = (struct archive_write *)a; + strm->byteout.Write = ppmd_write; + strm->range_enc.Stream = &(strm->byteout); + __archive_ppmd7_functions.Ppmd7z_RangeEnc_Init(&(strm->range_enc)); + strm->stat = 0; + + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_ppmd; + lastrm->end = compression_end_ppmd; + lastrm->prop_size = 5; + lastrm->props = props; + return (ARCHIVE_OK); +} + +static int +compression_code_ppmd(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + struct ppmd_stream *strm; + + strm = (struct ppmd_stream *)lastrm->real_stream; + + /* Copy encoded data if there are remaining bytes from previous call. */ + if (strm->buff_bytes) { + uint8_t *p = strm->buff_ptr - strm->buff_bytes; + while (lastrm->avail_out && strm->buff_bytes) { + *lastrm->next_out++ = *p++; + lastrm->avail_out--; + lastrm->total_out++; + strm->buff_bytes--; + } + if (strm->buff_bytes) + return (ARCHIVE_OK); + if (strm->stat == 1) + return (ARCHIVE_EOF); + strm->buff_ptr = strm->buff; + } + while (lastrm->avail_in && lastrm->avail_out) { + __archive_ppmd7_functions.Ppmd7_EncodeSymbol( + &(strm->ppmd7_context), &(strm->range_enc), + *lastrm->next_in++); + lastrm->avail_in--; + lastrm->total_in++; + } + if (lastrm->avail_in == 0 && action == ARCHIVE_Z_FINISH) { + __archive_ppmd7_functions.Ppmd7z_RangeEnc_FlushData( + &(strm->range_enc)); + strm->stat = 1; + /* Return EOF if there are no remaining bytes. */ + if (strm->buff_bytes == 0) + return (ARCHIVE_EOF); + } + return (ARCHIVE_OK); +} + +static int +compression_end_ppmd(struct archive *a, struct la_zstream *lastrm) +{ + struct ppmd_stream *strm; + + strm = (struct ppmd_stream *)lastrm->real_stream; + __archive_ppmd7_functions.Ppmd7_Free(&strm->ppmd7_context, &g_szalloc); + free(strm->buff); + free(strm); + lastrm->real_stream = NULL; + lastrm->valid = 0; + return (ARCHIVE_OK); +} + +static int +_7z_compression_init_encoder(struct archive_write *a, unsigned compression, + int compression_level) +{ + struct _7zip *zip; + int r; + + zip = (struct _7zip *)a->format_data; + switch (compression) { + case _7Z_DEFLATE: + r = compression_init_encoder_deflate( + &(a->archive), &(zip->stream), + compression_level, 0); + break; + case _7Z_BZIP2: + r = compression_init_encoder_bzip2( + &(a->archive), &(zip->stream), + compression_level); + break; + case _7Z_LZMA1: + r = compression_init_encoder_lzma1( + &(a->archive), &(zip->stream), + compression_level); + break; + case _7Z_LZMA2: + r = compression_init_encoder_lzma2( + &(a->archive), &(zip->stream), + compression_level); + break; + case _7Z_PPMD: + r = compression_init_encoder_ppmd( + &(a->archive), &(zip->stream), + PPMD7_DEFAULT_ORDER, PPMD7_DEFAULT_MEM_SIZE); + break; + case _7Z_COPY: + default: + r = compression_init_encoder_copy( + &(a->archive), &(zip->stream)); + break; + } + if (r == ARCHIVE_OK) { + zip->stream.total_in = 0; + zip->stream.next_out = zip->wbuff; + zip->stream.avail_out = sizeof(zip->wbuff); + zip->stream.total_out = 0; + } + + return (r); +} + +static int +compression_code(struct archive *a, struct la_zstream *lastrm, + enum la_zaction action) +{ + if (lastrm->valid) + return (lastrm->code(a, lastrm, action)); + return (ARCHIVE_OK); +} + +static int +compression_end(struct archive *a, struct la_zstream *lastrm) +{ + if (lastrm->valid) { + lastrm->prop_size = 0; + free(lastrm->props); + lastrm->props = NULL; + return (lastrm->end(a, lastrm)); + } + return (ARCHIVE_OK); +} + + diff --git a/libarchive/archive_write_set_format_ar.c b/libarchive/archive_write_set_format_ar.c new file mode 100644 index 0000000..956b932 --- /dev/null +++ b/libarchive/archive_write_set_format_ar.c @@ -0,0 +1,564 @@ +/*- + * Copyright (c) 2007 Kai Wang + * Copyright (c) 2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_ar.c 201108 2009-12-28 03:28:21Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct ar_w { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + int is_strtab; + int has_strtab; + char wrote_global_header; + char *strtab; +}; + +/* + * Define structure of the "ar" header. + */ +#define AR_name_offset 0 +#define AR_name_size 16 +#define AR_date_offset 16 +#define AR_date_size 12 +#define AR_uid_offset 28 +#define AR_uid_size 6 +#define AR_gid_offset 34 +#define AR_gid_size 6 +#define AR_mode_offset 40 +#define AR_mode_size 8 +#define AR_size_offset 48 +#define AR_size_size 10 +#define AR_fmag_offset 58 +#define AR_fmag_size 2 + +static int archive_write_set_format_ar(struct archive_write *); +static int archive_write_ar_header(struct archive_write *, + struct archive_entry *); +static ssize_t archive_write_ar_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_ar_free(struct archive_write *); +static int archive_write_ar_close(struct archive_write *); +static int archive_write_ar_finish_entry(struct archive_write *); +static const char *ar_basename(const char *path); +static int format_octal(int64_t v, char *p, int s); +static int format_decimal(int64_t v, char *p, int s); + +int +archive_write_set_format_ar_bsd(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_ar_bsd"); + r = archive_write_set_format_ar(a); + if (r == ARCHIVE_OK) { + a->archive.archive_format = ARCHIVE_FORMAT_AR_BSD; + a->archive.archive_format_name = "ar (BSD)"; + } + return (r); +} + +int +archive_write_set_format_ar_svr4(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_ar_svr4"); + r = archive_write_set_format_ar(a); + if (r == ARCHIVE_OK) { + a->archive.archive_format = ARCHIVE_FORMAT_AR_GNU; + a->archive.archive_format_name = "ar (GNU/SVR4)"; + } + return (r); +} + +/* + * Generic initialization. + */ +static int +archive_write_set_format_ar(struct archive_write *a) +{ + struct ar_w *ar; + + /* If someone else was already registered, unregister them. */ + if (a->format_free != NULL) + (a->format_free)(a); + + ar = (struct ar_w *)malloc(sizeof(*ar)); + if (ar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate ar data"); + return (ARCHIVE_FATAL); + } + memset(ar, 0, sizeof(*ar)); + a->format_data = ar; + + a->format_name = "ar"; + a->format_write_header = archive_write_ar_header; + a->format_write_data = archive_write_ar_data; + a->format_close = archive_write_ar_close; + a->format_free = archive_write_ar_free; + a->format_finish_entry = archive_write_ar_finish_entry; + return (ARCHIVE_OK); +} + +static int +archive_write_ar_header(struct archive_write *a, struct archive_entry *entry) +{ + int ret, append_fn; + char buff[60]; + char *ss, *se; + struct ar_w *ar; + const char *pathname; + const char *filename; + int64_t size; + + append_fn = 0; + ar = (struct ar_w *)a->format_data; + ar->is_strtab = 0; + filename = NULL; + size = archive_entry_size(entry); + + + /* + * Reject files with empty name. + */ + pathname = archive_entry_pathname(entry); + if (*pathname == '\0') { + archive_set_error(&a->archive, EINVAL, + "Invalid filename"); + return (ARCHIVE_WARN); + } + + /* + * If we are now at the beginning of the archive, + * we need first write the ar global header. + */ + if (!ar->wrote_global_header) { + __archive_write_output(a, "!\n", 8); + ar->wrote_global_header = 1; + } + + memset(buff, ' ', 60); + strncpy(&buff[AR_fmag_offset], "`\n", 2); + + if (strcmp(pathname, "/") == 0 ) { + /* Entry is archive symbol table in GNU format */ + buff[AR_name_offset] = '/'; + goto stat; + } + if (strcmp(pathname, "__.SYMDEF") == 0) { + /* Entry is archive symbol table in BSD format */ + strncpy(buff + AR_name_offset, "__.SYMDEF", 9); + goto stat; + } + if (strcmp(pathname, "//") == 0) { + /* + * Entry is archive filename table, inform that we should + * collect strtab in next _data call. + */ + ar->is_strtab = 1; + buff[AR_name_offset] = buff[AR_name_offset + 1] = '/'; + /* + * For archive string table, only ar_size field should + * be set. + */ + goto size; + } + + /* + * Otherwise, entry is a normal archive member. + * Strip leading paths from filenames, if any. + */ + if ((filename = ar_basename(pathname)) == NULL) { + /* Reject filenames with trailing "/" */ + archive_set_error(&a->archive, EINVAL, + "Invalid filename"); + return (ARCHIVE_WARN); + } + + if (a->archive.archive_format == ARCHIVE_FORMAT_AR_GNU) { + /* + * SVR4/GNU variant use a "/" to mark then end of the filename, + * make it possible to have embedded spaces in the filename. + * So, the longest filename here (without extension) is + * actually 15 bytes. + */ + if (strlen(filename) <= 15) { + strncpy(&buff[AR_name_offset], + filename, strlen(filename)); + buff[AR_name_offset + strlen(filename)] = '/'; + } else { + /* + * For filename longer than 15 bytes, GNU variant + * makes use of a string table and instead stores the + * offset of the real filename to in the ar_name field. + * The string table should have been written before. + */ + if (ar->has_strtab <= 0) { + archive_set_error(&a->archive, EINVAL, + "Can't find string table"); + return (ARCHIVE_WARN); + } + + se = (char *)malloc(strlen(filename) + 3); + if (se == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate filename buffer"); + return (ARCHIVE_FATAL); + } + + strncpy(se, filename, strlen(filename)); + strcpy(se + strlen(filename), "/\n"); + + ss = strstr(ar->strtab, se); + free(se); + + if (ss == NULL) { + archive_set_error(&a->archive, EINVAL, + "Invalid string table"); + return (ARCHIVE_WARN); + } + + /* + * GNU variant puts "/" followed by digits into + * ar_name field. These digits indicates the real + * filename string's offset to the string table. + */ + buff[AR_name_offset] = '/'; + if (format_decimal(ss - ar->strtab, + buff + AR_name_offset + 1, + AR_name_size - 1)) { + archive_set_error(&a->archive, ERANGE, + "string table offset too large"); + return (ARCHIVE_WARN); + } + } + } else if (a->archive.archive_format == ARCHIVE_FORMAT_AR_BSD) { + /* + * BSD variant: for any file name which is more than + * 16 chars or contains one or more embedded space(s), the + * string "#1/" followed by the ASCII length of the name is + * put into the ar_name field. The file size (stored in the + * ar_size field) is incremented by the length of the name. + * The name is then written immediately following the + * archive header. + */ + if (strlen(filename) <= 16 && strchr(filename, ' ') == NULL) { + strncpy(&buff[AR_name_offset], filename, strlen(filename)); + buff[AR_name_offset + strlen(filename)] = ' '; + } + else { + strncpy(buff + AR_name_offset, "#1/", 3); + if (format_decimal(strlen(filename), + buff + AR_name_offset + 3, + AR_name_size - 3)) { + archive_set_error(&a->archive, ERANGE, + "File name too long"); + return (ARCHIVE_WARN); + } + append_fn = 1; + size += strlen(filename); + } + } + +stat: + if (format_decimal(archive_entry_mtime(entry), buff + AR_date_offset, AR_date_size)) { + archive_set_error(&a->archive, ERANGE, + "File modification time too large"); + return (ARCHIVE_WARN); + } + if (format_decimal(archive_entry_uid(entry), buff + AR_uid_offset, AR_uid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric user ID too large"); + return (ARCHIVE_WARN); + } + if (format_decimal(archive_entry_gid(entry), buff + AR_gid_offset, AR_gid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric group ID too large"); + return (ARCHIVE_WARN); + } + if (format_octal(archive_entry_mode(entry), buff + AR_mode_offset, AR_mode_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric mode too large"); + return (ARCHIVE_WARN); + } + /* + * Sanity Check: A non-pseudo archive member should always be + * a regular file. + */ + if (filename != NULL && archive_entry_filetype(entry) != AE_IFREG) { + archive_set_error(&a->archive, EINVAL, + "Regular file required for non-pseudo member"); + return (ARCHIVE_WARN); + } + +size: + if (format_decimal(size, buff + AR_size_offset, AR_size_size)) { + archive_set_error(&a->archive, ERANGE, + "File size out of range"); + return (ARCHIVE_WARN); + } + + ret = __archive_write_output(a, buff, 60); + if (ret != ARCHIVE_OK) + return (ret); + + ar->entry_bytes_remaining = size; + ar->entry_padding = ar->entry_bytes_remaining % 2; + + if (append_fn > 0) { + ret = __archive_write_output(a, filename, strlen(filename)); + if (ret != ARCHIVE_OK) + return (ret); + ar->entry_bytes_remaining -= strlen(filename); + } + + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_ar_data(struct archive_write *a, const void *buff, size_t s) +{ + struct ar_w *ar; + int ret; + + ar = (struct ar_w *)a->format_data; + if (s > ar->entry_bytes_remaining) + s = ar->entry_bytes_remaining; + + if (ar->is_strtab > 0) { + if (ar->has_strtab > 0) { + archive_set_error(&a->archive, EINVAL, + "More than one string tables exist"); + return (ARCHIVE_WARN); + } + + ar->strtab = (char *)malloc(s); + if (ar->strtab == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate strtab buffer"); + return (ARCHIVE_FATAL); + } + strncpy(ar->strtab, buff, s); + ar->has_strtab = 1; + } + + ret = __archive_write_output(a, buff, s); + if (ret != ARCHIVE_OK) + return (ret); + + ar->entry_bytes_remaining -= s; + return (s); +} + +static int +archive_write_ar_free(struct archive_write *a) +{ + struct ar_w *ar; + + ar = (struct ar_w *)a->format_data; + + if (ar == NULL) + return (ARCHIVE_OK); + + if (ar->has_strtab > 0) { + free(ar->strtab); + ar->strtab = NULL; + } + + free(ar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_ar_close(struct archive_write *a) +{ + struct ar_w *ar; + int ret; + + /* + * If we haven't written anything yet, we need to write + * the ar global header now to make it a valid ar archive. + */ + ar = (struct ar_w *)a->format_data; + if (!ar->wrote_global_header) { + ar->wrote_global_header = 1; + ret = __archive_write_output(a, "!\n", 8); + return (ret); + } + + return (ARCHIVE_OK); +} + +static int +archive_write_ar_finish_entry(struct archive_write *a) +{ + struct ar_w *ar; + int ret; + + ar = (struct ar_w *)a->format_data; + + if (ar->entry_bytes_remaining != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Entry remaining bytes larger than 0"); + return (ARCHIVE_WARN); + } + + if (ar->entry_padding == 0) { + return (ARCHIVE_OK); + } + + if (ar->entry_padding != 1) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Padding wrong size: %ju should be 1 or 0", + (uintmax_t)ar->entry_padding); + return (ARCHIVE_WARN); + } + + ret = __archive_write_output(a, "\n", 1); + return (ret); +} + +/* + * Format a number into the specified field using base-8. + * NB: This version is slightly different from the one in + * _ustar.c + */ +static int +format_octal(int64_t v, char *p, int s) +{ + int len; + char *h; + + len = s; + h = p; + + /* Octal values can't be negative, so use 0. */ + if (v < 0) { + while (len-- > 0) + *p++ = '0'; + return (-1); + } + + p += s; /* Start at the end and work backwards. */ + do { + *--p = (char)('0' + (v & 7)); + v >>= 3; + } while (--s > 0 && v > 0); + + if (v == 0) { + memmove(h, p, len - s); + p = h + len - s; + while (s-- > 0) + *p++ = ' '; + return (0); + } + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '7'; + + return (-1); +} + +/* + * Format a number into the specified field using base-10. + */ +static int +format_decimal(int64_t v, char *p, int s) +{ + int len; + char *h; + + len = s; + h = p; + + /* Negative values in ar header are meaningless, so use 0. */ + if (v < 0) { + while (len-- > 0) + *p++ = '0'; + return (-1); + } + + p += s; + do { + *--p = (char)('0' + (v % 10)); + v /= 10; + } while (--s > 0 && v > 0); + + if (v == 0) { + memmove(h, p, len - s); + p = h + len - s; + while (s-- > 0) + *p++ = ' '; + return (0); + } + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '9'; + + return (-1); +} + +static const char * +ar_basename(const char *path) +{ + const char *endp, *startp; + + endp = path + strlen(path) - 1; + /* + * For filename with trailing slash(es), we return + * NULL indicating an error. + */ + if (*endp == '/') + return (NULL); + + /* Find the start of the base */ + startp = endp; + while (startp > path && *(startp - 1) != '/') + startp--; + + return (startp); +} diff --git a/libarchive/archive_write_set_format_by_name.c b/libarchive/archive_write_set_format_by_name.c new file mode 100644 index 0000000..9671f60 --- /dev/null +++ b/libarchive/archive_write_set_format_by_name.c @@ -0,0 +1,86 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_by_name.c 201168 2009-12-29 06:15:32Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_private.h" + +/* A table that maps names to functions. */ +static +struct { const char *name; int (*setter)(struct archive *); } names[] = +{ + { "7zip", archive_write_set_format_7zip }, + { "ar", archive_write_set_format_ar_bsd }, + { "arbsd", archive_write_set_format_ar_bsd }, + { "argnu", archive_write_set_format_ar_svr4 }, + { "arsvr4", archive_write_set_format_ar_svr4 }, + { "bsdtar", archive_write_set_format_pax_restricted }, + { "cd9660", archive_write_set_format_iso9660 }, + { "cpio", archive_write_set_format_cpio }, + { "gnutar", archive_write_set_format_gnutar }, + { "iso", archive_write_set_format_iso9660 }, + { "iso9660", archive_write_set_format_iso9660 }, + { "mtree", archive_write_set_format_mtree }, + { "newc", archive_write_set_format_cpio_newc }, + { "odc", archive_write_set_format_cpio }, + { "pax", archive_write_set_format_pax }, + { "paxr", archive_write_set_format_pax_restricted }, + { "posix", archive_write_set_format_pax }, + { "rpax", archive_write_set_format_pax_restricted }, + { "shar", archive_write_set_format_shar }, + { "shardump", archive_write_set_format_shar_dump }, + { "ustar", archive_write_set_format_ustar }, + { "xar", archive_write_set_format_xar }, + { "zip", archive_write_set_format_zip }, + { NULL, NULL } +}; + +int +archive_write_set_format_by_name(struct archive *a, const char *name) +{ + int i; + + for (i = 0; names[i].name != NULL; i++) { + if (strcmp(name, names[i].name) == 0) + return ((names[i].setter)(a)); + } + + archive_set_error(a, EINVAL, "No such format '%s'", name); + a->state = ARCHIVE_STATE_FATAL; + return (ARCHIVE_FATAL); +} diff --git a/libarchive/archive_write_set_format_cpio.c b/libarchive/archive_write_set_format_cpio.c new file mode 100644 index 0000000..92b9bfb --- /dev/null +++ b/libarchive/archive_write_set_format_cpio.c @@ -0,0 +1,464 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_cpio.c 201170 2009-12-29 06:34:23Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static ssize_t archive_write_cpio_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_cpio_close(struct archive_write *); +static int archive_write_cpio_free(struct archive_write *); +static int archive_write_cpio_finish_entry(struct archive_write *); +static int archive_write_cpio_header(struct archive_write *, + struct archive_entry *); +static int archive_write_cpio_options(struct archive_write *, + const char *, const char *); +static int format_octal(int64_t, void *, int); +static int64_t format_octal_recursive(int64_t, char *, int); +static int write_header(struct archive_write *, struct archive_entry *); + +struct cpio { + uint64_t entry_bytes_remaining; + + int64_t ino_next; + + struct { int64_t old; int new;} *ino_list; + size_t ino_list_size; + size_t ino_list_next; + + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; +}; + +#define c_magic_offset 0 +#define c_magic_size 6 +#define c_dev_offset 6 +#define c_dev_size 6 +#define c_ino_offset 12 +#define c_ino_size 6 +#define c_mode_offset 18 +#define c_mode_size 6 +#define c_uid_offset 24 +#define c_uid_size 6 +#define c_gid_offset 30 +#define c_gid_size 6 +#define c_nlink_offset 36 +#define c_nlink_size 6 +#define c_rdev_offset 42 +#define c_rdev_size 6 +#define c_mtime_offset 48 +#define c_mtime_size 11 +#define c_namesize_offset 59 +#define c_namesize_size 6 +#define c_filesize_offset 65 +#define c_filesize_size 11 + +/* + * Set output format to 'cpio' format. + */ +int +archive_write_set_format_cpio(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct cpio *cpio; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_cpio"); + + /* If someone else was already registered, unregister them. */ + if (a->format_free != NULL) + (a->format_free)(a); + + cpio = (struct cpio *)calloc(1, sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + a->format_data = cpio; + a->format_name = "cpio"; + a->format_options = archive_write_cpio_options; + a->format_write_header = archive_write_cpio_header; + a->format_write_data = archive_write_cpio_data; + a->format_finish_entry = archive_write_cpio_finish_entry; + a->format_close = archive_write_cpio_close; + a->format_free = archive_write_cpio_free; + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_POSIX; + a->archive.archive_format_name = "POSIX cpio"; + return (ARCHIVE_OK); +} + +static int +archive_write_cpio_options(struct archive_write *a, const char *key, + const char *val) +{ + struct cpio *cpio = (struct cpio *)a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: hdrcharset option needs a character-set name", + a->format_name); + else { + cpio->opt_sconv = archive_string_conversion_to_charset( + &a->archive, val, 0); + if (cpio->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown keyword ``%s''", a->format_name, key); + + return (ret); +} + +/* + * Ino values are as long as 64 bits on some systems; cpio format + * only allows 18 bits and relies on the ino values to identify hardlinked + * files. So, we can't merely "hash" the ino numbers since collisions + * would corrupt the archive. Instead, we generate synthetic ino values + * to store in the archive and maintain a map of original ino values to + * synthetic ones so we can preserve hardlink information. + * + * TODO: Make this more efficient. It's not as bad as it looks (most + * files don't have any hardlinks and we don't do any work here for those), + * but it wouldn't be hard to do better. + * + * TODO: Work with dev/ino pairs here instead of just ino values. + */ +static int +synthesize_ino_value(struct cpio *cpio, struct archive_entry *entry) +{ + int64_t ino = archive_entry_ino64(entry); + int ino_new; + size_t i; + + /* + * If no index number was given, don't assign one. In + * particular, this handles the end-of-archive marker + * correctly by giving it a zero index value. (This is also + * why we start our synthetic index numbers with one below.) + */ + if (ino == 0) + return (0); + + /* Don't store a mapping if we don't need to. */ + if (archive_entry_nlink(entry) < 2) { + return ++cpio->ino_next; + } + + /* Look up old ino; if we have it, this is a hardlink + * and we reuse the same value. */ + for (i = 0; i < cpio->ino_list_next; ++i) { + if (cpio->ino_list[i].old == ino) + return (cpio->ino_list[i].new); + } + + /* Assign a new index number. */ + ino_new = ++cpio->ino_next; + + /* Ensure space for the new mapping. */ + if (cpio->ino_list_size <= cpio->ino_list_next) { + size_t newsize = cpio->ino_list_size < 512 + ? 512 : cpio->ino_list_size * 2; + void *newlist = realloc(cpio->ino_list, + sizeof(cpio->ino_list[0]) * newsize); + if (newlist == NULL) + return (-1); + + cpio->ino_list_size = newsize; + cpio->ino_list = newlist; + } + + /* Record and return the new value. */ + cpio->ino_list[cpio->ino_list_next].old = ino; + cpio->ino_list[cpio->ino_list_next].new = ino_new; + ++cpio->ino_list_next; + return (ino_new); +} + + +static struct archive_string_conv * +get_sconv(struct archive_write *a) +{ + struct cpio *cpio; + struct archive_string_conv *sconv; + + cpio = (struct cpio *)a->format_data; + sconv = cpio->opt_sconv; + if (sconv == NULL) { + if (!cpio->init_default_conversion) { + cpio->sconv_default = + archive_string_default_conversion_for_write( + &(a->archive)); + cpio->init_default_conversion = 1; + } + sconv = cpio->sconv_default; + } + return (sconv); +} + +static int +archive_write_cpio_header(struct archive_write *a, struct archive_entry *entry) +{ + const char *path; + size_t len; + + if (archive_entry_filetype(entry) == 0) { + archive_set_error(&a->archive, -1, "Filetype required"); + return (ARCHIVE_FAILED); + } + + if (archive_entry_pathname_l(entry, &path, &len, get_sconv(a)) != 0 + && errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + if (len == 0 || path == NULL || path[0] == '\0') { + archive_set_error(&a->archive, -1, "Pathname required"); + return (ARCHIVE_FAILED); + } + + if (!archive_entry_size_is_set(entry) || archive_entry_size(entry) < 0) { + archive_set_error(&a->archive, -1, "Size required"); + return (ARCHIVE_FAILED); + } + return write_header(a, entry); +} + +static int +write_header(struct archive_write *a, struct archive_entry *entry) +{ + struct cpio *cpio; + const char *p, *path; + int pathlength, ret, ret_final; + int64_t ino; + char h[76]; + struct archive_string_conv *sconv; + size_t len; + + cpio = (struct cpio *)a->format_data; + ret_final = ARCHIVE_OK; + sconv = get_sconv(a); + + ret = archive_entry_pathname_l(entry, &path, &len, sconv); + if (ret != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", + archive_entry_pathname(entry), + archive_string_conversion_charset_name(sconv)); + ret_final = ARCHIVE_WARN; + } + /* Include trailing null. */ + pathlength = (int)len + 1; + + memset(h, 0, sizeof(h)); + format_octal(070707, h + c_magic_offset, c_magic_size); + format_octal(archive_entry_dev(entry), h + c_dev_offset, c_dev_size); + + ino = synthesize_ino_value(cpio, entry); + if (ino < 0) { + archive_set_error(&a->archive, ENOMEM, + "No memory for ino translation table"); + return (ARCHIVE_FATAL); + } else if (ino > 0777777) { + archive_set_error(&a->archive, ERANGE, + "Too many files for this cpio format"); + return (ARCHIVE_FATAL); + } + format_octal(ino & 0777777, h + c_ino_offset, c_ino_size); + + /* TODO: Set ret_final to ARCHIVE_WARN if any of these overflow. */ + format_octal(archive_entry_mode(entry), h + c_mode_offset, c_mode_size); + format_octal(archive_entry_uid(entry), h + c_uid_offset, c_uid_size); + format_octal(archive_entry_gid(entry), h + c_gid_offset, c_gid_size); + format_octal(archive_entry_nlink(entry), h + c_nlink_offset, c_nlink_size); + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) + format_octal(archive_entry_dev(entry), h + c_rdev_offset, c_rdev_size); + else + format_octal(0, h + c_rdev_offset, c_rdev_size); + format_octal(archive_entry_mtime(entry), h + c_mtime_offset, c_mtime_size); + format_octal(pathlength, h + c_namesize_offset, c_namesize_size); + + /* Non-regular files don't store bodies. */ + if (archive_entry_filetype(entry) != AE_IFREG) + archive_entry_set_size(entry, 0); + + /* Symlinks get the link written as the body of the entry. */ + ret = archive_entry_symlink_l(entry, &p, &len, sconv); + if (ret != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + archive_entry_symlink(entry), + archive_string_conversion_charset_name(sconv)); + ret_final = ARCHIVE_WARN; + } + if (len > 0 && p != NULL && *p != '\0') + ret = format_octal(strlen(p), h + c_filesize_offset, + c_filesize_size); + else + ret = format_octal(archive_entry_size(entry), + h + c_filesize_offset, c_filesize_size); + if (ret) { + archive_set_error(&a->archive, ERANGE, + "File is too large for cpio format."); + return (ARCHIVE_FAILED); + } + + ret = __archive_write_output(a, h, sizeof(h)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + ret = __archive_write_output(a, path, pathlength); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + cpio->entry_bytes_remaining = archive_entry_size(entry); + + /* Write the symlink now. */ + if (p != NULL && *p != '\0') { + ret = __archive_write_output(a, p, strlen(p)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + return (ret_final); +} + +static ssize_t +archive_write_cpio_data(struct archive_write *a, const void *buff, size_t s) +{ + struct cpio *cpio; + int ret; + + cpio = (struct cpio *)a->format_data; + if (s > cpio->entry_bytes_remaining) + s = cpio->entry_bytes_remaining; + + ret = __archive_write_output(a, buff, s); + cpio->entry_bytes_remaining -= s; + if (ret >= 0) + return (s); + else + return (ret); +} + +/* + * Format a number into the specified field. + */ +static int +format_octal(int64_t v, void *p, int digits) +{ + int64_t max; + int ret; + + max = (((int64_t)1) << (digits * 3)) - 1; + if (v >= 0 && v <= max) { + format_octal_recursive(v, (char *)p, digits); + ret = 0; + } else { + format_octal_recursive(max, (char *)p, digits); + ret = -1; + } + return (ret); +} + +static int64_t +format_octal_recursive(int64_t v, char *p, int s) +{ + if (s == 0) + return (v); + v = format_octal_recursive(v, p+1, s-1); + *p = '0' + (v & 7); + return (v >> 3); +} + +static int +archive_write_cpio_close(struct archive_write *a) +{ + int er; + struct archive_entry *trailer; + + trailer = archive_entry_new2(NULL); + /* nlink = 1 here for GNU cpio compat. */ + archive_entry_set_nlink(trailer, 1); + archive_entry_set_size(trailer, 0); + archive_entry_set_pathname(trailer, "TRAILER!!!"); + er = write_header(a, trailer); + archive_entry_free(trailer); + return (er); +} + +static int +archive_write_cpio_free(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + free(cpio->ino_list); + free(cpio); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_cpio_finish_entry(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + return (__archive_write_nulls(a, cpio->entry_bytes_remaining)); +} diff --git a/libarchive/archive_write_set_format_cpio_newc.c b/libarchive/archive_write_set_format_cpio_newc.c new file mode 100644 index 0000000..d06c391 --- /dev/null +++ b/libarchive/archive_write_set_format_cpio_newc.c @@ -0,0 +1,420 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2006 Rudolf Marek SYSGO s.r.o. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_cpio_newc.c 201160 2009-12-29 05:41:57Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +static ssize_t archive_write_newc_data(struct archive_write *, + const void *buff, size_t s); +static int archive_write_newc_close(struct archive_write *); +static int archive_write_newc_free(struct archive_write *); +static int archive_write_newc_finish_entry(struct archive_write *); +static int archive_write_newc_header(struct archive_write *, + struct archive_entry *); +static int archive_write_newc_options(struct archive_write *, + const char *, const char *); +static int format_hex(int64_t, void *, int); +static int64_t format_hex_recursive(int64_t, char *, int); +static int write_header(struct archive_write *, struct archive_entry *); + +struct cpio { + uint64_t entry_bytes_remaining; + int padding; + + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; +}; + +#define c_magic_offset 0 +#define c_magic_size 6 +#define c_ino_offset 6 +#define c_ino_size 8 +#define c_mode_offset 14 +#define c_mode_size 8 +#define c_uid_offset 22 +#define c_uid_size 8 +#define c_gid_offset 30 +#define c_gid_size 8 +#define c_nlink_offset 38 +#define c_nlink_size 8 +#define c_mtime_offset 46 +#define c_mtime_size 8 +#define c_filesize_offset 54 +#define c_filesize_size 8 +#define c_devmajor_offset 62 +#define c_devmajor_size 8 +#define c_devminor_offset 70 +#define c_devminor_size 8 +#define c_rdevmajor_offset 78 +#define c_rdevmajor_size 8 +#define c_rdevminor_offset 86 +#define c_rdevminor_size 8 +#define c_namesize_offset 94 +#define c_namesize_size 8 +#define c_checksum_offset 102 +#define c_checksum_size 8 +#define c_header_size 110 + +/* Logic trick: difference between 'n' and next multiple of 4 */ +#define PAD4(n) (3 & (1 + ~(n))) + +/* + * Set output format to 'cpio' format. + */ +int +archive_write_set_format_cpio_newc(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct cpio *cpio; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_cpio_newc"); + + /* If someone else was already registered, unregister them. */ + if (a->format_free != NULL) + (a->format_free)(a); + + cpio = (struct cpio *)malloc(sizeof(*cpio)); + if (cpio == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate cpio data"); + return (ARCHIVE_FATAL); + } + memset(cpio, 0, sizeof(*cpio)); + a->format_data = cpio; + a->format_name = "cpio"; + a->format_options = archive_write_newc_options; + a->format_write_header = archive_write_newc_header; + a->format_write_data = archive_write_newc_data; + a->format_finish_entry = archive_write_newc_finish_entry; + a->format_close = archive_write_newc_close; + a->format_free = archive_write_newc_free; + a->archive.archive_format = ARCHIVE_FORMAT_CPIO_SVR4_NOCRC; + a->archive.archive_format_name = "SVR4 cpio nocrc"; + return (ARCHIVE_OK); +} + +static int +archive_write_newc_options(struct archive_write *a, const char *key, + const char *val) +{ + struct cpio *cpio = (struct cpio *)a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: hdrcharset option needs a character-set name", + a->format_name); + else { + cpio->opt_sconv = archive_string_conversion_to_charset( + &a->archive, val, 0); + if (cpio->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown keyword ``%s''", a->format_name, key); + + return (ret); +} + +static struct archive_string_conv * +get_sconv(struct archive_write *a) +{ + struct cpio *cpio; + struct archive_string_conv *sconv; + + cpio = (struct cpio *)a->format_data; + sconv = cpio->opt_sconv; + if (sconv == NULL) { + if (!cpio->init_default_conversion) { + cpio->sconv_default = + archive_string_default_conversion_for_write( + &(a->archive)); + cpio->init_default_conversion = 1; + } + sconv = cpio->sconv_default; + } + return (sconv); +} + +static int +archive_write_newc_header(struct archive_write *a, struct archive_entry *entry) +{ + const char *path; + size_t len; + + if (archive_entry_filetype(entry) == 0) { + archive_set_error(&a->archive, -1, "Filetype required"); + return (ARCHIVE_FAILED); + } + + if (archive_entry_pathname_l(entry, &path, &len, get_sconv(a)) != 0 + && errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + if (len == 0 || path == NULL || path[0] == '\0') { + archive_set_error(&a->archive, -1, "Pathname required"); + return (ARCHIVE_FAILED); + } + + if (archive_entry_hardlink(entry) == NULL + && (!archive_entry_size_is_set(entry) || archive_entry_size(entry) < 0)) { + archive_set_error(&a->archive, -1, "Size required"); + return (ARCHIVE_FAILED); + } + return write_header(a, entry); +} + +static int +write_header(struct archive_write *a, struct archive_entry *entry) +{ + int64_t ino; + struct cpio *cpio; + const char *p, *path; + int pathlength, ret, ret_final; + char h[c_header_size]; + struct archive_string_conv *sconv; + size_t len; + int pad; + + cpio = (struct cpio *)a->format_data; + ret_final = ARCHIVE_OK; + sconv = get_sconv(a); + + ret = archive_entry_pathname_l(entry, &path, &len, sconv); + if (ret != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", + archive_entry_pathname(entry), + archive_string_conversion_charset_name(sconv)); + ret_final = ARCHIVE_WARN; + } + pathlength = (int)len + 1; /* Include trailing null. */ + + memset(h, 0, c_header_size); + format_hex(0x070701, h + c_magic_offset, c_magic_size); + format_hex(archive_entry_devmajor(entry), h + c_devmajor_offset, + c_devmajor_size); + format_hex(archive_entry_devminor(entry), h + c_devminor_offset, + c_devminor_size); + + ino = archive_entry_ino64(entry); + if (ino > 0xffffffff) { + archive_set_error(&a->archive, ERANGE, + "large inode number truncated"); + ret_final = ARCHIVE_WARN; + } + + /* TODO: Set ret_final to ARCHIVE_WARN if any of these overflow. */ + format_hex(ino & 0xffffffff, h + c_ino_offset, c_ino_size); + format_hex(archive_entry_mode(entry), h + c_mode_offset, c_mode_size); + format_hex(archive_entry_uid(entry), h + c_uid_offset, c_uid_size); + format_hex(archive_entry_gid(entry), h + c_gid_offset, c_gid_size); + format_hex(archive_entry_nlink(entry), h + c_nlink_offset, c_nlink_size); + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + format_hex(archive_entry_rdevmajor(entry), h + c_rdevmajor_offset, c_rdevmajor_size); + format_hex(archive_entry_rdevminor(entry), h + c_rdevminor_offset, c_rdevminor_size); + } else { + format_hex(0, h + c_rdevmajor_offset, c_rdevmajor_size); + format_hex(0, h + c_rdevminor_offset, c_rdevminor_size); + } + format_hex(archive_entry_mtime(entry), h + c_mtime_offset, c_mtime_size); + format_hex(pathlength, h + c_namesize_offset, c_namesize_size); + format_hex(0, h + c_checksum_offset, c_checksum_size); + + /* Non-regular files don't store bodies. */ + if (archive_entry_filetype(entry) != AE_IFREG) + archive_entry_set_size(entry, 0); + + /* Symlinks get the link written as the body of the entry. */ + ret = archive_entry_symlink_l(entry, &p, &len, sconv); + if (ret != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Likname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + archive_entry_symlink(entry), + archive_string_conversion_charset_name(sconv)); + ret_final = ARCHIVE_WARN; + } + if (len > 0 && p != NULL && *p != '\0') + ret = format_hex(strlen(p), h + c_filesize_offset, + c_filesize_size); + else + ret = format_hex(archive_entry_size(entry), + h + c_filesize_offset, c_filesize_size); + if (ret) { + archive_set_error(&a->archive, ERANGE, + "File is too large for this format."); + return (ARCHIVE_FAILED); + } + + ret = __archive_write_output(a, h, c_header_size); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Pad pathname to even length. */ + ret = __archive_write_output(a, path, pathlength); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + pad = PAD4(pathlength + c_header_size); + if (pad) { + ret = __archive_write_output(a, "\0\0\0", pad); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + cpio->entry_bytes_remaining = archive_entry_size(entry); + cpio->padding = PAD4(cpio->entry_bytes_remaining); + + /* Write the symlink now. */ + if (p != NULL && *p != '\0') { + ret = __archive_write_output(a, p, strlen(p)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + pad = PAD4(strlen(p)); + ret = __archive_write_output(a, "\0\0\0", pad); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + return (ret_final); +} + +static ssize_t +archive_write_newc_data(struct archive_write *a, const void *buff, size_t s) +{ + struct cpio *cpio; + int ret; + + cpio = (struct cpio *)a->format_data; + if (s > cpio->entry_bytes_remaining) + s = cpio->entry_bytes_remaining; + + ret = __archive_write_output(a, buff, s); + cpio->entry_bytes_remaining -= s; + if (ret >= 0) + return (s); + else + return (ret); +} + +/* + * Format a number into the specified field. + */ +static int +format_hex(int64_t v, void *p, int digits) +{ + int64_t max; + int ret; + + max = (((int64_t)1) << (digits * 4)) - 1; + if (v >= 0 && v <= max) { + format_hex_recursive(v, (char *)p, digits); + ret = 0; + } else { + format_hex_recursive(max, (char *)p, digits); + ret = -1; + } + return (ret); +} + +static int64_t +format_hex_recursive(int64_t v, char *p, int s) +{ + if (s == 0) + return (v); + v = format_hex_recursive(v, p+1, s-1); + *p = "0123456789abcdef"[v & 0xf]; + return (v >> 4); +} + +static int +archive_write_newc_close(struct archive_write *a) +{ + int er; + struct archive_entry *trailer; + + trailer = archive_entry_new(); + archive_entry_set_nlink(trailer, 1); + archive_entry_set_size(trailer, 0); + archive_entry_set_pathname(trailer, "TRAILER!!!"); + /* Bypass the required data checks. */ + er = write_header(a, trailer); + archive_entry_free(trailer); + return (er); +} + +static int +archive_write_newc_free(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + free(cpio); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_newc_finish_entry(struct archive_write *a) +{ + struct cpio *cpio; + + cpio = (struct cpio *)a->format_data; + return (__archive_write_nulls(a, cpio->entry_bytes_remaining + cpio->padding)); +} diff --git a/libarchive/archive_write_set_format_gnutar.c b/libarchive/archive_write_set_format_gnutar.c new file mode 100644 index 0000000..e091ed2 --- /dev/null +++ b/libarchive/archive_write_set_format_gnutar.c @@ -0,0 +1,684 @@ +/*- + * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). + * Author: Jonas Gastal + * Copyright (c) 2011 Michihiro NAKAJIMA + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_gnu_tar.c 191579 2009-04-27 18:35:03Z gastal $"); + + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct gnutar { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + const char * linkname; + size_t linkname_length; + const char * pathname; + size_t pathname_length; + const char * uname; + size_t uname_length; + const char * gname; + size_t gname_length; + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; +}; + +/* + * Define structure of GNU tar header. + */ +#define GNUTAR_name_offset 0 +#define GNUTAR_name_size 100 +#define GNUTAR_mode_offset 100 +#define GNUTAR_mode_size 7 +#define GNUTAR_mode_max_size 8 +#define GNUTAR_uid_offset 108 +#define GNUTAR_uid_size 7 +#define GNUTAR_uid_max_size 8 +#define GNUTAR_gid_offset 116 +#define GNUTAR_gid_size 7 +#define GNUTAR_gid_max_size 8 +#define GNUTAR_size_offset 124 +#define GNUTAR_size_size 11 +#define GNUTAR_size_max_size 12 +#define GNUTAR_mtime_offset 136 +#define GNUTAR_mtime_size 11 +#define GNUTAR_mtime_max_size 11 +#define GNUTAR_checksum_offset 148 +#define GNUTAR_checksum_size 8 +#define GNUTAR_typeflag_offset 156 +#define GNUTAR_typeflag_size 1 +#define GNUTAR_linkname_offset 157 +#define GNUTAR_linkname_size 100 +#define GNUTAR_magic_offset 257 +#define GNUTAR_magic_size 6 +#define GNUTAR_version_offset 263 +#define GNUTAR_version_size 2 +#define GNUTAR_uname_offset 265 +#define GNUTAR_uname_size 32 +#define GNUTAR_gname_offset 297 +#define GNUTAR_gname_size 32 +#define GNUTAR_rdevmajor_offset 329 +#define GNUTAR_rdevmajor_size 6 +#define GNUTAR_rdevmajor_max_size 8 +#define GNUTAR_rdevminor_offset 337 +#define GNUTAR_rdevminor_size 6 +#define GNUTAR_rdevminor_max_size 8 + +/* + * A filled-in copy of the header for initialization. + */ +static const char template_header[] = { + /* name: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Mode, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* uid, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* gid, null termination: 8 bytes */ + '0','0','0','0','0','0', '0','\0', + /* size, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', '\0', + /* mtime, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', '\0', + /* Initial checksum value: 8 spaces */ + ' ',' ',' ',' ',' ',' ',' ',' ', + /* Typeflag: 1 byte */ + '0', /* '0' = regular file */ + /* Linkname: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Magic: 8 bytes */ + 'u','s','t','a','r',' ', ' ','\0', + /* Uname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* Gname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* rdevmajor + null padding: 8 bytes */ + '\0','\0','\0','\0','\0','\0', '\0','\0', + /* rdevminor + null padding: 8 bytes */ + '\0','\0','\0','\0','\0','\0', '\0','\0', + /* Padding: 167 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0 +}; + +static int archive_write_gnutar_options(struct archive_write *, + const char *, const char *); +static int archive_format_gnutar_header(struct archive_write *, char h[512], + struct archive_entry *, int tartype); +static int archive_write_gnutar_header(struct archive_write *, + struct archive_entry *entry); +static ssize_t archive_write_gnutar_data(struct archive_write *a, const void *buff, + size_t s); +static int archive_write_gnutar_free(struct archive_write *); +static int archive_write_gnutar_close(struct archive_write *); +static int archive_write_gnutar_finish_entry(struct archive_write *); +static int format_256(int64_t, char *, int); +static int format_number(int64_t, char *, int size, int maxsize); +static int format_octal(int64_t, char *, int); + +/* + * Set output format to 'GNU tar' format. + */ +int +archive_write_set_format_gnutar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct gnutar *gnutar; + + gnutar = (struct gnutar *)calloc(1, sizeof(*gnutar)); + if (gnutar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate gnutar data"); + return (ARCHIVE_FATAL); + } + a->format_data = gnutar; + a->format_name = "gnutar"; + a->format_options = archive_write_gnutar_options; + a->format_write_header = archive_write_gnutar_header; + a->format_write_data = archive_write_gnutar_data; + a->format_close = archive_write_gnutar_close; + a->format_free = archive_write_gnutar_free; + a->format_finish_entry = archive_write_gnutar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_GNUTAR; + a->archive.archive_format_name = "GNU tar"; + return (ARCHIVE_OK); +} + +static int +archive_write_gnutar_options(struct archive_write *a, const char *key, + const char *val) +{ + struct gnutar *gnutar = (struct gnutar *)a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: hdrcharset option needs a character-set name", + a->format_name); + else { + gnutar->opt_sconv = archive_string_conversion_to_charset( + &a->archive, val, 0); + if (gnutar->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown keyword ``%s''", a->format_name, key); + + return (ret); +} + +static int +archive_write_gnutar_close(struct archive_write *a) +{ + return (__archive_write_nulls(a, 512*2)); +} + +static int +archive_write_gnutar_free(struct archive_write *a) +{ + struct gnutar *gnutar; + + gnutar = (struct gnutar *)a->format_data; + free(gnutar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_gnutar_finish_entry(struct archive_write *a) +{ + struct gnutar *gnutar; + int ret; + + gnutar = (struct gnutar *)a->format_data; + ret = __archive_write_nulls(a, + gnutar->entry_bytes_remaining + gnutar->entry_padding); + gnutar->entry_bytes_remaining = gnutar->entry_padding = 0; + return (ret); +} + +static ssize_t +archive_write_gnutar_data(struct archive_write *a, const void *buff, size_t s) +{ + struct gnutar *gnutar; + int ret; + + gnutar = (struct gnutar *)a->format_data; + if (s > gnutar->entry_bytes_remaining) + s = gnutar->entry_bytes_remaining; + ret = __archive_write_output(a, buff, s); + gnutar->entry_bytes_remaining -= s; + if (ret != ARCHIVE_OK) + return (ret); + return (s); +} + +static int +archive_write_gnutar_header(struct archive_write *a, + struct archive_entry *entry) +{ + char buff[512]; + int r, ret, ret2 = ARCHIVE_OK; + int tartype; + struct gnutar *gnutar; + struct archive_string_conv *sconv; + + gnutar = (struct gnutar *)a->format_data; + + /* Setup default string conversion. */ + if (gnutar->opt_sconv == NULL) { + if (!gnutar->init_default_conversion) { + gnutar->sconv_default = + archive_string_default_conversion_for_write( + &(a->archive)); + gnutar->init_default_conversion = 1; + } + sconv = gnutar->sconv_default; + } else + sconv = gnutar->opt_sconv; + + /* Only regular files (not hardlinks) have data. */ + if (archive_entry_hardlink(entry) != NULL || + archive_entry_symlink(entry) != NULL || + !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_set_size(entry, 0); + + if (AE_IFDIR == archive_entry_filetype(entry)) { + const char *p; + char *t; + /* + * Ensure a trailing '/'. Modify the entry so + * the client sees the change. + */ + p = archive_entry_pathname(entry); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate gnutar data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry, t); + free(t); + } + } + + r = archive_entry_pathname_l(entry, &(gnutar->pathname), + &(gnutar->pathname_length), sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathame"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", + archive_entry_pathname(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + r = archive_entry_uname_l(entry, &(gnutar->uname), + &(gnutar->uname_length), sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Uname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate uname '%s' to %s", + archive_entry_uname(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + r = archive_entry_gname_l(entry, &(gnutar->gname), + &(gnutar->gname_length), sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Gname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate gname '%s' to %s", + archive_entry_gname(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + + /* If linkname is longer than 100 chars we need to add a 'K' header. */ + r = archive_entry_hardlink_l(entry, &(gnutar->linkname), + &(gnutar->linkname_length), sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + archive_entry_hardlink(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + if (gnutar->linkname_length == 0) { + r = archive_entry_symlink_l(entry, &(gnutar->linkname), + &(gnutar->linkname_length), sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + archive_entry_hardlink(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + } + if (gnutar->linkname_length > GNUTAR_linkname_size) { + size_t todo = gnutar->linkname_length; + struct archive_entry *temp = archive_entry_new2(&a->archive); + + /* Uname/gname here don't really matter since noone reads them; + * these are the values that GNU tar happens to use on FreeBSD. */ + archive_entry_set_uname(temp, "root"); + archive_entry_set_gname(temp, "wheel"); + + archive_entry_set_pathname(temp, "././@LongLink"); + archive_entry_set_size(temp, gnutar->linkname_length + 1); + ret = archive_format_gnutar_header(a, buff, temp, 'K'); + if (ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_output(a, buff, 512); + if(ret < ARCHIVE_WARN) + return (ret); + archive_entry_free(temp); + /* Write as many 512 bytes blocks as needed to write full name. */ + ret = __archive_write_output(a, gnutar->linkname, todo); + if(ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_nulls(a, 0x1ff & (-(ssize_t)todo)); + if (ret < ARCHIVE_WARN) + return (ret); + } + + /* If pathname is longer than 100 chars we need to add an 'L' header. */ + if (gnutar->pathname_length > GNUTAR_name_size) { + const char *pathname = gnutar->pathname; + size_t todo = gnutar->pathname_length; + struct archive_entry *temp = archive_entry_new2(&a->archive); + + /* Uname/gname here don't really matter since noone reads them; + * these are the values that GNU tar happens to use on FreeBSD. */ + archive_entry_set_uname(temp, "root"); + archive_entry_set_gname(temp, "wheel"); + + archive_entry_set_pathname(temp, "././@LongLink"); + archive_entry_set_size(temp, gnutar->pathname_length + 1); + ret = archive_format_gnutar_header(a, buff, temp, 'L'); + if (ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_output(a, buff, 512); + if(ret < ARCHIVE_WARN) + return (ret); + archive_entry_free(temp); + /* Write as many 512 bytes blocks as needed to write full name. */ + ret = __archive_write_output(a, pathname, todo); + if(ret < ARCHIVE_WARN) + return (ret); + ret = __archive_write_nulls(a, 0x1ff & (-(ssize_t)todo)); + if (ret < ARCHIVE_WARN) + return (ret); + } + + if (archive_entry_hardlink(entry) != NULL) { + tartype = '1'; + } else + switch (archive_entry_filetype(entry)) { + case AE_IFREG: tartype = '0' ; break; + case AE_IFLNK: tartype = '2' ; break; + case AE_IFCHR: tartype = '3' ; break; + case AE_IFBLK: tartype = '4' ; break; + case AE_IFDIR: tartype = '5' ; break; + case AE_IFIFO: tartype = '6' ; break; + case AE_IFSOCK: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive socket"); + return (ARCHIVE_FAILED); + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (mode=0%lo)", + (unsigned long)archive_entry_mode(entry)); + return (ARCHIVE_FAILED); + } + + ret = archive_format_gnutar_header(a, buff, entry, tartype); + if (ret < ARCHIVE_WARN) + return (ret); + if (ret2 < ret) + ret = ret2; + ret2 = __archive_write_output(a, buff, 512); + if (ret2 < ARCHIVE_WARN) + return (ret2); + if (ret2 < ret) + ret = ret2; + + gnutar->entry_bytes_remaining = archive_entry_size(entry); + gnutar->entry_padding = 0x1ff & (-(int64_t)gnutar->entry_bytes_remaining); + return (ret); +} + +static int +archive_format_gnutar_header(struct archive_write *a, char h[512], + struct archive_entry *entry, int tartype) +{ + unsigned int checksum; + int i, ret; + size_t copy_length; + const char *p; + struct gnutar *gnutar; + + gnutar = (struct gnutar *)a->format_data; + + ret = 0; + + /* + * The "template header" already includes the signature, + * various end-of-field markers, and other required elements. + */ + memcpy(h, &template_header, 512); + + /* + * Because the block is already null-filled, and strings + * are allowed to exactly fill their destination (without null), + * I use memcpy(dest, src, strlen()) here a lot to copy strings. + */ + + if (tartype == 'K' || tartype == 'L') { + p = archive_entry_pathname(entry); + copy_length = strlen(p); + } else { + p = gnutar->pathname; + copy_length = gnutar->pathname_length; + } + if (copy_length > GNUTAR_name_size) + copy_length = GNUTAR_name_size; + memcpy(h + GNUTAR_name_offset, p, copy_length); + + if ((copy_length = gnutar->linkname_length) > 0) { + if (copy_length > GNUTAR_linkname_size) + copy_length = GNUTAR_linkname_size; + memcpy(h + GNUTAR_linkname_offset, gnutar->linkname, + copy_length); + } + + /* TODO: How does GNU tar handle unames longer than GNUTAR_uname_size? */ + if (tartype == 'K' || tartype == 'L') { + p = archive_entry_uname(entry); + copy_length = strlen(p); + } else { + p = gnutar->uname; + copy_length = gnutar->uname_length; + } + if (copy_length > 0) { + if (copy_length > GNUTAR_uname_size) + copy_length = GNUTAR_uname_size; + memcpy(h + GNUTAR_uname_offset, p, copy_length); + } + + /* TODO: How does GNU tar handle gnames longer than GNUTAR_gname_size? */ + if (tartype == 'K' || tartype == 'L') { + p = archive_entry_gname(entry); + copy_length = strlen(p); + } else { + p = gnutar->gname; + copy_length = gnutar->gname_length; + } + if (copy_length > 0) { + if (strlen(p) > GNUTAR_gname_size) + copy_length = GNUTAR_gname_size; + memcpy(h + GNUTAR_gname_offset, p, copy_length); + } + + /* By truncating the mode here, we ensure it always fits. */ + format_octal(archive_entry_mode(entry) & 07777, + h + GNUTAR_mode_offset, GNUTAR_mode_size); + + /* TODO: How does GNU tar handle large UIDs? */ + if (format_octal(archive_entry_uid(entry), + h + GNUTAR_uid_offset, GNUTAR_uid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric user ID %jd too large", + (intmax_t)archive_entry_uid(entry)); + ret = ARCHIVE_FAILED; + } + + /* TODO: How does GNU tar handle large GIDs? */ + if (format_octal(archive_entry_gid(entry), + h + GNUTAR_gid_offset, GNUTAR_gid_size)) { + archive_set_error(&a->archive, ERANGE, + "Numeric group ID %jd too large", + (intmax_t)archive_entry_gid(entry)); + ret = ARCHIVE_FAILED; + } + + /* GNU tar supports base-256 here, so should never overflow. */ + if (format_number(archive_entry_size(entry), h + GNUTAR_size_offset, + GNUTAR_size_size, GNUTAR_size_max_size)) { + archive_set_error(&a->archive, ERANGE, + "File size out of range"); + ret = ARCHIVE_FAILED; + } + + /* Shouldn't overflow before 2106, since mtime field is 33 bits. */ + format_octal(archive_entry_mtime(entry), + h + GNUTAR_mtime_offset, GNUTAR_mtime_size); + + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + if (format_octal(archive_entry_rdevmajor(entry), + h + GNUTAR_rdevmajor_offset, + GNUTAR_rdevmajor_size)) { + archive_set_error(&a->archive, ERANGE, + "Major device number too large"); + ret = ARCHIVE_FAILED; + } + + if (format_octal(archive_entry_rdevminor(entry), + h + GNUTAR_rdevminor_offset, + GNUTAR_rdevminor_size)) { + archive_set_error(&a->archive, ERANGE, + "Minor device number too large"); + ret = ARCHIVE_FAILED; + } + } + + h[GNUTAR_typeflag_offset] = tartype; + + checksum = 0; + for (i = 0; i < 512; i++) + checksum += 255 & (unsigned int)h[i]; + h[GNUTAR_checksum_offset + 6] = '\0'; /* Can't be pre-set in the template. */ + /* h[GNUTAR_checksum_offset + 7] = ' '; */ /* This is pre-set in the template. */ + format_octal(checksum, h + GNUTAR_checksum_offset, 6); + return (ret); +} + +/* + * Format a number into a field, falling back to base-256 if necessary. + */ +static int +format_number(int64_t v, char *p, int s, int maxsize) +{ + int64_t limit = ((int64_t)1 << (s*3)); + + if (v < limit) + return (format_octal(v, p, s)); + return (format_256(v, p, maxsize)); +} + +/* + * Format a number into the specified field using base-256. + */ +static int +format_256(int64_t v, char *p, int s) +{ + p += s; + while (s-- > 0) { + *--p = (char)(v & 0xff); + v >>= 8; + } + *p |= 0x80; /* Set the base-256 marker bit. */ + return (0); +} + +/* + * Format a number into the specified field using octal. + */ +static int +format_octal(int64_t v, char *p, int s) +{ + int len = s; + + /* Octal values can't be negative, so use 0. */ + if (v < 0) + v = 0; + + p += s; /* Start at the end and work backwards. */ + while (s-- > 0) { + *--p = (char)('0' + (v & 7)); + v >>= 3; + } + + if (v == 0) + return (0); + + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '7'; + + return (-1); +} diff --git a/libarchive/archive_write_set_format_iso9660.c b/libarchive/archive_write_set_format_iso9660.c new file mode 100644 index 0000000..4b01a64 --- /dev/null +++ b/libarchive/archive_write_set_format_iso9660.c @@ -0,0 +1,8114 @@ +/*- + * Copyright (c) 2009-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_UTSNAME_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_rb.h" +#include "archive_write_private.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) +#define getuid() 0 +#define getgid() 0 +#endif + +/*#define DEBUG 1*/ +#ifdef DEBUG +/* To compare to the ISO image file made by mkisofs. */ +#define COMPAT_MKISOFS 1 +#endif + +#define LOGICAL_BLOCK_BITS 11 +#define LOGICAL_BLOCK_SIZE 2048 +#define PATH_TABLE_BLOCK_SIZE 4096 + +#define SYSTEM_AREA_BLOCK 16 +#define PRIMARY_VOLUME_DESCRIPTOR_BLOCK 1 +#define SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK 1 +#define BOOT_RECORD_DESCRIPTOR_BLOCK 1 +#define VOLUME_DESCRIPTOR_SET_TERMINATOR_BLOCK 1 +#define NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK 1 +#define RRIP_ER_BLOCK 1 +#define PADDING_BLOCK 150 + +#define FD_1_2M_SIZE (1024 * 1200) +#define FD_1_44M_SIZE (1024 * 1440) +#define FD_2_88M_SIZE (1024 * 2880) +#define MULTI_EXTENT_SIZE (ARCHIVE_LITERAL_LL(1) << 32) /* 4Gi bytes. */ +#define MAX_DEPTH 8 +#define RR_CE_SIZE 28 /* SUSP "CE" extension size */ + +#define FILE_FLAG_EXISTENCE 0x01 +#define FILE_FLAG_DIRECTORY 0x02 +#define FILE_FLAG_ASSOCIATED 0x04 +#define FILE_FLAG_RECORD 0x08 +#define FILE_FLAG_PROTECTION 0x10 +#define FILE_FLAG_MULTI_EXTENT 0x80 + +static const char rrip_identifier[] = + "RRIP_1991A"; +static const char rrip_descriptor[] = + "THE ROCK RIDGE INTERCHANGE PROTOCOL PROVIDES SUPPORT FOR " + "POSIX FILE SYSTEM SEMANTICS"; +static const char rrip_source[] = + "PLEASE CONTACT DISC PUBLISHER FOR SPECIFICATION SOURCE. " + "SEE PUBLISHER IDENTIFIER IN PRIMARY VOLUME DESCRIPTOR FOR " + "CONTACT INFORMATION."; +#define RRIP_ER_ID_SIZE (sizeof(rrip_identifier)-1) +#define RRIP_ER_DSC_SIZE (sizeof(rrip_descriptor)-1) +#define RRIP_ER_SRC_SIZE (sizeof(rrip_source)-1) +#define RRIP_ER_SIZE (8 + RRIP_ER_ID_SIZE + \ + RRIP_ER_DSC_SIZE + RRIP_ER_SRC_SIZE) + +static const unsigned char zisofs_magic[8] = { + 0x37, 0xE4, 0x53, 0x96, 0xC9, 0xDB, 0xD6, 0x07 +}; + +#define ZF_HEADER_SIZE 16 /* zisofs header size. */ +#define ZF_LOG2_BS 15 /* log2 block size; 32K bytes. */ +#define ZF_BLOCK_SIZE (1UL << ZF_LOG2_BS) + +/* + * Manage extra records. + */ +struct extr_rec { + int location; + int offset; + unsigned char buf[LOGICAL_BLOCK_SIZE]; + struct extr_rec *next; +}; + +struct ctl_extr_rec { + int use_extr; + unsigned char *bp; + struct isoent *isoent; + unsigned char *ce_ptr; + int cur_len; + int dr_len; + int limit; + int extr_off; + int extr_loc; +}; +#define DR_SAFETY RR_CE_SIZE +#define DR_LIMIT (254 - DR_SAFETY) + +/* + * The relation of struct isofile and isoent and archive_entry. + * + * Primary volume tree --> struct isoent + * | + * v + * struct isofile --> archive_entry + * ^ + * | + * Joliet volume tree --> struct isoent + * + * struct isoent has specific information for volume. + */ + +struct isofile { + /* Used for managing struct isofile list. */ + struct isofile *allnext; + struct isofile *datanext; + /* Used for managing a hardlined struct isofile list. */ + struct isofile *hlnext; + struct isofile *hardlink_target; + + struct archive_entry *entry; + + /* + * Used for making a directory tree. + */ + struct archive_string parentdir; + struct archive_string basename; + struct archive_string basename_utf16; + struct archive_string symlink; + int dircnt; /* The number of elements of + * its parent directory */ + + /* + * Used for a Directory Record. + */ + struct content { + int64_t offset_of_temp; + int64_t size; + int blocks; + uint32_t location; + /* + * One extent equals one content. + * If this entry has multi extent, `next' variable points + * next content data. + */ + struct content *next; /* next content */ + } content, *cur_content; + int write_content; + + enum { + NO = 0, + BOOT_CATALOG, + BOOT_IMAGE, + } boot; + + /* + * Used for a zisofs. + */ + struct { + unsigned char header_size; + unsigned char log2_bs; + uint32_t uncompressed_size; + } zisofs; +}; + +struct isoent { + /* Keep `rbnode' at the first member of struct isoent. */ + struct archive_rb_node rbnode; + + struct isofile *file; + + struct isoent *parent; + /* A list of children.(use chnext) */ + struct { + struct isoent *first; + struct isoent **last; + int cnt; + } children; + struct archive_rb_tree rbtree; + + /* A list of sub directories.(use drnext) */ + struct { + struct isoent *first; + struct isoent **last; + int cnt; + } subdirs; + /* A sorted list of sub directories. */ + struct isoent **children_sorted; + /* Used for managing struct isoent list. */ + struct isoent *chnext; + struct isoent *drnext; + struct isoent *ptnext; + + /* + * Used for making a Directory Record. + */ + int dir_number; + struct { + int vd; + int self; + int parent; + int normal; + } dr_len; + uint32_t dir_location; + int dir_block; + + /* + * Identifier: + * on primary, ISO9660 file/directory name. + * on joliet, UCS2 file/directory name. + * ext_off : offset of identifier extension. + * ext_len : length of identifier extension. + * id_len : byte size of identifier. + * on primary, this is ext_off + ext_len + version length. + * on joliet, this is ext_off + ext_len. + * mb_len : length of multibyte-character of identifier. + * on primary, mb_len and id_len are always the same. + * on joliet, mb_len and id_len are different. + */ + char *identifier; + int ext_off; + int ext_len; + int id_len; + int mb_len; + + /* + * Used for making a Rockridge extension. + * This is a part of Directory Records. + */ + struct isoent *rr_parent; + struct isoent *rr_child; + + /* Extra Record.(which we call in this source file) + * A maximum size of the Directory Record is 254. + * so, if generated RRIP data of a file cannot into a Directory + * Record because of its size, that surplus data relocate this + * Extra Record. + */ + struct { + struct extr_rec *first; + struct extr_rec **last; + struct extr_rec *current; + } extr_rec_list; + + int virtual:1; + /* If set to one, this file type is a directory. + * A convenience flag to be used as + * "archive_entry_filetype(isoent->file->entry) == AE_IFDIR". + */ + int dir:1; +}; + +struct hardlink { + struct archive_rb_node rbnode; + int nlink; + struct { + struct isofile *first; + struct isofile **last; + } file_list; +}; + +/* + * ISO writer options + */ +struct iso_option { + /* + * Usage : abstract-file= + * Type : string, max 37 bytes + * Default: Not specified + * COMPAT : mkisofs -abstract + * + * Specifies Abstract Filename. + * This file shall be described in the Root Directory + * and containing a abstract statement. + */ + unsigned int abstract_file:1; +#define OPT_ABSTRACT_FILE_DEFAULT 0 /* Not specified */ +#define ABSTRACT_FILE_SIZE 37 + + /* + * Usage : application-id= + * Type : string, max 128 bytes + * Default: Not specified + * COMPAT : mkisofs -A/-appid . + * + * Specifies Application Identifier. + * If the first byte is set to '_'(5F), the remaining + * bytes of this option shall specify an identifier + * for a file containing the identification of the + * application. + * This file shall be described in the Root Directory. + */ + unsigned int application_id:1; +#define OPT_APPLICATION_ID_DEFAULT 0 /* Use default identifier */ +#define APPLICATION_IDENTIFIER_SIZE 128 + + /* + * Usage : !allow-vernum + * Type : boolean + * Default: Enabled + * : Violates the ISO9660 standard if disable. + * COMPAT: mkisofs -N + * + * Allow filenames to use version numbers. + */ + unsigned int allow_vernum:1; +#define OPT_ALLOW_VERNUM_DEFAULT 1 /* Enabled */ + + /* + * Usage : biblio-file= + * Type : string, max 37 bytes + * Default: Not specified + * COMPAT : mkisofs -biblio + * + * Specifies Bibliographic Filename. + * This file shall be described in the Root Directory + * and containing bibliographic records. + */ + unsigned int biblio_file:1; +#define OPT_BIBLIO_FILE_DEFAULT 0 /* Not specified */ +#define BIBLIO_FILE_SIZE 37 + + /* + * Usage : boot= + * Type : string + * Default: Not specified + * COMPAT : mkisofs -b/-eltorito-boot + * + * Specifies "El Torito" boot image file to make + * a bootable CD. + */ + unsigned int boot:1; +#define OPT_BOOT_DEFAULT 0 /* Not specified */ + + /* + * Usage : boot-catalog= + * Type : string + * Default: "boot.catalog" + * COMPAT : mkisofs -c/-eltorito-catalog + * + * Specifies a fullpath of El Torito boot catalog. + */ + unsigned int boot_catalog:1; +#define OPT_BOOT_CATALOG_DEFAULT 0 /* Not specified */ + + /* + * Usage : boot-info-table + * Type : boolean + * Default: Disabled + * COMPAT : mkisofs -boot-info-table + * + * Modify the boot image file specified by `boot' + * option; ISO writer stores boot file information + * into the boot file in ISO image at offset 8 + * through offset 64. + */ + unsigned int boot_info_table:1; +#define OPT_BOOT_INFO_TABLE_DEFAULT 0 /* Disabled */ + + /* + * Usage : boot-load-seg= + * Type : hexadecimal + * Default: Not specified + * COMPAT : mkisofs -boot-load-seg + * + * Specifies a load segment for boot image. + * This is used with no-emulation mode. + */ + unsigned int boot_load_seg:1; +#define OPT_BOOT_LOAD_SEG_DEFAULT 0 /* Not specified */ + + /* + * Usage : boot-load-size= + * Type : decimal + * Default: Not specified + * COMPAT : mkisofs -boot-load-size + * + * Specifies a sector count for boot image. + * This is used with no-emulation mode. + */ + unsigned int boot_load_size:1; +#define OPT_BOOT_LOAD_SIZE_DEFAULT 0 /* Not specified */ + + /* + * Usage : boot-type= + * : 'no-emulation' : 'no emulation' image + * : 'fd' : floppy disk image + * : 'hard-disk' : hard disk image + * Type : string + * Default: Auto detect + * : We check a size of boot image; + * : If ths size is just 1.22M/1.44M/2.88M, + * : we assume boot_type is 'fd'; + * : otherwise boot_type is 'no-emulation'. + * COMPAT : + * boot=no-emulation + * mkisofs -no-emul-boot + * boot=fd + * This is a default on the mkisofs. + * boot=hard-disk + * mkisofs -hard-disk-boot + * + * Specifies a type of "El Torito" boot image. + */ + unsigned int boot_type:2; +#define OPT_BOOT_TYPE_AUTO 0 /* auto detect */ +#define OPT_BOOT_TYPE_NO_EMU 1 /* ``no emulation'' image */ +#define OPT_BOOT_TYPE_FD 2 /* floppy disk image */ +#define OPT_BOOT_TYPE_HARD_DISK 3 /* hard disk image */ +#define OPT_BOOT_TYPE_DEFAULT OPT_BOOT_TYPE_AUTO + + /* + * Usage : compression-level= + * Type : decimal + * Default: Not specified + * COMPAT : NONE + * + * Specifies compression level for option zisofs=direct. + */ + unsigned int compression_level:1; +#define OPT_COMPRESSION_LEVEL_DEFAULT 0 /* Not specified */ + + /* + * Usage : copyright-file= + * Type : string, max 37 bytes + * Default: Not specified + * COMPAT : mkisofs -copyright + * + * Specifies Copyright Filename. + * This file shall be described in the Root Directory + * and containing a copyright statement. + */ + unsigned int copyright_file:1; +#define OPT_COPYRIGHT_FILE_DEFAULT 0 /* Not specified */ +#define COPYRIGHT_FILE_SIZE 37 + + /* + * Usage : gid= + * Type : decimal + * Default: Not specified + * COMPAT : mkisofs -gid + * + * Specifies a group id to rewrite the group id of all files. + */ + unsigned int gid:1; +#define OPT_GID_DEFAULT 0 /* Not specified */ + + /* + * Usage : iso-level=[1234] + * Type : decimal + * Default: 1 + * COMPAT : mkisofs -iso-level + * + * Specifies ISO9600 Level. + * Level 1: [DEFAULT] + * - limits each file size less than 4Gi bytes; + * - a File Name shall not contain more than eight + * d-characters or eight d1-characters; + * - a File Name Extension shall not contain more than + * three d-characters or three d1-characters; + * - a Directory Identifier shall not contain more + * than eight d-characters or eight d1-characters. + * Level 2: + * - limits each file size less than 4Giga bytes; + * - a File Name shall not contain more than thirty + * d-characters or thirty d1-characters; + * - a File Name Extension shall not contain more than + * thirty d-characters or thirty d1-characters; + * - a Directory Identifier shall not contain more + * than thirty-one d-characters or thirty-one + * d1-characters. + * Level 3: + * - no limit of file size; use multi extent. + * Level 4: + * - this level 4 simulates mkisofs option + * '-iso-level 4'; + * - crate a enhanced volume as mkisofs doing; + * - allow a File Name to have leading dot; + * - allow a File Name to have all ASCII letters; + * - allow a File Name to have multiple dots; + * - allow more then 8 depths of directory trees; + * - disable a version number to a File Name; + * - disable a forced period to the tail of a File Name; + * - the maxinum length of files and directories is raised to 193. + * if rockridge option is disabled, raised to 207. + */ + unsigned int iso_level:3; +#define OPT_ISO_LEVEL_DEFAULT 1 /* ISO Level 1 */ + + /* + * Usage : joliet[=long] + * : !joliet + * : Do not generate Joliet Volume and Records. + * : joliet [DEFAULT] + * : Generates Joliet Volume and Directory Records. + * : [COMPAT: mkisofs -J/-joliet] + * : joliet=long + * : The joliet filenames are up to 103 Unicode + * : characters. + * : This option breaks the Joliet specification. + * : [COMPAT: mkisofs -J -joliet-long] + * Type : boolean/string + * Default: Enabled + * COMPAT : mkisofs -J / -joliet-long + * + * Generates Joliet Volume and Directory Records. + */ + unsigned int joliet:2; +#define OPT_JOLIET_DISABLE 0 /* Not generate Joliet Records. */ +#define OPT_JOLIET_ENABLE 1 /* Generate Joliet Records. */ +#define OPT_JOLIET_LONGNAME 2 /* Use long joliet filenames.*/ +#define OPT_JOLIET_DEFAULT OPT_JOLIET_ENABLE + + /* + * Usage : !limit-depth + * Type : boolean + * Default: Enabled + * : Violates the ISO9660 standard if disable. + * COMPAT : mkisofs -D/-disable-deep-relocation + * + * The number of levels in hierarchy cannot exceed eight. + */ + unsigned int limit_depth:1; +#define OPT_LIMIT_DEPTH_DEFAULT 1 /* Enabled */ + + /* + * Usage : !limit-dirs + * Type : boolean + * Default: Enabled + * : Violates the ISO9660 standard if disable. + * COMPAT : mkisofs -no-limit-pathtables + * + * Limits the number of directories less than 65536 due + * to the size of the Parent Directory Number of Path + * Table. + */ + unsigned int limit_dirs:1; +#define OPT_LIMIT_DIRS_DEFAULT 1 /* Enabled */ + + /* + * Usage : !pad + * Type : boolean + * Default: Enabled + * COMPAT : -pad/-no-pad + * + * Pads the end of the ISO image by null of 300Ki bytes. + */ + unsigned int pad:1; +#define OPT_PAD_DEFAULT 1 /* Enabled */ + + /* + * Usage : publisher= + * Type : string, max 128 bytes + * Default: Not specified + * COMPAT : mkisofs -publisher + * + * Specifies Publisher Identifier. + * If the first byte is set to '_'(5F), the remaining + * bytes of this option shall specify an identifier + * for a file containing the identification of the user. + * This file shall be described in the Root Directory. + */ + unsigned int publisher:1; +#define OPT_PUBLISHER_DEFAULT 0 /* Not specified */ +#define PUBLISHER_IDENTIFIER_SIZE 128 + + /* + * Usage : rockridge + * : !rockridge + * : disable to generate SUSP and RR records. + * : rockridge + * : the same as 'rockridge=useful'. + * : rockridge=strict + * : generate SUSP and RR records. + * : [COMPAT: mkisofs -R] + * : rockridge=useful [DEFAULT] + * : generate SUSP and RR records. + * : [COMPAT: mkisofs -r] + * : NOTE Our rockridge=useful option does not set a zero + * : to uid and gid, you should use application + * : option such as --gid,--gname,--uid and --uname + * : badtar options instead. + * Type : boolean/string + * Default: Enabled as rockridge=useful + * COMPAT : mkisofs -r / -R + * + * Generates SUSP and RR records. + */ + unsigned int rr:2; +#define OPT_RR_DISABLED 0 +#define OPT_RR_STRICT 1 +#define OPT_RR_USEFUL 2 +#define OPT_RR_DEFAULT OPT_RR_USEFUL + + /* + * Usage : volume-id= + * Type : string, max 32 bytes + * Default: Not specified + * COMPAT : mkisofs -V + * + * Specifies Volume Identifier. + */ + unsigned int volume_id:1; +#define OPT_VOLUME_ID_DEFAULT 0 /* Use default identifier */ +#define VOLUME_IDENTIFIER_SIZE 32 + + /* + * Usage : !zisofs [DEFAULT] + * : Disable to generate RRIP 'ZF' extension. + * : zisofs + * : Make files zisofs file and generate RRIP 'ZF' + * : extension. So you do not need mkzftree utility + * : for making zisofs. + * : When the file size is less than one Logical Block + * : size, that file will not zisofs'ed since it does + * : reduece an ISO-image size. + * : + * : When you specify option 'boot=', that + * : 'boot-image' file won't be converted to zisofs file. + * Type : boolean + * Default: Disabled + * + * Generates RRIP 'ZF' System Use Entry. + */ + unsigned int zisofs:1; +#define OPT_ZISOFS_DISABLED 0 +#define OPT_ZISOFS_DIRECT 1 +#define OPT_ZISOFS_DEFAULT OPT_ZISOFS_DISABLED + +}; + +struct iso9660 { + /* The creation time of ISO image. */ + time_t birth_time; + /* A file stream of a temporary file, which file contents + * save to until ISO iamge can be created. */ + int temp_fd; + + struct isofile *cur_file; + struct isoent *cur_dirent; + struct archive_string cur_dirstr; + uint64_t bytes_remaining; + int need_multi_extent; + + /* Temporary string buffer for Joliet extension. */ + struct archive_string utf16be; + struct archive_string mbs; + + struct archive_string_conv *sconv_to_utf16be; + struct archive_string_conv *sconv_from_utf16be; + + /* A list of all of struct isofile entries. */ + struct { + struct isofile *first; + struct isofile **last; + } all_file_list; + + /* A list of struct isofile entries which have its + * contents and are not a directory, a hardlined file + * and a symlink file. */ + struct { + struct isofile *first; + struct isofile **last; + } data_file_list; + + /* Used for managing to find hardlinking files. */ + struct archive_rb_tree hardlink_rbtree; + + /* Used for making the Path Table Record. */ + struct vdd { + /* the root of entry tree. */ + struct isoent *rootent; + enum vdd_type { + VDD_PRIMARY, + VDD_JOLIET, + VDD_ENHANCED + } vdd_type; + + struct path_table { + struct isoent *first; + struct isoent **last; + struct isoent **sorted; + int cnt; + } *pathtbl; + int max_depth; + + int path_table_block; + int path_table_size; + int location_type_L_path_table; + int location_type_M_path_table; + int total_dir_block; + } primary, joliet; + + /* Used for making a Volume Descriptor. */ + int volume_space_size; + int volume_sequence_number; + int total_file_block; + struct archive_string volume_identifier; + struct archive_string publisher_identifier; + struct archive_string data_preparer_identifier; + struct archive_string application_identifier; + struct archive_string copyright_file_identifier; + struct archive_string abstract_file_identifier; + struct archive_string bibliographic_file_identifier; + + /* Used for making rockridge extensions. */ + int location_rrip_er; + + /* Used for making zisofs. */ + struct { + int detect_magic:1; + int making:1; + int allzero:1; + unsigned char magic_buffer[64]; + int magic_cnt; + +#ifdef HAVE_ZLIB_H + /* + * Copy a compressed file to iso9660.zisofs.temp_fd + * and also copy a uncompressed file(original file) to + * iso9660.temp_fd . If the number of logical block + * of the compressed file is less than the number of + * logical block of the uncompressed file, use it and + * remove the copy of the uncompressed file. + * but if not, we use uncompressed file and remove + * the copy of the compressed file. + */ + uint32_t *block_pointers; + size_t block_pointers_allocated; + int block_pointers_cnt; + int block_pointers_idx; + int64_t total_size; + int64_t block_offset; + + z_stream stream; + int stream_valid; + int64_t remaining; + int compression_level; +#endif + } zisofs; + + struct isoent *directories_too_deep; + int dircnt_max; + + /* Write buffer. */ +#define wb_buffmax() (LOGICAL_BLOCK_SIZE * 32) +#define wb_remaining(a) (((struct iso9660 *)(a)->format_data)->wbuff_remaining) +#define wb_offset(a) (((struct iso9660 *)(a)->format_data)->wbuff_offset \ + + wb_buffmax() - wb_remaining(a)) + unsigned char wbuff[LOGICAL_BLOCK_SIZE * 32]; + size_t wbuff_remaining; + enum { + WB_TO_STREAM, + WB_TO_TEMP + } wbuff_type; + int64_t wbuff_offset; + int64_t wbuff_written; + int64_t wbuff_tail; + + /* 'El Torito' boot data. */ + struct { + /* boot catalog file */ + struct archive_string catalog_filename; + struct isoent *catalog; + /* boot image file */ + struct archive_string boot_filename; + struct isoent *boot; + + unsigned char platform_id; +#define BOOT_PLATFORM_X86 0 +#define BOOT_PLATFORM_PPC 1 +#define BOOT_PLATFORM_MAC 2 + struct archive_string id; + unsigned char media_type; +#define BOOT_MEDIA_NO_EMULATION 0 +#define BOOT_MEDIA_1_2M_DISKETTE 1 +#define BOOT_MEDIA_1_44M_DISKETTE 2 +#define BOOT_MEDIA_2_88M_DISKETTE 3 +#define BOOT_MEDIA_HARD_DISK 4 + unsigned char system_type; + uint16_t boot_load_seg; + uint16_t boot_load_size; +#define BOOT_LOAD_SIZE 4 + } el_torito; + + struct iso_option opt; +}; + +/* + * Types of Volume Descriptor + */ +enum VD_type { + VDT_BOOT_RECORD=0, /* Boot Record Volume Descriptor */ + VDT_PRIMARY=1, /* Primary Volume Descriptor */ + VDT_SUPPLEMENTARY=2, /* Supplementary Volume Descriptor */ + VDT_TERMINATOR=255 /* Volume Descriptor Set Terminator */ +}; + +/* + * Types of Directory Record + */ +enum dir_rec_type { + DIR_REC_VD, /* Stored in Volume Descriptor. */ + DIR_REC_SELF, /* Stored as Current Directory. */ + DIR_REC_PARENT, /* Stored as Parent Directory. */ + DIR_REC_NORMAL, /* Stored as Child. */ +}; + +/* + * Kinds of Volume Descriptor Character + */ +enum vdc { + VDC_STD, + VDC_LOWERCASE, + VDC_UCS2, + VDC_UCS2_DIRECT, +}; + +/* + * IDentifier Resolver. + * Used for resolving duplicated filenames. + */ +struct idr { + struct idrent { + struct archive_rb_node rbnode; + /* Used in wait_list. */ + struct idrent *wnext; + struct idrent *avail; + + struct isoent *isoent; + int weight; + int noff; + int rename_num; + } *idrent_pool; + + struct archive_rb_tree rbtree; + + struct { + struct idrent *first; + struct idrent **last; + } wait_list; + + int pool_size; + int pool_idx; + int num_size; + int null_size; + + char char_map[0x80]; +}; + +enum char_type { + A_CHAR, + D_CHAR, +}; + + +static int iso9660_options(struct archive_write *, + const char *, const char *); +static int iso9660_write_header(struct archive_write *, + struct archive_entry *); +static ssize_t iso9660_write_data(struct archive_write *, + const void *, size_t); +static int iso9660_finish_entry(struct archive_write *); +static int iso9660_close(struct archive_write *); +static int iso9660_free(struct archive_write *); + +static void get_system_identitier(char *, size_t); +static void set_str(unsigned char *, const char *, size_t, char, + const char *); +static inline int joliet_allowed_char(unsigned char, unsigned char); +static int set_str_utf16be(struct archive_write *, unsigned char *, + const char *, size_t, uint16_t, enum vdc); +static int set_str_a_characters_bp(struct archive_write *, + unsigned char *, int, int, const char *, enum vdc); +static int set_str_d_characters_bp(struct archive_write *, + unsigned char *, int, int, const char *, enum vdc); +static void set_VD_bp(unsigned char *, enum VD_type, unsigned char); +static inline void set_unused_field_bp(unsigned char *, int, int); + +static unsigned char *extra_open_record(unsigned char *, int, + struct isoent *, struct ctl_extr_rec *); +static void extra_close_record(struct ctl_extr_rec *, int); +static unsigned char * extra_next_record(struct ctl_extr_rec *, int); +static unsigned char *extra_get_record(struct isoent *, int *, int *, int *); +static void extra_tell_used_size(struct ctl_extr_rec *, int); +static int extra_setup_location(struct isoent *, int); +static int set_directory_record_rr(unsigned char *, int, + struct isoent *, struct iso9660 *, enum dir_rec_type); +static int set_directory_record(unsigned char *, size_t, + struct isoent *, struct iso9660 *, enum dir_rec_type, + enum vdd_type); +static inline int get_dir_rec_size(struct iso9660 *, struct isoent *, + enum dir_rec_type, enum vdd_type); +static inline unsigned char *wb_buffptr(struct archive_write *); +static int wb_write_out(struct archive_write *); +static int wb_consume(struct archive_write *, size_t); +#ifdef HAVE_ZLIB_H +static int wb_set_offset(struct archive_write *, int64_t); +#endif +static int write_null(struct archive_write *, size_t); +static int write_VD_terminator(struct archive_write *); +static int set_file_identifier(unsigned char *, int, int, enum vdc, + struct archive_write *, struct vdd *, + struct archive_string *, const char *, int, + enum char_type); +static int write_VD(struct archive_write *, struct vdd *); +static int write_VD_boot_record(struct archive_write *); +static int write_information_block(struct archive_write *); +static int write_path_table(struct archive_write *, int, + struct vdd *); +static int write_directory_descriptors(struct archive_write *, + struct vdd *); +static int write_file_descriptors(struct archive_write *); +static int write_rr_ER(struct archive_write *); +static void calculate_path_table_size(struct vdd *); + +static void isofile_init_entry_list(struct iso9660 *); +static void isofile_add_entry(struct iso9660 *, struct isofile *); +static void isofile_free_all_entries(struct iso9660 *); +static void isofile_init_entry_data_file_list(struct iso9660 *); +static void isofile_add_data_file(struct iso9660 *, struct isofile *); +static struct isofile * isofile_new(struct archive_write *, + struct archive_entry *); +static void isofile_free(struct isofile *); +static int isofile_gen_utility_names(struct archive_write *, + struct isofile *); +static int isofile_register_hardlink(struct archive_write *, + struct isofile *); +static void isofile_connect_hardlink_files(struct iso9660 *); +static void isofile_init_hardlinks(struct iso9660 *); +static void isofile_free_hardlinks(struct iso9660 *); + +static struct isoent *isoent_new(struct isofile *); +static int isoent_clone_tree(struct archive_write *, + struct isoent **, struct isoent *); +static void _isoent_free(struct isoent *isoent); +static void isoent_free_all(struct isoent *); +static struct isoent * isoent_create_virtual_dir(struct archive_write *, + struct iso9660 *, const char *); +static int isoent_cmp_node(const struct archive_rb_node *, + const struct archive_rb_node *); +static int isoent_cmp_key(const struct archive_rb_node *, + const void *); +static int isoent_add_child_head(struct isoent *, struct isoent *); +static int isoent_add_child_tail(struct isoent *, struct isoent *); +static void isoent_remove_child(struct isoent *, struct isoent *); +static void isoent_setup_directory_location(struct iso9660 *, + int, struct vdd *); +static void isoent_setup_file_location(struct iso9660 *, int); +static int get_path_component(char *, int, const char *); +static int isoent_tree(struct archive_write *, struct isoent **); +static struct isoent *isoent_find_child(struct isoent *, const char *); +static struct isoent *isoent_find_entry(struct isoent *, const char *); +static void idr_relaxed_filenames(char *); +static void idr_init(struct iso9660 *, struct vdd *, struct idr *); +static void idr_cleanup(struct idr *); +static int idr_ensure_poolsize(struct archive_write *, struct idr *, + int); +static int idr_start(struct archive_write *, struct idr *, + int, int, int, int, const struct archive_rb_tree_ops *); +static void idr_register(struct idr *, struct isoent *, int, + int); +static void idr_extend_identifier(struct idrent *, int, int); +static void idr_resolve(struct idr *, void (*)(unsigned char *, int)); +static void idr_set_num(unsigned char *, int); +static void idr_set_num_beutf16(unsigned char *, int); +static int isoent_gen_iso9660_identifier(struct archive_write *, + struct isoent *, struct idr *); +static int isoent_gen_joliet_identifier(struct archive_write *, + struct isoent *, struct idr *); +static int isoent_cmp_iso9660_identifier(const struct isoent *, + const struct isoent *); +static int isoent_cmp_node_iso9660(const struct archive_rb_node *, + const struct archive_rb_node *); +static int isoent_cmp_key_iso9660(const struct archive_rb_node *, + const void *); +static int isoent_cmp_joliet_identifier(const struct isoent *, + const struct isoent *); +static int isoent_cmp_node_joliet(const struct archive_rb_node *, + const struct archive_rb_node *); +static int isoent_cmp_key_joliet(const struct archive_rb_node *, + const void *); +static inline void path_table_add_entry(struct path_table *, struct isoent *); +static inline struct isoent * path_table_last_entry(struct path_table *); +static int isoent_make_path_table(struct archive_write *); +static int isoent_find_out_boot_file(struct archive_write *, + struct isoent *); +static int isoent_create_boot_catalog(struct archive_write *, + struct isoent *); +static size_t fd_boot_image_size(int); +static int make_boot_catalog(struct archive_write *); +static int setup_boot_information(struct archive_write *); + +static int zisofs_init(struct archive_write *, struct isofile *); +static void zisofs_detect_magic(struct archive_write *, + const void *, size_t); +static int zisofs_write_to_temp(struct archive_write *, + const void *, size_t); +static int zisofs_finish_entry(struct archive_write *); +static int zisofs_rewind_boot_file(struct archive_write *); +static int zisofs_free(struct archive_write *); + +int +archive_write_set_format_iso9660(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct iso9660 *iso9660; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_iso9660"); + + /* If another format was already registered, unregister it. */ + if (a->format_free != NULL) + (a->format_free)(a); + + iso9660 = calloc(1, sizeof(*iso9660)); + if (iso9660 == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate iso9660 data"); + return (ARCHIVE_FATAL); + } + iso9660->birth_time = 0; + iso9660->temp_fd = -1; + iso9660->cur_file = NULL; + iso9660->primary.max_depth = 0; + iso9660->primary.vdd_type = VDD_PRIMARY; + iso9660->primary.pathtbl = NULL; + iso9660->joliet.rootent = NULL; + iso9660->joliet.max_depth = 0; + iso9660->joliet.vdd_type = VDD_JOLIET; + iso9660->joliet.pathtbl = NULL; + isofile_init_entry_list(iso9660); + isofile_init_entry_data_file_list(iso9660); + isofile_init_hardlinks(iso9660); + iso9660->directories_too_deep = NULL; + iso9660->dircnt_max = 1; + iso9660->wbuff_remaining = wb_buffmax(); + iso9660->wbuff_type = WB_TO_TEMP; + iso9660->wbuff_offset = 0; + iso9660->wbuff_written = 0; + iso9660->wbuff_tail = 0; + archive_string_init(&(iso9660->utf16be)); + archive_string_init(&(iso9660->mbs)); + + /* + * Init Identifiers used for PVD and SVD. + */ + archive_string_init(&(iso9660->volume_identifier)); + archive_strcpy(&(iso9660->volume_identifier), "CDROM"); + archive_string_init(&(iso9660->publisher_identifier)); + archive_string_init(&(iso9660->data_preparer_identifier)); + archive_string_init(&(iso9660->application_identifier)); + archive_strcpy(&(iso9660->application_identifier), + archive_version_string()); + archive_string_init(&(iso9660->copyright_file_identifier)); + archive_string_init(&(iso9660->abstract_file_identifier)); + archive_string_init(&(iso9660->bibliographic_file_identifier)); + + /* + * Init El Torito bootable CD variables. + */ + archive_string_init(&(iso9660->el_torito.catalog_filename)); + iso9660->el_torito.catalog = NULL; + /* Set default file name of boot catalog */ + archive_strcpy(&(iso9660->el_torito.catalog_filename), + "boot.catalog"); + archive_string_init(&(iso9660->el_torito.boot_filename)); + iso9660->el_torito.boot = NULL; + iso9660->el_torito.platform_id = BOOT_PLATFORM_X86; + archive_string_init(&(iso9660->el_torito.id)); + iso9660->el_torito.boot_load_seg = 0; + iso9660->el_torito.boot_load_size = BOOT_LOAD_SIZE; + + /* + * Init zisofs variables. + */ +#ifdef HAVE_ZLIB_H + iso9660->zisofs.block_pointers = NULL; + iso9660->zisofs.block_pointers_allocated = 0; + iso9660->zisofs.stream_valid = 0; + iso9660->zisofs.compression_level = 9; + memset(&(iso9660->zisofs.stream), 0, + sizeof(iso9660->zisofs.stream)); +#endif + + /* + * Set default value of iso9660 options. + */ + iso9660->opt.abstract_file = OPT_ABSTRACT_FILE_DEFAULT; + iso9660->opt.application_id = OPT_APPLICATION_ID_DEFAULT; + iso9660->opt.allow_vernum = OPT_ALLOW_VERNUM_DEFAULT; + iso9660->opt.biblio_file = OPT_BIBLIO_FILE_DEFAULT; + iso9660->opt.boot = OPT_BOOT_DEFAULT; + iso9660->opt.boot_catalog = OPT_BOOT_CATALOG_DEFAULT; + iso9660->opt.boot_info_table = OPT_BOOT_INFO_TABLE_DEFAULT; + iso9660->opt.boot_load_seg = OPT_BOOT_LOAD_SEG_DEFAULT; + iso9660->opt.boot_load_size = OPT_BOOT_LOAD_SIZE_DEFAULT; + iso9660->opt.boot_type = OPT_BOOT_TYPE_DEFAULT; + iso9660->opt.compression_level = OPT_COMPRESSION_LEVEL_DEFAULT; + iso9660->opt.copyright_file = OPT_COPYRIGHT_FILE_DEFAULT; + iso9660->opt.iso_level = OPT_ISO_LEVEL_DEFAULT; + iso9660->opt.joliet = OPT_JOLIET_DEFAULT; + iso9660->opt.limit_depth = OPT_LIMIT_DEPTH_DEFAULT; + iso9660->opt.limit_dirs = OPT_LIMIT_DIRS_DEFAULT; + iso9660->opt.pad = OPT_PAD_DEFAULT; + iso9660->opt.publisher = OPT_PUBLISHER_DEFAULT; + iso9660->opt.rr = OPT_RR_DEFAULT; + iso9660->opt.volume_id = OPT_VOLUME_ID_DEFAULT; + iso9660->opt.zisofs = OPT_ZISOFS_DEFAULT; + + /* Create the root directory. */ + iso9660->primary.rootent = + isoent_create_virtual_dir(a, iso9660, ""); + if (iso9660->primary.rootent == NULL) { + free(iso9660); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + iso9660->primary.rootent->parent = iso9660->primary.rootent; + iso9660->cur_dirent = iso9660->primary.rootent; + archive_string_init(&(iso9660->cur_dirstr)); + archive_string_ensure(&(iso9660->cur_dirstr), 1); + iso9660->cur_dirstr.s[0] = 0; + iso9660->sconv_to_utf16be = NULL; + iso9660->sconv_from_utf16be = NULL; + + a->format_data = iso9660; + a->format_name = "iso9660"; + a->format_options = iso9660_options; + a->format_write_header = iso9660_write_header; + a->format_write_data = iso9660_write_data; + a->format_finish_entry = iso9660_finish_entry; + a->format_close = iso9660_close; + a->format_free = iso9660_free; + a->archive.archive_format = ARCHIVE_FORMAT_ISO9660; + a->archive.archive_format_name = "ISO9660"; + + return (ARCHIVE_OK); +} + +static int +get_str_opt(struct archive_write *a, struct archive_string *s, + size_t maxsize, const char *key, const char *value) +{ + + if (strlen(value) > maxsize) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Value is longer than %zu characters " + "for option ``%s''", maxsize, key); + return (ARCHIVE_FATAL); + } + archive_strcpy(s, value); + return (ARCHIVE_OK); +} + +static int +get_num_opt(struct archive_write *a, int *num, int high, int low, + const char *key, const char *value) +{ + const char *p = value; + int data = 0; + int neg = 0; + + if (p == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid value(empty) for option ``%s''", key); + return (ARCHIVE_FATAL); + } + if (*p == '-') { + neg = 1; + p++; + } + while (*p) { + if (*p >= '0' && *p <= '9') + data = data * 10 + *p - '0'; + else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid value for option ``%s''", key); + return (ARCHIVE_FATAL); + } + if (data > high) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid value(over %d) for " + "option ``%s''", high, key); + return (ARCHIVE_FATAL); + } + if (data < low) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid value(under %d) for " + "option ``%s''", low, key); + return (ARCHIVE_FATAL); + } + p++; + } + if (neg) + data *= -1; + *num = data; + + return (ARCHIVE_OK); +} + +static int +iso9660_options(struct archive_write *a, const char *key, const char *value) +{ + struct iso9660 *iso9660 = a->format_data; + const char *p; + int r; + + switch (key[0]) { + case 'a': + if (strcmp(key, "abstract-file") == 0) { + r = get_str_opt(a, + &(iso9660->abstract_file_identifier), + ABSTRACT_FILE_SIZE, key, value); + iso9660->opt.abstract_file = r == ARCHIVE_OK; + return (r); + } + if (strcmp(key, "application-id") == 0) { + r = get_str_opt(a, + &(iso9660->application_identifier), + APPLICATION_IDENTIFIER_SIZE, key, value); + iso9660->opt.application_id = r == ARCHIVE_OK; + return (r); + } + if (strcmp(key, "allow-vernum") == 0) { + iso9660->opt.allow_vernum = value != NULL; + return (ARCHIVE_OK); + } + break; + case 'b': + if (strcmp(key, "biblio-file") == 0) { + r = get_str_opt(a, + &(iso9660->bibliographic_file_identifier), + BIBLIO_FILE_SIZE, key, value); + iso9660->opt.biblio_file = r == ARCHIVE_OK; + return (r); + } + if (strcmp(key, "boot") == 0) { + if (value == NULL) + iso9660->opt.boot = 0; + else { + iso9660->opt.boot = 1; + archive_strcpy( + &(iso9660->el_torito.boot_filename), + value); + } + return (ARCHIVE_OK); + } + if (strcmp(key, "boot-catalog") == 0) { + r = get_str_opt(a, + &(iso9660->el_torito.catalog_filename), + 1024, key, value); + iso9660->opt.boot_catalog = r == ARCHIVE_OK; + return (r); + } + if (strcmp(key, "boot-info-table") == 0) { + iso9660->opt.boot_info_table = value != NULL; + return (ARCHIVE_OK); + } + if (strcmp(key, "boot-load-seg") == 0) { + uint32_t seg; + + iso9660->opt.boot_load_seg = 0; + if (value == NULL) + goto invalid_value; + seg = 0; + p = value; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) + p += 2; + while (*p) { + if (seg) + seg <<= 4; + if (*p >= 'A' && *p <= 'F') + seg += *p - 'A' + 0x0a; + else if (*p >= 'a' && *p <= 'f') + seg += *p - 'a' + 0x0a; + else if (*p >= '0' && *p <= '9') + seg += *p - '0'; + else + goto invalid_value; + if (seg > 0xffff) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Invalid value(over 0xffff) for " + "option ``%s''", key); + return (ARCHIVE_FATAL); + } + p++; + } + iso9660->el_torito.boot_load_seg = (uint16_t)seg; + iso9660->opt.boot_load_seg = 1; + return (ARCHIVE_OK); + } + if (strcmp(key, "boot-load-size") == 0) { + int num = 0; + r = get_num_opt(a, &num, 0xffff, 1, key, value); + iso9660->opt.boot_load_size = r == ARCHIVE_OK; + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->el_torito.boot_load_size = (uint16_t)num; + return (ARCHIVE_OK); + } + if (strcmp(key, "boot-type") == 0) { + if (value == NULL) + goto invalid_value; + if (strcmp(value, "no-emulation") == 0) + iso9660->opt.boot_type = OPT_BOOT_TYPE_NO_EMU; + else if (strcmp(value, "fd") == 0) + iso9660->opt.boot_type = OPT_BOOT_TYPE_FD; + else if (strcmp(value, "hard-disk") == 0) + iso9660->opt.boot_type = OPT_BOOT_TYPE_HARD_DISK; + else + goto invalid_value; + return (ARCHIVE_OK); + } + break; + case 'c': + if (strcmp(key, "compression-level") == 0) { +#ifdef HAVE_ZLIB_H + if (value == NULL || + !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') + goto invalid_value; + iso9660->zisofs.compression_level = value[0] - '0'; + iso9660->opt.compression_level = 1; + return (ARCHIVE_OK); +#else + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Option ``%s'' " + "is not supported on this platform.", key); + return (ARCHIVE_FATAL); +#endif + } + if (strcmp(key, "copyright-file") == 0) { + r = get_str_opt(a, + &(iso9660->copyright_file_identifier), + COPYRIGHT_FILE_SIZE, key, value); + iso9660->opt.copyright_file = r == ARCHIVE_OK; + return (r); + } +#ifdef DEBUG + /* Specifies Volume creation date and time; + * year(4),month(2),day(2),hour(2),minute(2),second(2). + * e.g. "20090929033757" + */ + if (strcmp(key, "creation") == 0) { + struct tm tm; + char buf[5]; + + p = value; + if (p == NULL || strlen(p) < 14) + goto invalid_value; + memset(&tm, 0, sizeof(tm)); + memcpy(buf, p, 4); buf[4] = '\0'; p += 4; + tm.tm_year = strtol(buf, NULL, 10) - 1900; + memcpy(buf, p, 2); buf[2] = '\0'; p += 2; + tm.tm_mon = strtol(buf, NULL, 10) - 1; + memcpy(buf, p, 2); buf[2] = '\0'; p += 2; + tm.tm_mday = strtol(buf, NULL, 10); + memcpy(buf, p, 2); buf[2] = '\0'; p += 2; + tm.tm_hour = strtol(buf, NULL, 10); + memcpy(buf, p, 2); buf[2] = '\0'; p += 2; + tm.tm_min = strtol(buf, NULL, 10); + memcpy(buf, p, 2); buf[2] = '\0'; + tm.tm_sec = strtol(buf, NULL, 10); + iso9660->birth_time = mktime(&tm); + return (ARCHIVE_OK); + } +#endif + break; + case 'i': + if (strcmp(key, "iso-level") == 0) { + if (value != NULL && value[1] == '\0' && + (value[0] >= '1' && value[0] <= '4')) { + iso9660->opt.iso_level = value[0]-'0'; + return (ARCHIVE_OK); + } + goto invalid_value; + } + break; + case 'j': + if (strcmp(key, "joliet") == 0) { + if (value == NULL) + iso9660->opt.joliet = OPT_JOLIET_DISABLE; + else if (strcmp(value, "1") == 0) + iso9660->opt.joliet = OPT_JOLIET_ENABLE; + else if (strcmp(value, "long") == 0) + iso9660->opt.joliet = OPT_JOLIET_LONGNAME; + else + goto invalid_value; + return (ARCHIVE_OK); + } + break; + case 'l': + if (strcmp(key, "limit-depth") == 0) { + iso9660->opt.limit_depth = value != NULL; + return (ARCHIVE_OK); + } + if (strcmp(key, "limit-dirs") == 0) { + iso9660->opt.limit_dirs = value != NULL; + return (ARCHIVE_OK); + } + break; + case 'p': + if (strcmp(key, "pad") == 0) { + iso9660->opt.pad = value != NULL; + return (ARCHIVE_OK); + } + if (strcmp(key, "publisher") == 0) { + r = get_str_opt(a, + &(iso9660->publisher_identifier), + PUBLISHER_IDENTIFIER_SIZE, key, value); + iso9660->opt.publisher = r == ARCHIVE_OK; + return (r); + } + break; + case 'r': + if (strcmp(key, "rockridge") == 0 || + strcmp(key, "Rockridge") == 0) { + if (value == NULL) + iso9660->opt.rr = OPT_RR_DISABLED; + else if (strcmp(value, "1") == 0) + iso9660->opt.rr = OPT_RR_USEFUL; + else if (strcmp(value, "strict") == 0) + iso9660->opt.rr = OPT_RR_STRICT; + else if (strcmp(value, "useful") == 0) + iso9660->opt.rr = OPT_RR_USEFUL; + else + goto invalid_value; + return (ARCHIVE_OK); + } + break; + case 'v': + if (strcmp(key, "volume-id") == 0) { + r = get_str_opt(a, &(iso9660->volume_identifier), + VOLUME_IDENTIFIER_SIZE, key, value); + iso9660->opt.volume_id = r == ARCHIVE_OK; + return (r); + } + break; + case 'z': + if (strcmp(key, "zisofs") == 0) { + if (value == NULL) + iso9660->opt.zisofs = OPT_ZISOFS_DISABLED; + else { +#ifdef HAVE_ZLIB_H + iso9660->opt.zisofs = OPT_ZISOFS_DIRECT; +#else + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "``zisofs'' " + "is not supported on this platform."); + return (ARCHIVE_FATAL); +#endif + } + return (ARCHIVE_OK); + } + break; + } + +invalid_value: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid value for option ``%s''", key); + return (ARCHIVE_FAILED); +} + +static int +iso9660_write_header(struct archive_write *a, struct archive_entry *entry) +{ + struct iso9660 *iso9660; + struct isofile *file; + struct isoent *isoent; + int r, ret = ARCHIVE_OK; + + iso9660 = a->format_data; + + iso9660->cur_file = NULL; + iso9660->bytes_remaining = 0; + iso9660->need_multi_extent = 0; + if (archive_entry_filetype(entry) == AE_IFLNK + && iso9660->opt.rr == OPT_RR_DISABLED) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Ignore symlink file."); + iso9660->cur_file = NULL; + return (ARCHIVE_WARN); + } + if (archive_entry_filetype(entry) == AE_IFREG && + archive_entry_size(entry) >= MULTI_EXTENT_SIZE) { + if (iso9660->opt.iso_level < 3) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Ignore over %lld bytes file. " + "This file too large.", + MULTI_EXTENT_SIZE); + iso9660->cur_file = NULL; + return (ARCHIVE_WARN); + } + iso9660->need_multi_extent = 1; + } + + file = isofile_new(a, entry); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + return (ARCHIVE_FATAL); + } + r = isofile_gen_utility_names(a, file); + if (r < ARCHIVE_WARN) { + isofile_free(file); + return (r); + } + else if (r < ret) + ret = r; + + /* + * Ignore a path which looks like the top of directory name + * since we have already made the root directory of an ISO image. + */ + if (archive_strlen(&(file->parentdir)) == 0 && + archive_strlen(&(file->basename)) == 0) { + isofile_free(file); + return (r); + } + + isofile_add_entry(iso9660, file); + isoent = isoent_new(file); + if (isoent == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + return (ARCHIVE_FATAL); + } + if (isoent->file->dircnt > iso9660->dircnt_max) + iso9660->dircnt_max = isoent->file->dircnt; + + /* Add the current file into tree */ + r = isoent_tree(a, &isoent); + if (r != ARCHIVE_OK) + return (r); + + /* If there is the same file in tree and + * the current file is older than the file in tree. + * So we don't need the current file data anymore. */ + if (isoent->file != file) + return (ARCHIVE_OK); + + /* Non regular files contents are unneeded to be saved to + * temporary files. */ + if (archive_entry_filetype(file->entry) != AE_IFREG) + return (ret); + + /* + * Set the current file to cur_file to read its contents. + */ + iso9660->cur_file = file; + + if (archive_entry_nlink(file->entry) > 1) { + r = isofile_register_hardlink(a, file); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* + * Prepare to save the contents of the file. + */ + if (iso9660->temp_fd < 0) { + iso9660->temp_fd = __archive_mktemp(NULL); + if (iso9660->temp_fd < 0) { + archive_set_error(&a->archive, errno, + "Couldn't create temporary file"); + return (ARCHIVE_FATAL); + } + } + + /* Save an offset of current file in temporary file. */ + file->content.offset_of_temp = wb_offset(a); + file->cur_content = &(file->content); + r = zisofs_init(a, file); + if (r < ret) + ret = r; + iso9660->bytes_remaining = archive_entry_size(file->entry); + + return (ret); +} + +static int +write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + struct iso9660 *iso9660 = a->format_data; + ssize_t written; + const unsigned char *b; + + b = (const unsigned char *)buff; + while (s) { + written = write(iso9660->temp_fd, b, s); + if (written < 0) { + archive_set_error(&a->archive, errno, + "Can't write to temporary file"); + return (ARCHIVE_FATAL); + } + s -= written; + b += written; + } + return (ARCHIVE_OK); +} + +static int +wb_write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + const char *xp = buff; + size_t xs = s; + + /* + * If a written data size is big enough to use system-call + * and there is no waiting data, this calls write_to_temp() in + * order to reduce a extra memory copy. + */ + if (wb_remaining(a) == wb_buffmax() && s > (1024 * 16)) { + struct iso9660 *iso9660 = (struct iso9660 *)a->format_data; + xs = s % LOGICAL_BLOCK_SIZE; + iso9660->wbuff_offset += s - xs; + if (write_to_temp(a, buff, s - xs) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if (xs == 0) + return (ARCHIVE_OK); + xp += s - xs; + } + + while (xs) { + size_t size = xs; + if (size > wb_remaining(a)) + size = wb_remaining(a); + memcpy(wb_buffptr(a), xp, size); + if (wb_consume(a, size) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + xs -= size; + xp += size; + } + return (ARCHIVE_OK); +} + +static int +wb_write_padding_to_temp(struct archive_write *a, int64_t csize) +{ + size_t ns; + int ret; + + ns = csize % LOGICAL_BLOCK_SIZE; + if (ns != 0) + ret = write_null(a, LOGICAL_BLOCK_SIZE - ns); + else + ret = ARCHIVE_OK; + return (ret); +} + +static ssize_t +write_iso9660_data(struct archive_write *a, const void *buff, size_t s) +{ + struct iso9660 *iso9660 = a->format_data; + size_t ws; + + if (iso9660->temp_fd < 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Couldn't create temporary file"); + return (ARCHIVE_FATAL); + } + + ws = s; + if (iso9660->need_multi_extent && + (iso9660->cur_file->cur_content->size + ws) >= + (MULTI_EXTENT_SIZE - LOGICAL_BLOCK_SIZE)) { + struct content *con; + size_t ts; + + ts = MULTI_EXTENT_SIZE - LOGICAL_BLOCK_SIZE - + iso9660->cur_file->cur_content->size; + + if (iso9660->zisofs.detect_magic) + zisofs_detect_magic(a, buff, ts); + + if (iso9660->zisofs.making) { + if (zisofs_write_to_temp(a, buff, ts) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + if (wb_write_to_temp(a, buff, ts) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->cur_file->cur_content->size += ts; + } + + /* Write padding. */ + if (wb_write_padding_to_temp(a, + iso9660->cur_file->cur_content->size) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Compute the logical block number. */ + iso9660->cur_file->cur_content->blocks = + (iso9660->cur_file->cur_content->size + + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS; + + /* + * Make next extent. + */ + ws -= ts; + buff = (const void *)(((const unsigned char *)buff) + ts); + /* Make a content for next extent. */ + con = calloc(1, sizeof(*con)); + if (con == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate content data"); + return (ARCHIVE_FATAL); + } + con->offset_of_temp = wb_offset(a); + iso9660->cur_file->cur_content->next = con; + iso9660->cur_file->cur_content = con; +#ifdef HAVE_ZLIB_H + iso9660->zisofs.block_offset = 0; +#endif + } + + if (iso9660->zisofs.detect_magic) + zisofs_detect_magic(a, buff, ws); + + if (iso9660->zisofs.making) { + if (zisofs_write_to_temp(a, buff, ws) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + if (wb_write_to_temp(a, buff, ws) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->cur_file->cur_content->size += ws; + } + + return (s); +} + +static ssize_t +iso9660_write_data(struct archive_write *a, const void *buff, size_t s) +{ + struct iso9660 *iso9660 = a->format_data; + ssize_t r; + + if (iso9660->cur_file == NULL) + return (0); + if (archive_entry_filetype(iso9660->cur_file->entry) != AE_IFREG) + return (0); + if (s > iso9660->bytes_remaining) + s = iso9660->bytes_remaining; + if (s == 0) + return (0); + + r = write_iso9660_data(a, buff, s); + if (r > 0) + iso9660->bytes_remaining -= r; + return (r); +} + +static int +iso9660_finish_entry(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + + if (iso9660->cur_file == NULL) + return (ARCHIVE_OK); + if (archive_entry_filetype(iso9660->cur_file->entry) != AE_IFREG) + return (ARCHIVE_OK); + if (iso9660->cur_file->content.size == 0) + return (ARCHIVE_OK); + + /* If there are unwritten data, write null data instead. */ + while (iso9660->bytes_remaining > 0) { + size_t s; + + s = (iso9660->bytes_remaining > a->null_length)? + a->null_length: (size_t)iso9660->bytes_remaining; + if (write_iso9660_data(a, a->nulls, s) < 0) + return (ARCHIVE_FATAL); + iso9660->bytes_remaining -= s; + } + + if (iso9660->zisofs.making && zisofs_finish_entry(a) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write padding. */ + if (wb_write_padding_to_temp(a, iso9660->cur_file->cur_content->size) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Compute the logical block number. */ + iso9660->cur_file->cur_content->blocks = + (iso9660->cur_file->cur_content->size + + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS; + + /* Add the current file to data file list. */ + isofile_add_data_file(iso9660, iso9660->cur_file); + + return (ARCHIVE_OK); +} + +static int +iso9660_close(struct archive_write *a) +{ + struct iso9660 *iso9660; + int ret, blocks; + + iso9660 = a->format_data; + + /* + * Write remaining data out to the temprary file. + */ + if (wb_remaining(a) > 0) { + ret = wb_write_out(a); + if (ret < 0) + return (ret); + } + + /* + * Preparations... + */ +#ifdef DEBUG + if (iso9660->birth_time == 0) +#endif + time(&(iso9660->birth_time)); + + /* + * Prepare a bootable ISO image. + */ + if (iso9660->opt.boot) { + /* Find out the boot file entry. */ + ret = isoent_find_out_boot_file(a, iso9660->primary.rootent); + if (ret < 0) + return (ret); + /* Reconvert the boot file from zisofs'ed form to + * plain form. */ + ret = zisofs_rewind_boot_file(a); + if (ret < 0) + return (ret); + /* Write remaining data out to the temprary file. */ + if (wb_remaining(a) > 0) { + ret = wb_write_out(a); + if (ret < 0) + return (ret); + } + /* Create the boot catalog. */ + ret = isoent_create_boot_catalog(a, iso9660->primary.rootent); + if (ret < 0) + return (ret); + } + + /* + * Prepare joliet extensions. + */ + if (iso9660->opt.joliet) { + /* Make a new tree for joliet. */ + ret = isoent_clone_tree(a, &(iso9660->joliet.rootent), + iso9660->primary.rootent); + if (ret < 0) + return (ret); + /* Make sure we have UTF-16BE convertors. + * if there is no file entry, convertors are still + * uninitilized. */ + if (iso9660->sconv_to_utf16be == NULL) { + iso9660->sconv_to_utf16be = + archive_string_conversion_to_charset( + &(a->archive), "UTF-16BE", 1); + if (iso9660->sconv_to_utf16be == NULL) + /* Couldn't allocate memory */ + return (ARCHIVE_FATAL); + iso9660->sconv_from_utf16be = + archive_string_conversion_from_charset( + &(a->archive), "UTF-16BE", 1); + if (iso9660->sconv_from_utf16be == NULL) + /* Couldn't allocate memory */ + return (ARCHIVE_FATAL); + } + } + + /* + * Make Path Tables. + */ + ret = isoent_make_path_table(a); + if (ret < 0) + return (ret); + + /* + * Calculate a total volume size and setup all locations of + * contents of an iso9660 image. + */ + blocks = SYSTEM_AREA_BLOCK + + PRIMARY_VOLUME_DESCRIPTOR_BLOCK + + VOLUME_DESCRIPTOR_SET_TERMINATOR_BLOCK + + NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK; + if (iso9660->opt.boot) + blocks += BOOT_RECORD_DESCRIPTOR_BLOCK; + if (iso9660->opt.joliet) + blocks += SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK; + if (iso9660->opt.iso_level == 4) + blocks += SUPPLEMENTARY_VOLUME_DESCRIPTOR_BLOCK; + + /* Setup the locations of Path Table. */ + iso9660->primary.location_type_L_path_table = blocks; + blocks += iso9660->primary.path_table_block; + iso9660->primary.location_type_M_path_table = blocks; + blocks += iso9660->primary.path_table_block; + if (iso9660->opt.joliet) { + iso9660->joliet.location_type_L_path_table = blocks; + blocks += iso9660->joliet.path_table_block; + iso9660->joliet.location_type_M_path_table = blocks; + blocks += iso9660->joliet.path_table_block; + } + + /* Setup the locations of directories. */ + isoent_setup_directory_location(iso9660, blocks, + &(iso9660->primary)); + blocks += iso9660->primary.total_dir_block; + if (iso9660->opt.joliet) { + isoent_setup_directory_location(iso9660, blocks, + &(iso9660->joliet)); + blocks += iso9660->joliet.total_dir_block; + } + + if (iso9660->opt.rr) { + iso9660->location_rrip_er = blocks; + blocks += RRIP_ER_BLOCK; + } + + /* Setup the locations of all file contents. */ + isoent_setup_file_location(iso9660, blocks); + blocks += iso9660->total_file_block; + if (iso9660->opt.boot && iso9660->opt.boot_info_table) { + ret = setup_boot_information(a); + if (ret < 0) + return (ret); + } + + /* Now we have a total volume size. */ + iso9660->volume_space_size = blocks; + if (iso9660->opt.pad) + iso9660->volume_space_size += PADDING_BLOCK; + iso9660->volume_sequence_number = 1; + + + /* + * Write an ISO 9660 image. + */ + + /* Switc to start using wbuff as file buffer. */ + iso9660->wbuff_remaining = wb_buffmax(); + iso9660->wbuff_type = WB_TO_STREAM; + iso9660->wbuff_offset = 0; + iso9660->wbuff_written = 0; + iso9660->wbuff_tail = 0; + + /* Write The System Area */ + ret = write_null(a, SYSTEM_AREA_BLOCK * LOGICAL_BLOCK_SIZE); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Primary Volume Descriptor */ + ret = write_VD(a, &(iso9660->primary)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + if (iso9660->opt.boot) { + /* Write Boot Record Volume Descriptor */ + ret = write_VD_boot_record(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + if (iso9660->opt.iso_level == 4) { + /* Write Enhanced Volume Descriptor */ + iso9660->primary.vdd_type = VDD_ENHANCED; + ret = write_VD(a, &(iso9660->primary)); + iso9660->primary.vdd_type = VDD_PRIMARY; + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + if (iso9660->opt.joliet) { + ret = write_VD(a, &(iso9660->joliet)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* Write Volume Descriptor Set Terminator */ + ret = write_VD_terminator(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Non-ISO File System Information */ + ret = write_information_block(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Type L Path Table */ + ret = write_path_table(a, 0, &(iso9660->primary)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Type M Path Table */ + ret = write_path_table(a, 1, &(iso9660->primary)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + if (iso9660->opt.joliet) { + /* Write Type L Path Table */ + ret = write_path_table(a, 0, &(iso9660->joliet)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Type M Path Table */ + ret = write_path_table(a, 1, &(iso9660->joliet)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* Write Directory Descriptors */ + ret = write_directory_descriptors(a, &(iso9660->primary)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + if (iso9660->opt.joliet) { + ret = write_directory_descriptors(a, &(iso9660->joliet)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + if (iso9660->opt.rr) { + /* Write Rockridge ER(Extensions Reference) */ + ret = write_rr_ER(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* Write File Descriptors */ + ret = write_file_descriptors(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Write Padding */ + if (iso9660->opt.pad) { + ret = write_null(a, PADDING_BLOCK * LOGICAL_BLOCK_SIZE); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + if (iso9660->directories_too_deep != NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: Directories too deep.", + archive_entry_pathname( + iso9660->directories_too_deep->file->entry)); + return (ARCHIVE_WARN); + } + + /* Write remaining data out. */ + ret = wb_write_out(a); + + return (ret); +} + +static int +iso9660_free(struct archive_write *a) +{ + struct iso9660 *iso9660; + int i, ret; + + iso9660 = a->format_data; + + /* Close the temporary file. */ + if (iso9660->temp_fd >= 0) + close(iso9660->temp_fd); + + /* Free some stuff for zisofs operations. */ + ret = zisofs_free(a); + + /* Remove directory entries in tree which includes file entries. */ + isoent_free_all(iso9660->primary.rootent); + for (i = 0; i < iso9660->primary.max_depth; i++) + free(iso9660->primary.pathtbl[i].sorted); + free(iso9660->primary.pathtbl); + + if (iso9660->opt.joliet) { + isoent_free_all(iso9660->joliet.rootent); + for (i = 0; i < iso9660->joliet.max_depth; i++) + free(iso9660->joliet.pathtbl[i].sorted); + free(iso9660->joliet.pathtbl); + } + + /* Remove isofile entries. */ + isofile_free_all_entries(iso9660); + isofile_free_hardlinks(iso9660); + + archive_string_free(&(iso9660->cur_dirstr)); + archive_string_free(&(iso9660->volume_identifier)); + archive_string_free(&(iso9660->publisher_identifier)); + archive_string_free(&(iso9660->data_preparer_identifier)); + archive_string_free(&(iso9660->application_identifier)); + archive_string_free(&(iso9660->copyright_file_identifier)); + archive_string_free(&(iso9660->abstract_file_identifier)); + archive_string_free(&(iso9660->bibliographic_file_identifier)); + archive_string_free(&(iso9660->el_torito.catalog_filename)); + archive_string_free(&(iso9660->el_torito.boot_filename)); + archive_string_free(&(iso9660->el_torito.id)); + archive_string_free(&(iso9660->utf16be)); + archive_string_free(&(iso9660->mbs)); + + free(iso9660); + a->format_data = NULL; + + return (ret); +} + +/* + * Get the System Identifier + */ +static void +get_system_identitier(char *system_id, size_t size) +{ +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname u; + + uname(&u); + strncpy(system_id, u.sysname, size-1); + system_id[size-1] = '\0'; +#elif defined(_WIN32) && !defined(__CYGWIN__) + strncpy(system_id, "Windows", size-1); + system_id[size-1] = '\0'; +#else +#error no way to get the system identifier on your platform. +#endif +} + +static void +set_str(unsigned char *p, const char *s, size_t l, char f, const char *map) +{ + unsigned char c; + + if (s == NULL) + s = ""; + while ((c = *s++) != 0 && l > 0) { + if (c >= 0x80 || map[c] == 0) + { + /* illegal character */ + if (c >= 'a' && c <= 'z') { + /* convert c from a-z to A-Z */ + c -= 0x20; + } else + c = 0x5f; + } + *p++ = c; + l--; + } + /* If l isn't zero, fill p buffer by the character + * which indicated by f. */ + if (l > 0) + memset(p , f, l); +} + +static inline int +joliet_allowed_char(unsigned char high, unsigned char low) +{ + int utf16 = (high << 8) | low; + + if (utf16 <= 0x001F) + return (0); + + switch (utf16) { + case 0x002A: /* '*' */ + case 0x002F: /* '/' */ + case 0x003A: /* ':' */ + case 0x003B: /* ';' */ + case 0x003F: /* '?' */ + case 0x005C: /* '\' */ + return (0);/* Not allowed. */ + } + return (1); +} + +static int +set_str_utf16be(struct archive_write *a, unsigned char *p, const char *s, + size_t l, uint16_t uf, enum vdc vdc) +{ + size_t size, i; + int onepad; + + if (s == NULL) + s = ""; + if (l & 0x01) { + onepad = 1; + l &= ~1; + } else + onepad = 0; + if (vdc == VDC_UCS2) { + struct iso9660 *iso9660 = a->format_data; + if (archive_strncpy_in_locale(&iso9660->utf16be, s, strlen(s), + iso9660->sconv_to_utf16be) != 0 && errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for UTF-16BE"); + return (ARCHIVE_FATAL); + } + size = iso9660->utf16be.length; + if (size > l) + size = l; + memcpy(p, iso9660->utf16be.s, size); + } else { + const uint16_t *u16 = (const uint16_t *)s; + + size = 0; + while (*u16++) + size += 2; + if (size > l) + size = l; + memcpy(p, s, size); + } + for (i = 0; i < size; i += 2, p += 2) { + if (!joliet_allowed_char(p[0], p[1])) + archive_be16enc(p, 0x005F);/* '_' */ + } + l -= size; + while (l > 0) { + archive_be16enc(p, uf); + p += 2; + l -= 2; + } + if (onepad) + *p = 0; + return (ARCHIVE_OK); +} + +static const char a_characters_map[0x80] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */ + 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 20-2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 30-3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 60-6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 70-7F */ +}; + +static const char a1_characters_map[0x80] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */ + 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 20-2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 30-3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 60-6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,/* 70-7F */ +}; + +static const char d_characters_map[0x80] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 20-2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,/* 30-3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 60-6F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 70-7F */ +}; + +static const char d1_characters_map[0x80] = { +/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 00-0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 10-1F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/* 20-2F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,/* 30-3F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 40-4F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,/* 50-5F */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,/* 60-6F */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,/* 70-7F */ +}; + +static int +set_str_a_characters_bp(struct archive_write *a, unsigned char *bp, + int from, int to, const char *s, enum vdc vdc) +{ + int r; + + switch (vdc) { + case VDC_STD: + set_str(bp+from, s, to - from + 1, 0x20, + a_characters_map); + r = ARCHIVE_OK; + break; + case VDC_LOWERCASE: + set_str(bp+from, s, to - from + 1, 0x20, + a1_characters_map); + r = ARCHIVE_OK; + break; + case VDC_UCS2: + case VDC_UCS2_DIRECT: + r = set_str_utf16be(a, bp+from, s, to - from + 1, + 0x0020, vdc); + break; + default: + r = ARCHIVE_FATAL; + } + return (r); +} + +static int +set_str_d_characters_bp(struct archive_write *a, unsigned char *bp, + int from, int to, const char *s, enum vdc vdc) +{ + int r; + + switch (vdc) { + case VDC_STD: + set_str(bp+from, s, to - from + 1, 0x20, + d_characters_map); + r = ARCHIVE_OK; + break; + case VDC_LOWERCASE: + set_str(bp+from, s, to - from + 1, 0x20, + d1_characters_map); + r = ARCHIVE_OK; + break; + case VDC_UCS2: + case VDC_UCS2_DIRECT: + r = set_str_utf16be(a, bp+from, s, to - from + 1, + 0x0020, vdc); + break; + default: + r = ARCHIVE_FATAL; + } + return (r); +} + +static void +set_VD_bp(unsigned char *bp, enum VD_type type, unsigned char ver) +{ + + /* Volume Descriptor Type */ + bp[1] = (unsigned char)type; + /* Standard Identifier */ + memcpy(bp + 2, "CD001", 5); + /* Volume Descriptor Version */ + bp[7] = ver; +} + +static inline void +set_unused_field_bp(unsigned char *bp, int from, int to) +{ + memset(bp + from, 0, to - from + 1); +} + +/* + * 8-bit unsigned numerical values. + * ISO9660 Standard 7.1.1 + */ +static inline void +set_num_711(unsigned char *p, unsigned char value) +{ + *p = value; +} + +/* + * 8-bit signed numerical values. + * ISO9660 Standard 7.1.2 + */ +static inline void +set_num_712(unsigned char *p, char value) +{ + *((char *)p) = value; +} + +/* + * Least significant byte first. + * ISO9660 Standard 7.2.1 + */ +static inline void +set_num_721(unsigned char *p, uint16_t value) +{ + archive_le16enc(p, value); +} + +/* + * Most significant byte first. + * ISO9660 Standard 7.2.2 + */ +static inline void +set_num_722(unsigned char *p, uint16_t value) +{ + archive_be16enc(p, value); +} + +/* + * Both-byte orders. + * ISO9660 Standard 7.2.3 + */ +static void +set_num_723(unsigned char *p, uint16_t value) +{ + archive_le16enc(p, value); + archive_be16enc(p+2, value); +} + +/* + * Least significant byte first. + * ISO9660 Standard 7.3.1 + */ +static inline void +set_num_731(unsigned char *p, uint32_t value) +{ + archive_le32enc(p, value); +} + +/* + * Most significant byte first. + * ISO9660 Standard 7.3.2 + */ +static inline void +set_num_732(unsigned char *p, uint32_t value) +{ + archive_be32enc(p, value); +} + +/* + * Both-byte orders. + * ISO9660 Standard 7.3.3 + */ +static inline void +set_num_733(unsigned char *p, uint32_t value) +{ + archive_le32enc(p, value); + archive_be32enc(p+4, value); +} + +static void +set_digit(unsigned char *p, size_t s, int value) +{ + + while (s--) { + p[s] = '0' + (value % 10); + value /= 10; + } +} + +#if defined(HAVE_STRUCT_TM_TM_GMTOFF) +#define get_gmoffset(tm) ((tm)->tm_gmtoff) +#elif defined(HAVE_STRUCT_TM___TM_GMTOFF) +#define get_gmoffset(tm) ((tm)->__tm_gmtoff) +#else +static long +get_gmoffset(struct tm *tm) +{ + long offset; + +#if defined(HAVE__GET_TIMEZONE) + _get_timezone(&offset); +#elif defined(__CYGWIN__) || defined(__MINGW32__) || defined(__BORLANDC__) + offset = _timezone; +#else + offset = timezone; +#endif + offset *= -1; + if (tm->tm_isdst) + offset += 3600; + return (offset); +} +#endif + +static void +get_tmfromtime(struct tm *tm, time_t *t) +{ +#if HAVE_LOCALTIME_R + tzset(); + localtime_r(t, tm); +#elif HAVE__LOCALTIME64_S + _localtime64_s(tm, t); +#else + memcpy(tm, localtime(t), sizeof(*tm)); +#endif +} + +/* + * Date and Time Format. + * ISO9660 Standard 8.4.26.1 + */ +static void +set_date_time(unsigned char *p, time_t t) +{ + struct tm tm; + + get_tmfromtime(&tm, &t); + set_digit(p, 4, tm.tm_year + 1900); + set_digit(p+4, 2, tm.tm_mon + 1); + set_digit(p+6, 2, tm.tm_mday); + set_digit(p+8, 2, tm.tm_hour); + set_digit(p+10, 2, tm.tm_min); + set_digit(p+12, 2, tm.tm_sec); + set_digit(p+14, 2, 0); + set_num_712(p+16, get_gmoffset(&tm)/(60*15)); +} + +static void +set_date_time_null(unsigned char *p) +{ + memset(p, '0', 16); + p[16] = 0; +} + +static void +set_time_915(unsigned char *p, time_t t) +{ + struct tm tm; + + get_tmfromtime(&tm, &t); + set_num_711(p+0, tm.tm_year); + set_num_711(p+1, tm.tm_mon+1); + set_num_711(p+2, tm.tm_mday); + set_num_711(p+3, tm.tm_hour); + set_num_711(p+4, tm.tm_min); + set_num_711(p+5, tm.tm_sec); + set_num_712(p+6, get_gmoffset(&tm)/(60*15)); +} + + +/* + * Write SUSP "CE" System Use Entry. + */ +static int +set_SUSP_CE(unsigned char *p, int location, int offset, int size) +{ + unsigned char *bp = p -1; + /* Extend the System Use Area + * "CE" Format: + * len ver + * +----+----+----+----+-----------+-----------+ + * | 'C'| 'E'| 1C | 01 | LOCATION1 | LOCATION2 | + * +----+----+----+----+-----------+-----------+ + * 0 1 2 3 4 12 20 + * +-----------+ + * | LOCATION3 | + * +-----------+ + * 20 28 + * LOCATION1 : Location of Continuation of System Use Area. + * LOCATION2 : Offset to Start of Continuation. + * LOCATION3 : Length of the Continuation. + */ + + bp[1] = 'C'; + bp[2] = 'E'; + bp[3] = RR_CE_SIZE; /* length */ + bp[4] = 1; /* version */ + set_num_733(bp+5, location); + set_num_733(bp+13, offset); + set_num_733(bp+21, size); + return (RR_CE_SIZE); +} + +/* + * The functions, which names are beginning with extra_, are used to + * control extra records. + * The maximum size of a Directory Record is 254. When a filename is + * very long, all of RRIP data of a file won't stored to the Directory + * Record and so remaining RRIP data store to an extra record instead. + */ +static unsigned char * +extra_open_record(unsigned char *bp, int dr_len, struct isoent *isoent, + struct ctl_extr_rec *ctl) +{ + ctl->bp = bp; + if (bp != NULL) + bp += dr_len; + ctl->use_extr = 0; + ctl->isoent = isoent; + ctl->ce_ptr = NULL; + ctl->cur_len = ctl->dr_len = dr_len; + ctl->limit = DR_LIMIT; + + return (bp); +} + +static void +extra_close_record(struct ctl_extr_rec *ctl, int ce_size) +{ + int padding = 0; + + if (ce_size > 0) + extra_tell_used_size(ctl, ce_size); + /* Padding. */ + if (ctl->cur_len & 0x01) { + ctl->cur_len++; + if (ctl->bp != NULL) + ctl->bp[ctl->cur_len] = 0; + padding = 1; + } + if (ctl->use_extr) { + if (ctl->ce_ptr != NULL) + set_SUSP_CE(ctl->ce_ptr, ctl->extr_loc, + ctl->extr_off, ctl->cur_len - padding); + } else + ctl->dr_len = ctl->cur_len; +} + +#define extra_space(ctl) ((ctl)->limit - (ctl)->cur_len) + +static unsigned char * +extra_next_record(struct ctl_extr_rec *ctl, int length) +{ + int cur_len = ctl->cur_len;/* save cur_len */ + + /* Close the current extra record or Directory Record. */ + extra_close_record(ctl, RR_CE_SIZE); + + /* Get a next extra record. */ + ctl->use_extr = 1; + if (ctl->bp != NULL) { + /* Storing data into an extra record. */ + unsigned char *p; + + /* Save the pointer where a CE extension will be + * stored to. */ + ctl->ce_ptr = &ctl->bp[cur_len+1]; + p = extra_get_record(ctl->isoent, + &ctl->limit, &ctl->extr_off, &ctl->extr_loc); + ctl->bp = p - 1;/* the base of bp offset is 1. */ + } else + /* Calculating the size of an extra record. */ + (void)extra_get_record(ctl->isoent, + &ctl->limit, NULL, NULL); + ctl->cur_len = 0; + /* Check if an extra record is almost full. + * If so, get a next one. */ + if (extra_space(ctl) < length) + (void)extra_next_record(ctl, length); + + return (ctl->bp); +} + +static inline struct extr_rec * +extra_last_record(struct isoent *isoent) +{ + if (isoent->extr_rec_list.first == NULL) + return (NULL); + return ((struct extr_rec *)(void *) + ((char *)(isoent->extr_rec_list.last) + - offsetof(struct extr_rec, next))); +} + +static unsigned char * +extra_get_record(struct isoent *isoent, int *space, int *off, int *loc) +{ + struct extr_rec *rec; + + isoent = isoent->parent; + if (off != NULL) { + /* Storing data into an extra record. */ + rec = isoent->extr_rec_list.current; + if (DR_SAFETY > LOGICAL_BLOCK_SIZE - rec->offset) + rec = rec->next; + } else { + /* Calculating the size of an extra record. */ + rec = extra_last_record(isoent); + if (rec == NULL || + DR_SAFETY > LOGICAL_BLOCK_SIZE - rec->offset) { + rec = malloc(sizeof(*rec)); + if (rec == NULL) + return (NULL); + rec->location = 0; + rec->offset = 0; + /* Insert `rec` into the tail of isoent->extr_rec_list */ + rec->next = NULL; + *isoent->extr_rec_list.last = rec; + isoent->extr_rec_list.last = &(rec->next); + } + } + *space = LOGICAL_BLOCK_SIZE - rec->offset - DR_SAFETY; + if (*space & 0x01) + *space -= 1;/* Keep padding space. */ + if (off != NULL) + *off = rec->offset; + if (loc != NULL) + *loc = rec->location; + isoent->extr_rec_list.current = rec; + + return (&rec->buf[rec->offset]); +} + +static void +extra_tell_used_size(struct ctl_extr_rec *ctl, int size) +{ + struct isoent *isoent; + struct extr_rec *rec; + + if (ctl->use_extr) { + isoent = ctl->isoent->parent; + rec = isoent->extr_rec_list.current; + if (rec != NULL) + rec->offset += size; + } + ctl->cur_len += size; +} + +static int +extra_setup_location(struct isoent *isoent, int location) +{ + struct extr_rec *rec; + int cnt; + + cnt = 0; + rec = isoent->extr_rec_list.first; + isoent->extr_rec_list.current = rec; + while (rec) { + cnt++; + rec->location = location++; + rec->offset = 0; + rec = rec->next; + } + return (cnt); +} + +/* + * Create the RRIP entries. + */ +static int +set_directory_record_rr(unsigned char *bp, int dr_len, + struct isoent *isoent, struct iso9660 *iso9660, enum dir_rec_type t) +{ + /* Flags(BP 5) of the Rockridge "RR" System Use Field */ + unsigned char rr_flag; +#define RR_USE_PX 0x01 +#define RR_USE_PN 0x02 +#define RR_USE_SL 0x04 +#define RR_USE_NM 0x08 +#define RR_USE_CL 0x10 +#define RR_USE_PL 0x20 +#define RR_USE_RE 0x40 +#define RR_USE_TF 0x80 + int length; + struct ctl_extr_rec ctl; + struct isoent *rr_parent, *pxent; + struct isofile *file; + + bp = extra_open_record(bp, dr_len, isoent, &ctl); + + if (t == DIR_REC_PARENT) { + rr_parent = isoent->rr_parent; + pxent = isoent->parent; + if (rr_parent != NULL) + isoent = rr_parent; + else + isoent = isoent->parent; + } else { + rr_parent = NULL; + pxent = isoent; + } + file = isoent->file; + + if (t != DIR_REC_NORMAL) { + rr_flag = RR_USE_PX | RR_USE_TF; + if (rr_parent != NULL) + rr_flag |= RR_USE_PL; + } else { + rr_flag = RR_USE_PX | RR_USE_NM | RR_USE_TF; + if (archive_entry_filetype(file->entry) == AE_IFLNK) + rr_flag |= RR_USE_SL; + if (isoent->rr_parent != NULL) + rr_flag |= RR_USE_RE; + if (isoent->rr_child != NULL) + rr_flag |= RR_USE_CL; + if (archive_entry_filetype(file->entry) == AE_IFCHR || + archive_entry_filetype(file->entry) == AE_IFBLK) + rr_flag |= RR_USE_PN; +#ifdef COMPAT_MKISOFS + /* + * mkisofs 2.01.01a63 records "RE" extension to + * the entry of "rr_moved" directory. + * I don't understand this behavior. + */ + if (isoent->virtual && + isoent->parent == iso9660->primary.rootent && + strcmp(isoent->file->basename.s, "rr_moved") == 0) + rr_flag |= RR_USE_RE; +#endif + } + + /* Write "SP" System Use Entry. */ + if (t == DIR_REC_SELF && isoent == isoent->parent) { + length = 7; + if (bp != NULL) { + bp[1] = 'S'; + bp[2] = 'P'; + bp[3] = length; + bp[4] = 1; /* version */ + bp[5] = 0xBE; /* Check Byte */ + bp[6] = 0xEF; /* Check Byte */ + bp[7] = 0; + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "RR" System Use Entry. */ + length = 5; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'R'; + bp[2] = 'R'; + bp[3] = length; + bp[4] = 1; /* version */ + bp[5] = rr_flag; + bp += length; + } + extra_tell_used_size(&ctl, length); + + /* Write "NM" System Use Entry. */ + if (rr_flag & RR_USE_NM) { + /* + * "NM" Format: + * e.g. a basename is 'foo' + * len ver flg + * +----+----+----+----+----+----+----+----+ + * | 'N'| 'M'| 08 | 01 | 00 | 'f'| 'o'| 'o'| + * +----+----+----+----+----+----+----+----+ + * <----------------- len -----------------> + */ + size_t nmlen = file->basename.length; + const char *nm = file->basename.s; + size_t nmmax; + + if (extra_space(&ctl) < 6) + bp = extra_next_record(&ctl, 6); + if (bp != NULL) { + bp[1] = 'N'; + bp[2] = 'M'; + bp[4] = 1; /* version */ + } + nmmax = extra_space(&ctl); + if (nmmax > 0xff) + nmmax = 0xff; + while (nmlen + 5 > nmmax) { + length = nmmax; + if (bp != NULL) { + bp[3] = length; + bp[5] = 0x01;/* Alternate Name continues + * in next "NM" field */ + memcpy(bp+6, nm, length - 5); + bp += length; + } + nmlen -= length - 5; + nm += length - 5; + extra_tell_used_size(&ctl, length); + if (extra_space(&ctl) < 6) { + bp = extra_next_record(&ctl, 6); + nmmax = extra_space(&ctl); + if (nmmax > 0xff) + nmmax = 0xff; + } + if (bp != NULL) { + bp[1] = 'N'; + bp[2] = 'M'; + bp[4] = 1; /* version */ + } + } + length = 5 + nmlen; + if (bp != NULL) { + bp[3] = length; + bp[5] = 0; + memcpy(bp+6, nm, nmlen); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "PX" System Use Entry. */ + if (rr_flag & RR_USE_PX) { + /* + * "PX" Format: + * len ver + * +----+----+----+----+-----------+-----------+ + * | 'P'| 'X'| 2C | 01 | FILE MODE | LINKS | + * +----+----+----+----+-----------+-----------+ + * 0 1 2 3 4 12 20 + * +-----------+-----------+------------------+ + * | USER ID | GROUP ID |FILE SERIAL NUMBER| + * +-----------+-----------+------------------+ + * 20 28 36 44 + */ + length = 44; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + mode_t mode; + uid_t uid; + gid_t gid; + + mode = archive_entry_mode(file->entry); + uid = archive_entry_uid(file->entry); + gid = archive_entry_gid(file->entry); + if (iso9660->opt.rr == OPT_RR_USEFUL) { + /* + * This action is simular mkisofs -r option + * but our rockridge=useful option does not + * set a zero to uid and gid. + */ + /* set all read bit ON */ + mode |= 0444; +#if !defined(_WIN32) && !defined(__CYGWIN__) + if (mode & 0111) +#endif + /* set all exec bit ON */ + mode |= 0111; + /* clear all write bits. */ + mode &= ~0222; + /* clear setuid,setgid,sticky bits. */ + mode &= ~07000; + } + + bp[1] = 'P'; + bp[2] = 'X'; + bp[3] = length; + bp[4] = 1; /* version */ + /* file mode */ + set_num_733(bp+5, mode); + /* file links (stat.st_nlink) */ + set_num_733(bp+13, + archive_entry_nlink(file->entry)); + set_num_733(bp+21, uid); + set_num_733(bp+29, gid); + /* File Serial Number */ + if (pxent->dir) + set_num_733(bp+37, pxent->dir_location); + else if (file->hardlink_target != NULL) + set_num_733(bp+37, + file->hardlink_target->cur_content->location); + else + set_num_733(bp+37, + file->cur_content->location); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "SL" System Use Entry. */ + if (rr_flag & RR_USE_SL) { + /* + * "SL" Format: + * e.g. a symbolic name is 'foo/bar' + * len ver flg + * +----+----+----+----+----+------------+ + * | 'S'| 'L'| 0F | 01 | 00 | components | + * +----+----+----+----+----+-----+------+ + * 0 1 2 3 4 5 ...|... 15 + * <----------------- len --------+------> + * components : | + * cflg clen | + * +----+----+----+----+----+ | + * | 00 | 03 | 'f'| 'o'| 'o'| <---+ + * +----+----+----+----+----+ | + * 5 6 7 8 9 10 | + * cflg clen | + * +----+----+----+----+----+ | + * | 00 | 03 | 'b'| 'a'| 'r'| <---+ + * +----+----+----+----+----+ + * 10 11 12 13 14 15 + * + * - cflg : flag of componet + * - clen : length of componet + */ + const char *sl; + char sl_last; + + if (extra_space(&ctl) < 7) + bp = extra_next_record(&ctl, 7); + sl = file->symlink.s; + sl_last = '\0'; + if (bp != NULL) { + bp[1] = 'S'; + bp[2] = 'L'; + bp[4] = 1; /* version */ + } + for (;;) { + unsigned char *nc, *cf, *cl, cldmy = 0; + int sllen, slmax; + + slmax = extra_space(&ctl); + if (slmax > 0xff) + slmax = 0xff; + if (bp != NULL) + nc = &bp[6]; + else + nc = NULL; + cf = cl = NULL; + sllen = 0; + while (*sl && sllen + 11 < slmax) { + if (sl_last == '\0' && sl[0] == '/') { + /* + * flg len + * +----+----+ + * | 08 | 00 | ROOT component. + * +----+----+ ("/") + * + * Root component has to appear + * at the first component only. + */ + if (nc != NULL) { + cf = nc++; + *cf = 0x08; /* ROOT */ + *nc++ = 0; + } + sllen += 2; + sl++; + sl_last = '/'; + cl = NULL; + continue; + } + if (((sl_last == '\0' || sl_last == '/') && + sl[0] == '.' && sl[1] == '.' && + (sl[2] == '/' || sl[2] == '\0')) || + (sl[0] == '/' && + sl[1] == '.' && sl[2] == '.' && + (sl[3] == '/' || sl[3] == '\0'))) { + /* + * flg len + * +----+----+ + * | 04 | 00 | PARENT component. + * +----+----+ ("..") + */ + if (nc != NULL) { + cf = nc++; + *cf = 0x04; /* PARENT */ + *nc++ = 0; + } + sllen += 2; + if (sl[0] == '/') + sl += 3;/* skip "/.." */ + else + sl += 2;/* skip ".." */ + sl_last = '.'; + cl = NULL; + continue; + } + if (((sl_last == '\0' || sl_last == '/') && + sl[0] == '.' && + (sl[1] == '/' || sl[1] == '\0')) || + (sl[0] == '/' && sl[1] == '.' && + (sl[2] == '/' || sl[2] == '\0'))) { + /* + * flg len + * +----+----+ + * | 02 | 00 | CURREENT component. + * +----+----+ (".") + */ + if (nc != NULL) { + cf = nc++; + *cf = 0x02; /* CURRENT */ + *nc++ = 0; + } + sllen += 2; + if (sl[0] == '/') + sl += 2;/* skip "/." */ + else + sl ++; /* skip "." */ + sl_last = '.'; + cl = NULL; + continue; + } + if (sl[0] == '/' || cl == NULL) { + if (nc != NULL) { + cf = nc++; + *cf = 0; + cl = nc++; + *cl = 0; + } else + cl = &cldmy; + sllen += 2; + if (sl[0] == '/') { + sl_last = *sl++; + continue; + } + } + sl_last = *sl++; + if (nc != NULL) { + *nc++ = sl_last; + (*cl) ++; + } + sllen++; + } + if (*sl) { + length = 5 + sllen; + if (bp != NULL) { + /* + * Mark flg as CONTINUE component. + */ + *cf |= 0x01; + /* + * len ver flg + * +----+----+----+----+----+- + * | 'S'| 'L'| XX | 01 | 01 | + * +----+----+----+----+----+- + * ^ + * continues in next "SL" + */ + bp[3] = length; + bp[5] = 0x01;/* This Symbolic Link + * continues in next + * "SL" field */ + bp += length; + } + extra_tell_used_size(&ctl, length); + if (extra_space(&ctl) < 11) + bp = extra_next_record(&ctl, 11); + if (bp != NULL) { + /* Next 'SL' */ + bp[1] = 'S'; + bp[2] = 'L'; + bp[4] = 1; /* version */ + } + } else { + length = 5 + sllen; + if (bp != NULL) { + bp[3] = length; + bp[5] = 0; + bp += length; + } + extra_tell_used_size(&ctl, length); + break; + } + } + } + + /* Write "TF" System Use Entry. */ + if (rr_flag & RR_USE_TF) { + /* + * "TF" Format: + * len ver + * +----+----+----+----+-----+-------------+ + * | 'T'| 'F'| XX | 01 |FLAGS| TIME STAMPS | + * +----+----+----+----+-----+-------------+ + * 0 1 2 3 4 5 XX + * TIME STAMPS : ISO 9660 Standard 9.1.5. + * If TF_LONG_FORM FLAGS is set, + * use ISO9660 Standard 8.4.26.1. + */ +#define TF_CREATION 0x01 /* Creation time recorded */ +#define TF_MODIFY 0x02 /* Modification time recorded */ +#define TF_ACCESS 0x04 /* Last Access time recorded */ +#define TF_ATTRIBUTES 0x08 /* Last Attribute Change time recorded */ +#define TF_BACKUP 0x10 /* Last Backup time recorded */ +#define TF_EXPIRATION 0x20 /* Expiration time recorded */ +#define TF_EFFECTIVE 0x40 /* Effective time recorded */ +#define TF_LONG_FORM 0x80 /* ISO 9660 17-byte time format used */ + unsigned char tf_flags; + + length = 5; + tf_flags = 0; +#ifndef COMPAT_MKISOFS + if (archive_entry_birthtime_is_set(file->entry) && + archive_entry_birthtime(file->entry) <= + archive_entry_mtime(file->entry)) { + length += 7; + tf_flags |= TF_CREATION; + } +#endif + if (archive_entry_mtime_is_set(file->entry)) { + length += 7; + tf_flags |= TF_MODIFY; + } + if (archive_entry_atime_is_set(file->entry)) { + length += 7; + tf_flags |= TF_ACCESS; + } + if (archive_entry_ctime_is_set(file->entry)) { + length += 7; + tf_flags |= TF_ATTRIBUTES; + } + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'T'; + bp[2] = 'F'; + bp[3] = length; + bp[4] = 1; /* version */ + bp[5] = tf_flags; + bp += 5; + /* Creation time */ + if (tf_flags & TF_CREATION) { + set_time_915(bp+1, + archive_entry_birthtime(file->entry)); + bp += 7; + } + /* Modification time */ + if (tf_flags & TF_MODIFY) { + set_time_915(bp+1, + archive_entry_mtime(file->entry)); + bp += 7; + } + /* Last Access time */ + if (tf_flags & TF_ACCESS) { + set_time_915(bp+1, + archive_entry_atime(file->entry)); + bp += 7; + } + /* Last Attribute Change time */ + if (tf_flags & TF_ATTRIBUTES) { + set_time_915(bp+1, + archive_entry_ctime(file->entry)); + bp += 7; + } + } + extra_tell_used_size(&ctl, length); + } + + /* Write "RE" System Use Entry. */ + if (rr_flag & RR_USE_RE) { + /* + * "RE" Format: + * len ver + * +----+----+----+----+ + * | 'R'| 'E'| 04 | 01 | + * +----+----+----+----+ + * 0 1 2 3 4 + */ + length = 4; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'R'; + bp[2] = 'E'; + bp[3] = length; + bp[4] = 1; /* version */ + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "PL" System Use Entry. */ + if (rr_flag & RR_USE_PL) { + /* + * "PL" Format: + * len ver + * +----+----+----+----+------------+ + * | 'P'| 'L'| 0C | 01 | *LOCATION | + * +----+----+----+----+------------+ + * 0 1 2 3 4 12 + * *LOCATION: location of parent directory + */ + length = 12; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'P'; + bp[2] = 'L'; + bp[3] = length; + bp[4] = 1; /* version */ + set_num_733(bp + 5, + rr_parent->dir_location); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "CL" System Use Entry. */ + if (rr_flag & RR_USE_CL) { + /* + * "CL" Format: + * len ver + * +----+----+----+----+------------+ + * | 'C'| 'L'| 0C | 01 | *LOCATION | + * +----+----+----+----+------------+ + * 0 1 2 3 4 12 + * *LOCATION: location of child directory + */ + length = 12; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'C'; + bp[2] = 'L'; + bp[3] = length; + bp[4] = 1; /* version */ + set_num_733(bp + 5, + isoent->rr_child->dir_location); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "PN" System Use Entry. */ + if (rr_flag & RR_USE_PN) { + /* + * "PN" Format: + * len ver + * +----+----+----+----+------------+------------+ + * | 'P'| 'N'| 14 | 01 | dev_t high | dev_t low | + * +----+----+----+----+------------+------------+ + * 0 1 2 3 4 12 20 + */ + length = 20; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + uint64_t dev; + + bp[1] = 'P'; + bp[2] = 'N'; + bp[3] = length; + bp[4] = 1; /* version */ + dev = (uint64_t)archive_entry_rdev(file->entry); + set_num_733(bp + 5, dev >> 32); + set_num_733(bp + 13, dev & 0xFFFFFFFF); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "ZF" System Use Entry. */ + if (file->zisofs.header_size) { + /* + * "ZF" Format: + * len ver + * +----+----+----+----+----+----+-------------+ + * | 'Z'| 'F'| 10 | 01 | 'p'| 'z'| Header Size | + * +----+----+----+----+----+----+-------------+ + * 0 1 2 3 4 5 6 7 + * +--------------------+-------------------+ + * | Log2 of block Size | Uncompressed Size | + * +--------------------+-------------------+ + * 7 8 16 + */ + length = 16; + if (extra_space(&ctl) < length) + bp = extra_next_record(&ctl, length); + if (bp != NULL) { + bp[1] = 'Z'; + bp[2] = 'F'; + bp[3] = length; + bp[4] = 1; /* version */ + bp[5] = 'p'; + bp[6] = 'z'; + bp[7] = file->zisofs.header_size; + bp[8] = file->zisofs.log2_bs; + set_num_733(bp + 9, file->zisofs.uncompressed_size); + bp += length; + } + extra_tell_used_size(&ctl, length); + } + + /* Write "CE" System Use Entry. */ + if (t == DIR_REC_SELF && isoent == isoent->parent) { + length = RR_CE_SIZE; + if (bp != NULL) + set_SUSP_CE(bp+1, iso9660->location_rrip_er, + 0, RRIP_ER_SIZE); + extra_tell_used_size(&ctl, length); + } + + extra_close_record(&ctl, 0); + + return (ctl.dr_len); +} + +/* + * Write data of a Directory Record or calculate writing bytes itself. + * If parameter `p' is NULL, calculates the size of writing data, which + * a Directory Record needs to write, then it saved and return + * the calculated size. + * Parameter `n' is a remaining size of buffer. when parameter `p' is + * not NULL, check whether that `n' is not less than the saved size. + * if that `n' is small, return zero. + * + * This format of the Directory Record is according to + * ISO9660 Standard 9.1 + */ +static int +set_directory_record(unsigned char *p, size_t n, struct isoent *isoent, + struct iso9660 *iso9660, enum dir_rec_type t, + enum vdd_type vdd_type) +{ + unsigned char *bp; + size_t dr_len; + size_t fi_len; + + if (p != NULL) { + /* + * Check whether a write buffer size is less than the + * saved size which is needed to write this Directory + * Record. + */ + switch (t) { + case DIR_REC_VD: + dr_len = isoent->dr_len.vd; break; + case DIR_REC_SELF: + dr_len = isoent->dr_len.self; break; + case DIR_REC_PARENT: + dr_len = isoent->dr_len.parent; break; + case DIR_REC_NORMAL: + default: + dr_len = isoent->dr_len.normal; break; + } + if (dr_len > n) + return (0);/* Needs more buffer size. */ + } + + if (t == DIR_REC_NORMAL && isoent->identifier != NULL) + fi_len = isoent->id_len; + else + fi_len = 1; + + if (p != NULL) { + struct isoent *xisoent; + struct isofile *file; + unsigned char flag; + + if (t == DIR_REC_PARENT) + xisoent = isoent->parent; + else + xisoent = isoent; + file = isoent->file; + if (file->hardlink_target != NULL) + file = file->hardlink_target; + /* Make a file flag. */ + if (xisoent->dir) + flag = FILE_FLAG_DIRECTORY; + else { + if (file->cur_content->next != NULL) + flag = FILE_FLAG_MULTI_EXTENT; + else + flag = 0; + } + + bp = p -1; + /* Extended Attribute Record Length */ + set_num_711(bp+2, 0); + /* Location of Extent */ + if (xisoent->dir) + set_num_733(bp+3, xisoent->dir_location); + else + set_num_733(bp+3, file->cur_content->location); + /* Data Length */ + if (xisoent->dir) + set_num_733(bp+11, + xisoent->dir_block * LOGICAL_BLOCK_SIZE); + else + set_num_733(bp+11, file->cur_content->size); + /* Recording Date and Time */ + /* NOTE: + * If a file type is symbolic link, you are seeing this + * field value is different from a value mkisofs makes. + * libarchive uses lstat to get this one, but it + * seems mkisofs uses stat to get. + */ + set_time_915(bp+19, + archive_entry_mtime(xisoent->file->entry)); + /* File Flags */ + bp[26] = flag; + /* File Unit Size */ + set_num_711(bp+27, 0); + /* Interleave Gap Size */ + set_num_711(bp+28, 0); + /* Volume Sequence Number */ + set_num_723(bp+29, iso9660->volume_sequence_number); + /* Length of File Identifier */ + set_num_711(bp+33, fi_len); + /* File Identifier */ + switch (t) { + case DIR_REC_VD: + case DIR_REC_SELF: + set_num_711(bp+34, 0); + break; + case DIR_REC_PARENT: + set_num_711(bp+34, 1); + break; + case DIR_REC_NORMAL: + if (isoent->identifier != NULL) + memcpy(bp+34, isoent->identifier, fi_len); + else + set_num_711(bp+34, 0); + break; + } + } else + bp = NULL; + dr_len = 33 + fi_len; + /* Padding Field */ + if (dr_len & 0x01) { + dr_len ++; + if (p != NULL) + bp[dr_len] = 0; + } + + /* Volume Descriptor does not record extension. */ + if (t == DIR_REC_VD) { + if (p != NULL) + /* Length of Directory Record */ + set_num_711(p, dr_len); + else + isoent->dr_len.vd = dr_len; + return (dr_len); + } + + /* Rockridge */ + if (iso9660->opt.rr && vdd_type != VDD_JOLIET) + dr_len = set_directory_record_rr(bp, dr_len, + isoent, iso9660, t); + + if (p != NULL) + /* Length of Directory Record */ + set_num_711(p, dr_len); + else { + /* + * Save the size which is needed to write this + * Directory Record. + */ + switch (t) { + case DIR_REC_VD: + /* This case does not come, but compiler + * complains that DIR_REC_VD not handled + * in switch .... */ + break; + case DIR_REC_SELF: + isoent->dr_len.self = dr_len; break; + case DIR_REC_PARENT: + isoent->dr_len.parent = dr_len; break; + case DIR_REC_NORMAL: + isoent->dr_len.normal = dr_len; break; + } + } + + return (dr_len); +} + +/* + * Calculate the size of a directory record. + */ +static inline int +get_dir_rec_size(struct iso9660 *iso9660, struct isoent *isoent, + enum dir_rec_type t, enum vdd_type vdd_type) +{ + + return (set_directory_record(NULL, SIZE_MAX, + isoent, iso9660, t, vdd_type)); +} + +/* + * Manage to write ISO-image data with wbuff to reduce calling + * __archive_write_output() for performance. + */ + + +static inline unsigned char * +wb_buffptr(struct archive_write *a) +{ + struct iso9660 *iso9660 = (struct iso9660 *)a->format_data; + + return (&(iso9660->wbuff[sizeof(iso9660->wbuff) + - iso9660->wbuff_remaining])); +} + +static int +wb_write_out(struct archive_write *a) +{ + struct iso9660 *iso9660 = (struct iso9660 *)a->format_data; + size_t wsize, nw; + int r; + + wsize = sizeof(iso9660->wbuff) - iso9660->wbuff_remaining; + nw = wsize % LOGICAL_BLOCK_SIZE; + if (iso9660->wbuff_type == WB_TO_STREAM) + r = __archive_write_output(a, iso9660->wbuff, wsize - nw); + else + r = write_to_temp(a, iso9660->wbuff, wsize - nw); + /* Increase the offset. */ + iso9660->wbuff_offset += wsize - nw; + if (iso9660->wbuff_offset > iso9660->wbuff_written) + iso9660->wbuff_written = iso9660->wbuff_offset; + iso9660->wbuff_remaining = sizeof(iso9660->wbuff); + if (nw) { + iso9660->wbuff_remaining -= nw; + memmove(iso9660->wbuff, iso9660->wbuff + wsize - nw, nw); + } + return (r); +} + +static int +wb_consume(struct archive_write *a, size_t size) +{ + struct iso9660 *iso9660 = (struct iso9660 *)a->format_data; + + if (size > iso9660->wbuff_remaining || + iso9660->wbuff_remaining == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal Programing error: iso9660:wb_consume()" + " size=%jd, wbuff_remaining=%jd", + (intmax_t)size, (intmax_t)iso9660->wbuff_remaining); + return (ARCHIVE_FATAL); + } + iso9660->wbuff_remaining -= size; + if (iso9660->wbuff_remaining < LOGICAL_BLOCK_SIZE) + return (wb_write_out(a)); + return (ARCHIVE_OK); +} + +#ifdef HAVE_ZLIB_H + +static int +wb_set_offset(struct archive_write *a, int64_t off) +{ + struct iso9660 *iso9660 = (struct iso9660 *)a->format_data; + int64_t used, ext_bytes; + + if (iso9660->wbuff_type != WB_TO_TEMP) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal Programing error: iso9660:wb_set_offset()"); + return (ARCHIVE_FATAL); + } + + used = sizeof(iso9660->wbuff) - iso9660->wbuff_remaining; + if (iso9660->wbuff_offset + used > iso9660->wbuff_tail) + iso9660->wbuff_tail = iso9660->wbuff_offset + used; + if (iso9660->wbuff_offset < iso9660->wbuff_written) { + if (used > 0 && + write_to_temp(a, iso9660->wbuff, used) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->wbuff_offset = iso9660->wbuff_written; + lseek(iso9660->temp_fd, iso9660->wbuff_offset, SEEK_SET); + iso9660->wbuff_remaining = sizeof(iso9660->wbuff); + used = 0; + } + if (off < iso9660->wbuff_offset) { + /* + * Write out waiting data. + */ + if (used > 0) { + if (wb_write_out(a) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + lseek(iso9660->temp_fd, off, SEEK_SET); + iso9660->wbuff_offset = off; + iso9660->wbuff_remaining = sizeof(iso9660->wbuff); + } else if (off <= iso9660->wbuff_tail) { + iso9660->wbuff_remaining = + sizeof(iso9660->wbuff) - (off - iso9660->wbuff_offset); + } else { + ext_bytes = off - iso9660->wbuff_tail; + iso9660->wbuff_remaining = sizeof(iso9660->wbuff) + - (iso9660->wbuff_tail - iso9660->wbuff_offset); + while (ext_bytes >= iso9660->wbuff_remaining) { + if (write_null(a, (size_t)iso9660->wbuff_remaining) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + ext_bytes -= iso9660->wbuff_remaining; + } + if (ext_bytes > 0) { + if (write_null(a, (size_t)ext_bytes) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + } + return (ARCHIVE_OK); +} + +#endif /* HAVE_ZLIB_H */ + +static int +write_null(struct archive_write *a, size_t size) +{ + size_t remaining; + unsigned char *p, *old; + int r; + + remaining = wb_remaining(a); + p = wb_buffptr(a); + if (size <= remaining) { + memset(p, 0, size); + return (wb_consume(a, size)); + } + memset(p, 0, remaining); + r = wb_consume(a, remaining); + if (r != ARCHIVE_OK) + return (r); + size -= remaining; + old = p; + p = wb_buffptr(a); + memset(p, 0, old - p); + remaining = wb_remaining(a); + while (size) { + size_t wsize = size; + + if (wsize > remaining) + wsize = remaining; + r = wb_consume(a, wsize); + if (r != ARCHIVE_OK) + return (r); + size -= wsize; + } + return (ARCHIVE_OK); +} + +/* + * Write Volume Descriptor Set Terminator + */ +static int +write_VD_terminator(struct archive_write *a) +{ + unsigned char *bp; + + bp = wb_buffptr(a) -1; + set_VD_bp(bp, VDT_TERMINATOR, 1); + set_unused_field_bp(bp, 8, LOGICAL_BLOCK_SIZE); + + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +static int +set_file_identifier(unsigned char *bp, int from, int to, enum vdc vdc, + struct archive_write *a, struct vdd *vdd, struct archive_string *id, + const char *label, int leading_under, enum char_type char_type) +{ + char identifier[256]; + struct isoent *isoent; + const char *ids; + size_t len; + int r; + + if (id->length > 0 && leading_under && id->s[0] != '_') { + if (char_type == A_CHAR) + r = set_str_a_characters_bp(a, bp, from, to, id->s, vdc); + else + r = set_str_d_characters_bp(a, bp, from, to, id->s, vdc); + } else if (id->length > 0) { + ids = id->s; + if (leading_under) + ids++; + isoent = isoent_find_entry(vdd->rootent, ids); + if (isoent == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Not Found %s `%s'.", + label, ids); + return (ARCHIVE_FATAL); + } + len = isoent->ext_off + isoent->ext_len; + if (vdd->vdd_type == VDD_JOLIET) { + if (len > sizeof(identifier)-2) + len = sizeof(identifier)-2; + } else { + if (len > sizeof(identifier)-1) + len = sizeof(identifier)-1; + } + memcpy(identifier, isoent->identifier, len); + identifier[len] = '\0'; + if (vdd->vdd_type == VDD_JOLIET) { + identifier[len+1] = 0; + vdc = VDC_UCS2_DIRECT; + } + if (char_type == A_CHAR) + r = set_str_a_characters_bp(a, bp, from, to, + identifier, vdc); + else + r = set_str_d_characters_bp(a, bp, from, to, + identifier, vdc); + } else { + if (char_type == A_CHAR) + r = set_str_a_characters_bp(a, bp, from, to, NULL, vdc); + else + r = set_str_d_characters_bp(a, bp, from, to, NULL, vdc); + } + return (r); +} + +/* + * Write Primary/Supplementary Volume Descriptor + */ +static int +write_VD(struct archive_write *a, struct vdd *vdd) +{ + struct iso9660 *iso9660; + unsigned char *bp; + uint16_t volume_set_size = 1; + char identifier[256]; + enum VD_type vdt; + enum vdc vdc; + unsigned char vd_ver, fst_ver; + int r; + + iso9660 = a->format_data; + switch (vdd->vdd_type) { + case VDD_JOLIET: + vdt = VDT_SUPPLEMENTARY; + vd_ver = fst_ver = 1; + vdc = VDC_UCS2; + break; + case VDD_ENHANCED: + vdt = VDT_SUPPLEMENTARY; + vd_ver = fst_ver = 2; + vdc = VDC_LOWERCASE; + break; + case VDD_PRIMARY: + default: + vdt = VDT_PRIMARY; + vd_ver = fst_ver = 1; +#ifdef COMPAT_MKISOFS + vdc = VDC_LOWERCASE; +#else + vdc = VDC_STD; +#endif + break; + } + + bp = wb_buffptr(a) -1; + /* Volume Descriptor Type */ + set_VD_bp(bp, vdt, vd_ver); + /* Unused Field */ + set_unused_field_bp(bp, 8, 8); + /* System Identifier */ + get_system_identitier(identifier, sizeof(identifier)); + r = set_str_a_characters_bp(a, bp, 9, 40, identifier, vdc); + if (r != ARCHIVE_OK) + return (r); + /* Volume Identifier */ + r = set_str_d_characters_bp(a, bp, 41, 72, + iso9660->volume_identifier.s, vdc); + if (r != ARCHIVE_OK) + return (r); + /* Unused Field */ + set_unused_field_bp(bp, 73, 80); + /* Volume Space Size */ + set_num_733(bp+81, iso9660->volume_space_size); + if (vdd->vdd_type == VDD_JOLIET) { + /* Escape Sequences */ + bp[89] = 0x25;/* UCS-2 Level 3 */ + bp[90] = 0x2F; + bp[91] = 0x45; + memset(bp + 92, 0, 120 - 92 + 1); + } else { + /* Unused Field */ + set_unused_field_bp(bp, 89, 120); + } + /* Volume Set Size */ + set_num_723(bp+121, volume_set_size); + /* Volume Sequence Number */ + set_num_723(bp+125, iso9660->volume_sequence_number); + /* Logical Block Size */ + set_num_723(bp+129, LOGICAL_BLOCK_SIZE); + /* Path Table Size */ + set_num_733(bp+133, vdd->path_table_size); + /* Location of Occurrence of Type L Path Table */ + set_num_731(bp+141, vdd->location_type_L_path_table); + /* Location of Optional Occurrence of Type L Path Table */ + set_num_731(bp+145, 0); + /* Location of Occurrence of Type M Path Table */ + set_num_732(bp+149, vdd->location_type_M_path_table); + /* Location of Optional Occurrence of Type M Path Table */ + set_num_732(bp+153, 0); + /* Directory Record for Root Directory(BP 157 to 190) */ + set_directory_record(bp+157, 190-157+1, vdd->rootent, + iso9660, DIR_REC_VD, vdd->vdd_type); + /* Volume Set Identifier */ + r = set_str_d_characters_bp(a, bp, 191, 318, "", vdc); + if (r != ARCHIVE_OK) + return (r); + /* Publisher Identifier */ + r = set_file_identifier(bp, 319, 446, vdc, a, vdd, + &(iso9660->publisher_identifier), + "Publisher File", 1, A_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Data Preparer Identifier */ + r = set_file_identifier(bp, 447, 574, vdc, a, vdd, + &(iso9660->data_preparer_identifier), + "Data Preparer File", 1, A_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Application Identifier */ + r = set_file_identifier(bp, 575, 702, vdc, a, vdd, + &(iso9660->application_identifier), + "Application File", 1, A_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Copyright File Identifier */ + r = set_file_identifier(bp, 703, 739, vdc, a, vdd, + &(iso9660->copyright_file_identifier), + "Copyright File", 0, D_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Abstract File Identifier */ + r = set_file_identifier(bp, 740, 776, vdc, a, vdd, + &(iso9660->abstract_file_identifier), + "Abstract File", 0, D_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Bibliongraphic File Identifier */ + r = set_file_identifier(bp, 777, 813, vdc, a, vdd, + &(iso9660->bibliographic_file_identifier), + "Bibliongraphic File", 0, D_CHAR); + if (r != ARCHIVE_OK) + return (r); + /* Volume Creation Date and Time */ + set_date_time(bp+814, iso9660->birth_time); + /* Volume Modification Date and Time */ + set_date_time(bp+831, iso9660->birth_time); + /* Volume Expiration Date and Time(obsolete) */ + set_date_time_null(bp+848); + /* Volume Effective Date and Time */ + set_date_time(bp+865, iso9660->birth_time); + /* File Structure Version */ + bp[882] = fst_ver; + /* Reserved */ + bp[883] = 0; + /* Application Use */ + memset(bp + 884, 0x20, 1395 - 884 + 1); + /* Reserved */ + set_unused_field_bp(bp, 1396, LOGICAL_BLOCK_SIZE); + + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +/* + * Write Boot Record Volume Descriptor + */ +static int +write_VD_boot_record(struct archive_write *a) +{ + struct iso9660 *iso9660; + unsigned char *bp; + + iso9660 = a->format_data; + bp = wb_buffptr(a) -1; + /* Volume Descriptor Type */ + set_VD_bp(bp, VDT_BOOT_RECORD, 1); + /* Boot System Identifier */ + memcpy(bp+8, "EL TORITO SPECIFICATION", 23); + set_unused_field_bp(bp, 8+23, 39); + /* Unused */ + set_unused_field_bp(bp, 40, 71); + /* Absolute pointer to first sector of Boot Catalog */ + set_num_731(bp+72, + iso9660->el_torito.catalog->file->content.location); + /* Unused */ + set_unused_field_bp(bp, 76, LOGICAL_BLOCK_SIZE); + + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +enum keytype { + KEY_FLG, + KEY_STR, + KEY_INT, + KEY_HEX, +}; +static void +set_option_info(struct archive_string *info, int *opt, const char *key, + enum keytype type, ...) +{ + va_list ap; + char prefix; + const char *s; + int d; + + prefix = (*opt==0)? ' ':','; + va_start(ap, type); + switch (type) { + case KEY_FLG: + d = va_arg(ap, int); + archive_string_sprintf(info, "%c%s%s", + prefix, (d == 0)?"!":"", key); + break; + case KEY_STR: + s = va_arg(ap, const char *); + archive_string_sprintf(info, "%c%s=%s", + prefix, key, s); + break; + case KEY_INT: + d = va_arg(ap, int); + archive_string_sprintf(info, "%c%s=%d", + prefix, key, d); + break; + case KEY_HEX: + d = va_arg(ap, int); + archive_string_sprintf(info, "%c%s=%x", + prefix, key, d); + break; + } + va_end(ap); + + *opt = 1; +} + +/* + * Make Non-ISO File System Information + */ +static int +write_information_block(struct archive_write *a) +{ + struct iso9660 *iso9660; + char buf[128]; + const char *v; + int opt, r; + struct archive_string info; + size_t info_size = LOGICAL_BLOCK_SIZE * + NON_ISO_FILE_SYSTEM_INFORMATION_BLOCK; + + iso9660 = (struct iso9660 *)a->format_data; + if (info_size > wb_remaining(a)) { + r = wb_write_out(a); + if (r != ARCHIVE_OK) + return (r); + } + archive_string_init(&info); + if (archive_string_ensure(&info, info_size) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + memset(info.s, 0, info_size); + opt = 0; +#if defined(HAVE__CTIME64_S) + _ctime64_s(buf, sizeof(buf), &(iso9660->birth_time)); +#elif defined(HAVE_CTIME_R) + ctime_r(&(iso9660->birth_time), buf); +#else + strncpy(buf, ctime(&(iso9660->birth_time)), sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; +#endif + archive_string_sprintf(&info, + "INFO %s%s", buf, archive_version_string()); + if (iso9660->opt.abstract_file != OPT_ABSTRACT_FILE_DEFAULT) + set_option_info(&info, &opt, "abstract-file", + KEY_STR, iso9660->abstract_file_identifier.s); + if (iso9660->opt.application_id != OPT_APPLICATION_ID_DEFAULT) + set_option_info(&info, &opt, "application-id", + KEY_STR, iso9660->application_identifier.s); + if (iso9660->opt.allow_vernum != OPT_ALLOW_VERNUM_DEFAULT) + set_option_info(&info, &opt, "allow-vernum", + KEY_FLG, iso9660->opt.allow_vernum); + if (iso9660->opt.biblio_file != OPT_BIBLIO_FILE_DEFAULT) + set_option_info(&info, &opt, "biblio-file", + KEY_STR, iso9660->bibliographic_file_identifier.s); + if (iso9660->opt.boot != OPT_BOOT_DEFAULT) + set_option_info(&info, &opt, "boot", + KEY_STR, iso9660->el_torito.boot_filename.s); + if (iso9660->opt.boot_catalog != OPT_BOOT_CATALOG_DEFAULT) + set_option_info(&info, &opt, "boot-catalog", + KEY_STR, iso9660->el_torito.catalog_filename.s); + if (iso9660->opt.boot_info_table != OPT_BOOT_INFO_TABLE_DEFAULT) + set_option_info(&info, &opt, "boot-info-table", + KEY_FLG, iso9660->opt.boot_info_table); + if (iso9660->opt.boot_load_seg != OPT_BOOT_LOAD_SEG_DEFAULT) + set_option_info(&info, &opt, "boot-load-seg", + KEY_HEX, iso9660->el_torito.boot_load_seg); + if (iso9660->opt.boot_load_size != OPT_BOOT_LOAD_SIZE_DEFAULT) + set_option_info(&info, &opt, "boot-load-size", + KEY_INT, iso9660->el_torito.boot_load_size); + if (iso9660->opt.boot_type != OPT_BOOT_TYPE_DEFAULT) { + v = "no-emulation"; + if (iso9660->opt.boot_type == OPT_BOOT_TYPE_FD) + v = "fd"; + if (iso9660->opt.boot_type == OPT_BOOT_TYPE_HARD_DISK) + v = "hard-disk"; + set_option_info(&info, &opt, "boot-type", + KEY_STR, v); + } +#ifdef HAVE_ZLIB_H + if (iso9660->opt.compression_level != OPT_COMPRESSION_LEVEL_DEFAULT) + set_option_info(&info, &opt, "compression-level", + KEY_INT, iso9660->zisofs.compression_level); +#endif + if (iso9660->opt.copyright_file != OPT_COPYRIGHT_FILE_DEFAULT) + set_option_info(&info, &opt, "copyright-file", + KEY_STR, iso9660->copyright_file_identifier.s); + if (iso9660->opt.iso_level != OPT_ISO_LEVEL_DEFAULT) + set_option_info(&info, &opt, "iso-level", + KEY_INT, iso9660->opt.iso_level); + if (iso9660->opt.joliet != OPT_JOLIET_DEFAULT) { + if (iso9660->opt.joliet == OPT_JOLIET_LONGNAME) + set_option_info(&info, &opt, "joliet", + KEY_STR, "long"); + else + set_option_info(&info, &opt, "joliet", + KEY_FLG, iso9660->opt.joliet); + } + if (iso9660->opt.limit_depth != OPT_LIMIT_DEPTH_DEFAULT) + set_option_info(&info, &opt, "limit-depth", + KEY_FLG, iso9660->opt.limit_depth); + if (iso9660->opt.limit_dirs != OPT_LIMIT_DIRS_DEFAULT) + set_option_info(&info, &opt, "limit-dirs", + KEY_FLG, iso9660->opt.limit_dirs); + if (iso9660->opt.pad != OPT_PAD_DEFAULT) + set_option_info(&info, &opt, "pad", + KEY_FLG, iso9660->opt.pad); + if (iso9660->opt.publisher != OPT_PUBLISHER_DEFAULT) + set_option_info(&info, &opt, "publisher", + KEY_STR, iso9660->publisher_identifier.s); + if (iso9660->opt.rr != OPT_RR_DEFAULT) { + if (iso9660->opt.rr == OPT_RR_DISABLED) + set_option_info(&info, &opt, "rockridge", + KEY_FLG, iso9660->opt.rr); + else if (iso9660->opt.rr == OPT_RR_STRICT) + set_option_info(&info, &opt, "rockridge", + KEY_STR, "strict"); + else if (iso9660->opt.rr == OPT_RR_USEFUL) + set_option_info(&info, &opt, "rockridge", + KEY_STR, "useful"); + } + if (iso9660->opt.volume_id != OPT_VOLUME_ID_DEFAULT) + set_option_info(&info, &opt, "volume-id", + KEY_STR, iso9660->volume_identifier.s); + if (iso9660->opt.zisofs != OPT_ZISOFS_DEFAULT) + set_option_info(&info, &opt, "zisofs", + KEY_FLG, iso9660->opt.zisofs); + + memcpy(wb_buffptr(a), info.s, info_size); + archive_string_free(&info); + return (wb_consume(a, info_size)); +} + +static int +write_rr_ER(struct archive_write *a) +{ + unsigned char *p; + + p = wb_buffptr(a); + + memset(p, 0, LOGICAL_BLOCK_SIZE); + p[0] = 'E'; + p[1] = 'R'; + p[3] = 0x01; + p[2] = RRIP_ER_SIZE; + p[4] = RRIP_ER_ID_SIZE; + p[5] = RRIP_ER_DSC_SIZE; + p[6] = RRIP_ER_SRC_SIZE; + p[7] = 0x01; + memcpy(&p[8], rrip_identifier, p[4]); + memcpy(&p[8+p[4]], rrip_descriptor, p[5]); + memcpy(&p[8+p[4]+p[5]], rrip_source, p[6]); + + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +static void +calculate_path_table_size(struct vdd *vdd) +{ + int depth, size; + struct path_table *pt; + + pt = vdd->pathtbl; + size = 0; + for (depth = 0; depth < vdd->max_depth; depth++) { + struct isoent **ptbl; + int i, cnt; + + if ((cnt = pt[depth].cnt) == 0) + break; + + ptbl = pt[depth].sorted; + for (i = 0; i < cnt; i++) { + int len; + + if (ptbl[i]->identifier == NULL) + len = 1; /* root directory */ + else + len = ptbl[i]->id_len; + if (len & 0x01) + len++; /* Padding Field */ + size += 8 + len; + } + } + vdd->path_table_size = size; + vdd->path_table_block = + ((size + PATH_TABLE_BLOCK_SIZE -1) / + PATH_TABLE_BLOCK_SIZE) * + (PATH_TABLE_BLOCK_SIZE / LOGICAL_BLOCK_SIZE); +} + +static int +_write_path_table(struct archive_write *a, int type_m, int depth, + struct vdd *vdd) +{ + unsigned char *bp, *wb; + struct isoent **ptbl; + size_t wbremaining; + int i, r, wsize; + + if (vdd->pathtbl[depth].cnt == 0) + return (0); + + wsize = 0; + wb = wb_buffptr(a); + wbremaining = wb_remaining(a); + bp = wb - 1; + ptbl = vdd->pathtbl[depth].sorted; + for (i = 0; i < vdd->pathtbl[depth].cnt; i++) { + struct isoent *np; + size_t len; + + np = ptbl[i]; + if (np->identifier == NULL) + len = 1; /* root directory */ + else + len = np->id_len; + if (wbremaining - ((bp+1) - wb) < (len + 1 + 8)) { + r = wb_consume(a, (bp+1) - wb); + if (r < 0) + return (r); + wb = wb_buffptr(a); + wbremaining = wb_remaining(a); + bp = wb -1; + } + /* Length of Directory Identifier */ + set_num_711(bp+1, len); + /* Extended Attribute Record Length */ + set_num_711(bp+2, 0); + /* Location of Extent */ + if (type_m) + set_num_732(bp+3, np->dir_location); + else + set_num_731(bp+3, np->dir_location); + /* Parent Directory Number */ + if (type_m) + set_num_722(bp+7, np->parent->dir_number); + else + set_num_721(bp+7, np->parent->dir_number); + /* Directory Identifier */ + if (np->identifier == NULL) + bp[9] = 0; + else + memcpy(&bp[9], np->identifier, len); + if (len & 0x01) { + /* Padding Field */ + bp[9+len] = 0; + len++; + } + wsize += 8 + len; + bp += 8 + len; + } + if ((bp + 1) > wb) { + r = wb_consume(a, (bp+1)-wb); + if (r < 0) + return (r); + } + return (wsize); +} + +static int +write_path_table(struct archive_write *a, int type_m, struct vdd *vdd) +{ + int depth, r; + size_t path_table_size; + + r = ARCHIVE_OK; + path_table_size = 0; + for (depth = 0; depth < vdd->max_depth; depth++) { + r = _write_path_table(a, type_m, depth, vdd); + if (r < 0) + return (r); + path_table_size += r; + } + + /* Write padding data. */ + path_table_size = path_table_size % PATH_TABLE_BLOCK_SIZE; + if (path_table_size > 0) + r = write_null(a, PATH_TABLE_BLOCK_SIZE - path_table_size); + return (r); +} + +static int +calculate_directory_descriptors(struct iso9660 *iso9660, struct vdd *vdd, + struct isoent *isoent, int depth) +{ + struct isoent **enttbl; + int bs, block, i; + + block = 1; + bs = get_dir_rec_size(iso9660, isoent, DIR_REC_SELF, vdd->vdd_type); + bs += get_dir_rec_size(iso9660, isoent, DIR_REC_PARENT, vdd->vdd_type); + + if (isoent->children.cnt <= 0 || (vdd->vdd_type != VDD_JOLIET && + !iso9660->opt.rr && depth + 1 >= vdd->max_depth)) + return (block); + + enttbl = isoent->children_sorted; + for (i = 0; i < isoent->children.cnt; i++) { + struct isoent *np = enttbl[i]; + struct isofile *file; + + file = np->file; + if (file->hardlink_target != NULL) + file = file->hardlink_target; + file->cur_content = &(file->content); + do { + int dr_l; + + dr_l = get_dir_rec_size(iso9660, np, DIR_REC_NORMAL, + vdd->vdd_type); + if ((bs + dr_l) > LOGICAL_BLOCK_SIZE) { + block ++; + bs = dr_l; + } else + bs += dr_l; + file->cur_content = file->cur_content->next; + } while (file->cur_content != NULL); + } + return (block); +} + +static int +_write_directory_descriptors(struct archive_write *a, struct vdd *vdd, + struct isoent *isoent, int depth) +{ + struct iso9660 *iso9660 = a->format_data; + struct isoent **enttbl; + unsigned char *p, *wb; + int i, r; + int dr_l; + + p = wb = wb_buffptr(a); +#define WD_REMAINING (LOGICAL_BLOCK_SIZE - (p - wb)) + p += set_directory_record(p, WD_REMAINING, isoent, + iso9660, DIR_REC_SELF, vdd->vdd_type); + p += set_directory_record(p, WD_REMAINING, isoent, + iso9660, DIR_REC_PARENT, vdd->vdd_type); + + if (isoent->children.cnt <= 0 || (vdd->vdd_type != VDD_JOLIET && + !iso9660->opt.rr && depth + 1 >= vdd->max_depth)) { + memset(p, 0, WD_REMAINING); + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); + } + + enttbl = isoent->children_sorted; + for (i = 0; i < isoent->children.cnt; i++) { + struct isoent *np = enttbl[i]; + struct isofile *file = np->file; + + if (file->hardlink_target != NULL) + file = file->hardlink_target; + file->cur_content = &(file->content); + do { + dr_l = set_directory_record(p, WD_REMAINING, + np, iso9660, DIR_REC_NORMAL, + vdd->vdd_type); + if (dr_l == 0) { + memset(p, 0, WD_REMAINING); + r = wb_consume(a, LOGICAL_BLOCK_SIZE); + if (r < 0) + return (r); + p = wb = wb_buffptr(a); + dr_l = set_directory_record(p, + WD_REMAINING, np, iso9660, + DIR_REC_NORMAL, vdd->vdd_type); + } + p += dr_l; + file->cur_content = file->cur_content->next; + } while (file->cur_content != NULL); + } + memset(p, 0, WD_REMAINING); + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +static int +write_directory_descriptors(struct archive_write *a, struct vdd *vdd) +{ + struct isoent *np; + int depth, r; + + depth = 0; + np = vdd->rootent; + do { + struct extr_rec *extr; + + r = _write_directory_descriptors(a, vdd, np, depth); + if (r < 0) + return (r); + if (vdd->vdd_type != VDD_JOLIET) { + /* + * This extract record is used by SUSP,RRIP. + * Not for joliet. + */ + for (extr = np->extr_rec_list.first; + extr != NULL; + extr = extr->next) { + unsigned char *wb; + + wb = wb_buffptr(a); + memcpy(wb, extr->buf, extr->offset); + memset(wb + extr->offset, 0, + LOGICAL_BLOCK_SIZE - extr->offset); + r = wb_consume(a, LOGICAL_BLOCK_SIZE); + if (r < 0) + return (r); + } + } + + if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) { + /* Enter to sub directories. */ + np = np->subdirs.first; + depth++; + continue; + } + while (np != np->parent) { + if (np->drnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + depth--; + } else { + np = np->drnext; + break; + } + } + } while (np != np->parent); + + return (ARCHIVE_OK); +} + +/* + * Read file contents from the temporary file, and write it. + */ +static int +write_file_contents(struct archive_write *a, int64_t offset, int64_t size) +{ + struct iso9660 *iso9660 = a->format_data; + int r; + + lseek(iso9660->temp_fd, offset, SEEK_SET); + + while (size) { + size_t rsize; + ssize_t rs; + unsigned char *wb; + + wb = wb_buffptr(a); + rsize = wb_remaining(a); + if (rsize > (size_t)size) + rsize = (size_t)size; + rs = read(iso9660->temp_fd, wb, rsize); + if (rs <= 0) { + archive_set_error(&a->archive, errno, + "Can't read temporary file(%jd)", (intmax_t)rs); + return (ARCHIVE_FATAL); + } + size -= rs; + r = wb_consume(a, rs); + if (r < 0) + return (r); + } + return (ARCHIVE_OK); +} + +static int +write_file_descriptors(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file; + int64_t blocks, offset; + int r; + + blocks = 0; + offset = 0; + + /* Make the boot catalog contents, and write it. */ + if (iso9660->el_torito.catalog != NULL) { + r = make_boot_catalog(a); + if (r < 0) + return (r); + } + + /* Write the boot file contents. */ + if (iso9660->el_torito.boot != NULL) { + struct isofile *file = iso9660->el_torito.boot->file; + + blocks = file->content.blocks; + offset = file->content.offset_of_temp; + if (offset != 0) { + r = write_file_contents(a, offset, + blocks << LOGICAL_BLOCK_BITS); + if (r < 0) + return (r); + blocks = 0; + offset = 0; + } + } + + /* Write out all file contents. */ + for (file = iso9660->data_file_list.first; + file != NULL; file = file->datanext) { + + if (!file->write_content) + continue; + + if ((offset + (blocks << LOGICAL_BLOCK_BITS)) < + file->content.offset_of_temp) { + if (blocks > 0) { + r = write_file_contents(a, offset, + blocks << LOGICAL_BLOCK_BITS); + if (r < 0) + return (r); + } + blocks = 0; + offset = file->content.offset_of_temp; + } + + file->cur_content = &(file->content); + do { + blocks += file->cur_content->blocks; + /* Next fragument */ + file->cur_content = file->cur_content->next; + } while (file->cur_content != NULL); + } + + /* Flush out remaining blocks. */ + if (blocks > 0) { + r = write_file_contents(a, offset, + blocks << LOGICAL_BLOCK_BITS); + if (r < 0) + return (r); + } + + return (ARCHIVE_OK); +} + +static void +isofile_init_entry_list(struct iso9660 *iso9660) +{ + iso9660->all_file_list.first = NULL; + iso9660->all_file_list.last = &(iso9660->all_file_list.first); +} + +static void +isofile_add_entry(struct iso9660 *iso9660, struct isofile *file) +{ + file->allnext = NULL; + *iso9660->all_file_list.last = file; + iso9660->all_file_list.last = &(file->allnext); +} + +static void +isofile_free_all_entries(struct iso9660 *iso9660) +{ + struct isofile *file, *file_next; + + file = iso9660->all_file_list.first; + while (file != NULL) { + file_next = file->allnext; + isofile_free(file); + file = file_next; + } +} + +static void +isofile_init_entry_data_file_list(struct iso9660 *iso9660) +{ + iso9660->data_file_list.first = NULL; + iso9660->data_file_list.last = &(iso9660->data_file_list.first); +} + +static void +isofile_add_data_file(struct iso9660 *iso9660, struct isofile *file) +{ + file->datanext = NULL; + *iso9660->data_file_list.last = file; + iso9660->data_file_list.last = &(file->datanext); +} + + +static struct isofile * +isofile_new(struct archive_write *a, struct archive_entry *entry) +{ + struct isofile *file; + + file = calloc(1, sizeof(*file)); + if (file == NULL) + return (NULL); + + if (entry != NULL) + file->entry = archive_entry_clone(entry); + else + file->entry = archive_entry_new2(&a->archive); + if (file->entry == NULL) { + free(file); + return (NULL); + } + archive_string_init(&(file->parentdir)); + archive_string_init(&(file->basename)); + archive_string_init(&(file->basename_utf16)); + archive_string_init(&(file->symlink)); + file->cur_content = &(file->content); + + return (file); +} + +static void +isofile_free(struct isofile *file) +{ + struct content *con, *tmp; + + con = file->content.next; + while (con != NULL) { + tmp = con; + con = con->next; + free(tmp); + } + archive_entry_free(file->entry); + archive_string_free(&(file->parentdir)); + archive_string_free(&(file->basename)); + archive_string_free(&(file->basename_utf16)); + archive_string_free(&(file->symlink)); + free(file); +} + +#if defined(_WIN32) || defined(__CYGWIN__) +static int +cleanup_backslash_1(char *p) +{ + int mb, dos; + + mb = dos = 0; + while (*p) { + if (*(unsigned char *)p > 127) + mb = 1; + if (*p == '\\') { + /* If we have not met any multi-byte characters, + * we can replace '\' with '/'. */ + if (!mb) + *p = '/'; + dos = 1; + } + p++; + } + if (!mb || !dos) + return (0); + return (-1); +} + +static void +cleanup_backslash_2(wchar_t *p) +{ + + /* Convert a path-separator from '\' to '/' */ + while (*p != L'\0') { + if (*p == L'\\') + *p = L'/'; + p++; + } +} +#endif + +/* + * Generate a parent directory name and a base name from a pathname. + */ +static int +isofile_gen_utility_names(struct archive_write *a, struct isofile *file) +{ + struct iso9660 *iso9660; + const char *pathname; + char *p, *dirname, *slash; + size_t len; + int ret = ARCHIVE_OK; + + iso9660 = a->format_data; + + archive_string_empty(&(file->parentdir)); + archive_string_empty(&(file->basename)); + archive_string_empty(&(file->basename_utf16)); + archive_string_empty(&(file->symlink)); + + pathname = archive_entry_pathname(file->entry); + if (pathname == NULL || pathname[0] == '\0') {/* virtual root */ + file->dircnt = 0; + return (ret); + } + + /* + * Make a UTF-16BE basename if Joliet extension enabled. + */ + if (iso9660->opt.joliet) { + const char *u16, *ulast; + size_t u16len, ulen_last; + + if (iso9660->sconv_to_utf16be == NULL) { + iso9660->sconv_to_utf16be = + archive_string_conversion_to_charset( + &(a->archive), "UTF-16BE", 1); + if (iso9660->sconv_to_utf16be == NULL) + /* Couldn't allocate memory */ + return (ARCHIVE_FATAL); + iso9660->sconv_from_utf16be = + archive_string_conversion_from_charset( + &(a->archive), "UTF-16BE", 1); + if (iso9660->sconv_from_utf16be == NULL) + /* Couldn't allocate memory */ + return (ARCHIVE_FATAL); + } + + /* + * Converte a filename to UTF-16BE. + */ + if (0 > archive_entry_pathname_l(file->entry, &u16, &u16len, + iso9660->sconv_to_utf16be)) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for UTF-16BE"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "A filename cannot be converted to UTF-16BE;" + "You should disable making Joliet extension"); + ret = ARCHIVE_WARN; + } + + /* + * Make sure a path separator is not in the last; + * Remove trailing '/'. + */ + while (u16len >= 2) { +#if defined(_WIN32) || defined(__CYGWIN__) + if (u16[u16len-2] == 0 && + (u16[u16len-1] == '/' || u16[u16len-1] == '\\')) +#else + if (u16[u16len-2] == 0 && u16[u16len-1] == '/') +#endif + { + u16len -= 2; + } else + break; + } + + /* + * Find a basename in UTF-16BE. + */ + ulast = u16; + u16len >>= 1; + ulen_last = u16len; + while (u16len > 0) { +#if defined(_WIN32) || defined(__CYGWIN__) + if (u16[0] == 0 && (u16[1] == '/' || u16[1] == '\\')) +#else + if (u16[0] == 0 && u16[1] == '/') +#endif + { + ulast = u16 + 2; + ulen_last = u16len -1; + } + u16 += 2; + u16len --; + } + ulen_last <<= 1; + if (archive_string_ensure(&(file->basename_utf16), + ulen_last) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for UTF-16BE"); + return (ARCHIVE_FATAL); + } + + /* + * Set UTF-16BE basename. + */ + memcpy(file->basename_utf16.s, ulast, ulen_last); + file->basename_utf16.length = ulen_last; + } + + archive_strcpy(&(file->parentdir), pathname); +#if defined(_WIN32) || defined(__CYGWIN__) + /* + * Convert a path-separator from '\' to '/' + */ + if (cleanup_backslash_1(file->parentdir.s) != 0) { + const wchar_t *wp = archive_entry_pathname_w(file->entry); + struct archive_wstring ws; + + if (wp != NULL) { + archive_string_init(&ws); + archive_wstrcpy(&ws, wp); + cleanup_backslash_2(ws.s); + archive_string_empty(&(file->parentdir)); + archive_string_append_from_wcs(&(file->parentdir), + ws.s, ws.length); + archive_wstring_free(&ws); + } + } +#endif + + len = file->parentdir.length; + p = dirname = file->parentdir.s; + + /* + * Remove leading '/', '../' and './' elements + */ + while (*p) { + if (p[0] == '/') { + p++; + len--; + } else if (p[0] != '.') + break; + else if (p[1] == '.' && p[2] == '/') { + p += 3; + len -= 3; + } else if (p[1] == '/' || (p[1] == '.' && p[2] == '\0')) { + p += 2; + len -= 2; + } else if (p[1] == '\0') { + p++; + len--; + } else + break; + } + if (p != dirname) { + memmove(dirname, p, len+1); + p = dirname; + } + /* + * Remove "/","/." and "/.." elements from tail. + */ + while (len > 0) { + size_t ll = len; + + if (len > 0 && p[len-1] == '/') { + p[len-1] = '\0'; + len--; + } + if (len > 1 && p[len-2] == '/' && p[len-1] == '.') { + p[len-2] = '\0'; + len -= 2; + } + if (len > 2 && p[len-3] == '/' && p[len-2] == '.' && + p[len-1] == '.') { + p[len-3] = '\0'; + len -= 3; + } + if (ll == len) + break; + } + while (*p) { + if (p[0] == '/') { + if (p[1] == '/') + /* Convert '//' --> '/' */ + strcpy(p, p+1); + else if (p[1] == '.' && p[2] == '/') + /* Convert '/./' --> '/' */ + strcpy(p, p+2); + else if (p[1] == '.' && p[2] == '.' && p[3] == '/') { + /* Convert 'dir/dir1/../dir2/' + * --> 'dir/dir2/' + */ + char *rp = p -1; + while (rp >= dirname) { + if (*rp == '/') + break; + --rp; + } + if (rp > dirname) { + strcpy(rp, p+3); + p = rp; + } else { + strcpy(dirname, p+4); + p = dirname; + } + } else + p++; + } else + p++; + } + p = dirname; + len = strlen(p); + + if (archive_entry_filetype(file->entry) == AE_IFLNK) { + /* Convert symlink name too. */ + pathname = archive_entry_symlink(file->entry); + archive_strcpy(&(file->symlink), pathname); +#if defined(_WIN32) || defined(__CYGWIN__) + /* + * Convert a path-separator from '\' to '/' + */ + if (archive_strlen(&(file->symlink)) > 0 && + cleanup_backslash_1(file->symlink.s) != 0) { + const wchar_t *wp = + archive_entry_symlink_w(file->entry); + struct archive_wstring ws; + + if (wp != NULL) { + archive_string_init(&ws); + archive_wstrcpy(&ws, wp); + cleanup_backslash_2(ws.s); + archive_string_empty(&(file->symlink)); + archive_string_append_from_wcs( + &(file->symlink), + ws.s, ws.length); + archive_wstring_free(&ws); + } + } +#endif + } + /* + * - Count up directory elements. + * - Find out the position which points the last position of + * path separator('/'). + */ + slash = NULL; + file->dircnt = 0; + for (; *p != '\0'; p++) + if (*p == '/') { + slash = p; + file->dircnt++; + } + if (slash == NULL) { + /* The pathname doesn't have a parent directory. */ + file->parentdir.length = len; + archive_string_copy(&(file->basename), &(file->parentdir)); + archive_string_empty(&(file->parentdir)); + *file->parentdir.s = '\0'; + return (ret); + } + + /* Make a basename from dirname and slash */ + *slash = '\0'; + file->parentdir.length = slash - dirname; + archive_strcpy(&(file->basename), slash + 1); + if (archive_entry_filetype(file->entry) == AE_IFDIR) + file->dircnt ++; + return (ret); +} + +/* + * Register a entry to get a hardlink target. + */ +static int +isofile_register_hardlink(struct archive_write *a, struct isofile *file) +{ + struct iso9660 *iso9660 = a->format_data; + struct hardlink *hl; + const char *pathname; + + archive_entry_set_nlink(file->entry, 1); + pathname = archive_entry_hardlink(file->entry); + if (pathname == NULL) { + /* This `file` is a hardlink target. */ + hl = malloc(sizeof(*hl)); + if (hl == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + hl->nlink = 1; + /* A hardlink target must be the first position. */ + file->hlnext = NULL; + hl->file_list.first = file; + hl->file_list.last = &(file->hlnext); + __archive_rb_tree_insert_node(&(iso9660->hardlink_rbtree), + (struct archive_rb_node *)hl); + } else { + hl = (struct hardlink *)__archive_rb_tree_find_node( + &(iso9660->hardlink_rbtree), pathname); + if (hl != NULL) { + /* Insert `file` entry into the tail. */ + file->hlnext = NULL; + *hl->file_list.last = file; + hl->file_list.last = &(file->hlnext); + hl->nlink++; + } + archive_entry_unset_size(file->entry); + } + + return (ARCHIVE_OK); +} + +/* + * Hardlinked files have to have the same location of extent. + * We have to find out hardlink target entries for the entries + * which have a hardlink target name. + */ +static void +isofile_connect_hardlink_files(struct iso9660 *iso9660) +{ + struct archive_rb_node *n; + struct hardlink *hl; + struct isofile *target, *nf; + + ARCHIVE_RB_TREE_FOREACH(n, &(iso9660->hardlink_rbtree)) { + hl = (struct hardlink *)n; + + /* The first entry must be a hardlink target. */ + target = hl->file_list.first; + archive_entry_set_nlink(target->entry, hl->nlink); + /* Set a hardlink target to reference entries. */ + for (nf = target->hlnext; + nf != NULL; nf = nf->hlnext) { + nf->hardlink_target = target; + archive_entry_set_nlink(nf->entry, hl->nlink); + } + } +} + +static int +isofile_hd_cmp_node(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + const struct hardlink *h1 = (const struct hardlink *)n1; + const struct hardlink *h2 = (const struct hardlink *)n2; + + return (strcmp(archive_entry_pathname(h1->file_list.first->entry), + archive_entry_pathname(h2->file_list.first->entry))); +} + +static int +isofile_hd_cmp_key(const struct archive_rb_node *n, const void *key) +{ + const struct hardlink *h = (const struct hardlink *)n; + + return (strcmp(archive_entry_pathname(h->file_list.first->entry), + (const char *)key)); +} + +static void +isofile_init_hardlinks(struct iso9660 *iso9660) +{ + static const struct archive_rb_tree_ops rb_ops = { + isofile_hd_cmp_node, isofile_hd_cmp_key, + }; + + __archive_rb_tree_init(&(iso9660->hardlink_rbtree), &rb_ops); +} + +static void +isofile_free_hardlinks(struct iso9660 *iso9660) +{ + struct archive_rb_node *n, *next; + + for (n = ARCHIVE_RB_TREE_MIN(&(iso9660->hardlink_rbtree)); n;) { + next = __archive_rb_tree_iterate(&(iso9660->hardlink_rbtree), + n, ARCHIVE_RB_DIR_RIGHT); + free(n); + n = next; + } +} + +static struct isoent * +isoent_new(struct isofile *file) +{ + struct isoent *isoent; + static const struct archive_rb_tree_ops rb_ops = { + isoent_cmp_node, isoent_cmp_key, + }; + + isoent = calloc(1, sizeof(*isoent)); + if (isoent == NULL) + return (NULL); + isoent->file = file; + isoent->children.first = NULL; + isoent->children.last = &(isoent->children.first); + __archive_rb_tree_init(&(isoent->rbtree), &rb_ops); + isoent->subdirs.first = NULL; + isoent->subdirs.last = &(isoent->subdirs.first); + isoent->extr_rec_list.first = NULL; + isoent->extr_rec_list.last = &(isoent->extr_rec_list.first); + isoent->extr_rec_list.current = NULL; + if (archive_entry_filetype(file->entry) == AE_IFDIR) + isoent->dir = 1; + + return (isoent); +} + +static inline struct isoent * +isoent_clone(struct isoent *src) +{ + return (isoent_new(src->file)); +} + +static void +_isoent_free(struct isoent *isoent) +{ + struct extr_rec *er, *er_next; + + free(isoent->children_sorted); + free(isoent->identifier); + er = isoent->extr_rec_list.first; + while (er != NULL) { + er_next = er->next; + free(er); + er = er_next; + } + free(isoent); +} + +static void +isoent_free_all(struct isoent *isoent) +{ + struct isoent *np, *np_temp; + + if (isoent == NULL) + return; + np = isoent; + for (;;) { + if (np->dir) { + if (np->children.first != NULL) { + /* Enter to sub directories. */ + np = np->children.first; + continue; + } + } + for (;;) { + np_temp = np; + if (np->chnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + _isoent_free(np_temp); + if (np == np_temp) + return; + } else { + np = np->chnext; + _isoent_free(np_temp); + break; + } + } + } +} + +static struct isoent * +isoent_create_virtual_dir(struct archive_write *a, struct iso9660 *iso9660, const char *pathname) +{ + struct isofile *file; + struct isoent *isoent; + + file = isofile_new(a, NULL); + if (file == NULL) + return (NULL); + archive_entry_set_pathname(file->entry, pathname); + archive_entry_unset_mtime(file->entry); + archive_entry_unset_atime(file->entry); + archive_entry_unset_ctime(file->entry); + archive_entry_set_uid(file->entry, getuid()); + archive_entry_set_gid(file->entry, getgid()); + archive_entry_set_mode(file->entry, 0555 | AE_IFDIR); + archive_entry_set_nlink(file->entry, 2); + if (isofile_gen_utility_names(a, file) < ARCHIVE_WARN) { + isofile_free(file); + return (NULL); + } + isofile_add_entry(iso9660, file); + + isoent = isoent_new(file); + if (isoent == NULL) + return (NULL); + isoent->dir = 1; + isoent->virtual = 1; + + return (isoent); +} + +static int +isoent_cmp_node(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + const struct isoent *e1 = (const struct isoent *)n1; + const struct isoent *e2 = (const struct isoent *)n2; + + return (strcmp(e1->file->basename.s, e2->file->basename.s)); +} + +static int +isoent_cmp_key(const struct archive_rb_node *n, const void *key) +{ + const struct isoent *e = (const struct isoent *)n; + + return (strcmp(e->file->basename.s, (const char *)key)); +} + +static int +isoent_add_child_head(struct isoent *parent, struct isoent *child) +{ + + if (!__archive_rb_tree_insert_node( + &(parent->rbtree), (struct archive_rb_node *)child)) + return (0); + if ((child->chnext = parent->children.first) == NULL) + parent->children.last = &(child->chnext); + parent->children.first = child; + parent->children.cnt++; + child->parent = parent; + + /* Add a child to a sub-directory chain */ + if (child->dir) { + if ((child->drnext = parent->subdirs.first) == NULL) + parent->subdirs.last = &(child->drnext); + parent->subdirs.first = child; + parent->subdirs.cnt++; + child->parent = parent; + } else + child->drnext = NULL; + return (1); +} + +static int +isoent_add_child_tail(struct isoent *parent, struct isoent *child) +{ + + if (!__archive_rb_tree_insert_node( + &(parent->rbtree), (struct archive_rb_node *)child)) + return (0); + child->chnext = NULL; + *parent->children.last = child; + parent->children.last = &(child->chnext); + parent->children.cnt++; + child->parent = parent; + + /* Add a child to a sub-directory chain */ + child->drnext = NULL; + if (child->dir) { + *parent->subdirs.last = child; + parent->subdirs.last = &(child->drnext); + parent->subdirs.cnt++; + child->parent = parent; + } + return (1); +} + +static void +isoent_remove_child(struct isoent *parent, struct isoent *child) +{ + struct isoent *ent; + + /* Remove a child entry from children chain. */ + ent = parent->children.first; + while (ent->chnext != child) + ent = ent->chnext; + if ((ent->chnext = ent->chnext->chnext) == NULL) + parent->children.last = &(ent->chnext); + parent->children.cnt--; + + if (child->dir) { + /* Remove a child entry from sub-directory chain. */ + ent = parent->subdirs.first; + while (ent->drnext != child) + ent = ent->drnext; + if ((ent->drnext = ent->drnext->drnext) == NULL) + parent->subdirs.last = &(ent->drnext); + parent->subdirs.cnt--; + } + + __archive_rb_tree_remove_node(&(parent->rbtree), + (struct archive_rb_node *)child); +} + +static int +isoent_clone_tree(struct archive_write *a, struct isoent **nroot, + struct isoent *root) +{ + struct isoent *np, *xroot, *newent; + + np = root; + xroot = NULL; + do { + newent = isoent_clone(np); + if (newent == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + if (xroot == NULL) { + *nroot = xroot = newent; + newent->parent = xroot; + } else + isoent_add_child_tail(xroot, newent); + if (np->dir && np->children.first != NULL) { + /* Enter to sub directories. */ + np = np->children.first; + xroot = newent; + continue; + } + while (np != np->parent) { + if (np->chnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + xroot = xroot->parent; + } else { + np = np->chnext; + break; + } + } + } while (np != np->parent); + + return (ARCHIVE_OK); +} + +/* + * Setup directory locations. + */ +static void +isoent_setup_directory_location(struct iso9660 *iso9660, int location, + struct vdd *vdd) +{ + struct isoent *np; + int depth; + + vdd->total_dir_block = 0; + depth = 0; + np = vdd->rootent; + do { + int block; + + np->dir_block = calculate_directory_descriptors( + iso9660, vdd, np, depth); + vdd->total_dir_block += np->dir_block; + np->dir_location = location; + location += np->dir_block; + block = extra_setup_location(np, location); + vdd->total_dir_block += block; + location += block; + + if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) { + /* Enter to sub directories. */ + np = np->subdirs.first; + depth++; + continue; + } + while (np != np->parent) { + if (np->drnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + depth--; + } else { + np = np->drnext; + break; + } + } + } while (np != np->parent); +} + +static void +_isoent_file_location(struct iso9660 *iso9660, struct isoent *isoent, + int *symlocation) +{ + struct isoent **children; + int n; + + if (isoent->children.cnt == 0) + return; + + children = isoent->children_sorted; + for (n = 0; n < isoent->children.cnt; n++) { + struct isoent *np; + struct isofile *file; + + np = children[n]; + if (np->dir) + continue; + if (np == iso9660->el_torito.boot) + continue; + file = np->file; + if (file->boot || file->hardlink_target != NULL) + continue; + if (archive_entry_filetype(file->entry) == AE_IFLNK || + file->content.size == 0) { + /* + * Do not point a valid location. + * Make sure entry is not hardlink file. + */ + file->content.location = (*symlocation)--; + continue; + } + + file->write_content = 1; + } +} + +/* + * Setup file locations. + */ +static void +isoent_setup_file_location(struct iso9660 *iso9660, int location) +{ + struct isoent *isoent; + struct isoent *np; + struct isofile *file; + size_t size; + int block; + int depth; + int joliet; + int symlocation; + int total_block; + + iso9660->total_file_block = 0; + if ((isoent = iso9660->el_torito.catalog) != NULL) { + isoent->file->content.location = location; + block = (archive_entry_size(isoent->file->entry) + + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS; + location += block; + iso9660->total_file_block += block; + } + if ((isoent = iso9660->el_torito.boot) != NULL) { + isoent->file->content.location = location; + size = fd_boot_image_size(iso9660->el_torito.media_type); + if (size == 0) + size = archive_entry_size(isoent->file->entry); + block = (size + LOGICAL_BLOCK_SIZE -1) >> LOGICAL_BLOCK_BITS; + location += block; + iso9660->total_file_block += block; + isoent->file->content.blocks = block; + } + + depth = 0; + symlocation = -16; + if (!iso9660->opt.rr && iso9660->opt.joliet) { + joliet = 1; + np = iso9660->joliet.rootent; + } else { + joliet = 0; + np = iso9660->primary.rootent; + } + do { + _isoent_file_location(iso9660, np, &symlocation); + + if (np->subdirs.first != NULL && + (joliet || + ((iso9660->opt.rr == OPT_RR_DISABLED && + depth + 2 < iso9660->primary.max_depth) || + (iso9660->opt.rr && + depth + 1 < iso9660->primary.max_depth)))) { + /* Enter to sub directories. */ + np = np->subdirs.first; + depth++; + continue; + } + while (np != np->parent) { + if (np->drnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + depth--; + } else { + np = np->drnext; + break; + } + } + } while (np != np->parent); + + total_block = 0; + for (file = iso9660->data_file_list.first; + file != NULL; file = file->datanext) { + + if (!file->write_content) + continue; + + file->cur_content = &(file->content); + do { + file->cur_content->location = location; + location += file->cur_content->blocks; + total_block += file->cur_content->blocks; + /* Next fragument */ + file->cur_content = file->cur_content->next; + } while (file->cur_content != NULL); + } + iso9660->total_file_block += total_block; +} + +static int +get_path_component(char *name, int n, const char *fn) +{ + char *p; + int l; + + p = strchr(fn, '/'); + if (p == NULL) { + if ((l = strlen(fn)) == 0) + return (0); + } else + l = p - fn; + if (l > n -1) + return (-1); + memcpy(name, fn, l); + name[l] = '\0'; + + return (l); +} + +/* + * Add a new entry into the tree. + */ +static int +isoent_tree(struct archive_write *a, struct isoent **isoentpp) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + char name[_MAX_FNAME];/* Included null terminator size. */ +#elif defined(NAME_MAX) && NAME_MAX >= 255 + char name[NAME_MAX+1]; +#else + char name[256]; +#endif + struct iso9660 *iso9660 = a->format_data; + struct isoent *dent, *isoent, *np; + struct isofile *f1, *f2; + const char *fn, *p; + int l; + + isoent = *isoentpp; + dent = iso9660->primary.rootent; + if (isoent->file->parentdir.length > 0) + fn = p = isoent->file->parentdir.s; + else + fn = p = ""; + + /* + * If the path of the parent directory of `isoent' entry is + * the same as the path of `cur_dirent', add isoent to + * `cur_dirent'. + */ + if (archive_strlen(&(iso9660->cur_dirstr)) + == archive_strlen(&(isoent->file->parentdir)) && + strcmp(iso9660->cur_dirstr.s, fn) == 0) { + if (!isoent_add_child_tail(iso9660->cur_dirent, isoent)) { + np = (struct isoent *)__archive_rb_tree_find_node( + &(iso9660->cur_dirent->rbtree), + isoent->file->basename.s); + goto same_entry; + } + return (ARCHIVE_OK); + } + + for (;;) { + l = get_path_component(name, sizeof(name), fn); + if (l == 0) { + np = NULL; + break; + } + if (l < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "A name buffer is too small"); + _isoent_free(isoent); + return (ARCHIVE_FATAL); + } + + np = isoent_find_child(dent, name); + if (np == NULL || fn[0] == '\0') + break; + + /* Find next subdirectory. */ + if (!np->dir) { + /* NOT Directory! */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "`%s' is not directory, we cannot insert `%s' ", + archive_entry_pathname(np->file->entry), + archive_entry_pathname(isoent->file->entry)); + _isoent_free(isoent); + *isoentpp = NULL; + return (ARCHIVE_FAILED); + } + fn += l; + if (fn[0] == '/') + fn++; + dent = np; + } + if (np == NULL) { + /* + * Create virtual parent directories. + */ + while (fn[0] != '\0') { + struct isoent *vp; + struct archive_string as; + + archive_string_init(&as); + archive_strncat(&as, p, fn - p + l); + if (as.s[as.length-1] == '/') { + as.s[as.length-1] = '\0'; + as.length--; + } + vp = isoent_create_virtual_dir(a, iso9660, as.s); + if (vp == NULL) { + archive_string_free(&as); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + _isoent_free(isoent); + *isoentpp = NULL; + return (ARCHIVE_FATAL); + } + archive_string_free(&as); + + if (vp->file->dircnt > iso9660->dircnt_max) + iso9660->dircnt_max = vp->file->dircnt; + isoent_add_child_tail(dent, vp); + np = vp; + + fn += l; + if (fn[0] == '/') + fn++; + l = get_path_component(name, sizeof(name), fn); + if (l < 0) { + archive_string_free(&as); + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "A name buffer is too small"); + _isoent_free(isoent); + *isoentpp = NULL; + return (ARCHIVE_FATAL); + } + dent = np; + } + + /* Found out the parent directory where isoent can be + * inserted. */ + iso9660->cur_dirent = dent; + archive_string_empty(&(iso9660->cur_dirstr)); + archive_string_ensure(&(iso9660->cur_dirstr), + archive_strlen(&(dent->file->parentdir)) + + archive_strlen(&(dent->file->basename)) + 2); + if (archive_strlen(&(dent->file->parentdir)) + + archive_strlen(&(dent->file->basename)) == 0) + iso9660->cur_dirstr.s[0] = 0; + else { + if (archive_strlen(&(dent->file->parentdir)) > 0) { + archive_string_copy(&(iso9660->cur_dirstr), + &(dent->file->parentdir)); + archive_strappend_char(&(iso9660->cur_dirstr), '/'); + } + archive_string_concat(&(iso9660->cur_dirstr), + &(dent->file->basename)); + } + + if (!isoent_add_child_tail(dent, isoent)) { + np = (struct isoent *)__archive_rb_tree_find_node( + &(dent->rbtree), isoent->file->basename.s); + goto same_entry; + } + return (ARCHIVE_OK); + } + +same_entry: + /* + * We have already has the entry the filename of which is + * the same. + */ + f1 = np->file; + f2 = isoent->file; + + /* If the file type of entries is different, + * we cannot handle it. */ + if (archive_entry_filetype(f1->entry) != + archive_entry_filetype(f2->entry)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Found duplicate entries `%s' and its file type is " + "different", + archive_entry_pathname(f1->entry)); + _isoent_free(isoent); + *isoentpp = NULL; + return (ARCHIVE_FAILED); + } + + /* Swap file entries. */ + np->file = f2; + isoent->file = f1; + np->virtual = 0; + + _isoent_free(isoent); + *isoentpp = np; + return (ARCHIVE_OK); +} + +/* + * Find a entry from `isoent' + */ +static struct isoent * +isoent_find_child(struct isoent *isoent, const char *child_name) +{ + struct isoent *np; + + np = (struct isoent *)__archive_rb_tree_find_node( + &(isoent->rbtree), child_name); + return (np); +} + +/* + * Find a entry full-path of which is specified by `fn' parameter, + * in the tree. + */ +static struct isoent * +isoent_find_entry(struct isoent *rootent, const char *fn) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + char name[_MAX_FNAME];/* Included null terminator size. */ +#elif defined(NAME_MAX) && NAME_MAX >= 255 + char name[NAME_MAX+1]; +#else + char name[256]; +#endif + struct isoent *isoent, *np; + int l; + + isoent = rootent; + np = NULL; + for (;;) { + l = get_path_component(name, sizeof(name), fn); + if (l == 0) + break; + fn += l; + if (fn[0] == '/') + fn++; + + np = isoent_find_child(isoent, name); + if (np == NULL) + break; + if (fn[0] == '\0') + break;/* We found out the entry */ + + /* Try sub directory. */ + isoent = np; + np = NULL; + if (!isoent->dir) + break;/* Not directory */ + } + + return (np); +} + +/* + * Following idr_* functions are used for resolving duplicated filenames + * and unreceivable filenames to generate ISO9660/Joliet Identifiers. + */ + +static void +idr_relaxed_filenames(char *map) +{ + int i; + + for (i = 0x21; i <= 0x2F; i++) + map[i] = 1; + for (i = 0x3A; i <= 0x41; i++) + map[i] = 1; + for (i = 0x5B; i <= 0x5E; i++) + map[i] = 1; + map[0x60] = 1; + for (i = 0x7B; i <= 0x7E; i++) + map[i] = 1; +} + +static void +idr_init(struct iso9660 *iso9660, struct vdd *vdd, struct idr *idr) +{ + + idr->idrent_pool = NULL; + idr->pool_size = 0; + if (vdd->vdd_type != VDD_JOLIET) { + if (iso9660->opt.iso_level <= 3) { + memcpy(idr->char_map, d_characters_map, + sizeof(idr->char_map)); + } else { + memcpy(idr->char_map, d1_characters_map, + sizeof(idr->char_map)); + idr_relaxed_filenames(idr->char_map); + } + } +} + +static void +idr_cleanup(struct idr *idr) +{ + free(idr->idrent_pool); +} + +static int +idr_ensure_poolsize(struct archive_write *a, struct idr *idr, + int cnt) +{ + + if (idr->pool_size < cnt) { + const int bk = (1 << 7) - 1; + int psize; + + psize = (cnt + bk) & ~bk; + idr->idrent_pool = realloc(idr->idrent_pool, + sizeof(struct idrent) * psize); + if (idr->idrent_pool == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + idr->pool_size = psize; + } + return (ARCHIVE_OK); +} + +static int +idr_start(struct archive_write *a, struct idr *idr, int cnt, int ffmax, + int num_size, int null_size, const struct archive_rb_tree_ops *rbt_ops) +{ + int r; + + (void)ffmax; /* UNUSED */ + + r = idr_ensure_poolsize(a, idr, cnt); + if (r != ARCHIVE_OK) + return (r); + __archive_rb_tree_init(&(idr->rbtree), rbt_ops); + idr->wait_list.first = NULL; + idr->wait_list.last = &(idr->wait_list.first); + idr->pool_idx = 0; + idr->num_size = num_size; + idr->null_size = null_size; + return (ARCHIVE_OK); +} + +static void +idr_register(struct idr *idr, struct isoent *isoent, int weight, int noff) +{ + struct idrent *idrent, *n; + + idrent = &(idr->idrent_pool[idr->pool_idx++]); + idrent->wnext = idrent->avail = NULL; + idrent->isoent = isoent; + idrent->weight = weight; + idrent->noff = noff; + idrent->rename_num = 0; + + if (!__archive_rb_tree_insert_node(&(idr->rbtree), &(idrent->rbnode))) { + n = (struct idrent *)__archive_rb_tree_find_node( + &(idr->rbtree), idrent->isoent); + if (n != NULL) { + /* this `idrent' needs to rename. */ + idrent->avail = n; + *idr->wait_list.last = idrent; + idr->wait_list.last = &(idrent->wnext); + } + } +} + +static void +idr_extend_identifier(struct idrent *wnp, int numsize, int nullsize) +{ + unsigned char *p; + int wnp_ext_off; + + wnp_ext_off = wnp->isoent->ext_off; + if (wnp->noff + numsize != wnp_ext_off) { + p = (unsigned char *)wnp->isoent->identifier; + /* Extend the filename; foo.c --> foo___.c */ + memmove(p + wnp->noff + numsize, p + wnp_ext_off, + wnp->isoent->ext_len + nullsize); + wnp->isoent->ext_off = wnp_ext_off = wnp->noff + numsize; + wnp->isoent->id_len = wnp_ext_off + wnp->isoent->ext_len; + } +} + +static void +idr_resolve(struct idr *idr, void (*fsetnum)(unsigned char *p, int num)) +{ + struct idrent *n; + unsigned char *p; + + for (n = idr->wait_list.first; n != NULL; n = n->wnext) { + idr_extend_identifier(n, idr->num_size, idr->null_size); + p = (unsigned char *)n->isoent->identifier + n->noff; + do { + fsetnum(p, n->avail->rename_num++); + } while (!__archive_rb_tree_insert_node( + &(idr->rbtree), &(n->rbnode))); + } +} + +static void +idr_set_num(unsigned char *p, int num) +{ + static const char xdig[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + + num %= sizeof(xdig) * sizeof(xdig) * sizeof(xdig); + p[0] = xdig[(num / (sizeof(xdig) * sizeof(xdig)))]; + num %= sizeof(xdig) * sizeof(xdig); + p[1] = xdig[ (num / sizeof(xdig))]; + num %= sizeof(xdig); + p[2] = xdig[num]; +} + +static void +idr_set_num_beutf16(unsigned char *p, int num) +{ + static const uint16_t xdig[] = { + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, + 0x0036, 0x0037, 0x0038, 0x0039, + 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, + 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, + 0x004D, 0x004E, 0x004F, 0x0050, 0x0051, 0x0052, + 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, + 0x0059, 0x005A + }; +#define XDIG_CNT (sizeof(xdig)/sizeof(xdig[0])) + + num %= XDIG_CNT * XDIG_CNT * XDIG_CNT; + archive_be16enc(p, xdig[(num / (XDIG_CNT * XDIG_CNT))]); + num %= XDIG_CNT * XDIG_CNT; + archive_be16enc(p+2, xdig[ (num / XDIG_CNT)]); + num %= XDIG_CNT; + archive_be16enc(p+4, xdig[num]); +} + +/* + * Generate ISO9660 Identifier. + */ +static int +isoent_gen_iso9660_identifier(struct archive_write *a, struct isoent *isoent, + struct idr *idr) +{ + struct iso9660 *iso9660; + struct isoent *np; + char *p; + int l, r; + const char *char_map; + char allow_ldots, allow_multidot, allow_period, allow_vernum; + int fnmax, ffmax, dnmax; + static const struct archive_rb_tree_ops rb_ops = { + isoent_cmp_node_iso9660, isoent_cmp_key_iso9660 + }; + + if (isoent->children.cnt == 0) + return (0); + + iso9660 = a->format_data; + char_map = idr->char_map; + if (iso9660->opt.iso_level <= 3) { + allow_ldots = 0; + allow_multidot = 0; + allow_period = 1; + allow_vernum = iso9660->opt.allow_vernum; + if (iso9660->opt.iso_level == 1) { + fnmax = 8; + ffmax = 12;/* fnmax + '.' + 3 */ + dnmax = 8; + } else { + fnmax = 30; + ffmax = 31; + dnmax = 31; + } + } else { + allow_ldots = allow_multidot = 1; + allow_period = allow_vernum = 0; + if (iso9660->opt.rr) + /* + * MDR : The maximum size of Directory Record(254). + * DRL : A Directory Record Length(33). + * CE : A size of SUSP CE System Use Entry(28). + * MDR - DRL - CE = 254 - 33 - 28 = 193. + */ + fnmax = ffmax = dnmax = 193; + else + /* + * XA : CD-ROM XA System Use Extension + * Information(14). + * MDR - DRL - XA = 254 - 33 -14 = 207. + */ + fnmax = ffmax = dnmax = 207; + } + + r = idr_start(a, idr, isoent->children.cnt, ffmax, 3, 1, &rb_ops); + if (r < 0) + return (r); + + for (np = isoent->children.first; np != NULL; np = np->chnext) { + char *dot, *xdot; + int ext_off, noff, weight; + + l = np->file->basename.length; + p = malloc(l+31+2+1); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + memcpy(p, np->file->basename.s, l); + p[l] = '\0'; + np->identifier = p; + + dot = xdot = NULL; + if (!allow_ldots) { + /* + * If there is a '.' character at the first byte, + * it has to be replaced by '_' character. + */ + if (*p == '.') + *p++ = '_'; + } + for (;*p; p++) { + if (*p & 0x80) { + *p = '_'; + continue; + } + if (char_map[(unsigned char)*p]) { + /* if iso-level is '4', a character '.' is + * allowed by char_map. */ + if (*p == '.') { + xdot = dot; + dot = p; + } + continue; + } + if (*p >= 'a' && *p <= 'z') { + *p -= 'a' - 'A'; + continue; + } + if (*p == '.') { + xdot = dot; + dot = p; + if (allow_multidot) + continue; + } + *p = '_'; + } + p = np->identifier; + weight = -1; + if (dot == NULL) { + int nammax; + + if (np->dir) + nammax = dnmax; + else + nammax = fnmax; + + if (l > nammax) { + p[nammax] = '\0'; + weight = nammax; + ext_off = nammax; + } else + ext_off = l; + } else { + *dot = '.'; + ext_off = dot - p; + + if (iso9660->opt.iso_level == 1) { + if (dot - p <= 8) { + if (strlen(dot) > 4) { + /* A length of a file extension + * must be less than 4 */ + dot[4] = '\0'; + weight = 0; + } + } else { + p[8] = dot[0]; + p[9] = dot[1]; + p[10] = dot[2]; + p[11] = dot[3]; + p[12] = '\0'; + weight = 8; + ext_off = 8; + } + } else if (np->dir) { + if (l > dnmax) { + p[dnmax] = '\0'; + weight = dnmax; + if (ext_off > dnmax) + ext_off = dnmax; + } + } else if (l > ffmax) { + int extlen = strlen(dot); + int xdoff; + + if (xdot != NULL) + xdoff = xdot - p; + else + xdoff = 0; + + if (extlen > 1 && xdoff < fnmax-1) { + int off; + + if (extlen > ffmax) + extlen = ffmax; + off = ffmax - extlen; + if (off == 0) { + /* A dot('.') character + * does't place to the first + * byte of identifier. */ + off ++; + extlen --; + } + memmove(p+off, dot, extlen); + p[ffmax] = '\0'; + ext_off = off; + weight = off; +#ifdef COMPAT_MKISOFS + } else if (xdoff >= fnmax-1) { + /* Simulate a bug(?) of mkisofs. */ + p[fnmax-1] = '\0'; + ext_off = fnmax-1; + weight = fnmax-1; +#endif + } else { + p[fnmax] = '\0'; + ext_off = fnmax; + weight = fnmax; + } + } + } + /* Save an offset of a file name extension to sort files. */ + np->ext_off = ext_off; + np->ext_len = strlen(&p[ext_off]); + np->id_len = l = ext_off + np->ext_len; + + /* Make an offset of the number which is used to be set + * hexadecimal number to avoid duplicate identififier. */ + if (iso9660->opt.iso_level == 1) { + if (ext_off >= 5) + noff = 5; + else + noff = ext_off; + } else { + if (l == ffmax) + noff = ext_off - 3; + else if (l == ffmax-1) + noff = ext_off - 2; + else if (l == ffmax-2) + noff = ext_off - 1; + else + noff = ext_off; + } + /* Register entry to the identifier resolver. */ + idr_register(idr, np, weight, noff); + } + + /* Resolve duplicate identifier. */ + idr_resolve(idr, idr_set_num); + + /* Add a period and a version number to identifiers. */ + for (np = isoent->children.first; np != NULL; np = np->chnext) { + if (!np->dir && np->rr_child == NULL) { + p = np->identifier + np->ext_off + np->ext_len; + if (np->ext_len == 0 && allow_period) { + *p++ = '.'; + np->ext_len = 1; + } + if (np->ext_len == 1 && !allow_period) { + *--p = '\0'; + np->ext_len = 0; + } + np->id_len = np->ext_off + np->ext_len; + if (allow_vernum) { + *p++ = ';'; + *p++ = '1'; + np->id_len += 2; + } + *p = '\0'; + } else + np->id_len = np->ext_off + np->ext_len; + np->mb_len = np->id_len; + } + return (ARCHIVE_OK); +} + +/* + * Generate Joliet Identifier. + */ +static int +isoent_gen_joliet_identifier(struct archive_write *a, struct isoent *isoent, + struct idr *idr) +{ + struct iso9660 *iso9660; + struct isoent *np; + unsigned char *p; + size_t l; + int r; + int ffmax, parent_len; + static const struct archive_rb_tree_ops rb_ops = { + isoent_cmp_node_joliet, isoent_cmp_key_joliet + }; + + if (isoent->children.cnt == 0) + return (0); + + iso9660 = a->format_data; + if (iso9660->opt.joliet == OPT_JOLIET_LONGNAME) + ffmax = 206; + else + ffmax = 128; + + r = idr_start(a, idr, isoent->children.cnt, ffmax, 6, 2, &rb_ops); + if (r < 0) + return (r); + + parent_len = 1; + for (np = isoent; np->parent != np; np = np->parent) + parent_len += np->mb_len + 1; + + for (np = isoent->children.first; np != NULL; np = np->chnext) { + unsigned char *dot; + int ext_off, noff, weight; + size_t lt; + + if ((int)(l = np->file->basename_utf16.length) > ffmax) + l = ffmax; + + p = malloc((l+1)*2); + if (p == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + memcpy(p, np->file->basename_utf16.s, l); + p[l] = 0; + p[l+1] = 0; + + np->identifier = (char *)p; + lt = l; + dot = p + l; + weight = 0; + while (lt > 0) { + if (!joliet_allowed_char(p[0], p[1])) + archive_be16enc(p, 0x005F); /* '_' */ + else if (p[0] == 0 && p[1] == 0x2E) /* '.' */ + dot = p; + p += 2; + lt -= 2; + } + ext_off = dot - (unsigned char *)np->identifier; + np->ext_off = ext_off; + np->ext_len = l - ext_off; + np->id_len = l; + + /* + * Get a length of MBS of a full-pathname. + */ + if ((int)np->file->basename_utf16.length > ffmax) { + archive_strncpy_in_locale(&iso9660->mbs, + (const char *)np->identifier, l, + iso9660->sconv_from_utf16be); + np->mb_len = iso9660->mbs.length; + if (np->mb_len != (int)np->file->basename.length) + weight = np->mb_len; + } else + np->mb_len = np->file->basename.length; + + /* If a length of full-pathname is longer than 240 bytes, + * it violates Joliet extensions regulation. */ + if (parent_len + np->mb_len > 240) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "The regulation of Joliet extensions;" + " A lenght of a full-pathname of `%s' is " + "longer than 240 bytes, (p=%d, b=%d)", + archive_entry_pathname(np->file->entry), + (int)parent_len, (int)np->mb_len); + return (ARCHIVE_FATAL); + } + + /* Make an offset of the number which is used to be set + * hexadecimal number to avoid duplicate identififier. */ + if ((int)l == ffmax) + noff = ext_off - 6; + else if ((int)l == ffmax-2) + noff = ext_off - 4; + else if ((int)l == ffmax-4) + noff = ext_off - 2; + else + noff = ext_off; + /* Register entry to the identifier resolver. */ + idr_register(idr, np, weight, noff); + } + + /* Resolve duplicate identifier with Joliet Volume. */ + idr_resolve(idr, idr_set_num_beutf16); + + return (ARCHIVE_OK); +} + +/* + * This comparing rule is acording to ISO9660 Standard 9.3 + */ +static int +isoent_cmp_iso9660_identifier(const struct isoent *p1, const struct isoent *p2) +{ + const char *s1, *s2; + int cmp; + int l; + + s1 = p1->identifier; + s2 = p2->identifier; + + /* Compare File Name */ + l = p1->ext_off; + if (l > p2->ext_off) + l = p2->ext_off; + cmp = memcmp(s1, s2, l); + if (cmp != 0) + return (cmp); + if (p1->ext_off < p2->ext_off) { + s2 += l; + l = p2->ext_off - p1->ext_off; + while (l--) + if (0x20 != *s2++) + return (0x20 + - *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_off > p2->ext_off) { + s1 += l; + l = p1->ext_off - p2->ext_off; + while (l--) + if (0x20 != *s1++) + return (*(const unsigned char *)(s1 - 1) + - 0x20); + } + /* Compare File Name Extension */ + if (p1->ext_len == 0 && p2->ext_len == 0) + return (0); + if (p1->ext_len == 1 && p2->ext_len == 1) + return (0); + if (p1->ext_len <= 1) + return (-1); + if (p2->ext_len <= 1) + return (1); + l = p1->ext_len; + if (l > p2->ext_len) + l = p2->ext_len; + s1 = p1->identifier + p1->ext_off; + s2 = p2->identifier + p2->ext_off; + if (l > 1) { + cmp = memcmp(s1, s2, l); + if (cmp != 0) + return (cmp); + } + if (p1->ext_len < p2->ext_len) { + s2 += l; + l = p2->ext_len - p1->ext_len; + while (l--) + if (0x20 != *s2++) + return (0x20 + - *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_len < p2->ext_len) { + s1 += l; + l = p1->ext_len - p2->ext_len; + while (l--) + if (0x20 != *s1++) + return (*(const unsigned char *)(s1 - 1) + - 0x20); + } + /* Compare File Version Number */ + /* No operation. The File Version Number is always one. */ + + return (cmp); +} + +static int +isoent_cmp_node_iso9660(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + const struct idrent *e1 = (const struct idrent *)n1; + const struct idrent *e2 = (const struct idrent *)n2; + + return (isoent_cmp_iso9660_identifier(e2->isoent, e1->isoent)); +} + +static int +isoent_cmp_key_iso9660(const struct archive_rb_node *node, const void *key) +{ + const struct isoent *isoent = (const struct isoent *)key; + const struct idrent *idrent = (const struct idrent *)node; + + return (isoent_cmp_iso9660_identifier(isoent, idrent->isoent)); +} + +static int +isoent_cmp_joliet_identifier(const struct isoent *p1, const struct isoent *p2) +{ + const unsigned char *s1, *s2; + int cmp; + int l; + + s1 = (const unsigned char *)p1->identifier; + s2 = (const unsigned char *)p2->identifier; + + /* Compare File Name */ + l = p1->ext_off; + if (l > p2->ext_off) + l = p2->ext_off; + cmp = memcmp(s1, s2, l); + if (cmp != 0) + return (cmp); + if (p1->ext_off < p2->ext_off) { + s2 += l; + l = p2->ext_off - p1->ext_off; + while (l--) + if (0 != *s2++) + return (- *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_off > p2->ext_off) { + s1 += l; + l = p1->ext_off - p2->ext_off; + while (l--) + if (0 != *s1++) + return (*(const unsigned char *)(s1 - 1)); + } + /* Compare File Name Extension */ + if (p1->ext_len == 0 && p2->ext_len == 0) + return (0); + if (p1->ext_len == 2 && p2->ext_len == 2) + return (0); + if (p1->ext_len <= 2) + return (-1); + if (p2->ext_len <= 2) + return (1); + l = p1->ext_len; + if (l > p2->ext_len) + l = p2->ext_len; + s1 = (unsigned char *)(p1->identifier + p1->ext_off); + s2 = (unsigned char *)(p2->identifier + p2->ext_off); + if (l > 1) { + cmp = memcmp(s1, s2, l); + if (cmp != 0) + return (cmp); + } + if (p1->ext_len < p2->ext_len) { + s2 += l; + l = p2->ext_len - p1->ext_len; + while (l--) + if (0 != *s2++) + return (- *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_len < p2->ext_len) { + s1 += l; + l = p1->ext_len - p2->ext_len; + while (l--) + if (0 != *s1++) + return (*(const unsigned char *)(s1 - 1)); + } + /* Compare File Version Number */ + /* No operation. The File Version Number is always one. */ + + return (cmp); +} + +static int +isoent_cmp_node_joliet(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + const struct idrent *e1 = (const struct idrent *)n1; + const struct idrent *e2 = (const struct idrent *)n2; + + return (isoent_cmp_joliet_identifier(e2->isoent, e1->isoent)); +} + +static int +isoent_cmp_key_joliet(const struct archive_rb_node *node, const void *key) +{ + const struct isoent *isoent = (const struct isoent *)key; + const struct idrent *idrent = (const struct idrent *)node; + + return (isoent_cmp_joliet_identifier(isoent, idrent->isoent)); +} + +static int +isoent_make_sorted_files(struct archive_write *a, struct isoent *isoent, + struct idr *idr) +{ + struct archive_rb_node *rn; + struct isoent **children; + + children = malloc(isoent->children.cnt * sizeof(struct isoent *)); + if (children == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + isoent->children_sorted = children; + + ARCHIVE_RB_TREE_FOREACH(rn, &(idr->rbtree)) { + struct idrent *idrent = (struct idrent *)rn; + *children ++ = idrent->isoent; + } + return (ARCHIVE_OK); +} + +/* + * - Generate ISO9660 and Joliet identifiers from basenames. + * - Sort files by each directory. + */ +static int +isoent_traverse_tree(struct archive_write *a, struct vdd* vdd) +{ + struct iso9660 *iso9660 = a->format_data; + struct isoent *np; + struct idr idr; + int depth; + int r; + int (*genid)(struct archive_write *a, struct isoent *isoent, + struct idr *idr); + + idr_init(iso9660, vdd, &idr); + np = vdd->rootent; + depth = 0; + if (vdd->vdd_type == VDD_JOLIET) + genid = isoent_gen_joliet_identifier; + else + genid = isoent_gen_iso9660_identifier; + do { + if (np->virtual && + !archive_entry_mtime_is_set(np->file->entry)) { + /* Set properly times to virtual directory */ + archive_entry_set_mtime(np->file->entry, + iso9660->birth_time, 0); + archive_entry_set_atime(np->file->entry, + iso9660->birth_time, 0); + archive_entry_set_ctime(np->file->entry, + iso9660->birth_time, 0); + } + if (np->children.first != NULL) { + if (vdd->vdd_type != VDD_JOLIET && + !iso9660->opt.rr && depth + 1 >= vdd->max_depth) { + if (np->children.cnt > 0) + iso9660->directories_too_deep = np; + } else { + /* Generate Identifier */ + r = genid(a, np, &idr); + if (r < 0) + goto exit_traverse_tree; + r = isoent_make_sorted_files(a, np, &idr); + if (r < 0) + goto exit_traverse_tree; + + if (np->subdirs.first != NULL && + depth + 1 < vdd->max_depth) { + /* Enter to sub directories. */ + np = np->subdirs.first; + depth++; + continue; + } + } + } + while (np != np->parent) { + if (np->drnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + depth--; + } else { + np = np->drnext; + break; + } + } + } while (np != np->parent); + + r = ARCHIVE_OK; +exit_traverse_tree: + idr_cleanup(&idr); + + return (r); +} + +/* + * Collect directory entries into path_table by a directory depth. + */ +static int +isoent_collect_dirs(struct vdd *vdd, struct isoent *rootent, int depth) +{ + struct isoent *np; + + if (rootent == NULL) + rootent = vdd->rootent; + np = rootent; + do { + /* Register current directory to pathtable. */ + path_table_add_entry(&(vdd->pathtbl[depth]), np); + + if (np->subdirs.first != NULL && depth + 1 < vdd->max_depth) { + /* Enter to sub directories. */ + np = np->subdirs.first; + depth++; + continue; + } + while (np != rootent) { + if (np->drnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + depth--; + } else { + np = np->drnext; + break; + } + } + } while (np != rootent); + + return (ARCHIVE_OK); +} + +/* + * The entry whose number of levels in a directory hierarchy is + * large than eight relocate to rr_move directory. + */ +static int +isoent_rr_move_dir(struct archive_write *a, struct isoent **rr_moved, + struct isoent *isoent, struct isoent **newent) +{ + struct iso9660 *iso9660 = a->format_data; + struct isoent *rrmoved, *mvent, *np; + + if ((rrmoved = *rr_moved) == NULL) { + struct isoent *rootent = iso9660->primary.rootent; + /* There isn't rr_move entry. + * Create rr_move entry and insert it into the root entry. + */ + rrmoved = isoent_create_virtual_dir(a, iso9660, "rr_moved"); + if (rrmoved == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + /* Add "rr_moved" entry to the root entry. */ + isoent_add_child_head(rootent, rrmoved); + archive_entry_set_nlink(rootent->file->entry, + archive_entry_nlink(rootent->file->entry) + 1); + /* Register "rr_moved" entry to second level pathtable. */ + path_table_add_entry(&(iso9660->primary.pathtbl[1]), rrmoved); + /* Save rr_moved. */ + *rr_moved = rrmoved; + } + /* + * Make a clone of isoent which is going to be relocated + * to rr_moved. + */ + mvent = isoent_clone(isoent); + if (mvent == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + /* linking.. and use for creating "CL", "PL" and "RE" */ + mvent->rr_parent = isoent->parent; + isoent->rr_child = mvent; + /* + * Move subdirectories from the isoent to mvent + */ + if (isoent->children.first != NULL) { + *mvent->children.last = isoent->children.first; + mvent->children.last = isoent->children.last; + } + for (np = mvent->children.first; np != NULL; np = np->chnext) + np->parent = mvent; + mvent->children.cnt = isoent->children.cnt; + isoent->children.cnt = 0; + isoent->children.first = NULL; + isoent->children.last = &isoent->children.first; + + if (isoent->subdirs.first != NULL) { + *mvent->subdirs.last = isoent->subdirs.first; + mvent->subdirs.last = isoent->subdirs.last; + } + mvent->subdirs.cnt = isoent->subdirs.cnt; + isoent->subdirs.cnt = 0; + isoent->subdirs.first = NULL; + isoent->subdirs.last = &isoent->subdirs.first; + + /* + * The mvent becomes a child of the rr_moved entry. + */ + isoent_add_child_tail(rrmoved, mvent); + archive_entry_set_nlink(rrmoved->file->entry, + archive_entry_nlink(rrmoved->file->entry) + 1); + /* + * This entry which relocated to the rr_moved directory + * has to set the flag as a file. + * See also RRIP 4.1.5.1 Description of the "CL" System Use Entry. + */ + isoent->dir = 0; + + *newent = mvent; + + return (ARCHIVE_OK); +} + +static int +isoent_rr_move(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + struct path_table *pt; + struct isoent *rootent, *rr_moved; + struct isoent *np, *last; + int r; + + pt = &(iso9660->primary.pathtbl[MAX_DEPTH-1]); + /* Theare aren't level 8 directories reaching a deepr level. */ + if (pt->cnt == 0) + return (ARCHIVE_OK); + + rootent = iso9660->primary.rootent; + /* If "rr_moved" directory is already existing, + * we have to use it. */ + rr_moved = isoent_find_child(rootent, "rr_moved"); + if (rr_moved != NULL && + rr_moved != rootent->children.first) { + /* + * It's necessary that rr_move is the first entry + * of the root. + */ + /* Remove "rr_moved" entry from children chain. */ + isoent_remove_child(rootent, rr_moved); + + /* Add "rr_moved" entry into the head of children chain. */ + isoent_add_child_head(rootent, rr_moved); + } + + /* + * Check level 8 path_table. + * If find out sub directory entries, that entries move to rr_move. + */ + np = pt->first; + while (np != NULL) { + last = path_table_last_entry(pt); + for (; np != NULL; np = np->ptnext) { + struct isoent *mvent; + struct isoent *newent; + + if (!np->dir) + continue; + for (mvent = np->subdirs.first; + mvent != NULL; mvent = mvent->drnext) { + r = isoent_rr_move_dir(a, &rr_moved, + mvent, &newent); + if (r < 0) + return (r); + isoent_collect_dirs(&(iso9660->primary), + newent, 2); + } + } + /* If new entries are added to level 8 path_talbe, + * its sub directory entries move to rr_move too. + */ + np = last->ptnext; + } + + return (ARCHIVE_OK); +} + +/* + * This comparing rule is according to ISO9660 Standard 6.9.1 + */ +static int +_compare_path_table(const void *v1, const void *v2) +{ + const struct isoent *p1, *p2; + const char *s1, *s2; + int cmp, l; + + p1 = *((const struct isoent **)(uintptr_t)v1); + p2 = *((const struct isoent **)(uintptr_t)v2); + + /* Compare parent directory number */ + cmp = p1->parent->dir_number - p2->parent->dir_number; + if (cmp != 0) + return (cmp); + + /* Compare indetifier */ + s1 = p1->identifier; + s2 = p2->identifier; + l = p1->ext_off; + if (l > p2->ext_off) + l = p2->ext_off; + cmp = strncmp(s1, s2, l); + if (cmp != 0) + return (cmp); + if (p1->ext_off < p2->ext_off) { + s2 += l; + l = p2->ext_off - p1->ext_off; + while (l--) + if (0x20 != *s2++) + return (0x20 + - *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_off > p2->ext_off) { + s1 += l; + l = p1->ext_off - p2->ext_off; + while (l--) + if (0x20 != *s1++) + return (*(const unsigned char *)(s1 - 1) + - 0x20); + } + return (0); +} + +static int +_compare_path_table_joliet(const void *v1, const void *v2) +{ + const struct isoent *p1, *p2; + const unsigned char *s1, *s2; + int cmp, l; + + p1 = *((const struct isoent **)(uintptr_t)v1); + p2 = *((const struct isoent **)(uintptr_t)v2); + + /* Compare parent directory number */ + cmp = p1->parent->dir_number - p2->parent->dir_number; + if (cmp != 0) + return (cmp); + + /* Compare indetifier */ + s1 = (const unsigned char *)p1->identifier; + s2 = (const unsigned char *)p2->identifier; + l = p1->ext_off; + if (l > p2->ext_off) + l = p2->ext_off; + cmp = memcmp(s1, s2, l); + if (cmp != 0) + return (cmp); + if (p1->ext_off < p2->ext_off) { + s2 += l; + l = p2->ext_off - p1->ext_off; + while (l--) + if (0 != *s2++) + return (- *(const unsigned char *)(s2 - 1)); + } else if (p1->ext_off > p2->ext_off) { + s1 += l; + l = p1->ext_off - p2->ext_off; + while (l--) + if (0 != *s1++) + return (*(const unsigned char *)(s1 - 1)); + } + return (0); +} + +static inline void +path_table_add_entry(struct path_table *pathtbl, struct isoent *ent) +{ + ent->ptnext = NULL; + *pathtbl->last = ent; + pathtbl->last = &(ent->ptnext); + pathtbl->cnt ++; +} + +static inline struct isoent * +path_table_last_entry(struct path_table *pathtbl) +{ + if (pathtbl->first == NULL) + return (NULL); + return (((struct isoent *)(void *) + ((char *)(pathtbl->last) - offsetof(struct isoent, ptnext)))); +} + +/* + * Sort directory entries in path_table + * and assign directory number to each entries. + */ +static int +isoent_make_path_table_2(struct archive_write *a, struct vdd *vdd, + int depth, int *dir_number) +{ + struct isoent *np; + struct isoent **enttbl; + struct path_table *pt; + int i; + + pt = &vdd->pathtbl[depth]; + if (pt->cnt == 0) { + pt->sorted = NULL; + return (ARCHIVE_OK); + } + enttbl = malloc(pt->cnt * sizeof(struct isoent *)); + if (enttbl == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + pt->sorted = enttbl; + for (np = pt->first; np != NULL; np = np->ptnext) + *enttbl ++ = np; + enttbl = pt->sorted; + + switch (vdd->vdd_type) { + case VDD_PRIMARY: + case VDD_ENHANCED: + qsort(enttbl, pt->cnt, sizeof(struct isoent *), + _compare_path_table); + break; + case VDD_JOLIET: + qsort(enttbl, pt->cnt, sizeof(struct isoent *), + _compare_path_table_joliet); + break; + } + for (i = 0; i < pt->cnt; i++) + enttbl[i]->dir_number = (*dir_number)++; + + return (ARCHIVE_OK); +} + +static int +isoent_alloc_path_table(struct archive_write *a, struct vdd *vdd, + int max_depth) +{ + int i; + + vdd->max_depth = max_depth; + vdd->pathtbl = malloc(sizeof(*vdd->pathtbl) * vdd->max_depth); + if (vdd->pathtbl == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + for (i = 0; i < vdd->max_depth; i++) { + vdd->pathtbl[i].first = NULL; + vdd->pathtbl[i].last = &(vdd->pathtbl[i].first); + vdd->pathtbl[i].sorted = NULL; + vdd->pathtbl[i].cnt = 0; + } + return (ARCHIVE_OK); +} + +/* + * Make Path Tables + */ +static int +isoent_make_path_table(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + int depth, r; + int dir_number; + + /* + * Init Path Table. + */ + if (iso9660->dircnt_max >= MAX_DEPTH && + (!iso9660->opt.limit_depth || iso9660->opt.iso_level == 4)) + r = isoent_alloc_path_table(a, &(iso9660->primary), + iso9660->dircnt_max + 1); + else + /* The number of levels in the hierarchy cannot exceed + * eight. */ + r = isoent_alloc_path_table(a, &(iso9660->primary), + MAX_DEPTH); + if (r < 0) + return (r); + if (iso9660->opt.joliet) { + r = isoent_alloc_path_table(a, &(iso9660->joliet), + iso9660->dircnt_max + 1); + if (r < 0) + return (r); + } + + /* Step 0. + * - Collect directories for primary and joliet. + */ + isoent_collect_dirs(&(iso9660->primary), NULL, 0); + if (iso9660->opt.joliet) + isoent_collect_dirs(&(iso9660->joliet), NULL, 0); + /* + * Rockridge; move deeper depth directories to rr_moved. + */ + if (iso9660->opt.rr) { + r = isoent_rr_move(a); + if (r < 0) + return (r); + } + + /* Update nlink. */ + isofile_connect_hardlink_files(iso9660); + + /* Step 1. + * - Renew a value of the depth of that directories. + * - Resolve hardlinks. + * - Convert pathnames to ISO9660 name or UCS2(joliet). + * - Sort files by each directory. + */ + r = isoent_traverse_tree(a, &(iso9660->primary)); + if (r < 0) + return (r); + if (iso9660->opt.joliet) { + r = isoent_traverse_tree(a, &(iso9660->joliet)); + if (r < 0) + return (r); + } + + /* Step 2. + * - Sort directories. + * - Assign all directory number. + */ + dir_number = 1; + for (depth = 0; depth < iso9660->primary.max_depth; depth++) { + r = isoent_make_path_table_2(a, &(iso9660->primary), + depth, &dir_number); + if (r < 0) + return (r); + } + if (iso9660->opt.joliet) { + dir_number = 1; + for (depth = 0; depth < iso9660->joliet.max_depth; depth++) { + r = isoent_make_path_table_2(a, &(iso9660->joliet), + depth, &dir_number); + if (r < 0) + return (r); + } + } + if (iso9660->opt.limit_dirs && dir_number > 0xffff) { + /* + * Maximum number of directories is 65535(0xffff) + * doe to size(16bit) of Parent Directory Number of + * the Path Table. + * See also ISO9660 Standard 9.4. + */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Too many directories(%d) over 65535.", dir_number); + return (ARCHIVE_FATAL); + } + + /* Get the size of the Path Table. */ + calculate_path_table_size(&(iso9660->primary)); + if (iso9660->opt.joliet) + calculate_path_table_size(&(iso9660->joliet)); + + return (ARCHIVE_OK); +} + +static int +isoent_find_out_boot_file(struct archive_write *a, struct isoent *rootent) +{ + struct iso9660 *iso9660 = a->format_data; + + /* Find a isoent of the boot file. */ + iso9660->el_torito.boot = isoent_find_entry(rootent, + iso9660->el_torito.boot_filename.s); + if (iso9660->el_torito.boot == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't find the boot image file ``%s''", + iso9660->el_torito.boot_filename.s); + return (ARCHIVE_FATAL); + } + iso9660->el_torito.boot->file->boot = BOOT_IMAGE; + return (ARCHIVE_OK); +} + +static int +isoent_create_boot_catalog(struct archive_write *a, struct isoent *rootent) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file; + struct isoent *isoent; + struct archive_entry *entry; + + (void)rootent; /* UNUSED */ + /* + * Create the entry which is the "boot.catalog" file. + */ + file = isofile_new(a, NULL); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + archive_entry_set_pathname(file->entry, + iso9660->el_torito.catalog_filename.s); + archive_entry_set_size(file->entry, LOGICAL_BLOCK_SIZE); + archive_entry_set_mtime(file->entry, iso9660->birth_time, 0); + archive_entry_set_atime(file->entry, iso9660->birth_time, 0); + archive_entry_set_ctime(file->entry, iso9660->birth_time, 0); + archive_entry_set_uid(file->entry, getuid()); + archive_entry_set_gid(file->entry, getgid()); + archive_entry_set_mode(file->entry, AE_IFREG | 0444); + archive_entry_set_nlink(file->entry, 1); + + if (isofile_gen_utility_names(a, file) < ARCHIVE_WARN) { + isofile_free(file); + return (ARCHIVE_FATAL); + } + file->boot = BOOT_CATALOG; + file->content.size = LOGICAL_BLOCK_SIZE; + isofile_add_entry(iso9660, file); + + isoent = isoent_new(file); + if (isoent == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + isoent->virtual = 1; + + /* Add the "boot.catalog" entry into tree */ + if (isoent_tree(a, &isoent) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + iso9660->el_torito.catalog = isoent; + /* + * Get a boot medai type. + */ + switch (iso9660->opt.boot_type) { + default: + case OPT_BOOT_TYPE_AUTO: + /* Try detecting a media type of the boot image. */ + entry = iso9660->el_torito.boot->file->entry; + if (archive_entry_size(entry) == FD_1_2M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_1_2M_DISKETTE; + else if (archive_entry_size(entry) == FD_1_44M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_1_44M_DISKETTE; + else if (archive_entry_size(entry) == FD_2_88M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_2_88M_DISKETTE; + else + /* We cannot decide whether the boot image is + * hard-disk. */ + iso9660->el_torito.media_type = + BOOT_MEDIA_NO_EMULATION; + break; + case OPT_BOOT_TYPE_NO_EMU: + iso9660->el_torito.media_type = BOOT_MEDIA_NO_EMULATION; + break; + case OPT_BOOT_TYPE_HARD_DISK: + iso9660->el_torito.media_type = BOOT_MEDIA_HARD_DISK; + break; + case OPT_BOOT_TYPE_FD: + entry = iso9660->el_torito.boot->file->entry; + if (archive_entry_size(entry) <= FD_1_2M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_1_2M_DISKETTE; + else if (archive_entry_size(entry) <= FD_1_44M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_1_44M_DISKETTE; + else if (archive_entry_size(entry) <= FD_2_88M_SIZE) + iso9660->el_torito.media_type = + BOOT_MEDIA_2_88M_DISKETTE; + else { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Boot image file(``%s'') size is too big " + "for fd type.", + iso9660->el_torito.boot_filename.s); + return (ARCHIVE_FATAL); + } + break; + } + + /* + * Get a system type. + * TODO: `El Torito' specification says "A copy of byte 5 from the + * Partition Table found in the boot image". + */ + iso9660->el_torito.system_type = 0; + + /* + * Get an ID. + */ + if (iso9660->opt.publisher) + archive_string_copy(&(iso9660->el_torito.id), + &(iso9660->publisher_identifier)); + + + return (ARCHIVE_OK); +} + +/* + * If a media type is floppy, return its image size. + * otherwise return 0. + */ +static size_t +fd_boot_image_size(int media_type) +{ + switch (media_type) { + case BOOT_MEDIA_1_2M_DISKETTE: + return (FD_1_2M_SIZE); + case BOOT_MEDIA_1_44M_DISKETTE: + return (FD_1_44M_SIZE); + case BOOT_MEDIA_2_88M_DISKETTE: + return (FD_2_88M_SIZE); + default: + return (0); + } +} + +/* + * Make a boot catalog image data. + */ +static int +make_boot_catalog(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + unsigned char *block; + unsigned char *p; + uint16_t sum, *wp; + + block = wb_buffptr(a); + memset(block, 0, LOGICAL_BLOCK_SIZE); + p = block; + /* + * Validation Entry + */ + /* Header ID */ + p[0] = 1; + /* Platform ID */ + p[1] = iso9660->el_torito.platform_id; + /* Reserved */ + p[2] = p[3] = 0; + /* ID */ + if (archive_strlen(&(iso9660->el_torito.id)) > 0) + strncpy((char *)p+4, iso9660->el_torito.id.s, 23); + p[27] = 0; + /* Checksum */ + p[28] = p[29] = 0; + /* Key */ + p[30] = 0x55; + p[31] = 0xAA; + + sum = 0; + wp = (uint16_t *)block; + while (wp < (uint16_t *)&block[32]) + sum += archive_le16dec(wp++); + set_num_721(&block[28], (~sum) + 1); + + /* + * Initial/Default Entry + */ + p = &block[32]; + /* Boot Indicator */ + p[0] = 0x88; + /* Boot media type */ + p[1] = iso9660->el_torito.media_type; + /* Load Segment */ + if (iso9660->el_torito.media_type == BOOT_MEDIA_NO_EMULATION) + set_num_721(&p[2], iso9660->el_torito.boot_load_seg); + else + set_num_721(&p[2], 0); + /* System Type */ + p[4] = iso9660->el_torito.system_type; + /* Unused */ + p[5] = 0; + /* Sector Count */ + if (iso9660->el_torito.media_type == BOOT_MEDIA_NO_EMULATION) + set_num_721(&p[6], iso9660->el_torito.boot_load_size); + else + set_num_721(&p[6], 1); + /* Load RBA */ + set_num_731(&p[8], + iso9660->el_torito.boot->file->content.location); + /* Unused */ + memset(&p[12], 0, 20); + + return (wb_consume(a, LOGICAL_BLOCK_SIZE)); +} + +static int +setup_boot_information(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + struct isoent *np; + int64_t size; + uint32_t sum; + unsigned char buff[4096]; + + np = iso9660->el_torito.boot; + lseek(iso9660->temp_fd, + np->file->content.offset_of_temp + 64, SEEK_SET); + size = archive_entry_size(np->file->entry) - 64; + if (size <= 0) { + archive_set_error(&a->archive, errno, + "Boot file(%jd) is too small", (intmax_t)size + 64); + return (ARCHIVE_FATAL); + } + sum = 0; + while (size > 0) { + size_t rsize; + ssize_t i, rs; + + if (size > sizeof(buff)) + rsize = sizeof(buff); + else + rsize = (size_t)size; + + rs = read(iso9660->temp_fd, buff, rsize); + if (rs <= 0) { + archive_set_error(&a->archive, errno, + "Can't read temporary file(%jd)", + (intmax_t)rs); + return (ARCHIVE_FATAL); + } + for (i = 0; i < rs; i += 4) + sum += archive_le32dec(buff + i); + size -= rs; + } + /* Set the location of Primary Volume Descriptor. */ + set_num_731(buff, SYSTEM_AREA_BLOCK); + /* Set the location of the boot file. */ + set_num_731(buff+4, np->file->content.location); + /* Set the size of the boot file. */ + size = fd_boot_image_size(iso9660->el_torito.media_type); + if (size == 0) + size = archive_entry_size(np->file->entry); + set_num_731(buff+8, (uint32_t)size); + /* Set the sum of the boot file. */ + set_num_731(buff+12, sum); + /* Clear reserved bytes. */ + memset(buff+16, 0, 40); + + /* Overwrite the boot file. */ + lseek(iso9660->temp_fd, + np->file->content.offset_of_temp + 8, SEEK_SET); + return (write_to_temp(a, buff, 56)); +} + +#ifdef HAVE_ZLIB_H + +static int +zisofs_init_zstream(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + int r; + + iso9660->zisofs.stream.next_in = NULL; + iso9660->zisofs.stream.avail_in = 0; + iso9660->zisofs.stream.total_in = 0; + iso9660->zisofs.stream.total_out = 0; + if (iso9660->zisofs.stream_valid) + r = deflateReset(&(iso9660->zisofs.stream)); + else { + r = deflateInit(&(iso9660->zisofs.stream), + iso9660->zisofs.compression_level); + iso9660->zisofs.stream_valid = 1; + } + switch (r) { + case Z_OK: + break; + default: + case Z_STREAM_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid setup parameter"); + return (ARCHIVE_FATAL); + case Z_MEM_ERROR: + archive_set_error(&a->archive, ENOMEM, + "Internal error initializing " + "compression library"); + return (ARCHIVE_FATAL); + case Z_VERSION_ERROR: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal error initializing " + "compression library: invalid library version"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +#endif /* HAVE_ZLIB_H */ + +static int +zisofs_init(struct archive_write *a, struct isofile *file) +{ + struct iso9660 *iso9660 = a->format_data; +#ifdef HAVE_ZLIB_H + uint64_t tsize; + size_t ceil, bpsize; + int r; +#endif + + iso9660->zisofs.detect_magic = 0; + iso9660->zisofs.making = 0; + + if (!iso9660->opt.rr || !iso9660->opt.zisofs) + return (ARCHIVE_OK); + + if (archive_entry_size(file->entry) >= 24 && + archive_entry_size(file->entry) < MULTI_EXTENT_SIZE) { + /* Acceptable file size for zisofs. */ + iso9660->zisofs.detect_magic = 1; + iso9660->zisofs.magic_cnt = 0; + } + if (!iso9660->zisofs.detect_magic) + return (ARCHIVE_OK); + +#ifdef HAVE_ZLIB_H + /* The number of Logical Blocks which uncompressed data + * will use in iso-image file is the same as the number of + * Logical Blocks which zisofs(compressed) data will use + * in ISO-image file. It won't reduce iso-image file size. */ + if (archive_entry_size(file->entry) <= LOGICAL_BLOCK_SIZE) + return (ARCHIVE_OK); + + /* Initialize compression library */ + r = zisofs_init_zstream(a); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Mark file->zisofs to create RRIP 'ZF' Use Entry. */ + file->zisofs.header_size = ZF_HEADER_SIZE >> 2; + file->zisofs.log2_bs = ZF_LOG2_BS; + file->zisofs.uncompressed_size = archive_entry_size(file->entry); + + /* Calculate a size of Block Pointers of zisofs. */ + ceil = (file->zisofs.uncompressed_size + ZF_BLOCK_SIZE -1) + >> file->zisofs.log2_bs; + iso9660->zisofs.block_pointers_cnt = ceil + 1; + iso9660->zisofs.block_pointers_idx = 0; + + /* Ensure a buffer size used for Block Pointers */ + bpsize = iso9660->zisofs.block_pointers_cnt * + sizeof(iso9660->zisofs.block_pointers[0]); + if (iso9660->zisofs.block_pointers_allocated < bpsize) { + free(iso9660->zisofs.block_pointers); + iso9660->zisofs.block_pointers = malloc(bpsize); + if (iso9660->zisofs.block_pointers == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + return (ARCHIVE_FATAL); + } + iso9660->zisofs.block_pointers_allocated = bpsize; + } + + /* + * Skip zisofs header and Block Pointers, which we will write + * after all compressed data of a file written to the temporary + * file. + */ + tsize = ZF_HEADER_SIZE + bpsize; + if (write_null(a, tsize) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* + * Initialize some variables to make zisofs. + */ + archive_le32enc(&(iso9660->zisofs.block_pointers[0]), tsize); + iso9660->zisofs.remaining = file->zisofs.uncompressed_size; + iso9660->zisofs.making = 1; + iso9660->zisofs.allzero = 1; + iso9660->zisofs.block_offset = tsize; + iso9660->zisofs.total_size = tsize; + iso9660->cur_file->cur_content->size = tsize; +#endif + + return (ARCHIVE_OK); +} + +static void +zisofs_detect_magic(struct archive_write *a, const void *buff, size_t s) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file = iso9660->cur_file; + const unsigned char *p, *endp; + const unsigned char *magic_buff; + uint32_t uncompressed_size; + unsigned char header_size; + unsigned char log2_bs; + size_t ceil, doff; + uint32_t bst, bed; + int magic_max; + int64_t entry_size; + + entry_size = archive_entry_size(file->entry); + if (sizeof(iso9660->zisofs.magic_buffer) > entry_size) + magic_max = entry_size; + else + magic_max = sizeof(iso9660->zisofs.magic_buffer); + + if (iso9660->zisofs.magic_cnt == 0 && s >= (size_t)magic_max) + /* It's unnecessary we copy buffer. */ + magic_buff = buff; + else { + if (iso9660->zisofs.magic_cnt < magic_max) { + size_t l; + + l = sizeof(iso9660->zisofs.magic_buffer) + - iso9660->zisofs.magic_cnt; + if (l > s) + l = s; + memcpy(iso9660->zisofs.magic_buffer + + iso9660->zisofs.magic_cnt, buff, l); + iso9660->zisofs.magic_cnt += l; + if (iso9660->zisofs.magic_cnt < magic_max) + return; + } + magic_buff = iso9660->zisofs.magic_buffer; + } + iso9660->zisofs.detect_magic = 0; + p = magic_buff; + + /* Check the magic code of zisofs. */ + if (memcmp(p, zisofs_magic, sizeof(zisofs_magic)) != 0) + /* This is not zisofs file which made by mkzftree. */ + return; + p += sizeof(zisofs_magic); + + /* Read a zisofs header. */ + uncompressed_size = archive_le32dec(p); + header_size = p[4]; + log2_bs = p[5]; + if (uncompressed_size < 24 || header_size != 4 || + log2_bs > 30 || log2_bs < 7) + return;/* Invalid or not supported header. */ + + /* Calculate a size of Block Pointers of zisofs. */ + ceil = (uncompressed_size + + (ARCHIVE_LITERAL_LL(1) << log2_bs) -1) >> log2_bs; + doff = (ceil + 1) * 4 + 16; + if (entry_size < doff) + return;/* Invalid data. */ + + /* Check every Block Pointer has valid value. */ + p = magic_buff + 16; + endp = magic_buff + magic_max; + while (ceil && p + 8 <= endp) { + bst = archive_le32dec(p); + if (bst != doff) + return;/* Invalid data. */ + p += 4; + bed = archive_le32dec(p); + if (bed < bst || bed > entry_size) + return;/* Invalid data. */ + doff += bed - bst; + ceil--; + } + + file->zisofs.uncompressed_size = uncompressed_size; + file->zisofs.header_size = header_size; + file->zisofs.log2_bs = log2_bs; + + /* Disable making a zisofs image. */ + iso9660->zisofs.making = 0; +} + +#ifdef HAVE_ZLIB_H + +/* + * Compress data and write it to a temporary file. + */ +static int +zisofs_write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file = iso9660->cur_file; + const unsigned char *b; + z_stream *zstrm; + size_t avail, csize; + int flush, r; + + zstrm = &(iso9660->zisofs.stream); + zstrm->next_out = wb_buffptr(a); + zstrm->avail_out = wb_remaining(a); + b = (const unsigned char *)buff; + do { + avail = ZF_BLOCK_SIZE - zstrm->total_in; + if (s < avail) { + avail = s; + flush = Z_NO_FLUSH; + } else + flush = Z_FINISH; + iso9660->zisofs.remaining -= avail; + if (iso9660->zisofs.remaining <= 0) + flush = Z_FINISH; + + zstrm->next_in = (Bytef *)(uintptr_t)(const void *)b; + zstrm->avail_in = avail; + + /* + * Check if current data block are all zero. + */ + if (iso9660->zisofs.allzero) { + const unsigned char *nonzero = b; + const unsigned char *nonzeroend = b + avail; + + while (nonzero < nonzeroend) + if (*nonzero++) { + iso9660->zisofs.allzero = 0; + break; + } + } + b += avail; + s -= avail; + + /* + * If current data block are all zero, we do not use + * compressed data. + */ + if (flush == Z_FINISH && iso9660->zisofs.allzero && + avail + zstrm->total_in == ZF_BLOCK_SIZE) { + if (iso9660->zisofs.block_offset != + file->cur_content->size) { + int64_t diff; + + r = wb_set_offset(a, + file->cur_content->offset_of_temp + + iso9660->zisofs.block_offset); + if (r != ARCHIVE_OK) + return (r); + diff = file->cur_content->size - + iso9660->zisofs.block_offset; + file->cur_content->size -= diff; + iso9660->zisofs.total_size -= diff; + } + zstrm->avail_in = 0; + } + + /* + * Compress file data. + */ + while (zstrm->avail_in > 0) { + csize = zstrm->total_out; + r = deflate(zstrm, flush); + switch (r) { + case Z_OK: + case Z_STREAM_END: + csize = zstrm->total_out - csize; + if (wb_consume(a, csize) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->zisofs.total_size += csize; + iso9660->cur_file->cur_content->size += csize; + zstrm->next_out = wb_buffptr(a); + zstrm->avail_out = wb_remaining(a); + break; + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "Compression failed:" + " deflate() call returned status %d", + r); + return (ARCHIVE_FATAL); + } + } + + if (flush == Z_FINISH) { + /* + * Save the information of one zisofs block. + */ + iso9660->zisofs.block_pointers_idx ++; + archive_le32enc(&(iso9660->zisofs.block_pointers[ + iso9660->zisofs.block_pointers_idx]), + iso9660->zisofs.total_size); + r = zisofs_init_zstream(a); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + iso9660->zisofs.allzero = 1; + iso9660->zisofs.block_offset = file->cur_content->size; + } + } while (s); + + return (ARCHIVE_OK); +} + +static int +zisofs_finish_entry(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file = iso9660->cur_file; + unsigned char buff[16]; + size_t s; + int64_t tail; + + /* Direct temp file stream to zisofs temp file stream. */ + archive_entry_set_size(file->entry, iso9660->zisofs.total_size); + + /* + * Save a file pointer which points the end of current zisofs data. + */ + tail = wb_offset(a); + + /* + * Make a header. + * + * +-----------------+----------------+-----------------+ + * | Header 16 bytes | Block Pointers | Compressed data | + * +-----------------+----------------+-----------------+ + * 0 16 +X + * Block Pointers : + * 4 * (((Uncompressed file size + block_size -1) / block_size) + 1) + * + * Write zisofs header. + * Magic number + * +----+----+----+----+----+----+----+----+ + * | 37 | E4 | 53 | 96 | C9 | DB | D6 | 07 | + * +----+----+----+----+----+----+----+----+ + * 0 1 2 3 4 5 6 7 8 + * + * +------------------------+------------------+ + * | Uncompressed file size | header_size >> 2 | + * +------------------------+------------------+ + * 8 12 13 + * + * +-----------------+----------------+ + * | log2 block_size | Reserved(0000) | + * +-----------------+----------------+ + * 13 14 16 + */ + memcpy(buff, zisofs_magic, 8); + set_num_731(buff+8, file->zisofs.uncompressed_size); + buff[12] = file->zisofs.header_size; + buff[13] = file->zisofs.log2_bs; + buff[14] = buff[15] = 0;/* Reserved */ + + /* Move to the right position to write the header. */ + wb_set_offset(a, file->content.offset_of_temp); + + /* Write the header. */ + if (wb_write_to_temp(a, buff, 16) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* + * Write zisofs Block Pointers. + */ + s = iso9660->zisofs.block_pointers_cnt * + sizeof(iso9660->zisofs.block_pointers[0]); + if (wb_write_to_temp(a, iso9660->zisofs.block_pointers, s) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Set a file pointer back to the end of the temporary file. */ + wb_set_offset(a, tail); + + return (ARCHIVE_OK); +} + +static int +zisofs_free(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + int ret = ARCHIVE_OK; + + free(iso9660->zisofs.block_pointers); + if (iso9660->zisofs.stream_valid && + deflateEnd(&(iso9660->zisofs.stream)) != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + iso9660->zisofs.block_pointers = NULL; + iso9660->zisofs.stream_valid = 0; + return (ret); +} + +struct zisofs_extract { + int pz_log2_bs; /* Log2 of block size */ + uint64_t pz_uncompressed_size; + size_t uncompressed_buffer_size; + + int initialized:1; + int header_passed:1; + + uint32_t pz_offset; + unsigned char *block_pointers; + size_t block_pointers_size; + size_t block_pointers_avail; + size_t block_off; + uint32_t block_avail; + + z_stream stream; + int stream_valid; +}; + +static ssize_t +zisofs_extract_init(struct archive_write *a, struct zisofs_extract *zisofs, + const unsigned char *p, size_t bytes) +{ + size_t avail = bytes; + size_t ceil, xsize; + + /* Allocate block pointers buffer. */ + ceil = (zisofs->pz_uncompressed_size + + (1LL << zisofs->pz_log2_bs) - 1) + >> zisofs->pz_log2_bs; + xsize = (ceil + 1) * 4; + if (zisofs->block_pointers == NULL) { + size_t alloc = ((xsize >> 10) + 1) << 10; + zisofs->block_pointers = malloc(alloc); + if (zisofs->block_pointers == NULL) { + archive_set_error(&a->archive, ENOMEM, + "No memory for zisofs decompression"); + return (ARCHIVE_FATAL); + } + } + zisofs->block_pointers_size = xsize; + + /* Allocate uncompressed data buffer. */ + zisofs->uncompressed_buffer_size = 1UL << zisofs->pz_log2_bs; + + /* + * Read the file header, and check the magic code of zisofs. + */ + if (!zisofs->header_passed) { + int err = 0; + if (avail < 16) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs file body"); + return (ARCHIVE_FATAL); + } + + if (memcmp(p, zisofs_magic, sizeof(zisofs_magic)) != 0) + err = 1; + else if (archive_le32dec(p + 8) != zisofs->pz_uncompressed_size) + err = 1; + else if (p[12] != 4 || p[13] != zisofs->pz_log2_bs) + err = 1; + if (err) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs file body"); + return (ARCHIVE_FATAL); + } + avail -= 16; + p += 16; + zisofs->header_passed = 1; + } + + /* + * Read block pointers. + */ + if (zisofs->header_passed && + zisofs->block_pointers_avail < zisofs->block_pointers_size) { + xsize = zisofs->block_pointers_size + - zisofs->block_pointers_avail; + if (avail < xsize) + xsize = avail; + memcpy(zisofs->block_pointers + + zisofs->block_pointers_avail, p, xsize); + zisofs->block_pointers_avail += xsize; + avail -= xsize; + if (zisofs->block_pointers_avail + == zisofs->block_pointers_size) { + /* We've got all block pointers and initialize + * related variables. */ + zisofs->block_off = 0; + zisofs->block_avail = 0; + /* Complete a initialization */ + zisofs->initialized = 1; + } + } + return ((ssize_t)avail); +} + +static ssize_t +zisofs_extract(struct archive_write *a, struct zisofs_extract *zisofs, + const unsigned char *p, size_t bytes) +{ + size_t avail; + int r; + + if (!zisofs->initialized) { + ssize_t rs = zisofs_extract_init(a, zisofs, p, bytes); + if (rs < 0) + return (rs); + if (!zisofs->initialized) { + /* We need more data. */ + zisofs->pz_offset += bytes; + return (bytes); + } + avail = rs; + p += bytes - avail; + } else + avail = bytes; + + /* + * Get block offsets from block pointers. + */ + if (zisofs->block_avail == 0) { + uint32_t bst, bed; + + if (zisofs->block_off + 4 >= zisofs->block_pointers_size) { + /* There isn't a pair of offsets. */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers"); + return (ARCHIVE_FATAL); + } + bst = archive_le32dec( + zisofs->block_pointers + zisofs->block_off); + if (bst != zisofs->pz_offset + (bytes - avail)) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers(cannot seek)"); + return (ARCHIVE_FATAL); + } + bed = archive_le32dec( + zisofs->block_pointers + zisofs->block_off + 4); + if (bed < bst) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Illegal zisofs block pointers"); + return (ARCHIVE_FATAL); + } + zisofs->block_avail = bed - bst; + zisofs->block_off += 4; + + /* Initialize compression library for new block. */ + if (zisofs->stream_valid) + r = inflateReset(&zisofs->stream); + else + r = inflateInit(&zisofs->stream); + if (r != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't initialize zisofs decompression."); + return (ARCHIVE_FATAL); + } + zisofs->stream_valid = 1; + zisofs->stream.total_in = 0; + zisofs->stream.total_out = 0; + } + + /* + * Make uncompressed data. + */ + if (zisofs->block_avail == 0) { + /* + * It's basically 32K bytes NUL data. + */ + unsigned char *wb; + size_t size, wsize; + + size = zisofs->uncompressed_buffer_size; + while (size) { + wb = wb_buffptr(a); + if (size > wb_remaining(a)) + wsize = wb_remaining(a); + else + wsize = size; + memset(wb, 0, wsize); + r = wb_consume(a, wsize); + if (r < 0) + return (r); + size -= wsize; + } + } else { + zisofs->stream.next_in = (Bytef *)(uintptr_t)(const void *)p; + if (avail > zisofs->block_avail) + zisofs->stream.avail_in = zisofs->block_avail; + else + zisofs->stream.avail_in = avail; + zisofs->stream.next_out = wb_buffptr(a); + zisofs->stream.avail_out = wb_remaining(a); + + r = inflate(&zisofs->stream, 0); + switch (r) { + case Z_OK: /* Decompressor made some progress.*/ + case Z_STREAM_END: /* Found end of stream. */ + break; + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "zisofs decompression failed (%d)", r); + return (ARCHIVE_FATAL); + } + avail -= zisofs->stream.next_in - p; + zisofs->block_avail -= zisofs->stream.next_in - p; + r = wb_consume(a, wb_remaining(a) - zisofs->stream.avail_out); + if (r < 0) + return (r); + } + zisofs->pz_offset += bytes; + return (bytes - avail); +} + +static int +zisofs_rewind_boot_file(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + struct isofile *file; + unsigned char *rbuff; + ssize_t r; + size_t remaining, rbuff_size; + struct zisofs_extract zext; + int64_t read_offset, write_offset, new_offset; + int fd, ret = ARCHIVE_OK; + + file = iso9660->el_torito.boot->file; + /* + * There is nothing to do if this boot file does not have + * zisofs header. + */ + if (file->zisofs.header_size == 0) + return (ARCHIVE_OK); + + /* + * Uncompress the zisofs'ed file contents. + */ + memset(&zext, 0, sizeof(zext)); + zext.pz_uncompressed_size = file->zisofs.uncompressed_size; + zext.pz_log2_bs = file->zisofs.log2_bs; + + fd = iso9660->temp_fd; + new_offset = wb_offset(a); + read_offset = file->content.offset_of_temp; + remaining = file->content.size; + if (remaining > 1024 * 32) + rbuff_size = 1024 * 32; + else + rbuff_size = remaining; + + rbuff = malloc(rbuff_size); + if (rbuff == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + while (remaining) { + size_t rsize; + ssize_t rs; + + /* Get the current file pointer. */ + write_offset = lseek(fd, 0, SEEK_CUR); + + /* Change the file pointer to read. */ + lseek(fd, read_offset, SEEK_SET); + + rsize = rbuff_size; + if (rsize > remaining) + rsize = remaining; + rs = read(iso9660->temp_fd, rbuff, rsize); + if (rs <= 0) { + archive_set_error(&a->archive, errno, + "Can't read temporary file(%jd)", (intmax_t)rs); + ret = ARCHIVE_FATAL; + break; + } + remaining -= rs; + read_offset += rs; + + /* Put the file pointer back to write. */ + lseek(fd, write_offset, SEEK_SET); + + r = zisofs_extract(a, &zext, rbuff, rs); + if (r < 0) { + ret = (int)r; + break; + } + } + + if (ret == ARCHIVE_OK) { + /* + * Change the boot file content from zisofs'ed data + * to plain data. + */ + file->content.offset_of_temp = new_offset; + file->content.size = file->zisofs.uncompressed_size; + archive_entry_set_size(file->entry, file->content.size); + /* Set to be no zisofs. */ + file->zisofs.header_size = 0; + file->zisofs.log2_bs = 0; + file->zisofs.uncompressed_size = 0; + r = wb_write_padding_to_temp(a, file->content.size); + if (r < 0) + ret = ARCHIVE_FATAL; + } + + /* + * Free the resource we used in this function only. + */ + free(rbuff); + free(zext.block_pointers); + if (zext.stream_valid && inflateEnd(&(zext.stream)) != Z_OK) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + ret = ARCHIVE_FATAL; + } + + return (ret); +} + +#else + +static int +zisofs_write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + (void)buff; /* UNUSED */ + (void)s; /* UNUSED */ + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, "Programing error"); + return (ARCHIVE_FATAL); +} + +static int +zisofs_rewind_boot_file(struct archive_write *a) +{ + struct iso9660 *iso9660 = a->format_data; + + if (iso9660->el_torito.boot->file->zisofs.header_size != 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "We cannot extract the zisofs imaged boot file;" + " this may not boot in being zisofs imaged"); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); +} + +static int +zisofs_finish_entry(struct archive_write *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +static int +zisofs_free(struct archive_write *a) +{ + (void)a; /* UNUSED */ + return (ARCHIVE_OK); +} + +#endif /* HAVE_ZLIB_H */ + diff --git a/libarchive/archive_write_set_format_mtree.c b/libarchive/archive_write_set_format_mtree.c new file mode 100644 index 0000000..3802a25 --- /dev/null +++ b/libarchive/archive_write_set_format_mtree.c @@ -0,0 +1,1468 @@ +/*- + * Copyright (c) 2009,2011 Michihiro NAKAJIMA + * Copyright (c) 2008 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_mtree.c 201171 2009-12-29 06:39:07Z kientzle $"); + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#include +#include + +#include "archive.h" +#include "archive_crypto_private.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#define INDENTNAMELEN 15 +#define MAXLINELEN 80 +#define SET_KEYS \ + (F_FLAGS | F_GID | F_GNAME | F_MODE | F_TYPE | F_UID | F_UNAME) + +struct mtree_entry { + struct mtree_entry *next; + + char *pathname; + char *symlink; + unsigned int nlink; + mode_t filetype; + mode_t mode; + int64_t uid; + int64_t gid; + char *uname; + char *gname; + char *fflags_text; + unsigned long fflags_set; + unsigned long fflags_clear; + time_t mtime; + long mtime_nsec; + dev_t rdevmajor; + dev_t rdevminor; + int64_t size; + + int compute_sum; + uint32_t crc; +#ifdef ARCHIVE_HAS_MD5 + unsigned char buf_md5[16]; +#endif +#ifdef ARCHIVE_HAS_RMD160 + unsigned char buf_rmd160[20]; +#endif +#ifdef ARCHIVE_HAS_SHA1 + unsigned char buf_sha1[20]; +#endif +#ifdef ARCHIVE_HAS_SHA256 + unsigned char buf_sha256[32]; +#endif +#ifdef ARCHIVE_HAS_SHA384 + unsigned char buf_sha384[48]; +#endif +#ifdef ARCHIVE_HAS_SHA512 + unsigned char buf_sha512[64]; +#endif +}; + +struct attr_counter { + struct attr_counter *prev; + struct attr_counter *next; + int count; + struct mtree_entry *m_entry; +}; + +struct mtree_writer { + struct mtree_entry *mtree_entry; + struct archive_string ebuf; + struct archive_string buf; + int first; + uint64_t entry_bytes_remaining; + struct { + int output; + int processed; + struct archive_string parent; + mode_t type; + int keys; + int64_t uid; + int64_t gid; + mode_t mode; + unsigned long fflags_set; + unsigned long fflags_clear; + + struct attr_counter *uid_list; + struct attr_counter *gid_list; + struct attr_counter *mode_list; + struct attr_counter *flags_list; + struct mtree_entry *me_first; + struct mtree_entry **me_last; + } set; + /* check sum */ + int compute_sum; + uint32_t crc; + uint64_t crc_len; +#ifdef ARCHIVE_HAS_MD5 + archive_md5_ctx md5ctx; +#endif +#ifdef ARCHIVE_HAS_RMD160 + archive_rmd160_ctx rmd160ctx; +#endif +#ifdef ARCHIVE_HAS_SHA1 + archive_sha1_ctx sha1ctx; +#endif +#ifdef ARCHIVE_HAS_SHA256 + archive_sha256_ctx sha256ctx; +#endif +#ifdef ARCHIVE_HAS_SHA384 + archive_sha384_ctx sha384ctx; +#endif +#ifdef ARCHIVE_HAS_SHA512 + archive_sha512_ctx sha512ctx; +#endif + /* Keyword options */ + int keys; +#define F_CKSUM 0x00000001 /* check sum */ +#define F_DEV 0x00000002 /* device type */ +#define F_DONE 0x00000004 /* directory done */ +#define F_FLAGS 0x00000008 /* file flags */ +#define F_GID 0x00000010 /* gid */ +#define F_GNAME 0x00000020 /* group name */ +#define F_IGN 0x00000040 /* ignore */ +#define F_MAGIC 0x00000080 /* name has magic chars */ +#define F_MD5 0x00000100 /* MD5 digest */ +#define F_MODE 0x00000200 /* mode */ +#define F_NLINK 0x00000400 /* number of links */ +#define F_NOCHANGE 0x00000800 /* If owner/mode "wrong", do + * not change */ +#define F_OPT 0x00001000 /* existence optional */ +#define F_RMD160 0x00002000 /* RIPEMD160 digest */ +#define F_SHA1 0x00004000 /* SHA-1 digest */ +#define F_SIZE 0x00008000 /* size */ +#define F_SLINK 0x00010000 /* symbolic link */ +#define F_TAGS 0x00020000 /* tags */ +#define F_TIME 0x00040000 /* modification time */ +#define F_TYPE 0x00080000 /* file type */ +#define F_UID 0x00100000 /* uid */ +#define F_UNAME 0x00200000 /* user name */ +#define F_VISIT 0x00400000 /* file visited */ +#define F_SHA256 0x00800000 /* SHA-256 digest */ +#define F_SHA384 0x01000000 /* SHA-384 digest */ +#define F_SHA512 0x02000000 /* SHA-512 digest */ + + /* Options */ + int dironly; /* if the dironly is 1, ignore everything except + * directory type files. like mtree(8) -d option. + */ + int indent; /* if the indent is 1, indent writing data. */ +}; + +#define DEFAULT_KEYS (F_DEV | F_FLAGS | F_GID | F_GNAME | F_SLINK | F_MODE\ + | F_NLINK | F_SIZE | F_TIME | F_TYPE | F_UID\ + | F_UNAME) + +static struct attr_counter * new_attr_count(struct mtree_entry *, + struct attr_counter *); +static void free_attr_count(struct attr_counter **); +static int inc_attr_count(struct attr_counter **, struct attr_counter *, + struct attr_counter *, struct mtree_entry *); +static int collect_set_values(struct mtree_writer *, struct mtree_entry *); +static int get_keys(struct mtree_writer *, struct mtree_entry *); +static void sum_init(struct mtree_writer *); +static void sum_update(struct mtree_writer *, const void *, size_t); +static void sum_final(struct mtree_writer *, struct mtree_entry *); +static void sum_write(struct archive_string *, struct mtree_entry *); + +#define COMPUTE_CRC(var, ch) (var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)] +static const uint32_t crctab[] = { + 0x0, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +static int +mtree_safe_char(char c) +{ + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + return 1; + if (c >= '0' && c <= '9') + return 1; + if (c == 35 || c == 61 || c == 92) + return 0; /* #, = and \ are always quoted */ + + if (c >= 33 && c <= 47) /* !"$%&'()*+,-./ */ + return 1; + if (c >= 58 && c <= 64) /* :;<>?@ */ + return 1; + if (c >= 91 && c <= 96) /* []^_` */ + return 1; + if (c >= 123 && c <= 126) /* {|}~ */ + return 1; + return 0; +} + +static void +mtree_quote(struct archive_string *s, const char *str) +{ + const char *start; + char buf[4]; + unsigned char c; + + for (start = str; *str != '\0'; ++str) { + if (mtree_safe_char(*str)) + continue; + if (start != str) + archive_strncat(s, start, str - start); + c = (unsigned char)*str; + buf[0] = '\\'; + buf[1] = (c / 64) + '0'; + buf[2] = (c / 8 % 8) + '0'; + buf[3] = (c % 8) + '0'; + archive_strncat(s, buf, 4); + start = str + 1; + } + + if (start != str) + archive_strncat(s, start, str - start); +} + +/* + * Indent a line as mtree utility to be readable for people. + */ +static void +mtree_indent(struct mtree_writer *mtree) +{ + int i, fn; + const char *r, *s, *x; + + fn = 1; + s = r = mtree->ebuf.s; + x = NULL; + while (*r == ' ') + r++; + while ((r = strchr(r, ' ')) != NULL) { + if (fn) { + fn = 0; + archive_strncat(&mtree->buf, s, r - s); + if (r -s > INDENTNAMELEN) { + archive_strncat(&mtree->buf, " \\\n", 3); + for (i = 0; i < (INDENTNAMELEN + 1); i++) + archive_strappend_char(&mtree->buf, ' '); + } else { + for (i = r -s; i < (INDENTNAMELEN + 1); i++) + archive_strappend_char(&mtree->buf, ' '); + } + s = ++r; + x = NULL; + continue; + } + if (r - s <= MAXLINELEN - 3 - INDENTNAMELEN) + x = r++; + else { + if (x == NULL) + x = r; + archive_strncat(&mtree->buf, s, x - s); + archive_strncat(&mtree->buf, " \\\n", 3); + for (i = 0; i < (INDENTNAMELEN + 1); i++) + archive_strappend_char(&mtree->buf, ' '); + s = r = ++x; + x = NULL; + } + } + if (x != NULL && strlen(s) > MAXLINELEN - 3 - INDENTNAMELEN) { + /* Last keyword is longer. */ + archive_strncat(&mtree->buf, s, x - s); + archive_strncat(&mtree->buf, " \\\n", 3); + for (i = 0; i < (INDENTNAMELEN + 1); i++) + archive_strappend_char(&mtree->buf, ' '); + s = ++x; + } + archive_strcat(&mtree->buf, s); + archive_string_empty(&mtree->ebuf); +} + +#if !defined(_WIN32) || defined(__CYGWIN__) +static size_t +dir_len(struct mtree_entry *me) +{ + const char *path, *r; + + path = me->pathname; + r = strrchr(path, '/'); + if (r == NULL) + return (0); + /* Include a separator size */ + return (r - path + 1); +} + +#else /* _WIN32 && !__CYGWIN__ */ +/* + * Note: We should use wide-character for findng '\' character, + * a directory separator on Windows, because some character-set have + * been using the '\' character for a part of its multibyte character + * code. + */ +static size_t +dir_len(struct mtree_entry *me) +{ + wchar_t wc; + const char *path; + const char *p, *rp; + size_t al, l, size; + + path = me->pathname; + al = l = -1; + for (p = path; *p != '\0'; ++p) { + if (*p == '\\') + al = l = p - path; + else if (*p == '/') + al = p - path; + } + if (l == -1) + goto alen; + size = p - path; + rp = p = path; + while (*p != '\0') { + l = mbtowc(&wc, p, size); + if (l == -1) + goto alen; + if (l == 1 && (wc == L'/' || wc == L'\\')) + rp = p; + p += l; + size -= l; + } + return (rp - path + 1); +alen: + if (al == -1) + return (0); + return (al + 1); +} +#endif /* _WIN32 && !__CYGWIN__ */ + +/* + * Test if a parent directory of the current entry is changed. + */ +static int +parent_dir_changed(struct archive_string *dir, struct mtree_entry *me) +{ + const char *path; + size_t l; + + l = dir_len(me); + path = me->pathname; + if (archive_strlen(dir) > 0) { + if (l == 0) { + archive_string_empty(dir); + return (1); + } + if (strncmp(dir->s, path, l) == 0) + return (0); /* The parent directory is the same. */ + } else if (l == 0) + return (0); /* The parent directory is the same. */ + archive_strncpy(dir, path, l); + return (1); +} + +/* + * Write /set keyword. + * Set most used value of uid,gid,mode and fflags, which are + * collected by collect_set_values() function. + */ +static void +write_global(struct mtree_writer *mtree) +{ + struct archive_string setstr; + struct archive_string unsetstr; + const char *name; + int keys, oldkeys, effkeys; + struct attr_counter *ac; + + archive_string_init(&setstr); + archive_string_init(&unsetstr); + keys = mtree->keys & SET_KEYS; + oldkeys = mtree->set.keys; + effkeys = keys; + if (mtree->set.processed) { + /* + * Check if the global data needs updating. + */ + effkeys &= ~F_TYPE; + if (oldkeys & (F_UNAME | F_UID)) { + ac = mtree->set.uid_list; + do { + if (mtree->set.uid == ac->m_entry->uid) { + effkeys &= ~(F_UNAME | F_UID); + break; + } + if (ac->next != NULL && + ac->next->count == ac->count) + continue; + } while (0); + } + if (oldkeys & (F_GNAME | F_GID)) { + ac = mtree->set.gid_list; + do { + if (mtree->set.gid == ac->m_entry->gid) { + effkeys &= ~(F_GNAME | F_GID); + break; + } + if (ac->next != NULL && + ac->next->count == ac->count) + continue; + } while (0); + } + if (oldkeys & F_MODE) { + ac = mtree->set.mode_list; + do { + if (mtree->set.mode == ac->m_entry->mode) { + effkeys &= ~F_MODE; + break; + } + if (ac->next != NULL && + ac->next->count == ac->count) + continue; + } while (0); + } + if ((oldkeys & F_FLAGS) != 0) { + ac = mtree->set.flags_list; + do { + if (ac->m_entry->fflags_set == + mtree->set.fflags_set && + ac->m_entry->fflags_clear == + mtree->set.fflags_clear) { + effkeys &= ~F_FLAGS; + break; + } + if (ac->next != NULL && + ac->next->count == ac->count) + continue; + } while (0); + } + } + if ((keys & effkeys & F_TYPE) != 0) { + if (mtree->dironly) { + archive_strcat(&setstr, " type=dir"); + mtree->set.type = AE_IFDIR; + } else { + archive_strcat(&setstr, " type=file"); + mtree->set.type = AE_IFREG; + } + } + if ((keys & effkeys & F_UNAME) != 0) { + name = mtree->set.uid_list->m_entry->uname; + if (name != NULL) { + archive_strcat(&setstr, " uname="); + mtree_quote(&setstr, name); + } else { + keys &= ~F_UNAME; + if ((oldkeys & F_UNAME) != 0) + archive_strcat(&unsetstr, " uname"); + } + } + if ((keys & effkeys & F_UID) != 0) { + mtree->set.uid = mtree->set.uid_list->m_entry->uid; + archive_string_sprintf(&setstr, " uid=%jd", + (intmax_t)mtree->set.uid); + } + if ((keys & effkeys & F_GNAME) != 0) { + name = mtree->set.gid_list->m_entry->gname; + if (name != NULL) { + archive_strcat(&setstr, " gname="); + mtree_quote(&setstr, name); + } else { + keys &= ~F_GNAME; + if ((oldkeys & F_GNAME) != 0) + archive_strcat(&unsetstr, " gname"); + } + } + if ((keys & effkeys & F_GID) != 0) { + mtree->set.gid = mtree->set.gid_list->m_entry->gid; + archive_string_sprintf(&setstr, " gid=%jd", + (intmax_t)mtree->set.gid); + } + if ((keys & effkeys & F_MODE) != 0) { + mtree->set.mode = mtree->set.mode_list->m_entry->mode; + archive_string_sprintf(&setstr, " mode=%o", + (unsigned int)mtree->set.mode); + } + if ((keys & effkeys & F_FLAGS) != 0) { + name = mtree->set.flags_list->m_entry->fflags_text; + if (name != NULL) { + archive_strcat(&setstr, " flags="); + mtree_quote(&setstr, name); + mtree->set.fflags_set = + mtree->set.flags_list->m_entry->fflags_set; + mtree->set.fflags_clear = + mtree->set.flags_list->m_entry->fflags_clear; + } else { + keys &= ~F_FLAGS; + if ((oldkeys & F_FLAGS) != 0) + archive_strcat(&unsetstr, " flags"); + } + } + if (unsetstr.length > 0) + archive_string_sprintf(&mtree->buf, "/unset%s\n", unsetstr.s); + archive_string_free(&unsetstr); + if (setstr.length > 0) + archive_string_sprintf(&mtree->buf, "/set%s\n", setstr.s); + archive_string_free(&setstr); + mtree->set.keys = keys; + mtree->set.processed = 1; + + free_attr_count(&mtree->set.uid_list); + free_attr_count(&mtree->set.gid_list); + free_attr_count(&mtree->set.mode_list); + free_attr_count(&mtree->set.flags_list); +} + +static struct attr_counter * +new_attr_count(struct mtree_entry *me, struct attr_counter *prev) +{ + struct attr_counter *ac; + + ac = malloc(sizeof(*ac)); + if (ac != NULL) { + ac->prev = prev; + ac->next = NULL; + ac->count = 1; + ac->m_entry = me; + } + return (ac); +} + +static void +free_attr_count(struct attr_counter **top) +{ + struct attr_counter *ac, *tac; + + if (*top == NULL) + return; + ac = *top; + while (ac != NULL) { + tac = ac->next; + free(ac); + ac = tac; + } + *top = NULL; +} + +static int +inc_attr_count(struct attr_counter **top, struct attr_counter *ac, + struct attr_counter *last, struct mtree_entry *me) +{ + struct attr_counter *pac; + + if (ac != NULL) { + ac->count++; + if (*top == ac || ac->prev->count >= ac->count) + return (0); + for (pac = ac->prev; pac; pac = pac->prev) { + if (pac->count >= ac->count) + break; + } + ac->prev->next = ac->next; + if (ac->next != NULL) + ac->next->prev = ac->prev; + if (pac != NULL) { + ac->prev = pac; + ac->next = pac->next; + pac->next = ac; + if (ac->next != NULL) + ac->next->prev = ac; + } else { + ac->prev = NULL; + ac->next = *top; + *top = ac; + ac->next->prev = ac; + } + } else { + ac = new_attr_count(me, last); + if (ac == NULL) + return (-1); + last->next = ac; + } + return (0); +} + +static int +collect_set_values(struct mtree_writer *mtree, struct mtree_entry *me) +{ + int keys = mtree->keys; + struct attr_counter *ac, *last; + + if (keys & (F_UNAME | F_UID)) { + if (mtree->set.uid_list == NULL) { + mtree->set.uid_list = new_attr_count(me, NULL); + if (mtree->set.uid_list == NULL) + return (-1); + } else { + last = NULL; + for (ac = mtree->set.uid_list; ac; ac = ac->next) { + if (ac->m_entry->uid == me->uid) + break; + last = ac; + } + if (inc_attr_count( + &mtree->set.uid_list, ac, last, me) < 0) + return (-1); + } + } + if (keys & (F_GNAME | F_GID)) { + if (mtree->set.gid_list == NULL) { + mtree->set.gid_list = new_attr_count(me, NULL); + if (mtree->set.gid_list == NULL) + return (-1); + } else { + last = NULL; + for (ac = mtree->set.gid_list; ac; ac = ac->next) { + if (ac->m_entry->gid == me->gid) + break; + last = ac; + } + if (inc_attr_count( + &mtree->set.gid_list, ac, last, me) < 0) + return (-1); + } + } + if (keys & F_MODE) { + if (mtree->set.mode_list == NULL) { + mtree->set.mode_list = new_attr_count(me, NULL); + if (mtree->set.mode_list == NULL) + return (-1); + } else { + last = NULL; + for (ac = mtree->set.mode_list; ac; ac = ac->next) { + if (ac->m_entry->mode == me->mode) + break; + last = ac; + } + if (inc_attr_count( + &mtree->set.mode_list, ac, last, me) < 0) + return (-1); + } + } + if (keys & F_FLAGS) { + if (mtree->set.flags_list == NULL) { + mtree->set.flags_list = new_attr_count(me, NULL); + if (mtree->set.flags_list == NULL) + return (-1); + } else { + last = NULL; + for (ac = mtree->set.flags_list; ac; ac = ac->next) { + if (ac->m_entry->fflags_set == me->fflags_set && + ac->m_entry->fflags_clear == me->fflags_clear) + break; + last = ac; + } + if (inc_attr_count( + &mtree->set.flags_list, ac, last, me) < 0) + return (-1); + } + } + + /* + * Save a entry. + */ + me->next = NULL; + *mtree->set.me_last = me; + mtree->set.me_last = &me->next; + return (0); +} + +static int +get_keys(struct mtree_writer *mtree, struct mtree_entry *me) +{ + int keys; + + keys = mtree->keys; + + /* + * If a keyword has been set by /set, we do not need to + * output it. + */ + if (mtree->set.keys == 0) + return (keys);/* /set is not used. */ + + if ((mtree->set.keys & (F_GNAME | F_GID)) != 0 && + mtree->set.gid == me->gid) + keys &= ~(F_GNAME | F_GID); + if ((mtree->set.keys & (F_UNAME | F_UID)) != 0 && + mtree->set.uid == me->uid) + keys &= ~(F_UNAME | F_UID); + if (mtree->set.keys & F_FLAGS) { + if (mtree->set.fflags_set == me->fflags_set && + mtree->set.fflags_clear == me->fflags_clear) + keys &= ~F_FLAGS; + } + if ((mtree->set.keys & F_MODE) != 0 && mtree->set.mode == me->mode) + keys &= ~F_MODE; + + switch (me->filetype) { + case AE_IFLNK: case AE_IFSOCK: case AE_IFCHR: + case AE_IFBLK: case AE_IFIFO: + break; + case AE_IFDIR: + if ((mtree->set.keys & F_TYPE) != 0 && + mtree->set.type == AE_IFDIR) + keys &= ~F_TYPE; + break; + case AE_IFREG: + default: /* Handle unknown file types as regular files. */ + if ((mtree->set.keys & F_TYPE) != 0 && + mtree->set.type == AE_IFREG) + keys &= ~F_TYPE; + break; + } + + return (keys); +} + +static struct mtree_entry * +new_mtree_entry(struct archive_entry *entry) +{ + struct mtree_entry *me; + const char *s; + + me = calloc(1, sizeof(*me)); + if (me == NULL) + return (NULL); + me->pathname = strdup(archive_entry_pathname(entry)); + if ((s = archive_entry_symlink(entry)) != NULL) + me->symlink = strdup(s); + else + me->symlink = NULL; + me->nlink = archive_entry_nlink(entry); + me->filetype = archive_entry_filetype(entry); + me->mode = archive_entry_mode(entry) & 07777; + me->uid = archive_entry_uid(entry); + me->gid = archive_entry_gid(entry); + if ((s = archive_entry_uname(entry)) != NULL) + me->uname = strdup(s); + else + me->uname = NULL; + if ((s = archive_entry_gname(entry)) != NULL) + me->gname = strdup(s); + else + me->gname = NULL; + if ((s = archive_entry_fflags_text(entry)) != NULL) + me->fflags_text = strdup(s); + else + me->fflags_text = NULL; + archive_entry_fflags(entry, &me->fflags_set, &me->fflags_clear); + me->mtime = archive_entry_mtime(entry); + me->mtime_nsec = archive_entry_mtime_nsec(entry); + me->rdevmajor = archive_entry_rdevmajor(entry); + me->rdevminor = archive_entry_rdevminor(entry); + me->size = archive_entry_size(entry); + me->compute_sum = 0; + + return (me); +} + +static void +free_mtree_entry(struct mtree_entry *me) +{ + free(me->pathname); + free(me->symlink); + free(me->uname); + free(me->gname); + free(me->fflags_text); + free(me); +} + +static int +archive_write_mtree_header(struct archive_write *a, + struct archive_entry *entry) +{ + struct mtree_writer *mtree= a->format_data; + + if (mtree->first) { + mtree->first = 0; + archive_strcat(&mtree->buf, "#mtree\n"); + if ((mtree->keys & SET_KEYS) == 0) + mtree->set.output = 0;/* Disalbed. */ + } + + mtree->entry_bytes_remaining = archive_entry_size(entry); + if (mtree->dironly && archive_entry_filetype(entry) != AE_IFDIR) + return (ARCHIVE_OK); + + mtree->mtree_entry = new_mtree_entry(entry); + if (mtree->mtree_entry == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate mtree entry"); + return (ARCHIVE_FATAL); + } + + mtree->compute_sum = 0; + + /* If current file is not a regular file, we do not have to + * compute the sum of its content. */ + if (archive_entry_filetype(entry) != AE_IFREG) + return (ARCHIVE_OK); + + /* Initialize a bunch of sum check context. */ + sum_init(mtree); + + return (ARCHIVE_OK); +} + +static int +write_entry(struct archive_write *a, struct mtree_entry *me) +{ + struct mtree_writer *mtree = a->format_data; + struct archive_string *str; + int keys, ret; + + archive_string_empty(&mtree->ebuf); + str = (mtree->indent)? &mtree->ebuf : &mtree->buf; + mtree_quote(str, me->pathname); + keys = get_keys(mtree, me); + if ((keys & F_NLINK) != 0 && + me->nlink != 1 && me->filetype != AE_IFDIR) + archive_string_sprintf(str, " nlink=%u", me->nlink); + + if ((keys & F_GNAME) != 0 && me->gname != NULL) { + archive_strcat(str, " gname="); + mtree_quote(str, me->gname); + } + if ((keys & F_UNAME) != 0 && me->uname != NULL) { + archive_strcat(str, " uname="); + mtree_quote(str, me->uname); + } + if ((keys & F_FLAGS) != 0) { + if (me->fflags_text != NULL) { + archive_strcat(str, " flags="); + mtree_quote(str, me->fflags_text); + } else if (mtree->set.processed && + (mtree->set.keys & F_FLAGS) != 0) + /* Overwrite the global parameter. */ + archive_strcat(str, " flags=none"); + } + if ((keys & F_TIME) != 0) + archive_string_sprintf(str, " time=%jd.%jd", + (intmax_t)me->mtime, (intmax_t)me->mtime_nsec); + if ((keys & F_MODE) != 0) + archive_string_sprintf(str, " mode=%o", (unsigned int)me->mode); + if ((keys & F_GID) != 0) + archive_string_sprintf(str, " gid=%jd", (intmax_t)me->gid); + if ((keys & F_UID) != 0) + archive_string_sprintf(str, " uid=%jd", (intmax_t)me->uid); + + switch (me->filetype) { + case AE_IFLNK: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=link"); + if ((keys & F_SLINK) != 0) { + archive_strcat(str, " link="); + mtree_quote(str, me->symlink); + } + break; + case AE_IFSOCK: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=socket"); + break; + case AE_IFCHR: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=char"); + if ((keys & F_DEV) != 0) { + archive_string_sprintf(str, + " device=native,%ju,%ju", + (uintmax_t)me->rdevmajor, + (uintmax_t)me->rdevminor); + } + break; + case AE_IFBLK: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=block"); + if ((keys & F_DEV) != 0) { + archive_string_sprintf(str, + " device=native,%ju,%ju", + (uintmax_t)me->rdevmajor, + (uintmax_t)me->rdevminor); + } + break; + case AE_IFDIR: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=dir"); + break; + case AE_IFIFO: + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=fifo"); + break; + case AE_IFREG: + default: /* Handle unknown file types as regular files. */ + if ((keys & F_TYPE) != 0) + archive_strcat(str, " type=file"); + if ((keys & F_SIZE) != 0) + archive_string_sprintf(str, " size=%jd", + (intmax_t)me->size); + break; + } + + /* Write a bunch of sum. */ + if (me->filetype == AE_IFREG) + sum_write(str, me); + + archive_strcat(str, "\n"); + if (mtree->indent) + mtree_indent(mtree); + + if (mtree->buf.length > 32768) { + ret = __archive_write_output(a, mtree->buf.s, mtree->buf.length); + archive_string_empty(&mtree->buf); + } else + ret = ARCHIVE_OK; + return (ret); +} + +/* + * Write mtree entries saved at collect_set_values() function. + */ +static int +write_mtree_entries(struct archive_write *a) +{ + struct mtree_writer *mtree = a->format_data; + struct mtree_entry *me, *tme; + int ret; + + for (me = mtree->set.me_first; me; me = me->next) { + ret = write_entry(a, me); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + me = mtree->set.me_first; + while (me != NULL) { + tme = me->next; + free_mtree_entry(me); + me = tme; + } + mtree->set.me_first = NULL; + mtree->set.me_last = &mtree->set.me_first; + return (ARCHIVE_OK); +} + +static int +archive_write_mtree_finish_entry(struct archive_write *a) +{ + struct mtree_writer *mtree = a->format_data; + struct mtree_entry *me; + int ret; + + if ((me = mtree->mtree_entry) == NULL) + return (ARCHIVE_OK); + mtree->mtree_entry = NULL; + + if (me->filetype == AE_IFREG) + sum_final(mtree, me); + + if (mtree->set.output) { + if (!mtree->dironly) { + if (archive_strlen(&mtree->set.parent) == 0) + parent_dir_changed(&mtree->set.parent, me); + if (parent_dir_changed(&mtree->set.parent, me)) { + /* Write /set keyword */ + write_global(mtree); + /* Write entries saved by + * collect_set_values() function. */ + ret = write_mtree_entries(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + } + /* Tabulate uid,gid,mode and fflags of a entry + * in order to be used for /set. and, at this time + * we do not write a entry. */ + collect_set_values(mtree, me); + return (ARCHIVE_OK); + } else { + /* Write the current entry and free it. */ + ret = write_entry(a, me); + free_mtree_entry(me); + } + return (ret == ARCHIVE_OK ? ret : ARCHIVE_FATAL); +} + +static int +archive_write_mtree_close(struct archive_write *a) +{ + struct mtree_writer *mtree= a->format_data; + int ret; + + if (mtree->set.output && mtree->set.me_first != NULL) { + write_global(mtree); + ret = write_mtree_entries(a); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + archive_write_set_bytes_in_last_block(&a->archive, 1); + + return __archive_write_output(a, mtree->buf.s, mtree->buf.length); +} + +static ssize_t +archive_write_mtree_data(struct archive_write *a, const void *buff, size_t n) +{ + struct mtree_writer *mtree= a->format_data; + + if (n > mtree->entry_bytes_remaining) + n = mtree->entry_bytes_remaining; + mtree->entry_bytes_remaining -= n; + + /* We don't need to compute a regular file sum */ + if (mtree->mtree_entry == NULL) + return (n); + + if (mtree->mtree_entry->filetype == AE_IFREG) + sum_update(mtree, buff, n); + + return (n); +} + +static int +archive_write_mtree_free(struct archive_write *a) +{ + struct mtree_writer *mtree= a->format_data; + struct mtree_entry *me, *tme; + + if (mtree == NULL) + return (ARCHIVE_OK); + + /* Make sure we dot not leave any entries. */ + me = mtree->set.me_first; + while (me != NULL) { + tme = me->next; + free_mtree_entry(me); + me = tme; + } + archive_string_free(&mtree->ebuf); + archive_string_free(&mtree->buf); + archive_string_free(&mtree->set.parent); + free_attr_count(&mtree->set.uid_list); + free_attr_count(&mtree->set.gid_list); + free_attr_count(&mtree->set.mode_list); + free_attr_count(&mtree->set.flags_list); + free(mtree); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_mtree_options(struct archive_write *a, const char *key, + const char *value) +{ + struct mtree_writer *mtree= a->format_data; + int keybit = 0; + + switch (key[0]) { + case 'a': + if (strcmp(key, "all") == 0) + keybit = ~0; + break; + case 'c': + if (strcmp(key, "cksum") == 0) + keybit = F_CKSUM; + break; + case 'd': + if (strcmp(key, "device") == 0) + keybit = F_DEV; + else if (strcmp(key, "dironly") == 0) { + mtree->dironly = (value != NULL)? 1: 0; + return (ARCHIVE_OK); + } + break; + case 'f': + if (strcmp(key, "flags") == 0) + keybit = F_FLAGS; + break; + case 'g': + if (strcmp(key, "gid") == 0) + keybit = F_GID; + else if (strcmp(key, "gname") == 0) + keybit = F_GNAME; + break; + case 'i': + if (strcmp(key, "indent") == 0) { + mtree->indent = (value != NULL)? 1: 0; + return (ARCHIVE_OK); + } + break; + case 'l': + if (strcmp(key, "link") == 0) + keybit = F_SLINK; + break; + case 'm': + if (strcmp(key, "md5") == 0 || + strcmp(key, "md5digest") == 0) + keybit = F_MD5; + if (strcmp(key, "mode") == 0) + keybit = F_MODE; + break; + case 'n': + if (strcmp(key, "nlink") == 0) + keybit = F_NLINK; + break; + case 'r': + if (strcmp(key, "ripemd160digest") == 0 || + strcmp(key, "rmd160") == 0 || + strcmp(key, "rmd160digest") == 0) + keybit = F_RMD160; + break; + case 's': + if (strcmp(key, "sha1") == 0 || + strcmp(key, "sha1digest") == 0) + keybit = F_SHA1; + if (strcmp(key, "sha256") == 0 || + strcmp(key, "sha256digest") == 0) + keybit = F_SHA256; + if (strcmp(key, "sha384") == 0 || + strcmp(key, "sha384digest") == 0) + keybit = F_SHA384; + if (strcmp(key, "sha512") == 0 || + strcmp(key, "sha512digest") == 0) + keybit = F_SHA512; + if (strcmp(key, "size") == 0) + keybit = F_SIZE; + break; + case 't': + if (strcmp(key, "time") == 0) + keybit = F_TIME; + else if (strcmp(key, "type") == 0) + keybit = F_TYPE; + break; + case 'u': + if (strcmp(key, "uid") == 0) + keybit = F_UID; + else if (strcmp(key, "uname") == 0) + keybit = F_UNAME; + else if (strcmp(key, "use-set") == 0) { + mtree->set.output = (value != NULL)? 1: 0; + return (ARCHIVE_OK); + } + break; + } + if (keybit != 0) { + if (value != NULL) + mtree->keys |= keybit; + else + mtree->keys &= ~keybit; + return (ARCHIVE_OK); + } + + return (ARCHIVE_FAILED); +} + +int +archive_write_set_format_mtree(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct mtree_writer *mtree; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_mtree"); + + if (a->format_free != NULL) + (a->format_free)(a); + + if ((mtree = calloc(1, sizeof(*mtree))) == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate mtree data"); + return (ARCHIVE_FATAL); + } + + mtree->mtree_entry = NULL; + mtree->first = 1; + memset(&(mtree->set), 0, sizeof(mtree->set)); + archive_string_init(&mtree->set.parent); + mtree->keys = DEFAULT_KEYS; + mtree->dironly = 0; + mtree->indent = 0; + archive_string_init(&mtree->ebuf); + archive_string_init(&mtree->buf); + mtree->set.me_first = NULL; + mtree->set.me_last = &mtree->set.me_first; + a->format_data = mtree; + a->format_free = archive_write_mtree_free; + a->format_name = "mtree"; + a->format_options = archive_write_mtree_options; + a->format_write_header = archive_write_mtree_header; + a->format_close = archive_write_mtree_close; + a->format_write_data = archive_write_mtree_data; + a->format_finish_entry = archive_write_mtree_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_MTREE; + a->archive.archive_format_name = "mtree"; + + return (ARCHIVE_OK); +} + +static void +sum_init(struct mtree_writer *mtree) +{ + if (mtree->keys & F_CKSUM) { + mtree->compute_sum |= F_CKSUM; + mtree->crc = 0; + mtree->crc_len = 0; + } +#ifdef ARCHIVE_HAS_MD5 + if (mtree->keys & F_MD5) { + if (archive_md5_init(&mtree->md5ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_MD5; + else + mtree->keys &= ~F_MD5;/* Not supported. */ + } +#endif +#ifdef ARCHIVE_HAS_RMD160 + if (mtree->keys & F_RMD160) { + if (archive_rmd160_init(&mtree->rmd160ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_RMD160; + else + mtree->keys &= ~F_RMD160;/* Not supported. */ + } +#endif +#ifdef ARCHIVE_HAS_SHA1 + if (mtree->keys & F_SHA1) { + if (archive_sha1_init(&mtree->sha1ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_SHA1; + else + mtree->keys &= ~F_SHA1;/* Not supported. */ + } +#endif +#ifdef ARCHIVE_HAS_SHA256 + if (mtree->keys & F_SHA256) { + if (archive_sha256_init(&mtree->sha256ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_SHA256; + else + mtree->keys &= ~F_SHA256;/* Not supported. */ + } +#endif +#ifdef ARCHIVE_HAS_SHA384 + if (mtree->keys & F_SHA384) { + if (archive_sha384_init(&mtree->sha384ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_SHA384; + else + mtree->keys &= ~F_SHA384;/* Not supported. */ + } +#endif +#ifdef ARCHIVE_HAS_SHA512 + if (mtree->keys & F_SHA512) { + if (archive_sha512_init(&mtree->sha512ctx) == ARCHIVE_OK) + mtree->compute_sum |= F_SHA512; + else + mtree->keys &= ~F_SHA512;/* Not supported. */ + } +#endif +} + +static void +sum_update(struct mtree_writer *mtree, const void *buff, size_t n) +{ + if (mtree->compute_sum & F_CKSUM) { + /* + * Compute a POSIX 1003.2 checksum + */ + const unsigned char *p; + size_t nn; + + for (nn = n, p = buff; nn--; ++p) + COMPUTE_CRC(mtree->crc, *p); + mtree->crc_len += n; + } +#ifdef ARCHIVE_HAS_MD5 + if (mtree->compute_sum & F_MD5) + archive_md5_update(&mtree->md5ctx, buff, n); +#endif +#ifdef ARCHIVE_HAS_RMD160 + if (mtree->compute_sum & F_RMD160) + archive_rmd160_update(&mtree->rmd160ctx, buff, n); +#endif +#ifdef ARCHIVE_HAS_SHA1 + if (mtree->compute_sum & F_SHA1) + archive_sha1_update(&mtree->sha1ctx, buff, n); +#endif +#ifdef ARCHIVE_HAS_SHA256 + if (mtree->compute_sum & F_SHA256) + archive_sha256_update(&mtree->sha256ctx, buff, n); +#endif +#ifdef ARCHIVE_HAS_SHA384 + if (mtree->compute_sum & F_SHA384) + archive_sha384_update(&mtree->sha384ctx, buff, n); +#endif +#ifdef ARCHIVE_HAS_SHA512 + if (mtree->compute_sum & F_SHA512) + archive_sha512_update(&mtree->sha512ctx, buff, n); +#endif +} + +static void +sum_final(struct mtree_writer *mtree, struct mtree_entry *me) +{ + + if (mtree->compute_sum & F_CKSUM) { + uint64_t len; + /* Include the length of the file. */ + for (len = mtree->crc_len; len != 0; len >>= 8) + COMPUTE_CRC(mtree->crc, len & 0xff); + me->crc = ~mtree->crc; + } +#ifdef ARCHIVE_HAS_MD5 + if (mtree->compute_sum & F_MD5) + archive_md5_final(&mtree->md5ctx, me->buf_md5); +#endif +#ifdef ARCHIVE_HAS_RMD160 + if (mtree->compute_sum & F_RMD160) + archive_rmd160_final(&mtree->rmd160ctx, me->buf_rmd160); +#endif +#ifdef ARCHIVE_HAS_SHA1 + if (mtree->compute_sum & F_SHA1) + archive_sha1_final(&mtree->sha1ctx, me->buf_sha1); +#endif +#ifdef ARCHIVE_HAS_SHA256 + if (mtree->compute_sum & F_SHA256) + archive_sha256_final(&mtree->sha256ctx, me->buf_sha256); +#endif +#ifdef ARCHIVE_HAS_SHA384 + if (mtree->compute_sum & F_SHA384) + archive_sha384_final(&mtree->sha384ctx, me->buf_sha384); +#endif +#ifdef ARCHIVE_HAS_SHA512 + if (mtree->compute_sum & F_SHA512) + archive_sha512_final(&mtree->sha512ctx, me->buf_sha512); +#endif + /* Save what types of sum are computed. */ + me->compute_sum = mtree->compute_sum; +} + +#if defined(ARCHIVE_HAS_MD5) || defined(ARCHIVE_HAS_RMD160) || \ + defined(ARCHIVE_HAS_SHA1) || defined(ARCHIVE_HAS_SHA256) || \ + defined(ARCHIVE_HAS_SHA384) || defined(ARCHIVE_HAS_SHA512) +static void +strappend_bin(struct archive_string *s, const unsigned char *bin, int n) +{ + static const char hex[] = "0123456789abcdef"; + int i; + + for (i = 0; i < n; i++) { + archive_strappend_char(s, hex[bin[i] >> 4]); + archive_strappend_char(s, hex[bin[i] & 0x0f]); + } +} +#endif + +static void +sum_write(struct archive_string *str, struct mtree_entry *me) +{ + + if (me->compute_sum & F_CKSUM) { + archive_string_sprintf(str, " cksum=%ju", + (uintmax_t)me->crc); + } +#ifdef ARCHIVE_HAS_MD5 + if (me->compute_sum & F_MD5) { + archive_strcat(str, " md5digest="); + strappend_bin(str, me->buf_md5, sizeof(me->buf_md5)); + } +#endif +#ifdef ARCHIVE_HAS_RMD160 + if (me->compute_sum & F_RMD160) { + archive_strcat(str, " rmd160digest="); + strappend_bin(str, me->buf_rmd160, sizeof(me->buf_rmd160)); + } +#endif +#ifdef ARCHIVE_HAS_SHA1 + if (me->compute_sum & F_SHA1) { + archive_strcat(str, " sha1digest="); + strappend_bin(str, me->buf_sha1, sizeof(me->buf_sha1)); + } +#endif +#ifdef ARCHIVE_HAS_SHA256 + if (me->compute_sum & F_SHA256) { + archive_strcat(str, " sha256digest="); + strappend_bin(str, me->buf_sha256, sizeof(me->buf_sha256)); + } +#endif +#ifdef ARCHIVE_HAS_SHA384 + if (me->compute_sum & F_SHA384) { + archive_strcat(str, " sha384digest="); + strappend_bin(str, me->buf_sha384, sizeof(me->buf_sha384)); + } +#endif +#ifdef ARCHIVE_HAS_SHA512 + if (me->compute_sum & F_SHA512) { + archive_strcat(str, " sha512digest="); + strappend_bin(str, me->buf_sha512, sizeof(me->buf_sha512)); + } +#endif +} diff --git a/libarchive/archive_write_set_format_pax.c b/libarchive/archive_write_set_format_pax.c new file mode 100644 index 0000000..8affb2c --- /dev/null +++ b/libarchive/archive_write_set_format_pax.c @@ -0,0 +1,1833 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_pax.c 201162 2009-12-29 05:47:46Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct sparse_block { + struct sparse_block *next; + int is_hole; + uint64_t offset; + uint64_t remaining; +}; + +struct pax { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + struct archive_string l_url_encoded_name; + struct archive_string pax_header; + struct archive_string sparse_map; + size_t sparse_map_padding; + struct sparse_block *sparse_list; + struct sparse_block *sparse_tail; + struct archive_string_conv *sconv_utf8; + int opt_binary; +}; + +static void add_pax_attr(struct archive_string *, const char *key, + const char *value); +static void add_pax_attr_int(struct archive_string *, + const char *key, int64_t value); +static void add_pax_attr_time(struct archive_string *, + const char *key, int64_t sec, + unsigned long nanos); +static ssize_t archive_write_pax_data(struct archive_write *, + const void *, size_t); +static int archive_write_pax_close(struct archive_write *); +static int archive_write_pax_free(struct archive_write *); +static int archive_write_pax_finish_entry(struct archive_write *); +static int archive_write_pax_header(struct archive_write *, + struct archive_entry *); +static int archive_write_pax_options(struct archive_write *, + const char *, const char *); +static char *base64_encode(const char *src, size_t len); +static char *build_gnu_sparse_name(char *dest, const char *src); +static char *build_pax_attribute_name(char *dest, const char *src); +static char *build_ustar_entry_name(char *dest, const char *src, + size_t src_length, const char *insert); +static char *format_int(char *dest, int64_t); +static int has_non_ASCII(const char *); +static void sparse_list_clear(struct pax *); +static int sparse_list_add(struct pax *, int64_t, int64_t); +static char *url_encode(const char *in); + +/* + * Set output format to 'restricted pax' format. + * + * This is the same as normal 'pax', but tries to suppress + * the pax header whenever possible. This is the default for + * bsdtar, for instance. + */ +int +archive_write_set_format_pax_restricted(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + int r; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_pax_restricted"); + + r = archive_write_set_format_pax(&a->archive); + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_RESTRICTED; + a->archive.archive_format_name = "restricted POSIX pax interchange"; + return (r); +} + +/* + * Set output format to 'pax' format. + */ +int +archive_write_set_format_pax(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct pax *pax; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_pax"); + + if (a->format_free != NULL) + (a->format_free)(a); + + pax = (struct pax *)malloc(sizeof(*pax)); + if (pax == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate pax data"); + return (ARCHIVE_FATAL); + } + memset(pax, 0, sizeof(*pax)); + a->format_data = pax; + a->format_name = "pax"; + a->format_options = archive_write_pax_options; + a->format_write_header = archive_write_pax_header; + a->format_write_data = archive_write_pax_data; + a->format_close = archive_write_pax_close; + a->format_free = archive_write_pax_free; + a->format_finish_entry = archive_write_pax_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE; + a->archive.archive_format_name = "POSIX pax interchange"; + return (ARCHIVE_OK); +} + +static int +archive_write_pax_options(struct archive_write *a, const char *key, + const char *val) +{ + struct pax *pax = (struct pax *)a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "hdrcharset") == 0) { + /* + * The character-set we can use are defined in + * IEEE Std 1003.1-2001 + */ + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "pax: hdrcharset option needs a character-set name"); + else if (strcmp(val, "BINARY") == 0 || + strcmp(val, "binary") == 0) { + /* + * Specify binary mode. We will not convert + * filenames, uname and gname to any charsets. + */ + pax->opt_binary = 1; + ret = ARCHIVE_OK; + } else if (strcmp(val, "UTF-8") == 0) { + /* + * Specify UTF-8 character-set to be used for + * filenames. This is almost the test that + * running platform supports the string conversion. + * Especially libarchive_test needs this trick for + * its test. + */ + pax->sconv_utf8 = archive_string_conversion_to_charset( + &(a->archive), "UTF-8", 0); + if (pax->sconv_utf8 == NULL) + ret = ARCHIVE_FATAL; + else + ret = ARCHIVE_OK; + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "pax: invalid charset name"); + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "pax: unknown keyword ``%s''", key); + + return (ret); +} + +/* + * Note: This code assumes that 'nanos' has the same sign as 'sec', + * which implies that sec=-1, nanos=200000000 represents -1.2 seconds + * and not -0.8 seconds. This is a pretty pedantic point, as we're + * unlikely to encounter many real files created before Jan 1, 1970, + * much less ones with timestamps recorded to sub-second resolution. + */ +static void +add_pax_attr_time(struct archive_string *as, const char *key, + int64_t sec, unsigned long nanos) +{ + int digit, i; + char *t; + /* + * Note that each byte contributes fewer than 3 base-10 + * digits, so this will always be big enough. + */ + char tmp[1 + 3*sizeof(sec) + 1 + 3*sizeof(nanos)]; + + tmp[sizeof(tmp) - 1] = 0; + t = tmp + sizeof(tmp) - 1; + + /* Skip trailing zeros in the fractional part. */ + for (digit = 0, i = 10; i > 0 && digit == 0; i--) { + digit = nanos % 10; + nanos /= 10; + } + + /* Only format the fraction if it's non-zero. */ + if (i > 0) { + while (i > 0) { + *--t = "0123456789"[digit]; + digit = nanos % 10; + nanos /= 10; + i--; + } + *--t = '.'; + } + t = format_int(t, sec); + + add_pax_attr(as, key, t); +} + +static char * +format_int(char *t, int64_t i) +{ + uint64_t ui; + + if (i < 0) + ui = (i == INT64_MIN) ? (uint64_t)(INT64_MAX) + 1 : (uint64_t)(-i); + else + ui = i; + + do { + *--t = "0123456789"[ui % 10]; + } while (ui /= 10); + if (i < 0) + *--t = '-'; + return (t); +} + +static void +add_pax_attr_int(struct archive_string *as, const char *key, int64_t value) +{ + char tmp[1 + 3 * sizeof(value)]; + + tmp[sizeof(tmp) - 1] = 0; + add_pax_attr(as, key, format_int(tmp + sizeof(tmp) - 1, value)); +} + +/* + * Add a key/value attribute to the pax header. This function handles + * the length field and various other syntactic requirements. + */ +static void +add_pax_attr(struct archive_string *as, const char *key, const char *value) +{ + int digits, i, len, next_ten; + char tmp[1 + 3 * sizeof(int)]; /* < 3 base-10 digits per byte */ + + /*- + * PAX attributes have the following layout: + * <=> + */ + len = 1 + (int)strlen(key) + 1 + (int)strlen(value) + 1; + + /* + * The field includes the length of the field, so + * computing the correct length is tricky. I start by + * counting the number of base-10 digits in 'len' and + * computing the next higher power of 10. + */ + next_ten = 1; + digits = 0; + i = len; + while (i > 0) { + i = i / 10; + digits++; + next_ten = next_ten * 10; + } + /* + * For example, if string without the length field is 99 + * chars, then adding the 2 digit length "99" will force the + * total length past 100, requiring an extra digit. The next + * statement adjusts for this effect. + */ + if (len + digits >= next_ten) + digits++; + + /* Now, we have the right length so we can build the line. */ + tmp[sizeof(tmp) - 1] = 0; /* Null-terminate the work area. */ + archive_strcat(as, format_int(tmp + sizeof(tmp) - 1, len + digits)); + archive_strappend_char(as, ' '); + archive_strcat(as, key); + archive_strappend_char(as, '='); + archive_strcat(as, value); + archive_strappend_char(as, '\n'); +} + +static int +archive_write_pax_header_xattrs(struct archive_write *a, + struct pax *pax, struct archive_entry *entry) +{ + struct archive_string s; + int i = archive_entry_xattr_reset(entry); + + while (i--) { + const char *name; + const void *value; + char *encoded_value; + char *url_encoded_name = NULL, *encoded_name = NULL; + size_t size; + int r; + + archive_entry_xattr_next(entry, &name, &value, &size); + url_encoded_name = url_encode(name); + if (url_encoded_name != NULL) { + /* Convert narrow-character to UTF-8. */ + r = archive_strcpy_in_locale( + &(pax->l_url_encoded_name), + url_encoded_name, pax->sconv_utf8); + free(url_encoded_name); /* Done with this. */ + if (r == 0) + encoded_name = pax->l_url_encoded_name.s; + else if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + } + + encoded_value = base64_encode((const char *)value, size); + + if (encoded_name != NULL && encoded_value != NULL) { + archive_string_init(&s); + archive_strcpy(&s, "LIBARCHIVE.xattr."); + archive_strcat(&s, encoded_name); + add_pax_attr(&(pax->pax_header), s.s, encoded_value); + archive_string_free(&s); + } + free(encoded_value); + } + return (ARCHIVE_OK); +} + +static int +get_entry_hardlink(struct archive_write *a, struct archive_entry *entry, + const char **name, size_t *length, struct archive_string_conv *sc) +{ + int r; + + r = archive_entry_hardlink_l(entry, name, length, sc); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +get_entry_pathname(struct archive_write *a, struct archive_entry *entry, + const char **name, size_t *length, struct archive_string_conv *sc) +{ + int r; + + r = archive_entry_pathname_l(entry, name, length, sc); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +get_entry_uname(struct archive_write *a, struct archive_entry *entry, + const char **name, size_t *length, struct archive_string_conv *sc) +{ + int r; + + r = archive_entry_uname_l(entry, name, length, sc); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Uname"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +get_entry_gname(struct archive_write *a, struct archive_entry *entry, + const char **name, size_t *length, struct archive_string_conv *sc) +{ + int r; + + r = archive_entry_gname_l(entry, name, length, sc); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Gname"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +static int +get_entry_symlink(struct archive_write *a, struct archive_entry *entry, + const char **name, size_t *length, struct archive_string_conv *sc) +{ + int r; + + r = archive_entry_symlink_l(entry, name, length, sc); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_WARN); + } + return (ARCHIVE_OK); +} + +/* + * TODO: Consider adding 'comment' and 'charset' fields to + * archive_entry so that clients can specify them. Also, consider + * adding generic key/value tags so clients can add arbitrary + * key/value data. + * + * TODO: Break up this 700-line function!!!! Yowza! + */ +static int +archive_write_pax_header(struct archive_write *a, + struct archive_entry *entry_original) +{ + struct archive_entry *entry_main; + const char *p; + char *t; + const char *suffix; + int need_extension, r, ret; + int sparse_count; + uint64_t sparse_total, real_size; + struct pax *pax; + const char *hardlink; + const char *path = NULL, *linkpath = NULL; + const char *uname = NULL, *gname = NULL; + const void *mac_metadata; + size_t mac_metadata_size; + struct archive_string_conv *sconv; + size_t hardlink_length, path_length, linkpath_length; + size_t uname_length, gname_length; + + char paxbuff[512]; + char ustarbuff[512]; + char ustar_entry_name[256]; + char pax_entry_name[256]; + char gnu_sparse_name[256]; + struct archive_string entry_name; + + ret = ARCHIVE_OK; + need_extension = 0; + pax = (struct pax *)a->format_data; + + /* Sanity check. */ + if (archive_entry_pathname(entry_original) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't record entry in tar file without pathname"); + return (ARCHIVE_FAILED); + } + + /* + * Choose a header encoding. + */ + if (pax->opt_binary) + sconv = NULL;/* Binary mode. */ + else { + /* Header encoding is UTF-8. */ + if (pax->sconv_utf8 == NULL) { + /* Initialize the string conversion object + * we must need */ + pax->sconv_utf8 = archive_string_conversion_to_charset( + &(a->archive), "UTF-8", 1); + if (pax->sconv_utf8 == NULL) + /* Couldn't allocate memory */ + return (ARCHIVE_FAILED); + } + sconv = pax->sconv_utf8; + } + + r = get_entry_hardlink(a, entry_original, &hardlink, + &hardlink_length, sconv); + if (r == ARCHIVE_FATAL) + return (r); + else if (r != ARCHIVE_OK) { + r = get_entry_hardlink(a, entry_original, &hardlink, + &hardlink_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", hardlink, + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + sconv = NULL;/* The header charset switches to binary mode. */ + } + + /* Make sure this is a type of entry that we can handle here */ + if (hardlink == NULL) { + switch (archive_entry_filetype(entry_original)) { + case AE_IFBLK: + case AE_IFCHR: + case AE_IFIFO: + case AE_IFLNK: + case AE_IFREG: + break; + case AE_IFDIR: + /* + * Ensure a trailing '/'. Modify the original + * entry so the client sees the change. + */ + p = archive_entry_pathname(entry_original); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate pax data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry_original, t); + free(t); + } + break; + case AE_IFSOCK: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive socket"); + return (ARCHIVE_FAILED); + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (type=0%lo)", + (unsigned long) + archive_entry_filetype(entry_original)); + return (ARCHIVE_FAILED); + } + } + + /* + * If Mac OS metadata blob is here, recurse to write that + * as a separate entry. This is realy a pretty poor design: + * In particular, it doubles the overhead for long filenames. + * TODO: Help Apple folks design something better and figure + * out how to transition from this legacy format. + * + * Note that this code is present on every platform; clients + * on non-Mac are unlikely to ever provide this data, but + * applications that copy entries from one archive to another + * should not lose data just because the local filesystem + * can't store it. + */ + mac_metadata = + archive_entry_mac_metadata(entry_original, &mac_metadata_size); + if (mac_metadata != NULL) { + const char *oname; + char *name, *bname; + size_t name_length; + struct archive_entry *extra = archive_entry_new2(&a->archive); + + oname = archive_entry_pathname(entry_original); + name_length = strlen(oname); + name = malloc(name_length + 3); + if (name == NULL) { + /* XXX error message */ + return (ARCHIVE_FAILED); + } + strcpy(name, oname); + /* Find last '/'; strip trailing '/' characters */ + bname = strrchr(name, '/'); + while (bname != NULL && bname[1] == '\0') { + *bname = '\0'; + bname = strrchr(name, '/'); + } + if (bname == NULL) { + memmove(name + 2, name, name_length + 1); + memmove(name, "._", 2); + } else { + bname += 1; + memmove(bname + 2, bname, strlen(bname) + 1); + memmove(bname, "._", 2); + } + archive_entry_copy_pathname(extra, name); + free(name); + + archive_entry_set_size(extra, mac_metadata_size); + archive_entry_set_filetype(extra, AE_IFREG); + archive_entry_set_perm(extra, + archive_entry_perm(entry_original)); + archive_entry_set_mtime(extra, + archive_entry_mtime(entry_original), + archive_entry_mtime_nsec(entry_original)); + archive_entry_set_gid(extra, + archive_entry_gid(entry_original)); + archive_entry_set_gname(extra, + archive_entry_gname(entry_original)); + archive_entry_set_uid(extra, + archive_entry_uid(entry_original)); + archive_entry_set_uname(extra, + archive_entry_uname(entry_original)); + + /* Recurse to write the special copyfile entry. */ + r = archive_write_pax_header(a, extra); + if (r < ARCHIVE_WARN) + return (r); + if (r < ret) + ret = r; + r = archive_write_pax_data(a, mac_metadata, mac_metadata_size); + if (r < ARCHIVE_WARN) + return (r); + if (r < ret) + ret = r; + r = archive_write_pax_finish_entry(a); + if (r < ARCHIVE_WARN) + return (r); + if (r < ret) + ret = r; + } + + /* Copy entry so we can modify it as needed. */ + entry_main = archive_entry_clone(entry_original); + archive_string_empty(&(pax->pax_header)); /* Blank our work area. */ + archive_string_empty(&(pax->sparse_map)); + sparse_total = 0; + sparse_list_clear(pax); + + if (hardlink == NULL && + archive_entry_filetype(entry_main) == AE_IFREG) + sparse_count = archive_entry_sparse_reset(entry_main); + else + sparse_count = 0; + if (sparse_count) { + int64_t offset, length, last_offset = 0; + /* Get the last entry of sparse block. */ + while (archive_entry_sparse_next( + entry_main, &offset, &length) == ARCHIVE_OK) + last_offset = offset + length; + + /* If the last sparse block does not reach the end of file, + * We have to add a empty sparse block as the last entry to + * manage storing file data. */ + if (last_offset < archive_entry_size(entry_main)) + archive_entry_sparse_add_entry(entry_main, + archive_entry_size(entry_main), 0); + sparse_count = archive_entry_sparse_reset(entry_main); + } + + /* + * First, check the name fields and see if any of them + * require binary coding. If any of them does, then all of + * them do. + */ + r = get_entry_pathname(a, entry_main, &path, &path_length, sconv); + if (r == ARCHIVE_FATAL) + return (r); + else if (r != ARCHIVE_OK) { + r = get_entry_pathname(a, entry_main, &path, + &path_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", path, + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + sconv = NULL;/* The header charset switches to binary mode. */ + } + r = get_entry_uname(a, entry_main, &uname, &uname_length, sconv); + if (r == ARCHIVE_FATAL) + return (r); + else if (r != ARCHIVE_OK) { + r = get_entry_uname(a, entry_main, &uname, &uname_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate uname '%s' to %s", uname, + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + sconv = NULL;/* The header charset switches to binary mode. */ + } + r = get_entry_gname(a, entry_main, &gname, &gname_length, sconv); + if (r == ARCHIVE_FATAL) + return (r); + else if (r != ARCHIVE_OK) { + r = get_entry_gname(a, entry_main, &gname, &gname_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate gname '%s' to %s", gname, + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + sconv = NULL;/* The header charset switches to binary mode. */ + } + linkpath = hardlink; + linkpath_length = hardlink_length; + if (linkpath == NULL) { + r = get_entry_symlink(a, entry_main, &linkpath, + &linkpath_length, sconv); + if (r == ARCHIVE_FATAL) + return (r); + else if (r != ARCHIVE_OK) { + r = get_entry_symlink(a, entry_main, &linkpath, + &linkpath_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", linkpath, + archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + sconv = NULL; + } + } + + /* If any string conversions failed, get all attributes + * in binary-mode. */ + if (sconv == NULL && !pax->opt_binary) { + if (hardlink != NULL) { + r = get_entry_hardlink(a, entry_main, &hardlink, + &hardlink_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + linkpath = hardlink; + linkpath_length = hardlink_length; + } + r = get_entry_pathname(a, entry_main, &path, + &path_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + r = get_entry_uname(a, entry_main, &uname, &uname_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + r = get_entry_gname(a, entry_main, &gname, &gname_length, NULL); + if (r == ARCHIVE_FATAL) + return (r); + } + + /* Store the header encoding first, to be nice to readers. */ + if (sconv == NULL) + add_pax_attr(&(pax->pax_header), "hdrcharset", "BINARY"); + + + /* + * If name is too long, or has non-ASCII characters, add + * 'path' to pax extended attrs. (Note that an unconvertible + * name must have non-ASCII characters.) + */ + if (has_non_ASCII(path)) { + /* We have non-ASCII characters. */ + add_pax_attr(&(pax->pax_header), "path", path); + archive_entry_set_pathname(entry_main, + build_ustar_entry_name(ustar_entry_name, + path, path_length, NULL)); + need_extension = 1; + } else { + /* We have an all-ASCII path; we'd like to just store + * it in the ustar header if it will fit. Yes, this + * duplicates some of the logic in + * archive_write_set_format_ustar.c + */ + if (path_length <= 100) { + /* Fits in the old 100-char tar name field. */ + } else { + /* Find largest suffix that will fit. */ + /* Note: strlen() > 100, so strlen() - 100 - 1 >= 0 */ + suffix = strchr(path + path_length - 100 - 1, '/'); + /* Don't attempt an empty prefix. */ + if (suffix == path) + suffix = strchr(suffix + 1, '/'); + /* We can put it in the ustar header if it's + * all ASCII and it's either <= 100 characters + * or can be split at a '/' into a prefix <= + * 155 chars and a suffix <= 100 chars. (Note + * the strchr() above will return NULL exactly + * when the path can't be split.) + */ + if (suffix == NULL /* Suffix > 100 chars. */ + || suffix[1] == '\0' /* empty suffix */ + || suffix - path > 155) /* Prefix > 155 chars */ + { + add_pax_attr(&(pax->pax_header), "path", path); + archive_entry_set_pathname(entry_main, + build_ustar_entry_name(ustar_entry_name, + path, path_length, NULL)); + need_extension = 1; + } + } + } + + if (linkpath != NULL) { + /* If link name is too long or has non-ASCII characters, add + * 'linkpath' to pax extended attrs. */ + if (linkpath_length > 100 || has_non_ASCII(linkpath)) { + add_pax_attr(&(pax->pax_header), "linkpath", linkpath); + if (linkpath_length > 100) { + if (hardlink != NULL) + archive_entry_set_hardlink(entry_main, + "././@LongHardLink"); + else + archive_entry_set_symlink(entry_main, + "././@LongSymLink"); + } + need_extension = 1; + } + } + /* Save a pathname since it will be renamed if `entry_main` has + * sparse blocks. */ + archive_string_init(&entry_name); + archive_strcpy(&entry_name, archive_entry_pathname(entry_main)); + + /* If file size is too large, add 'size' to pax extended attrs. */ + if (archive_entry_size(entry_main) >= (((int64_t)1) << 33)) { + add_pax_attr_int(&(pax->pax_header), "size", + archive_entry_size(entry_main)); + need_extension = 1; + } + + /* If numeric GID is too large, add 'gid' to pax extended attrs. */ + if ((unsigned int)archive_entry_gid(entry_main) >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "gid", + archive_entry_gid(entry_main)); + need_extension = 1; + } + + /* If group name is too large or has non-ASCII characters, add + * 'gname' to pax extended attrs. */ + if (gname != NULL) { + if (gname_length > 31 || has_non_ASCII(gname)) { + add_pax_attr(&(pax->pax_header), "gname", gname); + need_extension = 1; + } + } + + /* If numeric UID is too large, add 'uid' to pax extended attrs. */ + if ((unsigned int)archive_entry_uid(entry_main) >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "uid", + archive_entry_uid(entry_main)); + need_extension = 1; + } + + /* Add 'uname' to pax extended attrs if necessary. */ + if (uname != NULL) { + if (uname_length > 31 || has_non_ASCII(uname)) { + add_pax_attr(&(pax->pax_header), "uname", uname); + need_extension = 1; + } + } + + /* + * POSIX/SUSv3 doesn't provide a standard key for large device + * numbers. I use the same keys here that Joerg Schilling + * used for 'star.' (Which, somewhat confusingly, are called + * "devXXX" even though they code "rdev" values.) No doubt, + * other implementations use other keys. Note that there's no + * reason we can't write the same information into a number of + * different keys. + * + * Of course, this is only needed for block or char device entries. + */ + if (archive_entry_filetype(entry_main) == AE_IFBLK + || archive_entry_filetype(entry_main) == AE_IFCHR) { + /* + * If rdevmajor is too large, add 'SCHILY.devmajor' to + * extended attributes. + */ + int rdevmajor, rdevminor; + rdevmajor = archive_entry_rdevmajor(entry_main); + rdevminor = archive_entry_rdevminor(entry_main); + if (rdevmajor >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "SCHILY.devmajor", + rdevmajor); + /* + * Non-strict formatting below means we don't + * have to truncate here. Not truncating improves + * the chance that some more modern tar archivers + * (such as GNU tar 1.13) can restore the full + * value even if they don't understand the pax + * extended attributes. See my rant below about + * file size fields for additional details. + */ + /* archive_entry_set_rdevmajor(entry_main, + rdevmajor & ((1 << 18) - 1)); */ + need_extension = 1; + } + + /* + * If devminor is too large, add 'SCHILY.devminor' to + * extended attributes. + */ + if (rdevminor >= (1 << 18)) { + add_pax_attr_int(&(pax->pax_header), "SCHILY.devminor", + rdevminor); + /* Truncation is not necessary here, either. */ + /* archive_entry_set_rdevminor(entry_main, + rdevminor & ((1 << 18) - 1)); */ + need_extension = 1; + } + } + + /* + * Technically, the mtime field in the ustar header can + * support 33 bits, but many platforms use signed 32-bit time + * values. The cutoff of 0x7fffffff here is a compromise. + * Yes, this check is duplicated just below; this helps to + * avoid writing an mtime attribute just to handle a + * high-resolution timestamp in "restricted pax" mode. + */ + if (!need_extension && + ((archive_entry_mtime(entry_main) < 0) + || (archive_entry_mtime(entry_main) >= 0x7fffffff))) + need_extension = 1; + + /* I use a star-compatible file flag attribute. */ + p = archive_entry_fflags_text(entry_main); + if (!need_extension && p != NULL && *p != '\0') + need_extension = 1; + + /* If there are non-trivial ACL entries, we need an extension. */ + if (!need_extension && archive_entry_acl_count(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS) > 0) + need_extension = 1; + + /* If there are non-trivial ACL entries, we need an extension. */ + if (!need_extension && archive_entry_acl_count(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) > 0) + need_extension = 1; + + /* If there are extended attributes, we need an extension */ + if (!need_extension && archive_entry_xattr_count(entry_original) > 0) + need_extension = 1; + + /* If there are sparse info, we need an extension */ + if (!need_extension && sparse_count > 0) + need_extension = 1; + + /* + * The following items are handled differently in "pax + * restricted" format. In particular, in "pax restricted" + * format they won't be added unless need_extension is + * already set (we're already generating an extended header, so + * may as well include these). + */ + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_RESTRICTED || + need_extension) { + + if (archive_entry_mtime(entry_main) < 0 || + archive_entry_mtime(entry_main) >= 0x7fffffff || + archive_entry_mtime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "mtime", + archive_entry_mtime(entry_main), + archive_entry_mtime_nsec(entry_main)); + + if (archive_entry_ctime(entry_main) != 0 || + archive_entry_ctime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "ctime", + archive_entry_ctime(entry_main), + archive_entry_ctime_nsec(entry_main)); + + if (archive_entry_atime(entry_main) != 0 || + archive_entry_atime_nsec(entry_main) != 0) + add_pax_attr_time(&(pax->pax_header), "atime", + archive_entry_atime(entry_main), + archive_entry_atime_nsec(entry_main)); + + /* Store birth/creationtime only if it's earlier than mtime */ + if (archive_entry_birthtime_is_set(entry_main) && + archive_entry_birthtime(entry_main) + < archive_entry_mtime(entry_main)) + add_pax_attr_time(&(pax->pax_header), + "LIBARCHIVE.creationtime", + archive_entry_birthtime(entry_main), + archive_entry_birthtime_nsec(entry_main)); + + /* I use a star-compatible file flag attribute. */ + p = archive_entry_fflags_text(entry_main); + if (p != NULL && *p != '\0') + add_pax_attr(&(pax->pax_header), "SCHILY.fflags", p); + + /* I use star-compatible ACL attributes. */ + r = archive_entry_acl_text_l(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_ACCESS | + ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID, + &p, NULL, pax->sconv_utf8); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for " + "ACL.access"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate ACL.access to UTF-8"); + ret = ARCHIVE_WARN; + } else if (p != NULL && *p != '\0') { + add_pax_attr(&(pax->pax_header), + "SCHILY.acl.access", p); + } + r = archive_entry_acl_text_l(entry_original, + ARCHIVE_ENTRY_ACL_TYPE_DEFAULT | + ARCHIVE_ENTRY_ACL_STYLE_EXTRA_ID, + &p, NULL, pax->sconv_utf8); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for " + "ACL.default"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate ACL.default to UTF-8"); + ret = ARCHIVE_WARN; + } else if (p != NULL && *p != '\0') { + add_pax_attr(&(pax->pax_header), + "SCHILY.acl.default", p); + } + + /* We use GNU-tar-compatible sparse attributes. */ + if (sparse_count > 0) { + int64_t soffset, slength; + + add_pax_attr_int(&(pax->pax_header), + "GNU.sparse.major", 1); + add_pax_attr_int(&(pax->pax_header), + "GNU.sparse.minor", 0); + add_pax_attr(&(pax->pax_header), + "GNU.sparse.name", entry_name.s); + add_pax_attr_int(&(pax->pax_header), + "GNU.sparse.realsize", + archive_entry_size(entry_main)); + + /* Rename the file name which will be used for + * ustar header to a special name, which GNU + * PAX Format 1.0 requires */ + archive_entry_set_pathname(entry_main, + build_gnu_sparse_name(gnu_sparse_name, + entry_name.s)); + + /* + * - Make a sparse map, which will precede a file data. + * - Get the total size of available data of sparse. + */ + archive_string_sprintf(&(pax->sparse_map), "%d\n", + sparse_count); + while (archive_entry_sparse_next(entry_main, + &soffset, &slength) == ARCHIVE_OK) { + archive_string_sprintf(&(pax->sparse_map), + "%jd\n%jd\n", + (intmax_t)soffset, + (intmax_t)slength); + sparse_total += slength; + if (sparse_list_add(pax, soffset, slength) + != ARCHIVE_OK) { + archive_set_error(&a->archive, + ENOMEM, + "Can't allocate memory"); + archive_entry_free(entry_main); + archive_string_free(&entry_name); + return (ARCHIVE_FATAL); + } + } + } + + /* Store extended attributes */ + if (archive_write_pax_header_xattrs(a, pax, entry_original) + == ARCHIVE_FATAL) { + archive_entry_free(entry_main); + archive_string_free(&entry_name); + return (ARCHIVE_FATAL); + } + } + + /* Only regular files have data. */ + if (archive_entry_filetype(entry_main) != AE_IFREG) + archive_entry_set_size(entry_main, 0); + + /* + * Pax-restricted does not store data for hardlinks, in order + * to improve compatibility with ustar. + */ + if (a->archive.archive_format != ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE && + hardlink != NULL) + archive_entry_set_size(entry_main, 0); + + /* + * XXX Full pax interchange format does permit a hardlink + * entry to have data associated with it. I'm not supporting + * that here because the client expects me to tell them whether + * or not this format expects data for hardlinks. If I + * don't check here, then every pax archive will end up with + * duplicated data for hardlinks. Someday, there may be + * need to select this behavior, in which case the following + * will need to be revisited. XXX + */ + if (hardlink != NULL) + archive_entry_set_size(entry_main, 0); + + /* Save a real file size. */ + real_size = archive_entry_size(entry_main); + /* + * Overwrite a file size by the total size of sparse blocks and + * the size of sparse map info. That file size is the length of + * the data, which we will exactly store into an archive file. + */ + if (archive_strlen(&(pax->sparse_map))) { + size_t mapsize = archive_strlen(&(pax->sparse_map)); + pax->sparse_map_padding = 0x1ff & (-(ssize_t)mapsize); + archive_entry_set_size(entry_main, + mapsize + pax->sparse_map_padding + sparse_total); + } + + /* Format 'ustar' header for main entry. + * + * The trouble with file size: If the reader can't understand + * the file size, they may not be able to locate the next + * entry and the rest of the archive is toast. Pax-compliant + * readers are supposed to ignore the file size in the main + * header, so the question becomes how to maximize portability + * for readers that don't support pax attribute extensions. + * For maximum compatibility, I permit numeric extensions in + * the main header so that the file size stored will always be + * correct, even if it's in a format that only some + * implementations understand. The technique used here is: + * + * a) If possible, follow the standard exactly. This handles + * files up to 8 gigabytes minus 1. + * + * b) If that fails, try octal but omit the field terminator. + * That handles files up to 64 gigabytes minus 1. + * + * c) Otherwise, use base-256 extensions. That handles files + * up to 2^63 in this implementation, with the potential to + * go up to 2^94. That should hold us for a while. ;-) + * + * The non-strict formatter uses similar logic for other + * numeric fields, though they're less critical. + */ + if (__archive_write_format_header_ustar(a, ustarbuff, entry_main, -1, 0, + NULL) == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + /* If we built any extended attributes, write that entry first. */ + if (archive_strlen(&(pax->pax_header)) > 0) { + struct archive_entry *pax_attr_entry; + time_t s; + int64_t uid, gid; + int mode; + + pax_attr_entry = archive_entry_new2(&a->archive); + p = entry_name.s; + archive_entry_set_pathname(pax_attr_entry, + build_pax_attribute_name(pax_entry_name, p)); + archive_entry_set_size(pax_attr_entry, + archive_strlen(&(pax->pax_header))); + /* Copy uid/gid (but clip to ustar limits). */ + uid = archive_entry_uid(entry_main); + if (uid >= 1 << 18) + uid = (1 << 18) - 1; + archive_entry_set_uid(pax_attr_entry, uid); + gid = archive_entry_gid(entry_main); + if (gid >= 1 << 18) + gid = (1 << 18) - 1; + archive_entry_set_gid(pax_attr_entry, gid); + /* Copy mode over (but not setuid/setgid bits) */ + mode = archive_entry_mode(entry_main); +#ifdef S_ISUID + mode &= ~S_ISUID; +#endif +#ifdef S_ISGID + mode &= ~S_ISGID; +#endif +#ifdef S_ISVTX + mode &= ~S_ISVTX; +#endif + archive_entry_set_mode(pax_attr_entry, mode); + + /* Copy uname/gname. */ + archive_entry_set_uname(pax_attr_entry, + archive_entry_uname(entry_main)); + archive_entry_set_gname(pax_attr_entry, + archive_entry_gname(entry_main)); + + /* Copy mtime, but clip to ustar limits. */ + s = archive_entry_mtime(entry_main); + if (s < 0) { s = 0; } + if (s >= 0x7fffffff) { s = 0x7fffffff; } + archive_entry_set_mtime(pax_attr_entry, s, 0); + + /* Standard ustar doesn't support atime. */ + archive_entry_set_atime(pax_attr_entry, 0, 0); + + /* Standard ustar doesn't support ctime. */ + archive_entry_set_ctime(pax_attr_entry, 0, 0); + + r = __archive_write_format_header_ustar(a, paxbuff, + pax_attr_entry, 'x', 1, NULL); + + archive_entry_free(pax_attr_entry); + + /* Note that the 'x' header shouldn't ever fail to format */ + if (r < ARCHIVE_WARN) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "archive_write_pax_header: " + "'x' header failed?! This can't happen.\n"); + return (ARCHIVE_FATAL); + } else if (r < ret) + ret = r; + r = __archive_write_output(a, paxbuff, 512); + if (r != ARCHIVE_OK) { + sparse_list_clear(pax); + pax->entry_bytes_remaining = 0; + pax->entry_padding = 0; + return (ARCHIVE_FATAL); + } + + pax->entry_bytes_remaining = archive_strlen(&(pax->pax_header)); + pax->entry_padding = + 0x1ff & (-(int64_t)pax->entry_bytes_remaining); + + r = __archive_write_output(a, pax->pax_header.s, + archive_strlen(&(pax->pax_header))); + if (r != ARCHIVE_OK) { + /* If a write fails, we're pretty much toast. */ + return (ARCHIVE_FATAL); + } + /* Pad out the end of the entry. */ + r = __archive_write_nulls(a, pax->entry_padding); + if (r != ARCHIVE_OK) { + /* If a write fails, we're pretty much toast. */ + return (ARCHIVE_FATAL); + } + pax->entry_bytes_remaining = pax->entry_padding = 0; + } + + /* Write the header for main entry. */ + r = __archive_write_output(a, ustarbuff, 512); + if (r != ARCHIVE_OK) + return (r); + + /* + * Inform the client of the on-disk size we're using, so + * they can avoid unnecessarily writing a body for something + * that we're just going to ignore. + */ + archive_entry_set_size(entry_original, real_size); + if (pax->sparse_list == NULL && real_size > 0) { + /* This is not a sparse file but we handle its data as + * a sparse block. */ + sparse_list_add(pax, 0, real_size); + sparse_total = real_size; + } + pax->entry_padding = 0x1ff & (-(int64_t)sparse_total); + archive_entry_free(entry_main); + archive_string_free(&entry_name); + + return (ret); +} + +/* + * We need a valid name for the regular 'ustar' entry. This routine + * tries to hack something more-or-less reasonable. + * + * The approach here tries to preserve leading dir names. We do so by + * working with four sections: + * 1) "prefix" directory names, + * 2) "suffix" directory names, + * 3) inserted dir name (optional), + * 4) filename. + * + * These sections must satisfy the following requirements: + * * Parts 1 & 2 together form an initial portion of the dir name. + * * Part 3 is specified by the caller. (It should not contain a leading + * or trailing '/'.) + * * Part 4 forms an initial portion of the base filename. + * * The filename must be <= 99 chars to fit the ustar 'name' field. + * * Parts 2, 3, 4 together must be <= 99 chars to fit the ustar 'name' fld. + * * Part 1 must be <= 155 chars to fit the ustar 'prefix' field. + * * If the original name ends in a '/', the new name must also end in a '/' + * * Trailing '/.' sequences may be stripped. + * + * Note: Recall that the ustar format does not store the '/' separating + * parts 1 & 2, but does store the '/' separating parts 2 & 3. + */ +static char * +build_ustar_entry_name(char *dest, const char *src, size_t src_length, + const char *insert) +{ + const char *prefix, *prefix_end; + const char *suffix, *suffix_end; + const char *filename, *filename_end; + char *p; + int need_slash = 0; /* Was there a trailing slash? */ + size_t suffix_length = 99; + size_t insert_length; + + /* Length of additional dir element to be added. */ + if (insert == NULL) + insert_length = 0; + else + /* +2 here allows for '/' before and after the insert. */ + insert_length = strlen(insert) + 2; + + /* Step 0: Quick bailout in a common case. */ + if (src_length < 100 && insert == NULL) { + strncpy(dest, src, src_length); + dest[src_length] = '\0'; + return (dest); + } + + /* Step 1: Locate filename and enforce the length restriction. */ + filename_end = src + src_length; + /* Remove trailing '/' chars and '/.' pairs. */ + for (;;) { + if (filename_end > src && filename_end[-1] == '/') { + filename_end --; + need_slash = 1; /* Remember to restore trailing '/'. */ + continue; + } + if (filename_end > src + 1 && filename_end[-1] == '.' + && filename_end[-2] == '/') { + filename_end -= 2; + need_slash = 1; /* "foo/." will become "foo/" */ + continue; + } + break; + } + if (need_slash) + suffix_length--; + /* Find start of filename. */ + filename = filename_end - 1; + while ((filename > src) && (*filename != '/')) + filename --; + if ((*filename == '/') && (filename < filename_end - 1)) + filename ++; + /* Adjust filename_end so that filename + insert fits in 99 chars. */ + suffix_length -= insert_length; + if (filename_end > filename + suffix_length) + filename_end = filename + suffix_length; + /* Calculate max size for "suffix" section (#3 above). */ + suffix_length -= filename_end - filename; + + /* Step 2: Locate the "prefix" section of the dirname, including + * trailing '/'. */ + prefix = src; + prefix_end = prefix + 155; + if (prefix_end > filename) + prefix_end = filename; + while (prefix_end > prefix && *prefix_end != '/') + prefix_end--; + if ((prefix_end < filename) && (*prefix_end == '/')) + prefix_end++; + + /* Step 3: Locate the "suffix" section of the dirname, + * including trailing '/'. */ + suffix = prefix_end; + suffix_end = suffix + suffix_length; /* Enforce limit. */ + if (suffix_end > filename) + suffix_end = filename; + if (suffix_end < suffix) + suffix_end = suffix; + while (suffix_end > suffix && *suffix_end != '/') + suffix_end--; + if ((suffix_end < filename) && (*suffix_end == '/')) + suffix_end++; + + /* Step 4: Build the new name. */ + /* The OpenBSD strlcpy function is safer, but less portable. */ + /* Rather than maintain two versions, just use the strncpy version. */ + p = dest; + if (prefix_end > prefix) { + strncpy(p, prefix, prefix_end - prefix); + p += prefix_end - prefix; + } + if (suffix_end > suffix) { + strncpy(p, suffix, suffix_end - suffix); + p += suffix_end - suffix; + } + if (insert != NULL) { + /* Note: assume insert does not have leading or trailing '/' */ + strcpy(p, insert); + p += strlen(insert); + *p++ = '/'; + } + strncpy(p, filename, filename_end - filename); + p += filename_end - filename; + if (need_slash) + *p++ = '/'; + *p = '\0'; + + return (dest); +} + +/* + * The ustar header for the pax extended attributes must have a + * reasonable name: SUSv3 requires 'dirname'/PaxHeader.'pid'/'filename' + * where 'pid' is the PID of the archiving process. Unfortunately, + * that makes testing a pain since the output varies for each run, + * so I'm sticking with the simpler 'dirname'/PaxHeader/'filename' + * for now. (Someday, I'll make this settable. Then I can use the + * SUS recommendation as default and test harnesses can override it + * to get predictable results.) + * + * Joerg Schilling has argued that this is unnecessary because, in + * practice, if the pax extended attributes get extracted as regular + * files, noone is going to bother reading those attributes to + * manually restore them. Based on this, 'star' uses + * /tmp/PaxHeader/'basename' as the ustar header name. This is a + * tempting argument, in part because it's simpler than the SUSv3 + * recommendation, but I'm not entirely convinced. I'm also + * uncomfortable with the fact that "/tmp" is a Unix-ism. + * + * The following routine leverages build_ustar_entry_name() above and + * so is simpler than you might think. It just needs to provide the + * additional path element and handle a few pathological cases). + */ +static char * +build_pax_attribute_name(char *dest, const char *src) +{ + char buff[64]; + const char *p; + + /* Handle the null filename case. */ + if (src == NULL || *src == '\0') { + strcpy(dest, "PaxHeader/blank"); + return (dest); + } + + /* Prune final '/' and other unwanted final elements. */ + p = src + strlen(src); + for (;;) { + /* Ends in "/", remove the '/' */ + if (p > src && p[-1] == '/') { + --p; + continue; + } + /* Ends in "/.", remove the '.' */ + if (p > src + 1 && p[-1] == '.' + && p[-2] == '/') { + --p; + continue; + } + break; + } + + /* Pathological case: After above, there was nothing left. + * This includes "/." "/./." "/.//./." etc. */ + if (p == src) { + strcpy(dest, "/PaxHeader/rootdir"); + return (dest); + } + + /* Convert unadorned "." into a suitable filename. */ + if (*src == '.' && p == src + 1) { + strcpy(dest, "PaxHeader/currentdir"); + return (dest); + } + + /* + * TODO: Push this string into the 'pax' structure to avoid + * recomputing it every time. That will also open the door + * to having clients override it. + */ +#if HAVE_GETPID && 0 /* Disable this for now; see above comment. */ + sprintf(buff, "PaxHeader.%d", getpid()); +#else + /* If the platform can't fetch the pid, don't include it. */ + strcpy(buff, "PaxHeader"); +#endif + /* General case: build a ustar-compatible name adding + * "/PaxHeader/". */ + build_ustar_entry_name(dest, src, p - src, buff); + + return (dest); +} + +/* + * GNU PAX Format 1.0 requires the special name, which pattern is: + * /GNUSparseFile./ + * + * This function is used for only Sparse file, a file type of which + * is regular file. + */ +static char * +build_gnu_sparse_name(char *dest, const char *src) +{ + char buff[64]; + const char *p; + + /* Handle the null filename case. */ + if (src == NULL || *src == '\0') { + strcpy(dest, "GNUSparseFile/blank"); + return (dest); + } + + /* Prune final '/' and other unwanted final elements. */ + p = src + strlen(src); + for (;;) { + /* Ends in "/", remove the '/' */ + if (p > src && p[-1] == '/') { + --p; + continue; + } + /* Ends in "/.", remove the '.' */ + if (p > src + 1 && p[-1] == '.' + && p[-2] == '/') { + --p; + continue; + } + break; + } + +#if HAVE_GETPID && 0 /* Disable this as pax attribute name. */ + sprintf(buff, "GNUSparseFile.%d", getpid()); +#else + /* If the platform can't fetch the pid, don't include it. */ + strcpy(buff, "GNUSparseFile"); +#endif + /* General case: build a ustar-compatible name adding + * "/GNUSparseFile/". */ + build_ustar_entry_name(dest, src, p - src, buff); + + return (dest); +} + +/* Write two null blocks for the end of archive */ +static int +archive_write_pax_close(struct archive_write *a) +{ + return (__archive_write_nulls(a, 512 * 2)); +} + +static int +archive_write_pax_free(struct archive_write *a) +{ + struct pax *pax; + + pax = (struct pax *)a->format_data; + if (pax == NULL) + return (ARCHIVE_OK); + + archive_string_free(&pax->pax_header); + archive_string_free(&pax->sparse_map); + archive_string_free(&pax->l_url_encoded_name); + sparse_list_clear(pax); + free(pax); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_pax_finish_entry(struct archive_write *a) +{ + struct pax *pax; + uint64_t remaining; + int ret; + + pax = (struct pax *)a->format_data; + remaining = pax->entry_bytes_remaining; + if (remaining == 0) { + while (pax->sparse_list) { + struct sparse_block *sb; + remaining += pax->sparse_list->remaining; + sb = pax->sparse_list->next; + free(pax->sparse_list); + pax->sparse_list = sb; + } + } + ret = __archive_write_nulls(a, remaining + pax->entry_padding); + pax->entry_bytes_remaining = pax->entry_padding = 0; + return (ret); +} + +static ssize_t +archive_write_pax_data(struct archive_write *a, const void *buff, size_t s) +{ + struct pax *pax; + size_t ws; + size_t total; + int ret; + + pax = (struct pax *)a->format_data; + + /* + * According to GNU PAX format 1.0, write a sparse map + * before the body. + */ + if (archive_strlen(&(pax->sparse_map))) { + ret = __archive_write_output(a, pax->sparse_map.s, + archive_strlen(&(pax->sparse_map))); + if (ret != ARCHIVE_OK) + return (ret); + ret = __archive_write_nulls(a, pax->sparse_map_padding); + if (ret != ARCHIVE_OK) + return (ret); + archive_string_empty(&(pax->sparse_map)); + } + + total = 0; + while (total < s) { + const unsigned char *p; + + while (pax->sparse_list != NULL && + pax->sparse_list->remaining == 0) { + struct sparse_block *sb = pax->sparse_list->next; + free(pax->sparse_list); + pax->sparse_list = sb; + } + + if (pax->sparse_list == NULL) + return (total); + + p = ((const unsigned char *)buff) + total; + ws = s; + if (ws > pax->sparse_list->remaining) + ws = pax->sparse_list->remaining; + + if (pax->sparse_list->is_hole) { + /* Current block is hole thus we do not write + * the body. */ + pax->sparse_list->remaining -= ws; + total += ws; + continue; + } + + ret = __archive_write_output(a, p, ws); + pax->sparse_list->remaining -= ws; + total += ws; + if (ret != ARCHIVE_OK) + return (ret); + } + return (total); +} + +static int +has_non_ASCII(const char *_p) +{ + const unsigned char *p = (const unsigned char *)_p; + + if (p == NULL) + return (1); + while (*p != '\0' && *p < 128) + p++; + return (*p != '\0'); +} + +/* + * Used by extended attribute support; encodes the name + * so that there will be no '=' characters in the result. + */ +static char * +url_encode(const char *in) +{ + const char *s; + char *d; + int out_len = 0; + char *out; + + for (s = in; *s != '\0'; s++) { + if (*s < 33 || *s > 126 || *s == '%' || *s == '=') + out_len += 3; + else + out_len++; + } + + out = (char *)malloc(out_len + 1); + if (out == NULL) + return (NULL); + + for (s = in, d = out; *s != '\0'; s++) { + /* encode any non-printable ASCII character or '%' or '=' */ + if (*s < 33 || *s > 126 || *s == '%' || *s == '=') { + /* URL encoding is '%' followed by two hex digits */ + *d++ = '%'; + *d++ = "0123456789ABCDEF"[0x0f & (*s >> 4)]; + *d++ = "0123456789ABCDEF"[0x0f & *s]; + } else { + *d++ = *s; + } + } + *d = '\0'; + return (out); +} + +/* + * Encode a sequence of bytes into a C string using base-64 encoding. + * + * Returns a null-terminated C string allocated with malloc(); caller + * is responsible for freeing the result. + */ +static char * +base64_encode(const char *s, size_t len) +{ + static const char digits[64] = + { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', + 'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d', + 'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s', + 't','u','v','w','x','y','z','0','1','2','3','4','5','6','7', + '8','9','+','/' }; + int v; + char *d, *out; + + /* 3 bytes becomes 4 chars, but round up and allow for trailing NUL */ + out = (char *)malloc((len * 4 + 2) / 3 + 1); + if (out == NULL) + return (NULL); + d = out; + + /* Convert each group of 3 bytes into 4 characters. */ + while (len >= 3) { + v = (((int)s[0] << 16) & 0xff0000) + | (((int)s[1] << 8) & 0xff00) + | (((int)s[2]) & 0x00ff); + s += 3; + len -= 3; + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + *d++ = digits[(v >> 6) & 0x3f]; + *d++ = digits[(v) & 0x3f]; + } + /* Handle final group of 1 byte (2 chars) or 2 bytes (3 chars). */ + switch (len) { + case 0: break; + case 1: + v = (((int)s[0] << 16) & 0xff0000); + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + break; + case 2: + v = (((int)s[0] << 16) & 0xff0000) + | (((int)s[1] << 8) & 0xff00); + *d++ = digits[(v >> 18) & 0x3f]; + *d++ = digits[(v >> 12) & 0x3f]; + *d++ = digits[(v >> 6) & 0x3f]; + break; + } + /* Add trailing NUL character so output is a valid C string. */ + *d = '\0'; + return (out); +} + +static void +sparse_list_clear(struct pax *pax) +{ + while (pax->sparse_list != NULL) { + struct sparse_block *sb = pax->sparse_list; + pax->sparse_list = sb->next; + free(sb); + } + pax->sparse_tail = NULL; +} + +static int +_sparse_list_add_block(struct pax *pax, int64_t offset, int64_t length, + int is_hole) +{ + struct sparse_block *sb; + + sb = (struct sparse_block *)malloc(sizeof(*sb)); + if (sb == NULL) + return (ARCHIVE_FATAL); + sb->next = NULL; + sb->is_hole = is_hole; + sb->offset = offset; + sb->remaining = length; + if (pax->sparse_list == NULL) + pax->sparse_list = pax->sparse_tail = sb; + else { + pax->sparse_tail->next = sb; + pax->sparse_tail = sb; + } + return (ARCHIVE_OK); +} + +static int +sparse_list_add(struct pax *pax, int64_t offset, int64_t length) +{ + int64_t last_offset; + int r; + + if (pax->sparse_tail == NULL) + last_offset = 0; + else { + last_offset = pax->sparse_tail->offset + + pax->sparse_tail->remaining; + } + if (last_offset < offset) { + /* Add a hole block. */ + r = _sparse_list_add_block(pax, last_offset, + offset - last_offset, 1); + if (r != ARCHIVE_OK) + return (r); + } + /* Add data block. */ + return (_sparse_list_add_block(pax, offset, length, 0)); +} + diff --git a/libarchive/archive_write_set_format_shar.c b/libarchive/archive_write_set_format_shar.c new file mode 100644 index 0000000..9ec15f9 --- /dev/null +++ b/libarchive/archive_write_set_format_shar.c @@ -0,0 +1,642 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2008 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_shar.c 189438 2009-03-06 05:58:56Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct shar { + int dump; + int end_of_line; + struct archive_entry *entry; + int has_data; + char *last_dir; + + /* Line buffer for uuencoded dump format */ + char outbuff[45]; + size_t outpos; + + int wrote_header; + struct archive_string work; + struct archive_string quoted_name; +}; + +static int archive_write_shar_close(struct archive_write *); +static int archive_write_shar_free(struct archive_write *); +static int archive_write_shar_header(struct archive_write *, + struct archive_entry *); +static ssize_t archive_write_shar_data_sed(struct archive_write *, + const void * buff, size_t); +static ssize_t archive_write_shar_data_uuencode(struct archive_write *, + const void * buff, size_t); +static int archive_write_shar_finish_entry(struct archive_write *); + +/* + * Copy the given string to the buffer, quoting all shell meta characters + * found. + */ +static void +shar_quote(struct archive_string *buf, const char *str, int in_shell) +{ + static const char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~"; + size_t len; + + while (*str != '\0') { + if ((len = strcspn(str, meta)) != 0) { + archive_strncat(buf, str, len); + str += len; + } else if (*str == '\n') { + if (in_shell) + archive_strcat(buf, "\"\n\""); + else + archive_strcat(buf, "\\n"); + ++str; + } else { + archive_strappend_char(buf, '\\'); + archive_strappend_char(buf, *str); + ++str; + } + } +} + +/* + * Set output format to 'shar' format. + */ +int +archive_write_set_format_shar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct shar *shar; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_shar"); + + /* If someone else was already registered, unregister them. */ + if (a->format_free != NULL) + (a->format_free)(a); + + shar = (struct shar *)malloc(sizeof(*shar)); + if (shar == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate shar data"); + return (ARCHIVE_FATAL); + } + memset(shar, 0, sizeof(*shar)); + archive_string_init(&shar->work); + archive_string_init(&shar->quoted_name); + a->format_data = shar; + a->format_name = "shar"; + a->format_write_header = archive_write_shar_header; + a->format_close = archive_write_shar_close; + a->format_free = archive_write_shar_free; + a->format_write_data = archive_write_shar_data_sed; + a->format_finish_entry = archive_write_shar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_SHAR_BASE; + a->archive.archive_format_name = "shar"; + return (ARCHIVE_OK); +} + +/* + * An alternate 'shar' that uses uudecode instead of 'sed' to encode + * file contents and can therefore be used to archive binary files. + * In addition, this variant also attempts to restore ownership, file modes, + * and other extended file information. + */ +int +archive_write_set_format_shar_dump(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct shar *shar; + + archive_write_set_format_shar(&a->archive); + shar = (struct shar *)a->format_data; + shar->dump = 1; + a->format_write_data = archive_write_shar_data_uuencode; + a->archive.archive_format = ARCHIVE_FORMAT_SHAR_DUMP; + a->archive.archive_format_name = "shar dump"; + return (ARCHIVE_OK); +} + +static int +archive_write_shar_header(struct archive_write *a, struct archive_entry *entry) +{ + const char *linkname; + const char *name; + char *p, *pp; + struct shar *shar; + + shar = (struct shar *)a->format_data; + if (!shar->wrote_header) { + archive_strcat(&shar->work, "#!/bin/sh\n"); + archive_strcat(&shar->work, "# This is a shell archive\n"); + shar->wrote_header = 1; + } + + /* Save the entry for the closing. */ + if (shar->entry) + archive_entry_free(shar->entry); + shar->entry = archive_entry_clone(entry); + name = archive_entry_pathname(entry); + + /* Handle some preparatory issues. */ + switch(archive_entry_filetype(entry)) { + case AE_IFREG: + /* Only regular files have non-zero size. */ + break; + case AE_IFDIR: + archive_entry_set_size(entry, 0); + /* Don't bother trying to recreate '.' */ + if (strcmp(name, ".") == 0 || strcmp(name, "./") == 0) + return (ARCHIVE_OK); + break; + case AE_IFIFO: + case AE_IFCHR: + case AE_IFBLK: + /* All other file types have zero size in the archive. */ + archive_entry_set_size(entry, 0); + break; + default: + archive_entry_set_size(entry, 0); + if (archive_entry_hardlink(entry) == NULL && + archive_entry_symlink(entry) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "shar format cannot archive this"); + return (ARCHIVE_WARN); + } + } + + archive_string_empty(&shar->quoted_name); + shar_quote(&shar->quoted_name, name, 1); + + /* Stock preparation for all file types. */ + archive_string_sprintf(&shar->work, "echo x %s\n", shar->quoted_name.s); + + if (archive_entry_filetype(entry) != AE_IFDIR) { + /* Try to create the dir. */ + p = strdup(name); + pp = strrchr(p, '/'); + /* If there is a / character, try to create the dir. */ + if (pp != NULL) { + *pp = '\0'; + + /* Try to avoid a lot of redundant mkdir commands. */ + if (strcmp(p, ".") == 0) { + /* Don't try to "mkdir ." */ + free(p); + } else if (shar->last_dir == NULL) { + archive_strcat(&shar->work, "mkdir -p "); + shar_quote(&shar->work, p, 1); + archive_strcat(&shar->work, + " > /dev/null 2>&1\n"); + shar->last_dir = p; + } else if (strcmp(p, shar->last_dir) == 0) { + /* We've already created this exact dir. */ + free(p); + } else if (strlen(p) < strlen(shar->last_dir) && + strncmp(p, shar->last_dir, strlen(p)) == 0) { + /* We've already created a subdir. */ + free(p); + } else { + archive_strcat(&shar->work, "mkdir -p "); + shar_quote(&shar->work, p, 1); + archive_strcat(&shar->work, + " > /dev/null 2>&1\n"); + shar->last_dir = p; + } + } else { + free(p); + } + } + + /* Handle file-type specific issues. */ + shar->has_data = 0; + if ((linkname = archive_entry_hardlink(entry)) != NULL) { + archive_strcat(&shar->work, "ln -f "); + shar_quote(&shar->work, linkname, 1); + archive_string_sprintf(&shar->work, " %s\n", + shar->quoted_name.s); + } else if ((linkname = archive_entry_symlink(entry)) != NULL) { + archive_strcat(&shar->work, "ln -fs "); + shar_quote(&shar->work, linkname, 1); + archive_string_sprintf(&shar->work, " %s\n", + shar->quoted_name.s); + } else { + switch(archive_entry_filetype(entry)) { + case AE_IFREG: + if (archive_entry_size(entry) == 0) { + /* More portable than "touch." */ + archive_string_sprintf(&shar->work, + "test -e \"%s\" || :> \"%s\"\n", + shar->quoted_name.s, shar->quoted_name.s); + } else { + if (shar->dump) { + unsigned int mode = archive_entry_mode(entry) & 0777; + archive_string_sprintf(&shar->work, + "uudecode -p > %s << 'SHAR_END'\n", + shar->quoted_name.s); + archive_string_sprintf(&shar->work, + "begin %o ", mode); + shar_quote(&shar->work, name, 0); + archive_strcat(&shar->work, "\n"); + } else { + archive_string_sprintf(&shar->work, + "sed 's/^X//' > %s << 'SHAR_END'\n", + shar->quoted_name.s); + } + shar->has_data = 1; + shar->end_of_line = 1; + shar->outpos = 0; + } + break; + case AE_IFDIR: + archive_string_sprintf(&shar->work, + "mkdir -p %s > /dev/null 2>&1\n", + shar->quoted_name.s); + /* Record that we just created this directory. */ + if (shar->last_dir != NULL) + free(shar->last_dir); + + shar->last_dir = strdup(name); + /* Trim a trailing '/'. */ + pp = strrchr(shar->last_dir, '/'); + if (pp != NULL && pp[1] == '\0') + *pp = '\0'; + /* + * TODO: Put dir name/mode on a list to be fixed + * up at end of archive. + */ + break; + case AE_IFIFO: + archive_string_sprintf(&shar->work, + "mkfifo %s\n", shar->quoted_name.s); + break; + case AE_IFCHR: + archive_string_sprintf(&shar->work, + "mknod %s c %ju %ju\n", shar->quoted_name.s, + (uintmax_t)archive_entry_rdevmajor(entry), + (uintmax_t)archive_entry_rdevminor(entry)); + break; + case AE_IFBLK: + archive_string_sprintf(&shar->work, + "mknod %s b %ju %ju\n", shar->quoted_name.s, + (uintmax_t)archive_entry_rdevmajor(entry), + (uintmax_t)archive_entry_rdevminor(entry)); + break; + default: + return (ARCHIVE_WARN); + } + } + + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_shar_data_sed(struct archive_write *a, const void *buff, size_t n) +{ + static const size_t ensured = 65533; + struct shar *shar; + const char *src; + char *buf, *buf_end; + int ret; + size_t written = n; + + shar = (struct shar *)a->format_data; + if (!shar->has_data || n == 0) + return (0); + + src = (const char *)buff; + + /* + * ensure is the number of bytes in buffer before expanding the + * current character. Each operation writes the current character + * and optionally the start-of-new-line marker. This can happen + * twice before entering the loop, so make sure three additional + * bytes can be written. + */ + if (archive_string_ensure(&shar->work, ensured + 3) == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + + if (shar->work.length > ensured) { + ret = __archive_write_output(a, shar->work.s, + shar->work.length); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + archive_string_empty(&shar->work); + } + buf = shar->work.s + shar->work.length; + buf_end = shar->work.s + ensured; + + if (shar->end_of_line) { + *buf++ = 'X'; + shar->end_of_line = 0; + } + + while (n-- != 0) { + if ((*buf++ = *src++) == '\n') { + if (n == 0) + shar->end_of_line = 1; + else + *buf++ = 'X'; + } + + if (buf >= buf_end) { + shar->work.length = buf - shar->work.s; + ret = __archive_write_output(a, shar->work.s, + shar->work.length); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + archive_string_empty(&shar->work); + buf = shar->work.s; + } + } + + shar->work.length = buf - shar->work.s; + + return (written); +} + +#define UUENC(c) (((c)!=0) ? ((c) & 077) + ' ': '`') + +static void +uuencode_group(const char _in[3], char out[4]) +{ + const unsigned char *in = (const unsigned char *)_in; + int t; + + t = (in[0] << 16) | (in[1] << 8) | in[2]; + out[0] = UUENC( 0x3f & (t >> 18) ); + out[1] = UUENC( 0x3f & (t >> 12) ); + out[2] = UUENC( 0x3f & (t >> 6) ); + out[3] = UUENC( 0x3f & t ); +} + +static int +_uuencode_line(struct archive_write *a, struct shar *shar, const char *inbuf, size_t len) +{ + char *buf; + size_t alloc_len; + + /* len <= 45 -> expanded to 60 + len byte + new line */ + alloc_len = shar->work.length + 62; + if (archive_string_ensure(&shar->work, alloc_len) == NULL) { + archive_set_error(&a->archive, ENOMEM, "Out of memory"); + return (ARCHIVE_FATAL); + } + + buf = shar->work.s + shar->work.length; + *buf++ = UUENC(len); + while (len >= 3) { + uuencode_group(inbuf, buf); + len -= 3; + inbuf += 3; + buf += 4; + } + if (len != 0) { + char tmp_buf[3]; + tmp_buf[0] = inbuf[0]; + if (len == 1) + tmp_buf[1] = '\0'; + else + tmp_buf[1] = inbuf[1]; + tmp_buf[2] = '\0'; + uuencode_group(tmp_buf, buf); + buf += 4; + } + *buf++ = '\n'; + if ((buf - shar->work.s) > (ptrdiff_t)(shar->work.length + 62)) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, "Buffer overflow"); + return (ARCHIVE_FATAL); + } + shar->work.length = buf - shar->work.s; + return (ARCHIVE_OK); +} + +#define uuencode_line(__a, __shar, __inbuf, __len) \ + do { \ + int r = _uuencode_line(__a, __shar, __inbuf, __len); \ + if (r != ARCHIVE_OK) \ + return (ARCHIVE_FATAL); \ + } while (0) + +static ssize_t +archive_write_shar_data_uuencode(struct archive_write *a, const void *buff, + size_t length) +{ + struct shar *shar; + const char *src; + size_t n; + int ret; + + shar = (struct shar *)a->format_data; + if (!shar->has_data) + return (ARCHIVE_OK); + src = (const char *)buff; + + if (shar->outpos != 0) { + n = 45 - shar->outpos; + if (n > length) + n = length; + memcpy(shar->outbuff + shar->outpos, src, n); + if (shar->outpos + n < 45) { + shar->outpos += n; + return length; + } + uuencode_line(a, shar, shar->outbuff, 45); + src += n; + n = length - n; + } else { + n = length; + } + + while (n >= 45) { + uuencode_line(a, shar, src, 45); + src += 45; + n -= 45; + + if (shar->work.length < 65536) + continue; + ret = __archive_write_output(a, shar->work.s, + shar->work.length); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + archive_string_empty(&shar->work); + } + if (n != 0) { + memcpy(shar->outbuff, src, n); + shar->outpos = n; + } + return (length); +} + +static int +archive_write_shar_finish_entry(struct archive_write *a) +{ + const char *g, *p, *u; + struct shar *shar; + int ret; + + shar = (struct shar *)a->format_data; + if (shar->entry == NULL) + return (0); + + if (shar->dump) { + /* Finish uuencoded data. */ + if (shar->has_data) { + if (shar->outpos > 0) + uuencode_line(a, shar, shar->outbuff, + shar->outpos); + archive_strcat(&shar->work, "`\nend\n"); + archive_strcat(&shar->work, "SHAR_END\n"); + } + /* Restore file mode, owner, flags. */ + /* + * TODO: Don't immediately restore mode for + * directories; defer that to end of script. + */ + archive_string_sprintf(&shar->work, "chmod %o ", + (unsigned int)(archive_entry_mode(shar->entry) & 07777)); + shar_quote(&shar->work, archive_entry_pathname(shar->entry), 1); + archive_strcat(&shar->work, "\n"); + + u = archive_entry_uname(shar->entry); + g = archive_entry_gname(shar->entry); + if (u != NULL || g != NULL) { + archive_strcat(&shar->work, "chown "); + if (u != NULL) + shar_quote(&shar->work, u, 1); + if (g != NULL) { + archive_strcat(&shar->work, ":"); + shar_quote(&shar->work, g, 1); + } + shar_quote(&shar->work, + archive_entry_pathname(shar->entry), 1); + archive_strcat(&shar->work, "\n"); + } + + if ((p = archive_entry_fflags_text(shar->entry)) != NULL) { + archive_string_sprintf(&shar->work, "chflags %s ", p); + shar_quote(&shar->work, + archive_entry_pathname(shar->entry), 1); + archive_strcat(&shar->work, "\n"); + } + + /* TODO: restore ACLs */ + + } else { + if (shar->has_data) { + /* Finish sed-encoded data: ensure last line ends. */ + if (!shar->end_of_line) + archive_strappend_char(&shar->work, '\n'); + archive_strcat(&shar->work, "SHAR_END\n"); + } + } + + archive_entry_free(shar->entry); + shar->entry = NULL; + + if (shar->work.length < 65536) + return (ARCHIVE_OK); + + ret = __archive_write_output(a, shar->work.s, shar->work.length); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + archive_string_empty(&shar->work); + + return (ARCHIVE_OK); +} + +static int +archive_write_shar_close(struct archive_write *a) +{ + struct shar *shar; + int ret; + + /* + * TODO: Accumulate list of directory names/modes and + * fix them all up at end-of-archive. + */ + + shar = (struct shar *)a->format_data; + + /* + * Only write the end-of-archive markers if the archive was + * actually started. This avoids problems if someone sets + * shar format, then sets another format (which would invoke + * shar_finish to free the format-specific data). + */ + if (shar->wrote_header == 0) + return (ARCHIVE_OK); + + archive_strcat(&shar->work, "exit\n"); + + ret = __archive_write_output(a, shar->work.s, shar->work.length); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + + /* Shar output is never padded. */ + archive_write_set_bytes_in_last_block(&a->archive, 1); + /* + * TODO: shar should also suppress padding of + * uncompressed data within gzip/bzip2 streams. + */ + + return (ARCHIVE_OK); +} + +static int +archive_write_shar_free(struct archive_write *a) +{ + struct shar *shar; + + shar = (struct shar *)a->format_data; + if (shar == NULL) + return (ARCHIVE_OK); + + archive_entry_free(shar->entry); + free(shar->last_dir); + archive_string_free(&(shar->work)); + archive_string_free(&(shar->quoted_name)); + free(shar); + a->format_data = NULL; + return (ARCHIVE_OK); +} diff --git a/libarchive/archive_write_set_format_ustar.c b/libarchive/archive_write_set_format_ustar.c new file mode 100644 index 0000000..4b96ac2 --- /dev/null +++ b/libarchive/archive_write_set_format_ustar.c @@ -0,0 +1,692 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_ustar.c 191579 2009-04-27 18:35:03Z kientzle $"); + + +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif + +#include "archive.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +struct ustar { + uint64_t entry_bytes_remaining; + uint64_t entry_padding; + + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; +}; + +/* + * Define structure of POSIX 'ustar' tar header. + */ +#define USTAR_name_offset 0 +#define USTAR_name_size 100 +#define USTAR_mode_offset 100 +#define USTAR_mode_size 6 +#define USTAR_mode_max_size 8 +#define USTAR_uid_offset 108 +#define USTAR_uid_size 6 +#define USTAR_uid_max_size 8 +#define USTAR_gid_offset 116 +#define USTAR_gid_size 6 +#define USTAR_gid_max_size 8 +#define USTAR_size_offset 124 +#define USTAR_size_size 11 +#define USTAR_size_max_size 12 +#define USTAR_mtime_offset 136 +#define USTAR_mtime_size 11 +#define USTAR_mtime_max_size 11 +#define USTAR_checksum_offset 148 +#define USTAR_checksum_size 8 +#define USTAR_typeflag_offset 156 +#define USTAR_typeflag_size 1 +#define USTAR_linkname_offset 157 +#define USTAR_linkname_size 100 +#define USTAR_magic_offset 257 +#define USTAR_magic_size 6 +#define USTAR_version_offset 263 +#define USTAR_version_size 2 +#define USTAR_uname_offset 265 +#define USTAR_uname_size 32 +#define USTAR_gname_offset 297 +#define USTAR_gname_size 32 +#define USTAR_rdevmajor_offset 329 +#define USTAR_rdevmajor_size 6 +#define USTAR_rdevmajor_max_size 8 +#define USTAR_rdevminor_offset 337 +#define USTAR_rdevminor_size 6 +#define USTAR_rdevminor_max_size 8 +#define USTAR_prefix_offset 345 +#define USTAR_prefix_size 155 +#define USTAR_padding_offset 500 +#define USTAR_padding_size 12 + +/* + * A filled-in copy of the header for initialization. + */ +static const char template_header[] = { + /* name: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Mode, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* uid, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* gid, space-null termination: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* size, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', ' ', + /* mtime, space termation: 12 bytes */ + '0','0','0','0','0','0','0','0','0','0','0', ' ', + /* Initial checksum value: 8 spaces */ + ' ',' ',' ',' ',' ',' ',' ',' ', + /* Typeflag: 1 byte */ + '0', /* '0' = regular file */ + /* Linkname: 100 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0, + /* Magic: 6 bytes, Version: 2 bytes */ + 'u','s','t','a','r','\0', '0','0', + /* Uname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* Gname: 32 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + /* rdevmajor + space/null padding: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* rdevminor + space/null padding: 8 bytes */ + '0','0','0','0','0','0', ' ','\0', + /* Prefix: 155 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0, + /* Padding: 12 bytes */ + 0,0,0,0,0,0,0,0, 0,0,0,0 +}; + +static ssize_t archive_write_ustar_data(struct archive_write *a, const void *buff, + size_t s); +static int archive_write_ustar_free(struct archive_write *); +static int archive_write_ustar_close(struct archive_write *); +static int archive_write_ustar_finish_entry(struct archive_write *); +static int archive_write_ustar_header(struct archive_write *, + struct archive_entry *entry); +static int archive_write_ustar_options(struct archive_write *, + const char *, const char *); +static int format_256(int64_t, char *, int); +static int format_number(int64_t, char *, int size, int max, int strict); +static int format_octal(int64_t, char *, int); + +/* + * Set output format to 'ustar' format. + */ +int +archive_write_set_format_ustar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct ustar *ustar; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_ustar"); + + /* If someone else was already registered, unregister them. */ + if (a->format_free != NULL) + (a->format_free)(a); + + /* Basic internal sanity test. */ + if (sizeof(template_header) != 512) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Internal: template_header wrong size: %zu should be 512", + sizeof(template_header)); + return (ARCHIVE_FATAL); + } + + ustar = (struct ustar *)malloc(sizeof(*ustar)); + if (ustar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate ustar data"); + return (ARCHIVE_FATAL); + } + memset(ustar, 0, sizeof(*ustar)); + a->format_data = ustar; + a->format_name = "ustar"; + a->format_options = archive_write_ustar_options; + a->format_write_header = archive_write_ustar_header; + a->format_write_data = archive_write_ustar_data; + a->format_close = archive_write_ustar_close; + a->format_free = archive_write_ustar_free; + a->format_finish_entry = archive_write_ustar_finish_entry; + a->archive.archive_format = ARCHIVE_FORMAT_TAR_USTAR; + a->archive.archive_format_name = "POSIX ustar"; + return (ARCHIVE_OK); +} + +static int +archive_write_ustar_options(struct archive_write *a, const char *key, + const char *val) +{ + struct ustar *ustar = (struct ustar *)a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: hdrcharset option needs a character-set name", + a->format_name); + else { + ustar->opt_sconv = archive_string_conversion_to_charset( + &a->archive, val, 0); + if (ustar->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown keyword ``%s''", a->format_name, key); + + return (ret); +} + +static int +archive_write_ustar_header(struct archive_write *a, struct archive_entry *entry) +{ + char buff[512]; + int ret, ret2; + struct ustar *ustar; + struct archive_string_conv *sconv; + + ustar = (struct ustar *)a->format_data; + + /* Setup default string conversion. */ + if (ustar->opt_sconv == NULL) { + if (!ustar->init_default_conversion) { + ustar->sconv_default = + archive_string_default_conversion_for_write(&(a->archive)); + ustar->init_default_conversion = 1; + } + sconv = ustar->sconv_default; + } else + sconv = ustar->opt_sconv; + + /* Sanity check. */ + if (archive_entry_pathname(entry) == NULL) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Can't record entry in tar file without pathname"); + return (ARCHIVE_FAILED); + } + + /* Only regular files (not hardlinks) have data. */ + if (archive_entry_hardlink(entry) != NULL || + archive_entry_symlink(entry) != NULL || + !(archive_entry_filetype(entry) == AE_IFREG)) + archive_entry_set_size(entry, 0); + + if (AE_IFDIR == archive_entry_filetype(entry)) { + const char *p; + char *t; + /* + * Ensure a trailing '/'. Modify the entry so + * the client sees the change. + */ + p = archive_entry_pathname(entry); + if (p[strlen(p) - 1] != '/') { + t = (char *)malloc(strlen(p) + 2); + if (t == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate ustar data"); + return(ARCHIVE_FATAL); + } + strcpy(t, p); + strcat(t, "/"); + archive_entry_copy_pathname(entry, t); + free(t); + } + } + + ret = __archive_write_format_header_ustar(a, buff, entry, -1, 1, sconv); + if (ret < ARCHIVE_WARN) + return (ret); + ret2 = __archive_write_output(a, buff, 512); + if (ret2 < ARCHIVE_WARN) + return (ret2); + if (ret2 < ret) + ret = ret2; + + ustar->entry_bytes_remaining = archive_entry_size(entry); + ustar->entry_padding = 0x1ff & (-(int64_t)ustar->entry_bytes_remaining); + return (ret); +} + +/* + * Format a basic 512-byte "ustar" header. + * + * Returns -1 if format failed (due to field overflow). + * Note that this always formats as much of the header as possible. + * If "strict" is set to zero, it will extend numeric fields as + * necessary (overwriting terminators or using base-256 extensions). + * + * This is exported so that other 'tar' formats can use it. + */ +int +__archive_write_format_header_ustar(struct archive_write *a, char h[512], + struct archive_entry *entry, int tartype, int strict, + struct archive_string_conv *sconv) +{ + unsigned int checksum; + int i, r, ret; + size_t copy_length; + const char *p, *pp; + int mytartype; + + ret = 0; + mytartype = -1; + /* + * The "template header" already includes the "ustar" + * signature, various end-of-field markers and other required + * elements. + */ + memcpy(h, &template_header, 512); + + /* + * Because the block is already null-filled, and strings + * are allowed to exactly fill their destination (without null), + * I use memcpy(dest, src, strlen()) here a lot to copy strings. + */ + r = archive_entry_pathname_l(entry, &pp, ©_length, sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", + pp, archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + if (copy_length <= USTAR_name_size) + memcpy(h + USTAR_name_offset, pp, copy_length); + else { + /* Store in two pieces, splitting at a '/'. */ + p = strchr(pp + copy_length - USTAR_name_size - 1, '/'); + /* + * Look for the next '/' if we chose the first character + * as the separator. (ustar format doesn't permit + * an empty prefix.) + */ + if (p == pp) + p = strchr(p + 1, '/'); + /* Fail if the name won't fit. */ + if (!p) { + /* No separator. */ + archive_set_error(&a->archive, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_FAILED; + } else if (p[1] == '\0') { + /* + * The only feasible separator is a final '/'; + * this would result in a non-empty prefix and + * an empty name, which POSIX doesn't + * explicitly forbid, but it just feels wrong. + */ + archive_set_error(&a->archive, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_FAILED; + } else if (p > pp + USTAR_prefix_size) { + /* Prefix is too long. */ + archive_set_error(&a->archive, ENAMETOOLONG, + "Pathname too long"); + ret = ARCHIVE_FAILED; + } else { + /* Copy prefix and remainder to appropriate places */ + memcpy(h + USTAR_prefix_offset, pp, p - pp); + memcpy(h + USTAR_name_offset, p + 1, + pp + copy_length - p - 1); + } + } + + r = archive_entry_hardlink_l(entry, &p, ©_length, sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + p, archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + if (copy_length > 0) + mytartype = '1'; + else { + r = archive_entry_symlink_l(entry, &p, ©_length, sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate linkname '%s' to %s", + p, archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + } + if (copy_length > 0) { + if (copy_length > USTAR_linkname_size) { + archive_set_error(&a->archive, ENAMETOOLONG, + "Link contents too long"); + ret = ARCHIVE_FAILED; + copy_length = USTAR_linkname_size; + } + memcpy(h + USTAR_linkname_offset, p, copy_length); + } + + r = archive_entry_uname_l(entry, &p, ©_length, sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Uname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate uname '%s' to %s", + p, archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + if (copy_length > 0) { + if (copy_length > USTAR_uname_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Username too long"); + ret = ARCHIVE_FAILED; + copy_length = USTAR_uname_size; + } + memcpy(h + USTAR_uname_offset, p, copy_length); + } + + r = archive_entry_gname_l(entry, &p, ©_length, sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Gname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate gname '%s' to %s", + p, archive_string_conversion_charset_name(sconv)); + ret = ARCHIVE_WARN; + } + if (copy_length > 0) { + if (strlen(p) > USTAR_gname_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Group name too long"); + ret = ARCHIVE_FAILED; + copy_length = USTAR_gname_size; + } + memcpy(h + USTAR_gname_offset, p, copy_length); + } + + if (format_number(archive_entry_mode(entry) & 07777, + h + USTAR_mode_offset, USTAR_mode_size, USTAR_mode_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Numeric mode too large"); + ret = ARCHIVE_FAILED; + } + + if (format_number(archive_entry_uid(entry), + h + USTAR_uid_offset, USTAR_uid_size, USTAR_uid_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Numeric user ID too large"); + ret = ARCHIVE_FAILED; + } + + if (format_number(archive_entry_gid(entry), + h + USTAR_gid_offset, USTAR_gid_size, USTAR_gid_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Numeric group ID too large"); + ret = ARCHIVE_FAILED; + } + + if (format_number(archive_entry_size(entry), + h + USTAR_size_offset, USTAR_size_size, USTAR_size_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "File size out of range"); + ret = ARCHIVE_FAILED; + } + + if (format_number(archive_entry_mtime(entry), + h + USTAR_mtime_offset, USTAR_mtime_size, USTAR_mtime_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "File modification time too large"); + ret = ARCHIVE_FAILED; + } + + if (archive_entry_filetype(entry) == AE_IFBLK + || archive_entry_filetype(entry) == AE_IFCHR) { + if (format_number(archive_entry_rdevmajor(entry), + h + USTAR_rdevmajor_offset, USTAR_rdevmajor_size, + USTAR_rdevmajor_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Major device number too large"); + ret = ARCHIVE_FAILED; + } + + if (format_number(archive_entry_rdevminor(entry), + h + USTAR_rdevminor_offset, USTAR_rdevminor_size, + USTAR_rdevminor_max_size, strict)) { + archive_set_error(&a->archive, ERANGE, + "Minor device number too large"); + ret = ARCHIVE_FAILED; + } + } + + if (tartype >= 0) { + h[USTAR_typeflag_offset] = tartype; + } else if (mytartype >= 0) { + h[USTAR_typeflag_offset] = mytartype; + } else { + switch (archive_entry_filetype(entry)) { + case AE_IFREG: h[USTAR_typeflag_offset] = '0' ; break; + case AE_IFLNK: h[USTAR_typeflag_offset] = '2' ; break; + case AE_IFCHR: h[USTAR_typeflag_offset] = '3' ; break; + case AE_IFBLK: h[USTAR_typeflag_offset] = '4' ; break; + case AE_IFDIR: h[USTAR_typeflag_offset] = '5' ; break; + case AE_IFIFO: h[USTAR_typeflag_offset] = '6' ; break; + case AE_IFSOCK: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive socket"); + return (ARCHIVE_FAILED); + default: + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "tar format cannot archive this (mode=0%lo)", + (unsigned long)archive_entry_mode(entry)); + ret = ARCHIVE_FAILED; + } + } + + checksum = 0; + for (i = 0; i < 512; i++) + checksum += 255 & (unsigned int)h[i]; + h[USTAR_checksum_offset + 6] = '\0'; /* Can't be pre-set in the template. */ + /* h[USTAR_checksum_offset + 7] = ' '; */ /* This is pre-set in the template. */ + format_octal(checksum, h + USTAR_checksum_offset, 6); + return (ret); +} + +/* + * Format a number into a field, with some intelligence. + */ +static int +format_number(int64_t v, char *p, int s, int maxsize, int strict) +{ + int64_t limit; + + limit = ((int64_t)1 << (s*3)); + + /* "Strict" only permits octal values with proper termination. */ + if (strict) + return (format_octal(v, p, s)); + + /* + * In non-strict mode, we allow the number to overwrite one or + * more bytes of the field termination. Even old tar + * implementations should be able to handle this with no + * problem. + */ + if (v >= 0) { + while (s <= maxsize) { + if (v < limit) + return (format_octal(v, p, s)); + s++; + limit <<= 3; + } + } + + /* Base-256 can handle any number, positive or negative. */ + return (format_256(v, p, maxsize)); +} + +/* + * Format a number into the specified field using base-256. + */ +static int +format_256(int64_t v, char *p, int s) +{ + p += s; + while (s-- > 0) { + *--p = (char)(v & 0xff); + v >>= 8; + } + *p |= 0x80; /* Set the base-256 marker bit. */ + return (0); +} + +/* + * Format a number into the specified field. + */ +static int +format_octal(int64_t v, char *p, int s) +{ + int len; + + len = s; + + /* Octal values can't be negative, so use 0. */ + if (v < 0) { + while (len-- > 0) + *p++ = '0'; + return (-1); + } + + p += s; /* Start at the end and work backwards. */ + while (s-- > 0) { + *--p = (char)('0' + (v & 7)); + v >>= 3; + } + + if (v == 0) + return (0); + + /* If it overflowed, fill field with max value. */ + while (len-- > 0) + *p++ = '7'; + + return (-1); +} + +static int +archive_write_ustar_close(struct archive_write *a) +{ + return (__archive_write_nulls(a, 512*2)); +} + +static int +archive_write_ustar_free(struct archive_write *a) +{ + struct ustar *ustar; + + ustar = (struct ustar *)a->format_data; + free(ustar); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +static int +archive_write_ustar_finish_entry(struct archive_write *a) +{ + struct ustar *ustar; + int ret; + + ustar = (struct ustar *)a->format_data; + ret = __archive_write_nulls(a, + ustar->entry_bytes_remaining + ustar->entry_padding); + ustar->entry_bytes_remaining = ustar->entry_padding = 0; + return (ret); +} + +static ssize_t +archive_write_ustar_data(struct archive_write *a, const void *buff, size_t s) +{ + struct ustar *ustar; + int ret; + + ustar = (struct ustar *)a->format_data; + if (s > ustar->entry_bytes_remaining) + s = ustar->entry_bytes_remaining; + ret = __archive_write_output(a, buff, s); + ustar->entry_bytes_remaining -= s; + if (ret != ARCHIVE_OK) + return (ret); + return (s); +} diff --git a/libarchive/archive_write_set_format_xar.c b/libarchive/archive_write_set_format_xar.c new file mode 100644 index 0000000..4447f5a --- /dev/null +++ b/libarchive/archive_write_set_format_xar.c @@ -0,0 +1,3168 @@ +/*- + * Copyright (c) 2010-2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LIMITS_H +#include +#endif +#include +#if HAVE_LIBXML_XMLWRITER_H +#include +#endif +#ifdef HAVE_BZLIB_H +#include +#endif +#if HAVE_LZMA_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_crypto_private.h" +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_rb.h" +#include "archive_string.h" +#include "archive_write_private.h" + +/* + * Differences to xar utility. + * - Subdocument is not supported yet. + * - ACL is not supported yet. + * - When writing an XML element , + * which is a file type a symbolic link is referencing is always marked + * as "broken". Xar utility uses stat(2) to get the file type, but, in + * libarcive format writer, we should not use it; if it is needed, we + * should get about it at archive_read_disk.c. + * - It is possible to appear both and elements. + * Xar utility generates on BSD platform and on Linux + * platform. + * + */ + +#if !(defined(HAVE_LIBXML_XMLWRITER_H) && defined(LIBXML_VERSION) &&\ + LIBXML_VERSION >= 20703) ||\ + !defined(HAVE_ZLIB_H) || \ + !defined(ARCHIVE_HAS_MD5) || !defined(ARCHIVE_HAS_SHA1) +/* + * xar needs several external libraries. + * o libxml2 + * o openssl or MD5/SHA1 hash function + * o zlib + * o bzlib2 (option) + * o liblzma (option) + */ +int +archive_write_set_format_xar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Xar not supported on this platform"); + return (ARCHIVE_WARN); +} + +#else /* Support xar format */ + +/*#define DEBUG_PRINT_TOC 1 */ + +#define HEADER_MAGIC 0x78617221 +#define HEADER_SIZE 28 +#define HEADER_VERSION 1 + +enum sumalg { + CKSUM_NONE = 0, + CKSUM_SHA1 = 1, + CKSUM_MD5 = 2 +}; + +#define MD5_SIZE 16 +#define SHA1_SIZE 20 +#define MAX_SUM_SIZE 20 +#define MD5_NAME "md5" +#define SHA1_NAME "sha1" + +enum enctype { + NONE, + GZIP, + BZIP2, + LZMA, + XZ, +}; + +struct chksumwork { + enum sumalg alg; +#ifdef ARCHIVE_HAS_MD5 + archive_md5_ctx md5ctx; +#endif +#ifdef ARCHIVE_HAS_SHA1 + archive_sha1_ctx sha1ctx; +#endif +}; + +enum la_zaction { + ARCHIVE_Z_FINISH, + ARCHIVE_Z_RUN +}; + +/* + * Universal zstream. + */ +struct la_zstream { + const unsigned char *next_in; + size_t avail_in; + uint64_t total_in; + + unsigned char *next_out; + size_t avail_out; + uint64_t total_out; + + int valid; + void *real_stream; + int (*code) (struct archive *a, + struct la_zstream *lastrm, + enum la_zaction action); + int (*end)(struct archive *a, + struct la_zstream *lastrm); +}; + +struct chksumval { + enum sumalg alg; + size_t len; + unsigned char val[MAX_SUM_SIZE]; +}; + +struct heap_data { + int id; + struct heap_data *next; + uint64_t temp_offset; + uint64_t length; /* archived size. */ + uint64_t size; /* extracted size. */ + enum enctype compression; + struct chksumval a_sum; /* archived checksum. */ + struct chksumval e_sum; /* extracted checksum. */ +}; + +struct file { + struct archive_rb_node rbnode; + + int id; + struct archive_entry *entry; + + struct archive_rb_tree rbtree; + struct file *next; + struct file *chnext; + struct file *hlnext; + /* For hardlinked files. + * Use only when archive_entry_nlink() > 1 */ + struct file *hardlink_target; + struct file *parent; /* parent directory entry */ + /* + * To manage sub directory files. + * We use 'chnext' a menber of struct file to chain. + */ + struct { + struct file *first; + struct file **last; + } children; + + /* For making a directory tree. */ + struct archive_string parentdir; + struct archive_string basename; + struct archive_string symlink; + + int ea_idx; + struct { + struct heap_data *first; + struct heap_data **last; + } xattr; + struct heap_data data; + struct archive_string script; + + int virtual:1; + int dir:1; +}; + +struct hardlink { + struct archive_rb_node rbnode; + int nlink; + struct { + struct file *first; + struct file **last; + } file_list; +}; + +struct xar { + int temp_fd; + uint64_t temp_offset; + + int file_idx; + struct file *root; + struct file *cur_dirent; + struct archive_string cur_dirstr; + struct file *cur_file; + uint64_t bytes_remaining; + struct archive_string tstr; + struct archive_string vstr; + + enum sumalg opt_toc_sumalg; + enum sumalg opt_sumalg; + enum enctype opt_compression; + int opt_compression_level; + + struct chksumwork a_sumwrk; /* archived checksum. */ + struct chksumwork e_sumwrk; /* extracted checksum. */ + struct la_zstream stream; + struct archive_string_conv *sconv; + /* + * Compressed data buffer. + */ + unsigned char wbuff[1024 * 64]; + size_t wbuff_remaining; + + struct heap_data toc; + /* + * The list of all file entries is used to manage struct file + * objects. + * We use 'next' a menber of struct file to chain. + */ + struct { + struct file *first; + struct file **last; + } file_list; + /* + * The list of hard-linked file entries. + * We use 'hlnext' a menber of struct file to chain. + */ + struct archive_rb_tree hardlink_rbtree; +}; + +static int xar_options(struct archive_write *, + const char *, const char *); +static int xar_write_header(struct archive_write *, + struct archive_entry *); +static ssize_t xar_write_data(struct archive_write *, + const void *, size_t); +static int xar_finish_entry(struct archive_write *); +static int xar_close(struct archive_write *); +static int xar_free(struct archive_write *); + +static struct file *file_new(struct archive_write *a, struct archive_entry *); +static void file_free(struct file *); +static struct file *file_create_virtual_dir(struct archive_write *a, struct xar *, + const char *); +static int file_add_child_tail(struct file *, struct file *); +static struct file *file_find_child(struct file *, const char *); +static int file_gen_utility_names(struct archive_write *, + struct file *); +static int get_path_component(char *, int, const char *); +static int file_tree(struct archive_write *, struct file **); +static void file_register(struct xar *, struct file *); +static void file_init_register(struct xar *); +static void file_free_register(struct xar *); +static int file_register_hardlink(struct archive_write *, + struct file *); +static void file_connect_hardlink_files(struct xar *); +static void file_init_hardlinks(struct xar *); +static void file_free_hardlinks(struct xar *); + +static void checksum_init(struct chksumwork *, enum sumalg); +static void checksum_update(struct chksumwork *, const void *, size_t); +static void checksum_final(struct chksumwork *, struct chksumval *); +static int compression_init_encoder_gzip(struct archive *, + struct la_zstream *, int, int); +static int compression_code_gzip(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_gzip(struct archive *, struct la_zstream *); +static int compression_init_encoder_bzip2(struct archive *, + struct la_zstream *, int); +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) +static int compression_code_bzip2(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_bzip2(struct archive *, struct la_zstream *); +#endif +static int compression_init_encoder_lzma(struct archive *, + struct la_zstream *, int); +static int compression_init_encoder_xz(struct archive *, + struct la_zstream *, int); +#if defined(HAVE_LZMA_H) +static int compression_code_lzma(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end_lzma(struct archive *, struct la_zstream *); +#endif +static int xar_compression_init_encoder(struct archive_write *); +static int compression_code(struct archive *, + struct la_zstream *, enum la_zaction); +static int compression_end(struct archive *, + struct la_zstream *); +static int save_xattrs(struct archive_write *, struct file *); +static int getalgsize(enum sumalg); +static const char *getalgname(enum sumalg); + +int +archive_write_set_format_xar(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct xar *xar; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_xar"); + + /* If another format was already registered, unregister it. */ + if (a->format_free != NULL) + (a->format_free)(a); + + xar = calloc(1, sizeof(*xar)); + if (xar == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate xar data"); + return (ARCHIVE_FATAL); + } + xar->temp_fd = -1; + file_init_register(xar); + file_init_hardlinks(xar); + archive_string_init(&(xar->tstr)); + archive_string_init(&(xar->vstr)); + + /* + * Create the root directory. + */ + xar->root = file_create_virtual_dir(a, xar, ""); + if (xar->root == NULL) { + free(xar); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate xar data"); + return (ARCHIVE_FATAL); + } + xar->root->parent = xar->root; + file_register(xar, xar->root); + xar->cur_dirent = xar->root; + archive_string_init(&(xar->cur_dirstr)); + archive_string_ensure(&(xar->cur_dirstr), 1); + xar->cur_dirstr.s[0] = 0; + + /* + * Initialize option. + */ + /* Set default checksum type. */ + xar->opt_toc_sumalg = CKSUM_SHA1; + xar->opt_sumalg = CKSUM_SHA1; + /* Set default compression type and level. */ + xar->opt_compression = GZIP; + xar->opt_compression_level = 6; + + a->format_data = xar; + + a->format_name = "xar"; + a->format_options = xar_options; + a->format_write_header = xar_write_header; + a->format_write_data = xar_write_data; + a->format_finish_entry = xar_finish_entry; + a->format_close = xar_close; + a->format_free = xar_free; + a->archive.archive_format = ARCHIVE_FORMAT_XAR; + a->archive.archive_format_name = "xar"; + + return (ARCHIVE_OK); +} + +static int +xar_options(struct archive_write *a, const char *key, const char *value) +{ + struct xar *xar; + + xar = (struct xar *)a->format_data; + + if (strcmp(key, "checksum") == 0) { + if (value == NULL) + xar->opt_sumalg = CKSUM_NONE; + else if (strcmp(value, "sha1") == 0) + xar->opt_sumalg = CKSUM_SHA1; + else if (strcmp(value, "md5") == 0) + xar->opt_sumalg = CKSUM_MD5; + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unkonwn checksum name: `%s'", + value); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); + } + if (strcmp(key, "compression") == 0) { + const char *name = NULL; + + if (value == NULL) + xar->opt_compression = NONE; + else if (strcmp(value, "gzip") == 0) + xar->opt_compression = GZIP; + else if (strcmp(value, "bzip2") == 0) +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) + xar->opt_compression = BZIP2; +#else + name = "bzip2"; +#endif + else if (strcmp(value, "lzma") == 0) +#if HAVE_LZMA_H + xar->opt_compression = LZMA; +#else + name = "lzma"; +#endif + else if (strcmp(value, "xz") == 0) +#if HAVE_LZMA_H + xar->opt_compression = XZ; +#else + name = "xz"; +#endif + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unkonwn compression name: `%s'", + value); + return (ARCHIVE_FAILED); + } + if (name != NULL) { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "`%s' compression not supported " + "on this platform", + name); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); + } + if (strcmp(key, "compression-level") == 0) { + if (value == NULL || + !(value[0] >= '0' && value[0] <= '9') || + value[1] != '\0') { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Illeagal value `%s'", + value); + return (ARCHIVE_FAILED); + } + xar->opt_compression_level = value[0] - '0'; + return (ARCHIVE_OK); + } + if (strcmp(key, "toc-checksum") == 0) { + if (value == NULL) + xar->opt_toc_sumalg = CKSUM_NONE; + else if (strcmp(value, "sha1") == 0) + xar->opt_toc_sumalg = CKSUM_SHA1; + else if (strcmp(value, "md5") == 0) + xar->opt_toc_sumalg = CKSUM_MD5; + else { + archive_set_error(&(a->archive), + ARCHIVE_ERRNO_MISC, + "Unkonwn checksum name: `%s'", + value); + return (ARCHIVE_FAILED); + } + return (ARCHIVE_OK); + } + + return (ARCHIVE_FAILED); +} + +static int +xar_write_header(struct archive_write *a, struct archive_entry *entry) +{ + struct xar *xar; + struct file *file; + struct archive_entry *file_entry; + int r, r2; + + xar = (struct xar *)a->format_data; + xar->cur_file = NULL; + xar->bytes_remaining = 0; + + if (xar->sconv == NULL) { + xar->sconv = archive_string_conversion_to_charset( + &a->archive, "UTF-8", 1); + if (xar->sconv == NULL) + return (ARCHIVE_FATAL); + } + + file = file_new(a, entry); + if (file == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate data"); + return (ARCHIVE_FATAL); + } + r2 = file_gen_utility_names(a, file); + if (r2 < ARCHIVE_WARN) + return (r2); + + /* + * Ignore a path which looks like the top of directory name + * since we have already made the root directory of an Xar archive. + */ + if (archive_strlen(&(file->parentdir)) == 0 && + archive_strlen(&(file->basename)) == 0) { + file_free(file); + return (r2); + } + + /* Add entry into tree */ + file_entry = file->entry; + r = file_tree(a, &file); + if (r != ARCHIVE_OK) + return (r); + /* There is the same file in tree and + * the current file is older than the file in tree. + * So we don't need the current file data anymore. */ + if (file->entry != file_entry) + return (r2); + if (file->id == 0) + file_register(xar, file); + + /* A virtual file, which is a directory, does not have + * any contents and we won't store it into a archive + * file other than its name. */ + if (file->virtual) + return (r2); + + /* + * Prepare to save the contents of the file. + */ + if (xar->temp_fd == -1) { + int algsize; + xar->temp_offset = 0; + xar->temp_fd = __archive_mktemp(NULL); + if (xar->temp_fd < 0) { + archive_set_error(&a->archive, errno, + "Couldn't create temporary file"); + return (ARCHIVE_FATAL); + } + algsize = getalgsize(xar->opt_toc_sumalg); + if (algsize > 0) { + if (lseek(xar->temp_fd, algsize, SEEK_SET) < 0) { + archive_set_error(&(a->archive), errno, + "lseek failed"); + return (ARCHIVE_FATAL); + } + xar->temp_offset = algsize; + } + } + + if (archive_entry_hardlink(file->entry) == NULL) { + r = save_xattrs(a, file); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + + /* Non regular files contents are unneeded to be saved to + * a temporary file. */ + if (archive_entry_filetype(file->entry) != AE_IFREG) + return (r2); + + /* + * Set the current file to cur_file to read its contents. + */ + xar->cur_file = file; + + if (archive_entry_nlink(file->entry) > 1) { + r = file_register_hardlink(a, file); + if (r != ARCHIVE_OK) + return (r); + if (archive_entry_hardlink(file->entry) != NULL) { + archive_entry_unset_size(file->entry); + return (r2); + } + } + + /* Save a offset of current file in temporary file. */ + file->data.temp_offset = xar->temp_offset; + file->data.size = archive_entry_size(file->entry); + file->data.compression = xar->opt_compression; + xar->bytes_remaining = archive_entry_size(file->entry); + checksum_init(&(xar->a_sumwrk), xar->opt_sumalg); + checksum_init(&(xar->e_sumwrk), xar->opt_sumalg); + r = xar_compression_init_encoder(a); + + if (r != ARCHIVE_OK) + return (r); + else + return (r2); +} + +static int +write_to_temp(struct archive_write *a, const void *buff, size_t s) +{ + struct xar *xar; + unsigned char *p; + ssize_t ws; + + xar = (struct xar *)a->format_data; + p = (unsigned char *)buff; + while (s) { + ws = write(xar->temp_fd, p, s); + if (ws < 0) { + archive_set_error(&(a->archive), errno, + "fwrite function failed"); + return (ARCHIVE_FATAL); + } + s -= ws; + p += ws; + xar->temp_offset += ws; + } + return (ARCHIVE_OK); +} + +static ssize_t +xar_write_data(struct archive_write *a, const void *buff, size_t s) +{ + struct xar *xar; + enum la_zaction run; + size_t size, rsize; + int r; + + xar = (struct xar *)a->format_data; + + if (s > xar->bytes_remaining) + s = xar->bytes_remaining; + if (s == 0 || xar->cur_file == NULL) + return (0); + if (xar->cur_file->data.compression == NONE) { + checksum_update(&(xar->e_sumwrk), buff, s); + checksum_update(&(xar->a_sumwrk), buff, s); + size = rsize = s; + } else { + xar->stream.next_in = (const unsigned char *)buff; + xar->stream.avail_in = s; + if (xar->bytes_remaining > s) + run = ARCHIVE_Z_RUN; + else + run = ARCHIVE_Z_FINISH; + /* Compress file data. */ + r = compression_code(&(a->archive), &(xar->stream), run); + if (r != ARCHIVE_OK && r != ARCHIVE_EOF) + return (ARCHIVE_FATAL); + rsize = s - xar->stream.avail_in; + checksum_update(&(xar->e_sumwrk), buff, rsize); + size = sizeof(xar->wbuff) - xar->stream.avail_out; + checksum_update(&(xar->a_sumwrk), xar->wbuff, size); + } +#if !defined(_WIN32) || defined(__CYGWIN__) + if (xar->bytes_remaining == + archive_entry_size(xar->cur_file->entry)) { + /* + * Get the path of a shell script if so. + */ + const unsigned char *b = (const unsigned char *)buff; + + archive_string_empty(&(xar->cur_file->script)); + if (rsize > 2 && b[0] == '#' && b[1] == '!') { + char path[PATH_MAX]; + size_t i, end, off; + + end = sizeof(path); + if (end > rsize) + end = rsize; + off = 2; + if (b[off] == ' ') + off++; + for (i = off; i < end && b[i] != '\0' && + b[i] != '\n' && b[i] != '\r' && + b[i] != ' ' && b[i] != '\t'; i++) + path[i - off] = b[i]; + path[i - off] = '\0'; + archive_strcpy(&(xar->cur_file->script), path); + } + } +#endif + + if (xar->cur_file->data.compression == NONE) { + if (write_to_temp(a, buff, size) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } else { + if (write_to_temp(a, xar->wbuff, size) != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + xar->bytes_remaining -= rsize; + xar->cur_file->data.length += size; + + return (rsize); +} + +static int +xar_finish_entry(struct archive_write *a) +{ + struct xar *xar; + struct file *file; + size_t s; + ssize_t w; + + xar = (struct xar *)a->format_data; + if (xar->cur_file == NULL) + return (ARCHIVE_OK); + + while (xar->bytes_remaining > 0) { + s = xar->bytes_remaining; + if (s > a->null_length) + s = a->null_length; + w = xar_write_data(a, a->nulls, s); + if (w > 0) + xar->bytes_remaining -= w; + else + return (w); + } + file = xar->cur_file; + checksum_final(&(xar->e_sumwrk), &(file->data.e_sum)); + checksum_final(&(xar->a_sumwrk), &(file->data.a_sum)); + xar->cur_file = NULL; + + return (ARCHIVE_OK); +} + +static int +xmlwrite_string_attr(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, const char *value, + const char *attrkey, const char *attrvalue) +{ + int r; + + r = xmlTextWriterStartElement(writer, BAD_CAST(key)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + if (attrkey != NULL && attrvalue != NULL) { + r = xmlTextWriterWriteAttribute(writer, + BAD_CAST(attrkey), BAD_CAST(attrvalue)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + if (value != NULL) { + r = xmlTextWriterWriteString(writer, BAD_CAST(value)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteString() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static int +xmlwrite_string(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, const char *value) +{ + int r; + + if (value == NULL) + return (ARCHIVE_OK); + + r = xmlTextWriterStartElement(writer, BAD_CAST(key)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + if (value != NULL) { + r = xmlTextWriterWriteString(writer, BAD_CAST(value)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteString() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static int +xmlwrite_fstring(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, const char *fmt, ...) +{ + struct xar *xar; + va_list ap; + + xar = (struct xar *)a->format_data; + va_start(ap, fmt); + archive_string_empty(&xar->vstr); + archive_string_vsprintf(&xar->vstr, fmt, ap); + va_end(ap); + return (xmlwrite_string(a, writer, key, xar->vstr.s)); +} + +static int +xmlwrite_time(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, time_t t, int z) +{ + char timestr[100]; + struct tm tm; + +#if defined(HAVE_GMTIME_R) + gmtime_r(&t, &tm); +#elif defined(HAVE__GMTIME64_S) + _gmtime64_s(&tm, &t); +#else + memcpy(&tm, gmtime(&t), sizeof(tm)); +#endif + memset(×tr, 0, sizeof(timestr)); + /* Do not use %F and %T for portability. */ + strftime(timestr, sizeof(timestr), "%Y-%m-%dT%H:%M:%S", &tm); + if (z) + strcat(timestr, "Z"); + return (xmlwrite_string(a, writer, key, timestr)); +} + +static int +xmlwrite_mode(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, mode_t mode) +{ + char ms[5]; + + ms[0] = '0'; + ms[1] = '0' + ((mode >> 6) & 07); + ms[2] = '0' + ((mode >> 3) & 07); + ms[3] = '0' + (mode & 07); + ms[4] = '\0'; + + return (xmlwrite_string(a, writer, key, ms)); +} + +static int +xmlwrite_sum(struct archive_write *a, xmlTextWriterPtr writer, + const char *key, struct chksumval *sum) +{ + const char *algname; + int algsize; + char buff[MAX_SUM_SIZE*2 + 1]; + char *p; + unsigned char *s; + int i, r; + + if (sum->len > 0) { + algname = getalgname(sum->alg); + algsize = getalgsize(sum->alg); + if (algname != NULL) { + const char *hex = "0123456789abcdef"; + p = buff; + s = sum->val; + for (i = 0; i < algsize; i++) { + *p++ = hex[(*s >> 4)]; + *p++ = hex[(*s & 0x0f)]; + s++; + } + *p = '\0'; + r = xmlwrite_string_attr(a, writer, + key, buff, + "style", algname); + if (r < 0) + return (ARCHIVE_FATAL); + } + } + return (ARCHIVE_OK); +} + +static int +xmlwrite_heap(struct archive_write *a, xmlTextWriterPtr writer, + struct heap_data *heap) +{ + const char *encname; + int r; + + r = xmlwrite_fstring(a, writer, "length", "%ju", heap->length); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_fstring(a, writer, "offset", "%ju", heap->temp_offset); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_fstring(a, writer, "size", "%ju", heap->size); + if (r < 0) + return (ARCHIVE_FATAL); + switch (heap->compression) { + case GZIP: + encname = "application/x-gzip"; break; + case BZIP2: + encname = "application/x-bzip2"; break; + case LZMA: + encname = "application/x-lzma"; break; + case XZ: + encname = "application/x-xz"; break; + default: + encname = "application/octet-stream"; break; + } + r = xmlwrite_string_attr(a, writer, "encoding", NULL, + "style", encname); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_sum(a, writer, "archived-checksum", &(heap->a_sum)); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_sum(a, writer, "extracted-checksum", &(heap->e_sum)); + if (r < 0) + return (ARCHIVE_FATAL); + return (ARCHIVE_OK); +} + +/* + * xar utility records fflags as following xml elements: + * + * + * ..... + * + * or + * + * + * ..... + * + * If xar is running on BSD platform, records ..; + * if xar is running on linux platform, records ..; + * otherwise does not record. + * + * Our implements records both and if it's necessary. + */ +static int +make_fflags_entry(struct archive_write *a, xmlTextWriterPtr writer, + const char *element, const char *fflags_text) +{ + static const struct flagentry { + const char *name; + const char *xarname; + } + flagbsd[] = { + { "sappnd", "SystemAppend"}, + { "sappend", "SystemAppend"}, + { "arch", "SystemArchived"}, + { "archived", "SystemArchived"}, + { "schg", "SystemImmutable"}, + { "schange", "SystemImmutable"}, + { "simmutable", "SystemImmutable"}, + { "nosunlnk", "SystemNoUnlink"}, + { "nosunlink", "SystemNoUnlink"}, + { "snapshot", "SystemSnapshot"}, + { "uappnd", "UserAppend"}, + { "uappend", "UserAppend"}, + { "uchg", "UserImmutable"}, + { "uchange", "UserImmutable"}, + { "uimmutable", "UserImmutable"}, + { "nodump", "UserNoDump"}, + { "noopaque", "UserOpaque"}, + { "nouunlnk", "UserNoUnlink"}, + { "nouunlink", "UserNoUnlink"}, + { NULL, NULL} + }, + flagext2[] = { + { "sappnd", "AppendOnly"}, + { "sappend", "AppendOnly"}, + { "schg", "Immutable"}, + { "schange", "Immutable"}, + { "simmutable", "Immutable"}, + { "nodump", "NoDump"}, + { "nouunlnk", "Undelete"}, + { "nouunlink", "Undelete"}, + { "btree", "BTree"}, + { "comperr", "CompError"}, + { "compress", "Compress"}, + { "noatime", "NoAtime"}, + { "compdirty", "CompDirty"}, + { "comprblk", "CompBlock"}, + { "dirsync", "DirSync"}, + { "hashidx", "HashIndexed"}, + { "imagic", "iMagic"}, + { "journal", "Journaled"}, + { "securedeletion", "SecureDeletion"}, + { "sync", "Synchronous"}, + { "notail", "NoTail"}, + { "topdir", "TopDir"}, + { "reserved", "Reserved"}, + { NULL, NULL} + }; + const struct flagentry *fe, *flagentry; +#define FLAGENTRY_MAXSIZE ((sizeof(flagbsd)+sizeof(flagext2))/sizeof(flagbsd)) + const struct flagentry *avail[FLAGENTRY_MAXSIZE]; + const char *p; + int i, n, r; + + if (strcmp(element, "ext2") == 0) + flagentry = flagext2; + else + flagentry = flagbsd; + n = 0; + p = fflags_text; + do { + const char *cp; + + cp = strchr(p, ','); + if (cp == NULL) + cp = p + strlen(p); + + for (fe = flagentry; fe->name != NULL; fe++) { + if (fe->name[cp - p] != '\0' + || p[0] != fe->name[0]) + continue; + if (strncmp(p, fe->name, cp - p) == 0) { + avail[n++] = fe; + break; + } + } + if (*cp == ',') + p = cp + 1; + else + p = NULL; + } while (p != NULL); + + if (n > 0) { + r = xmlTextWriterStartElement(writer, BAD_CAST(element)); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + for (i = 0; i < n; i++) { + r = xmlwrite_string(a, writer, + avail[i]->xarname, NULL); + if (r != ARCHIVE_OK) + return (r); + } + + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + return (ARCHIVE_OK); +} + +static int +make_file_entry(struct archive_write *a, xmlTextWriterPtr writer, + struct file *file) +{ + struct xar *xar; + const char *filetype, *filelink, *fflags; + struct archive_string linkto; + struct heap_data *heap; + unsigned char *tmp; + const char *p; + size_t len; + int r, r2, l, ll; + + xar = (struct xar *)a->format_data; + r2 = ARCHIVE_OK; + + /* + * Make a file name entry, "". + */ + l = ll = archive_strlen(&(file->basename)); + tmp = malloc(l); + if (tmp == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + r = UTF8Toisolat1(tmp, &l, BAD_CAST(file->basename.s), &ll); + free(tmp); + if (r < 0) { + r = xmlTextWriterStartElement(writer, BAD_CAST("name")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlTextWriterWriteAttribute(writer, + BAD_CAST("enctype"), BAD_CAST("base64")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlTextWriterWriteBase64(writer, file->basename.s, + 0, archive_strlen(&(file->basename))); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteBase64() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + } else { + r = xmlwrite_string(a, writer, "name", file->basename.s); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a file type entry, "". + */ + filelink = NULL; + archive_string_init(&linkto); + switch (archive_entry_filetype(file->entry)) { + case AE_IFDIR: + filetype = "directory"; break; + case AE_IFLNK: + filetype = "symlink"; break; + case AE_IFCHR: + filetype = "character special"; break; + case AE_IFBLK: + filetype = "block special"; break; + case AE_IFSOCK: + filetype = "socket"; break; + case AE_IFIFO: + filetype = "fifo"; break; + case AE_IFREG: + default: + if (file->hardlink_target != NULL) { + filetype = "hardlink"; + filelink = "link"; + if (file->hardlink_target == file) + archive_strcpy(&linkto, "original"); + else + archive_string_sprintf(&linkto, "%d", + file->hardlink_target->id); + } else + filetype = "file"; + break; + } + r = xmlwrite_string_attr(a, writer, "type", filetype, + filelink, linkto.s); + archive_string_free(&linkto); + if (r < 0) + return (ARCHIVE_FATAL); + + /* + * On a virtual directory, we record "name" and "type" only. + */ + if (file->virtual) + return (ARCHIVE_OK); + + switch (archive_entry_filetype(file->entry)) { + case AE_IFLNK: + /* + * xar utility has checked a file type, which + * a symblic-link file has referenced. + * For example: + * ../ref/ + * The symlink target file is "../ref/" and its + * file type is a directory. + * + * ../f + * The symlink target file is "../f" and its + * file type is a regular file. + * + * But our implemention cannot do it, and then we + * always record that a attribute "type" is "borken", + * for example: + * foo/bar + * It means "foo/bar" is not reachable. + */ + r = xmlwrite_string_attr(a, writer, "link", + file->symlink.s, + "type", "broken"); + if (r < 0) + return (ARCHIVE_FATAL); + break; + case AE_IFCHR: + case AE_IFBLK: + r = xmlTextWriterStartElement(writer, BAD_CAST("device")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlwrite_fstring(a, writer, "major", + "%d", archive_entry_rdevmajor(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_fstring(a, writer, "minor", + "%d", archive_entry_rdevminor(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + break; + default: + break; + } + + /* + * Make a inode entry, "". + */ + r = xmlwrite_fstring(a, writer, "inode", + "%jd", archive_entry_ino64(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + if (archive_entry_dev(file->entry) != 0) { + r = xmlwrite_fstring(a, writer, "deviceno", + "%d", archive_entry_dev(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a file mode entry, "". + */ + r = xmlwrite_mode(a, writer, "mode", + archive_entry_mode(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + + /* + * Make a user entry, "" and ". + */ + r = xmlwrite_fstring(a, writer, "uid", + "%d", archive_entry_uid(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + r = archive_entry_uname_l(file->entry, &p, &len, xar->sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Uname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate uname '%s' to UTF-8", + archive_entry_uname(file->entry)); + r2 = ARCHIVE_WARN; + } + if (len > 0) { + r = xmlwrite_string(a, writer, "user", p); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a group entry, "" and ". + */ + r = xmlwrite_fstring(a, writer, "gid", + "%d", archive_entry_gid(file->entry)); + if (r < 0) + return (ARCHIVE_FATAL); + r = archive_entry_gname_l(file->entry, &p, &len, xar->sconv); + if (r != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Gname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate gname '%s' to UTF-8", + archive_entry_gname(file->entry)); + r2 = ARCHIVE_WARN; + } + if (len > 0) { + r = xmlwrite_string(a, writer, "group", p); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a ctime entry, "". + */ + if (archive_entry_ctime_is_set(file->entry)) { + r = xmlwrite_time(a, writer, "ctime", + archive_entry_ctime(file->entry), 1); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a mtime entry, "". + */ + if (archive_entry_mtime_is_set(file->entry)) { + r = xmlwrite_time(a, writer, "mtime", + archive_entry_mtime(file->entry), 1); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make a atime entry, "". + */ + if (archive_entry_atime_is_set(file->entry)) { + r = xmlwrite_time(a, writer, "atime", + archive_entry_atime(file->entry), 1); + if (r < 0) + return (ARCHIVE_FATAL); + } + + /* + * Make fflags entries, "" and "". + */ + fflags = archive_entry_fflags_text(file->entry); + if (fflags != NULL) { + r = make_fflags_entry(a, writer, "flags", fflags); + if (r < 0) + return (r); + r = make_fflags_entry(a, writer, "ext2", fflags); + if (r < 0) + return (r); + } + + /* + * Make extended attribute entries, "". + */ + archive_entry_xattr_reset(file->entry); + for (heap = file->xattr.first; heap != NULL; heap = heap->next) { + const char *name; + const void *value; + size_t size; + + archive_entry_xattr_next(file->entry, + &name, &value, &size); + r = xmlTextWriterStartElement(writer, BAD_CAST("ea")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlTextWriterWriteFormatAttribute(writer, + BAD_CAST("id"), "%d", heap->id); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() failed: %d", r); + return (ARCHIVE_FATAL); + } + r = xmlwrite_heap(a, writer, heap); + if (r < 0) + return (ARCHIVE_FATAL); + r = xmlwrite_string(a, writer, "name", name); + if (r < 0) + return (ARCHIVE_FATAL); + + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + + /* + * Make a file data entry, "". + */ + if (file->data.length > 0) { + r = xmlTextWriterStartElement(writer, BAD_CAST("data")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + + r = xmlwrite_heap(a, writer, &(file->data)); + if (r < 0) + return (ARCHIVE_FATAL); + + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + + if (archive_strlen(&file->script) > 0) { + r = xmlTextWriterStartElement(writer, BAD_CAST("content")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + + r = xmlwrite_string(a, writer, + "interpreter", file->script.s); + if (r < 0) + return (ARCHIVE_FATAL); + + r = xmlwrite_string(a, writer, "type", "script"); + if (r < 0) + return (ARCHIVE_FATAL); + + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + return (ARCHIVE_FATAL); + } + } + + return (r2); +} + +/* + * Make the TOC + */ +static int +make_toc(struct archive_write *a) +{ + struct xar *xar; + struct file *np; + xmlBufferPtr bp; + xmlTextWriterPtr writer; + int algsize; + int r, ret; + + xar = (struct xar *)a->format_data; + + ret = ARCHIVE_FATAL; + + /* + * Initialize xml writer. + */ + writer = NULL; + bp = xmlBufferCreate(); + if (bp == NULL) { + archive_set_error(&a->archive, ENOMEM, + "xmlBufferCreate() " + "couldn't create xml buffer"); + goto exit_toc; + } + writer = xmlNewTextWriterMemory(bp, 0); + if (writer == NULL) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlNewTextWriterMemory() " + "couldn't create xml writer"); + goto exit_toc; + } + r = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartDocument() failed: %d", r); + goto exit_toc; + } + r = xmlTextWriterSetIndent(writer, 4); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterSetIndent() failed: %d", r); + goto exit_toc; + } + + /* + * Start recoding TOC + */ + r = xmlTextWriterStartElement(writer, BAD_CAST("xar")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + goto exit_toc; + } + r = xmlTextWriterStartElement(writer, BAD_CAST("toc")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartDocument() failed: %d", r); + goto exit_toc; + } + + /* + * Record the creation time of the archive file. + */ + r = xmlwrite_time(a, writer, "creation-time", time(NULL), 0); + if (r < 0) + goto exit_toc; + + /* + * Record the checksum value of TOC + */ + algsize = getalgsize(xar->opt_toc_sumalg); + if (algsize) { + /* + * Record TOC checksum + */ + r = xmlTextWriterStartElement(writer, BAD_CAST("checksum")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() failed: %d", r); + goto exit_toc; + } + r = xmlTextWriterWriteAttribute(writer, BAD_CAST("style"), + BAD_CAST(getalgname(xar->opt_toc_sumalg))); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() failed: %d", r); + goto exit_toc; + } + + /* + * Record the offset of the value of checksum of TOC + */ + r = xmlwrite_string(a, writer, "offset", "0"); + if (r < 0) + goto exit_toc; + + /* + * Record the size of the value of checksum of TOC + */ + r = xmlwrite_fstring(a, writer, "size", "%d", algsize); + if (r < 0) + goto exit_toc; + + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() failed: %d", r); + goto exit_toc; + } + } + + np = xar->root; + do { + if (np != np->parent) { + r = make_file_entry(a, writer, np); + if (r != ARCHIVE_OK) + goto exit_toc; + } + + if (np->dir && np->children.first != NULL) { + /* Enter to sub directories. */ + np = np->children.first; + r = xmlTextWriterStartElement(writer, + BAD_CAST("file")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() " + "failed: %d", r); + goto exit_toc; + } + r = xmlTextWriterWriteFormatAttribute( + writer, BAD_CAST("id"), "%d", np->id); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() " + "failed: %d", r); + goto exit_toc; + } + continue; + } + while (np != np->parent) { + r = xmlTextWriterEndElement(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndElement() " + "failed: %d", r); + goto exit_toc; + } + if (np->chnext == NULL) { + /* Return to the parent directory. */ + np = np->parent; + } else { + np = np->chnext; + r = xmlTextWriterStartElement(writer, + BAD_CAST("file")); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterStartElement() " + "failed: %d", r); + goto exit_toc; + } + r = xmlTextWriterWriteFormatAttribute( + writer, BAD_CAST("id"), "%d", np->id); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterWriteAttribute() " + "failed: %d", r); + goto exit_toc; + } + break; + } + } + } while (np != np->parent); + + r = xmlTextWriterEndDocument(writer); + if (r < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "xmlTextWriterEndDocument() failed: %d", r); + goto exit_toc; + } +#if DEBUG_PRINT_TOC + fprintf(stderr, "\n---TOC-- %d bytes --\n%s\n", + strlen((const char *)bp->content), bp->content); +#endif + + /* + * Compress the TOC and calculate the sum of the TOC. + */ + xar->toc.temp_offset = xar->temp_offset; + xar->toc.size = bp->use; + checksum_init(&(xar->a_sumwrk), xar->opt_toc_sumalg); + + r = compression_init_encoder_gzip(&(a->archive), + &(xar->stream), 6, 1); + if (r != ARCHIVE_OK) + goto exit_toc; + xar->stream.next_in = bp->content; + xar->stream.avail_in = bp->use; + xar->stream.total_in = 0; + xar->stream.next_out = xar->wbuff; + xar->stream.avail_out = sizeof(xar->wbuff); + xar->stream.total_out = 0; + for (;;) { + size_t size; + + r = compression_code(&(a->archive), + &(xar->stream), ARCHIVE_Z_FINISH); + if (r != ARCHIVE_OK && r != ARCHIVE_EOF) + goto exit_toc; + size = sizeof(xar->wbuff) - xar->stream.avail_out; + checksum_update(&(xar->a_sumwrk), xar->wbuff, size); + if (write_to_temp(a, xar->wbuff, size) != ARCHIVE_OK) + goto exit_toc; + if (r == ARCHIVE_EOF) + break; + xar->stream.next_out = xar->wbuff; + xar->stream.avail_out = sizeof(xar->wbuff); + } + r = compression_end(&(a->archive), &(xar->stream)); + if (r != ARCHIVE_OK) + goto exit_toc; + xar->toc.length = xar->stream.total_out; + xar->toc.compression = GZIP; + checksum_final(&(xar->a_sumwrk), &(xar->toc.a_sum)); + + ret = ARCHIVE_OK; +exit_toc: + if (writer) + xmlFreeTextWriter(writer); + if (bp) + xmlBufferFree(bp); + + return (ret); +} + +static int +flush_wbuff(struct archive_write *a) +{ + struct xar *xar; + int r; + size_t s; + + xar = (struct xar *)a->format_data; + s = sizeof(xar->wbuff) - xar->wbuff_remaining; + r = __archive_write_output(a, xar->wbuff, s); + if (r != ARCHIVE_OK) + return (r); + xar->wbuff_remaining = sizeof(xar->wbuff); + return (r); +} + +static int +copy_out(struct archive_write *a, uint64_t offset, uint64_t length) +{ + struct xar *xar; + int r; + + xar = (struct xar *)a->format_data; + if (lseek(xar->temp_fd, offset, SEEK_SET) < 0) { + archive_set_error(&(a->archive), errno, "lseek failed"); + return (ARCHIVE_FATAL); + } + while (length) { + size_t rsize; + ssize_t rs; + unsigned char *wb; + + if (length > xar->wbuff_remaining) + rsize = xar->wbuff_remaining; + else + rsize = (size_t)length; + wb = xar->wbuff + (sizeof(xar->wbuff) - xar->wbuff_remaining); + rs = read(xar->temp_fd, wb, rsize); + if (rs < 0) { + archive_set_error(&(a->archive), errno, + "Can't read temporary file(%jd)", + (intmax_t)rs); + return (ARCHIVE_FATAL); + } + if (rs == 0) { + archive_set_error(&(a->archive), 0, + "Truncated xar archive"); + return (ARCHIVE_FATAL); + } + xar->wbuff_remaining -= rs; + length -= rs; + if (xar->wbuff_remaining == 0) { + r = flush_wbuff(a); + if (r != ARCHIVE_OK) + return (r); + } + } + return (ARCHIVE_OK); +} + +static int +xar_close(struct archive_write *a) +{ + struct xar *xar; + unsigned char *wb; + uint64_t length; + int r; + + xar = (struct xar *)a->format_data; + + /* Empty! */ + if (xar->root->children.first == NULL) + return (ARCHIVE_OK); + + /* Save the length of all file extended attributes and contents. */ + length = xar->temp_offset; + + /* Connect hardlinked files */ + file_connect_hardlink_files(xar); + + /* Make the TOC */ + r = make_toc(a); + if (r != ARCHIVE_OK) + return (r); + /* + * Make the xar header on wbuff(write buffer). + */ + wb = xar->wbuff; + xar->wbuff_remaining = sizeof(xar->wbuff); + archive_be32enc(&wb[0], HEADER_MAGIC); + archive_be16enc(&wb[4], HEADER_SIZE); + archive_be16enc(&wb[6], HEADER_VERSION); + archive_be64enc(&wb[8], xar->toc.length); + archive_be64enc(&wb[16], xar->toc.size); + archive_be32enc(&wb[24], xar->toc.a_sum.alg); + xar->wbuff_remaining -= HEADER_SIZE; + + /* + * Write the TOC + */ + r = copy_out(a, xar->toc.temp_offset, xar->toc.length); + if (r != ARCHIVE_OK) + return (r); + + /* Write the checksum value of the TOC. */ + if (xar->toc.a_sum.len) { + if (xar->wbuff_remaining < xar->toc.a_sum.len) { + r = flush_wbuff(a); + if (r != ARCHIVE_OK) + return (r); + } + wb = xar->wbuff + (sizeof(xar->wbuff) - xar->wbuff_remaining); + memcpy(wb, xar->toc.a_sum.val, xar->toc.a_sum.len); + xar->wbuff_remaining -= xar->toc.a_sum.len; + } + + /* + * Write all file extended attributes and contents. + */ + r = copy_out(a, xar->toc.a_sum.len, length); + if (r != ARCHIVE_OK) + return (r); + r = flush_wbuff(a); + return (r); +} + +static int +xar_free(struct archive_write *a) +{ + struct xar *xar; + + xar = (struct xar *)a->format_data; + archive_string_free(&(xar->cur_dirstr)); + archive_string_free(&(xar->tstr)); + archive_string_free(&(xar->vstr)); + file_free_hardlinks(xar); + file_free_register(xar); + compression_end(&(a->archive), &(xar->stream)); + free(xar); + + return (ARCHIVE_OK); +} + +static int +file_cmp_node(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + struct file *f1 = (struct file *)n1; + struct file *f2 = (struct file *)n2; + + return (strcmp(f1->basename.s, f2->basename.s)); +} + +static int +file_cmp_key(const struct archive_rb_node *n, const void *key) +{ + struct file *f = (struct file *)n; + + return (strcmp(f->basename.s, (const char *)key)); +} + +static struct file * +file_new(struct archive_write *a, struct archive_entry *entry) +{ + struct file *file; + static const struct archive_rb_tree_ops rb_ops = { + file_cmp_node, file_cmp_key + }; + + file = calloc(1, sizeof(*file)); + if (file == NULL) + return (NULL); + + if (entry != NULL) + file->entry = archive_entry_clone(entry); + else + file->entry = archive_entry_new2(&a->archive); + if (file->entry == NULL) { + free(file); + return (NULL); + } + __archive_rb_tree_init(&(file->rbtree), &rb_ops); + file->children.first = NULL; + file->children.last = &(file->children.first); + file->xattr.first = NULL; + file->xattr.last = &(file->xattr.first); + archive_string_init(&(file->parentdir)); + archive_string_init(&(file->basename)); + archive_string_init(&(file->symlink)); + archive_string_init(&(file->script)); + if (entry != NULL && archive_entry_filetype(entry) == AE_IFDIR) + file->dir = 1; + + return (file); +} + +static void +file_free(struct file *file) +{ + struct heap_data *heap, *next_heap; + + heap = file->xattr.first; + while (heap != NULL) { + next_heap = heap->next; + free(heap); + heap = next_heap; + } + archive_string_free(&(file->parentdir)); + archive_string_free(&(file->basename)); + archive_string_free(&(file->symlink)); + archive_string_free(&(file->script)); + free(file); +} + +static struct file * +file_create_virtual_dir(struct archive_write *a, struct xar *xar, + const char *pathname) +{ + struct file *file; + + file = file_new(a, NULL); + if (file == NULL) + return (NULL); + archive_entry_set_pathname(file->entry, pathname); + archive_entry_set_mode(file->entry, 0555 | AE_IFDIR); + + file->dir = 1; + file->virtual = 1; + + return (file); +} + +static int +file_add_child_tail(struct file *parent, struct file *child) +{ + if (!__archive_rb_tree_insert_node( + &(parent->rbtree), (struct archive_rb_node *)child)) + return (0); + child->chnext = NULL; + *parent->children.last = child; + parent->children.last = &(child->chnext); + child->parent = parent; + return (1); +} + +/* + * Find a entry from `parent' + */ +static struct file * +file_find_child(struct file *parent, const char *child_name) +{ + struct file *np; + + np = (struct file *)__archive_rb_tree_find_node( + &(parent->rbtree), child_name); + return (np); +} + +#if defined(_WIN32) || defined(__CYGWIN__) +static void +cleanup_backslash(char *utf8, size_t len) +{ + + /* Convert a path-separator from '\' to '/' */ + while (*utf8 != '\0' && len) { + if (*utf8 == '\\') + *utf8 = '/'; + ++utf8; + --len; + } +} +#else +#define cleanup_backslash(p, len) /* nop */ +#endif + +/* + * Generate a parent directory name and a base name from a pathname. + */ +static int +file_gen_utility_names(struct archive_write *a, struct file *file) +{ + struct xar *xar; + const char *pp; + char *p, *dirname, *slash; + size_t len; + int r = ARCHIVE_OK; + + xar = (struct xar *)a->format_data; + archive_string_empty(&(file->parentdir)); + archive_string_empty(&(file->basename)); + archive_string_empty(&(file->symlink)); + + if (file->parent == file)/* virtual root */ + return (ARCHIVE_OK); + + if (archive_entry_pathname_l(file->entry, &pp, &len, xar->sconv) + != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to UTF-8", + archive_entry_pathname(file->entry)); + r = ARCHIVE_WARN; + } + archive_strncpy(&(file->parentdir), pp, len); + len = file->parentdir.length; + p = dirname = file->parentdir.s; + /* + * Convert a path-separator from '\' to '/' + */ + cleanup_backslash(p, len); + + /* + * Remove leading '/', '../' and './' elements + */ + while (*p) { + if (p[0] == '/') { + p++; + len--; + } else if (p[0] != '.') + break; + else if (p[1] == '.' && p[2] == '/') { + p += 3; + len -= 3; + } else if (p[1] == '/' || (p[1] == '.' && p[2] == '\0')) { + p += 2; + len -= 2; + } else if (p[1] == '\0') { + p++; + len--; + } else + break; + } + if (p != dirname) { + memmove(dirname, p, len+1); + p = dirname; + } + /* + * Remove "/","/." and "/.." elements from tail. + */ + while (len > 0) { + size_t ll = len; + + if (len > 0 && p[len-1] == '/') { + p[len-1] = '\0'; + len--; + } + if (len > 1 && p[len-2] == '/' && p[len-1] == '.') { + p[len-2] = '\0'; + len -= 2; + } + if (len > 2 && p[len-3] == '/' && p[len-2] == '.' && + p[len-1] == '.') { + p[len-3] = '\0'; + len -= 3; + } + if (ll == len) + break; + } + while (*p) { + if (p[0] == '/') { + if (p[1] == '/') + /* Convert '//' --> '/' */ + strcpy(p, p+1); + else if (p[1] == '.' && p[2] == '/') + /* Convert '/./' --> '/' */ + strcpy(p, p+2); + else if (p[1] == '.' && p[2] == '.' && p[3] == '/') { + /* Convert 'dir/dir1/../dir2/' + * --> 'dir/dir2/' + */ + char *rp = p -1; + while (rp >= dirname) { + if (*rp == '/') + break; + --rp; + } + if (rp > dirname) { + strcpy(rp, p+3); + p = rp; + } else { + strcpy(dirname, p+4); + p = dirname; + } + } else + p++; + } else + p++; + } + p = dirname; + len = strlen(p); + + if (archive_entry_filetype(file->entry) == AE_IFLNK) { + size_t len2; + /* Convert symlink name too. */ + if (archive_entry_symlink_l(file->entry, &pp, &len2, + xar->sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Linkname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate symlink '%s' to UTF-8", + archive_entry_symlink(file->entry)); + r = ARCHIVE_WARN; + } + archive_strncpy(&(file->symlink), pp, len2); + cleanup_backslash(file->symlink.s, file->symlink.length); + } + /* + * - Count up directory elements. + * - Find out the position which points the last position of + * path separator('/'). + */ + slash = NULL; + for (; *p != '\0'; p++) + if (*p == '/') + slash = p; + if (slash == NULL) { + /* The pathname doesn't have a parent directory. */ + file->parentdir.length = len; + archive_string_copy(&(file->basename), &(file->parentdir)); + archive_string_empty(&(file->parentdir)); + file->parentdir.s = '\0'; + return (r); + } + + /* Make a basename from dirname and slash */ + *slash = '\0'; + file->parentdir.length = slash - dirname; + archive_strcpy(&(file->basename), slash + 1); + return (r); +} + +static int +get_path_component(char *name, int n, const char *fn) +{ + char *p; + int l; + + p = strchr(fn, '/'); + if (p == NULL) { + if ((l = strlen(fn)) == 0) + return (0); + } else + l = p - fn; + if (l > n -1) + return (-1); + memcpy(name, fn, l); + name[l] = '\0'; + + return (l); +} + +/* + * Add a new entry into the tree. + */ +static int +file_tree(struct archive_write *a, struct file **filepp) +{ +#if defined(_WIN32) && !defined(__CYGWIN__) + char name[_MAX_FNAME];/* Included null terminator size. */ +#elif defined(NAME_MAX) && NAME_MAX >= 255 + char name[NAME_MAX+1]; +#else + char name[256]; +#endif + struct xar *xar = (struct xar *)a->format_data; + struct file *dent, *file, *np; + struct archive_entry *ent; + const char *fn, *p; + int l; + + file = *filepp; + dent = xar->root; + if (file->parentdir.length > 0) + fn = p = file->parentdir.s; + else + fn = p = ""; + + /* + * If the path of the parent directory of `file' entry is + * the same as the path of `cur_dirent', add isoent to + * `cur_dirent'. + */ + if (archive_strlen(&(xar->cur_dirstr)) + == archive_strlen(&(file->parentdir)) && + strcmp(xar->cur_dirstr.s, fn) == 0) { + if (!file_add_child_tail(xar->cur_dirent, file)) { + np = (struct file *)__archive_rb_tree_find_node( + &(xar->cur_dirent->rbtree), + file->basename.s); + goto same_entry; + } + return (ARCHIVE_OK); + } + + for (;;) { + l = get_path_component(name, sizeof(name), fn); + if (l == 0) { + np = NULL; + break; + } + if (l < 0) { + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "A name buffer is too small"); + file_free(file); + *filepp = NULL; + return (ARCHIVE_FATAL); + } + + np = file_find_child(dent, name); + if (np == NULL || fn[0] == '\0') + break; + + /* Find next subdirectory. */ + if (!np->dir) { + /* NOT Directory! */ + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "`%s' is not directory, we cannot insert `%s' ", + archive_entry_pathname(np->entry), + archive_entry_pathname(file->entry)); + file_free(file); + *filepp = NULL; + return (ARCHIVE_FAILED); + } + fn += l; + if (fn[0] == '/') + fn++; + dent = np; + } + if (np == NULL) { + /* + * Create virtual parent directories. + */ + while (fn[0] != '\0') { + struct file *vp; + struct archive_string as; + + archive_string_init(&as); + archive_strncat(&as, p, fn - p + l); + if (as.s[as.length-1] == '/') { + as.s[as.length-1] = '\0'; + as.length--; + } + vp = file_create_virtual_dir(a, xar, as.s); + if (vp == NULL) { + archive_string_free(&as); + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + file_free(file); + *filepp = NULL; + return (ARCHIVE_FATAL); + } + archive_string_free(&as); + if (file_gen_utility_names(a, vp) <= ARCHIVE_FAILED) + return (ARCHIVE_FATAL); + file_add_child_tail(dent, vp); + file_register(xar, vp); + np = vp; + + fn += l; + if (fn[0] == '/') + fn++; + l = get_path_component(name, sizeof(name), fn); + if (l < 0) { + archive_string_free(&as); + archive_set_error(&a->archive, + ARCHIVE_ERRNO_MISC, + "A name buffer is too small"); + file_free(file); + *filepp = NULL; + return (ARCHIVE_FATAL); + } + dent = np; + } + + /* Found out the parent directory where isoent can be + * inserted. */ + xar->cur_dirent = dent; + archive_string_empty(&(xar->cur_dirstr)); + archive_string_ensure(&(xar->cur_dirstr), + archive_strlen(&(dent->parentdir)) + + archive_strlen(&(dent->basename)) + 2); + if (archive_strlen(&(dent->parentdir)) + + archive_strlen(&(dent->basename)) == 0) + xar->cur_dirstr.s[0] = 0; + else { + if (archive_strlen(&(dent->parentdir)) > 0) { + archive_string_copy(&(xar->cur_dirstr), + &(dent->parentdir)); + archive_strappend_char(&(xar->cur_dirstr), '/'); + } + archive_string_concat(&(xar->cur_dirstr), + &(dent->basename)); + } + + if (!file_add_child_tail(dent, file)) { + np = (struct file *)__archive_rb_tree_find_node( + &(dent->rbtree), file->basename.s); + goto same_entry; + } + return (ARCHIVE_OK); + } + +same_entry: + /* + * We have already has the entry the filename of which is + * the same. + */ + if (archive_entry_filetype(np->entry) != + archive_entry_filetype(file->entry)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Found duplicate entries `%s' and its file type is " + "different", + archive_entry_pathname(np->entry)); + file_free(file); + *filepp = NULL; + return (ARCHIVE_FAILED); + } + + /* Swap files. */ + ent = np->entry; + np->entry = file->entry; + file->entry = ent; + np->virtual = 0; + + file_free(file); + *filepp = np; + return (ARCHIVE_OK); +} + +static void +file_register(struct xar *xar, struct file *file) +{ + file->id = xar->file_idx++; + file->next = NULL; + *xar->file_list.last = file; + xar->file_list.last = &(file->next); +} + +static void +file_init_register(struct xar *xar) +{ + xar->file_list.first = NULL; + xar->file_list.last = &(xar->file_list.first); +} + +static void +file_free_register(struct xar *xar) +{ + struct file *file, *file_next; + + file = xar->file_list.first; + while (file != NULL) { + file_next = file->next; + file_free(file); + file = file_next; + } +} + +/* + * Register entry to get a hardlink target. + */ +static int +file_register_hardlink(struct archive_write *a, struct file *file) +{ + struct xar *xar = (struct xar *)a->format_data; + struct hardlink *hl; + const char *pathname; + + archive_entry_set_nlink(file->entry, 1); + pathname = archive_entry_hardlink(file->entry); + if (pathname == NULL) { + /* This `file` is a hardlink target. */ + hl = malloc(sizeof(*hl)); + if (hl == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory"); + return (ARCHIVE_FATAL); + } + hl->nlink = 1; + /* A hardlink target must be the first position. */ + file->hlnext = NULL; + hl->file_list.first = file; + hl->file_list.last = &(file->hlnext); + __archive_rb_tree_insert_node(&(xar->hardlink_rbtree), + (struct archive_rb_node *)hl); + } else { + hl = (struct hardlink *)__archive_rb_tree_find_node( + &(xar->hardlink_rbtree), pathname); + if (hl != NULL) { + /* Insert `file` entry into the tail. */ + file->hlnext = NULL; + *hl->file_list.last = file; + hl->file_list.last = &(file->hlnext); + hl->nlink++; + } + archive_entry_unset_size(file->entry); + } + + return (ARCHIVE_OK); +} + +/* + * Hardlinked files have to have the same location of extent. + * We have to find out hardlink target entries for entries which + * have a hardlink target name. + */ +static void +file_connect_hardlink_files(struct xar *xar) +{ + struct archive_rb_node *n; + struct hardlink *hl; + struct file *target, *nf; + + ARCHIVE_RB_TREE_FOREACH(n, &(xar->hardlink_rbtree)) { + hl = (struct hardlink *)n; + + /* The first entry must be a hardlink target. */ + target = hl->file_list.first; + archive_entry_set_nlink(target->entry, hl->nlink); + if (hl->nlink > 1) + /* It means this file is a hardlink + * targe itself. */ + target->hardlink_target = target; + for (nf = target->hlnext; + nf != NULL; nf = nf->hlnext) { + nf->hardlink_target = target; + archive_entry_set_nlink(nf->entry, hl->nlink); + } + } +} + +static int +file_hd_cmp_node(const struct archive_rb_node *n1, + const struct archive_rb_node *n2) +{ + struct hardlink *h1 = (struct hardlink *)n1; + struct hardlink *h2 = (struct hardlink *)n2; + + return (strcmp(archive_entry_pathname(h1->file_list.first->entry), + archive_entry_pathname(h2->file_list.first->entry))); +} + +static int +file_hd_cmp_key(const struct archive_rb_node *n, const void *key) +{ + struct hardlink *h = (struct hardlink *)n; + + return (strcmp(archive_entry_pathname(h->file_list.first->entry), + (const char *)key)); +} + + +static void +file_init_hardlinks(struct xar *xar) +{ + static const struct archive_rb_tree_ops rb_ops = { + file_hd_cmp_node, file_hd_cmp_key, + }; + + __archive_rb_tree_init(&(xar->hardlink_rbtree), &rb_ops); +} + +static void +file_free_hardlinks(struct xar *xar) +{ + struct archive_rb_node *n, *next; + + for (n = ARCHIVE_RB_TREE_MIN(&(xar->hardlink_rbtree)); n;) { + next = __archive_rb_tree_iterate(&(xar->hardlink_rbtree), + n, ARCHIVE_RB_DIR_RIGHT); + free(n); + n = next; + } +} + +static void +checksum_init(struct chksumwork *sumwrk, enum sumalg sum_alg) +{ + sumwrk->alg = sum_alg; + switch (sum_alg) { + case CKSUM_NONE: + break; + case CKSUM_SHA1: + archive_sha1_init(&(sumwrk->sha1ctx)); + break; + case CKSUM_MD5: + archive_md5_init(&(sumwrk->md5ctx)); + break; + } +} + +static void +checksum_update(struct chksumwork *sumwrk, const void *buff, size_t size) +{ + + switch (sumwrk->alg) { + case CKSUM_NONE: + break; + case CKSUM_SHA1: + archive_sha1_update(&(sumwrk->sha1ctx), buff, size); + break; + case CKSUM_MD5: + archive_md5_update(&(sumwrk->md5ctx), buff, size); + break; + } +} + +static void +checksum_final(struct chksumwork *sumwrk, struct chksumval *sumval) +{ + + switch (sumwrk->alg) { + case CKSUM_NONE: + sumval->len = 0; + break; + case CKSUM_SHA1: + archive_sha1_final(&(sumwrk->sha1ctx), sumval->val); + sumval->len = SHA1_SIZE; + break; + case CKSUM_MD5: + archive_md5_final(&(sumwrk->md5ctx), sumval->val); + sumval->len = MD5_SIZE; + break; + } + sumval->alg = sumwrk->alg; +} + +#if !defined(HAVE_BZLIB_H) || !defined(BZ_CONFIG_ERROR) || !defined(HAVE_LZMA_H) +static int +compression_unsupported_encoder(struct archive *a, + struct la_zstream *lastrm, const char *name) +{ + + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "%s compression not supported on this platform", name); + lastrm->valid = 0; + lastrm->real_stream = NULL; + return (ARCHIVE_FAILED); +} +#endif + +static int +compression_init_encoder_gzip(struct archive *a, + struct la_zstream *lastrm, int level, int withheader) +{ + z_stream *strm; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for gzip stream"); + return (ARCHIVE_FATAL); + } + /* zlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + if (deflateInit2(strm, level, Z_DEFLATED, + (withheader)?15:-15, + 8, Z_DEFAULT_STRATEGY) != Z_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_gzip; + lastrm->end = compression_end_gzip; + return (ARCHIVE_OK); +} + +static int +compression_code_gzip(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + z_stream *strm; + int r; + + strm = (z_stream *)lastrm->real_stream; + /* zlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (Bytef *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + r = deflate(strm, + (action == ARCHIVE_Z_FINISH)? Z_FINISH: Z_NO_FLUSH); + lastrm->next_in = strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = strm->total_in; + lastrm->next_out = strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = strm->total_out; + switch (r) { + case Z_OK: + return (ARCHIVE_OK); + case Z_STREAM_END: + return (ARCHIVE_EOF); + default: + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "GZip compression failed:" + " deflate() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_gzip(struct archive *a, struct la_zstream *lastrm) +{ + z_stream *strm; + int r; + + strm = (z_stream *)lastrm->real_stream; + r = deflateEnd(strm); + free(strm); + lastrm->real_stream = NULL; + lastrm->valid = 0; + if (r != Z_OK) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +#if defined(HAVE_BZLIB_H) && defined(BZ_CONFIG_ERROR) +static int +compression_init_encoder_bzip2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + bz_stream *strm; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for bzip2 stream"); + return (ARCHIVE_FATAL); + } + /* bzlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); + strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); + strm->next_out = (char *)lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); + strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); + if (BZ2_bzCompressInit(strm, level, 0, 30) != BZ_OK) { + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_bzip2; + lastrm->end = compression_end_bzip2; + return (ARCHIVE_OK); +} + +static int +compression_code_bzip2(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + bz_stream *strm; + int r; + + strm = (bz_stream *)lastrm->real_stream; + /* bzlib.h is not const-correct, so we need this one bit + * of ugly hackery to convert a const * pointer to + * a non-const pointer. */ + strm->next_in = (char *)(uintptr_t)(const void *)lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in_lo32 = (uint32_t)(lastrm->total_in & 0xffffffff); + strm->total_in_hi32 = (uint32_t)(lastrm->total_in >> 32); + strm->next_out = (char *)lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out_lo32 = (uint32_t)(lastrm->total_out & 0xffffffff); + strm->total_out_hi32 = (uint32_t)(lastrm->total_out >> 32); + r = BZ2_bzCompress(strm, + (action == ARCHIVE_Z_FINISH)? BZ_FINISH: BZ_RUN); + lastrm->next_in = (const unsigned char *)strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = + (((uint64_t)(uint32_t)strm->total_in_hi32) << 32) + + (uint64_t)(uint32_t)strm->total_in_lo32; + lastrm->next_out = (unsigned char *)strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = + (((uint64_t)(uint32_t)strm->total_out_hi32) << 32) + + (uint64_t)(uint32_t)strm->total_out_lo32; + switch (r) { + case BZ_RUN_OK: /* Non-finishing */ + case BZ_FINISH_OK: /* Finishing: There's more work to do */ + return (ARCHIVE_OK); + case BZ_STREAM_END: /* Finishing: all done */ + /* Only occurs in finishing case */ + return (ARCHIVE_EOF); + default: + /* Any other return value indicates an error */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Bzip2 compression failed:" + " BZ2_bzCompress() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_bzip2(struct archive *a, struct la_zstream *lastrm) +{ + bz_stream *strm; + int r; + + strm = (bz_stream *)lastrm->real_stream; + r = BZ2_bzCompressEnd(strm); + free(strm); + lastrm->real_stream = NULL; + lastrm->valid = 0; + if (r != BZ_OK) { + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Failed to clean up compressor"); + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +#else +static int +compression_init_encoder_bzip2(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "bzip2")); +} +#endif + +#if defined(HAVE_LZMA_H) +static int +compression_init_encoder_lzma(struct archive *a, + struct la_zstream *lastrm, int level) +{ + static const lzma_stream lzma_init_data = LZMA_STREAM_INIT; + lzma_stream *strm; + lzma_options_lzma lzma_opt; + int r; + + if (lastrm->valid) + compression_end(a, lastrm); + if (lzma_lzma_preset(&lzma_opt, level)) { + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + strm = calloc(1, sizeof(*strm)); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for lzma stream"); + return (ARCHIVE_FATAL); + } + *strm = lzma_init_data; + r = lzma_alone_encoder(strm, &lzma_opt); + switch (r) { + case LZMA_OK: + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_lzma; + lastrm->end = compression_end_lzma; + r = ARCHIVE_OK; + break; + case LZMA_MEM_ERROR: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library: " + "Cannot allocate memory"); + r = ARCHIVE_FATAL; + break; + default: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "It's a bug in liblzma"); + r = ARCHIVE_FATAL; + break; + } + return (r); +} + +static int +compression_init_encoder_xz(struct archive *a, + struct la_zstream *lastrm, int level) +{ + static const lzma_stream lzma_init_data = LZMA_STREAM_INIT; + lzma_stream *strm; + lzma_filter *lzmafilters; + lzma_options_lzma lzma_opt; + int r; + + if (lastrm->valid) + compression_end(a, lastrm); + strm = calloc(1, sizeof(*strm) + sizeof(*lzmafilters) * 2); + if (strm == NULL) { + archive_set_error(a, ENOMEM, + "Can't allocate memory for xz stream"); + return (ARCHIVE_FATAL); + } + lzmafilters = (lzma_filter *)(strm+1); + if (level > 6) + level = 6; + if (lzma_lzma_preset(&lzma_opt, level)) { + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library"); + return (ARCHIVE_FATAL); + } + lzmafilters[0].id = LZMA_FILTER_LZMA2; + lzmafilters[0].options = &lzma_opt; + lzmafilters[1].id = LZMA_VLI_UNKNOWN;/* Terminate */ + + *strm = lzma_init_data; + r = lzma_stream_encoder(strm, lzmafilters, LZMA_CHECK_CRC64); + switch (r) { + case LZMA_OK: + lastrm->real_stream = strm; + lastrm->valid = 1; + lastrm->code = compression_code_lzma; + lastrm->end = compression_end_lzma; + r = ARCHIVE_OK; + break; + case LZMA_MEM_ERROR: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ENOMEM, + "Internal error initializing compression library: " + "Cannot allocate memory"); + r = ARCHIVE_FATAL; + break; + default: + free(strm); + lastrm->real_stream = NULL; + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "Internal error initializing compression library: " + "It's a bug in liblzma"); + r = ARCHIVE_FATAL; + break; + } + return (r); +} + +static int +compression_code_lzma(struct archive *a, + struct la_zstream *lastrm, enum la_zaction action) +{ + lzma_stream *strm; + int r; + + strm = (lzma_stream *)lastrm->real_stream; + strm->next_in = lastrm->next_in; + strm->avail_in = lastrm->avail_in; + strm->total_in = lastrm->total_in; + strm->next_out = lastrm->next_out; + strm->avail_out = lastrm->avail_out; + strm->total_out = lastrm->total_out; + r = lzma_code(strm, + (action == ARCHIVE_Z_FINISH)? LZMA_FINISH: LZMA_RUN); + lastrm->next_in = strm->next_in; + lastrm->avail_in = strm->avail_in; + lastrm->total_in = strm->total_in; + lastrm->next_out = strm->next_out; + lastrm->avail_out = strm->avail_out; + lastrm->total_out = strm->total_out; + switch (r) { + case LZMA_OK: + /* Non-finishing case */ + return (ARCHIVE_OK); + case LZMA_STREAM_END: + /* This return can only occur in finishing case. */ + return (ARCHIVE_EOF); + case LZMA_MEMLIMIT_ERROR: + archive_set_error(a, ENOMEM, + "lzma compression error:" + " %ju MiB would have been needed", + (uintmax_t)((lzma_memusage(strm) + 1024 * 1024 -1) + / (1024 * 1024))); + return (ARCHIVE_FATAL); + default: + /* Any other return value indicates an error */ + archive_set_error(a, ARCHIVE_ERRNO_MISC, + "lzma compression failed:" + " lzma_code() call returned status %d", r); + return (ARCHIVE_FATAL); + } +} + +static int +compression_end_lzma(struct archive *a, struct la_zstream *lastrm) +{ + lzma_stream *strm; + + (void)a; /* UNUSED */ + strm = (lzma_stream *)lastrm->real_stream; + lzma_end(strm); + free(strm); + lastrm->valid = 0; + lastrm->real_stream = NULL; + return (ARCHIVE_OK); +} +#else +static int +compression_init_encoder_lzma(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "lzma")); +} +static int +compression_init_encoder_xz(struct archive *a, + struct la_zstream *lastrm, int level) +{ + + (void) level; /* UNUSED */ + if (lastrm->valid) + compression_end(a, lastrm); + return (compression_unsupported_encoder(a, lastrm, "xz")); +} +#endif + +static int +xar_compression_init_encoder(struct archive_write *a) +{ + struct xar *xar; + int r; + + xar = (struct xar *)a->format_data; + switch (xar->opt_compression) { + case GZIP: + r = compression_init_encoder_gzip( + &(a->archive), &(xar->stream), + xar->opt_compression_level, 1); + break; + case BZIP2: + r = compression_init_encoder_bzip2( + &(a->archive), &(xar->stream), + xar->opt_compression_level); + break; + case LZMA: + r = compression_init_encoder_lzma( + &(a->archive), &(xar->stream), + xar->opt_compression_level); + break; + case XZ: + r = compression_init_encoder_xz( + &(a->archive), &(xar->stream), + xar->opt_compression_level); + break; + default: + r = ARCHIVE_OK; + break; + } + if (r == ARCHIVE_OK) { + xar->stream.total_in = 0; + xar->stream.next_out = xar->wbuff; + xar->stream.avail_out = sizeof(xar->wbuff); + xar->stream.total_out = 0; + } + + return (r); +} + +static int +compression_code(struct archive *a, struct la_zstream *lastrm, + enum la_zaction action) +{ + if (lastrm->valid) + return (lastrm->code(a, lastrm, action)); + return (ARCHIVE_OK); +} + +static int +compression_end(struct archive *a, struct la_zstream *lastrm) +{ + if (lastrm->valid) + return (lastrm->end(a, lastrm)); + return (ARCHIVE_OK); +} + + +static int +save_xattrs(struct archive_write *a, struct file *file) +{ + struct xar *xar; + const char *name; + const void *value; + struct heap_data *heap; + size_t size; + int count, r; + + xar = (struct xar *)a->format_data; + count = archive_entry_xattr_reset(file->entry); + if (count == 0) + return (ARCHIVE_OK); + while (count--) { + archive_entry_xattr_next(file->entry, + &name, &value, &size); + checksum_init(&(xar->a_sumwrk), xar->opt_sumalg); + checksum_init(&(xar->e_sumwrk), xar->opt_sumalg); + + heap = calloc(1, sizeof(*heap)); + if (heap == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for xattr"); + return (ARCHIVE_FATAL); + } + heap->id = file->ea_idx++; + heap->temp_offset = xar->temp_offset; + heap->size = size;/* save a extracted size */ + heap->compression = xar->opt_compression; + /* Get a extracted sumcheck value. */ + checksum_update(&(xar->e_sumwrk), value, size); + checksum_final(&(xar->e_sumwrk), &(heap->e_sum)); + + /* + * Not compression to xattr is simple way. + */ + if (heap->compression == NONE) { + checksum_update(&(xar->a_sumwrk), value, size); + checksum_final(&(xar->a_sumwrk), &(heap->a_sum)); + if (write_to_temp(a, value, size) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + heap->length = size; + /* Add heap to the tail of file->xattr. */ + heap->next = NULL; + *file->xattr.last = heap; + file->xattr.last = &(heap->next); + /* Next xattr */ + continue; + } + + /* + * Init compression library. + */ + r = xar_compression_init_encoder(a); + if (r != ARCHIVE_OK) { + free(heap); + return (ARCHIVE_FATAL); + } + + xar->stream.next_in = (const unsigned char *)value; + xar->stream.avail_in = size; + for (;;) { + r = compression_code(&(a->archive), + &(xar->stream), ARCHIVE_Z_FINISH); + if (r != ARCHIVE_OK && r != ARCHIVE_EOF) { + free(heap); + return (ARCHIVE_FATAL); + } + size = sizeof(xar->wbuff) - xar->stream.avail_out; + checksum_update(&(xar->a_sumwrk), + xar->wbuff, size); + if (write_to_temp(a, xar->wbuff, size) + != ARCHIVE_OK) + return (ARCHIVE_FATAL); + if (r == ARCHIVE_OK) { + xar->stream.next_out = xar->wbuff; + xar->stream.avail_out = sizeof(xar->wbuff); + } else { + checksum_final(&(xar->a_sumwrk), + &(heap->a_sum)); + heap->length = xar->stream.total_out; + /* Add heap to the tail of file->xattr. */ + heap->next = NULL; + *file->xattr.last = heap; + file->xattr.last = &(heap->next); + break; + } + } + /* Clean up compression library. */ + r = compression_end(&(a->archive), &(xar->stream)); + if (r != ARCHIVE_OK) + return (ARCHIVE_FATAL); + } + return (ARCHIVE_OK); +} + +static int +getalgsize(enum sumalg sumalg) +{ + switch (sumalg) { + default: + case CKSUM_NONE: + return (0); + case CKSUM_SHA1: + return (SHA1_SIZE); + case CKSUM_MD5: + return (MD5_SIZE); + } +} + +static const char * +getalgname(enum sumalg sumalg) +{ + switch (sumalg) { + default: + case CKSUM_NONE: + return (NULL); + case CKSUM_SHA1: + return (SHA1_NAME); + case CKSUM_MD5: + return (MD5_NAME); + } +} + +#endif /* Support xar format */ + diff --git a/libarchive/archive_write_set_format_zip.c b/libarchive/archive_write_set_format_zip.c new file mode 100644 index 0000000..588c7f4 --- /dev/null +++ b/libarchive/archive_write_set_format_zip.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 2008 Anselm Strauss + * Copyright (c) 2009 Joerg Sonnenberger + * Copyright (c) 2011 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + */ + +/* + * Development supported by Google Summer of Code 2008. + */ + +/* + * The current implementation is very limited: + * + * - No encryption support. + * - No ZIP64 support. + * - No support for splitting and spanning. + * - Only supports regular file and folder entries. + * + * Note that generally data in ZIP files is little-endian encoded, + * with some exceptions. + * + * TODO: Since Libarchive is generally 64bit oriented, but this implementation + * does not yet support sizes exceeding 32bit, it is highly fragile for + * big archives. This should change when ZIP64 is finally implemented, otherwise + * some serious checking has to be done. + * + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD: head/lib/libarchive/archive_write_set_format_zip.c 201168 2009-12-29 06:15:32Z kientzle $"); + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_LANGINFO_H +#include +#endif +#ifdef HAVE_STDLIB_H +#include +#endif +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_ZLIB_H +#include +#endif + +#include "archive.h" +#include "archive_endian.h" +#include "archive_entry.h" +#include "archive_entry_locale.h" +#include "archive_private.h" +#include "archive_write_private.h" + +#ifndef HAVE_ZLIB_H +#include "archive_crc32.h" +#endif + +#define ZIP_SIGNATURE_LOCAL_FILE_HEADER 0x04034b50 +#define ZIP_SIGNATURE_DATA_DESCRIPTOR 0x08074b50 +#define ZIP_SIGNATURE_FILE_HEADER 0x02014b50 +#define ZIP_SIGNATURE_CENTRAL_DIRECTORY_END 0x06054b50 +#define ZIP_SIGNATURE_EXTRA_TIMESTAMP 0x5455 +#define ZIP_SIGNATURE_EXTRA_NEW_UNIX 0x7875 +#define ZIP_VERSION_EXTRACT 0x0014 /* ZIP version 2.0 is needed. */ +#define ZIP_VERSION_BY 0x0314 /* Made by UNIX, using ZIP version 2.0. */ +#define ZIP_FLAGS 0x08 /* Flagging bit 3 (count from 0) for using data descriptor. */ +#define ZIP_FLAGS_UTF8_NAME (1 << 11) + +enum compression { + COMPRESSION_STORE = 0 +#ifdef HAVE_ZLIB_H + , + COMPRESSION_DEFLATE = 8 +#endif +}; + +static ssize_t archive_write_zip_data(struct archive_write *, const void *buff, size_t s); +static int archive_write_zip_close(struct archive_write *); +static int archive_write_zip_free(struct archive_write *); +static int archive_write_zip_finish_entry(struct archive_write *); +static int archive_write_zip_header(struct archive_write *, struct archive_entry *); +static int archive_write_zip_options(struct archive_write *, + const char *, const char *); +static unsigned int dos_time(const time_t); +static size_t path_length(struct archive_entry *); +static int write_path(struct archive_entry *, struct archive_write *); + +struct zip_local_file_header { + char signature[4]; + char version[2]; + char flags[2]; + char compression[2]; + char timedate[4]; + char crc32[4]; + char compressed_size[4]; + char uncompressed_size[4]; + char filename_length[2]; + char extra_length[2]; +}; + +struct zip_file_header { + char signature[4]; + char version_by[2]; + char version_extract[2]; + char flags[2]; + char compression[2]; + char timedate[4]; + char crc32[4]; + char compressed_size[4]; + char uncompressed_size[4]; + char filename_length[2]; + char extra_length[2]; + char comment_length[2]; + char disk_number[2]; + char attributes_internal[2]; + char attributes_external[4]; + char offset[4]; +}; + +struct zip_data_descriptor { + char signature[4]; /* Not mandatory, but recommended by specification. */ + char crc32[4]; + char compressed_size[4]; + char uncompressed_size[4]; +}; + +struct zip_extra_data_local { + char time_id[2]; + char time_size[2]; + char time_flag[1]; + char mtime[4]; + char atime[4]; + char ctime[4]; + char unix_id[2]; + char unix_size[2]; + char unix_version; + char unix_uid_size; + char unix_uid[4]; + char unix_gid_size; + char unix_gid[4]; +}; + +struct zip_extra_data_central { + char time_id[2]; + char time_size[2]; + char time_flag[1]; + char mtime[4]; + char unix_id[2]; + char unix_size[2]; +}; + +struct zip_file_header_link { + struct zip_file_header_link *next; + struct archive_entry *entry; + int64_t offset; + unsigned long crc32; + int64_t compressed_size; + enum compression compression; + int flags; +}; + +struct zip { + struct zip_data_descriptor data_descriptor; + struct zip_file_header_link *central_directory; + struct zip_file_header_link *central_directory_end; + int64_t offset; + int64_t written_bytes; + int64_t remaining_data_bytes; + enum compression compression; + int flags; + struct archive_string_conv *opt_sconv; + struct archive_string_conv *sconv_default; + int init_default_conversion; + +#ifdef HAVE_ZLIB_H + z_stream stream; + size_t len_buf; + unsigned char *buf; +#endif +}; + +struct zip_central_directory_end { + char signature[4]; + char disk[2]; + char start_disk[2]; + char entries_disk[2]; + char entries[2]; + char size[4]; + char offset[4]; + char comment_length[2]; +}; + +static int +archive_write_zip_options(struct archive_write *a, const char *key, + const char *val) +{ + struct zip *zip = a->format_data; + int ret = ARCHIVE_FAILED; + + if (strcmp(key, "compression") == 0) { + if (val == NULL || val[0] == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: compression option needs a compression name", + a->format_name); + } else if (strcmp(val, "deflate") == 0) { +#ifdef HAVE_ZLIB_H + zip->compression = COMPRESSION_DEFLATE; + ret = ARCHIVE_OK; +#else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "deflate compression not supported"); +#endif + } else if (strcmp(val, "store") == 0) { + zip->compression = COMPRESSION_STORE; + ret = ARCHIVE_OK; + } + } else if (strcmp(key, "hdrcharset") == 0) { + if (val == NULL || val[0] == 0) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: hdrcharset option needs a character-set name", + a->format_name); + } else { + zip->opt_sconv = archive_string_conversion_to_charset( + &a->archive, val, 0); + if (zip->opt_sconv != NULL) + ret = ARCHIVE_OK; + else + ret = ARCHIVE_FATAL; + } + } else + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "%s: unknown keyword ``%s''", a->format_name, key); + return (ret); +} + +int +archive_write_set_format_zip(struct archive *_a) +{ + struct archive_write *a = (struct archive_write *)_a; + struct zip *zip; + + archive_check_magic(_a, ARCHIVE_WRITE_MAGIC, + ARCHIVE_STATE_NEW, "archive_write_set_format_zip"); + + /* If another format was already registered, unregister it. */ + if (a->format_free != NULL) + (a->format_free)(a); + + zip = (struct zip *) calloc(1, sizeof(*zip)); + if (zip == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate zip data"); + return (ARCHIVE_FATAL); + } + zip->central_directory = NULL; + zip->central_directory_end = NULL; + zip->offset = 0; + zip->written_bytes = 0; + zip->remaining_data_bytes = 0; + +#ifdef HAVE_ZLIB_H + zip->compression = COMPRESSION_DEFLATE; + zip->len_buf = 65536; + zip->buf = malloc(zip->len_buf); + if (zip->buf == NULL) { + archive_set_error(&a->archive, ENOMEM, "Can't allocate compression buffer"); + return (ARCHIVE_FATAL); + } +#else + zip->compression = COMPRESSION_STORE; +#endif + + a->format_data = zip; + a->format_name = "zip"; + a->format_options = archive_write_zip_options; + a->format_write_header = archive_write_zip_header; + a->format_write_data = archive_write_zip_data; + a->format_finish_entry = archive_write_zip_finish_entry; + a->format_close = archive_write_zip_close; + a->format_free = archive_write_zip_free; + a->archive.archive_format = ARCHIVE_FORMAT_ZIP; + a->archive.archive_format_name = "ZIP"; + + archive_le32enc(&zip->data_descriptor.signature, + ZIP_SIGNATURE_DATA_DESCRIPTOR); + + return (ARCHIVE_OK); +} + +static int +is_all_ascii(const char *p) +{ + const unsigned char *pp = (const unsigned char *)p; + + while (*pp) { + if (*pp++ > 127) + return (0); + } + return (1); +} + +static int +archive_write_zip_header(struct archive_write *a, struct archive_entry *entry) +{ + struct zip *zip; + struct zip_local_file_header h; + struct zip_extra_data_local e; + struct zip_data_descriptor *d; + struct zip_file_header_link *l; + struct archive_string_conv *sconv; + int ret, ret2 = ARCHIVE_OK; + int64_t size; + mode_t type; + + /* Entries other than a regular file or a folder are skipped. */ + type = archive_entry_filetype(entry); + if ((type != AE_IFREG) & (type != AE_IFDIR)) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Filetype not supported"); + return ARCHIVE_FAILED; + }; + + /* Directory entries should have a size of 0. */ + if (type == AE_IFDIR) + archive_entry_set_size(entry, 0); + + zip = a->format_data; + /* Setup default conversion. */ + if (zip->opt_sconv == NULL && !zip->init_default_conversion) { + zip->sconv_default = + archive_string_default_conversion_for_write(&(a->archive)); + zip->init_default_conversion = 1; + } + + if (zip->flags == 0) { + /* Initialize the general purpose flags. */ + zip->flags = ZIP_FLAGS; + if (zip->opt_sconv != NULL) { + if (strcmp(archive_string_conversion_charset_name( + zip->opt_sconv), "UTF-8") == 0) + zip->flags |= ZIP_FLAGS_UTF8_NAME; +#if HAVE_NL_LANGINFO + } else if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0) { + zip->flags |= ZIP_FLAGS_UTF8_NAME; +#endif + } + } + d = &zip->data_descriptor; + size = archive_entry_size(entry); + zip->remaining_data_bytes = size; + + /* Append archive entry to the central directory data. */ + l = (struct zip_file_header_link *) malloc(sizeof(*l)); + if (l == NULL) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate zip header data"); + return (ARCHIVE_FATAL); + } + l->entry = archive_entry_clone(entry); + l->flags = zip->flags; + if (zip->opt_sconv != NULL) + sconv = zip->opt_sconv; + else + sconv = zip->sconv_default; + if (sconv != NULL) { + const char *p; + size_t len; + + if (archive_entry_pathname_l(entry, &p, &len, sconv) != 0) { + if (errno == ENOMEM) { + archive_set_error(&a->archive, ENOMEM, + "Can't allocate memory for Pathname"); + return (ARCHIVE_FATAL); + } + archive_set_error(&a->archive, + ARCHIVE_ERRNO_FILE_FORMAT, + "Can't translate pathname '%s' to %s", + archive_entry_pathname(entry), + archive_string_conversion_charset_name(sconv)); + ret2 = ARCHIVE_WARN; + } + if (len > 0) + archive_entry_set_pathname(l->entry, p); + } + /* If all character of a filename is ASCII, Reset UTF-8 Name flag. */ + if ((l->flags & ZIP_FLAGS_UTF8_NAME) != 0 && + is_all_ascii(archive_entry_pathname(l->entry))) + l->flags &= ~ZIP_FLAGS_UTF8_NAME; + + /* Initialize the CRC variable and potentially the local crc32(). */ + l->crc32 = crc32(0, NULL, 0); + l->compression = zip->compression; + l->compressed_size = 0; + l->next = NULL; + if (zip->central_directory == NULL) { + zip->central_directory = l; + } else { + zip->central_directory_end->next = l; + } + zip->central_directory_end = l; + + /* Store the offset of this header for later use in central directory. */ + l->offset = zip->written_bytes; + + memset(&h, 0, sizeof(h)); + archive_le32enc(&h.signature, ZIP_SIGNATURE_LOCAL_FILE_HEADER); + archive_le16enc(&h.version, ZIP_VERSION_EXTRACT); + archive_le16enc(&h.flags, l->flags); + archive_le16enc(&h.compression, zip->compression); + archive_le32enc(&h.timedate, dos_time(archive_entry_mtime(entry))); + archive_le16enc(&h.filename_length, (uint16_t)path_length(l->entry)); + + switch (zip->compression) { + case COMPRESSION_STORE: + /* Setting compressed and uncompressed sizes even when specification says + * to set to zero when using data descriptors. Otherwise the end of the + * data for an entry is rather difficult to find. */ + archive_le32enc(&h.compressed_size, size); + archive_le32enc(&h.uncompressed_size, size); + break; +#ifdef HAVE_ZLIB_H + case COMPRESSION_DEFLATE: + archive_le32enc(&h.uncompressed_size, size); + + zip->stream.zalloc = Z_NULL; + zip->stream.zfree = Z_NULL; + zip->stream.opaque = Z_NULL; + zip->stream.next_out = zip->buf; + zip->stream.avail_out = zip->len_buf; + if (deflateInit2(&zip->stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -15, 8, Z_DEFAULT_STRATEGY) != Z_OK) { + archive_set_error(&a->archive, ENOMEM, + "Can't init deflate compressor"); + return (ARCHIVE_FATAL); + } + break; +#endif + } + + /* Formatting extra data. */ + archive_le16enc(&h.extra_length, sizeof(e)); + archive_le16enc(&e.time_id, ZIP_SIGNATURE_EXTRA_TIMESTAMP); + archive_le16enc(&e.time_size, sizeof(e.time_flag) + + sizeof(e.mtime) + sizeof(e.atime) + sizeof(e.ctime)); + e.time_flag[0] = 0x07; + archive_le32enc(&e.mtime, archive_entry_mtime(entry)); + archive_le32enc(&e.atime, archive_entry_atime(entry)); + archive_le32enc(&e.ctime, archive_entry_ctime(entry)); + + archive_le16enc(&e.unix_id, ZIP_SIGNATURE_EXTRA_NEW_UNIX); + archive_le16enc(&e.unix_size, sizeof(e.unix_version) + + sizeof(e.unix_uid_size) + sizeof(e.unix_uid) + + sizeof(e.unix_gid_size) + sizeof(e.unix_gid)); + e.unix_version = 1; + e.unix_uid_size = 4; + archive_le32enc(&e.unix_uid, archive_entry_uid(entry)); + e.unix_gid_size = 4; + archive_le32enc(&e.unix_gid, archive_entry_gid(entry)); + + archive_le32enc(&d->uncompressed_size, size); + + ret = __archive_write_output(a, &h, sizeof(h)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(h); + + ret = write_path(l->entry, a); + if (ret <= ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += ret; + + ret = __archive_write_output(a, &e, sizeof(e)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(e); + + if (ret2 != ARCHIVE_OK) + return (ret2); + return (ARCHIVE_OK); +} + +static ssize_t +archive_write_zip_data(struct archive_write *a, const void *buff, size_t s) +{ + int ret; + struct zip *zip = a->format_data; + struct zip_file_header_link *l = zip->central_directory_end; + + if ((int64_t)s > zip->remaining_data_bytes) + s = (size_t)zip->remaining_data_bytes; + + if (s == 0) return 0; + + switch (zip->compression) { + case COMPRESSION_STORE: + ret = __archive_write_output(a, buff, s); + if (ret != ARCHIVE_OK) return (ret); + zip->written_bytes += s; + zip->remaining_data_bytes -= s; + l->compressed_size += s; + l->crc32 = crc32(l->crc32, buff, s); + return (s); +#if HAVE_ZLIB_H + case COMPRESSION_DEFLATE: + zip->stream.next_in = (unsigned char*)(uintptr_t)buff; + zip->stream.avail_in = s; + do { + ret = deflate(&zip->stream, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) + return (ARCHIVE_FATAL); + if (zip->stream.avail_out == 0) { + ret = __archive_write_output(a, zip->buf, zip->len_buf); + if (ret != ARCHIVE_OK) + return (ret); + l->compressed_size += zip->len_buf; + zip->written_bytes += zip->len_buf; + zip->stream.next_out = zip->buf; + zip->stream.avail_out = zip->len_buf; + } + } while (zip->stream.avail_in != 0); + zip->remaining_data_bytes -= s; + /* If we have it, use zlib's fast crc32() */ + l->crc32 = crc32(l->crc32, buff, s); + return (s); +#endif + + default: + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "Invalid ZIP compression type"); + return ARCHIVE_FATAL; + } +} + +static int +archive_write_zip_finish_entry(struct archive_write *a) +{ + /* Write the data descripter after file data has been written. */ + int ret; + struct zip *zip = a->format_data; + struct zip_data_descriptor *d = &zip->data_descriptor; + struct zip_file_header_link *l = zip->central_directory_end; +#if HAVE_ZLIB_H + size_t reminder; +#endif + + switch(zip->compression) { + case COMPRESSION_STORE: + break; +#if HAVE_ZLIB_H + case COMPRESSION_DEFLATE: + for (;;) { + ret = deflate(&zip->stream, Z_FINISH); + if (ret == Z_STREAM_ERROR) + return (ARCHIVE_FATAL); + reminder = zip->len_buf - zip->stream.avail_out; + ret = __archive_write_output(a, zip->buf, reminder); + if (ret != ARCHIVE_OK) + return (ret); + l->compressed_size += reminder; + zip->written_bytes += reminder; + zip->stream.next_out = zip->buf; + if (zip->stream.avail_out != 0) + break; + zip->stream.avail_out = zip->len_buf; + } + deflateEnd(&zip->stream); + break; +#endif + } + + archive_le32enc(&d->crc32, l->crc32); + archive_le32enc(&d->compressed_size, l->compressed_size); + ret = __archive_write_output(a, d, sizeof(*d)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(*d); + return (ARCHIVE_OK); +} + +static int +archive_write_zip_close(struct archive_write *a) +{ + struct zip *zip; + struct zip_file_header_link *l; + struct zip_file_header h; + struct zip_central_directory_end end; + struct zip_extra_data_central e; + int64_t offset_start, offset_end; + int entries; + int ret; + + zip = a->format_data; + l = zip->central_directory; + + /* + * Formatting central directory file header fields that are fixed for all entries. + * Fields not used (and therefor 0) are: + * + * - comment_length + * - disk_number + * - attributes_internal + */ + memset(&h, 0, sizeof(h)); + archive_le32enc(&h.signature, ZIP_SIGNATURE_FILE_HEADER); + archive_le16enc(&h.version_by, ZIP_VERSION_BY); + archive_le16enc(&h.version_extract, ZIP_VERSION_EXTRACT); + + entries = 0; + offset_start = zip->written_bytes; + + /* Formatting individual header fields per entry and + * writing each entry. */ + while (l != NULL) { + archive_le16enc(&h.flags, l->flags); + archive_le16enc(&h.compression, l->compression); + archive_le32enc(&h.timedate, dos_time(archive_entry_mtime(l->entry))); + archive_le32enc(&h.crc32, l->crc32); + archive_le32enc(&h.compressed_size, l->compressed_size); + archive_le32enc(&h.uncompressed_size, archive_entry_size(l->entry)); + archive_le16enc(&h.filename_length, (uint16_t)path_length(l->entry)); + archive_le16enc(&h.extra_length, sizeof(e)); + archive_le16enc(&h.attributes_external[2], archive_entry_mode(l->entry)); + archive_le32enc(&h.offset, l->offset); + + /* Formatting extra data. */ + archive_le16enc(&e.time_id, ZIP_SIGNATURE_EXTRA_TIMESTAMP); + archive_le16enc(&e.time_size, sizeof(e.mtime) + sizeof(e.time_flag)); + e.time_flag[0] = 0x07; + archive_le32enc(&e.mtime, archive_entry_mtime(l->entry)); + archive_le16enc(&e.unix_id, ZIP_SIGNATURE_EXTRA_NEW_UNIX); + archive_le16enc(&e.unix_size, 0x0000); + + ret = __archive_write_output(a, &h, sizeof(h)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(h); + + ret = write_path(l->entry, a); + if (ret <= ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += ret; + + ret = __archive_write_output(a, &e, sizeof(e)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(e); + + l = l->next; + entries++; + } + offset_end = zip->written_bytes; + + /* Formatting end of central directory. */ + memset(&end, 0, sizeof(end)); + archive_le32enc(&end.signature, ZIP_SIGNATURE_CENTRAL_DIRECTORY_END); + archive_le16enc(&end.entries_disk, entries); + archive_le16enc(&end.entries, entries); + archive_le32enc(&end.size, offset_end - offset_start); + archive_le32enc(&end.offset, offset_start); + + /* Writing end of central directory. */ + ret = __archive_write_output(a, &end, sizeof(end)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + zip->written_bytes += sizeof(end); + return (ARCHIVE_OK); +} + +static int +archive_write_zip_free(struct archive_write *a) +{ + struct zip *zip; + struct zip_file_header_link *l; + + zip = a->format_data; + while (zip->central_directory != NULL) { + l = zip->central_directory; + zip->central_directory = l->next; + archive_entry_free(l->entry); + free(l); + } +#ifdef HAVE_ZLIB_H + free(zip->buf); +#endif + free(zip); + a->format_data = NULL; + return (ARCHIVE_OK); +} + +/* Convert into MSDOS-style date/time. */ +static unsigned int +dos_time(const time_t unix_time) +{ + struct tm *t; + unsigned int dt; + + /* This will not preserve time when creating/extracting the archive + * on two systems with different time zones. */ + t = localtime(&unix_time); + + /* MSDOS-style date/time is only between 1980-01-01 and 2107-12-31 */ + if (t->tm_year < 1980 - 1900) + /* Set minimum date/time '1980-01-01 00:00:00'. */ + dt = 0x00210000U; + else if (t->tm_year > 2107 - 1900) + /* Set maximum date/time '2107-12-31 23:59:58'. */ + dt = 0xff9fbf7dU; + else { + dt = 0; + dt += ((t->tm_year - 80) & 0x7f) << 9; + dt += ((t->tm_mon + 1) & 0x0f) << 5; + dt += (t->tm_mday & 0x1f); + dt <<= 16; + dt += (t->tm_hour & 0x1f) << 11; + dt += (t->tm_min & 0x3f) << 5; + dt += (t->tm_sec & 0x3e) >> 1; /* Only counting every 2 seconds. */ + } + return dt; +} + +static size_t +path_length(struct archive_entry *entry) +{ + mode_t type; + const char *path; + + type = archive_entry_filetype(entry); + path = archive_entry_pathname(entry); + + if ((type == AE_IFDIR) & (path[strlen(path) - 1] != '/')) { + return strlen(path) + 1; + } else { + return strlen(path); + } +} + +static int +write_path(struct archive_entry *entry, struct archive_write *archive) +{ + int ret; + const char *path; + mode_t type; + size_t written_bytes; + + path = archive_entry_pathname(entry); + type = archive_entry_filetype(entry); + written_bytes = 0; + + ret = __archive_write_output(archive, path, strlen(path)); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + written_bytes += strlen(path); + + /* Folders are recognized by a traling slash. */ + if ((type == AE_IFDIR) & (path[strlen(path) - 1] != '/')) { + ret = __archive_write_output(archive, "/", 1); + if (ret != ARCHIVE_OK) + return (ARCHIVE_FATAL); + written_bytes += 1; + } + + return ((int)written_bytes); +} diff --git a/libarchive/archive_write_set_options.3 b/libarchive/archive_write_set_options.3 new file mode 100644 index 0000000..7e5cf21 --- /dev/null +++ b/libarchive/archive_write_set_options.3 @@ -0,0 +1,437 @@ +.\" Copyright (c) 2003-2010 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/archive_write.3 201110 2009-12-28 03:31:29Z kientzle $ +.\" +.Dd Feb 27, 2010 +.Dt archive_write_options 3 +.Os +.Sh NAME +.Nm archive_write_set_filter_option , +.Nm archive_write_set_format_option , +.Nm archive_write_set_option , +.Nm archive_write_set_options +.Nd functions controlling options for reading archives +.Sh SYNOPSIS +.\" +.Sh SYNOPSIS +.Ft int +.Fo archive_write_set_filter_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_write_set_format_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_write_set_option +.Fa "struct archive *" +.Fa "const char *module" +.Fa "const char *option" +.Fa "const char *value" +.Fc +.Ft int +.Fo archive_write_set_options +.Fa "struct archive *" +.Fa "const char *options" +.Fc +.Sh DESCRIPTION +These functions provide a way for libarchive clients to configure +specific write modules. +.Bl -tag -width indent +.It Xo +.Fn archive_write_set_filter_option , +.Fn archive_write_set_format_option +.Xc +Specifies an option that will be passed to currently-registered +filters (including decompression filters) or format readers. +.Pp +If +.Ar option +and +.Ar value +are both +.Dv NULL , +these functions will do nothing and +.Cm ARCHIVE_OK +will be returned. +If +.Ar option +is +.Dv NULL +but +.Ar value +is not, these functions will do nothing and +.Cm ARCHIVE_FAILED +will be returned. +.Pp +If +.Ar module +is not +.Dv NULL , +.Ar option +and +.Ar value +will be provided to the filter or reader named +.Ar module . +The return value will be that of the module. +If there is no such module, +.Cm ARCHIVE_FAILED +will be returned. +.Pp +If +.Ar module +is +.Dv NULL , +.Ar option +and +.Ar value +will be provided to every registered module. +If any module returns +.Cm ARCHIVE_FATAL , +this value will be returned immediately. +Otherwise, +.Cm ARCHIVE_OK +will be returned if any module accepts the option, and +.Cm ARCHIVE_FAILED +in all other cases. +.\" +.It Xo +.Fn archive_write_set_option +.Xc +Calls +.Fn archive_write_set_format_option , +then +.Fn archive_write_set_filter_option . +If either function returns +.Cm ARCHIVE_FATAL , +.Cm ARCHIVE_FATAL +will be returned +immediately. +Otherwise, greater of the two values will be returned. +.\" +.It Xo +.Fn archive_write_set_options +.Xc +.Ar options +is a comma-separated list of options. +If +.Ar options +is +.Dv NULL +or empty, +.Cm ARCHIVE_OK +will be returned immediately. +.Pp +Individual options have one of the following forms: +.Bl -tag -compact -width indent +.It Ar option=value +The option/value pair will be provided to every module. +Modules that do not accept an option with this name will ignore it. +.It Ar option +The option will be provided to every module with a value of +.Dq 1 . +.It Ar !option +The option will be provided to every module with a NULL value. +.It Ar module:option=value , Ar module:option , Ar module:!option +As above, but the corresponding option and value will be provided +only to modules whose name matches +.Ar module . +.El +.El +.\" +.Sh OPTIONS +.Bl -tag -compact -width indent +.It Filter gzip +.Bl -tag -compact -width indent +.It Cm compression-level +The value is interpreted as a decimal integer specifying the +gzip compression level. +.El +.It Filter xz +.Bl -tag -compact -width indent +.It Cm compression-level +The value is interpreted as a decimal integer specifying the +compression level. +.El +.It Format mtree +.Bl -tag -compact -width indent +.It Cm cksum , Cm device , Cm flags , Cm gid , Cm gname , Cm indent , Cm link , Cm md5 , Cm mode , Cm nlink , Cm rmd160 , Cm sha1 , Cm sha256 , Cm sha384 , Cm sha512 , Cm size , Cm time , Cm uid , Cm uname +Enable a particular keyword in the mtree output. +Prefix with an exclamation mark to disable the corresponding keyword. +The default is equivalent to +.Dq device, flags, gid, gname, link, mode, nlink, size, time, type, uid, uname . +.It Cm all +Enables all of the above keywords. +.It Cm use-set +Enables generation of +.Cm /set +lines that specify default values for the following files and/or directories. +.It Cm indent +XXX needs explanation XXX +.El +.It Format iso9660 - volume metadata +These options are used to set standard ISO9660 metadata. +.Bl -tag -compact -width indent +.It Cm abstract-file Ns = Ns Ar filename +The file with the specified name will be identified in the ISO9660 metadata +as holding the abstract for this volume. Default: none. +.It Cm application-id Ns = Ns Ar filename +The file with the specified name will be identified in the ISO9660 metadata +as holding the application identifier for this volume. Default: none. +.It Cm biblio-file Ns = Ns Ar filename +The file with the specified name will be identified in the ISO9660 metadata +as holding the bibliography for this volume. Default: none. +.It Cm copyright-file Ns = Ns Ar filename +The file with the specified name will be identified in the ISO9660 metadata +as holding the copyright for this volume. Default: none. +.It Cm publisher Ns = Ns Ar filename +The file with the specified name will be identified in the ISO9660 metadata +as holding the publisher information for this volume. Default: none. +.It Cm volume-id Ns = Ns Ar string +The specified string will be used as the Volume Identifier in the ISO9660 metadata. +It is limited to 32 bytes. Default: none. +.El +.It Format iso9660 - boot support +These options are used to make an ISO9660 image that can be directly +booted on various systems. +.Bl -tag -compact -width indent +.It Cm boot Ns = Ns Ar filename +The file matching this name will be used as the El Torito boot image file. +.It Cm boot-catalog Ns = Ns Ar name +The name that will be used for the El Torito boot catalog. +Default: +.Ar boot.catalog +.It Cm boot-info-table +The boot image file provided by the +.Cm boot Ns = Ns Ar filename +option will be edited with appropriate boot information in bytes 8 through 64. +Default: disabled +.It Cm boot-load-seg Ns = Ns Ar hexadecimal-number +The load segment for a no-emulation boot image. +.It Cm boot-load-size Ns = Ns Ar decimal-number +The number of "virtual" 512-byte sectors to be loaded from a no-emulation boot image. +Some very old BIOSes can only load very small images, setting this +value to 4 will often allow such BIOSes to load the first part of +the boot image (which will then need to be intelligent enough to +load the rest of itself). +This should not be needed unless you are trying to support systems with very old BIOSes. +This defaults to the full size of the image. +.It Cm boot-type Ns = Ns Ar value +Specifies the boot semantics used by the El Torito boot image: +If the +.Ar value +is +.Cm fd , +then the boot image is assumed to be a bootable floppy image. +If the +.Ar value +is +.Cm hd , +then the the boot image is assumed to be a bootable hard disk image. +If the +.Ar value +is +.Cm no-emulation , +the boot image is used without floppy or hard disk emulation. +If the boot image is exactly 1.2MB, 1.44MB, or 2.88MB, then +the default is +.Cm fd , +otherwise the default is +.Cm no-emulation. +.El +.It Format iso9660 - filename and size extensions +Various extensions to the base ISO9660 format. +.Bl -tag -compact -width indent +.It Cm allow-ldots +If enabled, allows filenames to begin with a leading period. +If disabled, filenames that begin with a leading period will have +that period replaced by an underscore character in the standard ISO9660 +namespace. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: disabled. +.It Cm allow-lowercase +If enabled, allows filenames to contain lowercase characters. +If disabled, filenames will be forced to uppercase. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: disabled. +.It Cm allow-multidot +If enabled, allows filenames to contain multiple period characters, in violation of the ISO9660 specification. +If disabled, additional periods will be converted to underscore characters. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: disabled. +.It Cm allow-period +If enabled, allows filenames to contain trailing period characters, in violation of the ISO9660 specification. +If disabled,trailing periods will be converted to underscore characters. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: disabled. +.It Cm allow-pvd-lowercase +If enabled, the Primary Volume Descriptor may contain lowercase ASCII characters, in violation of the ISO9660 specification. +If disabled, characters will be converted to uppercase ASCII. +Default: disabled. +.It Cm allow-sharp-tilde +If enabled, sharp and tilde characters will be permitted in filenames, in violation if the ISO9660 specification. +If disabled, such characters will be converted to underscore characters. +Default: disabled. +.It Cm allow-vernum +If enabled, version numbers will be included with files. +If disabled, version numbers will be suppressed, in violation of the ISO9660 standard. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: enabled. +.It Cm iso-level +This enables support for file size and file name extensions in the +core ISO9660 area. +The name extensions specified here do not affect the names stored in the Rockridge or Joliet extension areas. +.Bl -tag -compact -width indent +.It Cm iso-level=1 +The most compliant form of ISO9660 image. +Filenames are limited to 8.3 uppercase format, +directory names are limited to 8 uppercase characters, +files are limited to 4 GiB, +the complete ISO9660 image cannot exceed 4 GiB. +.It Cm iso-level=2 +Filenames are limited to 30 uppercase characters with a 30-character extension, +directory names are limited to 30 characters, +files are limited to 4 GiB. +.It Cm iso-level=3 +As with +.Cm iso-level=2 , +except that files may exceed 4 GiB. +.It Cm iso-level=4 +As with +.Cm iso-level=3 , +except that filenames may be up to 193 characters +and may include arbitrary 8-bit characters. +.El +.It Cm joliet +Microsoft's Joliet extensions store a completely separate set of directory information about each file. +In particular, this information includes Unicode filenames of up to 255 characters. +Default: enabled. +.It Cm limit-depth +If enabled, libarchive will use directory relocation records to ensure that +no pathname exceeds the ISO9660 limit of 8 directory levels. +If disabled, no relocation will occur. +Default: enabled. +.It Cm limit-dirs +If enabled, libarchive will cause an error if there are more than +65536 directories. +If disabled, there is no limit on the number of directories. +Default: enabled +.It Cm pad +If enabled, 300 kiB of zero bytes will be appended to the end of the archive. +Default: enabled +.It Cm relaxed-filenames +If enabled, all 7-bit ASCII characters are permitted in filenames +(except lowercase characters unless +.Cm allow-lowercase +is also specified). +This violates ISO9660 standards. +This does not impact names stored in the Rockridge or Joliet extension area. +Default: disabled. +.It Cm rockridge +The Rockridge extensions store an additional set of POSIX-style file +information with each file, including mtime, atime, ctime, permissions, +and long filenames with arbitrary 8-bit characters. +These extensions also support symbolic links and other POSIX file types. +Default: enabled. +.El +.It Format iso9660 - zisofs support +The zisofs extensions permit each file to be independently compressed +using a gzip-compatible compression. +This can provide significant size savings, but requires the reading +system to have support for these extensions. +These extensions are disabled by default. +.Bl -tag -compact -width indent +.It Cm compression-level Ns = Ns number +The compression level used by the deflate compressor. +Ranges from 0 (least effort) to 9 (most effort). +Default: 6 +.It Cm zisofs +Synonym for +.Cm zisofs=direct . +.It Cm zisofs=direct +Compress each file in the archive. +Unlike +.Cm zisofs=indirect , +this is handled entirely within libarchive and does not require a +separate utility. +For best results, libarchive tests each file and will store +the file uncompressed if the compression does not actually save any space. +In particular, files under 2k will never be compressed. +Note that boot image files are never compressed. +.It Cm zisofs=indirect +Recognizes files that have already been compressed with the +.Cm mkzftree +utility and sets up the necessary file metadata so that +readers will correctly identify these as zisofs-compressed files. +.It Cm zisofs-exclude Ns = Ns Ar filename +Specifies a filename that should not be compressed when using +.Cm zisofs=direct . +This option can be provided multiple times to suppress compression +on many files. +.El +.El +.Sh EXAMPLES +The following example creates an archive write handle to +create a gzip-compressed ISO9660 format image. +The two options here specify that the ISO9660 archive will use +.Ar kernel.img +as the boot image for El Torito booting, and that the gzip +compressor should use the maximum compression level. +.Bd -literal -offset indent +a = archive_write_new(); +archive_write_add_filter_gzip(a); +archive_write_set_format_iso9660(a); +archive_write_set_options(a, "boot=kernel.img,compression=9"); +archive_write_open_filename(a, filename, blocksize); +.Ed +.\" +.Sh ERRORS +Detailed error codes and textual descriptions are available from the +.Fn archive_errno +and +.Fn archive_error_string +functions. +.\" +.Sh SEE ALSO +.Xr tar 1 , +.Xr libarchive 3 , +.Xr archive_read_set_options 3 , +.Xr archive_write 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The options support for libarchive was originally implemented by +.An Michihiro NAKAJIMA . +.Sh BUGS diff --git a/libarchive/archive_write_set_options.c b/libarchive/archive_write_set_options.c new file mode 100644 index 0000000..a8c2d23 --- /dev/null +++ b/libarchive/archive_write_set_options.c @@ -0,0 +1,124 @@ +/*- + * Copyright (c) 2003-2010 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" +__FBSDID("$FreeBSD$"); + +#include "archive_write_private.h" +#include "archive_options_private.h" + +static int archive_set_format_option(struct archive *a, + const char *m, const char *o, const char *v); +static int archive_set_filter_option(struct archive *a, + const char *m, const char *o, const char *v); +static int archive_set_option(struct archive *a, + const char *m, const char *o, const char *v); + +int +archive_write_set_format_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_WRITE_MAGIC, "archive_write_set_format_option", + archive_set_format_option); +} + +int +archive_write_set_filter_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_WRITE_MAGIC, "archive_write_set_filter_option", + archive_set_filter_option); +} + +int +archive_write_set_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_option(a, m, o, v, + ARCHIVE_WRITE_MAGIC, "archive_write_set_option", + archive_set_option); +} + +int +archive_write_set_options(struct archive *a, const char *options) +{ + return _archive_set_options(a, options, + ARCHIVE_WRITE_MAGIC, "archive_write_set_options", + archive_set_option); +} + +static int +archive_set_format_option(struct archive *_a, const char *m, const char *o, + const char *v) +{ + struct archive_write *a = (struct archive_write *)_a; + + if (a->format_name == NULL) + return (ARCHIVE_FAILED); + if (m != NULL && strcmp(m, a->format_name) != 0) + return (ARCHIVE_FAILED); + if (a->format_options == NULL) + return (ARCHIVE_FAILED); + return a->format_options(a, o, v); +} + +static int +archive_set_filter_option(struct archive *_a, const char *m, const char *o, + const char *v) +{ + struct archive_write *a = (struct archive_write *)_a; + struct archive_write_filter *filter; + int r, rv = ARCHIVE_FAILED; + + for (filter = a->filter_first; filter != NULL; filter = filter->next_filter) { + if (filter->options == NULL) + continue; + if (m != NULL && strcmp(filter->name, m) != 0) + continue; + + r = filter->options(filter, o, v); + + if (r == ARCHIVE_FATAL) + return (ARCHIVE_FATAL); + + if (m != NULL) + return (r); + + if (r == ARCHIVE_OK) + rv = ARCHIVE_OK; + } + return (rv); +} + +static int +archive_set_option(struct archive *a, const char *m, const char *o, + const char *v) +{ + return _archive_set_either_option(a, m, o, v, + archive_set_format_option, + archive_set_filter_option); +} diff --git a/libarchive/config_freebsd.h b/libarchive/config_freebsd.h new file mode 100644 index 0000000..2073431 --- /dev/null +++ b/libarchive/config_freebsd.h @@ -0,0 +1,160 @@ +/*- + * Copyright (c) 2003-2007 Tim Kientzle + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/config_freebsd.h 201079 2009-12-28 02:01:42Z kientzle $ + */ + +/* FreeBSD 5.0 and later have ACL and extattr support. */ +#if __FreeBSD__ > 4 +#define HAVE_ACL_CREATE_ENTRY 1 +#define HAVE_ACL_GET_LINK_NP 1 +#define HAVE_ACL_GET_PERM_NP 1 +#define HAVE_ACL_INIT 1 +#define HAVE_ACL_SET_FD 1 +#define HAVE_ACL_SET_FD_NP 1 +#define HAVE_ACL_SET_FILE 1 +#define HAVE_ACL_USER 1 +#define HAVE_EXTATTR_GET_FILE 1 +#define HAVE_EXTATTR_LIST_FILE 1 +#define HAVE_EXTATTR_SET_FD 1 +#define HAVE_EXTATTR_SET_FILE 1 +#define HAVE_SYS_ACL_H 1 +#define HAVE_SYS_EXTATTR_H 1 +#endif + +#ifdef WITH_OPENSSL +#define HAVE_OPENSSL_MD5_H 1 +#define HAVE_OPENSSL_RIPEMD_H 1 +#define HAVE_OPENSSL_SHA_H 1 +#define HAVE_SHA384 1 +#define HAVE_SHA512 1 +#endif + +#define HAVE_BSDXML_H 1 +#define HAVE_BZLIB_H 1 +#define HAVE_CHFLAGS 1 +#define HAVE_CHOWN 1 +#define HAVE_DECL_INT64_MAX 1 +#define HAVE_DECL_INT64_MIN 1 +#define HAVE_DECL_SIZE_MAX 1 +#define HAVE_DECL_SSIZE_MAX 1 +#define HAVE_DECL_STRERROR_R 1 +#define HAVE_DECL_UINT32_MAX 1 +#define HAVE_DECL_UINT64_MAX 1 +#define HAVE_DIRENT_H 1 +#define HAVE_EFTYPE 1 +#define HAVE_EILSEQ 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCHDIR 1 +#define HAVE_FCHFLAGS 1 +#define HAVE_FCHMOD 1 +#define HAVE_FCHOWN 1 +#define HAVE_FCNTL 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSTAT 1 +#define HAVE_FTRUNCATE 1 +#define HAVE_FUTIMES 1 +#define HAVE_GETEUID 1 +#define HAVE_GETGRGID_R 1 +#define HAVE_GETPID 1 +#define HAVE_GETPWUID_R 1 +#define HAVE_GRP_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LCHFLAGS 1 +#define HAVE_LCHMOD 1 +#define HAVE_LCHOWN 1 +#define HAVE_LIMITS_H 1 +#define HAVE_LINK 1 +#define HAVE_LSTAT 1 +#define HAVE_LUTIMES 1 +#define HAVE_MALLOC 1 +#define HAVE_MD5 1 +#define HAVE_MD5_H 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MKDIR 1 +#define HAVE_MKFIFO 1 +#define HAVE_MKNOD 1 +#define HAVE_PIPE 1 +#define HAVE_POLL 1 +#define HAVE_POLL_H 1 +#define HAVE_PWD_H 1 +#define HAVE_READLINK 1 +#define HAVE_RMD160 1 +#define HAVE_SELECT 1 +#define HAVE_SETENV 1 +#define HAVE_SHA_H 1 +#define HAVE_SHA1 1 +#define HAVE_SHA256 1 +#define HAVE_SHA256_H 1 +#define HAVE_SIGNAL_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRCHR 1 +#define HAVE_STRDUP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRERROR_R 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRUCT_STAT_ST_BLKSIZE 1 +#define HAVE_STRUCT_STAT_ST_BIRTHTIME 1 +#define HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC 1 +#define HAVE_STRUCT_STAT_ST_FLAGS 1 +#define HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC 1 +#define HAVE_STRUCT_TM_TM_GMTOFF 1 +#define HAVE_SYMLINK 1 +#define HAVE_SYS_CDEFS_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_MOUNT_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +#undef HAVE_SYS_UTIME_H +#define HAVE_SYS_UTSNAME_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_TIMEGM 1 +#define HAVE_TZSET 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UNSETENV 1 +#define HAVE_UTIME 1 +#define HAVE_UTIMES 1 +#define HAVE_UTIME_H 1 +#define HAVE_VFORK 1 +#define HAVE_WCHAR_H 1 +#define HAVE_WCSCPY 1 +#define HAVE_WCSLEN 1 +#define HAVE_WCTOMB 1 +#define HAVE_WMEMCMP 1 +#define HAVE_WMEMCPY 1 +#define HAVE_ZLIB_H 1 +#define TIME_WITH_SYS_TIME 1 + +/* FreeBSD 4 and earlier lack intmax_t/uintmax_t */ +#if __FreeBSD__ < 5 +#define intmax_t int64_t +#define uintmax_t uint64_t +#endif diff --git a/libarchive/cpio.5 b/libarchive/cpio.5 new file mode 100644 index 0000000..f544628 --- /dev/null +++ b/libarchive/cpio.5 @@ -0,0 +1,325 @@ +.\" Copyright (c) 2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/cpio.5,v 1.2 2008/05/26 17:00:23 kientzle Exp $ +.\" +.Dd October 5, 2007 +.Dt CPIO 5 +.Os +.Sh NAME +.Nm cpio +.Nd format of cpio archive files +.Sh DESCRIPTION +The +.Nm +archive format collects any number of files, directories, and other +file system objects (symbolic links, device nodes, etc.) into a single +stream of bytes. +.Ss General Format +Each file system object in a +.Nm +archive comprises a header record with basic numeric metadata +followed by the full pathname of the entry and the file data. +The header record stores a series of integer values that generally +follow the fields in +.Va struct stat . +(See +.Xr stat 2 +for details.) +The variants differ primarily in how they store those integers +(binary, octal, or hexadecimal). +The header is followed by the pathname of the +entry (the length of the pathname is stored in the header) +and any file data. +The end of the archive is indicated by a special record with +the pathname +.Dq TRAILER!!! . +.Ss PWB format +XXX Any documentation of the original PWB/UNIX 1.0 format? XXX +.Ss Old Binary Format +The old binary +.Nm +format stores numbers as 2-byte and 4-byte binary values. +Each entry begins with a header in the following format: +.Bd -literal -offset indent +struct header_old_cpio { + unsigned short c_magic; + unsigned short c_dev; + unsigned short c_ino; + unsigned short c_mode; + unsigned short c_uid; + unsigned short c_gid; + unsigned short c_nlink; + unsigned short c_rdev; + unsigned short c_mtime[2]; + unsigned short c_namesize; + unsigned short c_filesize[2]; +}; +.Ed +.Pp +The +.Va unsigned short +fields here are 16-bit integer values; the +.Va unsigned int +fields are 32-bit integer values. +The fields are as follows +.Bl -tag -width indent +.It Va magic +The integer value octal 070707. +This value can be used to determine whether this archive is +written with little-endian or big-endian integers. +.It Va dev , Va ino +The device and inode numbers from the disk. +These are used by programs that read +.Nm +archives to determine when two entries refer to the same file. +Programs that synthesize +.Nm +archives should be careful to set these to distinct values for each entry. +.It Va mode +The mode specifies both the regular permissions and the file type. +It consists of several bit fields as follows: +.Bl -tag -width "MMMMMMM" -compact +.It 0170000 +This masks the file type bits. +.It 0140000 +File type value for sockets. +.It 0120000 +File type value for symbolic links. +For symbolic links, the link body is stored as file data. +.It 0100000 +File type value for regular files. +.It 0060000 +File type value for block special devices. +.It 0040000 +File type value for directories. +.It 0020000 +File type value for character special devices. +.It 0010000 +File type value for named pipes or FIFOs. +.It 0004000 +SUID bit. +.It 0002000 +SGID bit. +.It 0001000 +Sticky bit. +On some systems, this modifies the behavior of executables and/or directories. +.It 0000777 +The lower 9 bits specify read/write/execute permissions +for world, group, and user following standard POSIX conventions. +.El +.It Va uid , Va gid +The numeric user id and group id of the owner. +.It Va nlink +The number of links to this file. +Directories always have a value of at least two here. +Note that hardlinked files include file data with every copy in the archive. +.It Va rdev +For block special and character special entries, +this field contains the associated device number. +For all other entry types, it should be set to zero by writers +and ignored by readers. +.It Va mtime +Modification time of the file, indicated as the number +of seconds since the start of the epoch, +00:00:00 UTC January 1, 1970. +The four-byte integer is stored with the most-significant 16 bits first +followed by the least-significant 16 bits. +Each of the two 16 bit values are stored in machine-native byte order. +.It Va namesize +The number of bytes in the pathname that follows the header. +This count includes the trailing NUL byte. +.It Va filesize +The size of the file. +Note that this archive format is limited to +four gigabyte file sizes. +See +.Va mtime +above for a description of the storage of four-byte integers. +.El +.Pp +The pathname immediately follows the fixed header. +If the +.Cm namesize +is odd, an additional NUL byte is added after the pathname. +The file data is then appended, padded with NUL +bytes to an even length. +.Pp +Hardlinked files are not given special treatment; +the full file contents are included with each copy of the +file. +.Ss Portable ASCII Format +.St -susv2 +standardized an ASCII variant that is portable across all +platforms. +It is commonly known as the +.Dq old character +format or as the +.Dq odc +format. +It stores the same numeric fields as the old binary format, but +represents them as 6-character or 11-character octal values. +.Bd -literal -offset indent +struct cpio_odc_header { + char c_magic[6]; + char c_dev[6]; + char c_ino[6]; + char c_mode[6]; + char c_uid[6]; + char c_gid[6]; + char c_nlink[6]; + char c_rdev[6]; + char c_mtime[11]; + char c_namesize[6]; + char c_filesize[11]; +}; +.Ed +.Pp +The fields are identical to those in the old binary format. +The name and file body follow the fixed header. +Unlike the old binary format, there is no additional padding +after the pathname or file contents. +If the files being archived are themselves entirely ASCII, then +the resulting archive will be entirely ASCII, except for the +NUL byte that terminates the name field. +.Ss New ASCII Format +The "new" ASCII format uses 8-byte hexadecimal fields for +all numbers and separates device numbers into separate fields +for major and minor numbers. +.Bd -literal -offset indent +struct cpio_newc_header { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +}; +.Ed +.Pp +Except as specified below, the fields here match those specified +for the old binary format above. +.Bl -tag -width indent +.It Va magic +The string +.Dq 070701 . +.It Va check +This field is always set to zero by writers and ignored by readers. +See the next section for more details. +.El +.Pp +The pathname is followed by NUL bytes so that the total size +of the fixed header plus pathname is a multiple of four. +Likewise, the file data is padded to a multiple of four bytes. +Note that this format supports only 4 gigabyte files (unlike the +older ASCII format, which supports 8 gigabyte files). +.Pp +In this format, hardlinked files are handled by setting the +filesize to zero for each entry except the last one that +appears in the archive. +.Ss New CRC Format +The CRC format is identical to the new ASCII format described +in the previous section except that the magic field is set +to +.Dq 070702 +and the +.Va check +field is set to the sum of all bytes in the file data. +This sum is computed treating all bytes as unsigned values +and using unsigned arithmetic. +Only the least-significant 32 bits of the sum are stored. +.Ss HP variants +The +.Nm cpio +implementation distributed with HPUX used XXXX but stored +device numbers differently XXX. +.Ss Other Extensions and Variants +Sun Solaris uses additional file types to store extended file +data, including ACLs and extended attributes, as special +entries in cpio archives. +.Pp +XXX Others? XXX +.Sh BUGS +The +.Dq CRC +format is mis-named, as it uses a simple checksum and +not a cyclic redundancy check. +.Pp +The old binary format is limited to 16 bits for user id, +group id, device, and inode numbers. +It is limited to 4 gigabyte file sizes. +.Pp +The old ASCII format is limited to 18 bits for +the user id, group id, device, and inode numbers. +It is limited to 8 gigabyte file sizes. +.Pp +The new ASCII format is limited to 4 gigabyte file sizes. +.Pp +None of the cpio formats store user or group names, +which are essential when moving files between systems with +dissimilar user or group numbering. +.Pp +Especially when writing older cpio variants, it may be necessary +to map actual device/inode values to synthesized values that +fit the available fields. +With very large filesystems, this may be necessary even for +the newer formats. +.Sh SEE ALSO +.Xr cpio 1 , +.Xr tar 5 +.Sh STANDARDS +The +.Nm cpio +utility is no longer a part of POSIX or the Single Unix Standard. +It last appeared in +.St -susv2 . +It has been supplanted in subsequent standards by +.Xr pax 1 . +The portable ASCII format is currently part of the specification for the +.Xr pax 1 +utility. +.Sh HISTORY +The original cpio utility was written by Dick Haight +while working in AT&T's Unix Support Group. +It appeared in 1977 as part of PWB/UNIX 1.0, the +.Dq Programmer's Work Bench +derived from +.At v6 +that was used internally at AT&T. +Both the old binary and old character formats were in use +by 1980, according to the System III source released +by SCO under their +.Dq Ancient Unix +license. +The character format was adopted as part of +.St -p1003.1-88 . +XXX when did "newc" appear? Who invented it? When did HP come out with their variant? When did Sun introduce ACLs and extended attributes? XXX diff --git a/libarchive/filter_fork.c b/libarchive/filter_fork.c new file mode 100644 index 0000000..d160524 --- /dev/null +++ b/libarchive/filter_fork.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +/* This capability is only available on POSIX systems. */ +#if defined(HAVE_PIPE) && defined(HAVE_FCNTL) && \ + (defined(HAVE_FORK) || defined(HAVE_VFORK)) + +__FBSDID("$FreeBSD: head/lib/libarchive/filter_fork.c 182958 2008-09-12 05:33:00Z kientzle $"); + +#if defined(HAVE_POLL) && (defined(HAVE_POLL_H) || defined(HAVE_SYS_POLL_H)) +# if defined(HAVE_POLL_H) +# include +# elif defined(HAVE_SYS_POLL_H) +# include +# endif +#elif defined(HAVE_SELECT) +# if defined(HAVE_SYS_SELECT_H) +# include +# elif defined(HAVE_UNISTD_H) +# include +# endif +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "filter_fork.h" + +pid_t +__archive_create_child(const char *path, int *child_stdin, int *child_stdout) +{ + pid_t child; + int stdin_pipe[2], stdout_pipe[2], tmp; + + if (pipe(stdin_pipe) == -1) + goto state_allocated; + if (stdin_pipe[0] == 1 /* stdout */) { + if ((tmp = dup(stdin_pipe[0])) == -1) + goto stdin_opened; + close(stdin_pipe[0]); + stdin_pipe[0] = tmp; + } + if (pipe(stdout_pipe) == -1) + goto stdin_opened; + if (stdout_pipe[1] == 0 /* stdin */) { + if ((tmp = dup(stdout_pipe[1])) == -1) + goto stdout_opened; + close(stdout_pipe[1]); + stdout_pipe[1] = tmp; + } + +#if HAVE_VFORK + switch ((child = vfork())) { +#else + switch ((child = fork())) { +#endif + case -1: + goto stdout_opened; + case 0: + close(stdin_pipe[1]); + close(stdout_pipe[0]); + if (dup2(stdin_pipe[0], 0 /* stdin */) == -1) + _exit(254); + if (stdin_pipe[0] != 0 /* stdin */) + close(stdin_pipe[0]); + if (dup2(stdout_pipe[1], 1 /* stdout */) == -1) + _exit(254); + if (stdout_pipe[1] != 1 /* stdout */) + close(stdout_pipe[1]); + execlp(path, path, (char *)NULL); + _exit(254); + default: + close(stdin_pipe[0]); + close(stdout_pipe[1]); + + *child_stdin = stdin_pipe[1]; + fcntl(*child_stdin, F_SETFL, O_NONBLOCK); + *child_stdout = stdout_pipe[0]; + fcntl(*child_stdout, F_SETFL, O_NONBLOCK); + } + + return child; + +stdout_opened: + close(stdout_pipe[0]); + close(stdout_pipe[1]); +stdin_opened: + close(stdin_pipe[0]); + close(stdin_pipe[1]); +state_allocated: + return -1; +} + +void +__archive_check_child(int in, int out) +{ +#if defined(HAVE_POLL) && (defined(HAVE_POLL_H) || defined(HAVE_SYS_POLL_H)) + struct pollfd fds[2]; + int idx; + + idx = 0; + if (in != -1) { + fds[idx].fd = in; + fds[idx].events = POLLOUT; + ++idx; + } + if (out != -1) { + fds[idx].fd = out; + fds[idx].events = POLLIN; + ++idx; + } + + poll(fds, idx, -1); /* -1 == INFTIM, wait forever */ +#elif defined(HAVE_SELECT) + fd_set fds_in, fds_out, fds_error; + + FD_ZERO(&fds_in); + FD_ZERO(&fds_out); + FD_ZERO(&fds_error); + if (out != -1) { + FD_SET(out, &fds_in); + FD_SET(out, &fds_error); + } + if (in != -1) { + FD_SET(in, &fds_out); + FD_SET(in, &fds_error); + } + select(in < out ? out + 1 : in + 1, &fds_in, &fds_out, &fds_error, NULL); +#else + sleep(1); +#endif +} + +#endif /* defined(HAVE_PIPE) && defined(HAVE_VFORK) && defined(HAVE_FCNTL) */ diff --git a/libarchive/filter_fork.h b/libarchive/filter_fork.h new file mode 100644 index 0000000..453d032 --- /dev/null +++ b/libarchive/filter_fork.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2007 Joerg Sonnenberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. + * + * $FreeBSD: head/lib/libarchive/filter_fork.h 201087 2009-12-28 02:18:26Z kientzle $ + */ + +#ifndef __LIBARCHIVE_BUILD +#error This header is only to be used internally to libarchive. +#endif + +#ifndef FILTER_FORK_H +#define FILTER_FORK_H + +pid_t +__archive_create_child(const char *path, int *child_stdin, int *child_stdout); + +void +__archive_check_child(int in, int out); + +#endif diff --git a/libarchive/filter_fork_windows.c b/libarchive/filter_fork_windows.c new file mode 100644 index 0000000..38b7097 --- /dev/null +++ b/libarchive/filter_fork_windows.c @@ -0,0 +1,113 @@ +/*- + * Copyright (c) 2009 Michihiro NAKAJIMA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "archive_platform.h" + +#if defined(_WIN32) && !defined(__CYGWIN__) + +#include "filter_fork.h" + +pid_t +__archive_create_child(const char *path, int *child_stdin, int *child_stdout) +{ + HANDLE childStdout[2], childStdin[2], childStdinWr, childStdoutRd; + SECURITY_ATTRIBUTES secAtts; + STARTUPINFO staInfo; + PROCESS_INFORMATION childInfo; + char cmd[MAX_PATH]; + DWORD mode; + + secAtts.nLength = sizeof(SECURITY_ATTRIBUTES); + secAtts.bInheritHandle = TRUE; + secAtts.lpSecurityDescriptor = NULL; + if (CreatePipe(&childStdout[0], &childStdout[1], &secAtts, 0) == 0) + goto fail; + if (DuplicateHandle(GetCurrentProcess(), childStdout[0], + GetCurrentProcess(), &childStdoutRd, 0, FALSE, + DUPLICATE_SAME_ACCESS) == 0) { + CloseHandle(childStdout[0]); + CloseHandle(childStdout[1]); + goto fail; + } + CloseHandle(childStdout[0]); + + if (CreatePipe(&childStdin[0], &childStdin[1], &secAtts, 0) == 0) { + CloseHandle(childStdoutRd); + CloseHandle(childStdout[1]); + goto fail; + } + + if (DuplicateHandle(GetCurrentProcess(), childStdin[1], + GetCurrentProcess(), &childStdinWr, 0, FALSE, + DUPLICATE_SAME_ACCESS) == 0) { + CloseHandle(childStdoutRd); + CloseHandle(childStdout[1]); + CloseHandle(childStdin[0]); + CloseHandle(childStdin[1]); + goto fail; + } + CloseHandle(childStdin[1]); + + memset(&staInfo, 0, sizeof(staInfo)); + staInfo.cb = sizeof(staInfo); + staInfo.hStdOutput = childStdout[1]; + staInfo.hStdInput = childStdin[0]; + staInfo.wShowWindow = SW_HIDE; + staInfo.dwFlags = STARTF_USEFILLATTRIBUTE | STARTF_USECOUNTCHARS | + STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + strncpy(cmd, path, sizeof(cmd)-1); + cmd[sizeof(cmd)-1] = '\0'; + if (CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, + &staInfo, &childInfo) == 0) { + CloseHandle(childStdoutRd); + CloseHandle(childStdout[1]); + CloseHandle(childStdin[0]); + CloseHandle(childStdinWr); + goto fail; + } + WaitForInputIdle(childInfo.hProcess, INFINITE); + CloseHandle(childInfo.hProcess); + CloseHandle(childInfo.hThread); + + mode = PIPE_NOWAIT; + SetNamedPipeHandleState(childStdoutRd, &mode, NULL, NULL); + *child_stdout = _open_osfhandle((intptr_t)childStdoutRd, _O_RDONLY); + *child_stdin = _open_osfhandle((intptr_t)childStdinWr, _O_WRONLY); + + return (childInfo.dwProcessId); + +fail: + return (-1); +} + +void +__archive_check_child(int in, int out) +{ + (void)in; /* UNSED */ + (void)out; /* UNSED */ + Sleep(100); +} + +#endif /* _WIN32 && !__CYGWIN__ */ diff --git a/libarchive/libarchive-formats.5 b/libarchive/libarchive-formats.5 new file mode 100644 index 0000000..bd6bfe7 --- /dev/null +++ b/libarchive/libarchive-formats.5 @@ -0,0 +1,361 @@ +.\" Copyright (c) 2003-2009 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/libarchive-formats.5 201077 2009-12-28 01:50:23Z kientzle $ +.\" +.Dd December 27, 2009 +.Dt libarchive-formats 5 +.Os +.Sh NAME +.Nm libarchive-formats +.Nd archive formats supported by the libarchive library +.Sh DESCRIPTION +The +.Xr libarchive 3 +library reads and writes a variety of streaming archive formats. +Generally speaking, all of these archive formats consist of a series of +.Dq entries . +Each entry stores a single file system object, such as a file, directory, +or symbolic link. +.Pp +The following provides a brief description of each format supported +by libarchive, with some information about recognized extensions or +limitations of the current library support. +Note that just because a format is supported by libarchive does not +imply that a program that uses libarchive will support that format. +Applications that use libarchive specify which formats they wish +to support, though many programs do use libarchive convenience +functions to enable all supported formats. +.Ss Tar Formats +The +.Xr libarchive 3 +library can read most tar archives. +However, it only writes POSIX-standard +.Dq ustar +and +.Dq pax interchange +formats. +.Pp +All tar formats store each entry in one or more 512-byte records. +The first record is used for file metadata, including filename, +timestamp, and mode information, and the file data is stored in +subsequent records. +Later variants have extended this by either appropriating undefined +areas of the header record, extending the header to multiple records, +or by storing special entries that modify the interpretation of +subsequent entries. +.Pp +.Bl -tag -width indent +.It Cm gnutar +The +.Xr libarchive 3 +library can read GNU-format tar archives. +It currently supports the most popular GNU extensions, including +modern long filename and linkname support, as well as atime and ctime data. +The libarchive library does not support multi-volume +archives, nor the old GNU long filename format. +It can read GNU sparse file entries, including the new POSIX-based +formats, but cannot write GNU sparse file entries. +.It Cm pax +The +.Xr libarchive 3 +library can read and write POSIX-compliant pax interchange format +archives. +Pax interchange format archives are an extension of the older ustar +format that adds a separate entry with additional attributes stored +as key/value pairs immediately before each regular entry. +The presence of these additional entries is the only difference between +pax interchange format and the older ustar format. +The extended attributes are of unlimited length and are stored +as UTF-8 Unicode strings. +Keywords defined in the standard are in all lowercase; vendors are allowed +to define custom keys by preceding them with the vendor name in all uppercase. +When writing pax archives, libarchive uses many of the SCHILY keys +defined by Joerg Schilling's +.Dq star +archiver and a few LIBARCHIVE keys. +The libarchive library can read most of the SCHILY keys +and most of the GNU keys introduced by GNU tar. +It silently ignores any keywords that it does not understand. +.It Cm restricted pax +The libarchive library can also write pax archives in which it +attempts to suppress the extended attributes entry whenever +possible. +The result will be identical to a ustar archive unless the +extended attributes entry is required to store a long file +name, long linkname, extended ACL, file flags, or if any of the standard +ustar data (user name, group name, UID, GID, etc) cannot be fully +represented in the ustar header. +In all cases, the result can be dearchived by any program that +can read POSIX-compliant pax interchange format archives. +Programs that correctly read ustar format (see below) will also be +able to read this format; any extended attributes will be extracted as +separate files stored in +.Pa PaxHeader +directories. +.It Cm ustar +The libarchive library can both read and write this format. +This format has the following limitations: +.Bl -bullet -compact +.It +Device major and minor numbers are limited to 21 bits. +Nodes with larger numbers will not be added to the archive. +.It +Path names in the archive are limited to 255 bytes. +(Shorter if there is no / character in exactly the right place.) +.It +Symbolic links and hard links are stored in the archive with +the name of the referenced file. +This name is limited to 100 bytes. +.It +Extended attributes, file flags, and other extended +security information cannot be stored. +.It +Archive entries are limited to 8 gigabytes in size. +.El +Note that the pax interchange format has none of these restrictions. +.El +.Pp +The libarchive library also reads a variety of commonly-used extensions to +the basic tar format. +These extensions are recognized automatically whenever they appear. +.Bl -tag -width indent +.It Numeric extensions. +The POSIX standards require fixed-length numeric fields to be written with +some character position reserved for terminators. +Libarchive allows these fields to be written without terminator characters. +This extends the allowable range; in particular, ustar archives with this +extension can support entries up to 64 gigabytes in size. +Libarchive also recognizes base-256 values in most numeric fields. +This essentially removes all limitations on file size, modification time, +and device numbers. +.It Solaris extensions +Libarchive recognizes ACL and extended attribute records written +by Solaris tar. +Currently, libarchive only has support for old-style ACLs; the +newer NFSv4 ACLs are recognized but discarded. +.El +.Pp +The first tar program appeared in Seventh Edition Unix in 1979. +The first official standard for the tar file format was the +.Dq ustar +(Unix Standard Tar) format defined by POSIX in 1988. +POSIX.1-2001 extended the ustar format to create the +.Dq pax interchange +format. +.Ss Cpio Formats +The libarchive library can read a number of common cpio variants and can write +.Dq odc +and +.Dq newc +format archives. +A cpio archive stores each entry as a fixed-size header followed +by a variable-length filename and variable-length data. +Unlike the tar format, the cpio format does only minimal padding +of the header or file data. +There are several cpio variants, which differ primarily in +how they store the initial header: some store the values as +octal or hexadecimal numbers in ASCII, others as binary values of +varying byte order and length. +.Bl -tag -width indent +.It Cm binary +The libarchive library transparently reads both big-endian and little-endian +variants of the original binary cpio format. +This format used 32-bit binary values for file size and mtime, +and 16-bit binary values for the other fields. +.It Cm odc +The libarchive library can both read and write this +POSIX-standard format, which is officially known as the +.Dq cpio interchange format +or the +.Dq octet-oriented cpio archive format +and sometimes unofficially referred to as the +.Dq old character format . +This format stores the header contents as octal values in ASCII. +It is standard, portable, and immune from byte-order confusion. +File sizes and mtime are limited to 33 bits (8GB file size), +other fields are limited to 18 bits. +.It Cm SVR4 +The libarchive library can read both CRC and non-CRC variants of +this format. +The SVR4 format uses eight-digit hexadecimal values for +all header fields. +This limits file size to 4GB, and also limits the mtime and +other fields to 32 bits. +The SVR4 format can optionally include a CRC of the file +contents, although libarchive does not currently verify this CRC. +.El +.Pp +Cpio first appeared in PWB/UNIX 1.0, which was released within +AT&T in 1977. +PWB/UNIX 1.0 formed the basis of System III Unix, released outside +of AT&T in 1981. +This makes cpio older than tar, although cpio was not included +in Version 7 AT&T Unix. +As a result, the tar command became much better known in universities +and research groups that used Version 7. +The combination of the +.Nm find +and +.Nm cpio +utilities provided very precise control over file selection. +Unfortunately, the format has many limitations that make it unsuitable +for widespread use. +Only the POSIX format permits files over 4GB, and its 18-bit +limit for most other fields makes it unsuitable for modern systems. +In addition, cpio formats only store numeric UID/GID values (not +usernames and group names), which can make it very difficult to correctly +transfer archives across systems with dissimilar user numbering. +.Ss Shar Formats +A +.Dq shell archive +is a shell script that, when executed on a POSIX-compliant +system, will recreate a collection of file system objects. +The libarchive library can write two different kinds of shar archives: +.Bl -tag -width indent +.It Cm shar +The traditional shar format uses a limited set of POSIX +commands, including +.Xr echo 1 , +.Xr mkdir 1 , +and +.Xr sed 1 . +It is suitable for portably archiving small collections of plain text files. +However, it is not generally well-suited for large archives +(many implementations of +.Xr sh 1 +have limits on the size of a script) nor should it be used with non-text files. +.It Cm shardump +This format is similar to shar but encodes files using +.Xr uuencode 1 +so that the result will be a plain text file regardless of the file contents. +It also includes additional shell commands that attempt to reproduce as +many file attributes as possible, including owner, mode, and flags. +The additional commands used to restore file attributes make +shardump archives less portable than plain shar archives. +.El +.Ss ISO9660 format +Libarchive can read and extract from files containing ISO9660-compliant +CDROM images. +In many cases, this can remove the need to burn a physical CDROM +just in order to read the files contained in an ISO9660 image. +It also avoids security and complexity issues that come with +virtual mounts and loopback devices. +Libarchive supports the most common Rockridge extensions and has partial +support for Joliet extensions. +If both extensions are present, the Joliet extensions will be +used and the Rockridge extensions will be ignored. +In particular, this can create problems with hardlinks and symlinks, +which are supported by Rockridge but not by Joliet. +.Ss Zip format +Libarchive can read and write zip format archives that have +uncompressed entries and entries compressed with the +.Dq deflate +algorithm. +Older zip compression algorithms are not supported. +It can extract jar archives, archives that use Zip64 extensions and many +self-extracting zip archives. +Libarchive reads Zip archives as they are being streamed, +which allows it to read archives of arbitrary size. +It currently does not use the central directory; this +limits libarchive's ability to support some self-extracting +archives and ones that have been modified in certain ways. +.Ss Archive (library) file format +The Unix archive format (commonly created by the +.Xr ar 1 +archiver) is a general-purpose format which is +used almost exclusively for object files to be +read by the link editor +.Xr ld 1 . +The ar format has never been standardised. +There are two common variants: +the GNU format derived from SVR4, +and the BSD format, which first appeared in 4.4BSD. +The two differ primarily in their handling of filenames +longer than 15 characters: +the GNU/SVR4 variant writes a filename table at the beginning of the archive; +the BSD format stores each long filename in an extension +area adjacent to the entry. +Libarchive can read both extensions, +including archives that may include both types of long filenames. +Programs using libarchive can write GNU/SVR4 format +if they provide a filename table to be written into +the archive before any of the entries. +Any entries whose names are not in the filename table +will be written using BSD-style long filenames. +This can cause problems for programs such as +GNU ld that do not support the BSD-style long filenames. +.Ss mtree +Libarchive can read and write files in +.Xr mtree 5 +format. +This format is not a true archive format, but rather a textual description +of a file hierarchy in which each line specifies the name of a file and +provides specific metadata about that file. +Libarchive can read all of the keywords supported by both +the NetBSD and FreeBSD versions of +.Xr mtree 1 , +although many of the keywords cannot currently be stored in an +.Tn archive_entry +object. +When writing, libarchive supports use of the +.Xr archive_write_set_options 3 +interface to specify which keywords should be included in the +output. +If libarchive was compiled with access to suitable +cryptographic libraries (such as the OpenSSL libraries), +it can compute hash entries such as +.Cm sha512 +or +.Cm md5 +from file data being written to the mtree writer. +.Pp +When reading an mtree file, libarchive will locate the corresponding +files on disk using the +.Cm contents +keyword if present or the regular filename. +If it can locate and open the file on disk, it will use that +to fill in any metadata that is missing from the mtree file +and will read the file contents and return those to the program +using libarchive. +If it cannot locate and open the file on disk, libarchive +will return an error for any attempt to read the entry +body. +.Ss RAR +libarchive has limited support to read files in RAR format. Currently, +libarchive can read single RAR files in RARv3 format which have been either +created uncompressed, or compressed using any of the compression methods +supported by the RARv3 format. libarchive can also extract RAR files which have +been created as self-extracting RAR files. +.Sh SEE ALSO +.Xr ar 1 , +.Xr cpio 1 , +.Xr mkisofs 1 , +.Xr shar 1 , +.Xr tar 1 , +.Xr zip 1 , +.Xr zlib 3 , +.Xr cpio 5 , +.Xr mtree 5 , +.Xr tar 5 diff --git a/libarchive/libarchive.3 b/libarchive/libarchive.3 new file mode 100644 index 0000000..d655404 --- /dev/null +++ b/libarchive/libarchive.3 @@ -0,0 +1,256 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/libarchive.3,v 1.11 2007/01/09 08:05:56 kientzle Exp $ +.\" +.Dd February 6, 2010 +.Dt LIBARCHIVE 3 +.Os +.Sh NAME +.Nm libarchive +.Nd functions for reading and writing streaming archives +.Sh LIBRARY +.Lb libarchive +.Sh OVERVIEW +The +.Nm +library provides a flexible interface for reading and writing +archives in various formats such as tar and cpio. +.Nm +also supports reading and writing archives compressed using +various compression filters such as gzip and bzip2. +The library is inherently stream-oriented; readers serially iterate through +the archive, writers serially add things to the archive. +In particular, note that there is currently no built-in support for +random access nor for in-place modification. +.Pp +When reading an archive, the library automatically detects the +format and the compression. +The library currently has read support for: +.Bl -bullet -compact +.It +old-style tar archives, +.It +most variants of the POSIX +.Dq ustar +format, +.It +the POSIX +.Dq pax interchange +format, +.It +GNU-format tar archives, +.It +most common cpio archive formats, +.It +ISO9660 CD images (including RockRidge and Joliet extensions), +.It +Zip archives. +.El +The library automatically detects archives compressed with +.Xr gzip 1 , +.Xr bzip2 1 , +.Xr xz 1 , +or +.Xr compress 1 +and decompresses them transparently. +.Pp +When writing an archive, you can specify the compression +to be used and the format to use. +The library can write +.Bl -bullet -compact +.It +POSIX-standard +.Dq ustar +archives, +.It +POSIX +.Dq pax interchange format +archives, +.It +POSIX octet-oriented cpio archives, +.It +Zip archive, +.It +two different variants of shar archives. +.El +Pax interchange format is an extension of the tar archive format that +eliminates essentially all of the limitations of historic tar formats +in a standard fashion that is supported +by POSIX-compliant +.Xr pax 1 +implementations on many systems as well as several newer implementations of +.Xr tar 1 . +Note that the default write format will suppress the pax extended +attributes for most entries; explicitly requesting pax format will +enable those attributes for all entries. +.Pp +The read and write APIs are accessed through the +.Fn archive_read_XXX +functions and the +.Fn archive_write_XXX +functions, respectively, and either can be used independently +of the other. +.Pp +The rest of this manual page provides an overview of the library +operation. +More detailed information can be found in the individual manual +pages for each API or utility function. +.\" +.Sh READING AN ARCHIVE +See +.Xr libarchive_read 3 . +.\" +.Sh WRITING AN ARCHIVE +See +.Xr libarchive_write 3 . +.\" +.Sh WRITING ENTRIES TO DISK +The +.Xr archive_write_disk 3 +API allows you to write +.Xr archive_entry 3 +objects to disk using the same API used by +.Xr archive_write 3 . +The +.Xr archive_write_disk 3 +API is used internally by +.Fn archive_read_extract ; +using it directly can provide greater control over how entries +get written to disk. +This API also makes it possible to share code between +archive-to-archive copy and archive-to-disk extraction +operations. +.Sh READING ENTRIES FROM DISK +The +.Xr archive_read_disk 3 +provides some support for populating +.Xr archive_entry 3 +objects from information in the filesystem. +.Sh DESCRIPTION +Detailed descriptions of each function are provided by the +corresponding manual pages. +.Pp +All of the functions utilize an opaque +.Tn struct archive +datatype that provides access to the archive contents. +.Pp +The +.Tn struct archive_entry +structure contains a complete description of a single archive +entry. +It uses an opaque interface that is fully documented in +.Xr archive_entry 3 . +.Pp +Users familiar with historic formats should be aware that the newer +variants have eliminated most restrictions on the length of textual fields. +Clients should not assume that filenames, link names, user names, or +group names are limited in length. +In particular, pax interchange format can easily accommodate pathnames +in arbitrary character sets that exceed +.Va PATH_MAX . +.Sh RETURN VALUES +Most functions return +.Cm ARCHIVE_OK +(zero) on success, non-zero on error. +The return value indicates the general severity of the error, ranging +from +.Cm ARCHIVE_WARN , +which indicates a minor problem that should probably be reported +to the user, to +.Cm ARCHIVE_FATAL , +which indicates a serious problem that will prevent any further +operations on this archive. +On error, the +.Fn archive_errno +function can be used to retrieve a numeric error code (see +.Xr errno 2 ) . +The +.Fn archive_error_string +returns a textual error message suitable for display. +.Pp +.Fn archive_read_new +and +.Fn archive_write_new +return pointers to an allocated and initialized +.Tn struct archive +object. +.Pp +.Fn archive_read_data +and +.Fn archive_write_data +return a count of the number of bytes actually read or written. +A value of zero indicates the end of the data for this entry. +A negative value indicates an error, in which case the +.Fn archive_errno +and +.Fn archive_error_string +functions can be used to obtain more information. +.Sh ENVIRONMENT +There are character set conversions within the +.Xr archive_entry 3 +functions that are impacted by the currently-selected locale. +.Sh SEE ALSO +.Xr tar 1 , +.Xr archive_entry 3 , +.Xr archive_read 3 , +.Xr archive_util 3 , +.Xr archive_write 3 , +.Xr tar 5 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . +.Sh BUGS +Some archive formats support information that is not supported by +.Tn struct archive_entry . +Such information cannot be fully archived or restored using this library. +This includes, for example, comments, character sets, +or the arbitrary key/value pairs that can appear in +pax interchange format archives. +.Pp +Conversely, of course, not all of the information that can be +stored in an +.Tn struct archive_entry +is supported by all formats. +For example, cpio formats do not support nanosecond timestamps; +old tar formats do not support large device numbers. +.Pp +The +.Xr archive_read_disk 3 +API should support iterating over filesystems; +that would make it possible to share code among +disk-to-archive, archive-to-archive, archive-to-disk, +and disk-to-disk operations. +Currently, it only supports reading the +information for a single file. +(Which is still quite useful, as it hides a lot +of system-specific details.) diff --git a/libarchive/libarchive_changes.3 b/libarchive/libarchive_changes.3 new file mode 100644 index 0000000..349eab9 --- /dev/null +++ b/libarchive/libarchive_changes.3 @@ -0,0 +1,341 @@ +.\" Copyright (c) 2011 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 27, 2011 +.Dt libarchive_changes 3 +.Os +.Sh NAME +.Nm changes in libarchive interface +.\" +.Sh CHANGES IN LIBARCHIVE 3 +This page describes user-visible changes in libarchive3, and lists +public functions and other symbols changed, deprecated or removed +in libarchive3, along with their replacements if any. +.Pp +.\" +.Ss Multiple Filters +.\" +Libarchive2 permitted a single (input or output) filter active +on an archive. +Libarchive3 extends this into a variable-length stack. +Where +.Fn archive_write_set_compression_XXX +would replace any existing filter, +.Fn archive_write_add_filter_XXX +extends the write pipeline with another filter. +.\" +.Ss Character Set Handling +.\" +Libarchive2 assumed that the local platform uses +.Tn Unicode +as the native +.Tn wchar_t +encoding, which is true on +.Tn Windows , +modern +.Tn Linux , +and a few other systems, but is certainly not universal. +As a result, pax format archives were written incorrectly on some +systems, since pax format requires +.Tn UTF-8 +and libarchive 2 incorrectly +assumed that +.Tn wchar_t +strings can be easily converted to +.Tn UTF-8 . +.Pp +Libarchive3 uses the standard iconv library to convert between character +sets and is introducing the notion of a +.Dq default character set for the archive . +To support this, +.Tn archive_entry +objects can now be bound to a particular archive when they are created. +The automatic character set conversions performed by +.Tn archive_entry +objects when reading and writing filenames, usernames, and other strings +will now use an appropriate default character set: +.Pp +If the +.Tn archive_entry +object is bound to an archive, it will use the +default character set for that archive. +.Pp +The platform default character encoding (as returned by +.Fn nl_langinfo CHARSET ) +will be used if nothing else is specified. +.Pp +Libarchive3 also introduces charset options to many of the archive +readers and writers to control the character set that will be used for +filenames written in those archives. +When possible, this will be set automatically based on information in +the archive itself. +Combining this with the notion of a default character set for the +archive should allow you to configure libarchive to read archives from +other platforms and have the filenames and other information +transparently converted to the character encoding suitable for your +application. +.\" +.Ss Prototype Changes +.\" +These changes break binary compatibility; libarchive3 has a new shared +library version to reflect these changes. +The library now uses portable wide types such as +.Tn int64_t +instead of less-portable types such as +.Tn off_t , +.Tn gid_t , +.Tn uid_t , +and +.Tn ino_t . +.Pp +There are a few cases where these changes will affect your source code: +.Bl -bullet -width ind +.It +In some cases, libarchive's wider types will introduce the possibility +of truncation: for example, on a system with a 16-bit +.Tn uid_t , you risk having uid +.Li 65536 +be truncated to uid +.Li 0 , +which can cause serious security problems. +.It +Typedef function pointer types will be incompatible. +For example, if you define custom skip callbacks, you may have to use +code similar to the following if you want to support building against +libarchive2 and libarchive3: +.Bd -literal +#if ARCHIVE_VERSION_NUMBER < 3000000 +typedef off_t myoff_t; +#else +typedef int64_t myoff_t; +#endif + +myoff_t +my_skip_function(struct archive *a, void *v, myoff_t o) +{ + ... implementation ... +} +.Ed +.El +.Pp +Affected functions: +.Pp +.Bl -bullet -compact +.It +.Xo +.Fn archive_entry_gid , +.Fn archive_entry_set_gid +.Xc +.It +.Xo +.Fn archive_entry_uid , +.Fn archive_entry_set_uid +.Xc +.It +.Xo +.Fn archive_entry_ino , +.Fn archive_entry_set_ino +.Xc +.It +.Xo +.Fn archive_read_data_block , +.Fn archive_write_data_block +.Xc +.It +.Xo +.Fn archive_read_disk_gname , +.Fn archive_read_disk_uname +.Xc +.It +.Xo +.Fn archive_read_disk_set_gname_lookup , +.Fn archive_read_disk_set_group_lookup , +.Fn archive_read_disk_set_uname_lookup , +.Fn archive_read_disk_set_user_lookup +.Xc +.It +.Fn archive_skip_callback +.It +.Xo +.Fn archive_read_extract_set_skip_file , +.Fn archive_write_disk_set_skip_file , +.Fn archive_write_set_skip_file +.Xc +.It +.Xo +.Fn archive_write_disk_set_group_lookup , +.Fn archive_write_disk_set_user_lookup +.Xc +.El +.Pp +Where these functions or their arguments took or returned +.Tn gid_t , +.Tn ino_t , +.Tn off_t , +or +.Tn uid_t +they now take or return +.Tn int64_t +or equivalent. +.\" +.Ss Deprecated Symbols +.\" +Symbols deprecated in libarchive3 will be removed in libarchive4. +These symbols, along with their replacements if any, are listed below: +.\" +.Bl -tag -width ind +.It Fn archive_position_compressed , Fn archive_position_uncompressed +.Fn archive_filter_bytes +.It Fn archive_compression +.Fn archive_filter_code +.It Fn archive_compression_name +.Fn archive_filter_name +.It Fn archive_read_finish , Fn archive_write_finish +.Fn archive_read_free , +.Fn archive_write_free +.It Fn archive_read_open_file , Fn archive_write_open_file +.Fn archive_read_open_filename , +.Fn archive_write_open_filename +.It Fn archive_read_support_compression_all +.\" archive_read_support_compression_* -> archive_read_support_filter_* +.Fn archive_read_support_filter_all +.It Fn archive_read_support_compression_bzip2 +.Fn archive_read_support_filter_bzip2 +.It Fn archive_read_support_compression_compress +.Fn archive_read_support_filter_compress +.It Fn archive_read_support_compression_gzip +.Fn archive_read_support_filter_gzip +.It Fn archive_read_support_compression_lzip +.Fn archive_read_support_filter_lzip +.It Fn archive_read_support_compression_lzma +.Fn archive_read_support_filter_lzma +.It Fn archive_read_support_compression_none +.Fn archive_read_support_filter_none +.It Fn archive_read_support_compression_program +.Fn archive_read_support_filter_program +.It Fn archive_read_support_compression_program_signature +.Fn archive_read_support_filter_program_signature +.It Fn archive_read_support_compression_rpm +.Fn archive_read_support_filter_rpm +.It Fn archive_read_support_compression_uu +.Fn archive_read_support_filter_uu +.It Fn archive_read_support_compression_xz +.Fn archive_read_support_filter_xz +.\" archive_write_set_compression_* -> archive_write_add_filter_* +.It Fn archive_write_set_compression_bzip2 +.Fn archive_write_add_filter_bzip2 +.It Fn archive_write_set_compression_compress +.Fn archive_write_add_filter_compress +.It Fn archive_write_set_compression_gzip +.Fn archive_write_add_filter_gzip +.It Fn archive_write_set_compression_lzip +.Fn archive_write_add_filter_lzip +.It Fn archive_write_set_compression_lzma +.Fn archive_write_add_filter_lzma +.It Fn archive_write_set_compression_none +.Fn archive_write_add_filter_none +.It Fn archive_write_set_compression_program +.Fn archive_write_add_filter_program +.It Fn archive_write_set_compression_filter +.Fn archive_write_add_filter_filter +.El +.\" +.Ss Removed Symbols +.\" +These symbols, listed below along with their replacements if any, +were deprecated in libarchive2, and are not part of libarchive3. +.\" +.Bl -tag -width ind +.It Fn archive_api_feature +.Fn archive_version_number +.It Fn archive_api_version +.Fn archive_version_number +.It Fn archive_version +.Fn archive_version_string +.It Fn archive_version_stamp +.Fn archive_version_number +.It Fn archive_read_set_filter_options +.Fn archive_read_set_options +or +.Fn archive_read_set_filter_option +.It Fn archive_read_set_format_options +.Fn archive_read_set_options +or +.Fn archive_read_set_format_option +.It Fn archive_write_set_filter_options +.Fn archive_write_set_options +or +.Fn archive_write_set_filter_option +.It Fn archive_write_set_format_options +.Fn archive_write_set_options +or +.Fn archive_write_set_format_option +.It Dv ARCHIVE_API_FEATURE +.Dv ARCHIVE_VERSION_NUMBER +.It Dv ARCHIVE_API_VERSION +.Dv ARCHIVE_VERSION_NUMBER +.It Dv ARCHIVE_VERSION_STAMP +.Dv ARCHIVE_VERSION_NUMBER +.It Dv ARCHIVE_LIBRARY_VERSION +.Dv ARCHIVE_VERSION_STRING +.\" +.It Dv ARCHIVE_COMPRESSION_NONE +.Dv ARCHIVE_FILTER_NONE +.It Dv ARCHIVE_COMPRESSION_GZIP +.Dv ARCHIVE_FILTER_GZIP +.It Dv ARCHIVE_COMPRESSION_BZIP2 +.Dv ARCHIVE_FILTER_BZIP2 +.It Dv ARCHIVE_COMPRESSION_COMPRESS +.Dv ARCHIVE_FILTER_COMPRESS +.It Dv ARCHIVE_COMPRESSION_PROGRAM +.Dv ARCHIVE_FILTER_PROGRAM +.It Dv ARCHIVE_COMPRESSION_LZMA +.Dv ARCHIVE_FILTER_LZMA +.It Dv ARCHIVE_COMPRESSION_XZ +.Dv ARCHIVE_FILTER_XZ +.It Dv ARCHIVE_COMPRESSION_UU +.Dv ARCHIVE_FILTER_UU +.It Dv ARCHIVE_COMPRESSION_RPM +.Dv ARCHIVE_FILTER_RPM +.It Dv ARCHIVE_COMPRESSION_LZIP +.Dv ARCHIVE_FILTER_LZIP +.\" +.It Dv ARCHIVE_BYTES_PER_RECORD +.Li 512 +.It Dv ARCHIVE_DEFAULT_BYTES_PER_BLOCK +.Li 10240 +.El +.Sh SEE ALSO +.Xr libarchive 3 , +.Xr archive_read 3 , +.Xr archive_read_filter 3 , +.Xr archive_read_format 3 , +.Xr archive_read_set_options 3 , +.Xr archive_write 3 , +.Xr archive_write_filter 3 , +.Xr archive_write_format 3 , +.Xr archive_write_set_options 3 , +.Xr archive_util 3 diff --git a/libarchive/libarchive_internals.3 b/libarchive/libarchive_internals.3 new file mode 100644 index 0000000..776f4c3 --- /dev/null +++ b/libarchive/libarchive_internals.3 @@ -0,0 +1,365 @@ +.\" Copyright (c) 2003-2007 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/lib/libarchive/libarchive_internals.3,v 1.2 2007/12/30 04:58:22 kientzle Exp $ +.\" +.Dd April 16, 2007 +.Dt LIBARCHIVE 3 +.Os +.Sh NAME +.Nm libarchive_internals +.Nd description of libarchive internal interfaces +.Sh OVERVIEW +The +.Nm libarchive +library provides a flexible interface for reading and writing +streaming archive files such as tar and cpio. +Internally, it follows a modular layered design that should +make it easy to add new archive and compression formats. +.Sh GENERAL ARCHITECTURE +Externally, libarchive exposes most operations through an +opaque, object-style interface. +The +.Xr archive_entry 3 +objects store information about a single filesystem object. +The rest of the library provides facilities to write +.Xr archive_entry 3 +objects to archive files, +read them from archive files, +and write them to disk. +(There are plans to add a facility to read +.Xr archive_entry 3 +objects from disk as well.) +.Pp +The read and write APIs each have four layers: a public API +layer, a format layer that understands the archive file format, +a compression layer, and an I/O layer. +The I/O layer is completely exposed to clients who can replace +it entirely with their own functions. +.Pp +In order to provide as much consistency as possible for clients, +some public functions are virtualized. +Eventually, it should be possible for clients to open +an archive or disk writer, and then use a single set of +code to select and write entries, regardless of the target. +.Sh READ ARCHITECTURE +From the outside, clients use the +.Xr archive_read 3 +API to manipulate an +.Nm archive +object to read entries and bodies from an archive stream. +Internally, the +.Nm archive +object is cast to an +.Nm archive_read +object, which holds all read-specific data. +The API has four layers: +The lowest layer is the I/O layer. +This layer can be overridden by clients, but most clients use +the packaged I/O callbacks provided, for example, by +.Xr archive_read_open_memory 3 , +and +.Xr archive_read_open_fd 3 . +The compression layer calls the I/O layer to +read bytes and decompresses them for the format layer. +The format layer unpacks a stream of uncompressed bytes and +creates +.Nm archive_entry +objects from the incoming data. +The API layer tracks overall state +(for example, it prevents clients from reading data before reading a header) +and invokes the format and compression layer operations +through registered function pointers. +In particular, the API layer drives the format-detection process: +When opening the archive, it reads an initial block of data +and offers it to each registered compression handler. +The one with the highest bid is initialized with the first block. +Similarly, the format handlers are polled to see which handler +is the best for each archive. +(Prior to 2.4.0, the format bidders were invoked for each +entry, but this design hindered error recovery.) +.Ss I/O Layer and Client Callbacks +The read API goes to some lengths to be nice to clients. +As a result, there are few restrictions on the behavior of +the client callbacks. +.Pp +The client read callback is expected to provide a block +of data on each call. +A zero-length return does indicate end of file, but otherwise +blocks may be as small as one byte or as large as the entire file. +In particular, blocks may be of different sizes. +.Pp +The client skip callback returns the number of bytes actually +skipped, which may be much smaller than the skip requested. +The only requirement is that the skip not be larger. +In particular, clients are allowed to return zero for any +skip that they don't want to handle. +The skip callback must never be invoked with a negative value. +.Pp +Keep in mind that not all clients are reading from disk: +clients reading from networks may provide different-sized +blocks on every request and cannot skip at all; +advanced clients may use +.Xr mmap 2 +to read the entire file into memory at once and return the +entire file to libarchive as a single block; +other clients may begin asynchronous I/O operations for the +next block on each request. +.Ss Decompresssion Layer +The decompression layer not only handles decompression, +it also buffers data so that the format handlers see a +much nicer I/O model. +The decompression API is a two stage peek/consume model. +A read_ahead request specifies a minimum read amount; +the decompression layer must provide a pointer to at least +that much data. +If more data is immediately available, it should return more: +the format layer handles bulk data reads by asking for a minimum +of one byte and then copying as much data as is available. +.Pp +A subsequent call to the +.Fn consume +function advances the read pointer. +Note that data returned from a +.Fn read_ahead +call is guaranteed to remain in place until +the next call to +.Fn read_ahead . +Intervening calls to +.Fn consume +should not cause the data to move. +.Pp +Skip requests must always be handled exactly. +Decompression handlers that cannot seek forward should +not register a skip handler; +the API layer fills in a generic skip handler that reads and discards data. +.Pp +A decompression handler has a specific lifecycle: +.Bl -tag -compact -width indent +.It Registration/Configuration +When the client invokes the public support function, +the decompression handler invokes the internal +.Fn __archive_read_register_compression +function to provide bid and initialization functions. +This function returns +.Cm NULL +on error or else a pointer to a +.Cm struct decompressor_t . +This structure contains a +.Va void * config +slot that can be used for storing any customization information. +.It Bid +The bid function is invoked with a pointer and size of a block of data. +The decompressor can access its config data +through the +.Va decompressor +element of the +.Cm archive_read +object. +The bid function is otherwise stateless. +In particular, it must not perform any I/O operations. +.Pp +The value returned by the bid function indicates its suitability +for handling this data stream. +A bid of zero will ensure that this decompressor is never invoked. +Return zero if magic number checks fail. +Otherwise, your initial implementation should return the number of bits +actually checked. +For example, if you verify two full bytes and three bits of another +byte, bid 19. +Note that the initial block may be very short; +be careful to only inspect the data you are given. +(The current decompressors require two bytes for correct bidding.) +.It Initialize +The winning bidder will have its init function called. +This function should initialize the remaining slots of the +.Va struct decompressor_t +object pointed to by the +.Va decompressor +element of the +.Va archive_read +object. +In particular, it should allocate any working data it needs +in the +.Va data +slot of that structure. +The init function is called with the block of data that +was used for tasting. +At this point, the decompressor is responsible for all I/O +requests to the client callbacks. +The decompressor is free to read more data as and when +necessary. +.It Satisfy I/O requests +The format handler will invoke the +.Va read_ahead , +.Va consume , +and +.Va skip +functions as needed. +.It Finish +The finish method is called only once when the archive is closed. +It should release anything stored in the +.Va data +and +.Va config +slots of the +.Va decompressor +object. +It should not invoke the client close callback. +.El +.Ss Format Layer +The read formats have a similar lifecycle to the decompression handlers: +.Bl -tag -compact -width indent +.It Registration +Allocate your private data and initialize your pointers. +.It Bid +Formats bid by invoking the +.Fn read_ahead +decompression method but not calling the +.Fn consume +method. +This allows each bidder to look ahead in the input stream. +Bidders should not look further ahead than necessary, as long +look aheads put pressure on the decompression layer to buffer +lots of data. +Most formats only require a few hundred bytes of look ahead; +look aheads of a few kilobytes are reasonable. +(The ISO9660 reader sometimes looks ahead by 48k, which +should be considered an upper limit.) +.It Read header +The header read is usually the most complex part of any format. +There are a few strategies worth mentioning: +For formats such as tar or cpio, reading and parsing the header is +straightforward since headers alternate with data. +For formats that store all header data at the beginning of the file, +the first header read request may have to read all headers into +memory and store that data, sorted by the location of the file +data. +Subsequent header read requests will skip forward to the +beginning of the file data and return the corresponding header. +.It Read Data +The read data interface supports sparse files; this requires that +each call return a block of data specifying the file offset and +size. +This may require you to carefully track the location so that you +can return accurate file offsets for each read. +Remember that the decompressor will return as much data as it has. +Generally, you will want to request one byte, +examine the return value to see how much data is available, and +possibly trim that to the amount you can use. +You should invoke consume for each block just before you return it. +.It Skip All Data +The skip data call should skip over all file data and trailing padding. +This is called automatically by the API layer just before each +header read. +It is also called in response to the client calling the public +.Fn data_skip +function. +.It Cleanup +On cleanup, the format should release all of its allocated memory. +.El +.Ss API Layer +XXX to do XXX +.Sh WRITE ARCHITECTURE +The write API has a similar set of four layers: +an API layer, a format layer, a compression layer, and an I/O layer. +The registration here is much simpler because only +one format and one compression can be registered at a time. +.Ss I/O Layer and Client Callbacks +XXX To be written XXX +.Ss Compression Layer +XXX To be written XXX +.Ss Format Layer +XXX To be written XXX +.Ss API Layer +XXX To be written XXX +.Sh WRITE_DISK ARCHITECTURE +The write_disk API is intended to look just like the write API +to clients. +Since it does not handle multiple formats or compression, it +is not layered internally. +.Sh GENERAL SERVICES +The +.Nm archive_read , +.Nm archive_write , +and +.Nm archive_write_disk +objects all contain an initial +.Nm archive +object which provides common support for a set of standard services. +(Recall that ANSI/ISO C90 guarantees that you can cast freely between +a pointer to a structure and a pointer to the first element of that +structure.) +The +.Nm archive +object has a magic value that indicates which API this object +is associated with, +slots for storing error information, +and function pointers for virtualized API functions. +.Sh MISCELLANEOUS NOTES +Connecting existing archiving libraries into libarchive is generally +quite difficult. +In particular, many existing libraries strongly assume that you +are reading from a file; they seek forwards and backwards as necessary +to locate various pieces of information. +In contrast, libarchive never seeks backwards in its input, which +sometimes requires very different approaches. +.Pp +For example, libarchive's ISO9660 support operates very differently +from most ISO9660 readers. +The libarchive support utilizes a work-queue design that +keeps a list of known entries sorted by their location in the input. +Whenever libarchive's ISO9660 implementation is asked for the next +header, checks this list to find the next item on the disk. +Directories are parsed when they are encountered and new +items are added to the list. +This design relies heavily on the ISO9660 image being optimized so that +directories always occur earlier on the disk than the files they +describe. +.Pp +Depending on the specific format, such approaches may not be possible. +The ZIP format specification, for example, allows archivers to store +key information only at the end of the file. +In theory, it is possible to create ZIP archives that cannot +be read without seeking. +Fortunately, such archives are very rare, and libarchive can read +most ZIP archives, though it cannot always extract as much information +as a dedicated ZIP program. +.Sh SEE ALSO +.Xr archive 3 , +.Xr archive_entry 3 , +.Xr archive_read 3 , +.Xr archive_write 3 , +.Xr archive_write_disk 3 +.Sh HISTORY +The +.Nm libarchive +library first appeared in +.Fx 5.3 . +.Sh AUTHORS +.An -nosplit +The +.Nm libarchive +library was written by +.An Tim Kientzle Aq kientzle@acm.org . diff --git a/libarchive/mtree.5 b/libarchive/mtree.5 new file mode 100644 index 0000000..b6637d6 --- /dev/null +++ b/libarchive/mtree.5 @@ -0,0 +1,269 @@ +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" From: @(#)mtree.8 8.2 (Berkeley) 12/11/93 +.\" $FreeBSD$ +.\" +.Dd August 20, 2007 +.Dt MTREE 5 +.Os +.Sh NAME +.Nm mtree +.Nd format of mtree dir hierarchy files +.Sh DESCRIPTION +The +.Nm +format is a textual format that describes a collection of filesystem objects. +Such files are typically used to create or verify directory hierarchies. +.Ss General Format +An +.Nm +file consists of a series of lines, each providing information +about a single filesystem object. +Leading whitespace is always ignored. +.Pp +When encoding file or pathnames, any backslash character or +character outside of the 95 printable ASCII characters must be +encoded as a a backslash followed by three +octal digits. +When reading mtree files, any appearance of a backslash +followed by three octal digits should be converted into the +corresponding character. +.Pp +Each line is interpreted independently as one of the following types: +.Bl -tag -width Cm +.It Signature +The first line of any mtree file must begin with +.Dq #mtree . +If a file contains any full path entries, the first line should +begin with +.Dq #mtree v2.0 , +otherwise, the first line should begin with +.Dq #mtree v1.0 . +.It Blank +Blank lines are ignored. +.It Comment +Lines beginning with +.Cm # +are ignored. +.It Special +Lines beginning with +.Cm / +are special commands that influence +the interpretation of later lines. +.It Relative +If the first whitespace-delimited word has no +.Cm / +characters, +it is the name of a file in the current directory. +Any relative entry that describes a directory changes the +current directory. +.It dot-dot +As a special case, a relative entry with the filename +.Pa .. +changes the current directory to the parent directory. +Options on dot-dot entries are always ignored. +.It Full +If the first whitespace-delimited word has a +.Cm / +character after +the first character, it is the pathname of a file relative to the +starting directory. +There can be multiple full entries describing the same file. +.El +.Pp +Some tools that process +.Nm +files may require that multiple lines describing the same file +occur consecutively. +It is not permitted for the same file to be mentioned using +both a relative and a full file specification. +.Ss Special commands +Two special commands are currently defined: +.Bl -tag -width Cm +.It Cm /set +This command defines default values for one or more keywords. +It is followed on the same line by one or more whitespace-separated +keyword definitions. +These definitions apply to all following files that do not specify +a value for that keyword. +.It Cm /unset +This command removes any default value set by a previous +.Cm /set +command. +It is followed on the same line by one or more keywords +separated by whitespace. +.El +.Ss Keywords +After the filename, a full or relative entry consists of zero +or more whitespace-separated keyword definitions. +Each such definition consists of a key from the following +list immediately followed by an '=' sign +and a value. +Software programs reading mtree files should warn about +unrecognized keywords. +.Pp +Currently supported keywords are as follows: +.Bl -tag -width Cm +.It Cm cksum +The checksum of the file using the default algorithm specified by +the +.Xr cksum 1 +utility. +.It Cm contents +The full pathname of a file that holds the contents of this file. +.It Cm flags +The file flags as a symbolic name. +See +.Xr chflags 1 +for information on these names. +If no flags are to be set the string +.Dq none +may be used to override the current default. +.It Cm gid +The file group as a numeric value. +.It Cm gname +The file group as a symbolic name. +.It Cm ignore +Ignore any file hierarchy below this file. +.It Cm link +The target of the symbolic link when type=link. +.It Cm md5 +The MD5 message digest of the file. +.It Cm md5digest +A synonym for +.Cm md5 . +.It Cm mode +The current file's permissions as a numeric (octal) or symbolic +value. +.It Cm nlink +The number of hard links the file is expected to have. +.It Cm nochange +Make sure this file or directory exists but otherwise ignore all attributes. +.It Cm ripemd160digest +The +.Tn RIPEMD160 +message digest of the file. +.It Cm rmd160 +A synonym for +.Cm ripemd160digest . +.It Cm rmd160digest +A synonym for +.Cm ripemd160digest . +.It Cm sha1 +The +.Tn FIPS +160-1 +.Pq Dq Tn SHA-1 +message digest of the file. +.It Cm sha1digest +A synonym for +.Cm sha1 . +.It Cm sha256 +The +.Tn FIPS +180-2 +.Pq Dq Tn SHA-256 +message digest of the file. +.It Cm sha256digest +A synonym for +.Cm sha256 . +.It Cm size +The size, in bytes, of the file. +.It Cm time +The last modification time of the file. +.It Cm type +The type of the file; may be set to any one of the following: +.Pp +.Bl -tag -width Cm -compact +.It Cm block +block special device +.It Cm char +character special device +.It Cm dir +directory +.It Cm fifo +fifo +.It Cm file +regular file +.It Cm link +symbolic link +.It Cm socket +socket +.El +.It Cm uid +The file owner as a numeric value. +.It Cm uname +The file owner as a symbolic name. +.El +.Pp +.Sh SEE ALSO +.Xr cksum 1 , +.Xr find 1 , +.Xr mtree 8 +.Sh BUGS +The +.Fx +implementation of mtree does not currently support +the +.Nm +2.0 +format. +The requirement for a +.Dq #mtree +signature line is new and not yet widely implemented. +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.3 Reno . +The +.Tn MD5 +digest capability was added in +.Fx 2.1 , +in response to the widespread use of programs which can spoof +.Xr cksum 1 . +The +.Tn SHA-1 +and +.Tn RIPEMD160 +digests were added in +.Fx 4.0 , +as new attacks have demonstrated weaknesses in +.Tn MD5 . +The +.Tn SHA-256 +digest was added in +.Fx 6.0 . +Support for file flags was added in +.Fx 4.0 , +and mostly comes from +.Nx . +The +.Dq full +entry format was added by +.Nx . diff --git a/libarchive/tar.5 b/libarchive/tar.5 new file mode 100644 index 0000000..e55999b --- /dev/null +++ b/libarchive/tar.5 @@ -0,0 +1,947 @@ +.\" Copyright (c) 2003-2009 Tim Kientzle +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: head/lib/libarchive/tar.5 201077 2009-12-28 01:50:23Z kientzle $ +.\" +.Dd December 27, 2009 +.Dt tar 5 +.Os +.Sh NAME +.Nm tar +.Nd format of tape archive files +.Sh DESCRIPTION +The +.Nm +archive format collects any number of files, directories, and other +file system objects (symbolic links, device nodes, etc.) into a single +stream of bytes. +The format was originally designed to be used with +tape drives that operate with fixed-size blocks, but is widely used as +a general packaging mechanism. +.Ss General Format +A +.Nm +archive consists of a series of 512-byte records. +Each file system object requires a header record which stores basic metadata +(pathname, owner, permissions, etc.) and zero or more records containing any +file data. +The end of the archive is indicated by two records consisting +entirely of zero bytes. +.Pp +For compatibility with tape drives that use fixed block sizes, +programs that read or write tar files always read or write a fixed +number of records with each I/O operation. +These +.Dq blocks +are always a multiple of the record size. +The maximum block size supported by early +implementations was 10240 bytes or 20 records. +This is still the default for most implementations +although block sizes of 1MiB (2048 records) or larger are +commonly used with modern high-speed tape drives. +(Note: the terms +.Dq block +and +.Dq record +here are not entirely standard; this document follows the +convention established by John Gilmore in documenting +.Nm pdtar . ) +.Ss Old-Style Archive Format +The original tar archive format has been extended many times to +include additional information that various implementors found +necessary. +This section describes the variant implemented by the tar command +included in +.At v7 , +which seems to be the earliest widely-used version of the tar program. +.Pp +The header record for an old-style +.Nm +archive consists of the following: +.Bd -literal -offset indent +struct header_old_tar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char linkflag[1]; + char linkname[100]; + char pad[255]; +}; +.Ed +All unused bytes in the header record are filled with nulls. +.Bl -tag -width indent +.It Va name +Pathname, stored as a null-terminated string. +Early tar implementations only stored regular files (including +hardlinks to those files). +One common early convention used a trailing "/" character to indicate +a directory name, allowing directory permissions and owner information +to be archived and restored. +.It Va mode +File mode, stored as an octal number in ASCII. +.It Va uid , Va gid +User id and group id of owner, as octal numbers in ASCII. +.It Va size +Size of file, as octal number in ASCII. +For regular files only, this indicates the amount of data +that follows the header. +In particular, this field was ignored by early tar implementations +when extracting hardlinks. +Modern writers should always store a zero length for hardlink entries. +.It Va mtime +Modification time of file, as an octal number in ASCII. +This indicates the number of seconds since the start of the epoch, +00:00:00 UTC January 1, 1970. +Note that negative values should be avoided +here, as they are handled inconsistently. +.It Va checksum +Header checksum, stored as an octal number in ASCII. +To compute the checksum, set the checksum field to all spaces, +then sum all bytes in the header using unsigned arithmetic. +This field should be stored as six octal digits followed by a null and a space +character. +Note that many early implementations of tar used signed arithmetic +for the checksum field, which can cause interoperability problems +when transferring archives between systems. +Modern robust readers compute the checksum both ways and accept the +header if either computation matches. +.It Va linkflag , Va linkname +In order to preserve hardlinks and conserve tape, a file +with multiple links is only written to the archive the first +time it is encountered. +The next time it is encountered, the +.Va linkflag +is set to an ASCII +.Sq 1 +and the +.Va linkname +field holds the first name under which this file appears. +(Note that regular files have a null value in the +.Va linkflag +field.) +.El +.Pp +Early tar implementations varied in how they terminated these fields. +The tar command in +.At v7 +used the following conventions (this is also documented in early BSD manpages): +the pathname must be null-terminated; +the mode, uid, and gid fields must end in a space and a null byte; +the size and mtime fields must end in a space; +the checksum is terminated by a null and a space. +Early implementations filled the numeric fields with leading spaces. +This seems to have been common practice until the +.St -p1003.1-88 +standard was released. +For best portability, modern implementations should fill the numeric +fields with leading zeros. +.Ss Pre-POSIX Archives +An early draft of +.St -p1003.1-88 +served as the basis for John Gilmore's +.Nm pdtar +program and many system implementations from the late 1980s +and early 1990s. +These archives generally follow the POSIX ustar +format described below with the following variations: +.Bl -bullet -compact -width indent +.It +The magic value consists of the five characters +.Dq ustar +followed by a space. +The version field contains a space character followed by a null. +.It +The numeric fields are generally filled with leading spaces +(not leading zeros as recommended in the final standard). +.It +The prefix field is often not used, limiting pathnames to +the 100 characters of old-style archives. +.El +.Ss POSIX ustar Archives +.St -p1003.1-88 +defined a standard tar file format to be read and written +by compliant implementations of +.Xr tar 1 . +This format is often called the +.Dq ustar +format, after the magic value used +in the header. +(The name is an acronym for +.Dq Unix Standard TAR . ) +It extends the historic format with new fields: +.Bd -literal -offset indent +struct header_posix_ustar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +}; +.Ed +.Bl -tag -width indent +.It Va typeflag +Type of entry. +POSIX extended the earlier +.Va linkflag +field with several new type values: +.Bl -tag -width indent -compact +.It Dq 0 +Regular file. +NUL should be treated as a synonym, for compatibility purposes. +.It Dq 1 +Hard link. +.It Dq 2 +Symbolic link. +.It Dq 3 +Character device node. +.It Dq 4 +Block device node. +.It Dq 5 +Directory. +.It Dq 6 +FIFO node. +.It Dq 7 +Reserved. +.It Other +A POSIX-compliant implementation must treat any unrecognized typeflag value +as a regular file. +In particular, writers should ensure that all entries +have a valid filename so that they can be restored by readers that do not +support the corresponding extension. +Uppercase letters "A" through "Z" are reserved for custom extensions. +Note that sockets and whiteout entries are not archivable. +.El +It is worth noting that the +.Va size +field, in particular, has different meanings depending on the type. +For regular files, of course, it indicates the amount of data +following the header. +For directories, it may be used to indicate the total size of all +files in the directory, for use by operating systems that pre-allocate +directory space. +For all other types, it should be set to zero by writers and ignored +by readers. +.It Va magic +Contains the magic value +.Dq ustar +followed by a NUL byte to indicate that this is a POSIX standard archive. +Full compliance requires the uname and gname fields be properly set. +.It Va version +Version. +This should be +.Dq 00 +(two copies of the ASCII digit zero) for POSIX standard archives. +.It Va uname , Va gname +User and group names, as null-terminated ASCII strings. +These should be used in preference to the uid/gid values +when they are set and the corresponding names exist on +the system. +.It Va devmajor , Va devminor +Major and minor numbers for character device or block device entry. +.It Va name , Va prefix +If the pathname is too long to fit in the 100 bytes provided by the standard +format, it can be split at any +.Pa / +character with the first portion going into the prefix field. +If the prefix field is not empty, the reader will prepend +the prefix value and a +.Pa / +character to the regular name field to obtain the full pathname. +The standard does not require a trailing +.Pa / +character on directory names, though most implementations still +include this for compatibility reasons. +.El +.Pp +Note that all unused bytes must be set to +.Dv NUL . +.Pp +Field termination is specified slightly differently by POSIX +than by previous implementations. +The +.Va magic , +.Va uname , +and +.Va gname +fields must have a trailing +.Dv NUL . +The +.Va pathname , +.Va linkname , +and +.Va prefix +fields must have a trailing +.Dv NUL +unless they fill the entire field. +(In particular, it is possible to store a 256-character pathname if it +happens to have a +.Pa / +as the 156th character.) +POSIX requires numeric fields to be zero-padded in the front, and requires +them to be terminated with either space or +.Dv NUL +characters. +.Pp +Currently, most tar implementations comply with the ustar +format, occasionally extending it by adding new fields to the +blank area at the end of the header record. +.Ss Numeric Extensions +There have been several attempts to extend the range of sizes +or times supported by modifying how numbers are stored in the +header. +.Pp +One obvious extension to increase the size of files is to +eliminate the terminating characters from the various +numeric fields. +For example, the standard only allows the size field to contain +11 octal digits, reserving the twelfth byte for a trailing +NUL character. +Allowing 12 octal digits allows file sizes up to 64 GB. +.Pp +Another extension, utilized by GNU tar, star, and other newer +.Nm +implementations, permits binary numbers in the standard numeric fields. +This is flagged by setting the high bit of the first byte. +The remainder of the field is treated as a signed twos-complement +value. +This permits 95-bit values for the length and time fields +and 63-bit values for the uid, gid, and device numbers. +In particular, this provides a consistent way to handle +negative time values. +GNU tar supports this extension for the +length, mtime, ctime, and atime fields. +Joerg Schilling's star program and the libarchive library support +this extension for all numeric fields. +Note that this extension is largely obsoleted by the extended +attribute record provided by the pax interchange format. +.Pp +Another early GNU extension allowed base-64 values rather than octal. +This extension was short-lived and is no longer supported by any +implementation. +.Ss Pax Interchange Format +There are many attributes that cannot be portably stored in a +POSIX ustar archive. +.St -p1003.1-2001 +defined a +.Dq pax interchange format +that uses two new types of entries to hold text-formatted +metadata that applies to following entries. +Note that a pax interchange format archive is a ustar archive in every +respect. +The new data is stored in ustar-compatible archive entries that use the +.Dq x +or +.Dq g +typeflag. +In particular, older implementations that do not fully support these +extensions will extract the metadata into regular files, where the +metadata can be examined as necessary. +.Pp +An entry in a pax interchange format archive consists of one or +two standard ustar entries, each with its own header and data. +The first optional entry stores the extended attributes +for the following entry. +This optional first entry has an "x" typeflag and a size field that +indicates the total size of the extended attributes. +The extended attributes themselves are stored as a series of text-format +lines encoded in the portable UTF-8 encoding. +Each line consists of a decimal number, a space, a key string, an equals +sign, a value string, and a new line. +The decimal number indicates the length of the entire line, including the +initial length field and the trailing newline. +An example of such a field is: +.Dl 25 ctime=1084839148.1212\en +Keys in all lowercase are standard keys. +Vendors can add their own keys by prefixing them with an all uppercase +vendor name and a period. +Note that, unlike the historic header, numeric values are stored using +decimal, not octal. +A description of some common keys follows: +.Bl -tag -width indent +.It Cm atime , Cm ctime , Cm mtime +File access, inode change, and modification times. +These fields can be negative or include a decimal point and a fractional value. +.It Cm hdrcharset +The character set used by the pax extension values. +By default, all textual values in the pax extended attributes +are assumed to be in UTF-8, including pathnames, user names, +and group names. +In some cases, it is not possible to translate local +conventions into UTF-8. +If this key is present and the value is the six-character ASCII string +.Dq BINARY , +then all textual values are assumed to be in a platform-dependent +multi-byte encoding. +Note that there are only two valid values for this key: +.Dq BINARY +or +.Dq ISO-IR\ 10646\ 2000\ UTF-8 . +No other values are permitted by the standard, and +the latter value should generally not be used as it is the +default when this key is not specified. +In particular, this flag should not be used as a general +mechanism to allow filenames to be stored in arbitrary +encodings. +.It Cm uname , Cm uid , Cm gname , Cm gid +User name, group name, and numeric UID and GID values. +The user name and group name stored here are encoded in UTF8 +and can thus include non-ASCII characters. +The UID and GID fields can be of arbitrary length. +.It Cm linkpath +The full path of the linked-to file. +Note that this is encoded in UTF8 and can thus include non-ASCII characters. +.It Cm path +The full pathname of the entry. +Note that this is encoded in UTF8 and can thus include non-ASCII characters. +.It Cm realtime.* , Cm security.* +These keys are reserved and may be used for future standardization. +.It Cm size +The size of the file. +Note that there is no length limit on this field, allowing conforming +archives to store files much larger than the historic 8GB limit. +.It Cm SCHILY.* +Vendor-specific attributes used by Joerg Schilling's +.Nm star +implementation. +.It Cm SCHILY.acl.access , Cm SCHILY.acl.default +Stores the access and default ACLs as textual strings in a format +that is an extension of the format specified by POSIX.1e draft 17. +In particular, each user or group access specification can include a fourth +colon-separated field with the numeric UID or GID. +This allows ACLs to be restored on systems that may not have complete +user or group information available (such as when NIS/YP or LDAP services +are temporarily unavailable). +.It Cm SCHILY.devminor , Cm SCHILY.devmajor +The full minor and major numbers for device nodes. +.It Cm SCHILY.fflags +The file flags. +.It Cm SCHILY.realsize +The full size of the file on disk. +XXX explain? XXX +.It Cm SCHILY.dev, Cm SCHILY.ino , Cm SCHILY.nlinks +The device number, inode number, and link count for the entry. +In particular, note that a pax interchange format archive using Joerg +Schilling's +.Cm SCHILY.* +extensions can store all of the data from +.Va struct stat . +.It Cm LIBARCHIVE.* +Vendor-specific attributes used by the +.Nm libarchive +library and programs that use it. +.It Cm LIBARCHIVE.creationtime +The time when the file was created. +(This should not be confused with the POSIX +.Dq ctime +attribute, which refers to the time when the file +metadata was last changed.) +.It Cm LIBARCHIVE.xattr. Ns Ar namespace Ns . Ns Ar key +Libarchive stores POSIX.1e-style extended attributes using +keys of this form. +The +.Ar key +value is URL-encoded: +All non-ASCII characters and the two special characters +.Dq = +and +.Dq % +are encoded as +.Dq % +followed by two uppercase hexadecimal digits. +The value of this key is the extended attribute value +encoded in base 64. +XXX Detail the base-64 format here XXX +.It Cm VENDOR.* +XXX document other vendor-specific extensions XXX +.El +.Pp +Any values stored in an extended attribute override the corresponding +values in the regular tar header. +Note that compliant readers should ignore the regular fields when they +are overridden. +This is important, as existing archivers are known to store non-compliant +values in the standard header fields in this situation. +There are no limits on length for any of these fields. +In particular, numeric fields can be arbitrarily large. +All text fields are encoded in UTF8. +Compliant writers should store only portable 7-bit ASCII characters in +the standard ustar header and use extended +attributes whenever a text value contains non-ASCII characters. +.Pp +In addition to the +.Cm x +entry described above, the pax interchange format +also supports a +.Cm g +entry. +The +.Cm g +entry is identical in format, but specifies attributes that serve as +defaults for all subsequent archive entries. +The +.Cm g +entry is not widely used. +.Pp +Besides the new +.Cm x +and +.Cm g +entries, the pax interchange format has a few other minor variations +from the earlier ustar format. +The most troubling one is that hardlinks are permitted to have +data following them. +This allows readers to restore any hardlink to a file without +having to rewind the archive to find an earlier entry. +However, it creates complications for robust readers, as it is no longer +clear whether or not they should ignore the size field for hardlink entries. +.Ss GNU Tar Archives +The GNU tar program started with a pre-POSIX format similar to that +described earlier and has extended it using several different mechanisms: +It added new fields to the empty space in the header (some of which was later +used by POSIX for conflicting purposes); +it allowed the header to be continued over multiple records; +and it defined new entries that modify following entries +(similar in principle to the +.Cm x +entry described above, but each GNU special entry is single-purpose, +unlike the general-purpose +.Cm x +entry). +As a result, GNU tar archives are not POSIX compatible, although +more lenient POSIX-compliant readers can successfully extract most +GNU tar archives. +.Bd -literal -offset indent +struct header_gnu_tar { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + char unused[1]; + struct { + char offset[12]; + char numbytes[12]; + } sparse[4]; + char isextended[1]; + char realsize[12]; + char pad[17]; +}; +.Ed +.Bl -tag -width indent +.It Va typeflag +GNU tar uses the following special entry types, in addition to +those defined by POSIX: +.Bl -tag -width indent +.It "7" +GNU tar treats type "7" records identically to type "0" records, +except on one obscure RTOS where they are used to indicate the +pre-allocation of a contiguous file on disk. +.It "D" +This indicates a directory entry. +Unlike the POSIX-standard "5" +typeflag, the header is followed by data records listing the names +of files in this directory. +Each name is preceded by an ASCII "Y" +if the file is stored in this archive or "N" if the file is not +stored in this archive. +Each name is terminated with a null, and +an extra null marks the end of the name list. +The purpose of this +entry is to support incremental backups; a program restoring from +such an archive may wish to delete files on disk that did not exist +in the directory when the archive was made. +.Pp +Note that the "D" typeflag specifically violates POSIX, which requires +that unrecognized typeflags be restored as normal files. +In this case, restoring the "D" entry as a file could interfere +with subsequent creation of the like-named directory. +.It "K" +The data for this entry is a long linkname for the following regular entry. +.It "L" +The data for this entry is a long pathname for the following regular entry. +.It "M" +This is a continuation of the last file on the previous volume. +GNU multi-volume archives guarantee that each volume begins with a valid +entry header. +To ensure this, a file may be split, with part stored at the end of one volume, +and part stored at the beginning of the next volume. +The "M" typeflag indicates that this entry continues an existing file. +Such entries can only occur as the first or second entry +in an archive (the latter only if the first entry is a volume label). +The +.Va size +field specifies the size of this entry. +The +.Va offset +field at bytes 369-380 specifies the offset where this file fragment +begins. +The +.Va realsize +field specifies the total size of the file (which must equal +.Va size +plus +.Va offset ) . +When extracting, GNU tar checks that the header file name is the one it is +expecting, that the header offset is in the correct sequence, and that +the sum of offset and size is equal to realsize. +.It "N" +Type "N" records are no longer generated by GNU tar. +They contained a +list of files to be renamed or symlinked after extraction; this was +originally used to support long names. +The contents of this record +are a text description of the operations to be done, in the form +.Dq Rename %s to %s\en +or +.Dq Symlink %s to %s\en ; +in either case, both +filenames are escaped using K&R C syntax. +Due to security concerns, "N" records are now generally ignored +when reading archives. +.It "S" +This is a +.Dq sparse +regular file. +Sparse files are stored as a series of fragments. +The header contains a list of fragment offset/length pairs. +If more than four such entries are required, the header is +extended as necessary with +.Dq extra +header extensions (an older format that is no longer used), or +.Dq sparse +extensions. +.It "V" +The +.Va name +field should be interpreted as a tape/volume header name. +This entry should generally be ignored on extraction. +.El +.It Va magic +The magic field holds the five characters +.Dq ustar +followed by a space. +Note that POSIX ustar archives have a trailing null. +.It Va version +The version field holds a space character followed by a null. +Note that POSIX ustar archives use two copies of the ASCII digit +.Dq 0 . +.It Va atime , Va ctime +The time the file was last accessed and the time of +last change of file information, stored in octal as with +.Va mtime . +.It Va longnames +This field is apparently no longer used. +.It Sparse Va offset / Va numbytes +Each such structure specifies a single fragment of a sparse +file. +The two fields store values as octal numbers. +The fragments are each padded to a multiple of 512 bytes +in the archive. +On extraction, the list of fragments is collected from the +header (including any extension headers), and the data +is then read and written to the file at appropriate offsets. +.It Va isextended +If this is set to non-zero, the header will be followed by additional +.Dq sparse header +records. +Each such record contains information about as many as 21 additional +sparse blocks as shown here: +.Bd -literal -offset indent +struct gnu_sparse_header { + struct { + char offset[12]; + char numbytes[12]; + } sparse[21]; + char isextended[1]; + char padding[7]; +}; +.Ed +.It Va realsize +A binary representation of the file's complete size, with a much larger range +than the POSIX file size. +In particular, with +.Cm M +type files, the current entry is only a portion of the file. +In that case, the POSIX size field will indicate the size of this +entry; the +.Va realsize +field will indicate the total size of the file. +.El +.Ss GNU tar pax archives +GNU tar 1.14 (XXX check this XXX) and later will write +pax interchange format archives when you specify the +.Fl -posix +flag. +This format follows the pax interchange format closely, +using some +.Cm SCHILY +tags and introducing new keywords to store sparse file information. +There have been three iterations of the sparse file support, referred to +as +.Dq 0.0 , +.Dq 0.1 , +and +.Dq 1.0 . +.Bl -tag -width indent +.It Cm GNU.sparse.numblocks , Cm GNU.sparse.offset , Cm GNU.sparse.numbytes , Cm GNU.sparse.size +The +.Dq 0.0 +format used an initial +.Cm GNU.sparse.numblocks +attribute to indicate the number of blocks in the file, a pair of +.Cm GNU.sparse.offset +and +.Cm GNU.sparse.numbytes +to indicate the offset and size of each block, +and a single +.Cm GNU.sparse.size +to indicate the full size of the file. +This is not the same as the size in the tar header because the +latter value does not include the size of any holes. +This format required that the order of attributes be preserved and +relied on readers accepting multiple appearances of the same attribute +names, which is not officially permitted by the standards. +.It Cm GNU.sparse.map +The +.Dq 0.1 +format used a single attribute that stored a comma-separated +list of decimal numbers. +Each pair of numbers indicated the offset and size, respectively, +of a block of data. +This does not work well if the archive is extracted by an archiver +that does not recognize this extension, since many pax implementations +simply discard unrecognized attributes. +.It Cm GNU.sparse.major , Cm GNU.sparse.minor , Cm GNU.sparse.name , Cm GNU.sparse.realsize +The +.Dq 1.0 +format stores the sparse block map in one or more 512-byte blocks +prepended to the file data in the entry body. +The pax attributes indicate the existence of this map +(via the +.Cm GNU.sparse.major +and +.Cm GNU.sparse.minor +fields) +and the full size of the file. +The +.Cm GNU.sparse.name +holds the true name of the file. +To avoid confusion, the name stored in the regular tar header +is a modified name so that extraction errors will be apparent +to users. +.El +.Ss Solaris Tar +XXX More Details Needed XXX +.Pp +Solaris tar (beginning with SunOS XXX 5.7 ?? XXX) supports an +.Dq extended +format that is fundamentally similar to pax interchange format, +with the following differences: +.Bl -bullet -compact -width indent +.It +Extended attributes are stored in an entry whose type is +.Cm X , +not +.Cm x , +as used by pax interchange format. +The detailed format of this entry appears to be the same +as detailed above for the +.Cm x +entry. +.It +An additional +.Cm A +header is used to store an ACL for the following regular entry. +The body of this entry contains a seven-digit octal number +followed by a zero byte, followed by the +textual ACL description. +The octal value is the number of ACL entries +plus a constant that indicates the ACL type: 01000000 +for POSIX.1e ACLs and 03000000 for NFSv4 ACLs. +.El +.Ss AIX Tar +XXX More details needed XXX +.Pp +AIX Tar uses a ustar-formatted header with the type +.Cm A +for storing coded ACL information. +Unlike the Solaris format, AIX tar writes this header after the +regular file body to which it applies. +The pathname in this header is either +.Cm NFS4 +or +.Cm AIXC +to indicate the type of ACL stored. +The actual ACL is stored in platform-specific binary format. +.Ss Mac OS X Tar +The tar distributed with Apple's Mac OS X stores most regular files +as two separate files in the tar archive. +The two files have the same name except that the first +one has +.Dq ._ +prepended to the last path element. +This special file stores an AppleDouble-encoded +binary blob with additional metadata about the second file, +including ACL, extended attributes, and resources. +To recreate the original file on disk, each +separate file can be extracted and the Mac OS X +.Fn copyfile +function can be used to unpack the separate +metadata file and apply it to th regular file. +Conversely, the same function provides a +.Dq pack +option to encode the extended metadata from +a file into a separate file whose contents +can then be put into a tar archive. +.Pp +Note that the Apple extended attributes interact +badly with long filenames. +Since each file is stored with the full name, +a separate set of extensions needs to be included +in the archive for each one, doubling the overhead +required for files with long names. +.Ss Summary of tar type codes +The following list is a condensed summary of the type codes +used in tar header records generated by different tar implementations. +More details about specific implementations can be found above: +.Bl -tag -compact -width XXX +.It NUL +Early tar programs stored a zero byte for regular files. +.It Cm 0 +POSIX standard type code for a regular file. +.It Cm 1 +POSIX standard type code for a hard link description. +.It Cm 2 +POSIX standard type code for a symbolic link description. +.It Cm 3 +POSIX standard type code for a character device node. +.It Cm 4 +POSIX standard type code for a block device node. +.It Cm 5 +POSIX standard type code for a directory. +.It Cm 6 +POSIX standard type code for a FIFO. +.It Cm 7 +POSIX reserved. +.It Cm 7 +GNU tar used for pre-allocated files on some systems. +.It Cm A +Solaris tar ACL description stored prior to a regular file header. +.It Cm A +AIX tar ACL description stored after the file body. +.It Cm D +GNU tar directory dump. +.It Cm K +GNU tar long linkname for the following header. +.It Cm L +GNU tar long pathname for the following header. +.It Cm M +GNU tar multivolume marker, indicating the file is a continuation of a file from the previous volume. +.It Cm N +GNU tar long filename support. Deprecated. +.It Cm S +GNU tar sparse regular file. +.It Cm V +GNU tar tape/volume header name. +.It Cm X +Solaris tar general-purpose extension header. +.It Cm g +POSIX pax interchange format global extensions. +.It Cm x +POSIX pax interchange format per-file extensions. +.El +.Sh SEE ALSO +.Xr ar 1 , +.Xr pax 1 , +.Xr tar 1 +.Sh STANDARDS +The +.Nm tar +utility is no longer a part of POSIX or the Single Unix Standard. +It last appeared in +.St -susv2 . +It has been supplanted in subsequent standards by +.Xr pax 1 . +The ustar format is currently part of the specification for the +.Xr pax 1 +utility. +The pax interchange file format is new with +.St -p1003.1-2001 . +.Sh HISTORY +A +.Nm tar +command appeared in Seventh Edition Unix, which was released in January, 1979. +It replaced the +.Nm tp +program from Fourth Edition Unix which in turn replaced the +.Nm tap +program from First Edition Unix. +John Gilmore's +.Nm pdtar +public-domain implementation (circa 1987) was highly influential +and formed the basis of +.Nm GNU tar +(circa 1988). +Joerg Shilling's +.Nm star +archiver is another open-source (GPL) archiver (originally developed +circa 1985) which features complete support for pax interchange +format. +.Pp +This documentation was written as part of the +.Nm libarchive +and +.Nm bsdtar +project by +.An Tim Kientzle Aq kientzle@FreeBSD.org . -- cgit v0.12