home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / guile / 1.6 / ice-9 / ftw.scm < prev    next >
Encoding:
Text File  |  2006-06-19  |  17.2 KB  |  387 lines

  1. ;;;; ftw.scm --- filesystem tree walk
  2.  
  3. ;;;;     Copyright (C) 2002, 2003 Free Software Foundation, Inc.
  4. ;;;;
  5. ;;;; This program is free software; you can redistribute it and/or modify
  6. ;;;; it under the terms of the GNU General Public License as published by
  7. ;;;; the Free Software Foundation; either version 2, or (at your option)
  8. ;;;; any later version.
  9. ;;;;
  10. ;;;; This program is distributed in the hope that it will be useful,
  11. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. ;;;; GNU General Public License for more details.
  14. ;;;;
  15. ;;;; You should have received a copy of the GNU General Public License
  16. ;;;; along with this software; see the file COPYING.  If not, write to
  17. ;;;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. ;;;; Boston, MA 02110-1301 USA
  19. ;;;;
  20. ;;;; As a special exception, the Free Software Foundation gives permission
  21. ;;;; for additional uses of the text contained in its release of GUILE.
  22. ;;;;
  23. ;;;; The exception is that, if you link the GUILE library with other files
  24. ;;;; to produce an executable, this does not by itself cause the
  25. ;;;; resulting executable to be covered by the GNU General Public License.
  26. ;;;; Your use of that executable is in no way restricted on account of
  27. ;;;; linking the GUILE library code into it.
  28. ;;;;
  29. ;;;; This exception does not however invalidate any other reasons why
  30. ;;;; the executable file might be covered by the GNU General Public License.
  31. ;;;;
  32. ;;;; This exception applies only to the code released by the
  33. ;;;; Free Software Foundation under the name GUILE.  If you copy
  34. ;;;; code from other Free Software Foundation releases into a copy of
  35. ;;;; GUILE, as the General Public License permits, the exception does
  36. ;;;; not apply to the code that you add in this way.  To avoid misleading
  37. ;;;; anyone as to the status of such modified files, you must delete
  38. ;;;; this exception notice from them.
  39. ;;;;
  40. ;;;; If you write modifications of your own for GUILE, it is your choice
  41. ;;;; whether to permit this exception to apply to your modifications.
  42. ;;;; If you do not wish that, delete this exception notice.
  43.  
  44. ;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
  45.  
  46. ;;; Commentary:
  47.  
  48. ;; Two procedures are provided: `ftw' and `nftw'.
  49.  
  50. ;; NOTE: The following description was adapted from the GNU libc info page, w/
  51. ;; significant modifications for a more "Schemey" interface.  Most noticible
  52. ;; are the inlining of `struct FTW *' parameters `base' and `level' and the
  53. ;; omission of `descriptors' parameters.
  54.  
  55. ;; * Types
  56. ;;
  57. ;;    The X/Open specification defines two procedures to process whole
  58. ;; hierarchies of directories and the contained files.  Both procedures
  59. ;; of this `ftw' family take as one of the arguments a callback procedure
  60. ;; which must be of these types.
  61. ;;
  62. ;;  - Data Type: __ftw_proc_t
  63. ;;           (lambda (filename statinfo flag) ...) => status
  64. ;;
  65. ;;      Type for callback procedures given to the `ftw' procedure.  The
  66. ;;      first parameter is a filename, the second parameter is the
  67. ;;      vector value as returned by calling `stat' on FILENAME.
  68. ;;
  69. ;;      The last parameter is a symbol giving more information about
  70. ;;      FILENAM.  It can have one of the following values:
  71. ;;
  72. ;;     `regular'
  73. ;;           The current item is a normal file or files which do not fit
  74. ;;           into one of the following categories.  This means
  75. ;;           especially special files, sockets etc.
  76. ;;
  77. ;;     `directory'
  78. ;;           The current item is a directory.
  79. ;;
  80. ;;     `invalid-stat'
  81. ;;           The `stat' call to fill the object pointed to by the second
  82. ;;           parameter failed and so the information is invalid.
  83. ;;
  84. ;;     `directory-not-readable'
  85. ;;           The item is a directory which cannot be read.
  86. ;;
  87. ;;     `symlink'
  88. ;;           The item is a symbolic link.  Since symbolic links are
  89. ;;           normally followed seeing this value in a `ftw' callback
  90. ;;           procedure means the referenced file does not exist.  The
  91. ;;           situation for `nftw' is different.
  92. ;;
  93. ;;  - Data Type: __nftw_proc_t
  94. ;;           (lambda (filename statinfo flag base level) ...) => status
  95. ;;
  96. ;;      The first three arguments have the same as for the
  97. ;;      `__ftw_proc_t' type.  A difference is that for the third
  98. ;;      argument some additional values are defined to allow finer
  99. ;;      differentiation:
  100. ;;
  101. ;;     `directory-processed'
  102. ;;           The current item is a directory and all subdirectories have
  103. ;;           already been visited and reported.  This flag is returned
  104. ;;           instead of `directory' if the `depth' flag is given to
  105. ;;           `nftw' (see below).
  106. ;;
  107. ;;     `stale-symlink'
  108. ;;           The current item is a stale symbolic link.  The file it
  109. ;;           points to does not exist.
  110. ;;
  111. ;;      The last two parameters are described below.  They contain
  112. ;;      information to help interpret FILENAME and give some information
  113. ;;      about current state of the traversal of the directory hierarchy.
  114. ;;
  115. ;;     `base'
  116. ;;           The value specifies which part of the filename argument
  117. ;;           given in the first parameter to the callback procedure is
  118. ;;           the name of the file.  The rest of the string is the path
  119. ;;           to locate the file.  This information is especially
  120. ;;           important if the `chdir' flag for `nftw' was set since then
  121. ;;           the current directory is the one the current item is found
  122. ;;           in.
  123. ;;
  124. ;;     `level'
  125. ;;           While processing the directory the procedures tracks how
  126. ;;           many directories have been examined to find the current
  127. ;;           item.  This nesting level is 0 for the item given starting
  128. ;;           item (file or directory) and is incremented by one for each
  129. ;;           entered directory.
  130. ;;
  131. ;; * Procedure: (ftw filename proc . options)
  132. ;;   Do a filesystem tree walk starting at FILENAME using PROC.
  133. ;;
  134. ;;   The `ftw' procedure calls the callback procedure given in the
  135. ;;   parameter PROC for every item which is found in the directory
  136. ;;   specified by FILENAME and all directories below.  The procedure
  137. ;;   follows symbolic links if necessary but does not process an item
  138. ;;   twice.  If FILENAME names no directory this item is the only
  139. ;;   object reported by calling the callback procedure.
  140. ;;
  141. ;;   The filename given to the callback procedure is constructed by
  142. ;;   taking the FILENAME parameter and appending the names of all
  143. ;;   passed directories and then the local file name.  So the
  144. ;;   callback procedure can use this parameter to access the file.
  145. ;;   Before the callback procedure is called `ftw' calls `stat' for
  146. ;;   this file and passes the information up to the callback
  147. ;;   procedure.  If this `stat' call was not successful the failure is
  148. ;;   indicated by setting the flag argument of the callback procedure
  149. ;;   to `invalid-stat'.  Otherwise the flag is set according to the
  150. ;;   description given in the description of `__ftw_proc_t' above.
  151. ;;
  152. ;;   The callback procedure is expected to return non-#f to indicate
  153. ;;   that no error occurred and the processing should be continued.
  154. ;;   If an error occurred in the callback procedure or the call to
  155. ;;   `ftw' shall return immediately the callback procedure can return
  156. ;;   #f.  This is the only correct way to stop the procedure.  The
  157. ;;   program must not use `throw' or similar techniques to continue
  158. ;;   the program in another place.  [Can we relax this? --ttn]
  159. ;;
  160. ;;   The return value of the `ftw' procedure is #t if all callback
  161. ;;   procedure calls returned #t and all actions performed by the
  162. ;;   `ftw' succeeded.  If some procedure call failed (other than
  163. ;;   calling `stat' on an item) the procedure returns #f.  If a
  164. ;;   callback procedure returns a value other than #t this value is
  165. ;;   returned as the return value of `ftw'.
  166. ;;
  167. ;; * Procedure: (nftw filename proc . control-flags)
  168. ;;   Do a new-style filesystem tree walk starting at FILENAME using PROC.
  169. ;;   Various optional CONTROL-FLAGS alter the default behavior.
  170. ;;
  171. ;;   The `nftw' procedures works like the `ftw' procedures.  It calls
  172. ;;   the callback procedure PROC for all items it finds in the
  173. ;;   directory FILENAME and below.
  174. ;;
  175. ;;   The differences are that for one the callback procedure is of a
  176. ;;   different type.  It takes also `base' and `level' parameters as
  177. ;;   described above.
  178. ;;
  179. ;;   The second difference is that `nftw' takes additional optional
  180. ;;   arguments which are zero or more of the following symbols:
  181. ;;
  182. ;;   physical'
  183. ;;        While traversing the directory symbolic links are not
  184. ;;        followed.  I.e., if this flag is given symbolic links are
  185. ;;        reported using the `symlink' value for the type parameter
  186. ;;        to the callback procedure.  Please note that if this flag is
  187. ;;        used the appearance of `symlink' in a callback procedure
  188. ;;        does not mean the referenced file does not exist.  To
  189. ;;        indicate this the extra value `stale-symlink' exists.
  190. ;;
  191. ;;   mount'
  192. ;;        The callback procedure is only called for items which are on
  193. ;;        the same mounted filesystem as the directory given as the
  194. ;;        FILENAME parameter to `nftw'.
  195. ;;
  196. ;;   chdir'
  197. ;;        If this flag is given the current working directory is
  198. ;;        changed to the directory containing the reported object
  199. ;;        before the callback procedure is called.
  200. ;;
  201. ;;   depth'
  202. ;;        If this option is given the procedure visits first all files
  203. ;;        and subdirectories before the callback procedure is called
  204. ;;        for the directory itself (depth-first processing).  This
  205. ;;        also means the type flag given to the callback procedure is
  206. ;;        `directory-processed' and not `directory'.
  207. ;;
  208. ;;   The return value is computed in the same way as for `ftw'.
  209. ;;   `nftw' returns #t if no failure occurred in `nftw' and all
  210. ;;   callback procedure call return values are also #t.  For internal
  211. ;;   errors such as memory problems the error `ftw-error' is thrown.
  212. ;;   If the return value of a callback invocation is not #t this
  213. ;;   very same value is returned.
  214.  
  215. ;;; Code:
  216.  
  217. (define-module (ice-9 ftw)
  218.   :export (ftw nftw))
  219.  
  220. (define (directory-files dir)
  221.   (let ((dir-stream (opendir dir)))
  222.     (let loop ((new (readdir dir-stream))
  223.                (acc '()))
  224.       (if (eof-object? new)
  225.           (begin
  226.         (closedir dir-stream)
  227.         acc)
  228.           (loop (readdir dir-stream)
  229.                 (if (or (string=? "."  new)             ;;; ignore
  230.                         (string=? ".." new))            ;;; ignore
  231.                     acc
  232.                     (cons new acc)))))))
  233.  
  234. (define (pathify . nodes)
  235.   (let loop ((nodes nodes)
  236.              (result ""))
  237.     (if (null? nodes)
  238.         (or (and (string=? "" result) "")
  239.             (substring result 1 (string-length result)))
  240.         (loop (cdr nodes) (string-append result "/" (car nodes))))))
  241.  
  242. (define (abs? filename)
  243.   (char=? #\/ (string-ref filename 0)))
  244.  
  245. (define (visited?-proc size)
  246.   (let ((visited (make-hash-table size)))
  247.     (lambda (s)
  248.       (and s (let ((ino (stat:ino s)))
  249.                (or (hash-ref visited ino)
  250.                    (begin
  251.                      (hash-set! visited ino #t)
  252.                      #f)))))))
  253.  
  254. (define (stat-dir-readable?-proc uid gid)
  255.   (let ((uid (getuid))
  256.         (gid (getgid)))
  257.     (lambda (s)
  258.       (let* ((perms (stat:perms s))
  259.              (perms-bit-set? (lambda (mask)
  260.                                (not (= 0 (logand mask perms))))))
  261.         (or (and (= uid (stat:uid s))
  262.                  (perms-bit-set? #o400))
  263.             (and (= gid (stat:gid s))
  264.                  (perms-bit-set? #o040))
  265.             (perms-bit-set? #o004))))))
  266.  
  267. (define (stat&flag-proc dir-readable? . control-flags)
  268.   (let* ((directory-flag (if (memq 'depth control-flags)
  269.                              'directory-processed
  270.                              'directory))
  271.          (stale-symlink-flag (if (memq 'nftw-style control-flags)
  272.                                  'stale-symlink
  273.                                  'symlink))
  274.          (physical? (memq 'physical control-flags))
  275.          (easy-flag (lambda (s)
  276.                       (let ((type (stat:type s)))
  277.                         (if (eq? 'directory type)
  278.                             (if (dir-readable? s)
  279.                                 directory-flag
  280.                                 'directory-not-readable)
  281.                             'regular)))))
  282.     (lambda (name)
  283.       (let ((s (false-if-exception (lstat name))))
  284.         (cond ((not s)
  285.                (values s 'invalid-stat))
  286.               ((eq? 'symlink (stat:type s))
  287.                (let ((s-follow (false-if-exception (stat name))))
  288.                  (cond ((not s-follow)
  289.                         (values s stale-symlink-flag))
  290.                        ((and s-follow physical?)
  291.                         (values s 'symlink))
  292.                        ((and s-follow (not physical?))
  293.                         (values s-follow (easy-flag s-follow))))))
  294.               (else (values s (easy-flag s))))))))
  295.  
  296. (define (clean name)
  297.   (let ((last-char-index (1- (string-length name))))
  298.     (if (char=? #\/ (string-ref name last-char-index))
  299.         (substring name 0 last-char-index)
  300.         name)))
  301.  
  302. (define (ftw filename proc . options)
  303.   (let* ((visited? (visited?-proc (cond ((memq 'hash-size options) => cadr)
  304.                                         (else 211))))
  305.          (stat&flag (stat&flag-proc
  306.                      (stat-dir-readable?-proc (getuid) (getgid)))))
  307.     (letrec ((go (lambda (fullname)
  308.                    (call-with-values (lambda () (stat&flag fullname))
  309.                      (lambda (s flag)
  310.                        (or (visited? s)
  311.                            (let ((ret (proc fullname s flag))) ; callback
  312.                              (or (eq? #t ret)
  313.                                  (throw 'ftw-early-exit ret))
  314.                              (and (eq? 'directory flag)
  315.                                   (for-each
  316.                                    (lambda (child)
  317.                                      (go (pathify fullname child)))
  318.                                    (directory-files fullname)))
  319.                              #t)))))))
  320.       (catch 'ftw-early-exit
  321.              (lambda () (go (clean filename)))
  322.              (lambda (key val) val)))))
  323.  
  324. (define (nftw filename proc . control-flags)
  325.   (let* ((od (getcwd))                  ; orig dir
  326.          (odev (let ((s (false-if-exception (lstat filename))))
  327.                  (if s (stat:dev s) -1)))
  328.          (same-dev? (if (memq 'mount control-flags)
  329.                         (lambda (s) (= (stat:dev s) odev))
  330.                         (lambda (s) #t)))
  331.          (base-sub (lambda (name base) (substring name 0 base)))
  332.          (maybe-cd (if (memq 'chdir control-flags)
  333.                        (if (abs? filename)
  334.                            (lambda (fullname base)
  335.                              (or (= 0 base)
  336.                                  (chdir (base-sub fullname base))))
  337.                            (lambda (fullname base)
  338.                              (chdir
  339.                               (pathify od (base-sub fullname base)))))
  340.                        (lambda (fullname base) #t)))
  341.          (maybe-cd-back (if (memq 'chdir control-flags)
  342.                             (lambda () (chdir od))
  343.                             (lambda () #t)))
  344.          (depth-first? (memq 'depth control-flags))
  345.          (visited? (visited?-proc
  346.                     (cond ((memq 'hash-size control-flags) => cadr)
  347.                           (else 211))))
  348.          (has-kids? (if depth-first?
  349.                         (lambda (flag) (eq? flag 'directory-processed))
  350.                         (lambda (flag) (eq? flag 'directory))))
  351.          (stat&flag (apply stat&flag-proc
  352.                            (stat-dir-readable?-proc (getuid) (getgid))
  353.                            (cons 'nftw-style control-flags))))
  354.     (letrec ((go (lambda (fullname base level)
  355.                    (call-with-values (lambda () (stat&flag fullname))
  356.                      (lambda (s flag)
  357.                        (letrec ((self (lambda ()
  358.                                         (maybe-cd fullname base)
  359.                                         ;; the callback
  360.                                         (let ((ret (proc fullname s flag
  361.                                                          base level)))
  362.                                           (maybe-cd-back)
  363.                                           (or (eq? #t ret)
  364.                                               (throw 'nftw-early-exit ret)))))
  365.                                 (kids (lambda ()
  366.                                         (and (has-kids? flag)
  367.                                              (for-each
  368.                                               (lambda (child)
  369.                                                 (go (pathify fullname child)
  370.                                                     (1+ (string-length
  371.                                                          fullname))
  372.                                                     (1+ level)))
  373.                                               (directory-files fullname))))))
  374.                          (or (visited? s)
  375.                              (not (same-dev? s))
  376.                              (if depth-first?
  377.                                  (begin (kids) (self))
  378.                                  (begin (self) (kids)))))))
  379.                    #t)))
  380.       (let ((ret (catch 'nftw-early-exit
  381.                         (lambda () (go (clean filename) 0 0))
  382.                         (lambda (key val) val))))
  383.         (chdir od)
  384.         ret))))
  385.  
  386. ;;; ftw.scm ends here
  387.