diff options
Diffstat (limited to 'ftpfs/ccache.c')
-rw-r--r-- | ftpfs/ccache.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/ftpfs/ccache.c b/ftpfs/ccache.c new file mode 100644 index 00000000..c18f7577 --- /dev/null +++ b/ftpfs/ccache.c @@ -0,0 +1,285 @@ +/* Remote file contents caching + + Copyright (C) 1997 Free Software Foundation, Inc. + Written by Miles Bader <miles@gnu.ai.mit.edu> + This file is part of the GNU Hurd. + + The GNU Hurd 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, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +#include <unistd.h> +#include <string.h> + +#include <hurd/netfs.h> + +#include "ccache.h" + +#define READ_CHUNK_SIZE (8*1024) +#define ALLOC_CHUNK_SIZE (64*1024) + +/* Read LEN bytes at OFFS in the file referred to by CC into DATA, or return + an error. */ +error_t +ccache_read (struct ccache *cc, off_t offs, size_t len, void *data) +{ + error_t err = 0; + size_t max = offs + len; + + mutex_lock (&cc->lock); + + if (max > cc->size) + max = cc->size; + + while (cc->max < max && !err) + { + if (cc->fetching_active) + /* Some thread is fetching data, so just let it do its thing, but get + a wakeup call when it's done. */ + { + if (hurd_condition_wait (&cc->wakeup, &cc->lock)) + err = EINTR; + } + else + { + int re_connected = 0; + struct netnode *nn = cc->node->nn; + + cc->fetching_active = 1; + + while (cc->max < max && !err) + { + mutex_unlock (&cc->lock); + + if (! cc->conn) + /* We need to setup a connection to fetch data over. */ + { + err = ftpfs_get_ftp_conn (nn->fs, &cc->conn); + if (! err) + { + err = ftp_conn_start_retrieve (cc->conn, nn->rmt_path, + &cc->data_conn); + if (err == ENOENT) + err = ESTALE; + if (err) + { + ftpfs_release_ftp_conn (nn->fs, cc->conn); + cc->conn = 0; + } + else + cc->data_conn_pos = 0; + } + re_connected = 1; + } + + if (! err) + /* Try and read some data over the connection. */ + { + size_t new_end = cc->max + READ_CHUNK_SIZE; + + if (new_end > cc->size) + new_end = cc->size; + + if (new_end > cc->alloced) + /* Make some room in memory for the new part of the + image. */ + { + size_t alloc_end = cc->alloced + ALLOC_CHUNK_SIZE; + + if (alloc_end < new_end) + alloc_end = new_end; + else if (alloc_end > cc->size) + alloc_end = cc->size; + + if (cc->alloced == 0) + { + vm_address_t addr = 0; + err = vm_allocate (mach_task_self (), + &addr, alloc_end, 1); + if (! err) + cc->image = (char *)addr; + } + else + { + vm_address_t addr = + (vm_address_t)cc->image + cc->alloced; + err = vm_allocate (mach_task_self (), + &addr, alloc_end - cc->alloced, + 0); + if (err == EKERN_NO_SPACE) + /* Gack. We've goota move the whole splooge. */ + { + addr = 0; + err = vm_allocate (mach_task_self (), + &addr, alloc_end, 1); + if (! err) + /* That worked; copy what's already-fetched. */ + { + bcopy (cc->image, (void *)addr, cc->max); + vm_deallocate (mach_task_self (), + (vm_address_t)cc->image, + cc->alloced); + cc->image = (char *)addr; + } + } + } + if (! err) + cc->alloced = alloc_end; + } + + if (! err) + { + ssize_t rd = + read (cc->data_conn, + cc->image + cc->data_conn_pos, + new_end - cc->data_conn_pos); + if (rd < 0) + err = errno; + else if (rd == 0) + /* EOF. This either means the file changed size, or + our data-connection got closed; we just try to + open the connection a second time, and then if + that fails, assume the size changed. */ + { + if (re_connected) + err = EIO; /* Something's fucked */ + else + /* Try opening the connection again. */ + { + close (cc->data_conn); + ftp_conn_finish_transfer (cc->conn); + ftpfs_release_ftp_conn (nn->fs, cc->conn); + cc->conn = 0; + } + } + else + { + cc->max += rd; + cc->data_conn_pos += rd; + } + } + } + + mutex_lock (&cc->lock); + + if (cc->max < max && !err) + /* If anyone's waiting for data, let them look (if we're done + fetching, this gets delayed until below). */ + condition_broadcast (&cc->wakeup); + } + + if (!err && cc->conn && cc->max == cc->size) + /* We're finished reading all data, close the data connection. */ + { + close (cc->data_conn); + ftp_conn_finish_transfer (cc->conn); + ftpfs_release_ftp_conn (nn->fs, cc->conn); + cc->conn = 0; + } + + /* We're done, error or no. */ + cc->fetching_active = 0; + + /* Let others know something's going on. */ + condition_broadcast (&cc->wakeup); + } + } + + if (! err) + bcopy (cc->image + offs, data, max - offs); + + mutex_unlock (&cc->lock); + + return err; +} + +/* Discard any cached contents in CC. */ +error_t +ccache_invalidate (struct ccache *cc) +{ + error_t err = 0; + + mutex_lock (&cc->lock); + + while (cc->fetching_active && !err) + /* Some thread is fetching data, so just let it do its thing, but get + a wakeup call when it's done. */ + { + if (hurd_condition_wait (&cc->wakeup, &cc->lock)) + err = EINTR; + } + + if (! err) + { + if (cc->alloced > 0) + { + vm_deallocate (mach_task_self (), + (vm_address_t)cc->image, cc->alloced); + cc->image = 0; + cc->alloced = 0; + cc->max = 0; + } + if (cc->conn) + { + close (cc->data_conn); + ftp_conn_finish_transfer (cc->conn); + ftpfs_release_ftp_conn (cc->node->nn->fs, cc->conn); + cc->conn = 0; + } + } + + mutex_unlock (&cc->lock); + + return err; +} + +/* Return a ccache object for NODE in CC. */ +error_t +ccache_create (struct node *node, struct ccache **cc) +{ + struct ccache *new = malloc (sizeof (struct ccache)); + + if (! new) + return ENOMEM; + + new->node = node; + new->image = 0; + new->size = node->nn_stat.st_size; + new->max = 0; + new->alloced = 0; + mutex_init (&new->lock); + condition_init (&new->wakeup); + new->fetching_active = 0; + new->conn = 0; + new->data_conn = -1; + + *cc = new; + + return 0; +} + +/* Free all resources used by CC. */ +void +ccache_free (struct ccache *cc) +{ + if (cc->alloced > 0) + vm_deallocate (mach_task_self (), (vm_address_t)cc->image, cc->alloced); + if (cc->data_conn >= 0) + close (cc->data_conn); + if (cc->conn) + { + ftp_conn_finish_transfer (cc->conn); + ftpfs_release_ftp_conn (cc->node->nn->fs, cc->conn); + } + free (cc); +} |