added concrete implementations of putc(), getc(), getchar() and gets()
[tangerine.git] / workbench / fs / fat / ops.c
blobcad22eb9b49cb3d7056b17456bf949abfa702a6b
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 #define DEBUG DEBUG_OPS
22 #include <aros/debug.h>
24 #define FREE_CLUSTER_CHAIN(sb,cl) \
25 do { \
26 ULONG cluster = cl; \
27 while (cluster >= 0 && cluster < sb->eoc_mark) { \
28 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
29 SET_NEXT_CLUSTER(sb, cluster, 0); \
30 cluster = next_cluster; \
31 } \
32 } while(0)
35 * this takes a full path and moves to the directory that would contain the
36 * last file in the path. ie calling with (dh, "foo/bar/baz", 11) will move to
37 * directory "foo/bar" under the dir specified by dh. dh will become a handle
38 * to the new dir. after the return name will be "baz" and namelen will be 3
40 static LONG MoveToSubdir(struct DirHandle *dh, UBYTE **pname, ULONG *pnamelen) {
41 LONG err;
42 UBYTE *name = *pname, *base;
43 ULONG namelen = *pnamelen, baselen;
44 struct DirEntry de;
45 int i;
47 /* if it starts with a volume specifier (or just a :), remove it and get
48 * us back to the root dir */
49 for (i = 0; i < namelen; i++)
50 if (name[i] == ':') {
51 D(bug("[fat] name has volume specifier, moving to the root dir\n"));
53 namelen -= (i+1);
54 name = &name[i+1];
56 InitDirHandle(dh->ioh.sb, 0, dh);
58 break;
61 /* we break the given name into two pieces - the name of the containing
62 * dir, and the name of the new dir to go within it. if the base ends up
63 * empty, then we just use the dirlock */
64 baselen = namelen;
65 base = name;
66 while (baselen > 0) {
67 if (base[baselen-1] != '/')
68 break;
69 baselen--;
71 while (baselen > 0) {
72 if (base[baselen-1] == '/')
73 break;
74 baselen--;
76 namelen -= baselen;
77 name = &base[baselen];
79 D(bug("[fat] base is '%.*s', name is '%.*s'\n", baselen, base, namelen, name));
81 if (baselen > 0) {
82 if ((err = GetDirEntryByPath(dh, base, baselen, &de)) != 0) {
83 D(bug("[fat] base not found\n"));
84 return err;
87 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh)) != 0)
88 return err;
91 *pname = name;
92 *pnamelen = namelen;
94 return 0;
97 LONG OpLockFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG access, struct ExtFileLock **filelock) {
98 /* if they passed in a name, go searching for it */
99 if (namelen != 0)
100 return LockFileByName(dirlock, name, namelen, access, filelock);
102 /* otherwise the empty filename, just make a copy */
103 else if (dirlock != NULL)
104 return CopyLock(dirlock, filelock);
106 /* null dir lock means they want the root */
107 else
108 return LockRoot(access, filelock);
111 void OpUnlockFile(struct ExtFileLock *lock) {
112 if (lock != NULL)
113 FreeLock(lock);
116 LONG OpCopyLock(struct ExtFileLock *lock, struct ExtFileLock **copy) {
117 if (lock != NULL)
118 return CopyLock(lock, copy);
119 else
120 return LockRoot(SHARED_LOCK, copy);
123 LONG OpLockParent(struct ExtFileLock *lock, struct ExtFileLock **parent) {
124 LONG err;
125 struct DirHandle dh;
126 struct DirEntry de;
127 ULONG parent_cluster;
129 /* the root has no parent, but as a special case we have to return success
130 * with the zero lock */
131 if (lock == NULL || lock->gl == &glob->sb->root_lock) {
132 *parent = NULL;
133 return 0;
136 /* if we're in the root directory, then the root is our parent */
137 if (lock->gl->dir_cluster == glob->sb->rootdir_cluster)
138 return LockRoot(SHARED_LOCK, parent);
140 /* get the parent dir */
141 InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh);
142 if ((err = GetDirEntryByPath(&dh, "/", 1, &de)) != 0) {
143 ReleaseDirHandle(&dh);
144 return err;
147 /* and its cluster */
148 parent_cluster = FIRST_FILE_CLUSTER(&de);
150 /* then we go through the parent dir, looking for a link back to us. we do
151 * this so that we have an entry with the proper name for copying by
152 * LockFile() */
153 InitDirHandle(glob->sb, parent_cluster, &dh);
154 while ((err = GetDirEntry(&dh, dh.cur_index + 1, &de)) == 0) {
155 /* don't go past the end */
156 if (de.e.entry.name[0] == 0x00) {
157 err = ERROR_OBJECT_NOT_FOUND;
158 break;
161 /* we found it if its not empty, and its not the volume id or a long
162 * name, and it is a directory, and it does point to us */
163 if (de.e.entry.name[0] != 0xe5 &&
164 !(de.e.entry.attr & ATTR_VOLUME_ID) &&
165 de.e.entry.attr & ATTR_DIRECTORY &&
166 FIRST_FILE_CLUSTER(&de) == lock->gl->dir_cluster) {
168 err = LockFile(parent_cluster, dh.cur_index, SHARED_LOCK, parent);
169 break;
173 ReleaseDirHandle(&dh);
174 return err;
178 * obtains a lock on the named file under the given dir. this is the service
179 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
180 * only return a lock on a file, never on a dir.
182 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG action, struct ExtFileLock **filelock) {
183 LONG err;
184 struct ExtFileLock *lock;
185 struct DirHandle dh;
186 struct DirEntry de;
188 D(bug("[fat] opening file '%.*s' in dir at cluster %ld, action %s\n",
189 namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0,
190 action == ACTION_FINDINPUT ? "FINDINPUT" :
191 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
192 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]"));
194 /* no filename means they're trying to open whatever dirlock is (which
195 * despite the name may not actually be a dir). since there's already an
196 * extant lock, it's never going to be possible to get an exclusive lock,
197 * so this will only work for FINDINPUT (read-only) */
198 if (namelen == 0) {
199 D(bug("[fat] trying to copy passed dir lock\n"));
201 if (action != ACTION_FINDINPUT) {
202 D(bug("[fat] can't copy lock for write (exclusive)\n"));
203 return ERROR_OBJECT_IN_USE;
206 /* dirs can't be opened */
207 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY) {
208 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
209 return ERROR_OBJECT_WRONG_TYPE;
212 /* it's a file, just copy the lock */
213 return CopyLock(dirlock, filelock);
216 /* lock the file */
217 err = LockFileByName(dirlock, name, namelen, action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock);
219 /* found it */
220 if (err == 0) {
221 D(bug("[fat] found existing file\n"));
223 /* can't open directories */
224 if (lock->gl->attr & ATTR_DIRECTORY) {
225 D(bug("[fat] its a directory, can't open it\n"));
226 FreeLock(lock);
227 return ERROR_OBJECT_WRONG_TYPE;
230 /* INPUT/UPDATE use the file as/is */
231 if (action != ACTION_FINDOUTPUT) {
232 D(bug("[fat] returning the lock\n"));
233 *filelock = lock;
234 return 0;
237 /* whereas OUTPUT truncates it */
238 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
240 if (lock->gl->attr & ATTR_READ_ONLY) {
241 D(bug("[fat] file is write protected, doing nothing\n"));
242 FreeLock(lock);
243 return ERROR_WRITE_PROTECTED;
246 /* update the dir entry to make the file empty */
247 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
248 GetDirEntry(&dh, lock->gl->dir_entry, &de);
249 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
250 de.e.entry.file_size = 0;
251 de.e.entry.attr |= ATTR_ARCHIVE;
252 UpdateDirEntry(&de);
254 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
256 /* free the clusters */
257 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
258 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
259 RESET_HANDLE(&lock->ioh);
260 lock->gl->size = 0;
262 D(bug("[fat] file truncated, returning the lock\n"));
264 /* file is empty, go */
265 *filelock = lock;
267 return 0;
270 /* any error other than "not found" should be taken as-is */
271 if (err != ERROR_OBJECT_NOT_FOUND)
272 return err;
274 /* not found. for INPUT we bail out */
275 if (action == ACTION_FINDINPUT) {
276 D(bug("[fat] file not found, and not creating it\n"));
277 return ERROR_OBJECT_NOT_FOUND;
280 D(bug("[fat] trying to create '%.*s'\n", namelen, name));
282 /* otherwise it's time to create the file. get a handle on the passed dir */
283 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
284 return err;
286 /* get down to the correct subdir */
287 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
288 ReleaseDirHandle(&dh);
289 return err;
292 /* if the dir is write protected, can't do anything. root dir is never
293 * write protected */
294 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
295 GetDirEntry(&dh, 0, &de);
296 if (de.e.entry.attr & ATTR_READ_ONLY) {
297 D(bug("[fat] containing dir is write protected, doing nothing\n"));
298 ReleaseDirHandle(&dh);
299 return ERROR_WRITE_PROTECTED;
303 /* create the entry */
304 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_ARCHIVE, 0, &de)) != 0) {
305 ReleaseDirHandle(&dh);
306 return err;
309 /* lock the new file */
310 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock);
312 /* done */
313 ReleaseDirHandle(&dh);
315 if (err == 0) {
316 (*filelock)->do_notify = TRUE;
317 D(bug("[fat] returning lock on new file\n"));
320 return err;
323 /* find the named file in the directory referenced by dirlock, and delete it.
324 * if the file is a directory, it will only be deleted if its empty */
325 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen) {
326 LONG err;
327 struct ExtFileLock *lock;
328 struct DirHandle dh;
329 struct DirEntry de;
331 D(bug("[fat] deleting file '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
333 /* obtain a lock on the file. we need an exclusive lock as we don't want
334 * to delete the file if its in use */
335 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock)) != 0) {
336 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
337 return err;
340 if (lock->gl->attr & ATTR_READ_ONLY) {
341 D(bug("[fat] file is write protected, doing nothing\n"));
342 FreeLock(lock);
343 return ERROR_DELETE_PROTECTED;
346 /* if its a directory, we have to make sure its empty */
347 if (lock->gl->attr & ATTR_DIRECTORY) {
348 D(bug("[fat] file is a directory, making sure its empty\n"));
350 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh)) != 0) {
351 FreeLock(lock);
352 return err;
355 /* loop over the entries, starting from entry 2 (the first real
356 * entry). skipping unused ones, we look for the end-of-directory
357 * marker. if we find it, the directory is empty. if we find a real
358 * name, its in use */
359 de.index = 1;
360 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0) {
361 /* skip unused entries */
362 if (de.e.entry.name[0] == 0xe5)
363 continue;
365 /* end of directory, its empty */
366 if (de.e.entry.name[0] == 0x00)
367 break;
369 /* otherwise the directory is still in use */
370 D(bug("[fat] directory still has files in it, won't delete it\n"));
372 ReleaseDirHandle(&dh);
373 FreeLock(lock);
374 return ERROR_DIRECTORY_NOT_EMPTY;
377 ReleaseDirHandle(&dh);
380 /* open the containing directory */
381 if ((err = InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh)) != 0) {
382 FreeLock(lock);
383 return err;
386 /* if the dir is write protected, can't do anything. root dir is never
387 * write protected */
388 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
389 GetDirEntry(&dh, 0, &de);
390 if (de.e.entry.attr & ATTR_READ_ONLY) {
391 D(bug("[fat] containing dir is write protected, doing nothing\n"));
392 ReleaseDirHandle(&dh);
393 FreeLock(lock);
394 return ERROR_WRITE_PROTECTED;
398 /* get the entry for the file */
399 GetDirEntry(&dh, lock->gl->dir_entry, &de);
401 /* kill it */
402 DeleteDirEntry(&de);
404 /* its all good */
405 ReleaseDirHandle(&dh);
407 /* now free the clusters the file was using */
408 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
410 /* notify */
411 SendNotifyByLock(lock->ioh.sb, lock->gl);
413 /* this lock is now completely meaningless */
414 FreeLock(lock);
416 D(bug("[fat] deleted '%.*s'\n", namelen, name));
418 return 0;
421 LONG OpRenameFile(struct ExtFileLock *sdirlock, UBYTE *sname, ULONG snamelen, struct ExtFileLock *ddirlock, UBYTE *dname, ULONG dnamelen) {
422 struct DirHandle sdh, ddh;
423 struct DirEntry sde, dde;
424 struct GlobalLock *gl;
425 LONG err;
426 ULONG len;
428 /* get the source dir handle */
429 if ((err = InitDirHandle(glob->sb, sdirlock != NULL ? sdirlock->ioh.first_cluster : 0, &sdh)) != 0)
430 return err;
432 /* get down to the correct subdir */
433 if ((err = MoveToSubdir(&sdh, &sname, &snamelen)) != 0) {
434 ReleaseDirHandle(&sdh);
435 return err;
438 /* get the entry */
439 if ((err = GetDirEntryByName(&sdh, sname, snamelen, &sde)) != 0) {
440 ReleaseDirHandle(&sdh);
441 return err;
444 /* make sure we can delete it */
445 if (sde.e.entry.attr & ATTR_READ_ONLY) {
446 D(bug("[fat] source file is write protected, doing nothing\n"));
447 ReleaseDirHandle(&sdh);
448 return ERROR_DELETE_PROTECTED;
451 /* now get a handle on the passed dest dir */
452 if ((err = InitDirHandle(glob->sb, ddirlock != NULL ? ddirlock->ioh.first_cluster : 0, &ddh)) != 0) {
453 ReleaseDirHandle(&sdh);
454 return err;
457 /* get down to the correct subdir */
458 if ((err = MoveToSubdir(&ddh, &dname, &dnamelen)) != 0) {
459 ReleaseDirHandle(&ddh);
460 ReleaseDirHandle(&sdh);
461 return err;
464 /* check the source and dest dirs. if either are readonly, do nothing */
465 GetDirEntry(&sdh, 0, &dde);
466 if (dde.e.entry.attr & ATTR_READ_ONLY) {
467 D(bug("[fat] source dir is read only, doing nothing\n"));
468 ReleaseDirHandle(&ddh);
469 ReleaseDirHandle(&sdh);
470 return ERROR_WRITE_PROTECTED;
472 GetDirEntry(&ddh, 0, &dde);
473 if (dde.e.entry.attr & ATTR_READ_ONLY) {
474 D(bug("[fat] dest dir is read only, doing nothing\n"));
475 ReleaseDirHandle(&ddh);
476 ReleaseDirHandle(&sdh);
477 return ERROR_WRITE_PROTECTED;
480 /* now see if the wanted name is in this dir. if it exists, do nothing */
481 if ((err = GetDirEntryByName(&ddh, dname, dnamelen, &dde)) == 0) {
482 ReleaseDirHandle(&ddh);
483 ReleaseDirHandle(&sdh);
484 return ERROR_OBJECT_EXISTS;
486 else if (err != ERROR_OBJECT_NOT_FOUND) {
487 ReleaseDirHandle(&ddh);
488 ReleaseDirHandle(&sdh);
489 return err;
492 /* at this point we have the source entry in sde, and we know the dest
493 * doesn't exist */
495 /* XXX if sdh and ddh are the same dir and there's room in the existing
496 * entries for the new name, just overwrite the name */
498 /* make a new entry in the target dir */
499 if ((err = CreateDirEntry(&ddh, dname, dnamelen, sde.e.entry.attr | ATTR_ARCHIVE, (sde.e.entry.first_cluster_hi << 16) | sde.e.entry.first_cluster_lo, &dde)) != 0) {
500 ReleaseDirHandle(&ddh);
501 ReleaseDirHandle(&sdh);
504 /* copy in the leftover attributes */
505 dde.e.entry.create_date = sde.e.entry.create_date;
506 dde.e.entry.create_time = sde.e.entry.create_time;
507 dde.e.entry.write_date = sde.e.entry.write_date;
508 dde.e.entry.write_time = sde.e.entry.write_time;
509 dde.e.entry.last_access_date = sde.e.entry.last_access_date;
510 dde.e.entry.create_time_tenth = sde.e.entry.create_time_tenth;
511 dde.e.entry.file_size = sde.e.entry.file_size;
513 UpdateDirEntry(&dde);
515 /* update the global lock (if present) with the new dir cluster/entry */
516 ForeachNode(&sdh.ioh.sb->locks, gl)
517 if (gl->dir_cluster == sde.cluster && gl->dir_entry == sde.index) {
518 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde.cluster, sde.index, dde.cluster, dde.index));
520 gl->dir_cluster = dde.cluster;
521 gl->dir_entry = dde.index;
523 /* update the filename too */
524 GetDirEntryShortName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
525 GetDirEntryLongName(&dde, &(gl->name[1]), &len); gl->name[0] = (UBYTE) len;
528 /* delete the original */
529 DeleteDirEntry(&sde);
531 /* notify */
532 SendNotifyByDirEntry(sdh.ioh.sb, &dde);
534 ReleaseDirHandle(&ddh);
535 ReleaseDirHandle(&sdh);
537 return 0;
540 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock) {
541 LONG err;
542 ULONG cluster;
543 struct DirHandle dh, sdh;
544 struct DirEntry de, sde;
546 D(bug("[fat] creating directory '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
548 /* get a handle on the passed dir */
549 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
550 return err;
552 /* get down to the correct subdir */
553 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
554 ReleaseDirHandle(&dh);
555 return err;
558 /* if the dir is write protected, can't do anything. root dir is never
559 * write protected */
560 if (dh.ioh.first_cluster != dh.ioh.sb->rootdir_cluster) {
561 GetDirEntry(&dh, 0, &de);
562 if (de.e.entry.attr & ATTR_READ_ONLY) {
563 D(bug("[fat] containing dir is write protected, doing nothing\n"));
564 ReleaseDirHandle(&dh);
565 return ERROR_WRITE_PROTECTED;
569 /* now see if the wanted name is in this dir. if it exists, then we do
570 * nothing */
571 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
572 D(bug("[fat] name exists, can't do anything\n"));
573 ReleaseDirHandle(&dh);
574 return ERROR_OBJECT_EXISTS;
577 /* find a free cluster to store the dir in */
578 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
579 ReleaseDirHandle(&dh);
580 return err;
583 /* allocate it */
584 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, dh.ioh.sb->eoc_mark);
586 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
588 /* create the entry, pointing to the new cluster */
589 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY | ATTR_ARCHIVE, cluster, &de)) != 0) {
590 /* deallocate the cluster */
591 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, 0);
593 ReleaseDirHandle(&dh);
594 return err;
597 /* now get a handle on the new directory */
598 InitDirHandle(dh.ioh.sb, cluster, &sdh);
600 /* create the dot entry. its a direct copy of the just-created entry, but
601 * with a different name */
602 GetDirEntry(&sdh, 0, &sde);
603 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
604 CopyMem(". ", &sde.e.entry.name, 11);
605 UpdateDirEntry(&sde);
607 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
608 * to point to the parent */
609 GetDirEntry(&sdh, 1, &sde);
610 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
611 CopyMem(".. ", &sde.e.entry.name, 11);
612 sde.e.entry.first_cluster_lo = dh.ioh.first_cluster & 0xffff;
613 sde.e.entry.first_cluster_hi = dh.ioh.first_cluster >> 16;
614 UpdateDirEntry(&sde);
616 /* put an empty entry at the end to mark end of directory */
617 GetDirEntry(&sdh, 2, &sde);
618 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
619 UpdateDirEntry(&sde);
621 /* new dir created */
622 ReleaseDirHandle(&sdh);
624 /* now obtain a lock on the new dir */
625 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock);
627 /* done */
628 ReleaseDirHandle(&dh);
630 /* notify */
631 SendNotifyByLock((*newdirlock)->ioh.sb, (*newdirlock)->gl);
633 return err;
636 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read) {
637 LONG err;
639 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
641 if (want == 0)
642 return 0;
644 if (want + lock->pos > lock->gl->size) {
645 want = lock->gl->size - lock->pos;
646 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
649 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
650 lock->pos += *read;
651 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
654 return err;
657 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written) {
658 LONG err;
659 BOOL update_entry = FALSE;
660 struct DirHandle dh;
661 struct DirEntry de;
663 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
665 /* need an exclusive lock */
666 if (lock->gl->access != EXCLUSIVE_LOCK) {
667 D(bug("[fat] can't modify global attributes via a shared lock\n"));
668 return ERROR_OBJECT_IN_USE;
671 /* don't modify the file if its protected */
672 if (lock->gl->attr & ATTR_READ_ONLY) {
673 D(bug("[fat] file is write protected\n"));
674 return ERROR_WRITE_PROTECTED;
677 if (want == 0)
678 return 0;
680 /* if this is the first write, make a note as we'll have to store the
681 * first cluster in the directory entry later */
682 if (lock->ioh.first_cluster == 0xffffffff)
683 update_entry = TRUE;
685 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
686 /* if nothing was written but success was returned (can that even
687 * happen?) then we don't want to mess with the dir entry */
688 if (*written == 0) {
689 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
690 return 0;
693 /* something changed, we need to tell people about it */
694 lock->do_notify = TRUE;
696 /* move to the end of the area written */
697 lock->pos += *written;
699 /* update the dir entry if the size changed */
700 if (lock->pos > lock->gl->size) {
701 lock->gl->size = lock->pos;
702 update_entry = TRUE;
705 /* force an update if the file hasn't already got an archive bit. this
706 * will happen if this was the first write to an existing file that
707 * didn't cause it to grow */
708 else if (!(lock->gl->attr & ATTR_ARCHIVE))
709 update_entry = TRUE;
711 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
713 if (update_entry) {
714 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
716 lock->gl->first_cluster = lock->ioh.first_cluster;
718 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
719 GetDirEntry(&dh, lock->gl->dir_entry, &de);
721 de.e.entry.file_size = lock->gl->size;
722 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
723 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
725 de.e.entry.attr |= ATTR_ARCHIVE;
726 UpdateDirEntry(&de);
728 ReleaseDirHandle(&dh);
732 return err;
735 LONG OpSetFileSize(struct ExtFileLock *lock, LONG offset, LONG whence, LONG *newsize) {
736 LONG err;
737 LONG size;
738 struct DirHandle dh;
739 struct DirEntry de;
740 ULONG want, count;
741 ULONG cl, next, first, last;
743 /* need an exclusive lock to do what is effectively a write */
744 if (lock->gl->access != EXCLUSIVE_LOCK) {
745 D(bug("[fat] can't modify global attributes via a shared lock\n"));
746 return ERROR_OBJECT_IN_USE;
749 /* don't modify the file if its protected */
750 if (lock->gl->attr & ATTR_READ_ONLY) {
751 D(bug("[fat] file is write protected\n"));
752 return ERROR_WRITE_PROTECTED;
755 /* calculate the new length based on the current position */
756 if (whence == OFFSET_BEGINNING && offset >= 0)
757 size = offset;
758 else if (whence == OFFSET_CURRENT && lock->pos + offset>= 0)
759 size = lock->pos + offset;
760 else if (whence == OFFSET_END && offset <= 0 && lock->gl->size + offset >= 0)
761 size = lock->gl->size + offset;
762 else
763 return ERROR_SEEK_ERROR;
765 if (lock->gl->size == size) {
766 D(bug("[fat] new size matches old size, nothing to do\n"));
767 return 0;
770 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n", lock->gl->size, size));
772 /* get the dir that this file is in */
773 if ((err = InitDirHandle(glob->sb, lock->gl->dir_cluster, &dh)) != 0)
774 return err;
776 /* and the entry */
777 if ((err = GetDirEntry(&dh, lock->gl->dir_entry, &de)) != 0) {
778 ReleaseDirHandle(&dh);
779 return err;
782 /* calculate how many clusters we need */
783 want = (size >> glob->sb->clustersize_bits) + ((size & (glob->sb->clustersize-1)) ? 1 : 0);
785 D(bug("[fat] want %ld clusters for file\n", want));
787 /* we're getting three things here - the first cluster of the existing
788 * file, the last cluster of the existing file (which might be the same),
789 * and the number of clusters currently allocated to it (its not safe to
790 * infer it from the current size as a broken fat implementation may have
791 * allocated it more than it needs). we handle file shrinking/truncation
792 * here as it falls out naturally from following the current cluster chain
795 cl = FIRST_FILE_CLUSTER(&de);
796 if (cl == 0) {
797 D(bug("[fat] file is empty\n"));
799 first = 0;
800 count = 0;
803 else if (want == 0) {
804 /* if we're fully truncating the file, then the below loop will
805 * actually not truncate the file at all (count will get incremented
806 * past want first time around the loop). its a pain to incorporate a
807 * full truncate into the loop, not counting the change to the first
808 * cluster, so its easier to just take care of it all here */
809 D(bug("[fat] want nothing, so truncating the entire file\n"));
811 FREE_CLUSTER_CHAIN(glob->sb, cl);
813 /* now it has nothing */
814 first = 0;
815 count = 0;
818 else {
819 first = cl;
820 count = 0;
822 /* do the actual count */
823 while ((last = GET_NEXT_CLUSTER(glob->sb, cl)) < glob->sb->eoc_mark) {
824 count++;
825 cl = last;
827 /* if we get as many clusters as we want, kill everything after it */
828 if (count == want) {
829 FREE_CLUSTER_CHAIN(glob->sb, GET_NEXT_CLUSTER(glob->sb, cl));
830 SET_NEXT_CLUSTER(glob->sb, cl, glob->sb->eoc_mark);
832 D(bug("[fat] truncated file\n"));
834 break;
838 D(bug("[fat] file has %ld clusters\n", count));
841 /* now we know how big the current file is. if we don't have enough,
842 * allocate more until we do */
843 if (count < want) {
844 D(bug("[fat] growing file\n"));
846 while (count < want) {
847 if ((err = FindFreeCluster(glob->sb, &next)) != 0) {
848 /* XXX probably no free clusters left. we should clean up the
849 * extras we allocated before returning. it won't hurt
850 * anything to leave them but it is dead space */
851 ReleaseDirHandle(&dh);
852 return err;
855 /* mark the cluster used */
856 SET_NEXT_CLUSTER(glob->sb, next, glob->sb->eoc_mark);
858 /* if the file had no clusters, then this is the first and we
859 * need to note it for later storage in the direntry */
860 if (cl == 0)
861 first = next;
863 /* otherwise, hook it up to the current one */
864 else
865 SET_NEXT_CLUSTER(glob->sb, cl, next);
867 /* one more */
868 count++;
869 cl = next;
873 /* clusters are fixed, now update the directory entry */
874 de.e.entry.first_cluster_lo = first & 0xffff;
875 de.e.entry.first_cluster_hi = first >> 16;
876 de.e.entry.file_size = size;
877 de.e.entry.attr |= ATTR_ARCHIVE;
878 UpdateDirEntry(&de);
880 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size, first));
882 /* done! */
883 *newsize = size;
885 return 0;
888 LONG OpSetProtect(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, ULONG prot) {
889 LONG err;
890 struct DirHandle dh;
891 struct DirEntry de;
893 /* fat barely supports anything. all protection bits must be 0, except
894 * ARCHIVE, DELETE and WRITE. additionally, DELETE and WRITE must be the
895 * same */
896 if ((prot & ~(FIBF_ARCHIVE | FIBF_DELETE | FIBF_WRITE)) ||
897 (!(prot & FIBF_DELETE) ^ !(prot & FIBF_WRITE)))
899 return ERROR_BAD_NUMBER;
901 /* get the dir handle */
902 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
903 return err;
905 /* get down to the correct subdir */
906 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
907 ReleaseDirHandle(&dh);
908 return err;
911 /* can't change permissions on the root */
912 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
913 D(bug("[fat] can't set protection on root dir\n"));
914 ReleaseDirHandle(&dh);
915 return ERROR_INVALID_LOCK;
918 /* get the entry */
919 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
920 ReleaseDirHandle(&dh);
921 return err;
924 /* set the attributes */
925 de.e.entry.attr &= ~(ATTR_ARCHIVE | ATTR_READ_ONLY);
926 de.e.entry.attr |= (prot & FIBF_ARCHIVE ? ATTR_ARCHIVE : 0);
927 de.e.entry.attr |= (prot & FIBF_WRITE ? ATTR_READ_ONLY : 0);
928 UpdateDirEntry(&de);
930 D(bug("[fat] new protection is 0x%08x\n", de.e.entry.attr));
932 SendNotifyByDirEntry(glob->sb, &de);
934 /* if its a directory, we also need to update the protections for the
935 * directory's . entry */
936 if (de.e.entry.attr & ATTR_DIRECTORY) {
937 ULONG attr = de.e.entry.attr;
939 D(bug("[fat] setting protections for directory '.' entry\n"));
941 InitDirHandle(glob->sb, FIRST_FILE_CLUSTER(&de), &dh);
942 GetDirEntry(&dh, 0, &de);
943 de.e.entry.attr = attr;
944 UpdateDirEntry(&de);
947 ReleaseDirHandle(&dh);
949 return 0;
952 LONG OpSetDate(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct DateStamp *ds) {
953 LONG err;
954 struct DirHandle dh;
955 struct DirEntry de;
957 /* get the dir handle */
958 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
959 return err;
961 /* get down to the correct subdir */
962 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
963 ReleaseDirHandle(&dh);
964 return err;
967 /* can't set date on the root */
968 if (dh.ioh.first_cluster == dh.ioh.sb->rootdir_cluster && namelen == 0) {
969 D(bug("[fat] can't set date on root dir\n"));
970 ReleaseDirHandle(&dh);
971 return ERROR_INVALID_LOCK;
974 /* get the entry */
975 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) != 0) {
976 ReleaseDirHandle(&dh);
977 return err;
980 /* set and update the date */
981 ConvertAROSDate(*ds, &de.e.entry.write_date, &de.e.entry.write_time);
982 UpdateDirEntry(&de);
984 SendNotifyByDirEntry(glob->sb, &de);
986 ReleaseDirHandle(&dh);
988 return 0;
991 LONG OpAddNotify(struct NotifyRequest *nr) {
992 LONG err;
993 struct DirHandle dh;
994 struct DirEntry de;
995 struct GlobalLock *gl = NULL, *tmp;
996 struct NotifyNode *nn;
997 BOOL exists;
999 D(bug("[fat] trying to add notification for '%s'\n", nr->nr_FullName));
1001 /* if the request is for the volume root, then we just link to the root lock */
1002 if (nr->nr_FullName[strlen(nr->nr_FullName)-1] == ':') {
1003 D(bug("[fat] adding notify for root dir\n"));
1004 gl = &glob->sb->root_lock;
1007 else {
1008 if ((err = InitDirHandle(glob->sb, 0, &dh)) != 0)
1009 return err;
1011 /* look for the entry */
1012 err = GetDirEntryByPath(&dh, nr->nr_FullName, strlen(nr->nr_FullName), &de);
1013 if (err != 0 && err != ERROR_OBJECT_NOT_FOUND)
1014 return err;
1016 /* if it was found, then it might be open. try to find the global lock */
1017 if (err == 0) {
1018 exists = TRUE;
1020 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de.cluster, de.index));
1022 ForeachNode(&glob->sb->locks, tmp)
1023 if (tmp->dir_cluster == de.cluster && tmp->dir_entry == de.index) {
1024 gl = tmp;
1026 D(bug("[fat] found global lock 0x%0x\n", gl));
1028 break;
1032 else {
1033 exists = FALSE;
1035 D(bug("[fat] file doesn't exist\n"));
1039 if (gl == NULL)
1040 D(bug("[fat] file not currently locked\n"));
1042 /* allocate space to for the notify node */
1043 if ((nn = AllocVecPooled(glob->mempool, sizeof(struct NotifyNode))) == NULL)
1044 return ERROR_NO_FREE_STORE;
1046 /* plug the bits in */
1047 nn->gl = gl;
1048 nn->nr = nr;
1050 /* add to the list */
1051 ADDTAIL(&glob->sb->notifies, nn);
1053 /* tell them that the file exists if they wanted to know */
1054 if (exists && nr->nr_Flags & NRF_NOTIFY_INITIAL)
1055 SendNotify(nr);
1057 D(bug("[fat] now reporting activity on '%s'\n", nr->nr_FullName));
1059 return 0;
1062 LONG OpRemoveNotify(struct NotifyRequest *nr) {
1063 struct NotifyNode *nn;
1065 D(bug("[fat] trying to remove notification for '%s'\n", nr->nr_FullName));
1067 ForeachNode(&glob->sb->notifies, nn)
1068 if (nn->nr == nr) {
1069 D(bug("[fat] found notify request in list, removing it\n"));
1070 REMOVE(nn);
1071 FreeVecPooled(glob->mempool, nn);
1072 return 0;
1075 D(bug("[fat] not found, doing nothing\n"));
1077 return 0;