msdosfs_conv.c revision 3152
13152Sphk/*	$Id: msdosfs_conv.c,v 1.1 1994/09/19 15:41:40 dfr 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)
62893Sdfr *
72893Sdfr * You can do anything you want with this software, just don't say you wrote
82893Sdfr * it, and don't remove this notice.
92893Sdfr *
102893Sdfr * This software is provided "as is".
112893Sdfr *
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.
162893Sdfr *
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 */
272893Sdfr
282893Sdfr/*
292893Sdfr * MSDOSFS include files.
302893Sdfr */
312893Sdfr#include <msdosfs/direntry.h>
322893Sdfr
332893Sdfr/*
342893Sdfr * Days in each month in a regular year.
352893Sdfr */
362893Sdfru_short regyear[] = {
372893Sdfr	31, 28, 31, 30, 31, 30,
382893Sdfr	31, 31, 30, 31, 30, 31
392893Sdfr};
402893Sdfr
412893Sdfr/*
422893Sdfr * Days in each month in a leap year.
432893Sdfr */
442893Sdfru_short leapyear[] = {
452893Sdfr	31, 29, 31, 30, 31, 30,
462893Sdfr	31, 31, 30, 31, 30, 31
472893Sdfr};
482893Sdfr
492893Sdfr/*
502893Sdfr * Variables used to remember parts of the last time conversion.  Maybe we
512893Sdfr * can avoid a full conversion.
522893Sdfr */
532893Sdfru_long lasttime;
542893Sdfru_long lastday;
552893Sdfru_short lastddate;
562893Sdfru_short lastdtime;
572893Sdfr
582893Sdfr/*
592893Sdfr * Convert the unix version of time to dos's idea of time to be used in
602893Sdfr * file timestamps. The passed in unix time is assumed to be in GMT.
612893Sdfr */
622893Sdfrvoid
632893Sdfrunix2dostime(tsp, ddp, dtp)
642893Sdfr	struct timespec *tsp;
652893Sdfr	u_short *ddp;
662893Sdfr	u_short *dtp;
672893Sdfr{
682893Sdfr	u_long t;
692893Sdfr	u_long days;
702893Sdfr	u_long inc;
712893Sdfr	u_long year;
722893Sdfr	u_long month;
732893Sdfr	u_short *months;
742893Sdfr
752893Sdfr	/*
762893Sdfr	 * If the time from the last conversion is the same as now, then
772893Sdfr	 * skip the computations and use the saved result.
782893Sdfr	 */
792893Sdfr	t = tsp->ts_sec - (tz.tz_minuteswest * 60)
802893Sdfr	     /* +- daylight savings time correction */ ;
812893Sdfr	if (lasttime != t) {
822893Sdfr		lasttime = t;
832893Sdfr		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
842893Sdfr		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
852893Sdfr		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
862893Sdfr
872893Sdfr		/*
882893Sdfr		 * If the number of days since 1970 is the same as the last
892893Sdfr		 * time we did the computation then skip all this leap year
902893Sdfr		 * and month stuff.
912893Sdfr		 */
922893Sdfr		days = t / (24 * 60 * 60);
932893Sdfr		if (days != lastday) {
942893Sdfr			lastday = days;
952893Sdfr			for (year = 1970;; year++) {
962893Sdfr				inc = year & 0x03 ? 365 : 366;
972893Sdfr				if (days < inc)
982893Sdfr					break;
992893Sdfr				days -= inc;
1002893Sdfr			}
1012893Sdfr			months = year & 0x03 ? regyear : leapyear;
1022893Sdfr			for (month = 0; month < 12; month++) {
1032893Sdfr				if (days < months[month])
1042893Sdfr					break;
1052893Sdfr				days -= months[month];
1062893Sdfr			}
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
1492893Sdfr	seconds = ((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT)
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;
1602893Sdfr		for (y = 0; y < year; y++) {
1612893Sdfr			days += y & 0x03 ? 365 : 366;
1622893Sdfr		}
1632893Sdfr		months = year & 0x03 ? regyear : leapyear;
1642893Sdfr		/*
1652893Sdfr		 * Prevent going from 0 to 0xffffffff in the following
1662893Sdfr		 * loop.
1672893Sdfr		 */
1682893Sdfr		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
1692893Sdfr		if (month == 0) {
1703152Sphk			printf(
1713152Sphk			    "dos2unixtime(): month value out of range (%ld)\n",
1723152Sphk			    month);
1732893Sdfr			month = 1;
1742893Sdfr		}
1752893Sdfr		for (m = 0; m < month - 1; m++) {
1762893Sdfr			days += months[m];
1772893Sdfr		}
1782893Sdfr		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
1792893Sdfr		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
1802893Sdfr	}
1812893Sdfr	tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
1822893Sdfr	     /* -+ daylight savings time correction */ ;
1832893Sdfr	tsp->ts_nsec = 0;
1842893Sdfr}
1852893Sdfr
1862893Sdfr/*
1872893Sdfr * Cheezy macros to do case detection and conversion for the ascii
1882893Sdfr * character set.  DOESN'T work for ebcdic.
1892893Sdfr */
1902893Sdfr#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
1912893Sdfr#define	islower(c)	(c >= 'a'  &&  c <= 'z')
1922893Sdfr#define	toupper(c)	(c & ~' ')
1932893Sdfr#define	tolower(c)	(c | ' ')
1942893Sdfr
1952893Sdfr/*
1962893Sdfr * DOS filenames are made of 2 parts, the name part and the extension part.
1972893Sdfr * The name part is 8 characters long and the extension part is 3
1982893Sdfr * characters long.  They may contain trailing blanks if the name or
1992893Sdfr * extension are not long enough to fill their respective fields.
2002893Sdfr */
2012893Sdfr
2022893Sdfr/*
2032893Sdfr * Convert a DOS filename to a unix filename. And, return the number of
2042893Sdfr * characters in the resulting unix filename excluding the terminating
2052893Sdfr * null.
2062893Sdfr */
2072893Sdfrint
2082893Sdfrdos2unixfn(dn, un)
2092893Sdfr	u_char dn[11];
2102893Sdfr	u_char *un;
2112893Sdfr{
2122893Sdfr	int i;
2132893Sdfr	int ni;
2142893Sdfr	int ei;
2152893Sdfr	int thislong = 0;
2162893Sdfr	u_char c;
2172893Sdfr	u_char *origun = un;
2182893Sdfr
2192893Sdfr	/*
2202893Sdfr	 * Find the last character in the name portion of the dos filename.
2212893Sdfr	 */
2222893Sdfr	for (ni = 7; ni >= 0; ni--)
2232893Sdfr		if (dn[ni] != ' ')
2242893Sdfr			break;
2252893Sdfr
2262893Sdfr	/*
2272893Sdfr	 * Find the last character in the extension portion of the
2282893Sdfr	 * filename.
2292893Sdfr	 */
2302893Sdfr	for (ei = 10; ei >= 8; ei--)
2312893Sdfr		if (dn[ei] != ' ')
2322893Sdfr			break;
2332893Sdfr
2342893Sdfr	/*
2352893Sdfr	 * Copy the name portion into the unix filename string. NOTE: DOS
2362893Sdfr	 * filenames are usually kept in upper case.  To make it more unixy
2372893Sdfr	 * we convert all DOS filenames to lower case.  Some may like this,
2382893Sdfr	 * some may not.
2392893Sdfr	 */
2402893Sdfr	for (i = 0; i <= ni; i++) {
2412893Sdfr		c = dn[i];
2422893Sdfr		*un++ = isupper(c) ? tolower(c) : c;
2432893Sdfr		thislong++;
2442893Sdfr	}
2452893Sdfr
2462893Sdfr	/*
2472893Sdfr	 * Now, if there is an extension then put in a period and copy in
2482893Sdfr	 * the extension.
2492893Sdfr	 */
2502893Sdfr	if (ei >= 8) {
2512893Sdfr		*un++ = '.';
2522893Sdfr		thislong++;
2532893Sdfr		for (i = 8; i <= ei; i++) {
2542893Sdfr			c = dn[i];
2552893Sdfr			*un++ = isupper(c) ? tolower(c) : c;
2562893Sdfr			thislong++;
2572893Sdfr		}
2582893Sdfr	}
2592893Sdfr	*un++ = 0;
2602893Sdfr
2612893Sdfr	/*
2622893Sdfr	 * If first char of the filename is SLOT_E5 (0x05), then the real
2632893Sdfr	 * first char of the filename should be 0xe5. But, they couldn't
2642893Sdfr	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
2652893Sdfr	 * directory slot. Another dos quirk.
2662893Sdfr	 */
2672893Sdfr	if (*origun == SLOT_E5)
2682893Sdfr		*origun = 0xe5;
2692893Sdfr
2702893Sdfr	return thislong;
2712893Sdfr}
2722893Sdfr
2732893Sdfr/*
2742893Sdfr * Convert a unix filename to a DOS filename. This function does not ensure
2752893Sdfr * that valid characters for a dos filename are supplied.
2762893Sdfr */
2772893Sdfrvoid
2782893Sdfrunix2dosfn(un, dn, unlen)
2792893Sdfr	u_char *un;
2802893Sdfr	u_char dn[11];
2812893Sdfr	int unlen;
2822893Sdfr{
2832893Sdfr	int i;
2842893Sdfr	u_char c;
2852893Sdfr
2862893Sdfr	/*
2872893Sdfr	 * Fill the dos filename string with blanks. These are DOS's pad
2882893Sdfr	 * characters.
2892893Sdfr	 */
2902893Sdfr	for (i = 0; i <= 10; i++)
2912893Sdfr		dn[i] = ' ';
2922893Sdfr
2932893Sdfr	/*
2942893Sdfr	 * The filenames "." and ".." are handled specially, since they
2952893Sdfr	 * don't follow dos filename rules.
2962893Sdfr	 */
2972893Sdfr	if (un[0] == '.' && unlen == 1) {
2982893Sdfr		dn[0] = '.';
2992893Sdfr		return;
3002893Sdfr	}
3012893Sdfr	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
3022893Sdfr		dn[0] = '.';
3032893Sdfr		dn[1] = '.';
3042893Sdfr		return;
3052893Sdfr	}
3062893Sdfr
3072893Sdfr	/*
3082893Sdfr	 * Copy the unix filename into the dos filename string upto the end
3092893Sdfr	 * of string, a '.', or 8 characters. Whichever happens first stops
3102893Sdfr	 * us. This forms the name portion of the dos filename. Fold to
3112893Sdfr	 * upper case.
3122893Sdfr	 */
3132893Sdfr	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
3142893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3152893Sdfr		un++;
3162893Sdfr		unlen--;
3172893Sdfr	}
3182893Sdfr
3192893Sdfr	/*
3202893Sdfr	 * If the first char of the filename is 0xe5, then translate it to
3212893Sdfr	 * 0x05.  This is because 0xe5 is the marker for a deleted
3222893Sdfr	 * directory slot.  I guess this means you can't have filenames
3232893Sdfr	 * that start with 0x05.  I suppose we should check for this and
3242893Sdfr	 * doing something about it.
3252893Sdfr	 */
3262893Sdfr	if (dn[0] == SLOT_DELETED)
3272893Sdfr		dn[0] = SLOT_E5;
3282893Sdfr
3292893Sdfr	/*
3302893Sdfr	 * Strip any further characters up to a '.' or the end of the
3312893Sdfr	 * string.
3322893Sdfr	 */
3332893Sdfr	while (unlen && (c = *un)) {
3342893Sdfr		un++;
3352893Sdfr		unlen--;
3362893Sdfr		/* Make sure we've skipped over the dot before stopping. */
3372893Sdfr		if (c == '.')
3382893Sdfr			break;
3392893Sdfr	}
3402893Sdfr
3412893Sdfr	/*
3422893Sdfr	 * Copy in the extension part of the name, if any. Force to upper
3432893Sdfr	 * case. Note that the extension is allowed to contain '.'s.
3442893Sdfr	 * Filenames in this form are probably inaccessable under dos.
3452893Sdfr	 */
3462893Sdfr	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
3472893Sdfr		dn[i] = islower(c) ? toupper(c) : c;
3482893Sdfr		un++;
3492893Sdfr		unlen--;
3502893Sdfr	}
3512893Sdfr}
3522893Sdfr
3532893Sdfr/*
3542893Sdfr * Get rid of these macros before someone discovers we are using such
3552893Sdfr * hideous things.
3562893Sdfr */
3572893Sdfr#undef	isupper
3582893Sdfr#undef	islower
3592893Sdfr#undef	toupper
3602893Sdfr#undef	tolower
361