From 4a59730ba4b410ee1fb0080f5e4d6e8cc798a298 Mon Sep 17 00:00:00 2001 From: Kjell Ahlstedt Date: Mon, 17 Apr 2023 16:01:10 +0200 Subject: Add Gio::Subprocess, SubprocessLauncher and examples/subprocess Fixes #106 --- examples/Makefile.am | 4 + examples/meson.build | 1 + examples/subprocess/main.cc | 114 +++++++++++++++++++ gio/giomm.h | 1 + gio/giomm/meson.build | 2 + gio/src/filelist.am | 2 + gio/src/gio_docs_override.xml | 1 + gio/src/gio_extra_objects.defs | 12 ++ gio/src/subprocess.ccg | 89 +++++++++++++++ gio/src/subprocess.hg | 253 +++++++++++++++++++++++++++++++++++++++++ gio/src/subprocesslauncher.ccg | 62 ++++++++++ gio/src/subprocesslauncher.hg | 121 ++++++++++++++++++++ tools/m4/convert_gio.m4 | 5 + 13 files changed, 667 insertions(+) create mode 100644 examples/subprocess/main.cc create mode 100644 gio/src/subprocess.ccg create mode 100644 gio/src/subprocess.hg create mode 100644 gio/src/subprocesslauncher.ccg create mode 100644 gio/src/subprocesslauncher.hg diff --git a/examples/Makefile.am b/examples/Makefile.am index a32cfb84..4de7e4e3 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -33,6 +33,7 @@ check_PROGRAMS = \ properties/example \ regex/example \ settings/settings \ + subprocess/example \ thread/dispatcher \ thread/dispatcher2 @@ -85,6 +86,9 @@ network_socket_server_LDADD = $(giomm_ldadd) settings_settings_SOURCES = settings/settings.cc settings_settings_LDADD = $(giomm_ldadd) +subprocess_example_SOURCES = subprocess/main.cc +subprocess_example_LDADD = $(giomm_ldadd) + dist_noinst_DATA = settings/org.gtkmm.demo.gschema.xml CLEANFILES = settings/gschemas.compiled diff --git a/examples/meson.build b/examples/meson.build index f2c47417..04a7608a 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -17,6 +17,7 @@ examples = [ [['properties'], 'example', ['properties_example.cc'], false], [['regex'], 'example', ['main.cc'], false], [['settings'], 'settings', ['settings.cc', 'org.gtkmm.demo.gschema.xml'], true], + [['subprocess'], 'example', ['main.cc'], true], [['thread'], 'dispatcher', ['dispatcher.cc'], false], [['thread'], 'dispatcher2', ['dispatcher2.cc'], false], ] diff --git a/examples/subprocess/main.cc b/examples/subprocess/main.cc new file mode 100644 index 00000000..f72526ee --- /dev/null +++ b/examples/subprocess/main.cc @@ -0,0 +1,114 @@ +/* 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 . + */ + +#include +#include + +namespace +{ +int finish_pending = 0; + +void on_communicate_finished(Glib::RefPtr& result, + const Glib::RefPtr& subprocess, const Glib::ustring& heading) +{ + try + { + std::cout << "\n" << heading << "\n"; + auto [stdout_buf, stderr_buf] = subprocess->communicate_utf8_finish(result); + std::cout << "stdout_buf: " << stdout_buf << "\n" + << "stderr_buf: " << stderr_buf << "\n"; + } + catch (const Glib::Error& error) + { + std::cerr << "on_communicate_finished(), Glib::Error: " << error.what() << std::endl; + } + catch (const std::exception& error) + { + std::cerr << "on_communicate_finished(), std::exception: " << error.what() << std::endl; + } + --finish_pending; +} +} // anonymous namespace + +int main(int argc, char** argv) +{ + Gio::init(); + + if (argc < 3) + { + std::cerr << "Usage: " << argv[0] << " input-data command [arguments]...\n"; + return 1; + } + + // Three character encodings can be involved: + // 1. The encoding in the user's preferred locale. + // 2. The filename encoding, used by GLib. + // 3. UTF-8. + // The encoding used in argv is determined by the operating system. + // It's assumed to be the encoding in the user's preferred locale, + // which is also the C and C++ global locale. See the documentation of + // Glib::set_init_to_users_preferred_locale(). + try + { + const auto stdin_buf = Glib::locale_to_utf8(argv[1]); + + std::vector arg_vector; + for (int i = 2; i < argc; ++i) + arg_vector.push_back(Glib::filename_from_utf8(Glib::locale_to_utf8(argv[i]))); + + Gio::Subprocess::Flags flags = + Gio::Subprocess::Flags::STDOUT_PIPE | Gio::Subprocess::Flags::STDERR_PIPE; + if (!stdin_buf.empty()) + flags |= Gio::Subprocess::Flags::STDIN_PIPE; + + // This example would be easier with the synchronous communicate_utf8(). + + // Without SubprocessLauncher. + auto subprocess = Gio::Subprocess::create(arg_vector, flags); + ++finish_pending; + subprocess->communicate_utf8_async(stdin_buf, + [&subprocess](Glib::RefPtr& result) + { + on_communicate_finished(result, subprocess, "Without SubprocessLauncher"); + }); + + // With SubprocessLauncher. + auto launcher = Gio::SubprocessLauncher::create(flags); + auto spawned_subprocess = launcher->spawn(arg_vector); + ++finish_pending; + spawned_subprocess->communicate_utf8_async(stdin_buf, + [&spawned_subprocess](Glib::RefPtr& result) + { + on_communicate_finished(result, spawned_subprocess, "With SubprocessLauncher"); + }); + } + catch (const Glib::Error& error) + { + std::cerr << "Glib::Error: " << error.what() << std::endl; + return 1; + } + catch (const std::exception& error) + { + std::cerr << "std::exception: " << error.what() << std::endl; + return 1; + } + + // Wait for on_communicate_finished() to finish. + auto main_context = Glib::MainContext::get_thread_default(); + while (finish_pending > 0) + main_context->iteration(true); + return 0; +} diff --git a/gio/giomm.h b/gio/giomm.h index 6778a914..04f300bd 100644 --- a/gio/giomm.h +++ b/gio/giomm.h @@ -146,6 +146,7 @@ #include #include #include +#include #include #include #include diff --git a/gio/giomm/meson.build b/gio/giomm/meson.build index 1fb44702..4aba4ae2 100644 --- a/gio/giomm/meson.build +++ b/gio/giomm/meson.build @@ -142,6 +142,8 @@ giomm_any_hg_ccg_basenames = [ 'socketlistener', 'socketservice', 'srvtarget', + 'subprocess', + 'subprocesslauncher', 'tcpconnection', 'tcpwrapperconnection', 'threadedsocketservice', diff --git a/gio/src/filelist.am b/gio/src/filelist.am index 12929523..8138f469 100644 --- a/gio/src/filelist.am +++ b/gio/src/filelist.am @@ -127,6 +127,8 @@ giomm_files_any_hg = \ socketlistener.hg \ socketservice.hg \ srvtarget.hg \ + subprocess.hg \ + subprocesslauncher.hg \ tcpconnection.hg \ tcpwrapperconnection.hg \ threadedsocketservice.hg \ diff --git a/gio/src/gio_docs_override.xml b/gio/src/gio_docs_override.xml index c889da8a..5c85a134 100644 --- a/gio/src/gio_docs_override.xml +++ b/gio/src/gio_docs_override.xml @@ -84,6 +84,7 @@ + diff --git a/gio/src/gio_extra_objects.defs b/gio/src/gio_extra_objects.defs index 999ba38e..2123d47d 100644 --- a/gio/src/gio_extra_objects.defs +++ b/gio/src/gio_extra_objects.defs @@ -229,6 +229,18 @@ (gtype-id "G_TYPE_SIMPLE_ACTION") ) +(define-object Subprocess + (in-module "Gio") + (c-name "GSubprocess") + (gtype-id "G_TYPE_SUBPROCESS") +) + +(define-object SubprocessLauncher + (in-module "Gio") + (c-name "GSubprocessLauncher") + (gtype-id "G_TYPE_SUBPROCESS_LAUNCHER") +) + (define-object TlsBackend (in-module "Gio") (c-name "GTlsBackend") diff --git a/gio/src/subprocess.ccg b/gio/src/subprocess.ccg new file mode 100644 index 00000000..73f1d9a3 --- /dev/null +++ b/gio/src/subprocess.ccg @@ -0,0 +1,89 @@ +/* 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 . + */ + +#include +#include + +namespace Gio +{ +Subprocess::Subprocess(const std::vector& argv, Flags flags) +: _CONSTRUCT("argv", Glib::ArrayHandler::vector_to_array(argv).data(), "flags", (GSubprocessFlags)flags) +{ + init(); +} + +std::pair, Glib::RefPtr> +Subprocess::communicate(const Glib::RefPtr& stdin_buf, + const Glib::RefPtr& cancellable) +{ + GError* gerror = nullptr; + GBytes* gstdout_buf = nullptr; + GBytes* gstderr_buf = nullptr; + (void)g_subprocess_communicate(gobj(), + const_cast(Glib::unwrap(stdin_buf)), + Glib::unwrap(cancellable), &gstdout_buf, &gstderr_buf, &gerror); + if (gerror) + ::Glib::Error::throw_exception(gerror); + + return {Glib::wrap(gstdout_buf), Glib::wrap(gstderr_buf)}; +} + +std::pair, Glib::RefPtr> +Subprocess::communicate_finish(const Glib::RefPtr& result) +{ + GError* gerror = nullptr; + GBytes* gstdout_buf = nullptr; + GBytes* gstderr_buf = nullptr; + (void)g_subprocess_communicate_finish(gobj(), Glib::unwrap(result), + &gstdout_buf, &gstderr_buf, &gerror); + if (gerror) + ::Glib::Error::throw_exception(gerror); + + return {Glib::wrap(gstdout_buf), Glib::wrap(gstderr_buf)}; +} + +std::pair +Subprocess::communicate_utf8(const Glib::ustring& stdin_buf, + const Glib::RefPtr& cancellable) +{ + GError* gerror = nullptr; + char* gstdout_buf = nullptr; + char* gstderr_buf = nullptr; + (void)g_subprocess_communicate_utf8(gobj(), Glib::c_str_or_nullptr(stdin_buf), + Glib::unwrap(cancellable), &gstdout_buf, &gstderr_buf, &gerror); + if (gerror) + ::Glib::Error::throw_exception(gerror); + + return {Glib::convert_return_gchar_ptr_to_ustring(gstdout_buf), + Glib::convert_return_gchar_ptr_to_ustring(gstderr_buf)}; +} + +std::pair +Subprocess::communicate_utf8_finish(const Glib::RefPtr& result) +{ + GError* gerror = nullptr; + char* gstdout_buf = nullptr; + char* gstderr_buf = nullptr; + (void)g_subprocess_communicate_utf8_finish(gobj(), Glib::unwrap(result), + &gstdout_buf, &gstderr_buf, &gerror); + if (gerror) + ::Glib::Error::throw_exception(gerror); + + return {Glib::convert_return_gchar_ptr_to_ustring(gstdout_buf), + Glib::convert_return_gchar_ptr_to_ustring(gstderr_buf)}; +} + +} // namespace Gio diff --git a/gio/src/subprocess.hg b/gio/src/subprocess.hg new file mode 100644 index 00000000..1b6322ca --- /dev/null +++ b/gio/src/subprocess.hg @@ -0,0 +1,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 . + */ + +_CONFIGINCLUDE(giommconfig.h) + +#include +#include +#include +#include +#include +#include +#include +#include // 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& 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& argv, Flags flags = Flags::NONE) + + _WRAP_METHOD(Glib::RefPtr get_stdin_pipe(), g_subprocess_get_stdin_pipe, refreturn, newin "2,78") + _WRAP_METHOD(Glib::RefPtr get_stdin_pipe() const, g_subprocess_get_stdin_pipe, refreturn, constversion, newin "2,78") + _WRAP_METHOD(Glib::RefPtr get_stdout_pipe(), g_subprocess_get_stdout_pipe, refreturn, newin "2,78") + _WRAP_METHOD(Glib::RefPtr get_stdout_pipe() const, g_subprocess_get_stdout_pipe, refreturn, constversion, newin "2,78") + _WRAP_METHOD(Glib::RefPtr get_stderr_pipe(), g_subprocess_get_stderr_pipe, refreturn, newin "2,78") + _WRAP_METHOD(Glib::RefPtr 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 = {}) const, g_subprocess_wait, errthrow, newin "2,78") + _WRAP_METHOD(void wait_async(const SlotAsyncReady& slot{callback}, const Glib::RefPtr& 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& result) const, g_subprocess_wait_finish, errthrow, newin "2,78") + + _WRAP_METHOD(void wait_check(const Glib::RefPtr& cancellable = {}) const, g_subprocess_wait_check, errthrow, newin "2,78") + _WRAP_METHOD(void wait_check_async(const SlotAsyncReady& slot{callback}, const Glib::RefPtr& 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& 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> + communicate(const Glib::RefPtr& stdin_buf, + const Glib::RefPtr& cancellable = {}); + _IGNORE(g_subprocess_communicate) + + _WRAP_METHOD(void communicate_async(const Glib::RefPtr& stdin_buf{.}, + const SlotAsyncReady& slot{callback}, const Glib::RefPtr& 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> + communicate_finish(const Glib::RefPtr& 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 communicate_utf8(const Glib::ustring& stdin_buf, + const Glib::RefPtr& 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{.} = {}), + 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 communicate_utf8_finish(const Glib::RefPtr& result); + _IGNORE(g_subprocess_communicate_utf8_finish) + + // _IGNORE_PROPERTY("flags", "argv") // write-only, construct-only +}; + +} // namespace Gio diff --git a/gio/src/subprocesslauncher.ccg b/gio/src/subprocesslauncher.ccg new file mode 100644 index 00000000..702b22d0 --- /dev/null +++ b/gio/src/subprocesslauncher.ccg @@ -0,0 +1,62 @@ +/* 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 . + */ + +#include + +namespace +{ +extern "C" +{ +#ifdef G_OS_UNIX +static void SubprocessLauncher_child_setup_callback(void* user_data) +{ + try + { + (*static_cast(user_data))(); + } + catch (...) + { + Glib::exception_handlers_invoke(); + } +} + +static void SubprocessLauncher_child_setup_callback_destroy(void* user_data) +{ + delete static_cast(user_data); +} +#endif // G_OS_UNIX +} // extern "C" +} // anonymous namespace + +namespace Gio +{ +void SubprocessLauncher::inherit_environ() +{ + g_subprocess_launcher_set_environ(gobj(), nullptr); +} + +#ifdef G_OS_UNIX +void SubprocessLauncher::set_child_setup(const Glib::SlotSpawnChildSetup& child_setup) +{ + const bool setup_slot = !child_setup.empty(); + + g_subprocess_launcher_set_child_setup(gobj(), + setup_slot ? &SubprocessLauncher_child_setup_callback : nullptr, + setup_slot ? new Glib::SlotSpawnChildSetup(child_setup) : nullptr, + setup_slot ? &SubprocessLauncher_child_setup_callback_destroy : nullptr); +} +#endif // G_OS_UNIX +} // namespace Gio diff --git a/gio/src/subprocesslauncher.hg b/gio/src/subprocesslauncher.hg new file mode 100644 index 00000000..e96a6ad0 --- /dev/null +++ b/gio/src/subprocesslauncher.hg @@ -0,0 +1,121 @@ +/* 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 . + */ + +_CONFIGINCLUDE(giommconfig.h) + +#include +#include +#include + +_DEFS(giomm,gio) +_PINCLUDE(glibmm/private/object_p.h) + +namespace Gio +{ + +/** Environment options for launching a child process. + * + * This class contains a set of options for launching child processes, + * such as where its standard input and output will be directed, the + * argument list, the environment, and more. + * + * While the Gio::Subprocess class has high level functions covering + * popular cases, use of this class allows access to more advanced + * options. It can also be used to launch multiple subprocesses with + * a similar configuration. + * + * @see Subprocess + * @newin{2,78} + */ +class GIOMM_API SubprocessLauncher : public Glib::Object +{ + _CLASS_GOBJECT(SubprocessLauncher, GSubprocessLauncher, G_SUBPROCESS_LAUNCHER, Glib::Object, GObject, , , GIOMM_API) + +public: + // g_subprocess_launcher_new() does more than call g_object_new(). + _WRAP_METHOD(static Glib::RefPtr create( + Subprocess::Flags flags = Subprocess::Flags::NONE), g_subprocess_launcher_new, newin "2,78") + +#m4 _CONVERSION(`const std::vector&', `const gchar*-const*',`Glib::ArrayHandler::vector_to_array($3).data()') + _WRAP_METHOD(Glib::RefPtr spawn(const std::vector& argv), + g_subprocess_launcher_spawnv, errthrow, newin "2,78") + _IGNORE(g_subprocess_launcher_spawn) + + /** Inherit the parent process’ environment. + * @newin{2,78} + */ + void inherit_environ(); +#m4 _CONVERSION(`const std::vector&', `gchar**',`const_cast(Glib::ArrayHandler::vector_to_array($3).data())') + _WRAP_METHOD(void set_environ(const std::vector& env), + g_subprocess_launcher_set_environ, newin "2,78") + _WRAP_METHOD(void setenv(const std::string& variable, const std::string& value, bool overwrite), + g_subprocess_launcher_setenv, newin "2,78") + _WRAP_METHOD(void unsetenv(const std::string& variable), + g_subprocess_launcher_unsetenv, newin "2,78") + _WRAP_METHOD(std::string getenv(const std::string& variable), + g_subprocess_launcher_getenv, newin "2,78") + + _WRAP_METHOD(void set_cwd(const std::string& cwd), g_subprocess_launcher_set_cwd, newin "2,78") + _WRAP_METHOD(void set_flags(Subprocess::Flags flags), g_subprocess_launcher_set_flags, newin "2,78") + + // Extended I/O control, only available on UNIX. + _WRAP_METHOD(void set_stdin_file_path(const std::string& path{NULL}), + g_subprocess_launcher_set_stdin_file_path, ifdef G_OS_UNIX, newin "2,78") + _WRAP_METHOD(void take_stdin_fd(int fd), g_subprocess_launcher_take_stdin_fd, + ifdef G_OS_UNIX, newin "2,78") + + _WRAP_METHOD(void set_stdout_file_path(const std::string& path{NULL}), + g_subprocess_launcher_set_stdout_file_path, ifdef G_OS_UNIX, newin "2,78") + _WRAP_METHOD(void take_stdout_fd(int fd), g_subprocess_launcher_take_stdout_fd, + ifdef G_OS_UNIX, newin "2,78") + + _WRAP_METHOD(void set_stderr_file_path(const std::string& path{NULL}), + g_subprocess_launcher_set_stderr_file_path, ifdef G_OS_UNIX, newin "2,78") + _WRAP_METHOD(void take_stderr_fd(int fd), g_subprocess_launcher_take_stderr_fd, + ifdef G_OS_UNIX, newin "2,78") + + _WRAP_METHOD(void take_fd(int source_fd, int target_fd), g_subprocess_launcher_take_fd, + ifdef G_OS_UNIX, newin "2,78") + + _WRAP_METHOD(void close(), g_subprocess_launcher_close, ifdef G_OS_UNIX, newin "2,78") + +#ifdef G_OS_UNIX + /** Sets up a child setup function. + * + * The child setup function will be called after fork() but before + * exec() on the child's side. + * + * A copy of the @a child_setup slot is stored. The copy will not be + * automatically deleted on the child's side of the fork(). It will only be + * deleted when the last reference on the %SubprocessLauncher is dropped + * or when a new child setup slot is given. + * + * An empty slot can be given as @a child_setup to disable the functionality. + * + * Child setup functions are only available on UNIX. + * + * @newin{2,78} + * + * @param child_setup A Glib::SlotSpawnChildSetup to use as the child setup function. + */ + void set_child_setup(const Glib::SlotSpawnChildSetup& child_setup = {}); + _IGNORE(g_subprocess_launcher_set_child_setup) +#endif // G_OS_UNIX + + // _IGNORE_PROPERTY("flags") // write-only, construct-only +}; + +} // namespace Gio diff --git a/tools/m4/convert_gio.m4 b/tools/m4/convert_gio.m4 index b75ffdfe..ab0785a7 100644 --- a/tools/m4/convert_gio.m4 +++ b/tools/m4/convert_gio.m4 @@ -75,6 +75,7 @@ _CONV_GIO_ENUM(SocketFamily) _CONV_GIO_INCLASS_ENUM(Socket,MsgFlags) _CONV_GIO_INCLASS_ENUM(Socket,Protocol) _CONV_GIO_INCLASS_ENUM(Socket,Type) +_CONV_GIO_INCLASS_ENUM(Subprocess,Flags) _CONV_GIO_ENUM(TlsCertificateFlags) _CONV_GIO_ENUM(TlsCertificateRequestFlags) _CONV_GIO_INCLASS_ENUM(TlsDatabase,VerifyFlags) @@ -334,6 +335,10 @@ _CONVERSION(`const Glib::RefPtr&',`GSocketConnection*',__CONVE #SocketControlMessage _CONVERSION(`GSocketControlMessage*',`Glib::RefPtr',`Glib::wrap($3)') +#Subprocess, SubprocessLauncher +_CONVERSION(`GSubprocess*',`Glib::RefPtr',`Glib::wrap($3)') +_CONVERSION(`GSubprocessLauncher*',`Glib::RefPtr',`Glib::wrap($3)') + #TimeZoneMonitor _CONVERSION(`GTimeZoneMonitor*',`Glib::RefPtr',`Glib::wrap($3)') -- cgit v1.2.1