From d9ae938f2c950f3bf1896fb07189c3e28b4d8029 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 28 Mar 2015 21:26:33 +0100 Subject: gexp: Add 'local-file'. * guix/gexp.scm (): New record type. (local-file): New procedure. (local-file-compiler): New compiler. (gexp->sexp) : Handle the case where 'lower' returns a file name. (text-file*): Update docstring.local-file doc * tests/gexp.scm ("one local file", "gexp->derivation, local-file"): New tests. * doc/guix.texi (G-Expressions): Mention local files early. Document 'local-file'. Update 'text-file*' documentation. --- guix/gexp.scm | 47 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index 01290dba18..2492974d8f 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -31,6 +31,8 @@ (define-module (guix gexp) gexp-input gexp-input? + local-file + local-file? gexp->derivation gexp->file @@ -133,6 +135,37 @@ (define-gexp-compiler (derivation-compiler (drv derivation?) system target) (with-monad %store-monad (return drv))) + +;;; +;;; Local files. +;;; + +(define-record-type + (%local-file file name recursive?) + local-file? + (file local-file-file) ;string + (name local-file-name) ;string + (recursive? local-file-recursive?)) ;Boolean + +(define* (local-file file #:optional (name (basename file)) + #:key (recursive? #t)) + "Return an object representing local file FILE to add to the store; this +object can be used in a gexp. FILE will be added to the store under NAME--by +default the base name of FILE. + +When RECURSIVE? is true, the contents of FILE are added recursively; if FILE +designates a flat file and RECURSIVE? is true, its contents are added, and its +permission bits are kept. + +This is the declarative counterpart of the 'interned-file' monadic procedure." + (%local-file file name recursive?)) + +(define-gexp-compiler (local-file-compiler (file local-file?) system target) + ;; "Compile" FILE by adding it to the store. + (match file + (($ file name recursive?) + (interned-file file name #:recursive? recursive?)))) + ;;; ;;; Inputs & outputs. @@ -453,8 +486,13 @@ (define* (reference->sexp ref #:optional native?) (($ (? struct? thing) output n?) (let ((lower (lookup-compiler thing)) (target (if (or n? native?) #f target))) - (mlet %store-monad ((drv (lower thing system target))) - (return (derivation->output-path drv output))))) + (mlet %store-monad ((obj (lower thing system target))) + ;; OBJ must be either a derivation or a store file name. + (return (match obj + ((? derivation? drv) + (derivation->output-path drv output)) + ((? string? file) + file)))))) (($ x) (return x)) (x @@ -809,8 +847,9 @@ (define (gexp->file name exp) (define* (text-file* name #:rest text) "Return as a monadic value a derivation that builds a text file containing -all of TEXT. TEXT may list, in addition to strings, packages, derivations, -and store file names; the resulting store file holds references to all these." +all of TEXT. TEXT may list, in addition to strings, objects of any type that +can be used in a gexp: packages, derivations, local file objects, etc. The +resulting store file holds references to all these." (define builder (gexp (call-with-output-file (ungexp output "out") (lambda (port) -- cgit v1.2.3 From 2242ff45fa25656a0b4420fc901e22058513e338 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Sat, 28 Mar 2015 18:24:03 +0100 Subject: gexp: Slightly simplify 'lower-inputs'. * guix/gexp.scm (lower-inputs): Simplify first case by removing the 'input' binding. --- guix/gexp.scm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'guix') diff --git a/guix/gexp.scm b/guix/gexp.scm index 2492974d8f..de8b7bbb46 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm @@ -204,12 +204,12 @@ (define* (lower-inputs inputs (with-monad %store-monad (sequence %store-monad (map (match-lambda - ((and ((? struct? thing) sub-drv ...) input) - (mlet* %store-monad ((lower -> (lookup-compiler thing)) - (drv (lower thing system target))) - (return `(,drv ,@sub-drv)))) - (input - (return input))) + (((? struct? thing) sub-drv ...) + (mlet* %store-monad ((lower -> (lookup-compiler thing)) + (drv (lower thing system target))) + (return `(,drv ,@sub-drv)))) + (input + (return input))) inputs)))) (define* (lower-reference-graphs graphs #:key system target) -- cgit v1.2.3 From 96af558907fecb22086a4ebc260b20f34fb25335 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 30 Mar 2015 15:20:55 +0200 Subject: elf: Add missing argument in 'elf-segment'. * guix/elf.scm (elf-segment): Add missing argument N. --- guix/elf.scm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'guix') diff --git a/guix/elf.scm b/guix/elf.scm index a4b0e819a5..4283dbd2e4 100644 --- a/guix/elf.scm +++ b/guix/elf.scm @@ -1,6 +1,6 @@ ;;; Guile ELF reader and writer -;; Copyright (C) 2011, 2012, 2013, 2014 Free Software Foundation, Inc. +;; Copyright (C) 2011, 2012, 2013, 2014, 2015 Free Software Foundation, Inc. ;;;; This library is free software; you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public @@ -571,6 +571,7 @@ (define (elf-segment elf n) ((4) parse-elf32-program-header) ((8) parse-elf64-program-header) (else (error "unhandled pointer size"))) + n (elf-bytes elf) (+ (elf-phoff elf) (* n (elf-phentsize elf))) (elf-byte-order elf))) -- cgit v1.2.3 From 15aa2c38429a5785ed08519c88ff89a0b7027f0f Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 31 Mar 2015 22:10:08 +0200 Subject: Add (guix build gremlin). * guix/build/gremlin.scm, tests/gremlin.scm: New files. * Makefile.am (MODULES): Add guix/build/gremlin.scm. (SCM_TESTS): Add tests/gremlin.scm. --- Makefile.am | 2 + guix/build/gremlin.scm | 236 +++++++++++++++++++++++++++++++++++++++++++++++++ tests/gremlin.scm | 57 ++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 guix/build/gremlin.scm create mode 100644 tests/gremlin.scm (limited to 'guix') diff --git a/Makefile.am b/Makefile.am index 4a1f8d0a88..cf709986ed 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,7 @@ MODULES = \ guix/build/cvs.scm \ guix/build/svn.scm \ guix/build/syscalls.scm \ + guix/build/gremlin.scm \ guix/build/emacs-utils.scm \ guix/build/graft.scm \ guix/packages.scm \ @@ -178,6 +179,7 @@ SCM_TESTS = \ tests/union.scm \ tests/profiles.scm \ tests/syscalls.scm \ + tests/gremlin.scm \ tests/lint.scm if HAVE_GUILE_JSON diff --git a/guix/build/gremlin.scm b/guix/build/gremlin.scm new file mode 100644 index 0000000000..e8429129e1 --- /dev/null +++ b/guix/build/gremlin.scm @@ -0,0 +1,236 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2015 Ludovic Courtès +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (guix build gremlin) + #:use-module (guix elf) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-26) + #:use-module (system foreign) + #:use-module (rnrs bytevectors) + #:use-module (rnrs io ports) + #:export (elf-dynamic-info + elf-dynamic-info? + elf-dynamic-info-sopath + elf-dynamic-info-needed + elf-dynamic-info-rpath + elf-dynamic-info-runpath + + validate-needed-in-runpath)) + +;;; Commentary: +;;; +;;; A gremlin is sort-of like an elf, you know, and this module provides tools +;;; to deal with dynamic-link information from ELF files. +;;; +;;; Code: + +(define (dynamic-link-segment elf) + "Return the 'PT_DYNAMIC' segment of ELF--i.e., the segment that contains +dynamic linking information." + (find (lambda (segment) + (= (elf-segment-type segment) PT_DYNAMIC)) + (elf-segments elf))) + +(define (word-reader size byte-order) + "Return a procedure to read a word of SIZE bytes according to BYTE-ORDER." + (case size + ((8) + (lambda (bv index) + (bytevector-u64-ref bv index byte-order))) + ((4) + (lambda (bv index) + (bytevector-u32-ref bv index byte-order))))) + + +;; Dynamic entry: +;; +;; typedef struct +;; { +;; Elf64_Sxword d_tag; /* Dynamic entry type */ +;; union +;; { +;; Elf64_Xword d_val; /* Integer value */ +;; Elf64_Addr d_ptr; /* Address value */ +;; } d_un; +;; } Elf64_Dyn; + +(define (raw-dynamic-entries elf segment) + "Return as a list of type/value pairs all the dynamic entries found in +SEGMENT, the 'PT_DYNAMIC' segment of ELF. In the result, each car is a DT_ +value, and the interpretation of the cdr depends on the type." + (define start + (elf-segment-offset segment)) + (define bytes + (elf-bytes elf)) + (define word-size + (elf-word-size elf)) + (define byte-order + (elf-byte-order elf)) + (define read-word + (word-reader word-size byte-order)) + + (let loop ((offset 0) + (result '())) + (if (>= offset (elf-segment-memsz segment)) + (reverse result) + (let ((type (read-word bytes (+ start offset))) + (value (read-word bytes (+ start offset word-size)))) + (if (= type DT_NULL) ;finished? + (reverse result) + (loop (+ offset (* 2 word-size)) + (alist-cons type value result))))))) + +(define (vma->offset elf vma) + "Convert VMA, a virtual memory address, to an offset within ELF. + +Do that by looking at the loadable program segment (PT_LOAD) of ELF that +contains VMA and by taking into account that segment's virtual address and +offset." + ;; See 'offset_from_vma' in Binutils. + (define loads + (filter (lambda (segment) + (= (elf-segment-type segment) PT_LOAD)) + (elf-segments elf))) + + (let ((load (find (lambda (segment) + (let ((vaddr (elf-segment-vaddr segment))) + (and (>= vma vaddr) + (< vma (+ (elf-segment-memsz segment) + vaddr))))) + loads))) + (+ (- vma (elf-segment-vaddr load)) + (elf-segment-offset load)))) + +(define (dynamic-entries elf segment) + "Return all the dynamic entries found in SEGMENT, the 'PT_DYNAMIC' segment +of ELF, as a list of type/value pairs. The type is a DT_ value, and the value +may be a string or an integer depending on the entry type (for instance, the +value of DT_NEEDED entries is a string.)" + (define entries + (raw-dynamic-entries elf segment)) + + (define string-table-offset + (any (match-lambda + ((type . value) + (and (= type DT_STRTAB) value)) + (_ #f)) + entries)) + + (define (interpret-dynamic-entry type value) + (cond ((memv type (list DT_NEEDED DT_SONAME DT_RPATH DT_RUNPATH)) + (if string-table-offset + (pointer->string + (bytevector->pointer (elf-bytes elf) + (vma->offset + elf + (+ string-table-offset value)))) + value)) + (else + value))) + + (map (match-lambda + ((type . value) + (cons type (interpret-dynamic-entry type value)))) + entries)) + + +;;; +;;; High-level interface. +;;; + +(define-record-type + (%elf-dynamic-info soname needed rpath runpath) + elf-dynamic-info? + (soname elf-dynamic-info-soname) + (needed elf-dynamic-info-needed) + (rpath elf-dynamic-info-rpath) + (runpath elf-dynamic-info-runpath)) + +(define search-path->list + (let ((not-colon (char-set-complement (char-set #\:)))) + (lambda (str) + "Split STR on ':' characters." + (string-tokenize str not-colon)))) + +(define (elf-dynamic-info elf) + "Return dynamic-link information for ELF as an object, or +#f if ELF lacks dynamic-link information." + (match (dynamic-link-segment elf) + (#f #f) + ((? elf-segment? dynamic) + (let ((entries (dynamic-entries elf dynamic))) + (%elf-dynamic-info (assv-ref entries DT_SONAME) + (filter-map (match-lambda + ((type . value) + (and (= type DT_NEEDED) value)) + (_ #f)) + entries) + (or (and=> (assv-ref entries DT_RPATH) + search-path->list) + '()) + (or (and=> (assv-ref entries DT_RUNPATH) + search-path->list) + '())))))) + +(define %libc-libraries + ;; List of libraries as of glibc 2.21 (there are more but those are + ;; typically mean to be LD_PRELOADed and thus do not appear as NEEDED.) + '("libanl.so" + "libcrypt.so" + "libc.so" + "libdl.so" + "libm.so" + "libpthread.so" + "libresolv.so" + "librt.so" + "libutil.so")) + +(define (libc-library? lib) + "Return #t if LIB is one of the libraries shipped with the GNU C Library." + (find (lambda (libc-lib) + (string-prefix? libc-lib lib)) + %libc-libraries)) + +(define* (validate-needed-in-runpath file + #:key (always-found? libc-library?)) + "Return #t if all the libraries listed as FILE's 'DT_NEEDED' entries are +present in its RUNPATH, or if FILE lacks dynamic-link information. Return #f +otherwise. Libraries whose name matches ALWAYS-FOUND? are considered to be +always available." + (let* ((elf (call-with-input-file file + (compose parse-elf get-bytevector-all))) + (dyninfo (elf-dynamic-info elf))) + (when dyninfo + (let* ((runpath (elf-dynamic-info-runpath dyninfo)) + (needed (remove always-found? + (elf-dynamic-info-needed dyninfo))) + (not-found (remove (cut search-path runpath <>) + needed))) + (for-each (lambda (lib) + (format (current-error-port) + "error: '~a' depends on '~a', which cannot \ +be found in RUNPATH ~s~%" + file lib runpath)) + not-found) + ;; (when (null? not-found) + ;; (format (current-error-port) "~a is OK~%" file)) + (null? not-found))))) + +;;; gremlin.scm ends here diff --git a/tests/gremlin.scm b/tests/gremlin.scm new file mode 100644 index 0000000000..225a72ff9f --- /dev/null +++ b/tests/gremlin.scm @@ -0,0 +1,57 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2015 Ludovic Courtès +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (test-gremlin) + #:use-module (guix elf) + #:use-module (guix build utils) + #:use-module (guix build gremlin) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-64) + #:use-module (rnrs io ports) + #:use-module (ice-9 match)) + +(define %guile-executable + (match (command-line) + ((program . _) + (and (file-exists? program) (elf-file? program) + program)) + (_ + #f))) + +(define read-elf + (compose parse-elf get-bytevector-all)) + + +(test-begin "gremlin") + +(unless %guile-executable (test-skip 1)) +(test-assert "elf-dynamic-info-needed, executable" + (let* ((elf (call-with-input-file %guile-executable read-elf)) + (dyninfo (elf-dynamic-info elf))) + (or (not dyninfo) ;static executable + (lset<= string=? + (list (string-append "libguile-" (effective-version)) + "libgc" "libunistring" "libffi") + (map (lambda (lib) + (string-take lib (string-contains lib ".so"))) + (elf-dynamic-info-needed dyninfo)))))) + +(test-end "gremlin") + + +(exit (= (test-runner-fail-count (test-runner-current)) 0)) -- cgit v1.2.3