X-Git-Url: http://repo.macrolet.net/gitweb/?a=blobdiff_plain;f=src%2Fcode%2Ffilesys.lisp;h=b14adc882f96c0801fbd25dfabb1da1a7aface51;hb=8ea7b1a452fc87f91273c96bead8aa862bbc8b98;hp=e5bda9e30d291d8bda76f07e7df9c70700767c01;hpb=0c44a68572089d61cf6c7dd07a2ac724b35a112e;p=sbcl.git diff --git a/src/code/filesys.lisp b/src/code/filesys.lisp index e5bda9e..b14adc8 100644 --- a/src/code/filesys.lisp +++ b/src/code/filesys.lisp @@ -296,6 +296,22 @@ (sb!unix:unix-stat filename) (declare (ignore ino nlink gid rdev size atime #!+win32 uid)) + #!+win32 + ;; On win32, stat regards UNC pathnames and device names as + ;; nonexisting, so we check once more with the native API. + (unless existsp + (setf existsp + (let ((handle (sb!win32:create-file + filename 0 0 nil + sb!win32:file-open-existing + 0 0))) + (when (/= -1 handle) + (setf mode + (or mode + (if (logbitp 4 + (sb!win32:get-file-attributes filename)) + sb!unix:s-ifdir 0))) + (progn (sb!win32:close-handle handle) t))))) (if existsp (case query-for (:existence (nth-value @@ -446,9 +462,11 @@ or if PATHSPEC is a wild pathname." (defun rename-file (file new-name) #!+sb-doc "Rename FILE to have the specified NEW-NAME. If FILE is a stream open to a - file, then the associated file is renamed." - (let* ((original (truename file)) - (original-namestring (native-namestring original :as-file t)) +file, then the associated file is renamed." + (let* ((original (merge-pathnames file (sane-default-pathname-defaults))) + (old-truename (truename original)) + (original-namestring (native-namestring (physicalize-pathname original) + :as-file t)) (new-name (merge-pathnames new-name original)) (new-namestring (native-namestring (physicalize-pathname new-name) :as-file t))) @@ -467,7 +485,7 @@ or if PATHSPEC is a wild pathname." :format-arguments (list original new-name (strerror error)))) (when (streamp file) (file-name file new-name)) - (values new-name original (truename new-name))))) + (values new-name old-truename (truename new-name))))) (defun delete-file (file) #!+sb-doc @@ -477,16 +495,79 @@ If FILE is a stream, on Windows the stream is closed immediately. On Unix plaforms the stream remains open, allowing IO to continue: the OS resources associated with the deleted file remain available till the stream is closed as per standard Unix unlink() behaviour." - (let* ((pathname (pathname file)) + (let* ((pathname (translate-logical-pathname + (merge-pathnames file (sane-default-pathname-defaults)))) (namestring (native-namestring pathname :as-file t))) - (truename file) ; for error-checking side-effect #!+win32 (when (streamp file) (close file)) (multiple-value-bind (res err) (sb!unix:unix-unlink namestring) (unless res - (simple-file-perror "couldn't delete ~A" namestring err)))) + (simple-file-perror "Couldn't delete file ~A" namestring err)))) t) + +(defun directorize-pathname (pathname) + (if (or (pathname-name pathname) + (pathname-type pathname)) + (make-pathname :directory (append (pathname-directory pathname) + (list (file-namestring pathname))) + :host (pathname-host pathname) + :device (pathname-device pathname)) + pathname)) + +(defun delete-directory (pathspec &key recursive) + "Deletes the directory designated by PATHSPEC (a pathname designator). +Returns the truename of the directory deleted. + +If RECURSIVE is false \(the default), signals an error unless the directory is +empty. If RECURSIVE is true, first deletes all files and subdirectories. If +RECURSIVE is true and the directory contains symbolic links, the links are +deleted, not the files and directories they point to. + +Signals an error if PATHSPEC designates a file or a symbolic link instead of a +directory, or if the directory could not be deleted for any reason. + +Both + + \(DELETE-DIRECTORY \"/tmp/foo\") + \(DELETE-DIRECTORY \"/tmp/foo/\") + +delete the \"foo\" subdirectory of \"/tmp\", or signal an error if it does not +exist or if is a file or a symbolic link." + (declare (type pathname-designator pathspec)) + (let ((physical (directorize-pathname + (physicalize-pathname + (merge-pathnames + pathspec (sane-default-pathname-defaults)))))) + (labels ((recurse-merged (dir) + (lambda (sub) + (recurse (merge-pathnames sub dir)))) + (delete-merged (dir) + (lambda (file) + (delete-file (merge-pathnames file dir)))) + (recurse (dir) + (map-directory (recurse-merged dir) dir + :files nil + :directories t + :classify-symlinks nil) + (map-directory (delete-merged dir) dir + :files t + :directories nil + :classify-symlinks nil) + (delete-dir dir)) + (delete-dir (dir) + (let* ((namestring (native-namestring dir :as-file t)) + (res (alien-funcall (extern-alien #!-win32 "rmdir" + #!+win32 "_rmdir" + (function int c-string)) + namestring))) + (if (minusp res) + (simple-file-perror "Couldn't delete directory ~A" + namestring (get-errno)) + dir)))) + (if recursive + (recurse physical) + (delete-dir physical))))) (defun sbcl-homedir-pathname () (let ((sbcl-home (posix-getenv "SBCL_HOME"))) @@ -498,30 +579,32 @@ per standard Unix unlink() behaviour." *default-pathname-defaults* :as-directory t)))) +(defun user-homedir-namestring (&optional username) + (if username + (sb!unix:user-homedir username) + (let ((env-home (posix-getenv "HOME"))) + (if (and env-home (not (string= env-home ""))) + env-home + #!-win32 + (sb!unix:uid-homedir (sb!unix:unix-getuid)))))) + ;;; (This is an ANSI Common Lisp function.) (defun user-homedir-pathname (&optional host) #!+sb-doc "Return the home directory of the user as a pathname. If the HOME environment variable has been specified, the directory it designates is returned; otherwise obtains the home directory from the operating -system." +system. HOST argument is ignored by SBCL." (declare (ignore host)) - (let ((env-home (posix-getenv "HOME"))) - (values - (parse-native-namestring - (if (and env-home (not (string= env-home ""))) - env-home - #!-win32 - (sb!unix:uid-homedir (sb!unix:unix-getuid)) - #!+win32 - ;; Needs to bypass PARSE-NATIVE-NAMESTRING & ENSURE-TRAILING-SLASH - ;; What?! -- RMK, 2007-12-31 - (return-from user-homedir-pathname - (sb!win32::get-folder-pathname sb!win32::csidl_profile))) - #!-win32 sb!impl::*unix-host* - #!+win32 sb!impl::*win32-host* - *default-pathname-defaults* - :as-directory t)))) + (values + (parse-native-namestring + (or (user-homedir-namestring) + #!+win32 + (sb!win32::get-folder-namestring sb!win32::csidl_profile)) + #!-win32 sb!impl::*unix-host* + #!+win32 sb!impl::*win32-host* + *default-pathname-defaults* + :as-directory t))) ;;;; DIRECTORY @@ -676,7 +759,8 @@ matching filenames." ;;; This is our core directory access interface that we use to implement ;;; DIRECTORY. -(defun map-directory (function directory &key (files t) (directories t) (errorp t)) +(defun map-directory (function directory &key (files t) (directories t) + (classify-symlinks t) (errorp t)) #!+sb-doc "Map over entries in DIRECTORY. Keyword arguments specify which entries to map over, and how: @@ -691,16 +775,18 @@ map over, and how: as a file in DIRECTORY. Otherwise the pathname used is a directory pathname. Defaults to T. + :CLASSIFY-SYMLINKS + If true, the decision to call FUNCTION with the pathname of a symbolic link + depends on the resolution of the link: if it points to a directory, it is + considered a directory entry, otherwise a file entry. If false, all + symbolic links are considered file entries. In both cases the pathname used + for the symbolic link is not fully resolved, but names it as an immediate + child of DIRECTORY. Defaults to T. + :ERRORP If true, signal an error if DIRECTORY does not exist, cannot be read, etc. Defaults to T. -On platforms supporting symbolic links the decision to call FUNCTION with its -pathname depends on the resolution of the link: if it points to a directory, -it is considered a directory entry. Whether it is considered a file or a -directory, the provided pathname is not fully resolved, but rather names the -symbolic link as an immediate child of DIRECTORY. - Experimental: interface subject to change." (declare (pathname-designator directory)) (let* ((fun (%coerce-callable-to-fun function)) @@ -723,31 +809,34 @@ Experimental: interface subject to change." :as-directory (and dirp (not as-files))) physical)))) (with-native-directory-iterator (next dirname :errorp errorp) - (loop for name = (next) - while name - do (let* ((full (concatenate 'string dirname name)) - (kind (native-file-kind full))) - (when kind - (case kind - (:directory - (when directories - (map-it name t))) - (:symlink - (let* ((tmpname (merge-pathnames - (parse-native-namestring - name nil physical :as-directory nil) - physical)) - (truename (query-file-system tmpname :truename nil))) - (if (or (not truename) - (or (pathname-name truename) (pathname-type truename))) - (when files - (funcall fun tmpname)) - (when directories - (map-it name t))))) - (t - ;; Anything else parses as a file. - (when files - (map-it name nil))))))))))) + (loop for name = (next) + while name + do (let* ((full (concatenate 'string dirname name)) + (kind (native-file-kind full))) + (when kind + (case kind + (:directory + (when directories + (map-it name t))) + (:symlink + (if classify-symlinks + (let* ((tmpname (merge-pathnames + (parse-native-namestring + name nil physical :as-directory nil) + physical)) + (truename (query-file-system tmpname :truename nil))) + (if (or (not truename) + (or (pathname-name truename) (pathname-type truename))) + (when files + (funcall fun tmpname)) + (when directories + (map-it name t)))) + (when files + (map-it name nil)))) + (t + ;; Anything else parses as a file. + (when files + (map-it name nil))))))))))) ;;; Part of DIRECTORY: implements matching the directory spec. Calls FUNCTION ;;; with all DIRECTORIES that match the directory portion of PATHSPEC. @@ -939,7 +1028,7 @@ Experimental: interface subject to change." ((or (null one) (eq one :unspecific)) two) ((or (null two) (eq two :unspecific)) one) ((string= one two) one) - (t nil))) + (t (return-from pathname-intersections nil)))) (intersect-directory (one two) (aver (typep one '(or null (member :wild :unspecific) list))) (aver (typep two '(or null (member :wild :unspecific) list))) @@ -1029,6 +1118,12 @@ Experimental: interface subject to change." (car one) (car two)) x)) (intersect-directory-helper (cdr one) (cdr two))))))))) + +(defun directory-pathname-p (pathname) + (and (pathnamep pathname) + (null (pathname-name pathname)) + (null (pathname-type pathname)))) + (defun ensure-directories-exist (pathspec &key verbose (mode #o777)) #!+sb-doc "Test whether the directories containing the specified file @@ -1041,37 +1136,48 @@ Experimental: interface subject to change." (error 'simple-file-error :format-control "bad place for a wild pathname" :pathname pathspec)) - (let ((dir (pathname-directory pathname))) + (let* ((dir (pathname-directory pathname)) + ;; *d-p-d* can have name and type components which would prevent + ;; PROBE-FILE below from working + (*default-pathname-defaults* + (make-pathname :directory dir :device (pathname-device pathname)))) (loop for i from 1 upto (length dir) - do (let ((newpath (make-pathname - :host (pathname-host pathname) - :device (pathname-device pathname) - :directory (subseq dir 0 i)))) - (unless (probe-file newpath) - (let ((namestring (coerce (native-namestring newpath) - 'string))) - (when verbose - (format *standard-output* - "~&creating directory: ~A~%" - namestring)) - (sb!unix:unix-mkdir namestring mode) - (unless (probe-file newpath) - (restart-case (error - 'simple-file-error - :pathname pathspec - :format-control - "can't create directory ~A" - :format-arguments (list namestring)) - (retry () - :report "Retry directory creation." - (ensure-directories-exist - pathspec - :verbose verbose :mode mode)) - (continue () - :report - "Continue as if directory creation was successful." - nil))) - (setf created-p t))))) + do + (let* ((newpath (make-pathname + :host (pathname-host pathname) + :device (pathname-device pathname) + :directory (subseq dir 0 i))) + (probed (probe-file newpath))) + (unless (directory-pathname-p probed) + (let ((namestring (coerce (native-namestring newpath) + 'string))) + (when verbose + (format *standard-output* + "~&creating directory: ~A~%" + namestring)) + (sb!unix:unix-mkdir namestring mode) + (unless (directory-pathname-p (probe-file newpath)) + (restart-case + (error + 'simple-file-error + :pathname pathspec + :format-control + (if (and probed + (not (directory-pathname-p probed))) + "Can't create directory ~A,~ + ~%a file with the same name already exists." + "Can't create directory ~A") + :format-arguments (list namestring)) + (retry () + :report "Retry directory creation." + (ensure-directories-exist + pathspec + :verbose verbose :mode mode)) + (continue () + :report + "Continue as if directory creation was successful." + nil))) + (setf created-p t))))) (values pathspec created-p)))) (/show0 "filesys.lisp 1000")