msdosfs_conv.c revision 5083
1/*	$Id: msdosfs_conv.c,v 1.2 1994/09/27 20:42:42 phk Exp $ */
2/*	$NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $	*/
3
4/*
5 * Written by Paul Popelka (paulp@uts.amdahl.com)
6 *
7 * You can do anything you want with this software, just don't say you wrote
8 * it, and don't remove this notice.
9 *
10 * This software is provided "as is".
11 *
12 * The author supplies this software to be publicly redistributed on the
13 * understanding that the author is not responsible for the correct
14 * functioning of this software in any circumstances and is not liable for
15 * any damages caused by this software.
16 *
17 * October 1992
18 */
19
20/*
21 * System include files.
22 */
23#include <sys/param.h>
24#include <sys/time.h>
25#include <sys/kernel.h>		/* defines tz */
26#include <sys/systm.h>		/* defines tz */
27
28/*
29 * MSDOSFS include files.
30 */
31#include <msdosfs/direntry.h>
32
33/*
34 * Days in each month in a regular year.
35 */
36u_short regyear[] = {
37	31, 28, 31, 30, 31, 30,
38	31, 31, 30, 31, 30, 31
39};
40
41/*
42 * Days in each month in a leap year.
43 */
44u_short leapyear[] = {
45	31, 29, 31, 30, 31, 30,
46	31, 31, 30, 31, 30, 31
47};
48
49/*
50 * Variables used to remember parts of the last time conversion.  Maybe we
51 * can avoid a full conversion.
52 */
53u_long lasttime;
54u_long lastday;
55u_short lastddate;
56u_short lastdtime;
57
58/*
59 * Convert the unix version of time to dos's idea of time to be used in
60 * file timestamps. The passed in unix time is assumed to be in GMT.
61 */
62void
63unix2dostime(tsp, ddp, dtp)
64	struct timespec *tsp;
65	u_short *ddp;
66	u_short *dtp;
67{
68	u_long t;
69	u_long days;
70	u_long inc;
71	u_long year;
72	u_long month;
73	u_short *months;
74
75	/*
76	 * If the time from the last conversion is the same as now, then
77	 * skip the computations and use the saved result.
78	 */
79	t = tsp->ts_sec - (tz.tz_minuteswest * 60)
80	     /* +- daylight savings time correction */ ;
81	if (lasttime != t) {
82		lasttime = t;
83		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
84		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
85		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);
86
87		/*
88		 * If the number of days since 1970 is the same as the last
89		 * time we did the computation then skip all this leap year
90		 * and month stuff.
91		 */
92		days = t / (24 * 60 * 60);
93		if (days != lastday) {
94			lastday = days;
95			for (year = 1970;; year++) {
96				inc = year & 0x03 ? 365 : 366;
97				if (days < inc)
98					break;
99				days -= inc;
100			}
101			months = year & 0x03 ? regyear : leapyear;
102			for (month = 0; month < 12; month++) {
103				if (days < months[month])
104					break;
105				days -= months[month];
106			}
107			lastddate = ((days + 1) << DD_DAY_SHIFT)
108			    + ((month + 1) << DD_MONTH_SHIFT);
109			/*
110			 * Remember dos's idea of time is relative to 1980.
111			 * unix's is relative to 1970.  If somehow we get a
112			 * time before 1980 then don't give totally crazy
113			 * results.
114			 */
115			if (year > 1980)
116				lastddate += (year - 1980) << DD_YEAR_SHIFT;
117		}
118	}
119	*dtp = lastdtime;
120	*ddp = lastddate;
121}
122
123/*
124 * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
125 * interval there were 8 regular years and 2 leap years.
126 */
127#define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))
128
129u_short lastdosdate;
130u_long lastseconds;
131
132/*
133 * Convert from dos' idea of time to unix'. This will probably only be
134 * called from the stat(), and fstat() system calls and so probably need
135 * not be too efficient.
136 */
137void
138dos2unixtime(dd, dt, tsp)
139	u_short dd;
140	u_short dt;
141	struct timespec *tsp;
142{
143	u_long seconds;
144	u_long m, month;
145	u_long y, year;
146	u_long days;
147	u_short *months;
148
149	seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
150	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
151	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
152	/*
153	 * If the year, month, and day from the last conversion are the
154	 * same then use the saved value.
155	 */
156	if (lastdosdate != dd) {
157		lastdosdate = dd;
158		days = 0;
159		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
160		for (y = 0; y < year; y++) {
161			days += y & 0x03 ? 365 : 366;
162		}
163		months = year & 0x03 ? regyear : leapyear;
164		/*
165		 * Prevent going from 0 to 0xffffffff in the following
166		 * loop.
167		 */
168		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
169		if (month == 0) {
170			printf(
171			    "dos2unixtime(): month value out of range (%ld)\n",
172			    month);
173			month = 1;
174		}
175		for (m = 0; m < month - 1; m++) {
176			days += months[m];
177		}
178		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
179		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
180	}
181	tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
182	     /* -+ daylight savings time correction */ ;
183	tsp->ts_nsec = 0;
184}
185
186/*
187 * Cheezy macros to do case detection and conversion for the ascii
188 * character set.  DOESN'T work for ebcdic.
189 */
190#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
191#define	islower(c)	(c >= 'a'  &&  c <= 'z')
192#define	toupper(c)	(c & ~' ')
193#define	tolower(c)	(c | ' ')
194
195/*
196 * DOS filenames are made of 2 parts, the name part and the extension part.
197 * The name part is 8 characters long and the extension part is 3
198 * characters long.  They may contain trailing blanks if the name or
199 * extension are not long enough to fill their respective fields.
200 */
201
202/*
203 * Convert a DOS filename to a unix filename. And, return the number of
204 * characters in the resulting unix filename excluding the terminating
205 * null.
206 */
207int
208dos2unixfn(dn, un)
209	u_char dn[11];
210	u_char *un;
211{
212	int i;
213	int ni;
214	int ei;
215	int thislong = 0;
216	u_char c;
217	u_char *origun = un;
218
219	/*
220	 * Find the last character in the name portion of the dos filename.
221	 */
222	for (ni = 7; ni >= 0; ni--)
223		if (dn[ni] != ' ')
224			break;
225
226	/*
227	 * Find the last character in the extension portion of the
228	 * filename.
229	 */
230	for (ei = 10; ei >= 8; ei--)
231		if (dn[ei] != ' ')
232			break;
233
234	/*
235	 * Copy the name portion into the unix filename string. NOTE: DOS
236	 * filenames are usually kept in upper case.  To make it more unixy
237	 * we convert all DOS filenames to lower case.  Some may like this,
238	 * some may not.
239	 */
240	for (i = 0; i <= ni; i++) {
241		c = dn[i];
242		*un++ = isupper(c) ? tolower(c) : c;
243		thislong++;
244	}
245
246	/*
247	 * Now, if there is an extension then put in a period and copy in
248	 * the extension.
249	 */
250	if (ei >= 8) {
251		*un++ = '.';
252		thislong++;
253		for (i = 8; i <= ei; i++) {
254			c = dn[i];
255			*un++ = isupper(c) ? tolower(c) : c;
256			thislong++;
257		}
258	}
259	*un++ = 0;
260
261	/*
262	 * If first char of the filename is SLOT_E5 (0x05), then the real
263	 * first char of the filename should be 0xe5. But, they couldn't
264	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
265	 * directory slot. Another dos quirk.
266	 */
267	if (*origun == SLOT_E5)
268		*origun = 0xe5;
269
270	return thislong;
271}
272
273/*
274 * Convert a unix filename to a DOS filename. This function does not ensure
275 * that valid characters for a dos filename are supplied.
276 */
277void
278unix2dosfn(un, dn, unlen)
279	u_char *un;
280	u_char dn[11];
281	int unlen;
282{
283	int i;
284	u_char c;
285
286	/*
287	 * Fill the dos filename string with blanks. These are DOS's pad
288	 * characters.
289	 */
290	for (i = 0; i <= 10; i++)
291		dn[i] = ' ';
292
293	/*
294	 * The filenames "." and ".." are handled specially, since they
295	 * don't follow dos filename rules.
296	 */
297	if (un[0] == '.' && unlen == 1) {
298		dn[0] = '.';
299		return;
300	}
301	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
302		dn[0] = '.';
303		dn[1] = '.';
304		return;
305	}
306
307	/*
308	 * Copy the unix filename into the dos filename string upto the end
309	 * of string, a '.', or 8 characters. Whichever happens first stops
310	 * us. This forms the name portion of the dos filename. Fold to
311	 * upper case.
312	 */
313	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
314		dn[i] = islower(c) ? toupper(c) : c;
315		un++;
316		unlen--;
317	}
318
319	/*
320	 * If the first char of the filename is 0xe5, then translate it to
321	 * 0x05.  This is because 0xe5 is the marker for a deleted
322	 * directory slot.  I guess this means you can't have filenames
323	 * that start with 0x05.  I suppose we should check for this and
324	 * doing something about it.
325	 */
326	if (dn[0] == SLOT_DELETED)
327		dn[0] = SLOT_E5;
328
329	/*
330	 * Strip any further characters up to a '.' or the end of the
331	 * string.
332	 */
333	while (unlen && (c = *un)) {
334		un++;
335		unlen--;
336		/* Make sure we've skipped over the dot before stopping. */
337		if (c == '.')
338			break;
339	}
340
341	/*
342	 * Copy in the extension part of the name, if any. Force to upper
343	 * case. Note that the extension is allowed to contain '.'s.
344	 * Filenames in this form are probably inaccessable under dos.
345	 */
346	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
347		dn[i] = islower(c) ? toupper(c) : c;
348		un++;
349		unlen--;
350	}
351}
352
353/*
354 * Get rid of these macros before someone discovers we are using such
355 * hideous things.
356 */
357#undef	isupper
358#undef	islower
359#undef	toupper
360#undef	tolower
361