/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2014 by Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "config.h" #include "system.h" #include #include "debug.h" #include "file.h" #include "dir.h" #include "disk_cache.h" #include "fileobj_mgr.h" #include "rb_namespace.h" /** * Manages file and directory streams on all volumes * * Intended for internal use by disk, file and directory code */ /* there will always be enough of these for all user handles, thus most of these functions don't return failure codes */ #define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS) /* describes the file as an image on the storage medium */ static struct fileobj_binding { struct file_base_binding bind; /* base info list item (first!) */ uint16_t flags; /* F(D)(O)_* bits of this file/dir */ uint16_t writers; /* number of writer streams */ struct filestr_cache cache; /* write mode shared cache */ file_size_t size; /* size of this file */ struct ll_head list; /* open streams for this file/dir */ } fobindings[MAX_FILEOBJS]; static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR; static struct ll_head free_bindings; static struct ll_head busy_bindings[NUM_VOLUMES]; #define BUSY_BINDINGS(volume) \ (&busy_bindings[IF_MV_VOL(volume)]) #define BASEBINDING_LIST(bindp) \ (BUSY_BINDINGS(BASEBINDING_VOL(bindp))) #define FREE_BINDINGS() \ (&free_bindings) #define BINDING_FIRST(type, volume...) \ ((struct fileobj_binding *)type##_BINDINGS(volume)->head) #define BINDING_NEXT(fobp) \ ((struct fileobj_binding *)(fobp)->bind.node.next) #define FOR_EACH_BINDING(volume, fobp) \ for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \ fobp; fobp = BINDING_NEXT(fobp)) #define STREAM_FIRST(fobp) \ ((struct filestr_base *)(fobp)->list.head) #define STREAM_NEXT(s) \ ((struct filestr_base *)(s)->node.next) #define STREAM_THIS(s) \ (s) #define FOR_EACH_STREAM(what, start, s) \ for (struct filestr_base *s = STREAM_##what(start); \ s; s = STREAM_NEXT(s)) /* once a file/directory, always a file/directory; such a change is a bug */ #define CHECK_FO_DIRECTORY(callflags, fobp) \ if (((callflags) ^ (fobp)->flags) & FO_DIRECTORY) \ { \ DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n", \ __func__, (fobp), (callflags)); \ } /* syncs information for the stream's old and new parent directory if any are currently opened */ static void fileobj_sync_parent(const struct file_base_info *infop[], int count) { FOR_EACH_BINDING(infop[0]->volume, fobp) { if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY) continue; /* not directory or removed can't be parent of anything */ struct filestr_base *parentstrp = STREAM_FIRST(fobp); if (!parentstrp) continue; struct fat_file *parentfilep = &parentstrp->infop->fatfile; for (int i = 0; i < count; i++) { if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile)) continue; /* discard scan/read caches' parent dir info */ FOR_EACH_STREAM(THIS, parentstrp, s) filestr_discard_cache(s); } } } /* see if this file has open streams and return that fileobj_binding if so, else grab a new one from the free list; returns true if this is new */ static bool binding_assign(const struct file_base_info *srcinfop, struct fileobj_binding **fobpp) { FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp) { if (fobp->flags & FO_REMOVED) continue; if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile)) { /* already has open streams/mounts*/ *fobpp = fobp; return false; } } /* not found - allocate anew */ *fobpp = BINDING_FIRST(FREE); ll_remove_first(FREE_BINDINGS()); ll_init(&(*fobpp)->list); return true; } /* mark descriptor as unused and return to the free list */ static void binding_add_to_free_list(struct fileobj_binding *fobp) { fobp->flags = 0; ll_insert_last(FREE_BINDINGS(), &fobp->bind.node); } static void bind_source_info(const struct file_base_info *srcinfop, struct fileobj_binding **fobpp) { if (!binding_assign(srcinfop, fobpp)) return; /* already in use */ /* is new */ (*fobpp)->bind.info = *srcinfop; fileobj_bind_file(&(*fobpp)->bind); } static void release_binding(struct fileobj_binding *fobp) { fileobj_unbind_file(&fobp->bind); binding_add_to_free_list(fobp); } /** File and directory internal interface **/ void file_binding_insert_last(struct file_base_binding *bindp) { ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node); } void file_binding_remove(struct file_base_binding *bindp) { ll_remove(BASEBINDING_LIST(bindp), &bindp->node); } #ifdef HAVE_DIRCACHE void file_binding_insert_first(struct file_base_binding *bindp) { ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node); } void file_binding_remove_next(struct file_base_binding *prevp, struct file_base_binding *bindp) { ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node); (void)bindp; } #endif /* HAVE_DIRCACHE */ /* mounts a file object as a target from elsewhere */ bool fileobj_mount(const struct file_base_info *srcinfop, unsigned int callflags, struct file_base_binding **bindpp) { struct fileobj_binding *fobp; bind_source_info(srcinfop, &fobp); CHECK_FO_DIRECTORY(callflags, fobp); if (fobp->flags & FO_MOUNTTARGET) return false; /* already mounted */ fobp->flags |= FDO_BUSY | FO_MOUNTTARGET | (callflags & FO_DIRECTORY); *bindpp = &fobp->bind; return true; } /* unmounts the file object and frees it if now unusued */ void fileobj_unmount(struct file_base_binding *bindp) { struct fileobj_binding *fobp = (struct fileobj_binding *)bindp; if (!(fobp->flags & FO_MOUNTTARGET)) return; /* not mounted */ if (STREAM_FIRST(fobp) == NULL) release_binding(fobp); /* no longer in use */ else fobp->flags &= ~FO_MOUNTTARGET; } /* opens the file object for a new stream and sets up the caches; * the stream must already be opened at the FS driver level and *stream * initialized. * * NOTE: switches stream->infop to the one kept in common for all streams of * the same file, making a copy for only the first stream */ void fileobj_fileop_open(struct filestr_base *stream, const struct file_base_info *srcinfop, unsigned int callflags) { /* assign base file information */ struct fileobj_binding *fobp; bind_source_info(srcinfop, &fobp); unsigned int foflags = fobp->flags; /* add stream to this file's list */ bool first = STREAM_FIRST(fobp) == NULL; ll_insert_last(&fobp->list, &stream->node); /* initiate the new stream into the enclave */ stream->flags = FDO_BUSY | (callflags & (FF_MASK|FD_WRITE|FD_WRONLY|FD_APPEND)); stream->infop = &fobp->bind.info; stream->fatstr.fatfilep = &fobp->bind.info.fatfile; stream->bindp = &fobp->bind; stream->mtx = &stream_mutexes[fobp - fobindings]; if (first) { /* first stream for file */ fobp->flags = foflags | FDO_BUSY | FO_SINGLE | (callflags & (FO_DIRECTORY|FO_TRUNC)); fobp->writers = 0; fobp->size = 0; } else { /* additional stream for file */ fobp->flags = (foflags & ~FO_SINGLE) | (callflags & FO_TRUNC); CHECK_FO_DIRECTORY(callflags, fobp); } if ((callflags & FD_WRITE) && ++fobp->writers == 1) { /* first writer */ file_cache_init(&fobp->cache); FOR_EACH_STREAM(FIRST, fobp, s) filestr_assign_cache(s, &fobp->cache); } else if (fobp->writers) { /* already writers present */ filestr_assign_cache(stream, &fobp->cache); } else { /* another reader and no writers present */ file_cache_reset(stream->cachep); } } /* close the stream and free associated resources */ void fileobj_fileop_close(struct filestr_base *stream) { if (!(stream->flags & FDO_BUSY)) { /* not added to manager or forced-closed by unmounting */ filestr_base_destroy(stream); return; } struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; unsigned int foflags = fobp->flags; ll_remove(&fobp->list, &stream->node); if (foflags & FO_SINGLE) { /* last stream for file; close everything */ if (fobp->writers) file_cache_free(&fobp->cache); /* binding must stay valid if something is mounted to here */ if (foflags & FO_MOUNTTARGET) fobp->flags = foflags & (FDO_BUSY|FO_DIRECTORY|FO_MOUNTTARGET); else release_binding(fobp); } else { if ((stream->flags & FD_WRITE) && --fobp->writers == 0) { /* only readers remain; switch back to stream-local caching */ FOR_EACH_STREAM(FIRST, fobp, s) filestr_copy_cache(s, &fobp->cache); file_cache_free(&fobp->cache); } if (fobp->list.head == fobp->list.tail) fobp->flags |= FO_SINGLE; /* only one open stream remaining */ } filestr_base_destroy(stream); } /* informs manager that file has been created */ void fileobj_fileop_create(struct filestr_base *stream, const struct file_base_info *srcinfop, unsigned int callflags) { fileobj_fileop_open(stream, srcinfop, callflags); fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); } /* informs manager that file has been removed */ void fileobj_fileop_remove(struct filestr_base *stream, const struct file_base_info *oldinfop) { ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED; fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1); } /* informs manager that file has been renamed */ void fileobj_fileop_rename(struct filestr_base *stream, const struct file_base_info *oldinfop) { /* if there is old info then this was a move and the old parent has to be informed */ int count = oldinfop ? 2 : 1; fileobj_sync_parent(&(const struct file_base_info *[]) { oldinfop, stream->infop }[2 - count], count); } /* informs manager than directory entries have been updated */ void fileobj_fileop_sync(struct filestr_base *stream) { if (((struct fileobj_binding *)stream->bindp)->flags & FO_REMOVED) return; /* no dir to sync */ fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1); } /* query for the pointer to the size storage for the file object */ file_size_t * fileobj_get_sizep(const struct filestr_base *stream) { if (!stream->bindp) return NULL; return &((struct fileobj_binding *)stream->bindp)->size; } /* iterate the list of streams for this stream's file */ struct filestr_base * fileobj_get_next_stream(const struct filestr_base *stream, const struct filestr_base *s) { if (!stream->bindp) return NULL; return s ? STREAM_NEXT(s) : STREAM_FIRST((struct fileobj_binding *)stream->bindp); } /* query manager bitflags for the file object */ unsigned int fileobj_get_flags(const struct filestr_base *stream) { if (!stream->bindp) return 0; return ((struct fileobj_binding *)stream->bindp)->flags; } /* change manager bitflags for the file object (permitted only) */ void fileobj_change_flags(struct filestr_base *stream, unsigned int flags, unsigned int mask) { struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp; if (!fobp) return; mask &= FDO_CHG_MASK; fobp->flags = (fobp->flags & ~mask) | (flags & mask); } /* mark all open streams on a device as "nonexistant" */ void fileobj_mgr_unmount(IF_MV_NONVOID(int volume)) { FOR_EACH_VOLUME(volume, v) { struct fileobj_binding *fobp; while ((fobp = BINDING_FIRST(BUSY, v))) { struct filestr_base *s; while ((s = STREAM_FIRST(fobp))) { /* last ditch effort to preserve FS integrity; we could still be alive (soft unmount, maybe); we get informed early */ fileop_onunmount_internal(s); if (STREAM_FIRST(fobp) == s) fileop_onclose_internal(s); /* above didn't close it */ /* keep it "busy" to avoid races; any valid file/directory descriptor returned by an open call should always be closed by whoever opened it (of course!) */ s->flags = (s->flags & ~FDO_BUSY) | FD_NONEXIST; } } } } /* one-time init at startup */ void fileobj_mgr_init(void) { for (unsigned int i = 0; i < NUM_VOLUMES; i++) ll_init(BUSY_BINDINGS(i)); ll_init(FREE_BINDINGS()); for (unsigned int i = 0; i < MAX_FILEOBJS; i++) { mutex_init(&stream_mutexes[i]); binding_add_to_free_list(&fobindings[i]); } }