1 ;;;; pathname parsing for Unix filesystems
3 ;;;; This software is part of the SBCL system. See the README file for
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
12 (in-package "SB!IMPL")
14 ;;; Take a string and return a list of cons cells that mark the char
15 ;;; separated subseq. The first value is true if absolute directories
17 (defun split-at-slashes (namestr start end)
18 (declare (type simple-string namestr)
19 (type index start end))
20 (let ((absolute (and (/= start end)
21 (char= (schar namestr start) #\/))))
24 ;; Next, split the remainder into slash-separated chunks.
27 (let ((slash (position #\/ namestr :start start :end end)))
28 (pieces (cons start (or slash end)))
31 (setf start (1+ slash))))
32 (values absolute (pieces)))))
34 (defun parse-unix-namestring (namestring start end)
35 (declare (type simple-string namestring)
36 (type index start end))
37 (setf namestring (coerce namestring 'simple-string))
38 (multiple-value-bind (absolute pieces)
39 (split-at-slashes namestring start end)
40 (multiple-value-bind (name type version)
41 (let* ((tail (car (last pieces)))
42 (tail-start (car tail))
43 (tail-end (cdr tail)))
44 (unless (= tail-start tail-end)
45 (setf pieces (butlast pieces))
46 (extract-name-type-and-version namestring tail-start tail-end)))
49 (let ((position (position-if (lambda (char)
50 (or (char= char (code-char 0))
54 (error 'namestring-parse-error
55 :complaint "can't embed #\\Nul or #\\/ in Unix namestring"
56 :namestring namestring
60 ;; Deal with ~ and ~user
62 (destructuring-bind (start . end) (car pieces)
63 (when (and (not absolute)
65 (string= namestring "~"
69 (if (> end (1+ start))
70 (setf home (list :home (subseq namestring (1+ start) end)))
74 ;; Now we have everything we want. So return it.
75 (values nil ; no host for Unix namestrings
76 nil ; no device for Unix namestrings
78 (dolist (piece pieces)
79 (let ((piece-start (car piece))
80 (piece-end (cdr piece)))
81 (unless (= piece-start piece-end)
82 (cond ((string= namestring ".."
86 ((string= namestring "**"
89 (dirs :wild-inferiors))
91 (dirs (maybe-make-pattern namestring
96 (list* :absolute home (dirs))
97 (cons :absolute (dirs))))
99 (cons :relative (dirs)))
106 (defun parse-native-unix-namestring (namestring start end as-directory)
107 (declare (type simple-string namestring)
108 (type index start end))
109 (setf namestring (coerce namestring 'simple-string))
110 (multiple-value-bind (absolute ranges)
111 (split-at-slashes namestring start end)
112 (let* ((components (loop for ((start . end) . rest) on ranges
113 for piece = (subseq namestring start end)
114 collect (if (and (string= piece "..") rest)
117 (directory (if (and as-directory
118 (string/= "" (car (last components))))
120 (butlast components)))
123 (let* ((end (first (last components)))
124 (dot (position #\. end :from-end t)))
125 ;; FIXME: can we get this dot-interpretation knowledge
126 ;; from existing code? EXTRACT-NAME-TYPE-AND-VERSION
127 ;; does slightly more work than that.
132 (list (subseq end 0 dot) (subseq end (1+ dot))))
137 (cons (if absolute :absolute :relative) directory)
138 (first name-and-type)
139 (second name-and-type)
142 (/show0 "filesys.lisp 300")
144 (defun unparse-unix-host (pathname)
145 (declare (type pathname pathname)
147 ;; this host designator needs to be recognized as a physical host in
148 ;; PARSE-NAMESTRING. Until sbcl-0.7.3.x, we had "Unix" here, but
149 ;; that's a valid Logical Hostname, so that's a bad choice. -- CSR,
153 (defun unparse-unix-file (pathname)
154 (declare (type pathname pathname))
156 (let* ((name (%pathname-name pathname))
157 (type (%pathname-type pathname))
158 (type-supplied (not (or (null type) (eq type :unspecific)))))
159 ;; Note: by ANSI 19.3.1.1.5, we ignore the version slot when
160 ;; translating logical pathnames to a filesystem without
161 ;; versions (like Unix).
163 (when (and (null type)
166 (position #\. name :start 1))
167 (error "too many dots in the name: ~S" pathname))
168 (when (and (typep name 'string)
170 (error "name is of length 0: ~S" pathname))
171 (strings (unparse-physical-piece name)))
174 (error "cannot specify the type without a file: ~S" pathname))
175 (when (typep type 'simple-string)
176 (when (position #\. type)
177 (error "type component can't have a #\. inside: ~S" pathname)))
179 (strings (unparse-physical-piece type))))
180 (apply #'concatenate 'simple-string (strings))))
182 (/show0 "filesys.lisp 406")
184 (defun unparse-unix-namestring (pathname)
185 (declare (type pathname pathname))
186 (concatenate 'simple-string
187 (unparse-physical-directory pathname)
188 (unparse-unix-file pathname)))
190 (defun unparse-native-unix-namestring (pathname as-file)
191 (declare (type pathname pathname))
192 (let* ((directory (pathname-directory pathname))
193 (name (pathname-name pathname))
194 (name-present-p (typep name '(not (member nil :unspecific))))
195 (name-string (if name-present-p name ""))
196 (type (pathname-type pathname))
197 (type-present-p (typep type '(not (member nil :unspecific))))
198 (type-string (if type-present-p type "")))
202 (with-output-to-string (s)
204 (ecase (pop directory)
206 (let ((next (pop directory)))
207 (cond ((eq :home next)
208 (write-string (user-homedir-namestring) s))
209 ((and (consp next) (eq :home (car next)))
210 (let ((where (user-homedir-namestring (second next))))
212 (write-string where s)
213 (error "User homedir unknown for: ~S" (second next)))))
215 (push next directory)))
218 (loop for (piece . subdirs) on directory
221 (write-string ".." s))
223 (write-string piece s))
225 (error "ungood directory segment in NATIVE-NAMESTRING: ~S"
227 if (or subdirs (stringp name))
228 do (write-char #\/ s)
234 (unless (stringp name-string) ;some kind of wild field
235 (error "ungood name component in NATIVE-NAMESTRING: ~S" name))
236 (write-string name-string s)
238 (unless (stringp type-string) ;some kind of wild field
239 (error "ungood type component in NATIVE-NAMESTRING: ~S" type))
241 (write-string type-string s)))
242 (when type-present-p ; type without a name
244 "type component without a name component in NATIVE-NAMESTRING: ~S"
248 (defun unparse-unix-enough (pathname defaults)
249 (declare (type pathname pathname defaults))
251 (error "~S cannot be represented relative to ~S."
254 (let* ((pathname-directory (%pathname-directory pathname))
255 (defaults-directory (%pathname-directory defaults))
256 (prefix-len (length defaults-directory))
258 (cond ((null pathname-directory) '(:relative))
259 ((eq (car pathname-directory) :relative)
261 ((and (> prefix-len 0)
262 (>= (length pathname-directory) prefix-len)
263 (compare-component (subseq pathname-directory
266 ;; Pathname starts with a prefix of default. So
267 ;; just use a relative directory from then on out.
268 (cons :relative (nthcdr prefix-len pathname-directory)))
269 ((eq (car pathname-directory) :absolute)
270 ;; We are an absolute pathname, so we can just use it.
273 (bug "Bad fallthrough in ~S" 'unparse-unix-enough)))))
274 (strings (unparse-physical-directory-list result-directory)))
275 (let* ((pathname-type (%pathname-type pathname))
276 (type-needed (and pathname-type
277 (not (eq pathname-type :unspecific))))
278 (pathname-name (%pathname-name pathname))
279 (name-needed (or type-needed
281 (not (compare-component pathname-name
285 (unless pathname-name (lose))
286 (when (and (null pathname-type)
287 (typep pathname-name 'simple-string)
288 (position #\. pathname-name :start 1))
289 (error "too many dots in the name: ~S" pathname))
290 (strings (unparse-physical-piece pathname-name)))
292 (when (or (null pathname-type) (eq pathname-type :unspecific))
294 (when (typep pathname-type 'simple-string)
295 (when (position #\. pathname-type)
296 (error "type component can't have a #\. inside: ~S" pathname)))
298 (strings (unparse-physical-piece pathname-type))))
299 (apply #'concatenate 'simple-string (strings)))))
301 (defun simplify-unix-namestring (src)
302 (declare (type simple-string src))
303 (let* ((src-len (length src))
304 (dst (make-string src-len :element-type 'character))
308 (macrolet ((deposit (char)
310 (setf (schar dst dst-len) ,char)
312 (dotimes (src-index src-len)
313 (let ((char (schar src src-index)))
314 (cond ((char= char #\.)
321 ;; either ``/...' or ``...//...'
323 (setf last-slash dst-len)
326 ;; either ``./...'' or ``..././...''
331 ((and last-slash (not (zerop last-slash)))
332 ;; There is something before this ..
333 (let ((prev-prev-slash
334 (position #\/ dst :end last-slash :from-end t)))
335 (cond ((and (= (+ (or prev-prev-slash 0) 2)
337 (char= (schar dst (- last-slash 2)) #\.)
338 (char= (schar dst (1- last-slash)) #\.))
339 ;; The something before this .. is another ..
341 (setf last-slash dst-len))
343 ;; The something is some directory or other.
348 (setf last-slash prev-prev-slash)))))
350 ;; There is nothing before this .., so we need to keep it
351 (setf last-slash dst-len)
354 ;; something other than a dot between slashes
355 (setf last-slash dst-len)
360 (setf (schar dst dst-len) char)
362 (when (and last-slash (not (zerop last-slash)))
365 ;; We've got ``foobar/.''
368 ;; We've got ``foobar/..''
369 (unless (and (>= last-slash 2)
370 (char= (schar dst (1- last-slash)) #\.)
371 (char= (schar dst (- last-slash 2)) #\.)
373 (char= (schar dst (- last-slash 3)) #\/)))
374 (let ((prev-prev-slash
375 (position #\/ dst :end last-slash :from-end t)))
377 (setf dst-len (1+ prev-prev-slash))
378 (return-from simplify-unix-namestring
379 (coerce "./" 'simple-string))))))))
380 (cond ((zerop dst-len)
385 (subseq dst 0 dst-len)))))