(scan-api): No longer include timestamp.
[bpt/guile.git] / ice-9 / ftw.scm
CommitLineData
df625172
TTN
1;;;; ftw.scm --- filesystem tree walk
2
3;;;; Copyright (C) 2002 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., 59 Temple Place, Suite 330,
18;;;; Boston, MA 02111-1307 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 acc
226 (loop (readdir dir-stream)
227 (if (or (string=? "." new) ;;; ignore
228 (string=? ".." new)) ;;; ignore
229 acc
230 (cons new acc)))))))
231
232(define (pathify . nodes)
233 (let loop ((nodes nodes)
234 (result ""))
235 (if (null? nodes)
236 (or (and (string=? "" result) "")
237 (substring result 1 (string-length result)))
238 (loop (cdr nodes) (string-append result "/" (car nodes))))))
239
240(define (abs? filename)
241 (char=? #\/ (string-ref filename 0)))
242
243(define (visited?-proc size)
244 (let ((visited (make-hash-table size)))
245 (lambda (s)
246 (and s (let ((ino (stat:ino s)))
247 (or (hash-ref visited ino)
248 (begin
249 (hash-set! visited ino #t)
250 #f)))))))
251
252(define (stat-dir-readable?-proc uid gid)
253 (let ((uid (getuid))
254 (gid (getgid)))
255 (lambda (s)
256 (let* ((perms (stat:perms s))
257 (perms-bit-set? (lambda (mask)
258 (not (= 0 (logand mask perms))))))
259 (or (and (= uid (stat:uid s))
260 (perms-bit-set? #o400))
261 (and (= gid (stat:gid s))
262 (perms-bit-set? #o040))
263 (perms-bit-set? #o004))))))
264
265(define (stat&flag-proc dir-readable? . control-flags)
266 (let* ((directory-flag (if (memq 'depth control-flags)
267 'directory-processed
268 'directory))
269 (stale-symlink-flag (if (memq 'nftw-style control-flags)
270 'stale-symlink
271 'symlink))
272 (physical? (memq 'physical control-flags))
273 (easy-flag (lambda (s)
274 (let ((type (stat:type s)))
275 (if (eq? 'directory type)
276 (if (dir-readable? s)
277 directory-flag
278 'directory-not-readable)
279 'regular)))))
280 (lambda (name)
281 (let ((s (false-if-exception (lstat name))))
282 (cond ((not s)
283 (values s 'invalid-stat))
284 ((eq? 'symlink (stat:type s))
285 (let ((s-follow (false-if-exception (stat name))))
286 (cond ((not s-follow)
287 (values s stale-symlink-flag))
288 ((and s-follow physical?)
289 (values s 'symlink))
290 ((and s-follow (not physical?))
291 (values s-follow (easy-flag s-follow))))))
292 (else (values s (easy-flag s))))))))
293
294(define (clean name)
295 (let ((last-char-index (1- (string-length name))))
296 (if (char=? #\/ (string-ref name last-char-index))
297 (substring name 0 last-char-index)
298 name)))
299
300(define (ftw filename proc . options)
301 (let* ((visited? (visited?-proc (cond ((memq 'hash-size options) => cadr)
302 (else 211))))
303 (stat&flag (stat&flag-proc
304 (stat-dir-readable?-proc (getuid) (getgid)))))
305 (letrec ((go (lambda (fullname)
306 (call-with-values (lambda () (stat&flag fullname))
307 (lambda (s flag)
308 (or (visited? s)
309 (let ((ret (proc fullname s flag))) ; callback
310 (or (eq? #t ret)
311 (throw 'ftw-early-exit ret))
312 (and (eq? 'directory flag)
313 (for-each
314 (lambda (child)
315 (go (pathify fullname child)))
316 (directory-files fullname)))
317 #t)))))))
318 (catch 'ftw-early-exit
319 (lambda () (go (clean filename)))
320 (lambda (key val) val)))))
321
322(define (nftw filename proc . control-flags)
323 (let* ((od (getcwd)) ; orig dir
324 (odev (let ((s (false-if-exception (lstat filename))))
325 (if s (stat:dev s) -1)))
326 (same-dev? (if (memq 'mount control-flags)
327 (lambda (s) (= (stat:dev s) odev))
328 (lambda (s) #t)))
329 (base-sub (lambda (name base) (substring name 0 base)))
330 (maybe-cd (if (memq 'chdir control-flags)
331 (if (abs? filename)
332 (lambda (fullname base)
333 (or (= 0 base)
334 (chdir (base-sub fullname base))))
335 (lambda (fullname base)
336 (chdir
337 (pathify od (base-sub fullname base)))))
338 (lambda (fullname base) #t)))
339 (maybe-cd-back (if (memq 'chdir control-flags)
340 (lambda () (chdir od))
341 (lambda () #t)))
342 (depth-first? (memq 'depth control-flags))
343 (visited? (visited?-proc
344 (cond ((memq 'hash-size control-flags) => cadr)
345 (else 211))))
346 (has-kids? (if depth-first?
347 (lambda (flag) (eq? flag 'directory-processed))
348 (lambda (flag) (eq? flag 'directory))))
349 (stat&flag (apply stat&flag-proc
350 (stat-dir-readable?-proc (getuid) (getgid))
351 (cons 'nftw-style control-flags))))
352 (letrec ((go (lambda (fullname base level)
353 (call-with-values (lambda () (stat&flag fullname))
354 (lambda (s flag)
355 (letrec ((self (lambda ()
356 (maybe-cd fullname base)
357 ;; the callback
358 (let ((ret (proc fullname s flag
359 base level)))
360 (maybe-cd-back)
361 (or (eq? #t ret)
362 (throw 'nftw-early-exit ret)))))
363 (kids (lambda ()
364 (and (has-kids? flag)
365 (for-each
366 (lambda (child)
367 (go (pathify fullname child)
368 (1+ (string-length
369 fullname))
370 (1+ level)))
371 (directory-files fullname))))))
372 (or (visited? s)
373 (not (same-dev? s))
374 (if depth-first?
375 (begin (kids) (self))
376 (begin (self) (kids)))))))
377 #t)))
378 (let ((ret (catch 'nftw-early-exit
379 (lambda () (go (clean filename) 0 0))
380 (lambda (key val) val))))
381 (chdir od)
382 ret))))
383
384;;; ftw.scm ends here