uuid.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2016, 2017, 2018, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2017 Danny Milosavljevic <dannym@scratchpost.org>
  4. ;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
  5. ;;;
  6. ;;; This file is part of GNU Guix.
  7. ;;;
  8. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  9. ;;; under the terms of the GNU General Public License as published by
  10. ;;; the Free Software Foundation; either version 3 of the License, or (at
  11. ;;; your option) any later version.
  12. ;;;
  13. ;;; GNU Guix is distributed in the hope that it will be useful, but
  14. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  15. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. ;;; GNU General Public License for more details.
  17. ;;;
  18. ;;; You should have received a copy of the GNU General Public License
  19. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  20. (define-module (gnu system uuid)
  21. #:use-module (srfi srfi-1)
  22. #:use-module (srfi srfi-9)
  23. #:use-module (rnrs bytevectors)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 vlist)
  26. #:use-module (ice-9 regex)
  27. #:use-module (ice-9 format)
  28. #:export (uuid
  29. uuid?
  30. uuid-type
  31. uuid-bytevector
  32. uuid=?
  33. bytevector->uuid
  34. uuid->string
  35. dce-uuid->string
  36. string->uuid
  37. string->dce-uuid
  38. string->iso9660-uuid
  39. string->ext2-uuid
  40. string->ext3-uuid
  41. string->ext4-uuid
  42. string->bcachefs-uuid
  43. string->btrfs-uuid
  44. string->fat-uuid
  45. string->jfs-uuid
  46. string->ntfs-uuid
  47. string->xfs-uuid
  48. iso9660-uuid->string
  49. ;; XXX: For lack of a better place.
  50. sub-bytevector
  51. latin1->string))
  52. ;;;
  53. ;;; Tools that lack a better place.
  54. ;;;
  55. (define (sub-bytevector bv start size)
  56. "Return a copy of the SIZE bytes of BV starting from offset START."
  57. (let ((result (make-bytevector size)))
  58. (bytevector-copy! bv start result 0 size)
  59. result))
  60. (define (latin1->string bv terminator)
  61. "Return a string of BV, a latin1 bytevector, or #f. TERMINATOR is a predicate
  62. that takes a number and returns #t when a termination character is found."
  63. (let ((bytes (take-while (negate terminator) (bytevector->u8-list bv))))
  64. (if (null? bytes)
  65. #f
  66. (list->string (map integer->char bytes)))))
  67. ;;;
  68. ;;; DCE UUIDs.
  69. ;;;
  70. (define-syntax %network-byte-order
  71. (identifier-syntax (endianness big)))
  72. (define (dce-uuid->string uuid)
  73. "Convert UUID, a 16-byte bytevector, to its string representation, something
  74. like \"6b700d61-5550-48a1-874c-a3d86998990e\"."
  75. ;; See <https://tools.ietf.org/html/rfc4122>.
  76. (let ((time-low (bytevector-uint-ref uuid 0 %network-byte-order 4))
  77. (time-mid (bytevector-uint-ref uuid 4 %network-byte-order 2))
  78. (time-hi (bytevector-uint-ref uuid 6 %network-byte-order 2))
  79. (clock-seq (bytevector-uint-ref uuid 8 %network-byte-order 2))
  80. (node (bytevector-uint-ref uuid 10 %network-byte-order 6)))
  81. (format #f "~8,'0x-~4,'0x-~4,'0x-~4,'0x-~12,'0x"
  82. time-low time-mid time-hi clock-seq node)))
  83. (define %uuid-rx
  84. ;; The regexp of a UUID.
  85. (make-regexp "^([[:xdigit:]]{8})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{4})-([[:xdigit:]]{12})$"))
  86. (define (string->dce-uuid str)
  87. "Parse STR as a DCE UUID (see <https://tools.ietf.org/html/rfc4122>) and
  88. return its contents as a 16-byte bytevector. Return #f if STR is not a valid
  89. UUID representation."
  90. (and=> (regexp-exec %uuid-rx str)
  91. (lambda (match)
  92. (letrec-syntax ((hex->number
  93. (syntax-rules ()
  94. ((_ index)
  95. (string->number (match:substring match index)
  96. 16))))
  97. (put!
  98. (syntax-rules ()
  99. ((_ bv index (number len) rest ...)
  100. (begin
  101. (bytevector-uint-set! bv index number
  102. (endianness big) len)
  103. (put! bv (+ index len) rest ...)))
  104. ((_ bv index)
  105. bv))))
  106. (let ((time-low (hex->number 1))
  107. (time-mid (hex->number 2))
  108. (time-hi (hex->number 3))
  109. (clock-seq (hex->number 4))
  110. (node (hex->number 5))
  111. (uuid (make-bytevector 16)))
  112. (put! uuid 0
  113. (time-low 4) (time-mid 2) (time-hi 2)
  114. (clock-seq 2) (node 6)))))))
  115. ;;;
  116. ;;; ISO-9660.
  117. ;;;
  118. ;; <http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-119.pdf>.
  119. (define %iso9660-uuid-rx
  120. ;; Y m d H M S ss
  121. (make-regexp "^([[:digit:]]{4})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})-([[:digit:]]{2})$"))
  122. (define (string->iso9660-uuid str)
  123. "Parse STR as a ISO9660 UUID (which is really a timestamp - see /dev/disk/by-uuid).
  124. Return its contents as a 16-byte bytevector. Return #f if STR is not a valid
  125. ISO9660 UUID representation."
  126. (and=> (regexp-exec %iso9660-uuid-rx str)
  127. (lambda (match)
  128. (letrec-syntax ((match-numerals
  129. (syntax-rules ()
  130. ((_ index (name rest ...) body)
  131. (let ((name (match:substring match index)))
  132. (match-numerals (+ 1 index) (rest ...) body)))
  133. ((_ index () body)
  134. body))))
  135. (match-numerals 1 (year month day hour minute second hundredths)
  136. (string->utf8 (string-append year month day
  137. hour minute second hundredths)))))))
  138. (define (iso9660-uuid->string uuid)
  139. "Given an UUID bytevector, return its timestamp string."
  140. (define (digits->string bytes)
  141. (latin1->string bytes (lambda (c) #f)))
  142. (let* ((year (sub-bytevector uuid 0 4))
  143. (month (sub-bytevector uuid 4 2))
  144. (day (sub-bytevector uuid 6 2))
  145. (hour (sub-bytevector uuid 8 2))
  146. (minute (sub-bytevector uuid 10 2))
  147. (second (sub-bytevector uuid 12 2))
  148. (hundredths (sub-bytevector uuid 14 2))
  149. (parts (list year month day hour minute second hundredths)))
  150. (string-append (string-join (map digits->string parts) "-"))))
  151. ;;;
  152. ;;; FAT32/FAT16.
  153. ;;;
  154. (define-syntax %fat-endianness
  155. ;; Endianness of FAT32/FAT16 file systems.
  156. (identifier-syntax (endianness little)))
  157. (define (fat-uuid->string uuid)
  158. "Convert FAT32/FAT16 UUID, a 4-byte bytevector, to its string representation."
  159. (let ((high (bytevector-uint-ref uuid 0 %fat-endianness 2))
  160. (low (bytevector-uint-ref uuid 2 %fat-endianness 2)))
  161. (format #f "~:@(~4,'0x-~4,'0x~)" low high)))
  162. (define %fat-uuid-rx
  163. (make-regexp "^([[:xdigit:]]{4})-([[:xdigit:]]{4})$"))
  164. (define (string->fat-uuid str)
  165. "Parse STR, which is in FAT32/FAT16 format, and return a bytevector or #f."
  166. (match (regexp-exec %fat-uuid-rx str)
  167. (#f
  168. #f)
  169. (rx-match
  170. (uint-list->bytevector (list (string->number
  171. (match:substring rx-match 2) 16)
  172. (string->number
  173. (match:substring rx-match 1) 16))
  174. %fat-endianness
  175. 2))))
  176. ;;;
  177. ;;; NTFS.
  178. ;;;
  179. (define-syntax %ntfs-endianness
  180. ;; Endianness of NTFS file system.
  181. (identifier-syntax (endianness little)))
  182. (define (ntfs-uuid->string uuid)
  183. "Convert NTFS UUID, a 8-byte bytevector, to its string representation."
  184. (format #f "~{~:@(~x~)~}" (reverse (bytevector->u8-list uuid))))
  185. (define %ntfs-uuid-rx
  186. (make-regexp "^([[:xdigit:]]{16})$"))
  187. (define (string->ntfs-uuid str)
  188. "Parse STR, which is in NTFS format, and return a bytevector or #f."
  189. (match (regexp-exec %ntfs-uuid-rx str)
  190. (#f
  191. #f)
  192. (rx-match
  193. (u8-list->bytevector
  194. (let loop ((str str)
  195. (res '()))
  196. (if (string=? str "")
  197. res
  198. (loop (string-drop str 2)
  199. (cons
  200. (string->number (string-take str 2) 16)
  201. res))))))))
  202. ;;;
  203. ;;; Generic interface.
  204. ;;;
  205. (define string->ext2-uuid string->dce-uuid)
  206. (define string->ext3-uuid string->dce-uuid)
  207. (define string->ext4-uuid string->dce-uuid)
  208. (define string->bcachefs-uuid string->dce-uuid)
  209. (define string->btrfs-uuid string->dce-uuid)
  210. (define string->f2fs-uuid string->dce-uuid)
  211. (define string->jfs-uuid string->dce-uuid)
  212. (define string->xfs-uuid string->dce-uuid)
  213. (define-syntax vhashq
  214. (syntax-rules (=>)
  215. ((_)
  216. vlist-null)
  217. ((_ (key others ... => value) rest ...)
  218. (vhash-consq key value
  219. (vhashq (others ... => value) rest ...)))
  220. ((_ (=> value) rest ...)
  221. (vhashq rest ...))))
  222. (define %uuid-parsers
  223. (vhashq
  224. ('dce 'ext2 'ext3 'ext4 'bcachefs 'btrfs 'f2fs 'jfs 'xfs 'luks
  225. => string->dce-uuid)
  226. ('fat32 'fat16 'fat => string->fat-uuid)
  227. ('ntfs => string->ntfs-uuid)
  228. ('iso9660 => string->iso9660-uuid)))
  229. (define %uuid-printers
  230. (vhashq
  231. ('dce 'ext2 'ext3 'ext4 'bcachefs 'btrfs 'f2fs 'jfs 'xfs 'luks
  232. => dce-uuid->string)
  233. ('iso9660 => iso9660-uuid->string)
  234. ('fat32 'fat16 'fat => fat-uuid->string)
  235. ('ntfs => ntfs-uuid->string)))
  236. (define* (string->uuid str #:optional (type 'dce))
  237. "Parse STR as a UUID of the given TYPE. On success, return the
  238. corresponding bytevector; otherwise return #f."
  239. (match (vhash-assq type %uuid-parsers)
  240. (#f #f)
  241. ((_ . (? procedure? parse)) (parse str))))
  242. ;; High-level UUID representation that carries its type with it.
  243. ;;
  244. ;; This is necessary to serialize bytevectors with the right printer in some
  245. ;; circumstances. For instance, GRUB "search --fs-uuid" command compares the
  246. ;; string representation of UUIDs, not the raw bytes; thus, when emitting a
  247. ;; GRUB 'search' command, we need to produce the right string representation
  248. ;; (see <https://debbugs.gnu.org/cgi/bugreport.cgi?msg=52;att=0;bug=27735>).
  249. (define-record-type <uuid>
  250. (make-uuid type bv)
  251. uuid?
  252. (type uuid-type) ;'dce | 'iso9660 | ...
  253. (bv uuid-bytevector))
  254. (define* (bytevector->uuid bv #:optional (type 'dce))
  255. "Return a UUID object make of BV and TYPE."
  256. (make-uuid type bv))
  257. (define-syntax uuid
  258. (lambda (s)
  259. "Return the UUID object corresponding to the given UUID representation or
  260. #f if the string could not be parsed."
  261. (syntax-case s (quote)
  262. ((_ str (quote type))
  263. (and (string? (syntax->datum #'str))
  264. (identifier? #'type))
  265. ;; A literal string: do the conversion at expansion time.
  266. (let ((bv (string->uuid (syntax->datum #'str)
  267. (syntax->datum #'type))))
  268. (unless bv
  269. (syntax-violation 'uuid "invalid UUID" s))
  270. #`(make-uuid 'type #,(datum->syntax s bv))))
  271. ((_ str)
  272. (string? (syntax->datum #'str))
  273. #'(uuid str 'dce))
  274. ((_ str)
  275. #'(let ((bv (string->uuid str 'dce)))
  276. (and bv (make-uuid 'dce bv))))
  277. ((_ str type)
  278. #'(let ((bv (string->uuid str type)))
  279. (and bv (make-uuid type bv)))))))
  280. (define uuid->string
  281. ;; Convert the given bytevector or UUID object, to the corresponding UUID
  282. ;; string representation.
  283. (match-lambda*
  284. (((? bytevector? bv))
  285. (uuid->string bv 'dce))
  286. (((? bytevector? bv) type)
  287. (match (vhash-assq type %uuid-printers)
  288. (#f #f)
  289. ((_ . (? procedure? unparse)) (unparse bv))))
  290. (((? uuid? uuid))
  291. (uuid->string (uuid-bytevector uuid) (uuid-type uuid)))))
  292. (define uuid=?
  293. ;; Return true if A is equal to B, comparing only the actual bits.
  294. (match-lambda*
  295. (((? bytevector? a) (? bytevector? b))
  296. (bytevector=? a b))
  297. (((? uuid? a) (? bytevector? b))
  298. (bytevector=? (uuid-bytevector a) b))
  299. (((? uuid? a) (? uuid? b))
  300. (bytevector=? (uuid-bytevector a) (uuid-bytevector b)))
  301. (((or (? uuid? a) (? bytevector? a)) (or (? uuid? b) (? bytevector? b)))
  302. (uuid=? b a))))