xref: /freebsd/sys/fs/msdosfs/msdosfs_conv.c (revision 17ee9d00bc1ae1e598c38f25826f861e4bc6c3ce)
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  */
36 u_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  */
44 u_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  */
53 u_long lasttime;
54 u_long lastday;
55 u_short lastddate;
56 u_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  */
62 void
63 unix2dostime(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 
129 u_short lastdosdate;
130 u_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  */
137 void
138 dos2unixtime(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  */
207 int
208 dos2unixfn(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  */
277 void
278 unix2dosfn(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