Corrected module name.
[AROS.git] / rom / filesys / fat / direntry.c
blob02b1943afeaae2468422f733b41ce1194a0e45b1
1 /*
2 * fat-handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2013 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, BOOL reuse) {
30 /* dh may or may not be initialised when this is called. if it is, then it
31 * probably has a valid cache block that we need to free, but we wouldn't
32 * know. we test the superblock pointer to figure out if it's valid or
33 * not */
34 if (reuse && (dh->ioh.sb == sb)) {
35 D(bug("[fat] reusing directory handle\n"));
36 if (dh->ioh.block != NULL) {
37 Cache_FreeBlock(sb->cache, dh->ioh.block);
38 dh->ioh.block = NULL;
41 else {
42 dh->ioh.sb = sb;
43 dh->ioh.block = NULL;
46 if (cluster == 0) {
47 dh->ioh.first_cluster = sb->rootdir_cluster;
48 dh->ioh.first_sector = sb->rootdir_sector;
50 else {
51 dh->ioh.first_cluster = cluster;
52 dh->ioh.first_sector = 0;
55 RESET_DIRHANDLE(dh);
56 dh->ioh.cur_sector = dh->ioh.first_sector;
57 dh->ioh.sector_offset = 0;
59 D(bug("[fat] initialised dir handle, first cluster is %ld, first sector is %ld\n", dh->ioh.first_cluster, dh->ioh.first_sector));
61 return 0;
64 LONG ReleaseDirHandle(struct DirHandle *dh) {
65 D(bug("[fat] releasing dir handle (cluster %ld)\n", dh->ioh.first_cluster));
66 RESET_DIRHANDLE(dh);
67 return 0;
70 LONG GetDirEntry(struct DirHandle *dh, ULONG index, struct DirEntry *de) {
71 LONG err = 0;
72 ULONG nread;
74 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n", index, dh->ioh.first_cluster));
76 /* fat dirs are limited to 2^16 entries */
77 if (index >= 0x10000) {
78 D(bug("[fat] request for out-of-range index, returning not found\n"));
79 return ERROR_OBJECT_NOT_FOUND;
82 /* setup the return object */
83 de->sb = dh->ioh.sb;
84 de->cluster = dh->ioh.first_cluster;
85 de->index = index;
86 de->pos = index * sizeof(struct FATDirEntry);
88 /* get the data directly into the entry */
89 err = ReadFileChunk(&(dh->ioh), de->pos, sizeof(struct FATDirEntry), (UBYTE *) &(de->e.entry), &nread);
90 if (err != 0) {
91 D(bug("[fat] dir entry lookup failed\n"));
92 return err;
95 /* remember where we are for GetNextDirEntry() */
96 dh->cur_index = index;
98 /* done! */
99 return 0;
102 LONG GetNextDirEntry(struct DirHandle *dh, struct DirEntry *de) {
103 LONG err;
105 D(bug("[fat] looking for next entry after index %ld\n", dh->cur_index));
107 /* cur_index defaults to -1, so this will do the right thing even on a
108 * fresh dirhandle */
109 while ((err = GetDirEntry(dh, dh->cur_index + 1, de)) == 0) {
110 /* end of directory, there is no next entry */
111 if (de->e.entry.name[0] == 0x00) {
112 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n", dh->cur_index));
113 RESET_DIRHANDLE(dh);
114 return ERROR_OBJECT_NOT_FOUND;
117 /* skip unused entries */
118 if (de->e.entry.name[0] == 0xe5) {
119 D(bug("[fat] entry %ld is empty, skipping it\n", dh->cur_index));
120 continue;
123 /* this flag will be set for both volume name entries and long
124 * filename entries. either way we want to skip them */
125 if (de->e.entry.attr & ATTR_VOLUME_ID) {
126 D(bug("[fat] entry %ld is a volume name or long filename, skipping it\n", dh->cur_index));
127 continue;
130 /* ignore the . and .. entries */
131 if (de->e.entry.name[0] == '.' &&
132 ((de->index == 0 && strncmp((char *) de->e.entry.name, ". ", 11) == 0) ||
133 (de->index == 1 && strncmp((char *) de->e.entry.name, ".. ", 11) == 0))) {
134 D(bug("[fat] skipping . or .. entry\n"));
135 continue;
138 D(bug("[fat] returning entry %ld\n", dh->cur_index));
140 return 0;
143 return err;
146 LONG GetParentDir(struct DirHandle *dh, struct DirEntry *de) {
147 LONG err = 0;
148 ULONG cluster;
150 D(bug("[fat] getting parent for directory at cluster %ld\n", dh->ioh.first_cluster));
152 /* if we're already at the root, then we can't go any further */
153 if (dh->ioh.first_cluster == dh->ioh.sb->rootdir_cluster) {
154 D(bug("[fat] trying to go up past the root, so entry not found\n"));
155 return ERROR_OBJECT_NOT_FOUND;
158 /* otherwise, the next cluster is held in the '..' entry, which is
159 * entry #1 */
160 GetDirEntry(dh, 1, de);
162 /* make sure it's actually the parent dir entry */
163 if (((de->e.entry.attr & ATTR_DIRECTORY) == 0) ||
164 strncmp((char *) de->e.entry.name, ".. ", 11) != 0) {
165 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
166 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n",
167 11, de->e.entry.name, de->e.entry.attr));
168 return ERROR_OBJECT_NOT_FOUND;
171 /* take us up */
172 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE);
174 /* get handle on grandparent dir so we can find entry with parent's
175 * name */
176 if (dh->ioh.first_cluster != dh->ioh.sb->rootdir_cluster) {
177 D(bug("[fat] getting grandparent, first cluster is %ld, root cluster is %ld\n", dh->ioh.first_cluster, dh->ioh.sb->rootdir_cluster));
178 cluster = dh->ioh.first_cluster;
179 GetDirEntry(dh, 1, de);
180 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE);
182 err = GetDirEntryByCluster(dh, cluster, de);
185 return err;
188 LONG GetDirEntryByCluster(struct DirHandle *dh, ULONG cluster,
189 struct DirEntry *de) {
190 LONG err;
192 D(bug("[fat] looking for dir entry with first cluster %lu\n", cluster));
194 /* start at the start */
195 RESET_DIRHANDLE(dh);
197 /* loop through the entries until we find a match */
198 while ((err = GetNextDirEntry(dh, de)) == 0) {
199 if (de->e.entry.first_cluster_hi == (cluster >> 16) &&
200 de->e.entry.first_cluster_lo == (cluster & 0xffff)) {
201 D(bug("[fat] matched starting cluster at entry %ld, returning\n",
202 dh->cur_index));
203 return 0;
207 D(bug("[fat] dir entry with first cluster %lu not found\n", cluster));
208 return err;
211 LONG GetDirEntryByName(struct DirHandle *dh, STRPTR name, ULONG namelen, struct DirEntry *de) {
212 UBYTE buf[256];
213 ULONG buflen;
214 LONG err;
216 D(bug("[fat] looking for dir entry with name '%s'\n", name));
218 /* start at the start */
219 RESET_DIRHANDLE(dh);
221 /* loop through the entries until we find a match */
222 while ((err = GetNextDirEntry(dh, de)) == 0) {
223 /* compare with the short name first, since we already have it */
224 GetDirEntryShortName(de, buf, &buflen);
225 if (namelen == buflen && strnicmp((char *) name, (char *) buf, buflen) == 0) {
226 D(bug("[fat] matched short name '%s' at entry %ld, returning\n", buf, dh->cur_index));
227 return 0;
230 /* no match, extract the long name and compare with that instead */
231 GetDirEntryLongName(de, buf, &buflen);
232 if (namelen == buflen && strnicmp((char *) name, (char *) buf, buflen) == 0) {
233 D(bug("[fat] matched long name '%s' at entry %ld, returning\n", buf, dh->cur_index));
234 return 0;
238 return err;
241 LONG GetDirEntryByPath(struct DirHandle *dh, STRPTR path, ULONG pathlen, struct DirEntry *de) {
242 LONG err;
243 ULONG len, i;
245 D(bug("[fat] looking for entry with path '"); RawPutChars(path, pathlen);
246 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster));
248 /* get back to the start of the dir */
249 RESET_DIRHANDLE(dh);
251 /* if it starts with a volume specifier (or just a :), remove it and get
252 * us back to the root dir */
253 for (i = 0; i < pathlen; i++)
254 if (path[i] == ':') {
255 D(bug("[fat] path has volume specifier, moving to the root dir\n"));
257 pathlen -= (i+1);
258 path = &path[i+1];
260 InitDirHandle(dh->ioh.sb, 0, dh, TRUE);
262 /* If we were called with simply ":" as the name we will return
263 immediately after this, so we prepare a fictional direntry for
264 such a case.
265 Note that we fill only fields which are actually used in our handler */
266 de->cluster = 0;
267 de->index = -1; /* WARNING! Dummy index */
268 de->e.entry.attr = ATTR_DIRECTORY;
269 de->e.entry.first_cluster_hi = 0;
270 de->e.entry.first_cluster_lo = 0;
272 break;
275 D(bug("[fat] now looking for entry with path '"); RawPutChars(path, pathlen);
276 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster));
278 /* each time around the loop we find one dir/file in the full path */
279 while (pathlen > 0) {
281 /* zoom forward and find the first dir separator */
282 for (len = 0; len < pathlen && path[len] != '/'; len++);
284 D(bug("[fat] remaining path is '"); RawPutChars(path, pathlen);
285 bug("' (%d bytes), current chunk is '", pathlen); RawPutChars(path, len);
286 bug("' (%d bytes)\n", len));
288 /* if the first character is a /, then we have to go up a level */
289 if (len == 0) {
291 /* get the parent dir, and bale if we've gone past it (i.e. we are
292 * the root) */
293 if ((err = GetParentDir(dh, de)) != 0)
294 return err;
297 /* otherwise, we want to search the current directory for this name */
298 else {
299 if ((err = GetDirEntryByName(dh, path, len, de)) != 0)
300 return ERROR_OBJECT_NOT_FOUND;
303 /* move up the buffer */
304 path += len;
305 pathlen -= len;
307 /* a / here is either the path separator or the directory we just went
308 * up. either way, we have to ignore it */
309 if (pathlen > 0 && path[0] == '/') {
310 path++;
311 pathlen--;
314 if (pathlen > 0) {
315 /* more to do, so this entry had better be a directory */
316 if (!(de->e.entry.attr & ATTR_DIRECTORY)) {
317 D(bug("[fat] '%.*s' is not a directory, so can't go any further\n", len, path));
318 return ERROR_OBJECT_WRONG_TYPE;
321 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE);
325 /* nothing left, so we've found it */
326 D(bug("[fat] found the entry, returning it\n"));
327 return 0;
330 LONG UpdateDirEntry(struct DirEntry *de) {
331 struct DirHandle dh;
332 LONG err = 0;
333 ULONG nwritten;
335 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n", de->index, de->cluster));
337 InitDirHandle(glob->sb, de->cluster, &dh, FALSE);
339 err = WriteFileChunk(&(dh.ioh), de->pos, sizeof(struct FATDirEntry), (UBYTE *) &(de->e.entry), &nwritten);
340 if (err != 0) {
341 D(bug("[fat] dir entry update failed\n"));
342 ReleaseDirHandle(&dh);
343 return err;
346 ReleaseDirHandle(&dh);
348 return 0;
351 LONG AllocDirEntry(struct DirHandle *dh, ULONG gap, struct DirEntry *de) {
352 ULONG nwant;
353 LONG err;
354 ULONG nfound;
355 BOOL clusteradded = FALSE;
357 /* find out how many entries we need */
358 nwant = gap + 1;
360 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant));
362 /* get back to the start of the dir */
363 RESET_DIRHANDLE(dh);
365 /* search the directory until we find a large enough gap */
366 nfound = 0;
367 de->index = -1;
368 while (nfound < nwant) {
369 err = GetDirEntry(dh, de->index+1, de);
371 /* if we can't get the entry, then we ran off the end, so there's no
372 * space left */
373 if (err == ERROR_OBJECT_NOT_FOUND) {
374 D(bug("[fat] ran off the end of the directory, no space left\n"));
375 return ERROR_NO_FREE_STORE;
378 /* return any other error direct */
379 if (err != 0)
380 return err;
382 /* if it's unused, make a note */
383 if (de->e.entry.name[0] == 0xe5) {
384 nfound++;
385 continue;
388 /* if we hit end-of-directory, then we can shortcut it */
389 if (de->e.entry.name[0] == 0x00) {
390 ULONG last;
392 if (de->index + nwant >= 0x10000) {
393 D(bug("[fat] hit end-of-directory marker, but there's not enough room left after it\n"));
394 return ERROR_NO_FREE_STORE;
397 D(bug("[fat] found end-of-directory marker, making space after it\n"));
399 last = de->index + nwant;
400 do {
401 if (GetDirEntry(dh, de->index + 1, de) != 0)
402 clusteradded = TRUE;
403 de->e.entry.name[0] = 0x00;
404 UpdateDirEntry(de);
405 } while(de->index != last);
407 D(bug("[fat] new end-of-directory is entry %ld\n", de->index));
409 /* clear all remaining entries in any new cluster added */
410 if (clusteradded)
411 while (GetDirEntry(dh, de->index + 1, de) == 0) {
412 memset(&de->e.entry, 0, sizeof(struct FATDirEntry));
413 UpdateDirEntry(de);
416 /* get the previous entry; this is the base (short name) entry */
417 GetDirEntry(dh, last - 1, de);
419 break;
422 /* anything else is an in-use entry, so reset our count */
423 nfound = 0;
426 D(bug("[fat] found a gap, base (short name) entry is %ld\n", de->index));
427 return 0;
430 LONG CreateDirEntry(struct DirHandle *dh, STRPTR name, ULONG namelen,
431 UBYTE attr, ULONG cluster, struct DirEntry *de) {
432 ULONG gap;
433 LONG err;
434 struct DateStamp ds;
436 D(bug("[fat] creating dir entry (name '"); RawPutChars(name, namelen);
437 bug("' attr 0x%02x cluster %ld)\n", attr, cluster));
439 /* find out how many extra entries we need for the long name */
440 gap = NumLongNameEntries(name, namelen);
442 /* search for a suitable unused entry */
443 err = AllocDirEntry(dh, gap, de);
444 if (err != 0)
445 return err;
447 /* build the entry */
448 de->e.entry.attr = attr;
449 de->e.entry.nt_res = 0;
451 DateStamp(&ds);
452 ConvertAROSDate(&ds, &(de->e.entry.create_date), &(de->e.entry.create_time));
453 de->e.entry.write_date = de->e.entry.create_date;
454 de->e.entry.write_time = de->e.entry.create_time;
455 de->e.entry.last_access_date = de->e.entry.create_date;
456 de->e.entry.create_time_tenth = ds.ds_Tick % (TICKS_PER_SECOND * 2)
457 / (TICKS_PER_SECOND / 10);
459 de->e.entry.first_cluster_lo = cluster & 0xffff;
460 de->e.entry.first_cluster_hi = cluster >> 16;
462 de->e.entry.file_size = 0;
464 SetDirEntryName(de, name, namelen);
466 if ((err = UpdateDirEntry(de)) != 0) {
467 D(bug("[fat] couldn't update base directory entry, creation failed\n"));
468 return err;
471 D(bug("[fat] created dir entry %ld\n", de->index));
473 return 0;
476 LONG DeleteDirEntry(struct DirEntry *de) {
477 struct DirHandle dh;
478 UBYTE checksum;
479 ULONG order;
480 LONG err;
482 InitDirHandle(glob->sb, de->cluster, &dh, FALSE);
484 /* calculate the short name checksum before we trample on the name */
485 CALC_SHORT_NAME_CHECKSUM(de->e.entry.name, checksum);
487 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
489 /* mark the short entry free */
490 de->e.entry.name[0] = 0xe5;
491 UpdateDirEntry(de);
493 D(bug("[fat] deleted short name entry\n"));
495 /* now we loop over the previous entries, looking for matching long name
496 * entries and killing them */
497 order = 1;
498 while ((err = GetDirEntry(&dh, de->index-1, de)) == 0) {
500 /* see if this is a matching long name entry. if it's not, we're done */
501 if (!((de->e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
502 (de->e.long_entry.order & ~0x40) != order ||
503 de->e.long_entry.checksum != checksum)
505 break;
507 /* kill it */
508 de->e.entry.name[0] = 0xe5;
509 UpdateDirEntry(de);
511 order++;
514 D(bug("[fat] deleted %ld long name entries\n", order-1));
516 ReleaseDirHandle(&dh);
518 return err;
523 #define sb glob->sb
525 LONG FillFIB (struct ExtFileLock *fl, struct FileInfoBlock *fib) {
526 struct GlobalLock *gl = (fl != NULL ? fl->gl : &sb->info->root_lock);
527 struct DirHandle dh;
528 struct DirEntry de;
529 LONG result = 0;
530 int len;
532 D(bug("\tFilling FIB data.\n"));
534 if (gl == &sb->info->root_lock) {
535 D(bug("\t\ttype: root directory\n"));
536 fib->fib_DirEntryType = ST_ROOT;
538 else if (gl->attr & ATTR_DIRECTORY) {
539 D(bug("\t\ttype: directory\n"));
540 fib->fib_DirEntryType = ST_USERDIR;
542 else {
543 D(bug("\t\ttype: file\n"));
544 fib->fib_DirEntryType = ST_FILE;
547 D(bug("\t\tsize: %ld\n", gl->size));
549 fib->fib_Size = gl->size;
550 fib->fib_NumBlocks = ((gl->size + (sb->clustersize - 1)) >> sb->clustersize_bits) << sb->cluster_sectors_bits;
551 fib->fib_EntryType = fib->fib_DirEntryType;
552 fib->fib_DiskKey = 0xfffffffflu; //fl->entry;
554 if (fib->fib_DirEntryType == ST_ROOT)
555 CopyMem(&sb->volume.create_time, &fib->fib_Date, sizeof(struct DateStamp));
556 else {
557 InitDirHandle(sb, gl->dir_cluster, &dh, FALSE);
558 GetDirEntry(&dh, gl->dir_entry, &de);
559 ConvertFATDate(de.e.entry.write_date, de.e.entry.write_time, &fib->fib_Date);
560 ReleaseDirHandle(&dh);
563 len = gl->name[0] <= 106 ? gl->name[0] : 106;
564 CopyMem(gl->name, fib->fib_FileName, len + 1);
565 fib->fib_FileName[len + 1] = '\0';
566 D(bug("\t\tname (len %ld) %s\n", len, fib->fib_FileName + 1));
568 fib->fib_Protection = 0;
569 if (gl->attr & ATTR_READ_ONLY) fib->fib_Protection |= (FIBF_DELETE | FIBF_WRITE);
570 if (gl->attr & ATTR_ARCHIVE) fib->fib_Protection |= FIBF_ARCHIVE;
572 fib->fib_Comment[0] = 0;
573 #if DEBUG_NAMES
574 CopyMem(gl->shortname, fib->fib_Comment, gl->shortname[0] + 1);
575 #endif
577 return result;