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.
13 #define AROS_ALMOST_COMPATIBLE
15 #include <aros/macros.h>
16 #include <exec/types.h>
18 #include <dos/notify.h>
19 #include <proto/exec.h>
22 #include "fat_protos.h"
24 #define DEBUG DEBUG_OPS
27 #define FREE_CLUSTER_CHAIN(sb,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; \
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
44 static LONG
MoveToSubdir(struct DirHandle
*dh
, UBYTE
**pname
,
45 ULONG
*pnamelen
, struct Globals
*glob
)
48 UBYTE
*name
= *pname
, *base
, ch
, *p
;
49 ULONG namelen
= *pnamelen
, baselen
;
52 /* Skip device name (if any) */
53 for (ch
= *(p
= name
); ch
!= ':' && ch
!= '\0'; ch
= *(++p
));
56 namelen
-= (p
- name
) + 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 */
67 if (base
[baselen
- 1] != '/')
73 if (base
[baselen
- 1] == '/')
78 name
= &base
[baselen
];
81 bug("[fat] base is '");
82 RawPutChars(base
, baselen
); bug("', name is '");
83 RawPutChars(name
, namelen
);
89 if ((err
= GetDirEntryByPath(dh
, base
, baselen
, &de
, glob
)) != 0)
91 D(bug("[fat] base not found\n"));
95 if ((err
= InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(&de
), dh
,
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 */
111 return LockFileByName(dirlock
, name
, namelen
, access
, filelock
,
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 */
120 return LockRoot(access
, filelock
, glob
);
123 void OpUnlockFile(struct ExtFileLock
*lock
, struct Globals
*glob
)
126 FreeLock(lock
, glob
);
129 LONG
OpCopyLock(struct ExtFileLock
*lock
, struct ExtFileLock
**copy
,
130 struct Globals
*glob
)
133 return CopyLock(lock
, copy
, glob
);
135 return LockRoot(SHARED_LOCK
, copy
, glob
);
138 LONG
OpLockParent(struct ExtFileLock
*lock
, struct ExtFileLock
**parent
,
139 struct Globals
*glob
)
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
)
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
);
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
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
;
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
)
191 LockFile(parent_cluster
, dh
.cur_index
, SHARED_LOCK
, parent
,
197 ReleaseDirHandle(&dh
, glob
);
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
)
210 struct ExtFileLock
*lock
;
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 */
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) */
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
);
253 err
= LockFileByName(dirlock
, name
, namelen
,
254 action
== ACTION_FINDINPUT
? SHARED_LOCK
: EXCLUSIVE_LOCK
, &lock
,
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"));
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
);
304 D(bug("[fat] file truncated, returning the lock\n"));
306 /* File is empty, go */
312 /* Any error other than "not found" should be taken as-is */
313 if (err
!= ERROR_OBJECT_NOT_FOUND
)
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
);
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
))
335 /* Get down to the correct subdir */
336 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
338 ReleaseDirHandle(&dh
, glob
);
342 /* If the dir is write protected, can't do anything. Root dir is never
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 */
357 CreateDirEntry(&dh
, name
, namelen
, ATTR_ARCHIVE
, 0, &de
, glob
)) != 0)
359 ReleaseDirHandle(&dh
, glob
);
363 /* Lock the new file */
364 err
= LockFile(de
.cluster
, de
.index
, EXCLUSIVE_LOCK
, filelock
, glob
);
367 ReleaseDirHandle(&dh
, glob
);
371 (*filelock
)->do_notify
= TRUE
;
372 D(bug("[fat] returning lock on new file\n"));
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
)
384 struct ExtFileLock
*lock
;
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);
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
,
402 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
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
,
421 FreeLock(lock
, glob
);
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 */
430 while ((err
= GetDirEntry(&dh
, de
.index
+ 1, &de
, glob
)) == 0)
432 /* Skip unused entries */
433 if (de
.e
.entry
.name
[0] == 0xe5)
436 /* End of directory, it's empty */
437 if (de
.e
.entry
.name
[0] == 0x00)
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
,
455 FreeLock(lock
, glob
);
459 /* If the dir is write protected, can't do anything. Root dir is never
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
);
477 DeleteDirEntry(&de
, glob
);
480 ReleaseDirHandle(&dh
, glob
);
482 /* Now free the clusters the file was using */
483 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
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
);
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
;
510 /* Get the source dir handle */
511 if ((err
= InitDirHandle(glob
->sb
,
512 sdirlock
!= NULL
? sdirlock
->ioh
.first_cluster
: 0, &sdh
,
516 /* Get down to the correct subdir */
517 if ((err
= MoveToSubdir(&sdh
, &sname
, &snamelen
, glob
)) != 0)
519 ReleaseDirHandle(&sdh
, glob
);
524 if ((err
= GetDirEntryByName(&sdh
, sname
, snamelen
, &sde
, glob
)) != 0)
526 ReleaseDirHandle(&sdh
, glob
);
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
,
535 ReleaseDirHandle(&sdh
, glob
);
539 /* Get down to the correct subdir */
540 if ((err
= MoveToSubdir(&ddh
, &dname
, &dnamelen
, glob
)) != 0)
542 ReleaseDirHandle(&ddh
, glob
);
543 ReleaseDirHandle(&sdh
, glob
);
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
);
579 /* At this point we have the source entry in sde, and we know the dest
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
,
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
);
630 SendNotifyByDirEntry(sdh
.ioh
.sb
, &dde
);
632 ReleaseDirHandle(&ddh
, glob
);
633 ReleaseDirHandle(&sdh
, glob
);
638 LONG
OpCreateDir(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
639 struct ExtFileLock
**newdirlock
, struct Globals
*glob
)
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
,
659 /* Get down to the correct subdir */
660 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
662 ReleaseDirHandle(&dh
, glob
);
666 /* Make sure 'name' is just the FilePart() */
667 for (i
= namelen
- 1; i
> 0; i
--)
669 if (name
[i
] == '/' || name
[i
] == ':')
677 /* If the dir is write protected, can't do anything. Root dir is never
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
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
);
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
);
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
)
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
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
);
759 ReleaseDirHandle(&dh
, glob
);
762 SendNotifyByLock((*newdirlock
)->ioh
.sb
, (*newdirlock
)->gl
);
767 LONG
OpRead(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
,
768 ULONG
*read
, struct Globals
*glob
)
772 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want
,
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)
788 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read
,
795 LONG
OpWrite(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
,
796 ULONG
*written
, struct Globals
*glob
)
799 BOOL update_entry
= FALSE
;
803 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want
,
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
;
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)
831 if ((err
= WriteFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
,
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 */
838 D(bug("[fat] nothing successfully written (!),"
839 " nothing else to do\n"));
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
;
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
))
862 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n",
863 *written
, lock
->pos
, lock
->gl
->size
));
867 D(bug("[fat] updating dir entry, first cluster is %ld,"
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
,
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
);
891 LONG
OpSetFileSize(struct ExtFileLock
*lock
, LONG offset
, LONG whence
,
892 LONG
*newsize
, struct Globals
*glob
)
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)
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
;
924 return ERROR_SEEK_ERROR
;
926 if (lock
->gl
->size
== size
)
928 D(bug("[fat] new size matches old size, nothing to do\n"));
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
,
942 if ((err
= GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
, glob
)) != 0)
944 ReleaseDirHandle(&dh
, glob
);
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
);
965 D(bug("[fat] file is empty\n"));
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 */
992 /* Do the actual count */
993 while ((last
= GET_NEXT_CLUSTER(glob
->sb
, cl
))
994 < glob
->sb
->eoc_mark
- 7)
999 /* If we get as many clusters as we want, kill everything after
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"));
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 */
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
);
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 */
1040 /* Otherwise, hook it up to the current one */
1042 SET_NEXT_CLUSTER(glob
->sb
, 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
,
1066 LONG
OpSetProtect(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
1067 ULONG prot
, struct Globals
*glob
)
1070 struct DirHandle dh
;
1073 /* Get the dir handle */
1074 if ((err
= InitDirHandle(glob
->sb
,
1075 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
,
1079 /* Get down to the correct subdir */
1080 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
1082 ReleaseDirHandle(&dh
, glob
);
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
;
1095 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
, glob
)) != 0)
1097 ReleaseDirHandle(&dh
, glob
);
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
);
1133 LONG
OpSetDate(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
,
1134 struct DateStamp
*ds
, struct Globals
*glob
)
1137 struct DirHandle dh
;
1140 /* Get the dir handle */
1141 if ((err
= InitDirHandle(glob
->sb
,
1142 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
,
1146 /* Get down to the correct subdir */
1147 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
, glob
)) != 0)
1149 ReleaseDirHandle(&dh
, glob
);
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
;
1162 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
, glob
)) != 0)
1164 ReleaseDirHandle(&dh
, glob
);
1168 /* Set and update the date */
1169 ConvertDOSDate(ds
, &de
.e
.entry
.write_date
, &de
.e
.entry
.write_time
,
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
);
1181 LONG
OpAddNotify(struct NotifyRequest
*nr
, struct Globals
*glob
)
1184 struct DirHandle dh
;
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
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
;
1202 if ((err
= InitDirHandle(glob
->sb
, 0, &dh
, FALSE
, glob
)) != 0)
1205 /* Look for the entry */
1207 GetDirEntryByPath(&dh
, nr
->nr_FullName
, strlen(nr
->nr_FullName
),
1209 if (err
!= 0 && err
!= ERROR_OBJECT_NOT_FOUND
)
1212 /* If it was found, then it might be open. try to find the global
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
)
1228 D(bug("[fat] found global lock 0x%0x\n", gl
));
1239 D(bug("[fat] file doesn't exist\n"));
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 */
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
));
1267 LONG
OpRemoveNotify(struct NotifyRequest
*nr
, struct Globals
*glob
)
1270 struct NotifyNode
*nn
, *nn2
;
1272 D(bug("[fat] trying to remove notification for '%s'\n",
1275 /* Search inserted volume for the request */
1276 if (glob
->sb
!= NULL
)
1278 ForeachNodeSafe(&glob
->sb
->info
->notifies
, nn
, nn2
)
1282 D(bug("[fat] found notify request in list, removing it\n"));
1284 FreeVecPooled(glob
->sb
->info
->mem_pool
, nn
);
1290 /* Search offline volumes for the request */
1291 ForeachNode(&glob
->sblist
, sb
)
1293 ForeachNodeSafe(&sb
->info
->notifies
, nn
, nn2
)
1297 D(bug("[fat] found notify request in list, removing it\n"));
1299 FreeVecPooled(sb
->info
->mem_pool
, nn
);
1300 AttemptDestroyVolume(sb
);
1306 D(bug("[fat] not found, doing nothing\n"));