/[cmucl]/src/code/run-program.lisp
ViewVC logotype

Contents of /src/code/run-program.lisp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (show annotations)
Thu Feb 22 10:40:31 1990 UTC (24 years, 2 months ago) by ram
Branch: MAIN
Initial revision
1 ;;; -*- Package: Extensions; Log: code.log -*-
2 ;;;
3 ;;; **********************************************************************
4 ;;; This code was written as part of the Spice Lisp project at
5 ;;; Carnegie-Mellon University, and has been placed in the public domain.
6 ;;; If you want to use this code or any part of Spice Lisp, please contact
7 ;;; Scott Fahlman (FAHLMAN@CMUC).
8 ;;; **********************************************************************
9 ;;;
10 ;;; RUN-PROGRAM and friends. Factilty for running unix programs from inside
11 ;;; a lisp.
12 ;;;
13 ;;; Written by Jim Healy and Bill Chiles, November 1987, using an earlier
14 ;;; version written by David McDonald.
15 ;;;
16 ;;; Completely re-written by William Lott, July 1989 - January 1990.
17 ;;;
18
19 (in-package "EXTENSIONS")
20
21 (export '(run-program process-status process-exit-code process-core-dumped
22 process-wait process-kill process-input process-output process-plist
23 process-pty process-error process-status-hook process-alive-p
24 process-close process-pid process-p))
25
26
27 ;;;; Import WAIT3 from unix.
28
29 (ext:def-c-pointer *wait (unsigned-byte 32))
30
31 (ext:def-c-routine ("wait3" c-wait3)
32 (int)
33 (status *wait :out)
34 (options int)
35 (rusage int))
36
37 (eval-when (load eval compile)
38 (defconstant wait-wstopped #o177)
39 (defconstant wait-wnohang 1)
40 (defconstant wait-wuntraced 2))
41
42 (defun wait3 (&optional do-not-hang check-for-stopped)
43 "Return any available status information on child processed. "
44 (multiple-value-bind (pid status)
45 (c-wait3 (logior (if do-not-hang
46 wait-wnohang
47 0)
48 (if check-for-stopped
49 wait-wuntraced
50 0))
51 0)
52 (cond ((or (minusp pid)
53 (zerop pid))
54 nil)
55 ((eql (ldb (byte 8 0) status)
56 wait-wstopped)
57 (values pid
58 :stopped
59 (ldb (byte 8 8) status)))
60 ((zerop (ldb (byte 7 0) status))
61 (values pid
62 :exited
63 (ldb (byte 8 8) status)))
64 (t
65 (let ((signal (ldb (byte 7 0) status)))
66 (values pid
67 (if (or (eql signal mach:sigstop)
68 (eql signal mach:sigtstp)
69 (eql signal mach:sigttin)
70 (eql signal mach:sigttou))
71 :stopped
72 :signaled)
73 signal
74 (not (zerop (ldb (byte 1 7) status)))))))))
75
76
77
78 ;;;; Process control stuff.
79
80 (defvar *active-processes* nil
81 "List of process structures for all active processes.")
82
83 (defstruct (process (:print-function %print-process))
84 pid ; PID of child process.
85 %status ; Either :RUNNING, :STOPPED, :EXITED, or :SIGNALED.
86 exit-code ; Either exit code or signal
87 core-dumped ; T if a core image was dumped.
88 pty ; Stream to child's pty or nil.
89 input ; Stream to child's input or nil.
90 output ; Stream from child's output or nil.
91 error ; Stream from child's error output or nil.
92 status-hook ; Closure to call when PROC changes status.
93 plist ; Place for clients to stash tings.
94 )
95
96 (defun %print-process (proc stream depth)
97 (declare (ignore depth))
98 (format stream "#<process ~D ~S>"
99 (process-pid proc)
100 (process-status proc)))
101
102 ;;; PROCESS-STATUS -- Public.
103 ;;;
104 (defun process-status (proc)
105 "Return the current status of process. The result is one of :running,
106 :stopped, :exited, :signaled."
107 (get-processes-status-changes)
108 (process-%status proc))
109
110
111 ;;; PROCESS-WAIT -- Public.
112 ;;;
113 (defun process-wait (proc &optional check-for-stopped)
114 "Wait for PROC to quit running for some reason. Returns PROC."
115 (loop
116 (unless (or (eq (process-status proc) :running)
117 (and check-for-stopped
118 (eq (process-status proc) :stopped)))
119 (return))
120 (system:serve-all-events 1))
121 proc)
122
123 ;;; FIND-CURRENT-FOREGROUND-PROCESS -- internal
124 ;;;
125 ;;; Finds the current foreground process group id.
126 ;;;
127 (defun find-current-foreground-process (proc)
128 (mach:with-trap-arg-block mach::int1 pgrp
129 (multiple-value-bind
130 (wonp error)
131 (mach:unix-ioctl (system:fd-stream-fd (ext:process-pty proc))
132 mach:TIOCGPGRP
133 (lisp::alien-value-sap mach::int1))
134 (unless wonp
135 (error "TIOCPGRP ioctl failed: ~S"
136 (mach:get-unix-error-msg error)))
137 (system:alien-access (mach::int1-int (system:alien-value pgrp))))))
138
139 ;;; PROCESS-KILL -- public
140 ;;;
141 ;;; Hand a process a signal.
142 ;;;
143 (defun process-kill (proc signal &optional (whom :pid))
144 "Hand SIGNAL to PROC. If whom is :pid, use the kill Unix system call. If
145 whom is :process-group, use the killpg Unix system call. If whom is
146 :pty-process-group deliver the signal to whichever process group is currently
147 in the foreground."
148 (let ((pid (ecase whom
149 ((:pid :process-group)
150 (process-pid proc))
151 (:pty-process-group
152 (find-current-foreground-process proc)))))
153 (multiple-value-bind (okay errno)
154 (if (eq whom :pty-process-group)
155 (mach:unix-killpg pid signal)
156 (mach:unix-kill pid signal))
157 (cond ((not okay)
158 (values nil errno))
159 ((and (eql pid (process-pid proc))
160 (= (unix-signal-number signal) mach:sigcont))
161 (setf (process-%status proc) :running)
162 (setf (process-exit-code proc) nil)
163 (when (process-status-hook proc)
164 (funcall (process-status-hook proc) proc))
165 t)
166 (t
167 t)))))
168
169 ;;; PROCESS-ALIVE-P -- public
170 ;;;
171 ;;; Returns T if the process is still alive, NIL otherwise.
172 ;;;
173 (defun process-alive-p (proc)
174 "Returns T if the process is still alive, NIL otherwise."
175 (let ((status (process-status proc)))
176 (if (or (eq status :running)
177 (eq status :stopped))
178 t
179 nil)))
180
181 ;;; PROCESS-CLOSE -- public
182 ;;;
183 ;;; Close all the streams held open by PROC.
184 ;;;
185 (defun process-close (proc)
186 "Close all streams connected to PROC and stop maintaining the status slot."
187 (macrolet ((frob (stream)
188 `(when ,stream (close ,stream))))
189 (frob (process-pty proc))
190 (frob (process-input proc))
191 (frob (process-output proc))
192 (frob (process-error proc))
193 (system:without-interrupts
194 (setf *active-processes* (delete proc *active-processes*)))
195 proc))
196
197 ;;; SIGCHLD-HANDLER -- Internal.
198 ;;;
199 ;;; This should be the handler for sigchld signals that RUN-PROGRAM establishes.
200 ;;;
201 ;;; Since interrupts are so broken in the current RT system, we don't turn on
202 ;;; sigchld signals for now. We call this by hand whenever we need to check
203 ;;; the status of a process.
204 ;;;
205 (defun sigchld-handler (ignore1 ignore2 ignore3)
206 (declare (ignore ignore1 ignore2 ignore3))
207 (get-processes-status-changes))
208
209 ;;; GET-PROCESSES-STATUS-CHANGES -- Internal.
210 ;;;
211 (defun get-processes-status-changes ()
212 (loop
213 (multiple-value-bind (pid what code core)
214 (wait3 t t)
215 (unless pid
216 (return))
217 (let ((proc (find pid *active-processes* :key #'process-pid)))
218 (when proc
219 (setf (process-%status proc) what)
220 (setf (process-exit-code proc) code)
221 (setf (process-core-dumped proc) core)
222 (when (process-status-hook proc)
223 (funcall (process-status-hook proc) proc))
224 (when (or (eq what :exited)
225 (eq what :signaled))
226 (system:without-interrupts
227 (setf *active-processes*
228 (delete proc *active-processes*)))))))))
229
230
231
232 ;;;; RUN-PROGRAM and close friends.
233
234 (defvar *close-on-error* nil
235 "List of file descriptors to close when RUN-PROGRAM exits due to an error.")
236 (defvar *close-in-parent* nil
237 "List of file descriptors to close when RUN-PROGRAM returns in the parent.")
238 (defvar *handlers-installed* nil
239 "List of handlers installed by RUN-PROGRAM.")
240
241
242 ;;; FIND-A-PTY -- internal
243 ;;;
244 ;;; Finds a pty that is not in use. Returns three values: the file descriptor
245 ;;; for the master side of the pty, the file descriptor for the slave side of
246 ;;; the pty, and the name of the tty device for the slave side.
247 ;;;
248 (defun find-a-pty ()
249 "Returns the master fd, the slave fd, and the name of the tty"
250 (dolist (char '(#\p #\q))
251 (dotimes (digit 16)
252 (let* ((master-name (format nil "/dev/pty~C~X" char digit))
253 (master-fd (mach:unix-open master-name
254 mach:o_rdwr
255 #o666)))
256 (when master-fd
257 (let* ((slave-name (format nil "/dev/tty~C~X" char digit))
258 (slave-fd (mach:unix-open slave-name
259 mach:o_rdwr
260 #o666)))
261 (when slave-fd
262 ; Maybe put a vhangup here?
263 (mach:unix-ioctl slave-fd
264 mach:TIOCGETP
265 (system:alien-sap mach:sgtty))
266 (setf (system:alien-access (mach::sgtty-flags mach:sgtty))
267 #o6300) ; XTABS|EVENP|ODDP
268 (mach:unix-ioctl slave-fd
269 mach:TIOCSETP
270 (system:alien-sap mach:sgtty))
271 (mach:unix-ioctl master-fd
272 mach:TIOCGETP
273 (system:alien-sap mach:sgtty))
274 (setf (system:alien-access (mach::sgtty-flags mach:sgtty))
275 (logand (system:alien-access (mach::sgtty-flags mach:sgtty))
276 (lognot 8))) ; ~ECHO
277 (mach:unix-ioctl master-fd
278 mach:TIOCSETP
279 (system:alien-sap mach:sgtty))
280 (return-from find-a-pty
281 (values master-fd
282 slave-fd
283 slave-name))))
284 (mach:unix-close master-fd)))))
285 (error "Could not find a pty."))
286
287 ;;; OPEN-PTY -- internal
288 ;;;
289 (defun open-pty (pty)
290 (when pty
291 (multiple-value-bind
292 (master slave name)
293 (find-a-pty)
294 (push master *close-on-error*)
295 (push slave *close-in-parent*)
296 (when (streamp pty)
297 (multiple-value-bind (won new-fd) (mach:unix-dup master)
298 (unless won
299 (error "Could not MACH:UNIX-DUP ~D: ~A"
300 master (mach:get-unix-error-msg new-fd)))
301 (push new-fd *close-on-error*)
302 (copy-descriptor-to-stream new-fd pty)))
303 (values name
304 (system:make-fd-stream master :input t :output t)))))
305
306 ;;; SETUP-CHILD -- internal
307 ;;;
308 ;;; Execs the program after setting up the environment correctly. This
309 ;;; routine never returns under any condition.
310 ;;;
311 (defun setup-child (pfile args env stdin stdout stderr pty-name before-execve)
312 (unwind-protect
313 (handler-bind ((error #'(lambda (condition)
314 (declare (ignore condition))
315 (mach:unix-exit 2))))
316 ;; Put us in our own pgrp.
317 (mach:unix-setpgrp 0 (mach:unix-getpid))
318 ;; If we want a pty, set it up.
319 (when pty-name
320 (let ((old-tty (mach:unix-open "/dev/tty" mach:o_rdwr 0)))
321 (when old-tty
322 (mach:unix-ioctl old-tty mach:TIOCNOTTY 0)
323 (mach:unix-close old-tty)))
324 (let ((new-tty (mach:unix-open pty-name mach:o_rdwr 0)))
325 (when new-tty
326 (mach:unix-dup2 new-tty 0)
327 (mach:unix-dup2 new-tty 1)
328 (mach:unix-dup2 new-tty 2))))
329 ;; Setup the three standard descriptors.
330 (when stdin
331 (mach:unix-dup2 stdin 0))
332 (when stdout
333 (mach:unix-dup2 stdout 1))
334 (when stderr
335 (mach:unix-dup2 stderr 2))
336 ;; Close all other descriptors.
337 (do ((fd (1- (mach:unix-getdtablesize))
338 (1- fd)))
339 ((= fd 3))
340 (mach:unix-close fd))
341 ;; Do the before-execve
342 (when before-execve
343 (funcall before-execve))
344 ;; Exec the program
345 (mach:unix-execve pfile args env))
346 ;; If exec returns, we lose.
347 (mach:unix-exit 1)))
348
349 ;;; RUN-PROGRAM -- public
350 ;;;
351 ;;; RUN-PROGRAM uses fork and execve to run a different program. Strange stuff
352 ;;; happens to keep the unix state of the world coherent.
353 ;;;
354 ;;; The child process needs to get it's input from somewhere, and send it's
355 ;;; output (both standard and error) to somewhere. We have to do different
356 ;;; things depending on where these somewheres really are.
357 ;;;
358 ;;; For input, there are five options:
359 ;;; - T: Just leave fd 0 alone. Pretty simple.
360 ;;; - "file": Read from the file. We need to open the file and pull the
361 ;;; descriptor out of the stream. The parent should close this stream after
362 ;;; the child is up and running to free any storage used in the parent.
363 ;;; - NIL: Same as "file", but use "/dev/null" as the file.
364 ;;; - :STREAM: Use unix-pipe to create two descriptors. Use system:make-fd-stream
365 ;;; to create the output stream on the writeable descriptor, and pass the
366 ;;; readable descriptor to the child. The parent must close the readable
367 ;;; descriptor for EOF to be passed up correctly.
368 ;;; - a stream: If it's a fd-stream, just pull the descriptor out of it.
369 ;;; Otherwise make a pipe as in :STREAM, and copy everything across.
370 ;;;
371 ;;; For output, there are n options:
372 ;;; - T: Leave descriptor 1 alone.
373 ;;; - "file": dump output to the file.
374 ;;; - NIL: dump output to /dev/null.
375 ;;; - :STREAM: return a stream that can be read from.
376 ;;; - a stream: if it's a fd-stream, use the descriptor in it. Otherwise, copy
377 ;;; stuff from output to stream.
378 ;;;
379 ;;; For error, there are all the same options as output plus:
380 ;;; - :OUTPUT: redirect to the same place as output.
381 ;;;
382 ;;; RUN-PROGRAM returns a process struct for the process if the fork worked,
383 ;;; and NIL if it did not.
384 ;;;
385 (defun run-program (program args
386 &key (env *environment-list*) (wait t) pty input
387 if-input-does-not-exist output (if-output-exists :error)
388 (error :output) (if-error-exists :error) status-hook
389 before-execve)
390 "Run-program creates a new process and runs the unix progam in the
391 file specified by the simple-string program. Args are the standard
392 arguments that can be passed to a Unix program, for no arguments
393 use NIL (which means just the name of the program is passed as arg 0).
394
395 Run program will either return NIL or a PROCESS structure. See the CMU
396 Common Lisp Users Manual for details about the PROCESS structure.
397
398 The keyword arguments have the following meanings:
399 :env -
400 An A-LIST mapping keyword environment variables to simple-string
401 values.
402 :wait -
403 If non-NIL (default), wait until the created process finishes. If
404 NIL, continue running Lisp until the program finishes.
405 :pty -
406 Either T, NIL, or a stream. Unless NIL, the subprocess is established
407 under a PTY. If :pty is a stream, all output to this pty is sent to
408 this stream, otherwise the PROCESS-PTY slot is filled in with a stream
409 connected to pty that can read output and write input.
410 :input -
411 Either T, NIL, a pathname, a stream, or :STREAM. If T, the standard
412 input for the current process is inherited. If NIL, /dev/null
413 is used. If a pathname, the file so spesified is used. If a stream,
414 all the input is read from that stream and send to the subprocess. If
415 :STREAM, the PROCESS-INPUT slot is filled in with a stream that sends
416 its output to the process. Defaults to NIL.
417 :if-input-does-not-exist (when :input is the name of a file) -
418 can be one of:
419 :error - generate an error.
420 :create - create an empty file.
421 nil (default) - return nil from run-program.
422 :output -
423 Either T, NIL, a pathname, a stream, or :STREAM. If T, the standard
424 input for the current process is inherited. If NIL, /dev/null
425 is used. If a pathname, the file so spesified is used. If a stream,
426 all the output from the process is written to this stream. If
427 :STREAM, the PROCESS-OUTPUT slot is filled in with a stream that can
428 be read to get the output. Defaults to NIL.
429 :if-output-exists (when :input is the name of a file) -
430 can be one of:
431 :error (default) - generates an error if the file already exists.
432 :supersede - output from the program supersedes the file.
433 :append - output from the program is appended to the file.
434 nil - run-program returns nil without doing anything.
435 :error and :if-error-exists -
436 Same as :output and :if-output-exists, except that :error can also be
437 spesified as :output in which case all error output is routed to the
438 same place as normal output.
439 :status-hook -
440 This is a function the system calls whenever the status of the
441 process changes. The function takes the process as an argument.
442 :before-execve -
443 This is a function, without arguments, RUN-PROGRAM runs in the child
444 process just before turning it into the specified program."
445
446 ;; Make sure the interrupt handler is installed.
447 ;(system:enable-interrupt mach:sigchld #'sigchld-handler)
448 ;; Make sure all the args are okay.
449 (unless (every #'simple-string-p args)
450 (error "All args to program must be simple strings -- ~S." args))
451 ;; Pre-pend the program to the argument list.
452 (push (namestring program) args)
453 ;; Clear random specials used by GET-DESCRIPTOR-FOR to communicate cleanup
454 ;; info. Also, establish proc at this level so we can return it.
455 (let (*close-on-error* *close-in-parent* *handlers-installed* proc)
456 (unwind-protect
457 (let ((pfile (namestring (truename (merge-pathnames program "path:")))))
458 (multiple-value-bind
459 (stdin input-stream)
460 (get-descriptor-for input :direction :input
461 :if-does-not-exist if-input-does-not-exist)
462 (multiple-value-bind
463 (stdout output-stream)
464 (get-descriptor-for output :direction :output
465 :if-exists if-output-exists)
466 (multiple-value-bind
467 (stderr error-stream)
468 (if (eq error :output)
469 (values stdout output-stream)
470 (get-descriptor-for error :direction :output
471 :if-exists if-error-exists))
472 (multiple-value-bind (pty-name pty-stream)
473 (open-pty pty)
474 ;; Make sure we are not notified about the child death before
475 ;; we have installed the process struct in *active-processes*
476 (system:without-interrupts
477 (multiple-value-bind
478 (child-pid errno)
479 (mach:unix-fork)
480 (cond ((zerop child-pid)
481 ;; We are the child. Note: setup-child NEVER returns
482 (setup-child pfile args env stdin stdout stderr
483 pty-name before-execve))
484 ((null child-pid)
485 ;; This should only happen if the bozo has too
486 ;; many running procs.
487 (error "Could not fork child process: ~A"
488 (mach:get-unix-error-msg errno)))
489 (t
490 ;; We are the parent.
491 (setf proc (make-process :pid child-pid
492 :%status :running
493 :pty pty-stream
494 :input input-stream
495 :output output-stream
496 :error error-stream
497 :status-hook status-hook))
498 (push proc *active-processes*))))))))))
499 (dolist (fd *close-in-parent*)
500 (mach:unix-close fd))
501 (unless proc
502 (dolist (fd *close-on-error*)
503 (mach:unix-close fd))
504 (dolist (handler *handlers-installed*)
505 (system:remove-fd-handler handler))))
506 (when (and wait proc)
507 (process-wait proc))
508 proc))
509
510 ;;; COPY-DESCRIPTOR-TO-STREAM -- internal
511 ;;;
512 ;;; Installs a handler for any input that shows up on the file descriptor.
513 ;;; The handler reads the data and writes it to the stream.
514 ;;;
515 (defun copy-descriptor-to-stream (descriptor stream)
516 (let ((string (make-string 256))
517 handler)
518 (setf handler
519 (system:add-fd-handler descriptor :input
520 #'(lambda (fd)
521 (declare (ignore fd))
522 (loop
523 (multiple-value-bind
524 (result readable/errno)
525 (mach:unix-select (1+ descriptor) (ash 1 descriptor)
526 0 0 0)
527 (cond ((null result)
528 (error "Could not select on sub-process: ~A"
529 (mach:get-unix-error-msg readable/errno)))
530 ((zerop result)
531 (return))))
532 (multiple-value-bind
533 (count errno)
534 (mach:unix-read descriptor
535 string
536 (length string))
537 (cond ((or (and (null count)
538 (eql errno mach:eio))
539 (eql count 0))
540 (system:remove-fd-handler handler)
541 (mach:unix-close descriptor)
542 (return))
543 ((null count)
544 (system:remove-fd-handler handler)
545 (error "Could not read input from sub-process: ~A"
546 (mach:get-unix-error-msg errno)))
547 (t
548 (write-string string stream
549 :end count))))))))))
550
551 ;;; GET-DESCRIPTOR-FOR -- internal
552 ;;;
553 ;;; Find a file descriptor to use for object given the direction. Returns
554 ;;; the descriptor. If object is :STREAM, returns the created stream as the
555 ;;; second value.
556 ;;;
557 (defun get-descriptor-for (object &rest keys &key direction &allow-other-keys)
558 (cond ((eq object t)
559 ;; No new descriptor is needed.
560 (values nil nil))
561 ((eq object nil)
562 ;; Use /dev/null.
563 (multiple-value-bind
564 (fd errno)
565 (mach:unix-open "/dev/null"
566 (case direction
567 (:input mach:o_rdonly)
568 (:output mach:o_wronly)
569 (t mach:o_rdwr))
570 #o666)
571 (unless fd
572 (error "Could not open \"/dev/null\": ~A"
573 (mach:get-unix-error-msg errno)))
574 (push fd *close-in-parent*)
575 (values fd nil)))
576 ((eq object :stream)
577 (multiple-value-bind
578 (read-fd write-fd)
579 (mach:unix-pipe)
580 (unless read-fd
581 (error "Could not create pipe: ~A"
582 (mach:get-unix-error-msg write-fd)))
583 (case direction
584 (:input
585 (push read-fd *close-in-parent*)
586 (push write-fd *close-on-error*)
587 (let ((stream (system:make-fd-stream write-fd :output t)))
588 (values read-fd stream)))
589 (:output
590 (push read-fd *close-on-error*)
591 (push write-fd *close-in-parent*)
592 (let ((stream (system:make-fd-stream read-fd :input t)))
593 (values write-fd stream)))
594 (t
595 (mach:unix-close read-fd)
596 (mach:unix-close write-fd)
597 (error "Direction must be either :INPUT or :OUTPUT, not ~S"
598 direction)))))
599 ((stringp object)
600 (with-open-stream (file (apply #'open object keys))
601 (multiple-value-bind (won fd)
602 (mach:unix-dup (system:fd-stream-fd file))
603 (cond (won
604 (push fd *close-in-parent*)
605 (values fd nil))
606 (t
607 (error "Could not duplicate file descriptor: ~A"
608 (mach:get-unix-error-msg fd)))))))
609 ((system:fd-stream-p object)
610 (values (system:fd-stream-fd object) nil))
611 ((streamp object)
612 (ecase direction
613 (:input
614 (dotimes (count
615 256
616 (error "Could not open a temporary file in /tmp"))
617 (let* ((name (format nil "/tmp/.run-program-~D" count))
618 (fd (mach:unix-open name
619 (logior mach:o_rdwr
620 mach:o_creat
621 mach:o_excl)
622 #o666)))
623 (mach:unix-unlink name)
624 (when fd
625 (let ((newline (string #\Newline)))
626 (loop
627 (multiple-value-bind
628 (line no-cr)
629 (read-line object nil nil)
630 (unless line
631 (return))
632 (mach:unix-write fd line 0 (length line))
633 (if no-cr
634 (return)
635 (mach:unix-write fd newline 0 1)))))
636 (mach:unix-lseek fd 0 mach:l_set)
637 (push fd *close-in-parent*)
638 (return (values fd nil))))))
639 (:output
640 (multiple-value-bind (read-fd write-fd)
641 (mach:unix-pipe)
642 (unless read-fd
643 (error "Cound not create pipe: ~A"
644 (mach:get-unix-error-msg write-fd)))
645 (copy-descriptor-to-stream read-fd object)
646 (push read-fd *close-on-error*)
647 (push write-fd *close-in-parent*)
648 (values write-fd nil)))))
649 (t
650 (error "Invalid option to run-program: ~S" object))))
651

  ViewVC Help
Powered by ViewVC 1.1.5