ftw.scm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. ;;;; ftw.scm --- file system tree walk
  2. ;;;; Copyright (C) 2002, 2003, 2006, 2011, 2012, 2014, 2016, 2018 Free Software Foundation, Inc.
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 3 of the License, or (at your option) any later version.
  8. ;;;;
  9. ;;;; This library is distributed in the hope that it will be useful,
  10. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;;; Lesser General Public License for more details.
  13. ;;;;
  14. ;;;; You should have received a copy of the GNU Lesser General Public
  15. ;;;; License along with this library; if not, write to the Free Software
  16. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
  18. ;;; Commentary:
  19. ;; Two procedures are provided: `ftw' and `nftw'.
  20. ;; NOTE: The following description was adapted from the GNU libc info page, w/
  21. ;; significant modifications for a more "Schemey" interface. Most noticible
  22. ;; are the inlining of `struct FTW *' parameters `base' and `level' and the
  23. ;; omission of `descriptors' parameters.
  24. ;; * Types
  25. ;;
  26. ;; The X/Open specification defines two procedures to process whole
  27. ;; hierarchies of directories and the contained files. Both procedures
  28. ;; of this `ftw' family take as one of the arguments a callback procedure
  29. ;; which must be of these types.
  30. ;;
  31. ;; - Data Type: __ftw_proc_t
  32. ;; (lambda (filename statinfo flag) ...) => status
  33. ;;
  34. ;; Type for callback procedures given to the `ftw' procedure. The
  35. ;; first parameter is a filename, the second parameter is the
  36. ;; vector value as returned by calling `stat' on FILENAME.
  37. ;;
  38. ;; The last parameter is a symbol giving more information about
  39. ;; FILENAM. It can have one of the following values:
  40. ;;
  41. ;; `regular'
  42. ;; The current item is a normal file or files which do not fit
  43. ;; into one of the following categories. This means
  44. ;; especially special files, sockets etc.
  45. ;;
  46. ;; `directory'
  47. ;; The current item is a directory.
  48. ;;
  49. ;; `invalid-stat'
  50. ;; The `stat' call to fill the object pointed to by the second
  51. ;; parameter failed and so the information is invalid.
  52. ;;
  53. ;; `directory-not-readable'
  54. ;; The item is a directory which cannot be read.
  55. ;;
  56. ;; `symlink'
  57. ;; The item is a symbolic link. Since symbolic links are
  58. ;; normally followed seeing this value in a `ftw' callback
  59. ;; procedure means the referenced file does not exist. The
  60. ;; situation for `nftw' is different.
  61. ;;
  62. ;; - Data Type: __nftw_proc_t
  63. ;; (lambda (filename statinfo flag base level) ...) => status
  64. ;;
  65. ;; The first three arguments have the same as for the
  66. ;; `__ftw_proc_t' type. A difference is that for the third
  67. ;; argument some additional values are defined to allow finer
  68. ;; differentiation:
  69. ;;
  70. ;; `directory-processed'
  71. ;; The current item is a directory and all subdirectories have
  72. ;; already been visited and reported. This flag is returned
  73. ;; instead of `directory' if the `depth' flag is given to
  74. ;; `nftw' (see below).
  75. ;;
  76. ;; `stale-symlink'
  77. ;; The current item is a stale symbolic link. The file it
  78. ;; points to does not exist.
  79. ;;
  80. ;; The last two parameters are described below. They contain
  81. ;; information to help interpret FILENAME and give some information
  82. ;; about current state of the traversal of the directory hierarchy.
  83. ;;
  84. ;; `base'
  85. ;; The value specifies which part of the filename argument
  86. ;; given in the first parameter to the callback procedure is
  87. ;; the name of the file. The rest of the string is the path
  88. ;; to locate the file. This information is especially
  89. ;; important if the `chdir' flag for `nftw' was set since then
  90. ;; the current directory is the one the current item is found
  91. ;; in.
  92. ;;
  93. ;; `level'
  94. ;; While processing the directory the procedures tracks how
  95. ;; many directories have been examined to find the current
  96. ;; item. This nesting level is 0 for the item given starting
  97. ;; item (file or directory) and is incremented by one for each
  98. ;; entered directory.
  99. ;;
  100. ;; * Procedure: (ftw filename proc . options)
  101. ;; Do a file system tree walk starting at FILENAME using PROC.
  102. ;;
  103. ;; The `ftw' procedure calls the callback procedure given in the
  104. ;; parameter PROC for every item which is found in the directory
  105. ;; specified by FILENAME and all directories below. The procedure
  106. ;; follows symbolic links if necessary but does not process an item
  107. ;; twice. If FILENAME names no directory this item is the only
  108. ;; object reported by calling the callback procedure.
  109. ;;
  110. ;; The filename given to the callback procedure is constructed by
  111. ;; taking the FILENAME parameter and appending the names of all
  112. ;; passed directories and then the local file name. So the
  113. ;; callback procedure can use this parameter to access the file.
  114. ;; Before the callback procedure is called `ftw' calls `stat' for
  115. ;; this file and passes the information up to the callback
  116. ;; procedure. If this `stat' call was not successful the failure is
  117. ;; indicated by setting the flag argument of the callback procedure
  118. ;; to `invalid-stat'. Otherwise the flag is set according to the
  119. ;; description given in the description of `__ftw_proc_t' above.
  120. ;;
  121. ;; The callback procedure is expected to return non-#f to indicate
  122. ;; that no error occurred and the processing should be continued.
  123. ;; If an error occurred in the callback procedure or the call to
  124. ;; `ftw' shall return immediately the callback procedure can return
  125. ;; #f. This is the only correct way to stop the procedure. The
  126. ;; program must not use `throw' or similar techniques to continue
  127. ;; the program in another place. [Can we relax this? --ttn]
  128. ;;
  129. ;; The return value of the `ftw' procedure is #t if all callback
  130. ;; procedure calls returned #t and all actions performed by the
  131. ;; `ftw' succeeded. If some procedure call failed (other than
  132. ;; calling `stat' on an item) the procedure returns #f. If a
  133. ;; callback procedure returns a value other than #t this value is
  134. ;; returned as the return value of `ftw'.
  135. ;;
  136. ;; * Procedure: (nftw filename proc . control-flags)
  137. ;; Do a new-style file system tree walk starting at FILENAME using PROC.
  138. ;; Various optional CONTROL-FLAGS alter the default behavior.
  139. ;;
  140. ;; The `nftw' procedures works like the `ftw' procedures. It calls
  141. ;; the callback procedure PROC for all items it finds in the
  142. ;; directory FILENAME and below.
  143. ;;
  144. ;; The differences are that for one the callback procedure is of a
  145. ;; different type. It takes also `base' and `level' parameters as
  146. ;; described above.
  147. ;;
  148. ;; The second difference is that `nftw' takes additional optional
  149. ;; arguments which are zero or more of the following symbols:
  150. ;;
  151. ;; physical'
  152. ;; While traversing the directory symbolic links are not
  153. ;; followed. I.e., if this flag is given symbolic links are
  154. ;; reported using the `symlink' value for the type parameter
  155. ;; to the callback procedure. Please note that if this flag is
  156. ;; used the appearance of `symlink' in a callback procedure
  157. ;; does not mean the referenced file does not exist. To
  158. ;; indicate this the extra value `stale-symlink' exists.
  159. ;;
  160. ;; mount'
  161. ;; The callback procedure is only called for items which are on
  162. ;; the same mounted file system as the directory given as the
  163. ;; FILENAME parameter to `nftw'.
  164. ;;
  165. ;; chdir'
  166. ;; If this flag is given the current working directory is
  167. ;; changed to the directory containing the reported object
  168. ;; before the callback procedure is called.
  169. ;;
  170. ;; depth'
  171. ;; If this option is given the procedure visits first all files
  172. ;; and subdirectories before the callback procedure is called
  173. ;; for the directory itself (depth-first processing). This
  174. ;; also means the type flag given to the callback procedure is
  175. ;; `directory-processed' and not `directory'.
  176. ;;
  177. ;; The return value is computed in the same way as for `ftw'.
  178. ;; `nftw' returns #t if no failure occurred in `nftw' and all
  179. ;; callback procedure call return values are also #t. For internal
  180. ;; errors such as memory problems the error `ftw-error' is thrown.
  181. ;; If the return value of a callback invocation is not #t this
  182. ;; very same value is returned.
  183. ;;; Code:
  184. (define-module (ice-9 ftw)
  185. #:use-module (ice-9 match)
  186. #:use-module (ice-9 vlist)
  187. #:use-module (srfi srfi-1)
  188. #:autoload (ice-9 i18n) (string-locale<?)
  189. #:export (ftw nftw
  190. file-system-fold
  191. file-system-tree
  192. scandir))
  193. (define-macro (getuid-or-false)
  194. (if (defined? 'getuid)
  195. (getuid)
  196. #f))
  197. (define-macro (getgid-or-false)
  198. (if (defined? 'getgid)
  199. (getgid)
  200. #f))
  201. (define (directory-files dir)
  202. (let ((dir-stream (opendir dir)))
  203. (let loop ((new (readdir dir-stream))
  204. (acc '()))
  205. (if (eof-object? new)
  206. (begin
  207. (closedir dir-stream)
  208. acc)
  209. (loop (readdir dir-stream)
  210. (if (or (string=? "." new) ;;; ignore
  211. (string=? ".." new)) ;;; ignore
  212. acc
  213. (cons new acc)))))))
  214. (define (pathify . nodes)
  215. (let loop ((nodes nodes)
  216. (result ""))
  217. (if (null? nodes)
  218. (or (and (string=? "" result) "")
  219. (substring result 1 (string-length result)))
  220. (loop (cdr nodes) (string-append result "/" (car nodes))))))
  221. ;; `visited?-proc' returns a test procedure VISITED? which when called as
  222. ;; (VISITED? stat-obj) returns #f the first time a distinct file is seen,
  223. ;; then #t on any subsequent sighting of it.
  224. ;;
  225. ;; stat:dev and stat:ino together uniquely identify a file (see "Attribute
  226. ;; Meanings" in the glibc manual). Often there'll be just one dev, and
  227. ;; usually there's just a handful mounted, so the strategy here is a small
  228. ;; hash table indexed by dev, containing hash tables indexed by ino.
  229. ;;
  230. ;; On some file systems, stat:ino is always zero. In that case,
  231. ;; a string hash of the full file name is used.
  232. ;;
  233. ;; It'd be possible to make a pair (dev . ino) and use that as the key to a
  234. ;; single hash table. It'd use an extra pair for every file visited, but
  235. ;; might be a little faster if it meant less scheme code.
  236. ;;
  237. (define (visited?-proc size)
  238. (let ((dev-hash (make-hash-table 7)))
  239. (lambda (s name)
  240. (and s
  241. (let* ((ino-hash (hashv-ref dev-hash (stat:dev s)))
  242. (%ino (stat:ino s))
  243. (ino (if (= 0 %ino)
  244. (string-hash name)
  245. %ino)))
  246. (or ino-hash
  247. (begin
  248. (set! ino-hash (make-hash-table size))
  249. (hashv-set! dev-hash (stat:dev s) ino-hash)))
  250. (or (hashv-ref ino-hash ino)
  251. (begin
  252. (hashv-set! ino-hash ino #t)
  253. #f)))))))
  254. (define (stat-dir-readable?-proc uid gid)
  255. (lambda (s)
  256. (let* ((perms (stat:perms s))
  257. (perms-bit-set? (lambda (mask)
  258. (logtest mask perms))))
  259. (or (equal? uid 0)
  260. (and (equal? uid (stat:uid s))
  261. (perms-bit-set? #o400))
  262. (and (equal? gid (stat:gid s))
  263. (perms-bit-set? #o040))
  264. (perms-bit-set? #o004)))))
  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. (define (clean name)
  294. (let ((end (- (string-length name) 1)))
  295. (if (and (positive? end) (char=? #\/ (string-ref name end)))
  296. (substring name 0 end)
  297. name)))
  298. (define (ftw filename proc . options)
  299. (let* ((visited? (visited?-proc (cond ((memq 'hash-size options) => cadr)
  300. (else 211))))
  301. (stat&flag (stat&flag-proc
  302. (stat-dir-readable?-proc (getuid-or-false)
  303. (getgid-or-false)))))
  304. (letrec ((go (lambda (fullname)
  305. (call-with-values (lambda () (stat&flag fullname))
  306. (lambda (s flag)
  307. (or (visited? s fullname)
  308. (let ((ret (proc fullname s flag))) ; callback
  309. (or (eq? #t ret)
  310. (throw 'ftw-early-exit ret))
  311. (and (eq? 'directory flag)
  312. (for-each
  313. (lambda (child)
  314. (go (pathify fullname child)))
  315. (directory-files fullname)))
  316. #t)))))))
  317. (catch 'ftw-early-exit
  318. (lambda () (go (clean filename)))
  319. (lambda (key val) val)))))
  320. (define (nftw filename proc . control-flags)
  321. (let* ((od (getcwd)) ; orig dir
  322. (odev (let ((s (false-if-exception (lstat filename))))
  323. (if s (stat:dev s) -1)))
  324. (same-dev? (if (memq 'mount control-flags)
  325. (lambda (s) (= (stat:dev s) odev))
  326. (lambda (s) #t)))
  327. (base-sub (lambda (name base) (substring name 0 base)))
  328. (maybe-cd (if (memq 'chdir control-flags)
  329. (if (absolute-file-name? filename)
  330. (lambda (fullname base)
  331. (or (= 0 base)
  332. (chdir (base-sub fullname base))))
  333. (lambda (fullname base)
  334. (chdir
  335. (pathify od (base-sub fullname base)))))
  336. (lambda (fullname base) #t)))
  337. (maybe-cd-back (if (memq 'chdir control-flags)
  338. (lambda () (chdir od))
  339. (lambda () #t)))
  340. (depth-first? (memq 'depth control-flags))
  341. (visited? (visited?-proc
  342. (cond ((memq 'hash-size control-flags) => cadr)
  343. (else 211))))
  344. (has-kids? (if depth-first?
  345. (lambda (flag) (eq? flag 'directory-processed))
  346. (lambda (flag) (eq? flag 'directory))))
  347. (stat&flag (apply stat&flag-proc
  348. (stat-dir-readable?-proc (getuid-or-false)
  349. (getgid-or-false))
  350. (cons 'nftw-style control-flags))))
  351. (letrec ((go (lambda (fullname base level)
  352. (call-with-values (lambda () (stat&flag fullname))
  353. (lambda (s flag)
  354. (letrec ((self (lambda ()
  355. (maybe-cd fullname base)
  356. ;; the callback
  357. (let ((ret (proc fullname s flag
  358. base level)))
  359. (maybe-cd-back)
  360. (or (eq? #t ret)
  361. (throw 'nftw-early-exit ret)))))
  362. (kids (lambda ()
  363. (and (has-kids? flag)
  364. (for-each
  365. (lambda (child)
  366. (go (pathify fullname child)
  367. (1+ (string-length
  368. fullname))
  369. (1+ level)))
  370. (directory-files fullname))))))
  371. (or (visited? s fullname)
  372. (not (same-dev? s))
  373. (if depth-first?
  374. (begin (kids) (self))
  375. (begin (self) (kids)))))))
  376. #t)))
  377. (let ((ret (catch 'nftw-early-exit
  378. (lambda () (go (clean filename) 0 0))
  379. (lambda (key val) val))))
  380. (chdir od)
  381. ret))))
  382. ;;;
  383. ;;; `file-system-fold' & co.
  384. ;;;
  385. (define-syntax-rule (errno-if-exception expr)
  386. (catch 'system-error
  387. (lambda ()
  388. expr)
  389. (lambda args
  390. (system-error-errno args))))
  391. (define* (file-system-fold enter? leaf down up skip error init file-name
  392. #:optional (stat lstat))
  393. "Traverse the directory at FILE-NAME, recursively. Enter
  394. sub-directories only when (ENTER? PATH STAT RESULT) returns true. When
  395. a sub-directory is entered, call (DOWN PATH STAT RESULT), where PATH is
  396. the path of the sub-directory and STAT the result of (stat PATH); when
  397. it is left, call (UP PATH STAT RESULT). For each file in a directory,
  398. call (LEAF PATH STAT RESULT). When ENTER? returns false, call (SKIP
  399. PATH STAT RESULT). When an `opendir' or STAT call raises an exception,
  400. call (ERROR PATH STAT ERRNO RESULT), with ERRNO being the operating
  401. system error number that was raised.
  402. Return the result of these successive applications.
  403. When FILE-NAME names a flat file, (LEAF PATH STAT INIT) is returned.
  404. The optional STAT parameter defaults to `lstat'."
  405. ;; Use drive and inode number as a hash key. If the filesystem
  406. ;; doesn't use inodes, fall back to a string hash.
  407. (define (mark v s fname)
  408. (vhash-cons (cons (stat:dev s)
  409. (if (= 0 (stat:ino s))
  410. (string-hash fname)
  411. (stat:ino s)))
  412. #t v))
  413. (define (visited? v s fname)
  414. (vhash-assoc (cons (stat:dev s)
  415. (if (= 0 (stat:ino s))
  416. (string-hash fname)
  417. (stat:ino s)))
  418. v))
  419. (let loop ((name file-name)
  420. (path "")
  421. (dir-stat (errno-if-exception (stat file-name)))
  422. (result init)
  423. (visited vlist-null))
  424. (define full-name
  425. (if (string=? path "")
  426. name
  427. (string-append path "/" name)))
  428. (cond
  429. ((integer? dir-stat)
  430. ;; FILE-NAME is not readable.
  431. (error full-name #f dir-stat result))
  432. ((visited? visited dir-stat full-name)
  433. (values result visited))
  434. ((eq? 'directory (stat:type dir-stat)) ; true except perhaps the 1st time
  435. (if (enter? full-name dir-stat result)
  436. (let ((dir (errno-if-exception (opendir full-name)))
  437. (visited (mark visited dir-stat full-name)))
  438. (cond
  439. ((directory-stream? dir)
  440. (let liip ((entry (readdir dir))
  441. (result (down full-name dir-stat result))
  442. (subdirs '()))
  443. (cond ((eof-object? entry)
  444. (begin
  445. (closedir dir)
  446. (let ((r+v
  447. (fold (lambda (subdir result+visited)
  448. (call-with-values
  449. (lambda ()
  450. (loop (car subdir)
  451. full-name
  452. (cdr subdir)
  453. (car result+visited)
  454. (cdr result+visited)))
  455. cons))
  456. (cons result visited)
  457. subdirs)))
  458. (values (up full-name dir-stat (car r+v))
  459. (cdr r+v)))))
  460. ((or (string=? entry ".")
  461. (string=? entry ".."))
  462. (liip (readdir dir)
  463. result
  464. subdirs))
  465. (else
  466. (let* ((child (string-append full-name "/" entry))
  467. (st (errno-if-exception (stat child))))
  468. (if (integer? st) ; CHILD is a dangling symlink?
  469. (liip (readdir dir)
  470. (error child #f st result)
  471. subdirs)
  472. (if (eq? (stat:type st) 'directory)
  473. (liip (readdir dir)
  474. result
  475. (alist-cons entry st subdirs))
  476. (liip (readdir dir)
  477. (leaf child st result)
  478. subdirs))))))))
  479. (else
  480. ;; Directory FULL-NAME not readable, but it is stat'able.
  481. (values (error full-name dir-stat dir result)
  482. visited))))
  483. (values (skip full-name dir-stat result)
  484. (mark visited dir-stat full-name))))
  485. (else
  486. ;; Caller passed a FILE-NAME that names a flat file, not a directory.
  487. (leaf full-name dir-stat result)))))
  488. (define* (file-system-tree file-name
  489. #:optional (enter? (lambda (n s) #t))
  490. (stat lstat))
  491. "Return a tree of the form (FILE-NAME STAT CHILDREN ...) where STAT is
  492. the result of (STAT FILE-NAME) and CHILDREN are similar structures for
  493. each file contained in FILE-NAME when it designates a directory. The
  494. optional ENTER? predicate is invoked as (ENTER? NAME STAT) and should
  495. return true to allow recursion into directory NAME; the default value is
  496. a procedure that always returns #t. When a directory does not match
  497. ENTER?, it nonetheless appears in the resulting tree, only with zero
  498. children. The optional STAT parameter defaults to `lstat'. Return #f
  499. when FILE-NAME is not readable."
  500. (define (enter?* name stat result)
  501. (enter? name stat))
  502. (define (leaf name stat result)
  503. (match result
  504. (((siblings ...) rest ...)
  505. (cons (alist-cons (basename name) (cons stat '()) siblings)
  506. rest))))
  507. (define (down name stat result)
  508. (cons '() result))
  509. (define (up name stat result)
  510. (match result
  511. (((children ...) (siblings ...) rest ...)
  512. (cons (alist-cons (basename name) (cons stat children)
  513. siblings)
  514. rest))))
  515. (define skip ; keep an entry for skipped directories
  516. leaf)
  517. (define (error name stat errno result)
  518. (if (string=? name file-name)
  519. result
  520. (leaf name stat result)))
  521. (match (file-system-fold enter?* leaf down up skip error '(())
  522. file-name stat)
  523. (((tree)) tree)
  524. ((()) #f))) ; FILE-NAME is unreadable
  525. (define* (scandir name #:optional (select? (const #t))
  526. (entry<? string-locale<?))
  527. "Return the list of the names of files contained in directory NAME
  528. that match predicate SELECT? (by default, all files.) The returned list
  529. of file names is sorted according to ENTRY<?, which defaults to
  530. `string-locale<?'. Return #f when NAME is unreadable or is not a
  531. directory."
  532. ;; This procedure is implemented in terms of 'readdir' instead of
  533. ;; 'file-system-fold' to avoid the extra 'stat' call that the latter
  534. ;; makes for each entry.
  535. (define (opendir* directory)
  536. (catch 'system-error
  537. (lambda ()
  538. (opendir directory))
  539. (const #f)))
  540. (and=> (opendir* name)
  541. (lambda (stream)
  542. (let loop ((entry (readdir stream))
  543. (files '()))
  544. (if (eof-object? entry)
  545. (begin
  546. (closedir stream)
  547. (sort files entry<?))
  548. (loop (readdir stream)
  549. (if (select? entry)
  550. (cons entry files)
  551. files)))))))
  552. ;;; ftw.scm ends here