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