summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2015-05-08 15:39:45 +0200
committerLudovic Courtès <ludo@gnu.org>2015-05-08 16:35:32 +0200
commita0dac7a01f766e75dc73200a889f31c3920a2d98 (patch)
tree023dff5bf902e98641e6cd55185a9efb484f028e
parent15137a29c23e4beab7641b6c6191fdff9716dea4 (diff)
profiles: Ensure the profile's etc/ directory is writable.
Reported by 宋文武 <iyzsong@gmail.com>. * guix/build/profiles.scm (build-etc/profile, ensure-writable-directory): New procedures. (build-profile): Use them. * tests/profiles.scm ("etc/profile when etc/ already exists"): New test.
-rw-r--r--guix/build/profiles.scm74
-rw-r--r--tests/profiles.scm29
2 files changed, 88 insertions, 15 deletions
diff --git a/guix/build/profiles.scm b/guix/build/profiles.scm
index eda54cb37a..525d59b979 100644
--- a/guix/build/profiles.scm
+++ b/guix/build/profiles.scm
@@ -21,6 +21,7 @@
#:use-module (guix build utils)
#:use-module (guix search-paths)
#:use-module (srfi srfi-26)
+ #:use-module (ice-9 ftw)
#:use-module (ice-9 match)
#:use-module (ice-9 pretty-print)
#:export (build-profile))
@@ -57,21 +58,9 @@ user-friendly name of the profile is, for instance ~/.guix-profile rather than
port)
(newline port))))
-(define* (build-profile output inputs
- #:key manifest search-paths)
- "Build a user profile from INPUTS in directory OUTPUT. Write MANIFEST, an
-sexp, to OUTPUT/manifest. Create OUTPUT/etc/profile with Bash definitions for
-all the variables listed in SEARCH-PATHS."
- ;; Make the symlinks.
- (union-build output inputs
- #:log-port (%make-void-port "w"))
-
- ;; Store meta-data.
- (call-with-output-file (string-append output "/manifest")
- (lambda (p)
- (pretty-print manifest p)))
-
- ;; Add a ready-to-use Bash profile.
+(define (build-etc/profile output search-paths)
+ "Build the 'OUTPUT/etc/profile' shell file containing environment variable
+definitions for all the SEARCH-PATHS."
(mkdir-p (string-append output "/etc"))
(call-with-output-file (string-append output "/etc/profile")
(lambda (port)
@@ -99,4 +88,59 @@ all the variables listed in SEARCH-PATHS."
(for-each (write-environment-variable-definition port)
(map (abstract-profile output) variables))))))
+(define (ensure-writable-directory directory)
+ "Ensure DIRECTORY exists and is writable. If DIRECTORY is currently a
+symlink (to a read-only directory in the store), then delete the symlink and
+instead make DIRECTORY a \"real\" directory containing symlinks."
+ (define (unsymlink link)
+ (let* ((target (readlink link))
+ (files (scandir target
+ (negate (cut member <> '("." ".."))))))
+ (delete-file link)
+ (mkdir link)
+ (for-each (lambda (file)
+ (symlink (string-append target "/" file)
+ (string-append link "/" file)))
+ files)))
+
+ (catch 'system-error
+ (lambda ()
+ (mkdir directory))
+ (lambda args
+ (let ((errno (system-error-errno args)))
+ (if (= errno EEXIST)
+ (let ((stat (lstat directory)))
+ (case (stat:type stat)
+ ((symlink)
+ ;; "Unsymlink" DIRECTORY so that it is writable.
+ (unsymlink directory))
+ ((directory)
+ #t)
+ (else
+ (error "cannot mkdir because a same-named file exists"
+ directory))))
+ (apply throw args))))))
+
+(define* (build-profile output inputs
+ #:key manifest search-paths)
+ "Build a user profile from INPUTS in directory OUTPUT. Write MANIFEST, an
+sexp, to OUTPUT/manifest. Create OUTPUT/etc/profile with Bash definitions for
+-all the variables listed in SEARCH-PATHS."
+ ;; Make the symlinks.
+ (union-build output inputs
+ #:log-port (%make-void-port "w"))
+
+ ;; Store meta-data.
+ (call-with-output-file (string-append output "/manifest")
+ (lambda (p)
+ (pretty-print manifest p)))
+
+ ;; Make sure we can write to 'OUTPUT/etc'. 'union-build' above could have
+ ;; made 'etc' a symlink to a read-only sub-directory in the store so we need
+ ;; to work around that.
+ (ensure-writable-directory (string-append output "/etc"))
+
+ ;; Write 'OUTPUT/etc/profile'.
+ (build-etc/profile output search-paths))
+
;;; profile.scm ends here
diff --git a/tests/profiles.scm b/tests/profiles.scm
index de1411dca2..ac7f28bf53 100644
--- a/tests/profiles.scm
+++ b/tests/profiles.scm
@@ -24,6 +24,7 @@
#:use-module (guix monads)
#:use-module (guix packages)
#:use-module (guix derivations)
+ #:use-module (guix build-system trivial)
#:use-module (gnu packages bootstrap)
#:use-module ((gnu packages base) #:prefix packages:)
#:use-module ((gnu packages guile) #:prefix packages:)
@@ -248,6 +249,34 @@
(and (zero? (close-pipe pipe))
(string-contains path (string-append profile "/bin"))))))))
+(test-assertm "etc/profile when etc/ already exists"
+ ;; Here 'union-build' makes the profile's etc/ a symlink to the package's
+ ;; etc/ directory, which makes it read-only. Make sure the profile build
+ ;; handles that.
+ (mlet* %store-monad
+ ((thing -> (dummy-package "dummy"
+ (build-system trivial-build-system)
+ (arguments
+ `(#:guile ,%bootstrap-guile
+ #:builder
+ (let ((out (assoc-ref %outputs "out")))
+ (mkdir out)
+ (mkdir (string-append out "/etc"))
+ (call-with-output-file (string-append out "/etc/foo")
+ (lambda (port)
+ (display "foo!" port))))))))
+ (entry -> (package->manifest-entry thing))
+ (drv (profile-derivation (manifest (list entry))
+ #:hooks '()))
+ (profile -> (derivation->output-path drv)))
+ (mbegin %store-monad
+ (built-derivations (list drv))
+ (return (and (file-exists? (string-append profile "/etc/profile"))
+ (string=? (call-with-input-file
+ (string-append profile "/etc/foo")
+ get-string-all)
+ "foo!"))))))
+
(test-end "profiles")