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 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * Copyright (c) 1980 Regents of the University of California. 28 * All rights reserved. The Berkeley software License Agreement 29 * specifies the terms and conditions for redistribution. 30 */ 31 32 #pragma ident "%Z%%M% %I% %E% SMI" 33 34 #ifndef KERNEL 35 #define KERNEL 36 #endif 37 38 #include <sys/param.h> 39 #include <sys/time.h> 40 #include <sys/conf.h> 41 #include <sys/sysmacros.h> 42 #include <sys/vfs.h> 43 #include <sys/debug.h> 44 #include <sys/errno.h> 45 #include <sys/cmn_err.h> 46 #include <sys/ddi.h> 47 #include <sys/sunddi.h> 48 #include <sys/byteorder.h> 49 #include <sys/fs/pc_fs.h> 50 #include <sys/fs/pc_label.h> 51 #include <sys/fs/pc_dir.h> 52 #include <sys/fs/pc_node.h> 53 54 /* 55 * Convert time between DOS formats: 56 * - years since 1980 57 * - months/days/hours/minutes/seconds, local TZ 58 * and the UNIX format (seconds since 01/01/1970, 00:00:00 UT). 59 * 60 * Timezones are adjusted for via mount option arg (secondswest), 61 * but daylight savings time corrections are not made. Calculated 62 * time may therefore end up being wrong by an hour, but this: 63 * a) will happen as well if media is interchanged between 64 * two DOS/Windows-based systems that use different 65 * timezone settings 66 * b) is the best option we have unless we decide to put 67 * a full ctime(3C) framework into the kernel, including 68 * all conversion tables - AND keeping them current ... 69 */ 70 71 int pc_tvtopct(timestruc_t *, struct pctime *); 72 void pc_pcttotv(struct pctime *, int64_t *); 73 74 /* 75 * Macros/Definitons required to convert between DOS-style and 76 * UNIX-style time recording. 77 * DOS year zero is 1980. 78 */ 79 static int daysinmonth[] = 80 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 81 82 #define YEAR_ZERO 1980 83 #define YZ_SECS (((8 * 365) + (2 * 366)) * 86400) 84 #define FAT_ENDOFTIME \ 85 LE_16(23 << HOURSHIFT | 59 << MINSHIFT | (59/2) << SECSHIFT) 86 #define FAT_ENDOFDATE \ 87 LE_16(127 << YEARSHIFT | 12 << MONSHIFT | 31 << DAYSHIFT) 88 #define leap_year(y) \ 89 (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) 90 91 static int 92 days_in_year(int y) 93 { 94 return (leap_year((y)) ? 366 : 365); 95 } 96 97 static int 98 days_in_month(int m, int y) 99 { 100 if (m == 2 && leap_year(y)) 101 return (29); 102 else 103 return (daysinmonth[m-1]); 104 } 105 106 struct pcfs_args pc_tz; /* this is set by pcfs_mount */ 107 108 /* 109 * Convert time from UNIX to DOS format. 110 * Return EOVERFLOW in case no valid DOS time representation 111 * exists for the given UNIX time. 112 */ 113 int 114 pc_tvtopct( 115 timestruc_t *tvp, /* UNIX time input */ 116 struct pctime *pctp) /* pctime output */ 117 { 118 uint_t year, month, day, hour, min, sec; 119 int64_t unixtime; 120 121 unixtime = (int64_t)tvp->tv_sec; 122 unixtime -= YZ_SECS; 123 unixtime -= pc_tz.secondswest; 124 if (unixtime <= 0) { 125 /* 126 * "before beginning of all time" for DOS ... 127 */ 128 return (EOVERFLOW); 129 } 130 for (year = YEAR_ZERO; unixtime >= days_in_year(year) * 86400; 131 year++) 132 unixtime -= 86400 * days_in_year(year); 133 134 if (year > 127 + YEAR_ZERO) { 135 /* 136 * "past end of all time" for DOS - can happen 137 * on a 64bit kernel via utimes() syscall ... 138 */ 139 return (EOVERFLOW); 140 } 141 142 for (month = 1; unixtime >= 86400 * days_in_month(month, year); 143 month++) 144 unixtime -= 86400 * days_in_month(month, year); 145 146 year -= YEAR_ZERO; 147 148 day = (int)(unixtime / 86400); 149 unixtime -= 86400 * day++; /* counting starts at 1 */ 150 151 hour = (int)(unixtime / 3600); 152 unixtime -= 3600 * hour; 153 154 min = (int)(unixtime / 60); 155 unixtime -= 60 * min; 156 157 sec = (int)unixtime; 158 159 PC_DPRINTF3(1, "ux2pc date: %d.%d.%d\n", day, month, YEAR_ZERO + year); 160 PC_DPRINTF3(1, "ux2pc time: %dh%dm%ds\n", hour, min, sec); 161 PC_DPRINTF1(1, "ux2pc unixtime: %lld\n", (long long)(unixtime)); 162 163 ASSERT(year >= 0 && year < 128); 164 ASSERT(month >= 1 && month <= 12); 165 ASSERT(day >= 1 && day <= days_in_month(month, year)); 166 ASSERT(hour < 24); 167 ASSERT(min < 60); 168 ASSERT(sec < 60); 169 170 pctp->pct_time = 171 LE_16(hour << HOURSHIFT | min << MINSHIFT | (sec / 2) << SECSHIFT); 172 pctp->pct_date = 173 LE_16(year << YEARSHIFT | month << MONSHIFT | day << DAYSHIFT); 174 175 return (0); 176 } 177 178 /* 179 * Convert time from DOS to UNIX time format. 180 * Since FAT timestamps cannot be expressed in 32bit time_t, 181 * the calculation is performed using 64bit values. It's up to 182 * the caller to decide what to do for out-of-UNIX-range values. 183 */ 184 void 185 pc_pcttotv( 186 struct pctime *pctp, /* DOS time input */ 187 int64_t *unixtime) /* caller converts to time_t */ 188 { 189 uint_t year, month, day, hour, min, sec; 190 191 sec = 2 * ((LE_16(pctp->pct_time) >> SECSHIFT) & SECMASK); 192 min = (LE_16(pctp->pct_time) >> MINSHIFT) & MINMASK; 193 hour = (LE_16(pctp->pct_time) >> HOURSHIFT) & HOURMASK; 194 day = (LE_16(pctp->pct_date) >> DAYSHIFT) & DAYMASK; 195 month = (LE_16(pctp->pct_date) >> MONSHIFT) & MONMASK; 196 year = (LE_16(pctp->pct_date) >> YEARSHIFT) & YEARMASK; 197 year += YEAR_ZERO; 198 199 /* 200 * Basic sanity checks. The FAT timestamp bitfields allow for 201 * impossible dates/times - return the "FAT epoch" for these. 202 */ 203 if (pctp->pct_date == 0) { 204 year = YEAR_ZERO; 205 month = 1; 206 day = 1; 207 } 208 if (month > 12 || month < 1 || 209 day < 1 || day > days_in_month(month, year) || 210 hour > 23 || min > 59 || sec > 59) { 211 cmn_err(CE_NOTE, "impossible FAT timestamp, " 212 "d/m/y %d/%d/%d, h:m:s %d:%d:%d", 213 day, month, year, hour, min, sec); 214 *unixtime = YZ_SECS + pc_tz.secondswest; 215 return; 216 } 217 218 PC_DPRINTF3(1, "pc2ux date: %d.%d.%d\n", day, month, year); 219 PC_DPRINTF3(1, "pc2ux time: %dh%dm%ds\n", hour, min, sec); 220 221 *unixtime = (int64_t)sec; 222 *unixtime += 60 * (int64_t)min; 223 *unixtime += 3600 * (int64_t)hour; 224 *unixtime += 86400 * (int64_t)(day -1); 225 while (month > 1) { 226 month--; 227 *unixtime += 86400 * (int64_t)days_in_month(month, year); 228 } 229 while (year > YEAR_ZERO) { 230 year--; 231 *unixtime += 86400 * (int64_t)days_in_year(year); 232 } 233 /* 234 * For FAT, the beginning of all time is 01/01/1980, 235 * and years are counted relative to that. 236 * We adjust this base value by the timezone offset 237 * that is passed in to pcfs at mount time. 238 */ 239 *unixtime += YZ_SECS; 240 *unixtime += pc_tz.secondswest; 241 242 /* 243 * FAT epoch is past UNIX epoch - negative UNIX times 244 * cannot result from the conversion. 245 */ 246 ASSERT(*unixtime > 0); 247 PC_DPRINTF1(1, "pc2ux unixtime: %lld\n", (long long)(*unixtime)); 248 } 249 250 /* 251 * Determine whether a character is valid for a long file name. 252 * It is easier to determine by filtering out invalid characters. 253 * Following are invalid characters in a long filename. 254 * / \ : * ? < > | " 255 */ 256 int 257 pc_valid_lfn_char(char c) 258 { 259 const char *cp; 260 int n; 261 262 static const char invaltab[] = { 263 "/\\:*?<>|\"" 264 }; 265 266 cp = invaltab; 267 n = sizeof (invaltab) - 1; 268 while (n--) { 269 if (c == *cp++) 270 return (0); 271 } 272 return (1); 273 } 274 275 int 276 pc_valid_long_fn(char *namep, int utf8) 277 { 278 char *tmp; 279 280 if (utf8) { 281 /* UTF-8 */ 282 for (tmp = namep; *tmp != '\0'; tmp++) 283 if (!pc_valid_lfn_char(*tmp)) 284 return (0); 285 if ((tmp - namep) > PCMAXNAMLEN) 286 return (0); 287 } else { 288 /* UTF-16 */ 289 for (tmp = namep; (*tmp != '\0') && (*(tmp+1) != '\0'); 290 tmp += 2) { 291 if ((*(tmp+1) == '\0') && !pc_valid_lfn_char(*tmp)) 292 return (0); 293 } 294 if ((tmp - namep) > (PCMAXNAMLEN * sizeof (uint16_t))) 295 return (0); 296 } 297 return (1); 298 } 299 300 int 301 pc_fname_ext_to_name(char *namep, char *fname, char *ext, int foldcase) 302 { 303 int i; 304 char *tp = namep; 305 char c; 306 307 i = PCFNAMESIZE; 308 while (i-- && ((c = *fname) != ' ')) { 309 if (!(c == '.' || pc_validchar(c))) { 310 return (-1); 311 } 312 if (foldcase) 313 *tp++ = tolower(c); 314 else 315 *tp++ = c; 316 fname++; 317 } 318 if (*ext != ' ') { 319 *tp++ = '.'; 320 i = PCFEXTSIZE; 321 while (i-- && ((c = *ext) != ' ')) { 322 if (!pc_validchar(c)) { 323 return (-1); 324 } 325 if (foldcase) 326 *tp++ = tolower(c); 327 else 328 *tp++ = c; 329 ext++; 330 } 331 } 332 *tp = '\0'; 333 return (0); 334 } 335