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