zstd: use own Cython-based binding, remove python-zstandard dep

currently requires an externally available libzstd >= 1.3.0,
no bundled zstd yet.

(cherry picked from commit aec36f64a2)
This commit is contained in:
Thomas Waldmann 2017-12-03 05:38:23 +01:00
parent 6bad2395dd
commit 01078328e2
3 changed files with 81 additions and 23 deletions

View File

@ -24,7 +24,7 @@ on_rtd = os.environ.get('READTHEDOCS')
# msgpack pure python data corruption was fixed in 0.4.6. # msgpack pure python data corruption was fixed in 0.4.6.
# Also, we might use some rather recent API features. # Also, we might use some rather recent API features.
install_requires = ['msgpack-python>=0.4.6', 'zstandard', ] install_requires = ['msgpack-python>=0.4.6', ]
# note for package maintainers: if you package borgbackup for distribution, # note for package maintainers: if you package borgbackup for distribution,
# please add llfuse as a *requirement* on all platforms that have a working # please add llfuse as a *requirement* on all platforms that have a working
@ -155,10 +155,20 @@ def detect_libb2(prefixes):
return prefix return prefix
def detect_libzstd(prefixes):
for prefix in prefixes:
filename = os.path.join(prefix, 'include', 'zstd.h')
if os.path.exists(filename):
with open(filename, 'r') as fd:
if 'ZSTD_getFrameContentSize' in fd.read():
return prefix
include_dirs = [] include_dirs = []
library_dirs = [] library_dirs = []
define_macros = [] define_macros = []
crypto_libraries = ['crypto'] crypto_libraries = ['crypto']
compression_libraries = ['lz4']
possible_openssl_prefixes = ['/usr', '/usr/local', '/usr/local/opt/openssl', '/usr/local/ssl', '/usr/local/openssl', possible_openssl_prefixes = ['/usr', '/usr/local', '/usr/local/opt/openssl', '/usr/local/ssl', '/usr/local/openssl',
'/usr/local/borg', '/opt/local', '/opt/pkg', ] '/usr/local/borg', '/opt/local', '/opt/pkg', ]
@ -194,6 +204,18 @@ if libb2_prefix:
crypto_libraries.append('b2') crypto_libraries.append('b2')
define_macros.append(('BORG_USE_LIBB2', 'YES')) define_macros.append(('BORG_USE_LIBB2', 'YES'))
possible_libzstd_prefixes = ['/usr', '/usr/local', '/usr/local/opt/libzstd', '/usr/local/libzstd',
'/usr/local/borg', '/opt/local', '/opt/pkg', ]
if os.environ.get('BORG_LIBZSTD_PREFIX'):
possible_libzstd_prefixes.insert(0, os.environ.get('BORG_LIBZSTD_PREFIX'))
libzstd_prefix = detect_libzstd(possible_libzstd_prefixes)
if libzstd_prefix:
print('Detected and preferring libzstd over bundled ZSTD')
include_dirs.append(os.path.join(libzstd_prefix, 'include'))
library_dirs.append(os.path.join(libzstd_prefix, 'lib'))
compression_libraries.append('zstd')
define_macros.append(('BORG_USE_LIBZSTD', 'YES'))
with open('README.rst', 'r') as fd: with open('README.rst', 'r') as fd:
long_description = fd.read() long_description = fd.read()
@ -754,7 +776,7 @@ cmdclass = {
ext_modules = [] ext_modules = []
if not on_rtd: if not on_rtd:
ext_modules += [ ext_modules += [
Extension('borg.compress', [compress_source], libraries=['lz4'], include_dirs=include_dirs, library_dirs=library_dirs, define_macros=define_macros), Extension('borg.compress', [compress_source], libraries=compression_libraries, include_dirs=include_dirs, library_dirs=library_dirs, define_macros=define_macros),
Extension('borg.crypto.low_level', [crypto_ll_source], libraries=crypto_libraries, include_dirs=include_dirs, library_dirs=library_dirs, define_macros=define_macros), Extension('borg.crypto.low_level', [crypto_ll_source], libraries=crypto_libraries, include_dirs=include_dirs, library_dirs=library_dirs, define_macros=define_macros),
Extension('borg.hashindex', [hashindex_source]), Extension('borg.hashindex', [hashindex_source]),
Extension('borg.item', [item_source]), Extension('borg.item', [item_source]),

View File

@ -0,0 +1,5 @@
#ifdef BORG_USE_LIBZSTD
#include <zstd.h>
#else
#error "TODO"
#endif

View File

@ -22,11 +22,6 @@ try:
except ImportError: except ImportError:
lzma = None lzma = None
try:
import zstd
except ImportError:
zstd = None
from .helpers import Buffer, DecompressionError from .helpers import Buffer, DecompressionError
@ -38,6 +33,17 @@ cdef extern from "lz4.h":
int LZ4_compressBound(int inputSize) nogil int LZ4_compressBound(int inputSize) nogil
cdef extern from "algorithms/zstd-libselect.h":
size_t ZSTD_compress(void* dst, size_t dstCapacity, const void* src, size_t srcSize, int compressionLevel) nogil
size_t ZSTD_decompress(void* dst, size_t dstCapacity, const void* src, size_t compressedSize) nogil
size_t ZSTD_compressBound(size_t srcSize) nogil
unsigned long long ZSTD_CONTENTSIZE_UNKNOWN
unsigned long long ZSTD_CONTENTSIZE_ERROR
unsigned long long ZSTD_getFrameContentSize(const void *src, size_t srcSize) nogil
unsigned ZSTD_isError(size_t code) nogil
const char* ZSTD_getErrorName(size_t code) nogil
buffer = Buffer(bytearray, size=0) buffer = Buffer(bytearray, size=0)
@ -203,25 +209,50 @@ class ZSTD(CompressorBase):
def __init__(self, level=3, **kwargs): def __init__(self, level=3, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.level = level self.level = level
if zstd is None:
raise ValueError('No zstd support found.')
def compress(self, data): def compress(self, idata):
if not isinstance(data, bytes): if not isinstance(idata, bytes):
data = bytes(data) # zstd < 0.9.0 does not work with memoryview idata = bytes(idata) # code below does not work with memoryview
cctx = zstd.ZstdCompressor(level=self.level, write_content_size=True) cdef int isize = len(idata)
data = cctx.compress(data) cdef size_t osize
return super().compress(data) cdef char *source = idata
cdef char *dest
cdef int level = self.level
osize = ZSTD_compressBound(isize)
buf = buffer.get(osize)
dest = <char *> buf
with nogil:
osize = ZSTD_compress(dest, osize, source, isize, level)
if ZSTD_isError(osize):
raise Exception('zstd compress failed: %s' % ZSTD_getErrorName(osize))
return super().compress(dest[:osize])
def decompress(self, data): def decompress(self, idata):
if not isinstance(data, bytes): if not isinstance(idata, bytes):
data = bytes(data) # zstd < 0.9.0 does not work with memoryview idata = bytes(idata) # code below does not work with memoryview
dctx = zstd.ZstdDecompressor() idata = super().decompress(idata)
data = super().decompress(data) cdef int isize = len(idata)
cdef unsigned long long osize
cdef unsigned long long rsize
cdef char *source = idata
cdef char *dest
osize = ZSTD_getFrameContentSize(source, isize)
if osize == ZSTD_CONTENTSIZE_ERROR:
raise DecompressionError('zstd get size failed: data was not compressed by zstd')
if osize == ZSTD_CONTENTSIZE_UNKNOWN:
raise DecompressionError('zstd get size failed: original size unknown')
try: try:
return dctx.decompress(data) buf = buffer.get(osize)
except zstd.ZstdError as e: except MemoryError:
raise DecompressionError(str(e)) from None raise DecompressionError('MemoryError')
dest = <char *> buf
with nogil:
rsize = ZSTD_decompress(dest, osize, source, isize)
if ZSTD_isError(rsize):
raise DecompressionError('zstd decompress failed: %s' % ZSTD_getErrorName(rsize))
if rsize != osize:
raise DecompressionError('zstd decompress failed: size mismatch')
return dest[:osize]
class ZLIB(CompressorBase): class ZLIB(CompressorBase):