xref: /freebsd/sys/fs/msdosfs/msdosfs_conv.c (revision 27a0bc89a433a500c7c8befb01ac54596858cdad)
127a0bc89SDoug Rabson /*	$Id$ */
227a0bc89SDoug Rabson /*	$NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $	*/
327a0bc89SDoug Rabson 
427a0bc89SDoug Rabson /*
527a0bc89SDoug Rabson  * Written by Paul Popelka (paulp@uts.amdahl.com)
627a0bc89SDoug Rabson  *
727a0bc89SDoug Rabson  * You can do anything you want with this software, just don't say you wrote
827a0bc89SDoug Rabson  * it, and don't remove this notice.
927a0bc89SDoug Rabson  *
1027a0bc89SDoug Rabson  * This software is provided "as is".
1127a0bc89SDoug Rabson  *
1227a0bc89SDoug Rabson  * The author supplies this software to be publicly redistributed on the
1327a0bc89SDoug Rabson  * understanding that the author is not responsible for the correct
1427a0bc89SDoug Rabson  * functioning of this software in any circumstances and is not liable for
1527a0bc89SDoug Rabson  * any damages caused by this software.
1627a0bc89SDoug Rabson  *
1727a0bc89SDoug Rabson  * October 1992
1827a0bc89SDoug Rabson  */
1927a0bc89SDoug Rabson 
2027a0bc89SDoug Rabson /*
2127a0bc89SDoug Rabson  * System include files.
2227a0bc89SDoug Rabson  */
2327a0bc89SDoug Rabson #include <sys/param.h>
2427a0bc89SDoug Rabson #include <sys/time.h>
2527a0bc89SDoug Rabson #include <sys/kernel.h>		/* defines tz */
2627a0bc89SDoug Rabson 
2727a0bc89SDoug Rabson /*
2827a0bc89SDoug Rabson  * MSDOSFS include files.
2927a0bc89SDoug Rabson  */
3027a0bc89SDoug Rabson #include <msdosfs/direntry.h>
3127a0bc89SDoug Rabson 
3227a0bc89SDoug Rabson /*
3327a0bc89SDoug Rabson  * Days in each month in a regular year.
3427a0bc89SDoug Rabson  */
3527a0bc89SDoug Rabson u_short regyear[] = {
3627a0bc89SDoug Rabson 	31, 28, 31, 30, 31, 30,
3727a0bc89SDoug Rabson 	31, 31, 30, 31, 30, 31
3827a0bc89SDoug Rabson };
3927a0bc89SDoug Rabson 
4027a0bc89SDoug Rabson /*
4127a0bc89SDoug Rabson  * Days in each month in a leap year.
4227a0bc89SDoug Rabson  */
4327a0bc89SDoug Rabson u_short leapyear[] = {
4427a0bc89SDoug Rabson 	31, 29, 31, 30, 31, 30,
4527a0bc89SDoug Rabson 	31, 31, 30, 31, 30, 31
4627a0bc89SDoug Rabson };
4727a0bc89SDoug Rabson 
4827a0bc89SDoug Rabson /*
4927a0bc89SDoug Rabson  * Variables used to remember parts of the last time conversion.  Maybe we
5027a0bc89SDoug Rabson  * can avoid a full conversion.
5127a0bc89SDoug Rabson  */
5227a0bc89SDoug Rabson u_long lasttime;
5327a0bc89SDoug Rabson u_long lastday;
5427a0bc89SDoug Rabson u_short lastddate;
5527a0bc89SDoug Rabson u_short lastdtime;
5627a0bc89SDoug Rabson 
5727a0bc89SDoug Rabson /*
5827a0bc89SDoug Rabson  * Convert the unix version of time to dos's idea of time to be used in
5927a0bc89SDoug Rabson  * file timestamps. The passed in unix time is assumed to be in GMT.
6027a0bc89SDoug Rabson  */
6127a0bc89SDoug Rabson void
6227a0bc89SDoug Rabson unix2dostime(tsp, ddp, dtp)
6327a0bc89SDoug Rabson 	struct timespec *tsp;
6427a0bc89SDoug Rabson 	u_short *ddp;
6527a0bc89SDoug Rabson 	u_short *dtp;
6627a0bc89SDoug Rabson {
6727a0bc89SDoug Rabson 	u_long t;
6827a0bc89SDoug Rabson 	u_long days;
6927a0bc89SDoug Rabson 	u_long inc;
7027a0bc89SDoug Rabson 	u_long year;
7127a0bc89SDoug Rabson 	u_long month;
7227a0bc89SDoug Rabson 	u_short *months;
7327a0bc89SDoug Rabson 
7427a0bc89SDoug Rabson 	/*
7527a0bc89SDoug Rabson 	 * If the time from the last conversion is the same as now, then
7627a0bc89SDoug Rabson 	 * skip the computations and use the saved result.
7727a0bc89SDoug Rabson 	 */
7827a0bc89SDoug Rabson 	t = tsp->ts_sec - (tz.tz_minuteswest * 60)
7927a0bc89SDoug Rabson 	     /* +- daylight savings time correction */ ;
8027a0bc89SDoug Rabson 	if (lasttime != t) {
8127a0bc89SDoug Rabson 		lasttime = t;
8227a0bc89SDoug Rabson 		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
8327a0bc89SDoug Rabson 		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
8427a0bc89SDoug Rabson 		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
8527a0bc89SDoug Rabson 
8627a0bc89SDoug Rabson 		/*
8727a0bc89SDoug Rabson 		 * If the number of days since 1970 is the same as the last
8827a0bc89SDoug Rabson 		 * time we did the computation then skip all this leap year
8927a0bc89SDoug Rabson 		 * and month stuff.
9027a0bc89SDoug Rabson 		 */
9127a0bc89SDoug Rabson 		days = t / (24 * 60 * 60);
9227a0bc89SDoug Rabson 		if (days != lastday) {
9327a0bc89SDoug Rabson 			lastday = days;
9427a0bc89SDoug Rabson 			for (year = 1970;; year++) {
9527a0bc89SDoug Rabson 				inc = year & 0x03 ? 365 : 366;
9627a0bc89SDoug Rabson 				if (days < inc)
9727a0bc89SDoug Rabson 					break;
9827a0bc89SDoug Rabson 				days -= inc;
9927a0bc89SDoug Rabson 			}
10027a0bc89SDoug Rabson 			months = year & 0x03 ? regyear : leapyear;
10127a0bc89SDoug Rabson 			for (month = 0; month < 12; month++) {
10227a0bc89SDoug Rabson 				if (days < months[month])
10327a0bc89SDoug Rabson 					break;
10427a0bc89SDoug Rabson 				days -= months[month];
10527a0bc89SDoug Rabson 			}
10627a0bc89SDoug Rabson 			lastddate = ((days + 1) << DD_DAY_SHIFT)
10727a0bc89SDoug Rabson 			    + ((month + 1) << DD_MONTH_SHIFT);
10827a0bc89SDoug Rabson 			/*
10927a0bc89SDoug Rabson 			 * Remember dos's idea of time is relative to 1980.
11027a0bc89SDoug Rabson 			 * unix's is relative to 1970.  If somehow we get a
11127a0bc89SDoug Rabson 			 * time before 1980 then don't give totally crazy
11227a0bc89SDoug Rabson 			 * results.
11327a0bc89SDoug Rabson 			 */
11427a0bc89SDoug Rabson 			if (year > 1980)
11527a0bc89SDoug Rabson 				lastddate += (year - 1980) << DD_YEAR_SHIFT;
11627a0bc89SDoug Rabson 		}
11727a0bc89SDoug Rabson 	}
11827a0bc89SDoug Rabson 	*dtp = lastdtime;
11927a0bc89SDoug Rabson 	*ddp = lastddate;
12027a0bc89SDoug Rabson }
12127a0bc89SDoug Rabson 
12227a0bc89SDoug Rabson /*
12327a0bc89SDoug Rabson  * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
12427a0bc89SDoug Rabson  * interval there were 8 regular years and 2 leap years.
12527a0bc89SDoug Rabson  */
12627a0bc89SDoug Rabson #define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))
12727a0bc89SDoug Rabson 
12827a0bc89SDoug Rabson u_short lastdosdate;
12927a0bc89SDoug Rabson u_long lastseconds;
13027a0bc89SDoug Rabson 
13127a0bc89SDoug Rabson /*
13227a0bc89SDoug Rabson  * Convert from dos' idea of time to unix'. This will probably only be
13327a0bc89SDoug Rabson  * called from the stat(), and fstat() system calls and so probably need
13427a0bc89SDoug Rabson  * not be too efficient.
13527a0bc89SDoug Rabson  */
13627a0bc89SDoug Rabson void
13727a0bc89SDoug Rabson dos2unixtime(dd, dt, tsp)
13827a0bc89SDoug Rabson 	u_short dd;
13927a0bc89SDoug Rabson 	u_short dt;
14027a0bc89SDoug Rabson 	struct timespec *tsp;
14127a0bc89SDoug Rabson {
14227a0bc89SDoug Rabson 	u_long seconds;
14327a0bc89SDoug Rabson 	u_long m, month;
14427a0bc89SDoug Rabson 	u_long y, year;
14527a0bc89SDoug Rabson 	u_long days;
14627a0bc89SDoug Rabson 	u_short *months;
14727a0bc89SDoug Rabson 
14827a0bc89SDoug Rabson 	seconds = ((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT)
14927a0bc89SDoug Rabson 	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
15027a0bc89SDoug Rabson 	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
15127a0bc89SDoug Rabson 	/*
15227a0bc89SDoug Rabson 	 * If the year, month, and day from the last conversion are the
15327a0bc89SDoug Rabson 	 * same then use the saved value.
15427a0bc89SDoug Rabson 	 */
15527a0bc89SDoug Rabson 	if (lastdosdate != dd) {
15627a0bc89SDoug Rabson 		lastdosdate = dd;
15727a0bc89SDoug Rabson 		days = 0;
15827a0bc89SDoug Rabson 		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
15927a0bc89SDoug Rabson 		for (y = 0; y < year; y++) {
16027a0bc89SDoug Rabson 			days += y & 0x03 ? 365 : 366;
16127a0bc89SDoug Rabson 		}
16227a0bc89SDoug Rabson 		months = year & 0x03 ? regyear : leapyear;
16327a0bc89SDoug Rabson 		/*
16427a0bc89SDoug Rabson 		 * Prevent going from 0 to 0xffffffff in the following
16527a0bc89SDoug Rabson 		 * loop.
16627a0bc89SDoug Rabson 		 */
16727a0bc89SDoug Rabson 		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
16827a0bc89SDoug Rabson 		if (month == 0) {
16927a0bc89SDoug Rabson 			printf("dos2unixtime(): month value out of range (%d)\n",
17027a0bc89SDoug Rabson 			       month);
17127a0bc89SDoug Rabson 			month = 1;
17227a0bc89SDoug Rabson 		}
17327a0bc89SDoug Rabson 		for (m = 0; m < month - 1; m++) {
17427a0bc89SDoug Rabson 			days += months[m];
17527a0bc89SDoug Rabson 		}
17627a0bc89SDoug Rabson 		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
17727a0bc89SDoug Rabson 		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
17827a0bc89SDoug Rabson 	}
17927a0bc89SDoug Rabson 	tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
18027a0bc89SDoug Rabson 	     /* -+ daylight savings time correction */ ;
18127a0bc89SDoug Rabson 	tsp->ts_nsec = 0;
18227a0bc89SDoug Rabson }
18327a0bc89SDoug Rabson 
18427a0bc89SDoug Rabson /*
18527a0bc89SDoug Rabson  * Cheezy macros to do case detection and conversion for the ascii
18627a0bc89SDoug Rabson  * character set.  DOESN'T work for ebcdic.
18727a0bc89SDoug Rabson  */
18827a0bc89SDoug Rabson #define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
18927a0bc89SDoug Rabson #define	islower(c)	(c >= 'a'  &&  c <= 'z')
19027a0bc89SDoug Rabson #define	toupper(c)	(c & ~' ')
19127a0bc89SDoug Rabson #define	tolower(c)	(c | ' ')
19227a0bc89SDoug Rabson 
19327a0bc89SDoug Rabson /*
19427a0bc89SDoug Rabson  * DOS filenames are made of 2 parts, the name part and the extension part.
19527a0bc89SDoug Rabson  * The name part is 8 characters long and the extension part is 3
19627a0bc89SDoug Rabson  * characters long.  They may contain trailing blanks if the name or
19727a0bc89SDoug Rabson  * extension are not long enough to fill their respective fields.
19827a0bc89SDoug Rabson  */
19927a0bc89SDoug Rabson 
20027a0bc89SDoug Rabson /*
20127a0bc89SDoug Rabson  * Convert a DOS filename to a unix filename. And, return the number of
20227a0bc89SDoug Rabson  * characters in the resulting unix filename excluding the terminating
20327a0bc89SDoug Rabson  * null.
20427a0bc89SDoug Rabson  */
20527a0bc89SDoug Rabson int
20627a0bc89SDoug Rabson dos2unixfn(dn, un)
20727a0bc89SDoug Rabson 	u_char dn[11];
20827a0bc89SDoug Rabson 	u_char *un;
20927a0bc89SDoug Rabson {
21027a0bc89SDoug Rabson 	int i;
21127a0bc89SDoug Rabson 	int ni;
21227a0bc89SDoug Rabson 	int ei;
21327a0bc89SDoug Rabson 	int thislong = 0;
21427a0bc89SDoug Rabson 	u_char c;
21527a0bc89SDoug Rabson 	u_char *origun = un;
21627a0bc89SDoug Rabson 
21727a0bc89SDoug Rabson 	/*
21827a0bc89SDoug Rabson 	 * Find the last character in the name portion of the dos filename.
21927a0bc89SDoug Rabson 	 */
22027a0bc89SDoug Rabson 	for (ni = 7; ni >= 0; ni--)
22127a0bc89SDoug Rabson 		if (dn[ni] != ' ')
22227a0bc89SDoug Rabson 			break;
22327a0bc89SDoug Rabson 
22427a0bc89SDoug Rabson 	/*
22527a0bc89SDoug Rabson 	 * Find the last character in the extension portion of the
22627a0bc89SDoug Rabson 	 * filename.
22727a0bc89SDoug Rabson 	 */
22827a0bc89SDoug Rabson 	for (ei = 10; ei >= 8; ei--)
22927a0bc89SDoug Rabson 		if (dn[ei] != ' ')
23027a0bc89SDoug Rabson 			break;
23127a0bc89SDoug Rabson 
23227a0bc89SDoug Rabson 	/*
23327a0bc89SDoug Rabson 	 * Copy the name portion into the unix filename string. NOTE: DOS
23427a0bc89SDoug Rabson 	 * filenames are usually kept in upper case.  To make it more unixy
23527a0bc89SDoug Rabson 	 * we convert all DOS filenames to lower case.  Some may like this,
23627a0bc89SDoug Rabson 	 * some may not.
23727a0bc89SDoug Rabson 	 */
23827a0bc89SDoug Rabson 	for (i = 0; i <= ni; i++) {
23927a0bc89SDoug Rabson 		c = dn[i];
24027a0bc89SDoug Rabson 		*un++ = isupper(c) ? tolower(c) : c;
24127a0bc89SDoug Rabson 		thislong++;
24227a0bc89SDoug Rabson 	}
24327a0bc89SDoug Rabson 
24427a0bc89SDoug Rabson 	/*
24527a0bc89SDoug Rabson 	 * Now, if there is an extension then put in a period and copy in
24627a0bc89SDoug Rabson 	 * the extension.
24727a0bc89SDoug Rabson 	 */
24827a0bc89SDoug Rabson 	if (ei >= 8) {
24927a0bc89SDoug Rabson 		*un++ = '.';
25027a0bc89SDoug Rabson 		thislong++;
25127a0bc89SDoug Rabson 		for (i = 8; i <= ei; i++) {
25227a0bc89SDoug Rabson 			c = dn[i];
25327a0bc89SDoug Rabson 			*un++ = isupper(c) ? tolower(c) : c;
25427a0bc89SDoug Rabson 			thislong++;
25527a0bc89SDoug Rabson 		}
25627a0bc89SDoug Rabson 	}
25727a0bc89SDoug Rabson 	*un++ = 0;
25827a0bc89SDoug Rabson 
25927a0bc89SDoug Rabson 	/*
26027a0bc89SDoug Rabson 	 * If first char of the filename is SLOT_E5 (0x05), then the real
26127a0bc89SDoug Rabson 	 * first char of the filename should be 0xe5. But, they couldn't
26227a0bc89SDoug Rabson 	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
26327a0bc89SDoug Rabson 	 * directory slot. Another dos quirk.
26427a0bc89SDoug Rabson 	 */
26527a0bc89SDoug Rabson 	if (*origun == SLOT_E5)
26627a0bc89SDoug Rabson 		*origun = 0xe5;
26727a0bc89SDoug Rabson 
26827a0bc89SDoug Rabson 	return thislong;
26927a0bc89SDoug Rabson }
27027a0bc89SDoug Rabson 
27127a0bc89SDoug Rabson /*
27227a0bc89SDoug Rabson  * Convert a unix filename to a DOS filename. This function does not ensure
27327a0bc89SDoug Rabson  * that valid characters for a dos filename are supplied.
27427a0bc89SDoug Rabson  */
27527a0bc89SDoug Rabson void
27627a0bc89SDoug Rabson unix2dosfn(un, dn, unlen)
27727a0bc89SDoug Rabson 	u_char *un;
27827a0bc89SDoug Rabson 	u_char dn[11];
27927a0bc89SDoug Rabson 	int unlen;
28027a0bc89SDoug Rabson {
28127a0bc89SDoug Rabson 	int i;
28227a0bc89SDoug Rabson 	u_char c;
28327a0bc89SDoug Rabson 
28427a0bc89SDoug Rabson 	/*
28527a0bc89SDoug Rabson 	 * Fill the dos filename string with blanks. These are DOS's pad
28627a0bc89SDoug Rabson 	 * characters.
28727a0bc89SDoug Rabson 	 */
28827a0bc89SDoug Rabson 	for (i = 0; i <= 10; i++)
28927a0bc89SDoug Rabson 		dn[i] = ' ';
29027a0bc89SDoug Rabson 
29127a0bc89SDoug Rabson 	/*
29227a0bc89SDoug Rabson 	 * The filenames "." and ".." are handled specially, since they
29327a0bc89SDoug Rabson 	 * don't follow dos filename rules.
29427a0bc89SDoug Rabson 	 */
29527a0bc89SDoug Rabson 	if (un[0] == '.' && unlen == 1) {
29627a0bc89SDoug Rabson 		dn[0] = '.';
29727a0bc89SDoug Rabson 		return;
29827a0bc89SDoug Rabson 	}
29927a0bc89SDoug Rabson 	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
30027a0bc89SDoug Rabson 		dn[0] = '.';
30127a0bc89SDoug Rabson 		dn[1] = '.';
30227a0bc89SDoug Rabson 		return;
30327a0bc89SDoug Rabson 	}
30427a0bc89SDoug Rabson 
30527a0bc89SDoug Rabson 	/*
30627a0bc89SDoug Rabson 	 * Copy the unix filename into the dos filename string upto the end
30727a0bc89SDoug Rabson 	 * of string, a '.', or 8 characters. Whichever happens first stops
30827a0bc89SDoug Rabson 	 * us. This forms the name portion of the dos filename. Fold to
30927a0bc89SDoug Rabson 	 * upper case.
31027a0bc89SDoug Rabson 	 */
31127a0bc89SDoug Rabson 	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
31227a0bc89SDoug Rabson 		dn[i] = islower(c) ? toupper(c) : c;
31327a0bc89SDoug Rabson 		un++;
31427a0bc89SDoug Rabson 		unlen--;
31527a0bc89SDoug Rabson 	}
31627a0bc89SDoug Rabson 
31727a0bc89SDoug Rabson 	/*
31827a0bc89SDoug Rabson 	 * If the first char of the filename is 0xe5, then translate it to
31927a0bc89SDoug Rabson 	 * 0x05.  This is because 0xe5 is the marker for a deleted
32027a0bc89SDoug Rabson 	 * directory slot.  I guess this means you can't have filenames
32127a0bc89SDoug Rabson 	 * that start with 0x05.  I suppose we should check for this and
32227a0bc89SDoug Rabson 	 * doing something about it.
32327a0bc89SDoug Rabson 	 */
32427a0bc89SDoug Rabson 	if (dn[0] == SLOT_DELETED)
32527a0bc89SDoug Rabson 		dn[0] = SLOT_E5;
32627a0bc89SDoug Rabson 
32727a0bc89SDoug Rabson 	/*
32827a0bc89SDoug Rabson 	 * Strip any further characters up to a '.' or the end of the
32927a0bc89SDoug Rabson 	 * string.
33027a0bc89SDoug Rabson 	 */
33127a0bc89SDoug Rabson 	while (unlen && (c = *un)) {
33227a0bc89SDoug Rabson 		un++;
33327a0bc89SDoug Rabson 		unlen--;
33427a0bc89SDoug Rabson 		/* Make sure we've skipped over the dot before stopping. */
33527a0bc89SDoug Rabson 		if (c == '.')
33627a0bc89SDoug Rabson 			break;
33727a0bc89SDoug Rabson 	}
33827a0bc89SDoug Rabson 
33927a0bc89SDoug Rabson 	/*
34027a0bc89SDoug Rabson 	 * Copy in the extension part of the name, if any. Force to upper
34127a0bc89SDoug Rabson 	 * case. Note that the extension is allowed to contain '.'s.
34227a0bc89SDoug Rabson 	 * Filenames in this form are probably inaccessable under dos.
34327a0bc89SDoug Rabson 	 */
34427a0bc89SDoug Rabson 	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
34527a0bc89SDoug Rabson 		dn[i] = islower(c) ? toupper(c) : c;
34627a0bc89SDoug Rabson 		un++;
34727a0bc89SDoug Rabson 		unlen--;
34827a0bc89SDoug Rabson 	}
34927a0bc89SDoug Rabson }
35027a0bc89SDoug Rabson 
35127a0bc89SDoug Rabson /*
35227a0bc89SDoug Rabson  * Get rid of these macros before someone discovers we are using such
35327a0bc89SDoug Rabson  * hideous things.
35427a0bc89SDoug Rabson  */
35527a0bc89SDoug Rabson #undef	isupper
35627a0bc89SDoug Rabson #undef	islower
35727a0bc89SDoug Rabson #undef	toupper
35827a0bc89SDoug Rabson #undef	tolower
359