/*
Virtual File System: External file system.
Copyright (C) 1995-2020
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;
/*** 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 vfs_s_entry *extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list);
/* --------------------------------------------------------------------------------------------- */
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->ent;
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 */
if (c == '\0')
break;
*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);
vfs_path_free (name_vpath);
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 FILE *
extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc)
{
const extfs_plugin_info_t *info;
static dev_t archive_counter = 0;
FILE *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);
open_error_pipe ();
result = popen (cmd, "r");
g_free (cmd);
if (result == NULL)
{
close_error_pipe (D_ERROR, NULL);
if (local_name_vpath != NULL)
{
mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE);
vfs_path_free (local_name_vpath);
}
goto ret;
}
#ifdef ___QNXNTO__
setvbuf (result, NULL, _IONBF, 0);
#endif
current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype);
current_archive->rdev = archive_counter++;
vfs_path_free (local_name_vpath);
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);
return result;
}
/* --------------------------------------------------------------------------------------------- */
/**
* Main loop for reading an archive.
* Return 0 on success, -1 on error.
*/
static int
extfs_read_archive (FILE * extfsd, struct extfs_super_t *current_archive)
{
int ret = 0;
char *buffer;
struct vfs_s_super *super = VFS_SUPER (current_archive);
buffer = g_malloc (BUF_4K);
while (fgets (buffer, BUF_4K, extfsd) != NULL)
{
struct stat hstat;
char *current_file_name = NULL, *current_link_name = NULL;
if (vfs_parse_ls_lga (buffer, &hstat, ¤t_file_name, ¤t_link_name, NULL))
{
struct vfs_s_entry *entry, *pent = NULL;
struct vfs_s_inode *inode;
char *p, *q, *cfn = current_file_name;
if (*cfn != '\0')
{
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;
break;
}
}
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;
break;
}
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 = current_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;
}
}
}
g_free (current_file_name);
g_free (current_link_name);
}
}
g_free (buffer);
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;
FILE *extfsd;
struct extfs_super_t *a;
extfsd = extfs_open_archive (fstype, name, archive);
a = *archive;
if (extfsd == 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"), info->prefix, name);
}
else if (extfs_read_archive (extfsd, a) != 0)
{
pclose (extfsd);
close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
}
else if (pclose (extfsd) != 0)
{
VFS_SUPER (a)->me->free (VFS_SUPER (a));
close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
}
else
{
close_error_pipe (D_ERROR, NULL);
result = 0;
}
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 = 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 vfs_path_element_t *path_element;
vpath = vfs_path_from_str (archive_name);
path_element = vfs_path_get_by_index (vpath, -1);
ret_str = g_strdup (path_element->path);
vfs_path_free (vpath);
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;
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);
open_error_pipe ();
retval = my_system (EXECUTE_AS_SHELL, mc_global.shell->path, cmd);
g_free (cmd);
close_error_pipe (D_ERROR, 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_by_index (local_filename_vpath, -1)->path;
if (!created && ((flags & O_TRUNC) == 0)
&& extfs_cmd (" copyout ", archive, entry, local_filename))
{
unlink (local_filename);
vfs_path_free (local_filename_vpath);
my_errno = EIO;
return NULL;
}
entry->ino->localname = g_strdup (local_filename);
vfs_path_free (local_filename_vpath);
}
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))
{
const vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
path_element->class->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))
{
const vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
path_element->class->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;
const vfs_path_element_t *path_element;
(void) mode;
path_element = vfs_path_get_by_index (vpath, -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)
{
path_element->class->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))
{
path_element->class->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))
{
const vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
path_element->class->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)
&& (((s.st_mode & S_IXOTH) != 0) ||
((s.st_mode & S_IXUSR) != 0) || ((s.st_mode & S_IXGRP) != 0)))
{
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_strdup (filename);
/* 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; 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 */
if ((strcmp (info.path, p->path) != 0)
&& (strcmp (info.prefix, p->prefix) == 0))
{
found = TRUE;
break;
}
}
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);
}
/* --------------------------------------------------------------------------------------------- */