zstd.scm 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. ;;; Guile-zstd --- GNU Guile bindings to the zstd compression library.
  2. ;;; Copyright © 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of Guile-zstd.
  5. ;;;
  6. ;;; Guile-zstd is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; Guile-zstd is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Guile-zstd. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (zstd)
  19. #:use-module (zstd config)
  20. #:use-module (ice-9 binary-ports)
  21. #:use-module (rnrs bytevectors)
  22. #:use-module (system foreign)
  23. #:use-module (ice-9 match)
  24. #:export (%default-compression-level
  25. make-zstd-input-port
  26. call-with-zstd-input-port
  27. make-zstd-output-port
  28. call-with-zstd-output-port
  29. error-code?
  30. error-name))
  31. ;;; Commentary:
  32. ;;;
  33. ;;; This module provides an interface to the zstd compression library.
  34. ;;;
  35. ;;; Code:
  36. (define %zstd-library
  37. (dynamic-link %zstd-library-file-name))
  38. (define (zstd-procedure return name args)
  39. (pointer->procedure return (dynamic-func name %zstd-library)
  40. args))
  41. (define %input-buffer-struct ;ZSTD_inBuffer_s
  42. `(* ,size_t ,size_t))
  43. (define %output-buffer-struct ;ZSTD_outBuffer_s
  44. %input-buffer-struct)
  45. (define error-code?
  46. (let ((proc (zstd-procedure unsigned-int "ZSTD_isError" (list size_t))))
  47. (lambda (err)
  48. "Return true if ERR, an integer returned by a zstd function, denotes an
  49. error."
  50. (not (zero? (proc err))))))
  51. (define error-name
  52. (let ((proc (zstd-procedure '* "ZSTD_getErrorName" (list size_t))))
  53. (lambda (err)
  54. "Return the error name corresponding to ERR."
  55. (pointer->string (proc err)))))
  56. ;;;
  57. ;;; Compression.
  58. ;;;
  59. (define stream-compression-input-size
  60. (zstd-procedure size_t "ZSTD_CStreamInSize" '()))
  61. (define stream-compression-output-size
  62. (zstd-procedure size_t "ZSTD_CStreamOutSize" '()))
  63. (define make-compression-context
  64. (let ((make (zstd-procedure '* "ZSTD_createCCtx" '()))
  65. (free (delay (dynamic-func "ZSTD_freeCCtx" %zstd-library))))
  66. (lambda ()
  67. (let ((context (make)))
  68. (set-pointer-finalizer! context (force free))
  69. context))))
  70. (define set-compression-context-parameter!
  71. (zstd-procedure void "ZSTD_CCtx_setParameter"
  72. `(* ,int ,int)))
  73. (define compress!
  74. (let ((proc (zstd-procedure size_t "ZSTD_compressStream2"
  75. `(* * * ,int))))
  76. (lambda (context input output mode)
  77. (let ((result (proc context input output mode)))
  78. (when (< result 0)
  79. (throw 'zstd-error 'compress! result))
  80. result))))
  81. ;; ZSTD_cParameter
  82. (define ZSTD_C_COMPRESSION_LEVEL 100)
  83. (define ZSTD_C_CHECKSUM_FLAG 201)
  84. ;; ZSTD_EndDirective
  85. (define ZSTD_E_END 2)
  86. (define ZSTD_E_CONTINUE 0)
  87. (define %default-compression-level 4)
  88. (define* (make-zstd-output-port port
  89. #:key
  90. (close? #t)
  91. (checksum? #t)
  92. (level %default-compression-level))
  93. "Return an output port that compresses data at the given LEVEL, using PORT
  94. as its sink. When CLOSE? is true, PORT is automatically closed when the
  95. resulting port is closed."
  96. (define context
  97. (make-compression-context))
  98. (define input-size (stream-compression-input-size))
  99. (define input-available 0)
  100. (define input-buffer (make-bytevector input-size))
  101. (define output-size (stream-compression-output-size))
  102. (define output-buffer (make-bytevector output-size))
  103. (define output-ptr (bytevector->pointer output-buffer))
  104. (define (flush mode)
  105. (let* ((input-ptr (bytevector->pointer input-buffer))
  106. (input (make-c-struct %input-buffer-struct
  107. (list input-ptr input-available 0))))
  108. (let loop ()
  109. (define output
  110. (make-c-struct %output-buffer-struct
  111. (list output-ptr output-size 0)))
  112. (define remaining
  113. (compress! context output input mode))
  114. (when (error-code? remaining)
  115. (throw 'zstd-error 'compress! remaining))
  116. (match (parse-c-struct output %output-buffer-struct)
  117. ((_ _ position)
  118. (put-bytevector port output-buffer 0 position)))
  119. (match (parse-c-struct input %input-buffer-struct)
  120. ((_ _ position)
  121. (if (or (= position input-available)
  122. (and (= mode ZSTD_E_END)
  123. (zero? remaining)))
  124. (set! input-available 0)
  125. (loop)))))))
  126. (define (write! bv start count)
  127. (if (< input-available input-size)
  128. (let ((count (min count (- input-size input-available))))
  129. (bytevector-copy! bv start
  130. input-buffer input-available
  131. count)
  132. (set! input-available (+ input-available count))
  133. count)
  134. (begin
  135. (flush ZSTD_E_CONTINUE)
  136. (write! bv start count))))
  137. (define (close)
  138. (unless (zero? input-available)
  139. (flush ZSTD_E_END))
  140. (when close?
  141. (close-port port)))
  142. (set-compression-context-parameter! context
  143. ZSTD_C_COMPRESSION_LEVEL level)
  144. (set-compression-context-parameter! context
  145. ZSTD_C_CHECKSUM_FLAG
  146. (if checksum? 1 0))
  147. (make-custom-binary-output-port "zstd-output" write! #f #f close))
  148. (define* (call-with-zstd-output-port port proc
  149. #:key
  150. (level %default-compression-level))
  151. "Call PROC with an output port that wraps PORT and compresses data. PORT is
  152. closed upon completion."
  153. (let ((zstd (make-zstd-output-port port
  154. #:level level #:close? #t)))
  155. (dynamic-wind
  156. (const #t)
  157. (lambda ()
  158. (proc zstd))
  159. (lambda ()
  160. (close-port zstd)))))
  161. ;;;
  162. ;;; Decompression.
  163. ;;;
  164. (define stream-decompression-input-size
  165. (zstd-procedure size_t "ZSTD_DStreamInSize" '()))
  166. (define stream-decompression-output-size
  167. (zstd-procedure size_t "ZSTD_DStreamOutSize" '()))
  168. (define make-decompression-context
  169. (let ((make (zstd-procedure '* "ZSTD_createDCtx" '()))
  170. (free (delay (dynamic-func "ZSTD_freeDCtx" %zstd-library))))
  171. (lambda ()
  172. (let ((context (make)))
  173. (set-pointer-finalizer! context (force free))
  174. context))))
  175. (define decompress!
  176. (zstd-procedure size_t "ZSTD_decompressStream" '(* * *)))
  177. (define* (make-zstd-input-port port #:key (close? #t))
  178. "Return an input port that decompresses data read from PORT.
  179. When CLOSE? is true, PORT is automatically closed when the resulting port is
  180. closed."
  181. (define context
  182. (make-decompression-context))
  183. (define input-size (stream-decompression-input-size))
  184. (define input-available 0)
  185. (define input-buffer (make-bytevector input-size))
  186. (define input-ptr (bytevector->pointer input-buffer))
  187. (define input (make-c-struct %input-buffer-struct
  188. (list input-ptr input-size 0)))
  189. (define eof? #f)
  190. (define expect-more? #f)
  191. (define (read! bv start count)
  192. (if (zero? input-available)
  193. (if eof?
  194. (if expect-more?
  195. (throw 'zstd-error 'decompress! 0) ;premature EOF
  196. 0)
  197. (begin
  198. (set! input-available
  199. (match (get-bytevector-n! port input-buffer 0 input-size)
  200. ((? eof-object?) 0)
  201. (n n)))
  202. (set! input
  203. (make-c-struct %input-buffer-struct
  204. (list input-ptr input-available 0)))
  205. (when (zero? input-available)
  206. (set! eof? #t))
  207. (read! bv start count)))
  208. (let* ((output-ptr (bytevector->pointer bv start))
  209. (output (make-c-struct %output-buffer-struct
  210. (list output-ptr count 0)))
  211. (ret (decompress! context output input)))
  212. (when (error-code? ret)
  213. (throw 'zstd-error 'decompress! ret))
  214. (set! expect-more? (not (zero? ret)))
  215. (match (parse-c-struct input %input-buffer-struct)
  216. ((_ size position)
  217. (set! input-available (- size position))))
  218. (match (parse-c-struct output %output-buffer-struct)
  219. ((_ _ position)
  220. (if (zero? position) ;didn't write anything
  221. (read! bv start count)
  222. position))))))
  223. (define (close)
  224. (when close?
  225. (close-port port)))
  226. (make-custom-binary-input-port "zstd-input" read! #f #f close))
  227. (define* (call-with-zstd-input-port port proc)
  228. "Call PROC with a port that wraps PORT and decompresses data read from it.
  229. PORT is closed upon completion."
  230. (let ((zstd (make-zstd-input-port port #:close? #t)))
  231. (dynamic-wind
  232. (const #t)
  233. (lambda ()
  234. (proc zstd))
  235. (lambda ()
  236. (close-port zstd)))))