revert between 56095 -> 55830 in arch
[AROS.git] / rom / filesys / fat / ops.c
blob3ea3d88516e3ec8a2c4772322fddff074b6199ca
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 #define AROS_ALMOST_COMPATIBLE
15 #include <aros/macros.h>
16 #include <exec/types.h>
17 #include <dos/dos.h>
18 #include <dos/notify.h>
19 #include <proto/exec.h>
21 #include "fat_fs.h"
22 #include "fat_protos.h"
24 #define DEBUG DEBUG_OPS
25 #include "debug.h"
27 #define FREE_CLUSTER_CHAIN(sb,cl) \
28 do { \
29 ULONG cluster = cl; \
30 while (cluster >= 0 && cluster < sb->eoc_mark - 7) { \
31 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
32 FreeCluster(sb, cluster); \
33 cluster = next_cluster; \
34 } \
35 } while(0)
38 * This takes a full path and moves to the directory that would contain the
39 * last file in the path. E.g. calling with (dh, "foo/bar/baz", 11) will move
40 * to directory "foo/bar" under the dir specified by dh. dh will become a
41 * handle to the new dir. After the return, name will be "baz" and namelen
42 * will be 3
44 static LONG MoveToSubdir(struct DirHandle *dh, UBYTE **pname,
45 ULONG *pnamelen, struct Globals *glob)
47 LONG err;
48 UBYTE *name = *pname, *base, ch, *p;
49 ULONG namelen = *pnamelen, baselen;
50 struct DirEntry de;
52 /* Skip device name (if any) */
53 for (ch = *(p = name); ch != ':' && ch != '\0'; ch = *(++p));
54 if (ch == ':')
56 namelen -= (p - name) + 1;
57 name = p + 1;
60 /* We break the given name into two pieces - the name of the containing
61 * dir, and the name of the new dir to go within it. If the base ends up
62 * empty, then we just use the dirlock */
63 baselen = namelen;
64 base = name;
65 while (baselen > 0)
67 if (base[baselen - 1] != '/')
68 break;
69 baselen--;
71 while (baselen > 0)
73 if (base[baselen - 1] == '/')
74 break;
75 baselen--;
77 namelen -= baselen;
78 name = &base[baselen];
81 bug("[fat] base is '");
82 RawPutChars(base, baselen); bug("', name is '");
83 RawPutChars(name, namelen);
84 bug("'\n");
87 if (baselen > 0)
89 if ((err = GetDirEntryByPath(dh, base, baselen, &de, glob)) != 0)
91 D(bug("[fat] base not found\n"));
92 return err;
95 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh,
96 TRUE, glob)) != 0)
97 return err;
100 *pname = name;
101 *pnamelen = namelen;
103 return 0;
106 LONG OpLockFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
107 LONG access, struct ExtFileLock **filelock, struct Globals *glob)
109 /* If they passed in a name, go searching for it */
110 if (namelen != 0)
111 return LockFileByName(dirlock, name, namelen, access, filelock,
112 glob);
114 /* Otherwise the empty filename, just make a copy */
115 else if (dirlock != NULL)
116 return CopyLock(dirlock, filelock, glob);
118 /* Null dir lock means they want the root */
119 else
120 return LockRoot(access, filelock, glob);
123 void OpUnlockFile(struct ExtFileLock *lock, struct Globals *glob)
125 if (lock != NULL)
126 FreeLock(lock, glob);
129 LONG OpCopyLock(struct ExtFileLock *lock, struct ExtFileLock **copy,
130 struct Globals *glob)
132 if (lock != NULL)
133 return CopyLock(lock, copy, glob);
134 else
135 return LockRoot(SHARED_LOCK, copy, glob);
138 LONG OpLockParent(struct ExtFileLock *lock, struct ExtFileLock **parent,
139 struct Globals *glob)
141 LONG err;
142 struct DirHandle dh;
143 struct DirEntry de;
144 ULONG parent_cluster;
146 /* The root has no parent, but as a special case we have to return success
147 * with the zero lock */
148 if (lock == NULL || lock->gl == &glob->sb->info->root_lock)
150 *parent = NULL;
151 return 0;
154 /* If we're in the root directory, then the root is our parent */
155 if (lock->gl->dir_cluster == glob->sb->rootdir_cluster)
156 return LockRoot(SHARED_LOCK, parent, glob);
158 /* Get the parent dir */
159 InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE, glob);
160 if ((err = GetDirEntryByPath(&dh, "/", 1, &de, glob)) != 0)
162 ReleaseDirHandle(&dh, glob);
163 return err;
166 /* And its cluster */
167 if ((parent_cluster = FIRST_FILE_CLUSTER(&de)) == 0)
168 parent_cluster = glob->sb->rootdir_cluster;
170 /* Then we go through the parent dir, looking for a link back to us. We do
171 * this so that we have an entry with the proper name for copying by
172 * LockFile() */
173 InitDirHandle(glob->sb, parent_cluster, &dh, TRUE, glob);
174 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de, glob)) == 0)
176 /* Don't go past the end */
177 if (de.e.entry.name[0] == 0x00)
179 err = ERROR_OBJECT_NOT_FOUND;
180 break;
183 /* We found it if it's not empty, and it's not the volume id or a long
184 * name, and it is a directory, and it does point to us */
185 if (de.e.entry.name[0] != 0xe5 &&
186 !(de.e.entry.attr & ATTR_VOLUME_ID) &&
187 de.e.entry.attr & ATTR_DIRECTORY &&
188 FIRST_FILE_CLUSTER(&de) == lock->gl->dir_cluster)
190 err =
191 LockFile(parent_cluster, dh.cur_index, SHARED_LOCK, parent,
192 glob);
193 break;
197 ReleaseDirHandle(&dh, glob);
198 return err;
202 * Obtains a lock on the named file under the given dir. This is the service
203 * routine for DOS Open() (i.e. FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such
204 * may only return a lock on a file, never on a dir.
206 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
207 LONG action, struct ExtFileLock **filelock, struct Globals *glob)
209 LONG err;
210 struct ExtFileLock *lock;
211 struct DirHandle dh;
212 struct DirEntry de;
215 bug("[fat] opening file '");
216 RawPutChars(name, namelen);
217 bug("' in dir at cluster %ld, action %s\n",
218 dirlock != NULL ? dirlock->ioh.first_cluster : 0,
219 action == ACTION_FINDINPUT ? "FINDINPUT" :
220 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
221 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]");
224 /* Explicitly mark the dirhandle as uninitialised */
225 dh.ioh.sb = NULL;
227 /* No filename means they're trying to open whatever dirlock is (which
228 * despite the name may not actually be a dir). Since there's already an
229 * extant lock, it's never going to be possible to get an exclusive lock,
230 * so this will only work for FINDINPUT (read-only) */
231 if (namelen == 0)
233 D(bug("[fat] trying to copy passed dir lock\n"));
235 if (action != ACTION_FINDINPUT)
237 D(bug("[fat] can't copy lock for write (exclusive)\n"));
238 return ERROR_OBJECT_IN_USE;
241 /* Dirs can't be opened */
242 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY)
244 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
245 return ERROR_OBJECT_WRONG_TYPE;
248 /* It's a file, just copy the lock */
249 return CopyLock(dirlock, filelock, glob);
252 /* Lock the file */
253 err = LockFileByName(dirlock, name, namelen,
254 action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock,
255 glob);
257 /* Found it */
258 if (err == 0)
260 D(bug("[fat] found existing file\n"));
262 /* Can't open directories */
263 if (lock->gl->attr & ATTR_DIRECTORY)
265 D(bug("[fat] it's a directory, can't open it\n"));
266 FreeLock(lock, glob);
267 return ERROR_OBJECT_WRONG_TYPE;
270 /* INPUT/UPDATE use the file as/is */
271 if (action != ACTION_FINDOUTPUT)
273 D(bug("[fat] returning the lock\n"));
274 *filelock = lock;
275 return 0;
278 /* Whereas OUTPUT truncates it */
279 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
281 if (lock->gl->attr & ATTR_READ_ONLY)
283 D(bug("[fat] file is write protected, doing nothing\n"));
284 FreeLock(lock, glob);
285 return ERROR_WRITE_PROTECTED;
288 /* Update the dir entry to make the file empty */
289 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE, glob);
290 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
291 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
292 de.e.entry.file_size = 0;
293 de.e.entry.attr |= ATTR_ARCHIVE;
294 UpdateDirEntry(&de, glob);
296 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
298 /* Free the clusters */
299 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
300 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
301 RESET_HANDLE(&lock->ioh);
302 lock->gl->size = 0;
304 D(bug("[fat] file truncated, returning the lock\n"));
306 /* File is empty, go */
307 *filelock = lock;
309 return 0;
312 /* Any error other than "not found" should be taken as-is */
313 if (err != ERROR_OBJECT_NOT_FOUND)
314 return err;
316 /* Not found. For INPUT we bail out */
317 if (action == ACTION_FINDINPUT)
319 D(bug("[fat] file not found, and not creating it\n"));
320 return ERROR_OBJECT_NOT_FOUND;
324 bug("[fat] trying to create '");
325 RawPutChars(name, namelen);
326 bug("'\n");
329 /* Otherwise it's time to create the file. Get a handle on the passed dir */
330 if ((err = InitDirHandle(glob->sb,
331 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, TRUE, glob))
332 != 0)
333 return err;
335 /* Get down to the correct subdir */
336 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
338 ReleaseDirHandle(&dh, glob);
339 return err;
342 /* If the dir is write protected, can't do anything. Root dir is never
343 * write protected */
344 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
346 GetDirEntry(&dh, 0, &de, glob);
347 if (de.e.entry.attr & ATTR_READ_ONLY)
349 D(bug("[fat] containing dir is write protected, doing nothing\n"));
350 ReleaseDirHandle(&dh, glob);
351 return ERROR_WRITE_PROTECTED;
355 /* Create the entry */
356 if ((err =
357 CreateDirEntry(&dh, name, namelen, ATTR_ARCHIVE, 0, &de, glob)) != 0)
359 ReleaseDirHandle(&dh, glob);
360 return err;
363 /* Lock the new file */
364 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock, glob);
366 /* Done */
367 ReleaseDirHandle(&dh, glob);
369 if (err == 0)
371 (*filelock)->do_notify = TRUE;
372 D(bug("[fat] returning lock on new file\n"));
375 return err;
378 /* Find the named file in the directory referenced by dirlock, and delete it.
379 * If the file is a directory, it will only be deleted if it's empty */
380 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
381 struct Globals *glob)
383 LONG err;
384 struct ExtFileLock *lock;
385 struct DirHandle dh;
386 struct DirEntry de;
389 bug("[fat] deleting file '");
390 RawPutChars(name, namelen);
391 bug("' in directory at cluster % ld\n",
392 dirlock != NULL ? dirlock->ioh.first_cluster : 0);
395 dh.ioh.sb = NULL;
397 /* Obtain a lock on the file. We need an exclusive lock as we don't want
398 * to delete the file if it's in use */
399 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock,
400 glob)) != 0)
402 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
403 return err;
406 if (lock->gl->attr & ATTR_READ_ONLY)
408 D(bug("[fat] file is write protected, doing nothing\n"));
409 FreeLock(lock, glob);
410 return ERROR_DELETE_PROTECTED;
413 /* If it's a directory, we have to make sure it's empty */
414 if (lock->gl->attr & ATTR_DIRECTORY)
416 D(bug("[fat] file is a directory, making sure it's empty\n"));
418 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh,
419 FALSE, glob)) != 0)
421 FreeLock(lock, glob);
422 return err;
425 /* Loop over the entries, starting from entry 2 (the first real
426 * entry). Skipping unused ones, we look for the end-of-directory
427 * marker. If we find it, the directory is empty. If we find a real
428 * name, it's in use */
429 de.index = 1;
430 while ((err = GetDirEntry(&dh, de.index + 1, &de, glob)) == 0)
432 /* Skip unused entries */
433 if (de.e.entry.name[0] == 0xe5)
434 continue;
436 /* End of directory, it's empty */
437 if (de.e.entry.name[0] == 0x00)
438 break;
440 /* Otherwise the directory is still in use */
441 D(bug("[fat] directory still has files in it, won't delete it\n"));
443 ReleaseDirHandle(&dh, glob);
444 FreeLock(lock, glob);
445 return ERROR_DIRECTORY_NOT_EMPTY;
448 ReleaseDirHandle(&dh, glob);
451 /* Open the containing directory */
452 if ((err =InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh,
453 TRUE, glob)) != 0)
455 FreeLock(lock, glob);
456 return err;
459 /* If the dir is write protected, can't do anything. Root dir is never
460 * write protected */
461 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
463 GetDirEntry(&dh, 0, &de, glob);
464 if (de.e.entry.attr & ATTR_READ_ONLY)
466 D(bug("[fat] containing dir is write protected, doing nothing\n"));
467 ReleaseDirHandle(&dh, glob);
468 FreeLock(lock, glob);
469 return ERROR_WRITE_PROTECTED;
473 /* Get the entry for the file */
474 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
476 /* Kill it */
477 DeleteDirEntry(&de, glob);
479 /* It's all good */
480 ReleaseDirHandle(&dh, glob);
482 /* Now free the clusters the file was using */
483 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
485 /* Notify */
486 SendNotifyByLock(lock->ioh.sb, lock->gl);
488 /* This lock is now completely meaningless */
489 FreeLock(lock, glob);
492 bug("[fat] deleted '");
493 RawPutChars(name, namelen);
494 bug("'\n");
497 return 0;
500 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname,
501 ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname,
502 ULONG dnamelen, struct Globals *glob)
504 struct DirHandle sdh, ddh;
505 struct DirEntry sde, dde;
506 struct GlobalLock *gl;
507 LONG err;
508 ULONG len;
510 /* Get the source dir handle */
511 if ((err = InitDirHandle(glob->sb,
512 sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh,
513 FALSE, glob)) != 0)
514 return err;
516 /* Get down to the correct subdir */
517 if ((err = MoveToSubdir(&sdh, &sname, &snamelen, glob)) != 0)
519 ReleaseDirHandle(&sdh, glob);
520 return err;
523 /* Get the entry */
524 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde, glob)) != 0)
526 ReleaseDirHandle(&sdh, glob);
527 return err;
530 /* Now get a handle on the passed dest dir */
531 if ((err = InitDirHandle(glob->sb,
532 ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh,
533 FALSE, glob)) != 0)
535 ReleaseDirHandle(&sdh, glob);
536 return err;
539 /* Get down to the correct subdir */
540 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen, glob)) != 0)
542 ReleaseDirHandle(&ddh, glob);
543 ReleaseDirHandle(&sdh, glob);
544 return err;
547 /* Check the source and dest dirs. If either is read-only, do nothing */
548 GetDirEntry(&sdh, 0, &dde, glob);
549 if (dde.e.entry.attr & ATTR_READ_ONLY)
551 D(bug("[fat] source dir is read only, doing nothing\n"));
552 ReleaseDirHandle(&ddh, glob);
553 ReleaseDirHandle(&sdh, glob);
554 return ERROR_WRITE_PROTECTED;
556 GetDirEntry(&ddh, 0, &dde, glob);
557 if (dde.e.entry.attr & ATTR_READ_ONLY)
559 D(bug("[fat] dest dir is read only, doing nothing\n"));
560 ReleaseDirHandle(&ddh, glob);
561 ReleaseDirHandle(&sdh, glob);
562 return ERROR_WRITE_PROTECTED;
565 /* Now see if the wanted name is in this dir. If it exists, do nothing */
566 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde, glob)) == 0)
568 ReleaseDirHandle(&ddh, glob);
569 ReleaseDirHandle(&sdh, glob);
570 return ERROR_OBJECT_EXISTS;
572 else if (err != ERROR_OBJECT_NOT_FOUND)
574 ReleaseDirHandle(&ddh, glob);
575 ReleaseDirHandle(&sdh, glob);
576 return err;
579 /* At this point we have the source entry in sde, and we know the dest
580 * doesn't exist */
582 /* XXX: if sdh and ddh are the same dir and there's room in the existing
583 * entries for the new name, just overwrite the name */
585 /* Make a new entry in the target dir */
586 if ((err = CreateDirEntry(&ddh, dname, dnamelen,
587 sde.e.entry.attr | ATTR_ARCHIVE,
588 (sde.e.entry.first_cluster_hi << 16) | sde.e.entry.first_cluster_lo,
589 &dde, glob)) != 0)
591 ReleaseDirHandle(&ddh, glob);
592 ReleaseDirHandle(&sdh, glob);
595 /* Copy in the leftover attributes */
596 dde.e.entry.create_date = sde.e.entry.create_date;
597 dde.e.entry.create_time = sde.e.entry.create_time;
598 dde.e.entry.write_date = sde.e.entry.write_date;
599 dde.e.entry.write_time = sde.e.entry.write_time;
600 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
601 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
602 dde.e.entry.file_size = sde.e.entry.file_size;
604 UpdateDirEntry(&dde, glob);
606 /* Update the global lock (if present) with the new dir cluster/entry */
607 ForeachNode(&sdh.ioh.sb->info->locks, gl)
609 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index)
611 D(bug("[fat] found lock with old dir entry (%ld/%ld),"
612 " changing to (%ld/%ld)\n",
613 sde.cluster, sde.index, dde.cluster, dde.index));
615 gl->dir_cluster = dde.cluster;
616 gl->dir_entry = dde.index;
618 /* Update the filename too */
619 GetDirEntryShortName(&dde, &(gl->name[1]), &len, glob);
620 gl->name[0] = (UBYTE) len;
621 GetDirEntryLongName(&dde, &(gl->name[1]), &len);
622 gl->name[0] = (UBYTE) len;
626 /* Delete the original */
627 DeleteDirEntry(&sde, glob);
629 /* Notify */
630 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
632 ReleaseDirHandle(&ddh, glob);
633 ReleaseDirHandle(&sdh, glob);
635 return 0;
638 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
639 struct ExtFileLock **newdirlock, struct Globals *glob)
641 LONG err, i;
642 ULONG cluster;
643 struct DirHandle dh, sdh;
644 struct DirEntry de, sde;
647 bug("[fat] creating directory '");
648 RawPutChars(name, namelen);
649 bug("' in directory at cluster %ld\n",
650 dirlock != NULL ? dirlock->ioh.first_cluster : 0);
653 /* Get a handle on the passed dir */
654 if ((err = InitDirHandle(glob->sb,
655 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
656 glob)) != 0)
657 return err;
659 /* Get down to the correct subdir */
660 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
662 ReleaseDirHandle(&dh, glob);
663 return err;
666 /* Make sure 'name' is just the FilePart() */
667 for (i = namelen - 1; i > 0; i--)
669 if (name[i] == '/' || name[i] == ':')
671 namelen -= (i + 1);
672 name += (i + 1);
673 break;
677 /* If the dir is write protected, can't do anything. Root dir is never
678 * write protected */
679 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
681 GetDirEntry(&dh, 0, &de, glob);
682 if (de.e.entry.attr & ATTR_READ_ONLY)
684 D(bug("[fat] containing dir is write protected, doing nothing\n"));
685 ReleaseDirHandle(&dh, glob);
686 return ERROR_WRITE_PROTECTED;
690 /* Now see if the wanted name is in this dir. If it exists, then we do
691 * nothing */
692 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) == 0)
694 D(bug("[fat] name exists, can't do anything\n"));
695 ReleaseDirHandle(&dh, glob);
696 return ERROR_OBJECT_EXISTS;
699 /* Find a free cluster to store the dir in */
700 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0)
702 ReleaseDirHandle(&dh, glob);
703 return err;
706 /* Allocate it */
707 AllocCluster(dh.ioh.sb, cluster);
709 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
711 /* Create the entry, pointing to the new cluster */
712 if ((err = CreateDirEntry(&dh, name, namelen,
713 ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de, glob)) != 0)
715 /* Deallocate the cluster */
716 FreeCluster(dh.ioh.sb, cluster);
718 ReleaseDirHandle(&dh, glob);
719 return err;
722 /* Now get a handle on the new directory */
723 InitDirHandle(dh.ioh.sb, cluster, &sdh, FALSE, glob);
725 /* Create the dot entry. It's a direct copy of the just-created entry, but
726 * with a different name */
727 GetDirEntry(&sdh, 0, &sde, glob);
728 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
729 CopyMem(". ", &sde.e.entry.name, FAT_MAX_SHORT_NAME);
730 UpdateDirEntry(&sde, glob);
732 /* Create the dot-dot entry. Again, a copy, with the cluster pointer set
733 * up to point to the parent */
734 GetDirEntry(&sdh, 1, &sde, glob);
735 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
736 CopyMem(".. ", &sde.e.entry.name, FAT_MAX_SHORT_NAME);
737 cluster = dh.ioh.first_cluster;
738 if (cluster == dh.ioh.sb->rootdir_cluster)
739 cluster = 0;
740 sde.e.entry.first_cluster_lo = cluster & 0xffff;
741 sde.e.entry.first_cluster_hi = cluster >> 16;
742 UpdateDirEntry(&sde, glob);
744 /* Clear all remaining entries (the first of which marks the end of the
745 * directory) */
746 for (i = 2; GetDirEntry(&sdh, i, &sde, glob) == 0; i++)
748 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
749 UpdateDirEntry(&sde, glob);
752 /* New dir created */
753 ReleaseDirHandle(&sdh, glob);
755 /* Now obtain a lock on the new dir */
756 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock, glob);
758 /* Done */
759 ReleaseDirHandle(&dh, glob);
761 /* Notify */
762 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
764 return err;
767 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want,
768 ULONG *read, struct Globals *glob)
770 LONG err;
772 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want,
773 lock->pos));
775 if (want == 0)
776 return 0;
778 if (want + lock->pos > lock->gl->size)
780 want = lock->gl->size - lock->pos;
781 D(bug("[fat] full read would take us past end-of-file,"
782 " adjusted want to %ld bytes\n", want));
785 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0)
787 lock->pos += *read;
788 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read,
789 lock->pos));
792 return err;
795 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want,
796 ULONG *written, struct Globals *glob)
798 LONG err;
799 BOOL update_entry = FALSE;
800 struct DirHandle dh;
801 struct DirEntry de;
803 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want,
804 lock->pos));
806 /* Need an exclusive lock */
807 if (lock->gl->access != EXCLUSIVE_LOCK)
809 D(bug("[fat] can't modify global attributes via a shared lock\n"));
810 return ERROR_OBJECT_IN_USE;
813 /* Don't modify the file if it's protected */
814 if (lock->gl->attr & ATTR_READ_ONLY)
816 D(bug("[fat] file is write protected\n"));
817 return ERROR_WRITE_PROTECTED;
820 if (want == 0)
822 *written = 0;
823 return 0;
826 /* If this is the first write, make a note as we'll have to store the
827 * first cluster in the directory entry later */
828 if (lock->ioh.first_cluster == 0xffffffff)
829 update_entry = TRUE;
831 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data,
832 written)) == 0)
834 /* If nothing was written but success was returned (can that even
835 * happen?) then we don't want to mess with the dir entry */
836 if (*written == 0)
838 D(bug("[fat] nothing successfully written (!),"
839 " nothing else to do\n"));
840 return 0;
843 /* Something changed, we need to tell people about it */
844 lock->do_notify = TRUE;
846 /* Move to the end of the area written */
847 lock->pos += *written;
849 /* Update the dir entry if the size changed */
850 if (lock->pos > lock->gl->size)
852 lock->gl->size = lock->pos;
853 update_entry = TRUE;
856 /* Force an update if the file hasn't already got an archive bit. This
857 * will happen if this was the first write to an existing file that
858 * didn't cause it to grow */
859 else if (!(lock->gl->attr & ATTR_ARCHIVE))
860 update_entry = TRUE;
862 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n",
863 *written, lock->pos, lock->gl->size));
865 if (update_entry)
867 D(bug("[fat] updating dir entry, first cluster is %ld,"
868 " size is %ld\n",
869 lock->ioh.first_cluster, lock->gl->size));
871 lock->gl->first_cluster = lock->ioh.first_cluster;
873 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE,
874 glob);
875 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
877 de.e.entry.file_size = lock->gl->size;
878 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
879 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
881 de.e.entry.attr |= ATTR_ARCHIVE;
882 UpdateDirEntry(&de, glob);
884 ReleaseDirHandle(&dh, glob);
888 return err;
891 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence,
892 LONG *newsize, struct Globals *glob)
894 LONG err;
895 LONG size;
896 struct DirHandle dh;
897 struct DirEntry de;
898 ULONG want, count;
899 ULONG cl, next, first, last;
901 /* Need an exclusive lock to do what is effectively a write */
902 if (lock->gl->access != EXCLUSIVE_LOCK)
904 D(bug("[fat] can't modify global attributes via a shared lock\n"));
905 return ERROR_OBJECT_IN_USE;
908 /* Don't modify the file if it's protected */
909 if (lock->gl->attr & ATTR_READ_ONLY)
911 D(bug("[fat] file is write protected\n"));
912 return ERROR_WRITE_PROTECTED;
915 /* Calculate the new length based on the current position */
916 if (whence == OFFSET_BEGINNING && offset >= 0)
917 size = offset;
918 else if (whence == OFFSET_CURRENT && lock->pos + offset >= 0)
919 size = lock->pos + offset;
920 else if (whence == OFFSET_END && offset <= 0
921 && lock->gl->size + offset >= 0)
922 size = lock->gl->size + offset;
923 else
924 return ERROR_SEEK_ERROR;
926 if (lock->gl->size == size)
928 D(bug("[fat] new size matches old size, nothing to do\n"));
929 *newsize = size;
930 return 0;
933 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n",
934 lock->gl->size, size));
936 /* Get the dir that this file is in */
937 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh,
938 FALSE, glob)) != 0)
939 return err;
941 /* And the entry */
942 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de, glob)) != 0)
944 ReleaseDirHandle(&dh, glob);
945 return err;
948 /* Calculate how many clusters we need */
949 want = (size >> glob->sb->clustersize_bits)
950 + ((size & (glob->sb->clustersize - 1)) ? 1 : 0);
952 D(bug("[fat] want %ld clusters for file\n", want));
954 /* We're getting three things here - the first cluster of the existing
955 * file, the last cluster of the existing file (which might be the same),
956 * and the number of clusters currently allocated to it (it's not safe to
957 * infer it from the current size as a broken fat implementation may have
958 * allocated it more than it needs). We handle file shrinking/truncation
959 * here as it falls out naturally from following the current cluster chain
962 cl = FIRST_FILE_CLUSTER(&de);
963 if (cl == 0)
965 D(bug("[fat] file is empty\n"));
967 first = 0;
968 count = 0;
971 else if (want == 0)
973 /* If we're fully truncating the file, then the below loop will
974 * actually not truncate the file at all (count will get incremented
975 * past want first time around the loop). It's a pain to incorporate a
976 * full truncate into the loop, not counting the change to the first
977 * cluster, so it's easier to just take care of it all here */
978 D(bug("[fat] want nothing, so truncating the entire file\n"));
980 FREE_CLUSTER_CHAIN(glob->sb, cl);
982 /* Now it has nothing */
983 first = 0;
984 count = 0;
987 else
989 first = cl;
990 count = 0;
992 /* Do the actual count */
993 while ((last = GET_NEXT_CLUSTER(glob->sb, cl))
994 < glob->sb->eoc_mark - 7)
996 count++;
997 cl = last;
999 /* If we get as many clusters as we want, kill everything after
1000 * it */
1001 if (count == want)
1003 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
1004 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
1006 D(bug("[fat] truncated file\n"));
1008 break;
1012 D(bug("[fat] file has %ld clusters\n", count));
1015 /* Now we know how big the current file is. If we don't have enough,
1016 * allocate more until we do */
1017 if (count < want)
1019 D(bug("[fat] growing file\n"));
1021 while (count < want)
1023 if ((err = FindFreeCluster(glob->sb, &next)) != 0)
1025 /* XXX: probably no free clusters left. We should clean up the
1026 * extras we allocated before returning. It won't hurt
1027 * anything to leave them but it is dead space */
1028 ReleaseDirHandle(&dh, glob);
1029 return err;
1032 /* Mark the cluster used */
1033 AllocCluster(glob->sb, next);
1035 /* If the file had no clusters, then this is the first and we
1036 * need to note it for later storage in the direntry */
1037 if (cl == 0)
1038 first = next;
1040 /* Otherwise, hook it up to the current one */
1041 else
1042 SET_NEXT_CLUSTER(glob->sb, cl, next);
1044 /* One more */
1045 count++;
1046 cl = next;
1050 /* Clusters are fixed, now update the directory entry */
1051 de.e.entry.first_cluster_lo = first & 0xffff;
1052 de.e.entry.first_cluster_hi = first >> 16;
1053 de.e.entry.file_size = size;
1054 de.e.entry.attr |= ATTR_ARCHIVE;
1055 UpdateDirEntry(&de, glob);
1057 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size,
1058 first));
1060 /* Done! */
1061 *newsize = size;
1063 return 0;
1066 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
1067 ULONG prot, struct Globals *glob)
1069 LONG err;
1070 struct DirHandle dh;
1071 struct DirEntry de;
1073 /* Get the dir handle */
1074 if ((err = InitDirHandle(glob->sb,
1075 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
1076 glob)) != 0)
1077 return err;
1079 /* Get down to the correct subdir */
1080 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
1082 ReleaseDirHandle(&dh, glob);
1083 return err;
1086 /* Can't change permissions on the root */
1087 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0)
1089 D(bug("[fat] can't set protection on root dir\n"));
1090 ReleaseDirHandle(&dh, glob);
1091 return ERROR_INVALID_LOCK;
1094 /* Get the entry */
1095 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) != 0)
1097 ReleaseDirHandle(&dh, glob);
1098 return err;
1101 /* Set the attributes */
1102 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
1103 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
1105 /* Only set read-only if neither writable nor deletable */
1106 if ((prot & (FIBF_WRITE | FIBF_DELETE)) == (FIBF_WRITE | FIBF_DELETE))
1107 de.e.entry.attr |= ATTR_READ_ONLY;
1108 UpdateDirEntry(&de, glob);
1110 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
1112 SendNotifyByDirEntry(glob->sb, &de);
1114 /* If it's a directory, we also need to update the protections for the
1115 * directory's . entry */
1116 if (de.e.entry.attr & ATTR_DIRECTORY)
1118 ULONG attr = de.e.entry.attr;
1120 D(bug("[fat] setting protections for directory '.' entry\n"));
1122 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh, TRUE, glob);
1123 GetDirEntry(&dh, 0, &de, glob);
1124 de.e.entry.attr = attr;
1125 UpdateDirEntry(&de, glob);
1128 ReleaseDirHandle(&dh, glob);
1130 return 0;
1133 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
1134 struct DateStamp *ds, struct Globals *glob)
1136 LONG err;
1137 struct DirHandle dh;
1138 struct DirEntry de;
1140 /* Get the dir handle */
1141 if ((err = InitDirHandle(glob->sb,
1142 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
1143 glob)) != 0)
1144 return err;
1146 /* Get down to the correct subdir */
1147 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
1149 ReleaseDirHandle(&dh, glob);
1150 return err;
1153 /* Can't set date on the root */
1154 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0)
1156 D(bug("[fat] can't set date on root dir\n"));
1157 ReleaseDirHandle(&dh, glob);
1158 return ERROR_INVALID_LOCK;
1161 /* Get the entry */
1162 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) != 0)
1164 ReleaseDirHandle(&dh, glob);
1165 return err;
1168 /* Set and update the date */
1169 ConvertDOSDate(ds, &de.e.entry.write_date, &de.e.entry.write_time,
1170 glob);
1171 de.e.entry.last_access_date = de.e.entry.write_date;
1172 UpdateDirEntry(&de, glob);
1174 SendNotifyByDirEntry(glob->sb, &de);
1176 ReleaseDirHandle(&dh, glob);
1178 return 0;
1181 LONG OpAddNotify(struct NotifyRequest *nr, struct Globals *glob)
1183 LONG err;
1184 struct DirHandle dh;
1185 struct DirEntry de;
1186 struct GlobalLock *gl = NULL, *tmp;
1187 struct NotifyNode *nn;
1188 BOOL exists = FALSE;
1190 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
1192 /* If the request is for the volume root, then we just link to the root
1193 * lock */
1194 if (nr->nr_FullName[strlen(nr->nr_FullName) - 1] == ':')
1196 D(bug("[fat] adding notify for root dir\n"));
1197 gl = &glob->sb->info->root_lock;
1200 else
1202 if ((err = InitDirHandle(glob->sb, 0, &dh, FALSE, glob)) != 0)
1203 return err;
1205 /* Look for the entry */
1206 err =
1207 GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName),
1208 &de, glob);
1209 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1210 return err;
1212 /* If it was found, then it might be open. try to find the global
1213 * lock */
1214 if (err == 0)
1216 exists = TRUE;
1218 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n",
1219 de.cluster, de.index));
1221 ForeachNode(&glob->sb->info->locks, tmp)
1223 if (tmp->dir_cluster == de.cluster
1224 && tmp->dir_entry == de.index)
1226 gl = tmp;
1228 D(bug("[fat] found global lock 0x%0x\n", gl));
1230 break;
1235 else
1237 exists = FALSE;
1239 D(bug("[fat] file doesn't exist\n"));
1243 if (gl == NULL)
1244 D(bug("[fat] file not currently locked\n"));
1246 /* Allocate space for the notify node */
1247 if ((nn = AllocVecPooled(glob->sb->info->mem_pool,
1248 sizeof(struct NotifyNode))) == NULL)
1249 return ERROR_NO_FREE_STORE;
1251 /* Plug the bits in */
1252 nn->gl = gl;
1253 nn->nr = nr;
1255 /* Add to the list */
1256 ADDTAIL(&glob->sb->info->notifies, nn);
1258 /* Tell them that the file exists if they wanted to know */
1259 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1260 SendNotify(nr, glob);
1262 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1264 return 0;
1267 LONG OpRemoveNotify(struct NotifyRequest *nr, struct Globals *glob)
1269 struct FSSuper *sb;
1270 struct NotifyNode *nn, *nn2;
1272 D(bug("[fat] trying to remove notification for '%s'\n",
1273 nr->nr_FullName));
1275 /* Search inserted volume for the request */
1276 if (glob->sb != NULL)
1278 ForeachNodeSafe(&glob->sb->info->notifies, nn, nn2)
1280 if (nn->nr == nr)
1282 D(bug("[fat] found notify request in list, removing it\n"));
1283 REMOVE(nn);
1284 FreeVecPooled(glob->sb->info->mem_pool, nn);
1285 return 0;
1290 /* Search offline volumes for the request */
1291 ForeachNode(&glob->sblist, sb)
1293 ForeachNodeSafe(&sb->info->notifies, nn, nn2)
1295 if (nn->nr == nr)
1297 D(bug("[fat] found notify request in list, removing it\n"));
1298 REMOVE(nn);
1299 FreeVecPooled(sb->info->mem_pool, nn);
1300 AttemptDestroyVolume(sb);
1301 return 0;
1306 D(bug("[fat] not found, doing nothing\n"));
1308 return 0;