~ chicken-core (chicken-5) 65ee25d80a374905ffd36807dfa914d125a2a3dc


commit 65ee25d80a374905ffd36807dfa914d125a2a3dc
Author:     felix <felix@call-with-current-continuation.org>
AuthorDate: Mon Feb 3 15:41:09 2025 +0100
Commit:     felix <felix@call-with-current-continuation.org>
CommitDate: Wed Feb 5 18:57:32 2025 +0100

    use process-objects; more wchar_t related changes in posixwin

diff --git a/NEWS b/NEWS
index 61df9e40..a2832681 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,11 @@
     supported are currently UTF-8 (the default) and Latin-1 (ISO-8859-1).
   - `file-read', `file-write', 'set-pseudo-random-seed!' and `random-bytes'
     require a bytevector argument and do not accept strings.
+  - `process-fork', `process-run', `process' and `process*' return now
+    a process-object instead of a PID, use process record accessors to
+    retrieve exit-status and input/output ports. `process-wait' and
+    `process-signal' accept either a PID or a process object as
+    argument.
   - File-locking operations in the (chicken file posix) module now use the
     flock(2) system call, operator over whole files, are thread-safe and
     use a simpler interface. `file-test-lock' has been removed.
diff --git a/manual/Module (chicken process) b/manual/Module (chicken process)
index bcb433c1..d3cbbe73 100644
--- a/manual/Module (chicken process)	
+++ b/manual/Module (chicken process)	
@@ -5,10 +5,11 @@
 
 This module offers procedures for interacting with subprocesses.
 
-* New in CHICKEN 5.4.0: Errors caused by underlying C calls that
-  change errno will produce a condition object with an {{errno}}
-  property, which can be accessed with
-  {{(get-condition-property <the-condition-object> 'exn 'errno)}}.
+Note:
+Errors caused by underlying C calls that
+change errno will produce a condition object with an {{errno}}
+property, which can be accessed with
+{{(get-condition-property <the-condition-object> 'exn 'errno)}}.
 
 === Processes
 
@@ -40,8 +41,9 @@ quotes. When {{#t}} no such wrapping occurs.
 <procedure>(process-fork [THUNK [KILLOTHERS?]])</procedure>
 
 Creates a new child process with the UNIX system call
-{{fork()}}. Returns either the PID of the child process or 0. If
-{{THUNK}} is given, then the child process calls it as a procedure
+{{fork()}}. In the parent process this procedure returns a process-object representing the child process
+and in the child process {{process-fork}} returns {{#f}}.
+If {{THUNK}} is given, then the child process calls it as a procedure
 with no arguments and terminates. If {{THUNK}} is given and the
 optional argument {{KILLOTHERS?}} is true, then kill all other
 existing threads in the child process, leaving only the current thread
@@ -55,7 +57,7 @@ procedure is unimplemented and will raise an error.
 <procedure>(process-run COMMANDLINE)</procedure><br>
 <procedure>(process-run COMMAND ARGUMENT-LIST)</procedure>
 
-Creates a new child process. The PID of the new process is returned.
+Creates a new child process. The process object representing the new process is returned.
 
 * The single parameter version passes the {{COMMANDLINE}} to the
 system shell, so usual argument expansion can take place.  Be careful
@@ -67,9 +69,10 @@ single-parameter version because of its better safety.
 
 ==== process-signal
 
-<procedure>(process-signal PID [SIGNAL])</procedure>
+<procedure>(process-signal PROCESS [SIGNAL])</procedure>
 
-Sends {{SIGNAL}} to the process with the id {{PID}} using the
+Sends {{SIGNAL}} to the process with the integer id or prcoess
+object {{PROCESS}} using the
 UNIX system call {{kill()}}. {{SIGNAL}} defaults to the value
 of the variable {{signal/term}}.
 
@@ -90,8 +93,7 @@ argument strings. When {{#t}} quote-wrapping is not performed.
 
 Returns:
 * the exit status when synchronous
-* the PID when asynchronous
-* -1 when failure
+* a process object when asynchronous
 
 '''NOTE''': On all Unix-like builds (all except native MingW-based
 Windows platforms), this procedure is unimplemented and will raise an
@@ -114,11 +116,12 @@ semantics of {{process-spawn}}:
 
 ==== process-wait
 
-<procedure>(process-wait [PID [NOHANG]])</procedure>
+<procedure>(process-wait [PROCESS [NOHANG]])</procedure>
 
-Suspends the current process until the child process with
-the id {{PID}} has terminated using the UNIX system call
-{{waitpid()}}. If {{PID}} is not given, then this procedure
+Suspends the current process until the child process identifier by {{PROCESS}},
+which should be a process object or an integer process id (pid),
+has terminated using the UNIX system call
+{{waitpid()}}. If {{PROCESS}} is not given, then this procedure
 waits for any child process. If {{NOHANG}} is given and not
 {{#f}} then the current process is not suspended.  This procedure
 returns three values:
@@ -130,6 +133,9 @@ returns three values:
 Note that suspending the current process implies that all threads
 are suspended as well.
 
+The exit status and the flag indicating whether the process returned normally
+are also stored in {{PROCESS}}, when given to be retrieved later, if desired.
+
 On Windows, {{process-wait}} always returns {{#t}} for a terminated
 process and only the exit status is available. (Windows does not
 provide signals as an interprocess communication method.)
@@ -149,16 +155,9 @@ if a signal occurred.
 <procedure>(process COMMANDLINE)</procedure><br>
 <procedure>(process COMMAND ARGUMENT-LIST [ENVIRONMENT-ALIST ENCODING])</procedure>
 
-Creates a subprocess and returns three values: an input port from
-which data written by the sub-process can be read, an output port from
-which any data written to will be received as input in the sub-process
-and the process-id of the started sub-process. Blocking reads and writes
-to or from the ports returned by {{process}} only block the current
-thread, not other threads executing concurrently.
-
-Standard error for the subprocess is linked up to the current
-process's standard error (see {{process*}} if you want to reify
-its standard error into a separate port).
+Creates a subprocess and returns a process object, with the input-, output- and
+error ports stored in the object, which can be accessed using accessors described
+below.
 
 * The single parameter version passes the string {{COMMANDLINE}} to the host-system's shell that
 is invoked as a subprocess.
@@ -175,7 +174,10 @@ Not using the shell may be preferrable for security reasons.
 Once both the input- and output ports are closed, an implicit
 {{waitpid(3)}} is done to wait for the subprocess to finish or to reap
 a subprocess that has terminated. If the subprocess has not finished,
-waiting for it will necessarily block all executing threads.
+waiting for it will necessarily block all executing threads. The exit status
+and whether the process exitted normally will be stored in the returned
+process object to be retrieved later by the accessors described below,
+if so desired.
 
 ==== process*
 
@@ -188,6 +190,39 @@ which any data written to will be received as input in the sub-process,
 the process-id of the started sub-process, and an input port from
 which data written by the sub-process to {{stderr}} can be read.
 
+==== process?
+==== process-id
+==== process-exit-status
+==== process-returned-normally?
+==== process-input-port
+==== process-output-port
+==== process-error-port
+
+<procedure>(process? X)</procedure>
+
+Returns a boolean indicating whether {{X}} is a process object.
+
+<procedure>(process-id PROCESS)</procedure>
+<procedure>(process-exit-status PROCESS)</procedure>
+<procedure>(process-returned-normally? PROCESS)</procedure>
+<procedure>(process-input-port PROCESS)</procedure>
+<procedure>(process-output-port PROCESS)</procedure>
+<procedure>(process-error-port PROCESS)</procedure>
+
+Accessors for process-object attributes. The ports values are only
+defined for processes created with {{process}} or {{process*}} and represent
+the input port from
+which data written by the sub-process can be read, the output port from
+which any data written to will be received as input in the sub-process
+and the error port where to which the sub-process directs its error output.
+Blocking reads and writes
+to or from the ports returned by {{process}} only block the current
+thread, not other threads executing concurrently.
+
+Standard error for the subprocess is linked up to the current
+process's standard error (see {{process*}} if you want to reify
+its standard error into a separate port).
+
 === Shell commands
 
 The commands below are all string-based.  This means you have to be
@@ -221,7 +256,7 @@ failed, an exception is raised. Otherwise the return status of the
 subprocess is returned unaltered.
 
 
-On a UNIX system, that value is the raw return value of waitpid(2), which contains signal, core dump and exit status.    It is 0 on success.  To pull out the signal number or exit status portably requires POSIX calls, but in a pinch you can use something like this: 
+On a UNIX system, that value is the raw return value of waitpid(2), which contains signal, core dump and exit status.    It is 0 on success.  To pull out the signal number or exit status portably requires POSIX calls, but in a pinch you can use something like this:
 
 <enscript highlight='scheme'>
 ;; Returns two values: #t if the process exited normally or #f otherwise;
diff --git a/posix-common.scm b/posix-common.scm
index 7ea2cf56..2935ff3c 100644
--- a/posix-common.scm
+++ b/posix-common.scm
@@ -677,6 +677,36 @@ EOF
 
 ;;; Processes
 
+(define children '())
+
+(define-record process
+  id returned-normally? input-port output-port error-port exit-status)
+
+(define (get-pid x #!optional default)
+  (cond ((fixnum? x) x)
+        ((process? x) (process-id x))
+        (else default)))
+
+(define (register-pid pid)
+  (let ((p (make-process pid #f #f #f #f #f)))
+    (set! children (cons (cons pid p) children))
+    p))
+
+(define (drop-child pid)
+  (set! children
+    (let rec ((cs children))
+       (cond ((null? cs) '())
+             ((eq? pid (caar cs)) (cdr cs))
+             (else (rec (cdr cs)))))))
+
+(set! chicken.process#process? process?)
+(set! chicken.process#process-id process-id)
+(set! chicken.process#process-exit-status process-exit-status)
+(set! chicken.process#process-returned-normally? process-returned-normally?)
+(set! chicken.process#process-input-port process-input-port)
+(set! chicken.process#process-output-port process-output-port)
+(set! chicken.process#process-error-port process-error-port)
+
 (set! chicken.process#process-sleep
   (lambda (n)
     (##sys#check-fixnum n 'process-sleep)
@@ -684,54 +714,60 @@ EOF
 
 (set! chicken.process#process-wait
   (lambda args
-    (let-optionals* args ((pid #f) (nohang #f))
-      (let ((pid (or pid -1)))
-        (##sys#check-fixnum pid 'process-wait)
-        (receive (epid enorm ecode) (process-wait-impl pid nohang)
-          (if (fx= epid -1)
-              (posix-error #:process-error 'process-wait "waiting for child process failed" pid)
-              (values epid enorm ecode) ) ) ) ) ) )
+    (let-optionals* args ((proc #f) (nohang #f))
+      (if (and proc (process? proc) (process-exit-status proc))
+          (values (process-id proc)
+                  (process-returned-normally? proc)
+                  (process-exit-status proc))
+          (let ((pid (get-pid proc -1)))
+            (##sys#check-fixnum pid 'process-wait)
+            (receive (epid enorm ecode) (process-wait-impl pid nohang)
+              (unless proc
+                (let ((a (assq pid children)))
+                  (when a
+                    (set! proc (cdr a))
+                    (drop-child pid))))
+              (when (process? proc)
+                (process-returned-normally?-set! proc enorm)
+                (process-exit-status-set! proc ecode))
+              (if (fx= epid -1)
+                  (posix-error #:process-error 'process-wait
+                               "waiting for child process failed" pid)
+                  (values epid enorm ecode) ) ) )) ) ) )
 
 ;; This can construct argv or envp for process-execute or process-run
 (define list->c-string-buffer
-  (let ((c-string->allocated-pointer
-	 (foreign-lambda* c-pointer ((scheme-object o))
-	   "char *ptr = C_malloc(C_header_size(o)); \n"
-	   "if (ptr != NULL) {\n"
-	   "  C_memcpy(ptr, C_data_pointer(o), C_header_size(o)); \n"
-	   "}\n"
-	   "C_return(ptr);")))
     (lambda (string-list convert loc)
       (##sys#check-list string-list loc)
 
       (let* ((string-count (##sys#length string-list))
-	     ;; NUL-terminated, so we must add one
-	     (buffer (make-pointer-vector (add1 string-count) #f)))
+             ;; NUL-terminated, so we must add one
+             (buffer (make-pointer-vector (add1 string-count) #f)))
 
-	(handle-exceptions exn
-	    ;; Free to avoid memory leak, then reraise
-	    (begin (free-c-string-buffer buffer) (signal exn))
+        (handle-exceptions exn
+            ;; Free to avoid memory leak, then reraise
+            (begin (free-c-string-buffer buffer) (signal exn))
 
-	  (do ((sl string-list (cdr sl))
-	       (i 0 (fx+ i 1)))
-	      ((or (null? sl) (fx= i string-count))) ; Should coincide
+          (do ((sl string-list (cdr sl))
+               (i 0 (fx+ i 1)))
+              ((or (null? sl) (fx= i string-count))) ; Should coincide
 
-	    (##sys#check-string (car sl) loc)
-	    ;; This avoids embedded NULs and appends a NUL, so "cs" is
-	    ;; safe to copy and use as-is in the pointer-vector.
-	    (let* ((cs (##sys#make-c-string (convert (car sl)) loc))
-		   (csp (c-string->allocated-pointer cs)))
-	      (unless csp (error loc "Out of memory"))
-	      (pointer-vector-set! buffer i csp)))
+            (##sys#check-string (car sl) loc)
+            ;; This avoids embedded NULs and appends a NUL, so "cs" is
+            ;; safe to copy and use as-is in the pointer-vector.
+            (let* ((cs (##sys#make-c-string (convert (car sl)) loc))
+                   (csp (c-string->allocated-pointer cs)))
+              (unless csp (error loc "Out of memory"))
+              (pointer-vector-set! buffer i csp)))
 
-	  buffer)))))
+          buffer))))
 
 (define (free-c-string-buffer buffer-array)
   (let ((size (pointer-vector-length buffer-array)))
     (do ((i 0 (fx+ i 1)))
-	((fx= i size))
+        ((fx= i size))
       (and-let* ((s (pointer-vector-ref buffer-array i)))
-	(free s)))))
+        (free s)))))
 
 ;; Environments are represented as string->string association lists
 (define (check-environment-list lst loc)
diff --git a/posix.scm b/posix.scm
index c49906bc..43069da4 100644
--- a/posix.scm
+++ b/posix.scm
@@ -187,7 +187,9 @@
    call-with-output-pipe close-input-pipe close-output-pipe create-pipe
    open-input-pipe open-output-pipe with-input-from-pipe
    with-output-to-pipe process process* process-sleep pipe/buf
-   spawn/overlay spawn/wait spawn/nowait spawn/nowaito spawn/detach)
+   spawn/overlay spawn/wait spawn/nowait spawn/nowaito spawn/detach
+   process? process-exit-status process-returned-normally? process-input-port
+   process-output-port process-error-port process-id)
 
 (import scheme chicken.base chicken.fixnum chicken.platform)
 
@@ -251,6 +253,14 @@
 (define process*)
 (define process-sleep)
 
+(define process?)
+(define process-exit-status)
+(define process-returned-normally?)
+(define process-input-port)
+(define process-output-port)
+(define process-error-port)
+(define process-id)
+
 (define pipe/buf)
 
 (define spawn/overlay)
diff --git a/posixunix.scm b/posixunix.scm
index 9f6fdb28..a3fe86b9 100644
--- a/posixunix.scm
+++ b/posixunix.scm
@@ -1092,24 +1092,39 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
 
 ;;; Process handling:
 
+(define c-string->allocated-pointer
+  (foreign-lambda* c-pointer ((scheme-object o))
+     "char *ptr = C_malloc(C_header_size(o)); \n"
+     "if (ptr != NULL) {\n"
+     "  C_memcpy(ptr, C_data_pointer(o), C_header_size(o)); \n"
+     "}\n"
+     "C_return(ptr);"))
+
 (set! chicken.process#process-fork
   (let ((fork (foreign-lambda int "C_fork")))
     (lambda (#!optional thunk killothers)
       ;; flush all stdio streams before fork
       ((foreign-lambda int "C_fflush" c-pointer) #f)
-      (let ((pid (fork)))
-	(when (fx= -1 pid)
-	  (posix-error #:process-error 'process-fork "cannot create child process"))
-	(if (and thunk (zero? pid))
-	    ((if killothers
-		 ##sys#kill-other-threads
-		 (lambda (thunk) (thunk)))
-	     (lambda ()
-	       (##sys#call-with-cthulhu
-		(lambda ()
-		  (thunk)
-		  (exit 0)))))
-	    pid)))))
+      (let ((pid (fork))
+            (maybe-kill-others (lambda (thunk)
+                                 (if killothers
+                                     (##sys#kill-other-threads thunk)
+                                     (thunk)))))
+        (when (fx= -1 pid)
+          (posix-error #:process-error 'process-fork "cannot create child process"))
+        (cond ((zero? pid)
+               ;; child
+               (cond (thunk
+                      (##sys#call-with-cthulhu
+                       (maybe-kill-others (lambda ()
+                                            (set! children '())
+                                            (thunk)
+                                            (exit 0)))))
+                     (else
+        	      (maybe-kill-others (lambda ()
+                                           (set! children '())
+                                           #f)))))
+              (else (register-pid pid)))))))
 
 (set! chicken.process#process-execute
   (lambda (filename #!optional (arglist '()) envlist _)
@@ -1143,11 +1158,14 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
 
 (set! chicken.process#process-signal
   (lambda (id . sig)
-    (let ((sig (if (pair? sig) (car sig) _sigterm)))
-      (##sys#check-fixnum id 'process-signal)
+    (let ((sig (if (pair? sig) (car sig) _sigterm))
+          (pid (if (process? id) (process-id id) id)))
+      (##sys#check-fixnum pid 'process-signal)
       (##sys#check-fixnum sig 'process-signal)
-      (let ((r (##core#inline "C_kill" id sig)))
-      (when (fx= r -1) (posix-error #:process-error 'process-signal "could not send signal to process" id sig) ) ) ) ) )
+      (let ((r (##core#inline "C_kill" pid sig)))
+      (when (fx= r -1)
+        (posix-error #:process-error 'process-signal
+          "could not send signal to process" id sig) ) ) ) ) )
 
 (define (shell-command loc)
   (or (get-environment-variable "SHELL") "/bin/sh") )
@@ -1158,13 +1176,13 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
 (set! chicken.process#process-run
   (lambda (f . args)
     (let ((args (if (pair? args) (car args) #f))
-	  (pid (chicken.process#process-fork)) )
-      (cond ((not (eq? 0 pid)) pid)
-	    (args (chicken.process#process-execute f args))
-	    (else
-	     (chicken.process#process-execute
-	      (shell-command 'process-run)
-	      (shell-command-arguments f)) ) ) ) ) )
+          (proc (chicken.process#process-fork)) )
+      (cond (proc)
+            (args (chicken.process#process-execute f args))
+            (else
+             (chicken.process#process-execute
+              (shell-command 'process-run)
+              (shell-command-arguments f)) ) ) ) ) )
 
 ;;; Run subprocess connected with pipes:
 
@@ -1187,24 +1205,28 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
 
 (define process-impl
   (let ((replace-fd
-	 (lambda (loc fd stdfd)
-	   (unless (fx= stdfd fd)
-	     (chicken.file.posix#duplicate-fileno fd stdfd)
-	     (chicken.file.posix#file-close fd) ) )) )
+         (lambda (loc fd stdfd)
+           (unless (fx= stdfd fd)
+             (chicken.file.posix#duplicate-fileno fd stdfd)
+             (chicken.file.posix#file-close fd) ) )) )
     (let ((make-on-close
-	   (lambda (loc pid clsvec idx idxa idxb)
-	     (lambda ()
-	       (vector-set! clsvec idx #t)
-	       (when (and (vector-ref clsvec idxa) (vector-ref clsvec idxb))
-		 (receive (_ flg cod) (process-wait-impl pid #f)
-		   (unless flg
-		     (##sys#signal-hook #:process-error loc
-					"abnormal process exit" pid cod)) ) ) ) ))
-	  (needed-pipe
-	   (lambda (loc port)
-	     (and port
-		  (receive (i o) (chicken.process#create-pipe)
-		    (cons i o))) ))
+           (lambda (loc pid clsvec idx idxa idxb)
+             (lambda ()
+               (vector-set! clsvec idx #t)
+               (when (and (vector-ref clsvec idxa) (vector-ref clsvec idxb))
+                 (receive (_ flg cod) (process-wait-impl pid #f)
+                   (and-let* ((a (assq pid children)))
+                     (process-returned-normally?-set! (cdr a) flg)
+                     (process-exit-status-set! (cdr a) flg)
+                     (drop-child pid))
+                   (unless flg
+                     (##sys#signal-hook #:process-error loc
+                                        "abnormal process exit" pid cod)) ) ) ) ))
+          (needed-pipe
+           (lambda (loc port)
+             (and port
+                  (receive (i o) (chicken.process#create-pipe)
+                    (cons i o))) ))
         [connect-parent
           (lambda (loc pipe port fd)
             (and port
@@ -1244,56 +1266,53 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
               (and-let* ([fd (connect-parent loc pipe stdf stdfd)])
                 (##sys#custom-output-port loc cmd fd #t DEFAULT-OUTPUT-BUFFER-SIZE on-close enc) ) )] )
         (lambda (loc cmd args env stdoutf stdinf stderrf enc)
-          (receive [inpipe outpipe errpipe pid]
+          (receive [inpipe outpipe errpipe proc]
                      (spawn loc cmd args env stdoutf stdinf stderrf)
             ;When shared assume already "closed", since only created ports
             ;should be explicitly closed, and when one is closed we want
             ;to wait.
-            (let ((clsvec (vector (not stdinf) (not stdoutf) (not stderrf))))
-              (values
-	       (input-port loc pid cmd inpipe stdinf
-			   chicken.file.posix#fileno/stdin
-			   (make-on-close loc pid clsvec 0 1 2)
-                           enc)
-	       (output-port loc pid cmd outpipe stdoutf
-			    chicken.file.posix#fileno/stdout
-			    (make-on-close loc pid clsvec 1 0 2)
-                            enc)
-	       pid
-	       (input-port loc pid cmd errpipe stderrf
-			   chicken.file.posix#fileno/stderr
-			   (make-on-close loc pid clsvec 2 0 1)
-                           enc) ) ) ) ) ) ) ) )
+            (let ((clsvec (vector (not stdinf) (not stdoutf) (not stderrf)))
+                  (pid (process-id proc)))
+              (process-output-port-set! proc
+                (input-port loc pid cmd inpipe stdinf
+                            chicken.file.posix#fileno/stdin
+                            (make-on-close loc pid clsvec 0 1 2)
+                            enc))
+              (process-input-port-set! proc
+                (output-port loc pid cmd outpipe stdoutf
+                             chicken.file.posix#fileno/stdout
+                             (make-on-close loc pid clsvec 1 0 2)
+                             enc))
+              (process-error-port-set! proc
+                (input-port loc pid cmd errpipe stderrf
+                            chicken.file.posix#fileno/stderr
+                            (make-on-close loc pid clsvec 2 0 1)
+                            enc) )
+              proc) ) ) ) ) ) )
 
 ;;; Run subprocess connected with pipes:
 
 ;; TODO: See if this can be moved to posix-common
 (let ((%process
-        (lambda (loc err? cmd args env enc k)
+        (lambda (loc err? cmd args env enc)
           (let ((chkstrlst
-		 (lambda (lst)
-		   (##sys#check-list lst loc)
-		   (for-each (cut ##sys#check-string <> loc) lst) )))
+                 (lambda (lst)
+                   (##sys#check-list lst loc)
+                   (for-each (cut ##sys#check-string <> loc) lst) )))
             (##sys#check-string cmd loc)
             (if args
                 (chkstrlst args)
                 (begin
                   (set! args (shell-command-arguments cmd))
                   (set! cmd (shell-command loc)) ) )
-	    (when env (check-environment-list env loc))
-	    (##sys#call-with-values
-	     (lambda () (process-impl loc cmd args env #t #t err? enc))
-	     k)))))
+            (when env (check-environment-list env loc))
+            (process-impl loc cmd args env #t #t err? enc)))))
   (set! chicken.process#process
     (lambda (cmd #!optional args env (enc 'utf-8) exactf)
-      (%process
-       'process #f cmd args env enc
-       (lambda (i o p e) (values i o p)))))
+      (%process 'process #f cmd args env enc)))
   (set! chicken.process#process*
     (lambda (cmd #!optional args env (enc 'utf-8) exactf)
-      (%process
-       'process* #t cmd args env enc
-       values))))
+      (%process 'process* #t cmd args env enc))))
 
 
 ;;; chroot:
diff --git a/posixwin.scm b/posixwin.scm
index 41616c65..3d5e0f9d 100644
--- a/posixwin.scm
+++ b/posixwin.scm
@@ -107,30 +107,23 @@ static C_char C_username[255 + 1] = "";
 #define open_text_input_pipe(a, n, name)     open_binary_input_pipe(a, n, name)
 #define open_binary_output_pipe(a, n, name)  C_mpointer(a, _wpopen(C_OS_FILENAME(name, 0), L"w"))
 #define open_text_output_pipe(a, n, name)    open_binary_output_pipe(a, n, name)
-#define close_pipe(p)			     C_fix(_pclose(C_port_file(p)))
+#define close_pipe(p)                        C_fix(_pclose(C_port_file(p)))
 
-#define C_chmod(fn, m)	    C_fix(_wchmod(C_OS_FILENAME(fn, 0), C_unfix(m)))
-#define C_pipe(d, m)	    C_fix(_pipe(C_pipefds, PIPE_BUF, C_unfix(m)))
-#define C_close(fd)	    C_fix(close(C_unfix(fd)))
+#define C_chmod(fn, m)      C_fix(_wchmod(C_OS_FILENAME(fn, 0), C_unfix(m)))
+#define C_pipe(d, m)        C_fix(_pipe(C_pipefds, PIPE_BUF, C_unfix(m)))
+#define C_close(fd)         C_fix(close(C_unfix(fd)))
 
 #define C_u_i_lstat(fn)     C_u_i_stat(fn)
 
-#define C_u_i_execvp(f, a) C_fix(execvp(C_c_string(f), (void *)C_c_pointer_vector_or_null(a)))
-#define C_u_i_execve(f,a,e) C_fix(execve(C_c_string(f), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
-
-/* MS replacement for the fork-exec pair */
-#define C_u_i_spawnvp(m,f,a)    C_fix(spawnvp(C_unfix(m), C_c_string(f), (void *)C_c_pointer_vector_or_null(a)))
-#define C_u_i_spawnvpe(m,f,a,e) C_fix(spawnvpe(C_unfix(m), C_c_string(f), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
-
 #define C_open(fn, fl, m)   C_fix(_wopen(C_OS_FILENAME(fn, 0), C_unfix(fl), C_unfix(m)))
 #define C_read(fd, b, n)    C_fix(read(C_unfix(fd), C_data_pointer(b), C_unfix(n)))
 #define C_write(fd, b, n)   C_fix(write(C_unfix(fd), C_data_pointer(b), C_unfix(n)))
 
-#define C_flushall()	    C_fix(_flushall())
+#define C_flushall()        C_fix(_flushall())
 
 #define C_umask(m)          C_fix(_umask(C_unfix(m)))
 
-#define C_ctime(n)	    (C_secs = (n), ctime(&C_secs))
+#define C_ctime(n)          (C_secs = (n), ctime(&C_secs))
 
 #define TIME_STRING_MAXLENGTH 255
 static char C_time_string [TIME_STRING_MAXLENGTH + 1];
@@ -305,14 +298,14 @@ C_windows_nt()
 static int
 get_shlcmd()
 {
-        static wchar_t buf[ 255 ];
+    static wchar_t buf[ 255 ];
     /* Do we need to build the shell command pathname? */
     if (!strlen(C_shlcmd))
     {
       char *cmdnam = C_windows_nt() ? "\\cmd.exe" : "\\command.com";
-      UINT len = GetSystemDirectoryW(buf, sizeof(buf) - strlen(cmdnam));
+      UINT len = GetSystemDirectoryW(buf, sizeof(buf));
       if (len)
-        C_strlcpy(C_shlcmd + len, cmdnam, sizeof(C_shlcmd));
+        C_strlcpy(C_shlcmd + len, C_utf8(buf), sizeof(C_shlcmd));
       else
         return set_last_errno();
     }
@@ -328,7 +321,7 @@ get_shlcmd()
 static int
 get_user_name()
 {
-        static wchar_t buf[ 255 ];
+    static wchar_t buf[ 255 ];
     if (!C_strlen(C_username))
     {
         DWORD bufCharCount = sizeof(buf) / sizeof(buf[0]);
@@ -344,36 +337,37 @@ get_user_name()
 /*
     Spawn a process directly.
     Params:
-    app		Command to execute.
-    cmdlin	Command line (arguments).
-    env		Environment for the new process (may be NULL).
+    app         Command to execute.
+    cmdlin      Command line (arguments).
+    env         Environment for the new process (may be NULL).
     handle, stdin, stdout, stderr
-		Spawned process info are returned in integers.
-		When spawned process shares standard io stream with the parent
-		process the respective value in handle, stdin, stdout, stderr
-		is -1.
-    params	A bitmask controling operation.
-		Bit 1: Child & parent share standard input if this bit is set.
-		Bit 2: Share standard output if bit is set.
-		Bit 3: Share standard error if bit is set.
-
-    Returns: zero return value indicates failure.
+                Spawned process info are returned in integers.
+                When spawned process shares standard io stream with the parent
+                process the respective value in handle, stdin, stdout, stderr
+                is -1.
+    params      A bitmask controling operation.
+                Bit 1: Child & parent share standard input if this bit is set.
+                Bit 2: Share standard output if bit is set.
+                Bit 3: Share standard error if bit is set.
+
+    Returns: pid, zero return value indicates failure.
 */
-static int
+static DWORD
 C_process(const char *app, const char *cmdlin, const char **env,
-	  int *phandle, int *pstdin_fd, int *pstdout_fd, int *pstderr_fd,
-	  int params)
+          int *phandle, int *pstdin_fd, int *pstdout_fd, int *pstderr_fd,
+          int params)
 {
     int i;
     int success = TRUE;
+    DWORD pid;
     const int f_share_io[3] = { params & 1, params & 2, params & 4};
     int io_fds[3] = { -1, -1, -1 };
     HANDLE
-	child_io_handles[3] = { NULL, NULL, NULL },
-	standard_io_handles[3] = {
-	    GetStdHandle(STD_INPUT_HANDLE),
-	    GetStdHandle(STD_OUTPUT_HANDLE),
-	    GetStdHandle(STD_ERROR_HANDLE)};
+        child_io_handles[3] = { NULL, NULL, NULL },
+        standard_io_handles[3] = {
+            GetStdHandle(STD_INPUT_HANDLE),
+            GetStdHandle(STD_OUTPUT_HANDLE),
+            GetStdHandle(STD_ERROR_HANDLE)};
     const char modes[3] = "rww";
     HANDLE cur_process = GetCurrentProcess(), child_process = NULL;
     void* envblk = NULL;
@@ -411,24 +405,26 @@ C_process(const char *app, const char *cmdlin, const char **env,
 
     if (env && success)
     {
-	char** p;
-	int len = 0;
-
-	for (p = env; *p; ++p) len += strlen(*p) + 1;
-
-	if (envblk = C_malloc(len + 1))
-	{
-	    char* pb = (char*)envblk;
-	    for (p = env; *p; ++p)
-	    {
-		C_strlcpy(pb, *p, len+1);
-		pb += strlen(*p) + 1;
-	    }
-	    *pb = '\0';
+        char** p;
+        int len = 0;
+
+        for (p = env; *p; ++p) len += strlen(*p) + 1;
+
+        if (envblk = C_malloc((len + 1) * sizeof(wchar_t));
+        {
+            wchar_t* pb = (wchar_t*)envblk;
+            for (p = env; *p; ++p)
+            {
+            	wchar_t *u = C_utf16(*p, 0);
+            	int n = wcslen(*u);
+                C_memcpy(pb, *u, n + 1);
+                pb += n + 1;
+            }
+            *pb = '\0';
             /* This _should_ already have been checked for embedded NUL bytes */
-	}
-	else
-	    success = FALSE;
+        }
+        else
+            success = FALSE;
     }
 #endif
 
@@ -436,31 +432,32 @@ C_process(const char *app, const char *cmdlin, const char **env,
 
     if (success)
     {
-	PROCESS_INFORMATION pi;
-	STARTUPINFO si;
-
-	ZeroMemory(&pi,sizeof pi);
-	ZeroMemory(&si,sizeof si);
-	si.cb = sizeof si;
-	si.dwFlags = STARTF_USESTDHANDLES;
-	si.hStdInput = child_io_handles[0];
-	si.hStdOutput = child_io_handles[1];
-	si.hStdError = child_io_handles[2];
-
-	/* FIXME passing 'app' param causes failure & possible stack corruption */
-	success = CreateProcess(
-	    NULL, (char*)cmdlin, NULL, NULL, TRUE, 0, envblk, NULL, &si, &pi);
-
-	if (success)
-	{
-	    child_process=pi.hProcess;
-	    CloseHandle(pi.hThread);
-	}
-	else
-	    set_last_errno();
+        PROCESS_INFORMATION pi;
+        STARTUPINFO si;
+
+        ZeroMemory(&pi,sizeof pi);
+        ZeroMemory(&si,sizeof si);
+        si.cb = sizeof si;
+        si.dwFlags = STARTF_USESTDHANDLES;
+        si.hStdInput = child_io_handles[0];
+        si.hStdOutput = child_io_handles[1];
+        si.hStdError = child_io_handles[2];
+
+        /* FIXME passing 'app' param causes failure & possible stack corruption */
+        success = CreateProcessW(
+            NULL, C_utf16(cmdlin, 0), NULL, NULL, TRUE, 0, envblk, NULL, &si, &pi);
+
+        if (success)
+        {
+            child_process=pi.hProcess;
+            CloseHandle(pi.hThread);
+            pid = pi.dwProcessId;
+        }
+        else
+            set_last_errno();
     }
     else
-	set_last_errno();
+        set_last_errno();
 
     /****** cleanup & return *********/
 
@@ -513,6 +510,13 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
   return _wutime(fn, &tb);
 }
 
+#define C_u_i_execvp(f, a) C_fix(_wexecvp(C_c_string(f), (void *)C_c_pointer_vector_or_null(a)))
+#define C_u_i_execve(f,a,e) C_fix(_wexecve(C_c_string(f), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
+
+/* MS replacement for the fork-exec pair */
+#define C_u_i_spawnvp(m,f,a)    C_fix(_wspawnvp(C_unfix(m), C_c_string(f), (void *)C_c_pointer_vector_or_null(a)))
+#define C_u_i_spawnvpe(m,f,a,e) C_fix(_wspawnvpe(C_unfix(m), C_c_string(f), (void *)C_c_pointer_vector_or_null(a), (void *)C_c_pointer_vector_or_null(e)))
+
 <#
 
 (import (only chicken.string string-intersperse))
@@ -717,18 +721,27 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
 ; string-quote such arguments.
 (define quote-arg-string
   (let ((needs-quoting?
-	 ;; This is essentially (string-any char-whitespace? s) but we
-	 ;; don't want a SRFI-13 dependency. (Do we?)
-	 (lambda (s)
-	   (let ((len (string-length s)))
-	     (let loop ((i 0))
-	       (cond
-		((fx= i len) #f)
-		((char-whitespace? (string-ref s i)) #t)
-		(else (loop (fx+ i 1)))))))))
+         ;; This is essentially (string-any char-whitespace? s) but we
+         ;; don't want a SRFI-13 dependency. (Do we?)
+         (lambda (s)
+           (let ((len (string-length s)))
+             (let loop ((i 0))
+               (cond
+                ((fx= i len) #f)
+                ((char-whitespace? (string-ref s i)) #t)
+                (else (loop (fx+ i 1)))))))))
     (lambda (str)
       (if (needs-quoting? str) (string-append "\"" str "\"") str))))
 
+(define c-string->allocated-pointer
+  (foreign-lambda* c-pointer ((scheme-object o))
+     "char *ptr = C_malloc(C_header_size(o) * sizeof(wchar_t)); \n"
+     "if (ptr != NULL) {\n"
+     "  wchar_t *u = C_utf16(C_data_pointer(o), 0); \n"
+     "  C_memcpy(ptr, u, wcslen(u) + 1); \n"
+     "}\n"
+     "C_return(ptr);"))
+
 (set! chicken.process#process-execute
   (lambda (filename #!optional (arglist '()) envlist exactf)
     (let ((argconv (if exactf (lambda (x) x) quote-arg-string)))
@@ -749,13 +762,14 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
       (call-with-exec-args
        'process-spawn filename argconv arglist envlist
        (lambda (prg argbuf envbuf)
-	 (##core#inline "C_flushall")
-	 (let ((r (if envbuf
-		      (##core#inline "C_u_i_spawnvpe" mode prg argbuf envbuf)
-		      (##core#inline "C_u_i_spawnvp" mode prg argbuf))))
-	   (when (fx= r -1)
-	     (posix-error #:process-error 'process-spawn "cannot spawn process" filename))
-	   r))))))
+         (##core#inline "C_flushall")
+         (let ((r (if envbuf
+                      (##core#inline "C_u_i_spawnvpe" mode prg argbuf envbuf)
+                      (##core#inline "C_u_i_spawnvp" mode prg argbuf))))
+           (if (fx= r -1)
+               (posix-error #:process-error 'process-spawn
+                            "cannot spawn process" filename)
+               (register-pid r))))))))
 
 (define-foreign-variable _shlcmd c-string "C_shlcmd")
 
@@ -808,62 +822,60 @@ static int set_file_mtime(C_word filename, C_word atime, C_word mtime)
     ; For now any environment is ignored.
     (lambda (loc cmd args env stdoutf stdinf stderrf exactf enc)
       (let* ((arglist (cons cmd args))
-	     (cmdlin (string-intersperse
-		      (if exactf
-			  arglist
-			  (map quote-arg-string arglist)))))
-	(let-location ([handle int -1]
-		       [stdin_fd int -1] [stdout_fd int -1] [stderr_fd int -1])
-	  (let ([res
-		  (c-process cmd cmdlin #f
-		    (location handle)
-		    (location stdin_fd) (location stdout_fd) (location stderr_fd)
-		    (+ (if stdinf 0 1) (if stdoutf 0 2) (if stderrf 0 4)))])
-	    (if res
-	      (values
-	       (and stdoutf (chicken.file.posix#open-input-file*
-			     stdout_fd)) ;Parent stdin
-	       (and stdinf (chicken.file.posix#open-output-file*
-			    stdin_fd))  ;Parent stdout
-	       handle
-	       (and stderrf (chicken.file.posix#open-input-file*
-			     stderr_fd)))
+             (cmdlin (string-intersperse
+                      (if exactf
+                          arglist
+                          (map quote-arg-string arglist)))))
+        (let-location ([handle int -1]
+                       [stdin_fd int -1] [stdout_fd int -1] [stderr_fd int -1])
+          (let ([res
+                  (c-process cmd cmdlin #f
+                    (location handle)
+                    (location stdin_fd) (location stdout_fd) (location stderr_fd)
+                    (+ (if stdinf 0 1) (if stdoutf 0 2) (if stderrf 0 4)))])
+            (if (integer? res)
+              (make-process
+               res #f
+               (and stdoutf (chicken.file.posix#open-input-file*
+                             stdout_fd)) ;Parent stdin
+               (and stdinf (chicken.file.posix#open-output-file*
+                            stdin_fd))  ;Parent stdout
+               handle
+               (and stderrf (chicken.file.posix#open-input-file*
+                             stderr_fd)
+               #f))
               (##sys#signal-hook/errno
                #:process-error (##sys#update-errno) loc "cannot execute process" cmdlin))))))))
 
 ;; TODO: See if this can be moved to posix-common
 (let ((%process
-	(lambda (loc err? cmd args env exactf enc)
-	  (let ((chkstrlst
-		 (lambda (lst)
-		   (##sys#check-list lst loc)
-		   (for-each (cut ##sys#check-string <> loc) lst) )))
-	    (##sys#check-string cmd loc)
-	    (if args
-	      (chkstrlst args)
-	      (begin
-		(set! exactf #t)
-		(set! args (shell-command-arguments cmd))
-		(set! cmd (shell-command loc)) ) )
-	    (when env (check-environment-list env loc))
-	    (receive (in out pid err)
-		(process-impl loc cmd args env #t #t err? exactf enc)
-	      (if err?
-		(values in out pid err)
-		(values in out pid) ) ) ) )) )
+        (lambda (loc cmd args env exactf enc)
+          (let ((chkstrlst
+                 (lambda (lst)
+                   (##sys#check-list lst loc)
+                   (for-each (cut ##sys#check-string <> loc) lst) )))
+            (##sys#check-string cmd loc)
+            (if args
+              (chkstrlst args)
+              (begin
+                (set! exactf #t)
+                (set! args (shell-command-arguments cmd))
+                (set! cmd (shell-command loc)) ) )
+            (when env (check-environment-list env loc))
+            (process-impl loc cmd args env #t #t err? exactf enc)))))
   (set! chicken.process#process
     (lambda (cmd #!optional args env (enc 'utf-8) exactf)
-      (%process 'process #f cmd args env exactf enc) ))
+      (%process 'process cmd args env exactf enc) ))
   (set! chicken.process#process*
     (lambda (cmd #!optional args env (enc 'utf-8) exactf)
-      (%process 'process* #t cmd args env exactf enc) )) )
+      (%process 'process* cmd args env exactf enc) )) )
 
 (define-foreign-variable _exstatus int "C_exstatus")
 
 (define (process-wait-impl pid nohang)
-  (if (##core#inline "C_process_wait" pid nohang)
-    (values pid #t _exstatus)
-    (values -1 #f #f) ) )
+  (cond ((##core#inline "C_process_wait" pid nohang)
+          (values pid #t _exstatus))
+        (else (values -1 #f #f) ) ))
 
 
 ;;; Getting group- and user-information:
diff --git a/tests/posix-tests.scm b/tests/posix-tests.scm
index dccd483f..0aa3c7a3 100644
--- a/tests/posix-tests.scm
+++ b/tests/posix-tests.scm
@@ -41,17 +41,15 @@
 (assert-error (process-execute "false" '("1" "123\x00;456")))
 (assert-error (process-execute "false" '("123\x00;456") '(("foo\x00;bar" . "blabla") '("lalala" . "qux\x00;mooh"))))
 
-(receive (in out pid)
-    (process csi-path '("-n" "-I" ".." "-e"
-                        "(write 'err (current-error-port)) (write 'ok)"))
-  (assert (equal? 'ok (read in)))
+(let ((p (process csi-path '("-n" "-I" ".." "-e"
+                        "(write 'err (current-error-port)) (write 'ok)"))))
+  (assert (equal? 'ok (read (process-output-port p))))
   (newline (current-error-port)))
 
-(receive (in out pid err)
-    (process* csi-path '("-n" "-I" ".." "-e"
-                         "(write 'err (current-error-port)) (write 'ok)"))
-  (assert (equal? 'ok (read in)))
-  (assert (equal? 'err (read err))))
+(let ((p (process* csi-path '("-n" "-I" ".." "-e"
+                         "(write 'err (current-error-port)) (write 'ok)"))))
+  (assert (equal? 'ok (read (process-output-port p))))
+  (assert (equal? 'err (read (process-error-port p)))))
 
 ;; delete-directory
 (let* ((t (create-temporary-directory))
diff --git a/types.db b/types.db
index 3d10f564..79e5071f 100644
--- a/types.db
+++ b/types.db
@@ -2168,18 +2168,25 @@
 
 (chicken.process#process-execute
  (#(procedure #:clean #:enforce) chicken.process#process-execute (string #!optional (list-of string) (list-of (pair string string)) boolean) noreturn))
-(chicken.process#process-fork (#(procedure #:enforce) chicken.process#process-fork (#!optional (or (procedure () . *) false) *) fixnum))
+(chicken.process#process-fork (#(procedure #:enforce) chicken.process#process-fork (#!optional (or (procedure () . *) false) *) (or (struct process) boolean)))
 (chicken.process#qs (#(procedure #:clean #:enforce) chicken.process#qs (string #!optional symbol) string))
-(chicken.process#process-run (#(procedure #:clean #:enforce) chicken.process#process-run (string #!optional (list-of string)) fixnum))
-(chicken.process#process-signal (#(procedure #:clean #:enforce) chicken.process#process-signal (fixnum #!optional fixnum) undefined))
+(chicken.process#process-run (#(procedure #:clean #:enforce) chicken.process#process-run (string #!optional (list-of string)) (struct process)))
+(chicken.process#process-signal (#(procedure #:clean #:enforce) chicken.process#process-signal ((or (struct process) fixnum) #!optional fixnum) undefined))
 (chicken.process#process-spawn
- (#(procedure #:clean #:enforce) chicken.process#process-spawn (fixnum string #!optional (list-of string) (list-of (pair string string)) boolean) fixnum))
+ (#(procedure #:clean #:enforce) chicken.process#process-spawn (fixnum string #!optional (list-of string) (list-of (pair string string)) boolean) (struct process)))
 (chicken.process#system (#(procedure #:clean #:enforce) chicken.process#system (string) fixnum))
 (chicken.process#system* (#(procedure #:clean #:enforce) chicken.process#system* (string) undefined))
-(chicken.process#process (#(procedure #:clean #:enforce) chicken.process#process (string #!optional (list-of string) (list-of (pair string string)) symbol boolean) input-port output-port fixnum))
-(chicken.process#process* (#(procedure #:clean #:enforce) chicken.process#process* (string #!optional (list-of string) (list-of (pair string string)) symbol boolean) input-port output-port fixnum))
-(chicken.process#process-wait (#(procedure #:clean #:enforce) chicken.process#process-wait (#!optional fixnum *) fixnum fixnum fixnum))
+(chicken.process#process (#(procedure #:clean #:enforce) chicken.process#process (string #!optional (list-of string) (list-of (pair string string)) symbol boolean) (struct process)))
+(chicken.process#process* (#(procedure #:clean #:enforce) chicken.process#process* (string #!optional (list-of string) (list-of (pair string string)) symbol boolean) (struct process)))
+(chicken.process#process-wait (#(procedure #:clean #:enforce) chicken.process#process-wait (#!optional (or (struct process) fixnum) *) fixnum fixnum fixnum))
 (chicken.process#process-sleep (#(procedure #:clean #:enforce) chicken.process#process-sleep (fixnum) fixnum))
+(chicken.process#process-exit-status (#(procedure #:clean #:enforce) chicken.process#process-exit-status ((struct process)) *))
+(chicken.process#process-input-port (#(procedure #:clean #:enforce) chicken.process#process-input-port ((struct process)) output-port))
+(chicken.process#process-output-port (#(procedure #:clean #:enforce) chicken.process#process-output-port ((struct process)) input-port))
+(chicken.process#process-error-port (#(procedure #:clean #:enforce) chicken.process#process-error-port ((struct process)) input-port))
+(chicken.process#process-id (#(procedure #:clean #:enforce) chicken.process#process-id ((struct process)) fixnum))
+(chicken.process#process-returned-normally? (#(procedure #:clean #:enforce) chicken.process#process-returned-normally? ((struct process)) boolean))
+(chicken.process#process? (#(procedure #:clean #:enforce) chicken.process#process? (*) boolean))
 (chicken.process#call-with-input-pipe (#(procedure #:enforce) chicken.process#call-with-input-pipe (string (procedure (input-port) . *) #!optional keyword) . *))
 (chicken.process#call-with-output-pipe (#(procedure #:enforce) chicken.process#call-with-output-pipe (string (procedure (input-port) . *) #!optional keyword) . *))
 (chicken.process#close-input-pipe (#(procedure #:clean #:enforce) chicken.process#close-input-pipe (input-port) fixnum))
Trap