Deleted Added
full compact
msdosfs_conv.c (22975) msdosfs_conv.c (33181)
1/* $Id$ */
1/* $Id: msdosfs_conv.c,v 1.13 1997/02/22 09:40:46 peter 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 */
37static 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 */
45static 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 */
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 */
37static 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 */
45static 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 */
54u_long lasttime;
55u_long lastday;
56u_short lastddate;
57u_short lastdtime;
54static u_long lasttime;
55static u_long lastday;
56static u_short lastddate;
57static 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 */
63void
64unix2dostime(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
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 */
63void
64unix2dostime(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
130u_short lastdosdate;
131u_long lastseconds;
130static u_short lastdosdate;
131static 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 */
138void
139dos2unixtime(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 */
205int
206dos2unixfn(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 */
275void
276unix2dosfn(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
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 */
138void
139dos2unixtime(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 */
205int
206dos2unixfn(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 */
275void
276unix2dosfn(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