os.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       os.lisp (18098B)
       ---
            1 ;;;; ---------------------------------------------------------------------------
            2 ;;;; Access to the Operating System
            3 
            4 (uiop/package:define-package :uiop/os
            5   (:use :uiop/common-lisp :uiop/package :uiop/utility)
            6   (:export
            7    #:featurep #:os-unix-p #:os-macosx-p #:os-windows-p #:os-genera-p #:detect-os ;; features
            8    #:os-cond
            9    #:getenv #:getenvp ;; environment variables
           10    #:implementation-identifier ;; implementation identifier
           11    #:implementation-type #:*implementation-type*
           12    #:operating-system #:architecture #:lisp-version-string
           13    #:hostname #:getcwd #:chdir
           14    ;; Windows shortcut support
           15    #:read-null-terminated-string #:read-little-endian
           16    #:parse-file-location-info #:parse-windows-shortcut))
           17 (in-package :uiop/os)
           18 
           19 ;;; Features
           20 (with-upgradability ()
           21   (defun featurep (x &optional (*features* *features*))
           22     "Checks whether a feature expression X is true with respect to the *FEATURES* set,
           23 as per the CLHS standard for #+ and #-. Beware that just like the CLHS,
           24 we assume symbols from the KEYWORD package are used, but that unless you're using #+/#-
           25 your reader will not have magically used the KEYWORD package, so you need specify
           26 keywords explicitly."
           27     (cond
           28       ((atom x) (and (member x *features*) t))
           29       ((eq :not (car x)) (assert (null (cddr x))) (not (featurep (cadr x))))
           30       ((eq :or (car x)) (some #'featurep (cdr x)))
           31       ((eq :and (car x)) (every #'featurep (cdr x)))
           32       (t (parameter-error "~S: malformed feature specification ~S" 'featurep x))))
           33 
           34   ;; Starting with UIOP 3.1.5, these are runtime tests.
           35   ;; You may bind *features* with a copy of what your target system offers to test its properties.
           36   (defun os-macosx-p ()
           37     "Is the underlying operating system MacOS X?"
           38     ;; OS-MACOSX is not mutually exclusive with OS-UNIX,
           39     ;; in fact the former implies the latter.
           40     (featurep '(:or :darwin (:and :allegro :macosx) (:and :clisp :macos))))
           41 
           42   (defun os-unix-p ()
           43     "Is the underlying operating system some Unix variant?"
           44     (or (featurep '(:or :unix :cygwin)) (os-macosx-p)))
           45 
           46   (defun os-windows-p ()
           47     "Is the underlying operating system Microsoft Windows?"
           48     (and (not (os-unix-p)) (featurep '(:or :win32 :windows :mswindows :mingw32 :mingw64))))
           49 
           50   (defun os-genera-p ()
           51     "Is the underlying operating system Genera (running on a Symbolics Lisp Machine)?"
           52     (featurep :genera))
           53 
           54   (defun os-oldmac-p ()
           55     "Is the underlying operating system an (emulated?) MacOS 9 or earlier?"
           56     (featurep :mcl))
           57 
           58   (defun os-haiku-p ()
           59     "Is the underlying operating system Haiku?"
           60     (featurep :haiku))
           61 
           62   (defun os-mezzano-p ()
           63     "Is the underlying operating system Mezzano?"
           64     (featurep :mezzano))
           65 
           66   (defun detect-os ()
           67     "Detects the current operating system. Only needs be run at compile-time,
           68 except on ABCL where it might change between FASL compilation and runtime."
           69     (loop* :with o
           70            :for (feature . detect) :in '((:os-unix . os-unix-p) (:os-macosx . os-macosx-p)
           71                                          (:os-windows . os-windows-p)
           72                                          (:genera . os-genera-p) (:os-oldmac . os-oldmac-p)
           73                                          (:haiku . os-haiku-p)
           74                                          (:mezzano . os-mezzano-p))
           75            :when (and (or (not o) (eq feature :os-macosx)) (funcall detect))
           76            :do (setf o feature) (pushnew feature *features*)
           77            :else :do (setf *features* (remove feature *features*))
           78            :finally
           79            (return (or o (error "Congratulations for trying ASDF on an operating system~%~
           80 that is neither Unix, nor Windows, nor Genera, nor even old MacOS.~%Now you port it.")))))
           81 
           82   (defmacro os-cond (&rest clauses)
           83     #+abcl `(cond ,@clauses)
           84     #-abcl (loop* :for (test . body) :in clauses :when (eval test) :return `(progn ,@body)))
           85 
           86   (detect-os))
           87 
           88 ;;;; Environment variables: getting them, and parsing them.
           89 (with-upgradability ()
           90   (defun getenv (x)
           91     "Query the environment, as in C getenv.
           92 Beware: may return empty string if a variable is present but empty;
           93 use getenvp to return NIL in such a case."
           94     (declare (ignorable x))
           95     #+(or abcl clasp clisp ecl xcl) (ext:getenv x)
           96     #+allegro (sys:getenv x)
           97     #+clozure (ccl:getenv x)
           98     #+cmucl (unix:unix-getenv x)
           99     #+scl (cdr (assoc x ext:*environment-list* :test #'string=))
          100     #+cormanlisp
          101     (let* ((buffer (ct:malloc 1))
          102            (cname (ct:lisp-string-to-c-string x))
          103            (needed-size (win:getenvironmentvariable cname buffer 0))
          104            (buffer1 (ct:malloc (1+ needed-size))))
          105       (prog1 (if (zerop (win:getenvironmentvariable cname buffer1 needed-size))
          106                  nil
          107                  (ct:c-string-to-lisp-string buffer1))
          108         (ct:free buffer)
          109         (ct:free buffer1)))
          110     #+gcl (system:getenv x)
          111     #+(or genera mezzano) nil
          112     #+lispworks (lispworks:environment-variable x)
          113     #+mcl (ccl:with-cstrs ((name x))
          114             (let ((value (_getenv name)))
          115               (unless (ccl:%null-ptr-p value)
          116                 (ccl:%get-cstring value))))
          117     #+mkcl (#.(or (find-symbol* 'getenv :si nil) (find-symbol* 'getenv :mk-ext nil)) x)
          118     #+sbcl (sb-ext:posix-getenv x)
          119     #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mcl mezzano mkcl sbcl scl xcl)
          120     (not-implemented-error 'getenv))
          121 
          122   (defsetf getenv (x) (val)
          123     "Set an environment variable."
          124     (declare (ignorable x val))
          125     #+allegro `(setf (sys:getenv ,x) ,val)
          126     #+clasp `(ext:setenv ,x ,val)
          127     #+clisp `(system::setenv ,x ,val)
          128     #+clozure `(ccl:setenv ,x ,val)
          129     #+cmucl `(unix:unix-setenv ,x ,val 1)
          130     #+(or ecl clasp) `(ext:setenv ,x ,val)
          131     #+lispworks `(setf (lispworks:environment-variable ,x) ,val)
          132     #+mkcl `(mkcl:setenv ,x ,val)
          133     #+sbcl `(progn (require :sb-posix) (symbol-call :sb-posix :setenv ,x ,val 1))
          134     #-(or allegro clasp clisp clozure cmucl ecl lispworks mkcl sbcl)
          135     '(not-implemented-error '(setf getenv)))
          136 
          137   (defun getenvp (x)
          138     "Predicate that is true if the named variable is present in the libc environment,
          139 then returning the non-empty string value of the variable"
          140     (let ((g (getenv x))) (and (not (emptyp g)) g))))
          141 
          142 
          143 ;;;; implementation-identifier
          144 ;;
          145 ;; produce a string to identify current implementation.
          146 ;; Initially stolen from SLIME's SWANK, completely rewritten since.
          147 ;; We're back to runtime checking, for the sake of e.g. ABCL.
          148 
          149 (with-upgradability ()
          150   (defun first-feature (feature-sets)
          151     "A helper for various feature detection functions"
          152     (dolist (x feature-sets)
          153       (multiple-value-bind (short long feature-expr)
          154           (if (consp x)
          155               (values (first x) (second x) (cons :or (rest x)))
          156               (values x x x))
          157         (when (featurep feature-expr)
          158           (return (values short long))))))
          159 
          160   (defun implementation-type ()
          161     "The type of Lisp implementation used, as a short UIOP-standardized keyword"
          162     (first-feature
          163      '(:abcl (:acl :allegro) (:ccl :clozure) :clisp (:corman :cormanlisp)
          164        (:cmu :cmucl :cmu) :clasp :ecl :gcl
          165        (:lwpe :lispworks-personal-edition) (:lw :lispworks)
          166        :mcl :mezzano :mkcl :sbcl :scl (:smbx :symbolics) :xcl)))
          167 
          168   (defvar *implementation-type* (implementation-type)
          169     "The type of Lisp implementation used, as a short UIOP-standardized keyword")
          170 
          171   (defun operating-system ()
          172     "The operating system of the current host"
          173     (first-feature
          174      '(:cygwin
          175        (:win :windows :mswindows :win32 :mingw32) ;; try cygwin first!
          176        (:linux :linux :linux-target) ;; for GCL at least, must appear before :bsd
          177        (:macosx :macosx :darwin :darwin-target :apple) ; also before :bsd
          178        (:solaris :solaris :sunos)
          179        (:bsd :bsd :freebsd :netbsd :openbsd :dragonfly)
          180        :unix
          181        :genera
          182        :mezzano)))
          183 
          184   (defun architecture ()
          185     "The CPU architecture of the current host"
          186     (first-feature
          187      '((:x64 :x86-64 :x86_64 :x8664-target :amd64 (:and :word-size=64 :pc386))
          188        (:x86 :x86 :i386 :i486 :i586 :i686 :pentium3 :pentium4 :pc386 :iapx386 :x8632-target)
          189        (:ppc64 :ppc64 :ppc64-target) (:ppc32 :ppc32 :ppc32-target :ppc :powerpc)
          190        :hppa64 :hppa :sparc64 (:sparc32 :sparc32 :sparc)
          191        :mipsel :mipseb :mips :alpha (:arm :arm :arm-target) :imach
          192        ;; Java comes last: if someone uses C via CFFI or otherwise JNA or JNI,
          193        ;; we may have to segregate the code still by architecture.
          194        (:java :java :java-1.4 :java-1.5 :java-1.6 :java-1.7))))
          195 
          196   #+clozure
          197   (defun ccl-fasl-version ()
          198     ;; the fasl version is target-dependent from CCL 1.8 on.
          199     (or (let ((s 'ccl::target-fasl-version))
          200           (and (fboundp s) (funcall s)))
          201         (and (boundp 'ccl::fasl-version)
          202              (symbol-value 'ccl::fasl-version))
          203         (error "Can't determine fasl version.")))
          204 
          205   (defun lisp-version-string ()
          206     "return a string that identifies the current Lisp implementation version"
          207     (let ((s (lisp-implementation-version)))
          208       (car ; as opposed to OR, this idiom prevents some unreachable code warning
          209        (list
          210         #+allegro
          211         (format nil "~A~@[~A~]~@[~A~]~@[~A~]"
          212                 excl::*common-lisp-version-number*
          213                 ;; M means "modern", as opposed to ANSI-compatible mode (which I consider default)
          214                 (and (eq excl:*current-case-mode* :case-sensitive-lower) "M")
          215                 ;; Note if not using International ACL
          216                 ;; see http://www.franz.com/support/documentation/8.1/doc/operators/excl/ics-target-case.htm
          217                 (excl:ics-target-case (:-ics "8"))
          218                 (and (member :smp *features*) "S"))
          219         #+armedbear (format nil "~a-fasl~a" s system::*fasl-version*)
          220         #+clisp
          221         (subseq s 0 (position #\space s)) ; strip build information (date, etc.)
          222         #+clozure
          223         (format nil "~d.~d-f~d" ; shorten for windows
          224                 ccl::*openmcl-major-version*
          225                 ccl::*openmcl-minor-version*
          226                 (logand (ccl-fasl-version) #xFF))
          227         #+cmucl (substitute #\- #\/ s)
          228         #+scl (format nil "~A~A" s
          229                       ;; ANSI upper case vs lower case.
          230                       (ecase ext:*case-mode* (:upper "") (:lower "l")))
          231         #+ecl (format nil "~A~@[-~A~]" s
          232                       (let ((vcs-id (ext:lisp-implementation-vcs-id)))
          233                         (unless (equal vcs-id "UNKNOWN")
          234                           (subseq vcs-id 0 (min (length vcs-id) 8)))))
          235         #+gcl (subseq s (1+ (position #\space s)))
          236         #+genera
          237         (multiple-value-bind (major minor) (sct:get-system-version "System")
          238           (format nil "~D.~D" major minor))
          239         #+mcl (subseq s 8) ; strip the leading "Version "
          240         #+mezzano (format nil "~A-~D"
          241                           (subseq s 0 (position #\space s)) ; strip commit hash
          242                           sys.int::*llf-version*)
          243         ;; seems like there should be a shorter way to do this, like ACALL.
          244         #+mkcl (or
          245                 (let ((fname (find-symbol* '#:git-describe-this-mkcl :mkcl nil)))
          246                   (when (and fname (fboundp fname))
          247                     (funcall fname)))
          248                 s)
          249         s))))
          250 
          251   (defun implementation-identifier ()
          252     "Return a string that identifies the ABI of the current implementation,
          253 suitable for use as a directory name to segregate Lisp FASLs, C dynamic libraries, etc."
          254     (substitute-if
          255      #\_ #'(lambda (x) (find x " /:;&^\\|?<>(){}[]$#`'\""))
          256      (format nil "~(~a~@{~@[-~a~]~}~)"
          257              (or (implementation-type) (lisp-implementation-type))
          258              (lisp-version-string)
          259              (or (operating-system) (software-type))
          260              (or (architecture) (machine-type))))))
          261 
          262 
          263 ;;;; Other system information
          264 
          265 (with-upgradability ()
          266   (defun hostname ()
          267     "return the hostname of the current host"
          268     #+(or abcl clasp clozure cmucl ecl genera lispworks mcl mezzano mkcl sbcl scl xcl) (machine-instance)
          269     #+cormanlisp "localhost" ;; is there a better way? Does it matter?
          270     #+allegro (symbol-call :excl.osi :gethostname)
          271     #+clisp (first (split-string (machine-instance) :separator " "))
          272     #+gcl (system:gethostname)))
          273 
          274 
          275 ;;; Current directory
          276 (with-upgradability ()
          277 
          278   #+cmucl
          279   (defun parse-unix-namestring* (unix-namestring)
          280     "variant of LISP::PARSE-UNIX-NAMESTRING that returns a pathname object"
          281     (multiple-value-bind (host device directory name type version)
          282         (lisp::parse-unix-namestring unix-namestring 0 (length unix-namestring))
          283       (make-pathname :host (or host lisp::*unix-host*) :device device
          284                      :directory directory :name name :type type :version version)))
          285 
          286   (defun getcwd ()
          287     "Get the current working directory as per POSIX getcwd(3), as a pathname object"
          288     (or #+(or abcl genera mezzano xcl) (truename *default-pathname-defaults*) ;; d-p-d is canonical!
          289         #+allegro (excl::current-directory)
          290         #+clisp (ext:default-directory)
          291         #+clozure (ccl:current-directory)
          292         #+(or cmucl scl) (#+cmucl parse-unix-namestring* #+scl lisp::parse-unix-namestring
          293                         (strcat (nth-value 1 (unix:unix-current-directory)) "/"))
          294         #+cormanlisp (pathname (pl::get-current-directory)) ;; Q: what type does it return?
          295         #+(or clasp ecl) (ext:getcwd)
          296         #+gcl (let ((*default-pathname-defaults* #p"")) (truename #p""))
          297         #+lispworks (hcl:get-working-directory)
          298         #+mkcl (mk-ext:getcwd)
          299         #+sbcl (sb-ext:parse-native-namestring (sb-unix:posix-getcwd/))
          300         #+xcl (extensions:current-directory)
          301         (not-implemented-error 'getcwd)))
          302 
          303   (defun chdir (x)
          304     "Change current directory, as per POSIX chdir(2), to a given pathname object"
          305     (if-let (x (pathname x))
          306       #+(or abcl genera mezzano xcl) (setf *default-pathname-defaults* (truename x)) ;; d-p-d is canonical!
          307       #+allegro (excl:chdir x)
          308       #+clisp (ext:cd x)
          309       #+clozure (setf (ccl:current-directory) x)
          310       #+(or cmucl scl) (unix:unix-chdir (ext:unix-namestring x))
          311       #+cormanlisp (unless (zerop (win32::_chdir (namestring x)))
          312                      (error "Could not set current directory to ~A" x))
          313       #+ecl (ext:chdir x)
          314       #+clasp (ext:chdir x t)
          315       #+gcl (system:chdir x)
          316       #+lispworks (hcl:change-directory x)
          317       #+mkcl (mk-ext:chdir x)
          318       #+sbcl (progn (require :sb-posix) (symbol-call :sb-posix :chdir (sb-ext:native-namestring x)))
          319       #-(or abcl allegro clasp clisp clozure cmucl cormanlisp ecl gcl genera lispworks mkcl sbcl scl xcl)
          320       (not-implemented-error 'chdir))))
          321 
          322 
          323 ;;;; -----------------------------------------------------------------
          324 ;;;; Windows shortcut support.  Based on:
          325 ;;;;
          326 ;;;; Jesse Hager: The Windows Shortcut File Format.
          327 ;;;; http://www.wotsit.org/list.asp?fc=13
          328 
          329 #-(or clisp genera) ; CLISP doesn't need it, and READ-SEQUENCE annoys old Genera that doesn't need it
          330 (with-upgradability ()
          331   (defparameter *link-initial-dword* 76)
          332   (defparameter *link-guid* #(1 20 2 0 0 0 0 0 192 0 0 0 0 0 0 70))
          333 
          334   (defun read-null-terminated-string (s)
          335     "Read a null-terminated string from an octet stream S"
          336     ;; note: doesn't play well with UNICODE
          337     (with-output-to-string (out)
          338       (loop :for code = (read-byte s)
          339             :until (zerop code)
          340             :do (write-char (code-char code) out))))
          341 
          342   (defun read-little-endian (s &optional (bytes 4))
          343     "Read a number in little-endian format from an byte (octet) stream S,
          344 the number having BYTES octets (defaulting to 4)."
          345     (loop :for i :from 0 :below bytes
          346           :sum (ash (read-byte s) (* 8 i))))
          347 
          348   (defun parse-file-location-info (s)
          349     "helper to parse-windows-shortcut"
          350     (let ((start (file-position s))
          351           (total-length (read-little-endian s))
          352           (end-of-header (read-little-endian s))
          353           (fli-flags (read-little-endian s))
          354           (local-volume-offset (read-little-endian s))
          355           (local-offset (read-little-endian s))
          356           (network-volume-offset (read-little-endian s))
          357           (remaining-offset (read-little-endian s)))
          358       (declare (ignore total-length end-of-header local-volume-offset))
          359       (unless (zerop fli-flags)
          360         (cond
          361           ((logbitp 0 fli-flags)
          362            (file-position s (+ start local-offset)))
          363           ((logbitp 1 fli-flags)
          364            (file-position s (+ start
          365                                network-volume-offset
          366                                #x14))))
          367         (strcat (read-null-terminated-string s)
          368                 (progn
          369                   (file-position s (+ start remaining-offset))
          370                   (read-null-terminated-string s))))))
          371 
          372   (defun parse-windows-shortcut (pathname)
          373     "From a .lnk windows shortcut, extract the pathname linked to"
          374     ;; NB: doesn't do much checking & doesn't look like it will work well with UNICODE.
          375     (with-open-file (s pathname :element-type '(unsigned-byte 8))
          376       (handler-case
          377           (when (and (= (read-little-endian s) *link-initial-dword*)
          378                      (let ((header (make-array (length *link-guid*))))
          379                        (read-sequence header s)
          380                        (equalp header *link-guid*)))
          381             (let ((flags (read-little-endian s)))
          382               (file-position s 76)        ;skip rest of header
          383               (when (logbitp 0 flags)
          384                 ;; skip shell item id list
          385                 (let ((length (read-little-endian s 2)))
          386                   (file-position s (+ length (file-position s)))))
          387               (cond
          388                 ((logbitp 1 flags)
          389                  (parse-file-location-info s))
          390                 (t
          391                  (when (logbitp 2 flags)
          392                    ;; skip description string
          393                    (let ((length (read-little-endian s 2)))
          394                      (file-position s (+ length (file-position s)))))
          395                  (when (logbitp 3 flags)
          396                    ;; finally, our pathname
          397                    (let* ((length (read-little-endian s 2))
          398                           (buffer (make-array length)))
          399                      (read-sequence buffer s)
          400                      (map 'string #'code-char buffer)))))))
          401         (end-of-file (c)
          402           (declare (ignore c))
          403           nil)))))
          404 
          405