You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

336 lines
7.2 KiB

/*
* Copyright (C) 2013 Daniel Scharrer
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the author(s) be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "util/time.hpp"
#include "configure.hpp"
#if INNOEXTRACT_HAVE_TIMEGM || INNOEXTRACT_HAVE_GMTIME_R
#include <time.h>
#endif
#include <stdlib.h>
#if defined(_WIN32)
#include <windows.h>
#endif
#if INNOEXTRACT_HAVE_DLSYM
#include <dlfcn.h>
#endif
#if INNOEXTRACT_HAVE_AT_FDCWD
#include <fcntl.h>
#endif
#if INNOEXTRACT_HAVE_UTIMENSAT && INNOEXTRACT_HAVE_AT_FDCWD
#include <sys/stat.h>
#elif !defined(_WIN32) && INNOEXTRACT_HAVE_UTIMES
#include <sys/time.h>
#elif !defined(_WIN32)
#include <boost/filesystem/operations.hpp>
#endif
#include "util/log.hpp"
namespace util {
#if defined(_WIN32)
static const boost::int64_t FiletimeOffset = 0x19DB1DED53E8000ll;
static time from_filetime(FILETIME ft) {
boost::int64_t filetime = boost::int64_t(ft.dwHighDateTime) << 32;
filetime += boost::int64_t(ft.dwLowDateTime);
filetime -= FiletimeOffset;
return filetime / 10000000;
}
static FILETIME to_filetime(time t, boost::uint32_t nsec = 0) {
static const boost::int64_t FiletimeOffset = 0x19DB1DED53E8000ll;
boost::int64_t time = boost::int64_t(t) * 10000000 + boost::int64_t(nsec) / 100;
time += FiletimeOffset;
FILETIME filetime;
filetime.dwLowDateTime = DWORD(time);
filetime.dwHighDateTime = DWORD(time >> 32);
return filetime;
}
#endif
static void set_timezone(const char * value) {
const char * variable = "TZ";
#if defined(_WIN32)
SetEnvironmentVariableA(variable, value);
_tzset();
#else
if(value) {
setenv(variable, value, 1);
} else {
unsetenv(variable);
}
tzset();
#endif
}
time parse_time(std::tm tm) {
tm.tm_isdst = 0;
#if defined(_WIN32)
// Windows
SYSTEMTIME st;
st.wYear = WORD(tm.tm_year + 1900);
st.wMonth = WORD(tm.tm_mon + 1);
st.wDay = WORD(tm.tm_mday);
st.wHour = WORD(tm.tm_hour);
st.wMinute = WORD(tm.tm_min);
st.wSecond = WORD(tm.tm_sec);
st.wMilliseconds = 0;
FILETIME ft;
if(!SystemTimeToFileTime(&st, &ft)) {
return 0;
}
return from_filetime(ft);
#elif INNOEXTRACT_HAVE_TIMEGM
// GNU / BSD extension
return timegm(&tm);
#else
// Standard, but not thread-safe - should be OK for our use though
char * tz = getenv("TZ");
set_timezone("UTC");
time ret = std::mktime(&tm);
set_timezone(tz);
return ret;
#endif
}
template <typename Time>
static Time to_time_t(time t, const char * file = "conversion") {
Time ret = Time(t);
if(time(ret) != t) {
log_warning << "Truncating timestamp " << t << " to " << ret << " for " << file;
}
return ret;
}
std::tm format_time(time t) {
std::tm ret;
#if defined(_WIN32)
// Windows
FILETIME ft = to_filetime(t);
SYSTEMTIME st;
if(FileTimeToSystemTime(&ft, &st)) {
ret.tm_year = int(st.wYear) - 1900;
ret.tm_mon = int(st.wMonth) - 1;
ret.tm_wday = int(st.wDayOfWeek);
ret.tm_mday = int(st.wDay);
ret.tm_hour = int(st.wHour);
ret.tm_min = int(st.wMinute);
ret.tm_sec = int(st.wSecond);
} else {
ret.tm_year = ret.tm_mon = ret.tm_mday = -1;
ret.tm_hour = ret.tm_min = ret.tm_sec = -1;
}
ret.tm_isdst = -1;
#elif INNOEXTRACT_HAVE_GMTIME_R
// POSIX.1
time_t tt = to_time_t<time_t>(t);
gmtime_r(&tt, &ret);
#else
// Standard C++
std::time_t tt = to_time_t<std::time_t>(t);
std::tm * tmp = std::gmtime(&tt); /* not thread-safe */
if(tmp) {
ret = *tmp;
} else {
ret.tm_year = ret.tm_mon = ret.tm_mday = -1;
ret.tm_hour = ret.tm_min = ret.tm_sec = -1;
ret.tm_isdst = -1;
}
#endif
return ret;
}
time to_local_time(time t) {
// Format time as UTC ...
std::tm time = format_time(t);
// ... and interpret it as local time
time.tm_isdst = 0;
return std::mktime(&time);
}
void set_local_timezone(std::string timezone) {
/*
* The TZ variable interprets the offset as the change from local time
* to UTC while everyone else does the opposite.
* We flip the direction so that timezone strings such as GMT+1 work as expected.
*/
for(size_t i = 0; i < timezone.length(); i++) {
if(timezone[i] == '+') {
timezone[i] = '-';
} else if(timezone[i] == '-') {
timezone[i] = '+';
}
}
set_timezone(timezone.c_str());
}
#if defined(_WIN32)
static HANDLE open_file(LPCSTR name) {
return CreateFileA(name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
}
static HANDLE open_file(LPCWSTR name) {
return CreateFileW(name, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
}
#endif
#if INNOEXTRACT_HAVE_DYNAMIC_UTIMENSAT
extern "C" typedef int (*utimensat_proc)
(int fd, const char *path, const struct timespec times[2], int flag);
#endif
bool set_file_time(const boost::filesystem::path & path, time t, boost::uint32_t nsec) {
#if (INNOEXTRACT_HAVE_DYNAMIC_UTIMENSAT || INNOEXTRACT_HAVE_UTIMENSAT) \
&& INNOEXTRACT_HAVE_AT_FDCWD
// nanosecond precision, for Linux and POSIX.1-2008+ systems
struct timespec timens[2];
timens[0].tv_sec = to_time_t<time_t>(t, path.string().c_str());
timens[0].tv_nsec = boost::int32_t(nsec);
timens[1] = timens[0];
#endif
#if INNOEXTRACT_HAVE_DYNAMIC_UTIMENSAT && INNOEXTRACT_HAVE_AT_FDCWD
static utimensat_proc utimensat_func = (utimensat_proc)dlsym(RTLD_DEFAULT, "utimensat");
if(utimensat_func) {
return (utimensat_func(AT_FDCWD, path.string().c_str(), timens, 0) == 0);
}
#endif
#if INNOEXTRACT_HAVE_UTIMENSAT && INNOEXTRACT_HAVE_AT_FDCWD
return (utimensat(AT_FDCWD, path.string().c_str(), timens, 0) == 0);
#elif defined(_WIN32)
// 100-nanosecond precision, for Windows
// Prevent unused function warnings
(void)(HANDLE(*)(LPCSTR))open_file;
(void)(HANDLE(*)(LPCWSTR))open_file;
HANDLE handle = open_file(path.c_str());
if(handle == INVALID_HANDLE_VALUE) {
return false;
}
FILETIME filetime = to_filetime(t, nsec);
bool ret = (SetFileTime(handle, &filetime, &filetime, &filetime) != 0);
CloseHandle(handle);
return ret;
#elif INNOEXTRACT_HAVE_UTIMES
// microsecond precision, for older POSIX systems (4.3BSD, POSIX.1-2001)
struct timeval times[2];
times[0].tv_sec = to_time_t<time_t>(t, path.string().c_str());
times[0].tv_usec = boost::int32_t(nsec / 1000);
times[1] = times[0];
return (utimes(path.string().c_str(), times) == 0);
#else
// fallback with second precision or worse
try {
(void)nsec; // sub-second precision not supported by Boost
std::time_t tt = to_time_t<std::time_t>(t, path.string().c_str());
boost::filesystem::last_write_time(path, tt);
return true;
} catch(...) {
return false;
}
#endif
}
} // namespace util