Use the FAT_MAX_SHORT_NAME macro instead of hard-coded 11.
[AROS.git] / rom / filesys / fat / names.c
bloba82eb65809ffe1468a8fbcac03b463055e2755bf
1 /*
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.
10 * $Id$
13 #include <aros/macros.h>
14 #include <exec/types.h>
15 #include <dos/dos.h>
16 #include <proto/exec.h>
18 #include <string.h>
19 #include <stdio.h>
20 #include <ctype.h>
22 #include "fat_fs.h"
23 #include "fat_protos.h"
25 #define DEBUG DEBUG_NAMES
26 #include "debug.h"
29 * characters allowed in a new name:
30 * 0 = disallowed
31 * 1 = long names only
32 * 2 = allowed
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)
56 int i;
57 UBYTE *raw, *c;
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]));
65 *name = '\0';
66 len = 0;
67 return 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 */
77 c = name;
78 for (i = 0; i < FAT_MAX_SHORT_NAME; i++)
80 *c = tolower(raw[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.
93 if (i == 7)
95 /* backtrack to first non-space. this is safe because the first
96 * char won't be space, we checked above */
97 while (*c == 0x20)
98 c--;
100 /* forward one and drop in the dot */
101 c++;
102 *c = '.';
105 /* move along */
106 c++;
109 /* remove any trailing spaces, and perhaps a trailing . */
110 while (c[-1] == 0x20)
111 c--;
112 if (c[-1] == '.')
113 c--;
115 /* apply official hack for Japanese names */
116 if (*name == 0x05)
117 *name = 0xe5;
119 /* all done */
120 *c = '\0';
121 *len = strlen(name);
124 bug("[fat] extracted short name '");
125 RawPutChars(name, *len);
126 bug("'\n");
129 return 0;
132 LONG GetDirEntryLongName(struct DirEntry *short_de, STRPTR name,
133 ULONG *len)
135 struct Globals *glob = short_de->sb->glob;
136 UBYTE buf[256];
137 int i;
138 UBYTE *raw, *c;
139 UBYTE checksum;
140 struct DirHandle dh;
141 struct DirEntry de;
142 LONG index;
143 UBYTE order;
144 LONG err;
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]));
152 *name = '\0';
153 len = 0;
154 return 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 */
170 c = buf;
171 order = 1;
172 index = short_de->index - 1;
173 while (index >= 0)
175 D(bug("[fat] looking for long name order 0x%02x in entry %ld\n",
176 order, index));
178 if ((err = GetDirEntry(&dh, index, &de)) != 0)
179 break;
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;
193 break;
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
199 * in case. */
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])];
207 c++;
209 for (i = 0; i < 6; i++)
211 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name2[i])];
212 c++;
214 for (i = 0; i < 2; i++)
216 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name3[i])];
217 c++;
220 /* if this is the last entry, clean up and get us out of here */
221 if (de.e.long_entry.order & 0x40)
223 *c = 0;
224 *len = strlen((char *)buf);
225 CopyMem(buf, name, *len);
227 D(bug("[fat] extracted long name '%s'\n", buf));
229 ReleaseDirHandle(&dh);
231 return 0;
234 index--;
235 order++;
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];
252 ULONG nlong;
253 LONG src, dst, i, left, root_end;
254 ULONG seq = 0, cur = 0;
255 UBYTE tail[8];
256 struct DirHandle dh;
257 struct DirEntry de;
258 LONG err;
259 UBYTE checksum;
260 UBYTE order;
263 bug("[fat] setting name for entry index %ld to '", short_de->index);
264 RawPutChars(name, len);
265 bug("'\n");
268 /* discard leading spaces */
269 while (len > 0 && name[0] == ' ')
270 name++, len--;
272 /* discard trailing spaces and dots */
273 while (len > 0 && (name[len - 1] == ' ' || name[len - 1] == '.'))
274 len--;
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 */
294 dst = 0;
296 /* strip off leading periods */
297 for (src = 0; src < len; src++)
298 if (name[src] != '.')
299 break;
300 if (src != 0)
301 seq = 1;
303 /* copy the first eight chars in, ignoring spaces and stopping at period */
304 if (src != len)
306 while (src < len && dst < 8 && name[src] != '.')
308 if (name[src] != ' ')
310 if (allowed_ascii[name[src]] == 1)
312 basis[dst] = '_';
313 seq = 1;
315 else
316 basis[dst] = toupper(name[src]);
317 dst++;
319 src++;
323 /* if there were more bytes available, then we need a tail later */
324 if (src < len && name[src] != '.')
325 seq = 1;
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 */
329 left = dst;
331 /* remember the current value of src for the multiple-dot check below */
332 root_end = src;
334 /* pad the rest of the left side with spaces */
335 for (; dst < 8; dst++)
336 basis[dst] = ' ';
338 /* now go to the end and track back looking for a dot */
339 for (i = len - 1; i >= src && name[i] != '.'; i--);
341 /* found it */
342 if (i >= src)
344 /* if this isn't the same dot we found earlier, then we need a tail */
345 if (i != root_end)
346 seq = 1;
348 /* first char after the dot */
349 src = i + 1;
351 /* copy it in */
352 while (src < len && dst < FAT_MAX_SHORT_NAME)
354 if (name[src] != ' ')
356 if (allowed_ascii[name[src]] == 1)
358 basis[dst] = '_';
359 seq = 1;
361 else
362 basis[dst] = toupper(name[src]);
363 dst++;
365 src++;
368 /* if there were more bytes available, then we'll need a tail later */
369 if (src < len)
370 seq = 1;
373 /* pad the rest of the right side with spaces */
374 for (; dst < FAT_MAX_SHORT_NAME; dst++)
375 basis[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 */
385 if (nlong > 0)
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
391 * a gap */
392 while (1)
394 /* build a new tail if necessary */
395 if (cur != seq)
397 sprintf(tail, "~%lu", (unsigned long)seq);
398 while (left + strlen(tail) > 8)
399 left--;
400 CopyMem(tail, &basis[left], strlen(tail));
401 cur = seq;
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)
409 break;
411 /* abort on any other error */
412 if (err != 0)
414 ReleaseDirHandle(&dh);
415 return err;
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])
424 break;
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)
430 seq++;
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 */
444 if (nlong == 0)
446 D(bug("[fat] copied short name and long name not required,"
447 " we're done\n"));
448 ReleaseDirHandle(&dh);
449 return 0;
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 */
459 src = 0;
460 de.index = short_de->index;
461 order = 1;
462 while (src < len)
464 /* get the previous entry */
465 if ((err = GetDirEntry(&dh, de.index - 1, &de)) != 0)
467 ReleaseDirHandle(&dh);
468 return err;
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",
475 de.index));
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)
484 /* XXX corrupt */
485 ReleaseDirHandle(&dh);
486 return err;
490 ReleaseDirHandle(&dh);
491 return ERROR_NO_FREE_STORE;
494 D(bug("[fat] building long name entry %ld\n", de.index));
496 /* copy bytes in */
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 */
526 if (src >= len)
527 de.e.long_entry.order |= 0x40;
529 /* write the entry out */
530 UpdateDirEntry(&de);
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;
544 return 0;
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
551 ULONG i, left;
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
556 * running total */
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)
562 left = 0;
564 for (i = 0; i < 8 && i < len; i++)
566 if (name[i] == '.')
567 break;
568 if (name[i] != toupper(name[i]))
569 break;
570 if (allowed_ascii[name[i]] == 1)
571 break;
572 left++;
575 if (i == len)
576 return 0;
578 if (name[i] == '.')
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)
583 break;
585 if (left + 1 + i == len)
586 return 0;
589 #endif
591 return ((len - 1) / 13) + 1;