diff options
-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 | ||||
-rw-r--r-- | tests/process/driver.cxx | 65 |
5 files changed, 120 insertions, 91 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) diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index ba61ea8..efb4ca3 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -35,42 +35,28 @@ exec (const path& p, const char* cwd (!wd.empty () ? wd.string ().c_str () : nullptr); - auto args = [&p, bin, &cwd](bool i, bool o, bool e) -> cstrings - { - cstrings a {p.string ().c_str (), "-c"}; - - if (i) - a.push_back ("-i"); - - if (o) - a.push_back ("-o"); + cstrings args {p.string ().c_str (), "-c"}; - if (e) - a.push_back ("-e"); + if (bin) + args.push_back ("-b"); - if (bin) - a.push_back ("-b"); + if (cwd != nullptr) + args.push_back (cwd); - if (cwd != nullptr) - a.push_back (cwd); - - a.push_back (nullptr); - return a; - }; + args.push_back (nullptr); try { bool r (true); - cstrings a (args (!in.empty (), out, err)); // If both o and e are true, then redirect STDERR to STDOUT, so both can be // read from the same stream. // process pr (cwd, - a.data (), - !in.empty () ? -1 : 0, - out ? -1 : 1, - err ? (out ? 1 : -1) : 2); + args.data (), + !in.empty () ? -1 : -2, + out ? -1 : -2, + err ? (out ? 1 : -1) : -2); try { @@ -103,9 +89,8 @@ exec (const path& p, // input fd as an output for another one (pr2.out = pr3.in). The // overall pipeline looks like 'os -> pr -> pr2 -> pr3 -> is'. // - cstrings a (args (true, true, false)); - process pr3 (cwd, a.data (), -1, -1); - process pr2 (cwd, a.data (), pr, bin_mode (pr3.out_fd)); + process pr3 (cwd, args.data (), -1, -1, -2); + process pr2 (cwd, args.data (), pr, bin_mode (pr3.out_fd), -2); bool cr (fdclose (pr3.out_fd)); assert (cr); @@ -184,9 +169,6 @@ main (int argc, const char* argv[]) try { bool child (false); - bool in (false); - bool out (false); - bool err (false); bool bin (false); dir_path wd; // Working directory. @@ -198,12 +180,6 @@ try string v (argv[i]); if (v == "-c") child = true; - else if (v == "-i") - in = true; - else if (v == "-o") - out = true; - else if (v == "-e") - err = true; else if (v == "-b") bin = true; else @@ -255,9 +231,6 @@ try if (!wd.empty () && wd.realize () != dir_path::current ()) return 1; - if (!in) - return 0; // Nothing to read, so nothing to write. - try { if (bin) @@ -270,17 +243,11 @@ try vector<char> data ((istreambuf_iterator<char> (cin)), istreambuf_iterator<char> ()); - if (out) - { - cout.exceptions (istream::badbit); - copy (data.begin (), data.end (), ostream_iterator<char> (cout)); - } + cout.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator<char> (cout)); - if (err) - { - cerr.exceptions (istream::badbit); - copy (data.begin (), data.end (), ostream_iterator<char> (cerr)); - } + cerr.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator<char> (cerr)); } catch (const ios_base::failure&) { |