2 * fat.handler - FAT12/16/32 filesystem handler
4 * Copyright © 2006 Marek Szyprowski
5 * Copyright © 2007-2011 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 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
, ULONG
*pnamelen
) {
45 UBYTE
*name
= *pname
, *base
;
46 ULONG namelen
= *pnamelen
, baselen
;
49 /* we break the given name into two pieces - the name of the containing
50 * dir, and the name of the new dir to go within it. if the base ends up
51 * empty, then we just use the dirlock */
55 if (base
[baselen
-1] != '/')
60 if (base
[baselen
-1] == '/')
65 name
= &base
[baselen
];
67 D(bug("[fat] base is '"); RawPutChars(base
, baselen
);
68 bug("', name is '"); RawPutChars(name
, namelen
); bug("'\n"));
71 if ((err
= GetDirEntryByPath(dh
, base
, baselen
, &de
)) != 0) {
72 D(bug("[fat] base not found\n"));
76 if ((err
= InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(&de
), dh
, TRUE
)) != 0)
86 LONG
OpLockFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, LONG access
, struct ExtFileLock
**filelock
) {
87 /* if they passed in a name, go searching for it */
89 return LockFileByName(dirlock
, name
, namelen
, access
, filelock
);
91 /* otherwise the empty filename, just make a copy */
92 else if (dirlock
!= NULL
)
93 return CopyLock(dirlock
, filelock
);
95 /* null dir lock means they want the root */
97 return LockRoot(access
, filelock
);
100 void OpUnlockFile(struct ExtFileLock
*lock
) {
105 LONG
OpCopyLock(struct ExtFileLock
*lock
, struct ExtFileLock
**copy
) {
107 return CopyLock(lock
, copy
);
109 return LockRoot(SHARED_LOCK
, copy
);
112 LONG
OpLockParent(struct ExtFileLock
*lock
, struct ExtFileLock
**parent
) {
116 ULONG parent_cluster
;
118 /* the root has no parent, but as a special case we have to return success
119 * with the zero lock */
120 if (lock
== NULL
|| lock
->gl
== &glob
->sb
->info
->root_lock
) {
125 /* if we're in the root directory, then the root is our parent */
126 if (lock
->gl
->dir_cluster
== glob
->sb
->rootdir_cluster
)
127 return LockRoot(SHARED_LOCK
, parent
);
129 /* get the parent dir */
130 InitDirHandle(glob
->sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
);
131 if ((err
= GetDirEntryByPath(&dh
, "/", 1, &de
)) != 0) {
132 ReleaseDirHandle(&dh
);
136 /* and its cluster */
137 parent_cluster
= FIRST_FILE_CLUSTER(&de
);
139 /* then we go through the parent dir, looking for a link back to us. we do
140 * this so that we have an entry with the proper name for copying by
142 InitDirHandle(glob
->sb
, parent_cluster
, &dh
, TRUE
);
143 while ((err
= GetDirEntry(&dh
, dh
.cur_index
+ 1, &de
)) == 0) {
144 /* don't go past the end */
145 if (de
.e
.entry
.name
[0] == 0x00) {
146 err
= ERROR_OBJECT_NOT_FOUND
;
150 /* we found it if it's not empty, and it's not the volume id or a long
151 * name, and it is a directory, and it does point to us */
152 if (de
.e
.entry
.name
[0] != 0xe5 &&
153 !(de
.e
.entry
.attr
& ATTR_VOLUME_ID
) &&
154 de
.e
.entry
.attr
& ATTR_DIRECTORY
&&
155 FIRST_FILE_CLUSTER(&de
) == lock
->gl
->dir_cluster
) {
157 err
= LockFile(parent_cluster
, dh
.cur_index
, SHARED_LOCK
, parent
);
162 ReleaseDirHandle(&dh
);
167 * obtains a lock on the named file under the given dir. this is the service
168 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
169 * only return a lock on a file, never on a dir.
171 LONG
OpOpenFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, LONG action
, struct ExtFileLock
**filelock
) {
173 struct ExtFileLock
*lock
;
177 D(bug("[fat] opening file '"); RawPutChars(name
, namelen
);
178 bug("' in dir at cluster %ld, action %s\n",
179 dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0,
180 action
== ACTION_FINDINPUT
? "FINDINPUT" :
181 action
== ACTION_FINDOUTPUT
? "FINDOUTPUT" :
182 action
== ACTION_FINDUPDATE
? "FINDUPDATE" : "[unknown]"));
184 dh
.ioh
.sb
= NULL
; /* Explicitly mark the dirhandle as uninitialised */
186 /* no filename means they're trying to open whatever dirlock is (which
187 * despite the name may not actually be a dir). since there's already an
188 * extant lock, it's never going to be possible to get an exclusive lock,
189 * so this will only work for FINDINPUT (read-only) */
191 D(bug("[fat] trying to copy passed dir lock\n"));
193 if (action
!= ACTION_FINDINPUT
) {
194 D(bug("[fat] can't copy lock for write (exclusive)\n"));
195 return ERROR_OBJECT_IN_USE
;
198 /* dirs can't be opened */
199 if (dirlock
== NULL
|| dirlock
->gl
->attr
& ATTR_DIRECTORY
) {
200 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
201 return ERROR_OBJECT_WRONG_TYPE
;
204 /* it's a file, just copy the lock */
205 return CopyLock(dirlock
, filelock
);
209 err
= LockFileByName(dirlock
, name
, namelen
, action
== ACTION_FINDINPUT
? SHARED_LOCK
: EXCLUSIVE_LOCK
, &lock
);
213 D(bug("[fat] found existing file\n"));
215 /* can't open directories */
216 if (lock
->gl
->attr
& ATTR_DIRECTORY
) {
217 D(bug("[fat] it's a directory, can't open it\n"));
219 return ERROR_OBJECT_WRONG_TYPE
;
222 /* INPUT/UPDATE use the file as/is */
223 if (action
!= ACTION_FINDOUTPUT
) {
224 D(bug("[fat] returning the lock\n"));
229 /* whereas OUTPUT truncates it */
230 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
232 if (lock
->gl
->attr
& ATTR_READ_ONLY
) {
233 D(bug("[fat] file is write protected, doing nothing\n"));
235 return ERROR_WRITE_PROTECTED
;
238 /* update the dir entry to make the file empty */
239 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
);
240 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
241 de
.e
.entry
.first_cluster_lo
= de
.e
.entry
.first_cluster_hi
= 0;
242 de
.e
.entry
.file_size
= 0;
243 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
246 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
248 /* free the clusters */
249 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
250 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
= 0xffffffff;
251 RESET_HANDLE(&lock
->ioh
);
254 D(bug("[fat] file truncated, returning the lock\n"));
256 /* file is empty, go */
262 /* any error other than "not found" should be taken as-is */
263 if (err
!= ERROR_OBJECT_NOT_FOUND
)
266 /* not found. for INPUT we bail out */
267 if (action
== ACTION_FINDINPUT
) {
268 D(bug("[fat] file not found, and not creating it\n"));
269 return ERROR_OBJECT_NOT_FOUND
;
272 D(bug("[fat] trying to create '"); RawPutChars(name
, namelen
); bug("'\n"));
274 /* otherwise it's time to create the file. get a handle on the passed dir */
275 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, TRUE
)) != 0)
278 /* get down to the correct subdir */
279 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
280 ReleaseDirHandle(&dh
);
284 /* if the dir is write protected, can't do anything. root dir is never
286 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
) {
287 GetDirEntry(&dh
, 0, &de
);
288 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
) {
289 D(bug("[fat] containing dir is write protected, doing nothing\n"));
290 ReleaseDirHandle(&dh
);
291 return ERROR_WRITE_PROTECTED
;
295 /* create the entry */
296 if ((err
= CreateDirEntry(&dh
, name
, namelen
, ATTR_ARCHIVE
, 0, &de
)) != 0) {
297 ReleaseDirHandle(&dh
);
301 /* lock the new file */
302 err
= LockFile(de
.cluster
, de
.index
, EXCLUSIVE_LOCK
, filelock
);
305 ReleaseDirHandle(&dh
);
308 (*filelock
)->do_notify
= TRUE
;
309 D(bug("[fat] returning lock on new file\n"));
315 /* find the named file in the directory referenced by dirlock, and delete it.
316 * if the file is a directory, it will only be deleted if it's empty */
317 LONG
OpDeleteFile(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
) {
319 struct ExtFileLock
*lock
;
323 D(bug("[fat] deleting file '"); RawPutChars(name
, namelen
);
324 bug("' in directory at cluster % ld\n", dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0));
328 /* obtain a lock on the file. we need an exclusive lock as we don't want
329 * to delete the file if it's in use */
330 if ((err
= LockFileByName(dirlock
, name
, namelen
, EXCLUSIVE_LOCK
, &lock
)) != 0) {
331 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
335 if (lock
->gl
->attr
& ATTR_READ_ONLY
) {
336 D(bug("[fat] file is write protected, doing nothing\n"));
338 return ERROR_DELETE_PROTECTED
;
341 /* if it's a directory, we have to make sure it's empty */
342 if (lock
->gl
->attr
& ATTR_DIRECTORY
) {
343 D(bug("[fat] file is a directory, making sure it's empty\n"));
345 if ((err
= InitDirHandle(lock
->ioh
.sb
, lock
->ioh
.first_cluster
, &dh
, FALSE
)) != 0) {
350 /* loop over the entries, starting from entry 2 (the first real
351 * entry). skipping unused ones, we look for the end-of-directory
352 * marker. if we find it, the directory is empty. if we find a real
353 * name, it's in use */
355 while ((err
= GetDirEntry(&dh
, de
.index
+1, &de
)) == 0) {
356 /* skip unused entries */
357 if (de
.e
.entry
.name
[0] == 0xe5)
360 /* end of directory, it's empty */
361 if (de
.e
.entry
.name
[0] == 0x00)
364 /* otherwise the directory is still in use */
365 D(bug("[fat] directory still has files in it, won't delete it\n"));
367 ReleaseDirHandle(&dh
);
369 return ERROR_DIRECTORY_NOT_EMPTY
;
372 ReleaseDirHandle(&dh
);
375 /* open the containing directory */
376 if ((err
= InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
, TRUE
)) != 0) {
381 /* if the dir is write protected, can't do anything. root dir is never
383 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
) {
384 GetDirEntry(&dh
, 0, &de
);
385 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
) {
386 D(bug("[fat] containing dir is write protected, doing nothing\n"));
387 ReleaseDirHandle(&dh
);
389 return ERROR_WRITE_PROTECTED
;
393 /* get the entry for the file */
394 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
400 ReleaseDirHandle(&dh
);
402 /* now free the clusters the file was using */
403 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
406 SendNotifyByLock(lock
->ioh
.sb
, lock
->gl
);
408 /* this lock is now completely meaningless */
411 D(bug("[fat] deleted '"); RawPutChars(name
, namelen
); bug("'\n"));
416 LONG
OpRenameFile(struct ExtFileLock
*sdirlock
, UBYTE
*sname
, ULONG snamelen
, struct ExtFileLock
*ddirlock
, UBYTE
*dname
, ULONG dnamelen
) {
417 struct DirHandle sdh
, ddh
;
418 struct DirEntry sde
, dde
;
419 struct GlobalLock
*gl
;
423 /* get the source dir handle */
424 if ((err
= InitDirHandle(glob
->sb
, sdirlock
!= NULL
? sdirlock
->ioh
.first_cluster
: 0, &sdh
, FALSE
)) != 0)
427 /* get down to the correct subdir */
428 if ((err
= MoveToSubdir(&sdh
, &sname
, &snamelen
)) != 0) {
429 ReleaseDirHandle(&sdh
);
434 if ((err
= GetDirEntryByName(&sdh
, sname
, snamelen
, &sde
)) != 0) {
435 ReleaseDirHandle(&sdh
);
439 /* now get a handle on the passed dest dir */
440 if ((err
= InitDirHandle(glob
->sb
, ddirlock
!= NULL
? ddirlock
->ioh
.first_cluster
: 0, &ddh
, FALSE
)) != 0) {
441 ReleaseDirHandle(&sdh
);
445 /* get down to the correct subdir */
446 if ((err
= MoveToSubdir(&ddh
, &dname
, &dnamelen
)) != 0) {
447 ReleaseDirHandle(&ddh
);
448 ReleaseDirHandle(&sdh
);
452 /* check the source and dest dirs. if either is read-only, do nothing */
453 GetDirEntry(&sdh
, 0, &dde
);
454 if (dde
.e
.entry
.attr
& ATTR_READ_ONLY
) {
455 D(bug("[fat] source dir is read only, doing nothing\n"));
456 ReleaseDirHandle(&ddh
);
457 ReleaseDirHandle(&sdh
);
458 return ERROR_WRITE_PROTECTED
;
460 GetDirEntry(&ddh
, 0, &dde
);
461 if (dde
.e
.entry
.attr
& ATTR_READ_ONLY
) {
462 D(bug("[fat] dest dir is read only, doing nothing\n"));
463 ReleaseDirHandle(&ddh
);
464 ReleaseDirHandle(&sdh
);
465 return ERROR_WRITE_PROTECTED
;
468 /* now see if the wanted name is in this dir. if it exists, do nothing */
469 if ((err
= GetDirEntryByName(&ddh
, dname
, dnamelen
, &dde
)) == 0) {
470 ReleaseDirHandle(&ddh
);
471 ReleaseDirHandle(&sdh
);
472 return ERROR_OBJECT_EXISTS
;
474 else if (err
!= ERROR_OBJECT_NOT_FOUND
) {
475 ReleaseDirHandle(&ddh
);
476 ReleaseDirHandle(&sdh
);
480 /* at this point we have the source entry in sde, and we know the dest
483 /* XXX if sdh and ddh are the same dir and there's room in the existing
484 * entries for the new name, just overwrite the name */
486 /* make a new entry in the target dir */
487 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) {
488 ReleaseDirHandle(&ddh
);
489 ReleaseDirHandle(&sdh
);
492 /* copy in the leftover attributes */
493 dde
.e
.entry
.create_date
= sde
.e
.entry
.create_date
;
494 dde
.e
.entry
.create_time
= sde
.e
.entry
.create_time
;
495 dde
.e
.entry
.write_date
= sde
.e
.entry
.write_date
;
496 dde
.e
.entry
.write_time
= sde
.e
.entry
.write_time
;
497 dde
.e
.entry
.last_access_date
= sde
.e
.entry
.last_access_date
;
498 dde
.e
.entry
.create_time_tenth
= sde
.e
.entry
.create_time_tenth
;
499 dde
.e
.entry
.file_size
= sde
.e
.entry
.file_size
;
501 UpdateDirEntry(&dde
);
503 /* update the global lock (if present) with the new dir cluster/entry */
504 ForeachNode(&sdh
.ioh
.sb
->info
->locks
, gl
)
505 if (gl
->dir_cluster
== sde
.cluster
&& gl
->dir_entry
== sde
.index
) {
506 D(bug("[fat] found lock with old dir entry (%ld/%ld), changing to (%ld/%ld)\n", sde
.cluster
, sde
.index
, dde
.cluster
, dde
.index
));
508 gl
->dir_cluster
= dde
.cluster
;
509 gl
->dir_entry
= dde
.index
;
511 /* update the filename too */
512 GetDirEntryShortName(&dde
, &(gl
->name
[1]), &len
); gl
->name
[0] = (UBYTE
) len
;
513 GetDirEntryLongName(&dde
, &(gl
->name
[1]), &len
); gl
->name
[0] = (UBYTE
) len
;
516 /* delete the original */
517 DeleteDirEntry(&sde
);
520 SendNotifyByDirEntry(sdh
.ioh
.sb
, &dde
);
522 ReleaseDirHandle(&ddh
);
523 ReleaseDirHandle(&sdh
);
528 LONG
OpCreateDir(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, struct ExtFileLock
**newdirlock
) {
531 struct DirHandle dh
, sdh
;
532 struct DirEntry de
, sde
;
534 D(bug("[fat] creating directory '"); RawPutChars(name
, namelen
);
535 bug("' in directory at cluster %ld\n", dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0));
537 /* get a handle on the passed dir */
538 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
)) != 0)
541 /* get down to the correct subdir */
542 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
543 ReleaseDirHandle(&dh
);
547 /* Make sure 'name' is just the FilePart() */
548 for (i
= namelen
-1; i
> 0; i
--) {
549 if (name
[i
] == '/' ||
557 /* if the dir is write protected, can't do anything. root dir is never
559 if (dh
.ioh
.first_cluster
!= dh
.ioh
.sb
->rootdir_cluster
) {
560 GetDirEntry(&dh
, 0, &de
);
561 if (de
.e
.entry
.attr
& ATTR_READ_ONLY
) {
562 D(bug("[fat] containing dir is write protected, doing nothing\n"));
563 ReleaseDirHandle(&dh
);
564 return ERROR_WRITE_PROTECTED
;
568 /* now see if the wanted name is in this dir. if it exists, then we do
570 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) == 0) {
571 D(bug("[fat] name exists, can't do anything\n"));
572 ReleaseDirHandle(&dh
);
573 return ERROR_OBJECT_EXISTS
;
576 /* find a free cluster to store the dir in */
577 if ((err
= FindFreeCluster(dh
.ioh
.sb
, &cluster
)) != 0) {
578 ReleaseDirHandle(&dh
);
583 AllocCluster(dh
.ioh
.sb
, cluster
);
585 D(bug("[fat] allocated cluster %ld for directory\n", cluster
));
587 /* create the entry, pointing to the new cluster */
588 if ((err
= CreateDirEntry(&dh
, name
, namelen
, ATTR_DIRECTORY
| ATTR_ARCHIVE
, cluster
, &de
)) != 0) {
589 /* deallocate the cluster */
590 FreeCluster(dh
.ioh
.sb
, cluster
);
592 ReleaseDirHandle(&dh
);
596 /* now get a handle on the new directory */
597 InitDirHandle(dh
.ioh
.sb
, cluster
, &sdh
, FALSE
);
599 /* create the dot entry. it's a direct copy of the just-created entry, but
600 * with a different name */
601 GetDirEntry(&sdh
, 0, &sde
);
602 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
603 CopyMem(". ", &sde
.e
.entry
.name
, 11);
604 UpdateDirEntry(&sde
);
606 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
607 * to point to the parent */
608 GetDirEntry(&sdh
, 1, &sde
);
609 CopyMem(&de
.e
.entry
, &sde
.e
.entry
, sizeof(struct FATDirEntry
));
610 CopyMem(".. ", &sde
.e
.entry
.name
, 11);
611 cluster
= dh
.ioh
.first_cluster
;
612 if (cluster
== dh
.ioh
.sb
->rootdir_cluster
)
614 sde
.e
.entry
.first_cluster_lo
= cluster
& 0xffff;
615 sde
.e
.entry
.first_cluster_hi
= cluster
>> 16;
616 UpdateDirEntry(&sde
);
618 /* clear all remaining entries (the first of which marks the end of the
620 for (i
= 2; GetDirEntry(&sdh
, i
, &sde
) == 0; i
++) {
621 memset(&sde
.e
.entry
, 0, sizeof(struct FATDirEntry
));
622 UpdateDirEntry(&sde
);
625 /* new dir created */
626 ReleaseDirHandle(&sdh
);
628 /* now obtain a lock on the new dir */
629 err
= LockFile(de
.cluster
, de
.index
, SHARED_LOCK
, newdirlock
);
632 ReleaseDirHandle(&dh
);
635 SendNotifyByLock((*newdirlock
)->ioh
.sb
, (*newdirlock
)->gl
);
640 LONG
OpRead(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*read
) {
643 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want
, lock
->pos
));
648 if (want
+ lock
->pos
> lock
->gl
->size
) {
649 want
= lock
->gl
->size
- lock
->pos
;
650 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want
));
653 if ((err
= ReadFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
, read
)) == 0) {
655 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read
, lock
->pos
));
661 LONG
OpWrite(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*written
) {
663 BOOL update_entry
= FALSE
;
667 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want
, lock
->pos
));
669 /* need an exclusive lock */
670 if (lock
->gl
->access
!= EXCLUSIVE_LOCK
) {
671 D(bug("[fat] can't modify global attributes via a shared lock\n"));
672 return ERROR_OBJECT_IN_USE
;
675 /* don't modify the file if it's protected */
676 if (lock
->gl
->attr
& ATTR_READ_ONLY
) {
677 D(bug("[fat] file is write protected\n"));
678 return ERROR_WRITE_PROTECTED
;
686 /* if this is the first write, make a note as we'll have to store the
687 * first cluster in the directory entry later */
688 if (lock
->ioh
.first_cluster
== 0xffffffff)
691 if ((err
= WriteFileChunk(&(lock
->ioh
), lock
->pos
, want
, data
, written
)) == 0) {
692 /* if nothing was written but success was returned (can that even
693 * happen?) then we don't want to mess with the dir entry */
695 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
699 /* something changed, we need to tell people about it */
700 lock
->do_notify
= TRUE
;
702 /* move to the end of the area written */
703 lock
->pos
+= *written
;
705 /* update the dir entry if the size changed */
706 if (lock
->pos
> lock
->gl
->size
) {
707 lock
->gl
->size
= lock
->pos
;
711 /* force an update if the file hasn't already got an archive bit. this
712 * will happen if this was the first write to an existing file that
713 * didn't cause it to grow */
714 else if (!(lock
->gl
->attr
& ATTR_ARCHIVE
))
717 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written
, lock
->pos
, lock
->gl
->size
));
720 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock
->ioh
.first_cluster
, lock
->gl
->size
));
722 lock
->gl
->first_cluster
= lock
->ioh
.first_cluster
;
724 InitDirHandle(lock
->ioh
.sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
);
725 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
727 de
.e
.entry
.file_size
= lock
->gl
->size
;
728 de
.e
.entry
.first_cluster_lo
= lock
->gl
->first_cluster
& 0xffff;
729 de
.e
.entry
.first_cluster_hi
= lock
->gl
->first_cluster
>> 16;
731 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
734 ReleaseDirHandle(&dh
);
741 LONG
OpSetFileSize(struct ExtFileLock
*lock
, LONG offset
, LONG whence
, LONG
*newsize
) {
747 ULONG cl
, next
, first
, last
;
749 /* need an exclusive lock to do what is effectively a write */
750 if (lock
->gl
->access
!= EXCLUSIVE_LOCK
) {
751 D(bug("[fat] can't modify global attributes via a shared lock\n"));
752 return ERROR_OBJECT_IN_USE
;
755 /* don't modify the file if it's protected */
756 if (lock
->gl
->attr
& ATTR_READ_ONLY
) {
757 D(bug("[fat] file is write protected\n"));
758 return ERROR_WRITE_PROTECTED
;
761 /* calculate the new length based on the current position */
762 if (whence
== OFFSET_BEGINNING
&& offset
>= 0)
764 else if (whence
== OFFSET_CURRENT
&& lock
->pos
+ offset
>= 0)
765 size
= lock
->pos
+ offset
;
766 else if (whence
== OFFSET_END
&& offset
<= 0 && lock
->gl
->size
+ offset
>= 0)
767 size
= lock
->gl
->size
+ offset
;
769 return ERROR_SEEK_ERROR
;
771 if (lock
->gl
->size
== size
) {
772 D(bug("[fat] new size matches old size, nothing to do\n"));
776 D(bug("[fat] old size was %ld bytes, new size is %ld bytes\n", lock
->gl
->size
, size
));
778 /* get the dir that this file is in */
779 if ((err
= InitDirHandle(glob
->sb
, lock
->gl
->dir_cluster
, &dh
, FALSE
)) != 0)
783 if ((err
= GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
)) != 0) {
784 ReleaseDirHandle(&dh
);
788 /* calculate how many clusters we need */
789 want
= (size
>> glob
->sb
->clustersize_bits
) + ((size
& (glob
->sb
->clustersize
-1)) ? 1 : 0);
791 D(bug("[fat] want %ld clusters for file\n", want
));
793 /* we're getting three things here - the first cluster of the existing
794 * file, the last cluster of the existing file (which might be the same),
795 * and the number of clusters currently allocated to it (it's not safe to
796 * infer it from the current size as a broken fat implementation may have
797 * allocated it more than it needs). we handle file shrinking/truncation
798 * here as it falls out naturally from following the current cluster chain
801 cl
= FIRST_FILE_CLUSTER(&de
);
803 D(bug("[fat] file is empty\n"));
809 else if (want
== 0) {
810 /* if we're fully truncating the file, then the below loop will
811 * actually not truncate the file at all (count will get incremented
812 * past want first time around the loop). it's a pain to incorporate a
813 * full truncate into the loop, not counting the change to the first
814 * cluster, so it's easier to just take care of it all here */
815 D(bug("[fat] want nothing, so truncating the entire file\n"));
817 FREE_CLUSTER_CHAIN(glob
->sb
, cl
);
819 /* now it has nothing */
828 /* do the actual count */
829 while ((last
= GET_NEXT_CLUSTER(glob
->sb
, cl
))
830 < glob
->sb
->eoc_mark
- 7) {
834 /* if we get as many clusters as we want, kill everything after it */
836 FREE_CLUSTER_CHAIN(glob
->sb
, GET_NEXT_CLUSTER(glob
->sb
, cl
));
837 SET_NEXT_CLUSTER(glob
->sb
, cl
, glob
->sb
->eoc_mark
);
839 D(bug("[fat] truncated file\n"));
845 D(bug("[fat] file has %ld clusters\n", count
));
848 /* now we know how big the current file is. if we don't have enough,
849 * allocate more until we do */
851 D(bug("[fat] growing file\n"));
853 while (count
< want
) {
854 if ((err
= FindFreeCluster(glob
->sb
, &next
)) != 0) {
855 /* XXX probably no free clusters left. we should clean up the
856 * extras we allocated before returning. it won't hurt
857 * anything to leave them but it is dead space */
858 ReleaseDirHandle(&dh
);
862 /* mark the cluster used */
863 AllocCluster(glob
->sb
, next
);
865 /* if the file had no clusters, then this is the first and we
866 * need to note it for later storage in the direntry */
870 /* otherwise, hook it up to the current one */
872 SET_NEXT_CLUSTER(glob
->sb
, cl
, next
);
880 /* clusters are fixed, now update the directory entry */
881 de
.e
.entry
.first_cluster_lo
= first
& 0xffff;
882 de
.e
.entry
.first_cluster_hi
= first
>> 16;
883 de
.e
.entry
.file_size
= size
;
884 de
.e
.entry
.attr
|= ATTR_ARCHIVE
;
887 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size
, first
));
895 LONG
OpSetProtect(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, ULONG prot
) {
900 /* get the dir handle */
901 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
)) != 0)
904 /* get down to the correct subdir */
905 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
906 ReleaseDirHandle(&dh
);
910 /* can't change permissions on the root */
911 if (dh
.ioh
.first_cluster
== dh
.ioh
.sb
->rootdir_cluster
&& namelen
== 0) {
912 D(bug("[fat] can't set protection on root dir\n"));
913 ReleaseDirHandle(&dh
);
914 return ERROR_INVALID_LOCK
;
918 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) != 0) {
919 ReleaseDirHandle(&dh
);
923 /* set the attributes */
924 de
.e
.entry
.attr
&= ~(ATTR_ARCHIVE
| ATTR_READ_ONLY
);
925 de
.e
.entry
.attr
|= (prot
& FIBF_ARCHIVE
? ATTR_ARCHIVE
: 0);
927 /* only set read-only if neither writable nor deletable */
928 if ((prot
& (FIBF_WRITE
| FIBF_DELETE
)) == (FIBF_WRITE
| FIBF_DELETE
))
929 de
.e
.entry
.attr
|= ATTR_READ_ONLY
;
932 D(bug("[fat] new protection is 0x%08x\n", de
.e
.entry
.attr
));
934 SendNotifyByDirEntry(glob
->sb
, &de
);
936 /* if it's a directory, we also need to update the protections for the
937 * directory's . entry */
938 if (de
.e
.entry
.attr
& ATTR_DIRECTORY
) {
939 ULONG attr
= de
.e
.entry
.attr
;
941 D(bug("[fat] setting protections for directory '.' entry\n"));
943 InitDirHandle(glob
->sb
, FIRST_FILE_CLUSTER(&de
), &dh
, TRUE
);
944 GetDirEntry(&dh
, 0, &de
);
945 de
.e
.entry
.attr
= attr
;
949 ReleaseDirHandle(&dh
);
954 LONG
OpSetDate(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, struct DateStamp
*ds
) {
959 /* get the dir handle */
960 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
, FALSE
)) != 0)
963 /* get down to the correct subdir */
964 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
965 ReleaseDirHandle(&dh
);
969 /* can't set date on the root */
970 if (dh
.ioh
.first_cluster
== dh
.ioh
.sb
->rootdir_cluster
&& namelen
== 0) {
971 D(bug("[fat] can't set date on root dir\n"));
972 ReleaseDirHandle(&dh
);
973 return ERROR_INVALID_LOCK
;
977 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) != 0) {
978 ReleaseDirHandle(&dh
);
982 /* set and update the date */
983 ConvertAROSDate(ds
, &de
.e
.entry
.write_date
, &de
.e
.entry
.write_time
);
984 de
.e
.entry
.last_access_date
= de
.e
.entry
.write_date
;
987 SendNotifyByDirEntry(glob
->sb
, &de
);
989 ReleaseDirHandle(&dh
);
994 LONG
OpAddNotify(struct NotifyRequest
*nr
) {
998 struct GlobalLock
*gl
= NULL
, *tmp
;
999 struct NotifyNode
*nn
;
1000 BOOL exists
= FALSE
;
1002 D(bug("[fat] trying to add notification for '%s'\n", nr
->nr_FullName
));
1004 /* if the request is for the volume root, then we just link to the root lock */
1005 if (nr
->nr_FullName
[strlen(nr
->nr_FullName
)-1] == ':') {
1006 D(bug("[fat] adding notify for root dir\n"));
1007 gl
= &glob
->sb
->info
->root_lock
;
1011 if ((err
= InitDirHandle(glob
->sb
, 0, &dh
, FALSE
)) != 0)
1014 /* look for the entry */
1015 err
= GetDirEntryByPath(&dh
, nr
->nr_FullName
, strlen(nr
->nr_FullName
), &de
);
1016 if (err
!= 0 && err
!= ERROR_OBJECT_NOT_FOUND
)
1019 /* if it was found, then it might be open. try to find the global lock */
1023 D(bug("[fat] file exists (%ld/%ld), looking for global lock\n", de
.cluster
, de
.index
));
1025 ForeachNode(&glob
->sb
->info
->locks
, tmp
)
1026 if (tmp
->dir_cluster
== de
.cluster
&& tmp
->dir_entry
== de
.index
) {
1029 D(bug("[fat] found global lock 0x%0x\n", gl
));
1038 D(bug("[fat] file doesn't exist\n"));
1043 D(bug("[fat] file not currently locked\n"));
1045 /* allocate space for the notify node */
1046 if ((nn
= AllocVecPooled(glob
->sb
->info
->mem_pool
,
1047 sizeof(struct NotifyNode
))) == NULL
)
1048 return ERROR_NO_FREE_STORE
;
1050 /* plug the bits in */
1054 /* add to the list */
1055 ADDTAIL(&glob
->sb
->info
->notifies
, nn
);
1057 /* tell them that the file exists if they wanted to know */
1058 if (exists
&& nr
->nr_Flags
& NRF_NOTIFY_INITIAL
)
1061 D(bug("[fat] now reporting activity on '%s'\n", nr
->nr_FullName
));
1066 LONG
OpRemoveNotify(struct NotifyRequest
*nr
) {
1068 struct NotifyNode
*nn
, *nn2
;
1070 D(bug("[fat] trying to remove notification for '%s'\n", nr
->nr_FullName
));
1072 /* search inserted volume for the request */
1073 if (glob
->sb
!= NULL
) {
1074 ForeachNodeSafe(&glob
->sb
->info
->notifies
, nn
, nn2
) {
1076 D(bug("[fat] found notify request in list, removing it\n"));
1078 FreeVecPooled(glob
->sb
->info
->mem_pool
, nn
);
1084 /* search offline volumes for the request */
1085 ForeachNode(&glob
->sblist
, sb
) {
1086 ForeachNodeSafe(&sb
->info
->notifies
, nn
, nn2
) {
1088 D(bug("[fat] found notify request in list, removing it\n"));
1090 FreeVecPooled(sb
->info
->mem_pool
, nn
);
1091 AttemptDestroyVolume(sb
);
1097 D(bug("[fat] not found, doing nothing\n"));