mc/lib/vfs/direntry.c

1520 lines
41 KiB
C
Raw Normal View History

/** \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>, distribute under LGPL.
* \date 1998
*
* \warning Paths here do _not_ begin with '/', so root directory of
* archive/site is simply "".
*/
#include <config.h>
#include <errno.h>
#include <fcntl.h> /* include fcntl.h -> sys/fcntl.h only */
/* includes fcntl.h see IEEE Std 1003.1-2008 */
2009-02-06 02:30:45 +03:00
#include <time.h>
#include <sys/time.h> /* gettimeofday() */
#include <inttypes.h> /* uintmax_t */
#include <stdarg.h>
#include "lib/global.h"
#include "lib/tty/tty.h" /* enable/disable interrupt key */
#include "lib/util.h" /* concat_dir_and_file */
#if 0
#include "lib/widget.h" /* message() */
#endif
#include "vfs.h"
#include "utilvfs.h"
1998-10-24 00:18:48 +04:00
#include "xdirentry.h"
#include "gc.h" /* vfs_rmstamp */
/*** global variables ****************************************************************************/
/*** file scope macro definitions ****************************************************************/
#define CALL(x) if (MEDATA->x) MEDATA->x
/*** file scope type declarations ****************************************************************/
struct dirhandle
{
GList *cur;
struct vfs_s_inode *dir;
};
/*** file scope variables ************************************************************************/
static volatile int total_inodes = 0, total_entries = 0;
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */
static 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);
}
/* --------------------------------------------------------------------------------------------- */
static 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 (ino->subdir != NULL)
vfs_s_free_entry (me, (struct vfs_s_entry *) ino->subdir->data);
CALL (free_inode) (me, ino);
g_free (ino->linkname);
if (ino->localname != NULL)
{
unlink (ino->localname);
g_free (ino->localname);
}
total_inodes--;
ino->super->ino_usage--;
g_free (ino);
}
/* --------------------------------------------------------------------------------------------- */
/* 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';
1999-01-18 02:10:05 +03:00
res = vfs_s_generate_entry (me, path, dir, flags & FL_MKDIR ? (0777 | S_IFDIR) : 0777);
vfs_s_insert_entry (me, dir, res);
if (sep != NULL)
*sep = PATH_SEP;
1999-01-18 02:10:05 +03:00
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)
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 (*linkname != PATH_SEP)
{
char *fullpath = vfs_s_fullpath (me, entry->dir);
if (fullpath)
{
fullname = g_strconcat (fullpath, "/", linkname, (char *) NULL);
linkname = fullname;
g_free (fullpath);
}
}
target = (MEDATA->find_entry) (me, entry->dir->super->root, linkname, follow - 1, 0);
g_free (fullname);
return target;
}
/* --------------------------------------------------------------------------------------------- */
1999-01-18 02:10:05 +03:00
/*
* 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;
Ticket #1828: Improved symlink handling in ftpfs Originally from: http://mail.gnome.org/archives/mc-devel/2005-April/msg00035.html The routines `vfs_s_find_entry_linear()' and `vfs_s_find_entry_tree()' call `canonicalize_pathname()' on entry. This routine eats `..' path components in certain cases. In case of ftpfs this is not desired - the path should be kept as is since the code in direntry.c doesn't have enough knowledge of the directory structure on the remote end. Assume that there is a path like this on the remote server /path1/path2/path3 The `path2' component is a symlink to some directory and `path3' is a symlink stored in `path2' which is relative to `path2' i.e. path2 path3 -> ../some/other/path Now, the code in direntry.c will determine that `path3' is a symlink and will try to resolve (vfs_s_resolve_symlink) it by passing the following path /path1/path2/../some/other/path to `vfs_s_find_entry_linear' . As I've said above this routine calls `canonicalize_pathname' on entry which will modify the path like this: /path1/some/other/path Now this is clearly wrong since `path2' is a symlink and it should be resolved first. In the case of ftpfs the code in direntry.c doesn't have enough knowledge about physycal directory layout on the remote filesystem so it shouldn't try to canonicalize the path. The path should be left as is and passed to the remote end for processing. Fix issue: Changed function canonicalize_pathname (as fact, renamed to custom_canonicalize_pathname) In this function added ability to partial canonicalize of pathname. And some functions from vfs/direntry.c calls custom_canonicalize_pathname without removal of '..' stuff. Signed-off-by: Slava Zanko <slavazanko@gmail.com>
2009-11-23 11:45:46 +03:00
/* canonicalize as well, but don't remove '../' from path */
custom_canonicalize_pathname (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
while (root != NULL)
{
GList *iter;
while (*path == PATH_SEP) /* Strip leading '/' */
path++;
if (path[0] == '\0')
{
g_free (pathref);
return ent;
}
for (pseg = 0; path[pseg] != '\0' && path[pseg] != PATH_SEP; pseg++)
;
for (iter = root->subdir; iter != NULL; iter = g_list_next (iter))
{
ent = (struct vfs_s_entry *) iter->data;
if (strlen (ent->name) == pseg && strncmp (ent->name, path, pseg) == 0)
/* FOUND! */
break;
}
ent = iter != NULL ? (struct vfs_s_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;
}
/* --------------------------------------------------------------------------------------------- */
1999-01-18 02:10:05 +03:00
static void
split_dir_name (struct vfs_class *me, char *path, char **dir, char **name, char **save)
{
char *s;
(void) me;
1999-01-18 02:10:05 +03:00
s = strrchr (path, PATH_SEP);
if (s == NULL)
{
*save = NULL;
*name = path;
*dir = path + strlen (path); /* an empty string */
}
else
{
*save = s;
*dir = path;
*s++ = '\0';
*name = s;
}
}
/* --------------------------------------------------------------------------------------------- */
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);
struct vfs_s_entry *retval = NULL;
GList *iter;
if (root->super->root != root)
vfs_die ("We have to use _real_ root. Always. Sorry.");
Ticket #1828: Improved symlink handling in ftpfs Originally from: http://mail.gnome.org/archives/mc-devel/2005-April/msg00035.html The routines `vfs_s_find_entry_linear()' and `vfs_s_find_entry_tree()' call `canonicalize_pathname()' on entry. This routine eats `..' path components in certain cases. In case of ftpfs this is not desired - the path should be kept as is since the code in direntry.c doesn't have enough knowledge of the directory structure on the remote end. Assume that there is a path like this on the remote server /path1/path2/path3 The `path2' component is a symlink to some directory and `path3' is a symlink stored in `path2' which is relative to `path2' i.e. path2 path3 -> ../some/other/path Now, the code in direntry.c will determine that `path3' is a symlink and will try to resolve (vfs_s_resolve_symlink) it by passing the following path /path1/path2/../some/other/path to `vfs_s_find_entry_linear' . As I've said above this routine calls `canonicalize_pathname' on entry which will modify the path like this: /path1/some/other/path Now this is clearly wrong since `path2' is a symlink and it should be resolved first. In the case of ftpfs the code in direntry.c doesn't have enough knowledge about physycal directory layout on the remote filesystem so it shouldn't try to canonicalize the path. The path should be left as is and passed to the remote end for processing. Fix issue: Changed function canonicalize_pathname (as fact, renamed to custom_canonicalize_pathname) In this function added ability to partial canonicalize of pathname. And some functions from vfs/direntry.c calls custom_canonicalize_pathname without removal of '..' stuff. Signed-off-by: Slava Zanko <slavazanko@gmail.com>
2009-11-23 11:45:46 +03:00
/* canonicalize as well, but don't remove '../' from path */
custom_canonicalize_pathname (path, CANON_PATH_ALL & (~CANON_PATH_REMDOUBLEDOTS));
if ((flags & FL_DIR) == 0)
{
char *dirname, *name, *save;
struct vfs_s_inode *ino;
split_dir_name (me, path, &dirname, &name, &save);
ino = vfs_s_find_inode (me, root->super, dirname, follow, flags | FL_DIR);
if (save != NULL)
*save = PATH_SEP;
retval = vfs_s_find_entry_tree (me, ino, name, follow, flags);
g_free (path);
return retval;
}
iter = g_list_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
ent = iter != NULL ? (struct vfs_s_entry *) iter->data : NULL;
if (ent != NULL && !MEDATA->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 (MEDATA->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_list_find_custom (root->subdir, path, (GCompareFunc) vfs_s_entry_compare);
ent = iter != NULL ? (struct vfs_s_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))
{
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)
{
MEDATA->supers = g_list_prepend (MEDATA->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 ammount of memory, sometimes. Fix it if you can. */
if (super->ino_usage)
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
MEDATA->supers = g_list_remove (MEDATA->supers, super);
1999-01-18 02:10:05 +03:00
CALL (free_archive) (me, super);
#ifdef ENABLE_VFS_NET
vfs_url_free (super->url);
#endif
g_free (super->name);
g_free (super);
}
/* --------------------------------------------------------------------------------------------- */
/**
* Dissect the path and create corresponding superblock.
* The result should be freed.
*/
static char *
vfs_s_get_path (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags)
{
return g_strdup (vfs_s_get_path_mangle (vpath, archive, flags));
}
/* --------------------------------------------------------------------------------------------- */
/* 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;
char *q;
vfs_path_element_t *path_element;
q = vfs_s_get_path (vpath, &super, 0);
if (q == NULL)
return NULL;
path_element = vfs_path_get_by_index (vpath, -1);
ino =
vfs_s_find_inode (path_element->class, super, q,
flags & FL_FOLLOW ? LINK_FOLLOW : LINK_NO_FOLLOW, flags & ~FL_FOLLOW);
if ((!ino) && (!*q))
/* We are asking about / directory of ftp server: assume it exists */
ino =
vfs_s_find_inode (path_element->class, super, q,
flags & FL_FOLLOW ? LINK_FOLLOW :
LINK_NO_FOLLOW, FL_DIR | (flags & ~FL_FOLLOW));
g_free (q);
return ino;
}
/* --------------------------------------------------------------------------------------------- */
static void *
vfs_s_opendir (const vfs_path_t * vpath)
{
struct vfs_s_inode *dir;
struct dirhandle *info;
vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
dir = vfs_s_inode_from_path (vpath, FL_DIR | FL_FOLLOW);
if (dir == NULL)
return NULL;
if (!S_ISDIR (dir->st.st_mode))
{
path_element->class->verrno = ENOTDIR;
return NULL;
}
dir->st.st_nlink++;
#if 0
if (dir->subdir == NULL) /* This can actually happen if we allow empty directories */
{
path_element->class->verrno = EAGAIN;
return NULL;
}
#endif
info = g_new (struct dirhandle, 1);
info->cur = dir->subdir;
info->dir = dir;
return info;
}
/* --------------------------------------------------------------------------------------------- */
static void *
vfs_s_readdir (void *data)
{
static union vfs_dirent dir;
struct dirhandle *info = (struct dirhandle *) data;
const char *name;
if (info->cur == NULL || info->cur->data == NULL)
return NULL;
name = ((struct vfs_s_entry *) info->cur->data)->name;
if (name != NULL)
g_strlcpy (dir.dent.d_name, name, MC_MAXPATHLEN);
else
vfs_die ("Null in structure-cannot happen");
compute_namelen (&dir.dent);
info->cur = g_list_next (info->cur);
return (void *) &dir;
}
/* --------------------------------------------------------------------------------------------- */
static int
1999-01-18 02:10:05 +03:00
vfs_s_closedir (void *data)
{
struct dirhandle *info = (struct dirhandle *) data;
struct vfs_s_inode *dir = info->dir;
1999-01-18 02:10:05 +03:00
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;
1999-01-18 02:10:05 +03:00
vfs_s_closedir (data);
return 0;
}
/* --------------------------------------------------------------------------------------------- */
/* --------------------------- stat and friends ---------------------------- */
1999-01-18 02:10:05 +03:00
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_stat (const vfs_path_t * vpath, struct stat *buf)
{
return vfs_s_internal_stat (vpath, buf, FL_FOLLOW);
}
/* --------------------------------------------------------------------------------------------- */
static int
vfs_s_lstat (const vfs_path_t * vpath, struct stat *buf)
{
return vfs_s_internal_stat (vpath, buf, FL_NONE);
}
/* --------------------------------------------------------------------------------------------- */
static int
1999-01-18 02:10:05 +03:00
vfs_s_fstat (void *fh, struct stat *buf)
{
*buf = FH->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;
vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
ino = vfs_s_inode_from_path (vpath, 0);
if (!ino)
return -1;
if (!S_ISLNK (ino->st.st_mode))
{
path_element->class->verrno = EINVAL;
return -1;
}
if (ino->linkname == NULL)
{
path_element->class->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)
{
int n;
struct vfs_class *me = FH_SUPER->me;
if (FH->linear == LS_LINEAR_PREOPEN)
{
if (!MEDATA->linear_start (me, FH, FH->pos))
return -1;
}
if (FH->linear == LS_LINEAR_CLOSED)
vfs_die ("linear_start() did not set linear_state!");
if (FH->linear == LS_LINEAR_OPEN)
return MEDATA->linear_read (me, FH, buffer, count);
if (FH->handle != -1)
{
n = read (FH->handle, buffer, count);
if (n < 0)
me->verrno = errno;
return n;
}
1999-01-18 02:10:05 +03:00
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)
{
int n;
struct vfs_class *me = FH_SUPER->me;
if (FH->linear)
vfs_die ("no writing to linear files, please");
FH->changed = 1;
if (FH->handle != -1)
{
n = write (FH->handle, buffer, count);
if (n < 0)
me->verrno = errno;
return n;
}
1999-01-18 02:10:05 +03:00
vfs_die ("vfs_s_write: This should not happen\n");
return 0;
}
/* --------------------------------------------------------------------------------------------- */
static off_t
1999-01-18 02:10:05 +03:00
vfs_s_lseek (void *fh, off_t offset, int whence)
{
off_t size = FH->ino->st.st_size;
if (FH->linear == LS_LINEAR_OPEN)
vfs_die ("cannot lseek() after linear_read!");
if (FH->handle != -1)
{ /* If we have local file opened, we want to work with it */
off_t retval = lseek (FH->handle, offset, whence);
if (retval == -1)
FH->ino->super->me->verrno = errno;
return retval;
}
switch (whence)
{
case SEEK_CUR:
offset += FH->pos;
break;
case SEEK_END:
offset += size;
break;
}
if (offset < 0)
FH->pos = 0;
else if (offset < size)
FH->pos = offset;
else
FH->pos = size;
return FH->pos;
}
/* --------------------------------------------------------------------------------------------- */
static int
1999-01-18 02:10:05 +03:00
vfs_s_close (void *fh)
{
int res = 0;
struct vfs_class *me = FH_SUPER->me;
FH_SUPER->fd_usage--;
if (!FH_SUPER->fd_usage)
vfs_stamp_create (me, FH_SUPER);
if (FH->linear == LS_LINEAR_OPEN)
MEDATA->linear_close (me, fh);
if (MEDATA->fh_close)
res = MEDATA->fh_close (me, fh);
if (FH->changed && MEDATA->file_store)
{
char *s = vfs_s_fullpath (me, FH->ino);
if (!s)
res = -1;
else
{
res = MEDATA->file_store (me, fh, s, FH->ino->localname);
g_free (s);
}
vfs_s_invalidate (me, FH_SUPER);
}
if (FH->handle != -1)
close (FH->handle);
vfs_s_free_inode (me, FH->ino);
g_free (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)
{
static const char *i18n_percent_transf_format = NULL;
static const char *i18n_transf_format = NULL;
if (i18n_percent_transf_format == NULL)
{
i18n_percent_transf_format = "%s: %s: %s %3d%% (%" PRIuMAX " %s";
i18n_transf_format = "%s: %s: %s %" PRIuMAX " %s";
}
if (need)
vfs_print_message (i18n_percent_transf_format, fs_name, action,
file_name, (int) ((double) have * 100 / need), (uintmax_t) have,
_("bytes transferred"));
else
vfs_print_message (i18n_transf_format, fs_name, action, file_name, (uintmax_t) have,
_("bytes transferred"));
}
/* --------------------------------------------------------------------------------------------- */
/* ------------------------------- mc support ---------------------------- */
static void
vfs_s_fill_names (struct vfs_class *me, fill_names_f func)
{
GList *iter;
for (iter = MEDATA->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, "#", me->prefix, "/",
/* 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 char *
vfs_s_getlocalcopy (const vfs_path_t * vpath)
{
vfs_file_handler_t *fh;
char *local = NULL;
fh = vfs_s_open (vpath, O_RDONLY, 0);
if (fh != NULL)
{
if ((fh->ino != NULL) && (fh->ino->localname != NULL))
local = g_strdup (fh->ino->localname);
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 char *local, int 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)
{
vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
switch (ctlop)
{
case VFS_SETCTL_STALE_DATA:
{
struct vfs_s_inode *ino = vfs_s_inode_from_path (vpath, 0);
if (ino == NULL)
return 0;
if (arg)
ino->super->want_stale = 1;
else
{
ino->super->want_stale = 0;
vfs_s_invalidate (path_element->class, ino->super);
}
return 1;
}
case VFS_SETCTL_LOGFILE:
((struct vfs_s_subclass *) path_element->class->data)->logfile = fopen ((char *) arg, "w");
return 1;
case VFS_SETCTL_FLUSH:
((struct vfs_s_subclass *) path_element->class->data)->flush = 1;
return 1;
}
return 0;
}
/* --------------------------------------------------------------------------------------------- */
/* ----------------------------- Stamping support -------------------------- */
static vfsid
vfs_s_getid (const vfs_path_t * vpath)
{
struct vfs_s_super *archive = NULL;
char *p;
p = vfs_s_get_path (vpath, &archive, FL_NO_OPEN);
if (p == NULL)
return NULL;
g_free (p);
return (vfsid) archive;
}
/* --------------------------------------------------------------------------------------------- */
static int
1999-01-18 02:10:05 +03:00
vfs_s_nothingisopen (vfsid id)
{
(void) id;
/* Our data structures should survive free of superblock at any time */
return 1;
}
/* --------------------------------------------------------------------------------------------- */
static void
1999-01-18 02:10:05 +03:00
vfs_s_free (vfsid id)
{
vfs_s_free_super (((struct vfs_s_super *) id)->me, (struct vfs_s_super *) id);
}
/* --------------------------------------------------------------------------------------------- */
static int
vfs_s_dir_uptodate (struct vfs_class *me, struct vfs_s_inode *ino)
{
struct timeval tim;
if (MEDATA->flush)
{
MEDATA->flush = 0;
return 0;
}
gettimeofday (&tim, NULL);
if (tim.tv_sec < ino->timestamp.tv_sec)
return 1;
return 0;
}
/* --------------------------------------------------------------------------------------------- */
/*** 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)
ino->st = *initstat;
ino->super = super;
ino->st.st_nlink = 0;
ino->st.st_ino = MEDATA->inode_counter++;
ino->st.st_dev = MEDATA->rdev;
super->ino_usage++;
total_inodes++;
CALL (init_inode) (me, ino);
return 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);
total_entries++;
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)
ent->dir->subdir = g_list_remove (ent->dir->subdir, ent);
g_free (ent->name);
/* ent->name = NULL; */
if (ent->ino != NULL)
{
ent->ino->ent = NULL;
vfs_s_free_inode (me, ent->ino);
}
total_entries--;
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++;
dir->subdir = g_list_append (dir->subdir, ent);
}
/* --------------------------------------------------------------------------------------------- */
struct stat *
vfs_s_default_stat (struct vfs_class *me, mode_t mode)
{
static struct stat st;
int myumask;
(void) me;
myumask = umask (022);
umask (myumask);
mode &= ~myumask;
st.st_mode = mode;
st.st_ino = 0;
st.st_dev = 0;
st.st_rdev = 0;
st.st_uid = getuid ();
st.st_gid = getgid ();
st.st_size = 0;
st.st_mtime = st.st_atime = st.st_ctime = time (NULL);
return &st;
}
/* --------------------------------------------------------------------------------------------- */
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 (((MEDATA->flags & VFS_S_REMOTE) == 0) && (*path == '\0'))
return super->root;
ent = (MEDATA->find_entry) (me, super->root, path, follow, flags);
return (ent != NULL) ? ent->ino : NULL;
}
/* --------------------------------------------------------------------------------------------- */
/* Ook, these were functions around directory entries / inodes */
/* -------------------------------- superblock games -------------------------- */
/*
* Dissect the path and create corresponding superblock. Note that inname
* can be changed and the result may point inside the original string.
*/
const char *
vfs_s_get_path_mangle (const vfs_path_t * vpath, struct vfs_s_super **archive, int flags)
{
GList *iter;
const char *retval;
char *archive_name;
int result = -1;
struct vfs_s_super *super;
void *cookie = NULL;
vfs_path_element_t *path_element;
struct vfs_s_subclass *subclass;
path_element = vfs_path_get_by_index (vpath, -1);
subclass = ((struct vfs_s_subclass *) path_element->class->data);
archive_name = vfs_path_to_str_elements_count (vpath, -1);
retval = (path_element->path != NULL) ? path_element->path : "";
if (subclass->archive_check != NULL)
{
cookie =
subclass->archive_check (path_element->class, archive_name, path_element->raw_url_str);
if (cookie == NULL)
{
g_free (archive_name);
return NULL;
}
}
for (iter = subclass->supers; iter != NULL; iter = g_list_next (iter))
{
int i;
super = (struct vfs_s_super *) iter->data;
/* 0 == other, 1 == same, return it, 2 == other but stop scanning */
i = subclass->archive_same (path_element->class, super, archive_name,
path_element->raw_url_str, cookie);
if (i != 0)
{
if (i == 1)
goto return_success;
break;
}
}
if (flags & FL_NO_OPEN)
{
path_element->class->verrno = EIO;
g_free (archive_name);
return NULL;
}
super = vfs_s_new_super (path_element->class);
if (subclass->open_archive != NULL)
result =
subclass->open_archive (path_element->class, super, archive_name,
path_element->raw_url_str);
if (result == -1)
{
vfs_s_free_super (path_element->class, super);
path_element->class->verrno = EIO;
g_free (archive_name);
return NULL;
}
if (!super->name)
vfs_die ("You have to fill name\n");
if (!super->root)
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;
g_free (archive_name);
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)
ERRNOR (EAGAIN, NULL);
if (!(MEDATA->flags & VFS_S_REMOTE))
{
/* archives */
char *newpath;
char *path = g_strdup (ino->ent->name);
while (1)
{
ino = ino->ent->dir;
if (ino == ino->super->root)
break;
newpath = g_strconcat (ino->ent->name, "/", path, (char *) NULL);
g_free (path);
path = newpath;
}
return path;
}
/* remote systems */
if ((!ino->ent->dir) || (!ino->ent->dir->ent))
return g_strdup (ino->ent->name);
return g_strconcat (ino->ent->dir->ent->name, PATH_SEP_STR, ino->ent->name, (char *) NULL);
}
/* --------------------------------------------------------------------------------------------- */
/* --------------------------- stat and friends ---------------------------- */
void *
vfs_s_open (const vfs_path_t * vpath, int flags, mode_t mode)
{
int was_changed = 0;
vfs_file_handler_t *fh;
struct vfs_s_super *super;
char *q;
struct vfs_s_inode *ino;
vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
q = vfs_s_get_path (vpath, &super, 0);
if (q == NULL)
return NULL;
ino = vfs_s_find_inode (path_element->class, super, q, LINK_FOLLOW, FL_NONE);
if (ino && ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL)))
{
g_free (q);
path_element->class->verrno = EEXIST;
return NULL;
}
if (!ino)
{
char *dirname, *name, *save;
struct vfs_s_entry *ent;
struct vfs_s_inode *dir;
int tmp_handle;
/* If the filesystem is read-only, disable file creation */
if (!(flags & O_CREAT) || !(path_element->class->write))
{
g_free (q);
return NULL;
}
split_dir_name (path_element->class, q, &dirname, &name, &save);
dir = vfs_s_find_inode (path_element->class, super, dirname, LINK_FOLLOW, FL_DIR);
if (dir == NULL)
{
g_free (q);
return NULL;
}
if (save)
*save = PATH_SEP;
ent = vfs_s_generate_entry (path_element->class, name, dir, 0755);
ino = ent->ino;
vfs_s_insert_entry (path_element->class, dir, ent);
tmp_handle = vfs_mkstemps (&ino->localname, path_element->class->name, name);
if (tmp_handle == -1)
{
g_free (q);
return NULL;
}
close (tmp_handle);
was_changed = 1;
}
g_free (q);
if (S_ISDIR (ino->st.st_mode))
{
path_element->class->verrno = EISDIR;
return NULL;
}
fh = g_new (vfs_file_handler_t, 1);
fh->pos = 0;
fh->ino = ino;
fh->handle = -1;
fh->changed = was_changed;
fh->linear = 0;
fh->data = NULL;
if (IS_LINEAR (flags))
{
if (VFSDATA (path_element)->linear_start)
{
vfs_print_message (_("Starting linear transfer..."));
fh->linear = LS_LINEAR_PREOPEN;
}
}
else if ((VFSDATA (path_element)->fh_open != NULL)
&& (VFSDATA (path_element)->fh_open (path_element->class, fh, flags, mode) != 0))
{
g_free (fh);
return NULL;
}
if (fh->ino->localname)
{
fh->handle = open (fh->ino->localname, NO_LINEAR (flags), mode);
if (fh->handle == -1)
{
g_free (fh);
path_element->class->verrno = errno;
return NULL;
}
}
/* i.e. we had no open files and now we have one */
vfs_rmstamp (path_element->class, (vfsid) super);
super->fd_usage++;
fh->ino->st.st_nlink++;
return fh;
}
/* --------------------------------------------------------------------------------------------- */
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[8192];
int handle, n;
off_t stat_size = ino->st.st_size;
vfs_file_handler_t fh;
memset (&fh, 0, sizeof (fh));
fh.ino = ino;
fh.handle = -1;
handle = vfs_mkstemps (&ino->localname, me->name, ino->ent->name);
if (handle == -1)
{
me->verrno = errno;
goto error_4;
}
if (!MEDATA->linear_start (me, &fh, 0))
goto error_3;
/* Clear the interrupt status */
tty_got_interrupt ();
tty_enable_interrupt_key ();
while ((n = MEDATA->linear_read (me, &fh, buffer, sizeof (buffer))))
{
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;
}
}
MEDATA->linear_close (me, &fh);
close (handle);
tty_disable_interrupt_key ();
g_free (fh.data);
return 0;
error_1:
MEDATA->linear_close (me, &fh);
error_3:
tty_disable_interrupt_key ();
close (handle);
unlink (ino->localname);
error_4:
g_free (ino->localname);
ino->localname = NULL;
g_free (fh.data);
return -1;
}
/* --------------------------------------------------------------------------------------------- */
/* ----------------------------- Stamping support -------------------------- */
/* Initialize one of our subclasses - fill common functions */
void
vfs_s_init_class (struct vfs_class *vclass, struct vfs_s_subclass *sub)
{
vclass->data = sub;
vclass->fill_names = vfs_s_fill_names;
vclass->open = vfs_s_open;
vclass->close = vfs_s_close;
vclass->read = vfs_s_read;
if (!(sub->flags & VFS_S_READONLY))
{
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;
if (sub->flags & VFS_S_REMOTE)
{
vclass->getlocalcopy = vfs_s_getlocalcopy;
vclass->ungetlocalcopy = vfs_s_ungetlocalcopy;
sub->find_entry = vfs_s_find_entry_linear;
}
else
{
sub->find_entry = vfs_s_find_entry_tree;
}
vclass->setctl = vfs_s_setctl;
sub->dir_uptodate = vfs_s_dir_uptodate;
}
/* --------------------------------------------------------------------------------------------- */
/** Find VFS id for given directory name */
vfsid
vfs_getid (const vfs_path_t * vpath)
{
vfs_path_element_t *path_element;
path_element = vfs_path_get_by_index (vpath, -1);
if (path_element == NULL || path_element->class->getid == NULL)
return NULL;
return (*path_element->class->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 = (fd1 > fd2 ? fd1 : fd2) + 1;
time_out.tv_sec = 1;
time_out.tv_usec = 0;
1999-01-18 02:10:05 +03:00
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 = MEDATA->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)
{
size_t ret1;
int ret2;
ret1 = fwrite (buf, 1, 1, logfile);
ret2 = fflush (logfile);
}
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)
{
size_t ret1;
int ret2;
ret1 = fwrite (&c, 1, 1, logfile);
ret2 = fflush (logfile);
}
if (c == '\n')
return 1;
}
return 0;
}
/* --------------------------------------------------------------------------------------------- */
int
vfs_s_get_line_interruptible (struct vfs_class *me, char *buffer, int size, int fd)
{
int n;
int i;
(void) me;
tty_enable_interrupt_key ();
for (i = 0; i < size - 1; i++)
{
n = read (fd, buffer + i, 1);
tty_disable_interrupt_key ();
if (n == -1 && errno == EINTR)
{
buffer[i] = 0;
return EINTR;
}
if (n == 0)
{
buffer[i] = 0;
return 0;
}
if (buffer[i] == '\n')
{
buffer[i] = 0;
return 1;
}
}
buffer[size - 1] = 0;
return 0;
}
#endif /* ENABLE_VFS_NET */
/* --------------------------------------------------------------------------------------------- */