grub2: bring back build of aros-side grub2 tools
[AROS.git] / rom / filesys / fat / names.c
blobb6fd2cacc7616bcb2e31cf9084a3dcbce72b2dbf
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,
55 struct Globals *glob)
57 int i;
58 UBYTE *raw, *c;
60 /* Make sure the entry is good */
61 raw = de->e.entry.name;
62 if (raw[0] == 0x00 || raw[0] == 0xe5 || raw[0] == 0x20)
64 D(bug("[fat] entry name has first byte 0x%02x,"
65 " returning empty short name\n", raw[0]));
66 *name = '\0';
67 len = 0;
68 return 0;
72 bug("[fat] extracting short name for name '");
73 RawPutChars(raw, FAT_MAX_SHORT_NAME);
74 bug("' (index %ld)\n", de->index);
77 /* Copy the chars into the return string */
78 c = name;
79 for (i = 0; i < FAT_MAX_SHORT_NAME; i++)
81 *c = tolower(raw[i]);
84 * FAT names are weird. the name FOO.BAR is stored as "FOO BAR".
85 * In that case we've already copied in all the spaces, and we have to
86 * backup and insert the dot.
88 * Note that spaces (0x20) are allowed in the filename, just not as the
89 * first char. See FATdoc 1.03 p24 for the details. Most people don't
90 * know that about fat names. The point of this is to say that we
91 * can't just flick forward in our copying at the first sight of a
92 * space; it's technically incorrect.
94 if (i == 7)
96 /* Backtrack to first non-space. This is safe because the first
97 * char won't be a space; we checked above */
98 while (*c == 0x20)
99 c--;
101 /* Forward one and drop in the dot */
102 c++;
103 *c = '.';
106 /* Move along */
107 c++;
110 /* Remove any trailing spaces, and perhaps a trailing . */
111 while (c[-1] == 0x20)
112 c--;
113 if (c[-1] == '.')
114 c--;
116 /* Apply official hack for Japanese names */
117 if (*name == 0x05)
118 *name = 0xe5;
120 /* All done */
121 *c = '\0';
122 *len = strlen(name);
125 bug("[fat] extracted short name '");
126 RawPutChars(name, *len);
127 bug("'\n");
130 return 0;
133 LONG GetDirEntryLongName(struct DirEntry *short_de, STRPTR name,
134 ULONG *len)
136 struct Globals *glob = short_de->sb->glob;
137 UBYTE buf[256];
138 int i;
139 UBYTE *raw, *c;
140 UBYTE checksum;
141 struct DirHandle dh;
142 struct DirEntry de;
143 LONG index;
144 UBYTE order;
145 LONG err;
147 /* Make sure the entry is good */
148 raw = short_de->e.entry.name;
149 if (raw[0] == 0x00 || raw[0] == 0xe5 || raw[0] == 0x20)
151 D(bug("[fat] entry name has first byte 0x%02x,"
152 " returning empty long name\n", raw[0]));
153 *name = '\0';
154 len = 0;
155 return 0;
158 D(bug("[fat] looking for long name for name '%.*s' (index %ld)\n",
159 FAT_MAX_SHORT_NAME, raw, short_de->index));
161 /* Compute the short name checksum. This value is held in every associated
162 * long name entry to help us identify it. See FATdoc 1.03 p28 */
163 CALC_SHORT_NAME_CHECKSUM(raw, checksum);
165 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
167 /* Get a handle on the directory */
168 InitDirHandle(short_de->sb, short_de->cluster, &dh, FALSE, glob);
170 /* Loop over the long name entries */
171 c = buf;
172 order = 1;
173 index = short_de->index - 1;
174 while (index >= 0)
176 D(bug("[fat] looking for long name order 0x%02x in entry %ld\n",
177 order, index));
179 if ((err = GetDirEntry(&dh, index, &de, glob)) != 0)
180 break;
182 /* Make sure it's valid */
183 if (!((de.e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
184 (de.e.long_entry.order & ~0x40) != order ||
185 de.e.long_entry.checksum != checksum)
188 D(bug("[fat] bad long name entry %ld"
189 " (attr 0x%02x order 0x%02x checksum 0x%02x)\n",
190 index, de.e.entry.attr, de.e.long_entry.order,
191 de.e.long_entry.checksum));
193 err = ERROR_OBJECT_NOT_FOUND;
194 break;
197 /* Copy the characters into the name buffer. Note that filename
198 * entries can have null-termination, but don't have to. We take the
199 * easy way out - copy everything, and bolt on an additional null just
200 * in case. */
202 /* XXX: these are in UTF-16, but we're just taking the bottom byte.
203 * That works well enough but is still a hack. If our DOS ever
204 * supports unicode, this should be revisited */
205 for (i = 0; i < 5; i++)
207 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name1[i])];
208 c++;
210 for (i = 0; i < 6; i++)
212 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name2[i])];
213 c++;
215 for (i = 0; i < 2; i++)
217 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name3[i])];
218 c++;
221 /* If this is the last entry, clean up and get us out of here */
222 if (de.e.long_entry.order & 0x40)
224 *c = 0;
225 *len = strlen((char *)buf);
226 CopyMem(buf, name, *len);
228 D(bug("[fat] extracted long name '%s'\n", buf));
230 ReleaseDirHandle(&dh, glob);
232 return 0;
235 index--;
236 order++;
239 ReleaseDirHandle(&dh, glob);
241 D(bug("[fat] long name construction failed\n"));
243 return ERROR_OBJECT_NOT_FOUND;
246 /* Set the name of an entry. This will set the long name too. It assumes
247 * that there is room before the entry to store the long filename. If there
248 * isn't, the whole thing will fail */
249 LONG SetDirEntryName(struct DirEntry *short_de, STRPTR name, ULONG len)
251 struct Globals *glob = short_de->sb->glob;
252 UBYTE basis[FAT_MAX_SHORT_NAME];
253 ULONG nlong;
254 LONG src, dst, i, left, root_end;
255 ULONG seq = 0, cur = 0;
256 UBYTE tail[8];
257 struct DirHandle dh;
258 struct DirEntry de;
259 LONG err;
260 UBYTE checksum;
261 UBYTE order;
264 bug("[fat] setting name for entry index %ld to '", short_de->index);
265 RawPutChars(name, len);
266 bug("'\n");
269 /* Discard leading spaces */
270 while (len > 0 && name[0] == ' ')
271 name++, len--;
273 /* Discard trailing spaces and dots */
274 while (len > 0 && (name[len - 1] == ' ' || name[len - 1] == '.'))
275 len--;
277 /* Check for empty name and reserved names "." and ".." */
278 if (len == 0 || (name[0] == '.'
279 && (len == 1 || (name[1] == '.' && len == 2))))
280 return ERROR_INVALID_COMPONENT_NAME;
282 /* Check for illegal characters */
283 for (i = 0; i < len; i++)
284 if (name[i] <= 127 && allowed_ascii[name[i]] == 0)
285 return ERROR_INVALID_COMPONENT_NAME;
287 nlong = NumLongNameEntries(name, len);
288 D(bug("[fat] name requires %ld long name entries\n", nlong));
290 /* First we generate the "basis name" of the passed in name. XXX: we just
291 * take the first eight characters and any three-letter extension and mash
292 * them together. FATDoc 1.03 p30-31 outlines a more comprehensive
293 * algorithm that handles unicode, but we're not doing unicode yet */
295 dst = 0;
297 /* Strip off leading periods */
298 for (src = 0; src < len; src++)
299 if (name[src] != '.')
300 break;
301 if (src != 0)
302 seq = 1;
304 /* Copy the first eight chars in, ignoring spaces and stopping at period */
305 if (src != len)
307 while (src < len && dst < 8 && name[src] != '.')
309 if (name[src] != ' ')
311 if (allowed_ascii[name[src]] == 1)
313 basis[dst] = '_';
314 seq = 1;
316 else
317 basis[dst] = toupper(name[src]);
318 dst++;
320 src++;
324 /* If there were more bytes available, then we need a tail later */
325 if (src < len && name[src] != '.')
326 seq = 1;
328 /* Make a note of the length of the left side. This gets used further down
329 * to determine the position to add the tail */
330 left = dst;
332 /* Remember the current value of src for the multiple-dot check below */
333 root_end = src;
335 /* Pad the rest of the left side with spaces */
336 for (; dst < 8; dst++)
337 basis[dst] = ' ';
339 /* Now go to the end and track back looking for a dot */
340 for (i = len - 1; i >= src && name[i] != '.'; i--);
342 /* Found it */
343 if (i >= src)
345 /* If this isn't the same dot we found earlier, then we need a tail */
346 if (i != root_end)
347 seq = 1;
349 /* First char after the dot */
350 src = i + 1;
352 /* Copy it in */
353 while (src < len && dst < FAT_MAX_SHORT_NAME)
355 if (name[src] != ' ')
357 if (allowed_ascii[name[src]] == 1)
359 basis[dst] = '_';
360 seq = 1;
362 else
363 basis[dst] = toupper(name[src]);
364 dst++;
366 src++;
369 /* If there were more bytes available, then we'll need a tail later */
370 if (src < len)
371 seq = 1;
374 /* Pad the rest of the right side with spaces */
375 for (; dst < FAT_MAX_SHORT_NAME; dst++)
376 basis[dst] = ' ';
378 D(bug("[fat] basis name is '%.*s'\n", FAT_MAX_SHORT_NAME, basis));
380 /* Get a fresh handle on the current directory */
381 InitDirHandle(short_de->sb, short_de->cluster, &dh, FALSE, glob);
383 /* If the name will require one or more entries, then our basis name is
384 * actually some conversion of the real name, and we have to look to make
385 * sure it's not in use */
386 if (nlong > 0)
388 D(bug("[fat] searching for basis name to confirm that"
389 " it's not in use\n"));
391 /* Loop over the entries and compare them with the basis until we find
392 * a gap */
393 while (1)
395 /* Build a new tail if necessary */
396 if (cur != seq)
398 sprintf(tail, "~%lu", (unsigned long)seq);
399 while (left + strlen(tail) > 8)
400 left--;
401 CopyMem(tail, &basis[left], strlen(tail));
402 cur = seq;
404 D(bug("[fat] new basis name is '%.*s'\n",
405 FAT_MAX_SHORT_NAME, basis));
408 /* Get the next entry, and bail if we hit the end of the dir */
409 if ((err = GetNextDirEntry(&dh, &de, glob))
410 == ERROR_OBJECT_NOT_FOUND)
411 break;
413 /* Abort on any other error */
414 if (err != 0)
416 ReleaseDirHandle(&dh, glob);
417 return err;
420 /* Compare the two names */
421 D(bug("[fat] comparing '%.*s' with '%.*s'\n",
422 FAT_MAX_SHORT_NAME, basis,
423 FAT_MAX_SHORT_NAME, de.e.entry.name));
424 for (i = 0; i < FAT_MAX_SHORT_NAME; i++)
425 if (de.e.entry.name[i] != basis[i])
426 break;
428 /* If we reached the end, then our current basis is in use and we
429 * need to generate a new one and start again */
430 if (i == FAT_MAX_SHORT_NAME)
432 seq++;
433 RESET_DIRHANDLE(&dh);
437 D(bug("[fat] basis name '%.*s' not in use, using it\n",
438 FAT_MAX_SHORT_NAME, basis));
441 /* Copy the new name into the original entry. We don't write it out -
442 * we'll leave that for the caller to do, it's his entry */
443 CopyMem(basis, short_de->e.entry.name, FAT_MAX_SHORT_NAME);
445 /* We can stop here if no long name is required */
446 if (nlong == 0)
448 D(bug("[fat] copied short name and long name not required,"
449 " we're done\n"));
450 ReleaseDirHandle(&dh, glob);
451 return 0;
454 /* Compute the short name checksum */
455 CALC_SHORT_NAME_CHECKSUM(basis, checksum);
457 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
459 /* Now we loop back over the previous entries and fill them in with
460 * long name components */
461 src = 0;
462 de.index = short_de->index;
463 order = 1;
464 while (src < len)
466 /* Get the previous entry */
467 if ((err = GetDirEntry(&dh, de.index - 1, &de, glob)) != 0)
469 ReleaseDirHandle(&dh, glob);
470 return err;
473 /* It must be unused (or end of directory) */
474 if (de.e.entry.name[0] != 0xe5 && de.e.entry.name[0] != 0x00)
476 D(bug("[fat] index %ld appears to be in use, aborting long name\n",
477 de.index));
479 /* Clean up any long name entries we already added */
480 while ((err = GetDirEntry(&dh, de.index + 1, &de, glob)) == 0
481 && (de.e.entry.attr & ATTR_LONG_NAME_MASK))
483 de.e.entry.name[0] = 0xe5;
484 if ((err = UpdateDirEntry(&de, glob)) != 0)
486 /* FIXME: leaving in corrupt state? */
487 ReleaseDirHandle(&dh, glob);
488 return err;
492 ReleaseDirHandle(&dh, glob);
493 return ERROR_NO_FREE_STORE;
496 D(bug("[fat] building long name entry %ld\n", de.index));
498 /* Copy bytes in */
499 for (dst = 0; dst < 5; dst++)
501 de.e.long_entry.name1[dst] =
502 src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) :
503 (src++ == len ? 0x0000 : 0xffff);
506 for (dst = 0; dst < 6; dst++)
508 de.e.long_entry.name2[dst] =
509 src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) :
510 (src++ == len ? 0x0000 : 0xffff);
513 for (dst = 0; dst < 2; dst++)
515 de.e.long_entry.name3[dst] =
516 src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) :
517 (src++ == len ? 0x0000 : 0xffff);
520 /* Set up the rest of the entry */
521 de.e.long_entry.order = order++;
522 de.e.long_entry.attr = ATTR_LONG_NAME;
523 de.e.long_entry.type = 0;
524 de.e.long_entry.checksum = checksum;
525 de.e.long_entry.first_cluster_lo = 0;
527 /* If we've reached the end then this is the last entry */
528 if (src >= len)
529 de.e.long_entry.order |= 0x40;
531 /* Write the entry out */
532 UpdateDirEntry(&de, glob);
534 D(bug("[fat] wrote long name entry %ld order 0x%02x\n", de.index,
535 de.e.long_entry.order));
538 ReleaseDirHandle(&dh, glob);
540 D(bug("[fat] successfully wrote short & long names\n"));
542 /* Set hidden flags on .info files */
543 if (strcmp(name + len - 5, ".info") == 0)
544 short_de->e.entry.attr |= ATTR_HIDDEN;
546 return 0;
549 /* Return the number of long name entries that are required to store this
550 * name */
551 ULONG NumLongNameEntries(STRPTR name, ULONG len)
553 return ((len - 1) / 13) + 1;