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