From 61ef82ec2b2ca396667f92a4e5c6ceb729c42086 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sun, 15 May 2016 17:11:27 +0300 Subject: Port to MinGW --- .gitignore | 1 + LICENSE | 4 +- butl/base64.cxx | 4 +- butl/fdstream | 43 +++ butl/fdstream.cxx | 120 +++++++- butl/filesystem | 8 +- butl/filesystem.cxx | 233 ++++++++++++-- butl/filesystem.ixx | 29 +- butl/pager.cxx | 27 +- butl/path.cxx | 8 +- butl/process | 35 ++- butl/process.cxx | 564 ++++++++++++++++++++++++---------- butl/strptime.c | 629 ++++++++++++++++++++++++++++++++++++++ butl/strptime.c.orig | 689 ++++++++++++++++++++++++++++++++++++++++++ butl/timelocal.c | 157 ++++++++++ butl/timelocal.c.orig | 153 ++++++++++ butl/timelocal.h | 65 ++++ butl/timelocal.h.orig | 61 ++++ butl/timestamp.cxx | 221 ++++++++------ tests/buildfile | 4 +- tests/dir-iterator/driver.cxx | 3 + tests/pager/buildfile | 7 + tests/pager/driver.cxx | 111 +++++++ tests/path/driver.cxx | 17 +- tests/process/buildfile | 7 + tests/process/driver.cxx | 357 ++++++++++++++++++++++ tests/timestamp/driver.cxx | 13 +- 27 files changed, 3231 insertions(+), 339 deletions(-) create mode 100644 butl/strptime.c create mode 100644 butl/strptime.c.orig create mode 100644 butl/timelocal.c create mode 100644 butl/timelocal.c.orig create mode 100644 butl/timelocal.h create mode 100644 butl/timelocal.h.orig create mode 100644 tests/pager/buildfile create mode 100644 tests/pager/driver.cxx create mode 100644 tests/process/buildfile create mode 100644 tests/process/driver.cxx 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& v) + string + base64_encode (const vector& v) { string r; back_insert_iterator 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 #ifndef _WIN32 -# include // close(), read(), write() +# include // close(), read(), write() #else -# include // _close(), _read(), _write() +# include // _close(), _read(), _write(), _setmode() +# include // _fileno(), stdin, stdout, stderr +# include // _O_BINARY, _O_TEXT #endif #include @@ -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 (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 // mode_t #ifndef _WIN32 -# include // DIR, struct dirent, *dir() +# include // DIR +#else +# include // intptr_t #endif #include // 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 +#include // errno, E* #include // stat, rmdir(), unlink() #include // stat -#include // stat, lstat(), S_IS*, mkdir() +#include // stat(), lstat(), S_IS*, mkdir() + +#ifndef _WIN32 +# include // struct dirent, *dir() +#else +# include // _find*() +# include // _mkdir() +#endif #include // unique_ptr #include @@ -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 ( - 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 (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 h (::opendir (d.string ().c_str ())); + unique_ptr 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 // close(), STDOUT_FILENO # include // ioctl() + +# include +# include // this_thread::sleep_for() #else # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif -# include // GetConsoleScreenBufferInfo(), GetStdHandle() -# include // _close() +# include // GetConsoleScreenBufferInfo(), GetStdHandle(), + // Sleep() +# include // _close() #endif -#include -#include // this_thread::sleep_for() -#include // strchr() +#include // strchr() #include using namespace std; @@ -44,7 +46,9 @@ namespace butl col = static_cast (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 (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 // CreatePipe, CreateProcess -# include // _open_osfhandle +# include // CreatePipe(), CreateProcess() +# include // _open_osfhandle(), _get_osfhandle(), _close() # include // _O_TEXT +# include // getenv() +# include // stat +# include // stat(), S_IS* + +# include // unique_ptr + +# include #endif #include @@ -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 (&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 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 (_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 (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 // isspace(), isdigit() +#include // _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 +#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 +#include +#include +#include +#include +#include +#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 + * 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 +__FBSDID("$FreeBSD$"); + +#include + +#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 +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 + * 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 +__FBSDID("$FreeBSD$"); + +#include + +#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 +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 -#include // localtime_r(), gmtime_r(), strptime(), timegm() +#include // localtime_{r,s}(), gmtime_{r,s}(), strptime(), timegm() #include // EINVAL -#include // tm, time_t, strftime(), mktime() +#include // tm, time_t, mktime() #include // strtoull() #include #include // put_time(), setw(), dec, right @@ -31,6 +31,7 @@ using namespace std; // of the std::tm argument. // #ifdef __GLIBCXX__ + #include // tm, strftime() #include @@ -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 // time_t, tm, mktime(), gmtime() +#else // NOT __GLIBCXX__ + +#include // tm #include #include #include #include #include // 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 ( + input + (is.eof () + ? strlen (input) + : static_cast (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 ( - input + (is.eof () - ? strlen (input) - : static_cast (is.tellg ()))); - } +#endif // __GLIBCXX__ - static time_t - timegm (tm* ctm) - { - const time_t e (static_cast (-1)); +#include // 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, >m) == 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(>m)) 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 (>m)); - 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 (-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, >m) == 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(>m)) + // 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 (>m)); + 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 (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 +#include +#include // move() +#include +#include +#include + +#include + +using namespace std; +using namespace butl; + +int +main (int argc, const char* argv[]) +{ + bool child (false); + bool interactive (false); + string pgprog; + vector 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] [ []]" + << 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 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::value, ""); - static_assert (is_nothrow_move_constructible::value, ""); - static_assert (is_nothrow_destructible::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::value, ""); static_assert (is_nothrow_move_constructible::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 // getenv(), setenv(), _putenv_s() + +#include +#include +#include +#include +#include +#include // istreambuf_iterator, ostream_iterator +#include // copy() + +#include +#include +#include + +using namespace std; +using namespace butl; + +static bool +exec (const path& p, + vector in = vector (), + 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; + + 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 (os)); + os.close (); + + if (out) + { + vector 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 ( + (istreambuf_iterator (is)), istreambuf_iterator ()); + + r = pr2.wait () && r; + r = pr3.wait () && r; + } + else + { + ifdstream is (bin_mode (pr.in_ofd)); + + o = vector ( + (istreambuf_iterator (is)), istreambuf_iterator ()); + } + + if (err) + { + // If STDERR is redirected to STDOUT then output will be + // duplicated. + // + vector 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 e + ((istreambuf_iterator (is)), istreambuf_iterator ()); + + 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 (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] []" + << 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 data + ((istreambuf_iterator (cin)), istreambuf_iterator ()); + + if (out) + { + cout.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator (cout)); + } + + if (err) + { + cerr.exceptions (istream::badbit); + copy (data.begin (), data.end (), ostream_iterator (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 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 (), 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 #include #include -#include #include #include @@ -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 -- cgit v1.1