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 }