rpm.scm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix 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. ;;; GNU Guix 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 GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix rpm)
  19. #:autoload (gcrypt hash) (hash-algorithm file-hash md5)
  20. #:use-module (guix build utils)
  21. #:use-module (ice-9 format)
  22. #:use-module (ice-9 match)
  23. #:use-module (ice-9 textual-ports)
  24. #:use-module (rnrs bytevectors)
  25. #:use-module (srfi srfi-1)
  26. #:use-module (srfi srfi-9)
  27. #:use-module (srfi srfi-26)
  28. #:use-module (srfi srfi-71)
  29. #:use-module (srfi srfi-171)
  30. #:export (generate-lead
  31. generate-signature
  32. generate-header
  33. assemble-rpm-metadata
  34. ;; XXX: These are internals, but the inline disabling trick
  35. ;; doesn't work on them.
  36. make-header-entry
  37. header-entry?
  38. header-entry-tag
  39. header-entry-count
  40. header-entry-value
  41. bytevector->hex-string
  42. fhs-directory?))
  43. ;;; Commentary:
  44. ;;;
  45. ;;; This module provides the building blocks required to construct RPM
  46. ;;; archives. It is intended to be importable on the build side, so shouldn't
  47. ;;; depend on (guix diagnostics) or other host-side-only modules.
  48. ;;;
  49. ;;; Code:
  50. (define (gnu-system-triplet->machine-type triplet)
  51. "Return the machine component of TRIPLET, a GNU system triplet."
  52. (first (string-split triplet #\-)))
  53. (define (gnu-machine-type->rpm-arch type)
  54. "Return the canonical RPM architecture string, given machine TYPE."
  55. (match type
  56. ("arm" "armv7hl")
  57. ("powerpc" "ppc")
  58. ("powerpc64le" "ppc64le")
  59. (machine machine))) ;unchanged
  60. (define (gnu-machine-type->rpm-number type)
  61. "Translate machine TYPE to its corresponding RPM integer value."
  62. ;; Refer to the rpmrc.in file in the RPM source for the complete
  63. ;; translation tables.
  64. (match type
  65. ((or "i486" "i586" "i686" "x86_64") 1)
  66. ((? (cut string-prefix? "powerpc" <>)) 5)
  67. ("mips64el" 11)
  68. ((? (cut string-prefix? "arm" <>)) 12)
  69. ("aarch64" 19)
  70. ((? (cut string-prefix? "riscv" <>)) 22)
  71. (_ (error "no RPM number known for machine type" type))))
  72. (define (u16-number->u8-list number)
  73. "Return a list of byte values made of NUMBER, a 16 bit unsigned integer."
  74. (let ((bv (uint-list->bytevector (list number) (endianness big) 2)))
  75. (bytevector->u8-list bv)))
  76. (define (u32-number->u8-list number)
  77. "Return a list of byte values made of NUMBER, a 32 bit unsigned integer."
  78. (let ((bv (uint-list->bytevector (list number) (endianness big) 4)))
  79. (bytevector->u8-list bv)))
  80. (define (s32-number->u8-list number)
  81. "Return a list of byte values made of NUMBER, a 32 bit signed integer."
  82. (let ((bv (sint-list->bytevector (list number) (endianness big) 4)))
  83. (bytevector->u8-list bv)))
  84. (define (u8-list->u32-number lst)
  85. "Return the 32 bit unsigned integer corresponding to the 4 bytes in LST."
  86. (bytevector-u32-ref (u8-list->bytevector lst) 0 (endianness big)))
  87. ;;;
  88. ;;; Lead section.
  89. ;;;
  90. ;; Refer to the docs/manual/format.md file of the RPM source for the details
  91. ;; regarding the binary format of an RPM archive.
  92. (define* (generate-lead name-version #:key (target %host-type))
  93. "Generate a RPM lead u8-list that uses NAME-VERSION, the name and version
  94. string of the package, and TARGET, a GNU triplet used to derive the target
  95. machine type."
  96. (define machine-type (gnu-system-triplet->machine-type target))
  97. (define magic (list #xed #xab #xee #xdb))
  98. (define file-format-version (list 3 0)) ;3.0
  99. (define type (list 0 0)) ;0 for binary packages
  100. (define arch-number (u16-number->u8-list
  101. (gnu-machine-type->rpm-number machine-type)))
  102. ;; The 66 bytes from 10 to 75 are for the name-version-release string.
  103. (define name
  104. (let ((padding-bytes (make-list (- 66 (string-length name-version)) 0)))
  105. (append (bytevector->u8-list (string->utf8 name-version))
  106. padding-bytes)))
  107. ;; There is no OS number corresponding to GNU/Hurd (GNU), only Linux, per
  108. ;; rpmrc.in.
  109. (define os-number (list 0 1))
  110. ;; For RPM format 3.0, the signature type is 5, which means a "Header-style"
  111. ;; signature.
  112. (define signature-type (list 0 5))
  113. (define reserved-bytes (make-list 16 0))
  114. (append magic file-format-version type arch-number name
  115. os-number signature-type reserved-bytes))
  116. ;;;
  117. ;;; Header section.
  118. ;;;
  119. (define header-magic (list #x8e #xad #xe8))
  120. (define header-version (list 1))
  121. (define header-reserved (make-list 4 0)) ;4 reserved bytes
  122. ;;; Every header starts with 8 bytes made by the header magic number, the
  123. ;;; header version and 4 reserved bytes.
  124. (define header-intro (append header-magic header-version header-reserved))
  125. ;;; Header entry data types.
  126. (define NULL 0)
  127. (define CHAR 1)
  128. (define INT8 2)
  129. (define INT16 3) ;2-bytes aligned
  130. (define INT32 4) ;4-bytes aligned
  131. (define INT64 5) ;8-bytes aligned
  132. (define STRING 6)
  133. (define BIN 7)
  134. (define STRING_ARRAY 8)
  135. (define I18NSTRIN_TYPE 9)
  136. ;;; Header entry tags.
  137. (define-record-type <rpm-tag>
  138. (make-rpm-tag number type)
  139. rpm-tag?
  140. (number rpm-tag-number)
  141. (type rpm-tag-type))
  142. ;;; The following are internal tags used to identify the data sections.
  143. (define RPMTAG_HEADERSIGNATURES (make-rpm-tag 62 BIN)) ;signature header
  144. (define RPMTAG_HEADERIMMUTABLE (make-rpm-tag 63 BIN)) ;main/data header
  145. (define RPMTAG_HEADERI18NTABLE (make-rpm-tag 100 STRING_ARRAY))
  146. ;;; Subset of RPM tags from include/rpm/rpmtag.h.
  147. (define RPMTAG_NAME (make-rpm-tag 1000 STRING))
  148. (define RPMTAG_VERSION (make-rpm-tag 1001 STRING))
  149. (define RPMTAG_RELEASE (make-rpm-tag 1002 STRING))
  150. (define RPMTAG_SUMMARY (make-rpm-tag 1004 STRING))
  151. (define RPMTAG_SIZE (make-rpm-tag 1009 INT32))
  152. (define RPMTAG_LICENSE (make-rpm-tag 1014 STRING))
  153. (define RPMTAG_OS (make-rpm-tag 1021 STRING))
  154. (define RPMTAG_ARCH (make-rpm-tag 1022 STRING))
  155. (define RPMTAG_PREIN (make-rpm-tag 1023 STRING))
  156. (define RPMTAG_POSTIN (make-rpm-tag 1024 STRING))
  157. (define RPMTAG_PREUN (make-rpm-tag 1025 STRING))
  158. (define RPMTAG_POSTUN (make-rpm-tag 1026 STRING))
  159. (define RPMTAG_FILESIZES (make-rpm-tag 1028 INT32))
  160. (define RPMTAG_FILEMODES (make-rpm-tag 1030 INT16))
  161. (define RPMTAG_FILEDIGESTS (make-rpm-tag 1035 STRING_ARRAY))
  162. (define RPMTAG_FILELINKTOS (make-rpm-tag 1036 STRING_ARRAY))
  163. (define RPMTAG_FILEUSERNAME (make-rpm-tag 1039 STRING_ARRAY))
  164. (define RPMTAG_GROUPNAME (make-rpm-tag 1040 STRING_ARRAY))
  165. (define RPMTAG_PREFIXES (make-rpm-tag 1098 STRING_ARRAY))
  166. (define RPMTAG_DIRINDEXES (make-rpm-tag 1116 INT32))
  167. (define RPMTAG_BASENAMES (make-rpm-tag 1117 STRING_ARRAY))
  168. (define RPMTAG_DIRNAMES (make-rpm-tag 1118 STRING_ARRAY))
  169. (define RPMTAG_PAYLOADFORMAT (make-rpm-tag 1124 STRING))
  170. (define RPMTAG_PAYLOADCOMPRESSOR (make-rpm-tag 1125 STRING))
  171. (define RPMTAG_LONGFILESIZES (make-rpm-tag 5008 INT64))
  172. (define RPMTAG_LONGSIZE (make-rpm-tag 5009 INT64))
  173. ;;; The algorithm used to compute the digest of each file, e.g. RPM_HASH_MD5.
  174. (define RPMTAG_FILEDIGESTALGO (make-rpm-tag 5011 INT32))
  175. ;;; RPMTAG_ENCODING specifies the encoding used for strings, e.g. "utf-8".
  176. (define RPMTAG_ENCODING (make-rpm-tag 5062 STRING))
  177. ;;; Compressed payload digest. Its type is a string array, but currently in
  178. ;;; practice it is equivalent to STRING, since only the first element is used.
  179. (define RPMTAG_PAYLOADDIGEST (make-rpm-tag 5092 STRING_ARRAY))
  180. ;;; The algorithm used to compute the payload digest, e.g. RPM_HASH_SHA256.
  181. (define RPMTAG_PAYLOADDIGESTALGO (make-rpm-tag 5093 INT32))
  182. ;;; The following are taken from the rpmHashAlgo_e enum in rpmcrypto.h.
  183. (define RPM_HASH_MD5 1)
  184. (define RPM_HASH_SHA256 8)
  185. ;;; Other useful internal definitions.
  186. (define REGION_TAG_COUNT 16) ;number of bytes
  187. (define INT32_MAX (1- (expt 2 32))) ;4294967295 bytes (unsigned)
  188. (define (rpm-tag->u8-list tag)
  189. "Return the u8 list corresponding to RPM-TAG, a <rpm-tag> object."
  190. (append (u32-number->u8-list (rpm-tag-number tag))
  191. (u32-number->u8-list (rpm-tag-type tag))))
  192. (define-record-type <header-entry>
  193. (make-header-entry tag count value)
  194. header-entry?
  195. (tag header-entry-tag) ;<rpm-tag>
  196. (count header-entry-count) ;number (u32)
  197. (value header-entry-value)) ;string|number|list|...
  198. (define (entry-type->alignement type)
  199. "Return the byte alignment of TYPE, an RPM header entry type."
  200. (cond ((= INT16 type) 2)
  201. ((= INT32 type) 4)
  202. ((= INT64 type) 8)
  203. (else 1)))
  204. (define (next-aligned-offset offset alignment)
  205. "Return the next position from OFFSET which satisfies ALIGNMENT."
  206. (if (= 0 (modulo offset alignment))
  207. offset
  208. (next-aligned-offset (1+ offset) alignment)))
  209. (define (header-entry->data entry)
  210. "Return the data of ENTRY, a <header-entry> object, as a u8 list."
  211. (let* ((tag (header-entry-tag entry))
  212. (count (header-entry-count entry))
  213. (value (header-entry-value entry))
  214. (number (rpm-tag-number tag))
  215. (type (rpm-tag-type tag)))
  216. (cond
  217. ((= STRING type)
  218. (unless (string? value)
  219. (error "expected string value for STRING type, got" value))
  220. (unless (= 1 count)
  221. (error "count must be 1 for STRING type"))
  222. (let ((value (cond ((= (rpm-tag-number RPMTAG_VERSION) number)
  223. ;; Hyphens are not allowed in version strings.
  224. (string-map (match-lambda
  225. (#\- #\+)
  226. (c c))
  227. value))
  228. (else value))))
  229. (append (bytevector->u8-list (string->utf8 value))
  230. (list 0)))) ;strings must end with null byte
  231. ((= STRING_ARRAY type)
  232. (unless (list? value)
  233. (error "expected a list of strings for STRING_ARRAY type, got" value))
  234. (unless (= count (length value))
  235. (error "expected count to be equal to" (length value) 'got count))
  236. (append-map (lambda (s)
  237. (append (bytevector->u8-list (string->utf8 s))
  238. (list 0))) ;null byte separated
  239. value))
  240. ((member type (list INT8 INT16 INT32))
  241. (if (= 1 count)
  242. (unless (number? value)
  243. (error "expected number value for scalar INT type; got" value))
  244. (unless (list? value)
  245. (error "expected list value for array INT type; got" value)))
  246. (if (list? value)
  247. (cond ((= INT8 type) value)
  248. ((= INT16 type) (append-map u16-number->u8-list value))
  249. ((= INT32 type) (append-map u32-number->u8-list value))
  250. (else (error "unexpected type" type)))
  251. (cond ((= INT8 type) (list value))
  252. ((= INT16 type) (u16-number->u8-list value))
  253. ((= INT32 type) (u32-number->u8-list value))
  254. (else (error "unexpected type" type)))))
  255. ((= BIN type)
  256. (unless (list? value)
  257. (error "expected list value for BIN type; got" value))
  258. value)
  259. (else (error "unimplemented type" type)))))
  260. (define (make-header-index+data entries)
  261. "Return the index and data sections as u8 number lists, via multiple values.
  262. An index is composed of four u32 (16 bytes total) quantities, in order: tag,
  263. type, offset and count."
  264. (match (fold (match-lambda*
  265. ((entry (offset . (index . data)))
  266. (let* ((tag (header-entry-tag entry))
  267. (tag-number (rpm-tag-number tag))
  268. (tag-type (rpm-tag-type tag))
  269. (count (header-entry-count entry))
  270. (data* (header-entry->data entry))
  271. (alignment (entry-type->alignement tag-type))
  272. (aligned-offset (next-aligned-offset offset alignment))
  273. (padding (make-list (- aligned-offset offset) 0)))
  274. (cons (+ aligned-offset (length data*))
  275. (cons (append index
  276. (u32-number->u8-list tag-number)
  277. (u32-number->u8-list tag-type)
  278. (u32-number->u8-list aligned-offset)
  279. (u32-number->u8-list count))
  280. (append data padding data*))))))
  281. '(0 . (() . ()))
  282. entries)
  283. ((offset . (index . data))
  284. (values index data))))
  285. ;; Prevent inlining of the variables/procedures accessed by unit tests.
  286. (set! make-header-index+data make-header-index+data)
  287. (set! RPMTAG_ARCH RPMTAG_ARCH)
  288. (set! RPMTAG_LICENSE RPMTAG_LICENSE)
  289. (set! RPMTAG_NAME RPMTAG_NAME)
  290. (set! RPMTAG_OS RPMTAG_OS)
  291. (set! RPMTAG_RELEASE RPMTAG_RELEASE)
  292. (set! RPMTAG_SUMMARY RPMTAG_SUMMARY)
  293. (set! RPMTAG_VERSION RPMTAG_VERSION)
  294. (define (wrap-in-region-tags header region-tag)
  295. "Wrap HEADER, a header provided as u8-list with REGION-TAG."
  296. (let* ((type (rpm-tag-type region-tag))
  297. (header-intro (take header 16))
  298. (header-rest (drop header 16))
  299. ;; Increment the existing index value to account for the added region
  300. ;; tag index.
  301. (index-length (1+ (u8-list->u32-number
  302. (drop-right (drop header-intro 8) 4)))) ;bytes 8-11
  303. ;; Increment the data length value to account for the added region
  304. ;; tag data.
  305. (data-length (+ REGION_TAG_COUNT
  306. (u8-list->u32-number
  307. (take-right header-intro 4))))) ;last 4 bytes of intro
  308. (unless (member region-tag (list RPMTAG_HEADERSIGNATURES
  309. RPMTAG_HEADERIMMUTABLE))
  310. (error "expected RPMTAG_HEADERSIGNATURES or RPMTAG_HEADERIMMUTABLE, got"
  311. region-tag))
  312. (append (drop-right header-intro 8) ;strip existing index and data lengths
  313. (u32-number->u8-list index-length)
  314. (u32-number->u8-list data-length)
  315. ;; Region tag (16 bytes).
  316. (u32-number->u8-list (rpm-tag-number region-tag)) ;number
  317. (u32-number->u8-list type) ;type
  318. (u32-number->u8-list (- data-length REGION_TAG_COUNT)) ;offset
  319. (u32-number->u8-list REGION_TAG_COUNT) ;count
  320. ;; Immutable region.
  321. header-rest
  322. ;; Region tag trailer (16 bytes). Note: the trailer offset value
  323. ;; is an enforced convention; it has no practical use.
  324. (u32-number->u8-list (rpm-tag-number region-tag)) ;number
  325. (u32-number->u8-list type) ;type
  326. (s32-number->u8-list (* -1 index-length 16)) ;negative offset
  327. (u32-number->u8-list REGION_TAG_COUNT)))) ;count
  328. (define (bytevector->hex-string bv)
  329. (format #f "~{~2,'0x~}" (bytevector->u8-list bv)))
  330. (define (files->md5-checksums files)
  331. "Return the MD5 checksums (formatted as hexadecimal strings) for FILES."
  332. (let ((file-md5 (cut file-hash (hash-algorithm md5) <>)))
  333. (map (lambda (f)
  334. (or (and=> (false-if-exception (file-md5 f))
  335. bytevector->hex-string)
  336. ;; Only regular files (e.g., not directories) can have their
  337. ;; checksum computed.
  338. ""))
  339. files)))
  340. (define (strip-leading-dot name)
  341. "Remove the leading \".\" from NAME, if present. If a single \".\" is
  342. encountered, translate it to \"/\"."
  343. (match name
  344. ("." "/") ;special case
  345. ((? (cut string-prefix? "." <>))
  346. (string-drop name 1))
  347. (x name)))
  348. ;;; An extensive list of required and optional FHS directories, per its 3.0
  349. ;;; revision.
  350. (define %fhs-directories
  351. (list "/bin" "/boot" "/dev"
  352. "/etc" "/etc/opt" "/etc/X11" "/etc/sgml" "/etc/xml"
  353. "/home" "/root" "/lib" "/media" "/mnt"
  354. "/opt" "/opt/bin" "/opt/doc" "/opt/include"
  355. "/opt/info" "/opt/lib" "/opt/man"
  356. "/run" "/sbin" "/srv" "/sys" "/tmp"
  357. "/usr" "/usr/bin" "/usr/include" "/usr/libexec"
  358. "/usr/share" "/usr/share/applications"
  359. "/usr/share/color" "/usr/share/dict" "/usr/share/doc" "/usr/share/games"
  360. "/usr/share/icons" "/usr/share/icons/hicolor"
  361. "/usr/share/icons/hicolor/48x48"
  362. "/usr/share/icons/hicolor/48x48/apps"
  363. "/usr/share/icons/hicolor/scalable"
  364. "/usr/share/icons/hicolor/scalable/apps"
  365. "/usr/share/info" "/usr/share/locale" "/usr/share/man"
  366. "/usr/share/metainfo" "/usr/share/misc"
  367. "/usr/share/nls" "/usr/share/ppd" "/usr/share/sgml"
  368. "/usr/share/terminfo" "/usr/share/tmac" "/usr/share/xml"
  369. "/usr/share/zoneinfo" "/usr/local" "/usr/local/bin" "/usr/local/etc"
  370. "/usr/local/games" "/usr/local/include" "/usr/local/lib"
  371. "/usr/local/man" "/usr/local/sbin" "/usr/local/sbin" "/usr/local/share"
  372. "/usr/local/src" "/var" "/var/account" "/var/backups"
  373. "/var/cache" "/var/cache/fonts" "/var/cache/man" "/var/cache/www"
  374. "/var/crash" "/var/cron" "/var/games" "/var/mail" "/var/msgs"
  375. "/var/lib" "/var/lib/color" "/var/lib/hwclock" "/var/lib/misc"
  376. "/var/local" "/var/lock" "/var/log" "/var/opt" "/var/preserve"
  377. "/var/run" "/var/spool" "/var/spool/lpd" "/var/spool/mqueue"
  378. "/var/spool/news" "/var/spool/rwho" "/var/spool/uucp"
  379. "/var/tmp" "/var/yp"))
  380. (define (fhs-directory? file-name)
  381. "Predicate to check if FILE-NAME is a known File Hierarchy Standard (FHS)
  382. directory."
  383. (member (strip-leading-dot file-name) %fhs-directories))
  384. (define (directory->file-entries directory)
  385. "Return the file lists triplet header entries for the files found under
  386. DIRECTORY."
  387. (with-directory-excursion directory
  388. ;; Skip the initial "." directory, as its name would get concatenated with
  389. ;; the "./" dirname and fail to match "." in the payload.
  390. (let* ((files (cdr (find-files "." #:directories? #t)))
  391. (file-stats (map lstat files))
  392. (directories
  393. (append (list ".")
  394. (filter-map (match-lambda
  395. ((index . file)
  396. (let ((st (list-ref file-stats index)))
  397. (and (eq? 'directory (stat:type st))
  398. file))))
  399. (list-transduce (tenumerate) rcons files))))
  400. ;; Omit any FHS directories found in FILES to avoid the RPM package
  401. ;; from owning them. This can occur when symlinks directives such
  402. ;; as "/usr/bin/hello -> bin/hello" are used.
  403. (package-files package-file-stats
  404. (unzip2 (reverse
  405. (fold (lambda (file stat res)
  406. (if (fhs-directory? file)
  407. res
  408. (cons (list file stat) res)))
  409. '() files file-stats))))
  410. ;; When provided with the index of a file, the directory index must
  411. ;; return the index of the corresponding directory entry.
  412. (dirindexes (map (lambda (d)
  413. (list-index (cut string=? <> d) directories))
  414. (map dirname package-files)))
  415. ;; The files owned are those appearing in 'basenames'; own them
  416. ;; all.
  417. (basenames (map basename package-files))
  418. ;; The directory names must end with a trailing "/".
  419. (dirnames (map (compose strip-leading-dot (cut string-append <> "/"))
  420. directories))
  421. ;; Note: All the file-related entries must have the same length as
  422. ;; the basenames entry.
  423. (symlink-targets (map (lambda (f)
  424. (if (symbolic-link? f)
  425. (readlink f)
  426. "")) ;unused
  427. package-files))
  428. (file-modes (map stat:mode package-file-stats))
  429. (file-sizes (map stat:size package-file-stats))
  430. (file-md5s (files->md5-checksums package-files)))
  431. (let ((basenames-length (length basenames))
  432. (dirindexes-length (length dirindexes)))
  433. (unless (= basenames-length dirindexes-length)
  434. (error "length mismatch for dirIndexes; expected/actual"
  435. basenames-length dirindexes-length))
  436. (append
  437. (if (> (apply max file-sizes) INT32_MAX)
  438. (list (make-header-entry RPMTAG_LONGFILESIZES (length file-sizes)
  439. file-sizes)
  440. (make-header-entry RPMTAG_LONGSIZE 1
  441. (reduce + 0 file-sizes)))
  442. (list (make-header-entry RPMTAG_FILESIZES (length file-sizes)
  443. file-sizes)
  444. (make-header-entry RPMTAG_SIZE 1 (reduce + 0 file-sizes))))
  445. (list
  446. (make-header-entry RPMTAG_FILEMODES (length file-modes) file-modes)
  447. (make-header-entry RPMTAG_FILEDIGESTS (length file-md5s) file-md5s)
  448. (make-header-entry RPMTAG_FILEDIGESTALGO 1 RPM_HASH_MD5)
  449. (make-header-entry RPMTAG_FILELINKTOS (length symlink-targets)
  450. symlink-targets)
  451. (make-header-entry RPMTAG_FILEUSERNAME basenames-length
  452. (make-list basenames-length "root"))
  453. (make-header-entry RPMTAG_GROUPNAME basenames-length
  454. (make-list basenames-length "root"))
  455. ;; The dirindexes, basenames and dirnames tags form the so-called RPM
  456. ;; "path triplet".
  457. (make-header-entry RPMTAG_DIRINDEXES dirindexes-length dirindexes)
  458. (make-header-entry RPMTAG_BASENAMES basenames-length basenames)
  459. (make-header-entry RPMTAG_DIRNAMES (length dirnames) dirnames)))))))
  460. (define (make-header entries)
  461. "Return the u8 list of a RPM header containing ENTRIES, a list of
  462. <rpm-entry> objects."
  463. (let* ((entries (sort entries (lambda (x y)
  464. (< (rpm-tag-number (header-entry-tag x))
  465. (rpm-tag-number (header-entry-tag y))))))
  466. (count (length entries))
  467. (index data (make-header-index+data entries)))
  468. (append header-intro ;8 bytes
  469. (u32-number->u8-list count) ;4 bytes
  470. (u32-number->u8-list (length data)) ;4 bytes
  471. ;; Now starts the header index, which can contain up to 32 entries
  472. ;; of 16 bytes each.
  473. index data)))
  474. (define* (generate-header name version
  475. payload-digest
  476. payload-directory
  477. payload-compressor
  478. #:key
  479. relocatable?
  480. prein-file postin-file
  481. preun-file postun-file
  482. (target %host-type)
  483. (release "0")
  484. (license "N/A")
  485. (summary "RPM archive generated by GNU Guix.")
  486. (os "Linux")) ;see rpmrc.in
  487. "Return the u8 list corresponding to the Header section. PAYLOAD-DIGEST is
  488. the SHA256 checksum string of the compressed payload. PAYLOAD-DIRECTORY is
  489. the directory containing the payload files. PAYLOAD-COMPRESSOR is the name of
  490. the compressor used to compress the CPIO payload, such as \"none\", \"gz\",
  491. \"xz\" or \"zstd\"."
  492. (let* ((rpm-arch (gnu-machine-type->rpm-arch
  493. (gnu-system-triplet->machine-type target)))
  494. (file->string (cut call-with-input-file <> get-string-all))
  495. (prein-script (and=> prein-file file->string))
  496. (postin-script (and=> postin-file file->string))
  497. (preun-script (and=> preun-file file->string))
  498. (postun-script (and=> postun-file file->string)))
  499. (wrap-in-region-tags
  500. (make-header (append
  501. (list (make-header-entry RPMTAG_HEADERI18NTABLE 1 (list "C"))
  502. (make-header-entry RPMTAG_NAME 1 name)
  503. (make-header-entry RPMTAG_VERSION 1 version)
  504. (make-header-entry RPMTAG_RELEASE 1 release)
  505. (make-header-entry RPMTAG_SUMMARY 1 summary)
  506. (make-header-entry RPMTAG_LICENSE 1 license)
  507. (make-header-entry RPMTAG_OS 1 os)
  508. (make-header-entry RPMTAG_ARCH 1 rpm-arch))
  509. (directory->file-entries payload-directory)
  510. (if relocatable?
  511. ;; Note: RPMTAG_PREFIXES must not have a trailing
  512. ;; slash, unless it's '/'. This allows installing the
  513. ;; package via 'rpm -i --prefix=/tmp', for example.
  514. (list (make-header-entry RPMTAG_PREFIXES 1 (list "/")))
  515. '())
  516. (if prein-script
  517. (list (make-header-entry RPMTAG_PREIN 1 prein-script))
  518. '())
  519. (if postin-script
  520. (list (make-header-entry RPMTAG_POSTIN 1 postin-script))
  521. '())
  522. (if preun-script
  523. (list (make-header-entry RPMTAG_PREUN 1 preun-script))
  524. '())
  525. (if postun-script
  526. (list (make-header-entry RPMTAG_POSTUN 1 postun-script))
  527. '())
  528. (if (string=? "none" payload-compressor)
  529. '()
  530. (list (make-header-entry RPMTAG_PAYLOADCOMPRESSOR 1
  531. payload-compressor)))
  532. (list (make-header-entry RPMTAG_ENCODING 1 "utf-8")
  533. (make-header-entry RPMTAG_PAYLOADFORMAT 1 "cpio")
  534. (make-header-entry RPMTAG_PAYLOADDIGEST 1
  535. (list payload-digest))
  536. (make-header-entry RPMTAG_PAYLOADDIGESTALGO 1
  537. RPM_HASH_SHA256))))
  538. RPMTAG_HEADERIMMUTABLE)))
  539. ;;;
  540. ;;; Signature section
  541. ;;;
  542. ;;; Header sha256 checksum.
  543. (define RPMSIGTAG_SHA256 (make-rpm-tag 273 STRING))
  544. ;;; Uncompressed payload size.
  545. (define RPMSIGTAG_PAYLOADSIZE (make-rpm-tag 1007 INT32))
  546. ;;; Header and compressed payload combined size.
  547. (define RPMSIGTAG_SIZE (make-rpm-tag 1000 INT32))
  548. ;;; Uncompressed payload size (when size > max u32).
  549. (define RPMSIGTAG_LONGARCHIVESIZE (make-rpm-tag 271 INT64))
  550. ;;; Header and compressed payload combined size (when size > max u32).
  551. (define RPMSIGTAG_LONGSIZE (make-rpm-tag 270 INT64))
  552. ;;; Extra space reserved for signatures (typically 32 bytes).
  553. (define RPMSIGTAG_RESERVEDSPACE (make-rpm-tag 1008 BIN))
  554. (define (generate-signature header-sha256
  555. header+compressed-payload-size
  556. ;; uncompressed-payload-size
  557. )
  558. "Return the u8 list representing a signature header containing the
  559. HEADER-SHA256 (a string) and the PAYLOAD-SIZE, which is the combined size of
  560. the header and compressed payload."
  561. (define size-tag (if (> header+compressed-payload-size INT32_MAX)
  562. RPMSIGTAG_LONGSIZE
  563. RPMSIGTAG_SIZE))
  564. (wrap-in-region-tags
  565. (make-header (list (make-header-entry RPMSIGTAG_SHA256 1 header-sha256)
  566. (make-header-entry size-tag 1
  567. header+compressed-payload-size)
  568. ;; (make-header-entry RPMSIGTAG_PAYLOADSIZE 1
  569. ;; uncompressed-payload-size)
  570. ;; Reserve 32 bytes of extra space in case users would
  571. ;; like to add signatures, as done in rpmGenerateSignature.
  572. (make-header-entry RPMSIGTAG_RESERVEDSPACE 32
  573. (make-list 32 0))))
  574. RPMTAG_HEADERSIGNATURES))
  575. (define (assemble-rpm-metadata lead signature header)
  576. "Align and append the various u8 list components together, and return the
  577. result as a bytevector."
  578. (let* ((offset (+ (length lead) (length signature)))
  579. (header-offset (next-aligned-offset offset 8))
  580. (padding (make-list (- header-offset offset) 0)))
  581. ;; The Header is 8-bytes aligned.
  582. (u8-list->bytevector (append lead signature padding header))))