1
0
Fork 0
forked from len0rd/rockbox
foxbox/firmware/common/dircache.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

1308 lines
33 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2005 by Miika Pekkarinen
*
* 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.
*
****************************************************************************/
/* TODO:
- Allow cache live updating while transparent rebuild is running.
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "debug.h"
#include "system.h"
#include "logf.h"
#include "dircache.h"
#include "thread.h"
#include "kernel.h"
#include "usb.h"
#include "file.h"
#include "buffer.h"
#include "dir.h"
#if CONFIG_RTC
#include "time.h"
#include "timefuncs.h"
#endif
/* Queue commands. */
#define DIRCACHE_BUILD 1
#define DIRCACHE_STOP 2
#define MAX_OPEN_DIRS 8
static DIR_CACHED opendirs[MAX_OPEN_DIRS];
static struct dircache_entry *fd_bindings[MAX_OPEN_FILES];
static struct dircache_entry *dircache_root;
#ifdef HAVE_MULTIVOLUME
static struct dircache_entry *append_position;
#endif
static bool dircache_initialized = false;
static bool dircache_initializing = false;
static bool thread_enabled = false;
static unsigned long allocated_size = DIRCACHE_LIMIT;
static unsigned long dircache_size = 0;
static unsigned long entry_count = 0;
static unsigned long reserve_used = 0;
static unsigned int cache_build_ticks = 0;
static unsigned long appflags = 0;
static char dircache_cur_path[MAX_PATH*2];
static struct event_queue dircache_queue;
static long dircache_stack[(DEFAULT_STACK_SIZE + 0x900)/sizeof(long)];
static const char dircache_thread_name[] = "dircache";
static struct fdbind_queue fdbind_cache[MAX_PENDING_BINDINGS];
static int fdbind_idx = 0;
/* --- Internal cache structure control functions --- */
/**
* Internal function to allocate a new dircache_entry from memory.
*/
static struct dircache_entry* allocate_entry(void)
{
struct dircache_entry *next_entry;
if (dircache_size > allocated_size - MAX_PATH*2)
{
logf("size limit reached");
return NULL;
}
next_entry = (struct dircache_entry *)((char *)dircache_root+dircache_size);
#ifdef ROCKBOX_STRICT_ALIGN
/* Make sure the entry is long aligned. */
if ((long)next_entry & 0x03)
{
dircache_size += 4 - ((long)next_entry & 0x03);
next_entry = (struct dircache_entry *)(((long)next_entry & ~0x03) + 0x04);
}
#endif
next_entry->name_len = 0;
next_entry->d_name = NULL;
next_entry->up = NULL;
next_entry->down = NULL;
next_entry->next = NULL;
dircache_size += sizeof(struct dircache_entry);
return next_entry;
}
/**
* Internal function to allocate a dircache_entry and set
* ->next entry pointers.
*/
static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce)
{
struct dircache_entry *next_entry;
if ( (next_entry = allocate_entry()) == NULL)
return NULL;
next_entry->up = ce->up;
ce->next = next_entry;
return next_entry;
}
/*
* Internal function to allocate a dircache_entry and set
* ->down entry pointers.
*/
static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce)
{
struct dircache_entry *next_entry;
if ( (next_entry = allocate_entry()) == NULL)
return NULL;
next_entry->up = ce;
ce->down = next_entry;
return next_entry;
}
/* This will eat ~30 KiB of memory!
* We should probably use that as additional reserve buffer in future. */
#define MAX_SCAN_DEPTH 16
static struct travel_data dir_recursion[MAX_SCAN_DEPTH];
/**
* Returns true if there is an event waiting in the queue
* that requires the current operation to be aborted.
*/
static bool check_event_queue(void)
{
struct queue_event ev;
queue_wait_w_tmo(&dircache_queue, &ev, 0);
switch (ev.id)
{
case DIRCACHE_STOP:
case SYS_USB_CONNECTED:
#ifdef HAVE_HOTSWAP
case SYS_FS_CHANGED:
#endif
/* Put the event back into the queue. */
queue_post(&dircache_queue, ev.id, ev.data);
return true;
}
return false;
}
/**
* Internal function to iterate a path.
*/
static int dircache_scan(IF_MV2(int volume,) struct travel_data *td)
{
#ifdef SIMULATOR
while ( ( td->entry = readdir_uncached(td->dir) ) )
#else
while ( (fat_getnext(td->dir, &td->entry) >= 0) && (td->entry.name[0]))
#endif
{
#ifdef SIMULATOR
if (!strcmp(".", td->entry->d_name) ||
!strcmp("..", td->entry->d_name))
{
continue;
}
td->ce->attribute = td->entry->attribute;
td->ce->name_len = strlen(td->entry->d_name) + 1;
td->ce->d_name = ((char *)dircache_root+dircache_size);
td->ce->size = td->entry->size;
td->ce->wrtdate = td->entry->wrtdate;
td->ce->wrttime = td->entry->wrttime;
memcpy(td->ce->d_name, td->entry->d_name, td->ce->name_len);
#else
if (!strcmp(".", td->entry.name) ||
!strcmp("..", td->entry.name))
{
continue;
}
td->ce->attribute = td->entry.attr;
td->ce->name_len = strlen(td->entry.name) + 1;
td->ce->d_name = ((char *)dircache_root+dircache_size);
td->ce->startcluster = td->entry.firstcluster;
td->ce->size = td->entry.filesize;
td->ce->wrtdate = td->entry.wrtdate;
td->ce->wrttime = td->entry.wrttime;
memcpy(td->ce->d_name, td->entry.name, td->ce->name_len);
#endif
dircache_size += td->ce->name_len;
entry_count++;
#ifdef SIMULATOR
if (td->entry->attribute & ATTR_DIRECTORY)
#else
if (td->entry.attr & FAT_ATTR_DIRECTORY)
#endif
{
td->down_entry = dircache_gen_down(td->ce);
if (td->down_entry == NULL)
return -2;
td->pathpos = strlen(dircache_cur_path);
strncpy(&dircache_cur_path[td->pathpos], "/",
sizeof(dircache_cur_path) - td->pathpos - 1);
#ifdef SIMULATOR
strncpy(&dircache_cur_path[td->pathpos+1], td->entry->d_name,
sizeof(dircache_cur_path) - td->pathpos - 2);
td->newdir = opendir_uncached(dircache_cur_path);
if (td->newdir == NULL)
{
logf("Failed to opendir_uncached(): %s", dircache_cur_path);
return -3;
}
#else
strncpy(&dircache_cur_path[td->pathpos+1], td->entry.name,
sizeof(dircache_cur_path) - td->pathpos - 2);
td->newdir = *td->dir;
if (fat_opendir(IF_MV2(volume,) &td->newdir,
td->entry.firstcluster, td->dir) < 0 )
{
return -3;
}
#endif
td->ce = dircache_gen_next(td->ce);
if (td->ce == NULL)
return -4;
return 1;
}
td->ce->down = NULL;
td->ce = dircache_gen_next(td->ce);
if (td->ce == NULL)
return -5;
/* When simulator is used, it's only safe to yield here. */
if (thread_enabled)
{
/* Stop if we got an external signal. */
if (check_event_queue())
return -6;
yield();
}
}
return 0;
}
/**
* Recursively scan the hard disk and build the cache.
*/
#ifdef SIMULATOR
static int dircache_travel(IF_MV2(int volume,) DIR_UNCACHED *dir, struct dircache_entry *ce)
#else
static int dircache_travel(IF_MV2(int volume,) struct fat_dir *dir, struct dircache_entry *ce)
#endif
{
int depth = 0;
int result;
memset(ce, 0, sizeof(struct dircache_entry));
#if defined(HAVE_MULTIVOLUME) && !defined(SIMULATOR)
if (volume > 0)
{
ce->d_name = ((char *)dircache_root+dircache_size);
snprintf(ce->d_name, VOL_ENUM_POS + 3, VOL_NAMES, volume);
ce->name_len = VOL_ENUM_POS + 3;
dircache_size += ce->name_len;
ce->attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME;
ce->size = 0;
append_position = dircache_gen_next(ce);
ce = dircache_gen_down(ce);
}
#endif
dir_recursion[0].dir = dir;
dir_recursion[0].ce = ce;
dir_recursion[0].first = ce;
do {
//logf("=> %s", dircache_cur_path);
result = dircache_scan(IF_MV2(volume,) &dir_recursion[depth]);
switch (result) {
case 0: /* Leaving the current directory. */
/* Add the standard . and .. entries. */
ce = dir_recursion[depth].ce;
ce->d_name = ".";
ce->name_len = 2;
#ifdef SIMULATOR
closedir_uncached(dir_recursion[depth].dir);
ce->attribute = ATTR_DIRECTORY;
#else
ce->attribute = FAT_ATTR_DIRECTORY;
ce->startcluster = dir_recursion[depth].dir->file.firstcluster;
#endif
ce->size = 0;
ce->down = dir_recursion[depth].first;
depth--;
if (depth < 0)
break ;
dircache_cur_path[dir_recursion[depth].pathpos] = '\0';
ce = dircache_gen_next(ce);
if (ce == NULL)
{
logf("memory allocation error");
return -3;
}
#ifdef SIMULATOR
ce->attribute = ATTR_DIRECTORY;
#else
ce->attribute = FAT_ATTR_DIRECTORY;
ce->startcluster = dir_recursion[depth].dir->file.firstcluster;
#endif
ce->d_name = "..";
ce->name_len = 3;
ce->size = 0;
ce->down = dir_recursion[depth].first;
break ;
case 1: /* Going down in the directory tree. */
depth++;
if (depth >= MAX_SCAN_DEPTH)
{
logf("Too deep directory structure");
return -2;
}
#ifdef SIMULATOR
dir_recursion[depth].dir = dir_recursion[depth-1].newdir;
#else
dir_recursion[depth].dir = &dir_recursion[depth-1].newdir;
#endif
dir_recursion[depth].first = dir_recursion[depth-1].down_entry;
dir_recursion[depth].ce = dir_recursion[depth-1].down_entry;
break ;
default:
logf("Scan failed");
logf("-> %s", dircache_cur_path);
return -1;
}
} while (depth >= 0) ;
return 0;
}
/**
* Internal function to get a pointer to dircache_entry for a given filename.
* path: Absolute path to a file or directory.
* get_before: Returns the cache pointer before the last valid entry found.
* only_directories: Match only filenames which are a directory type.
*/
static struct dircache_entry* dircache_get_entry(const char *path,
bool get_before, bool only_directories)
{
struct dircache_entry *cache_entry, *before;
char namecopy[MAX_PATH*2];
char* part;
char* end;
strncpy(namecopy, path, sizeof(namecopy) - 1);
cache_entry = dircache_root;
before = NULL;
for ( part = strtok_r(namecopy, "/", &end); part;
part = strtok_r(NULL, "/", &end)) {
/* scan dir for name */
while (1)
{
if (cache_entry == NULL)
{
return NULL;
}
else if (cache_entry->name_len == 0)
{
cache_entry = cache_entry->next;
continue ;
}
if (!strcasecmp(part, cache_entry->d_name))
{
before = cache_entry;
if (cache_entry->down || only_directories)
cache_entry = cache_entry->down;
break ;
}
cache_entry = cache_entry->next;
}
}
if (get_before)
cache_entry = before;
return cache_entry;
}
#ifdef HAVE_EEPROM_SETTINGS
/**
* Function to load the internal cache structure from disk to initialize
* the dircache really fast and little disk access.
*/
int dircache_load(void)
{
struct dircache_maindata maindata;
int bytes_read;
int fd;
if (dircache_initialized)
return -1;
logf("Loading directory cache");
dircache_size = 0;
fd = open(DIRCACHE_FILE, O_RDONLY);
if (fd < 0)
return -2;
bytes_read = read(fd, &maindata, sizeof(struct dircache_maindata));
if (bytes_read != sizeof(struct dircache_maindata)
|| maindata.size <= 0)
{
logf("Dircache file header error");
close(fd);
remove(DIRCACHE_FILE);
return -3;
}
dircache_root = buffer_alloc(0);
if ((long)maindata.root_entry != (long)dircache_root)
{
logf("Position missmatch");
close(fd);
remove(DIRCACHE_FILE);
return -4;
}
dircache_root = buffer_alloc(maindata.size + DIRCACHE_RESERVE);
entry_count = maindata.entry_count;
appflags = maindata.appflags;
bytes_read = read(fd, dircache_root, MIN(DIRCACHE_LIMIT, maindata.size));
close(fd);
remove(DIRCACHE_FILE);
if (bytes_read != maindata.size)
{
logf("Dircache read failed");
return -6;
}
/* Cache successfully loaded. */
dircache_size = maindata.size;
allocated_size = dircache_size + DIRCACHE_RESERVE;
reserve_used = 0;
logf("Done, %ld KiB used", dircache_size / 1024);
dircache_initialized = true;
memset(fd_bindings, 0, sizeof(fd_bindings));
return 0;
}
/**
* Function to save the internal cache stucture to disk for fast loading
* on boot.
*/
int dircache_save(void)
{
struct dircache_maindata maindata;
int fd;
unsigned long bytes_written;
remove(DIRCACHE_FILE);
if (!dircache_initialized)
return -1;
logf("Saving directory cache");
fd = open(DIRCACHE_FILE, O_WRONLY | O_CREAT | O_TRUNC);
maindata.magic = DIRCACHE_MAGIC;
maindata.size = dircache_size;
maindata.root_entry = dircache_root;
maindata.entry_count = entry_count;
maindata.appflags = appflags;
/* Save the info structure */
bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata));
if (bytes_written != sizeof(struct dircache_maindata))
{
close(fd);
logf("dircache: write failed #1");
return -2;
}
/* Dump whole directory cache to disk */
bytes_written = write(fd, dircache_root, dircache_size);
close(fd);
if (bytes_written != dircache_size)
{
logf("dircache: write failed #2");
return -3;
}
return 0;
}
#endif /* #if 0 */
/**
* Internal function which scans the disk and creates the dircache structure.
*/
static int dircache_do_rebuild(void)
{
#ifdef SIMULATOR
DIR_UNCACHED *pdir;
#else
struct fat_dir dir, *pdir;
#endif
unsigned int start_tick;
int i;
/* Measure how long it takes build the cache. */
start_tick = current_tick;
dircache_initializing = true;
appflags = 0;
entry_count = 0;
memset(dircache_cur_path, 0, sizeof(dircache_cur_path));
dircache_size = sizeof(struct dircache_entry);
#ifdef HAVE_MULTIVOLUME
append_position = dircache_root;
for (i = NUM_VOLUMES; i >= 0; i--)
{
if (fat_ismounted(i))
{
#endif
#ifdef SIMULATOR
pdir = opendir_uncached("/");
if (pdir == NULL)
{
logf("Failed to open rootdir");
dircache_initializing = false;
return -3;
}
#else
#ifdef HAVE_MULTIVOLUME
if ( fat_opendir(IF_MV2(i,) &dir, 0, NULL) < 0 ) {
#else
if ( fat_opendir(IF_MV2(0,) &dir, 0, NULL) < 0 ) {
#endif /* HAVE_MULTIVOLUME */
logf("Failed opening root dir");
dircache_initializing = false;
return -3;
}
pdir = &dir;
#endif
cpu_boost(true);
#ifdef HAVE_MULTIVOLUME
if (dircache_travel(IF_MV2(i,) pdir, append_position) < 0)
#else
if (dircache_travel(IF_MV2(0,) pdir, dircache_root) < 0)
#endif /* HAVE_MULTIVOLUME */
{
logf("dircache_travel failed");
cpu_boost(false);
dircache_size = 0;
dircache_initializing = false;
return -2;
}
cpu_boost(false);
#ifdef HAVE_MULTIVOLUME
}
}
#endif
logf("Done, %ld KiB used", dircache_size / 1024);
dircache_initialized = true;
dircache_initializing = false;
cache_build_ticks = current_tick - start_tick;
/* Initialized fd bindings. */
memset(fd_bindings, 0, sizeof(fd_bindings));
for (i = 0; i < fdbind_idx; i++)
dircache_bind(fdbind_cache[i].fd, fdbind_cache[i].path);
fdbind_idx = 0;
if (thread_enabled)
{
if (allocated_size - dircache_size < DIRCACHE_RESERVE)
reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size);
}
else
{
/* We have to long align the audiobuf to keep the buffer access fast. */
audiobuf += (long)((dircache_size & ~0x03) + 0x04);
audiobuf += DIRCACHE_RESERVE;
allocated_size = dircache_size + DIRCACHE_RESERVE;
reserve_used = 0;
}
return 1;
}
/**
* Internal thread that controls transparent cache building.
*/
static void dircache_thread(void)
{
struct queue_event ev;
while (1)
{
queue_wait(&dircache_queue, &ev);
switch (ev.id)
{
#ifdef HAVE_HOTSWAP
case SYS_FS_CHANGED:
if (!dircache_initialized)
break;
dircache_initialized = false;
#endif
case DIRCACHE_BUILD:
thread_enabled = true;
dircache_do_rebuild();
thread_enabled = false;
break ;
case DIRCACHE_STOP:
logf("Stopped the rebuilding.");
dircache_initialized = false;
break ;
#ifndef SIMULATOR
case SYS_USB_CONNECTED:
usb_acknowledge(SYS_USB_CONNECTED_ACK);
usb_wait_for_disconnect(&dircache_queue);
break ;
#endif
}
}
}
/**
* Start scanning the disk to build the dircache.
* Either transparent or non-transparent build method is used.
*/
int dircache_build(int last_size)
{
if (dircache_initialized || thread_enabled)
return -3;
logf("Building directory cache");
remove(DIRCACHE_FILE);
/* Background build, dircache has been previously allocated */
if (dircache_size > 0)
{
thread_enabled = true;
dircache_initializing = true;
queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
return 2;
}
if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT )
{
allocated_size = last_size + DIRCACHE_RESERVE;
dircache_root = buffer_alloc(allocated_size);
thread_enabled = true;
/* Start a transparent rebuild. */
queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
return 3;
}
dircache_root = (struct dircache_entry *)(((long)audiobuf & ~0x03) + 0x04);
/* Start a non-transparent rebuild. */
return dircache_do_rebuild();
}
/**
* Steal the allocated dircache buffer and disable dircache.
*/
void* dircache_steal_buffer(long *size)
{
dircache_disable();
if (dircache_size == 0)
{
*size = 0;
return NULL;
}
*size = dircache_size + (DIRCACHE_RESERVE-reserve_used);
return dircache_root;
}
/**
* Main initialization function that must be called before any other
* operations within the dircache.
*/
void dircache_init(void)
{
int i;
dircache_initialized = false;
dircache_initializing = false;
memset(opendirs, 0, sizeof(opendirs));
for (i = 0; i < MAX_OPEN_DIRS; i++)
{
opendirs[i].secondary_entry.d_name = buffer_alloc(MAX_PATH);
}
queue_init(&dircache_queue, true);
create_thread(dircache_thread, dircache_stack,
sizeof(dircache_stack), 0, dircache_thread_name
IF_PRIO(, PRIORITY_BACKGROUND)
IF_COP(, CPU));
}
/**
* Returns true if dircache has been initialized and is ready to be used.
*/
bool dircache_is_enabled(void)
{
return dircache_initialized;
}
/**
* Returns true if dircache is being initialized.
*/
bool dircache_is_initializing(void)
{
return dircache_initializing || thread_enabled;
}
/**
* Set application flags used to determine if dircache is still intact.
*/
void dircache_set_appflag(long mask)
{
appflags |= mask;
}
/**
* Get application flags used to determine if dircache is still intact.
*/
bool dircache_get_appflag(long mask)
{
return dircache_is_enabled() && (appflags & mask);
}
/**
* Returns the current number of entries (directories and files) in the cache.
*/
int dircache_get_entry_count(void)
{
return entry_count;
}
/**
* Returns the allocated space for dircache (without reserve space).
*/
int dircache_get_cache_size(void)
{
return dircache_is_enabled() ? dircache_size : 0;
}
/**
* Returns how many bytes of the reserve allocation for live cache
* updates have been used.
*/
int dircache_get_reserve_used(void)
{
return dircache_is_enabled() ? reserve_used : 0;
}
/**
* Returns the time in kernel ticks that took to build the cache.
*/
int dircache_get_build_ticks(void)
{
return dircache_is_enabled() ? cache_build_ticks : 0;
}
/**
* Disables the dircache. Usually called on shutdown or when
* accepting a usb connection.
*/
void dircache_disable(void)
{
int i;
bool cache_in_use;
if (thread_enabled)
queue_post(&dircache_queue, DIRCACHE_STOP, 0);
while (thread_enabled)
sleep(1);
dircache_initialized = false;
logf("Waiting for cached dirs to release");
do {
cache_in_use = false;
for (i = 0; i < MAX_OPEN_DIRS; i++) {
if (!opendirs[i].regulardir && opendirs[i].busy)
{
cache_in_use = true;
sleep(1);
break ;
}
}
} while (cache_in_use) ;
logf("Cache released");
entry_count = 0;
}
/**
* Usermode function to return dircache_entry pointer to the given path.
*/
const struct dircache_entry *dircache_get_entry_ptr(const char *filename)
{
if (!dircache_initialized || filename == NULL)
return NULL;
return dircache_get_entry(filename, false, false);
}
/**
* Function to copy the full absolute path from dircache to the given buffer
* using the given dircache_entry pointer.
*/
void dircache_copy_path(const struct dircache_entry *entry, char *buf, int size)
{
const struct dircache_entry *down[MAX_SCAN_DEPTH];
int depth = 0;
if (size <= 0)
return ;
buf[0] = '\0';
if (entry == NULL)
return ;
do {
down[depth] = entry;
entry = entry->up;
depth++;
} while (entry != NULL && depth < MAX_SCAN_DEPTH);
while (--depth >= 0)
{
snprintf(buf, size, "/%s", down[depth]->d_name);
buf += down[depth]->name_len; /* '/' + d_name */
size -= down[depth]->name_len;
if (size <= 0)
break ;
}
}
/* --- Directory cache live updating functions --- */
static int block_until_ready(void)
{
/* Block until dircache has been built. */
while (!dircache_initialized && dircache_is_initializing())
sleep(1);
if (!dircache_initialized)
return -1;
return 0;
}
static struct dircache_entry* dircache_new_entry(const char *path, int attribute)
{
struct dircache_entry *entry;
char basedir[MAX_PATH*2];
char *new;
long last_cache_size = dircache_size;
strncpy(basedir, path, sizeof(basedir)-1);
new = strrchr(basedir, '/');
if (new == NULL)
{
logf("error occurred");
dircache_initialized = false;
return NULL;
}
*new = '\0';
new++;
entry = dircache_get_entry(basedir, false, true);
if (entry == NULL)
{
logf("basedir not found!");
logf("%s", basedir);
dircache_initialized = false;
return NULL;
}
if (reserve_used + 2*sizeof(struct dircache_entry) + strlen(new)+1
>= DIRCACHE_RESERVE)
{
logf("not enough space");
dircache_initialized = false;
return NULL;
}
while (entry->next != NULL)
entry = entry->next;
if (entry->name_len > 0)
entry = dircache_gen_next(entry);
if (entry == NULL)
{
dircache_initialized = false;
return NULL;
}
entry->attribute = attribute;
entry->name_len = MIN(254, strlen(new)) + 1;
entry->d_name = ((char *)dircache_root+dircache_size);
entry->startcluster = 0;
entry->wrtdate = 0;
entry->wrttime = 0;
entry->size = 0;
memcpy(entry->d_name, new, entry->name_len);
dircache_size += entry->name_len;
if (attribute & ATTR_DIRECTORY)
{
logf("gen_down");
dircache_gen_down(entry);
}
reserve_used += dircache_size - last_cache_size;
return entry;
}
void dircache_bind(int fd, const char *path)
{
struct dircache_entry *entry;
/* Queue requests until dircache has been built. */
if (!dircache_initialized && dircache_is_initializing())
{
if (fdbind_idx >= MAX_PENDING_BINDINGS)
return ;
strncpy(fdbind_cache[fdbind_idx].path, path,
sizeof(fdbind_cache[fdbind_idx].path)-1);
fdbind_cache[fdbind_idx].fd = fd;
fdbind_idx++;
return ;
}
if (!dircache_initialized)
return ;
logf("bind: %d/%s", fd, path);
entry = dircache_get_entry(path, false, false);
if (entry == NULL)
{
logf("not found!");
dircache_initialized = false;
return ;
}
fd_bindings[fd] = entry;
}
void dircache_update_filesize(int fd, long newsize, long startcluster)
{
if (!dircache_initialized || fd < 0)
return ;
if (fd_bindings[fd] == NULL)
{
logf("dircache fd access error");
dircache_initialized = false;
return ;
}
fd_bindings[fd]->size = newsize;
fd_bindings[fd]->startcluster = startcluster;
}
void dircache_update_filetime(int fd)
{
#if CONFIG_RTC == 0
(void)fd;
#else
short year;
struct tm *now = get_time();
if (!dircache_initialized || fd < 0)
return ;
if (fd_bindings[fd] == NULL)
{
logf("dircache fd access error");
dircache_initialized = false;
return ;
}
year = now->tm_year+1900-1980;
fd_bindings[fd]->wrtdate = (((year)&0x7f)<<9) |
(((now->tm_mon+1)&0xf)<<5) |
(((now->tm_mday)&0x1f));
fd_bindings[fd]->wrttime = (((now->tm_hour)&0x1f)<<11) |
(((now->tm_min)&0x3f)<<5) |
(((now->tm_sec/2)&0x1f));
#endif
}
void dircache_mkdir(const char *path)
{ /* Test ok. */
if (block_until_ready())
return ;
logf("mkdir: %s", path);
dircache_new_entry(path, ATTR_DIRECTORY);
}
void dircache_rmdir(const char *path)
{ /* Test ok. */
struct dircache_entry *entry;
if (block_until_ready())
return ;
logf("rmdir: %s", path);
entry = dircache_get_entry(path, true, true);
if (entry == NULL)
{
logf("not found!");
dircache_initialized = false;
return ;
}
entry->down = NULL;
entry->name_len = 0;
}
/* Remove a file from cache */
void dircache_remove(const char *name)
{ /* Test ok. */
struct dircache_entry *entry;
if (block_until_ready())
return ;
logf("remove: %s", name);
entry = dircache_get_entry(name, false, false);
if (entry == NULL)
{
logf("not found!");
dircache_initialized = false;
return ;
}
entry->name_len = 0;
}
void dircache_rename(const char *oldpath, const char *newpath)
{ /* Test ok. */
struct dircache_entry *entry, *newentry;
struct dircache_entry oldentry;
char absolute_path[MAX_PATH*2];
char *p;
if (block_until_ready())
return ;
logf("rename: %s->%s", oldpath, newpath);
entry = dircache_get_entry(oldpath, true, false);
if (entry == NULL)
{
logf("not found!");
dircache_initialized = false;
return ;
}
/* Delete the old entry. */
entry->name_len = 0;
/** If we rename the same filename twice in a row, we need to
* save the data, because the entry will be re-used. */
oldentry = *entry;
/* Generate the absolute path for destination if necessary. */
if (newpath[0] != '/')
{
strncpy(absolute_path, oldpath, sizeof(absolute_path)-1);
p = strrchr(absolute_path, '/');
if (!p)
{
logf("Invalid path");
dircache_initialized = false;
return ;
}
*p = '\0';
strncpy(p, absolute_path, sizeof(absolute_path)-1-strlen(p));
newpath = absolute_path;
}
newentry = dircache_new_entry(newpath, entry->attribute);
if (newentry == NULL)
{
dircache_initialized = false;
return ;
}
newentry->down = oldentry.down;
newentry->size = oldentry.size;
newentry->startcluster = oldentry.startcluster;
newentry->wrttime = oldentry.wrttime;
newentry->wrtdate = oldentry.wrtdate;
}
void dircache_add_file(const char *path, long startcluster)
{
struct dircache_entry *entry;
if (block_until_ready())
return ;
logf("add file: %s", path);
entry = dircache_new_entry(path, 0);
if (entry == NULL)
return ;
entry->startcluster = startcluster;
}
DIR_CACHED* opendir_cached(const char* name)
{
struct dircache_entry *cache_entry;
int dd;
DIR_CACHED* pdir = opendirs;
if ( name[0] != '/' )
{
DEBUGF("Only absolute paths supported right now\n");
return NULL;
}
/* find a free dir descriptor */
for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
if ( !pdir->busy )
break;
if ( dd == MAX_OPEN_DIRS )
{
DEBUGF("Too many dirs open\n");
errno = EMFILE;
return NULL;
}
if (!dircache_initialized)
{
pdir->regulardir = opendir_uncached(name);
if (!pdir->regulardir)
return NULL;
pdir->busy = true;
return pdir;
}
pdir->busy = true;
pdir->regulardir = NULL;
cache_entry = dircache_get_entry(name, false, true);
pdir->entry = cache_entry;
if (cache_entry == NULL)
{
pdir->busy = false;
return NULL;
}
return pdir;
}
struct dircache_entry* readdir_cached(DIR_CACHED* dir)
{
struct dirent_uncached *regentry;
struct dircache_entry *ce;
if (!dir->busy)
return NULL;
if (dir->regulardir != NULL)
{
regentry = readdir_uncached(dir->regulardir);
if (regentry == NULL)
return NULL;
strncpy(dir->secondary_entry.d_name, regentry->d_name, MAX_PATH-1);
dir->secondary_entry.size = regentry->size;
dir->secondary_entry.startcluster = regentry->startcluster;
dir->secondary_entry.attribute = regentry->attribute;
dir->secondary_entry.wrttime = regentry->wrttime;
dir->secondary_entry.wrtdate = regentry->wrtdate;
dir->secondary_entry.next = NULL;
return &dir->secondary_entry;
}
do {
if (dir->entry == NULL)
return NULL;
ce = dir->entry;
if (ce->name_len == 0)
dir->entry = ce->next;
} while (ce->name_len == 0) ;
dir->entry = ce->next;
strncpy(dir->secondary_entry.d_name, ce->d_name, MAX_PATH-1);
/* Can't do `dir->secondary_entry = *ce`
because that modifies the d_name pointer. */
dir->secondary_entry.size = ce->size;
dir->secondary_entry.startcluster = ce->startcluster;
dir->secondary_entry.attribute = ce->attribute;
dir->secondary_entry.wrttime = ce->wrttime;
dir->secondary_entry.wrtdate = ce->wrtdate;
dir->secondary_entry.next = NULL;
dir->internal_entry = ce;
//logf("-> %s", ce->name);
return &dir->secondary_entry;
}
int closedir_cached(DIR_CACHED* dir)
{
if (!dir->busy)
return -1;
dir->busy=false;
if (dir->regulardir != NULL)
return closedir_uncached(dir->regulardir);
return 0;
}
int mkdir_cached(const char *name)
{
int rc=mkdir_uncached(name);
if (rc >= 0)
dircache_mkdir(name);
return(rc);
}
int rmdir_cached(const char* name)
{
int rc=rmdir_uncached(name);
if(rc>=0)
dircache_rmdir(name);
return(rc);
}