1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2018 Nexenta Systems, Inc. All rights reserved.
25 */
26
27 /*
28 * A few excerpts from smb_kutil.c
29 */
30
31 #include <sys/param.h>
32 #include <sys/types.h>
33 #include <sys/tzfile.h>
34 #include <sys/atomic.h>
35 #include <sys/debug.h>
36 #include <sys/time.h>
37 #include <smbsrv/smb_kproto.h>
38
39 time_t tzh_leapcnt = 0;
40
41 struct tm
42 *smb_gmtime_r(time_t *clock, struct tm *result);
43
44 time_t
45 smb_timegm(struct tm *tm);
46
47 struct tm {
48 int tm_sec;
49 int tm_min;
50 int tm_hour;
51 int tm_mday;
52 int tm_mon;
53 int tm_year;
54 int tm_wday;
55 int tm_yday;
56 int tm_isdst;
57 };
58
59 static const int days_in_month[] = {
60 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
61 };
62
63 uint64_t
smb_time_unix_to_nt(timestruc_t * unix_time)64 smb_time_unix_to_nt(timestruc_t *unix_time)
65 {
66 uint64_t nt_time;
67
68 if ((unix_time->tv_sec == 0) && (unix_time->tv_nsec == 0))
69 return (0);
70
71 nt_time = unix_time->tv_sec;
72 nt_time *= 10000000; /* seconds to 100ns */
73 nt_time += unix_time->tv_nsec / 100;
74 return (nt_time + NT_TIME_BIAS);
75 }
76
77 void
smb_time_nt_to_unix(uint64_t nt_time,timestruc_t * unix_time)78 smb_time_nt_to_unix(uint64_t nt_time, timestruc_t *unix_time)
79 {
80 uint32_t seconds;
81
82 ASSERT(unix_time);
83
84 if ((nt_time == 0) || (nt_time == -1)) {
85 unix_time->tv_sec = 0;
86 unix_time->tv_nsec = 0;
87 return;
88 }
89
90 /*
91 * Can't represent times less than or equal NT_TIME_BIAS,
92 * so convert them to the oldest date we can store.
93 * Note that time zero is "special" being converted
94 * both directions as 0:0 (unix-to-nt, nt-to-unix).
95 */
96 if (nt_time <= NT_TIME_BIAS) {
97 unix_time->tv_sec = 0;
98 unix_time->tv_nsec = 100;
99 return;
100 }
101
102 nt_time -= NT_TIME_BIAS;
103 seconds = nt_time / 10000000;
104 unix_time->tv_sec = seconds;
105 unix_time->tv_nsec = (nt_time % 10000000) * 100;
106 }
107
108
109 /*
110 * smb_time_dos_to_unix
111 *
112 * Convert SMB_DATE & SMB_TIME values to a unix timestamp.
113 *
114 * A date/time field of 0 means that that server file system
115 * assigned value need not be changed. The behaviour when the
116 * date/time field is set to -1 is not documented but is
117 * generally treated like 0.
118 * If date or time is 0 or -1 the unix time is returned as 0
119 * so that the caller can identify and handle this special case.
120 */
121 int32_t
smb_time_dos_to_unix(int16_t date,int16_t time)122 smb_time_dos_to_unix(int16_t date, int16_t time)
123 {
124 struct tm atm;
125
126 if (((date == 0) || (time == 0)) ||
127 ((date == -1) || (time == -1))) {
128 return (0);
129 }
130
131 atm.tm_year = ((date >> 9) & 0x3F) + 80;
132 atm.tm_mon = ((date >> 5) & 0x0F) - 1;
133 atm.tm_mday = ((date >> 0) & 0x1F);
134 atm.tm_hour = ((time >> 11) & 0x1F);
135 atm.tm_min = ((time >> 5) & 0x3F);
136 atm.tm_sec = ((time >> 0) & 0x1F) << 1;
137
138 return (smb_timegm(&atm));
139 }
140
141 void
smb_time_unix_to_dos(int32_t ux_time,int16_t * date_p,int16_t * time_p)142 smb_time_unix_to_dos(int32_t ux_time, int16_t *date_p, int16_t *time_p)
143 {
144 struct tm atm;
145 int i;
146 time_t tmp_time;
147
148 if (ux_time == 0) {
149 *date_p = 0;
150 *time_p = 0;
151 return;
152 }
153
154 tmp_time = (time_t)ux_time;
155 (void) smb_gmtime_r(&tmp_time, &atm);
156
157 if (date_p) {
158 i = 0;
159 i += atm.tm_year - 80;
160 i <<= 4;
161 i += atm.tm_mon + 1;
162 i <<= 5;
163 i += atm.tm_mday;
164
165 *date_p = (short)i;
166 }
167 if (time_p) {
168 i = 0;
169 i += atm.tm_hour;
170 i <<= 6;
171 i += atm.tm_min;
172 i <<= 5;
173 i += atm.tm_sec >> 1;
174
175 *time_p = (short)i;
176 }
177 }
178
179 /*
180 * smb_gmtime_r
181 *
182 * Thread-safe version of smb_gmtime. Returns a null pointer if either
183 * input parameter is a null pointer. Otherwise returns a pointer
184 * to result.
185 *
186 * Day of the week calculation: the Epoch was a thursday.
187 *
188 * There are no timezone corrections so tm_isdst and tm_gmtoff are
189 * always zero, and the zone is always WET.
190 */
191 struct tm *
smb_gmtime_r(time_t * clock,struct tm * result)192 smb_gmtime_r(time_t *clock, struct tm *result)
193 {
194 time_t tsec;
195 int year;
196 int month;
197 int sec_per_month;
198
199 if (clock == 0 || result == 0)
200 return (0);
201
202 bzero(result, sizeof (struct tm));
203 tsec = *clock;
204 tsec -= tzh_leapcnt;
205
206 result->tm_wday = tsec / SECSPERDAY;
207 result->tm_wday = (result->tm_wday + TM_THURSDAY) % DAYSPERWEEK;
208
209 year = EPOCH_YEAR;
210 while (tsec >= (isleap(year) ? (SECSPERDAY * DAYSPERLYEAR) :
211 (SECSPERDAY * DAYSPERNYEAR))) {
212 if (isleap(year))
213 tsec -= SECSPERDAY * DAYSPERLYEAR;
214 else
215 tsec -= SECSPERDAY * DAYSPERNYEAR;
216
217 ++year;
218 }
219
220 result->tm_year = year - TM_YEAR_BASE;
221 result->tm_yday = tsec / SECSPERDAY;
222
223 for (month = TM_JANUARY; month <= TM_DECEMBER; ++month) {
224 sec_per_month = days_in_month[month] * SECSPERDAY;
225
226 if (month == TM_FEBRUARY && isleap(year))
227 sec_per_month += SECSPERDAY;
228
229 if (tsec < sec_per_month)
230 break;
231
232 tsec -= sec_per_month;
233 }
234
235 result->tm_mon = month;
236 result->tm_mday = (tsec / SECSPERDAY) + 1;
237 tsec %= SECSPERDAY;
238 result->tm_sec = tsec % 60;
239 tsec /= 60;
240 result->tm_min = tsec % 60;
241 tsec /= 60;
242 result->tm_hour = (int)tsec;
243
244 return (result);
245 }
246
247
248 /*
249 * smb_timegm
250 *
251 * Converts the broken-down time in tm to a time value, i.e. the number
252 * of seconds since the Epoch (00:00:00 UTC, January 1, 1970). This is
253 * not a POSIX or ANSI function. Per the man page, the input values of
254 * tm_wday and tm_yday are ignored and, as the input data is assumed to
255 * represent GMT, we force tm_isdst and tm_gmtoff to 0.
256 *
257 * Before returning the clock time, we use smb_gmtime_r to set up tm_wday
258 * and tm_yday, and bring the other fields within normal range. I don't
259 * think this is really how it should be done but it's convenient for
260 * now.
261 */
262 time_t
smb_timegm(struct tm * tm)263 smb_timegm(struct tm *tm)
264 {
265 time_t tsec;
266 int dd;
267 int mm;
268 int yy;
269 int year;
270
271 if (tm == 0)
272 return (-1);
273
274 year = tm->tm_year + TM_YEAR_BASE;
275 tsec = tzh_leapcnt;
276
277 for (yy = EPOCH_YEAR; yy < year; ++yy) {
278 if (isleap(yy))
279 tsec += SECSPERDAY * DAYSPERLYEAR;
280 else
281 tsec += SECSPERDAY * DAYSPERNYEAR;
282 }
283
284 for (mm = TM_JANUARY; mm < tm->tm_mon; ++mm) {
285 dd = days_in_month[mm] * SECSPERDAY;
286
287 if (mm == TM_FEBRUARY && isleap(year))
288 dd += SECSPERDAY;
289
290 tsec += dd;
291 }
292
293 tsec += (tm->tm_mday - 1) * SECSPERDAY;
294 tsec += tm->tm_sec;
295 tsec += tm->tm_min * SECSPERMIN;
296 tsec += tm->tm_hour * SECSPERHOUR;
297
298 tm->tm_isdst = 0;
299 (void) smb_gmtime_r(&tsec, tm);
300 return (tsec);
301 }
302