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 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 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 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 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 * 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 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