2 * fat-handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2015 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
29 * characters allowed in a new name:
34 const UBYTE allowed_ascii
[] =
36 0, 0, 0, 0, 0, 0, 0, 0,
37 0, 0, 0, 0, 0, 0, 0, 0,
38 0, 0, 0, 0, 0, 0, 0, 0,
39 0, 0, 0, 0, 0, 0, 0, 0,
40 1, 2, 0, 2, 2, 2, 2, 2,
41 2, 2, 0, 1, 1, 2, 1, 0,
42 2, 2, 2, 2, 2, 2, 2, 2,
43 2, 2, 0, 1, 0, 1, 0, 0,
44 2, 2, 2, 2, 2, 2, 2, 2,
45 2, 2, 2, 2, 2, 2, 2, 2,
46 2, 2, 2, 2, 2, 2, 2, 2,
47 2, 2, 2, 1, 0, 1, 2, 2,
48 2, 2, 2, 2, 2, 2, 2, 2,
49 2, 2, 2, 2, 2, 2, 2, 2,
50 2, 2, 2, 2, 2, 2, 2, 2,
51 2, 2, 2, 2, 0, 2, 2, 0
54 LONG
GetDirEntryShortName(struct DirEntry
*de
, STRPTR name
, ULONG
*len
)
59 /* make sure the entry is good */
60 raw
= de
->e
.entry
.name
;
61 if (raw
[0] == 0x00 || raw
[0] == 0xe5 || raw
[0] == 0x20)
63 D(bug("[fat] entry name has first byte 0x%02x,"
64 " returning empty short name\n", raw
[0]));
71 bug("[fat] extracting short name for name '");
72 RawPutChars(raw
, FAT_MAX_SHORT_NAME
);
73 bug("' (index %ld)\n", de
->index
);
76 /* copy the chars into the return string */
78 for (i
= 0; i
< FAT_MAX_SHORT_NAME
; i
++)
83 * fat names are weird. the name FOO.BAR is stored as "FOO BAR".
84 * in that case we've already copied in all the spaces, and we have to
85 * backup and insert the dot.
87 * note that spaces (0x20) are allowed in the filename, just not as the
88 * first char. see FATdoc 1.03 p24 for the details. most people don't
89 * know that about fat names. the point of this is to say that we
90 * can't just flick forward in our copying at the first sight of a
91 * space, it's technically incorrect.
95 /* backtrack to first non-space. this is safe because the first
96 * char won't be space, we checked above */
100 /* forward one and drop in the dot */
109 /* remove any trailing spaces, and perhaps a trailing . */
110 while (c
[-1] == 0x20)
115 /* apply official hack for Japanese names */
124 bug("[fat] extracted short name '");
125 RawPutChars(name
, *len
);
132 LONG
GetDirEntryLongName(struct DirEntry
*short_de
, STRPTR name
,
135 struct Globals
*glob
= short_de
->sb
->glob
;
146 /* make sure the entry is good */
147 raw
= short_de
->e
.entry
.name
;
148 if (raw
[0] == 0x00 || raw
[0] == 0xe5 || raw
[0] == 0x20)
150 D(bug("[fat] entry name has first byte 0x%02x,"
151 " returning empty long name\n", raw
[0]));
157 D(bug("[fat] looking for long name for name '%.*s' (index %ld)\n",
158 FAT_MAX_SHORT_NAME
, raw
, short_de
->index
));
160 /* compute the short name checksum. this value is held in every associated
161 * long name entry to help us identify it. see FATdoc 1.03 p28 */
162 CALC_SHORT_NAME_CHECKSUM(raw
, checksum
);
164 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
166 /* get a handle on the directory */
167 InitDirHandle(short_de
->sb
, short_de
->cluster
, &dh
, FALSE
);
169 /* loop over the long name entries */
172 index
= short_de
->index
- 1;
175 D(bug("[fat] looking for long name order 0x%02x in entry %ld\n",
178 if ((err
= GetDirEntry(&dh
, index
, &de
)) != 0)
181 /* make sure it's valid */
182 if (!((de
.e
.entry
.attr
& ATTR_LONG_NAME_MASK
) == ATTR_LONG_NAME
) ||
183 (de
.e
.long_entry
.order
& ~0x40) != order
||
184 de
.e
.long_entry
.checksum
!= checksum
)
187 D(bug("[fat] bad long name entry %ld"
188 " (attr 0x%02x order 0x%02x checksum 0x%02x)\n",
189 index
, de
.e
.entry
.attr
, de
.e
.long_entry
.order
,
190 de
.e
.long_entry
.checksum
));
192 err
= ERROR_OBJECT_NOT_FOUND
;
196 /* copy the characters into the name buffer. note that filename
197 * entries can have null-termination, but don't have to. we take the
198 * easy way out - copy everything, and bolt on an additional null just
201 /* XXX these are in UTF-16, but we're just taking the bottom byte.
202 * that works well enough but is still a hack. if our dos ever
203 * supports unicode this should be revisited */
204 for (i
= 0; i
< 5; i
++)
206 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name1
[i
])];
209 for (i
= 0; i
< 6; i
++)
211 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name2
[i
])];
214 for (i
= 0; i
< 2; i
++)
216 *c
= glob
->from_unicode
[AROS_LE2WORD(de
.e
.long_entry
.name3
[i
])];
220 /* if this is the last entry, clean up and get us out of here */
221 if (de
.e
.long_entry
.order
& 0x40)
224 *len
= strlen((char *)buf
);
225 CopyMem(buf
, name
, *len
);
227 D(bug("[fat] extracted long name '%s'\n", buf
));
229 ReleaseDirHandle(&dh
);
238 ReleaseDirHandle(&dh
);
240 D(bug("[fat] long name construction failed\n"));
242 return ERROR_OBJECT_NOT_FOUND
;
245 /* set the name of an entry. this will set the long name too. it assumes
246 * that there is room before the entry to store the long filename. if there
247 * isn't the whole thing will fail */
248 LONG
SetDirEntryName(struct DirEntry
*short_de
, STRPTR name
, ULONG len
)
250 struct Globals
*glob
= short_de
->sb
->glob
;
251 UBYTE basis
[FAT_MAX_SHORT_NAME
];
253 LONG src
, dst
, i
, left
, root_end
;
254 ULONG seq
= 0, cur
= 0;
263 bug("[fat] setting name for entry index %ld to '", short_de
->index
);
264 RawPutChars(name
, len
);
268 /* discard leading spaces */
269 while (len
> 0 && name
[0] == ' ')
272 /* discard trailing spaces and dots */
273 while (len
> 0 && (name
[len
- 1] == ' ' || name
[len
- 1] == '.'))
276 /* check for empty name and reserved names "." and ".." */
277 if (len
== 0 || (name
[0] == '.'
278 && (len
== 1 || (name
[1] == '.' && len
== 2))))
279 return ERROR_INVALID_COMPONENT_NAME
;
281 /* check for illegal characters */
282 for (i
= 0; i
< len
; i
++)
283 if (name
[i
] <= 127 && allowed_ascii
[name
[i
]] == 0)
284 return ERROR_INVALID_COMPONENT_NAME
;
286 nlong
= NumLongNameEntries(name
, len
);
287 D(bug("[fat] name requires %ld long name entries\n", nlong
));
289 /* first we generate the "basis name" of the passed in name. XXX we just
290 * take the first eight characters and any three-letter extension and mash
291 * them together. FATDoc 1.03 p30-31 outlines a more comprehensive
292 * algorithm that handles unicode, but we're not doing unicode yet */
296 /* strip off leading periods */
297 for (src
= 0; src
< len
; src
++)
298 if (name
[src
] != '.')
303 /* copy the first eight chars in, ignoring spaces and stopping at period */
306 while (src
< len
&& dst
< 8 && name
[src
] != '.')
308 if (name
[src
] != ' ')
310 if (allowed_ascii
[name
[src
]] == 1)
316 basis
[dst
] = toupper(name
[src
]);
323 /* if there were more bytes available, then we need a tail later */
324 if (src
< len
&& name
[src
] != '.')
327 /* make a note of the length of the left side. this gets used further down
328 * to determine the position to add the tail */
331 /* remember the current value of src for the multiple-dot check below */
334 /* pad the rest of the left side with spaces */
335 for (; dst
< 8; dst
++)
338 /* now go to the end and track back looking for a dot */
339 for (i
= len
- 1; i
>= src
&& name
[i
] != '.'; i
--);
344 /* if this isn't the same dot we found earlier, then we need a tail */
348 /* first char after the dot */
352 while (src
< len
&& dst
< FAT_MAX_SHORT_NAME
)
354 if (name
[src
] != ' ')
356 if (allowed_ascii
[name
[src
]] == 1)
362 basis
[dst
] = toupper(name
[src
]);
368 /* if there were more bytes available, then we'll need a tail later */
373 /* pad the rest of the right side with spaces */
374 for (; dst
< FAT_MAX_SHORT_NAME
; dst
++)
377 D(bug("[fat] basis name is '%.*s'\n", FAT_MAX_SHORT_NAME
, basis
));
379 /* get a fresh handle on the current directory */
380 InitDirHandle(short_de
->sb
, short_de
->cluster
, &dh
, FALSE
);
382 /* if the name will require one or more entries, then our basis name is
383 * actually some conversion of the real name, and we have to look to make
384 * sure it's not in use */
387 D(bug("[fat] searching for basis name to confirm that"
388 " it's not in use\n"));
390 /* loop over the entries and compare them with the basis until we find
394 /* build a new tail if necessary */
397 sprintf(tail
, "~%lu", (unsigned long)seq
);
398 while (left
+ strlen(tail
) > 8)
400 CopyMem(tail
, &basis
[left
], strlen(tail
));
403 D(bug("[fat] new basis name is '%.*s'\n",
404 FAT_MAX_SHORT_NAME
, basis
));
407 /* get the next entry, and bail if we hit the end of the dir */
408 if ((err
= GetNextDirEntry(&dh
, &de
)) == ERROR_OBJECT_NOT_FOUND
)
411 /* abort on any other error */
414 ReleaseDirHandle(&dh
);
418 /* compare the two names */
419 D(bug("[fat] comparing '%.*s' with '%.*s'\n",
420 FAT_MAX_SHORT_NAME
, basis
,
421 FAT_MAX_SHORT_NAME
, de
.e
.entry
.name
));
422 for (i
= 0; i
< FAT_MAX_SHORT_NAME
; i
++)
423 if (de
.e
.entry
.name
[i
] != basis
[i
])
426 /* if we reached the end, then our current basis is in use and we
427 * need to generate a new one and start again */
428 if (i
== FAT_MAX_SHORT_NAME
)
431 RESET_DIRHANDLE(&dh
);
435 D(bug("[fat] basis name '%.*s' not in use, using it\n",
436 FAT_MAX_SHORT_NAME
, basis
));
439 /* copy the new name into the original entry. we don't write it out -
440 * we'll leave that for the caller to do, it's his entry */
441 CopyMem(basis
, short_de
->e
.entry
.name
, FAT_MAX_SHORT_NAME
);
443 /* we can stop here if no long name is required */
446 D(bug("[fat] copied short name and long name not required,"
448 ReleaseDirHandle(&dh
);
452 /* compute the short name checksum */
453 CALC_SHORT_NAME_CHECKSUM(basis
, checksum
);
455 D(bug("[fat] short name checksum is 0x%02x\n", checksum
));
457 /* now we loop back over the previous entries and fill them in with
458 * long name components */
460 de
.index
= short_de
->index
;
464 /* get the previous entry */
465 if ((err
= GetDirEntry(&dh
, de
.index
- 1, &de
)) != 0)
467 ReleaseDirHandle(&dh
);
471 /* it must be unused (or end of directory) */
472 if (de
.e
.entry
.name
[0] != 0xe5 && de
.e
.entry
.name
[0] != 0x00)
474 D(bug("[fat] index %ld appears to be in use, aborting long name\n",
477 /* clean up any long name entries we already added */
478 while ((err
= GetDirEntry(&dh
, de
.index
+ 1, &de
)) == 0 &&
479 (de
.e
.entry
.attr
& ATTR_LONG_NAME_MASK
))
481 de
.e
.entry
.name
[0] = 0xe5;
482 if ((err
= UpdateDirEntry(&de
)) != 0)
485 ReleaseDirHandle(&dh
);
490 ReleaseDirHandle(&dh
);
491 return ERROR_NO_FREE_STORE
;
494 D(bug("[fat] building long name entry %ld\n", de
.index
));
497 for (dst
= 0; dst
< 5; dst
++)
499 de
.e
.long_entry
.name1
[dst
] =
500 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
501 (src
++ == len
? 0x0000 : 0xffff);
504 for (dst
= 0; dst
< 6; dst
++)
506 de
.e
.long_entry
.name2
[dst
] =
507 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
508 (src
++ == len
? 0x0000 : 0xffff);
511 for (dst
= 0; dst
< 2; dst
++)
513 de
.e
.long_entry
.name3
[dst
] =
514 src
< len
? AROS_WORD2LE(glob
->to_unicode
[name
[src
++]]) :
515 (src
++ == len
? 0x0000 : 0xffff);
518 /* setup the rest of the entry */
519 de
.e
.long_entry
.order
= order
++;
520 de
.e
.long_entry
.attr
= ATTR_LONG_NAME
;
521 de
.e
.long_entry
.type
= 0;
522 de
.e
.long_entry
.checksum
= checksum
;
523 de
.e
.long_entry
.first_cluster_lo
= 0;
525 /* if we've reached the end then this is the last entry */
527 de
.e
.long_entry
.order
|= 0x40;
529 /* write the entry out */
532 D(bug("[fat] wrote long name entry %ld order 0x%02x\n", de
.index
,
533 de
.e
.long_entry
.order
));
536 ReleaseDirHandle(&dh
);
538 D(bug("[fat] successfully wrote short & long names\n"));
540 /* Set hidden flags on .info files */
541 if (strcmp(name
+ len
- 5, ".info") == 0)
542 short_de
->e
.entry
.attr
|= ATTR_HIDDEN
;
547 /* return the number of long name entries that are required to store this name */
548 ULONG
NumLongNameEntries(STRPTR name
, ULONG len
)
550 #if UPPERCASE_SHORT_NAMES
553 /* XXX because we don't handle unicode this is pretty simple - thirteen
554 * characters per long entry. if we ever support unicode, then this
555 * function will need to be changed to deal with each character and keep a
558 /* if the name is standard 8.3 (or less) then we don't need any long name
559 * entries - the name can be contained within the standard entry */
560 if (len
<= FAT_MAX_SHORT_NAME
+ 1)
564 for (i
= 0; i
< 8 && i
< len
; i
++)
568 if (name
[i
] != toupper(name
[i
]))
570 if (allowed_ascii
[name
[i
]] == 1)
580 for (i
= 0; i
< 3 && left
+ 1 + i
< len
; i
++)
581 if (name
[left
+ 1 + i
] != toupper(name
[left
+ 1 + i
])
582 || allowed_ascii
[name
[i
]] == 1)
585 if (left
+ 1 + i
== len
)
591 return ((len
- 1) / 13) + 1;