msdosfs_conv.c revision 18397
1226031Sstas/*	$Id: msdosfs_conv.c,v 1.9 1996/04/05 18:59:06 ache Exp $ */
2226031Sstas/*	$NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $	*/
3226031Sstas
4226031Sstas/*
5226031Sstas * Written by Paul Popelka (paulp@uts.amdahl.com)
6226031Sstas *
7226031Sstas * You can do anything you want with this software, just don't say you wrote
8226031Sstas * it, and don't remove this notice.
9226031Sstas *
10226031Sstas * This software is provided "as is".
11226031Sstas *
12226031Sstas * The author supplies this software to be publicly redistributed on the
13226031Sstas * understanding that the author is not responsible for the correct
14226031Sstas * functioning of this software in any circumstances and is not liable for
15226031Sstas * any damages caused by this software.
16226031Sstas *
17226031Sstas * October 1992
18226031Sstas */
19226031Sstas
20226031Sstas/*
21226031Sstas * System include files.
22226031Sstas */
23226031Sstas#include <sys/param.h>
24226031Sstas#include <sys/time.h>
25226031Sstas#include <sys/kernel.h>		/* defines tz */
26226031Sstas#include <sys/systm.h>		/* defines tz */
27226031Sstas#include <machine/clock.h>
28226031Sstas
29226031Sstas/*
30226031Sstas * MSDOSFS include files.
31226031Sstas */
32226031Sstas#include <msdosfs/direntry.h>
33226031Sstas
34226031Sstas/*
35226031Sstas * Total number of days that have passed for each month in a regular year.
36226031Sstas */
37226031Sstasstatic u_short regyear[] = {
38226031Sstas	31, 59, 90, 120, 151, 181,
39226031Sstas	212, 243, 273, 304, 334, 365
40226031Sstas};
41226031Sstas
42226031Sstas/*
43226031Sstas * Total number of days that have passed for each month in a leap year.
44226031Sstas */
45226031Sstasstatic u_short leapyear[] = {
46226031Sstas	31, 60, 91, 121, 152, 182,
47226031Sstas	213, 244, 274, 305, 335, 366
48226031Sstas};
49226031Sstas
50226031Sstas/*
51226031Sstas * Variables used to remember parts of the last time conversion.  Maybe we
52226031Sstas * can avoid a full conversion.
53226031Sstas */
54226031Sstasu_long lasttime;
55226031Sstasu_long lastday;
56226031Sstasu_short lastddate;
57226031Sstasu_short lastdtime;
58226031Sstas
59226031Sstas/*
60226031Sstas * Convert the unix version of time to dos's idea of time to be used in
61226031Sstas * file timestamps. The passed in unix time is assumed to be in GMT.
62226031Sstas */
63226031Sstasvoid
64226031Sstasunix2dostime(tsp, ddp, dtp)
65226031Sstas	struct timespec *tsp;
66226031Sstas	u_short *ddp;
67226031Sstas	u_short *dtp;
68226031Sstas{
69226031Sstas	u_long t;
70226031Sstas	u_long days;
71226031Sstas	u_long inc;
72226031Sstas	u_long year;
73226031Sstas	u_long month;
74226031Sstas	u_short *months;
75226031Sstas
76226031Sstas	/*
77226031Sstas	 * If the time from the last conversion is the same as now, then
78226031Sstas	 * skip the computations and use the saved result.
79226031Sstas	 */
80226031Sstas	t = tsp->tv_sec - (tz.tz_minuteswest * 60)
81226031Sstas	    - (wall_cmos_clock ? adjkerntz : 0);
82226031Sstas	    /* - daylight savings time correction */
83226031Sstas	if (lasttime != t) {
84226031Sstas		lasttime = t;
85226031Sstas		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
86226031Sstas		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
87226031Sstas		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
88226031Sstas
89226031Sstas		/*
90226031Sstas		 * If the number of days since 1970 is the same as the last
91226031Sstas		 * time we did the computation then skip all this leap year
92226031Sstas		 * and month stuff.
93226031Sstas		 */
94226031Sstas		days = t / (24 * 60 * 60);
95226031Sstas		if (days != lastday) {
96226031Sstas			lastday = days;
97226031Sstas			for (year = 1970;; year++) {
98226031Sstas				inc = year & 0x03 ? 365 : 366;
99226031Sstas				if (days < inc)
100226031Sstas					break;
101226031Sstas				days -= inc;
102226031Sstas			}
103226031Sstas			months = year & 0x03 ? regyear : leapyear;
104226031Sstas			for (month = 0; days > months[month]; month++)
105226031Sstas				;
106226031Sstas			if (month > 0)
107226031Sstas				days -= months[month - 1];
108226031Sstas			lastddate = ((days + 1) << DD_DAY_SHIFT)
109226031Sstas			    + ((month + 1) << DD_MONTH_SHIFT);
110226031Sstas			/*
111226031Sstas			 * Remember dos's idea of time is relative to 1980.
112226031Sstas			 * unix's is relative to 1970.  If somehow we get a
113226031Sstas			 * time before 1980 then don't give totally crazy
114226031Sstas			 * results.
115226031Sstas			 */
116226031Sstas			if (year > 1980)
117226031Sstas				lastddate += (year - 1980) << DD_YEAR_SHIFT;
118226031Sstas		}
119226031Sstas	}
120226031Sstas	*dtp = lastdtime;
121226031Sstas	*ddp = lastddate;
122226031Sstas}
123226031Sstas
124226031Sstas/*
125226031Sstas * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
126226031Sstas * interval there were 8 regular years and 2 leap years.
127226031Sstas */
128226031Sstas#define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))
129226031Sstas
130226031Sstasu_short lastdosdate;
131226031Sstasu_long lastseconds;
132226031Sstas
133226031Sstas/*
134226031Sstas * Convert from dos' idea of time to unix'. This will probably only be
135226031Sstas * called from the stat(), and fstat() system calls and so probably need
136226031Sstas * not be too efficient.
137226031Sstas */
138226031Sstasvoid
139226031Sstasdos2unixtime(dd, dt, tsp)
140226031Sstas	u_short dd;
141226031Sstas	u_short dt;
142226031Sstas	struct timespec *tsp;
143226031Sstas{
144226031Sstas	u_long seconds;
145226031Sstas	u_long month;
146226031Sstas	u_long year;
147226031Sstas	u_long days;
148226031Sstas	u_short *months;
149226031Sstas
150226031Sstas	seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
151226031Sstas	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
152226031Sstas	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
153226031Sstas	/*
154226031Sstas	 * If the year, month, and day from the last conversion are the
155226031Sstas	 * same then use the saved value.
156226031Sstas	 */
157226031Sstas	if (lastdosdate != dd) {
158226031Sstas		lastdosdate = dd;
159226031Sstas		days = 0;
160226031Sstas		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
161226031Sstas		days = year * 365;
162226031Sstas		days += year / 4 + 1;	/* add in leap days */
163226031Sstas		if ((year & 0x03) == 0)
164226031Sstas			days--;		/* if year is a leap year */
165226031Sstas		months = year & 0x03 ? regyear : leapyear;
166226031Sstas		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
167226031Sstas		if (month < 1 || month > 12) {
168226031Sstas			printf(
169226031Sstas			    "dos2unixtime(): month value out of range (%ld)\n",
170226031Sstas			    month);
171226031Sstas			month = 1;
172226031Sstas		}
173226031Sstas		if (month > 1)
174226031Sstas			days += months[month - 2];
175226031Sstas		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
176226031Sstas		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
177226031Sstas	}
178226031Sstas	tsp->tv_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
179226031Sstas	     + adjkerntz;
180226031Sstas	     /* + daylight savings time correction */
181226031Sstas	tsp->tv_nsec = 0;
182226031Sstas}
183226031Sstas
184226031Sstas/*
185226031Sstas * Cheezy macros to do case detection and conversion for the ascii
186226031Sstas * character set.  DOESN'T work for ebcdic.
187226031Sstas */
188226031Sstas#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
189226031Sstas#define	islower(c)	(c >= 'a'  &&  c <= 'z')
190226031Sstas#define	toupper(c)	(c & ~' ')
191226031Sstas#define	tolower(c)	(c | ' ')
192226031Sstas
193226031Sstas/*
194226031Sstas * DOS filenames are made of 2 parts, the name part and the extension part.
195226031Sstas * The name part is 8 characters long and the extension part is 3
196226031Sstas * characters long.  They may contain trailing blanks if the name or
197226031Sstas * extension are not long enough to fill their respective fields.
198226031Sstas */
199226031Sstas
200226031Sstas/*
201226031Sstas * Convert a DOS filename to a unix filename. And, return the number of
202226031Sstas * characters in the resulting unix filename excluding the terminating
203226031Sstas * null.
204226031Sstas */
205226031Sstasint
206226031Sstasdos2unixfn(dn, un)
207226031Sstas	u_char dn[11];
208226031Sstas	u_char *un;
209226031Sstas{
210226031Sstas	int i;
211226031Sstas	int ni;
212226031Sstas	int ei;
213226031Sstas	int thislong = 0;
214226031Sstas	u_char c;
215226031Sstas	u_char *origun = un;
216226031Sstas
217226031Sstas	/*
218226031Sstas	 * Find the last character in the name portion of the dos filename.
219226031Sstas	 */
220226031Sstas	for (ni = 7; ni >= 0; ni--)
221226031Sstas		if (dn[ni] != ' ')
222226031Sstas			break;
223226031Sstas
224226031Sstas	/*
225226031Sstas	 * Find the last character in the extension portion of the
226226031Sstas	 * filename.
227226031Sstas	 */
228226031Sstas	for (ei = 10; ei >= 8; ei--)
229226031Sstas		if (dn[ei] != ' ')
230226031Sstas			break;
231226031Sstas
232226031Sstas	/*
233226031Sstas	 * Copy the name portion into the unix filename string. NOTE: DOS
234226031Sstas	 * filenames are usually kept in upper case.  To make it more unixy
235226031Sstas	 * we convert all DOS filenames to lower case.  Some may like this,
236226031Sstas	 * some may not.
237226031Sstas	 */
238226031Sstas	for (i = 0; i <= ni; i++) {
239226031Sstas		c = dn[i];
240226031Sstas		*un++ = isupper(c) ? tolower(c) : c;
241226031Sstas		thislong++;
242226031Sstas	}
243226031Sstas
244226031Sstas	/*
245226031Sstas	 * Now, if there is an extension then put in a period and copy in
246226031Sstas	 * the extension.
247226031Sstas	 */
248226031Sstas	if (ei >= 8) {
249226031Sstas		*un++ = '.';
250226031Sstas		thislong++;
251226031Sstas		for (i = 8; i <= ei; i++) {
252226031Sstas			c = dn[i];
253226031Sstas			*un++ = isupper(c) ? tolower(c) : c;
254226031Sstas			thislong++;
255226031Sstas		}
256226031Sstas	}
257226031Sstas	*un++ = 0;
258226031Sstas
259226031Sstas	/*
260226031Sstas	 * If first char of the filename is SLOT_E5 (0x05), then the real
261226031Sstas	 * first char of the filename should be 0xe5. But, they couldn't
262226031Sstas	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
263226031Sstas	 * directory slot. Another dos quirk.
264226031Sstas	 */
265226031Sstas	if (*origun == SLOT_E5)
266226031Sstas		*origun = 0xe5;
267226031Sstas
268226031Sstas	return thislong;
269226031Sstas}
270226031Sstas
271226031Sstas/*
272226031Sstas * Convert a unix filename to a DOS filename. This function does not ensure
273226031Sstas * that valid characters for a dos filename are supplied.
274226031Sstas */
275226031Sstasvoid
276226031Sstasunix2dosfn(un, dn, unlen)
277226031Sstas	u_char *un;
278226031Sstas	u_char dn[11];
279226031Sstas	int unlen;
280226031Sstas{
281226031Sstas	int i;
282226031Sstas	u_char c;
283226031Sstas
284226031Sstas	/*
285226031Sstas	 * Fill the dos filename string with blanks. These are DOS's pad
286226031Sstas	 * characters.
287226031Sstas	 */
288226031Sstas	for (i = 0; i <= 10; i++)
289226031Sstas		dn[i] = ' ';
290226031Sstas
291226031Sstas	/*
292226031Sstas	 * The filenames "." and ".." are handled specially, since they
293226031Sstas	 * don't follow dos filename rules.
294226031Sstas	 */
295226031Sstas	if (un[0] == '.' && unlen == 1) {
296226031Sstas		dn[0] = '.';
297226031Sstas		return;
298226031Sstas	}
299226031Sstas	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
300226031Sstas		dn[0] = '.';
301226031Sstas		dn[1] = '.';
302226031Sstas		return;
303226031Sstas	}
304226031Sstas
305226031Sstas	/*
306226031Sstas	 * Copy the unix filename into the dos filename string upto the end
307226031Sstas	 * of string, a '.', or 8 characters. Whichever happens first stops
308226031Sstas	 * us. This forms the name portion of the dos filename. Fold to
309226031Sstas	 * upper case.
310226031Sstas	 */
311226031Sstas	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
312226031Sstas		dn[i] = islower(c) ? toupper(c) : c;
313226031Sstas		un++;
314226031Sstas		unlen--;
315226031Sstas	}
316226031Sstas
317226031Sstas	/*
318226031Sstas	 * If the first char of the filename is 0xe5, then translate it to
319226031Sstas	 * 0x05.  This is because 0xe5 is the marker for a deleted
320226031Sstas	 * directory slot.  I guess this means you can't have filenames
321226031Sstas	 * that start with 0x05.  I suppose we should check for this and
322226031Sstas	 * doing something about it.
323226031Sstas	 */
324226031Sstas	if (dn[0] == SLOT_DELETED)
325226031Sstas		dn[0] = SLOT_E5;
326226031Sstas
327226031Sstas	/*
328226031Sstas	 * Strip any further characters up to a '.' or the end of the
329226031Sstas	 * string.
330226031Sstas	 */
331226031Sstas	while (unlen && (c = *un)) {
332226031Sstas		un++;
333226031Sstas		unlen--;
334226031Sstas		/* Make sure we've skipped over the dot before stopping. */
335226031Sstas		if (c == '.')
336226031Sstas			break;
337226031Sstas	}
338226031Sstas
339226031Sstas	/*
340226031Sstas	 * Copy in the extension part of the name, if any. Force to upper
341226031Sstas	 * case. Note that the extension is allowed to contain '.'s.
342226031Sstas	 * Filenames in this form are probably inaccessable under dos.
343226031Sstas	 */
344226031Sstas	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
345226031Sstas		dn[i] = islower(c) ? toupper(c) : c;
346226031Sstas		un++;
347226031Sstas		unlen--;
348226031Sstas	}
349226031Sstas}
350226031Sstas
351226031Sstas/*
352226031Sstas * Get rid of these macros before someone discovers we are using such
353226031Sstas * hideous things.
354226031Sstas */
355226031Sstas#undef	isupper
356226031Sstas#undef	islower
357226031Sstas#undef	toupper
358226031Sstas#undef	tolower
359226031Sstas