aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--butl/fdstream11
-rw-r--r--butl/fdstream.cxx16
-rw-r--r--butl/process15
-rw-r--r--butl/process.cxx104
-rw-r--r--tests/process/driver.cxx65
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&)
{