summaryrefslogtreecommitdiffstats
path: root/lib/vfs/direntry.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/vfs/direntry.c1740
1 files changed, 1740 insertions, 0 deletions
diff --git a/lib/vfs/direntry.c b/lib/vfs/direntry.c
new file mode 100644
index 0000000..32b8594
--- /dev/null
+++ b/lib/vfs/direntry.c
@@ -0,0 +1,1740 @@
+/*
+ Directory cache support
+
+ Copyright (C) 1998-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Pavel Machek <pavel@ucw.cz>, 1998
+ Slava Zanko <slavazanko@gmail.com>, 2010-2013
+ Andrew Borodin <aborodin@vmail.ru> 2010-2022
+
+ This file is part of the Midnight Commander.
+
+ The Midnight Commander 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 3 of the License,
+ or (at your option) any later version.
+
+ The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
+
+ \warning Paths here do _not_ begin with '/', so root directory of
+ archive/site is simply "".
+ */
+
+/** \file
+ * \brief Source: directory cache support
+ *
+ * So that you do not have copy of this in each and every filesystem.
+ *
+ * Very loosely based on tar.c from midnight and archives.[ch] from
+ * avfs by Miklos Szeredi (mszeredi@inf.bme.hu)
+ *
+ * Unfortunately, I was unable to keep all filesystems
+ * uniform. tar-like filesystems use tree structure where each
+ * directory has pointers to its subdirectories. We can do this
+ * because we have full information about our archive.
+ *
+ * At ftp-like filesystems, situation is a little bit different. When
+ * you cd /usr/src/linux/drivers/char, you do _not_ want /usr,
+ * /usr/src, /usr/src/linux and /usr/src/linux/drivers to be
+ * listed. That means that we do not have complete information, and if
+ * /usr is symlink to /4, we will not know. Also we have to time out
+ * entries and things would get messy with tree-like approach. So we
+ * do different trick: root directory is completely special and
+ * completely fake, it contains entries such as 'usr', 'usr/src', ...,
+ * and we'll try to use custom find_entry function.
+ *
+ * \author Pavel Machek <pavel@ucw.cz>
+ * \date 1998
+ *
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <inttypes.h> /* uintmax_t */
+#include <stdarg.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "lib/global.h"
+
+#include "lib/tty/tty.h" /* enable/disable interrupt key */
+#include "lib/util.h" /* canonicalize_pathname_custom() */
+#if 0
+#include "lib/widget.h" /* message() */
+#endif
+
+#include "vfs.h"
+#include "utilvfs.h"
+#include "xdirentry.h"
+#include "gc.h" /* vfs_rmstamp */
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#define CALL(x) \
+ if (VFS_SUBCLASS (me)->x != NULL) \
+ VFS_SUBCLASS (me)->x
+
+/*** file scope type declarations ****************************************************************/
+
+struct dirhandle
+{
+ GList *cur;
+ struct vfs_s_inode *dir;
+};
+
+/*** file scope variables ************************************************************************/
+
+/*** forward declarations (file scope functions) *************************************************/
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+/* We were asked to create entries automagically */
+
+static struct vfs_s_entry *
+vfs_s_automake (struct vfs_class *me, struct vfs_s_inode *dir, char *path, int flags)
+{
+ struct vfs_s_entry *res;
+ char *sep;
+
+ sep = strchr (path, PATH_SEP);
+ if (sep != NULL)
+ *sep = '\0';
+
+ res = vfs_s_generate_entry (me, path, dir, (flags & FL_MKDIR) != 0 ? (0777 | S_IFDIR) : 0777);
+ vfs_s_insert_entry (me, dir, res);
+
+ if (sep != NULL)
+ *sep = PATH_SEP;
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* If the entry is a symlink, find the entry for its target */
+
+static struct vfs_s_entry *
+vfs_s_resolve_symlink (struct vfs_class *me, struct vfs_s_entry *entry, int follow)
+{
+ char *linkname;
+ char *fullname = NULL;
+ struct vfs_s_entry *target;
+
+ if (follow == LINK_NO_FOLLOW)
+ return entry;
+ if (follow == 0)
+ ERRNOR (ELOOP, NULL);
+ if (entry == NULL)
+ ERRNOR (ENOENT, NULL);
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ linkname = entry->ino->linkname;
+ if (linkname == NULL)
+ ERRNOR (EFAULT, NULL);
+
+ /* make full path from relative */
+ if (!IS_PATH_SEP (*linkname))
+ {
+ char *fullpath;
+
+ fullpath = vfs_s_fullpath (me, entry->dir);
+ if (fullpath != NULL)
+ {
+ fullname = g_strconcat (fullpath, PATH_SEP_STR, linkname, (char *) NULL);
+ linkname = fullname;
+ g_free (fullpath);
+ }
+ }
+
+ target =
+ VFS_SUBCLASS (me)->find_entry (me, entry->dir->super->root, linkname, follow - 1, FL_NONE);
+ g_free (fullname);
+ return target;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*
+ * Follow > 0: follow links, serves as loop protect,
+ * == -1: do not follow links
+ */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_tree (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ size_t pseg;
+ struct vfs_s_entry *ent = NULL;
+ char *const pathref = g_strdup (a_path);
+ char *path = pathref;
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ while (root != NULL)
+ {
+ GList *iter;
+
+ while (IS_PATH_SEP (*path)) /* Strip leading '/' */
+ path++;
+
+ if (path[0] == '\0')
+ {
+ g_free (pathref);
+ return ent;
+ }
+
+ for (pseg = 0; path[pseg] != '\0' && !IS_PATH_SEP (path[pseg]); pseg++)
+ ;
+
+ for (iter = g_queue_peek_head_link (root->subdir); iter != NULL; iter = g_list_next (iter))
+ {
+ ent = VFS_ENTRY (iter->data);
+ if (strlen (ent->name) == pseg && strncmp (ent->name, path, pseg) == 0)
+ /* FOUND! */
+ break;
+ }
+
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent == NULL && (flags & (FL_MKFILE | FL_MKDIR)) != 0)
+ ent = vfs_s_automake (me, root, path, flags);
+ if (ent == NULL)
+ {
+ me->verrno = ENOENT;
+ goto cleanup;
+ }
+
+ path += pseg;
+ /* here we must follow leading directories always;
+ only the actual file is optional */
+ ent = vfs_s_resolve_symlink (me, ent,
+ strchr (path, PATH_SEP) != NULL ? LINK_FOLLOW : follow);
+ if (ent == NULL)
+ goto cleanup;
+ root = ent->ino;
+ }
+ cleanup:
+ g_free (pathref);
+ return NULL;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+vfs_s_find_entry_linear (struct vfs_class *me, struct vfs_s_inode *root,
+ const char *a_path, int follow, int flags)
+{
+ struct vfs_s_entry *ent = NULL;
+ char *const path = g_strdup (a_path);
+ GList *iter;
+
+ if (root->super->root != root)
+ vfs_die ("We have to use _real_ root. Always. Sorry.");
+
+ /* canonicalize as well, but don't remove '../' from path */
+ canonicalize_pathname_custom (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
+
+ if ((flags & FL_DIR) == 0)
+ {
+ char *dirname, *name;
+ struct vfs_s_inode *ino;
+
+ dirname = g_path_get_dirname (path);
+ name = g_path_get_basename (path);
+ ino = vfs_s_find_inode (me, root->super, dirname, follow, flags | FL_DIR);
+ ent = vfs_s_find_entry_tree (me, ino, name, follow, flags);
+ g_free (dirname);
+ g_free (name);
+ g_free (path);
+ return ent;
+ }
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+
+ if (ent != NULL && !VFS_SUBCLASS (me)->dir_uptodate (me, ent->ino))
+ {
+#if 1
+ vfs_print_message (_("Directory cache expired for %s"), path);
+#endif
+ vfs_s_free_entry (me, ent);
+ ent = NULL;
+ }
+
+ if (ent == NULL)
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_new_inode (me, root->super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ ent = vfs_s_new_entry (me, path, ino);
+ if (VFS_SUBCLASS (me)->dir_load (me, ino, path) == -1)
+ {
+ vfs_s_free_entry (me, ent);
+ g_free (path);
+ return NULL;
+ }
+
+ vfs_s_insert_entry (me, root, ent);
+
+ iter = g_queue_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
+ ent = iter != NULL ? VFS_ENTRY (iter->data) : NULL;
+ }
+ if (ent == NULL)
+ vfs_die ("find_linear: success but directory is not there\n");
+
+#if 0
+ if (vfs_s_resolve_symlink (me, ent, follow) == NULL)
+ {
+ g_free (path);
+ return NULL;
+ }
+#endif
+ g_free (path);
+ return ent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+
+static struct vfs_s_super *
+vfs_s_new_super (struct vfs_class *me)
+{
+ struct vfs_s_super *super;
+
+ super = g_new0 (struct vfs_s_super, 1);
+ super->me = me;
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline void
+vfs_s_insert_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_super (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (super->root != NULL)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = NULL;
+ }
+
+#if 0
+ /* FIXME: We currently leak small amount of memory, sometimes. Fix it if you can. */
+ if (super->ino_usage != 0)
+ message (D_ERROR, "Direntry warning",
+ "Super ino_usage is %d, memory leak", super->ino_usage);
+
+ if (super->want_stale)
+ message (D_ERROR, "Direntry warning", "%s", "Super has want_stale set");
+#endif
+
+ VFS_SUBCLASS (me)->supers = g_list_remove (VFS_SUBCLASS (me)->supers, super);
+
+ CALL (free_archive) (me, super);
+#ifdef ENABLE_VFS_NET
+ vfs_path_element_free (super->path_element);
+#endif
+ g_free (super->name);
+ g_free (super);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_file_handler_t *
+vfs_s_new_fh (struct vfs_s_inode *ino, gboolean changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = g_new0 (vfs_file_handler_t, 1);
+ vfs_s_init_fh (fh, ino, changed);
+
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free_fh (struct vfs_s_subclass *s, vfs_file_handler_t * fh)
+{
+ if (s->fh_free != NULL)
+ s->fh_free (fh);
+
+ g_free (fh);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Support of archives */
+/* ------------------------ readdir & friends ----------------------------- */
+
+static struct vfs_s_inode *
+vfs_s_inode_from_path (const vfs_path_t * vpath, int flags)
+{
+ struct vfs_s_super *super;
+ struct vfs_s_inode *ino;
+ const char *q;
+ struct vfs_class *me;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ flags & ~FL_FOLLOW);
+ if (ino == NULL && *q == '\0')
+ /* We are asking about / directory of ftp server: assume it exists */
+ ino =
+ vfs_s_find_inode (me, super, q,
+ (flags & FL_FOLLOW) != 0 ? LINK_FOLLOW : LINK_NO_FOLLOW,
+ FL_DIR | (flags & ~FL_FOLLOW));
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+vfs_s_opendir (const vfs_path_t * vpath)
+{
+ struct vfs_s_inode *dir;
+ struct dirhandle *info;
+ struct vfs_class *me;
+
+ dir = vfs_s_inode_from_path (vpath, FL_DIR | FL_FOLLOW);
+ if (dir == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISDIR (dir->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ return NULL;
+ }
+
+ dir->st.st_nlink++;
+#if 0
+ if (dir->subdir == NULL) /* This can actually happen if we allow empty directories */
+ {
+ me->verrno = EAGAIN;
+ return NULL;
+ }
+#endif
+ info = g_new (struct dirhandle, 1);
+ info->cur = g_queue_peek_head_link (dir->subdir);
+ info->dir = dir;
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+vfs_s_readdir (void *data)
+{
+ struct vfs_dirent *dir = NULL;
+ struct dirhandle *info = (struct dirhandle *) data;
+ const char *name;
+
+ if (info->cur == NULL || info->cur->data == NULL)
+ return NULL;
+
+ name = VFS_ENTRY (info->cur->data)->name;
+ if (name != NULL)
+ dir = vfs_dirent_init (NULL, name, 0);
+ else
+ vfs_die ("Null in structure-cannot happen");
+
+ info->cur = g_list_next (info->cur);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_closedir (void *data)
+{
+ struct dirhandle *info = (struct dirhandle *) data;
+ struct vfs_s_inode *dir = info->dir;
+
+ vfs_s_free_inode (dir->super->me, dir);
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ data = vfs_s_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ vfs_s_closedir (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+static int
+vfs_s_internal_stat (const vfs_path_t * vpath, struct stat *buf, int flag)
+{
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, flag);
+ if (ino == NULL)
+ return (-1);
+ *buf = ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct vfs_s_inode *ino;
+ size_t len;
+ struct vfs_class *me;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return (-1);
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ if (!S_ISLNK (ino->st.st_mode))
+ {
+ me->verrno = EINVAL;
+ return (-1);
+ }
+
+ if (ino->linkname == NULL)
+ {
+ me->verrno = EFAULT;
+ return (-1);
+ }
+
+ len = strlen (ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ memcpy (buf, ino->linkname, len);
+ return len;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+
+ if (file->linear == LS_LINEAR_PREOPEN)
+ {
+ if (VFS_SUBCLASS (me)->linear_start (me, file, file->pos) == 0)
+ return (-1);
+ }
+
+ if (file->linear == LS_LINEAR_CLOSED)
+ vfs_die ("linear_start() did not set linear_state!");
+
+ if (file->linear == LS_LINEAR_OPEN)
+ return VFS_SUBCLASS (me)->linear_read (me, file, buffer, count);
+
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = read (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_read: This should not happen\n");
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+vfs_s_write (void *fh, const char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_class *me = VFS_FILE_HANDLER_SUPER (fh)->me;
+
+ if (file->linear != LS_NOT_LINEAR)
+ vfs_die ("no writing to linear files, please");
+
+ file->changed = TRUE;
+ if (file->handle != -1)
+ {
+ ssize_t n;
+
+ n = write (file->handle, buffer, count);
+ if (n < 0)
+ me->verrno = errno;
+ return n;
+ }
+ vfs_die ("vfs_s_write: This should not happen\n");
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+vfs_s_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ off_t size = file->ino->st.st_size;
+
+ if (file->linear == LS_LINEAR_OPEN)
+ vfs_die ("cannot lseek() after linear_read!");
+
+ if (file->handle != -1)
+ { /* If we have local file opened, we want to work with it */
+ off_t retval;
+
+ retval = lseek (file->handle, offset, whence);
+ if (retval == -1)
+ VFS_FILE_HANDLER_SUPER (fh)->me->verrno = errno;
+ return retval;
+ }
+
+ switch (whence)
+ {
+ case SEEK_CUR:
+ offset += file->pos;
+ break;
+ case SEEK_END:
+ offset += size;
+ break;
+ default:
+ break;
+ }
+ if (offset < 0)
+ file->pos = 0;
+ else if (offset < size)
+ file->pos = offset;
+ else
+ file->pos = size;
+ return file->pos;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ struct vfs_s_super *super = VFS_FILE_HANDLER_SUPER (fh);
+ struct vfs_class *me = super->me;
+ struct vfs_s_subclass *sub = VFS_SUBCLASS (me);
+ int res = 0;
+
+ if (me == NULL)
+ return (-1);
+
+ super->fd_usage--;
+ if (super->fd_usage == 0)
+ vfs_stamp_create (me, VFS_FILE_HANDLER_SUPER (fh));
+
+ if (file->linear == LS_LINEAR_OPEN)
+ sub->linear_close (me, fh);
+ if (sub->fh_close != NULL)
+ res = sub->fh_close (me, fh);
+ if ((me->flags & VFSF_USETMP) != 0 && file->changed && sub->file_store != NULL)
+ {
+ char *s;
+
+ s = vfs_s_fullpath (me, file->ino);
+
+ if (s == NULL)
+ res = -1;
+ else
+ {
+ res = sub->file_store (me, fh, s, file->ino->localname);
+ g_free (s);
+ }
+ vfs_s_invalidate (me, super);
+ }
+
+ if (file->handle != -1)
+ {
+ close (file->handle);
+ file->handle = -1;
+ }
+
+ vfs_s_free_inode (me, file->ino);
+ vfs_s_free_fh (sub, fh);
+
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_print_stats (const char *fs_name, const char *action,
+ const char *file_name, off_t have, off_t need)
+{
+ if (need != 0)
+ vfs_print_message (_("%s: %s: %s %3d%% (%lld) bytes transferred"), fs_name, action,
+ file_name, (int) ((double) have * 100 / need), (long long) have);
+ else
+ vfs_print_message (_("%s: %s: %s %lld bytes transferred"), fs_name, action, file_name,
+ (long long) have);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ------------------------------- mc support ---------------------------- */
+
+static void
+vfs_s_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ GList *iter;
+
+ for (iter = VFS_SUBCLASS (me)->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ const struct vfs_s_super *super = (const struct vfs_s_super *) iter->data;
+ char *name;
+
+ name = g_strconcat (super->name, PATH_SEP_STR, me->prefix, VFS_PATH_URL_DELIMITER,
+ /* super->current_dir->name, */ (char *) NULL);
+ func (name);
+ g_free (name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_ferrno (struct vfs_class *me)
+{
+ return me->verrno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Get local copy of the given file. We reuse the existing file cache
+ * for remote filesystems. Archives use standard VFS facilities.
+ */
+
+static vfs_path_t *
+vfs_s_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *local = NULL;
+
+ if (vpath == NULL)
+ return NULL;
+
+ fh = vfs_s_open (vpath, O_RDONLY, 0);
+
+ if (fh != NULL)
+ {
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if ((me->flags & VFSF_USETMP) != 0 && fh->ino != NULL)
+ local = vfs_path_from_str_flags (fh->ino->localname, VPF_NO_CANON);
+
+ vfs_s_close (fh);
+ }
+
+ return local;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Return the local copy. Since we are using our cache, we do nothing -
+ * the cache will be removed when the archive is closed.
+ */
+
+static int
+vfs_s_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ (void) vpath;
+ (void) local;
+ (void) has_changed;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+vfs_s_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ struct vfs_class *me;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ switch (ctlop)
+ {
+ case VFS_SETCTL_STALE_DATA:
+ {
+ struct vfs_s_inode *ino;
+
+ ino = vfs_s_inode_from_path (vpath, 0);
+ if (ino == NULL)
+ return 0;
+ if (arg != NULL)
+ ino->super->want_stale = TRUE;
+ else
+ {
+ ino->super->want_stale = FALSE;
+ vfs_s_invalidate (me, ino->super);
+ }
+ return 1;
+ }
+ case VFS_SETCTL_LOGFILE:
+ me->logfile = fopen ((char *) arg, "w");
+ return 1;
+ case VFS_SETCTL_FLUSH:
+ me->flush = TRUE;
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+static vfsid
+vfs_s_getid (const vfs_path_t * vpath)
+{
+ struct vfs_s_super *archive = NULL;
+ const char *p;
+
+ p = vfs_s_get_path (vpath, &archive, FL_NO_OPEN);
+ if (p == NULL)
+ return NULL;
+
+ return (vfsid) archive;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_nothingisopen (vfsid id)
+{
+ return (VFS_SUPER (id)->fd_usage <= 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+vfs_s_free (vfsid id)
+{
+ vfs_s_free_super (VFS_SUPER (id)->me, VFS_SUPER (id));
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+vfs_s_dir_uptodate (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ gint64 tim;
+
+ if (me->flush)
+ {
+ me->flush = FALSE;
+ return 0;
+ }
+
+ tim = g_get_monotonic_time ();
+
+ return (tim < ino->timestamp);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_new_inode (struct vfs_class *me, struct vfs_s_super *super, struct stat *initstat)
+{
+ struct vfs_s_inode *ino;
+
+ ino = g_try_new0 (struct vfs_s_inode, 1);
+ if (ino == NULL)
+ return NULL;
+
+ if (initstat != NULL)
+ ino->st = *initstat;
+ ino->super = super;
+ ino->subdir = g_queue_new ();
+ ino->st.st_nlink = 0;
+ ino->st.st_ino = VFS_SUBCLASS (me)->inode_counter++;
+ ino->st.st_dev = VFS_SUBCLASS (me)->rdev;
+
+ super->ino_usage++;
+
+ CALL (init_inode) (me, ino);
+
+ return ino;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino == NULL)
+ vfs_die ("Don't pass NULL to me");
+
+ /* ==0 can happen if freshly created entry is deleted */
+ if (ino->st.st_nlink > 1)
+ {
+ ino->st.st_nlink--;
+ return;
+ }
+
+ while (g_queue_get_length (ino->subdir) != 0)
+ {
+ struct vfs_s_entry *entry;
+
+ entry = VFS_ENTRY (g_queue_peek_head (ino->subdir));
+ vfs_s_free_entry (me, entry);
+ }
+
+ g_queue_free (ino->subdir);
+ ino->subdir = NULL;
+
+ CALL (free_inode) (me, ino);
+ g_free (ino->linkname);
+ if ((me->flags & VFSF_USETMP) != 0 && ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ g_free (ino->localname);
+ }
+ ino->super->ino_usage--;
+ g_free (ino);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_new_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+ entry->ino->ent = entry;
+ CALL (init_entry) (me, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_free_entry (struct vfs_class *me, struct vfs_s_entry *ent)
+{
+ if (ent->dir != NULL)
+ g_queue_remove (ent->dir->subdir, ent);
+
+ MC_PTR_FREE (ent->name);
+
+ if (ent->ino != NULL)
+ {
+ ent->ino->ent = NULL;
+ vfs_s_free_inode (me, ent->ino);
+ }
+
+ g_free (ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_insert_entry (struct vfs_class *me, struct vfs_s_inode *dir, struct vfs_s_entry *ent)
+{
+ (void) me;
+
+ ent->dir = dir;
+
+ ent->ino->st.st_nlink++;
+ g_queue_push_tail (dir->subdir, ent);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_entry_compare (const void *a, const void *b)
+{
+ const struct vfs_s_entry *e = (const struct vfs_s_entry *) a;
+ const char *name = (const char *) b;
+
+ return strcmp (e->name, name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct stat *
+vfs_s_default_stat (struct vfs_class *me, mode_t mode)
+{
+ static struct stat st;
+ mode_t myumask;
+
+ (void) me;
+
+ myumask = umask (022);
+ umask (myumask);
+ mode &= ~myumask;
+
+ st.st_mode = mode;
+ st.st_ino = 0;
+ st.st_dev = 0;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = 0;
+#endif
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ st.st_blksize = 512;
+#endif
+ st.st_size = 0;
+
+ st.st_mtime = st.st_atime = st.st_ctime = time (NULL);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ st.st_atim.tv_nsec = st.st_mtim.tv_nsec = st.st_ctim.tv_nsec = 0;
+#endif
+
+ vfs_adjust_stat (&st);
+
+ return &st;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Calculate number of st_blocks using st_size and st_blksize.
+ * In according to stat(2), st_blocks is the size in 512-byte units.
+ *
+ * @param s stat info
+ */
+
+void
+vfs_adjust_stat (struct stat *s)
+{
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+ if (s->st_size == 0)
+ s->st_blocks = 0;
+ else
+ {
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ blkcnt_t ioblocks;
+ blksize_t ioblock_size;
+
+ /* 1. Calculate how many IO blocks are occupied */
+ ioblocks = 1 + (s->st_size - 1) / s->st_blksize;
+ /* 2. Calculate size of st_blksize in 512-byte units */
+ ioblock_size = 1 + (s->st_blksize - 1) / 512;
+ /* 3. Calculate number of blocks */
+ s->st_blocks = ioblocks * ioblock_size;
+#else
+ /* Let IO block size is 512 bytes */
+ s->st_blocks = 1 + (s->st_size - 1) / 512;
+#endif /* HAVE_STRUCT_STAT_ST_BLKSIZE */
+ }
+#endif /* HAVE_STRUCT_STAT_ST_BLOCKS */
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_entry *
+vfs_s_generate_entry (struct vfs_class *me, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_s_inode *inode;
+ struct stat *st;
+
+ st = vfs_s_default_stat (me, mode);
+ inode = vfs_s_new_inode (me, parent->super, st);
+
+ return vfs_s_new_entry (me, name, inode);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+struct vfs_s_inode *
+vfs_s_find_inode (struct vfs_class *me, const struct vfs_s_super *super,
+ const char *path, int follow, int flags)
+{
+ struct vfs_s_entry *ent;
+
+ if (((me->flags & VFSF_REMOTE) == 0) && (*path == '\0'))
+ return super->root;
+
+ ent = VFS_SUBCLASS (me)->find_entry (me, super->root, path, follow, flags);
+ return (ent != NULL ? ent->ino : NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Ook, these were functions around directory entries / inodes */
+/* -------------------------------- superblock games -------------------------- */
+/**
+ * get superlock object by vpath
+ *
+ * @param vpath path
+ * @return superlock object or NULL if not found
+ */
+
+struct vfs_s_super *
+vfs_get_super_by_vpath (const vfs_path_t * vpath)
+{
+ GList *iter;
+ void *cookie = NULL;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+ struct vfs_s_super *super = NULL;
+ vfs_path_t *vpath_archive;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ if (subclass->archive_check != NULL)
+ {
+ cookie = subclass->archive_check (vpath_archive);
+ if (cookie == NULL)
+ goto ret;
+ }
+
+ if (subclass->archive_same == NULL)
+ goto ret;
+
+ for (iter = subclass->supers; iter != NULL; iter = g_list_next (iter))
+ {
+ int i;
+
+ super = VFS_SUPER (iter->data);
+
+ /* 0 == other, 1 == same, return it, 2 == other but stop scanning */
+ i = subclass->archive_same (path_element, super, vpath_archive, cookie);
+ if (i == 1)
+ goto ret;
+ if (i != 0)
+ break;
+
+ super = NULL;
+ }
+
+ ret:
+ vfs_path_free (vpath_archive, TRUE);
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * get path from last VFS-element and create corresponding superblock
+ *
+ * @param vpath source path object
+ * @param archive pointer to object for store newly created superblock
+ * @param flags flags
+ *
+ * @return path from last VFS-element
+ */
+const char *
+vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags)
+{
+ const char *retval = "";
+ int result = -1;
+ struct vfs_s_super *super;
+ const vfs_path_element_t *path_element;
+ struct vfs_s_subclass *subclass;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ if (path_element->path != NULL)
+ retval = path_element->path;
+
+ super = vfs_get_super_by_vpath (vpath);
+ if (super != NULL)
+ goto return_success;
+
+ if ((flags & FL_NO_OPEN) != 0)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+
+ subclass = VFS_SUBCLASS (path_element->class);
+
+ super = subclass->new_archive != NULL ?
+ subclass->new_archive (path_element->class) : vfs_s_new_super (path_element->class);
+
+ if (subclass->open_archive != NULL)
+ {
+ vfs_path_t *vpath_archive;
+
+ vpath_archive = vfs_path_clone (vpath);
+ vfs_path_remove_element_by_index (vpath_archive, -1);
+
+ result = subclass->open_archive (super, vpath_archive, path_element);
+ vfs_path_free (vpath_archive, TRUE);
+ }
+ if (result == -1)
+ {
+ vfs_s_free_super (path_element->class, super);
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ if (super->name == NULL)
+ vfs_die ("You have to fill name\n");
+ if (super->root == NULL)
+ vfs_die ("You have to fill root inode\n");
+
+ vfs_s_insert_super (path_element->class, super);
+ vfs_stamp_create (path_element->class, super);
+
+ return_success:
+ *archive = super;
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_invalidate (struct vfs_class *me, struct vfs_s_super *super)
+{
+ if (!super->want_stale)
+ {
+ vfs_s_free_inode (me, super->root);
+ super->root = vfs_s_new_inode (me, super, vfs_s_default_stat (me, S_IFDIR | 0755));
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+char *
+vfs_s_fullpath (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ if (ino->ent == NULL)
+ ERRNOR (EAGAIN, NULL);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ {
+ /* archives */
+ char *path;
+
+ path = g_strdup (ino->ent->name);
+
+ while (TRUE)
+ {
+ char *newpath;
+
+ ino = ino->ent->dir;
+ if (ino == ino->super->root)
+ break;
+
+ newpath = g_strconcat (ino->ent->name, PATH_SEP_STR, path, (char *) NULL);
+ g_free (path);
+ path = newpath;
+ }
+ return path;
+ }
+
+ /* remote systems */
+ if (ino->ent->dir == NULL || ino->ent->dir->ent == NULL)
+ return g_strdup (ino->ent->name);
+
+ return g_strconcat (ino->ent->dir->ent->name, PATH_SEP_STR, ino->ent->name, (char *) NULL);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_s_init_fh (vfs_file_handler_t * fh, struct vfs_s_inode *ino, gboolean changed)
+{
+ fh->ino = ino;
+ fh->handle = -1;
+ fh->changed = changed;
+ fh->linear = LS_NOT_LINEAR;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* --------------------------- stat and friends ---------------------------- */
+
+void *
+vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ gboolean was_changed = FALSE;
+ vfs_file_handler_t *fh;
+ struct vfs_s_super *super;
+ const char *q;
+ struct vfs_s_inode *ino;
+ struct vfs_class *me;
+ struct vfs_s_subclass *s;
+
+ q = vfs_s_get_path (vpath, &super, 0);
+ if (q == NULL)
+ return NULL;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+
+ ino = vfs_s_find_inode (me, super, q, LINK_FOLLOW, FL_NONE);
+ if (ino != NULL && (flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
+ {
+ me->verrno = EEXIST;
+ return NULL;
+ }
+
+ s = VFS_SUBCLASS (me);
+
+ if (ino == NULL)
+ {
+ char *name;
+ struct vfs_s_entry *ent;
+ struct vfs_s_inode *dir;
+
+ /* If the filesystem is read-only, disable file creation */
+ if ((flags & O_CREAT) == 0 || me->write == NULL)
+ return NULL;
+
+ name = g_path_get_dirname (q);
+ dir = vfs_s_find_inode (me, super, name, LINK_FOLLOW, FL_DIR);
+ g_free (name);
+ if (dir == NULL)
+ return NULL;
+
+ name = g_path_get_basename (q);
+ ent = vfs_s_generate_entry (me, name, dir, 0755);
+ ino = ent->ino;
+ vfs_s_insert_entry (me, dir, ent);
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0)
+ {
+ int tmp_handle;
+ vfs_path_t *tmp_vpath;
+
+ tmp_handle = vfs_mkstemps (&tmp_vpath, me->name, name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (tmp_handle == -1)
+ {
+ g_free (name);
+ return NULL;
+ }
+
+ close (tmp_handle);
+ }
+
+ g_free (name);
+ was_changed = TRUE;
+ }
+
+ if (S_ISDIR (ino->st.st_mode))
+ {
+ me->verrno = EISDIR;
+ return NULL;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, was_changed) : vfs_s_new_fh (ino, was_changed);
+
+ if (IS_LINEAR (flags))
+ {
+ if (s->linear_start != NULL)
+ {
+ vfs_print_message ("%s", _("Starting linear transfer..."));
+ fh->linear = LS_LINEAR_PREOPEN;
+ }
+ }
+ else
+ {
+ if (s->fh_open != NULL && s->fh_open (me, fh, flags, mode) != 0)
+ {
+ vfs_s_free_fh (s, fh);
+ return NULL;
+ }
+ }
+
+ if ((VFS_CLASS (s)->flags & VFSF_USETMP) != 0 && fh->ino->localname != NULL)
+ {
+ fh->handle = open (fh->ino->localname, NO_LINEAR (flags), mode);
+ if (fh->handle == -1)
+ {
+ vfs_s_free_fh (s, fh);
+ me->verrno = errno;
+ return NULL;
+ }
+ }
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (me, (vfsid) super);
+ super->fd_usage++;
+ fh->ino->st.st_nlink++;
+ return fh;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_FOLLOW);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return vfs_s_internal_stat (vpath, buf, FL_NONE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_fstat (void *fh, struct stat *buf)
+{
+ *buf = VFS_FILE_HANDLER (fh)->ino->st;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_retrieve_file (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ /* If you want reget, you'll have to open file with O_LINEAR */
+ off_t total = 0;
+ char buffer[BUF_8K];
+ int handle;
+ ssize_t n;
+ off_t stat_size = ino->st.st_size;
+ vfs_file_handler_t *fh = NULL;
+ vfs_path_t *tmp_vpath;
+ struct vfs_s_subclass *s = VFS_SUBCLASS (me);
+
+ if ((me->flags & VFSF_USETMP) == 0)
+ return (-1);
+
+ handle = vfs_mkstemps (&tmp_vpath, me->name, ino->ent->name);
+ ino->localname = vfs_path_free (tmp_vpath, FALSE);
+ if (handle == -1)
+ {
+ me->verrno = errno;
+ goto error_4;
+ }
+
+ fh = s->fh_new != NULL ? s->fh_new (ino, FALSE) : vfs_s_new_fh (ino, FALSE);
+
+ if (s->linear_start (me, fh, 0) == 0)
+ goto error_3;
+
+ /* Clear the interrupt status */
+ tty_got_interrupt ();
+ tty_enable_interrupt_key ();
+
+ while ((n = s->linear_read (me, fh, buffer, sizeof (buffer))) != 0)
+ {
+ int t;
+
+ if (n < 0)
+ goto error_1;
+
+ total += n;
+ vfs_s_print_stats (me->name, _("Getting file"), ino->ent->name, total, stat_size);
+
+ if (tty_got_interrupt ())
+ goto error_1;
+
+ t = write (handle, buffer, n);
+ if (t != n)
+ {
+ if (t == -1)
+ me->verrno = errno;
+ goto error_1;
+ }
+ }
+ s->linear_close (me, fh);
+ close (handle);
+
+ tty_disable_interrupt_key ();
+ vfs_s_free_fh (s, fh);
+ return 0;
+
+ error_1:
+ s->linear_close (me, fh);
+ error_3:
+ tty_disable_interrupt_key ();
+ close (handle);
+ unlink (ino->localname);
+ error_4:
+ MC_PTR_FREE (ino->localname);
+ if (fh != NULL)
+ vfs_s_free_fh (s, fh);
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------------------------- Stamping support -------------------------- */
+
+/* Initialize one of our subclasses - fill common functions */
+void
+vfs_init_class (struct vfs_class *vclass, const char *name, vfs_flags_t flags, const char *prefix)
+{
+ memset (vclass, 0, sizeof (struct vfs_class));
+
+ vclass->name = name;
+ vclass->flags = flags;
+ vclass->prefix = prefix;
+
+ vclass->fill_names = vfs_s_fill_names;
+ vclass->open = vfs_s_open;
+ vclass->close = vfs_s_close;
+ vclass->read = vfs_s_read;
+ if ((vclass->flags & VFSF_READONLY) == 0)
+ vclass->write = vfs_s_write;
+ vclass->opendir = vfs_s_opendir;
+ vclass->readdir = vfs_s_readdir;
+ vclass->closedir = vfs_s_closedir;
+ vclass->stat = vfs_s_stat;
+ vclass->lstat = vfs_s_lstat;
+ vclass->fstat = vfs_s_fstat;
+ vclass->readlink = vfs_s_readlink;
+ vclass->chdir = vfs_s_chdir;
+ vclass->ferrno = vfs_s_ferrno;
+ vclass->lseek = vfs_s_lseek;
+ vclass->getid = vfs_s_getid;
+ vclass->nothingisopen = vfs_s_nothingisopen;
+ vclass->free = vfs_s_free;
+ vclass->setctl = vfs_s_setctl;
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ {
+ vclass->getlocalcopy = vfs_s_getlocalcopy;
+ vclass->ungetlocalcopy = vfs_s_ungetlocalcopy;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_subclass (struct vfs_s_subclass *sub, const char *name, vfs_flags_t flags,
+ const char *prefix)
+{
+ struct vfs_class *vclass = VFS_CLASS (sub);
+ size_t len;
+ char *start;
+
+ vfs_init_class (vclass, name, flags, prefix);
+
+ len = sizeof (struct vfs_s_subclass) - sizeof (struct vfs_class);
+ start = (char *) sub + sizeof (struct vfs_class);
+ memset (start, 0, len);
+
+ if ((vclass->flags & VFSF_USETMP) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else if ((vclass->flags & VFSF_REMOTE) != 0)
+ sub->find_entry = vfs_s_find_entry_linear;
+ else
+ sub->find_entry = vfs_s_find_entry_tree;
+ sub->dir_uptodate = vfs_s_dir_uptodate;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Find VFS id for given directory name */
+
+vfsid
+vfs_getid (const vfs_path_t * vpath)
+{
+ const struct vfs_class *me;
+
+ me = vfs_path_get_last_path_vfs (vpath);
+ if (me == NULL || me->getid == NULL)
+ return NULL;
+
+ return me->getid (vpath);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* ----------- Utility functions for networked filesystems -------------- */
+
+#ifdef ENABLE_VFS_NET
+int
+vfs_s_select_on_two (int fd1, int fd2)
+{
+ fd_set set;
+ struct timeval time_out;
+ int v;
+ int maxfd = MAX (fd1, fd2) + 1;
+
+ time_out.tv_sec = 1;
+ time_out.tv_usec = 0;
+ FD_ZERO (&set);
+ FD_SET (fd1, &set);
+ FD_SET (fd2, &set);
+
+ v = select (maxfd, &set, 0, 0, &time_out);
+ if (v <= 0)
+ return v;
+ if (FD_ISSET (fd1, &set))
+ return 1;
+ if (FD_ISSET (fd2, &set))
+ return 2;
+ return (-1);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line (struct vfs_class *me, int sock, char *buf, int buf_len, char term)
+{
+ FILE *logfile = me->logfile;
+ int i;
+ char c;
+
+ for (i = 0; i < buf_len - 1; i++, buf++)
+ {
+ if (read (sock, buf, sizeof (char)) <= 0)
+ return 0;
+
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (buf, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+
+ if (*buf == term)
+ {
+ *buf = '\0';
+ return 1;
+ }
+ }
+
+ /* Line is too long - terminate buffer and discard the rest of line */
+ *buf = '\0';
+ while (read (sock, &c, sizeof (c)) > 0)
+ {
+ if (logfile != NULL)
+ {
+ size_t ret1;
+ int ret2;
+
+ ret1 = fwrite (&c, 1, 1, logfile);
+ ret2 = fflush (logfile);
+ (void) ret1;
+ (void) ret2;
+ }
+ if (c == '\n')
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+int
+vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd)
+{
+ int i;
+ int res = 0;
+
+ (void) me;
+
+ tty_enable_interrupt_key ();
+
+ for (i = 0; i < size - 1; i++)
+ {
+ ssize_t n;
+
+ n = read (fd, &buffer[i], 1);
+ if (n == -1 && errno == EINTR)
+ {
+ buffer[i] = '\0';
+ res = EINTR;
+ goto ret;
+ }
+ if (n == 0)
+ {
+ buffer[i] = '\0';
+ goto ret;
+ }
+ if (buffer[i] == '\n')
+ {
+ buffer[i] = '\0';
+ res = 1;
+ goto ret;
+ }
+ }
+
+ buffer[size - 1] = '\0';
+
+ ret:
+ tty_disable_interrupt_key ();
+
+ return res;
+}
+#endif /* ENABLE_VFS_NET */
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Normalize filenames start position
+ */
+
+void
+vfs_s_normalize_filename_leading_spaces (struct vfs_s_inode *root_inode, size_t final_num_spaces)
+{
+ GList *iter;
+
+ for (iter = g_queue_peek_head_link (root_inode->subdir); iter != NULL;
+ iter = g_list_next (iter))
+ {
+ struct vfs_s_entry *entry = VFS_ENTRY (iter->data);
+
+ if ((size_t) entry->leading_spaces > final_num_spaces)
+ {
+ char *source_name, *spacer;
+
+ source_name = entry->name;
+ spacer = g_strnfill ((size_t) entry->leading_spaces - final_num_spaces, ' ');
+ entry->name = g_strconcat (spacer, source_name, (char *) NULL);
+ g_free (spacer);
+ g_free (source_name);
+ }
+
+ entry->leading_spaces = -1;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */