2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2011 The AROS Development Team
7 * This program is free software; you can redistribute it and/or modify it
8 * under the same terms as AROS itself.
13 #include <aros/macros.h>
14 #include <exec/types.h>
16 #include <proto/exec.h>
23 #include "fat_protos.h"
25 #define DEBUG DEBUG_NAMES
28 LONG
GetDirEntryShortName(struct DirEntry
*de
, STRPTR name
, ULONG
*len
) {
32 /* make sure the entry is good */
33 raw
= de
->e
.entry
.name
;
34 if (raw
[0] == 0x00 || raw
[0] == 0xe5 || raw
[0] == 0x20) {
35 D(bug("[fat] entry name has first byte 0x%02x, returning empty short name\n", raw
[0]));
41 D(bug("[fat] extracting short name for name '"); RawPutChars(raw
, 11);
42 bug("' (index %ld)\n", de
->index
));
44 /* copy the chars into the return string */
46 for (i
= 0; i
< 11; i
++) {
50 * fat names are weird. the name FOO.BAR is stored as "FOO BAR".
51 * in that case we've already copied in all the spaces, and we have to
52 * backup and insert the dot.
54 * note that spaces (0x20) are allowed in the filename, just not as the
55 * first char. see FATdoc 1.03 p24 for the details. most people don't
56 * know that about fat names. the point of this is to say that we
57 * can't just flick forward in our copying at the first sight of a
58 * space, it's technically incorrect.
61 /* backtrack to first non-space. this is safe because the first
62 * char won't be space, we checked above */
63 while (*c
== 0x20) c
--;
65 /* forward one and drop in the dot */
74 /* remove any trailing spaces, and perhaps a trailing . */
75 while (c
[-1] == 0x20) c
--;
76 if (c
[-1] == '.') c
--;
78 /* apply official hack for Japanese names */
79 if (*name
== 0x05) *name
= 0xe5;
85 D(bug("[fat] extracted short name '"); RawPutChars(name
, *len
); bug("'\n"));
90 LONG
GetDirEntryLongName(struct DirEntry
*short_de
, STRPTR name
, ULONG
*len
) {
101 /* make sure the entry is good */
102 raw
= short_de
->e
.entry
.name
;
103 if (raw
[0] == 0x00 || raw
[0] == 0xe5 || raw
[0] == 0x20) {
104 D(bug("[fat] entry name has first byte 0x%02x, returning empty long name\n", raw
[0]));
110 D(bug("[fat] looking for long name for name '%.11s' (index %ld)\n", raw
, short_de
->index
));
112 /* compute the short name checksum. this value is held in every associated
113 * long name entry to help us identify it. see FATdoc 1.03 p28 */
114 CALC_SHORT_NAME_CHECKSUM(raw
, checksum
);
116 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
118 /* get a handle on the directory */
119 InitDirHandle(short_de
->sb
, short_de
->cluster
, &dh
, FALSE
);
121 /* loop over the long name entries */
124 index
= short_de
->index
- 1;
126 D(bug("[fat] looking for long name order 0x%02x in entry %ld\n", order
, index
));
128 if ((err
= GetDirEntry(&dh
, index
, &de
)) != 0)
131 /* make sure it's valid */
132 if (!((de
.e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
133 (de
.e
.long_entry
.order
& ~0x40) != order
||
134 de
.e
.long_entry
.checksum
!= checksum
) {
136 D(bug("[fat] bad long name entry %ld (attr 0x%02x order 0x%02x checksum 0x%02x)\n",
137 index
, de
.e
.entry
.attr
, de
.e
.long_entry
.order
, de
.e
.long_entry
.checksum
));
139 err
= ERROR_OBJECT_NOT_FOUND
;
143 /* copy the characters into the name buffer. note that filename
144 * entries can have null-termination, but don't have to. we take the
145 * easy way out - copy everything, and bolt on an additional null just
148 /* XXX these are in UTF-16, but we're just taking the bottom byte.
149 * that works well enough but is still a hack. if our dos ever
150 * supports unicode this should be revisited */
151 for (i
= 0; i
< 5; i
++) {
152 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name1
[i
])];
155 for (i
= 0; i
< 6; i
++) {
156 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name2
[i
])];
159 for (i
= 0; i
< 2; i
++) {
160 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name3
[i
])];
164 /* if this is the last entry, clean up and get us out of here */
165 if (de
.e
.long_entry
.order
& 0x40) {
167 *len
= strlen((char *) buf
);
168 CopyMem(buf
, name
, *len
);
170 D(bug("[fat] extracted long name '%s'\n", buf
));
172 ReleaseDirHandle(&dh
);
181 ReleaseDirHandle(&dh
);
183 D(bug("[fat] long name construction failed\n"));
185 return ERROR_OBJECT_NOT_FOUND
;
188 /* set the name of an entry. this will set the long name too. it assumes
189 * that there is room before the entry to store the long filename. if there
190 * isn't the whole thing will fail */
191 LONG
SetDirEntryName(struct DirEntry
*short_de
, STRPTR name
, ULONG len
) {
194 ULONG src
, dst
, i
, left
;
195 ULONG seq
= 0, cur
= 0;
203 D(bug("[fat] setting name for entry index %ld to '", short_de
->index
);
204 RawPutChars(name
, len
); bug("'\n"));
206 nlong
= NumLongNameEntries(name
, len
);
207 D(bug("[fat] name requires %ld long name entries\n", nlong
));
209 /* first we generate the "basis name" of the passed in name. XXX we just
210 * take the first eight characters and any three-letter extension and mash
211 * them together. FATDoc 1.03 p30-31 outlines a more comprehensive
212 * algorithm that handles unicode, but we're not doing unicode yet */
216 /* strip off leading spaces and periods */
217 for (src
= 0; src
< len
; src
++)
218 if (name
[src
] != ' ' && name
[src
] != '.')
221 /* copy the first eight chars in, ignoring spaces and stopping at period */
223 while (src
< len
&& dst
< 8 && name
[src
] != '.') {
224 if (name
[src
] != ' ') {
225 basis
[dst
] = toupper(name
[src
]);
226 if (basis
[dst
] != name
[src
])
234 /* if there was more bytes available, then we need a tail later */
235 if (src
< len
&& name
[src
] != '.')
238 /* make a note of the length of the left side. this gets used further down
239 * to determine the position to add the tail */
242 /* remember the current value of src for the multiple-dot check below */
245 /* pad the rest of the left side with spaces */
246 for (; dst
< 8; dst
++)
249 /* now go to the end and track back looking for a dot */
250 for (src
= len
-1; src
>= 0 && name
[src
] != '.'; src
--);
254 /* if this isn't the same dot we found earlier, then we need a tail */
258 /* first char after the dot */
262 while(src
< len
&& dst
< 11) {
263 if (name
[src
] != ' ') {
264 basis
[dst
] = toupper(name
[src
]);
265 if (basis
[dst
] != name
[src
])
273 /* if there were more bytes available, then we'll need a tail later */
277 /* pad the rest of the right side with spaces */
278 for (; dst
< 11; dst
++)
281 D(bug("[fat] basis name is '%.11s'\n", basis
));
283 /* get a fresh handle on the current directory */
284 InitDirHandle(short_de
->sb
, short_de
->cluster
, &dh
, FALSE
);
286 /* if the name will require one or more entries, then our basis name is
287 * actually some conversion of the real name, and we have to look to make
288 * sure it's not in use */
290 D(bug("[fat] searching for basis name to confirm that it's not in use\n"));
292 /* loop over the entries and compare them with the basis until we find
295 /* build a new tail if necessary */
297 sprintf(tail
, "~%lu", (unsigned long)seq
);
298 while (left
+ strlen(tail
) > 8) left
--;
299 CopyMem(tail
, &basis
[left
], strlen(tail
));
302 D(bug("[fat] new basis name is '%.11s'\n", basis
));
305 /* get the next entry, and bail if we hit the end of the dir */
306 if ((err
= GetNextDirEntry(&dh
, &de
)) == ERROR_OBJECT_NOT_FOUND
)
309 /* abort on any other error */
311 ReleaseDirHandle(&dh
);
315 /* compare the two names */
316 D(bug("[fat] comparing '%.11s' with '%.11s'\n", basis
,
318 for (i
= 0; i
< 11; i
++)
319 if (de
.e
.entry
.name
[i
] != basis
[i
])
322 /* if we reached the end, then our current basis is in use and we
323 * need to generate a new one and start again */
330 D(bug("[fat] basis name '%.11s' not in use, using it\n", basis
));
333 /* copy the new name into the original entry. we don't write it out -
334 * we'll leave that for the caller to do, it's his entry */
335 CopyMem(basis
, short_de
->e
.entry
.name
, 11);
337 /* we can stop here if no long name is required */
339 D(bug("[fat] copied short name and long name not required, we're done\n"));
340 ReleaseDirHandle(&dh
);
344 /* compute the short name checksum */
345 CALC_SHORT_NAME_CHECKSUM(basis
, checksum
);
347 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
349 /* now we loop back over the previous entries and fill them in with
350 * long name components */
352 de
.index
= short_de
->index
;
355 /* get the previous entry */
356 if ((err
= GetDirEntry(&dh
, de
.index
-1, &de
)) != 0) {
357 ReleaseDirHandle(&dh
);
361 /* it must be unused (or end of directory) */
362 if (de
.e
.entry
.name
[0] != 0xe5 && de
.e
.entry
.name
[0] != 0x00) {
363 D(bug("[fat] index %ld appears to be in use, aborting long name\n", de
.index
));
365 /* clean up any long name entries we already added */
366 while ((err
= GetDirEntry(&dh
, de
.index
+1, &de
)) == 0 &&
367 (de
.e
.entry
.attr
& ATTR_LONG_NAME_MASK
)) {
368 de
.e
.entry
.name
[0] = 0xe5;
369 if ((err
= UpdateDirEntry(&de
)) != 0) {
371 ReleaseDirHandle(&dh
);
376 ReleaseDirHandle(&dh
);
377 return ERROR_NO_FREE_STORE
;
380 D(bug("[fat] building long name entry %ld\n", de
.index
));
383 for (dst
= 0; dst
< 5; dst
++) {
384 de
.e
.long_entry
.name1
[dst
] =
385 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
386 (src
++ == len
? 0x0000 : 0xffff);
389 for (dst
= 0; dst
< 6; dst
++) {
390 de
.e
.long_entry
.name2
[dst
] =
391 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
392 (src
++ == len
? 0x0000 : 0xffff);
395 for (dst
= 0; dst
< 2; dst
++) {
396 de
.e
.long_entry
.name3
[dst
] =
397 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
398 (src
++ == len
? 0x0000 : 0xffff);
401 /* setup the rest of the entry */
402 de
.e
.long_entry
.order
= order
++;
403 de
.e
.long_entry
.attr
= ATTR_LONG_NAME
;
404 de
.e
.long_entry
.type
= 0;
405 de
.e
.long_entry
.checksum
= checksum
;
406 de
.e
.long_entry
.first_cluster_lo
= 0;
408 /* if we've reached the end then this is the last entry */
410 de
.e
.long_entry
.order
|= 0x40;
412 /* write the entry out */
415 D(bug("[fat] wrote long name entry %ld order 0x%02x\n", de
.index
, de
.e
.long_entry
.order
));
418 ReleaseDirHandle(&dh
);
420 D(bug("[fat] successfully wrote short & long names\n"));
422 /* Set hidden flags on .info files */
423 if (strcmp(name
+ len
- 5, ".info") == 0)
424 short_de
->e
.entry
.attr
|= ATTR_HIDDEN
;
429 /* return the number of long name entries that are required to store this name */
430 ULONG
NumLongNameEntries(STRPTR name
, ULONG len
) {
431 #if UPPERCASE_SHORT_NAMES
434 /* XXX because we don't handle unicode this is pretty simple - thirteen
435 * characters per long entry. if we ever support unicode, then this
436 * function will need to be changed to deal with each character and keep a
439 /* if the name is standard 8.3 (or less) then we don't need any long name
440 * entries - the name can be contained within the standard entry */
444 for (i
= 0; i
< 8 && i
< len
; i
++) {
447 if (name
[i
] != toupper(name
[i
]))
455 if (name
[i
] == '.') {
456 for (i
= 0; i
< 3 && left
+ 1 + i
< len
; i
++)
457 if (name
[left
+1+i
] != toupper(name
[left
+1+i
]))
460 if (left
+ 1 + i
== len
)
466 return ((len
-1) / 13) + 1;