1 ;;;; -*- Mode: Lisp; indent-tabs-mode: nil -*-
3 ;;; --- Wrapper over lfp_spawn(3)
9 ((pid :initarg
:pid
:reader process-pid
)
10 (reaped :initform nil
)
11 (stdin :initform nil
:reader process-stdin
)
12 (stdout :initform nil
:reader process-stdout
)
13 (stderr :initform nil
:reader process-stderr
)))
15 (defmethod initialize-instance :after
((process process
) &key stdin stdout stderr
)
16 (with-slots ((in stdin
) (out stdout
) (err stderr
))
18 (setf in
(and stdin
(make-instance 'iolib.streams
:dual-channel-gray-stream
:fd stdin
))
19 out
(and stdout
(make-instance 'iolib.streams
:dual-channel-gray-stream
:fd stdout
))
20 err
(and stderr
(make-instance 'iolib.streams
:dual-channel-gray-stream
:fd stderr
)))))
22 (defmethod close ((process process
) &key abort
)
23 (with-slots (pid reaped stdin stdout stderr
)
25 (when stdin
(close stdin
:abort abort
))
26 (when stdout
(close stdout
:abort abort
))
27 (when stderr
(close stderr
:abort abort
))
29 (isys:waitpid pid
(if abort isys
:wnohang
0)))
30 (setf pid nil stdin nil stdout nil stderr nil
)))
32 (defmethod print-object ((o process
) s
)
33 (print-unreadable-object (o s
:type t
:identity t
)
34 (format s
"~S ~S" :pid
(process-pid o
))))
36 (defmacro with-lfp-spawn-arguments
((attributes file-actions pid
) &body body
)
37 (with-gensyms (spawnattr-initialized-p file-actions-initialized-p
)
38 `(with-foreign-objects ((,attributes
'lfp-spawnattr-t
)
39 (,file-actions
'lfp-spawn-file-actions-t
)
41 (let (,spawnattr-initialized-p
42 ,file-actions-initialized-p
)
45 (lfp-spawnattr-init ,attributes
)
46 (setf ,spawnattr-initialized-p t
)
47 (lfp-spawn-file-actions-init ,file-actions
)
48 (setf ,file-actions-initialized-p t
)
50 (when ,spawnattr-initialized-p
51 (lfp-spawnattr-destroy ,attributes
))
52 (when ,file-actions-initialized-p
53 (lfp-spawn-file-actions-destroy ,file-actions
)))))))
55 (defun allocate-argv (argv argc program arglist
)
57 (setf (mem-aref argv
:pointer
0)
58 (foreign-string-alloc program
))
59 ;; copy program arguments
61 :for arg
:in arglist
:do
62 (setf (mem-aref argv
:pointer i
)
63 (foreign-string-alloc arg
)))
65 (setf (mem-aref argv
:pointer
(1- argc
)) (null-pointer)))
67 (defun find-program (program)
72 (file-path-namestring program
))))
74 (defmacro with-argv
(((arg0 argv
) program arguments
) &body body
)
76 `(let ((,program
(find-program ,program
))
77 (,argc
(+ 2 (length ,arguments
))))
78 (with-foreign-object (,argv
:pointer
,argc
)
81 (allocate-argv ,argv
,argc
,program
,arguments
)
82 (let ((,arg0
(mem-ref ,argv
:pointer
)))
84 (deallocate-null-ended-list ,argv
))))))
86 (defun redirect-one-stream (file-actions fd stream
&optional flags
(mode #o644
) close-old-fd
)
87 (flet ((dup-from-path (path)
88 (lfp-spawn-file-actions-addopen file-actions fd path flags mode
))
90 (lfp-spawn-file-actions-adddup2 file-actions oldfd fd
)
92 (lfp-spawn-file-actions-addclose file-actions oldfd
))))
95 ((or string file-path pathname
)
96 (dup-from-path (file-path-namestring stream
)))
98 (dup-from-path "/dev/null"))
100 (dup-from-fd stream
))
101 (iolib.streams
:dual-channel-fd-mixin
102 (dup-from-fd (iolib.streams
:fd-of stream
)))
104 (lfp-spawn-file-actions-addclose file-actions fd
)))))
106 (defun redirect-to-pipes (file-actions fd keep-write-fd
)
107 (multiple-value-bind (pipe-parent pipe-child
)
109 (when keep-write-fd
(rotatef pipe-parent pipe-child
))
110 (lfp-spawn-file-actions-adddup2 file-actions pipe-child fd
)
111 (lfp-spawn-file-actions-addclose file-actions pipe-parent
)
112 (lfp-spawn-file-actions-addclose file-actions pipe-child
)
113 (values pipe-parent pipe-child
)))
115 (defun setup-redirections (file-actions stdin stdout stderr
)
116 (let (infd infd-child outfd outfd-child errfd errfd-child
)
118 (if (eql :pipe stdin
)
119 (setf (values infd infd-child
)
120 (redirect-to-pipes file-actions
+stdin
+ t
))
121 (redirect-one-stream file-actions
+stdin
+ stdin isys
:o-rdonly
))
123 (if (eql :pipe stdout
)
124 (setf (values outfd outfd-child
)
125 (redirect-to-pipes file-actions
+stdout
+ nil
))
126 (redirect-one-stream file-actions
+stdout
+ stdout
(logior isys
:o-wronly
130 ((and stdout
(eql :stdout stderr
))
131 (redirect-one-stream file-actions
+stderr
+ +stdout
+))
133 (setf (values errfd errfd-child
)
134 (redirect-to-pipes file-actions
+stderr
+ nil
)))
136 (redirect-one-stream file-actions
+stderr
+ stderr
(logior isys
:o-wronly
138 (values infd infd-child outfd outfd-child errfd errfd-child
)))
140 (defun close-fds (&rest fds
)
142 (when fd
(isys:close fd
))))
144 (defmacro with-redirections
(((infd outfd errfd
) (file-actions stdin stdout stderr
))
146 (with-gensyms (infd-child outfd-child errfd-child
)
147 `(multiple-value-bind (,infd
,infd-child
,outfd
,outfd-child
,errfd
,errfd-child
)
148 (setup-redirections ,file-actions
,stdin
,stdout
,stderr
)
149 (unwind-protect-case ()
152 (close-fds ,infd-child
,outfd-child
,errfd-child
))
154 (close-fds ,infd
,outfd
,errfd
))))))
156 (defun process-other-spawn-args (attributes uid gid resetids
)
158 (lfp-spawnattr-setuid attributes uid
))
160 (lfp-spawnattr-setgid attributes gid
))
162 (lfp-spawnattr-setflags attributes lfp-spawn-resetids
)))
164 ;; program: :shell - the system shell
165 ;; file-path designator - a path
167 ;; search: boolean. whether or not to search PROGRAM in $PATH when PROGRAM only names a file,
168 ;; i.e., it's a relative path whose directory is NIL
169 ;; environment: t - inherit environment
170 ;; nil - NULL environment
171 ;; alist - the environment to use
172 ;; stdin, stdout, stderr:
173 ;; file-path designator - open file, redirect to it
174 ;; :null - redirect to /dev/null - useful because /dev/null doesn't exist on Windows
175 ;; file-descriptor designator(integer or stream) - file descriptor, redirecto to it
176 ;; :pipe - create pipe, redirect the child descriptor to one end and wrap the other end
177 ;; into a stream which goes into PROCESS slot
180 ;; stderr: :stdout - the same as stdout
181 ;; uid: user id - unsigned-byte or string
182 ;; gid: group id - unsigned-byte or string
183 ;; resetids: boolean - reset effective UID and GID to saved IDs
185 (defun create-process (program arguments
&key
(search t
) (environment t
)
186 (stdin t
) (stdout t
) (stderr t
)
188 (with-lfp-spawn-arguments (attributes file-actions pid
)
189 (with-argv ((arg0 argv
) program arguments
)
190 (with-c-environment (envp environment
)
191 (with-redirections ((infd outfd errfd
) (file-actions stdin stdout stderr
))
192 (process-other-spawn-args attributes uid gid resetids
)
194 (lfp-spawnp pid arg0 argv envp file-actions attributes
)
195 (lfp-spawn pid arg0 argv envp file-actions attributes
))
196 (make-instance 'process
:pid
(mem-ref pid
'pid-t
)
197 :stdin infd
:stdout outfd
:stderr errfd
))))))
199 (defun run-program (program &optional arguments
&key
(search t
) environment stderr
)
200 (check-type stderr
(member nil
:stdout
))
201 (flet ((slurp-stream-into-string (stream)
202 (with-output-to-string (s)
203 (loop :for c
:= (read-char stream nil nil
)
204 :while c
:do
(write-char c s
)))))
205 (let ((process (create-process program arguments
207 :environment environment
209 :stderr
(if (eql :stdout stderr
)
213 (values (process-wait process
)
214 (slurp-stream-into-string (process-stdout process
))
215 (if (eql :stdout stderr
)
217 (slurp-stream-into-string (process-stderr process
))))
220 (defmethod process-wait ((process process
))
222 (nth-value 1 (isys:waitpid
(process-pid process
) 0))
223 (setf (slot-value process
'reaped
) t
)))
225 (defmethod process-kill ((process process
) &optional
(signum :sigterm
))
226 (isys:kill
(process-pid process
) signum
))