diff options
Diffstat (limited to 'libbuild2/utility.cxx')
-rw-r--r-- | libbuild2/utility.cxx | 397 |
1 files changed, 357 insertions, 40 deletions
diff --git a/libbuild2/utility.cxx b/libbuild2/utility.cxx index 3eeeeaa..1135851 100644 --- a/libbuild2/utility.cxx +++ b/libbuild2/utility.cxx @@ -42,7 +42,7 @@ namespace build2 } ostream& - operator<< (ostream& os, const ::butl::path& p) + operator<< (ostream& os, const path& p) { using namespace build2; @@ -53,7 +53,7 @@ namespace build2 } ostream& - operator<< (ostream& os, const ::butl::path_name_view& v) + operator<< (ostream& os, const path_name_view& v) { assert (!v.empty ()); @@ -61,7 +61,7 @@ namespace build2 } ostream& - operator<< (ostream& os, const ::butl::process_path& p) + operator<< (ostream& os, const process_path& p) { using namespace build2; @@ -86,6 +86,51 @@ namespace build2 // namespace build2 { + static const char hex_digits[] = "0123456789abcdef"; + + string + to_string (uint64_t i, int b, size_t w) + { + // One day we can switch to C++17 std::to_chars(). + // + string r; + switch (b) + { + case 10: + { + r = to_string (i); + if (w > r.size ()) + r.insert (0, w - r.size (), '0'); + break; + } + case 16: + { + r.reserve (18); + r += "0x"; + + for (size_t j (64); j != 0; ) + { + j -= 4; + size_t d ((i >> j) & 0x0f); + + // Omit leading zeros but watch out for the i==0 corner case. + // + if (d != 0 || r.size () != 2 || j == 0) + r += hex_digits[d]; + } + + if (w > r.size () - 2) + r.insert (2, w - (r.size () - 2), '0'); + + break; + } + default: + throw invalid_argument ("unsupported base"); + } + + return r; + } + void (*terminate) (bool); process_path argv0; @@ -215,11 +260,10 @@ namespace build2 process run_start (uint16_t verbosity, const process_env& pe, - const char* args[], + const char* const* args, int in, int out, - bool err, - const dir_path& cwd, + int err, const location& l) try { @@ -233,17 +277,15 @@ namespace build2 args, in, out, - (err ? 2 : 1), - (!cwd.empty () - ? cwd.string ().c_str () - : pe.cwd != nullptr ? pe.cwd->string ().c_str () : nullptr), + err, + pe.cwd != nullptr ? pe.cwd->string ().c_str () : nullptr, pe.vars); } catch (const process_error& e) { if (e.child) { - // Note: run_finish() expects this exact message. + // Note: run_finish_impl() below expects this exact message. // cerr << "unable to execute " << args[0] << ": " << e << endl; @@ -258,7 +300,7 @@ namespace build2 } bool - run_wait (const char* args[], process& pr, const location& loc) + run_wait (const char* const* args, process& pr, const location& loc) try { return pr.wait (); @@ -269,55 +311,330 @@ namespace build2 } bool - run_finish_impl (const char* args[], + run_finish_impl (const char* const* args, process& pr, - bool err, + bool f, const string& l, + uint16_t v, + bool omit_normal, const location& loc) - try { tracer trace ("run_finish"); - if (pr.wait ()) - return true; - - const process_exit& e (*pr.exit); - - if (!e.normal ()) - fail (loc) << "process " << args[0] << " " << e; + try + { + if (pr.wait ()) + return true; + } + catch (const process_error& e) + { + fail (loc) << "unable to execute " << args[0] << ": " << e << endf; + } - // Normall but non-zero exit status. + // Note: see similar code in diag_buffer::close(). // - if (err) + const process_exit& pe (*pr.exit); + bool ne (pe.normal ()); + + // Even if the user redirected the diagnostics, one error that we want to + // let through is the inability to execute the program itself. We cannot + // reserve a special exit status to signal this so we will just have to + // compare the output. In a sense, we treat this as a special case of + // abnormal termination. This particular situation will result in a single + // error line printed by run_start() above. + // + if (ne && l.compare (0, 18, "unable to execute ") == 0) + fail (loc) << l; + + if (omit_normal && ne) + { + // While we assume diagnostics has already been issued (to stderr), if + // that's not the case, it's a real pain to debug. So trace it. (And + // if you think that doesn't happen in sensible programs, check GCC + // bug #107448). + // + l4 ([&]{trace << "process " << args[0] << " " << pe;}); + } + else { - // While we assuming diagnostics has already been issued (to STDERR), if - // that's not the case, it's a real pain to debug. So trace it. + // It's unclear whether we should print this only if printing the + // command line (we could also do things differently for normal/abnormal + // exit). Let's print this always and see how it wears. Note that we now + // rely on this in, for example, process_finish(), extract_metadata(). + // + // Note: make sure keep the above trace if decide not to print. // - l4 ([&]{trace << "process " << args[0] << " " << e;}); + diag_record dr; + dr << error (loc) << "process " << args[0] << " " << pe; - throw failed (); + if (verb >= 1 && verb <= v) + { + dr << info << "command line: "; + print_process (dr, args); + } } - // Even if the user asked to suppress diagnostiscs, one error that we - // want to let through is the inability to execute the program itself. - // We cannot reserve a special exit status to signal this so we will - // just have to compare the output. This particular situation will - // result in a single error line printed by run_start() above. - // - if (l.compare (0, 18, "unable to execute ") == 0) - fail (loc) << l; + if (f || !ne) + throw failed (); return false; } - catch (const process_error& e) + + bool + run_finish_impl (diag_buffer& dbuf, + const char* const* args, + process& pr, + bool f, + uint16_t v, + bool on, + const location& loc) { - fail (loc) << "unable to execute " << args[0] << ": " << e << endf; + try + { + pr.wait (); + } + catch (const process_error& e) + { + fail (loc) << "unable to execute " << args[0] << ": " << e << endf; + } + + const process_exit& pe (*pr.exit); + + dbuf.close (args, pe, v, on, loc); + + if (pe) + return true; + + if (f || !pe.normal ()) + throw failed (); + + return false; } void - run_io_error (const char* args[], const io_error& e) + run (context& ctx, + const process_env& pe, + const char* const* args, + uint16_t v) { - fail << "io error reading " << args[0] << " output: " << e << endf; + if (ctx.phase == run_phase::load) + { + process pr (run_start (pe, args)); + run_finish (args, pr, v); + } + else + { + process pr (run_start (pe, + args, + 0 /* stdin */, + 1 /* stdout */, + diag_buffer::pipe (ctx) /* stderr */)); + diag_buffer dbuf (ctx, args[0], pr); + dbuf.read (); + run_finish (dbuf, args, pr, v); + } + } + + bool + run (context& ctx, + uint16_t verbosity, + const process_env& pe, + const char* const* args, + uint16_t finish_verbosity, + const function<bool (string&, bool)>& f, + bool tr, + bool err, + bool ignore_exit, + sha256* checksum) + { + assert (!err || !ignore_exit); + + if (!err || ctx.phase == run_phase::load) + { + process pr (run_start (verbosity, + pe, + args, + 0 /* stdin */, + -1 /* stdout */, + err ? 2 : 1 /* stderr */)); + + string l; // Last line of output. + try + { + ifdstream is (move (pr.in_ofd), fdstream_mode::skip); + + bool empty (true); + + // Make sure we keep the last line. + // + for (bool last (is.peek () == ifdstream::traits_type::eof ()); + !last && getline (is, l); ) + { + last = (is.peek () == ifdstream::traits_type::eof ()); + + if (tr) + trim (l); + + if (checksum != nullptr) + checksum->append (l); + + if (empty) + { + empty = f (l, last); + + if (!empty && checksum == nullptr) + break; + } + } + + is.close (); + } + catch (const io_error& e) + { + if (run_wait (args, pr)) + fail << "io error reading " << args[0] << " output: " << e << endf; + + // If the child process has failed then assume the io error was + // caused by that and let run_finish() deal with it. + } + + // Omit normal exit code diagnostics if err is false. + // + if (!(run_finish_impl (args, pr, err, l, finish_verbosity, !err) || + ignore_exit)) + return false; + } + else + { + // We have to use the non-blocking setup since we have to read from stdout + // and stderr simultaneously. + // + process pr (run_start (verbosity, + pe, + args, + 0 /* stdin */, + -1 /* stdout */, + diag_buffer::pipe (ctx) /* stderr */)); + + // Note that while we read both streams until eof in the normal + // circumstances, we cannot use fdstream_mode::skip for the exception + // case on both of them: we may end up being blocked trying to read one + // stream while the process may be blocked writing to the other. So in + // case of an exception we only skip the diagnostics and close stdout + // hard. The latter should happen first so the order of the dbuf/is + // variables is important. + // + diag_buffer dbuf (ctx, args[0], pr, (fdstream_mode::non_blocking | + fdstream_mode::skip)); + try + { + ifdstream is (move (pr.in_ofd), + fdstream_mode::non_blocking, + ifdstream::badbit); + + bool empty (true); + + // Read until we reach EOF on all streams. + // + // Note that if dbuf is not opened, then we automatically get an + // inactive nullfd entry. + // + fdselect_set fds {is.fd (), dbuf.is.fd ()}; + fdselect_state& ist (fds[0]); + fdselect_state& dst (fds[1]); + + // To detect the last line we are going keep the previous line and + // only call the function once we've read the next. + // + optional<string> pl; + + for (string l; ist.fd != nullfd || dst.fd != nullfd; ) + { + if (ist.fd != nullfd && getline_non_blocking (is, l)) + { + if (eof (is)) + { + if (pl && empty) + f (*pl, true /* last */); + + ist.fd = nullfd; + } + else + { + if (checksum != nullptr || empty) + { + if (tr) + trim (l); + + if (checksum != nullptr) + checksum->append (l); + + if (empty) + { + if (pl) + { + if ((empty = f (*pl, false /* last */))) + swap (l, *pl); + + // Note that we cannot bail out like in the other version + // since we don't have the skip mode on is. Plus, we might + // still have the diagnostics. + } + else + pl = move (l); + } + } + + l.clear (); + } + + continue; + } + + ifdselect (fds); + + if (dst.ready) + { + if (!dbuf.read ()) + dst.fd = nullfd; + } + } + + is.close (); + } + catch (const io_error& e) + { + if (run_wait (args, pr)) + { + // Note that we will drop the diagnostics in this case since reading + // it could have been the cause of this error. + // + fail << "io error reading " << args[0] << " output: " << e << endf; + } + + // If the child process has failed then assume the io error was caused + // by that and let run_finish() deal with it. + } + + run_finish_impl (dbuf, args, pr, true /* fail */, finish_verbosity); + } + + return true; + } + + cstrings + process_args (const char* program, const strings& args) + { + cstrings r; + r.reserve (args.size () + 2); + + r.push_back (program); + + for (const string& a: args) + r.push_back (a.c_str ()); + + r.push_back (nullptr); + return r; } fdpipe |