Card Catalog
I ran into a problem with SBCL where not only did I need to save the lisp image into an executable, but I also had to save the shared libraries loaded, via dlopen(), with the lisp image into the current working directory next to the executable. The shared libraries I needed to save were located specifically in the sb-sys:*shared-objects* variable. The reason for this is so I could execute the application on another machine which very likely didn't have all of the shared libraries. The shared libraries were for interfaces from lisp to C generated by CFFI-GROVEL or other specialty libraries not often found on random machines.
There is a chunk of code which implements this mess, but the part which I'm going to show in this post is the portion which calls /sbin/ldconfig -p, parses the output, and returns a hash table that one can query against to convert a bare library to an absolute pathname. The code really isn't spectacular in any way and I'll probably iterate over it some more removing dumb crap I did, but it is very useful, which is good enough for me.
Of course, dlopen() uses other methods to find libraries, like RPATH and crap. This code only implements a library lookup via the /etc/ld.so.cache file.
;; This code snippet is under the Apache Version 2 license and is Copyright
;; 2010 Peter Keller (psilord@cs.wisc.edu). It requires CL-PPCRE and
;; is SBCL specific.
;; Perform the body, which is assumed to open the stream strm in question
;; and write to it. Ugly, make better...
(defmacro stream->string-list ((strm) &body body)
(let ((g (gensym))
(h (gensym)))
`(let ((,h '()))
(with-input-from-string
(,g (with-output-to-string (,strm)
(progn
,@body)))
(loop while (let ((line (read-line ,g nil)))
(when line
(push line ,h))
line))
(nreverse ,h)))))
;; Convert the machine type to a keyword.
(defun get-machine-type ()
(let ((mt (machine-type)))
(cond
((equalp "X86" mt)
:x86)
((equalp "X86-64" mt)
:x86-64)
(t
(error "Unknown machine type ~A, please port!~%" mt)))))
;; Given a list of flags associated with a line from ldconfig -p, find me
;; the library type the library is.
(defun find-lib-type (split-flags)
(if (find "libc6" split-flags :test #'equalp)
"libc6"
(if (find "ELF" split-flags :test #'equalp)
"ELF"
(assert "Unknown lib type. Please port!~%"))))
;; Given a list of flags associated with a line from the ldconfig -p, find
;; me the specific architecture associated with the library.
(defun find-lib-arch (split-flags)
(if (find "x86-64" split-flags :test #'equalp)
:x86-64
:x86))
;; Take a precut line from the ldconfig -p output and merge it with the rest
;; of the lines in the hash table ht.
(defun merge-ld.so.cache-line (bare-lib split-flags absolute-lib ht)
;; Ensure the bare-lib has a hash table entry in the master table.
(when (null (gethash bare-lib ht))
(setf (gethash bare-lib ht) (make-hash-table :test #'equalp)))
;; The type of the library is either libc6 or ELF, but not both. So
;; find out which one it is and set the type in the hash table value
;; for the library in question. Ensure the type didn't change!
(let ((lib-type (find-lib-type split-flags))
(vht (gethash bare-lib ht)))
(let ((prev-lib-type (gethash :type vht)))
(if (null prev-lib-type)
(setf (gethash :type vht) lib-type)
(when (not (equal prev-lib-type lib-type))
(error "~A changed library type!" bare-lib)))))
;; For each arch, if the value list doesn't exist, make one and
;; insert it, otherwise insert the entry at the end of the list. We
;; do it at the end because we're following the search order as
;; found in the file.
(let ((lib-arch (find-lib-arch split-flags))
(vht (gethash bare-lib ht)))
(let ((prev-lib-list (gethash lib-arch vht)))
(if (null prev-lib-list)
(setf (gethash lib-arch vht) (list absolute-lib))
(rplacd (last (gethash lib-arch vht)) (list absolute-lib))))))
(defun parse-ld.so.cache (&key (program "/sbin/ldconfig") (args '("-p")))
;; Read all of the output of the program as lines.
(let ((ht (make-hash-table :test #'equalp))
(lines (stream->string-list
(out-stream)
(sb-ext:process-close
(sb-ext:run-program program args :output out-stream)))))
;; Pop the first line off, it is a count of libs and other junk
(pop lines)
;; Assemble the master hash table which condenses the ldconfig -p info
;; into a meaningful object upon which I can query.
(dolist (line lines)
(register-groups-bind
(bare-lib flags absolute-lib)
("\\s*(.*)\\s+\\((.*)\\)\\s+=>\\s+(.*)\\s*" line)
(let ((split-flags
(mapcar #'(lambda (str)
(setf str (regex-replace "^\\s+" str ""))
(regex-replace "\\s+$" str ""))
(split "," flags))))
(merge-ld.so.cache-line bare-lib split-flags absolute-lib ht))))
ht))
;; Convert a bare-lib into an absolute path depending upon
;; architecture and whatnot. Either returns an absolute path, or nil.
;;
;; Can specify an ordering of :all, :first (the default), or :last.
;; The ordering of :all will present the libraries in the order found
;; out of the output for ldconfig -p.
(defun query-ld.so.cache (bare-lib ht &key (ordering :first))
(let ((vht (gethash bare-lib ht)))
(if (null vht)
nil
(let ((all-absolute-libs (gethash (get-machine-type) vht)))
(ecase ordering
(:all
all-absolute-libs)
(:first
(car all-absolute-libs))
(:last
(last all-absolute-libs)))))))
You'd use it like (suppose in SLIME's REPL):
CL-MW> (setf cache (parse-ld.so.cache))
#<HASH-TABLE :TEST EQUALP :COUNT 1275 {B546831}>
CL-MW> (query-ld.so.cache "libGL.so" cache)
"/usr/lib/mesa/libGL.so"
CL-MW> (query-ld.so.cache "libGL.so" cache :ordering :all)
("/usr/lib/mesa/libGL.so" "/usr/lib/libGL.so")
CL-MW> (query-ld.so.cache "libGL.so" cache :ordering :first)
"/usr/lib/mesa/libGL.so"
CL-MW> (query-ld.so.cache "libGL.so" cache :ordering :last)
("/usr/lib/libGL.so")
End of Line.