;;; unix.ms ;;; Copyright 1984-2017 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. (if (or (windows?) (equal? (getenv "USER") "root") (embedded?)) (mat unix-file-io (error? (errorf 'open-output-file "failed for testfile.ss: file exists")) (error? (errorf 'open-output-file "failed for testfile.ss: file exists")) (error? (errorf 'open-output-file "failed for testfile.ss: permission denied")) (error? (errorf 'open-output-file "failed for testfile.ss: permission denied")) (error? (errorf 'open-output-file "failed for testfile.ss: file exists")) (error? (errorf 'open-output-file "failed for testfile.ss: file exists")) (error? (errorf 'open-output-file "failed for testfile.ss: permission denied")) (error? (errorf 'open-output-file "failed for testfile.ss: permission denied")) (error? (errorf 'open-input-file "failed for testfile.ss: permission denied")) (error? (errorf 'open-input-output-file "failed for testfile.ss: permission denied")) (error? (errorf 'with-output-to-file "failed for testfile.ss: permission denied")) (error? (errorf 'with-input-from-file "failed for testfile.ss: permission denied")) (error? (errorf 'call-with-input-file "failed for testfile.ss: permission denied")) ) (mat unix-file-io (let ([p (open-output-file "/dev/null" 'truncate)]) (close-output-port p) #t) (let ([p (open-output-file "testfile.ss" 'truncate)]) (close-output-port p) (system "chmod -w testfile.ss") #t) (error? (open-output-file "testfile.ss")) (error? (open-output-file "testfile.ss" 'error)) (error? (open-output-file "testfile.ss" 'truncate)) (error? (open-output-file "testfile.ss" 'append)) (let ([p (open-output-file "testfile.ss" 'replace)]) (close-output-port p) #t) (delete-file "testfile.ss" #f) (eqv? (with-output-to-file "testfile.ss" (lambda () (display "hello\n")) '(mode #o000)) (void)) (error? (open-output-file "testfile.ss")) (error? (open-output-file "testfile.ss" 'error)) (error? (open-output-file "testfile.ss" 'truncate)) (error? (open-output-file "testfile.ss" 'append)) (error? (open-input-file "testfile.ss")) (error? (open-input-output-file "testfile.ss")) (error? (with-output-to-file "testfile.ss" void '(truncate))) (error? (with-input-from-file "testfile.ss" void)) (error? (call-with-input-file "testfile.ss" values)) (delete-file "testfile.ss" #f) ) ) (mat system (error? ; not a string (system 5)) ) (unless (windows?) (mat system (eqv? (with-output-to-file "testfile.ss" void '(replace)) (void)) (begin (system "rm -f testfile.ss") (system "echo hello > testfile.ss") (let ([p (open-input-file "testfile.ss")]) (and (eq? (read p) 'hello) (begin (close-input-port p) #t)))) ) ) (unless (windows?) (mat process-port (let () (define make-process-port (let () (define kill (lambda (pid sig) (if (= sig 0) -1 (system (format "kill -~s ~s" sig pid))))) (define make-handler (lambda (name ip op pid) (lambda (msg . args) (record-case (cons msg args) [block-read (p s n) (block-read ip s n)] [block-write (p s n) (block-write op s n)] [char-ready? (p) (char-ready? ip)] [clear-input-port (p) (clear-input-port ip)] [clear-output-port (p) (clear-output-port op)] [close-port (p) (close-port ip) (close-port op) (mark-port-closed! p)] [file-position (p . pos) (if (null? pos) (most-negative-fixnum) (errorf 'process-port "cannot reposition"))] [flush-output-port (p) (flush-output-port op)] [kill (p signal) (kill pid signal)] [peek-char (p) (peek-char ip)] [port-name (p) name] [read-char (p) (read-char ip)] [unread-char (c p) (unread-char c ip)] [write-char (c p) (write-char c op)] [else (errorf 'process-port "operation ~s not handled" msg)])))) (lambda (command) (let ([handler (apply make-handler (format "process ~s" command) (process command))]) (make-input/output-port handler "" ""))))) (define port-kill (lambda (p s) ((port-handler p) 'kill p s))) (and (let () (define p (make-process-port (format "exec ~a" $cat_flush))) (and (not (char-ready? p)) (begin (fprintf p "hello!~%") (eq? (read p) 'hello!)) (char-ready? p) (char=? (read-char p) #\newline) (not (char-ready? p)) (begin (close-port p) #t) ; sleep 1 may not be enough on a loaded system... (begin (system "sleep 5") (= (port-kill p 0) -1)))) (let () (define p (make-process-port (format "exec ~a" $cat_flush))) (and (not (char-ready? p)) (begin (fprintf p "hello!~%") (eq? (read p) 'hello!)) (char-ready? p) (char=? (read-char p) #\newline) (not (char-ready? p)) (= (port-kill p 15) 0) (let f () (if (char-ready? p) (eof-object? (read-char p)) (f))) ; sleep 1 may not be enough on a loaded system... (begin (system "sleep 1") (eqv? (port-kill p 0) -1)))))) ) ) (if (windows?) (mat register-signal-handler (error? (errorf 'register-signal-handler "# is not a fixnum")) (error? (errorf 'register-signal-handler "14 is not a procedure")) (error? (errorf 'register-signal-handler "# is not a fixnum")) ) (mat register-signal-handler (error? (register-signal-handler list 14)) (error? (register-signal-handler 14 14)) (error? (register-signal-handler list list)) (let ((x '())) (register-signal-handler 14 (lambda (sig) (set! x (cons sig x)))) ; guard the call to system, since openbsd gets an EINTR error, ; probably in system's call to waitpid, causing s_system to ; raise an exception (guard (c [#t (display-condition c) (printf "\nexception ignored\n")]) (system "exec kill -14 $PPID") (system "exec kill -14 $PPID") (system "exec kill -14 $PPID") (system "exec kill -14 $PPID")) (let f ((n 1000000)) (or (equal? x '(14 14 14 14)) (and (not (= n 0)) (f (- n 1)))))) ) ) (if (or (windows?) (equal? (getenv "USER") "root") (embedded?)) (mat file-operations (error? (errorf 'delete-directory "failed for ~a: ~a" "testlink1" "not a directory")) (error? (errorf 'delete-directory "failed for ~a: ~a" "testlink2" "not a directory")) (error? (errorf 'delete-directory "failed for ~a: ~a" "testdir/testfile.ss" "not a directory")) (error? (errorf 'delete-file "failed for ~a: ~a" "testdir/w" "permission denied")) (error? (errorf 'get-mode "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-access-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-change-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-modification-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'get-mode "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-access-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-change-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) (error? (errorf 'file-modification-time "failed for ~s: ~(~a~)" "testlink" "no such file or directory")) ) (mat file-operations (boolean? (delete-file "testlink1" #f)) (boolean? (delete-file "testlink2" #f)) (not (file-exists? "testdir")) (begin (system "ln -s testdir testlink1") (and (not (file-exists? "testlink1")) (not (file-exists? "testlink1" #t)) (file-exists? "testlink1" #f)) (and (not (file-regular? "testlink1")) (not (file-regular? "testlink1" #t)) (not (file-regular? "testlink1" #f))) (and (not (file-directory? "testlink1")) (not (file-directory? "testlink1" #t)) (not (file-directory? "testlink1" #f))) (file-symbolic-link? "testlink1")) (begin (system "ln -s testdir/testfile.ss testlink2") (and (not (file-exists? "testlink2")) (not (file-exists? "testlink2" #t)) (file-exists? "testlink2" #f)) (and (not (file-regular? "testlink2")) (not (file-regular? "testlink2" #t)) (not (file-regular? "testlink2" #f))) (and (not (file-directory? "testlink2")) (not (file-directory? "testlink2" #t)) (not (file-directory? "testlink2" #f))) (file-symbolic-link? "testlink2")) (begin (mkdir "testdir") (and (file-exists? "testlink1") (file-exists? "testlink1" #t) (file-exists? "testlink1" #f)) (and (not (file-regular? "testlink1")) (not (file-regular? "testlink1" #t)) (not (file-regular? "testlink1" #f))) (and (file-directory? "testlink1") (file-directory? "testlink1" #t) (not (file-directory? "testlink1" #f))) (file-symbolic-link? "testlink1")) (begin (and (not (file-exists? "testlink2")) (not (file-exists? "testlink2" #t)) (file-exists? "testlink2" #f)) (and (not (file-regular? "testlink2")) (not (file-regular? "testlink2" #t)) (not (file-regular? "testlink2" #f))) (and (not (file-directory? "testlink2")) (not (file-directory? "testlink2" #t)) (not (file-directory? "testlink2" #f))) (file-symbolic-link? "testlink2")) (begin (with-output-to-file "testdir/testfile.ss" values 'replace) (and (file-exists? "testlink2") (file-exists? "testlink2" #t) (file-exists? "testlink2" #f)) (and (file-regular? "testlink2") (file-regular? "testlink2" #t) (not (file-regular? "testlink2" #f))) (and (not (file-directory? "testlink2")) (not (file-directory? "testlink2" #t)) (not (file-directory? "testlink2" #f))) (file-symbolic-link? "testlink2")) (delete-file "testlink1" #f) (delete-file "testlink2" #f) (begin (system "ln -s testdir testlink1") (and (file-exists? "testlink1") (file-exists? "testlink1" #t) (file-exists? "testlink1" #f)) (and (not (file-regular? "testlink1")) (not (file-regular? "testlink1" #t)) (not (file-regular? "testlink1" #f))) (and (file-directory? "testlink1") (file-directory? "testlink1" #t) (not (file-directory? "testlink1" #f))) (file-symbolic-link? "testlink1")) (begin (system "ln -s testdir/testfile.ss testlink2") (and (file-exists? "testlink2") (file-exists? "testlink2" #t) (file-exists? "testlink2" #f)) (and (file-regular? "testlink2") (file-regular? "testlink2" #t) (not (file-regular? "testlink2" #f))) (and (not (file-directory? "testlink2")) (not (file-directory? "testlink2" #t)) (not (file-directory? "testlink2" #f))) (file-symbolic-link? "testlink2")) (error? (delete-directory "testlink1" #t)) (error? (delete-directory "testlink2" #t)) (delete-file "testlink1" #f) (delete-file "testlink2" #f) (guard (c [(and (i/o-filename-error? c) (equal? (i/o-error-filename c) "testdir"))]) (delete-directory "testdir" #t)) (error? (delete-directory "testdir/testfile.ss" #t)) (delete-file "testdir/testfile.ss" #f) (guard (c [(and (i/o-filename-error? c) (equal? (i/o-error-filename c) "testdir"))]) (delete-file "testdir" #t)) (eqv? (delete-directory "testdir" #t) (void)) (begin (mkdir "testdir" #o700) #t) (begin (with-output-to-file "testdir/r" values) (with-output-to-file "testdir/w" values) (with-output-to-file "testdir/x" values) (with-output-to-file "testdir/rx" values) (with-output-to-file "testdir/rw" values) (chmod "testdir/r" #o400) (chmod "testdir/w" #o200) (chmod "testdir/x" #o100) (chmod "testdir/rx" #o500) (chmod "testdir/rw" #o600) #t) (eqv? (chmod "testdir" #o500) (void)) (error? (delete-file "testdir/w" #t)) (eqv? (chmod "testdir" #o700) (void)) (guard (c [(and (i/o-filename-error? c) (equal? (i/o-error-filename c) "testdir"))]) (delete-directory "testdir" #t)) (eqv? (delete-file "testdir/w" #t) (void)) (eqv? (delete-file "testdir/rw" #t) (void)) (delete-file "testdir/r" #f) (delete-file "testdir/x" #f) (delete-file "testdir/rx") (delete-directory "testdir" #f) (begin (system "echo one > testfile.ss") (system "ln -s testfile.ss testlink") #t) (time=? (file-access-time "testlink") (file-access-time "testfile.ss")) (time=? (file-change-time "testlink") (file-change-time "testfile.ss")) (time=? (file-modification-time "testlink") (file-modification-time "testfile.ss")) ; no guarantee what times are returned for symbolic links. ; just make sure they return time objects (andmap time? (map (lambda (p) (p "testlink" #f)) (list file-access-time file-change-time file-modification-time))) (= (get-mode "testlink") (get-mode "testfile.ss")) (begin (define $taccess (file-access-time "testfile.ss")) (define $tmodification (file-modification-time "testfile.ss")) (define $tchange (file-change-time "testfile.ss")) #t) (eq? (sleep (make-time 'time-duration 0 2)) (void)) (symbol? (with-input-from-file "testfile.ss" read)) ; following should be time testfile.ss") #t) ; for whatever reason, there seems to be no guarantee about this either ... (time<=? $tmodification (file-modification-time "testfile.ss")) (or (begin (chmod "testfile.ss" #o770) (not (= (get-mode "testlink" #f) (get-mode "testfile.ss")))) (begin (chmod "testfile.ss" #o777) (not (= (get-mode "testlink" #f) (get-mode "testfile.ss"))))) ; ... or this (time>=? (file-change-time "testfile.ss") $tchange) (delete-file "testfile.ss" #f) (andmap time? (map (lambda (p) (p "testlink" #f)) (list file-access-time file-change-time file-modification-time))) (error? (get-mode "testlink")) (error? (file-access-time "testlink")) (error? (file-change-time "testlink")) (error? (file-modification-time "testlink")) (error? (get-mode "testlink" #t)) (error? (file-access-time "testlink" #t)) (error? (file-change-time "testlink" #t)) (error? (file-modification-time "testlink" #t)) (delete-file "testlink" #f) ) ) (if (windows?) (mat nonblocking ; verify no windows nonblocking support for binary file ports (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush)]) (dynamic-wind void (lambda () (and (not (port-has-port-nonblocking?? to-stdin)) (not (port-has-set-port-nonblocking!? to-stdin)) (not (port-has-port-nonblocking?? from-stdout)) (not (port-has-set-port-nonblocking!? from-stdout)) (not (port-has-port-nonblocking?? from-stderr)) (not (port-has-set-port-nonblocking!? from-stderr)))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; verify no windows nonblocking support for textual file ports (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush (buffer-mode block) (native-transcoder))]) (dynamic-wind void (lambda () (and (not (port-has-port-nonblocking?? to-stdin)) (not (port-has-set-port-nonblocking!? to-stdin)) (not (port-has-port-nonblocking?? from-stdout)) (not (port-has-set-port-nonblocking!? from-stdout)) (not (port-has-port-nonblocking?? from-stderr)) (not (port-has-set-port-nonblocking!? from-stderr)))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ) (mat nonblocking ; see also io.ms (mat open-process-ports ...) ; test get-bytevector-some on nonblocking binary input port (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush)]) (define put-string (lambda (bp s) (put-bytevector bp (string->utf8 s)))) (define get-string-some (lambda (bp) (let ([x (get-bytevector-some bp)]) (if (eof-object? x) x (utf8->string x))))) (define get-string-n (lambda (bp n) (let ([x (get-bytevector-n bp n)]) (if (eof-object? x) x (utf8->string x))))) (dynamic-wind void (lambda () (put-string to-stdin "life in the fast lane\n") (flush-output-port to-stdin) (let f () (when (input-port-ready? from-stderr) (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr))) (if (input-port-ready? from-stdout) (let ([s (get-string-n from-stdout 10)]) (unless (equal? s "life in th") (errorf #f "unexpected from-stdout string ~s" s))) (begin (display ".") (flush-output-port) (f)))) (set-port-nonblocking! from-stdout #t) (let f ([all ""]) (let ([s (get-string-some from-stdout)]) (when (eof-object? s) (errorf #f "unexpected from-stdout eof")) (if (equal? s "") (unless (equal? all "e fast lane\n") (display ".") (flush-output-port) (f all)) (f (string-append all s))))) (and (equal? (get-string-some from-stdout) "") (not (input-port-ready? from-stdout)) (equal? (get-string-some from-stdout) "") (begin (close-port to-stdin) (let f () (unless (and (port-eof? from-stdout) (port-eof? from-stderr)) (display ".") (flush-output-port) (f))) #t))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; test get-string-some on nonblocking textual input port (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush (buffer-mode block) (native-transcoder))]) (dynamic-wind void (lambda () (put-string to-stdin "life in the fast lane\n") (flush-output-port to-stdin) (let f () (when (input-port-ready? from-stderr) (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr))) (if (input-port-ready? from-stdout) (let ([s (get-string-n from-stdout 10)]) (unless (equal? s "life in th") (errorf #f "unexpected from-stdout string ~s" s))) (begin (display ".") (flush-output-port) (f)))) (set-port-nonblocking! from-stdout #t) (let f ([all ""]) (let ([s (get-string-some from-stdout)]) (when (eof-object? s) (errorf #f "unexpected from-stdout eof")) (if (equal? s "") (unless (equal? all "e fast lane\n") (display ".") (flush-output-port) (f all)) (f (string-append all s))))) (and (equal? (get-string-some from-stdout) "") (not (input-port-ready? from-stdout)) (equal? (get-string-some from-stdout) "") (begin (close-port to-stdin) (let f () (unless (and (port-eof? from-stdout) (port-eof? from-stderr)) (display ".") (flush-output-port) (f))) #t))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; test get-bytevector-some! on nonblocking binary input port (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush)]) (define get-bytevector-some (lambda (bp) (let ([buf (make-bytevector 5)]) (let ([n (get-bytevector-some! bp buf 0 (bytevector-length buf))]) (if (eof-object? n) n (bytevector-truncate! buf n)))))) (define put-string (lambda (bp s) (put-bytevector bp (string->utf8 s)))) (define get-string-some (lambda (bp) (let ([x (get-bytevector-some bp)]) (if (eof-object? x) x (utf8->string x))))) (define get-string-n (lambda (bp n) (let ([x (get-bytevector-n bp n)]) (if (eof-object? x) x (utf8->string x))))) (dynamic-wind void (lambda () (set-port-nonblocking! to-stdin #t) ; not testing whether this does anything (set-port-nonblocking! from-stdout #t) (set-port-nonblocking! from-stderr #t) (put-string to-stdin "that don't impress me much\n") (flush-output-port to-stdin) (let f ([all ""]) (unless (equal? all "that don't impress me much\n") (let ([s (get-string-some from-stderr)]) (when (eof-object? s) (errorf #f "unexpected from-stderr eof")) (unless (equal? s "") (errorf #f "unexpected from-stderr input ~s" s))) (let ([s (get-string-some from-stdout)]) (when (eof-object? s) (errorf #f "unexpected from-stdout eof")) (if (equal? s "") (begin (display ".") (flush-output-port) (f all)) (f (string-append all s)))))) (and (equal? (get-string-some from-stdout) "") (not (input-port-ready? from-stdout)) (equal? (get-string-some from-stdout) "") (begin (close-port to-stdin) (let f () (unless (and (port-eof? from-stdout) (port-eof? from-stderr)) (display ".") (flush-output-port) (f))) #t))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; test get-string-some! on nonblocking textual input port (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush (buffer-mode block) (native-transcoder))]) (define get-string-some (lambda (tp) (let ([buf (make-string 5)]) (let ([n (get-string-some! tp buf 0 (string-length buf))]) (if (eof-object? n) n (substring buf 0 n)))))) (dynamic-wind void (lambda () (set-port-nonblocking! to-stdin #t) ; not testing whether this does anything (set-port-nonblocking! from-stdout #t) (set-port-nonblocking! from-stderr #t) (put-string to-stdin "that don't impress me much\n") (flush-output-port to-stdin) (let f ([all ""]) (unless (equal? all "that don't impress me much\n") (let ([s (get-string-some from-stderr)]) (when (eof-object? s) (errorf #f "unexpected from-stderr eof")) (unless (equal? s "") (errorf #f "unexpected from-stderr input ~s" s))) (let ([s (get-string-some from-stdout)]) (when (eof-object? s) (errorf #f "unexpected from-stdout eof")) (if (equal? s "") (begin (display ".") (flush-output-port) (f all)) (f (string-append all s)))))) (and (equal? (get-string-some from-stdout) "") (not (input-port-ready? from-stdout)) (equal? (get-string-some from-stdout) "") (begin (close-port to-stdin) (let f () (unless (and (port-eof? from-stdout) (port-eof? from-stderr)) (display ".") (flush-output-port) (f))) #t))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; test put-bytevector-some on nonblocking binary output port, ; counting on O/S to limit amount we can write to a pipe that ; no one has yet read from (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush (buffer-mode none))]) (define put-string-some (lambda (bp s) (put-bytevector-some bp (string->utf8 s) 0 (string-length s)))) (define get-string-some (lambda (bp) (let ([x (get-bytevector-some bp)]) (if (eof-object? x) x (utf8->string x))))) (dynamic-wind void (lambda () (define s "my future lies beyond the yellow brick road") (set-port-nonblocking! to-stdin #t) (set-port-nonblocking! from-stdout #t) (set-port-nonblocking! from-stderr #t) (let ([len (string-length s)]) (let f ([n 0]) (let ([i (put-string-some to-stdin s)]) (if (= i len) (f (+ n 1)) (let f () (if (string=? (get-string-some from-stdout) "") (or (= (put-string-some to-stdin "\n") 1) (begin (display ".") (flush-output-port) (f))) (f)))))))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ; test put-string-some on nonblocking textual output port, ; counting on O/S to limit amount we can write to a pipe that ; no one has yet read from (let-values ([(to-stdin from-stdout from-stderr pid) (open-process-ports $cat_flush (buffer-mode none) (native-transcoder))]) (dynamic-wind void (lambda () (define s "my future lies beyond the yellow brick road") (set-port-nonblocking! to-stdin #t) (set-port-nonblocking! from-stdout #t) (set-port-nonblocking! from-stderr #t) (let ([len (string-length s)]) (let f ([n 0]) (let ([i (put-string-some to-stdin s)]) (if (= i len) (f (+ n 1)) (let f () (if (string=? (get-string-some from-stdout) "") (or (= (put-string-some to-stdin "\n") 1) (begin (display ".") (flush-output-port) (f))) (f)))))))) (lambda () (close-port to-stdin) (close-port from-stdout) (close-port from-stderr)))) ) )