msdosfs_conv.c revision 20138
120138Sbde/*	$Id: msdosfs_conv.c,v 1.10 1996/09/19 18:20:43 nate 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 */
3712144Sphkstatic u_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 */
4512144Sphkstatic u_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	 */
8018397Snate	t = tsp->tv_sec - (tz.tz_minuteswest * 60)
8115055Sache	    - (wall_cmos_clock ? adjkerntz : 0);
8215053Sache	    /* - daylight savings time correction */
832893Sdfr	if (lasttime != t) {
842893Sdfr		lasttime = t;
852893Sdfr		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
862893Sdfr		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
872893Sdfr		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
882893Sdfr
892893Sdfr		/*
902893Sdfr		 * If the number of days since 1970 is the same as the last
912893Sdfr		 * time we did the computation then skip all this leap year
922893Sdfr		 * and month stuff.
932893Sdfr		 */
942893Sdfr		days = t / (24 * 60 * 60);
952893Sdfr		if (days != lastday) {
962893Sdfr			lastday = days;
972893Sdfr			for (year = 1970;; year++) {
982893Sdfr				inc = year & 0x03 ? 365 : 366;
992893Sdfr				if (days < inc)
1002893Sdfr					break;
1012893Sdfr				days -= inc;
1022893Sdfr			}
1032893Sdfr			months = year & 0x03 ? regyear : leapyear;
10420138Sbde			for (month = 0; days >= months[month]; month++)
1057465Sache				;
1067465Sache			if (month > 0)
1077465Sache				days -= months[month - 1];
1082893Sdfr			lastddate = ((days + 1) << DD_DAY_SHIFT)
1092893Sdfr			    + ((month + 1) << DD_MONTH_SHIFT);
1102893Sdfr			/*
1112893Sdfr			 * Remember dos's idea of time is relative to 1980.
1122893Sdfr			 * unix's is relative to 1970.  If somehow we get a
1132893Sdfr			 * time before 1980 then don't give totally crazy
1142893Sdfr			 * results.
1152893Sdfr			 */
1162893Sdfr			if (year > 1980)
1172893Sdfr				lastddate += (year - 1980) << DD_YEAR_SHIFT;
1182893Sdfr		}
1192893Sdfr	}
1202893Sdfr	*dtp = lastdtime;
1212893Sdfr	*ddp = lastddate;
1222893Sdfr}
1232893Sdfr
1242893Sdfr/*
1252893Sdfr * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
1262893Sdfr * interval there were 8 regular years and 2 leap years.
1272893Sdfr */
1282893Sdfr#define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))
1292893Sdfr
1302893Sdfru_short lastdosdate;
1312893Sdfru_long lastseconds;
1322893Sdfr
1332893Sdfr/*
1342893Sdfr * Convert from dos' idea of time to unix'. This will probably only be
1352893Sdfr * called from the stat(), and fstat() system calls and so probably need
1362893Sdfr * not be too efficient.
1372893Sdfr */
1382893Sdfrvoid
1392893Sdfrdos2unixtime(dd, dt, tsp)
1402893Sdfr	u_short dd;
1412893Sdfr	u_short dt;
1422893Sdfr	struct timespec *tsp;
1432893Sdfr{
1442893Sdfr	u_long seconds;
14511921Sphk	u_long month;
14611921Sphk	u_long year;
1472893Sdfr	u_long days;
1482893Sdfr	u_short *months;
1492893Sdfr
1505083Sbde	seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
1512893Sdfr	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
1522893Sdfr	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
1532893Sdfr	/*
1542893Sdfr	 * If the year, month, and day from the last conversion are the
1552893Sdfr	 * same then use the saved value.
1562893Sdfr	 */
1572893Sdfr	if (lastdosdate != dd) {
1582893Sdfr		lastdosdate = dd;
1592893Sdfr		days = 0;
1602893Sdfr		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
1617465Sache		days = year * 365;
1627465Sache		days += year / 4 + 1;	/* add in leap days */
1637465Sache		if ((year & 0x03) == 0)
1647465Sache			days--;		/* if year is a leap year */
1652893Sdfr		months = year & 0x03 ? regyear : leapyear;
1662893Sdfr		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
1677465Sache		if (month < 1 || month > 12) {
1683152Sphk			printf(
1693152Sphk			    "dos2unixtime(): month value out of range (%ld)\n",
1703152Sphk			    month);
1712893Sdfr			month = 1;
1722893Sdfr		}
1737465Sache		if (month > 1)
1747465Sache			days += months[month - 2];
1752893Sdfr		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
1762893Sdfr		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
1772893Sdfr	}
17818397Snate	tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
17915055Sache	     + adjkerntz;
18015053Sache	     /* + daylight savings time correction */
18118397Snate	tsp->tv_nsec = 0;
1822893Sdfr}
1832893Sdfr
1842893Sdfr/*
1852893Sdfr * Cheezy macros to do case detection and conversion for the ascii
1862893Sdfr * character set.  DOESN'T work for ebcdic.
1872893Sdfr */
1882893Sdfr#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
1892893Sdfr#define	islower(c)	(c >= 'a'  &&  c <= 'z')
1902893Sdfr#define	toupper(c)	(c & ~' ')
1912893Sdfr#define	tolower(c)	(c | ' ')
1922893Sdfr
1932893Sdfr/*
1942893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part.
1952893Sdfr * The name part is 8 characters long and the extension part is 3
1962893Sdfr * characters long.  They may contain trailing blanks if the name or
1972893Sdfr * extension are not long enough to fill their respective fields.
1982893Sdfr */
1992893Sdfr
2002893Sdfr/*
2012893Sdfr * Convert a DOS filename to a unix filename. And, return the number of
2022893Sdfr * characters in the resulting unix filename excluding the terminating
2032893Sdfr * null.
2042893Sdfr */
2052893Sdfrint
2062893Sdfrdos2unixfn(dn, un)
2072893Sdfr	u_char dn[11];
2082893Sdfr	u_char *un;
2092893Sdfr{
2102893Sdfr	int i;
2112893Sdfr	int ni;
2122893Sdfr	int ei;
2132893Sdfr	int thislong = 0;
2142893Sdfr	u_char c;
2152893Sdfr	u_char *origun = un;
2162893Sdfr
2172893Sdfr	/*
2182893Sdfr	 * Find the last character in the name portion of the dos filename.
2192893Sdfr	 */
2202893Sdfr	for (ni = 7; ni >= 0; ni--)
2212893Sdfr		if (dn[ni] != ' ')
2222893Sdfr			break;
2232893Sdfr
2242893Sdfr	/*
2252893Sdfr	 * Find the last character in the extension portion of the
2262893Sdfr	 * filename.
2272893Sdfr	 */
2282893Sdfr	for (ei = 10; ei >= 8; ei--)
2292893Sdfr		if (dn[ei] != ' ')
2302893Sdfr			break;
2312893Sdfr
2322893Sdfr	/*
2332893Sdfr	 * Copy the name portion into the unix filename string. NOTE: DOS
2342893Sdfr	 * filenames are usually kept in upper case.  To make it more unixy
2352893Sdfr	 * we convert all DOS filenames to lower case.  Some may like this,
2362893Sdfr	 * some may not.
2372893Sdfr	 */
2382893Sdfr	for (i = 0; i <= ni; i++) {
2392893Sdfr		c = dn[i];
2402893Sdfr		*un++ = isupper(c) ? tolower(c) : c;
2412893Sdfr		thislong++;
2422893Sdfr	}
2432893Sdfr
2442893Sdfr	/*
2452893Sdfr	 * Now, if there is an extension then put in a period and copy in
2462893Sdfr	 * the extension.
2472893Sdfr	 */
2482893Sdfr	if (ei >= 8) {
2492893Sdfr		*un++ = '.';
2502893Sdfr		thislong++;
2512893Sdfr		for (i = 8; i <= ei; i++) {
2522893Sdfr			c = dn[i];
2532893Sdfr			*un++ = isupper(c) ? tolower(c) : c;
2542893Sdfr			thislong++;
2552893Sdfr		}
2562893Sdfr	}
2572893Sdfr	*un++ = 0;
2582893Sdfr
2592893Sdfr	/*
2602893Sdfr	 * If first char of the filename is SLOT_E5 (0x05), then the real
2612893Sdfr	 * first char of the filename should be 0xe5. But, they couldn't
2622893Sdfr	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
2632893Sdfr	 * directory slot. Another dos quirk.
2642893Sdfr	 */
2652893Sdfr	if (*origun == SLOT_E5)
2662893Sdfr		*origun = 0xe5;
2672893Sdfr
2682893Sdfr	return thislong;
2692893Sdfr}
2702893Sdfr
2712893Sdfr/*
2722893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure
2732893Sdfr * that valid characters for a dos filename are supplied.
2742893Sdfr */
2752893Sdfrvoid
2762893Sdfrunix2dosfn(un, dn, unlen)
2772893Sdfr	u_char *un;
2782893Sdfr	u_char dn[11];
2792893Sdfr	int unlen;
2802893Sdfr{
2812893Sdfr	int i;
2822893Sdfr	u_char c;
2832893Sdfr
2842893Sdfr	/*
2852893Sdfr	 * Fill the dos filename string with blanks. These are DOS's pad
2862893Sdfr	 * characters.
2872893Sdfr	 */
2882893Sdfr	for (i = 0; i <= 10; i++)
2892893Sdfr		dn[i] = ' ';
2902893Sdfr
2912893Sdfr	/*
2922893Sdfr	 * The filenames "." and ".." are handled specially, since they
2932893Sdfr	 * don't follow dos filename rules.
2942893Sdfr	 */
2952893Sdfr	if (un[0] == '.' && unlen == 1) {
2962893Sdfr		dn[0] = '.';
2972893Sdfr		return;
2982893Sdfr	}
2992893Sdfr	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
3002893Sdfr		dn[0] = '.';
3012893Sdfr		dn[1] = '.';
3022893Sdfr		return;
3032893Sdfr	}
3042893Sdfr
3052893Sdfr	/*
3062893Sdfr	 * Copy the unix filename into the dos filename string upto the end
3072893Sdfr	 * of string, a '.', or 8 characters. Whichever happens first stops
3082893Sdfr	 * us. This forms the name portion of the dos filename. Fold to
3092893Sdfr	 * upper case.
3102893Sdfr	 */
3112893Sdfr	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
3122893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3132893Sdfr		un++;
3142893Sdfr		unlen--;
3152893Sdfr	}
3162893Sdfr
3172893Sdfr	/*
3182893Sdfr	 * If the first char of the filename is 0xe5, then translate it to
3192893Sdfr	 * 0x05.  This is because 0xe5 is the marker for a deleted
3202893Sdfr	 * directory slot.  I guess this means you can't have filenames
3212893Sdfr	 * that start with 0x05.  I suppose we should check for this and
3222893Sdfr	 * doing something about it.
3232893Sdfr	 */
3242893Sdfr	if (dn[0] == SLOT_DELETED)
3252893Sdfr		dn[0] = SLOT_E5;
3262893Sdfr
3272893Sdfr	/*
3282893Sdfr	 * Strip any further characters up to a '.' or the end of the
3292893Sdfr	 * string.
3302893Sdfr	 */
3312893Sdfr	while (unlen && (c = *un)) {
3322893Sdfr		un++;
3332893Sdfr		unlen--;
3342893Sdfr		/* Make sure we've skipped over the dot before stopping. */
3352893Sdfr		if (c == '.')
3362893Sdfr			break;
3372893Sdfr	}
3382893Sdfr
3392893Sdfr	/*
3402893Sdfr	 * Copy in the extension part of the name, if any. Force to upper
3412893Sdfr	 * case. Note that the extension is allowed to contain '.'s.
3422893Sdfr	 * Filenames in this form are probably inaccessable under dos.
3432893Sdfr	 */
3442893Sdfr	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
3452893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3462893Sdfr		un++;
3472893Sdfr		unlen--;
3482893Sdfr	}
3492893Sdfr}
3502893Sdfr
3512893Sdfr/*
3522893Sdfr * Get rid of these macros before someone discovers we are using such
3532893Sdfr * hideous things.
3542893Sdfr */
3552893Sdfr#undef	isupper
3562893Sdfr#undef	islower
3572893Sdfr#undef	toupper
3582893Sdfr#undef	tolower
359