msdosfs_conv.c revision 8876
18876Srgrimes/*	$Id: msdosfs_conv.c,v 1.4 1995/03/29 18:48:06 ache Exp $ */
22893Sdfr/*	$NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $	*/
32893Sdfr
42893Sdfr/*
52893Sdfr * Written by Paul Popelka (paulp@uts.amdahl.com)
68876Srgrimes *
72893Sdfr * You can do anything you want with this software, just don't say you wrote
82893Sdfr * it, and don't remove this notice.
98876Srgrimes *
102893Sdfr * This software is provided "as is".
118876Srgrimes *
122893Sdfr * The author supplies this software to be publicly redistributed on the
132893Sdfr * understanding that the author is not responsible for the correct
142893Sdfr * functioning of this software in any circumstances and is not liable for
152893Sdfr * any damages caused by this software.
168876Srgrimes *
172893Sdfr * October 1992
182893Sdfr */
192893Sdfr
202893Sdfr/*
212893Sdfr * System include files.
222893Sdfr */
232893Sdfr#include <sys/param.h>
242893Sdfr#include <sys/time.h>
252893Sdfr#include <sys/kernel.h>		/* defines tz */
263152Sphk#include <sys/systm.h>		/* defines tz */
277465Sache#include <machine/clock.h>
282893Sdfr
292893Sdfr/*
302893Sdfr * MSDOSFS include files.
312893Sdfr */
322893Sdfr#include <msdosfs/direntry.h>
332893Sdfr
342893Sdfr/*
357465Sache * Total number of days that have passed for each month in a regular year.
362893Sdfr */
372893Sdfru_short regyear[] = {
387465Sache	31, 59, 90, 120, 151, 181,
397465Sache	212, 243, 273, 304, 334, 365
402893Sdfr};
412893Sdfr
422893Sdfr/*
437465Sache * Total number of days that have passed for each month in a leap year.
442893Sdfr */
452893Sdfru_short leapyear[] = {
467465Sache	31, 60, 91, 121, 152, 182,
477465Sache	213, 244, 274, 305, 335, 366
482893Sdfr};
492893Sdfr
502893Sdfr/*
512893Sdfr * Variables used to remember parts of the last time conversion.  Maybe we
522893Sdfr * can avoid a full conversion.
532893Sdfr */
542893Sdfru_long lasttime;
552893Sdfru_long lastday;
562893Sdfru_short lastddate;
572893Sdfru_short lastdtime;
582893Sdfr
592893Sdfr/*
602893Sdfr * Convert the unix version of time to dos's idea of time to be used in
612893Sdfr * file timestamps. The passed in unix time is assumed to be in GMT.
622893Sdfr */
632893Sdfrvoid
642893Sdfrunix2dostime(tsp, ddp, dtp)
652893Sdfr	struct timespec *tsp;
662893Sdfr	u_short *ddp;
672893Sdfr	u_short *dtp;
682893Sdfr{
692893Sdfr	u_long t;
702893Sdfr	u_long days;
712893Sdfr	u_long inc;
722893Sdfr	u_long year;
732893Sdfr	u_long month;
742893Sdfr	u_short *months;
752893Sdfr
762893Sdfr	/*
772893Sdfr	 * If the time from the last conversion is the same as now, then
782893Sdfr	 * skip the computations and use the saved result.
792893Sdfr	 */
807465Sache	t = tsp->ts_sec - (tz.tz_minuteswest * 60) - adjkerntz;
812893Sdfr	     /* +- daylight savings time correction */ ;
822893Sdfr	if (lasttime != t) {
832893Sdfr		lasttime = t;
842893Sdfr		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
852893Sdfr		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
862893Sdfr		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
872893Sdfr
882893Sdfr		/*
892893Sdfr		 * If the number of days since 1970 is the same as the last
902893Sdfr		 * time we did the computation then skip all this leap year
912893Sdfr		 * and month stuff.
922893Sdfr		 */
932893Sdfr		days = t / (24 * 60 * 60);
942893Sdfr		if (days != lastday) {
952893Sdfr			lastday = days;
962893Sdfr			for (year = 1970;; year++) {
972893Sdfr				inc = year & 0x03 ? 365 : 366;
982893Sdfr				if (days < inc)
992893Sdfr					break;
1002893Sdfr				days -= inc;
1012893Sdfr			}
1022893Sdfr			months = year & 0x03 ? regyear : leapyear;
1037465Sache			for (month = 0; days > months[month]; month++)
1047465Sache				;
1057465Sache			if (month > 0)
1067465Sache				days -= months[month - 1];
1072893Sdfr			lastddate = ((days + 1) << DD_DAY_SHIFT)
1082893Sdfr			    + ((month + 1) << DD_MONTH_SHIFT);
1092893Sdfr			/*
1102893Sdfr			 * Remember dos's idea of time is relative to 1980.
1112893Sdfr			 * unix's is relative to 1970.  If somehow we get a
1122893Sdfr			 * time before 1980 then don't give totally crazy
1132893Sdfr			 * results.
1142893Sdfr			 */
1152893Sdfr			if (year > 1980)
1162893Sdfr				lastddate += (year - 1980) << DD_YEAR_SHIFT;
1172893Sdfr		}
1182893Sdfr	}
1192893Sdfr	*dtp = lastdtime;
1202893Sdfr	*ddp = lastddate;
1212893Sdfr}
1222893Sdfr
1232893Sdfr/*
1242893Sdfr * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
1252893Sdfr * interval there were 8 regular years and 2 leap years.
1262893Sdfr */
1272893Sdfr#define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))
1282893Sdfr
1292893Sdfru_short lastdosdate;
1302893Sdfru_long lastseconds;
1312893Sdfr
1322893Sdfr/*
1332893Sdfr * Convert from dos' idea of time to unix'. This will probably only be
1342893Sdfr * called from the stat(), and fstat() system calls and so probably need
1352893Sdfr * not be too efficient.
1362893Sdfr */
1372893Sdfrvoid
1382893Sdfrdos2unixtime(dd, dt, tsp)
1392893Sdfr	u_short dd;
1402893Sdfr	u_short dt;
1412893Sdfr	struct timespec *tsp;
1422893Sdfr{
1432893Sdfr	u_long seconds;
1442893Sdfr	u_long m, month;
1452893Sdfr	u_long y, year;
1462893Sdfr	u_long days;
1472893Sdfr	u_short *months;
1482893Sdfr
1495083Sbde	seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
1502893Sdfr	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
1512893Sdfr	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
1522893Sdfr	/*
1532893Sdfr	 * If the year, month, and day from the last conversion are the
1542893Sdfr	 * same then use the saved value.
1552893Sdfr	 */
1562893Sdfr	if (lastdosdate != dd) {
1572893Sdfr		lastdosdate = dd;
1582893Sdfr		days = 0;
1592893Sdfr		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
1607465Sache		days = year * 365;
1617465Sache		days += year / 4 + 1;	/* add in leap days */
1627465Sache		if ((year & 0x03) == 0)
1637465Sache			days--;		/* if year is a leap year */
1642893Sdfr		months = year & 0x03 ? regyear : leapyear;
1652893Sdfr		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
1667465Sache		if (month < 1 || month > 12) {
1673152Sphk			printf(
1683152Sphk			    "dos2unixtime(): month value out of range (%ld)\n",
1693152Sphk			    month);
1702893Sdfr			month = 1;
1712893Sdfr		}
1727465Sache		if (month > 1)
1737465Sache			days += months[month - 2];
1742893Sdfr		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
1752893Sdfr		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
1762893Sdfr	}
1772893Sdfr	tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
1787465Sache	     + adjkerntz 	/* -+ daylight savings time correction */ ;
1792893Sdfr	tsp->ts_nsec = 0;
1802893Sdfr}
1812893Sdfr
1822893Sdfr/*
1832893Sdfr * Cheezy macros to do case detection and conversion for the ascii
1842893Sdfr * character set.  DOESN'T work for ebcdic.
1852893Sdfr */
1862893Sdfr#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
1872893Sdfr#define	islower(c)	(c >= 'a'  &&  c <= 'z')
1882893Sdfr#define	toupper(c)	(c & ~' ')
1892893Sdfr#define	tolower(c)	(c | ' ')
1902893Sdfr
1912893Sdfr/*
1922893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part.
1932893Sdfr * The name part is 8 characters long and the extension part is 3
1942893Sdfr * characters long.  They may contain trailing blanks if the name or
1952893Sdfr * extension are not long enough to fill their respective fields.
1962893Sdfr */
1972893Sdfr
1982893Sdfr/*
1992893Sdfr * Convert a DOS filename to a unix filename. And, return the number of
2002893Sdfr * characters in the resulting unix filename excluding the terminating
2012893Sdfr * null.
2022893Sdfr */
2032893Sdfrint
2042893Sdfrdos2unixfn(dn, un)
2052893Sdfr	u_char dn[11];
2062893Sdfr	u_char *un;
2072893Sdfr{
2082893Sdfr	int i;
2092893Sdfr	int ni;
2102893Sdfr	int ei;
2112893Sdfr	int thislong = 0;
2122893Sdfr	u_char c;
2132893Sdfr	u_char *origun = un;
2142893Sdfr
2152893Sdfr	/*
2162893Sdfr	 * Find the last character in the name portion of the dos filename.
2172893Sdfr	 */
2182893Sdfr	for (ni = 7; ni >= 0; ni--)
2192893Sdfr		if (dn[ni] != ' ')
2202893Sdfr			break;
2212893Sdfr
2222893Sdfr	/*
2232893Sdfr	 * Find the last character in the extension portion of the
2242893Sdfr	 * filename.
2252893Sdfr	 */
2262893Sdfr	for (ei = 10; ei >= 8; ei--)
2272893Sdfr		if (dn[ei] != ' ')
2282893Sdfr			break;
2292893Sdfr
2302893Sdfr	/*
2312893Sdfr	 * Copy the name portion into the unix filename string. NOTE: DOS
2322893Sdfr	 * filenames are usually kept in upper case.  To make it more unixy
2332893Sdfr	 * we convert all DOS filenames to lower case.  Some may like this,
2342893Sdfr	 * some may not.
2352893Sdfr	 */
2362893Sdfr	for (i = 0; i <= ni; i++) {
2372893Sdfr		c = dn[i];
2382893Sdfr		*un++ = isupper(c) ? tolower(c) : c;
2392893Sdfr		thislong++;
2402893Sdfr	}
2412893Sdfr
2422893Sdfr	/*
2432893Sdfr	 * Now, if there is an extension then put in a period and copy in
2442893Sdfr	 * the extension.
2452893Sdfr	 */
2462893Sdfr	if (ei >= 8) {
2472893Sdfr		*un++ = '.';
2482893Sdfr		thislong++;
2492893Sdfr		for (i = 8; i <= ei; i++) {
2502893Sdfr			c = dn[i];
2512893Sdfr			*un++ = isupper(c) ? tolower(c) : c;
2522893Sdfr			thislong++;
2532893Sdfr		}
2542893Sdfr	}
2552893Sdfr	*un++ = 0;
2562893Sdfr
2572893Sdfr	/*
2582893Sdfr	 * If first char of the filename is SLOT_E5 (0x05), then the real
2592893Sdfr	 * first char of the filename should be 0xe5. But, they couldn't
2602893Sdfr	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
2612893Sdfr	 * directory slot. Another dos quirk.
2622893Sdfr	 */
2632893Sdfr	if (*origun == SLOT_E5)
2642893Sdfr		*origun = 0xe5;
2652893Sdfr
2662893Sdfr	return thislong;
2672893Sdfr}
2682893Sdfr
2692893Sdfr/*
2702893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure
2712893Sdfr * that valid characters for a dos filename are supplied.
2722893Sdfr */
2732893Sdfrvoid
2742893Sdfrunix2dosfn(un, dn, unlen)
2752893Sdfr	u_char *un;
2762893Sdfr	u_char dn[11];
2772893Sdfr	int unlen;
2782893Sdfr{
2792893Sdfr	int i;
2802893Sdfr	u_char c;
2812893Sdfr
2822893Sdfr	/*
2832893Sdfr	 * Fill the dos filename string with blanks. These are DOS's pad
2842893Sdfr	 * characters.
2852893Sdfr	 */
2862893Sdfr	for (i = 0; i <= 10; i++)
2872893Sdfr		dn[i] = ' ';
2882893Sdfr
2892893Sdfr	/*
2902893Sdfr	 * The filenames "." and ".." are handled specially, since they
2912893Sdfr	 * don't follow dos filename rules.
2922893Sdfr	 */
2932893Sdfr	if (un[0] == '.' && unlen == 1) {
2942893Sdfr		dn[0] = '.';
2952893Sdfr		return;
2962893Sdfr	}
2972893Sdfr	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
2982893Sdfr		dn[0] = '.';
2992893Sdfr		dn[1] = '.';
3002893Sdfr		return;
3012893Sdfr	}
3022893Sdfr
3032893Sdfr	/*
3042893Sdfr	 * Copy the unix filename into the dos filename string upto the end
3052893Sdfr	 * of string, a '.', or 8 characters. Whichever happens first stops
3062893Sdfr	 * us. This forms the name portion of the dos filename. Fold to
3072893Sdfr	 * upper case.
3082893Sdfr	 */
3092893Sdfr	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
3102893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3112893Sdfr		un++;
3122893Sdfr		unlen--;
3132893Sdfr	}
3142893Sdfr
3152893Sdfr	/*
3162893Sdfr	 * If the first char of the filename is 0xe5, then translate it to
3172893Sdfr	 * 0x05.  This is because 0xe5 is the marker for a deleted
3182893Sdfr	 * directory slot.  I guess this means you can't have filenames
3192893Sdfr	 * that start with 0x05.  I suppose we should check for this and
3202893Sdfr	 * doing something about it.
3212893Sdfr	 */
3222893Sdfr	if (dn[0] == SLOT_DELETED)
3232893Sdfr		dn[0] = SLOT_E5;
3242893Sdfr
3252893Sdfr	/*
3262893Sdfr	 * Strip any further characters up to a '.' or the end of the
3272893Sdfr	 * string.
3282893Sdfr	 */
3292893Sdfr	while (unlen && (c = *un)) {
3302893Sdfr		un++;
3312893Sdfr		unlen--;
3322893Sdfr		/* Make sure we've skipped over the dot before stopping. */
3332893Sdfr		if (c == '.')
3342893Sdfr			break;
3352893Sdfr	}
3362893Sdfr
3372893Sdfr	/*
3382893Sdfr	 * Copy in the extension part of the name, if any. Force to upper
3392893Sdfr	 * case. Note that the extension is allowed to contain '.'s.
3402893Sdfr	 * Filenames in this form are probably inaccessable under dos.
3412893Sdfr	 */
3422893Sdfr	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
3432893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3442893Sdfr		un++;
3452893Sdfr		unlen--;
3462893Sdfr	}
3472893Sdfr}
3482893Sdfr
3492893Sdfr/*
3502893Sdfr * Get rid of these macros before someone discovers we are using such
3512893Sdfr * hideous things.
3522893Sdfr */
3532893Sdfr#undef	isupper
3542893Sdfr#undef	islower
3552893Sdfr#undef	toupper
3562893Sdfr#undef	tolower
357