summaryrefslogtreecommitdiff
path: root/ftpfs
diff options
context:
space:
mode:
authorMiles Bader <miles@gnu.org>1997-08-06 22:08:59 +0000
committerMiles Bader <miles@gnu.org>1997-08-06 22:08:59 +0000
commit80c437f2f35813085b86cc05987609ec36d18865 (patch)
treefdda379edf638d85d6bbf1bb9fb2934dba4cc1a6 /ftpfs
parentf20a48afff1d935eea20aa2c02b5fd805e85663c (diff)
Initial checkin
Diffstat (limited to 'ftpfs')
-rw-r--r--ftpfs/Makefile33
-rw-r--r--ftpfs/ccache.c285
-rw-r--r--ftpfs/ccache.h73
-rw-r--r--ftpfs/conn.c105
-rw-r--r--ftpfs/dir.c725
-rw-r--r--ftpfs/fs.c82
-rw-r--r--ftpfs/ftpfs.c313
-rw-r--r--ftpfs/ftpfs.h225
-rw-r--r--ftpfs/host.c156
-rw-r--r--ftpfs/ncache.c87
-rw-r--r--ftpfs/netfs.c441
-rw-r--r--ftpfs/node.c106
12 files changed, 2631 insertions, 0 deletions
diff --git a/ftpfs/Makefile b/ftpfs/Makefile
new file mode 100644
index 00000000..22110246
--- /dev/null
+++ b/ftpfs/Makefile
@@ -0,0 +1,33 @@
+# 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
+
+MIGSTUBS = ioServer.o fsysServer.o
+OBJS = $(SRCS:.c=.o) # $(MIGSTUBS)
+HURDLIBS = netfs fshelp iohelp ports threads ihash ftpconn shouldbeinlibc
+
+MIGSFLAGS = -imacros $(srcdir)/mig-mutate.h
+
+include ../Makeconf
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);
+}
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..5cde61a6
--- /dev/null
+++ b/ftpfs/dir.c
@@ -0,0 +1,725 @@
+/* Directory 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/netfs.h>
+
+#include "ftpfs.h"
+#include "ccache.h"
+
+/* Return an alloca'd string containing NAME appended to DIR's path; if
+ DIR_PFX_LEN is non-zero, the length of DIR's path is returned in it. */
+#define path_append(dir, name, dir_pfx_len) \
+({ \
+ struct ftpfs_dir *_dir = (dir); \
+ const char *_name = (name); \
+ size_t *_dir_pfx_len_p = (dir_pfx_len); \
+ size_t _dir_pfx_len = strlen (_dir->rmt_path) + 1; \
+ char *_path = alloca (_dir_pfx_len + strlen (_name) + 1); \
+ \
+ /* Form the composite name. */ \
+ if (_name && *_name) \
+ stpcpy (stpcpy (stpcpy (_path, _dir->rmt_path), "/"), _name); \
+ else \
+ { \
+ strcpy (_path, _dir->rmt_path); \
+ _dir_pfx_len--; \
+ } \
+ if (_dir_pfx_len_p) \
+ *_dir_pfx_len_p = _dir_pfx_len; \
+ \
+ _path; \
+})
+
+/* 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;
+
+ 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)
+ {
+ e = malloc (sizeof *e);
+ if (e)
+ {
+ e->hv = hv;
+ e->name = strdup (name);
+ e->dir = dir;
+ e->stat_timestamp = 0;
+ bzero (&e->stat, sizeof e->stat);
+ e->symlink_target = 0;
+ e->noent = 0;
+ e->valid = 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);
+
+ /* Now stick in the deleted list. */
+}
+
+/* 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)
+ delete (e, dir);
+}
+
+/* Inode numbers. */
+ino_t ftpfs_next_inode = 2;
+
+/* 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 struct ftpfs_dir_entry *
+update_entry (struct ftpfs_dir_entry *e, const struct stat *st,
+ const char *symlink_target, time_t timestamp, int no_lock)
+{
+ ino_t ino;
+
+ if (e->stat_timestamp == 0)
+ ino = ftpfs_next_inode++;
+ else
+ ino = e->stat.st_ino;
+
+ e->dirent_timestamp = timestamp;
+
+ e->symlink_target = symlink_target ? strdup (symlink_target) : 0;
+ e->stat = *st;
+ e->stat.st_ino = ino;
+ e->stat_timestamp = timestamp;
+
+ return e;
+}
+
+/* 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 limit = dir->fs->params.bulk_stat_limit;
+
+ 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)
+ > limit;
+}
+
+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;
+ struct ftpfs_dir_entry *prev_entry;
+ time_t timestamp;
+};
+
+/* 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 *rds = hook;
+ struct ftpfs_dir_entry *e = lookup (rds->dir, name, 1), *pe;
+
+ if (!e || !update_entry (e, st, symlink_target, rds->timestamp, 0))
+ return ENOMEM;
+
+ e->valid = 1;
+
+ assert (! e->ordered_self_p);
+ assert (! e->ordered_next);
+
+ /* Position E in the ordered chain. */
+ pe = rds->prev_entry; /* Previously seen entry. */
+ if (pe)
+ e->ordered_self_p = &pe->ordered_next; /* Put E after PE. */
+ else
+ e->ordered_self_p = &rds->dir->ordered; /* Put E at beginning. */
+ assert (! *e->ordered_self_p); /* There shouldn't be anything in E's place. */
+
+ *e->ordered_self_p = e; /* Put E there. */
+ rds->prev_entry = e; /* Put the next entry after this one. */
+
+ return 0;
+}
+
+/* Refresh DIR from the directory DIR_NAME in the filesystem FS. */
+static error_t
+refresh_dir (struct ftpfs_dir *dir, time_t timestamp)
+{
+ error_t err;
+ struct ftp_conn *conn;
+ struct dir_fetch_state rds;
+
+ if (dir->timestamp + dir->fs->params.dir_timeout >= timestamp)
+ /* We've already refreshed this directory recently. */
+ return 0;
+
+ err = ftpfs_get_ftp_conn (dir->fs, &conn);
+ if (err)
+ return err;
+
+ /* Clear DIR's ordered entry list. */
+ if (dir->ordered)
+ {
+ struct ftpfs_dir_entry *e, *next;
+ for (e = dir->ordered; e; e = next)
+ {
+ next = e->ordered_next;
+ e->ordered_next = 0;
+ e->ordered_self_p = 0;
+ }
+ dir->ordered = 0;
+ }
+
+ /* Mark directory entries so we can GC them later using sweep. */
+ mark (dir);
+
+ reset_bulk_stat_info (dir);
+
+ /* Refetch the directory from the server. */
+ rds.dir = dir;
+ rds.prev_entry = 0;
+ rds.timestamp = timestamp;
+ err = ftp_conn_get_stats (conn, dir->rmt_path, 1, update_ordered_entry, &rds);
+
+ if (! err)
+ /* GC any directory entries that weren't seen this time. */
+ {
+ dir->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, timestamp);
+}
+
+/* State shared between ftpfs_dir_entry_refresh and update_old_entry. */
+struct refresh_entry_state
+{
+ struct ftpfs_dir_entry *entry;
+ time_t timestamp;
+ /* Prefix to skip at beginning of name returned from listing. */
+ const char *dir_pfx;
+ size_t dir_pfx_len;
+};
+
+/* 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;
+
+ /* Skip the directory part of the name. */
+ if (strncmp (name, res->dir_pfx, res->dir_pfx_len) != 0)
+ return EGRATUITOUS;
+ else
+ name += res->dir_pfx_len;
+
+ if (strcmp (name, res->entry->name) != 0)
+ return EGRATUITOUS;
+
+ if (update_entry (res->entry, st, symlink_target, res->timestamp, 1))
+ return 0;
+ else
+ return ENOMEM;
+}
+
+/* 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->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, timestamp);
+ else
+ {
+ struct ftp_conn *conn;
+ struct refresh_entry_state res;
+ const char *path = path_append (dir, entry->name, &res.dir_pfx_len);
+
+ res.entry = entry;
+ res.timestamp = timestamp;
+ res.dir_pfx = path;
+
+ err = ftpfs_get_ftp_conn (dir->fs, &conn);
+ if (! err)
+ {
+ err =
+ ftp_conn_get_stats (conn, path, 0, update_old_entry, &res);
+ ftpfs_release_ftp_conn (dir->fs, conn);
+ }
+ }
+
+ 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;
+ /* Prefix to skip at beginning of name returned from listing. */
+ const char *dir_pfx;
+ size_t dir_pfx_len;
+};
+
+/* 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;
+
+ /* Skip the directory part of the name. */
+ if (strncmp (name, nes->dir_pfx, nes->dir_pfx_len) != 0)
+ return EGRATUITOUS;
+ else
+ name += nes->dir_pfx_len;
+
+ e = lookup (nes->dir, name, 1);
+ if (!e || !update_entry (e, st, symlink_target, nes->timestamp, 0))
+ return ENOMEM;
+
+ 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)
+{
+ error_t err = 0;
+ time_t timestamp = NOW;
+ struct ftpfs_dir_entry *e;
+
+ if (strcmp (name, ".") == 0)
+ {
+ netfs_nref (dir->node);
+ *node = dir->node;
+ return 0;
+ }
+ else if (strcmp (name, "..") == 0)
+ {
+ 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->dirent_timestamp + dir->fs->params.dirent_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, timestamp);
+ if (! err)
+ e = lookup (dir, name, 0);
+ }
+ else
+ {
+ struct ftp_conn *conn;
+
+ err = ftpfs_get_ftp_conn (dir->fs, &conn);
+ if (! err)
+ {
+ struct new_entry_state nes;
+ const char *path = path_append (dir, name, &nes.dir_pfx_len);
+
+ nes.dir = dir;
+ nes.timestamp = timestamp;
+ nes.dir_pfx = path;
+
+ err = ftp_conn_get_stats (conn, 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. */
+ }
+
+ 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. */
+ {
+ const char *path = path_append (dir, name, 0);
+ err = ftpfs_create_node (e, 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;
+ mutex_unlock (&dir->node->lock);
+ mutex_lock (&e->node->lock);
+ }
+ }
+ else
+ err = ENOENT;
+
+ if (err)
+ {
+ *node = 0;
+ mutex_unlock (&dir->node->lock);
+ }
+
+ 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->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..86697c62
--- /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,
+ 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->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_lookup (super_root_dir, "", &new->root);
+
+ if (err)
+ free (new);
+ else
+ {
+ mutex_unlock (&new->root->lock);
+ *fs = new;
+ }
+
+ return err;
+}
diff --git a/ftpfs/ftpfs.c b/ftpfs/ftpfs.c
new file mode 100644
index 00000000..a50a2d92
--- /dev/null
+++ b/ftpfs/ftpfs.c
@@ -0,0 +1,313 @@
+/* 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. */
+
+#include <string.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 = { 0 };
+
+/* 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 = 0;
+
+extern error_t lookup_server (const char *server,
+ struct ftp_conn_params **params, int *h_err);
+
+/* Prints ftp connection log to stderr. */
+static void
+cntl_debug (struct ftp_conn *conn, int type, const char *txt)
+{
+ char *type_str;
+ static struct mutex debug_lock = MUTEX_INITIALIZER;
+
+ 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);
+ fprintf (stderr, "%u.%s%s\n", (unsigned)conn->hook, type_str, txt);
+ mutex_unlock (&debug_lock);
+}
+
+/* Various default parameters. */
+#define DEFAULT_DIR_TIMEOUT 300
+#define DEFAULT_DIRENT_TIMEOUT 300
+#define DEFAULT_STAT_TIMEOUT 120
+
+#define DEFAULT_BULK_STAT_PERIOD 10
+#define DEFAULT_BULK_STAT_LIMIT 10
+
+#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_DIR_TIMEOUT 5
+#define OPT_DIRENT_TIMEOUT 6
+#define OPT_STAT_TIMEOUT 7
+#define OPT_NODE_CACHE_MAX 8
+
+/* Options usable both at startup and at runtime. */
+static const struct argp_option common_options[] =
+{
+ {"debug", 'D', 0, 0, "Turn on debugging output for ftp connections"},
+ {"no-debug", OPT_NO_DEBUG, 0, OPTION_HIDDEN },
+
+ {0,0,0,0, "Parameters:"},
+ {"dir-timeout", OPT_DIR_TIMEOUT, "SECS", 0,
+ "Amount of time directories are cached (default " _D(DIR_TIMEOUT) ")"},
+ {"dirent-timeout", OPT_DIRENT_TIMEOUT, "SECS", 0,
+ "Amount of time individual directory entries are cached (default "
+ _D(DIRENT_TIMEOUT) ")"},
+ {"stat-timeout", OPT_STAT_TIMEOUT, "SECS", 0,
+ "Amount of 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) ")"},
+
+ {0, 0}
+};
+
+static error_t
+parse_common_opt (int key, char *arg, struct argp_state *state)
+{
+ struct ftpfs_params *params = state->input;
+
+ switch (key)
+ {
+ case 'D':
+ ftpfs_ftp_hooks.cntl_debug = cntl_debug; break;
+ case OPT_NO_DEBUG:
+ ftpfs_ftp_hooks.cntl_debug = 0; break;
+
+ case OPT_NODE_CACHE_MAX:
+ params->node_cache_max = atoi (arg); break;
+ case OPT_DIR_TIMEOUT:
+ params->dir_timeout = atoi (arg); break;
+ case OPT_DIRENT_TIMEOUT:
+ params->dirent_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. */
+
+static const struct argp_child runtime_argp_children[] =
+ { {&common_argp}, {&netfs_std_runtime_argp}, {0} };
+static struct argp runtime_argp =
+ { 0, 0, 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)
+
+ if (ftpfs->params.dir_timeout != DEFAULT_DIR_TIMEOUT)
+ FOPT ("--dir-timeout=%d", ftpfs->params.dir_timeout);
+ if (ftpfs->params.dirent_timeout != DEFAULT_DIRENT_TIMEOUT)
+ FOPT ("--dirent-timeout=%d", ftpfs->params.dirent_timeout);
+ if (ftpfs->params.stat_timeout != DEFAULT_STAT_TIMEOUT)
+ FOPT ("--stat-timeout=%d", ftpfs->params.stat_timeout);
+ if (ftpfs->params.node_cache_max != DEFAULT_NODE_CACHE_MAX)
+ FOPT ("--node-cache-max=%d", ftpfs->params.node_cache_max);
+
+ 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.dir_timeout = DEFAULT_DIR_TIMEOUT;
+ ftpfs_params.dirent_timeout = DEFAULT_DIRENT_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_limit = DEFAULT_BULK_STAT_LIMIT;
+
+ 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, 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..3cf2c0e0
--- /dev/null
+++ b/ftpfs/ftpfs.h
@@ -0,0 +1,225 @@
+/* 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 dirent_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 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
+{
+ time_t dir_timeout;
+ time_t dirent_timeout;
+ time_t stat_timeout;
+
+ time_t bulk_stat_period;
+ unsigned bulk_stat_limit;
+
+ size_t node_cache_max;
+};
+
+/* A particular filesystem. */
+struct ftpfs
+{
+ /* Root of filesystem. */
+ struct node *root;
+
+ struct ftpfs_conn *free_conns;
+ struct ftpfs_conn *conns;
+ spin_lock_t conn_lock;
+
+ struct ftp_conn_params *ftp_params;
+ struct ftp_conn_hooks *ftp_hooks;
+
+ /* 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,
+ 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. */
+error_t ftpfs_dir_lookup (struct ftpfs_dir *dir, const char *name,
+ struct node **node);
+
+#endif /* __FTPFS_H__ */
diff --git a/ftpfs/host.c b/ftpfs/host.c
new file mode 100644
index 00000000..e46a56fa
--- /dev/null
+++ b/ftpfs/host.c
@@ -0,0 +1,156 @@
+/* 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. */
+#if 0 /* XXX don't do passwords */
+ 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 (*passd);
+ free (*host);
+ return ENOMEM;
+ }
+ }
+ else
+#endif
+ /* 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..2fdf8eec
--- /dev/null
+++ b/ftpfs/netfs.c
@@ -0,0 +1,441 @@
+/* ftpfs interface to libnetfs
+
+ 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 <stddef.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <string.h>
+#include <fcntl.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);
+
+ if (! err)
+ err = fshelp_isowner (&node->nn_stat, cred);
+
+ if (! err)
+ {
+ node->nn_stat.st_mtime = mtime->tv_sec;
+ node->nn_stat.st_mtime_usec = mtime->tv_nsec / 1000;
+ node->nn_stat.st_atime = atime->tv_sec;
+ node->nn_stat.st_atime_usec = atime->tv_nsec / 1000;
+ fshelp_touch (&node->nn_stat, TOUCH_CTIME, 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);
+
+ err = vm_allocate (mach_task_self (), (vm_address_t *) data, size, 1);
+
+ 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)
+ vm_deallocate (mach_task_self (), (vm_address_t)*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)
+ vm_deallocate (mach_task_self (),
+ 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)
+{
+ 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);
+}