Home | History | Annotate | Download | only in pcfs
      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