make fat.handler build again with debug enabled
[tangerine.git] / workbench / fs / fat / names.c
blob394102dd07a7da0e97a1789e40d72a84dc8afee3
1 /*
2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2008 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"
28 LONG GetDirEntryShortName(struct DirEntry *de, STRPTR name, ULONG *len) {
29 int i;
30 UBYTE *raw, *c;
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]));
36 *name = '\0';
37 len = 0;
38 return 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 */
45 c = name;
46 for (i = 0; i < 11; i++) {
47 *c = raw[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.
60 if (i == 7) {
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 */
66 c++;
67 *c = '.';
70 /* move along */
71 c++;
74 /* remove any trailing spaces, and perhaps a trailing . */
75 while (c[-1] == 0x20) c--;
76 if (c[-1] == '.') c--;
78 /* all done */
79 *c = '\0';
80 *len = strlen(name);
82 D(bug("[fat] extracted short name '"); RawPutChars(name, *len); bug("\n"));
84 return 0;
87 LONG GetDirEntryLongName(struct DirEntry *short_de, STRPTR name, ULONG *len) {
88 UBYTE buf[256];
89 int i;
90 UBYTE *raw, *c;
91 UBYTE checksum;
92 struct DirHandle dh;
93 struct DirEntry de;
94 LONG index;
95 UBYTE order;
96 LONG err;
98 /* make sure the entry is good */
99 raw = short_de->e.entry.name;
100 if (raw[0] == 0x00 || raw[0] == 0xe5 || raw[0] == 0x20) {
101 D(bug("[fat] entry name has first byte 0x%02x, returning empty long name\n", raw[0]));
102 *name = '\0';
103 len = 0;
104 return 0;
107 D(bug("[fat] looking for long name for name '%.11s' (index %ld)\n", raw, short_de->index));
109 /* compute the short name checksum. this value is held in every associated
110 * long name entry to help us identify it. see FATdoc 1.03 p28 */
111 CALC_SHORT_NAME_CHECKSUM(raw, checksum);
113 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
115 /* get a handle on the directory */
116 InitDirHandle(short_de->sb, short_de->cluster, &dh, FALSE);
118 /* loop over the long name entries */
119 c = buf;
120 order = 1;
121 index = short_de->index - 1;
122 while (index >= 0) {
123 D(bug("[fat] looking for long name order 0x%02x in entry %ld\n", order, index));
125 if ((err = GetDirEntry(&dh, index, &de)) != 0)
126 break;
128 /* make sure it's valid */
129 if (!((de.e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
130 (de.e.long_entry.order & ~0x40) != order ||
131 de.e.long_entry.checksum != checksum) {
133 D(bug("[fat] bad long name entry %ld (attr 0x%02x order 0x%02x checksum 0x%02x)\n",
134 index, de.e.entry.attr, de.e.long_entry.order, de.e.long_entry.checksum));
136 err = ERROR_OBJECT_NOT_FOUND;
137 break;
140 /* copy the characters into the name buffer. note that filename
141 * entries can have null-termination, but don't have to. we take the
142 * easy way out - copy everything, and bolt on an additional null just
143 * in case. */
145 /* XXX these are in UTF-16, but we're just taking the bottom byte.
146 * that works well enough but is still a hack. if our dos ever
147 * supports unicode this should be revisited */
148 for (i = 0; i < 5; i++) {
149 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name1[i])];
150 c++;
152 for (i = 0; i < 6; i++) {
153 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name2[i])];
154 c++;
156 for (i = 0; i < 2; i++) {
157 *c = glob->from_unicode[AROS_LE2WORD(de.e.long_entry.name3[i])];
158 c++;
161 /* if this is the last entry, clean up and get us out of here */
162 if (de.e.long_entry.order & 0x40) {
163 *c = 0;
164 *len = strlen((char *) buf);
165 CopyMem(buf, name, *len);
167 D(bug("[fat] extracted long name '%s'\n", name));
169 ReleaseDirHandle(&dh);
171 return 0;
174 index--;
175 order++;
178 ReleaseDirHandle(&dh);
180 D(bug("[fat] long name construction failed\n"));
182 return ERROR_OBJECT_NOT_FOUND;
185 /* set the name of an entry. this will also set the long name too. it assumes
186 * that there is room before the entry to store the long filename. if there
187 * isn't the whole thing will fail */
188 LONG SetDirEntryName(struct DirEntry *short_de, STRPTR name, ULONG len) {
189 UBYTE basis[11];
190 ULONG nlong;
191 ULONG src, dst, i, left;
192 ULONG seq = 0, cur = 0;
193 UBYTE tail[8];
194 struct DirHandle dh;
195 struct DirEntry de;
196 LONG err;
197 UBYTE checksum;
198 UBYTE order;
200 D(bug("[fat] setting name for entry index %ld to '", short_de->index);
201 RawPutChars(name, len); bug("'\n"));
203 nlong = NumLongNameEntries(name, len);
204 D(bug("[fat] name requires %ld long name entries\n", nlong));
206 /* first we generate the "basis name" of the passed in name. XXX we just
207 * take the first eight characters and any three-letter extension and mash
208 * them together. FATDoc 1.03 p30-31 outlines a more comprehensive
209 * algorithm that handles unicode, but we're not doing unicode yet */
211 dst = 0;
213 /* strip off leading spaces and periods */
214 for (src = 0; src < len; src++)
215 if (name[src] != ' ' && name[src] != '.')
216 break;
218 /* copy the first eight chars in, ignoring spaces and stopping at period */
219 if (src != len) {
220 while (src < len && dst < 8 && name[src] != '.') {
221 if (name[src] != ' ') {
222 basis[dst] = toupper(name[src]);
223 if (basis[dst] != name[src])
224 seq = 1;
225 dst++;
227 src++;
231 /* if there was more bytes available, then we need a tail later */
232 if (src < len && name[src] != '.')
233 seq = 1;
235 /* make a note of the length of the left side. this gets used further down
236 * to determine the position to add the tail */
237 left = dst;
239 /* remember the current value of src for the multiple-dot check below */
240 i = src;
242 /* pad the rest of the left side with spaces */
243 for (; dst < 8; dst++)
244 basis[dst] = ' ';
246 /* now go to the end and track back looking for a dot */
247 for (src = len-1; src >= 0 && name[src] != '.'; src--);
249 /* found it */
250 if (src != 0) {
251 /* if this isn't the same dot we found earlier, then we need a tail */
252 if (src != i)
253 seq = 1;
255 /* first char after the dot */
256 src++;
258 /* copy it in */
259 while(src < len && dst < 11) {
260 if (name[src] != ' ') {
261 basis[dst] = toupper(name[src]);
262 if (basis[dst] != name[src])
263 seq = 1;
264 dst++;
266 src++;
270 /* if there was more bytes available, then we'll need a tail later */
271 if (src < len)
272 seq = 1;
274 /* pad the rest of the right side with spaces */
275 for (; dst < 11; dst++)
276 basis[dst] = ' ';
278 D(bug("[fat] basis name is '%.11s'\n", basis));
280 /* get a fresh handle on the current directory */
281 InitDirHandle(short_de->sb, short_de->cluster, &dh, FALSE);
283 /* if the name will require one or more entries, then our basis name is
284 * actually some conversion of the real name, and we have to look to make
285 * sure it's not in use */
286 if (nlong > 0) {
287 D(bug("[fat] searching for basis name to confirm that it's not in use\n"));
289 /* loop over the entries and compare them with the basis until we find
290 * a gap */
291 while (1) {
292 /* build a new tail if necessary */
293 if (cur != seq) {
294 sprintf(tail, "~%ld", seq);
295 while (left + strlen(tail) > 8) left--;
296 CopyMem(tail, &basis[left], strlen(tail));
297 cur = seq;
299 D(bug("[fat] new basis name is '%.11s'\n", basis));
302 /* get the next entry, and bail if we hit the end of the dir */
303 if ((err = GetNextDirEntry(&dh, &de)) == ERROR_OBJECT_NOT_FOUND)
304 break;
306 /* abort on any other error */
307 if (err != 0) {
308 ReleaseDirHandle(&dh);
309 return err;
312 /* compare the two names */
313 for (i = 0; i < 11; i++)
314 if (de.e.entry.name[i] != basis[i])
315 break;
317 /* if we reached the end, then our current basis is in use and we need
318 * to generate a new one next time round */
319 if (src == 11)
320 seq++;
323 D(bug("[fat] basis name '%.11s' not in use, using it\n", basis));
326 /* copy the new name into the original entry. we don't write it out -
327 * we'll leave that for the caller to do, it's his entry */
328 CopyMem(basis, short_de->e.entry.name, 11);
330 /* we can stop here if no long name is required */
331 if (nlong == 0) {
332 D(bug("[fat] copied short name and long name not required, we're done\n"));
333 ReleaseDirHandle(&dh);
334 return 0;
337 /* compute the short name checksum */
338 CALC_SHORT_NAME_CHECKSUM(basis, checksum);
340 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
342 /* now we loop back over the previous entries and fill them in with
343 * long name components */
344 src = 0;
345 de.index = short_de->index;
346 order = 1;
347 while (src < len) {
348 /* get the previous entry */
349 if ((err = GetDirEntry(&dh, de.index-1, &de)) != 0) {
350 ReleaseDirHandle(&dh);
351 return err;
354 /* it must be unused (or end of directory) */
355 if (de.e.entry.name[0] != 0xe5 && de.e.entry.name[0] != 0x00) {
356 D(bug("[fat] index %ld appears to be in use, aborting long name\n", de.index));
358 /* clean up any long name entries we already added */
359 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0 &&
360 (de.e.entry.attr & ATTR_LONG_NAME_MASK)) {
361 de.e.entry.name[0] = 0xe5;
362 if ((err = UpdateDirEntry(&de)) != 0) {
363 /* XXX corrupt */
364 ReleaseDirHandle(&dh);
365 return err;
369 ReleaseDirHandle(&dh);
370 return ERROR_NO_FREE_STORE;
373 D(bug("[fat] building long name entry %ld\n", de.index));
375 /* copy bytes in */
376 for (dst = 0; dst < 5; dst++) {
377 de.e.long_entry.name1[dst] = src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) : 0x00;
380 for (dst = 0; dst < 6; dst++) {
381 de.e.long_entry.name2[dst] = src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) : 0x00;
384 for (dst = 0; dst < 2; dst++) {
385 de.e.long_entry.name3[dst] = src < len ? AROS_WORD2LE(glob->to_unicode[name[src++]]) : 0x00;
388 /* setup the rest of the entry */
389 de.e.long_entry.order = order++;
390 de.e.long_entry.attr = ATTR_LONG_NAME;
391 de.e.long_entry.type = 0;
392 de.e.long_entry.checksum = checksum;
393 de.e.long_entry.first_cluster_lo = 0;
395 /* if we've reached the end then this is the last entry */
396 if (src == len)
397 de.e.long_entry.order |= 0x40;
399 /* write the entry out */
400 UpdateDirEntry(&de);
402 D(bug("[fat] wrote long name entry %ld order 0x%02x\n", de.index, de.e.long_entry.order));
405 ReleaseDirHandle(&dh);
407 D(bug("[fat] successfully wrote short & long names\n"));
409 return 0;
412 /* return the number of long name entries that are required to store this name */
413 ULONG NumLongNameEntries(STRPTR name, ULONG len) {
414 ULONG i, left;
416 /* XXX because we don't handle unicode this is pretty simple - thirteen
417 * characters per long entry. if we ever support unicode, then this
418 * function will need to be changed to deal with each character and keep a
419 * running total */
421 /* if the name is standard 8.3 (or less) then we don't need any long name
422 * entries - the name can be contained within the standard entry */
423 if (len <= 12) {
424 left = 0;
426 for (i = 0; i < 8 && i < len; i++) {
427 if (name[i] == '.')
428 break;
429 if (name[i] != toupper(name[i]))
430 break;
431 left++;
434 if (i == len)
435 return 0;
437 if (name[i] == '.') {
438 for (i = 0; i < 3 && left + 1 + i < len; i++)
439 if (name[left+1+i] != toupper(name[left+1+i]))
440 break;
442 if (left + 1 + i == len)
443 return 0;
447 return ((len-1) / 13) + 1;