Made comments and debug output more consistent.
[AROS.git] / rom / filesys / fat / direntry.c
blob4533926a7646842a13a57ea46b44ad250be4b4ca
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>
17 #include <proto/dos.h>
19 #include <string.h>
21 #include "cache.h"
23 #include "fat_fs.h"
24 #include "fat_protos.h"
26 #define DEBUG DEBUG_DIRENTRY
27 #include "debug.h"
29 LONG InitDirHandle(struct FSSuper *sb, ULONG cluster, struct DirHandle *dh,
30 BOOL reuse, struct Globals *glob)
32 /* 'dh' may or may not be initialised when this is called. if it is, then
33 * it probably has a valid cache block that we need to free, but we
34 * wouldn't know. we test the superblock pointer to figure out if it's
35 * valid or not */
36 if (reuse && (dh->ioh.sb == sb))
38 D(bug("[fat] reusing directory handle\n"));
39 if (dh->ioh.block != NULL)
41 Cache_FreeBlock(sb->cache, dh->ioh.block);
42 dh->ioh.block = NULL;
45 else
47 dh->ioh.sb = sb;
48 dh->ioh.block = NULL;
51 if (cluster == 0)
53 dh->ioh.first_cluster = sb->rootdir_cluster;
54 dh->ioh.first_sector = sb->rootdir_sector;
56 else
58 dh->ioh.first_cluster = cluster;
59 dh->ioh.first_sector = 0;
62 RESET_DIRHANDLE(dh);
63 dh->ioh.cur_sector = dh->ioh.first_sector;
64 dh->ioh.sector_offset = 0;
66 D(bug("[fat] initialised dir handle, first cluster is %ld,"
67 " first sector is %ld\n", dh->ioh.first_cluster,
68 dh->ioh.first_sector));
70 return 0;
73 LONG ReleaseDirHandle(struct DirHandle *dh, struct Globals *glob)
75 D(bug("[fat] releasing dir handle (cluster %ld)\n",
76 dh->ioh.first_cluster));
77 RESET_DIRHANDLE(dh);
79 return 0;
82 LONG GetDirEntry(struct DirHandle *dh, ULONG index, struct DirEntry *de,
83 struct Globals *glob)
85 LONG err = 0;
86 ULONG nread;
88 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n",
89 index, dh->ioh.first_cluster));
91 /* FAT dirs are limited to 2^16 entries */
92 if (index >= 0x10000)
94 D(bug("[fat] request for out-of-range index, returning not found\n"));
95 return ERROR_OBJECT_NOT_FOUND;
98 /* Set up the return object */
99 de->sb = dh->ioh.sb;
100 de->cluster = dh->ioh.first_cluster;
101 de->index = index;
102 de->pos = index * sizeof(struct FATDirEntry);
104 /* Get the data directly into the entry */
105 err = ReadFileChunk(&(dh->ioh), de->pos, sizeof(struct FATDirEntry),
106 (UBYTE *) &(de->e.entry), &nread);
107 if (err != 0)
109 D(bug("[fat] dir entry lookup failed\n"));
110 return err;
113 /* Remember where we are for GetNextDirEntry() */
114 dh->cur_index = index;
116 /* Done! */
117 return 0;
120 LONG GetNextDirEntry(struct DirHandle *dh, struct DirEntry *de,
121 struct Globals *glob)
123 LONG err;
125 D(bug("[fat] looking for next entry after index %ld\n", dh->cur_index));
127 /* 'cur_index' defaults to -1, so this will do the right thing even on a
128 * fresh dirhandle */
129 while ((err = GetDirEntry(dh, dh->cur_index + 1, de, glob)) == 0)
131 /* End of directory, there is no next entry */
132 if (de->e.entry.name[0] == 0x00)
134 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n",
135 dh->cur_index));
136 RESET_DIRHANDLE(dh);
137 return ERROR_OBJECT_NOT_FOUND;
140 /* Skip unused entries */
141 if (de->e.entry.name[0] == 0xe5)
143 D(bug("[fat] entry %ld is empty, skipping it\n", dh->cur_index));
144 continue;
147 /* This flag will be set for both volume name entries and long
148 * filename entries. Either way we want to skip them */
149 if (de->e.entry.attr & ATTR_VOLUME_ID)
151 D(bug("[fat] entry %ld is a volume name or long filename,"
152 " skipping it\n", dh->cur_index));
153 continue;
156 /* Ignore the . and .. entries */
157 if (de->e.entry.name[0] == '.' && ((de->index == 0
158 && strncmp((char *)de->e.entry.name, ". ",
159 FAT_MAX_SHORT_NAME) == 0)
160 || (de->index == 1
161 && strncmp((char *)de->e.entry.name, ".. ",
162 FAT_MAX_SHORT_NAME) == 0)))
164 D(bug("[fat] skipping . or .. entry\n"));
165 continue;
168 D(bug("[fat] returning entry %ld\n", dh->cur_index));
170 return 0;
173 return err;
176 LONG GetParentDir(struct DirHandle *dh, struct DirEntry *de,
177 struct Globals *glob)
179 LONG err = 0;
180 ULONG cluster;
182 D(bug("[fat] getting parent for directory at cluster %ld\n",
183 dh->ioh.first_cluster));
185 /* If we're already at the root, then we can't go any further */
186 if (dh->ioh.first_cluster == dh->ioh.sb->rootdir_cluster)
188 D(bug("[fat] trying to go up past the root, so entry not found\n"));
189 return ERROR_OBJECT_NOT_FOUND;
192 /* Otherwise, the next cluster is held in the '..' entry, which is
193 * entry #1 */
194 GetDirEntry(dh, 1, de, glob);
196 /* Make sure it's actually the parent dir entry */
197 if (((de->e.entry.attr & ATTR_DIRECTORY) == 0) ||
198 strncmp((char *)de->e.entry.name, ".. ", FAT_MAX_SHORT_NAME)
199 != 0)
201 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
202 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n", FAT_MAX_SHORT_NAME,
203 de->e.entry.name, de->e.entry.attr));
204 return ERROR_OBJECT_NOT_FOUND;
207 /* Take us up */
208 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE, glob);
210 /* Get handle on grandparent dir so we can find entry with parent's
211 * name */
212 if (dh->ioh.first_cluster != dh->ioh.sb->rootdir_cluster)
214 D(bug("[fat] getting grandparent, first cluster is %ld,"
215 " root cluster is %ld\n", dh->ioh.first_cluster,
216 dh->ioh.sb->rootdir_cluster));
217 cluster = dh->ioh.first_cluster;
218 GetDirEntry(dh, 1, de, glob);
219 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE, glob);
221 err = GetDirEntryByCluster(dh, cluster, de, glob);
224 return err;
227 LONG GetDirEntryByCluster(struct DirHandle *dh, ULONG cluster,
228 struct DirEntry *de, struct Globals *glob)
230 LONG err;
232 D(bug("[fat] looking for dir entry with first cluster %lu\n", cluster));
234 /* Start at the start */
235 RESET_DIRHANDLE(dh);
237 /* Loop through the entries until we find a match */
238 while ((err = GetNextDirEntry(dh, de, glob)) == 0)
240 if (de->e.entry.first_cluster_hi == (cluster >> 16)
241 && de->e.entry.first_cluster_lo == (cluster & 0xffff))
243 D(bug("[fat] matched starting cluster at entry %ld, returning\n",
244 dh->cur_index));
245 return 0;
249 D(bug("[fat] dir entry with first cluster %lu not found\n", cluster));
250 return err;
253 LONG GetDirEntryByName(struct DirHandle *dh, STRPTR name, ULONG namelen,
254 struct DirEntry *de, struct Globals *glob)
256 UBYTE buf[256];
257 ULONG buflen;
258 LONG err;
260 D(bug("[fat] looking for dir entry with name '%s'\n", name));
262 /* Start at the start */
263 RESET_DIRHANDLE(dh);
265 /* Loop through the entries until we find a match */
266 while ((err = GetNextDirEntry(dh, de, glob)) == 0)
268 /* Compare with the short name first, since we already have it */
269 GetDirEntryShortName(de, buf, &buflen, glob);
270 if (namelen == buflen
271 && strnicmp((char *)name, (char *)buf, buflen) == 0)
273 D(bug("[fat] matched short name '%s' at entry %ld, returning\n",
274 buf, dh->cur_index));
275 return 0;
278 /* No match, extract the long name and compare with that instead */
279 GetDirEntryLongName(de, buf, &buflen);
280 if (namelen == buflen
281 && strnicmp((char *)name, (char *)buf, buflen) == 0)
283 D(bug("[fat] matched long name '%s' at entry %ld, returning\n",
284 buf, dh->cur_index));
285 return 0;
289 return err;
292 LONG GetDirEntryByPath(struct DirHandle *dh, STRPTR path, ULONG pathlen,
293 struct DirEntry *de, struct Globals *glob)
295 LONG err;
296 ULONG len, i;
299 bug("[fat] looking for entry with path '");
300 RawPutChars(path, pathlen);
301 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster);
304 /* Get back to the start of the dir */
305 RESET_DIRHANDLE(dh);
307 /* If it starts with a volume specifier (or just a :), remove it and get
308 * us back to the root dir */
309 for (i = 0; i < pathlen; i++)
310 if (path[i] == ':')
312 D(bug(
313 "[fat] path has volume specifier, moving to the root dir\n"));
315 pathlen -= (i + 1);
316 path = &path[i + 1];
318 InitDirHandle(dh->ioh.sb, 0, dh, TRUE, glob);
320 /* If we were called with simply ":" as the name we will return
321 immediately after this, so we prepare a fictional direntry for
322 such a case. Note that we fill only fields which are actually
323 used in our handler */
324 de->cluster = 0;
325 de->index = -1; /* WARNING! Dummy index */
326 de->e.entry.attr = ATTR_DIRECTORY;
327 de->e.entry.first_cluster_hi = 0;
328 de->e.entry.first_cluster_lo = 0;
330 break;
334 bug("[fat] now looking for entry with path '");
335 RawPutChars(path, pathlen);
336 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster);
339 /* Each time around the loop we find one dir/file in the full path */
340 while (pathlen > 0)
343 /* Zoom forward and find the first dir separator */
344 for (len = 0; len < pathlen && path[len] != '/'; len++);
347 bug("[fat] remaining path is '");
348 RawPutChars(path, pathlen);
349 bug("' (%d bytes), current chunk is '", pathlen);
350 RawPutChars(path, len); bug("' (%d bytes)\n", len);
353 /* If the first character is a '/', then we have to go up a level */
354 if (len == 0)
356 /* Get the parent dir, and bale if we've gone past it (i.e. we are
357 * the root) */
358 if ((err = GetParentDir(dh, de, glob)) != 0)
359 return err;
362 /* Otherwise, we want to search the current directory for this name */
363 else
365 if ((err = GetDirEntryByName(dh, path, len, de, glob)) != 0)
366 return ERROR_OBJECT_NOT_FOUND;
369 /* Move up the buffer */
370 path += len;
371 pathlen -= len;
373 /* A '/' here is either the path separator or the directory we just
374 * went up. Either way, we have to ignore it */
375 if (pathlen > 0 && path[0] == '/')
377 path++;
378 pathlen--;
381 if (pathlen > 0)
383 /* More to do, so this entry had better be a directory */
384 if (!(de->e.entry.attr & ATTR_DIRECTORY))
386 D(bug("[fat] '%.*s' is not a directory,"
387 " so can't go any further\n", len, path));
388 return ERROR_OBJECT_WRONG_TYPE;
391 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE,
392 glob);
396 /* Nothing left, so we've found it */
397 D(bug("[fat] found the entry, returning it\n"));
398 return 0;
401 LONG UpdateDirEntry(struct DirEntry *de, struct Globals *glob)
403 struct DirHandle dh;
404 LONG err = 0;
405 ULONG nwritten;
407 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n",
408 de->index, de->cluster));
410 InitDirHandle(glob->sb, de->cluster, &dh, FALSE, glob);
412 err =
413 WriteFileChunk(&(dh.ioh), de->pos, sizeof(struct FATDirEntry),
414 (UBYTE *) &(de->e.entry), &nwritten);
415 if (err != 0)
417 D(bug("[fat] dir entry update failed\n"));
418 ReleaseDirHandle(&dh, glob);
419 return err;
422 ReleaseDirHandle(&dh, glob);
424 return 0;
427 LONG AllocDirEntry(struct DirHandle *dh, ULONG gap, struct DirEntry *de,
428 struct Globals *glob)
430 ULONG nwant;
431 LONG err;
432 ULONG nfound;
433 BOOL clusteradded = FALSE;
435 /* Find out how many entries we need */
436 nwant = gap + 1;
438 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant));
440 /* Get back to the start of the dir */
441 RESET_DIRHANDLE(dh);
443 /* Search the directory until we find a large enough gap */
444 nfound = 0;
445 de->index = -1;
446 while (nfound < nwant)
448 err = GetDirEntry(dh, de->index + 1, de, glob);
450 /* If we can't get the entry, then we ran off the end, so there's no
451 * space left */
452 if (err == ERROR_OBJECT_NOT_FOUND)
454 D(bug("[fat] ran off the end of the directory, no space left\n"));
455 return ERROR_NO_FREE_STORE;
458 /* Return any other error direct */
459 if (err != 0)
460 return err;
462 /* If it's unused, make a note */
463 if (de->e.entry.name[0] == 0xe5)
465 nfound++;
466 continue;
469 /* If we hit end-of-directory, then we can shortcut it */
470 if (de->e.entry.name[0] == 0x00)
472 ULONG last;
474 if (de->index + nwant >= 0x10000)
476 D(bug("[fat] hit end-of-directory marker,"
477 " but there's not enough room left after it\n"));
478 return ERROR_NO_FREE_STORE;
481 D(bug("[fat] found end-of-directory marker,"
482 " making space after it\n"));
484 last = de->index + nwant;
487 if (GetDirEntry(dh, de->index + 1, de, glob) != 0)
488 clusteradded = TRUE;
489 de->e.entry.name[0] = 0x00;
490 UpdateDirEntry(de, glob);
492 while (de->index != last);
494 D(bug("[fat] new end-of-directory is entry %ld\n", de->index));
496 /* Clear all remaining entries in any new cluster added */
497 if (clusteradded)
498 while (GetDirEntry(dh, de->index + 1, de, glob) == 0)
500 memset(&de->e.entry, 0, sizeof(struct FATDirEntry));
501 UpdateDirEntry(de, glob);
504 /* Get the previous entry; this is the base (short name) entry */
505 GetDirEntry(dh, last - 1, de, glob);
507 break;
510 /* Anything else is an in-use entry, so reset our count */
511 nfound = 0;
514 D(bug("[fat] found a gap, base (short name) entry is %ld\n",
515 de->index));
516 return 0;
519 LONG CreateDirEntry(struct DirHandle *dh, STRPTR name, ULONG namelen,
520 UBYTE attr, ULONG cluster, struct DirEntry *de, struct Globals *glob)
522 ULONG gap;
523 LONG err;
526 bug("[fat] creating dir entry (name '");
527 RawPutChars(name, namelen);
528 bug("' attr 0x%02x cluster %ld)\n", attr, cluster);
531 /* Find out how many extra entries we need for the long name */
532 gap = NumLongNameEntries(name, namelen);
534 /* Search for a suitable unused entry */
535 err = AllocDirEntry(dh, gap, de, glob);
536 if (err != 0)
537 return err;
539 /* Build the entry */
540 FillDirEntry(de, attr, cluster, glob);
542 SetDirEntryName(de, name, namelen);
544 if ((err = UpdateDirEntry(de, glob)) != 0)
546 D(bug(
547 "[fat] couldn't update base directory entry, creation failed\n"));
548 return err;
551 D(bug("[fat] created dir entry %ld\n", de->index));
553 return 0;
556 void FillDirEntry(struct DirEntry *de, UBYTE attr, ULONG cluster,
557 struct Globals *glob)
559 struct DateStamp ds;
561 de->e.entry.attr = attr;
562 de->e.entry.nt_res = 0;
564 DateStamp(&ds);
565 ConvertDOSDate(&ds, &(de->e.entry.create_date),
566 &(de->e.entry.create_time), glob);
567 de->e.entry.write_date = de->e.entry.create_date;
568 de->e.entry.write_time = de->e.entry.create_time;
569 de->e.entry.last_access_date = de->e.entry.create_date;
570 de->e.entry.create_time_tenth = ds.ds_Tick % (TICKS_PER_SECOND * 2)
571 / (TICKS_PER_SECOND / 10);
573 de->e.entry.first_cluster_lo = cluster & 0xffff;
574 de->e.entry.first_cluster_hi = cluster >> 16;
576 de->e.entry.file_size = 0;
579 LONG DeleteDirEntry(struct DirEntry *de, struct Globals *glob)
581 struct DirHandle dh;
582 UBYTE checksum;
583 ULONG order;
584 LONG err;
586 InitDirHandle(glob->sb, de->cluster, &dh, FALSE, glob);
588 /* Calculate the short name checksum before we trample on the name */
589 CALC_SHORT_NAME_CHECKSUM(de->e.entry.name, checksum);
591 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
593 /* Mark the short entry free */
594 de->e.entry.name[0] = 0xe5;
595 UpdateDirEntry(de, glob);
597 D(bug("[fat] deleted short name entry\n"));
599 /* Now we loop over the previous entries, looking for matching long name
600 * entries and killing them */
601 order = 1;
602 while ((err = GetDirEntry(&dh, de->index - 1, de, glob)) == 0)
605 /* See if this is a matching long name entry. If it's not, we're done */
606 if (!((de->e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
607 (de->e.long_entry.order & ~0x40) != order ||
608 de->e.long_entry.checksum != checksum)
610 break;
612 /* Kill it */
613 de->e.entry.name[0] = 0xe5;
614 UpdateDirEntry(de, glob);
616 order++;
619 D(bug("[fat] deleted %ld long name entries\n", order - 1));
621 ReleaseDirHandle(&dh, glob);
623 return err;
626 LONG FillFIB(struct ExtFileLock *fl, struct FileInfoBlock *fib,
627 struct Globals *glob)
629 struct FSSuper *sb = glob->sb;
630 struct GlobalLock *gl = (fl != NULL ? fl->gl : &sb->info->root_lock);
631 struct DirHandle dh;
632 struct DirEntry de;
633 LONG result = 0;
634 int len;
636 D(bug("\tFilling FIB data.\n"));
638 if (gl == &sb->info->root_lock)
640 D(bug("\t\ttype: root directory\n"));
641 fib->fib_DirEntryType = ST_ROOT;
643 else if (gl->attr & ATTR_DIRECTORY)
645 D(bug("\t\ttype: directory\n"));
646 fib->fib_DirEntryType = ST_USERDIR;
648 else
650 D(bug("\t\ttype: file\n"));
651 fib->fib_DirEntryType = ST_FILE;
654 D(bug("\t\tsize: %ld\n", gl->size));
656 fib->fib_Size = gl->size;
657 fib->fib_NumBlocks = ((gl->size + (sb->clustersize - 1))
658 >> sb->clustersize_bits) << sb->cluster_sectors_bits;
659 fib->fib_EntryType = fib->fib_DirEntryType;
660 fib->fib_DiskKey = 0xfffffffflu; //fl->entry;
662 if (fib->fib_DirEntryType == ST_ROOT)
663 CopyMem(&sb->volume.create_time, &fib->fib_Date,
664 sizeof(struct DateStamp));
665 else
667 InitDirHandle(sb, gl->dir_cluster, &dh, FALSE, glob);
668 GetDirEntry(&dh, gl->dir_entry, &de, glob);
669 ConvertFATDate(de.e.entry.write_date, de.e.entry.write_time,
670 &fib->fib_Date, glob);
671 ReleaseDirHandle(&dh, glob);
674 len = gl->name[0] <= 106 ? gl->name[0] : 106;
675 CopyMem(gl->name, fib->fib_FileName, len + 1);
676 fib->fib_FileName[len + 1] = '\0';
677 D(bug("\t\tname (len %ld) %s\n", len, fib->fib_FileName + 1));
679 fib->fib_Protection = 0;
680 if (gl->attr & ATTR_READ_ONLY)
681 fib->fib_Protection |= (FIBF_DELETE | FIBF_WRITE);
682 if (gl->attr & ATTR_ARCHIVE)
683 fib->fib_Protection |= FIBF_ARCHIVE;
685 fib->fib_Comment[0] = 0;
686 #if DEBUG_NAMES
687 CopyMem(gl->shortname, fib->fib_Comment, gl->shortname[0] + 1);
688 #endif
690 return result;