Fixed binary search: no more infinite loops when vendor is unknown.
[tangerine.git] / workbench / fs / fat / direntry.c
blobc11dd9eedb4ab261c48531e273ac2a3619eb55c1
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>
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 #define RESET_DIRHANDLE(dh) \
30 do { \
31 RESET_HANDLE(&(dh->ioh)); \
32 dh->cur_index = 0xffffffff; \
33 } while(0);
35 LONG InitDirHandle(struct FSSuper *sb, ULONG cluster, struct DirHandle *dh, BOOL reuse) {
36 /* dh may or may not be initialised when this is called. if it is, then it
37 * probably has a valid cache block that we need to free, but we wouldn't
38 * know. we test the superblock pointer to figure out if it's valid or
39 * not */
40 if (reuse && (dh->ioh.sb == sb)) {
41 D(bug("[fat] reusing directory handle\n"));
42 if (dh->ioh.block != NULL) {
43 cache_put_block(sb->cache, dh->ioh.block, 0);
44 dh->ioh.block = NULL;
47 else {
48 dh->ioh.sb = sb;
49 dh->ioh.block = NULL;
52 if (cluster == 0) {
53 dh->ioh.first_cluster = sb->rootdir_cluster;
54 dh->ioh.first_sector = sb->rootdir_sector;
56 else {
57 dh->ioh.first_cluster = cluster;
58 dh->ioh.first_sector = 0;
61 RESET_DIRHANDLE(dh);
62 dh->ioh.cur_sector = dh->ioh.first_sector;
63 dh->ioh.sector_offset = 0;
65 D(bug("[fat] initialised dir handle, first cluster is %ld, first sector is %ld\n", dh->ioh.first_cluster, dh->ioh.first_sector));
67 return 0;
70 LONG ReleaseDirHandle(struct DirHandle *dh) {
71 D(bug("[fat] releasing dir handle (cluster %ld)\n", dh->ioh.first_cluster));
72 RESET_DIRHANDLE(dh);
73 return 0;
76 LONG GetDirEntry(struct DirHandle *dh, ULONG index, struct DirEntry *de) {
77 LONG err = 0;
78 ULONG nread;
80 D(bug("[fat] looking for dir entry %ld in dir starting at cluster %ld\n", index, dh->ioh.first_cluster));
82 /* fat dirs are limited to 2^16 entries */
83 if (index >= 0x10000) {
84 D(bug("[fat] request for out-of-range index, returning not found\n"));
85 return ERROR_OBJECT_NOT_FOUND;
88 /* setup the return object */
89 de->sb = dh->ioh.sb;
90 de->cluster = dh->ioh.first_cluster;
91 de->index = index;
92 de->pos = index * sizeof(struct FATDirEntry);
94 /* get the data directly into the entry */
95 err = ReadFileChunk(&(dh->ioh), de->pos, sizeof(struct FATDirEntry), (UBYTE *) &(de->e.entry), &nread);
96 if (err != 0) {
97 D(bug("[fat] dir entry lookup failed\n"));
98 return err;
101 /* remember where we are for GetNextDirEntry() */
102 dh->cur_index = index;
104 /* done! */
105 return 0;
108 LONG GetNextDirEntry(struct DirHandle *dh, struct DirEntry *de) {
109 LONG err;
111 D(bug("[fat] looking for next entry after index %ld\n", dh->cur_index));
113 /* cur_index defaults to -1, so this will do the right thing even on a
114 * fresh dirhandle */
115 while ((err = GetDirEntry(dh, dh->cur_index + 1, de)) == 0) {
116 /* end of directory, there is no next entry */
117 if (de->e.entry.name[0] == 0x00) {
118 D(bug("[fat] entry %ld is end-of-directory marker, we're done\n", dh->cur_index));
119 RESET_DIRHANDLE(dh);
120 return ERROR_OBJECT_NOT_FOUND;
123 /* skip unused entries */
124 if (de->e.entry.name[0] == 0xe5) {
125 D(bug("[fat] entry %ld is empty, skipping it\n", dh->cur_index));
126 continue;
129 /* this flag will be set for both volume name entries and long
130 * filename entries. either way we want to skip them */
131 if (de->e.entry.attr & ATTR_VOLUME_ID) {
132 D(bug("[fat] entry %ld is a volume name or long filename, skipping it\n", dh->cur_index));
133 continue;
136 /* ignore the . and .. entries */
137 if (de->e.entry.name[0] == '.' &&
138 ((de->index == 0 && strncmp((char *) de->e.entry.name, ". ", 11) == 0) ||
139 (de->index == 1 && strncmp((char *) de->e.entry.name, ".. ", 11) == 0))) {
140 D(bug("[fat] skipping . or .. entry\n"));
141 continue;
144 D(bug("[fat] returning entry %ld\n", dh->cur_index));
146 return 0;
149 return err;
152 LONG GetParentDir(struct DirHandle *dh, struct DirEntry *de) {
153 D(bug("[fat] getting parent for directory at cluster %ld\n", dh->ioh.first_cluster));
155 /* if we're already at the root, then we can't go any further */
156 if (dh->ioh.first_cluster == dh->ioh.sb->rootdir_cluster) {
157 D(bug("[fat] trying go up past the root, so entry not found\n"));
158 return ERROR_OBJECT_NOT_FOUND;
161 /* otherwise, the next cluster is held in the '..' entry, which is
162 * entry #1 */
163 GetDirEntry(dh, 1, de);
165 /* make sure it's actually the parent dir entry */
166 if (((de->e.entry.attr & ATTR_DIRECTORY) == 0) ||
167 strncmp((char *) de->e.entry.name, ".. ", 11) != 0) {
168 D(bug("[fat] entry index 1 does not have name '..', can't go up\n"));
169 D(bug("[fat] actual name: '%.*s', attrs: 0x%x\n",
170 11, de->e.entry.name, de->e.entry.attr));
171 return ERROR_OBJECT_NOT_FOUND;
174 /* take us up */
175 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE);
177 return 0;
180 LONG GetDirEntryByName(struct DirHandle *dh, STRPTR name, ULONG namelen, struct DirEntry *de) {
181 UBYTE buf[256];
182 ULONG buflen;
183 LONG err;
185 D(bug("[fat] looking for dir entry with name '%s'\n", name));
187 /* start at the start */
188 RESET_DIRHANDLE(dh);
190 /* loop through the entries until we find a match */
191 while ((err = GetNextDirEntry(dh, de)) == 0) {
192 /* compare with the short name first, since we already have it */
193 GetDirEntryShortName(de, buf, &buflen);
194 if (namelen == buflen && strnicmp((char *) name, (char *) buf, buflen) == 0) {
195 D(bug("[fat] matched short name '%s' at entry %ld, returning\n", buf, dh->cur_index));
196 return 0;
199 /* no match, extract the long name and compare with that instead */
200 GetDirEntryLongName(de, buf, &buflen);
201 if (namelen == buflen && strnicmp((char *) name, (char *) buf, buflen) == 0) {
202 D(bug("[fat] matched long name '%s' at entry %ld, returning\n", buf, dh->cur_index));
203 return 0;
207 return err;
210 LONG GetDirEntryByPath(struct DirHandle *dh, STRPTR path, ULONG pathlen, struct DirEntry *de) {
211 LONG err;
212 ULONG len, i;
214 D(bug("[fat] looking for entry with path '"); RawPutChars(path, pathlen);
215 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster));
217 /* get back to the start of the dir */
218 RESET_DIRHANDLE(dh);
220 /* if it starts with a volume specifier (or just a :), remove it and get
221 * us back to the root dir */
222 for (i = 0; i < pathlen; i++)
223 if (path[i] == ':') {
224 D(bug("[fat] path has volume specifier, moving to the root dir\n"));
226 pathlen -= (i+1);
227 path = &path[i+1];
229 InitDirHandle(dh->ioh.sb, 0, dh, TRUE);
231 /* If we were called with simply ":" as the name we will return
232 immediately after this, so we prepare a fictional direntry for
233 such a case.
234 Note that we fill only fields which are actually used in our handler */
235 de->cluster = 0;
236 de->index = -1; /* WARNING! Dummy index */
237 de->e.entry.attr = ATTR_DIRECTORY;
238 de->e.entry.first_cluster_hi = 0;
239 de->e.entry.first_cluster_lo = 0;
241 break;
244 D(bug("[fat] now looking for entry with path '"); RawPutChars(path, pathlen);
245 bug("' from dir at cluster %ld\n", dh->ioh.first_cluster));
247 /* each time around the loop we find one dir/file in the full path */
248 while (pathlen > 0) {
250 /* zoom forward and find the first dir separator */
251 for (len = 0; len < pathlen && path[len] != '/'; len++);
253 D(bug("[fat] remaining path is '"); RawPutChars(path, pathlen);
254 bug("' (%d bytes), current chunk is '", pathlen); RawPutChars(path, len);
255 bug("' (%d bytes)\n", len));
257 /* if the first character is a /, then we have to go up a level */
258 if (len == 0) {
260 /* get the parent dir, and bale if we've gone past it (i.e. we are
261 * the root) */
262 if ((err = GetParentDir(dh, de)) != 0)
263 return err;
266 /* otherwise, we want to search the current directory for this name */
267 else {
268 if ((err = GetDirEntryByName(dh, path, len, de)) != 0)
269 return ERROR_OBJECT_NOT_FOUND;
272 /* move up the buffer */
273 path += len;
274 pathlen -= len;
276 /* a / here is either the path separator or the directory we just went
277 * up. either way, we have to ignore it */
278 if (pathlen > 0 && path[0] == '/') {
279 path++;
280 pathlen--;
283 if (pathlen > 0) {
284 /* more to do, so this entry had better be a directory */
285 if (!(de->e.entry.attr & ATTR_DIRECTORY)) {
286 D(bug("[fat] '%.*s' is not a directory, so can't go any further\n", len, path));
287 return ERROR_OBJECT_WRONG_TYPE;
290 InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(de), dh, TRUE);
294 /* nothing left, so we've found it */
295 D(bug("[fat] found the entry, returning it\n"));
296 return 0;
299 LONG UpdateDirEntry(struct DirEntry *de) {
300 struct DirHandle dh;
301 LONG err = 0;
302 ULONG nwritten;
304 D(bug("[fat] writing dir entry %ld in dir starting at cluster %ld\n", de->index, de->cluster));
306 InitDirHandle(glob->sb, de->cluster, &dh, FALSE);
308 err = WriteFileChunk(&(dh.ioh), de->pos, sizeof(struct FATDirEntry), (UBYTE *) &(de->e.entry), &nwritten);
309 if (err != 0) {
310 D(bug("[fat] dir entry update failed\n"));
311 ReleaseDirHandle(&dh);
312 return err;
315 ReleaseDirHandle(&dh);
317 return 0;
320 LONG CreateDirEntry(struct DirHandle *dh, STRPTR name, ULONG namelen, UBYTE attr, ULONG cluster, struct DirEntry *de) {
321 ULONG nwant;
322 LONG err;
323 ULONG nfound;
324 struct DateStamp ds;
326 D(bug("[fat] creating dir entry (name '"); RawPutChars(name, namelen);
327 bug("' attr 0x%02x cluster %ld)\n", attr, cluster));
329 /* find out how many entries we need */
330 nwant = NumLongNameEntries(name, namelen) + 1;
332 D(bug("[fat] need to find room for %ld contiguous entries\n", nwant));
334 /* get back to the start of the dir */
335 RESET_DIRHANDLE(dh);
337 /* search the directory until we find a large enough gap */
338 nfound = 0;
339 de->index = -1;
340 while (nfound < nwant) {
341 err = GetDirEntry(dh, de->index+1, de);
343 /* if we can't get the entry, then we ran off the end, so there's no
344 * space left */
345 if (err == ERROR_OBJECT_NOT_FOUND) {
346 D(bug("[fat] ran off the end of the directory, no space left\n"));
347 return ERROR_NO_FREE_STORE;
350 /* return any other error direct */
351 if (err != 0)
352 return err;
354 /* if it's unused, make a note */
355 if (de->e.entry.name[0] == 0xe5) {
356 nfound++;
357 continue;
360 /* if we hit end-of-directory, then we can shortcut it */
361 if (de->e.entry.name[0] == 0x00) {
362 ULONG last;
364 if (de->index + nwant >= 0x10000) {
365 D(bug("[fat] hit end-of-directory marker, but there's not enough room left after it\n"));
366 return ERROR_NO_FREE_STORE;
369 D(bug("[fat] found end-of-directory marker, making space after it\n"));
371 last = de->index + nwant;
372 do {
373 GetDirEntry(dh, de->index+1, de);
374 de->e.entry.name[0] = 0x00;
375 UpdateDirEntry(de);
376 } while(de->index != last);
378 D(bug("[fat] new end-of-directory is entry %ld\n", de->index));
380 /* get the previous entry; this is this base (short name) entry */
381 GetDirEntry(dh, de->index-1, de);
383 break;
386 /* anything else is a in-use entry, so reset our count */
387 nfound = 0;
390 D(bug("[fat] found a gap, base (short name) entry is %ld\n", de->index));
392 /* build the entry */
393 de->e.entry.attr = attr;
394 de->e.entry.nt_res = 0;
396 DateStamp(&ds);
397 ConvertAROSDate(ds, &(de->e.entry.create_date), &(de->e.entry.create_time));
398 de->e.entry.write_date = de->e.entry.create_date;
399 de->e.entry.write_time = de->e.entry.create_time;
400 de->e.entry.last_access_date = de->e.entry.create_date;
402 /* XXX calculate this. I've just written ConvertAROSDate and I'm sick of
403 * dates and times for the moment */
404 de->e.entry.create_time_tenth = 0;
406 de->e.entry.first_cluster_lo = cluster & 0xffff;
407 de->e.entry.first_cluster_hi = cluster >> 16;
409 de->e.entry.file_size = 0;
411 SetDirEntryName(de, name, namelen);
413 if ((err = UpdateDirEntry(de)) != 0) {
414 D(bug("[fat] couldn't update base directory entry, creation failed\n"));
415 return err;
418 D(bug("[fat] created dir entry %ld\n", de->index));
420 return 0;
423 LONG DeleteDirEntry(struct DirEntry *de) {
424 struct DirHandle dh;
425 UBYTE checksum;
426 ULONG order;
427 LONG err;
429 InitDirHandle(glob->sb, de->cluster, &dh, FALSE);
431 /* calculate the short name checksum before we trample on the name */
432 CALC_SHORT_NAME_CHECKSUM(de->e.entry.name, checksum);
434 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
436 /* mark the short entry free */
437 de->e.entry.name[0] = 0xe5;
438 UpdateDirEntry(de);
440 D(bug("[fat] deleted short name entry\n"));
442 /* now we loop over the previous entries, looking for matching long name
443 * entries and killing them */
444 order = 1;
445 while ((err = GetDirEntry(&dh, de->index-1, de)) == 0) {
447 /* see if this is a matching long name entry. if it's not, we're done */
448 if (!((de->e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
449 (de->e.long_entry.order & ~0x40) != order ||
450 de->e.long_entry.checksum != checksum)
452 break;
454 /* kill it */
455 de->e.entry.name[0] = 0xe5;
456 UpdateDirEntry(de);
458 order++;
461 D(bug("[fat] deleted %ld long name entries\n", order-1));
463 ReleaseDirHandle(&dh);
465 return err;
470 #define sb glob->sb
472 LONG FillFIB (struct ExtFileLock *fl, struct FileInfoBlock *fib) {
473 struct GlobalLock *gl = (fl != NULL ? fl->gl : &sb->root_lock);
474 struct DirHandle dh;
475 struct DirEntry de;
476 LONG result = 0;
477 int len;
479 D(bug("\tFilling FIB data.\n"));
481 if (gl->dir_cluster == FAT_ROOTDIR_MARK) {
482 D(bug("\t\ttype: root directory\n"));
483 fib->fib_DirEntryType = ST_ROOT;
485 else if (gl->attr & ATTR_DIRECTORY) {
486 D(bug("\t\ttype: directory\n"));
487 fib->fib_DirEntryType = ST_USERDIR;
489 else {
490 D(bug("\t\ttype: file\n"));
491 fib->fib_DirEntryType = ST_FILE;
494 D(bug("\t\tsize: %ld\n", gl->size));
496 fib->fib_Size = gl->size;
497 fib->fib_NumBlocks = ((gl->size + (sb->clustersize - 1)) >> sb->clustersize_bits) << sb->cluster_sectors_bits;
498 fib->fib_EntryType = fib->fib_DirEntryType;
499 fib->fib_DiskKey = 0xfffffffflu; //fl->entry;
501 if (fib->fib_DirEntryType == ST_ROOT)
502 CopyMem(&sb->volume.create_time, &fib->fib_Date, sizeof(struct DateStamp));
503 else {
504 InitDirHandle(sb, gl->dir_cluster, &dh, FALSE);
505 GetDirEntry(&dh, gl->dir_entry, &de);
506 ConvertFATDate(de.e.entry.write_date, de.e.entry.write_time, &fib->fib_Date);
507 ReleaseDirHandle(&dh);
510 len = gl->name[0] <= 106 ? gl->name[0] : 106;
511 CopyMem(gl->name, fib->fib_FileName, len + 1);
512 fib->fib_FileName[len + 1] = '\0';
513 D(bug("\t\tname (len %ld) %s\n", len, fib->fib_FileName + 1));
515 fib->fib_Protection = 0;
516 if (gl->attr & ATTR_READ_ONLY) fib->fib_Protection |= (FIBF_DELETE | FIBF_WRITE);
517 if (gl->attr & ATTR_ARCHIVE) fib->fib_Protection |= FIBF_ARCHIVE;
519 fib->fib_Comment[0] = 0;
521 return result;