r26207@plastic: rob | 2007-04-29 00:06:54 +1000
[cake.git] / workbench / fs / fat / names.c
blobe91b27b192bc1f05774c8dce20e84d9fffe4b6aa
1 /*
2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007 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 <exec/types.h>
14 #include <dos/dos.h>
15 #include <proto/exec.h>
17 #include <string.h>
18 #include <stdio.h>
19 #include <ctype.h>
21 #include "fat_fs.h"
22 #include "fat_protos.h"
24 LONG GetDirEntryShortName(struct DirEntry *de, STRPTR name, ULONG *len) {
25 int i;
26 UBYTE *raw, *c;
28 /* make sure the entry is good */
29 raw = de->e.entry.name;
30 if (raw[0] == 0x00 || raw[0] == 0xe5 || raw[0] == 0x20) {
31 D(bug("[fat] entry name has first byte 0x%02x, returning empty short name\n", raw[0]));
32 *name = '\0';
33 len = 0;
34 return 0;
37 D(bug("[fat] extracting short name for name '%.11s' (index %ld)\n", raw, de->index));
39 /* copy the chars into the return string */
40 c = name;
41 for (i = 0; i < 11; i++) {
42 *c = raw[i];
45 * fat names are weird. the name FOO.BAR is stored as "FOO BAR".
46 * in that case we've already copied in all the spaces, and we have to
47 * backup and insert the dot.
49 * note that spaces (0x20) is allowed in the filename, just not as the
50 * first char. see FATdoc 1.03 p24 for the details. most people don't
51 * know that about fat names. the point of this is to say that we
52 * can't just flick forward in our copying at the first sight of a
53 * space, its technically incorrect.
55 * XXX it occurs to me just now that AROS may not like spaces in its
56 * names. in that case spaces should probably be converted to
57 * underscores. that or our dos should be changed so it does allow
58 * spaces. either way, thats a project for another time.
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 '%.*s'\n", *len, name));
84 return 0;
87 LONG GetDirEntryLongName(struct DirEntry *short_de, UBYTE *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 ULONG 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);
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 its 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 = de.e.long_entry.name1[i << 1];
150 c++;
152 for (i = 0; i < 6; i++) {
153 *c = de.e.long_entry.name2[i << 1];
154 c++;
156 for (i = 0; i < 2; i++) {
157 *c = de.e.long_entry.name3[i << 1];
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", *len, 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, UBYTE *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 '%.*s'\n", short_de->index, len, name));
202 nlong = NumLongNameEntries(name, len);
203 D(bug("[fat] name requires %ld long name entries\n", nlong));
205 /* first we generate the "basis name" of the passed in name. XXX we just
206 * take the first eight characters and any three-letter extension and mash
207 * them together. FATDoc 1.03 p30-31 outlines a more comprehensive
208 * algorithm that handles unicode, but we're not doing unicode yet */
210 dst = 0;
212 /* strip off leading spaces and periods */
213 for (src = 0; src < len; src++)
214 if (name[src] != ' ' && name[src] != '.')
215 break;
217 /* copy the first eight chars in, ignoring spaces and stopping at period */
218 if (src != len) {
219 while (src < len && dst < 8 && name[src] != '.') {
220 if (name[src] != ' ') {
221 basis[dst] = toupper(name[src]);
222 if (basis[dst] != name[src])
223 seq = 1;
224 dst++;
226 src++;
230 /* if there was more bytes available, then we need a tail later */
231 if (src < len && name[src] != '.')
232 seq = 1;
234 /* make a note of the length of the left side. this gets used further down
235 * to determine the position to add the tail */
236 left = dst;
238 /* remember the current value of src for the multiple-dot check below */
239 i = src;
241 /* pad the rest of the left side with spaces */
242 for (; dst < 8; dst++)
243 basis[dst] = ' ';
245 /* now go to the end and track back looking for a dot */
246 for (src = len-1; src >= 0 && name[src] != '.'; src--);
248 /* found it */
249 if (src != 0) {
250 /* if this isn't the same dot we found earlier, then we need a tail */
251 if (src != i)
252 seq = 1;
254 /* first char after the dot */
255 src++;
257 /* copy it in */
258 while(src < len && dst < 11) {
259 if (name[src] != ' ') {
260 basis[dst] = toupper(name[src]);
261 if (basis[dst] != name[src])
262 seq = 1;
263 dst++;
265 src++;
269 /* if there was more bytes available, then we'll need a tail later */
270 if (src < len)
271 seq = 1;
273 /* pad the rest of the right side with spaces */
274 for (; dst < 11; dst++)
275 basis[dst] = ' ';
277 D(bug("[fat] basis name is '%.11s'\n", basis));
279 /* get a fresh handle on the current directory */
280 InitDirHandle(short_de->sb, short_de->cluster, &dh);
282 /* if the name will require one or more entries, then our basis name is
283 * actually some conversion of the real name, and we have to look to make
284 * sure its not in use */
285 if (nlong > 0) {
286 D(bug("[fat] searching for basis name to confirm that its not in use\n"));
288 /* loop over the entries and compare them with the basis until we find
289 * a gap */
290 while (1) {
291 /* build a new tail if necessary */
292 if (cur != seq) {
293 sprintf(tail, "~%ld", seq);
294 while (left + strlen(tail) > 8) left--;
295 CopyMem(tail, &basis[left], strlen(tail));
296 cur = seq;
298 D(bug("[fat] new basis name is '%.11s'\n", basis));
301 /* get the next entry, and bail if we hit the end of the dir */
302 if ((err = GetNextDirEntry(&dh, &de)) == ERROR_OBJECT_NOT_FOUND)
303 break;
305 /* abort on any other error */
306 if (err != 0) {
307 ReleaseDirHandle(&dh);
308 return err;
311 /* compare the two names */
312 for (i = 0; i < 11; i++)
313 if (de.e.entry.name[i] != basis[i])
314 break;
316 /* if we reached the end, then our current basis is in use and we need
317 * to generate a new one next time round */
318 if (src == 11)
319 seq++;
322 D(bug("[fat] basis name '%.11s' not in use, using it\n", basis));
325 /* copy the new name into the original entry. we don't write it out -
326 * we'll leave that for the caller to do, its his entry */
327 CopyMem(basis, short_de->e.entry.name, 11);
329 /* we can stop here if no long name is required */
330 if (nlong == 0) {
331 D(bug("[fat] copied short name and long name not required, we're done\n"));
332 ReleaseDirHandle(&dh);
333 return 0;
336 /* compute the short name checksum */
337 CALC_SHORT_NAME_CHECKSUM(basis, checksum);
339 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
341 /* now we loop back over the previous entries and fill them in with
342 * long name components */
343 src = 0;
344 de.index = short_de->index;
345 order = 1;
346 while (src < len) {
347 /* get the previous entry */
348 if ((err = GetDirEntry(&dh, de.index-1, &de)) != 0) {
349 ReleaseDirHandle(&dh);
350 return err;
353 /* it must be unused (or end of directory) */
354 if (de.e.entry.name[0] != 0xe5 && de.e.entry.name[0] != 0x00) {
355 D(bug("[fat] index %ld appears to be in use, aborting long name\n", de.index));
357 /* clean up any long name entries we already added */
358 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0 &&
359 (de.e.entry.attr & ATTR_LONG_NAME_MASK)) {
360 de.e.entry.name[0] = 0xe5;
361 if ((err = UpdateDirEntry(&de)) != 0) {
362 /* XXX corrupt */
363 ReleaseDirHandle(&dh);
364 return err;
368 ReleaseDirHandle(&dh);
369 return ERROR_NO_FREE_STORE;
372 D(bug("[fat] building long name entry %ld\n", de.index));
374 /* copy bytes in */
375 for (dst = 0; dst < 5; dst++) {
376 de.e.long_entry.name1[dst << 1] = src < len ? name[src++] : 0x00;
377 de.e.long_entry.name1[(dst << 1)+1] = 0;
380 for (dst = 0; dst < 6; dst++) {
381 de.e.long_entry.name2[dst << 1] = src < len ? name[src++] : 0x00;
382 de.e.long_entry.name2[(dst << 1)+1] = 0;
385 for (dst = 0; dst < 2; dst++) {
386 de.e.long_entry.name3[dst << 1] = src < len ? name[src++] : 0x00;
387 de.e.long_entry.name3[(dst << 1)+1] = 0;
390 /* setup the rest of the entry */
391 de.e.long_entry.order = order++;
392 de.e.long_entry.attr = ATTR_LONG_NAME;
393 de.e.long_entry.type = 0;
394 de.e.long_entry.checksum = checksum;
395 de.e.long_entry.first_cluster_lo = 0;
397 /* if we've reached the end then this is the last entry */
398 if (src == len)
399 de.e.long_entry.order |= 0x40;
401 /* write the entry out */
402 UpdateDirEntry(&de);
404 D(bug("[fat] wrote long name entry %ld order 0x%02x\n", de.index, de.e.long_entry.order));
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;