aboutsummaryrefslogtreecommitdiff
path: root/src/lib/wallclock/time_to_tm.c
blob: f7cb21827bb635a7e39c9e5976743302eee6bbe3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/* Copyright (c) 2003-2004, Roger Dingledine
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2019, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file time_to_tm.c
 * \brief Convert to struct tm, portably.
 **/

#include "orconfig.h"
#include "lib/cc/torint.h"
#include "lib/cc/compat_compiler.h"
#include "lib/wallclock/time_to_tm.h"
#include "lib/string/printf.h"
#include "lib/err/torerr.h"

#include <errno.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#if !defined(_WIN32)
/** Defined iff we need to add locks when defining fake versions of reentrant
 * versions of time-related functions. */
#define TIME_FNS_NEED_LOCKS
#endif

/** Helper: Deal with confused or out-of-bounds values from localtime_r and
 * friends.  (On some platforms, they can give out-of-bounds values or can
 * return NULL.)  If <b>islocal</b>, this is a localtime result; otherwise
 * it's from gmtime.  The function returns <b>r</b>, when given <b>timep</b>
 * as its input. If we need to store new results, store them in
 * <b>resultbuf</b>. */
static struct tm *
correct_tm(int islocal, const time_t *timep, struct tm *resultbuf,
           struct tm *r, char **err_out)
{
  const char *outcome;

  if (PREDICT_LIKELY(r)) {
    /* We can't strftime dates after 9999 CE, and we want to avoid dates
     * before 1 CE (avoiding the year 0 issue and negative years). */
    if (r->tm_year > 8099) {
      r->tm_year = 8099;
      r->tm_mon = 11;
      r->tm_mday = 31;
      r->tm_yday = 364;
      r->tm_wday = 6;
      r->tm_hour = 23;
      r->tm_min = 59;
      r->tm_sec = 59;
    } else if (r->tm_year < (1-1900)) {
      r->tm_year = (1-1900);
      r->tm_mon = 0;
      r->tm_mday = 1;
      r->tm_yday = 0;
      r->tm_wday = 0;
      r->tm_hour = 0;
      r->tm_min = 0;
      r->tm_sec = 0;
    }
    return r;
  }

  /* If we get here, gmtime or localtime returned NULL. It might have done
   * this because of overrun or underrun, or it might have done it because of
   * some other weird issue. */
  if (timep) {
    if (*timep < 0) {
      r = resultbuf;
      r->tm_year = 70; /* 1970 CE */
      r->tm_mon = 0;
      r->tm_mday = 1;
      r->tm_yday = 0;
      r->tm_wday = 0;
      r->tm_hour = 0;
      r->tm_min = 0 ;
      r->tm_sec = 0;
      outcome = "Rounding up to 1970";
      goto done;
    } else if (*timep >= INT32_MAX) {
      /* Rounding down to INT32_MAX isn't so great, but keep in mind that we
       * only do it if gmtime/localtime tells us NULL. */
      r = resultbuf;
      r->tm_year = 137; /* 2037 CE */
      r->tm_mon = 11;
      r->tm_mday = 31;
      r->tm_yday = 364;
      r->tm_wday = 6;
      r->tm_hour = 23;
      r->tm_min = 59;
      r->tm_sec = 59;
      outcome = "Rounding down to 2037";
      goto done;
    }
  }

  /* If we get here, then gmtime/localtime failed without getting an extreme
   * value for *timep */
  /* LCOV_EXCL_START */
  r = resultbuf;
  memset(resultbuf, 0, sizeof(struct tm));
  outcome="can't recover";
  /* LCOV_EXCL_STOP */
 done:
  if (err_out) {
    tor_asprintf(err_out, "%s(%"PRId64") failed with error %s: %s",
                 islocal?"localtime":"gmtime",
                 timep?((int64_t)*timep):0,
                 strerror(errno),
                 outcome);
  }
  return r;
}

/** @{ */
/** As localtime_r, but defined for platforms that don't have it:
 *
 * Convert *<b>timep</b> to a struct tm in local time, and store the value in
 * *<b>result</b>.  Return the result on success, or NULL on failure.
 */
#ifdef HAVE_LOCALTIME_R
struct tm *
tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  r = localtime_r(timep, result);
  return correct_tm(1, timep, result, r, err_out);
}
#elif defined(TIME_FNS_NEED_LOCKS)
struct tm *
tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  static tor_mutex_t *m=NULL;
  if (!m) { m=tor_mutex_new(); }
  raw_assert(result);
  tor_mutex_acquire(m);
  r = localtime(timep);
  if (r)
    memcpy(result, r, sizeof(struct tm));
  tor_mutex_release(m);
  return correct_tm(1, timep, result, r, err_out);
}
#else
struct tm *
tor_localtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  raw_assert(result);
  r = localtime(timep);
  if (r)
    memcpy(result, r, sizeof(struct tm));
  return correct_tm(1, timep, result, r, err_out);
}
#endif /* defined(HAVE_LOCALTIME_R) || ... */
/** @} */

/** @{ */
/** As gmtime_r, but defined for platforms that don't have it:
 *
 * Convert *<b>timep</b> to a struct tm in UTC, and store the value in
 * *<b>result</b>.  Return the result on success, or NULL on failure.
 */
#ifdef HAVE_GMTIME_R
struct tm *
tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  r = gmtime_r(timep, result);
  return correct_tm(0, timep, result, r, err_out);
}
#elif defined(TIME_FNS_NEED_LOCKS)
struct tm *
tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  static tor_mutex_t *m=NULL;
  if (!m) { m=tor_mutex_new(); }
  raw_assert(result);
  tor_mutex_acquire(m);
  r = gmtime(timep);
  if (r)
    memcpy(result, r, sizeof(struct tm));
  tor_mutex_release(m);
  return correct_tm(0, timep, result, r, err_out);
}
#else
struct tm *
tor_gmtime_r_msg(const time_t *timep, struct tm *result, char **err_out)
{
  struct tm *r;
  raw_assert(result);
  r = gmtime(timep);
  if (r)
    memcpy(result, r, sizeof(struct tm));
  return correct_tm(0, timep, result, r, err_out);
}
#endif /* defined(HAVE_GMTIME_R) || ... */