summaryrefslogtreecommitdiff
path: root/gio/src/subprocess.hg
blob: 1b6322cad12716da7d090ffbe5c89bacbdbce110 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/* Copyright (C) 2023 The gtkmm Development Team
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

_CONFIGINCLUDE(giommconfig.h)

#include <glibmm/object.h>
#include <glibmm/bytes.h>
#include <giomm/initable.h>
#include <giomm/asyncresult.h>
#include <giomm/cancellable.h>
#include <giomm/inputstream.h>
#include <giomm/outputstream.h>
#include <utility> // std::pair

_DEFS(giomm,gio)
_PINCLUDE(glibmm/private/object_p.h)

namespace Gio
{

/** Child processes.
 *
 * %Gio::Subprocess allows the creation of and interaction with child
 * processes.
 *
 * Processes can be communicated with using standard GIO-style APIs
 * (Gio::InputStream, Gio::OutputStream). There are GIO-style APIs to wait for
 * process termination (cancellable and with an asynchronous variant).
 *
 * There is an API to force a process to terminate, as well as a
 * race-free API for sending UNIX signals to a subprocess.
 *
 * One major advantage that GIO brings over the core GLib library is
 * comprehensive API for asynchronous I/O, such as
 * Gio::OutputStream::splice_async(). This makes %Gio::Subprocess
 * significantly more powerful and flexible than equivalent APIs in
 * some other languages such as the `subprocess.py`
 * included with Python. For example, using %Gio::Subprocess one could
 * create two child processes, reading standard output from the first,
 * processing it, and writing to the input stream of the second, all
 * without blocking the main loop.
 *
 * A powerful communicate() API is provided similar to the
 * `%communicate()` method of `subprocess.py`. This enables very easy
 * interaction with a subprocess that has been opened with pipes.
 *
 * %Gio::Subprocess defaults to tight control over the file descriptors open
 * in the child process, avoiding dangling-fd issues that are caused by
 * a simple fork()/exec(). The only open file descriptors in the
 * spawned process are ones that were explicitly specified by the
 * %Gio::Subprocess API (unless Gio::Subprocess::Flags::INHERIT_FDS was
 * specified).
 *
 * %Gio::Subprocess will quickly reap all child processes as they exit,
 * avoiding "zombie processes" remaining around for long periods of
 * time. wait() can be used to wait for this to happen,
 * but it will happen even without the call being explicitly made.
 *
 * As a matter of principle, %Gio::Subprocess has no API that accepts
 * shell-style space-separated strings. It will, however, match the
 * typical shell behaviour of searching the PATH for executables that do
 * not contain a directory separator in their name. By default, the `PATH`
 * of the current process is used. You can specify
 * Gio::Subprocess::Flags::SEARCH_PATH_FROM_ENVP to use the `PATH` of the
 * launcher environment instead.
 *
 * %Gio::Subprocess attempts to have a very simple API for most uses (ie:
 * spawning a subprocess with arguments and support for most typical
 * kinds of input and output redirection). See create(). The
 * Gio::SubprocessLauncher API is provided for more complicated cases
 * (advanced types of redirection, environment variable manipulation,
 * change of working directory, child setup functions, etc).
 *
 * A typical use of %Gio::Subprocess will involve calling create(), followed by
 * wait_async() or wait(). After the process exits, the status can be
 * checked using functions such as get_if_exited() (which are similar to
 * the familiar WIFEXITED-style POSIX macros).
 *
 * @see Gio::SubprocessLauncher
 * @newin{2,78}
 */
class GIOMM_API Subprocess : public Glib::Object, public Initable
{
  _CLASS_GOBJECT(Subprocess, GSubprocess, G_SUBPROCESS, Glib::Object, GObject, , , GIOMM_API)
  _IMPLEMENTS_INTERFACE(Initable)

public:
  _WRAP_ENUM(Flags, GSubprocessFlags, newin "2,78", decl_prefix GIOMM_API)

protected:
  // Handwritten to ignore the final GError** parameter in the g_subprocess_newv() function.
  // It can throw, due to its call to Initable::init().
  explicit Subprocess(const std::vector<std::string>& argv, Flags flags = Flags::NONE);
  _IGNORE(g_subprocess_new, g_subprocess_newv)

public:
  /** Create a new process with the given flags and argument list.
   *
   * @newin{2,78}
   *
   * @param argv Commandline arguments for the subprocess.
   * @param flags Flags that define the behaviour of the subprocess.
   * @return A newly created Subprocess. On error, an exception is thrown.
   *
   * @throws Glib::Error
   */
  _WRAP_CREATE(const std::vector<std::string>& argv, Flags flags = Flags::NONE)

  _WRAP_METHOD(Glib::RefPtr<OutputStream> get_stdin_pipe(), g_subprocess_get_stdin_pipe, refreturn, newin "2,78")
  _WRAP_METHOD(Glib::RefPtr<const OutputStream> get_stdin_pipe() const, g_subprocess_get_stdin_pipe, refreturn, constversion, newin "2,78")
  _WRAP_METHOD(Glib::RefPtr<InputStream> get_stdout_pipe(), g_subprocess_get_stdout_pipe, refreturn, newin "2,78")
  _WRAP_METHOD(Glib::RefPtr<const InputStream> get_stdout_pipe() const, g_subprocess_get_stdout_pipe, refreturn, constversion, newin "2,78")
  _WRAP_METHOD(Glib::RefPtr<InputStream> get_stderr_pipe(), g_subprocess_get_stderr_pipe, refreturn, newin "2,78")
  _WRAP_METHOD(Glib::RefPtr<const InputStream> get_stderr_pipe() const, g_subprocess_get_stderr_pipe, refreturn, constversion, newin "2,78")

  _WRAP_METHOD(Glib::ustring get_identifier() const, g_subprocess_get_identifier, newin "2,78")
  _WRAP_METHOD(void send_signal(int signal_num), g_subprocess_send_signal, ifdef G_OS_UNIX, newin "2,78")
  _WRAP_METHOD(void force_exit(), g_subprocess_force_exit, newin "2,78")

  _WRAP_METHOD(void wait(const Glib::RefPtr<Cancellable>& cancellable = {}) const, g_subprocess_wait, errthrow, newin "2,78")
  _WRAP_METHOD(void wait_async(const SlotAsyncReady& slot{callback}, const Glib::RefPtr<Cancellable>& cancellable{.} = {}) const,
    g_subprocess_wait_async, slot_name slot, slot_callback giomm_SignalProxy_async_callback, newin "2,78")
  _WRAP_METHOD(void wait_finish(const Glib::RefPtr<AsyncResult>& result) const, g_subprocess_wait_finish, errthrow, newin "2,78")

  _WRAP_METHOD(void wait_check(const Glib::RefPtr<Cancellable>& cancellable = {}) const, g_subprocess_wait_check, errthrow, newin "2,78")
  _WRAP_METHOD(void wait_check_async(const SlotAsyncReady& slot{callback}, const Glib::RefPtr<Cancellable>& cancellable{.} = {}) const,
    g_subprocess_wait_check_async, slot_name slot, slot_callback giomm_SignalProxy_async_callback, newin "2,78")
  _WRAP_METHOD(void wait_check_finish(const Glib::RefPtr<AsyncResult>& result) const, g_subprocess_wait_check_finish, errthrow, newin "2,78")

  _WRAP_METHOD(int get_status() const, g_subprocess_get_status, newin "2,78")
  _WRAP_METHOD(bool get_successful() const, g_subprocess_get_successful, newin "2,78")
  _WRAP_METHOD(bool get_if_exited() const, g_subprocess_get_if_exited, newin "2,78")
  _WRAP_METHOD(int get_exit_status() const, g_subprocess_get_exit_status, newin "2,78")
  _WRAP_METHOD(bool get_if_signaled() const, g_subprocess_get_if_signaled, newin "2,78")
  _WRAP_METHOD(int get_term_sig() const, g_subprocess_get_term_sig, newin "2,78")

  /** Communicate with the subprocess until it terminates, and all input
   * and output has been completed.
   *
   * If @a stdin_buf is given, the subprocess must have been created with
   * Gio::Subprocess::Flags::STDIN_PIPE. The given data is fed to the
   * stdin of the subprocess and the pipe is closed (ie: EOF).
   *
   * At the same time (as not to cause blocking when dealing with large
   * amounts of data), if Gio::Subprocess::Flags::STDOUT_PIPE or
   * Gio::Subprocess::Flags::STDERR_PIPE were used, reads from those
   * streams. The data that was read is returned in @a stdout_buf and/or
   * the @a stderr_buf.
   *
   * If the subprocess was created with Gio::Subprocess::Flags::STDOUT_PIPE,
   *  @a stdout_buf will contain the data read from stdout.  Otherwise, for
   * subprocesses not created with Gio::Subprocess::Flags::STDOUT_PIPE,
   *  @a stdout_buf will be set to an empty RefPtr. Similar provisions apply to
   *  @a stderr_buf and Gio::Subprocess::Flags::STDERR_PIPE.
   *
   * If you desire the stdout and stderr data to be interleaved, create
   * the subprocess with Gio::Subprocess::Flags::STDOUT_PIPE and
   * Gio::Subprocess::Flags::STDERR_MERGE. The merged result will be returned
   * in @a stdout_buf, and @a stderr_buf will be set to an empty RefPtr.
   * 
   * In case of any error (including cancellation), an exception will be thrown.
   *
   * After a normal return (no exception thrown), the subprocess has exited and the
   * exit status inspection APIs (eg: get_if_exited(), get_exit_status()) may be used.
   *
   * You should not attempt to use any of the subprocess pipes after
   * starting this function, since they may be left in strange states,
   * even if the operation was cancelled. You should especially not
   * attempt to interact with the pipes while the operation is in progress
   * (either from another thread or if using the asynchronous version).
   *
   * @newin{2,78}
   *
   * @param stdin_buf Data to send to the stdin of the subprocess, or an empty RefPtr.
   * @param cancellable A Cancellable.
   * @return {stdout_buf, stderr_buf} stdout data and stderr data.
   *   Can be empty RefPtrs, if there are no data.
   *
   * @throws Glib::Error
   */
  std::pair<Glib::RefPtr<Glib::Bytes>, Glib::RefPtr<Glib::Bytes>>
    communicate(const Glib::RefPtr<const Glib::Bytes>& stdin_buf,
    const Glib::RefPtr<Cancellable>& cancellable = {});
  _IGNORE(g_subprocess_communicate)

  _WRAP_METHOD(void communicate_async(const Glib::RefPtr<const Glib::Bytes>& stdin_buf{.},
    const SlotAsyncReady& slot{callback}, const Glib::RefPtr<Cancellable>& cancellable{.} = {}),
    g_subprocess_communicate_async, slot_name slot, slot_callback giomm_SignalProxy_async_callback, newin "2,78")

  /** Complete an invocation of communicate_async().
   *
   * @newin{2,78}
   *
   * @param result Result.
   * @return {stdout_buf, stderr_buf} stdout data and stderr data.
   *   Can be empty RefPtrs, if there are no data.
   *
   * @throws Glib::Error
   */
  std::pair<Glib::RefPtr<Glib::Bytes>, Glib::RefPtr<Glib::Bytes>>
    communicate_finish(const Glib::RefPtr<AsyncResult>& result);
  _IGNORE(g_subprocess_communicate_finish)

  /** Like communicate(), but validates the output of the
   * process as UTF-8, and returns it as a regular Glib::ustring.
   *
   * On error, an exception is thrown.
   *
   * @newin{2,78}
   *
   * @param stdin_buf Data to send to the stdin of the subprocess, or an empty string.
   * @param cancellable A Cancellable.
   * @return {stdout_buf, stderr_buf} stdout data and stderr data.
   *
   * @throws Glib::Error
   */
  std::pair<Glib::ustring, Glib::ustring> communicate_utf8(const Glib::ustring& stdin_buf,
    const Glib::RefPtr<Cancellable>& cancellable = {});
  _IGNORE(g_subprocess_communicate_utf8)

  _WRAP_METHOD(void communicate_utf8_async(const Glib::ustring& stdin_buf{. NULL},
    const SlotAsyncReady& slot{callback}, const Glib::RefPtr<Cancellable>& cancellable{.} = {}),
    g_subprocess_communicate_utf8_async, slot_name slot, slot_callback giomm_SignalProxy_async_callback, newin "2,78")

  /** Complete an invocation of communicate_utf8_async().
   *
   * @newin{2,78}
   *
   * @param result Result.
   * @return {stdout_buf, stderr_buf} stdout data and stderr data.
   *
   * @throws Glib::Error
   */
  std::pair<Glib::ustring, Glib::ustring> communicate_utf8_finish(const Glib::RefPtr<AsyncResult>& result);
  _IGNORE(g_subprocess_communicate_utf8_finish)

  // _IGNORE_PROPERTY("flags", "argv") // write-only, construct-only
};

} // namespace Gio