aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2017-08-05 04:06:53 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2017-08-06 12:27:55 +0300
commit561a189f49441a4d211c0217dce8127f2ce7c32e (patch)
treec7928517d65fe7ee9deeaa677d91fd95650c6a9b
parent07f657754b0af656ee48c38540805fcec7cee27d (diff)
Fix printing progress to non-terminal STDERR
-rw-r--r--libbutl/diagnostics.cxx28
-rw-r--r--libbutl/diagnostics.hxx15
-rw-r--r--libbutl/fdstream.cxx116
-rw-r--r--libbutl/fdstream.hxx6
4 files changed, 151 insertions, 14 deletions
diff --git a/libbutl/diagnostics.cxx b/libbutl/diagnostics.cxx
index 4cfc291..73eb155 100644
--- a/libbutl/diagnostics.cxx
+++ b/libbutl/diagnostics.cxx
@@ -19,7 +19,8 @@
#include <cstddef> // size_t
#include <iostream> // cerr
-#include <libbutl/fdstream.hxx> // stderr_fd()
+#include <libbutl/optional.hxx>
+#include <libbutl/fdstream.hxx> // stderr_fd(), fdterm()
using namespace std;
@@ -33,6 +34,8 @@ namespace butl
static string diag_progress_blank; // Being printed blanks out the line.
static size_t diag_progress_size; // Size of the last printed progress.
+ static optional<bool> diag_term;
+
// Print the progress string to STDERR. Ignore underlying OS errors (this is
// a progress bar after all, and throwing from dtors wouldn't be nice). Must
// be called with the diag_mutex being aquired.
@@ -46,18 +49,31 @@ namespace butl
static inline void
progress_print (string& s)
{
- // If the new progress string is shorter than the printed one, then we will
- // complement it with the required number of spaces (to overwrite the
- // trailing junk) prior to printing, and restore it afterwards.
+ if (!diag_term)
+ try
+ {
+ diag_term = fdterm (stderr_fd());
+ }
+ catch (const ios::failure&)
+ {
+ diag_term = false;
+ }
+
+ // If we print to a terminal, and the new progress string is shorter than
+ // the printed one, then we will complement it with the required number of
+ // spaces (to overwrite the trailing junk) prior to printing, and restore
+ // it afterwards.
//
size_t n (s.size ());
- if (n < diag_progress_size)
+ if (*diag_term && n < diag_progress_size)
s.append (diag_progress_size - n, ' ');
if (!s.empty ())
{
- s += '\r'; // Position the cursor at the beginning of the line.
+ s += *diag_term
+ ? '\r' // Position the cursor at the beginning of the line.
+ : '\n';
try
{
diff --git a/libbutl/diagnostics.hxx b/libbutl/diagnostics.hxx
index 9544945..c1491d6 100644
--- a/libbutl/diagnostics.hxx
+++ b/libbutl/diagnostics.hxx
@@ -52,13 +52,14 @@ namespace butl
// Progress line facility.
//
// The idea is to keep a progress line at the bottom of the terminal with
- // other output scrolling above it. The printing of the progress line is
- // integrated into diag_stream_lock and diag_progress_lock. To print or
- // update the progress acquire diag_progress_lock and update the
- // diag_progress string. To remove the progress line, set this string to
- // empty. For better readability start the progress line with a space
- // (which is where the cursor will be parked). Should only be used if
- // diag_stream points to std::cerr.
+ // other output scrolling above it. For a non-terminal STDERR the progress
+ // line is printed as a regular one terminated with the newline character.
+ // The printing of the progress line is integrated into diag_stream_lock and
+ // diag_progress_lock. To print or update the progress acquire
+ // diag_progress_lock and update the diag_progress string. To remove the
+ // progress line, set this string to empty. For better readability start the
+ // progress line with a space (which is where the cursor will be parked).
+ // Should only be used if diag_stream points to std::cerr.
//
// Note that child processes writing to the same stream may not completely
// overwrite the progress line so in this case it makes sense to keep the
diff --git a/libbutl/fdstream.cxx b/libbutl/fdstream.cxx
index 7681c88..ab78e6a 100644
--- a/libbutl/fdstream.cxx
+++ b/libbutl/fdstream.cxx
@@ -7,7 +7,7 @@
#ifndef _WIN32
# include <fcntl.h> // open(), O_*, fcntl()
# include <unistd.h> // close(), read(), write(), lseek(), dup(), pipe(),
- // ssize_t, STD*_FILENO
+ // isatty(), ssize_t, STD*_FILENO
# include <sys/uio.h> // writev(), iovec
# include <sys/stat.h> // stat(), S_I*
# include <sys/types.h> // stat, off_t
@@ -20,6 +20,8 @@
# include <stdio.h> // _fileno(), stdin, stdout, stderr
# include <fcntl.h> // _O_*
# include <sys/stat.h> // S_I*
+
+# include <cwchar> // wcsncmp(), wcsstr()
#endif
#include <errno.h> // errno, E*
@@ -928,6 +930,26 @@ namespace butl
return r;
}
+ bool
+ fdterm (int fd)
+ {
+ int r (isatty (fd));
+
+ if (r == 1)
+ return true;
+
+ // POSIX specifies ENOTTY errno code for an indication that the descriptor
+ // doesn't refer to a terminal. However, both Linux and FreeBSD man pages
+ // also mention EINVAL for this case.
+ //
+ assert (r == 0);
+
+ if (errno == ENOTTY || errno == EINVAL)
+ return false;
+
+ throw_ios_failure (errno);
+ }
+
#else
auto_fd
@@ -1108,5 +1130,97 @@ namespace butl
return {auto_fd (pd[0]), auto_fd (pd[1])};
}
+
+ bool
+ fdterm (int fd)
+ {
+ // Resolve file descriptor to HANDLE. Note that the handle is closed either
+ // when CloseHandle() is called for it or when _close() is called for the
+ // associated file descriptor. So we don't need to close it.
+ //
+ HANDLE h (reinterpret_cast<HANDLE> (_get_osfhandle (fd)));
+ if (h == INVALID_HANDLE_VALUE)
+ throw_ios_failure (errno);
+
+ // Obtain the descriptor type.
+ //
+ DWORD t (GetFileType (h));
+ DWORD e;
+
+ if (t == FILE_TYPE_UNKNOWN && (e = GetLastError ()) != 0)
+ throw_ios_system_failure (e);
+
+ if (t == FILE_TYPE_CHAR) // Terminal.
+ return true;
+
+ if (t != FILE_TYPE_PIPE) // Pipe still can be a terminal (see below).
+ return false;
+
+ // MSYS2 terminal file descriptor has the pipe type. To distinguish it
+ // from other pipes we will try to obtain the associated file name and
+ // heuristically decide if it is a terminal or not. If we fail to obtain
+ // the name (for any reason), then we consider the descriptor as not
+ // referring to a terminal.
+ //
+ // Note that the API we need to use is only available starting with
+ // Windows Vista/Server 2008. To allow programs linked to libbutl to run
+ // on earlier Windows versions we will link the API in run-time and will
+ // fallback to the non-terminal descriptor type on failure. We also need
+ // to partially reproduce the original API types.
+ //
+ HMODULE kh (GetModuleHandle ("kernel32.dll"));
+ if (kh == nullptr)
+ return false;
+
+ // The original type is FILE_INFO_BY_HANDLE_CLASS enum.
+ //
+ enum file_info
+ {
+ file_name = 2
+ };
+
+ using func = BOOL (*) (HANDLE, file_info, LPVOID, DWORD);
+
+ func f (reinterpret_cast<func> (
+ GetProcAddress (kh, "GetFileInformationByHandleEx")));
+
+ if (f == nullptr)
+ return false;
+
+ // The original type is FILE_NAME_INFO structure.
+ //
+ struct
+ {
+ DWORD length;
+ wchar_t name[_MAX_PATH + 1];
+ } fn;
+
+ // Reserve space for the trailing NULL character.
+ //
+ if (!f (h, file_name, &fn, sizeof (fn) - sizeof (wchar_t)))
+ return false;
+
+ // Add the trailing NULL character. Sounds strange, but the name length is
+ // expressed in bytes.
+ //
+ fn.name[fn.length / sizeof (wchar_t)] = L'\0';
+
+ // The MSYS2 terminal descriptor file name looks like this:
+ //
+ // \msys-dd50a72ab4668b33-pty0-to-master
+ //
+ // We will recognize it by the '\msys-' prefix and the presence of the
+ // '-ptyN' entry.
+ //
+ if (wcsncmp (fn.name, L"\\msys-", 6) == 0)
+ {
+ const wchar_t* e (wcsstr (fn.name, L"-pty"));
+
+ return e != nullptr && e[4] >= L'0' && e[4] <= L'9' &&
+ (e[5] == L'-' || e[5] == L'\0');
+ }
+
+ return false;
+ }
#endif
}
diff --git a/libbutl/fdstream.hxx b/libbutl/fdstream.hxx
index fa62ff4..3bd8e8b 100644
--- a/libbutl/fdstream.hxx
+++ b/libbutl/fdstream.hxx
@@ -686,6 +686,12 @@ namespace butl
//
LIBBUTL_SYMEXPORT fdpipe
fdopen_pipe (fdopen_mode = fdopen_mode::none);
+
+ // Test whether a file descriptor refers to a terminal. Throw ios::failure on
+ // the underlying OS error.
+ //
+ LIBBUTL_SYMEXPORT bool
+ fdterm (int);
}
#include <libbutl/fdstream.ixx>