r26407@plastic: rob | 2007-05-15 19:13:02 +1000
[cake.git] / workbench / fs / fat / ops.c
blobec74ada0077d62ae7d0dcc45827f4f9a90be4539
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 <dos/notify.h>
16 #include <proto/exec.h>
18 #include "fat_fs.h"
19 #include "fat_protos.h"
21 #if defined(DEBUG_FULL) && DEBUG_FULL != 0
22 #define DEBUG 1
23 #else
24 #define DEBUG 0
25 #endif
26 #include <aros/debug.h>
28 #define FREE_CLUSTER_CHAIN(sb,cl) \
29 do { \
30 ULONG cluster = cl; \
31 while (cluster >= 0 && cluster < sb->eoc_mark) { \
32 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
33 SET_NEXT_CLUSTER(sb, cluster, 0); \
34 cluster = next_cluster; \
35 } \
36 } while(0)
39 * this takes a full path and moves to the directory that would contain the
40 * last file in the path. ie calling with (dh, "foo/bar/baz", 11) will move to
41 * directory "foo/bar" under the dir specified by dh. dh will become a handle
42 * to the new dir. after the return name will be "baz" and namelen will be 3
44 static LONG MoveToSubdir(struct DirHandle *dh, UBYTE **pname, ULONG *pnamelen) {
45 LONG err;
46 UBYTE *name = *pname, *base;
47 ULONG namelen = *pnamelen, baselen;
48 struct DirEntry de;
49 int i;
51 /* if it starts with a volume specifier (or just a :), remove it and get
52 * us back to the root dir */
53 for (i = 0; i < namelen; i++)
54 if (name[i] == ':') {
55 D(bug("[fat] name has volume specifier, moving to the root dir\n"));
57 namelen -= (i+1);
58 name = &name[i+1];
60 InitDirHandle(dh->ioh.sb, 0, dh);
62 break;
65 /* we break the given name into two pieces - the name of the containing
66 * dir, and the name of the new dir to go within it. if the base ends up
67 * empty, then we just use the dirlock */
68 baselen = namelen;
69 base = name;
70 while (baselen > 0) {
71 if (base[baselen-1] != '/')
72 break;
73 baselen--;
75 while (baselen > 0) {
76 if (base[baselen-1] == '/')
77 break;
78 baselen--;
80 namelen -= baselen;
81 name = &base[baselen];
83 D(bug("[fat] base is '%.*s', name is '%.*s'\n", baselen, base, namelen, name));
85 if (baselen > 0) {
86 if ((err = GetDirEntryByPath(dh, base, baselen, &de)) != 0) {
87 D(bug("[fat] base not found\n"));
88 return err;
91 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh)) != 0)
92 return err;
95 *pname = name;
96 *pnamelen = namelen;
98 return 0;
102 * obtains a lock on the named file under the given dir. this is the service
103 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
104 * only return a lock on a file, never on a dir.
106 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG action, struct ExtFileLock **filelock) {
107 LONG err;
108 struct ExtFileLock *lock;
109 struct DirHandle dh;
110 struct DirEntry de;
112 D(bug("[fat] opening file '%.*s' in dir at cluster %ld, action %s\n",
113 namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0,
114 action == ACTION_FINDINPUT ? "FINDINPUT" :
115 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
116 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]"));
118 /* no filename means they're trying to open whatever dirlock is (which
119 * despite the name may not actually be a dir). since there's already an
120 * extant lock, its never going be possible to get an exclusive lock, so
121 * this will only work for FINDINPUT (read-only) */
122 if (namelen == 0) {
123 D(bug("[fat] trying to copy passed dir lock\n"));
125 if (action != ACTION_FINDINPUT) {
126 D(bug("[fat] can't copy lock for write (exclusive)\n"));
127 return ERROR_OBJECT_IN_USE;
130 /* dirs can't be opened */
131 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY) {
132 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
133 return ERROR_OBJECT_WRONG_TYPE;
136 /* its a file, just copy the lock */
137 return CopyLock(dirlock, filelock);
140 /* lock the file */
141 err = LockFileByName(dirlock, name, namelen, action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock);
143 /* found it */
144 if (err == 0) {
145 D(bug("[fat] found existing file\n"));
147 /* can't open directories */
148 if (lock->gl->attr & ATTR_DIRECTORY) {
149 D(bug("[fat] its a directory, can't open it\n"));
150 FreeLock(lock);
151 return ERROR_OBJECT_WRONG_TYPE;
154 /* INPUT/UPDATE use the file as/is */
155 if (action != ACTION_FINDOUTPUT) {
156 D(bug("[fat] returning the lock\n"));
157 *filelock = lock;
158 return 0;
161 /* whereas OUTPUT truncates it */
162 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
164 /* update the dir entry to make the file empty */
165 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
166 GetDirEntry(&dh, lock->gl->dir_entry, &de);
167 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
168 de.e.entry.file_size = 0;
169 UpdateDirEntry(&de);
171 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
173 /* free the clusters */
174 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
175 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
176 RESET_HANDLE(&lock->ioh);
177 lock->gl->size = 0;
179 D(bug("[fat] file truncated, returning the lock\n"));
181 /* file is empty, go */
182 *filelock = lock;
184 return 0;
187 /* any error other than "not found" should be taken as-is */
188 if (err != ERROR_OBJECT_NOT_FOUND)
189 return err;
191 /* not found. for INPUT we bail out */
192 if (action == ACTION_FINDINPUT) {
193 D(bug("[fat] file not found, and not creating it\n"));
194 return ERROR_OBJECT_NOT_FOUND;
197 D(bug("[fat] trying to create '%.*s'\n", namelen, name));
199 /* otherwise its time to create the file. get a handle on the passed dir */
200 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
201 return err;
203 /* get down to the correct subdir */
204 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
205 ReleaseDirHandle(&dh);
206 return err;
209 /* create the entry */
210 if ((err = CreateDirEntry(&dh, name, namelen, 0, 0, &de)) != 0) {
211 ReleaseDirHandle(&dh);
212 return err;
215 /* lock the new file */
216 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock);
218 /* done */
219 ReleaseDirHandle(&dh);
221 if (err == 0) {
222 (*filelock)->do_notify = TRUE;
223 D(bug("[fat] returning lock on new file\n"));
226 return err;
229 /* find the named file in the directory referenced by dirlock, and delete it.
230 * if the file is a directory, it will only be deleted if its empty */
231 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen) {
232 LONG err;
233 struct ExtFileLock *lock;
234 struct DirHandle dh;
235 struct DirEntry de;
237 D(bug("[fat] deleting file '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
239 /* obtain a lock on the file. we need an exclusive lock as we don't want
240 * to delete the file if its in use */
241 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock)) != 0) {
242 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
243 return err;
246 /* if its a directory, we have to make sure its empty */
247 if (lock->gl->attr & ATTR_DIRECTORY) {
248 D(bug("[fat] file is a directory, making sure its empty\n"));
250 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh)) != 0) {
251 FreeLock(lock);
252 return err;
255 /* loop over the entries, starting from entry 2 (the first real
256 * entry). skipping unused ones, we look for the end-of-directory
257 * marker. if we find it, the directory is empty. if we find a real
258 * name, its in use */
259 de.index = 1;
260 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0) {
261 /* skip unused entries */
262 if (de.e.entry.name[0] == 0xe5)
263 continue;
265 /* end of directory, its empty */
266 if (de.e.entry.name[0] == 0x00)
267 break;
269 /* otherwise the directory is still in use */
270 D(bug("[fat] directory still has files in it, won't delete it\n"));
272 ReleaseDirHandle(&dh);
273 FreeLock(lock);
274 return ERROR_DIRECTORY_NOT_EMPTY;
277 ReleaseDirHandle(&dh);
280 /* open the containing directory */
281 if ((err = InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh)) != 0) {
282 FreeLock(lock);
283 return err;
286 /* get the entry for the file */
287 GetDirEntry(&dh, lock->gl->dir_entry, &de);
289 /* kill it */
290 DeleteDirEntry(&de);
292 /* its all good */
293 ReleaseDirHandle(&dh);
295 /* now free the clusters the file was using */
296 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
298 /* notify */
299 SendNotifyByLock(lock->gl);
301 /* this lock is now completely meaningless */
302 FreeLock(lock);
304 D(bug("[fat] deleted '%.*s'\n", namelen, name));
306 return 0;
309 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname, ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname, ULONG dnamelen) {
310 struct DirHandle sdh, ddh;
311 struct DirEntry sde, dde;
312 struct GlobalLock *gl;
313 LONG err;
314 ULONG len;
316 /* get the source dir handle */
317 if ((err = InitDirHandle(glob->sb, sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh)) != 0)
318 return err;
320 /* get down to the correct subdir */
321 if ((err = MoveToSubdir(&sdh, &sname, &snamelen)) != 0) {
322 ReleaseDirHandle(&sdh);
323 return err;
326 /* get the entry */
327 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde)) != 0) {
328 ReleaseDirHandle(&sdh);
329 return err;
332 /* now get a handle on the passed dest dir */
333 if ((err = InitDirHandle(glob->sb, ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh)) != 0) {
334 ReleaseDirHandle(&sdh);
335 return err;
338 /* get down to the correct subdir */
339 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen)) != 0) {
340 ReleaseDirHandle(&ddh);
341 ReleaseDirHandle(&sdh);
342 return err;
345 /* now see if the wanted name is in this dir. if it exists, do nothing */
346 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde)) == 0) {
347 ReleaseDirHandle(&ddh);
348 ReleaseDirHandle(&sdh);
349 return ERROR_OBJECT_EXISTS;
351 else if (err != ERROR_OBJECT_NOT_FOUND) {
352 ReleaseDirHandle(&ddh);
353 ReleaseDirHandle(&sdh);
354 return err;
357 /* at this point we have the source entry in sde, and we know the dest
358 * doesn't exist */
360 /* XXX if sdh and ddh are the same dir and there's room in the existing
361 * entries for the new name, just overwrite the name */
363 /* make a new entry in the target dir */
364 if ((err = CreateDirEntry(&ddh, dname, dnamelen, sde.e.entry.attr, (sde.e.entry.first_cluster_hi << 16) | sde.e.entry.first_cluster_lo, &dde)) != 0) {
365 ReleaseDirHandle(&ddh);
366 ReleaseDirHandle(&sdh);
369 /* copy in the leftover attributes */
370 dde.e.entry.create_date = sde.e.entry.create_date;
371 dde.e.entry.create_time = sde.e.entry.create_time;
372 dde.e.entry.write_date = sde.e.entry.write_date;
373 dde.e.entry.write_time = sde.e.entry.write_time;
374 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
375 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
376 dde.e.entry.file_size = sde.e.entry.file_size;
378 UpdateDirEntry(&dde);
380 /* update the global lock (if present) with the new dir cluster/entry */
381 ForeachNode(&sdh.ioh.sb->locks, gl)
382 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index) {
383 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde.cluster, sde.index, dde.cluster, dde.index));
385 gl->dir_cluster = dde.cluster;
386 gl->dir_entry = dde.index;
388 /* update the filename too */
389 GetDirEntryShortName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
390 GetDirEntryLongName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
393 /* delete the original */
394 DeleteDirEntry(&sde);
396 /* notify */
397 SendNotifyByDirEntry(&dde);
399 ReleaseDirHandle(&ddh);
400 ReleaseDirHandle(&sdh);
402 return 0;
405 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock) {
406 LONG err;
407 ULONG cluster;
408 struct DirHandle dh, sdh;
409 struct DirEntry de, sde;
411 D(bug("[fat] creating directory '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
413 /* get a handle on the passed dir */
414 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
415 return err;
417 /* get down to the correct subdir */
418 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
419 ReleaseDirHandle(&dh);
420 return err;
423 /* now see if the wanted name is in this dir. if it exists, then we do
424 * nothing */
425 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
426 D(bug("[fat] name exists, can't do anything\n"));
427 ReleaseDirHandle(&dh);
428 return ERROR_OBJECT_EXISTS;
431 /* find a free cluster to store the dir in */
432 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
433 ReleaseDirHandle(&dh);
434 return err;
437 /* allocate it */
438 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, dh.ioh.sb->eoc_mark);
440 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
442 /* create the entry, pointing to the new cluster */
443 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY, cluster, &de)) != 0) {
444 /* deallocate the cluster */
445 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, 0);
447 ReleaseDirHandle(&dh);
448 return err;
451 /* now get a handle on the new directory */
452 InitDirHandle(dh.ioh.sb, cluster, &sdh);
454 /* create the dot entry. its a direct copy of the just-created entry, but
455 * with a different name */
456 GetDirEntry(&sdh, 0, &sde);
457 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
458 CopyMem(". ", &sde.e.entry.name, 11);
459 UpdateDirEntry(&sde);
461 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
462 * to point to the parent */
463 GetDirEntry(&sdh, 1, &sde);
464 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
465 CopyMem(".. ", &sde.e.entry.name, 11);
466 sde.e.entry.first_cluster_lo = dh.ioh.first_cluster & 0xffff;
467 sde.e.entry.first_cluster_hi = dh.ioh.first_cluster >> 16;
468 UpdateDirEntry(&sde);
470 /* put an empty entry at the end to mark end of directory */
471 GetDirEntry(&sdh, 2, &sde);
472 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
473 UpdateDirEntry(&sde);
475 /* new dir created */
476 ReleaseDirHandle(&sdh);
478 /* now obtain a lock on the new dir */
479 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock);
481 /* done */
482 ReleaseDirHandle(&dh);
484 /* notify */
485 SendNotifyByLock((*newdirlock)->gl);
487 return err;
490 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read) {
491 LONG err;
493 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
495 if (want == 0)
496 return 0;
498 if (want + lock->pos > lock->gl->size) {
499 want = lock->gl->size - lock->pos;
500 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
503 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
504 lock->pos += *read;
505 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
508 return err;
511 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written) {
512 LONG err;
513 BOOL update_entry = FALSE;
514 struct DirHandle dh;
515 struct DirEntry de;
517 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
519 if (want == 0)
520 return 0;
522 /* if this is the first write, make a note as we'll have to store the
523 * first cluster in the directory entry later */
524 if (lock->ioh.first_cluster == 0xffffffff)
525 update_entry = TRUE;
527 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
528 /* if nothing was written but success was returned (can that even
529 * happen?) then we don't want to mess with the dir entry */
530 if (*written == 0) {
531 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
532 return 0;
535 lock->do_notify = TRUE;
537 lock->pos += *written;
538 if (lock->pos > lock->gl->size) {
539 lock->gl->size = lock->pos;
540 update_entry = TRUE;
543 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
545 if (update_entry) {
546 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
548 lock->gl->first_cluster = lock->ioh.first_cluster;
550 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
551 GetDirEntry(&dh, lock->gl->dir_entry, &de);
553 de.e.entry.file_size = lock->gl->size;
554 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
555 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
557 UpdateDirEntry(&de);
559 ReleaseDirHandle(&dh);
563 return err;
566 LONG OpAddNotify(struct NotifyRequest *nr) {
567 LONG err;
568 struct DirHandle dh;
569 struct DirEntry de;
570 struct GlobalLock *gl = NULL, *tmp;
571 struct NotifyNode *nn;
573 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
575 /* if the request is for the volume root, then we just link to the root lock */
576 if (nr->nr_FullName[strlen(nr->nr_FullName)-1] == ':') {
577 D(bug("[fat] adding notify for root dir\n"));
578 gl = &glob->sb->root_lock;
581 else {
582 if ((err = InitDirHandle(glob->sb, 0, &dh)) != 0)
583 return err;
585 /* look for the entry */
586 err = GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName), &de);
587 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
588 return err;
590 /* if it was found, then it might be open. try to find the global lock */
591 if (err == 0) {
592 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de.cluster, de.index));
594 ForeachNode(&glob->sb->locks, tmp)
595 if (tmp->dir_cluster == de.cluster && tmp->dir_entry == de.index) {
596 gl = tmp;
598 D(bug("[fat] found global lock 0x%0x\n", gl));
600 break;
603 else
604 D(bug("[fat] file doesn't exist\n"));
607 if (gl == NULL)
608 D(bug("[fat] file not currently locked\n"));
610 /* allocate space to for the notify node */
611 if ((nn = AllocVecPooled(glob->mempool, sizeof(struct NotifyNode))) == NULL)
612 return ERROR_NO_FREE_STORE;
614 /* plug the bits in */
615 nn->gl = gl;
616 nn->nr = nr;
618 /* add to the list */
619 ADDTAIL(&glob->sb->notifies, nn);
621 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
623 return 0;
626 LONG OpRemoveNotify(struct NotifyRequest *nr) {
627 struct NotifyNode *nn;
629 D(bug("[fat] trying to remove notification for '%s'\n", nr->nr_FullName));
631 ForeachNode(&glob->sb->notifies, nn)
632 if (nn->nr == nr) {
633 D(bug("[fat] found notify request in list, removing it\n"));
634 REMOVE(nn);
635 FreeVecPooled(glob->mempool, nn);
636 return 0;
639 D(bug("[fat] not found, doing nothing\n"));
641 return 0;