(declare (type simple-base-string namestr)
(type index start end))
(let* ((last-dot (position #\. namestr :start (1+ start) :end end
- :from-end t))
- (second-to-last-dot (and last-dot
- (position #\. namestr :start (1+ start)
- :end last-dot :from-end t)))
- (version :newest))
- ;; If there is a second-to-last dot, check to see whether there is
- ;; a valid version after the last dot.
- (when second-to-last-dot
- (cond ((and (= (+ last-dot 2) end)
- (char= (schar namestr (1+ last-dot)) #\*))
- (setf version :wild))
- ((and (< (1+ last-dot) end)
- (do ((index (1+ last-dot) (1+ index)))
- ((= index end) t)
- (unless (char<= #\0 (schar namestr index) #\9)
- (return nil))))
- (setf version
- (parse-integer namestr :start (1+ last-dot) :end end)))
- (t
- (setf second-to-last-dot nil))))
- (cond (second-to-last-dot
- (values (maybe-make-pattern namestr start second-to-last-dot)
- (maybe-make-pattern namestr
- (1+ second-to-last-dot)
- last-dot)
- version))
- (last-dot
- (values (maybe-make-pattern namestr start last-dot)
- (maybe-make-pattern namestr (1+ last-dot) end)
- version))
- (t
- (values (maybe-make-pattern namestr start end)
- nil
- version)))))
+ :from-end t)))
+ (cond
+ (last-dot
+ (values (maybe-make-pattern namestr start last-dot)
+ (maybe-make-pattern namestr (1+ last-dot) end)
+ :newest))
+ (t
+ (values (maybe-make-pattern namestr start end)
+ nil
+ :newest)))))
(/show0 "filesys.lisp 200")
(values absolute (pieces)))))
(defun parse-unix-namestring (namestr start end)
- (declare (type simple-base-string namestr)
+ (declare (type simple-string namestr)
(type index start end))
+ (setf namestr (coerce namestr 'simple-base-string))
(multiple-value-bind (absolute pieces) (split-at-slashes namestr start end)
(multiple-value-bind (name type version)
(let* ((tail (car (last pieces)))
(t
(error "invalid pattern piece: ~S" piece))))))
(apply #'concatenate
- 'simple-string
+ 'simple-base-string
(strings))))))
(defun unparse-unix-directory-list (directory)
(pieces "/"))
(t
(error "invalid directory component: ~S" dir)))))
- (apply #'concatenate 'simple-string (pieces))))
+ (apply #'concatenate 'simple-base-string (pieces))))
(defun unparse-unix-directory (pathname)
(declare (type pathname pathname))
;; translating logical pathnames to a filesystem without
;; versions (like Unix).
(when name
+ (when (and (null type)
+ (typep name 'string)
+ (> (length name) 0)
+ (position #\. name :start 1))
+ (error "too many dots in the name: ~S" pathname))
+ (when (and (typep name 'string)
+ (string= name ""))
+ (error "name is of length 0: ~S" pathname))
(strings (unparse-unix-piece name)))
(when type-supplied
(unless name
(error "cannot specify the type without a file: ~S" pathname))
+ (when (typep type 'simple-string)
+ (when (position #\. type)
+ (error "type component can't have a #\. inside: ~S" pathname)))
(strings ".")
(strings (unparse-unix-piece type))))
- (apply #'concatenate 'simple-string (strings))))
+ (apply #'concatenate 'simple-base-string (strings))))
(/show0 "filesys.lisp 406")
(defun unparse-unix-namestring (pathname)
(declare (type pathname pathname))
- (concatenate 'simple-string
+ (concatenate 'simple-base-string
(unparse-unix-directory pathname)
(unparse-unix-file pathname)))
(defaults-directory (%pathname-directory defaults))
(prefix-len (length defaults-directory))
(result-directory
- (cond ((and (> prefix-len 1)
+ (cond ((null pathname-directory) '(:relative))
+ ((eq (car pathname-directory) :relative)
+ pathname-directory)
+ ((and (> prefix-len 1)
(>= (length pathname-directory) prefix-len)
(compare-component (subseq pathname-directory
0 prefix-len)
;; We are an absolute pathname, so we can just use it.
pathname-directory)
(t
- ;; We are a relative directory. So we lose.
- (lose)))))
+ (bug "Bad fallthrough in ~S" 'unparse-unix-enough)))))
(strings (unparse-unix-directory-list result-directory)))
- (let* ((pathname-version (%pathname-version pathname))
- (version-needed (and pathname-version
- (not (eq pathname-version :newest))))
- (pathname-type (%pathname-type pathname))
- (type-needed (or version-needed
- (and pathname-type
- (not (eq pathname-type :unspecific)))))
+ (let* ((pathname-type (%pathname-type pathname))
+ (type-needed (and pathname-type
+ (not (eq pathname-type :unspecific))))
(pathname-name (%pathname-name pathname))
(name-needed (or type-needed
(and pathname-name
defaults)))))))
(when name-needed
(unless pathname-name (lose))
+ (when (and (null pathname-type)
+ (position #\. pathname-name :start 1))
+ (error "too many dots in the name: ~S" pathname))
(strings (unparse-unix-piece pathname-name)))
(when type-needed
(when (or (null pathname-type) (eq pathname-type :unspecific))
(lose))
+ (when (typep pathname-type 'simple-base-string)
+ (when (position #\. pathname-type)
+ (error "type component can't have a #\. inside: ~S" pathname)))
(strings ".")
- (strings (unparse-unix-piece pathname-type)))
- (when version-needed
- (typecase pathname-version
- ((member :wild)
- (strings ".*"))
- (integer
- (strings (format nil ".~D" pathname-version)))
- (t
- (lose)))))
+ (strings (unparse-unix-piece pathname-type))))
(apply #'concatenate 'simple-string (strings)))))
\f
;;;; wildcard matching stuff
verify-existence follow-links
nodes function))))
((member :wild-inferiors)
+ ;; now with extra error case handling from CLHS
+ ;; 19.2.2.4.3 -- CSR, 2004-01-24
+ (when (member (cadr tail) '(:up :back))
+ (error 'simple-file-error
+ :pathname pathname
+ :format-control "~@<invalid use of ~S after :WILD-INFERIORS~@:>."
+ :format-arguments (list (cadr tail))))
(%enumerate-directories head (rest tail) pathname
verify-existence follow-links
nodes function)
verify-existence follow-links
nodes function))))))))
((member :up)
- (with-directory-node-removed (head)
+ (when (string= head "/")
+ (error 'simple-file-error
+ :pathname pathname
+ :format-control "~@<invalid use of :UP after :ABSOLUTE.~@:>"))
+ (with-directory-node-removed (head)
(let ((head (concatenate 'base-string head "..")))
(with-directory-node-noted (head)
(%enumerate-directories (concatenate 'base-string head "/")
(rest tail) pathname
verify-existence follow-links
- nodes function)))))))
+ nodes function)))))
+ ((member :back)
+ ;; :WILD-INFERIORS is handled above, so the only case here
+ ;; should be (:ABSOLUTE :BACK)
+ (aver (string= head "/"))
+ (error 'simple-file-error
+ :pathname pathname
+ :format-control "~@<invalid use of :BACK after :ABSOLUTE.~@:>"))))
(%enumerate-files head pathname verify-existence function))))
;;; Call FUNCTION on files.
(/noshow0 "computed NAME, TYPE, and VERSION")
(cond ((member name '(nil :unspecific))
(/noshow0 "UNSPECIFIC, more or less")
- (when (or (not verify-existence)
- (sb!unix:unix-file-kind directory))
- (funcall function directory)))
+ (let ((directory (coerce directory 'base-string)))
+ (when (or (not verify-existence)
+ (sb!unix:unix-file-kind directory))
+ (funcall function directory))))
((or (pattern-p name)
(pattern-p type)
(eq name :wild)
;;; Convert PATHNAME into a string that can be used with UNIX system
;;; calls, or return NIL if no match is found. Wild-cards are expanded.
-;;; FIXME this should signal file-error if the pathname is wild, whether
-;;; or not it turns out to have only one match. Fix post 0.7.2
(defun unix-namestring (pathname-spec &optional (for-input t))
(let* ((namestring (physicalize-pathname (merge-pathnames pathname-spec)))
(matches nil)) ; an accumulator for actual matches
(/show0 "filesys.lisp 800")
+;;; NOTE: There is a fair amount of hair below that is probably not
+;;; strictly necessary.
+;;;
+;;; The issue is the following: what does (DIRECTORY "SYS:*;") mean?
+;;; Until 2004-01, SBCL's behaviour was unquestionably wrong, as it
+;;; did not translate the logical pathname at all, but instead treated
+;;; it as a physical one. Other Lisps seem to to treat this call as
+;;; equivalent to (DIRECTORY (TRANSLATE-LOGICAL-PATHNAME "SYS:*;")),
+;;; which is fine as far as it goes, but not very interesting, and
+;;; arguably counterintuitive. (PATHNAME-MATCH-P "SYS:SRC;" "SYS:*;")
+;;; is true, so why should "SYS:SRC;" not show up in the call to
+;;; DIRECTORY? (assuming the physical pathname corresponding to it
+;;; exists, of course).
+;;;
+;;; So, the interpretation that I am pushing is for all pathnames
+;;; matching the input pathname to be queried. This means that we
+;;; need to compute the intersection of the input pathname and the
+;;; logical host FROM translations, and then translate the resulting
+;;; pathname using the host to the TO translation; this treatment is
+;;; recursively invoked until we get a physical pathname, whereupon
+;;; our physical DIRECTORY implementation takes over.
+
+;;; FIXME: this is an incomplete implementation. It only works when
+;;; both are logical pathnames (which is OK, because that's the only
+;;; case when we call it), but there are other pitfalls as well: see
+;;; the DIRECTORY-HELPER below for some, but others include a lack of
+;;; pattern handling.
+(defun pathname-intersections (one two)
+ (aver (logical-pathname-p one))
+ (aver (logical-pathname-p two))
+ (labels
+ ((intersect-version (one two)
+ (aver (typep one '(or null (member :newest :wild :unspecific)
+ integer)))
+ (aver (typep two '(or null (member :newest :wild :unspecific)
+ integer)))
+ (cond
+ ((eq one :wild) two)
+ ((eq two :wild) one)
+ ((or (null one) (eq one :unspecific)) two)
+ ((or (null two) (eq two :unspecific)) one)
+ ((eql one two) one)
+ (t nil)))
+ (intersect-name/type (one two)
+ (aver (typep one '(or null (member :wild :unspecific) string)))
+ (aver (typep two '(or null (member :wild :unspecific) string)))
+ (cond
+ ((eq one :wild) two)
+ ((eq two :wild) one)
+ ((or (null one) (eq one :unspecific)) two)
+ ((or (null two) (eq two :unspecific)) one)
+ ((string= one two) one)
+ (t nil)))
+ (intersect-directory (one two)
+ (aver (typep one '(or null (member :wild :unspecific) list)))
+ (aver (typep two '(or null (member :wild :unspecific) list)))
+ (cond
+ ((eq one :wild) two)
+ ((eq two :wild) one)
+ ((or (null one) (eq one :unspecific)) two)
+ ((or (null two) (eq two :unspecific)) one)
+ (t (aver (eq (car one) (car two)))
+ (mapcar
+ (lambda (x) (cons (car one) x))
+ (intersect-directory-helper (cdr one) (cdr two)))))))
+ (let ((version (intersect-version
+ (pathname-version one) (pathname-version two)))
+ (name (intersect-name/type
+ (pathname-name one) (pathname-name two)))
+ (type (intersect-name/type
+ (pathname-type one) (pathname-type two)))
+ (host (pathname-host one)))
+ (mapcar (lambda (d)
+ (make-pathname :host host :name name :type type
+ :version version :directory d))
+ (intersect-directory
+ (pathname-directory one) (pathname-directory two))))))
+
+;;; FIXME: written as its own function because I (CSR) don't
+;;; understand it, so helping both debuggability and modularity. In
+;;; case anyone is motivated to rewrite it, it returns a list of
+;;; sublists representing the intersection of the two input directory
+;;; paths (excluding the initial :ABSOLUTE or :RELATIVE).
+;;;
+;;; FIXME: Does not work with :UP or :BACK
+;;; FIXME: Does not work with patterns
+;;;
+;;; FIXME: PFD suggests replacing this implementation with a DFA
+;;; conversion of a NDFA. Find out (a) what this means and (b) if it
+;;; turns out to be worth it.
+(defun intersect-directory-helper (one two)
+ (flet ((simple-intersection (cone ctwo)
+ (cond
+ ((eq cone :wild) ctwo)
+ ((eq ctwo :wild) cone)
+ (t (aver (typep cone 'string))
+ (aver (typep ctwo 'string))
+ (if (string= cone ctwo) cone nil)))))
+ (macrolet
+ ((loop-possible-wild-inferiors-matches
+ (lower-bound bounding-sequence order)
+ (let ((index (gensym)) (g2 (gensym)) (g3 (gensym)) (l (gensym)))
+ `(let ((,l (length ,bounding-sequence)))
+ (loop for ,index from ,lower-bound to ,l
+ append (mapcar (lambda (,g2)
+ (append
+ (butlast ,bounding-sequence (- ,l ,index))
+ ,g2))
+ (mapcar
+ (lambda (,g3)
+ (append
+ (if (eq (car (nthcdr ,index ,bounding-sequence))
+ :wild-inferiors)
+ '(:wild-inferiors)
+ nil) ,g3))
+ (intersect-directory-helper
+ ,@(if order
+ `((nthcdr ,index one) (cdr two))
+ `((cdr one) (nthcdr ,index two)))))))))))
+ (cond
+ ((and (eq (car one) :wild-inferiors)
+ (eq (car two) :wild-inferiors))
+ (delete-duplicates
+ (append (mapcar (lambda (x) (cons :wild-inferiors x))
+ (intersect-directory-helper (cdr one) (cdr two)))
+ (loop-possible-wild-inferiors-matches 2 one t)
+ (loop-possible-wild-inferiors-matches 2 two nil))
+ :test 'equal))
+ ((eq (car one) :wild-inferiors)
+ (delete-duplicates (loop-possible-wild-inferiors-matches 0 two nil)
+ :test 'equal))
+ ((eq (car two) :wild-inferiors)
+ (delete-duplicates (loop-possible-wild-inferiors-matches 0 one t)
+ :test 'equal))
+ ((and (null one) (null two)) (list nil))
+ ((null one) nil)
+ ((null two) nil)
+ (t (and (simple-intersection (car one) (car two))
+ (mapcar (lambda (x) (cons (simple-intersection
+ (car one) (car two)) x))
+ (intersect-directory-helper (cdr one) (cdr two)))))))))
+
(defun directory (pathname &key)
#!+sb-doc
"Return a list of PATHNAMEs, each the TRUENAME of a file that matched the
;; (which can arise when e.g. multiple symlinks map to the
;; same truename).
(truenames (make-hash-table :test #'equal))
+ ;; FIXME: Possibly this MERGE-PATHNAMES call should only
+ ;; happen once we get a physical pathname.
(merged-pathname (merge-pathnames pathname)))
- (!enumerate-matches (match merged-pathname)
- (let* ((*ignore-wildcards* t)
- ;; FIXME: Why not TRUENAME? As reported by Milan Zamazal
- ;; sbcl-devel 2003-10-05, using TRUENAME causes a race
- ;; condition whereby removal of a file during the
- ;; directory operation causes an error. It's not clear
- ;; what the right thing to do is, though. -- CSR,
- ;; 2003-10-13
- (truename (probe-file match)))
- (when truename
- (setf (gethash (namestring truename) truenames)
- truename))))
+ (labels ((do-physical-directory (pathname)
+ (aver (not (logical-pathname-p pathname)))
+ (!enumerate-matches (match pathname)
+ (let* ((*ignore-wildcards* t)
+ ;; FIXME: Why not TRUENAME? As reported by
+ ;; Milan Zamazal sbcl-devel 2003-10-05, using
+ ;; TRUENAME causes a race condition whereby
+ ;; removal of a file during the directory
+ ;; operation causes an error. It's not clear
+ ;; what the right thing to do is, though. --
+ ;; CSR, 2003-10-13
+ (truename (probe-file match)))
+ (when truename
+ (setf (gethash (namestring truename) truenames)
+ truename)))))
+ (do-directory (pathname)
+ (if (logical-pathname-p pathname)
+ (let ((host (intern-logical-host (pathname-host pathname))))
+ (dolist (x (logical-host-canon-transls host))
+ (destructuring-bind (from to) x
+ (let ((intersections
+ (pathname-intersections pathname from)))
+ (dolist (p intersections)
+ (do-directory (translate-pathname p from to)))))))
+ (do-physical-directory pathname))))
+ (do-directory merged-pathname))
(mapcar #'cdr
;; Sorting isn't required by the ANSI spec, but sorting
;; into some canonical order seems good just on the
:device (pathname-device pathname)
:directory (subseq dir 0 i))))
(unless (probe-file newpath)
- (let ((namestring (namestring newpath)))
+ (let ((namestring (coerce (namestring newpath) 'base-string)))
(when verbose
(format *standard-output*
"~&creating directory: ~A~%"