1 ;;;; pathname parsing for Win32 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 (defstruct (win32-host
16 (parse #'parse-win32-namestring
)
17 (parse-native #'parse-native-win32-namestring
)
18 (unparse #'unparse-win32-namestring
)
19 (unparse-native #'unparse-native-win32-namestring
)
20 (unparse-host #'unparse-win32-host
)
21 (unparse-directory #'unparse-win32-directory
)
22 (unparse-file #'unparse-win32-file
)
23 (unparse-enough #'unparse-win32-enough
)
24 (unparse-directory-separator "\\")
25 (simplify-namestring #'simplify-win32-namestring
)
26 (customary-case :lower
))))
28 (setq *physical-host
* (make-win32-host))
31 (define-symbol-macro +long-file-name-prefix
+ (quote "\\\\?\\"))
32 (define-symbol-macro +unc-file-name-prefix
+ (quote "\\\\?\\UNC"))
34 (defun extract-device (namestr start end
)
35 (declare (type simple-string namestr
)
36 (type index start end
))
37 (if (>= end
(+ start
2))
38 (let ((c0 (char namestr start
))
39 (c1 (char namestr
(1+ start
))))
40 (cond ((and (eql c1
#\
:) (alpha-char-p c0
))
41 ;; "X:" style, saved as X
42 (values (string (char namestr start
)) (+ start
2)))
43 ((and (member c0
'(#\
/ #\\)) (eql c0 c1
) (>= end
(+ start
3)))
44 ;; "//UNC" style, saved as :UNC device, with host and share
45 ;; becoming directory components.
46 (values :unc
(+ start
1)))
51 (defun split-at-slashes-and-backslashes (namestr start end
)
52 (declare (type simple-string namestr
)
53 (type index start end
))
54 ;; FIXME: There is a fundamental brokenness in using the same
55 ;; character as escape character and directory separator in
56 ;; non-native pathnames. (PATHNAME-DIRECTORY #P"\\*/") should
57 ;; probably be (:RELATIVE "*") everywhere, but on Windows it's
58 ;; (:ABSOLUTE :WILD)! See lp#673625.
59 (let ((absolute (and (/= start end
)
60 (or (char= (schar namestr start
) #\
/)
61 (char= (schar namestr start
) #\\)))))
64 ;; Next, split the remainder into slash-separated chunks.
67 (let ((slash (position-if (lambda (c)
70 namestr
:start start
:end end
)))
71 (pieces (cons start
(or slash end
)))
74 (setf start
(1+ slash
))))
75 (values absolute
(pieces)))))
77 (defun parse-win32-namestring (namestring start end
)
78 (declare (type simple-string namestring
)
79 (type index start end
))
80 (setf namestring
(coerce namestring
'simple-string
))
81 (multiple-value-bind (device new-start
)
82 (extract-device namestring start end
)
83 (multiple-value-bind (absolute pieces
)
84 (split-at-slashes-and-backslashes namestring new-start end
)
85 (multiple-value-bind (name type version
)
86 (let* ((tail (car (last pieces
)))
87 (tail-start (car tail
))
88 (tail-end (cdr tail
)))
89 (unless (= tail-start tail-end
)
90 (setf pieces
(butlast pieces
))
91 (extract-name-type-and-version namestring tail-start tail-end
#\^
)))
94 (let ((position (position-if (lambda (char)
95 (or (char= char
(code-char 0))
99 (error 'namestring-parse-error
100 :complaint
"can't embed #\\Nul or #\\/ in Windows namestring"
101 :namestring namestring
105 ;; Deal with ~ and ~user.
107 (destructuring-bind (start . end
) (car pieces
)
108 (when (and (not absolute
)
109 (not (eql start end
))
110 (string= namestring
"~"
114 (if (> end
(1+ start
))
115 (setf home
(list :home
(subseq namestring
(1+ start
) end
)))
119 ;; Now we have everything we want. So return it.
120 (values nil
; no host for Win32 namestrings
123 (dolist (piece pieces
)
124 (let ((piece-start (car piece
))
125 (piece-end (cdr piece
)))
126 (unless (= piece-start piece-end
)
127 (cond ((string= namestring
".."
131 ((string= namestring
"**"
134 (dirs :wild-inferiors
))
136 (dirs (maybe-make-pattern namestring
142 (list* :absolute home
(dirs))
143 (cons :absolute
(dirs))))
145 (cons :relative
(dirs)))
152 (defun parse-native-win32-namestring (namestring start end as-directory
)
153 (declare (type simple-string namestring
)
154 (type index start end
))
155 (setf namestring
(coerce namestring
'simple-string
))
156 (multiple-value-bind (device new-start
)
157 (cond ((= (length +unc-file-name-prefix
+)
158 (mismatch +unc-file-name-prefix
+ namestring
160 (values :unc
(+ start
(length +unc-file-name-prefix
+))))
161 ((= (length +long-file-name-prefix
+)
162 (mismatch +long-file-name-prefix
+ namestring
164 (extract-device namestring
165 (+ start
(length +long-file-name-prefix
+))
167 (t (extract-device namestring start end
)))
168 (multiple-value-bind (absolute ranges
)
169 (split-at-slashes-and-backslashes namestring new-start end
)
170 (let* ((components (loop for
((start . end
) . rest
) on ranges
171 for piece
= (subseq namestring start end
)
172 collect
(if (and (string= piece
"..") rest
)
175 (directory (if (and as-directory
176 (string/= "" (car (last components
))))
178 (butlast components
)))
181 (let* ((end (first (last components
)))
182 (dot (position #\. end
:from-end t
)))
183 ;; FIXME: can we get this dot-interpretation knowledge
184 ;; from existing code? EXTRACT-NAME-TYPE-AND-VERSION
185 ;; does slightly more work than that.
190 (list (subseq end
0 dot
) (subseq end
(1+ dot
))))
196 (cons :absolute directory
))
198 (cons :relative directory
))
201 (first name-and-type
)
202 (second name-and-type
)
205 (defun unparse-win32-host (pathname)
206 (declare (type pathname pathname
)
208 ;; FIXME: same as UNPARSE-UNIX-HOST. That's probably not good.
211 (defun unparse-win32-device (pathname &optional native
)
212 (declare (type pathname pathname
))
213 (let ((device (pathname-device pathname
))
214 (directory (pathname-directory pathname
)))
215 (cond ((not (pathname-component-present-p device
))
218 (if native
"\\" "/"))
219 ((and (= 1 (length device
)) (alpha-char-p (char device
0)))
220 (concatenate 'simple-string device
":"))
221 ((and (consp directory
) (eq :relative
(car directory
)))
222 (no-native-namestring-error
224 "there is no printed representation for a relative UNC pathname"))
227 (concatenate 'simple-string
"\\\\" device
)
228 (concatenate 'simple-string
"//" device
))))))
230 (defun unparse-win32-directory (pathname)
231 (unparse-physical-directory pathname
#\^
))
233 (defun unparse-win32-file (pathname)
234 (declare (type pathname pathname
))
235 (unparse-physical-file pathname
#\^
))
237 (defun unparse-win32-namestring (pathname)
238 (declare (type pathname pathname
))
239 (concatenate 'simple-string
240 (unparse-win32-device pathname
)
241 (unparse-physical-directory pathname
#\^
)
242 (unparse-physical-file pathname
#\^
)))
244 (defun unparse-native-win32-namestring (pathname as-file
)
245 (declare (type pathname pathname
))
246 (let* ((device (pathname-device pathname
))
247 (devicep (not (member device
'(:unc nil
))))
248 (directory (pathname-directory pathname
))
249 (absolutep (and device
(eql :absolute
(car directory
))))
250 (seperator-after-directory-p
251 (or (pathname-component-present-p (pathname-name pathname
))
253 (when (and absolutep
(member :up directory
))
254 ;; employ merge-pathnames to parse :BACKs into which we turn :UPs
258 (make-pathname :defaults pathname
:directory
'(:relative
))
259 (make-pathname :defaults pathname
260 :directory
(substitute :back
:up directory
))))))
262 (%with-output-to-string
(s)
264 (write-string (case device
265 (:unc
+unc-file-name-prefix
+)
266 (otherwise +long-file-name-prefix
+))
268 (when (or (not absolutep
) devicep
)
269 (write-string (unparse-win32-device pathname t
) s
))
271 (ecase (pop directory
)
273 (let ((next (pop directory
)))
275 ((typep next
'(or (eql :home
) (cons (eql :home
))))
276 (let* ((username (when (consp next
) (second next
)))
279 (parse-native-namestring
280 (user-homedir-namestring username
))
281 (user-homedir-pathname))
283 (no-native-namestring-error
285 "user homedir not known~@[ for ~S~]: ~A"
286 username condition
)))))
287 (when (and (or absolutep devicep
)
288 (not (string-equal device
(pathname-device home
))))
289 (no-native-namestring-error
291 "Device in homedir ~S conflicts which device ~S"
293 (write-string (native-namestring home
) s
)))
294 ;; namestring of user-homedir-pathname already has
298 (push next directory
))
300 (write-char #\\ s
)))))
302 (loop for
(piece . subdirs
) on directory
304 ((member :up
:back
) (write-string ".." s
))
305 (string (write-string piece s
))
306 (t (no-native-namestring-error pathname
307 "Bad directory segment in NATIVE-NAMESTRING: ~S."
309 when
(or subdirs seperator-after-directory-p
)
310 do
(write-char #\\ s
))
311 (write-string (unparse-native-physical-file pathname
) s
)
313 (let ((string (get-output-stream-string s
)))
314 (return-from unparse-native-win32-namestring
315 (cond ((< (- 260 12) (length string
))
316 ;; KLUDGE: account for additional length of 8.3 name to make
317 ;; directories always accessible
318 (coerce string
'simple-string
))
321 (subseq string
(1- (length +unc-file-name-prefix
+)))
323 (t (subseq string
(length +long-file-name-prefix
+))))))))
326 (defun unparse-win32-enough (pathname defaults
)
327 (unparse-physical-enough pathname defaults
#\^
))
329 ;; FIXME: This has been converted rather blindly from the Unix
330 ;; version, with no reference to any Windows docs what so ever.
331 (defun simplify-win32-namestring (src)
332 (declare (type simple-string src
))
333 (let* ((src-len (length src
))
334 (dst (make-string src-len
:element-type
'character
))
338 (flet ((deposit (char)
339 (setf (schar dst dst-len
) char
)
343 (dotimes (src-index src-len
)
344 (let ((char (schar src src-index
)))
345 (cond ((char= char
#\.
)
352 ;; either ``/...' or ``...//...'
354 (setf last-slash dst-len
)
357 ;; either ``./...'' or ``..././...''
362 ((and last-slash
(not (zerop last-slash
)))
363 ;; There is something before this ..
364 (let ((prev-prev-slash
365 (position-if #'slashp dst
:end last-slash
:from-end t
)))
366 (cond ((and (= (+ (or prev-prev-slash
0) 2)
368 (char= (schar dst
(- last-slash
2)) #\.
)
369 (char= (schar dst
(1- last-slash
)) #\.
))
370 ;; The something before this .. is another ..
372 (setf last-slash dst-len
))
374 ;; The something is some directory or other.
379 (setf last-slash prev-prev-slash
)))))
381 ;; There is nothing before this .., so we need to keep it
382 (setf last-slash dst-len
)
385 ;; something other than a dot between slashes
386 (setf last-slash dst-len
)
391 (setf (schar dst dst-len
) char
)
394 (when (and last-slash
(not (zerop last-slash
)))
397 ;; We've got ``foobar/.''
400 ;; We've got ``foobar/..''
401 (unless (and (>= last-slash
2)
402 (char= (schar dst
(1- last-slash
)) #\.
)
403 (char= (schar dst
(- last-slash
2)) #\.
)
405 (slashp (schar dst
(- last-slash
3)))))
406 (let ((prev-prev-slash
407 (position-if #'slashp dst
:end last-slash
:from-end t
)))
409 (setf dst-len
(1+ prev-prev-slash
))
410 (return-from simplify-win32-namestring
411 (coerce ".\\" 'simple-string
)))))))))
412 (cond ((zerop dst-len
)
417 (subseq dst
0 dst-len
)))))