/[cmucl]/src/code/filesys.lisp
ViewVC logotype

Contents of /src/code/filesys.lisp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.43.2.4 - (hide annotations)
Wed Aug 23 15:53:13 2000 UTC (13 years, 7 months ago) by dtc
Branch: RELENG_18
Changes since 1.43.2.3: +92 -40 lines
o Rework %enumerate-directories to handle :wild and :wild-inferiors
  within the pathname; noted by Christophe Rhodes. Further maintain a
  list of the directory inode and device numbers which are used to avoid
  paths loops during :wild-inferiors searches.

o Enhance %enumerate-directories to accept a new follow-links argument to
  allow the following of symbolic links to be disabled. Similarly add
  follow-links keyword argument to interface macro enumerate-matches.
1 ram 1.1 ;;; -*- Log: code.log; Package: Lisp -*-
2     ;;; **********************************************************************
3 ram 1.8 ;;; This code was written as part of the CMU Common Lisp project at
4     ;;; Carnegie Mellon University, and has been placed in the public domain.
5     ;;; If you want to use this code or any part of CMU Common Lisp, please contact
6     ;;; Scott Fahlman or slisp-group@cs.cmu.edu.
7     ;;;
8     (ext:file-comment
9 dtc 1.43.2.4 "$Header: /tiger/var/lib/cvsroots/cmucl/src/code/filesys.lisp,v 1.43.2.4 2000/08/23 15:53:13 dtc Exp $")
10 ram 1.8 ;;;
11 ram 1.1 ;;; **********************************************************************
12     ;;;
13 wlott 1.16 ;;; File system interface functions. This file is pretty UNIX specific.
14 ram 1.1 ;;;
15 wlott 1.16 ;;; Written by William Lott
16 ram 1.1 ;;;
17     ;;; **********************************************************************
18 ram 1.2
19     (in-package "LISP")
20    
21 wlott 1.16 (export '(truename probe-file user-homedir-pathname directory
22     rename-file delete-file file-write-date file-author))
23 ram 1.1
24     (use-package "EXTENSIONS")
25    
26     (in-package "EXTENSIONS")
27     (export '(print-directory complete-file ambiguous-files default-directory
28 wlott 1.5 file-writable unix-namestring))
29 ram 1.2 (in-package "LISP")
30 ram 1.1
31 wlott 1.5
32 wlott 1.16 ;;;; Unix pathname host support.
33 ram 1.1
34 wlott 1.16 ;;; Unix namestrings have the following format:
35     ;;;
36     ;;; namestring := [ directory ] [ file [ type [ version ]]]
37     ;;; directory := [ "/" | search-list ] { file "/" }*
38     ;;; search-list := [^:/]*:
39     ;;; file := [^/]*
40     ;;; type := "." [^/.]*
41     ;;; version := "." ([0-9]+ | "*")
42     ;;;
43     ;;; Note: this grammer is ambiguous. The string foo.bar.5 can be parsed
44     ;;; as either just the file specified or as specifying the file, type, and
45     ;;; version. Therefore, we use the following rules when confronted with
46     ;;; an ambiguous file.type.version string:
47     ;;;
48     ;;; - If the first character is a dot, it's part of the file. It is not
49     ;;; considered a dot in the following rules.
50     ;;;
51     ;;; - If there is only one dot, it seperates the file and the type.
52     ;;;
53     ;;; - If there are multiple dots and the stuff following the last dot
54     ;;; is a valid version, then that is the version and the stuff between
55     ;;; the second to last dot and the last dot is the type.
56     ;;;
57     ;;; Wildcard characters:
58     ;;;
59     ;;; If the directory, file, type components contain any of the following
60     ;;; characters, it is considered part of a wildcard pattern and has the
61     ;;; following meaning.
62     ;;;
63     ;;; ? - matches any character
64     ;;; * - matches any zero or more characters.
65     ;;; [abc] - matches any of a, b, or c.
66     ;;; {str1,str2,...,strn} - matches any of str1, str2, ..., or strn.
67     ;;;
68     ;;; Any of these special characters can be preceeded by a backslash to
69     ;;; cause it to be treated as a regular character.
70     ;;;
71 ram 1.1
72 wlott 1.16 (defun remove-backslashes (namestr start end)
73     "Remove and occurences of \\ from the string because we've already
74     checked for whatever they may have been backslashed."
75     (declare (type simple-base-string namestr)
76     (type index start end))
77     (let* ((result (make-string (- end start)))
78     (dst 0)
79     (quoted nil))
80     (do ((src start (1+ src)))
81     ((= src end))
82     (cond (quoted
83     (setf (schar result dst) (schar namestr src))
84     (setf quoted nil)
85     (incf dst))
86     (t
87     (let ((char (schar namestr src)))
88     (cond ((char= char #\\)
89     (setq quoted t))
90     (t
91     (setf (schar result dst) char)
92     (incf dst)))))))
93     (when quoted
94     (error 'namestring-parse-error
95     :complaint "Backslash in bad place."
96     :namestring namestr
97     :offset (1- end)))
98     (shrink-vector result dst)))
99 ram 1.1
100 wlott 1.16 (defvar *ignore-wildcards* nil)
101 ram 1.1
102 wlott 1.16 (defun maybe-make-pattern (namestr start end)
103     (declare (type simple-base-string namestr)
104     (type index start end))
105     (if *ignore-wildcards*
106     (subseq namestr start end)
107     (collect ((pattern))
108     (let ((quoted nil)
109     (any-quotes nil)
110     (last-regular-char nil)
111     (index start))
112     (flet ((flush-pending-regulars ()
113     (when last-regular-char
114     (pattern (if any-quotes
115     (remove-backslashes namestr
116     last-regular-char
117     index)
118     (subseq namestr last-regular-char index)))
119     (setf any-quotes nil)
120     (setf last-regular-char nil))))
121     (loop
122     (when (>= index end)
123     (return))
124     (let ((char (schar namestr index)))
125     (cond (quoted
126     (incf index)
127     (setf quoted nil))
128     ((char= char #\\)
129     (setf quoted t)
130     (setf any-quotes t)
131     (unless last-regular-char
132     (setf last-regular-char index))
133     (incf index))
134     ((char= char #\?)
135     (flush-pending-regulars)
136     (pattern :single-char-wild)
137     (incf index))
138     ((char= char #\*)
139     (flush-pending-regulars)
140     (pattern :multi-char-wild)
141     (incf index))
142     ((char= char #\[)
143     (flush-pending-regulars)
144     (let ((close-bracket
145     (position #\] namestr :start index :end end)))
146     (unless close-bracket
147     (error 'namestring-parse-error
148     :complaint "``['' with no corresponding ``]''"
149     :namestring namestr
150     :offset index))
151     (pattern (list :character-set
152     (subseq namestr
153     (1+ index)
154     close-bracket)))
155     (setf index (1+ close-bracket))))
156     (t
157     (unless last-regular-char
158     (setf last-regular-char index))
159     (incf index)))))
160     (flush-pending-regulars)))
161     (cond ((null (pattern))
162     "")
163 ram 1.31 ((null (cdr (pattern)))
164     (let ((piece (first (pattern))))
165     (typecase piece
166     ((member :multi-char-wild) :wild)
167     (simple-string piece)
168     (t
169     (make-pattern (pattern))))))
170 wlott 1.16 (t
171     (make-pattern (pattern)))))))
172 ram 1.1
173 wlott 1.16 (defun extract-name-type-and-version (namestr start end)
174     (declare (type simple-base-string namestr)
175     (type index start end))
176     (let* ((last-dot (position #\. namestr :start (1+ start) :end end
177     :from-end t))
178     (second-to-last-dot (and last-dot
179     (position #\. namestr :start (1+ start)
180     :end last-dot :from-end t)))
181     (version :newest))
182     ;; If there is a second-to-last dot, check to see if there is a valid
183     ;; version after the last dot.
184     (when second-to-last-dot
185     (cond ((and (= (+ last-dot 2) end)
186     (char= (schar namestr (1+ last-dot)) #\*))
187     (setf version :wild))
188     ((and (< (1+ last-dot) end)
189     (do ((index (1+ last-dot) (1+ index)))
190     ((= index end) t)
191     (unless (char<= #\0 (schar namestr index) #\9)
192     (return nil))))
193     (setf version
194     (parse-integer namestr :start (1+ last-dot) :end end)))
195     (t
196     (setf second-to-last-dot nil))))
197     (cond (second-to-last-dot
198     (values (maybe-make-pattern namestr start second-to-last-dot)
199     (maybe-make-pattern namestr
200     (1+ second-to-last-dot)
201     last-dot)
202     version))
203     (last-dot
204     (values (maybe-make-pattern namestr start last-dot)
205     (maybe-make-pattern namestr (1+ last-dot) end)
206     version))
207     (t
208     (values (maybe-make-pattern namestr start end)
209     nil
210     version)))))
211 ram 1.1
212 ram 1.31 ;;; Take a string and return a list of cons cells that mark the char
213     ;;; separated subseq. The first value t if absolute directories location.
214     ;;;
215     (defun split-at-slashes (namestr start end)
216     (declare (type simple-base-string namestr)
217 wlott 1.16 (type index start end))
218     (let ((absolute (and (/= start end)
219 ram 1.31 (char= (schar namestr start) #\/))))
220 wlott 1.16 (when absolute
221     (incf start))
222 ram 1.31 ;; Next, split the remainder into slash seperated chunks.
223 wlott 1.16 (collect ((pieces))
224     (loop
225 ram 1.31 (let ((slash (position #\/ namestr :start start :end end)))
226     (pieces (cons start (or slash end)))
227     (unless slash
228 wlott 1.16 (return))
229 ram 1.31 (setf start (1+ slash))))
230 wlott 1.16 (values absolute (pieces)))))
231 ram 1.1
232 wlott 1.16 (defun maybe-extract-search-list (namestr start end)
233     (declare (type simple-base-string namestr)
234     (type index start end))
235     (let ((quoted nil))
236     (do ((index start (1+ index)))
237     ((= index end)
238     (values nil start))
239     (if quoted
240     (setf quoted nil)
241     (case (schar namestr index)
242     (#\\
243     (setf quoted t))
244     (#\:
245     (return (values (remove-backslashes namestr start index)
246     (1+ index)))))))))
247 ram 1.1
248 wlott 1.16 (defun parse-unix-namestring (namestr start end)
249     (declare (type simple-base-string namestr)
250     (type index start end))
251     (multiple-value-bind
252     (absolute pieces)
253     (split-at-slashes namestr start end)
254     (let ((search-list
255     (if absolute
256     nil
257     (let ((first (car pieces)))
258     (multiple-value-bind
259     (search-list new-start)
260     (maybe-extract-search-list namestr
261     (car first) (cdr first))
262     (when search-list
263     (setf absolute t)
264     (setf (car first) new-start))
265     search-list)))))
266     (multiple-value-bind
267     (name type version)
268     (let* ((tail (car (last pieces)))
269     (tail-start (car tail))
270     (tail-end (cdr tail)))
271     (unless (= tail-start tail-end)
272     (setf pieces (butlast pieces))
273     (extract-name-type-and-version namestr tail-start tail-end)))
274 pw 1.43.2.3 ;; PVE: Make sure there are no illegal characters in the name
275     ;; such as #\Null and #\/.
276     (when (and (stringp name)
277     (find-if #'(lambda (x)
278     (or (char= x #\Null) (char= x #\/)))
279     name))
280     (error 'parse-error))
281 wlott 1.16 ;; Now we have everything we want. So return it.
282     (values nil ; no host for unix namestrings.
283     nil ; no devices for unix namestrings.
284     (collect ((dirs))
285     (when search-list
286     (dirs (intern-search-list search-list)))
287     (dolist (piece pieces)
288     (let ((piece-start (car piece))
289     (piece-end (cdr piece)))
290     (unless (= piece-start piece-end)
291 ram 1.31 (cond ((string= namestr ".." :start1 piece-start
292     :end1 piece-end)
293     (dirs :up))
294     ((string= namestr "**" :start1 piece-start
295     :end1 piece-end)
296     (dirs :wild-inferiors))
297     (t
298     (dirs (maybe-make-pattern namestr
299     piece-start
300     piece-end)))))))
301 wlott 1.25 (cond (absolute
302     (cons :absolute (dirs)))
303     ((dirs)
304     (cons :relative (dirs)))
305     (t
306     nil)))
307 wlott 1.16 name
308     type
309     version)))))
310 ram 1.1
311 wlott 1.16 (defun unparse-unix-host (pathname)
312     (declare (type pathname pathname)
313     (ignore pathname))
314     "Unix")
315 ram 1.1
316 wlott 1.16 (defun unparse-unix-piece (thing)
317     (etypecase thing
318 ram 1.31 ((member :wild) "*")
319 wlott 1.16 (simple-string
320     (let* ((srclen (length thing))
321     (dstlen srclen))
322     (dotimes (i srclen)
323     (case (schar thing i)
324     ((#\* #\? #\[)
325     (incf dstlen))))
326     (let ((result (make-string dstlen))
327     (dst 0))
328     (dotimes (src srclen)
329     (let ((char (schar thing src)))
330     (case char
331     ((#\* #\? #\[)
332     (setf (schar result dst) #\\)
333     (incf dst)))
334     (setf (schar result dst) char)
335     (incf dst)))
336     result)))
337     (pattern
338     (collect ((strings))
339     (dolist (piece (pattern-pieces thing))
340     (etypecase piece
341     (simple-string
342     (strings piece))
343     (symbol
344 ram 1.31 (ecase piece
345 wlott 1.16 (:multi-char-wild
346     (strings "*"))
347     (:single-char-wild
348 ram 1.31 (strings "?"))))
349 wlott 1.16 (cons
350     (case (car piece)
351     (:character-set
352     (strings "[")
353     (strings (cdr piece))
354     (strings "]"))
355     (t
356     (error "Invalid pattern piece: ~S" piece))))))
357     (apply #'concatenate
358     'simple-string
359     (strings))))))
360 ram 1.1
361 wlott 1.16 (defun unparse-unix-directory-list (directory)
362     (declare (type list directory))
363     (collect ((pieces))
364     (when directory
365     (ecase (pop directory)
366     (:absolute
367     (cond ((search-list-p (car directory))
368     (pieces (search-list-name (pop directory)))
369     (pieces ":"))
370     (t
371     (pieces "/"))))
372     (:relative
373     ;; Nothing special.
374     ))
375     (dolist (dir directory)
376     (typecase dir
377     ((member :up)
378     (pieces "../"))
379     ((member :back)
380     (error ":BACK cannot be represented in namestrings."))
381 ram 1.31 ((member :wild-inferiors)
382     (pieces "**/"))
383 wlott 1.16 ((or simple-string pattern)
384     (pieces (unparse-unix-piece dir))
385     (pieces "/"))
386     (t
387     (error "Invalid directory component: ~S" dir)))))
388     (apply #'concatenate 'simple-string (pieces))))
389 ram 1.1
390 wlott 1.16 (defun unparse-unix-directory (pathname)
391     (declare (type pathname pathname))
392     (unparse-unix-directory-list (%pathname-directory pathname)))
393    
394     (defun unparse-unix-file (pathname)
395     (declare (type pathname pathname))
396     (collect ((strings))
397     (let* ((name (%pathname-name pathname))
398     (type (%pathname-type pathname))
399     (type-supplied (not (or (null type) (eq type :unspecific))))
400     (version (%pathname-version pathname))
401     (version-supplied (not (or (null version) (eq version :newest)))))
402     (when name
403     (strings (unparse-unix-piece name)))
404     (when type-supplied
405     (unless name
406     (error "Cannot specify the type without a file: ~S" pathname))
407     (strings ".")
408     (strings (unparse-unix-piece type)))
409     (when version-supplied
410     (unless type-supplied
411     (error "Cannot specify the version without a type: ~S" pathname))
412     (strings (if (eq version :wild)
413     ".*"
414     (format nil ".~D" version)))))
415 pw 1.43.2.3 (and (strings) (apply #'concatenate 'simple-string (strings)))))
416 ram 1.1
417 wlott 1.16 (defun unparse-unix-namestring (pathname)
418     (declare (type pathname pathname))
419     (concatenate 'simple-string
420     (unparse-unix-directory pathname)
421     (unparse-unix-file pathname)))
422 ram 1.1
423 wlott 1.16 (defun unparse-unix-enough (pathname defaults)
424     (declare (type pathname pathname defaults))
425     (flet ((lose ()
426     (error "~S cannot be represented relative to ~S"
427     pathname defaults)))
428     (collect ((strings))
429     (let* ((pathname-directory (%pathname-directory pathname))
430     (defaults-directory (%pathname-directory defaults))
431     (prefix-len (length defaults-directory))
432     (result-dir
433     (cond ((and (> prefix-len 1)
434     (>= (length pathname-directory) prefix-len)
435     (compare-component (subseq pathname-directory
436     0 prefix-len)
437     defaults-directory))
438     ;; Pathname starts with a prefix of default. So just
439     ;; use a relative directory from then on out.
440     (cons :relative (nthcdr prefix-len pathname-directory)))
441     ((eq (car pathname-directory) :absolute)
442     ;; We are an absolute pathname, so we can just use it.
443     pathname-directory)
444     (t
445     ;; We are a relative directory. So we lose.
446     (lose)))))
447     (strings (unparse-unix-directory-list result-dir)))
448     (let* ((pathname-version (%pathname-version pathname))
449     (version-needed (and pathname-version
450     (not (eq pathname-version :newest))))
451     (pathname-type (%pathname-type pathname))
452     (type-needed (or version-needed
453     (and pathname-type
454     (not (eq pathname-type :unspecific)))))
455     (pathname-name (%pathname-name pathname))
456     (name-needed (or type-needed
457     (and pathname-name
458     (not (compare-component pathname-name
459     (%pathname-name
460     defaults)))))))
461     (when name-needed
462     (unless pathname-name (lose))
463     (strings (unparse-unix-piece pathname-name)))
464     (when type-needed
465     (when (or (null pathname-type) (eq pathname-type :unspecific))
466     (lose))
467     (strings ".")
468     (strings (unparse-unix-piece pathname-type)))
469     (when version-needed
470     (typecase pathname-version
471     ((member :wild)
472     (strings ".*"))
473     (integer
474     (strings (format nil ".~D" pathname-version)))
475     (t
476     (lose)))))
477     (apply #'concatenate 'simple-string (strings)))))
478 ram 1.1
479    
480 wlott 1.16 (defstruct (unix-host
481     (:include host
482     (:parse #'parse-unix-namestring)
483     (:unparse #'unparse-unix-namestring)
484     (:unparse-host #'unparse-unix-host)
485     (:unparse-directory #'unparse-unix-directory)
486     (:unparse-file #'unparse-unix-file)
487     (:unparse-enough #'unparse-unix-enough)
488     (:customary-case :lower))
489     (:make-load-form-fun make-unix-host-load-form))
490     )
491 ram 1.1
492 wlott 1.16 (defvar *unix-host* (make-unix-host))
493 ram 1.1
494 wlott 1.16 (defun make-unix-host-load-form (host)
495     (declare (ignore host))
496     '*unix-host*)
497 ram 1.1
498 wlott 1.16
499     ;;;; Wildcard matching stuff.
500 ram 1.1
501 wlott 1.16 (defmacro enumerate-matches ((var pathname &optional result
502 dtc 1.43.2.4 &key (verify-existance t) (follow-links t))
503 wlott 1.16 &body body)
504     (let ((body-name (gensym)))
505     `(block nil
506     (flet ((,body-name (,var)
507     ,@body))
508     (%enumerate-matches (pathname ,pathname)
509 dtc 1.43.2.4 ,verify-existance ,follow-links
510 wlott 1.16 #',body-name)
511     ,result))))
512 ram 1.1
513 dtc 1.43.2.4 (defun %enumerate-matches (pathname verify-existance follow-links function)
514 ram 1.34 (when (pathname-type pathname)
515     (unless (pathname-name pathname)
516     (error "Cannot supply a type without a name:~% ~S" pathname)))
517     (when (and (integerp (pathname-version pathname))
518     (member (pathname-type pathname) '(nil :unspecific)))
519     (error "Cannot supply a version without a type:~% ~S" pathname))
520     (let ((directory (pathname-directory pathname)))
521     (if directory
522     (ecase (car directory)
523     (:absolute
524     (%enumerate-directories "/" (cdr directory) pathname
525 dtc 1.43.2.4 verify-existance follow-links
526     nil function))
527 ram 1.34 (:relative
528     (%enumerate-directories "" (cdr directory) pathname
529 dtc 1.43.2.4 verify-existance follow-links
530     nil function)))
531 ram 1.34 (%enumerate-files "" pathname verify-existance function))))
532 ram 1.1
533 dtc 1.43.2.4 ;;; %enumerate-directories -- Internal
534     ;;;
535     ;;; The directory node and device numbers are maintained for the current path
536     ;;; during the search for the detection of paths loops upon :wild-inferiors.
537     ;;;
538     (defun %enumerate-directories (head tail pathname verify-existance
539     follow-links nodes function)
540 ram 1.37 (declare (simple-string head))
541 dtc 1.43.2.4 (macrolet ((unix-xstat (name)
542     `(if follow-links
543     (unix:unix-stat ,name)
544     (unix:unix-lstat ,name)))
545     (with-directory-node-noted ((head) &body body)
546     `(multiple-value-bind (res dev ino mode)
547     (unix-xstat ,head)
548     (when (and res (eql (logand mode unix:s-ifmt) unix:s-ifdir))
549     (let ((nodes (cons (cons dev ino) nodes)))
550     ,@body))))
551     (do-directory-entries ((name directory) &body body)
552     `(let ((dir (unix:open-dir ,directory)))
553     (when dir
554     (unwind-protect
555     (loop
556     (let ((,name (unix:read-dir dir)))
557     (cond ((null ,name)
558     (return))
559     ((string= ,name "."))
560     ((string= ,name ".."))
561     (t
562     ,@body))))
563     (unix:close-dir dir))))))
564     (if tail
565     (let ((piece (car tail)))
566     (etypecase piece
567     (simple-string
568     (let ((head (concatenate 'string head piece)))
569     (with-directory-node-noted (head)
570     (%enumerate-directories (concatenate 'string head "/")
571     (cdr tail) pathname
572     verify-existance follow-links
573     nodes function))))
574     ((member :wild-inferiors)
575     (%enumerate-directories head (rest tail) pathname
576     verify-existance follow-links
577     nodes function)
578     (do-directory-entries (name head)
579     (let ((subdir (concatenate 'string head name)))
580     (multiple-value-bind (res dev ino mode)
581     (unix-xstat subdir)
582     (declare (type (or fixnum null) mode))
583     (when (and res (eql (logand mode unix:s-ifmt) unix:s-ifdir))
584     (unless (dolist (dir nodes nil)
585     (when (and (eql (car dir) dev)
586     (eql (cdr dir) ino))
587     (return t)))
588     (let ((nodes (cons (cons dev ino) nodes))
589     (subdir (concatenate 'string subdir "/")))
590     (%enumerate-directories subdir tail pathname
591     verify-existance follow-links
592     nodes function))))))))
593     ((or pattern (member :wild))
594     (do-directory-entries (name head)
595     (when (or (eq piece :wild) (pattern-matches piece name))
596     (let ((subdir (concatenate 'string head name)))
597     (multiple-value-bind (res dev ino mode)
598     (unix-xstat subdir)
599     (declare (type (or fixnum null) mode))
600     (when (and res
601     (eql (logand mode unix:s-ifmt) unix:s-ifdir))
602     (let ((nodes (cons (cons dev ino) nodes))
603     (subdir (concatenate 'string subdir "/")))
604     (%enumerate-directories subdir (rest tail) pathname
605     verify-existance follow-links
606     nodes function))))))))
607     ((member :up)
608     (let ((head (concatenate 'string head "..")))
609     (with-directory-node-noted (head)
610     (%enumerate-directories (concatenate 'string head "/")
611     (rest tail) pathname
612     verify-existance follow-links
613     nodes function))))))
614     (%enumerate-files head pathname verify-existance function))))
615 ram 1.1
616 wlott 1.16 (defun %enumerate-files (directory pathname verify-existance function)
617 ram 1.37 (declare (simple-string directory))
618 ram 1.36 (let ((name (%pathname-name pathname))
619     (type (%pathname-type pathname))
620     (version (%pathname-version pathname)))
621 ram 1.33 (cond ((member name '(nil :unspecific))
622 wlott 1.16 (when (or (not verify-existance)
623 wlott 1.26 (unix:unix-file-kind directory))
624 wlott 1.16 (funcall function directory)))
625     ((or (pattern-p name)
626     (pattern-p type)
627 ram 1.33 (eq name :wild)
628     (eq type :wild))
629 wlott 1.26 (let ((dir (unix:open-dir directory)))
630 wlott 1.16 (when dir
631     (unwind-protect
632     (loop
633 wlott 1.26 (let ((file (unix:read-dir dir)))
634 wlott 1.16 (if file
635 wlott 1.21 (unless (or (string= file ".")
636     (string= file ".."))
637     (multiple-value-bind
638     (file-name file-type file-version)
639     (let ((*ignore-wildcards* t))
640     (extract-name-type-and-version
641     file 0 (length file)))
642     (when (and (components-match file-name name)
643     (components-match file-type type)
644     (components-match file-version
645     version))
646     (funcall function
647     (concatenate 'string
648     directory
649     file)))))
650 wlott 1.16 (return))))
651 wlott 1.26 (unix:close-dir dir)))))
652 wlott 1.16 (t
653     (let ((file (concatenate 'string directory name)))
654 wlott 1.19 (unless (or (null type) (eq type :unspecific))
655 wlott 1.16 (setf file (concatenate 'string file "." type)))
656 ram 1.33 (unless (member version '(nil :newest :wild))
657 wlott 1.19 (setf file (concatenate 'string file "."
658 wlott 1.20 (quick-integer-to-string version))))
659 wlott 1.16 (when (or (not verify-existance)
660 ram 1.40 (unix:unix-file-kind file t))
661 wlott 1.16 (funcall function file)))))))
662 ram 1.1
663     (defun quick-integer-to-string (n)
664 wlott 1.16 (declare (type integer n))
665 ram 1.38 (cond ((not (fixnump n))
666     (write-to-string n :base 10 :radix nil))
667     ((zerop n) "0")
668 ram 1.1 ((eql n 1) "1")
669     ((minusp n)
670     (concatenate 'simple-string "-"
671     (the simple-string (quick-integer-to-string (- n)))))
672     (t
673     (do* ((len (1+ (truncate (integer-length n) 3)))
674     (res (make-string len))
675     (i (1- len) (1- i))
676     (q n)
677     (r 0))
678     ((zerop q)
679     (incf i)
680     (replace res res :start2 i :end2 len)
681 wlott 1.3 (shrink-vector res (- len i)))
682 ram 1.1 (declare (simple-string res)
683 ram 1.38 (fixnum len i r q))
684 ram 1.1 (multiple-value-setq (q r) (truncate q 10))
685     (setf (schar res i) (schar "0123456789" r))))))
686    
687 wlott 1.16
688     ;;;; UNIX-NAMESTRING -- public
689 wlott 1.5 ;;;
690 wlott 1.39 (defun unix-namestring (pathname &optional (for-input t) executable-only)
691 wlott 1.16 "Convert PATHNAME into a string that can be used with UNIX system calls.
692 pw 1.43.2.3 Search-lists and wild-cards are expanded. If optional argument
693     FOR-INPUT is true and PATHNAME doesn't exist, NIL is returned.
694     If optional argument EXECUTABLE-ONLY is true, NIL is returned
695     unless an executable version of PATHNAME exists."
696 ram 1.41 ;; toy@rtp.ericsson.se: Let unix-namestring also handle logical
697     ;; pathnames too.
698     (let ((path (let ((lpn (pathname pathname)))
699     (if (logical-pathname-p lpn)
700     (namestring (translate-logical-pathname lpn))
701     pathname))))
702 wlott 1.16 (enumerate-search-list
703 ram 1.41 (pathname path)
704 wlott 1.16 (collect ((names))
705 dtc 1.43.2.4 (enumerate-matches (name pathname nil :verify-existance for-input
706     :follow-links t)
707 wlott 1.39 (when (or (not executable-only)
708     (and (eq (unix:unix-file-kind name) :file)
709     (unix:unix-access name unix:x_ok)))
710     (names name)))
711 wlott 1.16 (let ((names (names)))
712     (when names
713     (when (cdr names)
714 pw 1.43.2.3 (error 'simple-file-error
715     :format-control "~S is ambiguous:~{~% ~A~}"
716     :format-arguments (list pathname names)))
717 ram 1.41 (return (car names))))))))
718 wlott 1.4
719 ram 1.1
720 wlott 1.16 ;;;; TRUENAME and PROBE-FILE.
721 ram 1.1
722     ;;; Truename -- Public
723     ;;;
724 wlott 1.4 ;;; Another silly file function trivially different from another function.
725 ram 1.1 ;;;
726     (defun truename (pathname)
727     "Return the pathname for the actual file described by the pathname
728 pw 1.43.2.1 An error of type file-error is signalled if no such file exists,
729     or the pathname is wild."
730     (if (wild-pathname-p pathname)
731 dtc 1.43.2.2 (error 'simple-file-error
732 pw 1.43.2.1 :format-control "Bad place for a wild pathname."
733     :pathname pathname)
734     (let ((result (probe-file pathname)))
735     (unless result
736 dtc 1.43.2.2 (error 'simple-file-error
737 pw 1.43.2.1 :pathname pathname
738     :format-control "The file ~S does not exist."
739     :format-arguments (list (namestring pathname))))
740     result)))
741 ram 1.1
742     ;;; Probe-File -- Public
743     ;;;
744 wlott 1.4 ;;; If PATHNAME exists, return it's truename, otherwise NIL.
745 ram 1.1 ;;;
746     (defun probe-file (pathname)
747     "Return a pathname which is the truename of the file if it exists, NIL
748 pw 1.43.2.1 otherwise. An error of type file-error is signaled if pathname is wild."
749     (if (wild-pathname-p pathname)
750 dtc 1.43.2.2 (error 'simple-file-error
751 pw 1.43.2.1 :pathname pathname
752     :format-control "Bad place for a wild pathname.")
753     (let ((namestring (unix-namestring pathname t)))
754     (when (and namestring (unix:unix-file-kind namestring))
755     (let ((truename (unix:unix-resolve-links
756     (unix:unix-maybe-prepend-current-directory
757     namestring))))
758     (when truename
759     (let ((*ignore-wildcards* t))
760     (pathname (unix:unix-simplify-pathname truename)))))))))
761 ram 1.1
762    
763 wlott 1.4 ;;;; Other random operations.
764 ram 1.1
765     ;;; Rename-File -- Public
766     ;;;
767     (defun rename-file (file new-name)
768     "Rename File to have the specified New-Name. If file is a stream open to a
769 ram 1.35 file, then the associated file is renamed."
770 wlott 1.4 (let* ((original (truename file))
771 wlott 1.16 (original-namestring (unix-namestring original t))
772 wlott 1.4 (new-name (merge-pathnames new-name original))
773     (new-namestring (unix-namestring new-name nil)))
774 wlott 1.17 (unless new-namestring
775 dtc 1.43.2.2 (error 'simple-file-error
776 pw 1.43.2.1 :pathname new-name
777     :format-control "~S can't be created."
778     :format-arguments (list new-name)))
779 wlott 1.4 (multiple-value-bind (res error)
780 wlott 1.26 (unix:unix-rename original-namestring
781 wlott 1.4 new-namestring)
782     (unless res
783 dtc 1.43.2.2 (error 'simple-file-error
784 pw 1.43.2.1 :pathname new-name
785     :format-control "Failed to rename ~A to ~A: ~A"
786     :format-arguments (list original new-name
787     (unix:get-unix-error-msg error))))
788 wlott 1.4 (when (streamp file)
789     (file-name file new-namestring))
790 wlott 1.16 (values new-name original (truename new-name)))))
791 ram 1.1
792     ;;; Delete-File -- Public
793     ;;;
794     ;;; Delete the file, Man.
795     ;;;
796     (defun delete-file (file)
797     "Delete the specified file."
798 wlott 1.4 (let ((namestring (unix-namestring file t)))
799 ram 1.1 (when (streamp file)
800     (close file :abort t))
801 ram 1.24 (unless namestring
802 dtc 1.43.2.2 (error 'simple-file-error
803 pw 1.43.2.1 :pathname file
804     :format-control "~S doesn't exist."
805     :format-arguments (list file)))
806 ram 1.24
807 wlott 1.26 (multiple-value-bind (res err) (unix:unix-unlink namestring)
808 ram 1.24 (unless res
809 dtc 1.43.2.2 (error 'simple-file-error
810 pw 1.43.2.1 :pathname namestring
811     :format-control "Could not delete ~A: ~A."
812     :format-arguments (list namestring
813     (unix:get-unix-error-msg err))))))
814 ram 1.1 t)
815 wlott 1.4
816 ram 1.1
817     ;;; User-Homedir-Pathname -- Public
818     ;;;
819 ram 1.12 ;;; Return Home:, which is set up for us at initialization time.
820 ram 1.1 ;;;
821     (defun user-homedir-pathname (&optional host)
822     "Returns the home directory of the logged in user as a pathname.
823 ram 1.12 This is obtained from the logical name \"home:\"."
824 ram 1.1 (declare (ignore host))
825 ram 1.12 #p"home:")
826 ram 1.1
827     ;;; File-Write-Date -- Public
828     ;;;
829     (defun file-write-date (file)
830 pw 1.43.2.1 "Return file's creation date, or NIL if it doesn't exist.
831     An error of type file-error is signaled if file is a wild pathname"
832     (if (wild-pathname-p file)
833 dtc 1.43.2.2 (error 'simple-file-error
834 pw 1.43.2.1 :pathname file
835     :format-control "Bad place for a wild pathname.")
836     (let ((name (unix-namestring file t)))
837     (when name
838     (multiple-value-bind
839     (res dev ino mode nlink uid gid rdev size atime mtime)
840     (unix:unix-stat name)
841     (declare (ignore dev ino mode nlink uid gid rdev size atime))
842     (when res
843     (+ unix-to-universal-time mtime)))))))
844 ram 1.1
845     ;;; File-Author -- Public
846     ;;;
847     (defun file-author (file)
848     "Returns the file author as a string, or nil if the author cannot be
849 pw 1.43.2.1 determined. Signals an error of type file-error if file doesn't exist,
850     or file is a wild pathname."
851     (if (wild-pathname-p file)
852 dtc 1.43.2.2 (error 'simple-file-error
853 pw 1.43.2.1 :pathname file
854     "Bad place for a wild pathname.")
855     (let ((name (unix-namestring (pathname file) t)))
856     (unless name
857 dtc 1.43.2.2 (error 'simple-file-error
858 pw 1.43.2.1 :pathname file
859     :format-control "~S doesn't exist."
860     :format-arguments (list file)))
861     (multiple-value-bind (winp dev ino mode nlink uid)
862     (unix:unix-stat name)
863     (declare (ignore dev ino mode nlink))
864     (if winp (lookup-login-name uid))))))
865 ram 1.1
866    
867     ;;;; DIRECTORY.
868    
869 wlott 1.4 ;;; DIRECTORY -- public.
870     ;;;
871 ram 1.23 (defun directory (pathname &key (all t) (check-for-subdirs t)
872     (follow-links t))
873 ram 1.1 "Returns a list of pathnames, one for each file that matches the given
874 ram 1.23 pathname. Supplying :ALL as nil causes this to ignore Unix dot files. This
875     never includes Unix dot and dot-dot in the result. If :FOLLOW-LINKS is NIL,
876     then symblolic links in the result are not expanded. This is not the
877     default because TRUENAME does follow links, and the result pathnames are
878     defined to be the TRUENAME of the pathname (the truename of a link may well
879     be in another directory.)"
880 wlott 1.16 (let ((results nil))
881     (enumerate-search-list
882 wlott 1.21 (pathname (merge-pathnames pathname
883     (make-pathname :name :wild
884     :type :wild
885     :version :wild)))
886 dtc 1.43.2.4 (enumerate-matches (name pathname nil :follow-links follow-links)
887 wlott 1.16 (when (or all
888     (let ((slash (position #\/ name :from-end t)))
889     (or (null slash)
890     (= (1+ slash) (length name))
891     (char/= (schar name (1+ slash)) #\.))))
892     (push name results))))
893     (let ((*ignore-wildcards* t))
894     (mapcar #'(lambda (name)
895 ram 1.23 (let ((name (if (and check-for-subdirs
896 wlott 1.26 (eq (unix:unix-file-kind name)
897 ram 1.23 :directory))
898     (concatenate 'string name "/")
899     name)))
900     (if follow-links (truename name) (pathname name))))
901 wlott 1.16 (sort (delete-duplicates results :test #'string=) #'string<)))))
902 ram 1.1
903 wlott 1.4
904     ;;;; Printing directories.
905 ram 1.1
906     ;;; PRINT-DIRECTORY is exported from the EXTENSIONS package.
907     ;;;
908     (defun print-directory (pathname &optional stream &key all verbose return-list)
909     "Like Directory, but prints a terse, multi-coloumn directory listing
910     instead of returning a list of pathnames. When :all is supplied and
911     non-nil, then Unix dot files are included too (as ls -a). When :vervose
912     is supplied and non-nil, then a long listing of miscellaneous
913     information is output one file per line."
914 wlott 1.16 (let ((*standard-output* (out-synonym-of stream))
915     (pathname pathname))
916 ram 1.1 (if verbose
917     (print-directory-verbose pathname all return-list)
918     (print-directory-formatted pathname all return-list))))
919    
920     (defun print-directory-verbose (pathname all return-list)
921 ram 1.23 (let ((contents (directory pathname :all all :check-for-subdirs nil
922     :follow-links nil))
923 wlott 1.7 (result nil))
924     (format t "Directory of ~A :~%" (namestring pathname))
925     (dolist (file contents)
926     (let* ((namestring (unix-namestring file))
927     (tail (subseq namestring
928     (1+ (or (position #\/ namestring
929     :from-end t
930     :test #'char=)
931     -1)))))
932     (multiple-value-bind
933     (reslt dev-or-err ino mode nlink uid gid rdev size atime mtime)
934 wlott 1.26 (unix:unix-stat namestring)
935 wlott 1.7 (declare (ignore ino gid rdev atime)
936     (fixnum uid mode))
937     (cond (reslt
938     ;;
939     ;; Print characters for file modes.
940     (macrolet ((frob (bit name &optional sbit sname negate)
941     `(if ,(if negate
942     `(not (logbitp ,bit mode))
943     `(logbitp ,bit mode))
944     ,(if sbit
945     `(if (logbitp ,sbit mode)
946     (write-char ,sname)
947     (write-char ,name))
948     `(write-char ,name))
949     (write-char #\-))))
950     (frob 15 #\d nil nil t)
951     (frob 8 #\r)
952     (frob 7 #\w)
953     (frob 6 #\x 11 #\s)
954     (frob 5 #\r)
955     (frob 4 #\w)
956     (frob 3 #\x 10 #\s)
957     (frob 2 #\r)
958     (frob 1 #\w)
959     (frob 0 #\x))
960     ;;
961     ;; Print the rest.
962     (multiple-value-bind (sec min hour date month year)
963     (get-decoded-time)
964     (declare (ignore sec min hour date month))
965     (format t "~2D ~8A ~8D ~12A ~A~@[/~]~%"
966     nlink
967     (or (lookup-login-name uid) uid)
968     size
969     (decode-universal-time-for-files mtime year)
970     tail
971 wlott 1.27 (= (logand mode unix:s-ifmt) unix:s-ifdir))))
972 wlott 1.7 (t (format t "Couldn't stat ~A -- ~A.~%"
973     tail
974 wlott 1.26 (unix:get-unix-error-msg dev-or-err))))
975 ram 1.1 (when return-list
976 wlott 1.27 (push (if (= (logand mode unix:s-ifmt) unix:s-ifdir)
977 wlott 1.7 (pathname (concatenate 'string namestring "/"))
978     file)
979     result)))))
980     (nreverse result)))
981 ram 1.1
982     (defun decode-universal-time-for-files (time current-year)
983     (multiple-value-bind (sec min hour day month year)
984     (decode-universal-time (+ time unix-to-universal-time))
985     (declare (ignore sec))
986     (format nil "~A ~2,' D ~:[ ~D~;~*~2,'0D:~2,'0D~]"
987     (svref '#("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug"
988     "Sep" "Oct" "Nov" "Dec")
989     (1- month))
990     day (= current-year year) year hour min)))
991    
992     (defun print-directory-formatted (pathname all return-list)
993     (let ((width (or (line-length *standard-output*) 80))
994     (names ())
995     (cnt 0)
996     (max-len 0)
997 ram 1.23 (result (directory pathname :all all :follow-links nil)))
998 ram 1.1 (declare (list names) (fixnum max-len cnt))
999     ;;
1000     ;; Get the data.
1001 wlott 1.7 (dolist (file result)
1002     (let* ((name (unix-namestring file))
1003     (length (length name))
1004     (end (if (and (plusp length)
1005     (char= (schar name (1- length)) #\/))
1006     (1- length)
1007     length))
1008     (slash-name (subseq name
1009     (1+ (or (position #\/ name
1010     :from-end t
1011     :end end
1012     :test #'char=)
1013     -1))))
1014     (len (length slash-name)))
1015     (declare (simple-string slash-name)
1016     (fixnum len))
1017     (if (> len max-len) (setq max-len len))
1018     (incf cnt)
1019     (push slash-name names)))
1020     (setq names (nreverse names))
1021     ;;
1022     ;; Do the output.
1023     (let* ((col-width (1+ max-len))
1024     (cols (max (truncate width col-width) 1))
1025     (lines (ceiling cnt cols)))
1026     (declare (fixnum cols lines))
1027     (format t "Directory of ~A :~%" (namestring pathname))
1028     (dotimes (i lines)
1029     (declare (fixnum i))
1030     (dotimes (j cols)
1031     (declare (fixnum j))
1032     (let ((name (nth (+ i (the fixnum (* j lines))) names)))
1033     (when name
1034     (write-string name)
1035     (unless (eql j (1- cols))
1036 wlott 1.15 (dotimes (i (- col-width (length (the simple-string name))))
1037     (write-char #\space))))))
1038 wlott 1.7 (terpri)))
1039     (when return-list
1040     result)))
1041 ram 1.1
1042    
1043    
1044     ;;;; Translating uid's and gid's.
1045    
1046     (defvar *uid-hash-table* (make-hash-table)
1047     "Hash table for keeping track of uid's and login names.")
1048    
1049     ;;; LOOKUP-LOGIN-NAME translates a user id into a login name. Previous
1050     ;;; lookups are cached in a hash table since groveling the passwd(s) files
1051     ;;; is somewhat expensive. The table may hold nil for id's that cannot
1052     ;;; be looked up since this means the files are searched in their entirety
1053     ;;; each time this id is translated.
1054     ;;;
1055     (defun lookup-login-name (uid)
1056     (multiple-value-bind (login-name foundp) (gethash uid *uid-hash-table*)
1057     (if foundp
1058     login-name
1059     (setf (gethash uid *uid-hash-table*)
1060     (get-group-or-user-name :user uid)))))
1061    
1062     (defvar *gid-hash-table* (make-hash-table)
1063     "Hash table for keeping track of gid's and group names.")
1064    
1065     ;;; LOOKUP-GROUP-NAME translates a group id into a group name. Previous
1066     ;;; lookups are cached in a hash table since groveling the group(s) files
1067     ;;; is somewhat expensive. The table may hold nil for id's that cannot
1068     ;;; be looked up since this means the files are searched in their entirety
1069     ;;; each time this id is translated.
1070     ;;;
1071     (defun lookup-group-name (gid)
1072     (multiple-value-bind (group-name foundp) (gethash gid *gid-hash-table*)
1073     (if foundp
1074     group-name
1075     (setf (gethash gid *gid-hash-table*)
1076     (get-group-or-user-name :group gid)))))
1077    
1078    
1079     ;;; GET-GROUP-OR-USER-NAME first tries "/etc/passwd" ("/etc/group") since it is
1080     ;;; a much smaller file, contains all the local id's, and most uses probably
1081     ;;; involve id's on machines one would login into. Then if necessary, we look
1082     ;;; in "/etc/passwds" ("/etc/groups") which is really long and has to be
1083     ;;; fetched over the net.
1084     ;;;
1085     (defun get-group-or-user-name (group-or-user id)
1086     "Returns the simple-string user or group name of the user whose uid or gid
1087     is id, or NIL if no such user or group exists. Group-or-user is either
1088     :group or :user."
1089     (let ((id-string (let ((*print-base* 10)) (prin1-to-string id))))
1090     (declare (simple-string id-string))
1091     (multiple-value-bind (file1 file2)
1092     (ecase group-or-user
1093     (:group (values "/etc/group" "/etc/groups"))
1094     (:user (values "/etc/passwd" "/etc/passwd")))
1095     (or (get-group-or-user-name-aux id-string file1)
1096     (get-group-or-user-name-aux id-string file2)))))
1097    
1098     (defun get-group-or-user-name-aux (id-string passwd-file)
1099     (with-open-file (stream passwd-file)
1100     (loop
1101     (let ((entry (read-line stream nil)))
1102     (unless entry (return nil))
1103     (let ((name-end (position #\: (the simple-string entry)
1104     :test #'char=)))
1105     (when name-end
1106     (let ((id-start (position #\: (the simple-string entry)
1107     :start (1+ name-end) :test #'char=)))
1108     (when id-start
1109     (incf id-start)
1110     (let ((id-end (position #\: (the simple-string entry)
1111     :start id-start :test #'char=)))
1112     (when (and id-end
1113     (string= id-string entry
1114     :start2 id-start :end2 id-end))
1115     (return (subseq entry 0 name-end))))))))))))
1116    
1117    
1118 wlott 1.4 ;;;; File completion.
1119 ram 1.1
1120 chiles 1.13 ;;; COMPLETE-FILE -- Public
1121     ;;;
1122 wlott 1.4 (defun complete-file (pathname &key (defaults *default-pathname-defaults*)
1123     ignore-types)
1124 wlott 1.22 (let ((files (directory (complete-file-directory-arg pathname defaults)
1125 ram 1.23 :check-for-subdirs nil
1126     :follow-links nil)))
1127 wlott 1.4 (cond ((null files)
1128     (values nil nil))
1129     ((null (cdr files))
1130     (values (merge-pathnames (file-namestring (car files))
1131     pathname)
1132     t))
1133     (t
1134     (let ((good-files
1135     (delete-if #'(lambda (pathname)
1136 wlott 1.16 (and (simple-string-p
1137     (pathname-type pathname))
1138 wlott 1.4 (member (pathname-type pathname)
1139     ignore-types
1140     :test #'string=)))
1141     files)))
1142     (cond ((null good-files))
1143     ((null (cdr good-files))
1144     (return-from complete-file
1145     (values (merge-pathnames (file-namestring
1146     (car good-files))
1147     pathname)
1148     t)))
1149     (t
1150     (setf files good-files)))
1151     (let ((common (file-namestring (car files))))
1152     (dolist (file (cdr files))
1153     (let ((name (file-namestring file)))
1154     (dotimes (i (min (length common) (length name))
1155     (when (< (length name) (length common))
1156     (setf common name)))
1157     (unless (char= (schar common i) (schar name i))
1158     (setf common (subseq common 0 i))
1159     (return)))))
1160     (values (merge-pathnames common pathname)
1161     nil)))))))
1162 ram 1.1
1163 chiles 1.13 ;;; COMPLETE-FILE-DIRECTORY-ARG -- Internal.
1164     ;;;
1165     (defun complete-file-directory-arg (pathname defaults)
1166 wlott 1.22 (let* ((pathname (merge-pathnames pathname (directory-namestring defaults)))
1167 wlott 1.16 (type (pathname-type pathname)))
1168     (flet ((append-multi-char-wild (thing)
1169     (etypecase thing
1170     (null :wild)
1171     (pattern
1172     (make-pattern (append (pattern-pieces thing)
1173     (list :multi-char-wild))))
1174     (simple-string
1175     (make-pattern (list thing :multi-char-wild))))))
1176     (if (or (null type) (eq type :unspecific))
1177     ;; There is no type.
1178     (make-pathname :defaults pathname
1179     :name (append-multi-char-wild (pathname-name pathname))
1180     :type :wild)
1181     ;; There already is a type, so just extend it.
1182     (make-pathname :defaults pathname
1183     :name (pathname-name pathname)
1184     :type (append-multi-char-wild (pathname-type pathname)))))))
1185 chiles 1.13
1186 wlott 1.4 ;;; Ambiguous-Files -- Public
1187 ram 1.1 ;;;
1188 wlott 1.16 (defun ambiguous-files (pathname
1189     &optional (defaults *default-pathname-defaults*))
1190 wlott 1.4 "Return a list of all files which are possible completions of Pathname.
1191 chiles 1.13 We look in the directory specified by Defaults as well as looking down
1192     the search list."
1193 wlott 1.16 (directory (complete-file-directory-arg pathname defaults)
1194 ram 1.23 :follow-links nil
1195 wlott 1.16 :check-for-subdirs nil))
1196 wlott 1.4
1197 wlott 1.16
1198 ram 1.1
1199     ;;; File-writable -- exported from extensions.
1200     ;;;
1201     ;;; Determines whether the single argument (which should be a pathname)
1202 pw 1.43.2.1 ;;; can be written by the current task.
1203 wlott 1.16 ;;;
1204 ram 1.1 (defun file-writable (name)
1205     "File-writable accepts a pathname and returns T if the current
1206     process can write it, and NIL otherwise."
1207 wlott 1.16 (let ((name (unix-namestring name nil)))
1208 wlott 1.17 (cond ((null name)
1209     nil)
1210 wlott 1.26 ((unix:unix-file-kind name)
1211     (values (unix:unix-access name unix:w_ok)))
1212 wlott 1.17 (t
1213     (values
1214 wlott 1.26 (unix:unix-access (subseq name
1215 wlott 1.17 0
1216     (or (position #\/ name :from-end t)
1217     0))
1218 wlott 1.26 (logior unix:w_ok unix:x_ok)))))))
1219 ram 1.1
1220    
1221     ;;; Pathname-Order -- Internal
1222     ;;;
1223     ;;; Predicate to order pathnames by. Goes by name.
1224     ;;;
1225     (defun pathname-order (x y)
1226     (let ((xn (%pathname-name x))
1227     (yn (%pathname-name y)))
1228     (if (and xn yn)
1229     (let ((res (string-lessp xn yn)))
1230     (cond ((not res) nil)
1231     ((= res (length (the simple-string xn))) t)
1232     ((= res (length (the simple-string yn))) nil)
1233     (t t)))
1234     xn)))
1235    
1236    
1237     ;;; Default-Directory -- Public
1238     ;;;
1239     (defun default-directory ()
1240     "Returns the pathname for the default directory. This is the place where
1241     a file will be written if no directory is specified. This may be changed
1242     with setf."
1243     (multiple-value-bind (gr dir-or-error)
1244 wlott 1.26 (unix:unix-current-directory)
1245 ram 1.1 (if gr
1246 wlott 1.16 (let ((*ignore-wildcards* t))
1247     (pathname (concatenate 'simple-string dir-or-error "/")))
1248 wlott 1.3 (error dir-or-error))))
1249 ram 1.1
1250     ;;; %Set-Default-Directory -- Internal
1251     ;;;
1252     (defun %set-default-directory (new-val)
1253 wlott 1.17 (let ((namestring (unix-namestring new-val t)))
1254     (unless namestring
1255     (error "~S doesn't exist." new-val))
1256     (multiple-value-bind (gr error)
1257 wlott 1.26 (unix:unix-chdir namestring)
1258 wlott 1.17 (if gr
1259     (setf (search-list "default:") (default-directory))
1260 wlott 1.26 (error (unix:get-unix-error-msg error))))
1261 wlott 1.17 new-val))
1262 wlott 1.16 ;;;
1263     (defsetf default-directory %set-default-directory)
1264    
1265     (defun filesys-init ()
1266     (setf *default-pathname-defaults*
1267     (%make-pathname *unix-host* nil nil nil nil :newest))
1268     (setf (search-list "default:") (default-directory))
1269     nil)
1270 dtc 1.42
1271     ;;; Ensure-Directories-Exist -- Public
1272     ;;;
1273     (defun ensure-directories-exist (pathspec &key verbose (mode #o777))
1274 pw 1.43.2.1 "Tests whether the directories containing the specified file
1275     actually exist, and attempts to create them if they do not.
1276     Portable programs should avoid using the :MODE keyword argument."
1277 dtc 1.43 (let* ((pathname (pathname pathspec))
1278     (pathname (if (logical-pathname-p pathname)
1279     (translate-logical-pathname pathname)
1280     pathname))
1281     (created-p nil))
1282     (when (wild-pathname-p pathname)
1283 dtc 1.43.2.2 (error 'simple-file-error
1284 pw 1.43.2.1 :format-control "Bad place for a wild pathname."
1285     :pathname pathspec))
1286     (enumerate-search-list (pathname pathname)
1287     (let ((dir (pathname-directory pathname)))
1288     (loop for i from 1 upto (length dir)
1289     do (let ((newpath (make-pathname
1290     :host (pathname-host pathname)
1291     :device (pathname-device pathname)
1292     :directory (subseq dir 0 i))))
1293     (unless (probe-file newpath)
1294     (let ((namestring (namestring newpath)))
1295     (when verbose
1296     (format *standard-output* "~&Creating directory: ~A~%"
1297     namestring))
1298     (unix:unix-mkdir namestring mode)
1299     (unless (probe-file namestring)
1300 dtc 1.43.2.2 (error 'simple-file-error
1301 pw 1.43.2.1 :pathname pathspec
1302     :format-control "Can't create directory ~A."
1303     :format-arguments (list namestring)))
1304     (setf created-p t)))))
1305     ;; Only the first path in a search-list is considered.
1306     (return (values pathname created-p))))))

  ViewVC Help
Powered by ViewVC 1.1.5