cmTimestamp.cxx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__)
  4. // POSIX APIs are needed
  5. // NOLINTNEXTLINE(bugprone-reserved-identifier)
  6. # define _POSIX_C_SOURCE 200809L
  7. #endif
  8. #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__)
  9. // For isascii
  10. // NOLINTNEXTLINE(bugprone-reserved-identifier)
  11. # define _XOPEN_SOURCE 700
  12. #endif
  13. #if defined(__APPLE__)
  14. // Restore Darwin APIs removed by _POSIX_C_SOURCE:
  15. // aligned_alloc
  16. // timespec_get
  17. // NOLINTNEXTLINE(bugprone-reserved-identifier)
  18. # define _DARWIN_C_SOURCE
  19. #endif
  20. #include "cmTimestamp.h"
  21. #include <cstdlib>
  22. #include <cstring>
  23. #include <ctime>
  24. #include <sstream>
  25. #include <utility>
  26. #ifdef __MINGW32__
  27. # include <libloaderapi.h>
  28. #endif
  29. #include <cm3p/uv.h>
  30. #include "cmStringAlgorithms.h"
  31. #include "cmSystemTools.h"
  32. std::string cmTimestamp::CurrentTime(cm::string_view formatString,
  33. bool utcFlag) const
  34. {
  35. // get current time with microsecond resolution
  36. uv_timeval64_t timeval;
  37. uv_gettimeofday(&timeval);
  38. auto currentTimeT = static_cast<time_t>(timeval.tv_sec);
  39. auto microseconds = static_cast<uint32_t>(timeval.tv_usec);
  40. // check for override via SOURCE_DATE_EPOCH for reproducible builds
  41. std::string source_date_epoch;
  42. cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch);
  43. if (!source_date_epoch.empty()) {
  44. std::istringstream iss(source_date_epoch);
  45. iss >> currentTimeT;
  46. if (iss.fail() || !iss.eof()) {
  47. cmSystemTools::Error("Cannot parse SOURCE_DATE_EPOCH as integer");
  48. exit(27);
  49. }
  50. // SOURCE_DATE_EPOCH has only a resolution in the seconds range
  51. microseconds = 0;
  52. }
  53. if (currentTimeT == static_cast<time_t>(-1)) {
  54. return std::string();
  55. }
  56. return this->CreateTimestampFromTimeT(currentTimeT, microseconds,
  57. static_cast<std::string>(formatString),
  58. utcFlag);
  59. }
  60. std::string cmTimestamp::FileModificationTime(char const* path,
  61. cm::string_view formatString,
  62. bool utcFlag) const
  63. {
  64. std::string real_path =
  65. cmSystemTools::GetRealPathResolvingWindowsSubst(path);
  66. if (!cmsys::SystemTools::FileExists(real_path)) {
  67. return std::string();
  68. }
  69. // use libuv's implementation of stat(2) to get the file information
  70. time_t mtime = 0;
  71. uint32_t microseconds = 0;
  72. uv_fs_t req;
  73. if (uv_fs_stat(nullptr, &req, real_path.c_str(), nullptr) == 0) {
  74. mtime = static_cast<time_t>(req.statbuf.st_mtim.tv_sec);
  75. // tv_nsec has nanosecond resolution, but we truncate it to microsecond
  76. // resolution in order to be consistent with cmTimestamp::CurrentTime()
  77. microseconds = static_cast<uint32_t>(req.statbuf.st_mtim.tv_nsec / 1000);
  78. }
  79. uv_fs_req_cleanup(&req);
  80. return this->CreateTimestampFromTimeT(
  81. mtime, microseconds, static_cast<std::string>(formatString), utcFlag);
  82. }
  83. std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
  84. std::string formatString,
  85. bool utcFlag) const
  86. {
  87. return this->CreateTimestampFromTimeT(timeT, 0, std::move(formatString),
  88. utcFlag);
  89. }
  90. std::string cmTimestamp::CreateTimestampFromTimeT(time_t timeT,
  91. uint32_t const microseconds,
  92. std::string formatString,
  93. bool utcFlag) const
  94. {
  95. if (formatString.empty()) {
  96. formatString = "%Y-%m-%dT%H:%M:%S";
  97. if (utcFlag) {
  98. formatString += "Z";
  99. }
  100. }
  101. struct tm timeStruct;
  102. memset(&timeStruct, 0, sizeof(timeStruct));
  103. struct tm* ptr = nullptr;
  104. if (utcFlag) {
  105. ptr = gmtime(&timeT);
  106. } else {
  107. ptr = localtime(&timeT);
  108. }
  109. if (!ptr) {
  110. return std::string();
  111. }
  112. timeStruct = *ptr;
  113. std::string result;
  114. for (std::string::size_type i = 0; i < formatString.size(); ++i) {
  115. char c1 = formatString[i];
  116. char c2 = (i + 1 < formatString.size()) ? formatString[i + 1]
  117. : static_cast<char>(0);
  118. if (c1 == '%' && c2 != 0) {
  119. result += this->AddTimestampComponent(c2, timeStruct, timeT, utcFlag,
  120. microseconds);
  121. ++i;
  122. } else {
  123. result += c1;
  124. }
  125. }
  126. return result;
  127. }
  128. time_t cmTimestamp::CreateUtcTimeTFromTm(struct tm& tm) const
  129. {
  130. #if defined(_MSC_VER) && _MSC_VER >= 1400
  131. return _mkgmtime(&tm);
  132. #else
  133. // From Linux timegm() manpage.
  134. std::string tz_old;
  135. bool const tz_was_set = cmSystemTools::GetEnv("TZ", tz_old);
  136. tz_old = "TZ=" + tz_old;
  137. // The standard says that "TZ=" or "TZ=[UNRECOGNIZED_TZ]" means UTC.
  138. // It seems that "TZ=" does NOT work, at least under Windows
  139. // with neither MSVC nor MinGW, so let's use explicit "TZ=UTC"
  140. cmSystemTools::PutEnv("TZ=UTC");
  141. tzset();
  142. time_t result = mktime(&tm);
  143. # ifndef CMAKE_BOOTSTRAP
  144. if (tz_was_set) {
  145. cmSystemTools::PutEnv(tz_old);
  146. } else {
  147. cmSystemTools::UnsetEnv("TZ");
  148. }
  149. # else
  150. // No UnsetEnv during bootstrap. This is good enough for CMake itself.
  151. cmSystemTools::PutEnv(tz_old);
  152. static_cast<void>(tz_was_set);
  153. # endif
  154. tzset();
  155. return result;
  156. #endif
  157. }
  158. std::string cmTimestamp::AddTimestampComponent(
  159. char flag, struct tm& timeStruct, time_t const timeT, bool const utcFlag,
  160. uint32_t const microseconds) const
  161. {
  162. std::string formatString = cmStrCat('%', flag);
  163. switch (flag) {
  164. case 'a':
  165. case 'A':
  166. case 'b':
  167. case 'B':
  168. case 'd':
  169. case 'H':
  170. case 'I':
  171. case 'j':
  172. case 'm':
  173. case 'M':
  174. case 'S':
  175. case 'U':
  176. case 'V':
  177. case 'w':
  178. case 'y':
  179. case 'Y':
  180. case '%':
  181. break;
  182. case 'Z':
  183. #if defined(__GLIBC__)
  184. // 'struct tm' has the time zone, so strftime can honor UTC.
  185. static_cast<void>(utcFlag);
  186. #else
  187. // 'struct tm' may not have the time zone, so strftime may
  188. // use local time. Hard-code the UTC result.
  189. if (utcFlag) {
  190. return std::string("GMT");
  191. }
  192. #endif
  193. break;
  194. case 'z': {
  195. #if defined(__GLIBC__)
  196. // 'struct tm' has the time zone, so strftime can honor UTC.
  197. static_cast<void>(utcFlag);
  198. #else
  199. // 'struct tm' may not have the time zone, so strftime may
  200. // use local time. Hard-code the UTC result.
  201. if (utcFlag) {
  202. return std::string("+0000");
  203. }
  204. #endif
  205. #ifndef _AIX
  206. break;
  207. #else
  208. std::string xpg_sus_old;
  209. bool const xpg_sus_was_set =
  210. cmSystemTools::GetEnv("XPG_SUS_ENV", xpg_sus_old);
  211. if (xpg_sus_was_set && xpg_sus_old == "ON") {
  212. break;
  213. }
  214. xpg_sus_old = "XPG_SUS_ENV=" + xpg_sus_old;
  215. // On AIX systems, %z requires XPG_SUS_ENV=ON to work as desired.
  216. cmSystemTools::PutEnv("XPG_SUS_ENV=ON");
  217. tzset();
  218. char buffer[16];
  219. size_t size = strftime(buffer, sizeof(buffer), "%z", &timeStruct);
  220. # ifndef CMAKE_BOOTSTRAP
  221. if (xpg_sus_was_set) {
  222. cmSystemTools::PutEnv(xpg_sus_old);
  223. } else {
  224. cmSystemTools::UnsetEnv("XPG_SUS_ENV");
  225. }
  226. # else
  227. // No UnsetEnv during bootstrap. This is good enough for CMake itself.
  228. cmSystemTools::PutEnv(xpg_sus_old);
  229. static_cast<void>(xpg_sus_was_set);
  230. # endif
  231. tzset();
  232. return std::string(buffer, size);
  233. #endif
  234. }
  235. case 's': // Seconds since UNIX epoch (midnight 1-jan-1970)
  236. {
  237. // Build a time_t for UNIX epoch and subtract from the input "timeT":
  238. struct tm tmUnixEpoch;
  239. memset(&tmUnixEpoch, 0, sizeof(tmUnixEpoch));
  240. tmUnixEpoch.tm_mday = 1;
  241. tmUnixEpoch.tm_year = 1970 - 1900;
  242. time_t const unixEpoch = this->CreateUtcTimeTFromTm(tmUnixEpoch);
  243. if (unixEpoch == -1) {
  244. cmSystemTools::Error(
  245. "Error generating UNIX epoch in string(TIMESTAMP ...) or "
  246. "file(TIMESTAMP ...). Please, file a bug report against CMake");
  247. return std::string();
  248. }
  249. return std::to_string(
  250. static_cast<int64_t>(std::difftime(timeT, unixEpoch)));
  251. }
  252. case 'f': // microseconds
  253. {
  254. // clip number to 6 digits and pad with leading zeros
  255. std::string microsecs = std::to_string(microseconds % 1000000);
  256. return std::string(6 - microsecs.length(), '0') + microsecs;
  257. }
  258. default: {
  259. return formatString;
  260. }
  261. }
  262. char buffer[16];
  263. #ifdef __MINGW32__
  264. /* See a bug in MinGW: https://sourceforge.net/p/mingw-w64/bugs/793/. A work
  265. * around is to try to use strftime() from ucrtbase.dll. */
  266. using T = size_t(__cdecl*)(char*, size_t, char const*, const struct tm*);
  267. auto loadUcrtStrftime = []() -> T {
  268. auto handle =
  269. LoadLibraryExA("ucrtbase.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
  270. if (handle) {
  271. # pragma GCC diagnostic push
  272. # pragma GCC diagnostic ignored "-Wcast-function-type"
  273. return reinterpret_cast<T>(GetProcAddress(handle, "strftime"));
  274. # pragma GCC diagnostic pop
  275. }
  276. return nullptr;
  277. };
  278. static T ucrtStrftime = loadUcrtStrftime();
  279. if (ucrtStrftime) {
  280. size_t size =
  281. ucrtStrftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
  282. return std::string(buffer, size);
  283. }
  284. #endif
  285. size_t size =
  286. strftime(buffer, sizeof(buffer), formatString.c_str(), &timeStruct);
  287. return std::string(buffer, size);
  288. }