summaryrefslogtreecommitdiffstats
path: root/src/vfs/extfs/extfs.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/vfs/extfs/extfs.c1722
1 files changed, 1722 insertions, 0 deletions
diff --git a/src/vfs/extfs/extfs.c b/src/vfs/extfs/extfs.c
new file mode 100644
index 0000000..d6ef7af
--- /dev/null
+++ b/src/vfs/extfs/extfs.c
@@ -0,0 +1,1722 @@
+/*
+ Virtual File System: External file system.
+
+ Copyright (C) 1995-2023
+ Free Software Foundation, Inc.
+
+ Written by:
+ Jakub Jelinek, 1995
+ Pavel Machek, 1998
+ Andrew T. Veliath, 1999
+ Slava Zanko <slavazanko@gmail.com>, 2013
+
+ 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/>.
+ */
+
+/**
+ * \file
+ * \brief Source: Virtual File System: External file system
+ * \author Jakub Jelinek
+ * \author Pavel Machek
+ * \author Andrew T. Veliath
+ * \date 1995, 1998, 1999
+ */
+
+/* Namespace: init_extfs */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include "lib/global.h"
+#include "lib/fileloc.h"
+#include "lib/mcconfig.h"
+#include "lib/util.h"
+#include "lib/widget.h" /* message() */
+
+#include "src/execute.h" /* For shell_execute */
+
+#include "lib/vfs/vfs.h"
+#include "lib/vfs/utilvfs.h"
+#include "lib/vfs/xdirentry.h"
+#include "lib/vfs/gc.h" /* vfs_rmstamp */
+
+#include "extfs.h"
+
+/*** global variables ****************************************************************************/
+
+/*** file scope macro definitions ****************************************************************/
+
+#undef ERRNOR
+#define ERRNOR(x,y) do { my_errno = x; return y; } while(0)
+
+#define RECORDSIZE 512
+
+#define EXTFS_SUPER(a) ((struct extfs_super_t *) (a))
+
+/*** file scope type declarations ****************************************************************/
+
+struct extfs_super_t
+{
+ struct vfs_s_super base; /* base class */
+
+ int fstype;
+ char *local_name;
+ struct stat local_stat;
+ dev_t rdev;
+};
+
+typedef struct
+{
+ char *path;
+ char *prefix;
+ gboolean need_archive;
+} extfs_plugin_info_t;
+
+/*** forward declarations (file scope functions) *************************************************/
+
+static struct vfs_s_entry *extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list);
+
+/*** file scope variables ************************************************************************/
+
+static GArray *extfs_plugins = NULL;
+
+static gboolean errloop;
+static gboolean notadir;
+
+static struct vfs_s_subclass extfs_subclass;
+static struct vfs_class *vfs_extfs_ops = VFS_CLASS (&extfs_subclass);
+
+static int my_errno = 0;
+
+/* --------------------------------------------------------------------------------------------- */
+/*** file scope functions ************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+static struct extfs_super_t *
+extfs_super_new (struct vfs_class *me, const char *name, const vfs_path_t * local_name_vpath,
+ int fstype)
+{
+ struct extfs_super_t *super;
+ struct vfs_s_super *vsuper;
+
+ super = g_new0 (struct extfs_super_t, 1);
+ vsuper = VFS_SUPER (super);
+
+ vsuper->me = me;
+ vsuper->name = g_strdup (name);
+
+ super->fstype = fstype;
+
+ if (local_name_vpath != NULL)
+ {
+ super->local_name = g_strdup (vfs_path_get_last_path_str (local_name_vpath));
+ mc_stat (local_name_vpath, &super->local_stat);
+ }
+
+ VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super);
+
+ return super;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* unlike vfs_s_new_entry(), inode->ent is kept */
+static struct vfs_s_entry *
+extfs_entry_new (struct vfs_class *me, const char *name, struct vfs_s_inode *inode)
+{
+ struct vfs_s_entry *entry;
+
+ (void) me;
+
+ entry = g_new0 (struct vfs_s_entry, 1);
+
+ entry->name = g_strdup (name);
+ entry->ino = inode;
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_name (void *data, void *user_data)
+{
+ struct vfs_s_super *a = VFS_SUPER (data);
+ fill_names_f func = (fill_names_f) user_data;
+ extfs_plugin_info_t *info;
+ char *name;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, EXTFS_SUPER (a)->fstype);
+ name =
+ g_strconcat (a->name != NULL ? a->name : "", PATH_SEP_STR, info->prefix,
+ VFS_PATH_URL_DELIMITER, (char *) NULL);
+ func (name);
+ g_free (name);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gint
+extfs_cmp_archive (const void *a, const void *b)
+{
+ const struct vfs_s_super *ar = (const struct vfs_s_super *) a;
+ const char *archive_name = (const char *) b;
+
+ return (ar->name != NULL && strcmp (ar->name, archive_name) == 0) ? 0 : 1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_generate_entry (struct extfs_super_t *archive, const char *name, struct vfs_s_inode *parent,
+ mode_t mode)
+{
+ struct vfs_class *me = VFS_SUPER (archive)->me;
+ struct stat st;
+ mode_t myumask;
+ struct vfs_s_inode *inode;
+ struct vfs_s_entry *entry;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = VFS_SUPER (archive)->ino_usage++;
+ st.st_dev = archive->rdev;
+ myumask = umask (022);
+ umask (myumask);
+ st.st_mode = mode & ~myumask;
+ st.st_uid = getuid ();
+ st.st_gid = getgid ();
+ st.st_mtime = time (NULL);
+ st.st_atime = st.st_mtime;
+ st.st_ctime = st.st_mtime;
+ st.st_nlink = 1;
+
+ inode = vfs_s_new_inode (me, VFS_SUPER (archive), &st);
+ entry = vfs_s_new_entry (me, name, inode);
+ if (parent != NULL)
+ vfs_s_insert_entry (me, parent, entry);
+
+ return entry;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry_int (struct vfs_s_inode *dir, const char *name, GSList * list, int flags)
+{
+ struct vfs_s_entry *pent, *pdir;
+ const char *p, *name_end;
+ char *q;
+ char c = PATH_SEP;
+ struct extfs_super_t *super;
+
+ if (g_path_is_absolute (name))
+ {
+ /* Handle absolute paths */
+ name = g_path_skip_root (name);
+ dir = dir->super->root;
+ }
+
+ super = EXTFS_SUPER (dir->super);
+ pent = dir->ent;
+ p = name;
+ name_end = name + strlen (name);
+
+ while ((pent != NULL) && (c != '\0') && (*p != '\0'))
+ {
+ q = strchr (p, PATH_SEP);
+ if (q == NULL)
+ q = (char *) name_end;
+
+ c = *q;
+ *q = '\0';
+
+ if (DIR_IS_DOTDOT (p))
+ pent = pent->dir != NULL ? pent->dir->ent : NULL;
+ else
+ {
+ GList *pl;
+
+ pent = extfs_resolve_symlinks_int (pent, list);
+ if (pent == NULL)
+ {
+ *q = c;
+ return NULL;
+ }
+
+ if (!S_ISDIR (pent->ino->st.st_mode))
+ {
+ *q = c;
+ notadir = TRUE;
+ return NULL;
+ }
+
+ pdir = pent;
+ pl = g_queue_find_custom (pent->ino->subdir, p, vfs_s_entry_compare);
+ pent = pl != NULL ? VFS_ENTRY (pl->data) : NULL;
+ if (pent != NULL && q + 1 > name_end)
+ {
+ /* Hack: I keep the original semanthic unless q+1 would break in the strchr */
+ *q = c;
+ notadir = !S_ISDIR (pent->ino->st.st_mode);
+ return pent;
+ }
+
+ /* When we load archive, we create automagically non-existent directories */
+ if (pent == NULL && (flags & FL_MKDIR) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFDIR | 0777);
+ if (pent == NULL && (flags & FL_MKFILE) != 0)
+ pent = extfs_generate_entry (super, p, pdir->ino, S_IFREG | 0666);
+ }
+
+ /* Next iteration */
+ *q = c;
+ if (c != '\0')
+ p = q + 1;
+ }
+ if (pent == NULL)
+ my_errno = ENOENT;
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_find_entry (struct vfs_s_inode *dir, const char *name, int flags)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+
+ res = extfs_find_entry_int (dir, name, NULL, flags);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_fill_names (struct vfs_class *me, fill_names_f func)
+{
+ g_list_foreach (VFS_SUBCLASS (me)->supers, extfs_fill_name, func);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+/* Create this function because VFSF_USETMP flag is not used in extfs */
+static void
+extfs_free_inode (struct vfs_class *me, struct vfs_s_inode *ino)
+{
+ (void) me;
+
+ if (ino->localname != NULL)
+ {
+ unlink (ino->localname);
+ MC_PTR_FREE (ino->localname);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_free_archive (struct vfs_class *me, struct vfs_s_super *psup)
+{
+ struct extfs_super_t *archive = EXTFS_SUPER (psup);
+
+ (void) me;
+
+ if (archive->local_name != NULL)
+ {
+ struct stat my;
+ vfs_path_t *local_name_vpath, *name_vpath;
+
+ local_name_vpath = vfs_path_from_str (archive->local_name);
+ name_vpath = vfs_path_from_str (psup->name);
+ mc_stat (local_name_vpath, &my);
+ mc_ungetlocalcopy (name_vpath, local_name_vpath,
+ archive->local_stat.st_mtime != my.st_mtime);
+ vfs_path_free (local_name_vpath, TRUE);
+ vfs_path_free (name_vpath, TRUE);
+ g_free (archive->local_name);
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static inline char *
+extfs_skip_leading_dotslash (char *s)
+{
+ /* Skip leading "./" (if present).
+ * Some programs don't understand it:
+ *
+ * $ zip file.zip ./-file2.txt file1.txt
+ * adding: -file2.txt (stored 0%)
+ * adding: file1.txt (stored 0%)
+ * $ /usr/lib/mc/extfs.d/uzip copyout file.zip ./-file2.txt ./tmp-file2.txt
+ * caution: filename not matched: ./-file2.txt
+ */
+ if (s[0] == '.' && s[1] == PATH_SEP)
+ s += 2;
+
+ return s;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_add_file (struct extfs_super_t *archive, const char *file_name)
+{
+ struct vfs_s_super *super = VFS_SUPER (archive);
+ struct stat hstat;
+ char *current_file_name = NULL, *current_link_name = NULL;
+ int ret = 0;
+
+ if (vfs_parse_ls_lga (file_name, &hstat, &current_file_name, &current_link_name, NULL))
+ {
+ char *cfn = current_file_name;
+
+ if (*cfn != '\0')
+ {
+ struct vfs_s_entry *entry;
+ struct vfs_s_entry *pent = NULL;
+ struct vfs_s_inode *inode;
+ char *p, *q;
+
+ cfn = extfs_skip_leading_dotslash (cfn);
+ if (IS_PATH_SEP (*cfn))
+ cfn++;
+ p = strchr (cfn, '\0');
+ if (p != cfn && IS_PATH_SEP (p[-1]))
+ p[-1] = '\0';
+ p = strrchr (cfn, PATH_SEP);
+ if (p == NULL)
+ {
+ p = cfn;
+ q = strchr (cfn, '\0');
+ }
+ else
+ {
+ *(p++) = '\0';
+ q = cfn;
+ }
+
+ if (*q != '\0')
+ {
+ pent = extfs_find_entry (super->root, q, FL_MKDIR);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ if (pent != NULL)
+ {
+ entry = extfs_entry_new (super->me, p, pent->ino);
+ entry->dir = pent->ino;
+ g_queue_push_tail (pent->ino->subdir, entry);
+ }
+ else
+ {
+ entry = extfs_entry_new (super->me, p, super->root);
+ entry->dir = super->root;
+ g_queue_push_tail (super->root->subdir, entry);
+ }
+
+ if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL))
+ {
+ pent = extfs_find_entry (super->root, current_link_name, FL_NONE);
+ if (pent == NULL)
+ {
+ ret = -1;
+ goto done;
+ }
+
+ pent->ino->st.st_nlink++;
+ entry->ino = pent->ino;
+ }
+ else
+ {
+ struct stat st;
+
+ memset (&st, 0, sizeof (st));
+ st.st_ino = super->ino_usage++;
+ st.st_nlink = 1;
+ st.st_dev = archive->rdev;
+ st.st_mode = hstat.st_mode;
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st.st_rdev = hstat.st_rdev;
+#endif
+ st.st_uid = hstat.st_uid;
+ st.st_gid = hstat.st_gid;
+ st.st_size = hstat.st_size;
+ st.st_mtime = hstat.st_mtime;
+ st.st_atime = hstat.st_atime;
+ st.st_ctime = hstat.st_ctime;
+
+ if (current_link_name == NULL && S_ISLNK (hstat.st_mode))
+ st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */
+
+ inode = vfs_s_new_inode (super->me, super, &st);
+ inode->ent = entry;
+ entry->ino = inode;
+
+ if (current_link_name != NULL && S_ISLNK (hstat.st_mode))
+ {
+ inode->linkname = current_link_name;
+ current_link_name = NULL;
+ }
+ }
+ }
+
+ done:
+ g_free (current_file_name);
+ g_free (current_link_name);
+ }
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static mc_pipe_t *
+extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc, GError ** error)
+{
+ const extfs_plugin_info_t *info;
+ static dev_t archive_counter = 0;
+ mc_pipe_t *result = NULL;
+ mode_t mode;
+ char *cmd;
+ struct stat mystat;
+ struct extfs_super_t *current_archive;
+ struct vfs_s_entry *root_entry;
+ char *tmp = NULL;
+ vfs_path_t *local_name_vpath = NULL;
+ vfs_path_t *name_vpath;
+
+ memset (&mystat, 0, sizeof (mystat));
+
+ name_vpath = vfs_path_from_str (name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+
+ if (info->need_archive)
+ {
+ if (mc_stat (name_vpath, &mystat) == -1)
+ goto ret;
+
+ if (!vfs_file_is_local (name_vpath))
+ {
+ local_name_vpath = mc_getlocalcopy (name_vpath);
+ if (local_name_vpath == NULL)
+ goto ret;
+ }
+
+ tmp = name_quote (vfs_path_get_last_path_str (name_vpath), FALSE);
+ }
+
+ cmd = g_strconcat (info->path, info->prefix, " list ",
+ vfs_path_get_last_path_str (local_name_vpath) != NULL ?
+ vfs_path_get_last_path_str (local_name_vpath) : tmp, (char *) NULL);
+ g_free (tmp);
+
+ result = mc_popen (cmd, TRUE, TRUE, error);
+ g_free (cmd);
+
+ if (result == NULL)
+ {
+ if (local_name_vpath != NULL)
+ {
+ mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE);
+ vfs_path_free (local_name_vpath, TRUE);
+ }
+ goto ret;
+ }
+
+ current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype);
+ current_archive->rdev = archive_counter++;
+ vfs_path_free (local_name_vpath, TRUE);
+
+ mode = mystat.st_mode & 07777;
+ if (mode & 0400)
+ mode |= 0100;
+ if (mode & 0040)
+ mode |= 0010;
+ if (mode & 0004)
+ mode |= 0001;
+ mode |= S_IFDIR;
+
+ root_entry = extfs_generate_entry (current_archive, PATH_SEP_STR, NULL, mode);
+ root_entry->ino->st.st_uid = mystat.st_uid;
+ root_entry->ino->st.st_gid = mystat.st_gid;
+ root_entry->ino->st.st_atime = mystat.st_atime;
+ root_entry->ino->st.st_ctime = mystat.st_ctime;
+ root_entry->ino->st.st_mtime = mystat.st_mtime;
+ root_entry->ino->ent = root_entry;
+ VFS_SUPER (current_archive)->root = root_entry->ino;
+
+ *pparc = current_archive;
+
+ ret:
+ vfs_path_free (name_vpath, TRUE);
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Main loop for reading an archive.
+ * Return 0 on success, -1 on error.
+ */
+
+static int
+extfs_read_archive (mc_pipe_t * pip, struct extfs_super_t *archive, GError ** error)
+{
+ int ret = 0;
+ GString *buffer;
+ GString *err_msg = NULL;
+ GString *remain_file_name = NULL;
+
+ while (ret != -1)
+ {
+ /* init buffers before call of mc_pread() */
+ pip->out.len = MC_PIPE_BUFSIZE;
+ pip->err.len = MC_PIPE_BUFSIZE;
+
+ mc_pread (pip, error);
+
+ if (*error != NULL)
+ return (-1);
+
+ if (pip->err.len > 0)
+ {
+ /* join errors/warnings */
+ if (err_msg == NULL)
+ err_msg = g_string_new_len (pip->err.buf, pip->err.len);
+ else
+ {
+ if (err_msg->str[err_msg->len - 1] != '\n')
+ g_string_append_c (err_msg, '\n');
+ g_string_append_len (err_msg, pip->err.buf, pip->err.len);
+ }
+ }
+
+ if (pip->out.len == MC_PIPE_STREAM_EOF)
+ break;
+
+ if (pip->out.len == 0)
+ continue;
+
+ if (pip->out.len == MC_PIPE_ERROR_READ)
+ return (-1);
+
+ while (ret != -1 && (buffer = mc_pstream_get_string (&pip->out)) != NULL)
+ {
+ /* handle a \n-separated file list */
+
+ if (buffer->str[buffer->len - 1] == '\n')
+ {
+ /* entire file name or last chunk */
+
+ g_string_truncate (buffer, buffer->len - 1);
+
+ /* join filename chunks */
+ if (remain_file_name != NULL)
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ buffer = remain_file_name;
+ remain_file_name = NULL;
+ }
+ }
+ else
+ {
+ /* first or middle chunk of file name */
+
+ if (remain_file_name == NULL)
+ remain_file_name = buffer;
+ else
+ {
+ g_string_append_len (remain_file_name, buffer->str, buffer->len);
+ g_string_free (buffer, TRUE);
+ }
+
+ continue;
+ }
+
+ ret = extfs_add_file (archive, buffer->str);
+
+ g_string_free (buffer, TRUE);
+ }
+ }
+
+ if (remain_file_name != NULL)
+ g_string_free (remain_file_name, TRUE);
+
+ if (err_msg != NULL)
+ {
+ if (*error == NULL)
+ mc_propagate_error (error, 0, "%s", err_msg->str);
+
+ g_string_free (err_msg, TRUE);
+ }
+ else if (ret == -1)
+ mc_propagate_error (error, 0, "%s", _("Inconsistent archive"));
+
+ return ret;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_which (struct vfs_class *me, const char *path)
+{
+ size_t path_len;
+ size_t i;
+
+ (void) me;
+
+ path_len = strlen (path);
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ if ((strncmp (path, info->prefix, path_len) == 0)
+ && ((info->prefix[path_len] == '\0') || (info->prefix[path_len] == '+')))
+ return i;
+ }
+ return -1;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_open_and_read_archive (int fstype, const char *name, struct extfs_super_t **archive)
+{
+ int result = -1;
+ struct extfs_super_t *a;
+ mc_pipe_t *pip;
+ GError *error = NULL;
+
+ pip = extfs_open_archive (fstype, name, archive, &error);
+
+ a = *archive;
+
+ if (pip == NULL)
+ {
+ const extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
+ message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s:\n%s"), info->prefix, name,
+ error->message);
+ g_error_free (error);
+ }
+ else
+ {
+ result = extfs_read_archive (pip, a, &error);
+
+ if (result != 0)
+ VFS_SUPER (a)->me->free (VFS_SUPER (a));
+
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ }
+
+ mc_pclose (pip, NULL);
+ }
+
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/**
+ * Dissect the path and create corresponding superblock.
+ */
+static const char *
+extfs_get_path (const vfs_path_t * vpath, struct extfs_super_t **archive, int flags)
+{
+ char *archive_name;
+ int result = -1;
+ GList *parc;
+ int fstype;
+ const vfs_path_element_t *path_element;
+ struct extfs_super_t *a = NULL;
+
+ path_element = vfs_path_get_by_index (vpath, -1);
+
+ fstype = extfs_which (path_element->class, path_element->vfs_prefix);
+ if (fstype == -1)
+ return NULL;
+
+ archive_name = vfs_path_to_str_elements_count (vpath, -1);
+
+ /* All filesystems should have some local archive, at least it can be PATH_SEP ('/'). */
+ parc = g_list_find_custom (extfs_subclass.supers, archive_name, extfs_cmp_archive);
+ if (parc != NULL)
+ {
+ a = EXTFS_SUPER (parc->data);
+ vfs_stamp (vfs_extfs_ops, (vfsid) a);
+ g_free (archive_name);
+ }
+ else
+ {
+ if ((flags & FL_NO_OPEN) == 0)
+ result = extfs_open_and_read_archive (fstype, archive_name, &a);
+
+ g_free (archive_name);
+
+ if (result == -1)
+ {
+ path_element->class->verrno = EIO;
+ return NULL;
+ }
+ }
+
+ *archive = a;
+ return path_element->path;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/* Return allocated path (without leading slash) inside the archive */
+
+static char *
+extfs_get_path_from_entry (const struct vfs_s_entry *entry)
+{
+ const struct vfs_s_entry *e;
+ GString *localpath;
+
+ localpath = g_string_new ("");
+
+ for (e = entry; e->dir != NULL; e = e->dir->ent)
+ {
+ g_string_prepend (localpath, e->name);
+ if (e->dir->ent->dir != NULL)
+ g_string_prepend_c (localpath, PATH_SEP);
+ }
+
+ return g_string_free (localpath, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list)
+{
+ struct vfs_s_entry *pent = NULL;
+
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ return entry;
+
+ if (g_slist_find (list, entry) != NULL)
+ {
+ /* Here we protect us against symlink looping */
+ errloop = TRUE;
+ }
+ else
+ {
+ GSList *looping;
+
+ looping = g_slist_prepend (list, entry);
+ pent = extfs_find_entry_int (entry->dir, entry->ino->linkname, looping, FL_NONE);
+ looping = g_slist_delete_link (looping, looping);
+
+ if (pent == NULL)
+ my_errno = ENOENT;
+ }
+
+ return pent;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_s_entry *
+extfs_resolve_symlinks (struct vfs_s_entry *entry)
+{
+ struct vfs_s_entry *res;
+
+ errloop = FALSE;
+ notadir = FALSE;
+ res = extfs_resolve_symlinks_int (entry, NULL);
+ if (res == NULL)
+ {
+ if (errloop)
+ my_errno = ELOOP;
+ else if (notadir)
+ my_errno = ENOTDIR;
+ }
+ return res;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static char *
+extfs_get_archive_name (const struct extfs_super_t *archive)
+{
+ const char *archive_name;
+
+ if (archive->local_name != NULL)
+ archive_name = archive->local_name;
+ else
+ archive_name = CONST_VFS_SUPER (archive)->name;
+
+ if (archive_name == NULL || *archive_name == '\0')
+ return g_strdup ("no_archive_name");
+ else
+ {
+ char *ret_str;
+ vfs_path_t *vpath;
+ const char *path;
+
+ vpath = vfs_path_from_str (archive_name);
+ path = vfs_path_get_last_path_str (vpath);
+ ret_str = g_strdup (path);
+ vfs_path_free (vpath, TRUE);
+ return ret_str;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/** Don't pass localname as NULL */
+
+static int
+extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive,
+ const struct vfs_s_entry *entry, const char *localname)
+{
+ char *file;
+ char *quoted_file;
+ char *quoted_localname;
+ char *archive_name, *quoted_archive_name;
+ const extfs_plugin_info_t *info;
+ char *cmd;
+ int retval = 0;
+ GError *error = NULL;
+ mc_pipe_t *pip;
+
+ file = extfs_get_path_from_entry (entry);
+ quoted_file = name_quote (file, FALSE);
+ g_free (file);
+
+ /* Skip leading "./" (if present) added in name_quote() */
+ file = extfs_skip_leading_dotslash (quoted_file);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ quoted_localname = name_quote (localname, FALSE);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd = g_strconcat (info->path, info->prefix, str_extfs_cmd,
+ quoted_archive_name, " ", file, " ", quoted_localname, (char *) NULL);
+ g_free (quoted_file);
+ g_free (quoted_localname);
+ g_free (quoted_archive_name);
+
+ /* don't read stdout */
+ pip = mc_popen (cmd, FALSE, TRUE, &error);
+ g_free (cmd);
+
+ if (pip == NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ return (-1);
+ }
+
+ pip->err.null_term = TRUE;
+
+ mc_pread (pip, &error);
+ if (error != NULL)
+ {
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message);
+ g_error_free (error);
+ retval = -1;
+ }
+ else if (pip->err.len > 0)
+ message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), pip->err.buf);
+
+ mc_pclose (pip, NULL);
+
+ return retval;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_run (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+ char *q, *archive_name, *quoted_archive_name;
+ char *cmd;
+ const extfs_plugin_info_t *info;
+
+ p = extfs_get_path (vpath, &archive, FL_NONE);
+ if (p == NULL)
+ return;
+ q = name_quote (p, FALSE);
+
+ archive_name = extfs_get_archive_name (archive);
+ quoted_archive_name = name_quote (archive_name, FALSE);
+ g_free (archive_name);
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
+ cmd =
+ g_strconcat (info->path, info->prefix, " run ", quoted_archive_name, " ", q, (char *) NULL);
+ g_free (quoted_archive_name);
+ g_free (q);
+ shell_execute (cmd, 0);
+ g_free (cmd);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
+{
+ vfs_file_handler_t *extfs_info;
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int local_handle;
+ gboolean created = FALSE;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if ((entry == NULL) && ((flags & O_CREAT) != 0))
+ {
+ /* Create new entry */
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKFILE);
+ created = (entry != NULL);
+ }
+
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+
+ if (S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (EISDIR, NULL);
+
+ if (entry->ino->localname == NULL)
+ {
+ vfs_path_t *local_filename_vpath;
+ const char *local_filename;
+
+ local_handle = vfs_mkstemps (&local_filename_vpath, "extfs", entry->name);
+
+ if (local_handle == -1)
+ return NULL;
+ close (local_handle);
+ local_filename = vfs_path_get_last_path_str (local_filename_vpath);
+
+ if (!created && ((flags & O_TRUNC) == 0)
+ && extfs_cmd (" copyout ", archive, entry, local_filename))
+ {
+ unlink (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ my_errno = EIO;
+ return NULL;
+ }
+ entry->ino->localname = g_strdup (local_filename);
+ vfs_path_free (local_filename_vpath, TRUE);
+ }
+
+ local_handle = open (entry->ino->localname, NO_LINEAR (flags), mode);
+
+ if (local_handle == -1)
+ {
+ /* file exists(may be). Need to drop O_CREAT flag and truncate file content */
+ flags = ~O_CREAT & (NO_LINEAR (flags) | O_TRUNC);
+ local_handle = open (entry->ino->localname, flags, mode);
+ }
+
+ if (local_handle == -1)
+ ERRNOR (EIO, NULL);
+
+ extfs_info = g_new (vfs_file_handler_t, 1);
+ vfs_s_init_fh (extfs_info, entry->ino, created);
+ extfs_info->handle = local_handle;
+
+ /* i.e. we had no open files and now we have one */
+ vfs_rmstamp (vfs_extfs_ops, (vfsid) archive);
+ VFS_SUPER (archive)->fd_usage++;
+ return extfs_info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_read (void *fh, char *buffer, size_t count)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return read (file->handle, buffer, count);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_close (void *fh)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+ int errno_code = 0;
+
+ close (file->handle);
+ file->handle = -1;
+
+ /* Commit the file if it has changed */
+ if (file->changed)
+ {
+ struct stat file_status;
+
+ if (extfs_cmd
+ (" copyin ", EXTFS_SUPER (VFS_FILE_HANDLER_SUPER (fh)), file->ino->ent,
+ file->ino->localname))
+ errno_code = EIO;
+
+ if (stat (file->ino->localname, &file_status) != 0)
+ errno_code = EIO;
+ else
+ file->ino->st.st_size = file_status.st_size;
+
+ file->ino->st.st_mtime = time (NULL);
+ }
+
+ if (--VFS_FILE_HANDLER_SUPER (fh)->fd_usage == 0)
+ vfs_stamp_create (vfs_extfs_ops, VFS_FILE_HANDLER_SUPER (fh));
+
+ g_free (fh);
+ if (errno_code != 0)
+ ERRNOR (EIO, -1);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_errno (struct vfs_class *me)
+{
+ (void) me;
+ return my_errno;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void *
+extfs_opendir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *q;
+ struct vfs_s_entry *entry;
+ GList **info;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ return NULL;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ return NULL;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ return NULL;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ ERRNOR (ENOTDIR, NULL);
+
+ info = g_new (GList *, 1);
+ *info = g_queue_peek_head_link (entry->ino->subdir);
+
+ return info;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static struct vfs_dirent *
+extfs_readdir (void *data)
+{
+ struct vfs_dirent *dir;
+ GList **info = (GList **) data;
+
+ if (*info == NULL)
+ return NULL;
+
+ dir = vfs_dirent_init (NULL, VFS_ENTRY ((*info)->data)->name, 0); /* FIXME: inode */
+
+ *info = g_list_next (*info);
+
+ return dir;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_closedir (void *data)
+{
+ g_free (data);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+
+static void
+extfs_stat_move (struct stat *buf, const struct vfs_s_inode *inode)
+{
+ *buf = inode->st;
+
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+ buf->st_blksize = RECORDSIZE;
+#endif
+ vfs_adjust_stat (buf);
+#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0;
+#endif
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_internal_stat (const vfs_path_t * vpath, struct stat *buf, gboolean resolve)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (resolve)
+ {
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ }
+ extfs_stat_move (buf, entry->ino);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_stat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_lstat (const vfs_path_t * vpath, struct stat *buf)
+{
+ return extfs_internal_stat (vpath, buf, FALSE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_fstat (void *fh, struct stat *buf)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ extfs_stat_move (buf, file->ino);
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ size_t len;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISLNK (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EINVAL;
+ goto cleanup;
+ }
+ len = strlen (entry->ino->linkname);
+ if (size < len)
+ len = size;
+ /* readlink() does not append a NUL character to buf */
+ result = len;
+ memcpy (buf, entry->ino->linkname, result);
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
+{
+ (void) vpath;
+ (void) owner;
+ (void) group;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chmod (const vfs_path_t * vpath, mode_t mode)
+{
+ (void) vpath;
+ (void) mode;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static ssize_t
+extfs_write (void *fh, const char *buf, size_t nbyte)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ file->changed = TRUE;
+ return write (file->handle, buf, nbyte);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_unlink (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = EISDIR;
+ goto cleanup;
+ }
+ if (extfs_cmd (" rm ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_mkdir (const vfs_path_t * vpath, mode_t mode)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+ struct vfs_class *me;
+
+ (void) mode;
+
+ me = VFS_CLASS (vfs_path_get_last_path_vfs (vpath));
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry != NULL)
+ {
+ me->verrno = EEXIST;
+ goto cleanup;
+ }
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKDIR);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ me->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" mkdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ goto cleanup;
+ }
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_rmdir (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive;
+ const char *q;
+ struct vfs_s_entry *entry;
+ int result = -1;
+
+ q = extfs_get_path (vpath, &archive, FL_NONE);
+ if (q == NULL)
+ goto cleanup;
+ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE);
+ if (entry == NULL)
+ goto cleanup;
+ entry = extfs_resolve_symlinks (entry);
+ if (entry == NULL)
+ goto cleanup;
+ if (!S_ISDIR (entry->ino->st.st_mode))
+ {
+ VFS_CLASS (vfs_path_get_last_path_vfs (vpath))->verrno = ENOTDIR;
+ goto cleanup;
+ }
+
+ if (extfs_cmd (" rmdir ", archive, entry, ""))
+ {
+ my_errno = EIO;
+ goto cleanup;
+ }
+ vfs_s_free_entry (VFS_SUPER (archive)->me, entry);
+ result = 0;
+ cleanup:
+ return result;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_chdir (const vfs_path_t * vpath)
+{
+ void *data;
+
+ my_errno = ENOTDIR;
+ data = extfs_opendir (vpath);
+ if (data == NULL)
+ return (-1);
+ extfs_closedir (data);
+ my_errno = 0;
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static off_t
+extfs_lseek (void *fh, off_t offset, int whence)
+{
+ vfs_file_handler_t *file = VFS_FILE_HANDLER (fh);
+
+ return lseek (file->handle, offset, whence);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfsid
+extfs_getid (const vfs_path_t * vpath)
+{
+ struct extfs_super_t *archive = NULL;
+ const char *p;
+
+ p = extfs_get_path (vpath, &archive, FL_NO_OPEN);
+ return (p == NULL ? NULL : (vfsid) archive);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static vfs_path_t *
+extfs_getlocalcopy (const vfs_path_t * vpath)
+{
+ vfs_file_handler_t *fh;
+ vfs_path_t *p;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return NULL;
+ if (fh->ino->localname == NULL)
+ {
+ extfs_close ((void *) fh);
+ return NULL;
+ }
+ p = vfs_path_from_str (fh->ino->localname);
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage++;
+ extfs_close ((void *) fh);
+ return p;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed)
+{
+ vfs_file_handler_t *fh;
+
+ fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0));
+ if (fh == NULL)
+ return 0;
+
+ if (strcmp (fh->ino->localname, vfs_path_get_last_path_str (local)) == 0)
+ {
+ VFS_FILE_HANDLER_SUPER (fh)->fd_usage--;
+ if (has_changed)
+ fh->changed = TRUE;
+ extfs_close ((void *) fh);
+ return 0;
+ }
+ else
+ {
+ /* Should not happen */
+ extfs_close ((void *) fh);
+ return 0;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static gboolean
+extfs_get_plugins (const char *where, gboolean silent)
+{
+ char *dirname;
+ GDir *dir;
+ const char *filename;
+
+ dirname = g_build_path (PATH_SEP_STR, where, MC_EXTFS_DIR, (char *) NULL);
+ dir = g_dir_open (dirname, 0, NULL);
+
+ /* We may not use vfs_die() message or message or similar,
+ * UI is not initialized at this time and message would not
+ * appear on screen. */
+ if (dir == NULL)
+ {
+ if (!silent)
+ fprintf (stderr, _("Warning: cannot open %s directory\n"), dirname);
+ g_free (dirname);
+ return FALSE;
+ }
+
+ if (extfs_plugins == NULL)
+ extfs_plugins = g_array_sized_new (FALSE, TRUE, sizeof (extfs_plugin_info_t), 32);
+
+ while ((filename = g_dir_read_name (dir)) != NULL)
+ {
+ char fullname[MC_MAXPATHLEN];
+ struct stat s;
+
+ g_snprintf (fullname, sizeof (fullname), "%s" PATH_SEP_STR "%s", dirname, filename);
+
+ if ((stat (fullname, &s) == 0) && S_ISREG (s.st_mode) && !S_ISDIR (s.st_mode)
+ && is_exe (s.st_mode))
+ {
+ int f;
+
+ f = open (fullname, O_RDONLY);
+
+ if (f >= 0)
+ {
+ size_t len, i;
+ extfs_plugin_info_t info;
+ gboolean found = FALSE;
+
+ close (f);
+
+ /* Handle those with a trailing '+', those flag that the
+ * file system does not require an archive to work
+ */
+ len = strlen (filename);
+ info.need_archive = (filename[len - 1] != '+');
+ info.path = g_strconcat (dirname, PATH_SEP_STR, (char *) NULL);
+ info.prefix = g_strndup (filename, len);
+
+ /* prepare to compare file names without trailing '+' */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '\0';
+
+ /* don't overload already found plugin */
+ for (i = 0; i < extfs_plugins->len && !found; i++)
+ {
+ extfs_plugin_info_t *p;
+
+ p = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+
+ /* 2 files with same names cannot be in a directory */
+ found = strcmp (info.path, p->path) != 0
+ && strcmp (info.prefix, p->prefix) == 0;
+ }
+
+ if (found)
+ {
+ g_free (info.path);
+ g_free (info.prefix);
+ }
+ else
+ {
+ /* restore file name */
+ if (!info.need_archive)
+ info.prefix[len - 1] = '+';
+ g_array_append_val (extfs_plugins, info);
+ }
+ }
+ }
+ }
+
+ g_dir_close (dir);
+ g_free (dirname);
+
+ return TRUE;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_init (struct vfs_class *me)
+{
+ gboolean d1, d2;
+
+ (void) me;
+
+ /* 1st: scan user directory */
+ d1 = extfs_get_plugins (mc_config_get_data_path (), TRUE); /* silent about user dir */
+ /* 2nd: scan system dir */
+ d2 = extfs_get_plugins (LIBEXECDIR, d1);
+
+ return (d1 || d2 ? 1 : 0);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+extfs_done (struct vfs_class *me)
+{
+ size_t i;
+
+ while (VFS_SUBCLASS (me)->supers != NULL)
+ me->free ((vfsid) VFS_SUBCLASS (me)->supers->data);
+
+ if (extfs_plugins == NULL)
+ return;
+
+ for (i = 0; i < extfs_plugins->len; i++)
+ {
+ extfs_plugin_info_t *info;
+
+ info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
+ g_free (info->path);
+ g_free (info->prefix);
+ }
+
+ g_array_free (extfs_plugins, TRUE);
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static int
+extfs_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
+{
+ (void) arg;
+
+ if (ctlop == VFS_SETCTL_RUN)
+ {
+ extfs_run (vpath);
+ return 1;
+ }
+ return 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+/*** public functions ****************************************************************************/
+/* --------------------------------------------------------------------------------------------- */
+
+void
+vfs_init_extfs (void)
+{
+ vfs_init_subclass (&extfs_subclass, "extfs", VFSF_UNKNOWN, NULL);
+ vfs_extfs_ops->init = extfs_init;
+ vfs_extfs_ops->done = extfs_done;
+ vfs_extfs_ops->fill_names = extfs_fill_names;
+ vfs_extfs_ops->which = extfs_which;
+ vfs_extfs_ops->open = extfs_open;
+ vfs_extfs_ops->close = extfs_close;
+ vfs_extfs_ops->read = extfs_read;
+ vfs_extfs_ops->write = extfs_write;
+ vfs_extfs_ops->opendir = extfs_opendir;
+ vfs_extfs_ops->readdir = extfs_readdir;
+ vfs_extfs_ops->closedir = extfs_closedir;
+ vfs_extfs_ops->stat = extfs_stat;
+ vfs_extfs_ops->lstat = extfs_lstat;
+ vfs_extfs_ops->fstat = extfs_fstat;
+ vfs_extfs_ops->chmod = extfs_chmod;
+ vfs_extfs_ops->chown = extfs_chown;
+ vfs_extfs_ops->readlink = extfs_readlink;
+ vfs_extfs_ops->unlink = extfs_unlink;
+ vfs_extfs_ops->chdir = extfs_chdir;
+ vfs_extfs_ops->ferrno = extfs_errno;
+ vfs_extfs_ops->lseek = extfs_lseek;
+ vfs_extfs_ops->getid = extfs_getid;
+ vfs_extfs_ops->getlocalcopy = extfs_getlocalcopy;
+ vfs_extfs_ops->ungetlocalcopy = extfs_ungetlocalcopy;
+ vfs_extfs_ops->mkdir = extfs_mkdir;
+ vfs_extfs_ops->rmdir = extfs_rmdir;
+ vfs_extfs_ops->setctl = extfs_setctl;
+ extfs_subclass.free_inode = extfs_free_inode;
+ extfs_subclass.free_archive = extfs_free_archive;
+ vfs_register_class (vfs_extfs_ops);
+}
+
+/* --------------------------------------------------------------------------------------------- */