store-copy.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013, 2014, 2017, 2018, 2020 Ludovic Courtès <ludo@gnu.org>
  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 build store-copy)
  19. #:use-module ((guix build utils) #:hide (copy-recursively))
  20. #:use-module (guix sets)
  21. #:use-module (guix progress)
  22. #:autoload (guix store deduplication) (copy-file/deduplicate)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-9)
  25. #:use-module (srfi srfi-26)
  26. #:use-module (ice-9 match)
  27. #:use-module (ice-9 rdelim)
  28. #:use-module (ice-9 ftw)
  29. #:use-module (ice-9 vlist)
  30. #:export (store-info?
  31. store-info
  32. store-info-item
  33. store-info-deriver
  34. store-info-references
  35. read-reference-graph
  36. file-size
  37. closure-size
  38. copy-store-item
  39. populate-store))
  40. ;;; Commentary:
  41. ;;;
  42. ;;; This module provides the tools to copy store items and their dependencies
  43. ;;; to another store. It relies on the availability of "reference graph"
  44. ;;; files as produced by 'gexp->derivation' et al. with the
  45. ;;; #:references-graphs parameter.
  46. ;;;
  47. ;;; Code:
  48. ;; Information about a store item as produced by #:references-graphs.
  49. (define-record-type <store-info>
  50. (store-info item deriver references)
  51. store-info?
  52. (item store-info-item) ;string
  53. (deriver store-info-deriver) ;#f | string
  54. (references store-info-references)) ;?
  55. ;; TODO: Factorize with that in (guix store).
  56. (define (topological-sort nodes edges)
  57. "Return NODES in topological order according to EDGES. EDGES must be a
  58. one-argument procedure that takes a node and returns the nodes it is connected
  59. to."
  60. (define (traverse)
  61. ;; Do a simple depth-first traversal of all of PATHS.
  62. (let loop ((nodes nodes)
  63. (visited (setq))
  64. (result '()))
  65. (match nodes
  66. ((head tail ...)
  67. (if (set-contains? visited head)
  68. (loop tail visited result)
  69. (call-with-values
  70. (lambda ()
  71. (loop (edges head)
  72. (set-insert head visited)
  73. result))
  74. (lambda (visited result)
  75. (loop tail visited (cons head result))))))
  76. (()
  77. (values visited result)))))
  78. (call-with-values traverse
  79. (lambda (_ result)
  80. (reverse result))))
  81. (define (read-reference-graph port)
  82. "Read the reference graph as produced by #:references-graphs from PORT and
  83. return it as a list of <store-info> records in topological order--i.e., leaves
  84. come first. IOW, store items in the resulting list can be registered in the
  85. order in which they appear.
  86. The reference graph format consists of sequences of lines like this:
  87. FILE
  88. DERIVER
  89. NUMBER-OF-REFERENCES
  90. REF1
  91. ...
  92. REFN
  93. It is meant as an internal format."
  94. (let loop ((result '())
  95. (table vlist-null)
  96. (referrers vlist-null))
  97. (match (read-line port)
  98. ((? eof-object?)
  99. ;; 'guix-daemon' gives us something that's in "reverse topological
  100. ;; order"--i.e., leaves (items with zero references) come last. Here
  101. ;; we compute the topological order that we want: leaves come first.
  102. (let ((unreferenced? (lambda (item)
  103. (let ((referrers (vhash-fold* cons '()
  104. (store-info-item item)
  105. referrers)))
  106. (or (null? referrers)
  107. (equal? (list item) referrers))))))
  108. (topological-sort (filter unreferenced? result)
  109. (lambda (item)
  110. (map (lambda (item)
  111. (match (vhash-assoc item table)
  112. ((_ . node) node)))
  113. (store-info-references item))))))
  114. (item
  115. (let* ((deriver (match (read-line port)
  116. ("" #f)
  117. (line line)))
  118. (count (string->number (read-line port)))
  119. (refs (unfold-right (cut >= <> count)
  120. (lambda (n)
  121. (read-line port))
  122. 1+
  123. 0))
  124. (item (store-info item deriver refs)))
  125. (loop (cons item result)
  126. (vhash-cons (store-info-item item) item table)
  127. (fold (cut vhash-cons <> item <>)
  128. referrers
  129. refs)))))))
  130. (define (file-size file)
  131. "Return the size in bytes of FILE, entering it if FILE is a directory."
  132. (file-system-fold (const #t)
  133. (lambda (file stat result) ;leaf
  134. (+ (stat:size stat) result))
  135. (lambda (directory stat result) ;down
  136. (+ (stat:size stat) result))
  137. (lambda (directory stat result) ;up
  138. result)
  139. (lambda (file stat result) ;skip
  140. result)
  141. (lambda (file stat errno result)
  142. (format (current-error-port)
  143. "file-size: ~a: ~a~%" file
  144. (strerror errno))
  145. result)
  146. 0
  147. file
  148. lstat))
  149. (define (closure-size reference-graphs)
  150. "Return an estimate of the size of the closure described by
  151. REFERENCE-GRAPHS, a list of reference-graph files."
  152. (define (graph-from-file file)
  153. (map store-info-item
  154. (call-with-input-file file read-reference-graph)))
  155. (define items
  156. (delete-duplicates (append-map graph-from-file reference-graphs)))
  157. (reduce + 0 (map file-size items)))
  158. ;; TODO: Remove when the one in (guix build utils) has #:keep-permissions?,
  159. ;; the fix for <https://bugs.gnu.org/44741>, and when #:keep-mtime? works for
  160. ;; symlinks.
  161. (define* (copy-recursively source destination
  162. #:key
  163. (log (current-output-port))
  164. (follow-symlinks? #f)
  165. (copy-file copy-file)
  166. keep-mtime? keep-permissions?)
  167. "Copy SOURCE directory to DESTINATION. Follow symlinks if FOLLOW-SYMLINKS?
  168. is true; otherwise, just preserve them. Call COPY-FILE to copy regular files.
  169. When KEEP-MTIME? is true, keep the modification time of the files in SOURCE on
  170. those of DESTINATION. When KEEP-PERMISSIONS? is true, preserve file
  171. permissions. Write verbose output to the LOG port."
  172. (define AT_SYMLINK_NOFOLLOW
  173. ;; Guile 2.0 did not define this constant, hence this hack.
  174. (let ((variable (module-variable the-root-module 'AT_SYMLINK_NOFOLLOW)))
  175. (if variable
  176. (variable-ref variable)
  177. 256))) ;for GNU/Linux
  178. (define (set-file-time file stat)
  179. (utime file
  180. (stat:atime stat)
  181. (stat:mtime stat)
  182. (stat:atimensec stat)
  183. (stat:mtimensec stat)
  184. AT_SYMLINK_NOFOLLOW))
  185. (define strip-source
  186. (let ((len (string-length source)))
  187. (lambda (file)
  188. (substring file len))))
  189. (file-system-fold (const #t) ; enter?
  190. (lambda (file stat result) ; leaf
  191. (let ((dest (string-append destination
  192. (strip-source file))))
  193. (format log "`~a' -> `~a'~%" file dest)
  194. (case (stat:type stat)
  195. ((symlink)
  196. (let ((target (readlink file)))
  197. (symlink target dest)))
  198. (else
  199. (copy-file file dest)
  200. (when keep-permissions?
  201. (chmod dest (stat:perms stat)))))
  202. (when keep-mtime?
  203. (set-file-time dest stat))))
  204. (lambda (dir stat result) ; down
  205. (let ((target (string-append destination
  206. (strip-source dir))))
  207. (mkdir-p target)))
  208. (lambda (dir stat result) ; up
  209. (let ((target (string-append destination
  210. (strip-source dir))))
  211. (when keep-mtime?
  212. (set-file-time target stat))
  213. (when keep-permissions?
  214. (chmod target (stat:perms stat)))))
  215. (const #t) ; skip
  216. (lambda (file stat errno result)
  217. (format (current-error-port) "i/o error: ~a: ~a~%"
  218. file (strerror errno))
  219. #f)
  220. #t
  221. source
  222. (if follow-symlinks?
  223. stat
  224. lstat)))
  225. (define* (copy-store-item item target
  226. #:key
  227. (deduplicate? #t)
  228. (log-port (%make-void-port "w")))
  229. "Copy ITEM, a store item, to the store under TARGET, the target root
  230. directory. When DEDUPLICATE? is true, deduplicate it within TARGET."
  231. (define store
  232. (string-append target (%store-directory)))
  233. (copy-recursively item (string-append target item)
  234. #:keep-mtime? #t
  235. #:keep-permissions? #t
  236. #:copy-file
  237. (if deduplicate?
  238. (cut copy-file/deduplicate <> <> #:store store)
  239. copy-file)
  240. #:log log-port))
  241. (define* (populate-store reference-graphs target
  242. #:key
  243. (deduplicate? #t)
  244. (log-port (current-error-port)))
  245. "Populate the store under directory TARGET with the items specified in
  246. REFERENCE-GRAPHS, a list of reference-graph files. Items copied to TARGET
  247. maintain timestamps and permissions. When DEDUPLICATE? is true, deduplicate
  248. regular files as they are copied to TARGET."
  249. (define store
  250. (string-append target (%store-directory)))
  251. (define (things-to-copy)
  252. ;; Return the list of store files to copy to the image.
  253. (define (graph-from-file file)
  254. (map store-info-item
  255. (call-with-input-file file read-reference-graph)))
  256. (delete-duplicates (append-map graph-from-file reference-graphs)))
  257. (mkdir-p store)
  258. (chmod store #o1775)
  259. (let* ((things (things-to-copy))
  260. (len (length things))
  261. (progress (progress-reporter/bar len
  262. (format #f "copying ~a store items"
  263. len)
  264. log-port)))
  265. (call-with-progress-reporter progress
  266. (lambda (report)
  267. (for-each (lambda (thing)
  268. (copy-store-item thing target
  269. #:deduplicate? deduplicate?)
  270. (report))
  271. things)))))
  272. ;;; store-copy.scm ends here