diff options
Diffstat (limited to 'ftpfs')
-rw-r--r-- | ftpfs/ChangeLog | 172 | ||||
-rw-r--r-- | ftpfs/Makefile | 30 | ||||
-rw-r--r-- | ftpfs/ccache.c | 293 | ||||
-rw-r--r-- | ftpfs/ccache.h | 73 | ||||
-rw-r--r-- | ftpfs/conn.c | 105 | ||||
-rw-r--r-- | ftpfs/dir.c | 859 | ||||
-rw-r--r-- | ftpfs/fs.c | 82 | ||||
-rw-r--r-- | ftpfs/ftpfs.c | 397 | ||||
-rw-r--r-- | ftpfs/ftpfs.h | 249 | ||||
-rw-r--r-- | ftpfs/host.c | 154 | ||||
-rw-r--r-- | ftpfs/ncache.c | 87 | ||||
-rw-r--r-- | ftpfs/netfs.c | 462 | ||||
-rw-r--r-- | ftpfs/node.c | 106 |
13 files changed, 3069 insertions, 0 deletions
diff --git a/ftpfs/ChangeLog b/ftpfs/ChangeLog new file mode 100644 index 00000000..99527918 --- /dev/null +++ b/ftpfs/ChangeLog @@ -0,0 +1,172 @@ +1999-07-10 Roland McGrath <roland@baalperazim.frob.com> + + * netfs.c: Add #include <sys/mman.h> for munmap decl. + * ccache.c: Likewise. + +1999-07-09 Thomas Bushnell, BSG <tb@mit.edu> + + * ccache.c (ccache_read): Use mmap instead of vm_allocate. + * netfs.c (get_dirents): Likewise. + +1999-07-03 Thomas Bushnell, BSG <tb@mit.edu> + + * ccache.c (ccache_read): Use munmap instead of vm_deallocate. + (ccache_invalidate): Likewise. + (ccache_free): Likewise. + * netfs.c (get_dirents): Likewise. + +Sun Jan 31 18:33:55 1999 Thomas Bushnell, BSG <tb@mit.edu> + + * netfs.c (netfs_attempt_utimes): Implement new possibility that + ATIME or MTIME might be null. + +1998-10-20 Roland McGrath <roland@baalperazim.frob.com> + + * ftpfs.c (netfs_append_args): Add braces to silence gcc warning. + * netfs.c (get_dirents): Likewise. + (netfs_get_dirents): Likewise. + * dir.c (ftpfs_refresh_node): Likewise. + (ftpfs_dir_lookup): Likewise. + +1998-09-04 Roland McGrath <roland@baalperazim.frob.com> + + * ftpfs.c (netfs_append_args): Use %ld for time_t and %Zu for size_t. + +1997-09-09 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (parse_runtime_opt): New function. + (runtime_argp): Use it. + (netfs_append_args): Fix printed name of --node-cache-size. + + * dir.c (delete): Immediately free entries without nodes. + +1997-08-29 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (parse_startup_opt): Report an error if no filesystem is + supplied. + +1997-08-27 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (parse_common_opt): Turn on debugging when there *aren't* + any errors... + +1997-08-22 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (parse_common_opt): Release DEBUG_LOCK even if we get an + error. + +1997-08-19 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (netfs_maxsymlinks): Initialize to 12. + + * ccache.c (ccache_read): Always update CC->max to something + reasonable after a transfer. + +1997-08-18 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (debug_stream, debug_stream_name): New variables. + (cntl_debug): Print to DEBUG_STREAM. + (debug_lock): New variable (was local to cntl_debug). + (parse_common_opt): Accept a FILE argument to --debug. + (netfs_append_args): Print FILE argument to --debug. + +1997-08-15 Miles Bader <miles@gnu.ai.mit.edu> + + * netfs.c (netfs_attempt_mkfile): Unlock DIR. + + * dir.c (ftpfs_dir_null_lookup): New function. + (ftpfs_dir_lookup): Handle "" lookups like ".". + * ftpfs.h (ftpfs_dir_null_lookup): New declaration. + * fs.c (ftpfs_create): Use ftpfs_dir_null_lookup instead of + ftpfs_dir_lookup. + + * dir.c (ftpfs_dir_lookup): Set E's name timestamp for noent entries. + (ftpfs_refresh_node): Record ENOENT entries. + (refresh_dir): Add PRESERVE_ENTRY parameter; all callers changed. + +1997-08-14 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (ftpfs_ftp_hooks): Use ports_self_interrupted to check + for interrupts. + * ccache.c (ccache_read): Likewise. + +1997-08-12 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (main): Supply the FSID argument to ftpfs_create. + * dir.c (update_entry): Set E->stat.st_fsid & st_fstype. + (ftpfs_next_inode): Variable removed. + * ftpfs.h (struct ftpfs ): Add FSID & NEXT_INODE fields. + (ftpfs_create): Add FSID parameter. + * fs.c (ftpfs_create): Add FSID parameter. Initialize FSID & + NEXT_INODE fields. + +1997-08-11 Miles Bader <miles@gnu.ai.mit.edu> + + * dir.c (sweep): Don't delete entries that have the NOENT flag set. + (refresh_dir): Ensure there are entries for `.' and `..'. + +1997-08-09 Miles Bader <miles@gnu.ai.mit.edu> + + * dir.c (struct new_entry_state): DIR_PFX & DIR_PFX_LEN fields removed. + (update_new_entry): Don't futz with NAME anymore. + + * ftpfs.c (ftpfs_hooks): Initialize INTERRUPT_CHECK Field. + * ccache.c (ccache_read): Check for thread cancelation. + +1997-08-08 Miles Bader <miles@gnu.ai.mit.edu> + + * dir.c (update_ordered_entry): Correctly handle a non-empty list. + Only insert E in the ordered entry list if it's not already there. + (refresh_dir): Don't clear the ordered entry list. + Only reset bulk stat detection state if actually doing a bulk stat. + Initialize DFS.prev_entry_next_p instead of DFS.prev_entry. + (struct dir_fetch_state): Replace PREV_ENTRY by PREV_ENTRY_NEXT_P. + (lookup): Grow the hash table when it gets large. + (rehash): Zero the new hash table. + + * dir.c (path_append): Macro removed. + (ftpfs_dir_lookup, ftpfs_refresh_node): Use ftp_conn_append_name + instead of path_append. + Don't set dir_pfx fields in RES anymore. + (update_old_entry): Don't futz with NAME anymore. + (struct refresh_entry_state): DIR_PFX & DIR_PFX_LEN fields removed. + +1997-08-07 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpfs.c (DEFAULT_NAME_TIMEOUT): New macro. + (DEFAULT_DIR_TIMEOUT, DEFAULT_DIRENT_TIMEOUT): Macros removed. + (OPT_NAME_TIMEOUT): New macro. + (OPT_DIR_TIMEOUT, OPT_DIRENT_TIMEOUT): Macros removed. + (common_options, parse_common_opt, netfs_append_args, main): + Replace --dir-timeout & --dirent-timeout by --name-timeout. + Add --bulk-stat-period and --bulk-stat-threshold options. + (netfs_append_args): Output --debug. + + * ftpfs.h (struct ftpfs_params): Remove dirent_timeout and + dir_timeout fields. Add name_timeout field. + * dir.c (ftpfs_dir_lookup): dirent_timeout parameter renamed to + name_timeout and dirent_timestamp field renamed to name_timestamp. + (lookup): Initialize timestamps. + * ftpfs.h (struct ftpfs_dir): timestamp renamed to stat_timestamp. + Add name_timestamp field. + (struct ftpfs_dir_entry): dirent_timestamp renamed to name_timestamp. + + * dir.c (update_ordered_name): New function. + (refresh_dir): Use ftp_conn_get_names instead of huge wodge of code. + Update new timestamps. + +1997-08-06 Miles Bader <miles@gnu.ai.mit.edu> + + * dir.c (update_entry): Allow ST to be zero. Don't leak memory + when updating symlink info. Change return type to void. Remove + NO_LOCK parameter. + (update_ordered_entry, update_old_entry, update_new_entry): Update + calls to update_entry. + (refresh_dir): Add UPDATE_STATS parameter, and only fetch names if + it's zero. + (ftpfs_dir_refresh, ftpfs_refresh_node, ftpfs_dir_lookup): Supply + UPDATE_STATS argument to refresh_dir. + + * host.c (split_server_name): Enable password parsing. + + * dir.c (lookup): Initialize the NODE field. diff --git a/ftpfs/Makefile b/ftpfs/Makefile new file mode 100644 index 00000000..944bdd0a --- /dev/null +++ b/ftpfs/Makefile @@ -0,0 +1,30 @@ +# Makefile for ftpfs +# +# Copyright (C) 1997 Free Software Foundation, Inc. +# +# 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, or (at +# your option) any later version. +# +# This program 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +dir := ftpfs +makemode := server + +target = ftpfs + +SRCS = ftpfs.c fs.c host.c netfs.c dir.c conn.c ccache.c node.c ncache.c +LCLHDRS = ftpfs.h ccache.h + +OBJS = $(SRCS:.c=.o) +HURDLIBS = netfs fshelp iohelp ports threads ihash ftpconn shouldbeinlibc + +include ../Makeconf diff --git a/ftpfs/ccache.c b/ftpfs/ccache.c new file mode 100644 index 00000000..56f3bf1e --- /dev/null +++ b/ftpfs/ccache.c @@ -0,0 +1,293 @@ +/* Remote file contents caching + + Copyright (C) 1997, 1999 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 <sys/mman.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; + addr = (vm_address_t) mmap (0, alloc_end, + PROT_READ|PROT_WRITE, + MAP_ANON, 0, 0); + err = (addr == -1) ? errno : 0; + if (! err) + cc->image = (char *)addr; + } + else + { + vm_address_t addr = + (vm_address_t)cc->image + cc->alloced; + /* XXX. This can't be replaced with mmap until we + have MAP_EXCL. -tb */ + 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; + addr = (vm_address_t) mmap (0, alloc_end, + PROT_READ|PROT_WRITE, + MAP_ANON, 0, 0); + err = (addr == -1) ? errno : 0; + if (! err) + /* That worked; copy what's already-fetched. */ + { + bcopy (cc->image, (void *)addr, cc->max); + munmap (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->data_conn_pos += rd; + if (cc->data_conn_pos > cc->max) + cc->max = cc->data_conn_pos; + } + } + + if (!err && ports_self_interrupted ()) + err = EINTR; + } + + 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) + { + munmap (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) + munmap (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); +} diff --git a/ftpfs/ccache.h b/ftpfs/ccache.h new file mode 100644 index 00000000..410720c3 --- /dev/null +++ b/ftpfs/ccache.h @@ -0,0 +1,73 @@ +/* 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. */ + +#ifndef __CCACHE_H__ +#define __CCACHE_H__ + +#include "ftpfs.h" + +struct ccache +{ + /* The filesystem node this is a cache of. */ + struct node *node; + + /* In memory file image, alloced using vm_allocate. */ + char *image; + + /* Size of data. */ + off_t size; + + /* Upper bounds of fetched image. */ + off_t max; + + /* Amount of IMAGE that has been allocated. */ + size_t alloced; + + struct mutex lock; + + /* People can wait for a reading thread on this condition. */ + struct condition wakeup; + + /* True if some thread is now fetching data. Only that thread should + modify the DATA_CONN, DATA_CONN_POS, and MAX fields. */ + int fetching_active; + + /* Ftp connection over which data is being fetched, or 0. */ + struct ftp_conn *conn; + /* File descriptor over which data is being fetched. */ + int data_conn; + /* Where DATA_CONN points in the file. */ + off_t data_conn_pos; +}; + +/* 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); + +/* Discard any cached contents in CC. */ +error_t ccache_invalidate (struct ccache *cc); + +/* Return a ccache object for NODE in CC. */ +error_t ccache_create (struct node *node, struct ccache **cc); + +/* Free all resources used by CC. */ +void ccache_free (struct ccache *cc); + +#endif /* __CCACHE_H__ */ diff --git a/ftpfs/conn.c b/ftpfs/conn.c new file mode 100644 index 00000000..930e2eb7 --- /dev/null +++ b/ftpfs/conn.c @@ -0,0 +1,105 @@ +/* Ftp connection management + + 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 <assert.h> + +#include "ftpfs.h" + +/* A particular connection. */ +struct ftpfs_conn +{ + struct ftp_conn *conn; + struct ftpfs_conn *next; +}; + +/* For debugging purposes, give each connection a unique integer id. */ +static unsigned conn_id = 0; + +/* Get an ftp connection to use for an operation. */ +error_t +ftpfs_get_ftp_conn (struct ftpfs *fs, struct ftp_conn **conn) +{ + struct ftpfs_conn *fsc; + + spin_lock (&fs->conn_lock); + fsc = fs->free_conns; + if (fsc) + fs->free_conns = fsc->next; + spin_unlock (&fs->conn_lock); + + if (! fsc) + { + error_t err; + + fsc = malloc (sizeof (struct ftpfs_conn)); + if (! fsc) + return ENOMEM; + + err = ftp_conn_create (fs->ftp_params, fs->ftp_hooks, &fsc->conn); + + if (! err) + { + /* Set connection type to binary. */ + err = ftp_conn_set_type (fsc->conn, "I"); + if (err) + ftp_conn_free (fsc->conn); + } + + if (err) + { + free (fsc); + return err; + } + + /* For debugging purposes, give each connection a unique integer id. */ + fsc->conn->hook = (void *)conn_id++; + } + + spin_lock (&fs->conn_lock); + fsc->next = fs->conns; + fs->conns = fsc; + spin_unlock (&fs->conn_lock); + + *conn = fsc->conn; + + return 0; +} + +/* Return CONN to the pool of free connections in FS. */ +void +ftpfs_release_ftp_conn (struct ftpfs *fs, struct ftp_conn *conn) +{ + struct ftpfs_conn *fsc, *pfsc; + + spin_lock (&fs->conn_lock); + for (pfsc = 0, fsc = fs->conns; fsc; pfsc = fsc, fsc = fsc->next) + if (fsc->conn == conn) + { + if (pfsc) + pfsc->next = fsc->next; + else + fs->conns = fsc->next; + fsc->next = fs->free_conns; + fs->free_conns = fsc; + break; + } + assert (fsc); + spin_unlock (&fs->conn_lock); +} diff --git a/ftpfs/dir.c b/ftpfs/dir.c new file mode 100644 index 00000000..f083848a --- /dev/null +++ b/ftpfs/dir.c @@ -0,0 +1,859 @@ +/* Directory operations + + Copyright (C) 1997, 1998 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 "ftpfs.h" +#include "ccache.h" + +/* Free the directory entry E and all resources it consumes. */ +void +free_entry (struct ftpfs_dir_entry *e) +{ + assert (! e->self_p); /* We should only free deleted nodes. */ + free (e->name); + if (e->symlink_target) + free (e->symlink_target); + free (e); +} + +/* Put the directory entry E into the hash table HTABLE, of length HTABLE_LEN. */ +static void +insert (struct ftpfs_dir_entry *e, + struct ftpfs_dir_entry **htable, size_t htable_len) +{ + struct ftpfs_dir_entry **t = &htable[e->hv % htable_len]; + if (*t) + (*t)->self_p = &e->next; + e->next = *t; + e->self_p = t; + *t = e; +} + +/* Replace DIR's hashtable with a new one of length NEW_LEN, retaining all + existing entries. */ +static error_t +rehash (struct ftpfs_dir *dir, size_t new_len) +{ + int i; + size_t old_len = dir->htable_len; + struct ftpfs_dir_entry **old_htable = dir->htable; + struct ftpfs_dir_entry **new_htable = + malloc (new_len * sizeof (struct ftpfs_dir_entry *)); + + if (! new_htable) + return ENOMEM; + + bzero (new_htable, new_len * sizeof (struct ftpfs_dir_entry *)); + + for (i = 0; i < old_len; i++) + while (old_htable[i]) + { + struct ftpfs_dir_entry *e = old_htable[i]; + + /* Remove E from the old table (don't bother to fixup + e->next->self_p). */ + old_htable[i] = e->next; + + insert (e, new_htable, new_len); + } + + free (old_htable); + + dir->htable = new_htable; + dir->htable_len = new_len; + + return 0; +} + +/* Calculate NAME's hash value. */ +static size_t +hash (const char *name) +{ + size_t hv = 0; + while (*name) + hv = ((hv << 5) + *name++) & 0xFFFFFF; + return hv; +} + +/* Lookup NAME in DIR and return its entry. If there is no such entry, and + ADD is true, then a new entry is allocated and returned, otherwise 0 is + returned (if ADD is true then 0 can be returned if a memory allocation + error occurs). */ +struct ftpfs_dir_entry * +lookup (struct ftpfs_dir *dir, const char *name, int add) +{ + size_t hv = hash (name); + struct ftpfs_dir_entry *h = dir->htable[hv % dir->htable_len], *e = h; + + while (e && strcmp (name, e->name) != 0) + e = e->next; + + if (!e && add) + { + if (dir->num_entries > dir->htable_len) + /* Grow the hash table. */ + if (rehash (dir, (dir->htable_len + 1) * 2 - 1) != 0) + return 0; + + e = malloc (sizeof *e); + if (e) + { + e->hv = hv; + e->name = strdup (name); + e->node = 0; + e->dir = dir; + e->stat_timestamp = 0; + bzero (&e->stat, sizeof e->stat); + e->symlink_target = 0; + e->noent = 0; + e->valid = 0; + e->name_timestamp = e->stat_timestamp = 0; + e->ordered_next = 0; + e->ordered_self_p = 0; + e->next = 0; + e->self_p = 0; + insert (e, dir->htable, dir->htable_len); + dir->num_entries++; + } + } + + return e; +} + +/* Remove E from its position in the ordered_next chain. */ +static void +ordered_unlink (struct ftpfs_dir_entry *e) +{ + if (e->ordered_self_p) + *e->ordered_self_p = e->ordered_next; + if (e->ordered_next) + e->ordered_next->self_p = e->ordered_self_p; +} + +/* Delete E from its directory, freeing any resources it holds. */ +static void +delete (struct ftpfs_dir_entry *e, struct ftpfs_dir *dir) +{ + dir->num_entries--; + + /* Take out of the hash chain. */ + if (e->self_p) + *e->self_p = e->next; + if (e->next) + e->next->self_p = e->self_p; + + /* This indicates a deleted entry. */ + e->self_p = 0; + e->next = 0; + + /* Take out of the directory ordered list. */ + ordered_unlink (e); + + /* If there's a node attached, we'll delete the entry whenever it goes + away, otherwise, just delete it now. */ + if (! e->node) + free_entry (e); +} + +/* Clear the valid bit in all DIR's htable. */ +static void +mark (struct ftpfs_dir *dir) +{ + size_t len = dir->htable_len, i; + struct ftpfs_dir_entry **htable = dir->htable, *e; + + for (i = 0; i < len; i++) + for (e = htable[i]; e; e = e->next) + e->valid = 0; +} + +/* Delete any entries in DIR which don't have their valid bit set. */ +static void +sweep (struct ftpfs_dir *dir) +{ + size_t len = dir->htable_len, i; + struct ftpfs_dir_entry **htable = dir->htable, *e; + + for (i = 0; i < len; i++) + for (e = htable[i]; e; e = e->next) + if (!e->valid && !e->noent) + delete (e, dir); +} + +/* Update the directory entry for NAME to reflect ST and SYMLINK_TARGET. + True is returned if successful, or false if there was a memory allocation + error. TIMESTAMP is used to record the time of this update. */ +static void +update_entry (struct ftpfs_dir_entry *e, const struct stat *st, + const char *symlink_target, time_t timestamp) +{ + ino_t ino; + struct ftpfs *fs = e->dir->fs; + + if (e->stat.st_ino) + ino = e->stat.st_ino; + else + ino = fs->next_inode++; + + e->name_timestamp = timestamp; + + if (st) + /* The ST and SYMLINK_TARGET parameters are only valid if ST isn't 0. */ + { + e->stat = *st; + e->stat_timestamp = timestamp; + + if (!e->symlink_target || !symlink_target + || strcmp (e->symlink_target, symlink_target) != 0) + { + if (e->symlink_target) + free (e->symlink_target); + e->symlink_target = symlink_target ? strdup (symlink_target) : 0; + } + } + + /* The st_ino field is always valid. */ + e->stat.st_ino = ino; + e->stat.st_fsid = fs->fsid; + e->stat.st_fstype = FSTYPE_FTP; +} + +/* Add the timestamp TIMESTAMP to the set used to detect bulk stats, and + return true if there have been enough individual stats recently to call + for just refetching the whole directory. */ +static int +need_bulk_stat (time_t timestamp, struct ftpfs_dir *dir) +{ + time_t period = dir->fs->params.bulk_stat_period; + unsigned threshold = dir->fs->params.bulk_stat_threshold; + + if (timestamp > dir->bulk_stat_base_stamp + period * 3) + /* No stats done in a while, just start over. */ + { + dir->bulk_stat_count_first_half = 1; + dir->bulk_stat_count_second_half = 0; + dir->bulk_stat_base_stamp = (timestamp / period) * period; + } + else if (timestamp > dir->bulk_stat_base_stamp + period * 2) + /* Start a new period, but keep the second half of the old one. */ + { + dir->bulk_stat_count_first_half = dir->bulk_stat_count_second_half; + dir->bulk_stat_count_second_half = 1; + dir->bulk_stat_base_stamp += period; + } + else if (timestamp > dir->bulk_stat_base_stamp + period) + dir->bulk_stat_count_second_half++; + else + dir->bulk_stat_count_first_half++; + + return + (dir->bulk_stat_count_first_half + dir->bulk_stat_count_second_half) + > threshold; +} + +static void +reset_bulk_stat_info (struct ftpfs_dir *dir) +{ + dir->bulk_stat_count_first_half = 0; + dir->bulk_stat_count_second_half = 0; + dir->bulk_stat_base_stamp = 0; +} + +/* State shared between ftpfs_dir_refresh and update_ordered_entry. */ +struct dir_fetch_state +{ + struct ftpfs_dir *dir; + time_t timestamp; + + /* A pointer to the NEXT-field of the previously seen entry, or a pointer + to the ORDERED field in the directory if this is the first. */ + struct ftpfs_dir_entry **prev_entry_next_p; +}; + +/* Update the directory entry for NAME to reflect ST and SYMLINK_TARGET, also + rearranging the entries to reflect the order in which they are sent from + the server, and setting their valid bits so that obsolete entries can be + deleted. HOOK points to the state from ftpfs_dir_fetch. */ +static error_t +update_ordered_entry (const char *name, const struct stat *st, + const char *symlink_target, void *hook) +{ + struct dir_fetch_state *dfs = hook; + struct ftpfs_dir_entry *e = lookup (dfs->dir, name, 1); + + if (! e) + return ENOMEM; + + update_entry (e, st, symlink_target, dfs->timestamp); + e->valid = 1; + + if (! e->ordered_self_p) + /* Position E in the ordered chain following the previously seen entry. */ + { + /* The PREV_ENTRY_NEXT_P field holds a pointer to the NEXT-field of the + previous entry, or a pointer to the ORDERED field in the directory. */ + e->ordered_self_p = dfs->prev_entry_next_p; + + if (*e->ordered_self_p) + /* Update the self_p pointer of the previous successor. */ + (*e->ordered_self_p)->ordered_self_p = &e->ordered_next; + + /* E comes before the previous successor. */ + e->ordered_next = *e->ordered_self_p; + + *e->ordered_self_p = e; /* Put E there. */ + } + + /* Put the next entry after this one. */ + dfs->prev_entry_next_p = &e->ordered_next; + + return 0; +} + +/* Update the directory entry for NAME, rearranging the entries to reflect + the order in which they are sent from the server, and setting their valid + bits so that obsolete entries can be deleted. HOOK points to the state + from ftpfs_dir_fetch. */ +static error_t +update_ordered_name (const char *name, void *hook) +{ + /* We just do the same thing as for stats, but without the stat info. */ + return update_ordered_entry (name, 0, 0, hook); +} + +/* Refresh DIR from the directory DIR_NAME in the filesystem FS. If + UPDATE_STATS is true, then directory stat information will also be + updated. If PRESERVE_ENTRY is non-0, that entry won't be deleted if it's + not in the directory after the refresh, but instead will have its NOENT + flag turned on. */ +static error_t +refresh_dir (struct ftpfs_dir *dir, int update_stats, time_t timestamp, + struct ftpfs_dir_entry *preserve_entry) +{ + error_t err; + struct ftp_conn *conn; + struct dir_fetch_state dfs; + + if ((update_stats + ? dir->stat_timestamp + dir->fs->params.stat_timeout + : dir->name_timestamp + dir->fs->params.name_timeout) + >= timestamp) + /* We've already refreshed this directory recently. */ + return 0; + + err = ftpfs_get_ftp_conn (dir->fs, &conn); + if (err) + return err; + + /* Mark directory entries so we can GC them later using sweep. */ + mark (dir); + + if (update_stats) + /* We're doing a bulk stat now, so don't do another for a while. */ + reset_bulk_stat_info (dir); + + /* Info passed to update_ordered_entry. */ + dfs.dir = dir; + dfs.timestamp = timestamp; + dfs.prev_entry_next_p = &dir->ordered; + + /* Make sure `.' and `..' are always included (if the actual list also + includes `.' and `..', the ordered may be rearranged). */ + err = update_ordered_name (".", &dfs); + if (! err) + err = update_ordered_name ("..", &dfs); + + /* Refetch the directory from the server. */ + if (update_stats) + /* Fetch both names and stat info. */ + err = ftp_conn_get_stats (conn, dir->rmt_path, 1, + update_ordered_entry, &dfs); + else + /* Just fetch names. */ + err = ftp_conn_get_names (conn, dir->rmt_path, update_ordered_name, &dfs); + + if (! err) + /* GC any directory entries that weren't seen this time. */ + { + dir->name_timestamp = timestamp; + if (update_stats) + dir->stat_timestamp = timestamp; + if (preserve_entry && !preserve_entry->valid) + { + preserve_entry->noent = 1; + preserve_entry->name_timestamp = timestamp; + } + sweep (dir); + } + + ftpfs_release_ftp_conn (dir->fs, conn); + + return err; +} + +/* Refresh DIR. */ +error_t +ftpfs_dir_refresh (struct ftpfs_dir *dir) +{ + time_t timestamp = NOW; + return refresh_dir (dir, 0, timestamp, 0); +} + +/* State shared between ftpfs_dir_entry_refresh and update_old_entry. */ +struct refresh_entry_state +{ + struct ftpfs_dir_entry *entry; + time_t timestamp; +}; + +/* Update the directory entry for NAME to reflect ST and SYMLINK_TARGET. + HOOK points to the state from ftpfs_dir_fetch_entry. */ +static error_t +update_old_entry (const char *name, const struct stat *st, + const char *symlink_target, void *hook) +{ + struct refresh_entry_state *res = hook; + + if (strcmp (name, res->entry->name) != 0) + return EGRATUITOUS; + + update_entry (res->entry, st, symlink_target, res->timestamp); + + return 0; +} + +/* Refresh stat information for NODE. This may actually refresh the whole + directory if that is deemed desirable. NODE should be locked. */ +error_t +ftpfs_refresh_node (struct node *node) +{ + struct netnode *nn = node->nn; + struct ftpfs_dir_entry *entry = nn->dir_entry; + + if (! entry) + /* This is a deleted node, don't attempt to do anything. */ + return 0; + else + { + error_t err = 0; + time_t timestamp = NOW; + struct ftpfs_dir *dir = entry->dir; + + mutex_lock (&dir->node->lock); + + if (! entry->self_p) + /* This is a deleted entry, just awaiting disposal; do so. */ + { + nn->dir_entry = 0; + free_entry (entry); + return 0; + } + else if ((entry->name_timestamp + dir->fs->params.name_timeout + >= timestamp) + && entry->noent) + err = ENOENT; + else if (entry->stat_timestamp + dir->fs->params.stat_timeout < timestamp) + { + /* Stat information needs updating. */ + if (need_bulk_stat (timestamp, dir)) + /* Refetch the whole directory from the server. */ + { + err = refresh_dir (entry->dir, 1, timestamp, entry); + if (!err && entry->noent) + err = ENOENT; + } + else + { + struct ftp_conn *conn; + + err = ftpfs_get_ftp_conn (dir->fs, &conn); + + if (! err) + { + char *rmt_path; + + err = ftp_conn_append_name (conn, dir->rmt_path, entry->name, + &rmt_path); + if (! err) + { + struct refresh_entry_state res; + + res.entry = entry; + res.timestamp = timestamp; + + if (! err) + err = ftp_conn_get_stats (conn, rmt_path, 0, + update_old_entry, &res); + + free (rmt_path); + } + + ftpfs_release_ftp_conn (dir->fs, conn); + } + + if (err == ENOENT) + { + entry->noent = 1; /* A negative entry. */ + entry->name_timestamp = timestamp; + } + } + } + + if ((entry->stat.st_mtime < node->nn_stat.st_mtime + || entry->stat.st_size != node->nn_stat.st_size) + && nn && nn->contents) + /* The file has changed. */ + ccache_invalidate (nn->contents); + + node->nn_stat = entry->stat; + if (!nn->dir && S_ISDIR (entry->stat.st_mode)) + ftpfs_dir_create (nn->fs, node, nn->rmt_path, &nn->dir); + + mutex_unlock (&dir->node->lock); + + ftpfs_cache_node (node); + + return err; + } +} + +/* Remove NODE from its entry (if the entry is still valid, it will remain + without a node). NODE should be locked. */ +error_t +ftpfs_detach_node (struct node *node) +{ + struct netnode *nn = node->nn; + struct ftpfs_dir_entry *entry = nn->dir_entry; + + if (entry) + /* NODE is still attached to some entry, so detach it. */ + { + struct ftpfs_dir *dir = entry->dir; + + mutex_lock (&dir->node->lock); + + if (entry->self_p) + /* Just detach NODE from the still active entry. */ + entry->node = 0; + else + /* This is a deleted entry, just awaiting disposal; do so. */ + { + nn->dir_entry = 0; + free_entry (entry); + } + + if (--dir->num_live_entries == 0) + netfs_nput (dir->node); + else + mutex_unlock (&dir->node->lock); + } + + return 0; +} + +/* State shared between ftpfs_dir_lookup and update_new_entry. */ +struct new_entry_state +{ + time_t timestamp; + struct ftpfs_dir *dir; + struct ftpfs_dir_entry *entry; +}; + +/* Update the directory entry for NAME to reflect ST and SYMLINK_TARGET. + HOOK->entry will be updated to reflect the new entry. */ +static error_t +update_new_entry (const char *name, const struct stat *st, + const char *symlink_target, void *hook) +{ + struct ftpfs_dir_entry *e; + struct new_entry_state *nes = hook; + + e = lookup (nes->dir, name, 1); + if (! e) + return ENOMEM; + + update_entry (e, st, symlink_target, nes->timestamp); + nes->entry = e; + + return 0; +} + +/* Lookup NAME in DIR, returning its entry, or an error. DIR's node should + be locked, and will be unlocked after returning; *NODE will contain the + result node, locked, and with an additional reference, or 0 if an error + occurs. */ +error_t +ftpfs_dir_lookup (struct ftpfs_dir *dir, const char *name, + struct node **node) +{ + struct ftp_conn *conn; + struct ftpfs_dir_entry *e; + error_t err = 0; + char *rmt_path = 0; + time_t timestamp = NOW; + + if (*name == '\0' || strcmp (name, ".") == 0) + /* Current directory -- just add an additional reference to DIR's node + and return it. */ + { + netfs_nref (dir->node); + *node = dir->node; + return 0; + } + else if (strcmp (name, "..") == 0) + /* Parent directory. */ + { + if (dir->node->nn->dir_entry) + { + *node = dir->node->nn->dir_entry->dir->node; + mutex_lock (&(*node)->lock); + netfs_nref (*node); + } + else + { + err = ENOENT; /* No .. */ + *node = 0; + } + + mutex_unlock (&dir->node->lock); + + return err; + } + + e = lookup (dir, name, 0); + if (!e || e->name_timestamp + dir->fs->params.name_timeout < timestamp) + /* Try to fetch info about NAME. */ + { + if (need_bulk_stat (timestamp, dir)) + /* Refetch the whole directory from the server. */ + { + err = refresh_dir (dir, 1, timestamp, e); + if (!err && !e) + e = lookup (dir, name, 0); + } + else + { + err = ftpfs_get_ftp_conn (dir->fs, &conn); + if (! err) + { + err = ftp_conn_append_name (conn, dir->rmt_path, name, + &rmt_path); + if (! err) + { + struct new_entry_state nes; + + nes.dir = dir; + nes.timestamp = timestamp; + + err = ftp_conn_get_stats (conn, rmt_path, 0, + update_new_entry, &nes); + if (! err) + e = nes.entry; + else if (err == ENOENT) + { + e = lookup (dir, name, 1); + if (! e) + err = ENOMEM; + else + { + e->noent = 1; /* A negative entry. */ + e->name_timestamp = timestamp; + } + } + } + + ftpfs_release_ftp_conn (dir->fs, conn); + } + } + } + + if (! err) + { + if (e && !e->noent) + /* We've got a dir entry, get a node for it. */ + { + /* If there's already a node, add a ref so that it doesn't go + away. */ + spin_lock (&netfs_node_refcnt_lock); + if (e->node) + e->node->references++; + spin_unlock (&netfs_node_refcnt_lock); + + if (! e->node) + /* No node; make one and install it into E. */ + { + if (! rmt_path) + /* We have to cons up the absolute path. We need the + connection just for the pathname frobbing functions. */ + { + err = ftpfs_get_ftp_conn (dir->fs, &conn); + if (! err) + { + err = ftp_conn_append_name (conn, dir->rmt_path, name, + &rmt_path); + ftpfs_release_ftp_conn (dir->fs, conn); + } + } + + if (! err) + { + err = ftpfs_create_node (e, rmt_path, &e->node); + + if (!err && dir->num_live_entries++ == 0) + /* Keep a reference to dir's node corresponding to + children. */ + { + spin_lock (&netfs_node_refcnt_lock); + dir->node->references++; + spin_unlock (&netfs_node_refcnt_lock); + } + } + } + + if (! err) + { + *node = e->node; + /* We have to unlock DIR's node before locking the child node + because the locking order is always child-parent. We know + the child node won't go away because we already hold the + additional reference to it. */ + mutex_unlock (&dir->node->lock); + mutex_lock (&e->node->lock); + } + } + else + err = ENOENT; + } + + if (err) + { + *node = 0; + mutex_unlock (&dir->node->lock); + } + + if (rmt_path) + free (rmt_path); + + return err; +} + +/* Lookup the null name in DIR, and return a node for it in NODE. Unlike + ftpfs_dir_lookup, this won't attempt to validate the existance of the + entry (to avoid opening a new connection if possible) -- that will happen + the first time the entry is refreshed. Also unlink ftpfs_dir_lookup, this + function doesn't expect DIR to be locked, and won't return *NODE locked. + This function is only used for bootstrapping the root node. */ +error_t +ftpfs_dir_null_lookup (struct ftpfs_dir *dir, struct node **node) +{ + struct ftpfs_dir_entry *e; + error_t err = 0; + + e = lookup (dir, "", 1); + if (! e) + return ENOMEM; + + if (! e->noent) + /* We've got a dir entry, get a node for it. */ + { + /* If there's already a node, add a ref so that it doesn't go away. */ + spin_lock (&netfs_node_refcnt_lock); + if (e->node) + e->node->references++; + spin_unlock (&netfs_node_refcnt_lock); + + if (! e->node) + /* No node; make one and install it into E. */ + { + err = ftpfs_create_node (e, dir->rmt_path, &e->node); + + if (!err && dir->num_live_entries++ == 0) + /* Keep a reference to dir's node corresponding to children. */ + { + spin_lock (&netfs_node_refcnt_lock); + dir->node->references++; + spin_unlock (&netfs_node_refcnt_lock); + } + } + + if (! err) + *node = e->node; + } + else + err = ENOENT; + + return err; +} + +/* Return in DIR a new ftpfs directory, in the filesystem FS, with node NODE + and remote path RMT_PATH. RMT_PATH is *not copied*, so it shouldn't ever + change while this directory is active. */ +error_t +ftpfs_dir_create (struct ftpfs *fs, struct node *node, const char *rmt_path, + struct ftpfs_dir **dir) +{ + struct ftpfs_dir *new = malloc (sizeof (struct ftpfs_dir)); + + if (! new) + return ENOMEM; + + /* Hold a reference to the new dir's node. */ + spin_lock (&netfs_node_refcnt_lock); + node->references++; + spin_unlock (&netfs_node_refcnt_lock); + + new->num_entries = 0; + new->num_live_entries = 0; + new->htable_len = 5; + new->htable = malloc (new->htable_len * sizeof (struct ftpfs_dir_entry *)); + bzero (new->htable, sizeof *new->htable * new->htable_len); + new->ordered = 0; + new->rmt_path = rmt_path; + new->fs = fs; + new->node = node; + new->stat_timestamp = 0; + new->name_timestamp = 0; + new->bulk_stat_base_stamp = 0; + new->bulk_stat_count_first_half = 0; + new->bulk_stat_count_second_half = 0; + + *dir = new; + + return 0; +} + +void +ftpfs_dir_free (struct ftpfs_dir *dir) +{ + /* Free all entries. */ + mark (dir); + sweep (dir); + + if (dir->htable) + free (dir->htable); + + netfs_nrele (dir->node); + + free (dir); +} diff --git a/ftpfs/fs.c b/ftpfs/fs.c new file mode 100644 index 00000000..2e049179 --- /dev/null +++ b/ftpfs/fs.c @@ -0,0 +1,82 @@ +/* Fs operations + + 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 <string.h> + +#include <hurd/ihash.h> +#include <hurd/netfs.h> + +#include "ftpfs.h" + +/* Create a new ftp filesystem with the given parameters. */ +error_t +ftpfs_create (char *rmt_path, int fsid, + struct ftp_conn_params *ftp_params, + struct ftp_conn_hooks *ftp_hooks, + struct ftpfs_params *params, + struct ftpfs **fs) +{ + error_t err; + /* Since nodes keep some of their state in the enclosing directory, we need + one for the root node. */ + struct ftpfs_dir *super_root_dir; + /* And also a super-root node, just used for locking SUPER_ROOT_DIR. */ + struct node *super_root; + /* The new node. */ + struct ftpfs *new = malloc (sizeof (struct ftpfs)); + + if (! new) + return ENOMEM; + + new->free_conns = 0; + new->conns = 0; + spin_lock_init (&new->conn_lock); + new->node_cache_mru = new->node_cache_lru = 0; + new->node_cache_len = 0; + mutex_init (&new->node_cache_lock); + + new->fsid = fsid; + new->next_inode = 2; + + new->params = *params; + new->ftp_params = ftp_params; + new->ftp_hooks = ftp_hooks; + + err = ihash_create (&new->inode_mappings); + spin_lock_init (&new->inode_mappings_lock); + + if (! err) + { + super_root = netfs_make_node (0); + if (! super_root) + err = ENOMEM; + } + if (! err) + err = ftpfs_dir_create (new, super_root, rmt_path, &super_root_dir); + if (! err) + err = ftpfs_dir_null_lookup (super_root_dir, &new->root); + + if (err) + free (new); + else + *fs = new; + + return err; +} diff --git a/ftpfs/ftpfs.c b/ftpfs/ftpfs.c new file mode 100644 index 00000000..646f9b1b --- /dev/null +++ b/ftpfs/ftpfs.c @@ -0,0 +1,397 @@ +/* Ftp filesystem + + Copyright (C) 1997, 1998 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 <string.h> +#include <unistd.h> +#include <argp.h> +#include <error.h> +#include <argz.h> +#include <netdb.h> + +#include <hurd/netfs.h> + +#include "ftpfs.h" + +static char *args_doc = "REMOTE_FS [SERVER]"; +static char *doc = "Hurd ftp filesystem translator" +"\vIf SERVER is not specified, an attempt is made to extract" +" it from REMOTE_FS, using `SERVER:FS' notation." +" SERVER can be a hostname, in which case anonymous ftp is used," +" or may include a user and password like `USER:PASSWORD@HOST' (the" +" `:PASSWORD' part is optional)."; + +/* The filesystem. */ +struct ftpfs *ftpfs; + +/* Parameters describing the server we're connecting to. */ +struct ftp_conn_params *ftpfs_ftp_params = 0; + +/* customization hooks. */ +struct ftp_conn_hooks ftpfs_ftp_hooks = { interrupt_check: ports_self_interrupted }; + +/* The (user-specified) name of the SERVER:FILESYSTEM we're connected too. */ +char *ftpfs_remote_fs; + +/* The FILESYSTEM component of FTPFS_REMOTE_FS. */ +char *ftpfs_remote_root; + +/* Random parameters for the filesystem. */ +struct ftpfs_params ftpfs_params; + +volatile struct mapped_time_value *ftpfs_maptime; + +int netfs_maxsymlinks = 12; + +extern error_t lookup_server (const char *server, + struct ftp_conn_params **params, int *h_err); + +static FILE *debug_stream = 0; +static char *debug_stream_name = 0; +static struct mutex debug_lock = MUTEX_INITIALIZER; + +/* Prints ftp connection log to DEBUG_STREAM. */ +static void +cntl_debug (struct ftp_conn *conn, int type, const char *txt) +{ + char *type_str; + + switch (type) + { + case FTP_CONN_CNTL_DEBUG_CMD: type_str = ">"; break; + case FTP_CONN_CNTL_DEBUG_REPLY: type_str = "="; break; + default: type_str = "?"; break; + } + + mutex_lock (&debug_lock); + if (debug_stream) + { + fprintf (debug_stream, "%u.%s%s\n", (unsigned)conn->hook, type_str, txt); + fflush (debug_stream); + } + mutex_unlock (&debug_lock); +} + +/* Various default parameters. */ +#define DEFAULT_NAME_TIMEOUT 300 +#define DEFAULT_STAT_TIMEOUT 120 + +#define DEFAULT_BULK_STAT_PERIOD 10 +#define DEFAULT_BULK_STAT_THRESHOLD 5 + +#define DEFAULT_NODE_CACHE_MAX 50 + +/* Return a string corresponding to the printed rep of DEFAULT_what */ +#define ___D(what) #what +#define __D(what) ___D(what) +#define _D(what) __D(DEFAULT_ ## what) + +/* Common (runtime & startup) options. */ + +#define OPT_NO_DEBUG 1 + +#define OPT_NAME_TIMEOUT 5 +#define OPT_STAT_TIMEOUT 7 +#define OPT_NODE_CACHE_MAX 8 +#define OPT_BULK_STAT_PERIOD 9 +#define OPT_BULK_STAT_THRESHOLD 10 + +/* Options usable both at startup and at runtime. */ +static const struct argp_option common_options[] = +{ + {"debug", 'D', "FILE", OPTION_ARG_OPTIONAL, "Print debug output to FILE"}, + {"no-debug", OPT_NO_DEBUG, 0, OPTION_HIDDEN }, + + {0,0,0,0, "Parameters:"}, + {"name-timeout", OPT_NAME_TIMEOUT, "SECS", 0, + "Time directory names are cached (default " _D(NAME_TIMEOUT) ")"}, + {"stat-timeout", OPT_STAT_TIMEOUT, "SECS", 0, + "Time stat information is cached (default " _D(STAT_TIMEOUT) ")"}, + {"node-cache-size", OPT_NODE_CACHE_MAX, "ENTRIES", 0, + "Number of recently used filesystem nodes that are cached (default " + _D(NODE_CACHE_MAX) ")"}, + + {"bulk-stat-period", OPT_BULK_STAT_PERIOD, "SECS", 0, + "Period for detecting bulk stats (default " _D(BULK_STAT_PERIOD) ")"}, + {"bulk-stat-threshold", OPT_BULK_STAT_THRESHOLD, "SECS", 0, + "Number of stats within the bulk-stat-period that trigger a bulk stat" + " (default " _D(BULK_STAT_THRESHOLD) ")"}, + + {0, 0} +}; + +static error_t +parse_common_opt (int key, char *arg, struct argp_state *state) +{ + error_t err = 0; + struct ftpfs_params *params = state->input; + + switch (key) + { + case 'D': + mutex_lock (&debug_lock); + + if (debug_stream && debug_stream != stderr) + fclose (debug_stream); + if (debug_stream_name) + { + free (debug_stream_name); + debug_stream_name = 0; + } + + if (arg) + { + debug_stream_name = strdup (arg); + if (! debug_stream_name) + { + argp_failure (state, 0, ENOMEM, "%s: Cannot open debugging file", arg); + err = ENOMEM; + } + + if (! err) + { + debug_stream = fopen (arg, "w+"); + if (! debug_stream) + { + err = errno; + argp_failure (state, 0, err, "%s: Cannot open debugging file", arg); + } + } + } + else + debug_stream = stderr; + + if (! err) + ftpfs_ftp_hooks.cntl_debug = cntl_debug; + + mutex_unlock (&debug_lock); + + return err; + + case OPT_NO_DEBUG: + mutex_lock (&debug_lock); + if (debug_stream && debug_stream != stderr) + fclose (debug_stream); + ftpfs_ftp_hooks.cntl_debug = 0; + mutex_unlock (&debug_lock); + break; + + case OPT_NODE_CACHE_MAX: + params->node_cache_max = atoi (arg); break; + case OPT_NAME_TIMEOUT: + params->name_timeout = atoi (arg); break; + case OPT_STAT_TIMEOUT: + params->stat_timeout = atoi (arg); break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp common_argp = { common_options, parse_common_opt }; + +/* Startup options. */ + +static const struct argp_option startup_options[] = +{ + { 0 } +}; + +/* Parse a single command line option/argument. */ +static error_t +parse_startup_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case ARGP_KEY_ARG: + if (state->arg_num > 1) + argp_usage (state); + else if (state->arg_num == 0) + ftpfs_remote_fs = arg; + else + /* If the fs & server are two separate args, glom them together into the + ":" notation. */ + { + char *rfs = malloc (strlen (ftpfs_remote_fs) + 1 + strlen (arg) + 1); + if (! rfs) + argp_failure (state, 99, ENOMEM, "%s", arg); + stpcpy (stpcpy (stpcpy (rfs, arg), ":"), ftpfs_remote_fs); + ftpfs_remote_fs = rfs; + } + break; + + case ARGP_KEY_SUCCESS: + /* Validate the remote fs arg; at this point FTPFS_REMOTE_FS is in + SERVER:FS notation. */ + if (state->arg_num == 0) + argp_error (state, "No remote filesystem specified"); + else + { + int h_err; /* Host lookup error. */ + error_t err; + char *sep = strchr (ftpfs_remote_fs, '@'); + + if (sep) + /* FTPFS_REMOTE_FS includes a '@', which means that it's in + USER[:PASS]@HOST:FS notation, so we have to be careful not to + choose the wrong `:' as the SERVER-FS separator. */ + sep = strchr (sep, ':'); + else + sep = strchr (ftpfs_remote_fs, ':'); + + if (! sep) + argp_error (state, "%s: No server specified", ftpfs_remote_fs); + + ftpfs_remote_root = sep + 1; + + /* Lookup the ftp server (the part before the `:'). */ + *sep = '\0'; + err = lookup_server (ftpfs_remote_fs, &ftpfs_ftp_params, &h_err); + if (err == EINVAL) + argp_failure (state, 10, 0, "%s: %s", + ftpfs_remote_fs, hstrerror (h_err)); + else if (err) + argp_failure (state, 11, err, "%s", ftpfs_remote_fs); + *sep = ':'; + } + + case ARGP_KEY_INIT: + /* Setup up state for our first child parser (common options). */ + state->child_inputs[0] = &ftpfs_params; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Runtime options. */ + +/* Parse a single command line option/argument. */ +static error_t +parse_runtime_opt (int key, char *arg, struct argp_state *state) +{ + if (key == ARGP_KEY_INIT) + /* Setup up state for our first child parser (common options). */ + { + state->child_inputs[0] = &ftpfs->params; + return 0; + } + else + return ARGP_ERR_UNKNOWN; +} + +static const struct argp_child runtime_argp_children[] = + { {&common_argp}, {&netfs_std_runtime_argp}, {0} }; +static struct argp runtime_argp = + { 0, parse_runtime_opt, 0, 0, runtime_argp_children }; + +/* Use by netfs_set_options to handle runtime option parsing. */ +struct argp *netfs_runtime_argp = &runtime_argp; + +/* Return an argz string describing the current options. Fill *ARGZ + with a pointer to newly malloced storage holding the list and *LEN + to the length of that storage. */ +error_t +netfs_append_args (char **argz, size_t *argz_len) +{ + char buf[80]; + error_t err = 0; + +#define FOPT(fmt, arg) \ + do { \ + if (! err) \ + { \ + snprintf (buf, sizeof buf, fmt, arg); \ + err = argz_add (argz, argz_len, buf); \ + } \ + } while (0) + + mutex_lock (&debug_lock); + if (ftpfs_ftp_hooks.cntl_debug && debug_stream) + { + if (debug_stream != stderr) + { + char *rep; + asprintf (&rep, "--debug=%s", debug_stream_name); + err = argz_add (argz, argz_len, rep); + free (rep); + } + else + err = argz_add (argz, argz_len, "--debug"); + } + mutex_unlock (&debug_lock); + + if (ftpfs->params.name_timeout != DEFAULT_NAME_TIMEOUT) + FOPT ("--name-timeout=%ld", ftpfs->params.name_timeout); + if (ftpfs->params.stat_timeout != DEFAULT_STAT_TIMEOUT) + FOPT ("--stat-timeout=%ld", ftpfs->params.stat_timeout); + if (ftpfs->params.node_cache_max != DEFAULT_NODE_CACHE_MAX) + FOPT ("--node-cache-size=%Zu", ftpfs->params.node_cache_max); + if (ftpfs->params.bulk_stat_period != DEFAULT_BULK_STAT_PERIOD) + FOPT ("--bulk-stat-period=%ld", ftpfs->params.bulk_stat_period); + if (ftpfs->params.bulk_stat_threshold != DEFAULT_BULK_STAT_THRESHOLD) + FOPT ("--bulk-stat-threshold=%d", ftpfs->params.bulk_stat_threshold); + + return argz_add (argz, argz_len, ftpfs_remote_fs); +} + +/* Program entry point. */ +int +main (int argc, char **argv) +{ + error_t err; + mach_port_t bootstrap; + const struct argp_child argp_children[] = + { {&common_argp}, {&netfs_std_startup_argp}, {0} }; + struct argp argp = + { startup_options, parse_startup_opt, args_doc, doc, argp_children }; + + ftpfs_params.name_timeout = DEFAULT_NAME_TIMEOUT; + ftpfs_params.stat_timeout = DEFAULT_STAT_TIMEOUT; + ftpfs_params.node_cache_max = DEFAULT_NODE_CACHE_MAX; + ftpfs_params.bulk_stat_period = DEFAULT_BULK_STAT_PERIOD; + ftpfs_params.bulk_stat_threshold = DEFAULT_BULK_STAT_THRESHOLD; + + argp_parse (&argp, argc, argv, 0, 0, 0); + + task_get_bootstrap_port (mach_task_self (), &bootstrap); + + netfs_init (); + + err = maptime_map (0, 0, &ftpfs_maptime); + if (err) + error (3, err, "mapping time"); + + err = ftpfs_create (ftpfs_remote_root, getpid (), + ftpfs_ftp_params, &ftpfs_ftp_hooks, + &ftpfs_params, &ftpfs); + if (err) + error (4, err, "%s", ftpfs_remote_fs); + + netfs_root_node = ftpfs->root; + + netfs_startup (bootstrap, 0); + + for (;;) + netfs_server_loop (); +} diff --git a/ftpfs/ftpfs.h b/ftpfs/ftpfs.h new file mode 100644 index 00000000..0ca67aa4 --- /dev/null +++ b/ftpfs/ftpfs.h @@ -0,0 +1,249 @@ +/* Ftp filesystem + + 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. */ + +#ifndef __FTPFS_H__ +#define __FTPFS_H__ + +#include <stdlib.h> +#include <cthreads.h> +#include <ftpconn.h> +#include <maptime.h> + +/* Anonymous types. */ +struct ccache; +struct ftpfs_conn; + +/* A single entry in a directory. */ +struct ftpfs_dir_entry +{ + char *name; /* Name of this entry */ + size_t hv; /* Hash value of NAME (before mod'ing) */ + + /* The active node referred to by this name (may be 0). + NETFS_NODE_REFCNT_LOCK should be held while frobbing this. */ + struct node *node; + + struct stat stat; + char *symlink_target; + time_t stat_timestamp; + + /* The directory to which this entry belongs. */ + struct ftpfs_dir *dir; + + /* Link to next entry in hash bucket, and address of previous entry's (or + hash table's) pointer to this entry. If the SELF_P field is 0, then + this is a deleted entry, awaiting final disposal. */ + struct ftpfs_dir_entry *next, **self_p; + + /* Next entry in `directory order', or 0 if none known. */ + struct ftpfs_dir_entry *ordered_next, **ordered_self_p; + + /* When the presence/absence of this file was last checked. */ + time_t name_timestamp; + + void **inode_locp; /* Used for removing this entry */ + + int noent : 1; /* A negative lookup result. */ + int valid : 1; /* Marker for GC'ing. */ +}; + +/* A directory. */ +struct ftpfs_dir +{ + /* Number of entries in HTABLE. */ + size_t num_entries; + + /* The number of entries that have nodes attached. We keep an additional + reference to our node if there are any, to prevent it from going away. */ + size_t num_live_entries; + + /* Hash table of entries. */ + struct ftpfs_dir_entry **htable; + size_t htable_len; /* # of elements in HTABLE (not bytes). */ + + /* List of dir entries in `directory order', in a linked list using the + ORDERED_NEXT and ORDERED_SELF_P fields in each entry. Not all entries + in HTABLE need be in this list. */ + struct ftpfs_dir_entry *ordered; + + /* The filesystem node that this is the directory for. */ + struct node *node; + + /* The filesystem this directory is in. */ + struct ftpfs *fs; + + /* The path to this directory on the server. */ + const char *rmt_path; + + time_t stat_timestamp; + time_t name_timestamp; + + /* Stuff for detecting bulk stats. */ + + /* The timestamp of the first sample in bulk_stat_count1, rounded to + BULK_STAT_PERIOD seconds. */ + time_t bulk_stat_base_stamp; + + /* The number of stats done in the period [bulk_stat_base_stamp, + bulk_stat_base_stamp+BULK_STAT_PERIOD). */ + unsigned bulk_stat_count_first_half; + /* The number of stats done in the period + [bulk_stat_base_stamp+BULK_STAT_PERIOD, + bulk_stat_base_stamp+BULK_STAT_PERIOD*2). */ + unsigned bulk_stat_count_second_half; +}; + +/* libnetfs node structure. */ +struct netnode +{ + /* The remote filesystem. */ + struct ftpfs *fs; + + /* The directory entry for this node. */ + struct ftpfs_dir_entry *dir_entry; + + /* The path in FS that this file corresponds to. */ + const char *rmt_path; + + /* If this is a regular file, an optional cache of the contents. This may + be 0, if no cache has yet been created, but once created, it only goes + away when the node is destroyed. */ + struct ccache *contents; + + /* If this is a directory, the contents, or 0 if not fetched. */ + struct ftpfs_dir *dir; + + /* Position in the node cache. */ + struct node *ncache_next, *ncache_prev; +}; + +/* Various parameters that can be used to change the behavior of an ftpfs. */ +struct ftpfs_params +{ + /* Amount of time name existance is cached. */ + time_t name_timeout; + + /* Amount of time stat information is cached. */ + time_t stat_timeout; + + /* Parameters for detecting bulk stats; if more than BULK_STAT_THRESHOLD + stats are done within BULK_STAT_PERIOD seconds, the whole enclosing + directory is fetched. */ + time_t bulk_stat_period; + unsigned bulk_stat_threshold; + + /* The size of the node cache. */ + size_t node_cache_max; +}; + +/* A particular filesystem. */ +struct ftpfs +{ + /* Root of filesystem. */ + struct node *root; + + /* A pool of ftp connections for server threads to use. */ + struct ftpfs_conn *free_conns; + struct ftpfs_conn *conns; + spin_lock_t conn_lock; + + /* Parameters for making new ftp connections. */ + struct ftp_conn_params *ftp_params; + struct ftp_conn_hooks *ftp_hooks; + + /* Inode numbers are assigned sequentially in order of creation. */ + ino_t next_inode; + int fsid; + + /* A hash table mapping inode numbers to directory entries. */ + struct ihash *inode_mappings; + spin_lock_t inode_mappings_lock; + + struct ftpfs_params params; + + /* A cache that holds a reference to recently used nodes. */ + struct node *node_cache_mru, *node_cache_lru; + size_t node_cache_len; /* Number of entries in it. */ + struct mutex node_cache_lock; +}; + +extern volatile struct mapped_time_value *ftpfs_maptime; + +/* The current time. */ +#define NOW \ + ({ struct timeval tv; maptime_read (ftpfs_maptime, &tv); tv.tv_sec; }) + +/* Create a new ftp filesystem with the given parameters. */ +error_t ftpfs_create (char *rmt_root, int fsid, + struct ftp_conn_params *ftp_params, + struct ftp_conn_hooks *ftp_hooks, + struct ftpfs_params *params, + struct ftpfs **fs); + +/* Refresh stat information for NODE. This may actually refresh the whole + directory if that is deemed desirable. */ +error_t ftpfs_refresh_node (struct node *node); + +/* Remove NODE from its entry (if the entry is still valid, it will remain + without a node). NODE should be locked. */ +error_t ftpfs_detach_node (struct node *node); + +/* Return a new node in NODE, with a name NAME, and return the new node + with a single reference in NODE. E may be 0, if this is the root node. */ +error_t ftpfs_create_node (struct ftpfs_dir_entry *e, const char *rmt_path, + struct node **node); + +/* Add NODE to the recently-used-node cache, which adds a reference to + prevent it from going away. NODE should be locked. */ +void ftpfs_cache_node (struct node *node); + +/* Get an ftp connection to use for an operation. */ +error_t ftpfs_get_ftp_conn (struct ftpfs *fs, struct ftp_conn **conn); + +/* Return CONN to the pool of free connections in FS. */ +void ftpfs_release_ftp_conn (struct ftpfs *fs, struct ftp_conn *conn); + +/* Return in DIR a new ftpfs directory, in the filesystem FS, with node NODE + and remote path RMT_PATH. RMT_PATH is *not copied*, so it shouldn't ever + change while this directory is active. */ +error_t ftpfs_dir_create (struct ftpfs *fs, struct node *node, + const char *rmt_path, struct ftpfs_dir **dir); + +void ftpfs_dir_free (struct ftpfs_dir *dir); + +/* Refresh DIR. */ +error_t ftpfs_dir_refresh (struct ftpfs_dir *dir); + +/* Lookup NAME in DIR, returning its entry, or an error. DIR's node should + be locked, and will be unlocked after returning; *NODE will contain the + result node, locked, and with an additional reference, or 0 if an error + occurs. */ +error_t ftpfs_dir_lookup (struct ftpfs_dir *dir, const char *name, + struct node **node); + +/* Lookup the null name in DIR, and return a node for it in NODE. Unlike + ftpfs_dir_lookup, this won't attempt to validate the existance of the + entry (to avoid opening a new connection if possible) -- that will happen + the first time the entry is refreshed. Also unlink ftpfs_dir_lookup, this + function doesn't expect DIR to be locked, and won't return *NODE locked. + This function is only used for bootstrapping the root node. */ +error_t ftpfs_dir_null_lookup (struct ftpfs_dir *dir, struct node **node); + +#endif /* __FTPFS_H__ */ diff --git a/ftpfs/host.c b/ftpfs/host.c new file mode 100644 index 00000000..a0703afe --- /dev/null +++ b/ftpfs/host.c @@ -0,0 +1,154 @@ +/* Server lookup + + 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 <stdlib.h> +#include <string.h> +#include <errno.h> +#include <netdb.h> +#include <ftpconn.h> + +/* Split the server-specification string SERVER into its components: a + hostname (returned in HOST), username (USER), and password (PASSWD). */ +static error_t +split_server_name (const char *server, char **host, char **user, char **passwd) +{ + size_t plim; + const char *p = server, *sep; + + *host = 0; + *user = 0; + *passwd = 0; + + /* Extract the hostname; syntax is either `HOST:...', `...@HOST', or just + HOST if there are no user parameters specified. */ + sep = strchr (p, '@'); + if (sep) + /* ...@HOST */ + { + *host = strdup (sep + 1); + if (! *host) + return ENOMEM; + plim = sep - server; + } + else + { + sep = strchr (server, ':'); + if (sep) + /* HOST:... */ + { + *host = strndup (server, sep - server); + if (! *host) + return ENOMEM; + p = sep + 1; + plim = strlen (p); + } + else + /* Just HOST */ + { + *host = strdup (server); + if (! *host) + return ENOMEM; + return 0; + } + } + + /* Now P...P+PLIM contains any user parameters for HOST. */ + sep = memchr (p, ':', plim); + if (sep) + /* USERNAME:PASSWD */ + { + *user = strndup (p, sep - p); + *passwd = strndup (sep + 1, plim - (sep + 1 - p)); + if (!*user || !*passwd) + { + if (*user) + free (*user); + if (*passwd) + free (*passwd); + free (*host); + return ENOMEM; + } + } + else + /* Just USERNAME */ + { + *user = strndup (p, plim); + if (! *user) + free (*user); + } + + return 0; +} + +/* */ +error_t +lookup_server (const char *server, struct ftp_conn_params **params, int *h_err) +{ + char hostent_data[2048]; /* XXX what size should this be???? */ + struct hostent _he, *he; + char *host, *user, *passwd; + error_t err = split_server_name (server, &host, &user, &passwd); + + if (err) + return err; + + /* We didn't find a pre-existing host entry. Make a new one. Note that + since we don't lock anything while making up our new structure, another + thread could have inserted a duplicate entry for the same host name, but + this isn't really a problem, just annoying. */ + + if (gethostbyname_r (host, &_he, hostent_data, sizeof hostent_data, + &he, h_err) == 0) + { + *params = malloc (sizeof (struct ftp_conn_params)); + if (! *params) + err = ENOMEM; + else + { + (*params)->addr = malloc (he->h_length); + if (! (*params)->addr) + { + free (*params); + err = ENOMEM; + } + else + { + bcopy (he->h_addr_list[0], (*params)->addr, he->h_length); + (*params)->addr_len = he->h_length; + (*params)->addr_type = he->h_addrtype; + (*params)->user = user; + (*params)->pass = passwd; + (*params)->acct = 0; + } + } + } + else + err = EINVAL; + + free (host); + + if (err) + { + free (user); + free (passwd); + } + + return err; +} diff --git a/ftpfs/ncache.c b/ftpfs/ncache.c new file mode 100644 index 00000000..27b868a7 --- /dev/null +++ b/ftpfs/ncache.c @@ -0,0 +1,87 @@ +/* Node 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 "ftpfs.h" + +/* Remove NN's node from its position in FS's node cache. */ +static void +node_unlink (struct node *node, struct ftpfs *fs) +{ + struct netnode *nn = node->nn; + if (nn->ncache_next) + nn->ncache_next->nn->ncache_prev = nn->ncache_prev; + if (nn->ncache_prev) + nn->ncache_prev->nn->ncache_next = nn->ncache_next; + if (fs->node_cache_mru == node) + fs->node_cache_mru = nn->ncache_next; + if (fs->node_cache_lru == node) + fs->node_cache_lru = nn->ncache_prev; + nn->ncache_next = 0; + nn->ncache_prev = 0; + fs->node_cache_len--; +} + +/* Add NODE to the recently-used-node cache, which adds a reference to + prevent it from going away. NODE should be locked. */ +void +ftpfs_cache_node (struct node *node) +{ + struct netnode *nn = node->nn; + struct ftpfs *fs = nn->fs; + + mutex_lock (&fs->node_cache_lock); + + if (fs->params.node_cache_max > 0 || fs->node_cache_len > 0) + { + if (fs->node_cache_mru != node) + { + if (nn->ncache_next || nn->ncache_prev) + /* Node is already in the cache. */ + node_unlink (node, fs); + else + /* Add a reference from the cache. */ + netfs_nref (node); + + nn->ncache_next = fs->node_cache_mru; + nn->ncache_prev = 0; + if (fs->node_cache_mru) + fs->node_cache_mru->nn->ncache_prev = node; + if (! fs->node_cache_lru) + fs->node_cache_lru = node; + fs->node_cache_mru = node; + fs->node_cache_len++; + } + + /* Forget the least used nodes. */ + while (fs->node_cache_len > fs->params.node_cache_max) + { + struct node *lru = fs->node_cache_lru; + node_unlink (lru, fs); + netfs_nrele (lru); + } + } + + mutex_unlock (&fs->node_cache_lock); +} diff --git a/ftpfs/netfs.c b/ftpfs/netfs.c new file mode 100644 index 00000000..ced7310a --- /dev/null +++ b/ftpfs/netfs.c @@ -0,0 +1,462 @@ +/* ftpfs interface to libnetfs + + Copyright (C) 1997, 1998, 1999 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 <stddef.h> +#include <stdlib.h> +#include <dirent.h> +#include <string.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include <hurd/netfs.h> + +#include "ftpfs.h" +#include "ccache.h" + +/* Attempt to create a file named NAME in DIR for USER with MODE. Set *NODE + to the new node upon return. On any error, clear *NODE. *NODE should be + locked on success; no matter what, unlock DIR before returning. */ +error_t +netfs_attempt_create_file (struct iouser *user, struct node *dir, + char *name, mode_t mode, struct node **node) +{ + *node = 0; + mutex_unlock (&dir->lock); + return EOPNOTSUPP; +} + +/* Node NODE is being opened by USER, with FLAGS. NEWNODE is nonzero if we + just created this node. Return an error if we should not permit the open + to complete because of a permission restriction. */ +error_t +netfs_check_open_permissions (struct iouser *user, struct node *node, + int flags, int newnode) +{ + error_t err = ftpfs_refresh_node (node); + if (!err && (flags & O_READ)) + err = fshelp_access (&node->nn_stat, S_IREAD, user); + if (!err && (flags & O_WRITE)) + err = fshelp_access (&node->nn_stat, S_IWRITE, user); + if (!err && (flags & O_EXEC)) + err = fshelp_access (&node->nn_stat, S_IEXEC, user); + return err; +} + +/* This should attempt a utimes call for the user specified by CRED on node + NODE, to change the atime to ATIME and the mtime to MTIME. */ +error_t +netfs_attempt_utimes (struct iouser *cred, struct node *node, + struct timespec *atime, struct timespec *mtime) +{ + error_t err = ftpfs_refresh_node (node); + int flags = TOUCH_CTIME; + + if (! err) + err = fshelp_isowner (&node->nn_stat, cred); + + if (! err) + { + if (atime) + { + node->nn_stat.st_atime = atime->tv_sec; + node->nn_stat.st_atime_usec = atime->tv_nsec / 1000; + } + else + flags |= TOUCH_ATIME; + + if (mtime) + { + node->nn_stat.st_mtime = mtime->tv_sec; + node->nn_stat.st_mtime_usec = mtime->tv_nsec / 1000; + } + else + flags |= TOUCH_MTIME; + + fshelp_touch (&node->nn_stat, flags, ftpfs_maptime); + } + + return err; +} + +/* Return the valid access types (bitwise OR of O_READ, O_WRITE, and O_EXEC) + in *TYPES for file NODE and user CRED. */ +error_t +netfs_report_access (struct iouser *cred, struct node *node, int *types) +{ + error_t err = ftpfs_refresh_node (node); + + if (! err) + { + *types = 0; + if (fshelp_access (&node->nn_stat, S_IREAD, cred) == 0) + *types |= O_READ; + if (fshelp_access (&node->nn_stat, S_IWRITE, cred) == 0) + *types |= O_WRITE; + if (fshelp_access (&node->nn_stat, S_IEXEC, cred) == 0) + *types |= O_EXEC; + } + + return err; +} + +/* Trivial definitions. */ + +/* Make sure that NP->nn_stat is filled with current information. CRED + identifies the user responsible for the operation. */ +error_t +netfs_validate_stat (struct node *node, struct iouser *cred) +{ + return ftpfs_refresh_node (node); +} + +/* This should sync the file NODE completely to disk, for the user CRED. If + WAIT is set, return only after sync is completely finished. */ +error_t +netfs_attempt_sync (struct iouser *cred, struct node *node, int wait) +{ + return 0; +} + +/* The granularity with which we allocate space to return our result. */ +#define DIRENTS_CHUNK_SIZE (8*1024) + +/* Returned directory entries are aligned to blocks this many bytes long. + Must be a power of two. */ +#define DIRENT_ALIGN 4 +#define DIRENT_NAME_OFFS offsetof (struct dirent, d_name) + +/* Length is structure before the name + the name + '\0', all + padded to a four-byte alignment. */ +#define DIRENT_LEN(name_len) \ + ((DIRENT_NAME_OFFS + (name_len) + 1 + (DIRENT_ALIGN - 1)) \ + & ~(DIRENT_ALIGN - 1)) + +/* Fetch a directory, as for netfs_get_dirents. */ +static error_t +get_dirents (struct ftpfs_dir *dir, + int first_entry, int max_entries, char **data, + mach_msg_type_number_t *data_len, + vm_size_t max_data_len, int *data_entries) +{ + struct ftpfs_dir_entry *e; + error_t err = 0; + + if (! dir) + return ENOTDIR; + + e = dir->ordered; + + /* Find the first entry. */ + while (first_entry-- > 0) + if (! e) + { + max_entries = 0; + break; + } + else + e = e->ordered_next; + + if (max_entries != 0) + { + size_t size = + (max_data_len == 0 || max_data_len > DIRENTS_CHUNK_SIZE + ? DIRENTS_CHUNK_SIZE + : max_data_len); + + *data = mmap (0, size, PROT_READ|PROT_WRITE, + MAP_ANON, 0, 0); + err = ((void *) *data == (void *) -1) ? errno : 0; + + if (! err) + { + char *p = *data; + int count = 0; + + /* See how much space we need for the result. */ + while ((max_entries == -1 || count < max_entries) && e) + { + struct dirent hdr; + size_t name_len = strlen (e->name); + size_t sz = DIRENT_LEN (name_len); + int entry_type = + e->stat_timestamp ? IFTODT (e->stat.st_mode) : DT_UNKNOWN; + + if ((p - *data) + sz > size) + { + if (max_data_len > 0) + break; + else + /* Try to grow our return buffer. */ + { + vm_address_t extension = (vm_address_t)(*data + size); + err = vm_allocate (mach_task_self (), &extension, + DIRENTS_CHUNK_SIZE, 0); + if (err) + break; + size += DIRENTS_CHUNK_SIZE; + } + } + + hdr.d_namlen = name_len; + hdr.d_fileno = e->stat.st_ino; + hdr.d_reclen = sz; + hdr.d_type = entry_type; + + memcpy (p, &hdr, DIRENT_NAME_OFFS); + strcpy (p + DIRENT_NAME_OFFS, e->name); + p += sz; + + count++; + e = e->ordered_next; + } + + if (err) + munmap (*data, size); + else + { + vm_address_t alloc_end = (vm_address_t)(*data + size); + vm_address_t real_end = round_page (p); + if (alloc_end > real_end) + munmap ((caddr_t) real_end, alloc_end - real_end); + *data_len = p - *data; + *data_entries = count; + } + } + } + else + { + *data_len = 0; + *data_entries = 0; + } + + return err; +} + +error_t +netfs_get_dirents (struct iouser *cred, struct node *dir, + int first_entry, int max_entries, char **data, + mach_msg_type_number_t *data_len, + vm_size_t max_data_len, int *data_entries) +{ + error_t err = ftpfs_refresh_node (dir); + + if (! err) + { + if (dir->nn->dir) + { + err = ftpfs_dir_refresh (dir->nn->dir); + if (! err) + err = get_dirents (dir->nn->dir, first_entry, max_entries, + data, data_len, max_entries, data_entries); + } + else + err = ENOTDIR; + } + + return err; +} + +/* Lookup NAME in DIR for USER; set *NODE to the found name upon return. If + the name was not found, then return ENOENT. On any error, clear *NODE. + (*NODE, if found, should be locked, this call should unlock DIR no matter + what.) */ +error_t netfs_attempt_lookup (struct iouser *user, struct node *dir, + char *name, struct node **node) +{ + error_t err = ftpfs_refresh_node (dir); + if (! err) + err = ftpfs_dir_lookup (dir->nn->dir, name, node); + return err; +} + +/* Delete NAME in DIR for USER. */ +error_t netfs_attempt_unlink (struct iouser *user, struct node *dir, + char *name) +{ + return EROFS; +} + +/* Note that in this one call, neither of the specific nodes are locked. */ +error_t netfs_attempt_rename (struct iouser *user, struct node *fromdir, + char *fromname, struct node *todir, + char *toname, int excl) +{ + return EROFS; +} + +/* Attempt to create a new directory named NAME in DIR for USER with mode + MODE. */ +error_t netfs_attempt_mkdir (struct iouser *user, struct node *dir, + char *name, mode_t mode) +{ + return EROFS; +} + +/* Attempt to remove directory named NAME in DIR for USER. */ +error_t netfs_attempt_rmdir (struct iouser *user, + struct node *dir, char *name) +{ + return EROFS; +} + +/* This should attempt a chmod call for the user specified by CRED on node + NODE, to change the owner to UID and the group to GID. */ +error_t netfs_attempt_chown (struct iouser *cred, struct node *node, + uid_t uid, uid_t gid) +{ + return EROFS; +} + +/* This should attempt a chauthor call for the user specified by CRED on node + NODE, to change the author to AUTHOR. */ +error_t netfs_attempt_chauthor (struct iouser *cred, struct node *node, + uid_t author) +{ + return EROFS; +} + +/* This should attempt a chmod call for the user specified by CRED on node + NODE, to change the mode to MODE. Unlike the normal Unix and Hurd meaning + of chmod, this function is also used to attempt to change files into other + types. If such a transition is attempted which is impossible, then return + EOPNOTSUPP. */ +error_t netfs_attempt_chmod (struct iouser *cred, struct node *node, + mode_t mode) +{ + return EROFS; +} + +/* Attempt to turn NODE (user CRED) into a symlink with target NAME. */ +error_t netfs_attempt_mksymlink (struct iouser *cred, struct node *node, + char *name) +{ + return EROFS; +} + +/* Attempt to turn NODE (user CRED) into a device. TYPE is either S_IFBLK or + S_IFCHR. */ +error_t netfs_attempt_mkdev (struct iouser *cred, struct node *node, + mode_t type, dev_t indexes) +{ + return EROFS; +} + +/* Attempt to set the passive translator record for FILE to ARGZ (of length + ARGZLEN) for user CRED. */ +error_t netfs_set_translator (struct iouser *cred, struct node *node, + char *argz, size_t argzlen) +{ + return EROFS; +} + +/* This should attempt a chflags call for the user specified by CRED on node + NODE, to change the flags to FLAGS. */ +error_t netfs_attempt_chflags (struct iouser *cred, struct node *node, + int flags) +{ + return EROFS; +} + +/* This should attempt to set the size of the file NODE (for user CRED) to + SIZE bytes long. */ +error_t netfs_attempt_set_size (struct iouser *cred, struct node *node, + off_t size) +{ + return EROFS; +} + +/* This should attempt to fetch filesystem status information for the remote + filesystem, for the user CRED. */ +error_t netfs_attempt_statfs (struct iouser *cred, struct node *node, + struct statfs *st) +{ + return EOPNOTSUPP; +} + +/* This should sync the entire remote filesystem. If WAIT is set, return + only after sync is completely finished. */ +error_t netfs_attempt_syncfs (struct iouser *cred, int wait) +{ + return 0; +} + +/* Create a link in DIR with name NAME to FILE for USER. Note that neither + DIR nor FILE are locked. If EXCL is set, do not delete the target, but + return EEXIST if NAME is already found in DIR. */ +error_t netfs_attempt_link (struct iouser *user, struct node *dir, + struct node *file, char *name, int excl) +{ + return EROFS; +} + +/* Attempt to create an anonymous file related to DIR for USER with MODE. + Set *NODE to the returned file upon success. No matter what, unlock DIR. */ +error_t netfs_attempt_mkfile (struct iouser *user, struct node *dir, + mode_t mode, struct node **node) +{ + *node = 0; + mutex_unlock (&dir->lock); + return EROFS; +} + +/* Read the contents of NODE (a symlink), for USER, into BUF. */ +error_t netfs_attempt_readlink (struct iouser *user, struct node *node, char *buf) +{ + error_t err = ftpfs_refresh_node (node); + if (! err) + { + struct ftpfs_dir_entry *e = node->nn->dir_entry; + if (e) + bcopy (e->symlink_target, buf, node->nn_stat.st_size); + else + err = EINVAL; + } + return err; +} + +/* Read from the file NODE for user CRED starting at OFFSET and continuing for + up to *LEN bytes. Put the data at DATA. Set *LEN to the amount + successfully read upon return. */ +error_t netfs_attempt_read (struct iouser *cred, struct node *node, + off_t offset, size_t *len, void *data) +{ + error_t err = 0; + + if (! node->nn->contents) + err = ccache_create (node, &node->nn->contents); + if (! err) + { + if (*len > node->nn_stat.st_size - offset) + *len = node->nn_stat.st_size - offset; + if (*len > 0) + err = ccache_read (node->nn->contents, offset, *len, data); + } + + return err; +} + +/* Write to the file NODE for user CRED starting at OFSET and continuing for up + to *LEN bytes from DATA. Set *LEN to the amount seccessfully written upon + return. */ +error_t netfs_attempt_write (struct iouser *cred, struct node *node, + off_t offset, size_t *len, void *data) +{ + return EROFS; +} diff --git a/ftpfs/node.c b/ftpfs/node.c new file mode 100644 index 00000000..73af41f2 --- /dev/null +++ b/ftpfs/node.c @@ -0,0 +1,106 @@ +/* General fs node functions + + 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 <fcntl.h> +#include <string.h> + +#include <hurd/ihash.h> +#include <hurd/fshelp.h> +#include <hurd/iohelp.h> +#include <hurd/netfs.h> + +#include "ftpfs.h" +#include "ccache.h" + +/* Node maintenance. */ + +/* Return a new node in NODE, with a name NAME, and return the new node + with a single reference in NODE. E may be 0, if this is the root node. */ +error_t +ftpfs_create_node (struct ftpfs_dir_entry *e, const char *rmt_path, + struct node **node) +{ + struct node *new; + struct netnode *nn = malloc (sizeof (struct netnode)); + + if (! nn) + return ENOMEM; + + nn->fs = e->dir->fs; + nn->dir_entry = e; + nn->contents = 0; + nn->dir = 0; + nn->rmt_path = strdup (rmt_path); + nn->ncache_next = nn->ncache_prev = 0; + + new = netfs_make_node (nn); + if (! new) + { + free (nn); + return ENOMEM; + } + + fshelp_touch (&new->nn_stat, TOUCH_ATIME|TOUCH_MTIME|TOUCH_CTIME, + ftpfs_maptime); + + spin_lock (&nn->fs->inode_mappings_lock); + ihash_add (nn->fs->inode_mappings, e->stat.st_ino, new, &e->inode_locp); + spin_unlock (&nn->fs->inode_mappings_lock); + + e->node = new; + *node = new; + + return 0; +} + +/* Node NP is all done; free all its associated storage. */ +void +netfs_node_norefs (struct node *node) +{ + struct netnode *nn = node->nn; + + /* Ftpfs_detach_node does ref count frobbing (of other nodes), so we have + to unlock NETFS_NODE_REFCNT_LOCK during it. */ + node->references++; + spin_unlock (&netfs_node_refcnt_lock); + + /* Remove NODE from any entry it is attached to. */ + ftpfs_detach_node (node); + + if (nn->dir) + { + assert (nn->dir->num_live_entries == 0); + ftpfs_dir_free (nn->dir); + } + + /* Remove this entry from the set of known inodes. */ + spin_lock (&nn->fs->inode_mappings_lock); + ihash_locp_remove (nn->fs->inode_mappings, nn->dir_entry->inode_locp); + spin_unlock (&nn->fs->inode_mappings_lock); + + if (nn->contents) + ccache_free (nn->contents); + + free (nn); + free (node); + + /* Caller expects us to leave this locked... */ + spin_lock (&netfs_node_refcnt_lock); +} |