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

  ViewVC Help
Powered by ViewVC 1.1.5