~ chicken-core (master) /file.scm
Trap1;;;; file.scm - File operations2;3; Copyright (c) 2008-2022, The CHICKEN Team4; Copyright (c) 2000-2007, Felix L. Winkelmann5; All rights reserved.6;7; Redistribution and use in source and binary forms, with or without8; modification, are permitted provided that the following conditions are9; met:10;11; Redistributions of source code must retain the above copyright12; notice, this list of conditions and the following disclaimer.13;14; Redistributions in binary form must reproduce the above copyright15; notice, this list of conditions and the following disclaimer in the16; documentation and/or other materials provided with the distribution.17;18; Neither the name of the author nor the names of its contributors may19; be used to endorse or promote products derived from this software20; without specific prior written permission.21;22; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS23; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT24; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR25; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT26; HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,27; INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,28; BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS29; OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND30; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR31; TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE32; USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH33; DAMAGE.343536(declare37 (unit file)38 (uses extras irregex pathname)39 (fixnum)40 (disable-interrupts)41 (foreign-declare #<<EOF42#include <errno.h>4344/* For Windows */45#ifndef R_OK46# define R_OK 247#endif48#ifndef W_OK49# define W_OK 450#endif51#ifndef X_OK52# define X_OK 253#endif5455#if defined(_WIN32) && !defined(__CYGWIN__)56# include <direct.h>57# define C_test_access(fn, m) C_fix(_waccess(C_utf16(fn, 0), C_unfix(m)))5859static C_word C_rename(C_word old, C_word new) {60 wchar_t *s = C_utf16(old, 0), *s2 = C_utf16(new, 1);61 _wremove(s2);62 return(C_fix(_wrename(s, s2)));63}6465# define C_remove(str) C_fix(_wremove(C_utf16(str, 0)))66# define C_rmdir(str) C_fix(_wrmdir(C_utf16(str, 0)))67# define C_mkdir(str) C_fix(_wmkdir(C_utf16(str, 0)))68#else69# include <sys/stat.h>70# define C_test_access(fn, m) C_fix(access(C_c_string(fn), C_unfix(m)))71# define C_rename(old, new) C_fix(rename(C_c_string(old), C_c_string(new)))72# define C_remove(str) C_fix(remove(C_c_string(str)))73# define C_rmdir(str) C_fix(rmdir(C_c_string(str)))74# define C_mkdir(str) C_fix(mkdir(C_c_string(str), S_IRWXU | S_IRWXG | S_IRWXO))75#endif7677#include <sys/types.h>78#include <dirent.h>7980#if defined(_WIN32) && !defined(__CYGWIN__)81# define C_opendir(s,h) C_set_block_item(h, 0, (C_word) _wopendir(C_utf16(s, 0)))8283static C_word C_foundfile(C_word e,C_word b,C_word l) {84 C_char *s = C_utf8(((struct _wdirent *)C_block_item(e, 0))->d_name);85 C_char *p = s;86 while(*p != 0) {87 *p = *p == '\\' ? '/' : *p;88 ++p;89 }90 C_strlcpy(C_c_string(b), s, C_unfix(l));91 return(C_fix(C_strlen(s)));92}93# define C_readdir(h,e) C_set_block_item(e, 0, (C_word) _wreaddir((_WDIR *)C_block_item(h, 0)))94# define C_closedir(h) (_wclosedir((_WDIR *)C_block_item(h, 0)), C_SCHEME_UNDEFINED)95#else96# define C_opendir(s,h) C_set_block_item(h, 0, (C_word) opendir(C_c_string(s)))97# define C_foundfile(e,b,l) (C_strlcpy(C_c_string(b), ((struct dirent *)C_block_item(e, 0))->d_name, C_unfix(l)), C_fix(strlen(((struct dirent *) C_block_item(e, 0))->d_name)))9899# define C_readdir(h,e) C_set_block_item(e, 0, (C_word) readdir((DIR *)C_block_item(h, 0)))100# define C_closedir(h) (closedir((DIR *)C_block_item(h, 0)), C_SCHEME_UNDEFINED)101#endif102103static C_word C_u_i_lstat(C_word path)104{105#if !defined(_WIN32) || defined(__CYGWIN__)106 struct stat buf;107 if (lstat(C_c_string(path), &buf) == 0)108 return C_fix(buf.st_mode);109#else110 struct _stat buf;111 if(_wstat(C_utf16(path, 0), &buf) == 0)112 return C_SCHEME_TRUE;113#endif114 return C_SCHEME_FALSE;115}116117#if !defined(_WIN32) || defined(__CYGWIN__)118# define C_u_i_symbolic_linkp(m) C_mk_bool(S_ISLNK(C_unfix(m)))119#else120# define C_u_i_symbolic_linkp(m) C_SCHEME_FALSE121#endif122123EOF124))125126(module chicken.file127 (create-directory delete-directory128 create-temporary-file create-temporary-directory129 delete-file delete-file* copy-file move-file rename-file130 file-exists? directory-exists?131 file-readable? file-writable? file-executable?132 directory find-files glob)133134(import scheme135 chicken.base136 chicken.condition137 chicken.fixnum138 chicken.foreign139 chicken.io140 chicken.irregex141 chicken.pathname142 chicken.process-context)143144(include "common-declarations.scm")145146(define-foreign-variable strerror c-string "strerror(errno)")147148;; TODO: Some duplication from POSIX, to give better error messages.149;; This really isn't so much posix-specific, and code like this is150;; also in library.scm. This should be deduplicated across the board.151(define posix-error152 (let ([strerror (foreign-lambda c-string "strerror" int)]153 [string-append string-append] )154 (lambda (type loc msg . args)155 (let ([rn (##sys#update-errno)])156 (apply ##sys#signal-hook/errno157 type rn loc (string-append msg " - " (strerror rn)) args)))))158159160;;; Existence checks:161162(define (file-exists? name)163 (##sys#check-string name 'file-exists?)164 (and (##sys#file-exists? name #f #f 'file-exists?) name))165166(define (directory-exists? name)167 (##sys#check-string name 'directory-exists?)168 (and (##sys#file-exists? name #f #t 'directory-exists?) name))169170171;;; Permissions:172173(define-foreign-variable _r_ok int "R_OK")174(define-foreign-variable _w_ok int "W_OK")175(define-foreign-variable _x_ok int "X_OK")176177(define (test-access filename acc loc)178 (##sys#check-string filename loc)179 (let ((r (##core#inline "C_test_access" (##sys#make-c-string filename loc) acc)))180 (or (fx= r 0)181 (if (fx= (##sys#update-errno) (foreign-value "EACCES" int))182 #f183 (posix-error #:file-error loc "cannot access file" filename)))))184185(define (file-readable? filename) (test-access filename _r_ok 'file-readable?))186(define (file-writable? filename) (test-access filename _w_ok 'file-writable?))187(define (file-executable? filename) (test-access filename _x_ok 'file-executable?))188189190;;; Directories:191192(define (directory #!optional (spec (current-directory)) show-dotfiles?)193 (##sys#check-string spec 'directory)194 (let ((buffer (##sys#make-bytevector 256))195 (handle (##sys#make-pointer))196 (entry (##sys#make-pointer)))197 (##core#inline198 "C_opendir"199 (##sys#make-c-string spec 'directory) handle)200 (if (##sys#null-pointer? handle)201 (posix-error #:file-error 'directory "cannot open directory" spec)202 (let loop ()203 (##core#inline "C_readdir" handle entry)204 (if (##sys#null-pointer? entry)205 (begin (##core#inline "C_closedir" handle) '())206 (let* ((flen (##core#inline "C_foundfile" entry buffer (##sys#size buffer)))207 (file (##sys#buffer->string buffer 0 flen))208 (char1 (string-ref file 0))209 (char2 (and (fx> flen 1) (string-ref file 1))))210 (if (and (eq? #\. char1)211 (or (not char2)212 (and (eq? #\. char2) (eq? 2 flen))213 (not show-dotfiles?)))214 (loop)215 (cons file (loop)))))))))216217(define-inline (*lstat name loc)218 (##core#inline "C_u_i_lstat" (##sys#make-c-string name loc)))219220(define-inline (*symbolic-link? m)221 (##core#inline "C_u_i_symbolic_linkp" m))222223(define-inline (*create-directory loc name)224 (unless (fx= 0 (##core#inline "C_mkdir" (##sys#make-c-string name loc)))225 (posix-error #:file-error loc "cannot create directory" name)))226227(define create-directory228 (lambda (name #!optional recursive)229 (##sys#check-string name 'create-directory)230 (unless (or (fx= 0 (string-length name))231 (file-exists? name))232 (if recursive233 (let loop ((dir (let-values (((dir file ext) (decompose-pathname name)))234 (if file (make-pathname dir file ext) dir))))235 (when (and dir (not (directory-exists? dir)))236 (loop (pathname-directory dir))237 (*create-directory 'create-directory dir)))238 (*create-directory 'create-directory name)))239 name))240241(define delete-directory242 (lambda (name #!optional recursive)243 (define (rmdir dir)244 (let ((sname (##sys#make-c-string dir)))245 (when (and (not (fx= 0 (##core#inline "C_rmdir" sname)))246 (not (fx= (##sys#update-errno) (foreign-value "ENOENT" int))))247 (posix-error #:file-error 'delete-directory "cannot delete directory" dir))))248 (##sys#check-string name 'delete-directory)249 (if recursive250 (let ((files (find-files ; relies on `find-files' to list dir-contents before dir251 name252 dotfiles: #t253 follow-symlinks: #f)))254 (for-each255 (lambda (f)256 ((cond ((*symbolic-link? (*lstat f 'delete-directory)) delete-file)257 ((directory-exists? f) rmdir)258 (else delete-file))259 f))260 files)261 (rmdir name))262 (rmdir name))))263264265;;; File management:266267(define (delete-file filename)268 (##sys#check-string filename 'delete-file)269 (unless (eq? 0 (##core#inline "C_remove" (##sys#make-c-string filename 'delete-file)))270 (##sys#signal-hook/errno271 #:file-error (##sys#update-errno) 'delete-file272 (##sys#string-append "cannot delete file - " strerror) filename))273 filename)274275(define (delete-file* file)276 (and (*lstat file 'delete-file*)277 (delete-file file)))278279(define (rename-file oldfile newfile #!optional (clobber #f))280 (##sys#check-string oldfile 'rename-file)281 (##sys#check-string newfile 'rename-file)282 (when (and (not clobber) (file-exists? newfile))283 (##sys#error 'rename-file "newfile exists but clobber is false" newfile))284 (unless (eq? 0 (##core#inline285 "C_rename"286 (##sys#make-c-string oldfile 'rename-file)287 (##sys#make-c-string newfile 'rename-file)))288 (##sys#signal-hook/errno289 #:file-error (##sys#update-errno) 'rename-file290 (##sys#string-append "cannot rename file - " strerror) oldfile newfile))291 newfile)292293(define (copy-file oldfile newfile #!optional (clobber #f) (blocksize 1024))294 (##sys#check-string oldfile 'copy-file)295 (##sys#check-string newfile 'copy-file)296 (##sys#check-number blocksize 'copy-file)297 (unless (and (integer? blocksize) (> blocksize 0))298 (##sys#error 'copy-file "invalid blocksize - not a positive integer" blocksize))299 (when (directory-exists? oldfile)300 (##sys#error 'copy-file "cannot copy directories" oldfile))301 (when (and (not clobber) (file-exists? newfile))302 (##sys#error 'copy-file "newfile exists but clobber is false" newfile))303 (let* ((i (open-input-file oldfile #:binary))304 (o (open-output-file newfile #:binary))305 (s (##sys#make-bytevector blocksize)))306 (let loop ((d (read-bytevector! s i))307 (l 0))308 (if (fx= 0 d)309 (begin310 (close-input-port i)311 (close-output-port o)312 l)313 (begin314 (write-bytevector s o 0 d)315 (loop (read-bytevector! s i) (fx+ d l)))))))316317(define (move-file oldfile newfile #!optional (clobber #f) (blocksize 1024))318 (##sys#check-string oldfile 'move-file)319 (##sys#check-string newfile 'move-file)320 (##sys#check-number blocksize 'move-file)321 (unless (and (integer? blocksize) (> blocksize 0))322 (##sys#error 'move-file "invalid blocksize - not a positive integer" blocksize))323 (when (directory-exists? oldfile)324 (##sys#error 'move-file "cannot move directories" oldfile))325 (when (and (not clobber) (file-exists? newfile))326 (##sys#error 'move-file "newfile exists but clobber is false" newfile))327 (let* ((i (open-input-file oldfile #:binary))328 (o (open-output-file newfile #:binary))329 (s (##sys#make-bytevector blocksize)))330 (let loop ((d (read-bytevector! s i))331 (l 0))332 (if (fx= 0 d)333 (begin334 (close-input-port i)335 (close-output-port o)336 (delete-file oldfile)337 l)338 (begin339 (write-bytevector s o 0 d)340 (loop (read-bytevector! s i) (fx+ d l)))))))341342343;;; Temporary file creation:344345(define create-temporary-file)346(define create-temporary-directory)347348(let ((temp-prefix "temp")349 (string-append string-append))350 (define (tempdir)351 (or (get-environment-variable "TMPDIR")352 (get-environment-variable "TEMP")353 (get-environment-variable "TMP")354 (if ##sys#windows-platform355 (let ((up (get-environment-variable "USERPROFILE")))356 (if up357 (string-append up "/AppData/Local/Temp")358 "."))359 "/tmp")))360 (set! create-temporary-file361 (lambda (#!optional (ext "tmp"))362 (##sys#check-string ext 'create-temporary-file)363 (let loop ()364 (let* ((n (##core#inline "C_random_fixnum" #x10000))365 (getpid (foreign-lambda int "C_getpid"))366 (pn (make-pathname367 (tempdir)368 (string-append369 temp-prefix370 (number->string n 16)371 "."372 (##sys#number->string (getpid)))373 ext)))374 (if (file-exists? pn)375 (loop)376 (call-with-output-file pn (lambda (p) pn)))))))377 (set! create-temporary-directory378 (lambda ()379 (let loop ()380 (let* ((n (##core#inline "C_random_fixnum" #x10000))381 (getpid (foreign-lambda int "C_getpid"))382 (pn (make-pathname383 (tempdir)384 (string-append385 temp-prefix386 (number->string n 16)387 "."388 (##sys#number->string (getpid))))))389 (if (file-exists? pn)390 (loop)391 (let ((r (##core#inline "C_mkdir" (##sys#make-c-string pn 'create-temporary-directory))))392 (if (eq? r 0)393 pn394 (##sys#signal-hook395 #:file-error 'create-temporary-directory396 (##sys#string-append "cannot create temporary directory - " strerror)397 pn)))))))))398399400;;; Filename globbing:401402(define (glob . paths)403 (let conc-loop ((paths paths))404 (if (null? paths)405 '()406 (let ((path (car paths)))407 (let-values (((dir fil ext) (decompose-pathname path)))408 (let ((dir* (or dir "."))409 (rx (irregex (glob->sre (make-pathname #f (or fil "*") ext)))))410 (let loop ((fns (condition-case (directory dir* #t)411 ((exn i/o file) #f))))412 (cond ((not (pair? fns)) (conc-loop (cdr paths)))413 ((irregex-match rx (car fns)) =>414 (lambda (m)415 (cons (make-pathname dir (irregex-match-substring m))416 (loop (cdr fns)))))417 (else (loop (cdr fns)))))))))))418419420;;; Find matching files:421422(define (find-files dir #!key (test (lambda _ #t))423 (action (lambda (x y) (cons x y)))424 (seed '())425 (limit #f)426 (dotfiles #f)427 (follow-symlinks #f))428 (##sys#check-string dir 'find-files)429 (let* ((depth 0)430 (lproc431 (cond ((not limit) (lambda _ #t))432 ((fixnum? limit) (lambda _ (fx< depth limit)))433 (else limit)))434 (pproc435 (if (procedure? test)436 test437 (let ((test (irregex test))) ; force compilation438 (lambda (x) (irregex-match test x))))))439 (let loop ((dir dir)440 (fs (directory dir dotfiles))441 (r seed))442 (if (null? fs)443 r444 (let* ((filename (##sys#slot fs 0))445 (f (make-pathname dir filename))446 (rest (##sys#slot fs 1)))447 (cond ((directory-exists? f)448 (cond ((member filename '("." "..")) (loop dir rest r))449 ((and (*symbolic-link? (*lstat f 'find-files)) (not follow-symlinks))450 (loop dir rest (if (pproc f) (action f r) r)))451 ((lproc f)452 (loop dir453 rest454 (fluid-let ((depth (fx+ depth 1)))455 (loop f456 (directory f dotfiles)457 (if (pproc f) (action f r) r)))))458 (else (loop dir rest (if (pproc f) (action f r) r)))))459 ((pproc f) (loop dir rest (action f r)))460 (else (loop dir rest r))))))))461462)