diff options
Diffstat (limited to 'butl')
-rw-r--r-- | butl/fdstream | 11 | ||||
-rw-r--r-- | butl/fdstream.cxx | 16 | ||||
-rw-r--r-- | butl/process | 15 | ||||
-rw-r--r-- | butl/process.cxx | 104 |
4 files changed, 104 insertions, 42 deletions
diff --git a/butl/fdstream b/butl/fdstream index f144578..0be3708 100644 --- a/butl/fdstream +++ b/butl/fdstream @@ -142,7 +142,7 @@ namespace butl fdtranslate stdout_fdmode (fdtranslate); fdtranslate stderr_fdmode (fdtranslate); - // Low-level, nothrow file descriptor API. Primarily useful in tests. + // Low-level, nothrow file descriptor API. // // Close the file descriptor. Return true on success, set errno and return @@ -150,6 +150,15 @@ namespace butl // bool fdclose (int) noexcept; + + // Open the null device (e.g., /dev/null) that discards all data written to + // it and provides no data for read operations (i.e., yelds EOF on read). + // Return file descriptor on success, set errno and return -1 otherwise. + // Note that it's the caller's responsibility to close the returned file + // descriptor. + // + int + fdnull () noexcept; } #endif // BUTL_FDSTREAM diff --git a/butl/fdstream.cxx b/butl/fdstream.cxx index d99be70..13becfa 100644 --- a/butl/fdstream.cxx +++ b/butl/fdstream.cxx @@ -5,9 +5,11 @@ #include <butl/fdstream> #ifndef _WIN32 +# include <fcntl.h> // open(), O_RDWR # include <unistd.h> // close(), read(), write() #else -# include <io.h> // _close(), _read(), _write(), _setmode() +# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen() +# include <share.h> // _SH_DENYNO # include <stdio.h> // _fileno(), stdin, stdout, stderr # include <fcntl.h> // _O_BINARY, _O_TEXT #endif @@ -153,6 +155,12 @@ namespace butl return close (fd) == 0; } + int + fdnull () noexcept + { + return open ("/dev/null", O_RDWR); + } + fdtranslate fdmode (int, fdtranslate) { @@ -185,6 +193,12 @@ namespace butl return _close (fd) == 0; } + int + fdnull () noexcept + { + return _sopen ("nul", _O_RDWR, _SH_DENYNO); + } + fdtranslate fdmode (int fd, fdtranslate m) { diff --git a/butl/process b/butl/process index 0f68943..54a8dae 100644 --- a/butl/process +++ b/butl/process @@ -38,11 +38,16 @@ namespace butl { public: // Start another process using the specified command line. The default - // values to the in, out and err arguments indicate that the child - // process should inherit the parent process stdin, stdout, and stderr, + // values to the in, out and err arguments indicate that the child process + // should inherit the parent process stdin, stdout, and stderr, // respectively. If -1 is passed instead, then the corresponding child // process descriptor is connected (via a pipe) to out_fd for stdin, - // in_ofd for stdout, and in_efd for stderr (see data members below). + // in_ofd for stdout, and in_efd for stderr (see data members below). If + // -2 is passed, then the corresponding child process descriptor is + // replaced with the null device descriptor (e.g., /dev/null). This + // results in the child process not being able to read anything from stdin + // (gets immediate EOF) and all data written to stdout/stderr being + // discarded. // // On Windows parent process pipe descriptors are set to text mode to be // consistent with the default (text) mode of standard file descriptors of @@ -52,8 +57,8 @@ namespace butl // translated into the 0xD, 0xA sequence. Use the _setmode() function to // change the mode, if required. // - // Instead of passing -1 or the default value, you can also pass your own - // descriptors. Note, however, that in this case they are not closed by + // Instead of passing -1, -2 or the default value, you can also pass your + // own descriptors. Note, however, that in this case they are not closed by // the parent. So you should do this yourself, if required. For example, // to redirect the child process stdout to stderr, you can do: // diff --git a/butl/process.cxx b/butl/process.cxx index 1a795ea..4b834fb 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -25,6 +25,8 @@ #include <cassert> +#include <butl/fdstream> // fdnull(), fdclose() + using namespace std; namespace butl @@ -56,16 +58,13 @@ namespace butl { if (fd_ != -1) { -#ifndef _WIN32 - int r (close (fd_)); -#else - int r (_close (fd_)); -#endif + bool r (fdclose (fd_)); + // The valid file descriptor that has no IO operations being // performed on it should close successfully, unless something is // severely damaged. // - assert (r != -1); + assert (r); } fd_ = fd; @@ -98,14 +97,31 @@ namespace butl p[1].reset (pd[1]); }; + auto create_null = [&fail](auto_fd& n) + { + int fd (fdnull ()); + if (fd == -1) + fail (false); + + n.reset (fd); + }; + + // If we are asked to open null (-2) then open "half-pipe". + // if (in == -1) create_pipe (out_fd); + else if (in == -2) + create_null (out_fd[0]); if (out == -1) create_pipe (in_ofd); + else if (out == -2) + create_null (in_ofd[1]); if (err == -1) create_pipe (in_efd); + else if (err == -2) + create_null (in_efd[1]); handle = fork (); @@ -116,16 +132,16 @@ namespace butl { // Child. // - // Duplicate the user-supplied (fd != -1) or the created pipe descriptor + // Duplicate the user-supplied (fd > -1) or the created pipe descriptor // to the standard stream descriptor (read end for STDIN_FILENO, write // end otherwise). Close the the pipe afterwards. // auto duplicate = [&fail](int sd, int fd, pipe& pd) { - if (fd == -1) + if (fd == -1 || fd == -2) fd = pd[sd == STDIN_FILENO ? 0 : 1].get (); - assert (fd != -1); + assert (fd > -1); if (dup2 (fd, sd) == -1) fail (true); @@ -385,14 +401,55 @@ namespace butl fail (); }; + // Resolve file descriptor to HANDLE and make sure it is inherited. Note + // that the handle is closed either when CloseHandle() is called for it or + // when _close() is called for the associated file descriptor. Make sure + // that either the original file descriptor or the resulted HANDLE is + // closed but not both of them. + // + auto get_osfhandle = [&fail](int fd) -> HANDLE + { + HANDLE h (reinterpret_cast<HANDLE> (_get_osfhandle (fd))); + if (h == INVALID_HANDLE_VALUE) + fail ("unable to obtain file handle"); + + // SetHandleInformation() fails for standard handles. We assume they are + // inherited by default. + // + if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) + { + if (!SetHandleInformation ( + h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + fail (); + } + + return h; + }; + + auto create_null = [&get_osfhandle, &fail](auto_handle& n) + { + auto_fd fd (fdnull ()); + if (fd.get () == -1) + fail (); + + n.reset (get_osfhandle (fd.get ())); + fd.release (); // Not to close the handle twice. + }; + if (in == -1) create_pipe (out_h, 1); + else if (in == -2) + create_null (out_h[0]); if (out == -1) create_pipe (in_oh, 0); + else if (out == -2) + create_null (in_oh[1]); if (err == -1) create_pipe (in_eh, 0); + else if (err == -2) + create_null (in_eh[1]); // Create the process. // @@ -447,42 +504,19 @@ namespace butl si.cb = sizeof (STARTUPINFO); si.dwFlags |= STARTF_USESTDHANDLES; - // Resolve file descriptor to HANDLE and make sure it is inherited. Note - // that the handle is closed when _close() is called for the associated - // file descriptor. - // - auto get_osfhandle = [&fail](int fd) -> HANDLE - { - HANDLE h (reinterpret_cast<HANDLE> (_get_osfhandle (fd))); - if (h == INVALID_HANDLE_VALUE) - fail ("unable to obtain file handle"); - - // SetHandleInformation() fails for standard handles. We assume they are - // inherited by default. - // - if (fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO) - { - if (!SetHandleInformation ( - h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) - fail (); - } - - return h; - }; - - si.hStdInput = in == -1 + si.hStdInput = in == -1 || in == -2 ? out_h[0].get () : in == STDIN_FILENO ? GetStdHandle (STD_INPUT_HANDLE) : get_osfhandle (in); - si.hStdOutput = out == -1 + si.hStdOutput = out == -1 || out == -2 ? in_oh[1].get () : out == STDOUT_FILENO ? GetStdHandle (STD_OUTPUT_HANDLE) : get_osfhandle (out); - si.hStdError = err == -1 + si.hStdError = err == -1 || err == -2 ? in_eh[1].get () : err == STDERR_FILENO ? GetStdHandle (STD_ERROR_HANDLE) |