mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-01 04:10:18 +02:00
In the copy-file test, is uses call-with-input-file and get-string-all to retrieve file contents. Since the file is not opened in binary mode, CRLF line ending may be added when writing and then reading the string back from the filesystem. * test-suite/tests/filesys.test (normalize-newlines): new helper function ("copy file: copy-file dest content"): pass even if CRLF line endings are added ("copy file: read only dest"): handle posibility of geteuid not being defined
871 lines
30 KiB
Scheme
871 lines
30 KiB
Scheme
;;;; filesys.test --- test file system functions -*- scheme -*-
|
|
;;;;
|
|
;;;; Copyright (C) 2004, 2006, 2013, 2019, 2021 Free Software Foundation, Inc.
|
|
;;;; Copyright (C) 2021 Maxime Devos <maximedevos@telenet.be>
|
|
;;;;
|
|
;;;; This library is free software; you can redistribute it and/or
|
|
;;;; modify it under the terms of the GNU Lesser General Public
|
|
;;;; License as published by the Free Software Foundation; either
|
|
;;;; version 3 of the License, or (at your option) any later version.
|
|
;;;;
|
|
;;;; This library is distributed in the hope that it will be useful,
|
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
;;;; Lesser General Public License for more details.
|
|
;;;;
|
|
;;;; You should have received a copy of the GNU Lesser General Public
|
|
;;;; License along with this library; if not, write to the Free Software
|
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
(define-module (test-suite test-filesys)
|
|
#:use-module (test-suite lib)
|
|
#:use-module (test-suite guile-test)
|
|
#:use-module (ice-9 threads)
|
|
#:use-module (ice-9 match)
|
|
#:use-module (rnrs io ports)
|
|
#:use-module (rnrs bytevectors))
|
|
|
|
(define (test-file)
|
|
(data-file-name "filesys-test.tmp"))
|
|
(define (test-symlink)
|
|
(data-file-name "filesys-test-link.tmp"))
|
|
(define (test-directory)
|
|
(data-file-name "filesys-test-dir.tmp"))
|
|
(define (test-directory2)
|
|
(data-file-name "filesys-test-dir2.tmp"))
|
|
|
|
(define (skip-on-darwin)
|
|
(when (string-ci=? "darwin" (utsname:sysname (uname)))
|
|
(throw 'untested)))
|
|
|
|
(define (%delete-file file)
|
|
;; lstat finds files and links. stat finds just files.
|
|
(when (false-if-exception (lstat file))
|
|
;; MinGW requires write permission to delete a file.
|
|
(when (and (stat file #f) (not (access? file W_OK)))
|
|
(chmod file (logior (stat:mode (stat file)) #o200)))
|
|
(delete-file file)))
|
|
|
|
(define (normalize-newlines s)
|
|
"Returns a copy of the input string, replacing any #\return #\newline
|
|
pairs with #\newline."
|
|
(let loop ((i 0) (result '()))
|
|
(if (< i (string-length s))
|
|
(if (and (< (+ i 1) (string-length s))
|
|
(char=? (string-ref s i) #\return)
|
|
(char=? (string-ref s (+ i 1)) #\newline))
|
|
(loop (+ i 2) (cons #\newline result))
|
|
(loop (+ i 1) (cons (string-ref s i) result)))
|
|
(list->string (reverse result)))))
|
|
|
|
;;;
|
|
;;; copy-file
|
|
;;;
|
|
|
|
(with-test-prefix "copy-file"
|
|
|
|
(with-test-prefix "missing source"
|
|
(with-exception-handler
|
|
(λ (ex) (pass-if-equal "errno is ENOENT" ENOENT (exception-errno ex)))
|
|
(λ () (copy-file "this-file-should-not-exist" "somewhere"))
|
|
#:unwind? #t))
|
|
|
|
(call-with-temp-dir
|
|
"copy-file-XXXXXX"
|
|
(λ (tmpdir)
|
|
(let ((msg "this is a test, this is only a test...\n")
|
|
(src (string-append tmpdir "/" "source"))
|
|
(dest (string-append tmpdir "/" "read-only-dest")))
|
|
(call-with-output-file src (λ (p) (display msg p)))
|
|
(call-with-output-file dest identity)
|
|
(with-test-prefix "successful copy"
|
|
(copy-file src dest)
|
|
(pass-if-equal "copy-file dest content" msg
|
|
;; Since call-with-input-file is always text mode, it may have
|
|
;; \r\n on MinGW.
|
|
(normalize-newlines
|
|
(call-with-input-file dest get-string-all))))
|
|
;; Checking for a geteuid of zero is testing of the user
|
|
;; is root, making the chmod to read-only meaningless. geteuid is
|
|
;; not defined in MinGW.
|
|
(unless (and (defined? 'geteuid) (zero? (geteuid)))
|
|
(with-test-prefix "read only dest"
|
|
(chmod dest #o444)
|
|
(with-exception-handler
|
|
(λ (ex)
|
|
(pass-if-equal "errno is EACCES" EACCES (exception-errno ex)))
|
|
(λ () (copy-file src dest))
|
|
#:unwind? #t))))))
|
|
|
|
;; return next prospective file descriptor number
|
|
(define (next-fd)
|
|
(let ((fd (dup 0)))
|
|
(close fd)
|
|
fd))
|
|
|
|
;; in guile 1.6.4 and earlier, copy-file didn't close the input fd when
|
|
;; the output could not be opened
|
|
(pass-if "fd leak when dest unwritable"
|
|
(let ((old-next (next-fd)))
|
|
(false-if-exception (copy-file "/dev/null" "no/such/dir/foo"))
|
|
(= old-next (next-fd)))))
|
|
|
|
;;;
|
|
;;; lstat
|
|
;;;
|
|
|
|
(with-test-prefix "lstat"
|
|
|
|
(pass-if "normal file"
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(eqv? 5 (stat:size (lstat (test-file)))))
|
|
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(%delete-file (test-symlink))
|
|
(if (not (false-if-exception
|
|
(begin (symlink (test-file) (test-symlink)) #t)))
|
|
(display "cannot create symlink, lstat test skipped\n")
|
|
(pass-if "symlink"
|
|
;; not much to test, except that it works
|
|
(->bool (lstat (test-symlink))))))
|
|
|
|
;;;
|
|
;;; opendir and friends
|
|
;;;
|
|
|
|
(with-test-prefix "opendir"
|
|
|
|
(with-test-prefix "root directory"
|
|
(let ((d (opendir "/")))
|
|
(pass-if "not empty"
|
|
(string? (readdir d)))
|
|
(pass-if "all entries are strings"
|
|
(let more ()
|
|
(let ((f (readdir d)))
|
|
(cond ((string? f)
|
|
(more))
|
|
((eof-object? f)
|
|
#t)
|
|
(else
|
|
#f)))))
|
|
(closedir d))))
|
|
|
|
;;;
|
|
;;; stat
|
|
;;;
|
|
|
|
(with-test-prefix "stat"
|
|
|
|
(with-test-prefix "filename"
|
|
|
|
(pass-if "size"
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(eqv? 5 (stat:size (stat (test-file))))))
|
|
|
|
(with-test-prefix "file descriptor"
|
|
|
|
(pass-if "size"
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(let* ((fd (open-fdes (test-file) O_RDONLY))
|
|
(st (stat fd)))
|
|
(close-fdes fd)
|
|
(eqv? 5 (stat:size st)))))
|
|
|
|
(with-test-prefix "port"
|
|
|
|
(pass-if "size"
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(let* ((port (open-file (test-file) "r+"))
|
|
(st (stat port)))
|
|
(close-port port)
|
|
(eqv? 5 (stat:size st))))))
|
|
|
|
(with-test-prefix "statat"
|
|
;; file-exists? from (ice-9 boot) dereferences symbolic links
|
|
;; (a bug?).
|
|
(define (file-exists? filename)
|
|
(catch 'system-error
|
|
(lambda () (lstat filename) #t)
|
|
(lambda args
|
|
(if (= (system-error-errno args) ENOENT)
|
|
;; For the purposes of the following tests,
|
|
;; it is safe to ignore errors like EPERM, but a correct
|
|
;; implementation would return #t for that error.
|
|
#f
|
|
(apply throw args)))))
|
|
(define (maybe-delete-directory)
|
|
(when (file-exists? (test-directory))
|
|
(for-each
|
|
(lambda (filename)
|
|
(define full-name (in-vicinity (test-directory) filename))
|
|
(%delete-file full-name))
|
|
'("test-file" "test-symlink"))
|
|
(rmdir (test-directory))))
|
|
(define (skip-unless-defined . things)
|
|
(for-each (lambda (thing)
|
|
(unless (defined? thing)
|
|
(throw 'unsupported)))
|
|
things))
|
|
(maybe-delete-directory)
|
|
(mkdir (test-directory))
|
|
(call-with-output-file (in-vicinity (test-directory) "test-file")
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
|
|
;; Return #true if the symlink was created, #false otherwise.
|
|
(define (maybe-create-symlink)
|
|
(if (file-exists? (in-vicinity (test-directory) "test-symlink"))
|
|
#t
|
|
(false-if-exception
|
|
(symlink "test-file"
|
|
(in-vicinity (test-directory) "test-symlink")))))
|
|
|
|
(pass-if-equal "regular file" 5
|
|
(skip-unless-defined 'statat)
|
|
(call-with-port
|
|
(open (test-directory) O_RDONLY)
|
|
(lambda (port)
|
|
(stat:size (statat port "test-file")))))
|
|
|
|
(pass-if-equal "regular file, AT_SYMLINK_NOFOLLOW" 5
|
|
(skip-unless-defined 'statat 'AT_SYMLINK_NOFOLLOW)
|
|
(call-with-port
|
|
(open (test-directory) O_RDONLY)
|
|
(lambda (port)
|
|
(stat:size (statat port "test-file" AT_SYMLINK_NOFOLLOW)))))
|
|
|
|
(pass-if-equal "symbolic links are dereferenced" '(regular 5)
|
|
;; Not all systems support symlinks.
|
|
(skip-unless-defined 'statat 'symlink)
|
|
(unless (maybe-create-symlink)
|
|
(throw 'unresolved))
|
|
(call-with-port
|
|
(open (test-directory) O_RDONLY)
|
|
(lambda (port)
|
|
(define result (statat port "test-symlink"))
|
|
(list (stat:type result) (stat:size result)))))
|
|
|
|
(pass-if-equal "symbolic links are not dereferenced"
|
|
`(symlink ,(string-length "test-file"))
|
|
;; Not all systems support symlinks.
|
|
(skip-unless-defined 'statat 'symlink)
|
|
(unless (maybe-create-symlink)
|
|
(throw 'unresolved))
|
|
(call-with-port
|
|
(open (test-directory) O_RDONLY)
|
|
(lambda (port)
|
|
(define result (statat port "test-symlink" AT_SYMLINK_NOFOLLOW))
|
|
(list (stat:type result) (stat:size result)))))
|
|
|
|
(maybe-delete-directory))
|
|
|
|
(with-test-prefix "sendfile"
|
|
|
|
(let* ((file (search-path %load-path "ice-9/boot-9.scm"))
|
|
(len (stat:size (stat file)))
|
|
(ref (call-with-input-file file get-bytevector-all)))
|
|
|
|
(pass-if-equal "file" (cons len ref)
|
|
(let* ((result (call-with-input-file file
|
|
(lambda (input)
|
|
(call-with-output-file (test-file)
|
|
(lambda (output)
|
|
(sendfile output input len 0))))))
|
|
(out (call-with-input-file (test-file) get-bytevector-all)))
|
|
(cons result out)))
|
|
|
|
(pass-if-equal "file with offset"
|
|
(cons (- len 777) (call-with-input-file file
|
|
(lambda (input)
|
|
(seek input 777 SEEK_SET)
|
|
(get-bytevector-all input))))
|
|
(let* ((result (call-with-input-file file
|
|
(lambda (input)
|
|
(call-with-output-file (test-file)
|
|
(lambda (output)
|
|
(sendfile output input (- len 777) 777))))))
|
|
(out (call-with-input-file (test-file) get-bytevector-all)))
|
|
(cons result out)))
|
|
|
|
(pass-if-equal "file with offset past the end"
|
|
(cons (- len 777) (call-with-input-file file
|
|
(lambda (input)
|
|
(seek input 777 SEEK_SET)
|
|
(get-bytevector-all input))))
|
|
(let* ((result (call-with-input-file file
|
|
(lambda (input)
|
|
(call-with-output-file (test-file)
|
|
(lambda (output)
|
|
(sendfile output input len 777))))))
|
|
(out (call-with-input-file (test-file) get-bytevector-all)))
|
|
(cons result out)))
|
|
|
|
(pass-if-equal "file with offset near the end"
|
|
(cons 77 (call-with-input-file file
|
|
(lambda (input)
|
|
(seek input (- len 77) SEEK_SET)
|
|
(get-bytevector-all input))))
|
|
(let* ((result (call-with-input-file file
|
|
(lambda (input)
|
|
(call-with-output-file (test-file)
|
|
(lambda (output)
|
|
(sendfile output input len (- len 77)))))))
|
|
(out (call-with-input-file (test-file) get-bytevector-all)))
|
|
(cons result out)))
|
|
|
|
(pass-if-equal "pipe" (cons len ref)
|
|
(if (provided? 'threads)
|
|
(let* ((in+out (pipe))
|
|
(child (call-with-new-thread
|
|
(lambda ()
|
|
(call-with-input-file file
|
|
(lambda (input)
|
|
(let ((result (sendfile (cdr in+out)
|
|
(fileno input)
|
|
len 0)))
|
|
(close-port (cdr in+out))
|
|
result)))))))
|
|
(let ((out (get-bytevector-all (car in+out))))
|
|
(close-port (car in+out))
|
|
(cons (join-thread child) out)))
|
|
(throw 'unresolved)))
|
|
|
|
(pass-if-equal "pipe with offset"
|
|
(cons (- len 777) (call-with-input-file file
|
|
(lambda (input)
|
|
(seek input 777 SEEK_SET)
|
|
(get-bytevector-all input))))
|
|
(if (provided? 'threads)
|
|
(let* ((in+out (pipe))
|
|
(child (call-with-new-thread
|
|
(lambda ()
|
|
(call-with-input-file file
|
|
(lambda (input)
|
|
(let ((result (sendfile (cdr in+out)
|
|
(fileno input)
|
|
(- len 777)
|
|
777)))
|
|
(close-port (cdr in+out))
|
|
result)))))))
|
|
(let ((out (get-bytevector-all (car in+out))))
|
|
(close-port (car in+out))
|
|
(cons (join-thread child) out)))
|
|
(throw 'unresolved)))))
|
|
|
|
(with-test-prefix "basename"
|
|
|
|
(pass-if-equal "/" "/" (basename "/"))
|
|
(pass-if-equal "//" "/" (basename "//"))
|
|
(pass-if-equal "a/b/c" "c" (basename "a/b/c"))
|
|
|
|
(pass-if-equal "a.b" (basename "a.b" "a.b"))
|
|
(pass-if-equal "a.b" (basename "/a.b" "a.b"))
|
|
(pass-if-equal "a" (basename "a.b" ".b"))
|
|
(pass-if-equal "a" (basename "/a.b" ".b"))
|
|
|
|
;; https://debbugs.gnu.org/69437
|
|
(pass-if-equal "bar" (basename "foo/bar" "o/bar")))
|
|
|
|
(%delete-file (test-file))
|
|
(%delete-file (test-symlink))
|
|
|
|
|
|
(with-test-prefix "mkdtemp"
|
|
|
|
(pass-if-exception "number arg" exception:wrong-type-arg
|
|
(if (not (defined? 'mkdtemp))
|
|
(throw 'unresolved)
|
|
(mkdtemp 123)))
|
|
|
|
(pass-if "template prefix is preserved"
|
|
(if (not (defined? 'mkdtemp))
|
|
(throw 'unresolved)
|
|
(let* ((template "T-XXXXXX")
|
|
(name (mkdtemp template)))
|
|
(false-if-exception (rmdir name))
|
|
(and
|
|
(string? name)
|
|
(string-contains name "T-")
|
|
(= (string-length name) 8)))))
|
|
|
|
(pass-if-exception "invalid template" exception:system-error
|
|
;; MacOS accepts any template and just does no replacements.
|
|
(skip-on-darwin)
|
|
(if (not (defined? 'mkdtemp))
|
|
(throw 'unresolved)
|
|
(mkdtemp "T-AAAAAA")))
|
|
|
|
(pass-if "directory created"
|
|
(if (not (defined? 'mkdtemp))
|
|
(throw 'unresolved)
|
|
(let* ((template "T-XXXXXX")
|
|
(name (mkdtemp template)))
|
|
(let* ((_stat (stat name))
|
|
(result (eqv? 'directory (stat:type _stat))))
|
|
(false-if-exception (rmdir name))
|
|
result)))))
|
|
|
|
;;;
|
|
;;; chmodat
|
|
;;;
|
|
|
|
(with-test-prefix "chmodat"
|
|
(call-with-output-file (test-file) (const #f))
|
|
(chmod (test-file) #o000)
|
|
|
|
(pass-if-equal "regular file"
|
|
#o300
|
|
(unless (defined? 'chmodat)
|
|
(throw 'unsupported))
|
|
(call-with-port
|
|
(open (dirname (test-file)) O_RDONLY)
|
|
(lambda (port)
|
|
(chmodat port (test-file) #o300)))
|
|
(stat:perms (stat (test-file))))
|
|
|
|
(chmod (test-file) #o000)
|
|
|
|
(pass-if-equal "regular file, AT_SYMLINK_NOFOLLOW"
|
|
#o300
|
|
(unless (and (defined? 'chmodat)
|
|
(defined? 'AT_SYMLINK_NOFOLLOW))
|
|
(throw 'unsupported))
|
|
(call-with-port
|
|
(open (dirname (test-file)) O_RDONLY)
|
|
(lambda (port)
|
|
(catch 'system-error
|
|
(lambda ()
|
|
(chmodat port (basename (test-file)) #o300 AT_SYMLINK_NOFOLLOW))
|
|
(lambda args
|
|
(close-port port)
|
|
;; AT_SYMLINK_NOFOLLOW is not supported on Linux (at least Linux
|
|
;; 5.11.2 with the btrfs file system), even for regular files.
|
|
(if (= ENOTSUP (system-error-errno args))
|
|
(begin
|
|
(display "fchmodat doesn't support AT_SYMLINK_NOFOLLOW\n")
|
|
(throw 'unresolved))
|
|
(apply throw args))))))
|
|
(stat:perms (stat (test-file))))
|
|
|
|
(pass-if-exception "not a port" exception:wrong-type-arg
|
|
(unless (defined? 'chmodat)
|
|
(throw 'unsupported))
|
|
(chmodat "bogus" (test-file) #o300))
|
|
|
|
(pass-if-exception "not a file port" exception:wrong-type-arg
|
|
(unless (defined? 'chmodat)
|
|
(throw 'unsupported))
|
|
(chmodat (open-input-string "") (test-file) #o300))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(unless (defined? 'chmodat)
|
|
(throw 'unsupported))
|
|
(chmodat (call-with-port (open "." O_RDONLY) identity) (test-file) #o300))
|
|
|
|
(chmod (test-file) #o600)
|
|
(delete-file (test-file)))
|
|
|
|
(with-test-prefix "chdir"
|
|
(pass-if-equal "current directory" (getcwd)
|
|
(begin (chdir ".") (getcwd)))
|
|
(define file (search-path %load-path "ice-9/boot-9.scm"))
|
|
|
|
|
|
;; canonicalize-path is used to not stumble over differences due to links
|
|
(pass-if-equal "test directory" (canonicalize-path (dirname file))
|
|
(let ((olddir (getcwd))
|
|
(dir #f))
|
|
(chdir (canonicalize-path (dirname file)))
|
|
(set! dir (getcwd))
|
|
(chdir olddir)
|
|
dir))
|
|
|
|
(pass-if-equal "test directory, via port" (canonicalize-path (dirname file))
|
|
(unless (provided? 'chdir-port)
|
|
(throw 'unresolved))
|
|
(let ((olddir (getcwd))
|
|
(port (open (canonicalize-path (dirname file)) O_RDONLY))
|
|
(dir #f))
|
|
(chdir port)
|
|
(set! dir (getcwd))
|
|
(chdir olddir)
|
|
dir))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(unless (provided? 'chdir-port)
|
|
(throw 'unresolved))
|
|
(let ((port (open (dirname file) O_RDONLY))
|
|
(olddir (getcwd)))
|
|
(close-port port)
|
|
(chdir port)
|
|
(chdir olddir))) ; should not be reached
|
|
|
|
(pass-if-exception "not a port or file name" exception:wrong-type-arg
|
|
(chdir '(stuff)))
|
|
|
|
(pass-if-exception "non-file port" exception:wrong-type-arg
|
|
(chdir (open-input-string ""))))
|
|
|
|
(with-test-prefix "readlink"
|
|
(%delete-file (test-symlink))
|
|
(%delete-file (test-file))
|
|
(call-with-output-file (test-file)
|
|
(lambda (port)
|
|
(display "hello" port)))
|
|
(if (not (false-if-exception
|
|
(begin (symlink (test-file) (test-symlink)) #t)))
|
|
(display "cannot create symlink, some readlink tests skipped\n")
|
|
(let ()
|
|
(pass-if-equal "file name of symlink" (test-file)
|
|
(readlink (test-symlink)))
|
|
|
|
(pass-if-equal "port representing a symlink" (test-file)
|
|
(let ()
|
|
(unless (and (provided? 'readlink-port)
|
|
(defined? 'O_NOFOLLOW)
|
|
(defined? 'O_PATH)
|
|
(not (= 0 O_NOFOLLOW))
|
|
(not (= 0 O_PATH)))
|
|
(throw 'unsupported))
|
|
(define port (open (test-symlink) (logior O_NOFOLLOW O_PATH)))
|
|
(define points-to (false-if-exception (readlink port)))
|
|
(close-port port)
|
|
points-to))
|
|
|
|
(pass-if-exception "not a port or file name" exception:wrong-type-arg
|
|
(readlink '(stuff)))))
|
|
|
|
(pass-if-equal "port representing a regular file" EINVAL
|
|
(unless (provided? 'readlink-port)
|
|
(throw 'unsupported))
|
|
(call-with-input-file (test-file)
|
|
(lambda (port)
|
|
(catch 'system-error
|
|
(lambda ()
|
|
(readlink port)
|
|
(close-port port) ; should be unreachable
|
|
#f)
|
|
(lambda args
|
|
(close-port port)
|
|
;; At least Linux 5.10.46 returns ENOENT instead of EINVAL.
|
|
;; Possibly surprising, but it is documented in some man
|
|
;; pages and it doesn't appear to be an accident:
|
|
;; <https://elixir.bootlin.com/linux/v5.10.46/source/fs/stat.c#L419>.
|
|
(define error (system-error-errno args))
|
|
(if (= error ENOENT)
|
|
EINVAL
|
|
error))))))
|
|
|
|
(pass-if-exception "non-file port" exception:wrong-type-arg
|
|
(readlink (open-input-string "")))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(let ((port (open-file (test-file) "r")))
|
|
(close-port port)
|
|
(readlink port)))
|
|
|
|
(%delete-file (test-symlink))
|
|
(%delete-file (test-file)))
|
|
|
|
(with-test-prefix "symlinkat"
|
|
(pass-if-equal "create" (test-file)
|
|
(unless (defined? 'symlinkat)
|
|
(throw 'unsupported))
|
|
(call-with-port
|
|
(open "." O_RDONLY)
|
|
(lambda (port)
|
|
(symlinkat port (test-file) (test-symlink))
|
|
(readlink (test-symlink)))))
|
|
(%delete-file (test-symlink))
|
|
|
|
(pass-if-exception "not a port" exception:wrong-type-arg
|
|
(unless (defined? 'symlinkat)
|
|
(throw 'unsupported))
|
|
(symlinkat "bogus" (test-file) (test-symlink)))
|
|
|
|
(pass-if-exception "not a file port" exception:wrong-type-arg
|
|
(unless (defined? 'symlinkat)
|
|
(throw 'unsupported))
|
|
(symlinkat (open-input-string "") (test-file) (test-symlink)))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(unless (defined? 'symlinkat)
|
|
(throw 'unsupported))
|
|
(symlinkat (call-with-port (open "." O_RDONLY) identity)
|
|
(test-file) (test-symlink))))
|
|
|
|
(with-test-prefix "mkdirat"
|
|
(define (skip-if-unsupported)
|
|
(unless (defined? 'mkdirat)
|
|
(throw 'unsupported)))
|
|
(define (maybe-delete-directory)
|
|
(when (file-exists? (test-directory))
|
|
(rmdir (test-directory))))
|
|
(maybe-delete-directory)
|
|
|
|
(pass-if-equal "create" 'directory
|
|
(skip-if-unsupported)
|
|
(call-with-port
|
|
(open "." O_RDONLY)
|
|
(lambda (port)
|
|
(mkdirat port (test-directory))
|
|
(stat:type (stat (test-directory))))))
|
|
(maybe-delete-directory)
|
|
|
|
(pass-if-equal "explicit perms" (logand #o111 (lognot (umask)))
|
|
(skip-if-unsupported)
|
|
(call-with-port
|
|
(open "." O_RDONLY)
|
|
(lambda (port)
|
|
(mkdirat port (test-directory) #o111)
|
|
(stat:perms (stat (test-directory))))))
|
|
(maybe-delete-directory)
|
|
|
|
(pass-if-equal "create, implicit perms" (logand #o777 (lognot (umask)))
|
|
(skip-if-unsupported)
|
|
(call-with-port
|
|
(open "." O_RDONLY)
|
|
(lambda (port)
|
|
(mkdirat port (test-directory))
|
|
(stat:perms (stat (test-directory))))))
|
|
(maybe-delete-directory))
|
|
|
|
(with-test-prefix "rename-file-at"
|
|
(define (skip-if-unsupported)
|
|
(unless (defined? 'rename-file-at)
|
|
(throw 'unsupported)))
|
|
(pass-if-equal "current working directory" '(#f "hello")
|
|
(skip-if-unsupported)
|
|
;; Create a file in the test directory
|
|
(call-with-output-file "filesys-test-a.tmp"
|
|
(lambda (port) (display "hello" port)))
|
|
;; Try to rename it
|
|
(rename-file-at #f "filesys-test-a.tmp" #f "filesys-test-b.tmp")
|
|
;; Verify it exists under the new name, and not under the old name
|
|
(list (file-exists? "filesys-test-a.tmp")
|
|
(call-with-input-file "filesys-test-b.tmp" get-string-all)))
|
|
|
|
(%delete-file "filesys-test-a.tmp")
|
|
(%delete-file "filesys-test-b.tmp")
|
|
|
|
(pass-if-equal "two ports" '(#f "hello")
|
|
(skip-if-unsupported)
|
|
(mkdir (test-directory))
|
|
(mkdir (test-directory2))
|
|
;; Create a file in the first directory
|
|
(call-with-output-file (in-vicinity (test-directory) "a")
|
|
(lambda (port) (display "hello" port)))
|
|
(let ((port1 (open (test-directory) O_RDONLY))
|
|
(port2 (open (test-directory2) O_RDONLY)))
|
|
;; Try to rename it
|
|
(rename-file-at port1 "a" port2 "b")
|
|
(close-port port1)
|
|
(close-port port2)
|
|
;; Verify it exists under the new name, and not under the old name
|
|
(list (file-exists? (in-vicinity (test-directory) "a"))
|
|
(call-with-input-file (in-vicinity (test-directory2) "b")
|
|
get-string-all))))
|
|
(%delete-file (in-vicinity (test-directory) "a"))
|
|
(%delete-file (in-vicinity (test-directory2) "b"))
|
|
(false-if-exception (rmdir (test-directory)))
|
|
(false-if-exception (rmdir (test-directory2)))
|
|
|
|
(pass-if-equal "port and current working directory" '(#f "hello")
|
|
(skip-if-unsupported)
|
|
(mkdir (test-directory))
|
|
;; Create a file in (test-directory)
|
|
(call-with-output-file (in-vicinity (test-directory) "a")
|
|
(lambda (port) (display "hello" port)))
|
|
(let ((port (open (test-directory) O_RDONLY)))
|
|
;; Try to rename it
|
|
(rename-file-at port "a" #f (basename (test-file)))
|
|
(close-port port)
|
|
;; Verify it exists under the new name, and not under the old name.
|
|
(list (file-exists? (in-vicinity (test-directory) "a"))
|
|
(call-with-input-file (test-file) get-string-all))))
|
|
(%delete-file (in-vicinity (test-directory) "a"))
|
|
(false-if-exception (rmdir (test-directory)))
|
|
(%delete-file (test-file))
|
|
|
|
(pass-if-equal "current working directory and port" '(#f "hello")
|
|
(skip-if-unsupported)
|
|
(mkdir (test-directory))
|
|
;; Create a file in the working directory
|
|
(call-with-output-file (test-file)
|
|
(lambda (port) (display "hello" port)))
|
|
(let ((port (open (test-directory) O_RDONLY)))
|
|
;; Try to rename it
|
|
(rename-file-at #f (basename (test-file)) port "b")
|
|
(close-port port)
|
|
;; Verify it exists under the new name, and not under the old name.
|
|
(list (file-exists? (test-file))
|
|
(call-with-input-file (in-vicinity (test-directory) "b")
|
|
get-string-all))))
|
|
|
|
(%delete-file (in-vicinity (test-directory) "b"))
|
|
(%delete-file (test-file))
|
|
(false-if-exception (rmdir (test-directory)))
|
|
|
|
(pass-if-exception "not a file port (1)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at (open-input-string "") "some" #f "thing"))
|
|
|
|
(pass-if-exception "not a file port (2)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at #f "some" (open-input-string "") "thing"))
|
|
|
|
(pass-if-exception "closed port (1)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at (call-with-port (open "." O_RDONLY) identity)
|
|
"some" #f "thing"))
|
|
|
|
(pass-if-exception "closed port (2)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at #f "some" (call-with-port (open "." O_RDONLY) identity)
|
|
"thing"))
|
|
|
|
(pass-if-exception "not a string (1)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at #f 'what #f "thing"))
|
|
|
|
(pass-if-exception "not a string (2)" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(rename-file-at #f "some" #f 'what)))
|
|
|
|
(with-test-prefix "delete-file-at"
|
|
(define (skip-if-unsupported)
|
|
(when (not (and (defined? 'delete-file-at)
|
|
(defined? 'AT_REMOVEDIR)))
|
|
(throw 'unsupported)))
|
|
(define (create-test-file)
|
|
(call-with-output-file (test-file) identity))
|
|
(define (create-test-directory)
|
|
(mkdir (test-directory)))
|
|
(define (delete-test-file)
|
|
(%delete-file (test-file)))
|
|
(define (delete-test-directory)
|
|
(when (file-exists? (test-directory))
|
|
(rmdir (test-directory))))
|
|
|
|
(pass-if-equal "regular file" #f
|
|
(skip-if-unsupported)
|
|
(create-test-file)
|
|
(call-with-port
|
|
(open (dirname (test-file)) O_RDONLY)
|
|
(lambda (port)
|
|
(delete-file-at port (basename (test-file)))))
|
|
(file-exists? (test-file)))
|
|
(delete-test-file)
|
|
|
|
(pass-if-equal "regular file, explicit flags" #f
|
|
(skip-if-unsupported)
|
|
(create-test-file)
|
|
(call-with-port
|
|
(open (dirname (test-file)) O_RDONLY)
|
|
(lambda (port)
|
|
(delete-file-at port (basename (test-file)) 0)))
|
|
(file-exists? (test-file)))
|
|
(delete-test-file)
|
|
|
|
(pass-if-equal "directory, explicit flags" #f
|
|
(skip-if-unsupported)
|
|
(create-test-directory)
|
|
(call-with-port
|
|
(open (dirname (test-directory)) O_RDONLY)
|
|
(lambda (port)
|
|
(delete-file-at port (basename (test-directory)) AT_REMOVEDIR)))
|
|
(file-exists? (test-directory)))
|
|
(delete-test-directory)
|
|
|
|
(pass-if-exception "not a port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(delete-file-at 'bogus "irrelevant"))
|
|
|
|
(pass-if-exception "not a file port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(delete-file-at (open-input-string "") "irrelevant"))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(delete-file-at (call-with-port (open "." O_RDONLY) identity)
|
|
"irrelevant")))
|
|
|
|
(with-test-prefix "openat"
|
|
(define (skip-if-unsupported)
|
|
(unless (defined? 'openat)
|
|
(throw 'unsupported)))
|
|
|
|
(define file (search-path %load-path "ice-9/boot-9.scm"))
|
|
|
|
(define (call-with-relatively-opened-file directory-arguments file-arguments
|
|
proc)
|
|
(call-with-port
|
|
(apply open directory-arguments)
|
|
(lambda (directory)
|
|
(call-with-port
|
|
(apply openat directory file-arguments)
|
|
(lambda (port)
|
|
(proc port))))))
|
|
|
|
(pass-if-equal "mode read-only" "r"
|
|
(skip-if-unsupported)
|
|
(call-with-relatively-opened-file
|
|
(list (dirname file) O_RDONLY)
|
|
(list (basename file) O_RDONLY)
|
|
(lambda (port) (port-mode port))))
|
|
|
|
(pass-if-equal "port-revealed count" 0
|
|
(skip-if-unsupported)
|
|
(call-with-relatively-opened-file
|
|
(list (dirname file) O_RDONLY)
|
|
(list (basename file) O_RDONLY)
|
|
(lambda (port) (port-revealed port))))
|
|
|
|
(%delete-file (test-file))
|
|
|
|
(pass-if-equal "O_CREAT/O_WRONLY" (list #t (logand (lognot (umask)) #o666) "w")
|
|
(skip-if-unsupported)
|
|
(call-with-relatively-opened-file
|
|
(list (dirname (test-file)) O_RDONLY)
|
|
(list (basename (test-file)) (logior O_WRONLY O_CREAT))
|
|
(lambda (port)
|
|
(list (file-exists? (test-file))
|
|
(stat:perms (stat (test-file)))
|
|
(port-mode port)))))
|
|
|
|
(%delete-file (test-file))
|
|
|
|
(pass-if-equal "O_CREAT/O_WRONLY, non-default mode"
|
|
(list #t (logand (lognot (umask)) #o700) "w")
|
|
(skip-if-unsupported)
|
|
(call-with-relatively-opened-file
|
|
(list (dirname (test-file)) O_RDONLY)
|
|
(list (basename (test-file)) (logior O_WRONLY O_CREAT) #o700)
|
|
(lambda (port)
|
|
(list (file-exists? (test-file))
|
|
(stat:perms (stat (test-file)))
|
|
(port-mode port)))))
|
|
|
|
(pass-if-exception "closed port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(openat (call-with-port (open "." O_RDONLY) identity) "." O_RDONLY))
|
|
|
|
(pass-if-exception "non-file port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(openat (open-input-string "") "." O_RDONLY))
|
|
|
|
(pass-if-exception "not a port" exception:wrong-type-arg
|
|
(skip-if-unsupported)
|
|
(openat "not a port" "." O_RDONLY))
|
|
|
|
(%delete-file (test-file)))
|