aboutsummaryrefslogtreecommitdiff
path: root/sys/fs/msdosfs/msdosfs_conv.c
blob: d351a4809f901836dba36f367817758c51865ba6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*	$Id: msdosfs_conv.c,v 1.3 1994/12/12 12:35:42 bde Exp $ */
/*	$NetBSD: msdosfs_conv.c,v 1.6.2.1 1994/08/30 02:27:57 cgd Exp $	*/

/*
 * Written by Paul Popelka (paulp@uts.amdahl.com)
 * 
 * You can do anything you want with this software, just don't say you wrote
 * it, and don't remove this notice.
 * 
 * This software is provided "as is".
 * 
 * The author supplies this software to be publicly redistributed on the
 * understanding that the author is not responsible for the correct
 * functioning of this software in any circumstances and is not liable for
 * any damages caused by this software.
 * 
 * October 1992
 */

/*
 * System include files.
 */
#include <sys/param.h>
#include <sys/time.h>
#include <sys/kernel.h>		/* defines tz */
#include <sys/systm.h>		/* defines tz */
#include <machine/clock.h>

/*
 * MSDOSFS include files.
 */
#include <msdosfs/direntry.h>

/*
 * Total number of days that have passed for each month in a regular year.
 */
u_short regyear[] = {
	31, 59, 90, 120, 151, 181,
	212, 243, 273, 304, 334, 365
};

/*
 * Total number of days that have passed for each month in a leap year.
 */
u_short leapyear[] = {
	31, 60, 91, 121, 152, 182,
	213, 244, 274, 305, 335, 366
};

/*
 * Variables used to remember parts of the last time conversion.  Maybe we
 * can avoid a full conversion.
 */
u_long lasttime;
u_long lastday;
u_short lastddate;
u_short lastdtime;

/*
 * Convert the unix version of time to dos's idea of time to be used in
 * file timestamps. The passed in unix time is assumed to be in GMT.
 */
void
unix2dostime(tsp, ddp, dtp)
	struct timespec *tsp;
	u_short *ddp;
	u_short *dtp;
{
	u_long t;
	u_long days;
	u_long inc;
	u_long year;
	u_long month;
	u_short *months;

	/*
	 * If the time from the last conversion is the same as now, then
	 * skip the computations and use the saved result.
	 */
	t = tsp->ts_sec - (tz.tz_minuteswest * 60) - adjkerntz;
	     /* +- daylight savings time correction */ ;
	if (lasttime != t) {
		lasttime = t;
		lastdtime = (((t % 60) >> 1) << DT_2SECONDS_SHIFT)
		    + (((t / 60) % 60) << DT_MINUTES_SHIFT)
		    + (((t / 3600) % 24) << DT_HOURS_SHIFT);

		/*
		 * If the number of days since 1970 is the same as the last
		 * time we did the computation then skip all this leap year
		 * and month stuff.
		 */
		days = t / (24 * 60 * 60);
		if (days != lastday) {
			lastday = days;
			for (year = 1970;; year++) {
				inc = year & 0x03 ? 365 : 366;
				if (days < inc)
					break;
				days -= inc;
			}
			months = year & 0x03 ? regyear : leapyear;
			for (month = 0; days > months[month]; month++)
				;
			if (month > 0)
				days -= months[month - 1];
			lastddate = ((days + 1) << DD_DAY_SHIFT)
			    + ((month + 1) << DD_MONTH_SHIFT);
			/*
			 * Remember dos's idea of time is relative to 1980.
			 * unix's is relative to 1970.  If somehow we get a
			 * time before 1980 then don't give totally crazy
			 * results.
			 */
			if (year > 1980)
				lastddate += (year - 1980) << DD_YEAR_SHIFT;
		}
	}
	*dtp = lastdtime;
	*ddp = lastddate;
}

/*
 * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that
 * interval there were 8 regular years and 2 leap years.
 */
#define	SECONDSTO1980	(((8 * 365) + (2 * 366)) * (24 * 60 * 60))

u_short lastdosdate;
u_long lastseconds;

/*
 * Convert from dos' idea of time to unix'. This will probably only be
 * called from the stat(), and fstat() system calls and so probably need
 * not be too efficient.
 */
void
dos2unixtime(dd, dt, tsp)
	u_short dd;
	u_short dt;
	struct timespec *tsp;
{
	u_long seconds;
	u_long m, month;
	u_long y, year;
	u_long days;
	u_short *months;

	seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1)
	    + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60
	    + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600;
	/*
	 * If the year, month, and day from the last conversion are the
	 * same then use the saved value.
	 */
	if (lastdosdate != dd) {
		lastdosdate = dd;
		days = 0;
		year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT;
		days = year * 365;
		days += year / 4 + 1;	/* add in leap days */
		if ((year & 0x03) == 0)
			days--;		/* if year is a leap year */
		months = year & 0x03 ? regyear : leapyear;
		month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT;
		if (month < 1 || month > 12) {
			printf(
			    "dos2unixtime(): month value out of range (%ld)\n",
			    month);
			month = 1;
		}
		if (month > 1)
			days += months[month - 2];
		days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1;
		lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980;
	}
	tsp->ts_sec = seconds + lastseconds + (tz.tz_minuteswest * 60)
	     + adjkerntz 	/* -+ daylight savings time correction */ ;
	tsp->ts_nsec = 0;
}

/*
 * Cheezy macros to do case detection and conversion for the ascii
 * character set.  DOESN'T work for ebcdic.
 */
#define	isupper(c)	(c >= 'A'  &&  c <= 'Z')
#define	islower(c)	(c >= 'a'  &&  c <= 'z')
#define	toupper(c)	(c & ~' ')
#define	tolower(c)	(c | ' ')

/*
 * DOS filenames are made of 2 parts, the name part and the extension part.
 * The name part is 8 characters long and the extension part is 3
 * characters long.  They may contain trailing blanks if the name or
 * extension are not long enough to fill their respective fields.
 */

/*
 * Convert a DOS filename to a unix filename. And, return the number of
 * characters in the resulting unix filename excluding the terminating
 * null.
 */
int
dos2unixfn(dn, un)
	u_char dn[11];
	u_char *un;
{
	int i;
	int ni;
	int ei;
	int thislong = 0;
	u_char c;
	u_char *origun = un;

	/*
	 * Find the last character in the name portion of the dos filename.
	 */
	for (ni = 7; ni >= 0; ni--)
		if (dn[ni] != ' ')
			break;

	/*
	 * Find the last character in the extension portion of the
	 * filename.
	 */
	for (ei = 10; ei >= 8; ei--)
		if (dn[ei] != ' ')
			break;

	/*
	 * Copy the name portion into the unix filename string. NOTE: DOS
	 * filenames are usually kept in upper case.  To make it more unixy
	 * we convert all DOS filenames to lower case.  Some may like this,
	 * some may not.
	 */
	for (i = 0; i <= ni; i++) {
		c = dn[i];
		*un++ = isupper(c) ? tolower(c) : c;
		thislong++;
	}

	/*
	 * Now, if there is an extension then put in a period and copy in
	 * the extension.
	 */
	if (ei >= 8) {
		*un++ = '.';
		thislong++;
		for (i = 8; i <= ei; i++) {
			c = dn[i];
			*un++ = isupper(c) ? tolower(c) : c;
			thislong++;
		}
	}
	*un++ = 0;

	/*
	 * If first char of the filename is SLOT_E5 (0x05), then the real
	 * first char of the filename should be 0xe5. But, they couldn't
	 * just have a 0xe5 mean 0xe5 because that is used to mean a freed
	 * directory slot. Another dos quirk.
	 */
	if (*origun == SLOT_E5)
		*origun = 0xe5;

	return thislong;
}

/*
 * Convert a unix filename to a DOS filename. This function does not ensure
 * that valid characters for a dos filename are supplied.
 */
void
unix2dosfn(un, dn, unlen)
	u_char *un;
	u_char dn[11];
	int unlen;
{
	int i;
	u_char c;

	/*
	 * Fill the dos filename string with blanks. These are DOS's pad
	 * characters.
	 */
	for (i = 0; i <= 10; i++)
		dn[i] = ' ';

	/*
	 * The filenames "." and ".." are handled specially, since they
	 * don't follow dos filename rules.
	 */
	if (un[0] == '.' && unlen == 1) {
		dn[0] = '.';
		return;
	}
	if (un[0] == '.' && un[1] == '.' && unlen == 2) {
		dn[0] = '.';
		dn[1] = '.';
		return;
	}

	/*
	 * Copy the unix filename into the dos filename string upto the end
	 * of string, a '.', or 8 characters. Whichever happens first stops
	 * us. This forms the name portion of the dos filename. Fold to
	 * upper case.
	 */
	for (i = 0; i <= 7 && unlen && (c = *un) && c != '.'; i++) {
		dn[i] = islower(c) ? toupper(c) : c;
		un++;
		unlen--;
	}

	/*
	 * If the first char of the filename is 0xe5, then translate it to
	 * 0x05.  This is because 0xe5 is the marker for a deleted
	 * directory slot.  I guess this means you can't have filenames
	 * that start with 0x05.  I suppose we should check for this and
	 * doing something about it.
	 */
	if (dn[0] == SLOT_DELETED)
		dn[0] = SLOT_E5;

	/*
	 * Strip any further characters up to a '.' or the end of the
	 * string.
	 */
	while (unlen && (c = *un)) {
		un++;
		unlen--;
		/* Make sure we've skipped over the dot before stopping. */
		if (c == '.')
			break;
	}

	/*
	 * Copy in the extension part of the name, if any. Force to upper
	 * case. Note that the extension is allowed to contain '.'s.
	 * Filenames in this form are probably inaccessable under dos.
	 */
	for (i = 8; i <= 10 && unlen && (c = *un); i++) {
		dn[i] = islower(c) ? toupper(c) : c;
		un++;
		unlen--;
	}
}

/*
 * Get rid of these macros before someone discovers we are using such
 * hideous things.
 */
#undef	isupper
#undef	islower
#undef	toupper
#undef	tolower