aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSE4
-rw-r--r--butl/base64.cxx4
-rw-r--r--butl/fdstream43
-rw-r--r--butl/fdstream.cxx120
-rw-r--r--butl/filesystem8
-rw-r--r--butl/filesystem.cxx233
-rw-r--r--butl/filesystem.ixx29
-rw-r--r--butl/pager.cxx27
-rw-r--r--butl/path.cxx8
-rw-r--r--butl/process35
-rw-r--r--butl/process.cxx564
-rw-r--r--butl/strptime.c629
-rw-r--r--butl/strptime.c.orig689
-rw-r--r--butl/timelocal.c157
-rw-r--r--butl/timelocal.c.orig153
-rw-r--r--butl/timelocal.h65
-rw-r--r--butl/timelocal.h.orig61
-rw-r--r--butl/timestamp.cxx221
-rw-r--r--tests/buildfile4
-rw-r--r--tests/dir-iterator/driver.cxx3
-rw-r--r--tests/pager/buildfile7
-rw-r--r--tests/pager/driver.cxx111
-rw-r--r--tests/path/driver.cxx17
-rw-r--r--tests/process/buildfile7
-rw-r--r--tests/process/driver.cxx357
-rw-r--r--tests/timestamp/driver.cxx13
27 files changed, 3231 insertions, 339 deletions
diff --git a/.gitignore b/.gitignore
index e97827c..f01859a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,5 @@
*.d
*.so
*.a
+*.exe
core
diff --git a/LICENSE b/LICENSE
index 0579b2b..b00f6d1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
-butl/sha256c.c:
+butl/sha256c.c, butl/strptime.c, butl/timelocal.h, butl/timelocal.c:
-2-clause BSD, see the file header for details.
+2-clause BSD, see the file headers for details.
The rest:
diff --git a/butl/base64.cxx b/butl/base64.cxx
index 2939b46..c2da936 100644
--- a/butl/base64.cxx
+++ b/butl/base64.cxx
@@ -159,8 +159,8 @@ namespace butl
is.setstate (istream::eofbit);
}
- std::string
- base64_encode (const std::vector<char>& v)
+ string
+ base64_encode (const vector<char>& v)
{
string r;
back_insert_iterator<string> o (r);
diff --git a/butl/fdstream b/butl/fdstream
index 3f96f57..f144578 100644
--- a/butl/fdstream
+++ b/butl/fdstream
@@ -76,37 +76,80 @@ namespace butl
char buf_[2048];
};
+ // File descriptor translation mode. It has the same semantics as the
+ // binary/text opening modes in std::fstream. Specifically, this is a
+ // noop for POSIX systems where the two modes are the same.
+ //
+ enum class fdtranslate
+ {
+ text,
+ binary
+ };
+
class fdstream_base
{
protected:
fdstream_base () = default;
fdstream_base (int fd): buf_ (fd) {}
+ fdstream_base (int, fdtranslate);
protected:
fdbuf buf_;
};
+ // Note that the destructor may throw.
+ //
class ifdstream: fdstream_base, public std::istream
{
public:
ifdstream (): std::istream (&buf_) {}
ifdstream (int fd): fdstream_base (fd), std::istream (&buf_) {}
+ ifdstream (int fd, fdtranslate m)
+ : fdstream_base (fd, m), std::istream (&buf_) {}
void close () {buf_.close ();}
void open (int fd) {buf_.open (fd);}
bool is_open () const {return buf_.is_open ();}
};
+ // Note that the destructor flushes the stream and may throw.
+ //
class ofdstream: fdstream_base, public std::ostream
{
public:
ofdstream (): std::ostream (&buf_) {}
ofdstream (int fd): fdstream_base (fd), std::ostream (&buf_) {}
+ ofdstream (int fd, fdtranslate m)
+ : fdstream_base (fd, m), std::ostream (&buf_) {}
+
+ ~ofdstream () override {if (is_open ()) buf_.sync ();}
void close () {flush (); buf_.close ();}
void open (int fd) {buf_.open (fd);}
bool is_open () const {return buf_.is_open ();}
};
+
+ // Set the translation mode for the file descriptor. Return the previous
+ // mode on success, throw std::system_error otherwise.
+ //
+ fdtranslate
+ fdmode (int, fdtranslate);
+
+ // Convenience functions for setting the translation mode for standard
+ // streams.
+ //
+ fdtranslate stdin_fdmode (fdtranslate);
+ fdtranslate stdout_fdmode (fdtranslate);
+ fdtranslate stderr_fdmode (fdtranslate);
+
+ // Low-level, nothrow file descriptor API. Primarily useful in tests.
+ //
+
+ // Close the file descriptor. Return true on success, set errno and return
+ // false otherwise.
+ //
+ bool
+ fdclose (int) noexcept;
}
#endif // BUTL_FDSTREAM
diff --git a/butl/fdstream.cxx b/butl/fdstream.cxx
index 4b8ccc2..d99be70 100644
--- a/butl/fdstream.cxx
+++ b/butl/fdstream.cxx
@@ -5,9 +5,11 @@
#include <butl/fdstream>
#ifndef _WIN32
-# include <unistd.h> // close(), read(), write()
+# include <unistd.h> // close(), read(), write()
#else
-# include <io.h> // _close(), _read(), _write()
+# include <io.h> // _close(), _read(), _write(), _setmode()
+# include <stdio.h> // _fileno(), stdin, stdout, stderr
+# include <fcntl.h> // _O_BINARY, _O_TEXT
#endif
#include <system_error>
@@ -16,6 +18,8 @@ using namespace std;
namespace butl
{
+ // fdbuf
+ //
fdbuf::
~fdbuf () {close ();}
@@ -33,11 +37,9 @@ namespace butl
{
if (is_open ())
{
-#ifndef _WIN32
- ::close (fd_);
-#else
- _close (fd_);
-#endif
+ if (!fdclose (fd_))
+ throw system_error (errno, system_category ());
+
fd_ = -1;
}
}
@@ -66,7 +68,7 @@ namespace butl
load ()
{
#ifndef _WIN32
- ssize_t n (::read (fd_, buf_, sizeof (buf_)));
+ ssize_t n (read (fd_, buf_, sizeof (buf_)));
#else
int n (_read (fd_, buf_, sizeof (buf_)));
#endif
@@ -112,9 +114,9 @@ namespace butl
if (n != 0)
{
#ifndef _WIN32
- ssize_t m (::write (fd_, buf_, n));
+ ssize_t m (write (fd_, buf_, n));
#else
- int m (_write (fd_, buf_, static_cast<unsigned int> (sizeof (buf_))));
+ int m (_write (fd_, buf_, n));
#endif
if (m == -1)
@@ -128,4 +130,102 @@ namespace butl
return true;
}
+
+ // fdstream_base
+ //
+ fdstream_base::
+ fdstream_base (int fd, fdtranslate m)
+ : fdstream_base (fd) // Delegate.
+ {
+ // Note that here we rely on fdstream_base() (and fdbuf() which it calls)
+ // to note read from the file.
+ //
+ fdmode (fd, m);
+ }
+
+ // Utility functions
+ //
+#ifndef _WIN32
+
+ bool
+ fdclose (int fd) noexcept
+ {
+ return close (fd) == 0;
+ }
+
+ fdtranslate
+ fdmode (int, fdtranslate)
+ {
+ return fdtranslate::binary;
+ }
+
+ fdtranslate
+ stdin_fdmode (fdtranslate)
+ {
+ return fdtranslate::binary;
+ }
+
+ fdtranslate
+ stdout_fdmode (fdtranslate)
+ {
+ return fdtranslate::binary;
+ }
+
+ fdtranslate
+ stderr_fdmode (fdtranslate)
+ {
+ return fdtranslate::binary;
+ }
+
+#else
+
+ bool
+ fdclose (int fd) noexcept
+ {
+ return _close (fd) == 0;
+ }
+
+ fdtranslate
+ fdmode (int fd, fdtranslate m)
+ {
+ int r (_setmode (fd, m == fdtranslate::binary ? _O_BINARY : _O_TEXT));
+ if (r == -1)
+ throw system_error (errno, system_category ());
+
+ return (r & _O_BINARY) == _O_BINARY
+ ? fdtranslate::binary
+ : fdtranslate::text;
+ }
+
+ fdtranslate
+ stdin_fdmode (fdtranslate m)
+ {
+ int fd (_fileno (stdin));
+ if (fd == -1)
+ throw system_error (errno, system_category ());
+
+ return fdmode (fd, m);
+ }
+
+ fdtranslate
+ stdout_fdmode (fdtranslate m)
+ {
+ int fd (_fileno (stdout));
+ if (fd == -1)
+ throw system_error (errno, system_category ());
+
+ return fdmode (fd, m);
+ }
+
+ fdtranslate
+ stderr_fdmode (fdtranslate m)
+ {
+ int fd (_fileno (stderr));
+ if (fd == -1)
+ throw system_error (errno, system_category ());
+
+ return fdmode (fd, m);
+ }
+
+#endif
}
diff --git a/butl/filesystem b/butl/filesystem
index 53012fd..943a346 100644
--- a/butl/filesystem
+++ b/butl/filesystem
@@ -8,7 +8,9 @@
#include <sys/types.h> // mode_t
#ifndef _WIN32
-# include <dirent.h> // DIR, struct dirent, *dir()
+# include <dirent.h> // DIR
+#else
+# include <stddef.h> // intptr_t
#endif
#include <cstddef> // ptrdiff_t
@@ -244,9 +246,7 @@ namespace butl
#ifndef _WIN32
DIR* h_ {nullptr};
#else
- // Check move implementation.
- //
-# error Win32 implementation lacking
+ intptr_t h_ {-1};
#endif
};
diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx
index e2e9735..72e1812 100644
--- a/butl/filesystem.cxx
+++ b/butl/filesystem.cxx
@@ -4,9 +4,17 @@
#include <butl/filesystem>
+#include <errno.h> // errno, E*
#include <unistd.h> // stat, rmdir(), unlink()
#include <sys/types.h> // stat
-#include <sys/stat.h> // stat, lstat(), S_IS*, mkdir()
+#include <sys/stat.h> // stat(), lstat(), S_IS*, mkdir()
+
+#ifndef _WIN32
+# include <dirent.h> // struct dirent, *dir()
+#else
+# include <io.h> // _find*()
+# include <direct.h> // _mkdir()
+#endif
#include <memory> // unique_ptr
#include <system_error>
@@ -19,7 +27,7 @@ namespace butl
dir_exists (const path& p)
{
struct stat s;
- if (::stat (p.string ().c_str (), &s) != 0)
+ if (stat (p.string ().c_str (), &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
return false;
@@ -34,7 +42,7 @@ namespace butl
file_exists (const path& p)
{
struct stat s;
- if (::stat (p.string ().c_str (), &s) != 0)
+ if (stat (p.string ().c_str (), &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
return false;
@@ -46,11 +54,15 @@ namespace butl
}
mkdir_status
+#ifndef _WIN32
try_mkdir (const dir_path& p, mode_t m)
{
- mkdir_status r (mkdir_status::success);
-
- if (::mkdir (p.string ().c_str (), m) != 0)
+ if (mkdir (p.string ().c_str (), m) != 0)
+#else
+ try_mkdir (const dir_path& p, mode_t)
+ {
+ if (_mkdir (p.string ().c_str ()) != 0)
+#endif
{
int e (errno);
@@ -63,7 +75,7 @@ namespace butl
throw system_error (e, system_category ());
}
- return r;
+ return mkdir_status::success;
}
mkdir_status
@@ -85,7 +97,7 @@ namespace butl
{
rmdir_status r (rmdir_status::success);
- if (::rmdir (p.string ().c_str ()) != 0)
+ if (rmdir (p.string ().c_str ()) != 0)
{
if (errno == ENOENT)
r = rmdir_status::not_exist;
@@ -129,7 +141,7 @@ namespace butl
{
rmfile_status r (rmfile_status::success);
- if (::unlink (p.string ().c_str ()) != 0)
+ if (unlink (p.string ().c_str ()) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
r = rmfile_status::not_exist;
@@ -173,7 +185,7 @@ namespace butl
file_mtime (const path& p)
{
struct stat s;
- if (::stat (p.string ().c_str (), &s) != 0)
+ if (stat (p.string ().c_str (), &s) != 0)
{
if (errno == ENOENT || errno == ENOTDIR)
return timestamp_nonexistent;
@@ -192,25 +204,67 @@ namespace butl
path_permissions (const path& p)
{
struct stat s;
- if (::stat (p.string ().c_str (), &s) != 0)
+ if (stat (p.string ().c_str (), &s) != 0)
throw system_error (errno, system_category ());
- return static_cast<permissions> (
- s.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
+ // VC++ has no S_IRWXU defined. MINGW GCC <= 4.9 has no S_IRWXG, S_IRWXO
+ // defined.
+ //
+ // We could extrapolate user permissions to group/other permissions if
+ // S_IRWXG/S_IRWXO are undefined. That is, we could consider their absence
+ // as meaning that the platform does not distinguish between permissions
+ // for different kinds of users. Let's wait for a use-case first.
+ //
+ mode_t f (S_IREAD | S_IWRITE | S_IEXEC);
+
+#ifdef S_IRWXG
+ f |= S_IRWXG;
+#endif
+
+#ifdef S_IRWXO
+ f |= S_IRWXO;
+#endif
+
+ return static_cast<permissions> (s.st_mode & f);
}
+ // dir_{entry,iterator}
+ //
#ifndef _WIN32
// dir_entry
//
+ dir_iterator::
+ ~dir_iterator ()
+ {
+ if (h_ != nullptr)
+ closedir (h_); // Ignore any errors.
+ }
+
+ dir_iterator& dir_iterator::
+ operator= (dir_iterator&& x)
+ {
+ if (this != &x)
+ {
+ e_ = move (x.e_);
+
+ if (h_ != nullptr && closedir (h_) == -1)
+ throw system_error (errno, system_category ());
+
+ h_ = x.h_;
+ x.h_ = nullptr;
+ }
+ return *this;
+ }
+
entry_type dir_entry::
type (bool link) const
{
path_type p (b_ / p_);
struct stat s;
if ((link
- ? ::stat (p.string ().c_str (), &s)
- : ::lstat (p.string ().c_str (), &s)) != 0)
+ ? stat (p.string ().c_str (), &s)
+ : lstat (p.string ().c_str (), &s)) != 0)
{
throw system_error (errno, system_category ());
}
@@ -233,13 +287,13 @@ namespace butl
//
struct dir_deleter
{
- void operator() (DIR* p) const {if (p != nullptr) ::closedir (p);}
+ void operator() (DIR* p) const {if (p != nullptr) closedir (p);}
};
dir_iterator::
dir_iterator (const dir_path& d)
{
- unique_ptr<DIR, dir_deleter> h (::opendir (d.string ().c_str ()));
+ unique_ptr<DIR, dir_deleter> h (opendir (d.string ().c_str ()));
h_ = h.get ();
if (h_ == nullptr)
@@ -294,7 +348,7 @@ namespace butl
for (;;)
{
errno = 0;
- if (struct dirent* de = ::readdir (h_))
+ if (struct dirent* de = readdir (h_))
{
const char* n (de->d_name);
@@ -311,7 +365,7 @@ namespace butl
{
// End of stream.
//
- ::closedir (h_);
+ closedir (h_);
h_ = nullptr;
}
else
@@ -321,5 +375,146 @@ namespace butl
}
}
#else
+
+ // dir_entry
+ //
+ dir_iterator::
+ ~dir_iterator ()
+ {
+ if (h_ != -1)
+ _findclose (h_); // Ignore any errors.
+ }
+
+ dir_iterator& dir_iterator::
+ operator= (dir_iterator&& x)
+ {
+ if (this != &x)
+ {
+ e_ = move (x.e_);
+
+ if (h_ != -1 && _findclose (h_) == -1)
+ throw system_error (errno, system_category ());
+
+ h_ = x.h_;
+ x.h_ = -1;
+ }
+ return *this;
+ }
+
+ entry_type dir_entry::
+ type (bool) const
+ {
+ // Note that we currently do not support symlinks (yes, there is symlink
+ // support since Vista).
+ //
+ path_type p (b_ / p_);
+
+ struct stat s;
+ if (stat (p.string ().c_str (), &s) != 0)
+ throw system_error (errno, system_category ());
+
+ entry_type r;
+ if (S_ISREG (s.st_mode))
+ r = entry_type::regular;
+ else if (S_ISDIR (s.st_mode))
+ r = entry_type::directory;
+ else
+ r = entry_type::other;
+
+ return r;
+ }
+
+ // dir_iterator
+ //
+ struct auto_dir
+ {
+ explicit
+ auto_dir (intptr_t& h): h_ (&h) {}
+
+ auto_dir (const auto_dir&) = delete;
+ auto_dir& operator= (const auto_dir&) = delete;
+
+ ~auto_dir ()
+ {
+ if (h_ != nullptr && *h_ != -1)
+ _findclose (*h_);
+ }
+
+ void release () {h_ = nullptr;}
+
+ private:
+ intptr_t* h_;
+ };
+
+ dir_iterator::
+ dir_iterator (const dir_path& d)
+ {
+ auto_dir h (h_);
+ e_.b_ = d; // Used by next() to call _findfirst().
+
+ next ();
+ h.release ();
+ }
+
+ void dir_iterator::
+ next ()
+ {
+ for (;;)
+ {
+ bool r;
+ _finddata_t fi;
+
+ if (h_ == -1)
+ {
+ // The call is made from the constructor. Any other call with h_ == -1
+ // is illegal.
+ //
+
+ // Check to distinguish non-existent vs empty directories.
+ //
+ if (!dir_exists (e_.b_))
+ throw system_error (ENOENT, system_category ());
+
+ h_ = _findfirst ((e_.b_ / path ("*")).string ().c_str (), &fi);
+ r = h_ != -1;
+ }
+ else
+ r = _findnext (h_, &fi) == 0;
+
+ if (r)
+ {
+ const char* n (fi.name);
+
+ // Skip '.' and '..'.
+ //
+ if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0')))
+ continue;
+
+ e_.p_ = path (n);
+
+ // We do not support symlinks at the moment.
+ //
+ e_.t_ = fi.attrib & _A_SUBDIR
+ ? entry_type::directory
+ : entry_type::regular;
+
+ e_.lt_ = entry_type::unknown;
+ }
+ else if (errno == ENOENT)
+ {
+ // End of stream.
+ //
+ if (h_ != -1)
+ {
+ _findclose (h_);
+ h_ = -1;
+ }
+ }
+ else
+ throw system_error (errno, system_category ());
+
+ break;
+ }
+ }
#endif
}
diff --git a/butl/filesystem.ixx b/butl/filesystem.ixx
index c7cd93b..86f654c 100644
--- a/butl/filesystem.ixx
+++ b/butl/filesystem.ixx
@@ -84,21 +84,14 @@ namespace butl
// dir_iterator
//
inline dir_iterator::
- dir_iterator (dir_iterator&& x): e_ (std::move (x.e_)), h_ (x.h_)
+ dir_iterator (dir_iterator&& x)
+ : e_ (std::move (x.e_)), h_ (x.h_)
{
+#ifndef _WIN32
x.h_ = nullptr;
- }
-
- inline dir_iterator& dir_iterator::
- operator= (dir_iterator&& x)
- {
- if (this != &x)
- {
- e_ = std::move (x.e_);
- h_ = x.h_;
- x.h_ = nullptr;
- }
- return *this;
+#else
+ x.h_ = -1;
+#endif
}
inline bool
@@ -112,14 +105,4 @@ namespace butl
{
return !(x == y);
}
-
-#ifndef _WIN32
- inline dir_iterator::
- ~dir_iterator ()
- {
- if (h_ != nullptr)
- ::closedir (h_); // Ignore any errors.
- }
-#else
-#endif
}
diff --git a/butl/pager.cxx b/butl/pager.cxx
index 4d0d9eb..9999bb0 100644
--- a/butl/pager.cxx
+++ b/butl/pager.cxx
@@ -7,17 +7,19 @@
#ifndef _WIN32
# include <unistd.h> // close(), STDOUT_FILENO
# include <sys/ioctl.h> // ioctl()
+
+# include <chrono>
+# include <thread> // this_thread::sleep_for()
#else
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
-# include <windows.h> // GetConsoleScreenBufferInfo(), GetStdHandle()
-# include <io.h> // _close()
+# include <windows.h> // GetConsoleScreenBufferInfo(), GetStdHandle(),
+ // Sleep()
+# include <io.h> // _close()
#endif
-#include <chrono>
-#include <thread> // this_thread::sleep_for()
-#include <cstring> // strchr()
+#include <cstring> // strchr()
#include <system_error>
using namespace std;
@@ -44,7 +46,9 @@ namespace butl
col = static_cast<size_t> (w.ws_col);
# endif
#else
-#error TODO: needs testing
+ // This works properly on PowerShell, while for the regular console
+ // (cmd.exe) it always returns 80 columns.
+ //
CONSOLE_SCREEN_BUFFER_INFO w;
if (GetConsoleScreenBufferInfo (GetStdHandle (STD_OUTPUT_HANDLE), &w))
col = static_cast<size_t> (w.srWindow.Right - w.srWindow.Left + 1);
@@ -70,7 +74,8 @@ namespace butl
prompt = "-Ps" + name + " (press q to quit, h for help)";
args.push_back ("less");
- args.push_back ("-R"); // Handle ANSI color.
+ args.push_back ("-R"); // Handle ANSI color.
+
args.push_back (prompt.c_str ());
}
@@ -117,13 +122,19 @@ namespace butl
// approach doesn't work; the pipe is buffered and therefore is always
// ready for writing).
//
+ // MINGW GCC 4.9 doesn't implement this_thread so use Win32 Sleep().
+ //
+#ifndef _WIN32
this_thread::sleep_for (chrono::milliseconds (50));
+#else
+ Sleep (50);
+#endif
bool r;
if (p_.try_wait (r))
{
#ifndef _WIN32
- ::close (p_.out_fd);
+ close (p_.out_fd);
#else
_close (p_.out_fd);
#endif
diff --git a/butl/path.cxx b/butl/path.cxx
index 1f8f195..0ec71d1 100644
--- a/butl/path.cxx
+++ b/butl/path.cxx
@@ -56,7 +56,7 @@ namespace butl
#ifdef _WIN32
char cwd[_MAX_PATH];
- if(_getcwd (cwd, _MAX_PATH) == 0)
+ if (_getcwd (cwd, _MAX_PATH) == 0)
throw system_error (errno, system_category ());
#else
char cwd[PATH_MAX];
@@ -72,7 +72,7 @@ namespace butl
current (string_type const& s)
{
#ifdef _WIN32
- if(_chdir (s.c_str ()) != 0)
+ if (_chdir (s.c_str ()) != 0)
throw system_error (errno, system_category ());
#else
if (chdir (s.c_str ()) != 0)
@@ -193,7 +193,7 @@ namespace butl
{
#ifdef _WIN32
wchar_t wcwd[_MAX_PATH];
- if(_wgetcwd (wcwd, _MAX_PATH) == 0)
+ if (_wgetcwd (wcwd, _MAX_PATH) == 0)
throw system_error (errno, system_category ());
#else
char cwd[PATH_MAX];
@@ -213,7 +213,7 @@ namespace butl
current (string_type const& s)
{
#ifdef _WIN32
- if(_wchdir (s.c_str ()) != 0)
+ if (_wchdir (s.c_str ()) != 0)
throw system_error (errno, system_category ());
#else
char ns[PATH_MAX + 1];
diff --git a/butl/process b/butl/process
index c485835..0f68943 100644
--- a/butl/process
+++ b/butl/process
@@ -17,12 +17,19 @@ namespace butl
{
struct process_error: std::system_error
{
- process_error (int e, bool child)
- : system_error (e, std::system_category ()), child_ (child) {}
-
bool
child () const {return child_;}
+ public:
+
+#ifndef _WIN32
+ process_error (int e, bool child)
+ : system_error (e, std::system_category ()), child_ (child) {}
+#else
+ process_error (const std::string& d)
+ : system_error (ECHILD, std::system_category (), d), child_ (false) {}
+#endif
+
private:
bool child_;
};
@@ -37,11 +44,18 @@ namespace butl
// 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).
//
- // 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 the parent. So you should do this yourself, if required.
- // For example, to redirect the child process stdout to stderr, you
- // can do:
+ // On Windows parent process pipe descriptors are set to text mode to be
+ // consistent with the default (text) mode of standard file descriptors of
+ // the child process. When reading in the text mode the sequence of 0xD,
+ // 0xA characters is translated into the single OxA character and 0x1A is
+ // interpreted as EOF. When writing in the text mode the OxA character is
+ // 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
+ // the parent. So you should do this yourself, if required. For example,
+ // to redirect the child process stdout to stderr, you can do:
//
// process p (..., 0, 2);
//
@@ -104,8 +118,9 @@ namespace butl
using id_type = pid_t;
using status_type = int;
#else
- using handle_type = void*; // Win32 HANDLE
- using id_type = std::uint32_t; // Win32 DWORD
+ using handle_type = void*; // Win32 HANDLE
+ using id_type = std::uint32_t; // Win32 DWORD
+ using status_type = std::uint32_t; // Win32 DWORD
#endif
static id_type
diff --git a/butl/process.cxx b/butl/process.cxx
index f00f03a..1a795ea 100644
--- a/butl/process.cxx
+++ b/butl/process.cxx
@@ -11,9 +11,16 @@
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
-# include <windows.h> // CreatePipe, CreateProcess
-# include <io.h> // _open_osfhandle
+# include <windows.h> // CreatePipe(), CreateProcess()
+# include <io.h> // _open_osfhandle(), _get_osfhandle(), _close()
# include <fcntl.h> // _O_TEXT
+# include <stdlib.h> // getenv()
+# include <sys/types.h> // stat
+# include <sys/stat.h> // stat(), S_IS*
+
+# include <memory> // unique_ptr
+
+# include <butl/path>
#endif
#include <cassert>
@@ -22,79 +29,133 @@ using namespace std;
namespace butl
{
+ class auto_fd
+ {
+ public:
+ explicit
+ auto_fd (int fd = -1) noexcept: fd_ (fd) {}
+
+ auto_fd (const auto_fd&) = delete;
+ auto_fd& operator= (const auto_fd&) = delete;
+
+ ~auto_fd () noexcept {reset ();}
+
+ int
+ get () const noexcept {return fd_;}
+
+ int
+ release () noexcept
+ {
+ int r (fd_);
+ fd_ = -1;
+ return r;
+ }
+
+ void
+ reset (int fd = -1) noexcept
+ {
+ if (fd_ != -1)
+ {
+#ifndef _WIN32
+ int r (close (fd_));
+#else
+ int r (_close (fd_));
+#endif
+ // The valid file descriptor that has no IO operations being
+ // performed on it should close successfully, unless something is
+ // severely damaged.
+ //
+ assert (r != -1);
+ }
+
+ fd_ = fd;
+ }
+
+ private:
+ int fd_;
+ };
+
#ifndef _WIN32
process::
process (const char* cwd, char const* const args[], int in, int out, int err)
{
- int out_fd[2] = {in, 0};
- int in_ofd[2] = {0, out};
- int in_efd[2] = {0, err};
+ using pipe = auto_fd[2];
+
+ pipe out_fd;
+ pipe in_ofd;
+ pipe in_efd;
- if ((in == -1 && pipe (out_fd) == -1) ||
- (out == -1 && pipe (in_ofd) == -1) ||
- (err == -1 && pipe (in_efd) == -1))
- throw process_error (errno, false);
+ auto fail = [](bool child) {throw process_error (errno, child);};
+
+ auto create_pipe = [&fail](pipe& p)
+ {
+ int pd[2];
+ if (::pipe (pd) == -1)
+ fail (false);
+
+ p[0].reset (pd[0]);
+ p[1].reset (pd[1]);
+ };
+
+ if (in == -1)
+ create_pipe (out_fd);
+
+ if (out == -1)
+ create_pipe (in_ofd);
+
+ if (err == -1)
+ create_pipe (in_efd);
handle = fork ();
if (handle == -1)
- throw process_error (errno, false);
+ fail (false);
if (handle == 0)
{
- // Child. If requested, close the write end of the pipe and duplicate
- // the read end to stdin. Then close the original read end descriptor.
+ // Child.
//
- if (in != STDIN_FILENO)
+ // 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 ((in == -1 && close (out_fd[1]) == -1) ||
- dup2 (out_fd[0], STDIN_FILENO) == -1 ||
- (in == -1 && close (out_fd[0]) == -1))
- throw process_error (errno, true);
- }
+ if (fd == -1)
+ fd = pd[sd == STDIN_FILENO ? 0 : 1].get ();
+
+ assert (fd != -1);
+ if (dup2 (fd, sd) == -1)
+ fail (true);
+
+ pd[0].reset (); // Close.
+ pd[1].reset (); // Close.
+ };
+
+ if (in != STDIN_FILENO)
+ duplicate (STDIN_FILENO, in, out_fd);
- // Do the same for the stdout if requested.
- //
if (out != STDOUT_FILENO)
- {
- if ((out == -1 && close (in_ofd[0]) == -1) ||
- dup2 (in_ofd[1], STDOUT_FILENO) == -1 ||
- (out == -1 && close (in_ofd[1]) == -1))
- throw process_error (errno, true);
- }
+ duplicate (STDOUT_FILENO, out, in_ofd);
- // Do the same for the stderr if requested.
- //
if (err != STDERR_FILENO)
- {
- if ((err == -1 && close (in_efd[0]) == -1) ||
- dup2 (in_efd[1], STDERR_FILENO) == -1 ||
- (err == -1 && close (in_efd[1]) == -1))
- throw process_error (errno, true);
- }
+ duplicate (STDERR_FILENO, err, in_efd);
// Change current working directory if requested.
//
if (cwd != nullptr && *cwd != '\0' && chdir (cwd) != 0)
- throw process_error (errno, true);
+ fail (true);
if (execvp (args[0], const_cast<char**> (&args[0])) == -1)
- throw process_error (errno, true);
- }
- else
- {
- // Parent. Close the other ends of the pipes.
- //
- if ((in == -1 && close (out_fd[0]) == -1) ||
- (out == -1 && close (in_ofd[1]) == -1) ||
- (err == -1 && close (in_efd[1]) == -1))
- throw process_error (errno, false);
+ fail (true);
}
- this->out_fd = in == -1 ? out_fd[1] : -1;
- this->in_ofd = out == -1 ? in_ofd[0] : -1;
- this->in_efd = err == -1 ? in_efd[0] : -1;
+ assert (handle != 0); // Shouldn't get here unless in the parent process.
+
+ this->out_fd = out_fd[1].release ();
+ this->in_ofd = in_ofd[0].release ();
+ this->in_efd = in_efd[0].release ();
}
process::
@@ -149,76 +210,190 @@ namespace butl
#else // _WIN32
- process::id_type process::
- current_id ()
+ struct msg_deleter
{
- return GetCurrentProcessId ();
- }
+ void operator() (char* p) const {LocalFree (p);}
+ };
- static void
- print_error (char const* name)
+ static string
+ error (DWORD e)
{
- LPTSTR msg;
- DWORD e (GetLastError());
-
- if (!FormatMessage(
+ char* msg;
+ if (!FormatMessageA (
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
0,
e,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPTSTR) &msg,
+ (char*)&msg,
0,
0))
- {
- cerr << name << ": error: unknown error code " << e << endl;
- return;
- }
+ return "unknown error code " + to_string (e);
- cerr << name << ": error: " << msg << endl;
- LocalFree (msg);
+ unique_ptr<char, msg_deleter> m (msg);
+ return msg;
}
- static process_info
- start_process (char const* args[], char const* name, bool err, bool out)
+ static inline string
+ last_error ()
{
- HANDLE out_h[2];
- HANDLE in_eh[2];
- HANDLE in_oh[2];
- SECURITY_ATTRIBUTES sa;
+ return error (GetLastError ());
+ }
- sa.nLength = sizeof (SECURITY_ATTRIBUTES);
- sa.bInheritHandle = true;
- sa.lpSecurityDescriptor = 0;
+ static path
+ path_search (const path& f)
+ {
+ typedef path::traits traits;
+
+ // If there is a directory component in the file, then the PATH search
+ // does not apply.
+ //
+ if (!f.directory ().empty ())
+ return f;
+
+ string paths;
- if (!CreatePipe (&out_h[0], &out_h[1], &sa, 0) ||
- !SetHandleInformation (out_h[1], HANDLE_FLAG_INHERIT, 0))
+ // If there is no PATH in the environment then the default search path is
+ // the current directory.
+ //
+ if (const char* s = getenv ("PATH"))
{
- print_error (name);
- throw process_failure ();
+ paths = s;
+
+ // Also check the current directory.
+ //
+ paths += traits::path_separator;
}
+ else
+ paths = traits::path_separator;
+
+ struct stat info;
- if (err)
+ for (size_t b (0), e (paths.find (traits::path_separator));
+ b != string::npos;)
{
- if (!CreatePipe (&in_eh[0], &in_eh[1], &sa, 0) ||
- !SetHandleInformation (in_eh[0], HANDLE_FLAG_INHERIT, 0))
+ path p (string (paths, b, e != string::npos ? e - b : e));
+
+ // Empty path (i.e., a double colon or a colon at the beginning or end
+ // of PATH) means search in the current dirrectory.
+ //
+ if (p.empty ())
+ p = path (".");
+
+ path dp (p / f);
+
+ // Just check that the file exist without checking for permissions, etc.
+ //
+ if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
+ return dp;
+
+ // Also try the path with the .exe extension.
+ //
+ dp += ".exe";
+
+ if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode))
+ return dp;
+
+ if (e == string::npos)
+ b = e;
+ else
{
- print_error (name);
- throw process_failure ();
+ b = e + 1;
+ e = paths.find (traits::path_separator, b);
}
}
- if (out)
+ return path ();
+ }
+
+ class auto_handle
+ {
+ public:
+ explicit
+ auto_handle (HANDLE h = INVALID_HANDLE_VALUE) noexcept: handle_ (h) {}
+
+ auto_handle (const auto_handle&) = delete;
+ auto_handle& operator= (const auto_handle&) = delete;
+
+ ~auto_handle () noexcept {reset ();}
+
+ HANDLE
+ get () const noexcept {return handle_;}
+
+ HANDLE
+ release () noexcept
+ {
+ HANDLE r (handle_);
+ handle_ = INVALID_HANDLE_VALUE;
+ return r;
+ }
+
+ void
+ reset (HANDLE h = INVALID_HANDLE_VALUE) noexcept
{
- if (!CreatePipe (&in_oh[0], &in_oh[1], &sa, 0) ||
- !SetHandleInformation (in_oh[0], HANDLE_FLAG_INHERIT, 0))
+ if (handle_ != INVALID_HANDLE_VALUE)
{
- print_error (name);
- throw process_failure ();
+ bool r (CloseHandle (handle_));
+
+ // The valid process, thread or file handle that has no IO operations
+ // being performed on it should close successfully, unless something
+ // is severely damaged.
+ //
+ assert (r);
}
+
+ handle_ = h;
}
+ private:
+ HANDLE handle_;
+ };
+
+ process::
+ process (const char* cwd, char const* const args[], int in, int out, int err)
+ {
+ using pipe = auto_handle[2];
+
+ pipe out_h;
+ pipe in_oh;
+ pipe in_eh;
+
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof (SECURITY_ATTRIBUTES);
+ sa.bInheritHandle = true;
+ sa.lpSecurityDescriptor = 0;
+
+ auto fail = [](const char* m = nullptr)
+ {
+ throw process_error (m == nullptr ? last_error () : m);
+ };
+
+ // Create a pipe and clear the inherit flag on the parent side.
+ //
+ auto create_pipe = [&sa, &fail](pipe& p, int parent)
+ {
+ HANDLE ph[2];
+ if (!CreatePipe (&ph[0], &ph[1], &sa, 0))
+ fail ();
+
+ p[0].reset (ph[0]);
+ p[1].reset (ph[1]);
+
+ if (!SetHandleInformation (p[parent].get (), HANDLE_FLAG_INHERIT, 0))
+ fail ();
+ };
+
+ if (in == -1)
+ create_pipe (out_h, 1);
+
+ if (out == -1)
+ create_pipe (in_oh, 0);
+
+ if (err == -1)
+ create_pipe (in_eh, 0);
+
// Create the process.
//
path file (args[0]);
@@ -229,23 +404,19 @@ namespace butl
file = path_search (file);
if (file.empty ())
- {
- cerr << args[0] << ": error: file not found" << endl;
- throw process_failure ();
- }
+ fail ("file not found");
// Serialize the arguments to string.
//
string cmd_line;
- for (char const** p (args); *p != 0; ++p)
+ for (char const* const* p (args); *p != 0; ++p)
{
if (p != args)
cmd_line += ' ';
- // On Windows we need to protect values with spaces using quotes.
- // Since there could be actual quotes in the value, we need to
- // escape them.
+ // On Windows we need to protect values with spaces using quotes. Since
+ // there could be actual quotes in the value, we need to escape them.
//
string a (*p);
bool quote (a.find (' ') != string::npos);
@@ -265,7 +436,7 @@ namespace butl
cmd_line += '"';
}
- // Prepare other info.
+ // Prepare other process information.
//
STARTUPINFO si;
PROCESS_INFORMATION pi;
@@ -273,20 +444,62 @@ namespace butl
memset (&si, 0, sizeof (STARTUPINFO));
memset (&pi, 0, sizeof (PROCESS_INFORMATION));
- si.cb = sizeof(STARTUPINFO);
+ si.cb = sizeof (STARTUPINFO);
+ si.dwFlags |= STARTF_USESTDHANDLES;
- if (err)
- si.hStdError = in_eh[1];
- else
- si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
+ // 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");
- if (out)
- si.hStdOutput = in_oh[1];
- else
- si.hStdOutput = GetStdHandle (STD_OUTPUT_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 ();
+ }
- si.hStdInput = out_h[0];
- si.dwFlags |= STARTF_USESTDHANDLES;
+ return h;
+ };
+
+ si.hStdInput = in == -1
+ ? out_h[0].get ()
+ : in == STDIN_FILENO
+ ? GetStdHandle (STD_INPUT_HANDLE)
+ : get_osfhandle (in);
+
+ si.hStdOutput = out == -1
+ ? in_oh[1].get ()
+ : out == STDOUT_FILENO
+ ? GetStdHandle (STD_OUTPUT_HANDLE)
+ : get_osfhandle (out);
+
+ si.hStdError = err == -1
+ ? in_eh[1].get ()
+ : err == STDERR_FILENO
+ ? GetStdHandle (STD_ERROR_HANDLE)
+ : get_osfhandle (err);
+
+ // Perform standard stream redirection if requested.
+ //
+ if (si.hStdError == GetStdHandle (STD_OUTPUT_HANDLE))
+ si.hStdError = si.hStdOutput;
+ else if (si.hStdOutput == GetStdHandle (STD_ERROR_HANDLE))
+ si.hStdOutput = si.hStdError;
+
+ if (si.hStdError == GetStdHandle (STD_INPUT_HANDLE) ||
+ si.hStdOutput == GetStdHandle (STD_INPUT_HANDLE) ||
+ si.hStdInput == GetStdHandle (STD_OUTPUT_HANDLE) ||
+ si.hStdInput == GetStdHandle (STD_ERROR_HANDLE))
+ fail ("invalid file descriptor");
if (!CreateProcess (
file.string ().c_str (),
@@ -296,80 +509,107 @@ namespace butl
true, // Inherit handles.
0, // Creation flags.
0, // Use our environment.
- 0, // Use our current directory.
+ cwd != nullptr && *cwd != '\0' ? cwd : nullptr,
&si,
&pi))
+ fail ();
+
+ auto_handle (pi.hThread).reset (); // Close.
+ auto_handle process (pi.hProcess);
+
+ // Convert file handles to file descriptors. Note that the handle is
+ // closed when _close() is called for the returned file descriptor.
+ //
+ auto open_osfhandle = [&fail](auto_handle& h) -> int
{
- print_error (name);
- throw process_failure ();
- }
+ int fd (
+ _open_osfhandle (reinterpret_cast<intptr_t> (h.get ()), _O_TEXT));
- CloseHandle (pi.hThread);
- CloseHandle (out_h[0]);
+ if (fd == -1)
+ fail ("unable to convert file handle to file descriptor");
- if (err)
- CloseHandle (in_eh[1]);
+ h.release ();
+ return fd;
+ };
- if (out)
- CloseHandle (in_oh[1]);
+ auto_fd out_fd (in == -1 ? open_osfhandle (out_h[1]) : -1);
+ auto_fd in_ofd (out == -1 ? open_osfhandle (in_oh[0]) : -1);
+ auto_fd in_efd (err == -1 ? open_osfhandle (in_eh[0]) : -1);
- process_info r;
- r.id = pi.hProcess;
- r.out_fd = _open_osfhandle ((intptr_t) (out_h[1]), 0);
+ this->out_fd = out_fd.release ();
+ this->in_ofd = in_ofd.release ();
+ this->in_efd = in_efd.release ();
- if (r.out_fd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
+ this->handle = process.release ();
- if (err)
- {
- // Pass _O_TEXT to get newline translation.
- //
- r.in_efd = _open_osfhandle ((intptr_t) (in_eh[0]), _O_TEXT);
+ // 0 has a special meaning denoting a terminated process handle.
+ //
+ assert (this->handle != 0 && this->handle != INVALID_HANDLE_VALUE);
+ }
- if (r.in_efd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
- }
- else
- r.in_efd = 0;
+ process::
+ process (const char* cwd, char const* const args[],
+ process& in, int out, int err)
+ : process (cwd, args, in.in_ofd, out, err)
+ {
+ assert (in.in_ofd != -1); // Should be a pipe.
+ _close (in.in_ofd); // Close it on our side.
+ }
- if (out)
+ bool process::
+ wait ()
+ {
+ if (handle != 0)
{
- // Pass _O_TEXT to get newline translation.
- //
- r.in_ofd = _open_osfhandle ((intptr_t) (in_oh[0]), _O_TEXT);
+ DWORD s;
+ DWORD e (NO_ERROR);
+ if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 ||
+ !GetExitCodeProcess (handle, &s))
+ e = GetLastError ();
- if (r.in_ofd == -1)
- {
- cerr << name << ": error: unable to obtain C file handle" << endl;
- throw process_failure ();
- }
+ auto_handle h (handle); // Auto-deleter.
+ handle = 0; // We have tried.
+
+ if (e != NO_ERROR)
+ throw process_error (error (e));
+
+ status = s;
}
- else
- r.in_ofd = 0;
- return r;
+ return status == 0;
}
- static bool
- wait_process (process_info pi, char const* name)
+ bool process::
+ try_wait (bool& s)
{
- DWORD status;
-
- if (WaitForSingleObject (pi.id, INFINITE) != WAIT_OBJECT_0 ||
- !GetExitCodeProcess (pi.id, &status))
+ if (handle != 0)
{
- print_error (name);
- throw process_failure ();
+ DWORD r (WaitForSingleObject (handle, 0));
+ if (r == WAIT_TIMEOUT)
+ return false;
+
+ DWORD s;
+ DWORD e (NO_ERROR);
+ if (r != WAIT_OBJECT_0 || !GetExitCodeProcess (handle, &s))
+ e = GetLastError ();
+
+ auto_handle h (handle);
+ handle = 0; // We have tried.
+
+ if (e != NO_ERROR)
+ throw process_error (error (e));
+
+ status = s;
}
- CloseHandle (pi.id);
- return status == 0;
+ s = status == 0;
+ return true;
+ }
+
+ process::id_type process::
+ current_id ()
+ {
+ return GetCurrentProcessId ();
}
#endif // _WIN32
diff --git a/butl/strptime.c b/butl/strptime.c
new file mode 100644
index 0000000..8bbfac5
--- /dev/null
+++ b/butl/strptime.c
@@ -0,0 +1,629 @@
+/*-
+ * Copyright (c) 2014 Gary Mills
+ * Copyright 2011, Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 1994 Powerdog Industries. All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of Powerdog Industries.
+ */
+
+// Fallback implementation of strptime() designed for Windows/gcc combination
+// when neither POSIX strptime() nor proper std::get_time() are available. No
+// locale support is provided, so the function works as if "C" locale is set.
+// FreeBSD libc strptime.c source file is taken as a basis (saved in
+// strptime.orig). POSIX non-compliant extensions are removed. Most of
+// #include directives are removed as reference internal headers, some
+// #define directives are added instead. The content of included
+// timelocal.{h,c} files is mostly commented out, having just required
+// lc_time_T struct and _C_time_locale constant definitions left. Otherwise the
+// code is kept untouched as much as possible.
+//
+
+#include <ctype.h> // isspace(), isdigit()
+#include <string.h> // _strnicmp()
+
+#include "timelocal.h" // lc_time_T
+#include "timelocal.c" // _C_time_locale
+
+#define isspace_l(c, l) isspace(c)
+#define isdigit_l(c, l) isdigit(c)
+#define strncasecmp_l(s1, s2, n, l) _strnicmp(s1, s2, n)
+
+#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
+#define FIX_LOCALE(l) l=l
+
+#define TM_YEAR_BASE 1900
+#define TM_SUNDAY 0
+#define TM_MONDAY 1
+
+typedef unsigned char u_char;
+
+// Stubs replacing the real locale support.
+//
+typedef const lc_time_T* locale_t;
+
+static locale_t
+__get_locale ()
+{
+ return &_C_time_locale;
+}
+
+static const struct lc_time_T *
+__get_current_time_locale (locale_t l)
+{
+ return l;
+}
+
+// From this point the code is unchanged, if not to count non-standard
+// specifiers support removal and the replacement of tabs with spaces.
+//
+static char *
+_strptime(const char *, const char *, struct tm *, int *, locale_t);
+
+#define asizeof(a) ((int)(sizeof(a) / sizeof((a)[0])))
+
+#define FLAG_NONE (1 << 0)
+#define FLAG_YEAR (1 << 1)
+#define FLAG_MONTH (1 << 2)
+#define FLAG_YDAY (1 << 3)
+#define FLAG_MDAY (1 << 4)
+#define FLAG_WDAY (1 << 5)
+
+/*
+ * Calculate the week day of the first day of a year. Valid for
+ * the Gregorian calendar, which began Sept 14, 1752 in the UK
+ * and its colonies. Ref:
+ * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
+ */
+
+static int
+first_wday_of(int year)
+{
+ return (((2 * (3 - (year / 100) % 4)) + (year % 100) +
+ ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7);
+}
+
+// Remove Glibc extensions (%F, %Z, %z, %s) to make implementation compliant
+// with POSIX strptime() and to simplify porting.
+//
+static char *
+_strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
+ locale_t locale)
+{
+ char c;
+ const char *ptr;
+ int day_offset = -1, wday_offset;
+ int week_offset;
+ int i, len;
+ int flags;
+ int Ealternative, Oalternative;
+ const struct lc_time_T *tptr = __get_current_time_locale(locale);
+ static int start_of_month[2][13] = {
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
+ };
+
+ flags = FLAG_NONE;
+
+ ptr = fmt;
+ while (*ptr != 0) {
+ c = *ptr++;
+
+ if (c != '%') {
+ if (isspace_l((unsigned char)c, locale))
+ while (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ else if (c != *buf++)
+ return (NULL);
+ continue;
+ }
+
+ // Skip according to
+ // http://pubs.opengroup.org/onlinepubs/9699919799/functions/strptime.html
+ //
+ c = *ptr;
+ if (c == '+' || c == '0')
+ ptr++;
+
+ Ealternative = 0;
+ Oalternative = 0;
+label:
+ c = *ptr++;
+ switch (c) {
+ case '%':
+ if (*buf++ != '%')
+ return (NULL);
+ break;
+
+ case 'C':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ /* XXX This will break for 3-digit centuries. */
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 19)
+ return (NULL);
+
+ tm->tm_year = i * 100 - TM_YEAR_BASE;
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'c':
+ buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'D':
+ buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'E':
+ if (Ealternative || Oalternative)
+ break;
+ Ealternative++;
+ goto label;
+
+ case 'O':
+ if (Ealternative || Oalternative)
+ break;
+ Oalternative++;
+ goto label;
+
+ case 'R':
+ buf = _strptime(buf, "%H:%M", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'r':
+ buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'T':
+ buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'X':
+ buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'x':
+ buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'j':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 3;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 366)
+ return (NULL);
+
+ tm->tm_yday = i - 1;
+ flags |= FLAG_YDAY;
+
+ break;
+
+ case 'M':
+ case 'S':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+
+ if (c == 'M') {
+ if (i > 59)
+ return (NULL);
+ tm->tm_min = i;
+ } else {
+ if (i > 60)
+ return (NULL);
+ tm->tm_sec = i;
+ }
+
+ break;
+
+ case 'H':
+ case 'I':
+ case 'k':
+ case 'l':
+ /*
+ * Of these, %l is the only specifier explicitly
+ * documented as not being zero-padded. However,
+ * there is no harm in allowing zero-padding.
+ *
+ * XXX The %l specifier may gobble one too many
+ * digits if used incorrectly.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'H' || c == 'k') {
+ if (i > 23)
+ return (NULL);
+ } else if (i > 12)
+ return (NULL);
+
+ tm->tm_hour = i;
+
+ break;
+
+ case 'p':
+ /*
+ * XXX This is bogus if parsed before hour-related
+ * specifiers.
+ */
+ len = strlen(tptr->am);
+ if (strncasecmp_l(buf, tptr->am, len, locale) == 0) {
+ if (tm->tm_hour > 12)
+ return (NULL);
+ if (tm->tm_hour == 12)
+ tm->tm_hour = 0;
+ buf += len;
+ break;
+ }
+
+ len = strlen(tptr->pm);
+ if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) {
+ if (tm->tm_hour > 12)
+ return (NULL);
+ if (tm->tm_hour != 12)
+ tm->tm_hour += 12;
+ buf += len;
+ break;
+ }
+
+ return (NULL);
+
+ case 'A':
+ case 'a':
+ for (i = 0; i < asizeof(tptr->weekday); i++) {
+ len = strlen(tptr->weekday[i]);
+ if (strncasecmp_l(buf, tptr->weekday[i],
+ len, locale) == 0)
+ break;
+ len = strlen(tptr->wday[i]);
+ if (strncasecmp_l(buf, tptr->wday[i],
+ len, locale) == 0)
+ break;
+ }
+ if (i == asizeof(tptr->weekday))
+ return (NULL);
+
+ buf += len;
+ tm->tm_wday = i;
+ flags |= FLAG_WDAY;
+ break;
+
+ case 'U':
+ case 'W':
+ /*
+ * XXX This is bogus, as we can not assume any valid
+ * information present in the tm structure at this
+ * point to calculate a real value, so just check the
+ * range for now.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i > 53)
+ return (NULL);
+
+ if (c == 'U')
+ day_offset = TM_SUNDAY;
+ else
+ day_offset = TM_MONDAY;
+
+
+ week_offset = i;
+
+ break;
+
+ case 'w':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ i = *buf - '0';
+ if (i > 6)
+ return (NULL);
+
+ tm->tm_wday = i;
+ flags |= FLAG_WDAY;
+
+ break;
+
+ case 'e':
+ /*
+ * With %e format, our strftime(3) adds a blank space
+ * before single digits.
+ */
+ if (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ /* FALLTHROUGH */
+ case 'd':
+ /*
+ * The %e specifier was once explicitly documented as
+ * not being zero-padded but was later changed to
+ * equivalent to %d. There is no harm in allowing
+ * such padding.
+ *
+ * XXX The %e specifier may gobble one too many
+ * digits if used incorrectly.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i > 31)
+ return (NULL);
+
+ tm->tm_mday = i;
+ flags |= FLAG_MDAY;
+
+ break;
+
+ case 'B':
+ case 'b':
+ case 'h':
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ if (Oalternative) {
+ if (c == 'B') {
+ len = strlen(tptr->alt_month[i]);
+ if (strncasecmp_l(buf,
+ tptr->alt_month[i],
+ len, locale) == 0)
+ break;
+ }
+ } else {
+ len = strlen(tptr->month[i]);
+ if (strncasecmp_l(buf, tptr->month[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ /*
+ * Try the abbreviated month name if the full name
+ * wasn't found and Oalternative was not requested.
+ */
+ if (i == asizeof(tptr->month) && !Oalternative) {
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ len = strlen(tptr->mon[i]);
+ if (strncasecmp_l(buf, tptr->mon[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ if (i == asizeof(tptr->month))
+ return (NULL);
+
+ tm->tm_mon = i;
+ buf += len;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 'm':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 12)
+ return (NULL);
+
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 'Y':
+ case 'y':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = (c == 'Y') ? 4 : 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'Y')
+ i -= TM_YEAR_BASE;
+ if (c == 'y' && i < 69)
+ i += 100;
+ if (i < 0)
+ return (NULL);
+
+ tm->tm_year = i;
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'n':
+ case 't':
+ while (isspace_l((unsigned char)*buf, locale))
+ buf++;
+ break;
+
+ default:
+ return (NULL);
+ }
+ }
+
+ if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) {
+ if ((flags & (FLAG_MONTH | FLAG_MDAY)) ==
+ (FLAG_MONTH | FLAG_MDAY)) {
+ tm->tm_yday = start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
+ flags |= FLAG_YDAY;
+ } else if (day_offset != -1) {
+ /* Set the date to the first Sunday (or Monday)
+ * of the specified week of the year.
+ */
+ if (!(flags & FLAG_WDAY)) {
+ tm->tm_wday = day_offset;
+ flags |= FLAG_WDAY;
+ }
+ tm->tm_yday = (7 -
+ first_wday_of(tm->tm_year + TM_YEAR_BASE) +
+ day_offset) % 7 + (week_offset - 1) * 7 +
+ tm->tm_wday - day_offset;
+ flags |= FLAG_YDAY;
+ }
+ }
+
+ if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) {
+ if (!(flags & FLAG_MONTH)) {
+ i = 0;
+ while (tm->tm_yday >=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][i])
+ i++;
+ if (i > 12) {
+ i = 1;
+ tm->tm_yday -=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][12];
+ tm->tm_year++;
+ }
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+ }
+ if (!(flags & FLAG_MDAY)) {
+ tm->tm_mday = tm->tm_yday -
+ start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)]
+ [tm->tm_mon] + 1;
+ flags |= FLAG_MDAY;
+ }
+ if (!(flags & FLAG_WDAY)) {
+ i = 0;
+ wday_offset = first_wday_of(tm->tm_year);
+ while (i++ <= tm->tm_yday) {
+ if (wday_offset++ >= 6)
+ wday_offset = 0;
+ }
+ tm->tm_wday = wday_offset;
+ flags |= FLAG_WDAY;
+ }
+ }
+
+ return ((char *)buf);
+}
+
+static char *
+strptime_l(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm, locale_t loc)
+{
+ char *ret;
+ int gmt;
+ FIX_LOCALE(loc);
+
+ gmt = 0;
+ ret = _strptime(buf, fmt, tm, &gmt, loc);
+
+ return (ret);
+}
+
+static char *
+strptime(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm)
+{
+ return strptime_l(buf, fmt, tm, __get_locale());
+}
diff --git a/butl/strptime.c.orig b/butl/strptime.c.orig
new file mode 100644
index 0000000..2be6358
--- /dev/null
+++ b/butl/strptime.c.orig
@@ -0,0 +1,689 @@
+/*-
+ * Copyright (c) 2014 Gary Mills
+ * Copyright 2011, Nexenta Systems, Inc. All rights reserved.
+ * Copyright (c) 1994 Powerdog Industries. All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation
+ * are those of the authors and should not be interpreted as representing
+ * official policies, either expressed or implied, of Powerdog Industries.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+#ifndef NOID
+static char copyright[] __unused =
+"@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved.";
+static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27";
+#endif /* !defined NOID */
+#endif /* not lint */
+__FBSDID("$FreeBSD$");
+
+#include "namespace.h"
+#include <time.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+#include "un-namespace.h"
+#include "libc_private.h"
+#include "timelocal.h"
+#include "tzfile.h"
+
+static char * _strptime(const char *, const char *, struct tm *, int *, locale_t);
+
+#define asizeof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define FLAG_NONE (1 << 0)
+#define FLAG_YEAR (1 << 1)
+#define FLAG_MONTH (1 << 2)
+#define FLAG_YDAY (1 << 3)
+#define FLAG_MDAY (1 << 4)
+#define FLAG_WDAY (1 << 5)
+
+/*
+ * Calculate the week day of the first day of a year. Valid for
+ * the Gregorian calendar, which began Sept 14, 1752 in the UK
+ * and its colonies. Ref:
+ * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
+ */
+
+static int
+first_wday_of(int year)
+{
+ return (((2 * (3 - (year / 100) % 4)) + (year % 100) +
+ ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7);
+}
+
+static char *
+_strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp,
+ locale_t locale)
+{
+ char c;
+ const char *ptr;
+ int day_offset = -1, wday_offset;
+ int week_offset;
+ int i, len;
+ int flags;
+ int Ealternative, Oalternative;
+ const struct lc_time_T *tptr = __get_current_time_locale(locale);
+ static int start_of_month[2][13] = {
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
+ {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
+ };
+
+ flags = FLAG_NONE;
+
+ ptr = fmt;
+ while (*ptr != 0) {
+ c = *ptr++;
+
+ if (c != '%') {
+ if (isspace_l((unsigned char)c, locale))
+ while (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ else if (c != *buf++)
+ return (NULL);
+ continue;
+ }
+
+ Ealternative = 0;
+ Oalternative = 0;
+label:
+ c = *ptr++;
+ switch (c) {
+ case '%':
+ if (*buf++ != '%')
+ return (NULL);
+ break;
+
+ case '+':
+ buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'C':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ /* XXX This will break for 3-digit centuries. */
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 19)
+ return (NULL);
+
+ tm->tm_year = i * 100 - TM_YEAR_BASE;
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'c':
+ buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'D':
+ buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'E':
+ if (Ealternative || Oalternative)
+ break;
+ Ealternative++;
+ goto label;
+
+ case 'O':
+ if (Ealternative || Oalternative)
+ break;
+ Oalternative++;
+ goto label;
+
+ case 'F':
+ buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'R':
+ buf = _strptime(buf, "%H:%M", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'r':
+ buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'T':
+ buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'X':
+ buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ break;
+
+ case 'x':
+ buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale);
+ if (buf == NULL)
+ return (NULL);
+ flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR;
+ break;
+
+ case 'j':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 3;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 366)
+ return (NULL);
+
+ tm->tm_yday = i - 1;
+ flags |= FLAG_YDAY;
+
+ break;
+
+ case 'M':
+ case 'S':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++){
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+
+ if (c == 'M') {
+ if (i > 59)
+ return (NULL);
+ tm->tm_min = i;
+ } else {
+ if (i > 60)
+ return (NULL);
+ tm->tm_sec = i;
+ }
+
+ break;
+
+ case 'H':
+ case 'I':
+ case 'k':
+ case 'l':
+ /*
+ * Of these, %l is the only specifier explicitly
+ * documented as not being zero-padded. However,
+ * there is no harm in allowing zero-padding.
+ *
+ * XXX The %l specifier may gobble one too many
+ * digits if used incorrectly.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'H' || c == 'k') {
+ if (i > 23)
+ return (NULL);
+ } else if (i > 12)
+ return (NULL);
+
+ tm->tm_hour = i;
+
+ break;
+
+ case 'p':
+ /*
+ * XXX This is bogus if parsed before hour-related
+ * specifiers.
+ */
+ len = strlen(tptr->am);
+ if (strncasecmp_l(buf, tptr->am, len, locale) == 0) {
+ if (tm->tm_hour > 12)
+ return (NULL);
+ if (tm->tm_hour == 12)
+ tm->tm_hour = 0;
+ buf += len;
+ break;
+ }
+
+ len = strlen(tptr->pm);
+ if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) {
+ if (tm->tm_hour > 12)
+ return (NULL);
+ if (tm->tm_hour != 12)
+ tm->tm_hour += 12;
+ buf += len;
+ break;
+ }
+
+ return (NULL);
+
+ case 'A':
+ case 'a':
+ for (i = 0; i < asizeof(tptr->weekday); i++) {
+ len = strlen(tptr->weekday[i]);
+ if (strncasecmp_l(buf, tptr->weekday[i],
+ len, locale) == 0)
+ break;
+ len = strlen(tptr->wday[i]);
+ if (strncasecmp_l(buf, tptr->wday[i],
+ len, locale) == 0)
+ break;
+ }
+ if (i == asizeof(tptr->weekday))
+ return (NULL);
+
+ buf += len;
+ tm->tm_wday = i;
+ flags |= FLAG_WDAY;
+ break;
+
+ case 'U':
+ case 'W':
+ /*
+ * XXX This is bogus, as we can not assume any valid
+ * information present in the tm structure at this
+ * point to calculate a real value, so just check the
+ * range for now.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i > 53)
+ return (NULL);
+
+ if (c == 'U')
+ day_offset = TM_SUNDAY;
+ else
+ day_offset = TM_MONDAY;
+
+
+ week_offset = i;
+
+ break;
+
+ case 'w':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ i = *buf - '0';
+ if (i > 6)
+ return (NULL);
+
+ tm->tm_wday = i;
+ flags |= FLAG_WDAY;
+
+ break;
+
+ case 'e':
+ /*
+ * With %e format, our strftime(3) adds a blank space
+ * before single digits.
+ */
+ if (*buf != 0 &&
+ isspace_l((unsigned char)*buf, locale))
+ buf++;
+ /* FALLTHROUGH */
+ case 'd':
+ /*
+ * The %e specifier was once explicitly documented as
+ * not being zero-padded but was later changed to
+ * equivalent to %d. There is no harm in allowing
+ * such padding.
+ *
+ * XXX The %e specifier may gobble one too many
+ * digits if used incorrectly.
+ */
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i > 31)
+ return (NULL);
+
+ tm->tm_mday = i;
+ flags |= FLAG_MDAY;
+
+ break;
+
+ case 'B':
+ case 'b':
+ case 'h':
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ if (Oalternative) {
+ if (c == 'B') {
+ len = strlen(tptr->alt_month[i]);
+ if (strncasecmp_l(buf,
+ tptr->alt_month[i],
+ len, locale) == 0)
+ break;
+ }
+ } else {
+ len = strlen(tptr->month[i]);
+ if (strncasecmp_l(buf, tptr->month[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ /*
+ * Try the abbreviated month name if the full name
+ * wasn't found and Oalternative was not requested.
+ */
+ if (i == asizeof(tptr->month) && !Oalternative) {
+ for (i = 0; i < asizeof(tptr->month); i++) {
+ len = strlen(tptr->mon[i]);
+ if (strncasecmp_l(buf, tptr->mon[i],
+ len, locale) == 0)
+ break;
+ }
+ }
+ if (i == asizeof(tptr->month))
+ return (NULL);
+
+ tm->tm_mon = i;
+ buf += len;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 'm':
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (i < 1 || i > 12)
+ return (NULL);
+
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+
+ break;
+
+ case 's':
+ {
+ char *cp;
+ int sverrno;
+ long n;
+ time_t t;
+
+ sverrno = errno;
+ errno = 0;
+ n = strtol_l(buf, &cp, 10, locale);
+ if (errno == ERANGE || (long)(t = n) != n) {
+ errno = sverrno;
+ return (NULL);
+ }
+ errno = sverrno;
+ buf = cp;
+ if (gmtime_r(&t, tm) == NULL)
+ return (NULL);
+ *GMTp = 1;
+ flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH |
+ FLAG_MDAY | FLAG_YEAR;
+ }
+ break;
+
+ case 'Y':
+ case 'y':
+ if (*buf == 0 ||
+ isspace_l((unsigned char)*buf, locale))
+ break;
+
+ if (!isdigit_l((unsigned char)*buf, locale))
+ return (NULL);
+
+ len = (c == 'Y') ? 4 : 2;
+ for (i = 0; len && *buf != 0 &&
+ isdigit_l((unsigned char)*buf, locale); buf++) {
+ i *= 10;
+ i += *buf - '0';
+ len--;
+ }
+ if (c == 'Y')
+ i -= TM_YEAR_BASE;
+ if (c == 'y' && i < 69)
+ i += 100;
+ if (i < 0)
+ return (NULL);
+
+ tm->tm_year = i;
+ flags |= FLAG_YEAR;
+
+ break;
+
+ case 'Z':
+ {
+ const char *cp;
+ char *zonestr;
+
+ for (cp = buf; *cp &&
+ isupper_l((unsigned char)*cp, locale); ++cp) {
+ /*empty*/}
+ if (cp - buf) {
+ zonestr = alloca(cp - buf + 1);
+ strncpy(zonestr, buf, cp - buf);
+ zonestr[cp - buf] = '\0';
+ tzset();
+ if (0 == strcmp(zonestr, "GMT") ||
+ 0 == strcmp(zonestr, "UTC")) {
+ *GMTp = 1;
+ } else if (0 == strcmp(zonestr, tzname[0])) {
+ tm->tm_isdst = 0;
+ } else if (0 == strcmp(zonestr, tzname[1])) {
+ tm->tm_isdst = 1;
+ } else {
+ return (NULL);
+ }
+ buf += cp - buf;
+ }
+ }
+ break;
+
+ case 'z':
+ {
+ int sign = 1;
+
+ if (*buf != '+') {
+ if (*buf == '-')
+ sign = -1;
+ else
+ return (NULL);
+ }
+
+ buf++;
+ i = 0;
+ for (len = 4; len > 0; len--) {
+ if (isdigit_l((unsigned char)*buf, locale)) {
+ i *= 10;
+ i += *buf - '0';
+ buf++;
+ } else
+ return (NULL);
+ }
+
+ tm->tm_hour -= sign * (i / 100);
+ tm->tm_min -= sign * (i % 100);
+ *GMTp = 1;
+ }
+ break;
+
+ case 'n':
+ case 't':
+ while (isspace_l((unsigned char)*buf, locale))
+ buf++;
+ break;
+
+ default:
+ return (NULL);
+ }
+ }
+
+ if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) {
+ if ((flags & (FLAG_MONTH | FLAG_MDAY)) ==
+ (FLAG_MONTH | FLAG_MDAY)) {
+ tm->tm_yday = start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1);
+ flags |= FLAG_YDAY;
+ } else if (day_offset != -1) {
+ /* Set the date to the first Sunday (or Monday)
+ * of the specified week of the year.
+ */
+ if (!(flags & FLAG_WDAY)) {
+ tm->tm_wday = day_offset;
+ flags |= FLAG_WDAY;
+ }
+ tm->tm_yday = (7 -
+ first_wday_of(tm->tm_year + TM_YEAR_BASE) +
+ day_offset) % 7 + (week_offset - 1) * 7 +
+ tm->tm_wday - day_offset;
+ flags |= FLAG_YDAY;
+ }
+ }
+
+ if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) {
+ if (!(flags & FLAG_MONTH)) {
+ i = 0;
+ while (tm->tm_yday >=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][i])
+ i++;
+ if (i > 12) {
+ i = 1;
+ tm->tm_yday -=
+ start_of_month[isleap(tm->tm_year +
+ TM_YEAR_BASE)][12];
+ tm->tm_year++;
+ }
+ tm->tm_mon = i - 1;
+ flags |= FLAG_MONTH;
+ }
+ if (!(flags & FLAG_MDAY)) {
+ tm->tm_mday = tm->tm_yday -
+ start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)]
+ [tm->tm_mon] + 1;
+ flags |= FLAG_MDAY;
+ }
+ if (!(flags & FLAG_WDAY)) {
+ i = 0;
+ wday_offset = first_wday_of(tm->tm_year);
+ while (i++ <= tm->tm_yday) {
+ if (wday_offset++ >= 6)
+ wday_offset = 0;
+ }
+ tm->tm_wday = wday_offset;
+ flags |= FLAG_WDAY;
+ }
+ }
+
+ return ((char *)buf);
+}
+
+char *
+strptime_l(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm, locale_t loc)
+{
+ char *ret;
+ int gmt;
+ FIX_LOCALE(loc);
+
+ gmt = 0;
+ ret = _strptime(buf, fmt, tm, &gmt, loc);
+ if (ret && gmt) {
+ time_t t = timegm(tm);
+
+ localtime_r(&t, tm);
+ }
+
+ return (ret);
+}
+
+char *
+strptime(const char * __restrict buf, const char * __restrict fmt,
+ struct tm * __restrict tm)
+{
+ return strptime_l(buf, fmt, tm, __get_locale());
+}
diff --git a/butl/timelocal.c b/butl/timelocal.c
new file mode 100644
index 0000000..7194341
--- /dev/null
+++ b/butl/timelocal.c
@@ -0,0 +1,157 @@
+/*-
+ * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
+ * Copyright (c) 1997 FreeBSD Inc.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stddef.h>
+
+#include "ldpart.h"
+#include "timelocal.h"
+
+struct xlocale_time {
+ struct xlocale_component header;
+ char *buffer;
+ struct lc_time_T locale;
+};
+
+struct xlocale_time __xlocale_global_time;
+
+#define LCTIME_SIZE (sizeof(struct lc_time_T) / sizeof(char *))
+
+*/
+static const struct lc_time_T _C_time_locale = {
+ {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ }, {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ }, {
+ "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"
+ }, {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+ },
+
+ /* X_fmt */
+ "%H:%M:%S",
+
+ /*
+ * x_fmt
+ * Since the C language standard calls for
+ * "date, using locale's date format," anything goes.
+ * Using just numbers (as here) makes Quakers happier;
+ * it's also compatible with SVR4.
+ */
+ "%m/%d/%y",
+
+ /*
+ * c_fmt
+ */
+ "%a %b %e %H:%M:%S %Y",
+
+ /* am */
+ "AM",
+
+ /* pm */
+ "PM",
+
+ /* date_fmt */
+ "%a %b %e %H:%M:%S %Z %Y",
+
+ /* alt_month
+ * Standalone months forms for %OB
+ */
+ {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ },
+
+ /* md_order
+ * Month / day order in dates
+ */
+ "md",
+
+ /* ampm_fmt
+ * To determine 12-hour clock format time (empty, if N/A)
+ */
+ "%I:%M:%S %p"
+};
+
+/*
+static void destruct_time(void *v)
+{
+ struct xlocale_time *l = v;
+ if (l->buffer)
+ free(l->buffer);
+ free(l);
+}
+
+#include <stdio.h>
+struct lc_time_T *
+__get_current_time_locale(locale_t loc)
+{
+ return (loc->using_time_locale
+ ? &((struct xlocale_time *)loc->components[XLC_TIME])->locale
+ : (struct lc_time_T *)&_C_time_locale);
+}
+
+static int
+time_load_locale(struct xlocale_time *l, int *using_locale, const char *name)
+{
+ struct lc_time_T *time_locale = &l->locale;
+ return (__part_load_locale(name, using_locale,
+ &l->buffer, "LC_TIME",
+ LCTIME_SIZE, LCTIME_SIZE,
+ (const char **)time_locale));
+}
+int
+__time_load_locale(const char *name)
+{
+ return time_load_locale(&__xlocale_global_time,
+ &__xlocale_global_locale.using_time_locale, name);
+}
+void* __time_load(const char* name, locale_t loc)
+{
+ struct xlocale_time *new = calloc(sizeof(struct xlocale_time), 1);
+ new->header.header.destructor = destruct_time;
+ if (time_load_locale(new, &loc->using_time_locale, name) == _LDP_ERROR)
+ {
+ xlocale_release(new);
+ return NULL;
+ }
+ return new;
+}
+*/
diff --git a/butl/timelocal.c.orig b/butl/timelocal.c.orig
new file mode 100644
index 0000000..48c3509
--- /dev/null
+++ b/butl/timelocal.c.orig
@@ -0,0 +1,153 @@
+/*-
+ * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
+ * Copyright (c) 1997 FreeBSD Inc.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stddef.h>
+
+#include "ldpart.h"
+#include "timelocal.h"
+
+struct xlocale_time {
+ struct xlocale_component header;
+ char *buffer;
+ struct lc_time_T locale;
+};
+
+struct xlocale_time __xlocale_global_time;
+
+#define LCTIME_SIZE (sizeof(struct lc_time_T) / sizeof(char *))
+
+static const struct lc_time_T _C_time_locale = {
+ {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+ }, {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ }, {
+ "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"
+ }, {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"
+ },
+
+ /* X_fmt */
+ "%H:%M:%S",
+
+ /*
+ * x_fmt
+ * Since the C language standard calls for
+ * "date, using locale's date format," anything goes.
+ * Using just numbers (as here) makes Quakers happier;
+ * it's also compatible with SVR4.
+ */
+ "%m/%d/%y",
+
+ /*
+ * c_fmt
+ */
+ "%a %b %e %H:%M:%S %Y",
+
+ /* am */
+ "AM",
+
+ /* pm */
+ "PM",
+
+ /* date_fmt */
+ "%a %b %e %H:%M:%S %Z %Y",
+
+ /* alt_month
+ * Standalone months forms for %OB
+ */
+ {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ },
+
+ /* md_order
+ * Month / day order in dates
+ */
+ "md",
+
+ /* ampm_fmt
+ * To determine 12-hour clock format time (empty, if N/A)
+ */
+ "%I:%M:%S %p"
+};
+
+static void destruct_time(void *v)
+{
+ struct xlocale_time *l = v;
+ if (l->buffer)
+ free(l->buffer);
+ free(l);
+}
+
+#include <stdio.h>
+struct lc_time_T *
+__get_current_time_locale(locale_t loc)
+{
+ return (loc->using_time_locale
+ ? &((struct xlocale_time *)loc->components[XLC_TIME])->locale
+ : (struct lc_time_T *)&_C_time_locale);
+}
+
+static int
+time_load_locale(struct xlocale_time *l, int *using_locale, const char *name)
+{
+ struct lc_time_T *time_locale = &l->locale;
+ return (__part_load_locale(name, using_locale,
+ &l->buffer, "LC_TIME",
+ LCTIME_SIZE, LCTIME_SIZE,
+ (const char **)time_locale));
+}
+int
+__time_load_locale(const char *name)
+{
+ return time_load_locale(&__xlocale_global_time,
+ &__xlocale_global_locale.using_time_locale, name);
+}
+void* __time_load(const char* name, locale_t loc)
+{
+ struct xlocale_time *new = calloc(sizeof(struct xlocale_time), 1);
+ new->header.header.destructor = destruct_time;
+ if (time_load_locale(new, &loc->using_time_locale, name) == _LDP_ERROR)
+ {
+ xlocale_release(new);
+ return NULL;
+ }
+ return new;
+}
diff --git a/butl/timelocal.h b/butl/timelocal.h
new file mode 100644
index 0000000..d9b77b9
--- /dev/null
+++ b/butl/timelocal.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1997-2002 FreeBSD Project.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TIMELOCAL_H_
+#define _TIMELOCAL_H_
+/*
+#include "xlocale_private.h"
+*/
+
+/*
+ * Private header file for the strftime and strptime localization
+ * stuff.
+ */
+struct lc_time_T {
+ const char *mon[12];
+ const char *month[12];
+ const char *wday[7];
+ const char *weekday[7];
+ const char *X_fmt;
+ const char *x_fmt;
+ const char *c_fmt;
+ const char *am;
+ const char *pm;
+ const char *date_fmt;
+ const char *alt_month[12];
+ const char *md_order;
+ const char *ampm_fmt;
+};
+
+/*
+struct lc_time_T *__get_current_time_locale(locale_t);
+int __time_load_locale(const char *);
+
+*/
+#endif /* !_TIMELOCAL_H_ */
diff --git a/butl/timelocal.h.orig b/butl/timelocal.h.orig
new file mode 100644
index 0000000..2e44415
--- /dev/null
+++ b/butl/timelocal.h.orig
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 1997-2002 FreeBSD Project.
+ * All rights reserved.
+ *
+ * Copyright (c) 2011 The FreeBSD Foundation
+ * All rights reserved.
+ * Portions of this software were developed by David Chisnall
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TIMELOCAL_H_
+#define _TIMELOCAL_H_
+#include "xlocale_private.h"
+
+/*
+ * Private header file for the strftime and strptime localization
+ * stuff.
+ */
+struct lc_time_T {
+ const char *mon[12];
+ const char *month[12];
+ const char *wday[7];
+ const char *weekday[7];
+ const char *X_fmt;
+ const char *x_fmt;
+ const char *c_fmt;
+ const char *am;
+ const char *pm;
+ const char *date_fmt;
+ const char *alt_month[12];
+ const char *md_order;
+ const char *ampm_fmt;
+};
+
+struct lc_time_T *__get_current_time_locale(locale_t);
+int __time_load_locale(const char *);
+
+#endif /* !_TIMELOCAL_H_ */
diff --git a/butl/timestamp.cxx b/butl/timestamp.cxx
index 2759592..bf02829 100644
--- a/butl/timestamp.cxx
+++ b/butl/timestamp.cxx
@@ -4,10 +4,10 @@
#include <butl/timestamp>
-#include <time.h> // localtime_r(), gmtime_r(), strptime(), timegm()
+#include <time.h> // localtime_{r,s}(), gmtime_{r,s}(), strptime(), timegm()
#include <errno.h> // EINVAL
-#include <ctime> // tm, time_t, strftime(), mktime()
+#include <ctime> // tm, time_t, mktime()
#include <cstdlib> // strtoull()
#include <cassert>
#include <iomanip> // put_time(), setw(), dec, right
@@ -31,6 +31,7 @@ using namespace std;
// of the std::tm argument.
//
#ifdef __GLIBCXX__
+
#include <ctime> // tm, strftime()
#include <ostream>
@@ -61,8 +62,38 @@ namespace details
}
using namespace details;
+
#endif
+// Thread-safe implementations of gmtime() and localtime().
+//
+// Normally we would provide POSIX function replacement for Windows if the
+// original function is absent. However, MinGW GCC can sometimes provide them.
+// And so to avoid name clashes we hide them in the details namespace.
+//
+namespace details
+{
+ static tm*
+ gmtime (const time_t* t, tm* r)
+ {
+#ifdef _WIN32
+ return gmtime_s (r, t) != 0 ? nullptr : r;
+#else
+ return gmtime_r (t, r);
+#endif
+ }
+
+ static tm*
+ localtime (const time_t* t, tm* r)
+ {
+#ifdef _WIN32
+ return localtime_s (r, t) != 0 ? nullptr : r;
+#else
+ return localtime_r (t, r);
+#endif
+ }
+}
+
namespace butl
{
ostream&
@@ -84,7 +115,9 @@ namespace butl
time_t t (system_clock::to_time_t (ts));
std::tm tm;
- if ((local ? localtime_r (&t, &tm) : gmtime_r (&t, &tm)) == nullptr)
+ if ((local
+ ? details::localtime (&t, &tm)
+ : details::gmtime (&t, &tm)) == nullptr)
throw system_error (errno, system_category ());
using namespace chrono;
@@ -214,7 +247,7 @@ namespace butl
if (fmt != nullptr)
{
std::tm tm;
- if (gmtime_r (&t, &tm) == nullptr)
+ if (details::gmtime (&t, &tm) == nullptr)
throw system_error (errno, system_category ());
if (t >= 24 * 60 * 60)
@@ -260,105 +293,119 @@ namespace butl
}
}
-// VC++ implementation of strptime() via std::get_time().
+// Implementation of strptime() and timegm() for Windows.
+//
+// Here we have several cases. If this is VC++, then we implement strptime()
+// via C++11 std::get_time(). And if this is MINGW GCC (or, more precisely,
+// libstdc++), then we have several problems. Firstly, GCC prior to 5 doesn't
+// implement std::get_time(). Secondly, GCC 5 and even 6 have buggy
+// std::get_time() (it cannot parse single-digit days). So what we are going
+// to do in this case is use a FreeBSD-based strptime() implementation.
//
-// To debug fallback functions with GCC, uncomment the following defines.
+#ifdef _WIN32
+
+#ifdef __GLIBCXX__
+
+// Fallback to a FreeBSD-based implementation.
//
-//#define _MSC_VER
-//#define strptime strptime_
-//#define timegm timegm_
+extern "C"
+{
+#include "strptime.c"
+}
-#ifdef _MSC_VER
-#include <ctime> // time_t, tm, mktime(), gmtime()
+#else // NOT __GLIBCXX__
+
+#include <ctime> // tm
#include <locale>
#include <clocale>
#include <sstream>
#include <iomanip>
#include <cstring> // strlen()
-namespace details
+// VC++ std::get_time()-based implementation.
+//
+static char*
+strptime (const char* input, const char* format, tm* time)
{
- static char*
- strptime (const char* input, const char* format, tm* time)
- {
- istringstream is (input);
-
- // The original strptime() function behaves according to the process' C
- // locale (set with std::setlocale()), which can differ from the process
- // C++ locale (set with std::locale::global()).
+ istringstream is (input);
+
+ // The original strptime() function behaves according to the process' C
+ // locale (set with std::setlocale()), which can differ from the process C++
+ // locale (set with std::locale::global()).
+ //
+ is.imbue (locale (setlocale (LC_ALL, nullptr)));
+
+ if (!(is >> get_time (time, format)))
+ return nullptr;
+ else
+ // tellg() behaves as UnformattedInputFunction, so returns failure status
+ // if eofbit is set.
//
- is.imbue (locale (setlocale (LC_ALL, nullptr)));
+ return const_cast<char*> (
+ input + (is.eof ()
+ ? strlen (input)
+ : static_cast<size_t> (is.tellg ())));
+}
- if (!(is >> get_time (time, format)))
- return nullptr;
- else
- // tellg () behaves as UnformattedInputFunction, so returns failure
- // status if eofbit is set.
- //
- return const_cast<char*> (
- input + (is.eof ()
- ? strlen (input)
- : static_cast<size_t> (is.tellg ())));
- }
+#endif // __GLIBCXX__
- static time_t
- timegm (tm* ctm)
- {
- const time_t e (static_cast<time_t> (-1));
+#include <ctime> // time_t, tm, mktime()
- // We will use an example to explain how it works. Say *ctm contains 9 AM
- // of some day. Note that no time zone information is available.
- //
- // Convert it to the time from Epoch as if it's in the local time zone.
- //
- ctm->tm_isdst = -1;
- time_t t (mktime (ctm));
- if (t == e)
- return e;
-
- // Let's say we are in Moscow, and t contains the time passed from Epoch
- // till 9 AM MSK. But that is not what we need. What we need is the time
- // passed from Epoch till 9 AM GMT. This is some bigger number, as it takes
- // longer to achieve the same calendar time for more Western location. So
- // we need to find that offset, and increment t with it to obtain the
- // desired value. The offset is effectively the time difference between MSK
- // and GMT time zones.
- //
- tm gtm;
- if (gmtime_r (&t, &gtm) == nullptr)
- return e;
-
- // gmtime_r() being called for the timepoint t returns 6 AM. So now we
- // have *ctm and gtm, which value difference (3 hours) reflects the
- // desired offset. The only problem is that we can not deduct gtm from
- // *ctm, to get the offset expressed as time_t. To do that we need to apply
- // to both of them the same conversion function transforming std::tm to
- // std::time_t. The mktime() can do that, so the expression (mktime(ctm) -
- // mktime(&gtm)) calculates the desired offset.
- //
- // To ensure mktime() works exactly the same way for both cases, we need
- // to reset Daylight Saving Time flag for each of *ctm and gtm.
- //
- ctm->tm_isdst = 0;
- time_t lt (mktime (ctm));
- if (lt == e)
- return e;
-
- gtm.tm_isdst = 0;
- time_t gt (mktime (&gtm));
- if (gt == e)
- return e;
-
- // C11 standard specifies time_t to be a real type (integer and real
- // floating types are collectively called real types). So we can not
- // consider it to be signed.
- //
- return lt > gt ? t + (lt - gt) : t - (gt - lt);
- }
+static time_t
+timegm (tm* ctm)
+{
+ const time_t e (static_cast<time_t> (-1));
+
+ // We will use an example to explain how it works. Say *ctm contains 9 AM of
+ // some day. Note that no time zone information is available.
+ //
+ // Convert it to the time from Epoch as if it's in the local time zone.
+ //
+ ctm->tm_isdst = -1;
+ time_t t (mktime (ctm));
+ if (t == e)
+ return e;
+
+ // Let's say we are in Moscow, and t contains the time passed from Epoch till
+ // 9 AM MSK. But that is not what we need. What we need is the time passed
+ // from Epoch till 9 AM GMT. This is some bigger number, as it takes longer
+ // to achieve the same calendar time for more Western location. So we need to
+ // find that offset, and increment t with it to obtain the desired value. The
+ // offset is effectively the time difference between MSK and GMT time zones.
+ //
+ tm gtm;
+ if (details::gmtime (&t, &gtm) == nullptr)
+ return e;
+
+ // gmtime() being called for the timepoint t returns 6 AM. So now we have
+ // *ctm and gtm, which value difference (3 hours) reflects the desired
+ // offset. The only problem is that we can not deduct gtm from *ctm, to get
+ // the offset expressed as time_t. To do that we need to apply to both of
+ // them the same conversion function transforming std::tm to std::time_t. The
+ // mktime() can do that, so the expression (mktime(ctm) - mktime(&gtm))
+ // calculates the desired offset.
+ //
+ // To ensure mktime() works exactly the same way for both cases, we need to
+ // reset Daylight Saving Time flag for each of *ctm and gtm.
+ //
+ ctm->tm_isdst = 0;
+ time_t lt (mktime (ctm));
+ if (lt == e)
+ return e;
+
+ gtm.tm_isdst = 0;
+ time_t gt (mktime (&gtm));
+ if (gt == e)
+ return e;
+
+ // C11 standard specifies time_t to be a real type (integer and real floating
+ // types are collectively called real types). So we can not consider it to be
+ // signed.
+ //
+ return lt > gt ? t + (lt - gt) : t - (gt - lt);
}
-using namespace details;
-#endif
+#endif // _WIN32
namespace butl
{
diff --git a/tests/buildfile b/tests/buildfile
index 59caba8..e6670ca 100644
--- a/tests/buildfile
+++ b/tests/buildfile
@@ -2,6 +2,8 @@
# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-d = base64/ dir-iterator/ path/ prefix-map/ sha256/ timestamp/ triplet/
+d = base64/ dir-iterator/ pager/ path/ prefix-map/ process/ sha256/ \
+ timestamp/ triplet/
+
.: $d
include $d
diff --git a/tests/dir-iterator/driver.cxx b/tests/dir-iterator/driver.cxx
index 4e81298..9eebb9f 100644
--- a/tests/dir-iterator/driver.cxx
+++ b/tests/dir-iterator/driver.cxx
@@ -24,6 +24,9 @@ operator<< (ostream& os, entry_type e)
return os << entry_type_string[static_cast<size_t> (e)];
}
+// @@ Should we make the test silent unless -v arg passed. In silen mode could
+// compare the output with a set of predefined dir entries.
+//
int
main (int argc, const char* argv[])
{
diff --git a/tests/pager/buildfile b/tests/pager/buildfile
new file mode 100644
index 0000000..e42f3b0
--- /dev/null
+++ b/tests/pager/buildfile
@@ -0,0 +1,7 @@
+# file : tests/pager/buildfile
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: cxx{driver} ../../butl/lib{butl}
+
+include ../../butl/
diff --git a/tests/pager/driver.cxx b/tests/pager/driver.cxx
new file mode 100644
index 0000000..cab3078
--- /dev/null
+++ b/tests/pager/driver.cxx
@@ -0,0 +1,111 @@
+// file : tests/pager/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <vector>
+#include <string>
+#include <utility> // move()
+#include <cassert>
+#include <sstream>
+#include <iostream>
+
+#include <butl/pager>
+
+using namespace std;
+using namespace butl;
+
+int
+main (int argc, const char* argv[])
+{
+ bool child (false);
+ bool interactive (false);
+ string pgprog;
+ vector<string> pgopts;
+
+ assert (argc > 0);
+
+ int i (1);
+ for (; i != argc; ++i)
+ {
+ string v (argv[i]);
+ if (pgprog.empty ())
+ {
+ if (v == "-c")
+ child = true;
+ else if (v == "-i")
+ interactive = true;
+ else
+ {
+ pgprog = move (v);
+ interactive = true;
+ }
+ }
+ else
+ pgopts.emplace_back (move (v));
+ }
+
+ if (i != argc)
+ {
+ if (!child)
+ cerr << "usage: " << argv[0] << " [-c] [-i] [<pager> [<options>]]"
+ << endl;
+
+ return 1;
+ }
+
+ const char* s (R"delim(
+class fdstream_base
+{
+protected:
+ fdstream_base () = default;
+ fdstream_base (int fd): buf_ (fd) {}
+
+protected:
+ fdbuf buf_;
+};
+
+class ifdstream: fdstream_base, public std::istream
+{
+public:
+ ifdstream (): std::istream (&buf_) {}
+ ifdstream (int fd): fdstream_base (fd), std::istream (&buf_) {}
+
+ void close () {buf_.close ();}
+ void open (int fd) {buf_.open (fd);}
+ bool is_open () const {return buf_.is_open ();}
+};
+)delim");
+
+ if (child)
+ {
+ string il;
+ string ol;
+ istringstream is (s);
+ do
+ {
+ getline (cin, il);
+ getline (is, ol);
+ }
+ while (cin.good () && is.good () && il == ol);
+ return cin.eof () && !cin.bad () && is.eof () ? 0 : 1;
+ }
+
+ try
+ {
+ string prog (argv[0]);
+ vector<string> opts ({"-c"});
+
+ pager p ("pager test",
+ false,
+ interactive ? (pgprog.empty () ? nullptr : &pgprog) : &prog,
+ interactive ? (pgopts.empty () ? nullptr : &pgopts) : &opts);
+
+ p.stream () << s;
+
+ assert (p.wait ());
+ }
+ catch (const system_error&)
+ {
+ assert (false);
+ }
+}
diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx
index 4df2a05..de8e8b1 100644
--- a/tests/path/driver.cxx
+++ b/tests/path/driver.cxx
@@ -15,11 +15,20 @@ using namespace butl;
int
main ()
{
+ // Make sure we have nothrow destructor and move constructor so that
+ // storage in containers is not pessimized.
+ //
static_assert (is_nothrow_destructible<path>::value, "");
- static_assert (is_nothrow_move_constructible<path>::value, "");
-
static_assert (is_nothrow_destructible<dir_path>::value, "");
+
+ // MINGW GCC 4.9 std::string is not nothrow-move-constructible, so path and
+ // dir_path (which have a member of std::string type) are not as such as
+ // well.
+ //
+#if !defined(_WIN32) || !defined(__GNUC__) || __GNUC__ > 4
+ static_assert (is_nothrow_move_constructible<path>::value, "");
static_assert (is_nothrow_move_constructible<dir_path>::value, "");
+#endif
assert (path ("/").string () == "/");
assert (path ("//").string () == "/");
@@ -174,7 +183,11 @@ main ()
assert (path (p.begin (), p.end ()) == p);
assert (path (++p.begin (), p.end ()) == path ("foo/bar"));
assert (path (++(++p.begin ()), p.end ()) == path ("bar"));
+
+#ifndef _WIN32
assert (path (p.begin (), ++p.begin ()) == path ("/"));
+#endif
+
assert (path (++p.begin (), ++(++p.begin ())) == path ("foo"));
assert (path (++(++p.begin ()), ++(++(++p.begin ()))) == path ("bar"));
}
diff --git a/tests/process/buildfile b/tests/process/buildfile
new file mode 100644
index 0000000..f3d2cd4
--- /dev/null
+++ b/tests/process/buildfile
@@ -0,0 +1,7 @@
+# file : tests/process/buildfile
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: cxx{driver} ../../butl/lib{butl}
+
+include ../../butl/
diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx
new file mode 100644
index 0000000..ba61ea8
--- /dev/null
+++ b/tests/process/driver.cxx
@@ -0,0 +1,357 @@
+// file : tests/process/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <stdlib.h> // getenv(), setenv(), _putenv_s()
+
+#include <ios>
+#include <string>
+#include <vector>
+#include <cassert>
+#include <iostream>
+#include <iterator> // istreambuf_iterator, ostream_iterator
+#include <algorithm> // copy()
+
+#include <butl/path>
+#include <butl/process>
+#include <butl/fdstream>
+
+using namespace std;
+using namespace butl;
+
+static bool
+exec (const path& p,
+ vector<char> in = vector<char> (),
+ bool out = false,
+ bool err = false,
+ bool pipeline = false,
+ bool bin = true, // Set binary mode for file descriptors.
+ dir_path wd = dir_path ()) // Set the working dir for the child process.
+{
+ using cstrings = vector<const char*>;
+
+ assert (!in.empty () || (!out && !err)); // Nothing to output if no input.
+ assert (!pipeline || out); // To pipeline need to output something.
+
+ 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");
+
+ if (e)
+ a.push_back ("-e");
+
+ if (bin)
+ a.push_back ("-b");
+
+ if (cwd != nullptr)
+ a.push_back (cwd);
+
+ a.push_back (nullptr);
+ return a;
+ };
+
+ 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);
+
+ try
+ {
+ if (!in.empty ())
+ {
+ bool s;
+ r = !pr.try_wait (s); // Couldn't exit as waiting for the input.
+
+ auto bin_mode = [bin](int fd) -> int
+ {
+ if (bin)
+ fdmode (fd, fdtranslate::binary);
+
+ return fd;
+ };
+
+ ofdstream os (bin_mode (pr.out_fd));
+ os.exceptions (ofdstream::badbit);
+ copy (in.begin (), in.end (), ostream_iterator<char> (os));
+ os.close ();
+
+ if (out)
+ {
+ vector<char> o;
+
+ if (pipeline)
+ {
+ // Here we test both passing process output fd as an input for
+ // another process (pr2.in = pr.out), as well as passing process
+ // 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));
+
+ bool cr (fdclose (pr3.out_fd));
+ assert (cr);
+
+ ifdstream is (bin_mode (pr3.in_ofd));
+
+ o = vector<char> (
+ (istreambuf_iterator<char> (is)), istreambuf_iterator<char> ());
+
+ r = pr2.wait () && r;
+ r = pr3.wait () && r;
+ }
+ else
+ {
+ ifdstream is (bin_mode (pr.in_ofd));
+
+ o = vector<char> (
+ (istreambuf_iterator<char> (is)), istreambuf_iterator<char> ());
+ }
+
+ if (err)
+ {
+ // If STDERR is redirected to STDOUT then output will be
+ // duplicated.
+ //
+ vector<char> v (in);
+ in.reserve (in.size () * 2);
+ in.insert (in.end (), v.begin (), v.end ());
+ }
+
+ r = in == o && r;
+ }
+
+ if (err && !out)
+ {
+ ifdstream is (bin_mode (pr.in_efd));
+
+ vector<char> e
+ ((istreambuf_iterator<char> (is)), istreambuf_iterator<char> ());
+
+ r = in == e && r;
+ }
+ }
+ }
+ catch (const ios_base::failure&)
+ {
+ r = false;
+ }
+
+ bool s;
+ return pr.wait () && pr.try_wait (s) && s && r;
+ }
+ catch (const process_error& e)
+ {
+ if (e.child ())
+ exit (1);
+
+ return false;
+ }
+}
+
+static bool
+exec (const path& p,
+ const string& i,
+ bool o = false,
+ bool e = false,
+ bool pipeline = false,
+ dir_path wd = dir_path ())
+{
+ return exec (
+ p, vector<char> (i.begin (), i.end ()), o, e, pipeline, false, wd);
+}
+
+int
+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.
+
+ assert (argc > 0);
+
+ int i (1);
+ for (; i != argc; ++i)
+ {
+ 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
+ {
+ if (!wd.empty ())
+ break;
+
+ try
+ {
+ wd = dir_path (v);
+ }
+ catch (const invalid_path&)
+ {
+ break;
+ }
+ }
+ }
+
+ if (i != argc)
+ {
+ if (!child)
+ cerr << "usage: " << argv[0] << " [-c] [-i] [-o] [-e] [-b] [<dir>]"
+ << endl;
+
+ return 1;
+ }
+
+ path p;
+
+ try
+ {
+ p = path (argv[0]);
+ }
+ catch (const invalid_path&)
+ {
+ if (child)
+ return 1;
+
+ assert (false);
+ }
+
+ if (child)
+ {
+ // Child process. Check if the working directory argument matches the
+ // current directory if specified. Read input data if requested, optionally
+ // write it to cout and/or cerr.
+ //
+
+ if (!wd.empty () && wd.realize () != dir_path::current ())
+ return 1;
+
+ if (!in)
+ return 0; // Nothing to read, so nothing to write.
+
+ try
+ {
+ if (bin)
+ {
+ stdin_fdmode (fdtranslate::binary);
+ stdout_fdmode (fdtranslate::binary);
+ stderr_fdmode (fdtranslate::binary);
+ }
+
+ 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));
+ }
+
+ if (err)
+ {
+ cerr.exceptions (istream::badbit);
+ copy (data.begin (), data.end (), ostream_iterator<char> (cerr));
+ }
+ }
+ catch (const ios_base::failure&)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ const char* s ("ABC\nXYZ");
+
+ assert (exec (p));
+ assert (exec (p, s));
+ assert (exec (p, s, true));
+ assert (exec (p, s, true, false, true)); // Same but with piping.
+ assert (exec (p, s, false, true));
+ assert (exec (p, s, true, true));
+ assert (exec (p, s, true, true, true)); // Same but with piping.
+
+ // Transmit large binary data through the child.
+ //
+ vector<char> v;
+ v.reserve (5000 * 256);
+ for (size_t i (0); i < 5000; ++i)
+ {
+ for (size_t c (0); c < 256; ++c)
+ v.push_back (c);
+ }
+
+ assert (exec (p, v, true, true));
+ assert (exec (p, v, true, true, true)); // Same as above but with piping.
+
+ // Execute the child using the full path.
+ //
+ path fp (p);
+ fp.complete ();
+ assert (exec (fp));
+
+ // Execute the child using the relative path.
+ //
+ dir_path::current (fp.directory ());
+
+ assert (exec (path (".") / fp.leaf ()));
+
+ // Fail for unexistent file path.
+ //
+ assert (!exec (path ("./dr")));
+
+ // Execute the child using file name having PATH variable being properly set.
+ //
+ string paths (fp.directory ().string ());
+
+ if (char const* s = getenv ("PATH"))
+ paths += string (1, path::traits::path_separator) + s;
+
+#ifndef _WIN32
+ assert (setenv ("PATH", paths.c_str (), 1) == 0);
+#else
+ assert (_putenv_s ("PATH", paths.c_str ()) == 0);
+#endif
+
+ dir_path::current (fp.directory () / dir_path (".."));
+
+ assert (exec (fp.leaf ()));
+
+ // Same as above but also with changing the child current directory.
+ //
+ assert (exec (
+ fp.leaf (), vector<char> (), false, false, false, true, fp.directory ()));
+}
+catch (const system_error&)
+{
+ assert (false);
+}
diff --git a/tests/timestamp/driver.cxx b/tests/timestamp/driver.cxx
index 4c366fc..f5f30b9 100644
--- a/tests/timestamp/driver.cxx
+++ b/tests/timestamp/driver.cxx
@@ -10,7 +10,6 @@
#include <cassert>
#include <sstream>
#include <iomanip>
-#include <iostream>
#include <system_error>
#include <butl/timestamp>
@@ -172,12 +171,16 @@ main ()
"%[.N]%Y-%m-%d %H:%M:%S",
"." + ns (384902285) + "2016-02-21 19:31:10"));
- /*
- setlocale (LC_ALL, "de_DE.utf-8");
- locale::global (locale ("de_DE.utf-8"));
+// setlocale (LC_ALL, "");
+// setlocale (LC_ALL, "de_DE.utf-8");
+// setlocale (LC_ALL, "de-de");
+// setlocale (LC_ALL, "German_Germany.1252");
+// locale::global (locale (""));
+// locale::global (locale ("de_DE.utf-8"));
+/*
assert (parse ("Mai 11 19:31:10 2016 GMT", "%b %d %H:%M:%S%[.N] %Y"));
locale::global (locale ("C"));
- */
+*/
// @@ When debuging strptime() fallback implementation compiled with GCC
// 5.3.1, the following asserts will fail due to bugs in implementation