1 // This is an open source non-commercial project. Dear PVS-Studio, please check
2 // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 
4 #include <assert.h>
5 #include <limits.h>
6 #include <uv.h>
7 
8 #include "nvim/assert.h"
9 #include "nvim/event/loop.h"
10 #include "nvim/main.h"
11 #include "nvim/os/input.h"
12 #include "nvim/os/os.h"
13 #include "nvim/os/time.h"
14 
15 static uv_mutex_t delay_mutex;
16 static uv_cond_t delay_cond;
17 
18 
19 #ifdef INCLUDE_GENERATED_DECLARATIONS
20 # include "os/time.c.generated.h"
21 #endif
22 
23 /// Initializes the time module
time_init(void)24 void time_init(void)
25 {
26   uv_mutex_init(&delay_mutex);
27   uv_cond_init(&delay_cond);
28 }
29 
30 /// Gets a high-resolution (nanosecond), monotonically-increasing time relative
31 /// to an arbitrary time in the past.
32 ///
33 /// Not related to the time of day and therefore not subject to clock drift.
34 ///
35 /// @return Relative time value with nanosecond precision.
os_hrtime(void)36 uint64_t os_hrtime(void)
37   FUNC_ATTR_WARN_UNUSED_RESULT
38 {
39   return uv_hrtime();
40 }
41 
42 /// Gets a millisecond-resolution, monotonically-increasing time relative to an
43 /// arbitrary time in the past.
44 ///
45 /// Not related to the time of day and therefore not subject to clock drift.
46 /// The value is cached by the loop, it will not change until the next
47 /// loop-tick (unless uv_update_time is called).
48 ///
49 /// @return Relative time value with millisecond precision.
os_now(void)50 uint64_t os_now(void)
51   FUNC_ATTR_WARN_UNUSED_RESULT
52 {
53   return uv_now(&main_loop.uv);
54 }
55 
56 /// Sleeps for `ms` milliseconds.
57 ///
58 /// @see uv_sleep() (libuv v1.34.0)
59 ///
60 /// @param ms          Number of milliseconds to sleep
61 /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
os_delay(uint64_t ms,bool ignoreinput)62 void os_delay(uint64_t ms, bool ignoreinput)
63 {
64   DLOG("%" PRIu64 " ms", ms);
65   if (ignoreinput) {
66     if (ms > INT_MAX) {
67       ms = INT_MAX;
68     }
69     LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
70   } else {
71     os_microdelay(ms * 1000u, ignoreinput);
72   }
73 }
74 
75 /// Sleeps for `us` microseconds.
76 ///
77 /// @see uv_sleep() (libuv v1.34.0)
78 ///
79 /// @param us          Number of microseconds to sleep.
80 /// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
81 ///                    If false, waiting is aborted on any input.
os_microdelay(uint64_t us,bool ignoreinput)82 void os_microdelay(uint64_t us, bool ignoreinput)
83 {
84   uint64_t elapsed = 0u;
85   uint64_t base = uv_hrtime();
86   // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
87   const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX;
88 
89   uv_mutex_lock(&delay_mutex);
90 
91   while (elapsed < ns) {
92     // If ignoring input, we simply wait the full delay.
93     // Else we check for input in ~100ms intervals.
94     const uint64_t ns_delta = ignoreinput
95                               ? ns - elapsed
96                               : MIN(ns - elapsed, 100000000u);  // 100ms
97 
98     const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
99     if (0 != rv && UV_ETIMEDOUT != rv) {
100       abort();
101       break;
102     }  // Else: Timeout proceeded normally.
103 
104     if (!ignoreinput && os_char_avail()) {
105       break;
106     }
107 
108     const uint64_t now = uv_hrtime();
109     elapsed += now - base;
110     base = now;
111   }
112 
113   uv_mutex_unlock(&delay_mutex);
114 }
115 
116 // Cache of the current timezone name as retrieved from TZ, or an empty string
117 // where unset, up to 64 octets long including trailing null byte.
118 static char tz_cache[64];
119 
120 /// Portable version of POSIX localtime_r()
121 ///
122 /// @return NULL in case of error
os_localtime_r(const time_t * restrict clock,struct tm * restrict result)123 struct tm *os_localtime_r(const time_t *restrict clock,
124                           struct tm *restrict result) FUNC_ATTR_NONNULL_ALL
125 {
126 #ifdef UNIX
127   // POSIX provides localtime_r() as a thread-safe version of localtime().
128   //
129   // Check to see if the environment variable TZ has changed since the last run.
130   // Call tzset(3) to update the global timezone variables if it has.
131   // POSIX standard doesn't require localtime_r() implementations to do that
132   // as it does with localtime(), and we don't want to call tzset() every time.
133   const char *tz = os_getenv("TZ");
134   if (tz == NULL) {
135     tz = "";
136   }
137   if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) {
138     tzset();
139     xstrlcpy(tz_cache, tz, sizeof(tz_cache));
140   }
141   return localtime_r(clock, result);  // NOLINT(runtime/threadsafe_fn)
142 #else
143   // Windows version of localtime() is thread-safe.
144   // See http://msdn.microsoft.com/en-us/library/bf12f0hc%28VS.80%29.aspx
145   struct tm *local_time = localtime(clock);  // NOLINT(runtime/threadsafe_fn)
146   if (!local_time) {
147     return NULL;
148   }
149   *result = *local_time;
150   return result;
151 #endif
152 }
153 
154 /// Gets the current Unix timestamp and adjusts it to local time.
155 ///
156 /// @param result Pointer to a 'struct tm' where the result should be placed
157 /// @return A pointer to a 'struct tm' in the current time zone (the 'result'
158 ///         argument) or NULL in case of error
os_localtime(struct tm * result)159 struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
160 {
161   time_t rawtime = time(NULL);
162   return os_localtime_r(&rawtime, result);
163 }
164 
165 /// Portable version of POSIX ctime_r()
166 ///
167 /// @param clock[in]
168 /// @param result[out] Pointer to a 'char' where the result should be placed
169 /// @param result_len length of result buffer
170 /// @return human-readable string of current local time
os_ctime_r(const time_t * restrict clock,char * restrict result,size_t result_len)171 char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len)
172   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
173 {
174   struct tm clock_local;
175   struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
176   // MSVC returns NULL for an invalid value of seconds.
177   if (clock_local_ptr == NULL) {
178     xstrlcpy(result, _("(Invalid)"), result_len);
179   } else {
180     strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr);
181   }
182   xstrlcat(result, "\n", result_len);
183   return result;
184 }
185 
186 /// Gets the current Unix timestamp and adjusts it to local time.
187 ///
188 /// @param result[out] Pointer to a 'char' where the result should be placed
189 /// @param result_len length of result buffer
190 /// @return human-readable string of current local time
os_ctime(char * result,size_t result_len)191 char *os_ctime(char *result, size_t result_len)
192   FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
193 {
194   time_t rawtime = time(NULL);
195   return os_ctime_r(&rawtime, result, result_len);
196 }
197 
198 /// Portable version of POSIX strptime()
199 ///
200 /// @param str[in]  string to convert
201 /// @param format[in]  format to parse "str"
202 /// @param tm[out]  time representation of "str"
203 /// @return Pointer to first unprocessed character or NULL
os_strptime(const char * str,const char * format,struct tm * tm)204 char *os_strptime(const char *str, const char *format, struct tm *tm)
205   FUNC_ATTR_NONNULL_ALL
206 {
207 #ifdef HAVE_STRPTIME
208   return strptime(str, format, tm);
209 #else
210   return NULL;
211 #endif
212 }
213 
214 /// Obtains the current Unix timestamp.
215 ///
216 /// @return Seconds since epoch.
os_time(void)217 Timestamp os_time(void)
218   FUNC_ATTR_WARN_UNUSED_RESULT
219 {
220   return (Timestamp)time(NULL);
221 }
222