popen.scm 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. ;; popen emulation, for non-stdio based ports.
  2. ;;;; Copyright (C) 1998-2001, 2003, 2006, 2010-2013, 2019
  3. ;;;; Free Software Foundation, Inc.
  4. ;;;;
  5. ;;;; This library is free software; you can redistribute it and/or
  6. ;;;; modify it under the terms of the GNU Lesser General Public
  7. ;;;; License as published by the Free Software Foundation; either
  8. ;;;; version 3 of the License, or (at your option) any later version.
  9. ;;;;
  10. ;;;; This library is distributed in the hope that it will be useful,
  11. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. ;;;; Lesser General Public License for more details.
  14. ;;;;
  15. ;;;; You should have received a copy of the GNU Lesser General Public
  16. ;;;; License along with this library; if not, write to the Free Software
  17. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. ;;;;
  19. (define-module (ice-9 popen)
  20. #:use-module (rnrs bytevectors)
  21. #:use-module (ice-9 binary-ports)
  22. #:use-module (ice-9 threads)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-9)
  25. #:export (port/pid-table open-pipe* open-pipe close-pipe open-input-pipe
  26. open-output-pipe open-input-output-pipe pipeline))
  27. (eval-when (expand load eval)
  28. (load-extension (string-append "libguile-" (effective-version))
  29. "scm_init_popen"))
  30. (define-record-type <pipe-info>
  31. (make-pipe-info pid)
  32. pipe-info?
  33. (pid pipe-info-pid set-pipe-info-pid!))
  34. (define (make-rw-port read-port write-port)
  35. (define (read! bv start count)
  36. (let ((result (get-bytevector-some! read-port bv start count)))
  37. (if (eof-object? result)
  38. 0
  39. result)))
  40. (define (write! bv start count)
  41. (put-bytevector write-port bv start count)
  42. count)
  43. (define (close)
  44. (close-port read-port)
  45. (close-port write-port))
  46. (define rw-port
  47. (make-custom-binary-input/output-port "ice-9-popen-rw-port"
  48. read!
  49. write!
  50. #f ;get-position
  51. #f ;set-position!
  52. close))
  53. ;; Enable buffering on 'read-port' so that 'get-bytevector-some' will
  54. ;; return non-trivial blocks.
  55. (setvbuf read-port 'block 16384)
  56. ;; Inherit the port-encoding from the read-port.
  57. (set-port-encoding! rw-port (port-encoding read-port))
  58. ;; Reset the port encoding on the underlying ports to inhibit BOM
  59. ;; handling there. Instead, the BOM handling (if any) will be handled
  60. ;; in the rw-port. In the current implementation of Guile ports,
  61. ;; using binary I/O primitives alone is not enough to reliably inhibit
  62. ;; BOM handling, if the port encoding is set to UTF-{8,16,32}.
  63. (set-port-encoding! read-port "ISO-8859-1")
  64. (set-port-encoding! write-port "ISO-8859-1")
  65. rw-port)
  66. ;; a guardian to ensure the cleanup is done correctly when
  67. ;; an open pipe is gc'd or a close-port is used.
  68. (define pipe-guardian (make-guardian))
  69. ;; a weak hash-table to store the process ids.
  70. ;; XXX use of this table is deprecated. It is no longer used here, and
  71. ;; is populated for backward compatibility only (since it is exported).
  72. (define port/pid-table (make-weak-key-hash-table))
  73. (define port/pid-table-mutex (make-mutex))
  74. (define (pipe->fdes)
  75. (let ((p (pipe)))
  76. (cons (port->fdes (car p))
  77. (port->fdes (cdr p)))))
  78. (define (open-process mode command . args)
  79. "Backwards compatible implementation of the former procedure in
  80. libguile/posix.c (scm_open_process) replaced by
  81. scm_piped_process. Executes the program @var{command} with optional
  82. arguments @var{args} (all strings) in a subprocess. A port to the
  83. process (based on pipes) is created and returned. @var{mode} specifies
  84. whether an input, an output or an input-output port to the process is
  85. created: it should be the value of @code{OPEN_READ}, @code{OPEN_WRITE}
  86. or @code{OPEN_BOTH}."
  87. (define (unbuffered port)
  88. (setvbuf port 'none)
  89. port)
  90. (define (fdes-pair ports)
  91. (and ports
  92. (cons (port->fdes (car ports)) (port->fdes (cdr ports)))))
  93. (let* ((from (and (or (string=? mode OPEN_READ)
  94. (string=? mode OPEN_BOTH))
  95. (pipe)))
  96. (to (and (or (string=? mode OPEN_WRITE)
  97. (string=? mode OPEN_BOTH))
  98. (pipe)))
  99. (pid (piped-process command args
  100. (fdes-pair from)
  101. (fdes-pair to))))
  102. ;; The original 'open-process' procedure would return unbuffered
  103. ;; ports; do the same here.
  104. (values (and from (unbuffered (car from)))
  105. (and to (unbuffered (cdr to)))
  106. pid)))
  107. (define (open-pipe* mode command . args)
  108. "Executes the program @var{command} with optional arguments
  109. @var{args} (all strings) in a subprocess.
  110. A port to the process (based on pipes) is created and returned.
  111. @var{mode} specifies whether an input, an output or an input-output
  112. port to the process is created: it should be the value of
  113. @code{OPEN_READ}, @code{OPEN_WRITE} or @code{OPEN_BOTH}."
  114. (call-with-values (lambda ()
  115. (apply open-process mode command args))
  116. (lambda (read-port write-port pid)
  117. (let ((port (or (and read-port write-port
  118. (make-rw-port read-port write-port))
  119. read-port
  120. write-port
  121. (%make-void-port mode)))
  122. (pipe-info (make-pipe-info pid)))
  123. ;; Guard the pipe-info instead of the port, so that we can still
  124. ;; call 'waitpid' even if 'close-port' is called (which clears
  125. ;; the port entry).
  126. (pipe-guardian pipe-info)
  127. (%set-port-property! port 'popen-pipe-info pipe-info)
  128. ;; XXX populate port/pid-table for backward compatibility.
  129. (with-mutex port/pid-table-mutex
  130. (hashq-set! port/pid-table port pid))
  131. port))))
  132. (define (open-pipe command mode)
  133. "Executes the shell command @var{command} (a string) in a subprocess.
  134. A port to the process (based on pipes) is created and returned.
  135. @var{mode} specifies whether an input, an output or an input-output
  136. port to the process is created: it should be the value of
  137. @code{OPEN_READ}, @code{OPEN_WRITE} or @code{OPEN_BOTH}."
  138. (open-pipe* mode "/bin/sh" "-c" command))
  139. (define (fetch-pipe-info port)
  140. (%port-property port 'popen-pipe-info))
  141. (define (close-process port pid)
  142. (close-port port)
  143. (cdr (waitpid pid)))
  144. (define (close-pipe p)
  145. "Closes the pipe created by @code{open-pipe}, then waits for the process
  146. to terminate and returns its status value, @xref{Processes, waitpid}, for
  147. information on how to interpret this value."
  148. (let ((pipe-info (fetch-pipe-info p)))
  149. (unless pipe-info
  150. (error "close-pipe: port not created by (ice-9 popen)"))
  151. (let ((pid (pipe-info-pid pipe-info)))
  152. (unless pid
  153. (error "close-pipe: pid has already been cleared"))
  154. ;; clear the pid to avoid repeated calls to 'waitpid'.
  155. (set-pipe-info-pid! pipe-info #f)
  156. (close-process p pid))))
  157. (define (reap-pipes)
  158. (let loop ()
  159. (let ((pipe-info (pipe-guardian)))
  160. (when pipe-info
  161. (let ((pid (pipe-info-pid pipe-info)))
  162. ;; maybe 'close-pipe' was already called.
  163. (when pid
  164. ;; clean up without reporting errors. also avoids blocking
  165. ;; the process: if the child isn't ready to be collected,
  166. ;; puts it back into the guardian's live list so it can be
  167. ;; tried again the next time the cleanup runs.
  168. (catch 'system-error
  169. (lambda ()
  170. (let ((pid/status (waitpid pid WNOHANG)))
  171. (if (zero? (car pid/status))
  172. (pipe-guardian pipe-info) ; not ready for collection
  173. (set-pipe-info-pid! pipe-info #f))))
  174. (lambda args #f))))
  175. (loop)))))
  176. (add-hook! after-gc-hook reap-pipes)
  177. (define (open-input-pipe command)
  178. "Equivalent to @code{open-pipe} with mode @code{OPEN_READ}"
  179. (open-pipe command OPEN_READ))
  180. (define (open-output-pipe command)
  181. "Equivalent to @code{open-pipe} with mode @code{OPEN_WRITE}"
  182. (open-pipe command OPEN_WRITE))
  183. (define (open-input-output-pipe command)
  184. "Equivalent to @code{open-pipe} with mode @code{OPEN_BOTH}"
  185. (open-pipe command OPEN_BOTH))
  186. (define (pipeline commands)
  187. "Execute a pipeline of @var{commands}, where each command is a list of a
  188. program and its arguments as strings, returning an input port to the
  189. end of the pipeline, an output port to the beginning of the pipeline and
  190. a list of PIDs of the processes executing the @var{commands}."
  191. (let* ((to (pipe->fdes))
  192. (pipes (map (lambda _ (pipe->fdes)) commands))
  193. (pipeline (fold (lambda (from proc prev)
  194. (let* ((to (car prev))
  195. (pids (cdr prev))
  196. (pid (piped-process (car proc)
  197. (cdr proc)
  198. from
  199. to)))
  200. (cons from (cons pid pids))))
  201. `(,to)
  202. pipes
  203. commands))
  204. (from (car pipeline))
  205. (pids (cdr pipeline)))
  206. (values (fdes->inport (car from)) (fdes->outport (cdr to)) pids)))