1 /* 2 * Copyright (c) 2000-2001, Boris Popov 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Boris Popov. 16 * 4. Neither the name of the author nor the names of any co-contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $Id: smbfs_subr.c,v 1.18 2005/02/02 00:22:23 lindak Exp $ 33 */ 34 35 /* 36 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 37 * Use is subject to license terms. 38 */ 39 40 /* 41 * Time conversion functions (to/from DOS, NT times) 42 * From BSD/Darwin smbfs_subr.c 43 */ 44 45 #include <sys/param.h> 46 #include <sys/systm.h> 47 #include <sys/time.h> 48 #include <sys/vnode.h> 49 #include <sys/sunddi.h> 50 51 #include <netsmb/smb_osdep.h> 52 53 #include <netsmb/smb.h> 54 #include <netsmb/smb_conn.h> 55 #include <netsmb/smb_subr.h> 56 57 /* 58 * Time & date conversion routines taken from msdosfs. Although leap 59 * year calculation is bogus, it's sufficient before 2100 :) 60 */ 61 /* 62 * This is the format of the contents of the deTime field in the direntry 63 * structure. 64 * We don't use bitfields because we don't know how compilers for 65 * arbitrary machines will lay them out. 66 */ 67 #define DT_2SECONDS_MASK 0x1F /* seconds divided by 2 */ 68 #define DT_2SECONDS_SHIFT 0 69 #define DT_MINUTES_MASK 0x7E0 /* minutes */ 70 #define DT_MINUTES_SHIFT 5 71 #define DT_HOURS_MASK 0xF800 /* hours */ 72 #define DT_HOURS_SHIFT 11 73 74 /* 75 * This is the format of the contents of the deDate field in the direntry 76 * structure. 77 */ 78 #define DD_DAY_MASK 0x1F /* day of month */ 79 #define DD_DAY_SHIFT 0 80 #define DD_MONTH_MASK 0x1E0 /* month */ 81 #define DD_MONTH_SHIFT 5 82 #define DD_YEAR_MASK 0xFE00 /* year - 1980 */ 83 #define DD_YEAR_SHIFT 9 84 /* 85 * Total number of days that have passed for each month in a regular year. 86 */ 87 static ushort_t regyear[] = { 88 31, 59, 90, 120, 151, 181, 89 212, 243, 273, 304, 334, 365 90 }; 91 92 /* 93 * Total number of days that have passed for each month in a leap year. 94 */ 95 static ushort_t leapyear[] = { 96 31, 60, 91, 121, 152, 182, 97 213, 244, 274, 305, 335, 366 98 }; 99 100 /* 101 * Variables used to remember parts of the last time conversion. Maybe we 102 * can avoid a full conversion. 103 */ 104 static ulong_t lasttime; 105 static ulong_t lastday; 106 static ushort_t lastddate; 107 static ushort_t lastdtime; 108 109 /* Lock for the lastxxx variables */ 110 static kmutex_t lastdt_lock; 111 112 /* 113 * Number of seconds between 1970 and 1601 year 114 * (134774 days) 115 */ 116 const uint64_t DIFF1970TO1601 = 11644473600ULL; 117 const uint32_t TEN_MIL = 10000000UL; 118 119 /* 120 * Convert NT time (tenths of microseconds since 1601) 121 * to Unix seconds+nanoseconds since 1970. Any time 122 * earlier than 1970 is converted to Unix time zero. 123 * Both are GMT-based (no time zone adjustments). 124 */ 125 void 126 smb_time_NT2local(uint64_t nt_time, struct timespec *tsp) 127 { 128 uint64_t nt_sec; /* seconds */ 129 uint64_t nt_tus; /* tenths of uSec. */ 130 131 /* Optimize time zero. */ 132 if (nt_time == 0) { 133 tsp->tv_sec = 0; 134 tsp->tv_nsec = 0; 135 return; 136 } 137 138 nt_sec = nt_time / TEN_MIL; 139 nt_tus = nt_time % TEN_MIL; 140 141 if (nt_sec <= DIFF1970TO1601) { 142 tsp->tv_sec = 0; 143 tsp->tv_nsec = 0; 144 return; 145 } 146 tsp->tv_sec = nt_sec - DIFF1970TO1601; 147 tsp->tv_nsec = nt_tus * 100; 148 } 149 150 /* 151 * Convert Unix time (seconds+nanoseconds since 1970) 152 * to NT time (tenths of microseconds since 1601). 153 * Exception: Convert time zero (really any time in 154 * the first second of 1970) to NT time zero. 155 * Both are GMT-based (no time zone adjustments). 156 */ 157 void 158 smb_time_local2NT(struct timespec *tsp, uint64_t *nt_time) 159 { 160 uint64_t nt_sec; /* seconds */ 161 uint64_t nt_tus; /* tenths of uSec. */ 162 163 if (tsp->tv_sec == 0) { 164 *nt_time = 0; 165 return; 166 } 167 168 nt_sec = tsp->tv_sec + DIFF1970TO1601; 169 nt_tus = tsp->tv_nsec / 100; 170 171 *nt_time = (uint64_t)nt_sec * TEN_MIL + nt_tus; 172 } 173 174 /* 175 * Time zone conversion stuff, only used in old dialects. 176 * Don't adjust time zero for either conversion. 177 */ 178 void 179 smb_time_local2server(struct timespec *tsp, int tzoff, long *seconds) 180 { 181 if (tsp->tv_sec <= (tzoff * 60)) 182 *seconds = 0; 183 else 184 *seconds = tsp->tv_sec - (tzoff * 60); 185 } 186 187 void 188 smb_time_server2local(ulong_t seconds, int tzoff, struct timespec *tsp) 189 { 190 if (seconds == 0) 191 tsp->tv_sec = 0; 192 else 193 tsp->tv_sec = seconds + tzoff * 60; 194 tsp->tv_nsec = 0; 195 } 196 197 /* 198 * Time conversions to/from DOS format, for old dialects. 199 */ 200 201 void 202 smb_time_unix2dos(struct timespec *tsp, int tzoff, u_int16_t *ddp, 203 u_int16_t *dtp, u_int8_t *dhp) 204 { 205 long t; 206 ulong_t days, year, month, inc; 207 ushort_t *months; 208 209 mutex_enter(&lastdt_lock); 210 211 /* 212 * If the time from the last conversion is the same as now, then 213 * skip the computations and use the saved result. 214 */ 215 smb_time_local2server(tsp, tzoff, &t); 216 t &= ~1; 217 if (lasttime != t) { 218 lasttime = t; 219 if (t < 0) { 220 /* 221 * This is before 1970, so it's before 1980, 222 * and can't be represented as a DOS time. 223 * Just represent it as the DOS epoch. 224 */ 225 lastdtime = 0; 226 lastddate = (1 << DD_DAY_SHIFT) 227 + (1 << DD_MONTH_SHIFT) 228 + ((1980 - 1980) << DD_YEAR_SHIFT); 229 } else { 230 lastdtime = (((t / 2) % 30) << DT_2SECONDS_SHIFT) 231 + (((t / 60) % 60) << DT_MINUTES_SHIFT) 232 + (((t / 3600) % 24) << DT_HOURS_SHIFT); 233 234 /* 235 * If the number of days since 1970 is the same as 236 * the last time we did the computation then skip 237 * all this leap year and month stuff. 238 */ 239 days = t / (24 * 60 * 60); 240 if (days != lastday) { 241 lastday = days; 242 for (year = 1970; ; year++) { 243 /* 244 * XXX - works in 2000, but won't 245 * work in 2100. 246 */ 247 inc = year & 0x03 ? 365 : 366; 248 if (days < inc) 249 break; 250 days -= inc; 251 } 252 /* 253 * XXX - works in 2000, but won't work in 2100. 254 */ 255 months = year & 0x03 ? regyear : leapyear; 256 for (month = 0; days >= months[month]; month++) 257 ; 258 if (month > 0) 259 days -= months[month - 1]; 260 lastddate = ((days + 1) << DD_DAY_SHIFT) 261 + ((month + 1) << DD_MONTH_SHIFT); 262 /* 263 * Remember DOS's idea of time is relative 264 * to 1980, but UN*X's is relative to 1970. 265 * If somehow we get a time before 1980 then 266 * don't give totally crazy results. 267 */ 268 if (year > 1980) 269 lastddate += (year - 1980) << 270 DD_YEAR_SHIFT; 271 } 272 } 273 } 274 if (dhp) 275 *dhp = (tsp->tv_sec & 1) * 100 + tsp->tv_nsec / 10000000; 276 277 *ddp = lastddate; 278 *dtp = lastdtime; 279 280 mutex_exit(&lastdt_lock); 281 } 282 283 /* 284 * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that 285 * interval there were 8 regular years and 2 leap years. 286 */ 287 #define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) 288 289 static ushort_t lastdosdate; 290 static ulong_t lastseconds; 291 292 void 293 smb_dos2unixtime(uint_t dd, uint_t dt, uint_t dh, int tzoff, 294 struct timespec *tsp) 295 { 296 ulong_t seconds; 297 ulong_t month; 298 ulong_t year; 299 ulong_t days; 300 ushort_t *months; 301 302 if (dd == 0) { 303 tsp->tv_sec = 0; 304 tsp->tv_nsec = 0; 305 return; 306 } 307 seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) 308 + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 309 + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600 310 + dh / 100; 311 312 /* 313 * If the year, month, and day from the last conversion are the 314 * same then use the saved value. 315 */ 316 mutex_enter(&lastdt_lock); 317 if (lastdosdate != dd) { 318 lastdosdate = (ushort_t)dd; 319 days = 0; 320 year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; 321 days = year * 365; 322 days += year / 4 + 1; /* add in leap days */ 323 /* 324 * XXX - works in 2000, but won't work in 2100. 325 */ 326 if ((year & 0x03) == 0) 327 days--; /* if year is a leap year */ 328 months = year & 0x03 ? regyear : leapyear; 329 month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; 330 if (month < 1 || month > 12) { 331 month = 1; 332 } 333 if (month > 1) 334 days += months[month - 2]; 335 days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; 336 lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; 337 } 338 smb_time_server2local(seconds + lastseconds, tzoff, tsp); 339 tsp->tv_nsec = (dh % 100) * 10000000; 340 mutex_exit(&lastdt_lock); 341 } 342 343 void 344 smb_time_init(void) 345 { 346 mutex_init(&lastdt_lock, NULL, MUTEX_DEFAULT, NULL); 347 } 348 349 void 350 smb_time_fini(void) 351 { 352 mutex_destroy(&lastdt_lock); 353 } 354