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.
13 #include <exec/types.h>
15 #include <dos/notify.h>
16 #include <proto/exec.h>
19 #include "fat_protos.h"
21 #define DEBUG DEBUG_OPS
22 #include <aros/debug.h>
24 #define FREE_CLUSTER_CHAIN(sb,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; \
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
) {
42 UBYTE
*name
= *pname
, *base
;
43 ULONG namelen
= *pnamelen
, baselen
;
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
++)
51 D(bug("[fat] name has volume specifier, moving to the root dir\n"));
56 InitDirHandle(dh
->ioh
.sb
, 0, dh
);
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 */
67 if (base
[baselen
-1] != '/')
72 if (base
[baselen
-1] == '/')
77 name
= &base
[baselen
];
79 D(bug("[fat] base is '%.*s', name is '%.*s'\n", baselen
, base
, namelen
, name
));
82 if ((err
= GetDirEntryByPath(dh
, base
, baselen
, &de
)) != 0) {
83 D(bug("[fat] base not found\n"));
87 if ((err
= InitDirHandle(dh
->ioh
.sb
, FIRST_FILE_CLUSTER(&de
), dh
)) != 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 */
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 */
108 return LockRoot(access
, filelock
);
111 void OpUnlockFile(struct ExtFileLock
*lock
) {
116 LONG
OpCopyLock(struct ExtFileLock
*lock
, struct ExtFileLock
**copy
) {
118 return CopyLock(lock
, copy
);
120 return LockRoot(SHARED_LOCK
, copy
);
123 LONG
OpLockParent(struct ExtFileLock
*lock
, struct ExtFileLock
**parent
) {
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
) {
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
);
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
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
;
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
);
173 ReleaseDirHandle(&dh
);
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
) {
184 struct ExtFileLock
*lock
;
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) */
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
);
217 err
= LockFileByName(dirlock
, name
, namelen
, action
== ACTION_FINDINPUT
? SHARED_LOCK
: EXCLUSIVE_LOCK
, &lock
);
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"));
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"));
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"));
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
;
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
);
262 D(bug("[fat] file truncated, returning the lock\n"));
264 /* file is empty, go */
270 /* any error other than "not found" should be taken as-is */
271 if (err
!= ERROR_OBJECT_NOT_FOUND
)
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)
286 /* get down to the correct subdir */
287 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
288 ReleaseDirHandle(&dh
);
292 /* if the dir is write protected, can't do anything. root dir is never
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
);
309 /* lock the new file */
310 err
= LockFile(de
.cluster
, de
.index
, EXCLUSIVE_LOCK
, filelock
);
313 ReleaseDirHandle(&dh
);
316 (*filelock
)->do_notify
= TRUE
;
317 D(bug("[fat] returning lock on new file\n"));
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
) {
327 struct ExtFileLock
*lock
;
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"));
340 if (lock
->gl
->attr
& ATTR_READ_ONLY
) {
341 D(bug("[fat] file is write protected, doing nothing\n"));
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) {
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 */
360 while ((err
= GetDirEntry(&dh
, de
.index
+1, &de
)) == 0) {
361 /* skip unused entries */
362 if (de
.e
.entry
.name
[0] == 0xe5)
365 /* end of directory, its empty */
366 if (de
.e
.entry
.name
[0] == 0x00)
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
);
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) {
386 /* if the dir is write protected, can't do anything. root dir is never
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
);
394 return ERROR_WRITE_PROTECTED
;
398 /* get the entry for the file */
399 GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
);
405 ReleaseDirHandle(&dh
);
407 /* now free the clusters the file was using */
408 FREE_CLUSTER_CHAIN(lock
->ioh
.sb
, lock
->ioh
.first_cluster
);
411 SendNotifyByLock(lock
->ioh
.sb
, lock
->gl
);
413 /* this lock is now completely meaningless */
416 D(bug("[fat] deleted '%.*s'\n", namelen
, name
));
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
;
428 /* get the source dir handle */
429 if ((err
= InitDirHandle(glob
->sb
, sdirlock
!= NULL
? sdirlock
->ioh
.first_cluster
: 0, &sdh
)) != 0)
432 /* get down to the correct subdir */
433 if ((err
= MoveToSubdir(&sdh
, &sname
, &snamelen
)) != 0) {
434 ReleaseDirHandle(&sdh
);
439 if ((err
= GetDirEntryByName(&sdh
, sname
, snamelen
, &sde
)) != 0) {
440 ReleaseDirHandle(&sdh
);
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
);
457 /* get down to the correct subdir */
458 if ((err
= MoveToSubdir(&ddh
, &dname
, &dnamelen
)) != 0) {
459 ReleaseDirHandle(&ddh
);
460 ReleaseDirHandle(&sdh
);
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
);
492 /* at this point we have the source entry in sde, and we know the dest
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
);
532 SendNotifyByDirEntry(sdh
.ioh
.sb
, &dde
);
534 ReleaseDirHandle(&ddh
);
535 ReleaseDirHandle(&sdh
);
540 LONG
OpCreateDir(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, struct ExtFileLock
**newdirlock
) {
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)
552 /* get down to the correct subdir */
553 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
554 ReleaseDirHandle(&dh
);
558 /* if the dir is write protected, can't do anything. root dir is never
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
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
);
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
);
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
);
628 ReleaseDirHandle(&dh
);
631 SendNotifyByLock((*newdirlock
)->ioh
.sb
, (*newdirlock
)->gl
);
636 LONG
OpRead(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*read
) {
639 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want
, lock
->pos
));
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) {
651 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read
, lock
->pos
));
657 LONG
OpWrite(struct ExtFileLock
*lock
, UBYTE
*data
, ULONG want
, ULONG
*written
) {
659 BOOL update_entry
= FALSE
;
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
;
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)
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 */
689 D(bug("[fat] nothing successfully written (!), nothing else to do\n"));
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
;
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
))
711 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written
, lock
->pos
, lock
->gl
->size
));
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
;
728 ReleaseDirHandle(&dh
);
735 LONG
OpSetFileSize(struct ExtFileLock
*lock
, LONG offset
, LONG whence
, LONG
*newsize
) {
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)
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
;
763 return ERROR_SEEK_ERROR
;
765 if (lock
->gl
->size
== size
) {
766 D(bug("[fat] new size matches old size, nothing to do\n"));
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)
777 if ((err
= GetDirEntry(&dh
, lock
->gl
->dir_entry
, &de
)) != 0) {
778 ReleaseDirHandle(&dh
);
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
);
797 D(bug("[fat] file is empty\n"));
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 */
822 /* do the actual count */
823 while ((last
= GET_NEXT_CLUSTER(glob
->sb
, cl
)) < glob
->sb
->eoc_mark
) {
827 /* if we get as many clusters as we want, kill everything after it */
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"));
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 */
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
);
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 */
863 /* otherwise, hook it up to the current one */
865 SET_NEXT_CLUSTER(glob
->sb
, 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
;
880 D(bug("[fat] set file size to %ld, first cluster is %ld\n", size
, first
));
888 LONG
OpSetProtect(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, ULONG prot
) {
893 /* fat barely supports anything. all protection bits must be 0, except
894 * ARCHIVE, DELETE and WRITE. additionally, DELETE and WRITE must be the
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)
905 /* get down to the correct subdir */
906 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
907 ReleaseDirHandle(&dh
);
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
;
919 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) != 0) {
920 ReleaseDirHandle(&dh
);
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);
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
;
947 ReleaseDirHandle(&dh
);
952 LONG
OpSetDate(struct ExtFileLock
*dirlock
, UBYTE
*name
, ULONG namelen
, struct DateStamp
*ds
) {
957 /* get the dir handle */
958 if ((err
= InitDirHandle(glob
->sb
, dirlock
!= NULL
? dirlock
->ioh
.first_cluster
: 0, &dh
)) != 0)
961 /* get down to the correct subdir */
962 if ((err
= MoveToSubdir(&dh
, &name
, &namelen
)) != 0) {
963 ReleaseDirHandle(&dh
);
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
;
975 if ((err
= GetDirEntryByName(&dh
, name
, namelen
, &de
)) != 0) {
976 ReleaseDirHandle(&dh
);
980 /* set and update the date */
981 ConvertAROSDate(*ds
, &de
.e
.entry
.write_date
, &de
.e
.entry
.write_time
);
984 SendNotifyByDirEntry(glob
->sb
, &de
);
986 ReleaseDirHandle(&dh
);
991 LONG
OpAddNotify(struct NotifyRequest
*nr
) {
995 struct GlobalLock
*gl
= NULL
, *tmp
;
996 struct NotifyNode
*nn
;
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
;
1008 if ((err
= InitDirHandle(glob
->sb
, 0, &dh
)) != 0)
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
)
1016 /* if it was found, then it might be open. try to find the global lock */
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
) {
1026 D(bug("[fat] found global lock 0x%0x\n", gl
));
1035 D(bug("[fat] file doesn't exist\n"));
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 */
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
)
1057 D(bug("[fat] now reporting activity on '%s'\n", nr
->nr_FullName
));
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
)
1069 D(bug("[fat] found notify request in list, removing it\n"));
1071 FreeVecPooled(glob
->mempool
, nn
);
1075 D(bug("[fat] not found, doing nothing\n"));