Pass 'glob' explicitly to nearly every function. Now builds again with
[AROS.git] / rom / filesys / fat / ops.c
blob53da5ce897fb77708a363327c57eb6a41727c273
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 to
40 * directory "foo/bar" under the dir specified by dh. dh will become a handle
41 * to the new dir. after the return name will be "baz" and namelen will be 3
43 static LONG MoveToSubdir(struct DirHandle *dh, UBYTE **pname,
44 ULONG *pnamelen, struct Globals *glob)
46 LONG err;
47 UBYTE *name = *pname, *base, ch, *p;
48 ULONG namelen = *pnamelen, baselen;
49 struct DirEntry de;
51 /* Skip device name (if any) */
52 for (ch = *(p = name); ch != ':' && ch != '\0'; ch = *(++p));
53 if (ch == ':')
55 namelen -= (p - name) + 1;
56 name = p + 1;
59 /* we break the given name into two pieces - the name of the containing
60 * dir, and the name of the new dir to go within it. if the base ends up
61 * empty, then we just use the dirlock */
62 baselen = namelen;
63 base = name;
64 while (baselen > 0)
66 if (base[baselen - 1] != '/')
67 break;
68 baselen--;
70 while (baselen > 0)
72 if (base[baselen - 1] == '/')
73 break;
74 baselen--;
76 namelen -= baselen;
77 name = &base[baselen];
80 bug("[fat] base is '");
81 RawPutChars(base, baselen); bug("', name is '");
82 RawPutChars(name, namelen);
83 bug("'\n");
86 if (baselen > 0)
88 if ((err = GetDirEntryByPath(dh, base, baselen, &de, glob)) != 0)
90 D(bug("[fat] base not found\n"));
91 return err;
94 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh,
95 TRUE, glob)) != 0)
96 return err;
99 *pname = name;
100 *pnamelen = namelen;
102 return 0;
105 LONG OpLockFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
106 LONG access, struct ExtFileLock **filelock, struct Globals *glob)
108 /* if they passed in a name, go searching for it */
109 if (namelen != 0)
110 return LockFileByName(dirlock, name, namelen, access, filelock,
111 glob);
113 /* otherwise the empty filename, just make a copy */
114 else if (dirlock != NULL)
115 return CopyLock(dirlock, filelock, glob);
117 /* null dir lock means they want the root */
118 else
119 return LockRoot(access, filelock, glob);
122 void OpUnlockFile(struct ExtFileLock *lock, struct Globals *glob)
124 if (lock != NULL)
125 FreeLock(lock, glob);
128 LONG OpCopyLock(struct ExtFileLock *lock, struct ExtFileLock **copy,
129 struct Globals *glob)
131 if (lock != NULL)
132 return CopyLock(lock, copy, glob);
133 else
134 return LockRoot(SHARED_LOCK, copy, glob);
137 LONG OpLockParent(struct ExtFileLock *lock, struct ExtFileLock **parent,
138 struct Globals *glob)
140 LONG err;
141 struct DirHandle dh;
142 struct DirEntry de;
143 ULONG parent_cluster;
145 /* the root has no parent, but as a special case we have to return success
146 * with the zero lock */
147 if (lock == NULL || lock->gl == &glob->sb->info->root_lock)
149 *parent = NULL;
150 return 0;
153 /* if we're in the root directory, then the root is our parent */
154 if (lock->gl->dir_cluster == glob->sb->rootdir_cluster)
155 return LockRoot(SHARED_LOCK, parent, glob);
157 /* get the parent dir */
158 InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh, FALSE, glob);
159 if ((err = GetDirEntryByPath(&dh, "/", 1, &de, glob)) != 0)
161 ReleaseDirHandle(&dh, glob);
162 return err;
165 /* and its cluster */
166 if ((parent_cluster = FIRST_FILE_CLUSTER(&de)) == 0)
167 parent_cluster = glob->sb->rootdir_cluster;
169 /* then we go through the parent dir, looking for a link back to us. we do
170 * this so that we have an entry with the proper name for copying by
171 * LockFile() */
172 InitDirHandle(glob->sb, parent_cluster, &dh, TRUE, glob);
173 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de, glob)) == 0)
175 /* don't go past the end */
176 if (de.e.entry.name[0] == 0x00)
178 err = ERROR_OBJECT_NOT_FOUND;
179 break;
182 /* we found it if it's not empty, and it's not the volume id or a long
183 * name, and it is a directory, and it does point to us */
184 if (de.e.entry.name[0] != 0xe5 &&
185 !(de.e.entry.attr & ATTR_VOLUME_ID) &&
186 de.e.entry.attr & ATTR_DIRECTORY &&
187 FIRST_FILE_CLUSTER(&de) == lock->gl->dir_cluster)
189 err =
190 LockFile(parent_cluster, dh.cur_index, SHARED_LOCK, parent,
191 glob);
192 break;
196 ReleaseDirHandle(&dh, glob);
197 return err;
201 * obtains a lock on the named file under the given dir. this is the service
202 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
203 * only return a lock on a file, never on a dir.
205 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
206 LONG action, struct ExtFileLock **filelock, struct Globals *glob)
208 LONG err;
209 struct ExtFileLock *lock;
210 struct DirHandle dh;
211 struct DirEntry de;
214 bug("[fat] opening file '");
215 RawPutChars(name, namelen);
216 bug("' in dir at cluster %ld, action %s\n",
217 dirlock != NULL ? dirlock->ioh.first_cluster : 0,
218 action == ACTION_FINDINPUT ? "FINDINPUT" :
219 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
220 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]");
223 /* Explicitly mark the dirhandle as uninitialised */
224 dh.ioh.sb = NULL;
226 /* no filename means they're trying to open whatever dirlock is (which
227 * despite the name may not actually be a dir). since there's already an
228 * extant lock, it's never going to be possible to get an exclusive lock,
229 * so this will only work for FINDINPUT (read-only) */
230 if (namelen == 0)
232 D(bug("[fat] trying to copy passed dir lock\n"));
234 if (action != ACTION_FINDINPUT)
236 D(bug("[fat] can't copy lock for write (exclusive)\n"));
237 return ERROR_OBJECT_IN_USE;
240 /* dirs can't be opened */
241 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY)
243 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
244 return ERROR_OBJECT_WRONG_TYPE;
247 /* it's a file, just copy the lock */
248 return CopyLock(dirlock, filelock, glob);
251 /* lock the file */
252 err = LockFileByName(dirlock, name, namelen,
253 action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock,
254 glob);
256 /* found it */
257 if (err == 0)
259 D(bug("[fat] found existing file\n"));
261 /* can't open directories */
262 if (lock->gl->attr & ATTR_DIRECTORY)
264 D(bug("[fat] it's a directory, can't open it\n"));
265 FreeLock(lock, glob);
266 return ERROR_OBJECT_WRONG_TYPE;
269 /* INPUT/UPDATE use the file as/is */
270 if (action != ACTION_FINDOUTPUT)
272 D(bug("[fat] returning the lock\n"));
273 *filelock = lock;
274 return 0;
277 /* whereas OUTPUT truncates it */
278 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
280 if (lock->gl->attr & ATTR_READ_ONLY)
282 D(bug("[fat] file is write protected, doing nothing\n"));
283 FreeLock(lock, glob);
284 return ERROR_WRITE_PROTECTED;
287 /* update the dir entry to make the file empty */
288 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE, glob);
289 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
290 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
291 de.e.entry.file_size = 0;
292 de.e.entry.attr |= ATTR_ARCHIVE;
293 UpdateDirEntry(&de, glob);
295 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
297 /* free the clusters */
298 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
299 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
300 RESET_HANDLE(&lock->ioh);
301 lock->gl->size = 0;
303 D(bug("[fat] file truncated, returning the lock\n"));
305 /* file is empty, go */
306 *filelock = lock;
308 return 0;
311 /* any error other than "not found" should be taken as-is */
312 if (err != ERROR_OBJECT_NOT_FOUND)
313 return err;
315 /* not found. for INPUT we bail out */
316 if (action == ACTION_FINDINPUT)
318 D(bug("[fat] file not found, and not creating it\n"));
319 return ERROR_OBJECT_NOT_FOUND;
323 bug("[fat] trying to create '");
324 RawPutChars(name, namelen);
325 bug("'\n");
328 /* otherwise it's time to create the file. get a handle on the passed dir */
329 if ((err = InitDirHandle(glob->sb,
330 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, TRUE, glob))
331 != 0)
332 return err;
334 /* get down to the correct subdir */
335 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
337 ReleaseDirHandle(&dh, glob);
338 return err;
341 /* if the dir is write protected, can't do anything. root dir is never
342 * write protected */
343 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
345 GetDirEntry(&dh, 0, &de, glob);
346 if (de.e.entry.attr & ATTR_READ_ONLY)
348 D(bug("[fat] containing dir is write protected, doing nothing\n"));
349 ReleaseDirHandle(&dh, glob);
350 return ERROR_WRITE_PROTECTED;
354 /* create the entry */
355 if ((err =
356 CreateDirEntry(&dh, name, namelen, ATTR_ARCHIVE, 0, &de, glob)) != 0)
358 ReleaseDirHandle(&dh, glob);
359 return err;
362 /* lock the new file */
363 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock, glob);
365 /* done */
366 ReleaseDirHandle(&dh, glob);
368 if (err == 0)
370 (*filelock)->do_notify = TRUE;
371 D(bug("[fat] returning lock on new file\n"));
374 return err;
377 /* find the named file in the directory referenced by dirlock, and delete it.
378 * if the file is a directory, it will only be deleted if it's empty */
379 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
380 struct Globals *glob)
382 LONG err;
383 struct ExtFileLock *lock;
384 struct DirHandle dh;
385 struct DirEntry de;
388 bug("[fat] deleting file '");
389 RawPutChars(name, namelen);
390 bug("' in directory at cluster % ld\n",
391 dirlock != NULL ? dirlock->ioh.first_cluster : 0);
394 dh.ioh.sb = NULL;
396 /* obtain a lock on the file. we need an exclusive lock as we don't want
397 * to delete the file if it's in use */
398 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock,
399 glob)) != 0)
401 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
402 return err;
405 if (lock->gl->attr & ATTR_READ_ONLY)
407 D(bug("[fat] file is write protected, doing nothing\n"));
408 FreeLock(lock, glob);
409 return ERROR_DELETE_PROTECTED;
412 /* if it's a directory, we have to make sure it's empty */
413 if (lock->gl->attr & ATTR_DIRECTORY)
415 D(bug("[fat] file is a directory, making sure it's empty\n"));
417 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh,
418 FALSE, glob)) != 0)
420 FreeLock(lock, glob);
421 return err;
424 /* loop over the entries, starting from entry 2 (the first real
425 * entry). skipping unused ones, we look for the end-of-directory
426 * marker. if we find it, the directory is empty. if we find a real
427 * name, it's in use */
428 de.index = 1;
429 while ((err = GetDirEntry(&dh, de.index + 1, &de, glob)) == 0)
431 /* skip unused entries */
432 if (de.e.entry.name[0] == 0xe5)
433 continue;
435 /* end of directory, it's empty */
436 if (de.e.entry.name[0] == 0x00)
437 break;
439 /* otherwise the directory is still in use */
440 D(bug("[fat] directory still has files in it, won't delete it\n"));
442 ReleaseDirHandle(&dh, glob);
443 FreeLock(lock, glob);
444 return ERROR_DIRECTORY_NOT_EMPTY;
447 ReleaseDirHandle(&dh, glob);
450 /* open the containing directory */
451 if ((err =InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh,
452 TRUE, glob)) != 0)
454 FreeLock(lock, glob);
455 return err;
458 /* if the dir is write protected, can't do anything. root dir is never
459 * write protected */
460 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
462 GetDirEntry(&dh, 0, &de, glob);
463 if (de.e.entry.attr & ATTR_READ_ONLY)
465 D(bug("[fat] containing dir is write protected, doing nothing\n"));
466 ReleaseDirHandle(&dh, glob);
467 FreeLock(lock, glob);
468 return ERROR_WRITE_PROTECTED;
472 /* get the entry for the file */
473 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
475 /* kill it */
476 DeleteDirEntry(&de, glob);
478 /* it's all good */
479 ReleaseDirHandle(&dh, glob);
481 /* now free the clusters the file was using */
482 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
484 /* notify */
485 SendNotifyByLock(lock->ioh.sb, lock->gl);
487 /* this lock is now completely meaningless */
488 FreeLock(lock, glob);
491 bug("[fat] deleted '");
492 RawPutChars(name, namelen);
493 bug("'\n");
496 return 0;
499 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname,
500 ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname,
501 ULONG dnamelen, struct Globals *glob)
503 struct DirHandle sdh, ddh;
504 struct DirEntry sde, dde;
505 struct GlobalLock *gl;
506 LONG err;
507 ULONG len;
509 /* get the source dir handle */
510 if ((err = InitDirHandle(glob->sb,
511 sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh,
512 FALSE, glob)) != 0)
513 return err;
515 /* get down to the correct subdir */
516 if ((err = MoveToSubdir(&sdh, &sname, &snamelen, glob)) != 0)
518 ReleaseDirHandle(&sdh, glob);
519 return err;
522 /* get the entry */
523 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde, glob)) != 0)
525 ReleaseDirHandle(&sdh, glob);
526 return err;
529 /* now get a handle on the passed dest dir */
530 if ((err = InitDirHandle(glob->sb,
531 ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh,
532 FALSE, glob)) != 0)
534 ReleaseDirHandle(&sdh, glob);
535 return err;
538 /* get down to the correct subdir */
539 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen, glob)) != 0)
541 ReleaseDirHandle(&ddh, glob);
542 ReleaseDirHandle(&sdh, glob);
543 return err;
546 /* check the source and dest dirs. if either is read-only, do nothing */
547 GetDirEntry(&sdh, 0, &dde, glob);
548 if (dde.e.entry.attr & ATTR_READ_ONLY)
550 D(bug("[fat] source dir is read only, doing nothing\n"));
551 ReleaseDirHandle(&ddh, glob);
552 ReleaseDirHandle(&sdh, glob);
553 return ERROR_WRITE_PROTECTED;
555 GetDirEntry(&ddh, 0, &dde, glob);
556 if (dde.e.entry.attr & ATTR_READ_ONLY)
558 D(bug("[fat] dest dir is read only, doing nothing\n"));
559 ReleaseDirHandle(&ddh, glob);
560 ReleaseDirHandle(&sdh, glob);
561 return ERROR_WRITE_PROTECTED;
564 /* now see if the wanted name is in this dir. if it exists, do nothing */
565 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde, glob)) == 0)
567 ReleaseDirHandle(&ddh, glob);
568 ReleaseDirHandle(&sdh, glob);
569 return ERROR_OBJECT_EXISTS;
571 else if (err != ERROR_OBJECT_NOT_FOUND)
573 ReleaseDirHandle(&ddh, glob);
574 ReleaseDirHandle(&sdh, glob);
575 return err;
578 /* at this point we have the source entry in sde, and we know the dest
579 * doesn't exist */
581 /* XXX if sdh and ddh are the same dir and there's room in the existing
582 * entries for the new name, just overwrite the name */
584 /* make a new entry in the target dir */
585 if ((err = CreateDirEntry(&ddh, dname, dnamelen,
586 sde.e.entry.attr | ATTR_ARCHIVE,
587 (sde.e.entry.first_cluster_hi << 16) | sde.e.entry.first_cluster_lo,
588 &dde, glob)) != 0)
590 ReleaseDirHandle(&ddh, glob);
591 ReleaseDirHandle(&sdh, glob);
594 /* copy in the leftover attributes */
595 dde.e.entry.create_date = sde.e.entry.create_date;
596 dde.e.entry.create_time = sde.e.entry.create_time;
597 dde.e.entry.write_date = sde.e.entry.write_date;
598 dde.e.entry.write_time = sde.e.entry.write_time;
599 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
600 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
601 dde.e.entry.file_size = sde.e.entry.file_size;
603 UpdateDirEntry(&dde, glob);
605 /* update the global lock (if present) with the new dir cluster/entry */
606 ForeachNode(&sdh.ioh.sb->info->locks, gl)
608 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index)
610 D(bug("[fat] found lock with old dir entry (%ld/%ld),"
611 " changing to (%ld/%ld)\n",
612 sde.cluster, sde.index, dde.cluster, dde.index));
614 gl->dir_cluster = dde.cluster;
615 gl->dir_entry = dde.index;
617 /* update the filename too */
618 GetDirEntryShortName(&dde, &(gl->name[1]), &len, glob);
619 gl->name[0] = (UBYTE) len;
620 GetDirEntryLongName(&dde, &(gl->name[1]), &len);
621 gl->name[0] = (UBYTE) len;
625 /* delete the original */
626 DeleteDirEntry(&sde, glob);
628 /* notify */
629 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
631 ReleaseDirHandle(&ddh, glob);
632 ReleaseDirHandle(&sdh, glob);
634 return 0;
637 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
638 struct ExtFileLock **newdirlock, struct Globals *glob)
640 LONG err, i;
641 ULONG cluster;
642 struct DirHandle dh, sdh;
643 struct DirEntry de, sde;
646 bug("[fat] creating directory '");
647 RawPutChars(name, namelen);
648 bug("' in directory at cluster %ld\n",
649 dirlock != NULL ? dirlock->ioh.first_cluster : 0);
652 /* get a handle on the passed dir */
653 if ((err = InitDirHandle(glob->sb,
654 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
655 glob)) != 0)
656 return err;
658 /* get down to the correct subdir */
659 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
661 ReleaseDirHandle(&dh, glob);
662 return err;
665 /* Make sure 'name' is just the FilePart() */
666 for (i = namelen - 1; i > 0; i--)
668 if (name[i] == '/' || name[i] == ':')
670 namelen -= (i + 1);
671 name += (i + 1);
672 break;
676 /* if the dir is write protected, can't do anything. root dir is never
677 * write protected */
678 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster)
680 GetDirEntry(&dh, 0, &de, glob);
681 if (de.e.entry.attr & ATTR_READ_ONLY)
683 D(bug("[fat] containing dir is write protected, doing nothing\n"));
684 ReleaseDirHandle(&dh, glob);
685 return ERROR_WRITE_PROTECTED;
689 /* now see if the wanted name is in this dir. if it exists, then we do
690 * nothing */
691 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) == 0)
693 D(bug("[fat] name exists, can't do anything\n"));
694 ReleaseDirHandle(&dh, glob);
695 return ERROR_OBJECT_EXISTS;
698 /* find a free cluster to store the dir in */
699 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0)
701 ReleaseDirHandle(&dh, glob);
702 return err;
705 /* allocate it */
706 AllocCluster(dh.ioh.sb, cluster);
708 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
710 /* create the entry, pointing to the new cluster */
711 if ((err = CreateDirEntry(&dh, name, namelen,
712 ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de, glob)) != 0)
714 /* deallocate the cluster */
715 FreeCluster(dh.ioh.sb, cluster);
717 ReleaseDirHandle(&dh, glob);
718 return err;
721 /* now get a handle on the new directory */
722 InitDirHandle(dh.ioh.sb, cluster, &sdh, FALSE, glob);
724 /* create the dot entry. it's a direct copy of the just-created entry, but
725 * with a different name */
726 GetDirEntry(&sdh, 0, &sde, glob);
727 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
728 CopyMem(". ", &sde.e.entry.name, FAT_MAX_SHORT_NAME);
729 UpdateDirEntry(&sde, glob);
731 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
732 * to point to the parent */
733 GetDirEntry(&sdh, 1, &sde, glob);
734 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
735 CopyMem(".. ", &sde.e.entry.name, FAT_MAX_SHORT_NAME);
736 cluster = dh.ioh.first_cluster;
737 if (cluster == dh.ioh.sb->rootdir_cluster)
738 cluster = 0;
739 sde.e.entry.first_cluster_lo = cluster & 0xffff;
740 sde.e.entry.first_cluster_hi = cluster >> 16;
741 UpdateDirEntry(&sde, glob);
743 /* clear all remaining entries (the first of which marks the end of the
744 * directory) */
745 for (i = 2; GetDirEntry(&sdh, i, &sde, glob) == 0; i++)
747 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
748 UpdateDirEntry(&sde, glob);
751 /* new dir created */
752 ReleaseDirHandle(&sdh, glob);
754 /* now obtain a lock on the new dir */
755 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock, glob);
757 /* done */
758 ReleaseDirHandle(&dh, glob);
760 /* notify */
761 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
763 return err;
766 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want,
767 ULONG *read, struct Globals *glob)
769 LONG err;
771 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want,
772 lock->pos));
774 if (want == 0)
775 return 0;
777 if (want + lock->pos > lock->gl->size)
779 want = lock->gl->size - lock->pos;
780 D(bug("[fat] full read would take us past end-of-file,"
781 " adjusted want to %ld bytes\n", want));
784 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0)
786 lock->pos += *read;
787 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read,
788 lock->pos));
791 return err;
794 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want,
795 ULONG *written, struct Globals *glob)
797 LONG err;
798 BOOL update_entry = FALSE;
799 struct DirHandle dh;
800 struct DirEntry de;
802 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want,
803 lock->pos));
805 /* need an exclusive lock */
806 if (lock->gl->access != EXCLUSIVE_LOCK)
808 D(bug("[fat] can't modify global attributes via a shared lock\n"));
809 return ERROR_OBJECT_IN_USE;
812 /* don't modify the file if it's protected */
813 if (lock->gl->attr & ATTR_READ_ONLY)
815 D(bug("[fat] file is write protected\n"));
816 return ERROR_WRITE_PROTECTED;
819 if (want == 0)
821 *written = 0;
822 return 0;
825 /* if this is the first write, make a note as we'll have to store the
826 * first cluster in the directory entry later */
827 if (lock->ioh.first_cluster == 0xffffffff)
828 update_entry = TRUE;
830 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data,
831 written)) == 0)
833 /* if nothing was written but success was returned (can that even
834 * happen?) then we don't want to mess with the dir entry */
835 if (*written == 0)
837 D(bug("[fat] nothing successfully written (!),"
838 " nothing else to do\n"));
839 return 0;
842 /* something changed, we need to tell people about it */
843 lock->do_notify = TRUE;
845 /* move to the end of the area written */
846 lock->pos += *written;
848 /* update the dir entry if the size changed */
849 if (lock->pos > lock->gl->size)
851 lock->gl->size = lock->pos;
852 update_entry = TRUE;
855 /* force an update if the file hasn't already got an archive bit. this
856 * will happen if this was the first write to an existing file that
857 * didn't cause it to grow */
858 else if (!(lock->gl->attr & ATTR_ARCHIVE))
859 update_entry = TRUE;
861 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n",
862 *written, lock->pos, lock->gl->size));
864 if (update_entry)
866 D(bug("[fat] updating dir entry, first cluster is %ld,"
867 " size is %ld\n",
868 lock->ioh.first_cluster, lock->gl->size));
870 lock->gl->first_cluster = lock->ioh.first_cluster;
872 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh, FALSE,
873 glob);
874 GetDirEntry(&dh, lock->gl->dir_entry, &de, glob);
876 de.e.entry.file_size = lock->gl->size;
877 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
878 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
880 de.e.entry.attr |= ATTR_ARCHIVE;
881 UpdateDirEntry(&de, glob);
883 ReleaseDirHandle(&dh, glob);
887 return err;
890 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence,
891 LONG *newsize, struct Globals *glob)
893 LONG err;
894 LONG size;
895 struct DirHandle dh;
896 struct DirEntry de;
897 ULONG want, count;
898 ULONG cl, next, first, last;
900 /* need an exclusive lock to do what is effectively a write */
901 if (lock->gl->access != EXCLUSIVE_LOCK)
903 D(bug("[fat] can't modify global attributes via a shared lock\n"));
904 return ERROR_OBJECT_IN_USE;
907 /* don't modify the file if it's protected */
908 if (lock->gl->attr & ATTR_READ_ONLY)
910 D(bug("[fat] file is write protected\n"));
911 return ERROR_WRITE_PROTECTED;
914 /* calculate the new length based on the current position */
915 if (whence == OFFSET_BEGINNING && offset >= 0)
916 size = offset;
917 else if (whence == OFFSET_CURRENT && lock->pos + offset >= 0)
918 size = lock->pos + offset;
919 else if (whence == OFFSET_END && offset <= 0
920 && lock->gl->size + offset >= 0)
921 size = lock->gl->size + offset;
922 else
923 return ERROR_SEEK_ERROR;
925 if (lock->gl->size == size)
927 D(bug("[fat] new size matches old size, nothing to do\n"));
928 *newsize = size;
929 return 0;
932 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n",
933 lock->gl->size, size));
935 /* get the dir that this file is in */
936 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh,
937 FALSE, glob)) != 0)
938 return err;
940 /* and the entry */
941 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de, glob)) != 0)
943 ReleaseDirHandle(&dh, glob);
944 return err;
947 /* calculate how many clusters we need */
948 want = (size >> glob->sb->clustersize_bits)
949 + ((size & (glob->sb->clustersize - 1)) ? 1 : 0);
951 D(bug("[fat] want %ld clusters for file\n", want));
953 /* we're getting three things here - the first cluster of the existing
954 * file, the last cluster of the existing file (which might be the same),
955 * and the number of clusters currently allocated to it (it's not safe to
956 * infer it from the current size as a broken fat implementation may have
957 * allocated it more than it needs). we handle file shrinking/truncation
958 * here as it falls out naturally from following the current cluster chain
961 cl = FIRST_FILE_CLUSTER(&de);
962 if (cl == 0)
964 D(bug("[fat] file is empty\n"));
966 first = 0;
967 count = 0;
970 else if (want == 0)
972 /* if we're fully truncating the file, then the below loop will
973 * actually not truncate the file at all (count will get incremented
974 * past want first time around the loop). it's a pain to incorporate a
975 * full truncate into the loop, not counting the change to the first
976 * cluster, so it's easier to just take care of it all here */
977 D(bug("[fat] want nothing, so truncating the entire file\n"));
979 FREE_CLUSTER_CHAIN(glob->sb, cl);
981 /* now it has nothing */
982 first = 0;
983 count = 0;
986 else
988 first = cl;
989 count = 0;
991 /* do the actual count */
992 while ((last = GET_NEXT_CLUSTER(glob->sb, cl))
993 < glob->sb->eoc_mark - 7)
995 count++;
996 cl = last;
998 /* if we get as many clusters as we want, kill everything after
999 * it */
1000 if (count == want)
1002 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
1003 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
1005 D(bug("[fat] truncated file\n"));
1007 break;
1011 D(bug("[fat] file has %ld clusters\n", count));
1014 /* now we know how big the current file is. if we don't have enough,
1015 * allocate more until we do */
1016 if (count < want)
1018 D(bug("[fat] growing file\n"));
1020 while (count < want)
1022 if ((err = FindFreeCluster(glob->sb, &next)) != 0)
1024 /* XXX probably no free clusters left. we should clean up the
1025 * extras we allocated before returning. it won't hurt
1026 * anything to leave them but it is dead space */
1027 ReleaseDirHandle(&dh, glob);
1028 return err;
1031 /* mark the cluster used */
1032 AllocCluster(glob->sb, next);
1034 /* if the file had no clusters, then this is the first and we
1035 * need to note it for later storage in the direntry */
1036 if (cl == 0)
1037 first = next;
1039 /* otherwise, hook it up to the current one */
1040 else
1041 SET_NEXT_CLUSTER(glob->sb, cl, next);
1043 /* one more */
1044 count++;
1045 cl = next;
1049 /* clusters are fixed, now update the directory entry */
1050 de.e.entry.first_cluster_lo = first & 0xffff;
1051 de.e.entry.first_cluster_hi = first >> 16;
1052 de.e.entry.file_size = size;
1053 de.e.entry.attr |= ATTR_ARCHIVE;
1054 UpdateDirEntry(&de, glob);
1056 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size,
1057 first));
1059 /* done! */
1060 *newsize = size;
1062 return 0;
1065 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
1066 ULONG prot, struct Globals *glob)
1068 LONG err;
1069 struct DirHandle dh;
1070 struct DirEntry de;
1072 /* get the dir handle */
1073 if ((err = InitDirHandle(glob->sb,
1074 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
1075 glob)) != 0)
1076 return err;
1078 /* get down to the correct subdir */
1079 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
1081 ReleaseDirHandle(&dh, glob);
1082 return err;
1085 /* can't change permissions on the root */
1086 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0)
1088 D(bug("[fat] can't set protection on root dir\n"));
1089 ReleaseDirHandle(&dh, glob);
1090 return ERROR_INVALID_LOCK;
1093 /* get the entry */
1094 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) != 0)
1096 ReleaseDirHandle(&dh, glob);
1097 return err;
1100 /* set the attributes */
1101 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
1102 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
1104 /* only set read-only if neither writable nor deletable */
1105 if ((prot & (FIBF_WRITE | FIBF_DELETE)) == (FIBF_WRITE | FIBF_DELETE))
1106 de.e.entry.attr |= ATTR_READ_ONLY;
1107 UpdateDirEntry(&de, glob);
1109 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
1111 SendNotifyByDirEntry(glob->sb, &de);
1113 /* if it's a directory, we also need to update the protections for the
1114 * directory's . entry */
1115 if (de.e.entry.attr & ATTR_DIRECTORY)
1117 ULONG attr = de.e.entry.attr;
1119 D(bug("[fat] setting protections for directory '.' entry\n"));
1121 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh, TRUE, glob);
1122 GetDirEntry(&dh, 0, &de, glob);
1123 de.e.entry.attr = attr;
1124 UpdateDirEntry(&de, glob);
1127 ReleaseDirHandle(&dh, glob);
1129 return 0;
1132 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen,
1133 struct DateStamp *ds, struct Globals *glob)
1135 LONG err;
1136 struct DirHandle dh;
1137 struct DirEntry de;
1139 /* get the dir handle */
1140 if ((err = InitDirHandle(glob->sb,
1141 dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh, FALSE,
1142 glob)) != 0)
1143 return err;
1145 /* get down to the correct subdir */
1146 if ((err = MoveToSubdir(&dh, &name, &namelen, glob)) != 0)
1148 ReleaseDirHandle(&dh, glob);
1149 return err;
1152 /* can't set date on the root */
1153 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0)
1155 D(bug("[fat] can't set date on root dir\n"));
1156 ReleaseDirHandle(&dh, glob);
1157 return ERROR_INVALID_LOCK;
1160 /* get the entry */
1161 if ((err = GetDirEntryByName(&dh, name, namelen, &de, glob)) != 0)
1163 ReleaseDirHandle(&dh, glob);
1164 return err;
1167 /* set and update the date */
1168 ConvertDOSDate(ds, &de.e.entry.write_date, &de.e.entry.write_time,
1169 glob);
1170 de.e.entry.last_access_date = de.e.entry.write_date;
1171 UpdateDirEntry(&de, glob);
1173 SendNotifyByDirEntry(glob->sb, &de);
1175 ReleaseDirHandle(&dh, glob);
1177 return 0;
1180 LONG OpAddNotify(struct NotifyRequest *nr, struct Globals *glob)
1182 LONG err;
1183 struct DirHandle dh;
1184 struct DirEntry de;
1185 struct GlobalLock *gl = NULL, *tmp;
1186 struct NotifyNode *nn;
1187 BOOL exists = FALSE;
1189 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
1191 /* if the request is for the volume root, then we just link to the root
1192 * lock */
1193 if (nr->nr_FullName[strlen(nr->nr_FullName) - 1] == ':')
1195 D(bug("[fat] adding notify for root dir\n"));
1196 gl = &glob->sb->info->root_lock;
1199 else
1201 if ((err = InitDirHandle(glob->sb, 0, &dh, FALSE, glob)) != 0)
1202 return err;
1204 /* look for the entry */
1205 err =
1206 GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName),
1207 &de, glob);
1208 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1209 return err;
1211 /* if it was found, then it might be open. try to find the global
1212 * lock */
1213 if (err == 0)
1215 exists = TRUE;
1217 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n",
1218 de.cluster, de.index));
1220 ForeachNode(&glob->sb->info->locks, tmp)
1222 if (tmp->dir_cluster == de.cluster
1223 && tmp->dir_entry == de.index)
1225 gl = tmp;
1227 D(bug("[fat] found global lock 0x%0x\n", gl));
1229 break;
1234 else
1236 exists = FALSE;
1238 D(bug("[fat] file doesn't exist\n"));
1242 if (gl == NULL)
1243 D(bug("[fat] file not currently locked\n"));
1245 /* allocate space for the notify node */
1246 if ((nn = AllocVecPooled(glob->sb->info->mem_pool,
1247 sizeof(struct NotifyNode))) == NULL)
1248 return ERROR_NO_FREE_STORE;
1250 /* plug the bits in */
1251 nn->gl = gl;
1252 nn->nr = nr;
1254 /* add to the list */
1255 ADDTAIL(&glob->sb->info->notifies, nn);
1257 /* tell them that the file exists if they wanted to know */
1258 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1259 SendNotify(nr, glob);
1261 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1263 return 0;
1266 LONG OpRemoveNotify(struct NotifyRequest *nr, struct Globals *glob)
1268 struct FSSuper *sb;
1269 struct NotifyNode *nn, *nn2;
1271 D(bug("[fat] trying to remove notification for '%s'\n",
1272 nr->nr_FullName));
1274 /* search inserted volume for the request */
1275 if (glob->sb != NULL)
1277 ForeachNodeSafe(&glob->sb->info->notifies, nn, nn2)
1279 if (nn->nr == nr)
1281 D(bug("[fat] found notify request in list, removing it\n"));
1282 REMOVE(nn);
1283 FreeVecPooled(glob->sb->info->mem_pool, nn);
1284 return 0;
1289 /* search offline volumes for the request */
1290 ForeachNode(&glob->sblist, sb)
1292 ForeachNodeSafe(&sb->info->notifies, nn, nn2)
1294 if (nn->nr == nr)
1296 D(bug("[fat] found notify request in list, removing it\n"));
1297 REMOVE(nn);
1298 FreeVecPooled(sb->info->mem_pool, nn);
1299 AttemptDestroyVolume(sb);
1300 return 0;
1305 D(bug("[fat] not found, doing nothing\n"));
1307 return 0;