r26343@plastic: rob | 2007-05-08 21:40:25 +1000
[cake.git] / workbench / fs / fat / ops.c
blobbf5aac4ff427cde15e1e8933c0ff340c781a3f67
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 <proto/exec.h>
17 #include "fat_fs.h"
18 #include "fat_protos.h"
20 #if defined(DEBUG_FULL) && DEBUG_FULL != 0
21 #define DEBUG 1
22 #else
23 #define DEBUG 0
24 #endif
25 #include <aros/debug.h>
27 #define FREE_CLUSTER_CHAIN(sb,cl) \
28 do { \
29 ULONG cluster = cl; \
30 while (cluster >= 0 && cluster < sb->eoc_mark) { \
31 ULONG next_cluster = GET_NEXT_CLUSTER(sb, cluster); \
32 SET_NEXT_CLUSTER(sb, cluster, 0); \
33 cluster = next_cluster; \
34 } \
35 } while(0)
38 * this takes a full path and moves to the directory that would contain the
39 * last file in the path. ie 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) {
44 LONG err;
45 UBYTE *name = *pname, *base;
46 ULONG namelen = *pnamelen, baselen;
47 struct DirEntry de;
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 */
52 baselen = namelen;
53 base = name;
54 while (baselen > 0) {
55 if (base[baselen-1] != '/')
56 break;
57 baselen--;
59 while (baselen > 0) {
60 if (base[baselen-1] == '/')
61 break;
62 baselen--;
64 namelen -= baselen;
65 name = &base[baselen];
67 D(bug("[fat] base is '%.*s', name is '%.*s'\n", baselen, base, namelen, name));
69 if (baselen > 0) {
70 if ((err = GetDirEntryByPath(dh, base, baselen, &de)) != 0) {
71 D(bug("[fat] base not found\n"));
72 return err;
75 if ((err = InitDirHandle(dh->ioh.sb, FIRST_FILE_CLUSTER(&de), dh)) != 0)
76 return err;
79 *pname = name;
80 *pnamelen = namelen;
82 return 0;
86 * obtains a lock on the named file under the given dir. this is the service
87 * routine for DOS Open() (ie FINDINPUT/FINDOUTPUT/FINDUPDATE) and as such may
88 * only return a lock on a file, never on a dir.
90 LONG OpOpenFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, LONG action, struct ExtFileLock **filelock) {
91 LONG err;
92 struct ExtFileLock *lock;
93 struct DirHandle dh;
94 struct DirEntry de;
96 D(bug("[fat] opening file '%.*s' in dir at cluster %ld, action %s\n",
97 namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0,
98 action == ACTION_FINDINPUT ? "FINDINPUT" :
99 action == ACTION_FINDOUTPUT ? "FINDOUTPUT" :
100 action == ACTION_FINDUPDATE ? "FINDUPDATE" : "[unknown]"));
102 /* no filename means they're trying to open whatever dirlock is (which
103 * despite the name may not actually be a dir). since there's already an
104 * extant lock, its never going be possible to get an exclusive lock, so
105 * this will only work for FINDINPUT (read-only) */
106 if (namelen == 0) {
107 D(bug("[fat] trying to copy passed dir lock\n"));
109 if (action != ACTION_FINDINPUT) {
110 D(bug("[fat] can't copy lock for write (exclusive)\n"));
111 return ERROR_OBJECT_IN_USE;
114 /* dirs can't be opened */
115 if (dirlock == NULL || dirlock->gl->attr & ATTR_DIRECTORY) {
116 D(bug("[fat] dir lock is a directory, which can't be opened\n"));
117 return ERROR_OBJECT_WRONG_TYPE;
120 /* its a file, just copy the lock */
121 return CopyLock(dirlock, filelock);
124 /* lock the file */
125 err = LockFileByName(dirlock, name, namelen, action == ACTION_FINDINPUT ? SHARED_LOCK : EXCLUSIVE_LOCK, &lock);
127 /* found it */
128 if (err == 0) {
129 D(bug("[fat] found existing file\n"));
131 /* can't open directories */
132 if (lock->gl->attr & ATTR_DIRECTORY) {
133 D(bug("[fat] its a directory, can't open it\n"));
134 FreeLock(lock);
135 return ERROR_OBJECT_WRONG_TYPE;
138 /* INPUT/UPDATE use the file as/is */
139 if (action != ACTION_FINDOUTPUT) {
140 D(bug("[fat] returning the lock\n"));
141 *filelock = lock;
142 return 0;
145 /* whereas OUTPUT truncates it */
146 D(bug("[fat] handling FINDOUTPUT, so truncating the file\n"));
148 /* update the dir entry to make the file empty */
149 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
150 GetDirEntry(&dh, lock->gl->dir_entry, &de);
151 de.e.entry.first_cluster_lo = de.e.entry.first_cluster_hi = 0;
152 de.e.entry.file_size = 0;
153 UpdateDirEntry(&de);
155 D(bug("[fat] set first cluster and size to 0 in directory entry\n"));
157 /* free the clusters */
158 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
159 lock->gl->first_cluster = lock->ioh.first_cluster = 0xffffffff;
160 RESET_HANDLE(&lock->ioh);
161 lock->gl->size = 0;
163 D(bug("[fat] file truncated, returning the lock\n"));
165 /* file is empty, go */
166 *filelock = lock;
168 return 0;
171 /* any error other than "not found" should be taken as-is */
172 if (err != ERROR_OBJECT_NOT_FOUND)
173 return err;
175 /* not found. for INPUT or UPDATE, we bail out */
176 if (action != ACTION_FINDOUTPUT) {
177 D(bug("[fat] file not found, and not creating it\n"));
178 return ERROR_OBJECT_NOT_FOUND;
181 D(bug("[fat] trying to create '%.*s'\n", namelen, name));
183 /* otherwise its time to create the file. get a handle on the passed dir */
184 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
185 return err;
187 /* get down to the correct subdir */
188 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
189 ReleaseDirHandle(&dh);
190 return err;
193 /* create the entry */
194 if ((err = CreateDirEntry(&dh, name, namelen, 0, 0, &de)) != 0) {
195 ReleaseDirHandle(&dh);
196 return err;
199 /* lock the new file */
200 err = LockFile(de.cluster, de.index, EXCLUSIVE_LOCK, filelock);
202 /* done */
203 ReleaseDirHandle(&dh);
205 if (err == 0)
206 D(bug("[fat] returning lock on new file\n"));
208 return err;
211 /* find the named file in the directory referenced by dirlock, and delete it.
212 * if the file is a directory, it will only be deleted if its empty */
213 LONG OpDeleteFile(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen) {
214 LONG err;
215 struct ExtFileLock *lock;
216 struct DirHandle dh;
217 struct DirEntry de;
218 UBYTE checksum;
219 ULONG order;
221 D(bug("[fat] deleting file '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
223 /* obtain a lock on the file. we need an exclusive lock as we don't want
224 * to delete the file if its in use */
225 if ((err = LockFileByName(dirlock, name, namelen, EXCLUSIVE_LOCK, &lock)) != 0) {
226 D(bug("[fat] couldn't obtain exclusive lock on named file\n"));
227 return err;
230 /* if its a directory, we have to make sure its empty */
231 if (lock->gl->attr & ATTR_DIRECTORY) {
232 D(bug("[fat] file is a directory, making sure its empty\n"));
234 if ((err = InitDirHandle(lock->ioh.sb, lock->ioh.first_cluster, &dh)) != 0) {
235 FreeLock(lock);
236 return err;
239 /* loop over the entries, starting from entry 2 (the first real
240 * entry). skipping unused ones, we look for the end-of-directory
241 * marker. if we find it, the directory is empty. if we find a real
242 * name, its in use */
243 de.index = 1;
244 while ((err = GetDirEntry(&dh, de.index+1, &de)) == 0) {
245 /* skip unused entries */
246 if (de.e.entry.name[0] == 0xe5)
247 continue;
249 /* end of directory, its empty */
250 if (de.e.entry.name[0] == 0x00)
251 break;
253 /* otherwise the directory is still in use */
254 D(bug("[fat] directory still has files in it, won't delete it\n"));
256 ReleaseDirHandle(&dh);
257 FreeLock(lock);
258 return ERROR_DIRECTORY_NOT_EMPTY;
261 ReleaseDirHandle(&dh);
264 /* open the containing directory */
265 if ((err = InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh)) != 0) {
266 FreeLock(lock);
267 return err;
270 /* get the entry for the file */
271 GetDirEntry(&dh, lock->gl->dir_entry, &de);
273 /* calculate the short name checksum before we trample on the name */
274 CALC_SHORT_NAME_CHECKSUM(de.e.entry.name, checksum);
276 D(bug("[fat] short name checksum is 0x%02x\n", checksum));
278 /* mark the short entry free */
279 de.e.entry.name[0] = 0xe5;
280 UpdateDirEntry(&de);
282 D(bug("[fat] deleted short name entry\n"));
284 /* now we loop over the previous entries, looking for matching long name
285 * entries and killing them */
286 order = 1;
287 while ((err = GetDirEntry(&dh, de.index-1, &de)) == 0) {
289 /* see if this is a matching long name entry. if its not, we're done */
290 if (!((de.e.entry.attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) ||
291 (de.e.long_entry.order & ~0x40) != order ||
292 de.e.long_entry.checksum != checksum)
294 break;
296 /* kill it */
297 de.e.entry.name[0] = 0xe5;
298 UpdateDirEntry(&de);
300 order++;
303 D(bug("[fat] deleted %ld long name entries\n", order-1));
305 /* directory entries are free */
306 ReleaseDirHandle(&dh);
308 /* now free the clusters the file was using */
309 FREE_CLUSTER_CHAIN(lock->ioh.sb, lock->ioh.first_cluster);
311 /* this lock is now completely meaningless */
312 FreeLock(lock);
314 D(bug("[fat] deleted '%.*s'\n", namelen, name));
316 return 0;
319 LONG OpCreateDir(struct ExtFileLock *dirlock, UBYTE *name, ULONG namelen, struct ExtFileLock **newdirlock) {
320 LONG err;
321 ULONG cluster;
322 struct DirHandle dh, sdh;
323 struct DirEntry de, sde;
325 D(bug("[fat] creating directory '%.*s' in directory at cluster %ld\n", namelen, name, dirlock != NULL ? dirlock->ioh.first_cluster : 0));
327 /* get a handle on the passed dir */
328 if ((err = InitDirHandle(glob->sb, dirlock != NULL ? dirlock->ioh.first_cluster : 0, &dh)) != 0)
329 return err;
331 /* get down to the correct subdir */
332 if ((err = MoveToSubdir(&dh, &name, &namelen)) != 0) {
333 ReleaseDirHandle(&dh);
334 return err;
337 /* now see if the wanted name is in this dir. if it exists, then we do
338 * nothing */
339 if ((err = GetDirEntryByName(&dh, name, namelen, &de)) == 0) {
340 D(bug("[fat] name exists, can't do anything\n"));
341 ReleaseDirHandle(&dh);
342 return ERROR_OBJECT_EXISTS;
345 /* find a free cluster to store the dir in */
346 if ((err = FindFreeCluster(dh.ioh.sb, &cluster)) != 0) {
347 ReleaseDirHandle(&dh);
348 return err;
351 /* allocate it */
352 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, dh.ioh.sb->eoc_mark);
354 D(bug("[fat] allocated cluster %ld for directory\n", cluster));
356 /* create the entry, pointing to the new cluster */
357 if ((err = CreateDirEntry(&dh, name, namelen, ATTR_DIRECTORY, cluster, &de)) != 0) {
358 /* deallocate the cluster */
359 SET_NEXT_CLUSTER(dh.ioh.sb, cluster, 0);
361 ReleaseDirHandle(&dh);
362 return err;
365 /* now get a handle on the new directory */
366 InitDirHandle(dh.ioh.sb, cluster, &sdh);
368 /* create the dot entry. its a direct copy of the just-created entry, but
369 * with a different name */
370 GetDirEntry(&sdh, 0, &sde);
371 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
372 CopyMem(". ", &sde.e.entry.name, 11);
373 UpdateDirEntry(&sde);
375 /* create the dot-dot entry. again, a copy, with the cluster pointer setup
376 * to point to the parent */
377 GetDirEntry(&sdh, 1, &sde);
378 CopyMem(&de.e.entry, &sde.e.entry, sizeof(struct FATDirEntry));
379 CopyMem(".. ", &sde.e.entry.name, 11);
380 sde.e.entry.first_cluster_lo = dh.ioh.first_cluster & 0xffff;
381 sde.e.entry.first_cluster_hi = dh.ioh.first_cluster >> 16;
382 UpdateDirEntry(&sde);
384 /* put an empty entry at the end to mark end of directory */
385 GetDirEntry(&sdh, 2, &sde);
386 memset(&sde.e.entry, 0, sizeof(struct FATDirEntry));
387 UpdateDirEntry(&sde);
389 /* new dir created */
390 ReleaseDirHandle(&sdh);
392 /* now obtain a lock on the new dir */
393 err = LockFile(de.cluster, de.index, SHARED_LOCK, newdirlock);
395 /* done */
396 ReleaseDirHandle(&dh);
398 return err;
401 LONG OpRead(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *read) {
402 LONG err;
404 D(bug("[fat] request to read %ld bytes from file pos %ld\n", want, lock->pos));
406 if (want + lock->pos > lock->gl->size) {
407 want = lock->gl->size - lock->pos;
408 D(bug("[fat] full read would take us past end-of-file, adjusted want to %ld bytes\n", want));
411 if ((err = ReadFileChunk(&(lock->ioh), lock->pos, want, data, read)) == 0) {
412 lock->pos += *read;
413 D(bug("[fat] read %ld bytes, new file pos is %ld\n", *read, lock->pos));
416 return err;
419 LONG OpWrite(struct ExtFileLock *lock, UBYTE *data, ULONG want, ULONG *written) {
420 LONG err;
421 BOOL update_entry = FALSE;
422 struct DirHandle dh;
423 struct DirEntry de;
425 D(bug("[fat] request to write %ld bytes to file pos %ld\n", want, lock->pos));
427 /* if this is the first write, make a note as we'll have to store the
428 * first cluster in the directory entry later */
429 if (lock->ioh.first_cluster == 0xffffffff)
430 update_entry = TRUE;
432 if ((err = WriteFileChunk(&(lock->ioh), lock->pos, want, data, written)) == 0) {
433 lock->pos += *written;
434 if (lock->pos > lock->gl->size) {
435 lock->gl->size = lock->pos;
436 update_entry = TRUE;
439 D(bug("[fat] wrote %ld bytes, new file pos is %ld, size is %ld\n", *written, lock->pos, lock->gl->size));
441 if (update_entry) {
442 D(bug("[fat] updating dir entry, first cluster is %ld, size is %ld\n", lock->ioh.first_cluster, lock->gl->size));
444 lock->gl->first_cluster = lock->ioh.first_cluster;
446 InitDirHandle(lock->ioh.sb, lock->gl->dir_cluster, &dh);
447 GetDirEntry(&dh, lock->gl->dir_entry, &de);
449 de.e.entry.file_size = lock->gl->size;
450 de.e.entry.first_cluster_lo = lock->gl->first_cluster & 0xffff;
451 de.e.entry.first_cluster_hi = lock->gl->first_cluster >> 16;
453 UpdateDirEntry(&de);
455 ReleaseDirHandle(&dh);
459 return err;