mc/lib/vfs/vfs.c

718 lines
19 KiB
C
Raw Normal View History

/*
Virtual File System switch code
Copyright (C) 1995-2019
Free Software Foundation, Inc.
1998-02-27 07:54:42 +03:00
Written by: 1995 Miguel de Icaza
Jakub Jelinek, 1995
Pavel Machek, 1998
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,
1998-02-27 07:54:42 +03:00
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.
1998-02-27 07:54:42 +03:00
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
1998-02-27 07:54:42 +03:00
/**
* \file
* \brief Source: Virtual File System switch code
* \author Miguel de Icaza
* \author Jakub Jelinek
* \author Pavel Machek
* \date 1995, 1998
* \warning functions like extfs_lstat() have right to destroy any
* strings you pass to them. This is acutally ok as you g_strdup what
* you are passing to them, anyway; still, beware.
*
* Namespace: exports *many* functions with vfs_ prefix; exports
* parse_ls_lga and friends which do not have that prefix.
*/
1998-10-13 02:07:53 +04:00
1998-02-27 07:54:42 +03:00
#include <config.h>
1998-02-27 07:54:42 +03:00
#include <errno.h>
Ticket #3093: keep symlinks in cwd at startup. If you navigate in your shell to a directory containing symlinks and then start mc, mc will show the canonical path instead. It would be nice to make it show the directory with the symlinks. Example: in your shell execute these: user:~$ mkdir -p /tmp/a/b /tmp/x ; ln -s /tmp/a/b /tmp/x/y user:~$ cd /tmp/x/y user:/tmp/x/y$ mc In mc you'll find yourself in /tmp/a/b, though it'd be nicer to see /tmp/x/y at the top, and correspondingly navigating to the parent would take you to /tmp/x. If you start bash or zsh from /tmp/x/y, the new instance will start displaying the working directory as such. They do this via the PWD env variable. On one hand, they set and maintain PWD to point to the current directory, using the path as specified by the user (possibly containing symbolic links). On the other hand, they check its value at startup. If $PWD points to the same physical directory as the actual working directory then they use this value. If $PWD points somewhere else then it's simply ignored (so it's a hint only as to which symlinks to use to get to the working directory, but never alters the actual cwd). Now mc also does the same at startup (with respect of "Cd follows links" option). Relative directories specified in the command line are applied after possibly replacing the canonical cwd with $PWD. This way for example user:/tmp/x/y$ mc . .. opens two panels in /tmp/x/y and /tmp/x instead of /tmp/a/b and /tmp/a (whereas /tmp/x is actually a different directory than /tmp/a). Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
2013-12-02 14:38:43 +04:00
#include <stdlib.h>
#ifdef __linux__
#ifdef HAVE_LINUX_FS_H
#include <linux/fs.h>
#endif /* HAVE_LINUX_FS_H */
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */
#endif /* __linux__ */
#include "lib/global.h"
#include "lib/strutil.h"
#include "lib/util.h"
#include "lib/widget.h" /* message() */
#include "lib/event.h"
#ifdef HAVE_CHARSET
#include "lib/charsets.h"
#endif
#include "vfs.h"
#include "utilvfs.h"
#include "gc.h"
/* TODO: move it to the separate .h */
extern struct dirent *mc_readdir_result;
extern GPtrArray *vfs__classes_list;
extern GString *vfs_str_buffer;
extern vfs_class *current_vfs;
/*** global variables ****************************************************************************/
GPtrArray *vfs__classes_list = NULL;
GString *vfs_str_buffer = NULL;
vfs_class *current_vfs = NULL;
/*** file scope macro definitions ****************************************************************/
#define VFS_FIRST_HANDLE 100
/*** file scope type declarations ****************************************************************/
1998-02-27 07:54:42 +03:00
struct vfs_openfile
{
int handle;
vfs_class *vclass;
void *fsinfo;
};
/*** file scope variables ************************************************************************/
/** They keep track of the current directory */
static vfs_path_t *current_path = NULL;
static GPtrArray *vfs_openfiles = NULL;
static long vfs_free_handle_list = -1;
1998-02-27 07:54:42 +03:00
/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */
/* now used only by vfs_translate_path, but could be used in other vfs
* plugin to automatic detect encoding
* path - path to translate
* size - how many bytes from path translate
* defcnv - convertor, that is used as default, when path does not contain any
* #enc: subtring
* buffer - used to store result of translation
*/
static estr_t
_vfs_translate_path (const char *path, int size, GIConv defcnv, GString * buffer)
{
estr_t state = ESTR_SUCCESS;
#ifdef HAVE_CHARSET
const char *semi;
if (size == 0)
return ESTR_SUCCESS;
size = (size > 0) ? size : (signed int) strlen (path);
/* try found /#enc: */
semi = g_strrstr_len (path, size, VFS_ENCODING_PREFIX);
if (semi != NULL && (semi == path || IS_PATH_SEP (semi[-1])))
{
char encoding[16];
const char *slash;
GIConv coder = INVALID_CONV;
int ms;
/* first must be translated part before #enc: */
ms = semi - path;
state = _vfs_translate_path (path, ms, defcnv, buffer);
if (state != ESTR_SUCCESS)
return state;
/* now can be translated part after #enc: */
semi += strlen (VFS_ENCODING_PREFIX); /* skip "#enc:" */
slash = strchr (semi, PATH_SEP);
/* ignore slashes after size; */
if (slash - path >= size)
slash = NULL;
ms = (slash != NULL) ? slash - semi : (int) strlen (semi);
ms = MIN ((unsigned int) ms, sizeof (encoding) - 1);
/* limit encoding size (ms) to path size (size) */
if (semi + ms > path + size)
ms = path + size - semi;
memcpy (encoding, semi, ms);
encoding[ms] = '\0';
if (is_supported_encoding (encoding))
coder = str_crt_conv_to (encoding);
if (coder != INVALID_CONV)
{
if (slash != NULL)
state = str_vfs_convert_to (coder, slash + 1, path + size - slash - 1, buffer);
str_close_conv (coder);
return state;
}
errno = EINVAL;
state = ESTR_FAILURE;
}
else
{
/* path can be translated whole at once */
state = str_vfs_convert_to (defcnv, path, size, buffer);
}
#else
(void) size;
(void) defcnv;
g_string_assign (buffer, path);
#endif /* HAVE_CHARSET */
return state;
}
/* --------------------------------------------------------------------------------------------- */
static struct vfs_openfile *
vfs_get_openfile (int handle)
{
struct vfs_openfile *h;
if (handle < VFS_FIRST_HANDLE || (guint) (handle - VFS_FIRST_HANDLE) >= vfs_openfiles->len)
return NULL;
h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, handle - VFS_FIRST_HANDLE);
if (h == NULL)
return NULL;
g_assert (h->handle == handle);
return h;
}
/* --------------------------------------------------------------------------------------------- */
static gboolean
vfs_test_current_dir (const vfs_path_t * vpath)
{
struct stat my_stat, my_stat2;
return (mc_global.vfs.cd_symlinks && mc_stat (vpath, &my_stat) == 0
&& mc_stat (vfs_get_raw_current_dir (), &my_stat2) == 0
&& my_stat.st_ino == my_stat2.st_ino && my_stat.st_dev == my_stat2.st_dev);
}
/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */
/** Free open file data for given file handle */
void
vfs_free_handle (int handle)
{
const int idx = handle - VFS_FIRST_HANDLE;
if (handle >= VFS_FIRST_HANDLE && (guint) idx < vfs_openfiles->len)
{
struct vfs_openfile *h;
h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, idx);
g_free (h);
g_ptr_array_index (vfs_openfiles, idx) = (void *) vfs_free_handle_list;
vfs_free_handle_list = idx;
}
}
/* --------------------------------------------------------------------------------------------- */
/** Find VFS class by file handle */
struct vfs_class *
vfs_class_find_by_handle (int handle, void **fsinfo)
{
struct vfs_openfile *h;
h = vfs_get_openfile (handle);
if (h == NULL)
return NULL;
if (fsinfo != NULL)
*fsinfo = h->fsinfo;
return h->vclass;
}
/* --------------------------------------------------------------------------------------------- */
/**
* Create new VFS handle and put it to the list
*/
int
vfs_new_handle (struct vfs_class *vclass, void *fsinfo)
{
struct vfs_openfile *h;
h = g_new (struct vfs_openfile, 1);
h->fsinfo = fsinfo;
h->vclass = vclass;
/* Allocate the first free handle */
h->handle = vfs_free_handle_list;
if (h->handle == -1)
{
/* No free allocated handles, allocate one */
h->handle = vfs_openfiles->len;
g_ptr_array_add (vfs_openfiles, h);
}
else
{
vfs_free_handle_list = (long) g_ptr_array_index (vfs_openfiles, vfs_free_handle_list);
g_ptr_array_index (vfs_openfiles, h->handle) = h;
}
h->handle += VFS_FIRST_HANDLE;
return h->handle;
}
/* --------------------------------------------------------------------------------------------- */
int
vfs_ferrno (struct vfs_class *vfs)
{
return vfs->ferrno ? (*vfs->ferrno) (vfs) : E_UNKNOWN;
/* Hope that error message is obscure enough ;-) */
}
/* --------------------------------------------------------------------------------------------- */
gboolean
vfs_register_class (struct vfs_class * vfs)
{
if (vfs->init != NULL) /* vfs has own initialization function */
if (!vfs->init (vfs)) /* but it failed */
return FALSE;
g_ptr_array_add (vfs__classes_list, vfs);
return TRUE;
}
/* --------------------------------------------------------------------------------------------- */
void
vfs_unregister_class (struct vfs_class *vfs)
{
if (vfs->done != NULL)
vfs->done (vfs);
g_ptr_array_remove (vfs__classes_list, vfs);
}
/* --------------------------------------------------------------------------------------------- */
/** Strip known vfs suffixes from a filename (possible improvement: strip
* suffix from last path component).
* \return a malloced string which has to be freed.
*/
char *
vfs_strip_suffix_from_filename (const char *filename)
{
char *semi, *p;
if (filename == NULL)
vfs_die ("vfs_strip_suffix_from_path got NULL: impossible");
p = g_strdup (filename);
semi = g_strrstr (p, VFS_PATH_URL_DELIMITER);
if (semi != NULL)
{
char *vfs_prefix;
*semi = '\0';
vfs_prefix = strrchr (p, PATH_SEP);
if (vfs_prefix == NULL)
*semi = *VFS_PATH_URL_DELIMITER;
else
*vfs_prefix = '\0';
}
return p;
}
/* --------------------------------------------------------------------------------------------- */
const char *
vfs_translate_path (const char *path)
{
estr_t state;
g_string_set_size (vfs_str_buffer, 0);
state = _vfs_translate_path (path, -1, str_cnv_from_term, vfs_str_buffer);
return (state != ESTR_FAILURE) ? vfs_str_buffer->str : NULL;
}
/* --------------------------------------------------------------------------------------------- */
char *
vfs_translate_path_n (const char *path)
{
const char *result;
result = vfs_translate_path (path);
return g_strdup (result);
}
/* --------------------------------------------------------------------------------------------- */
/**
* Get current directory without any OS calls.
*
* @return string contains current path
*/
const char *
vfs_get_current_dir (void)
{
return current_path->str;
}
/* --------------------------------------------------------------------------------------------- */
/**
* Get current directory without any OS calls.
*
* @return newly allocated string contains current path
*/
char *
vfs_get_current_dir_n (void)
1998-02-27 07:54:42 +03:00
{
return g_strdup (current_path->str);
}
/* --------------------------------------------------------------------------------------------- */
/**
* Get raw current directory object without any OS calls.
*
* @return object contain current path
*/
const vfs_path_t *
vfs_get_raw_current_dir (void)
{
return current_path;
}
/* --------------------------------------------------------------------------------------------- */
/**
* Set current directory object.
*
* @param vpath new path
*/
void
vfs_set_raw_current_dir (const vfs_path_t * vpath)
{
vfs_path_free (current_path);
current_path = (vfs_path_t *) vpath;
}
/* --------------------------------------------------------------------------------------------- */
/* Return TRUE is the current VFS class is local */
gboolean
vfs_current_is_local (void)
1998-02-27 07:54:42 +03:00
{
return (current_vfs->flags & VFS_LOCAL) != 0;
1998-02-27 07:54:42 +03:00
}
/* --------------------------------------------------------------------------------------------- */
/* Return flags of the VFS class of the given filename */
vfs_flags_t
vfs_file_class_flags (const vfs_path_t * vpath)
1998-02-27 07:54:42 +03:00
{
const vfs_path_element_t *path_element;
1998-02-27 07:54:42 +03:00
path_element = vfs_path_get_by_index (vpath, -1);
if (!vfs_path_element_valid (path_element))
return VFS_UNKNOWN;
return path_element->class->flags;
1998-02-27 07:54:42 +03:00
}
/* --------------------------------------------------------------------------------------------- */
1998-02-27 07:54:42 +03:00
void
vfs_init (void)
1998-02-27 07:54:42 +03:00
{
/* create the VFS handle arrays */
vfs__classes_list = g_ptr_array_new ();
/* create the VFS handle array */
vfs_openfiles = g_ptr_array_new ();
vfs_str_buffer = g_string_new ("");
}
/* --------------------------------------------------------------------------------------------- */
void
vfs_setup_work_dir (void)
{
const vfs_path_element_t *path_element;
vfs_setup_cwd ();
/* FIXME: is we really need for this check? */
/*
if (strlen (current_dir) > MC_MAXPATHLEN - 2)
vfs_die ("Current dir too long.\n");
*/
path_element = vfs_path_get_by_index (current_path, -1);
current_vfs = path_element->class;
1998-02-27 07:54:42 +03:00
}
/* --------------------------------------------------------------------------------------------- */
void
vfs_shut (void)
1998-02-27 07:54:42 +03:00
{
guint i;
1998-02-27 07:54:42 +03:00
vfs_gc_done ();
1998-02-27 07:54:42 +03:00
vfs_set_raw_current_dir (NULL);
1998-02-27 07:54:42 +03:00
for (i = 0; i < vfs__classes_list->len; i++)
{
struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
if (vfs->done != NULL)
vfs->done (vfs);
}
/* NULL-ize pointers to make unit tests happy */
g_ptr_array_free (vfs_openfiles, TRUE);
vfs_openfiles = NULL;
g_ptr_array_free (vfs__classes_list, TRUE);
vfs__classes_list = NULL;
g_string_free (vfs_str_buffer, TRUE);
vfs_str_buffer = NULL;
current_vfs = NULL;
vfs_free_handle_list = -1;
MC_PTR_FREE (mc_readdir_result);
1998-02-27 07:54:42 +03:00
}
/* --------------------------------------------------------------------------------------------- */
/**
* These ones grab information from the VFS
1998-02-27 07:54:42 +03:00
* and handles them to an upper layer
*/
void
vfs_fill_names (fill_names_f func)
1998-02-27 07:54:42 +03:00
{
guint i;
for (i = 0; i < vfs__classes_list->len; i++)
{
struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
if (vfs->fill_names != NULL)
vfs->fill_names (vfs, func);
}
1998-02-27 07:54:42 +03:00
}
/* --------------------------------------------------------------------------------------------- */
gboolean
vfs_file_is_local (const vfs_path_t * vpath)
{
return (vfs_file_class_flags (vpath) & VFS_LOCAL) != 0;
}
/* --------------------------------------------------------------------------------------------- */
void
vfs_print_message (const char *msg, ...)
{
ev_vfs_print_message_t event_data;
va_list ap;
va_start (ap, msg);
event_data.msg = g_strdup_vprintf (msg, ap);
va_end (ap);
mc_event_raise (MCEVENT_GROUP_CORE, "vfs_print_message", (gpointer) & event_data);
}
/* --------------------------------------------------------------------------------------------- */
/**
* If it's local, reread the current directory
* from the OS.
*/
void
vfs_setup_cwd (void)
{
Ticket #3093: keep symlinks in cwd at startup. If you navigate in your shell to a directory containing symlinks and then start mc, mc will show the canonical path instead. It would be nice to make it show the directory with the symlinks. Example: in your shell execute these: user:~$ mkdir -p /tmp/a/b /tmp/x ; ln -s /tmp/a/b /tmp/x/y user:~$ cd /tmp/x/y user:/tmp/x/y$ mc In mc you'll find yourself in /tmp/a/b, though it'd be nicer to see /tmp/x/y at the top, and correspondingly navigating to the parent would take you to /tmp/x. If you start bash or zsh from /tmp/x/y, the new instance will start displaying the working directory as such. They do this via the PWD env variable. On one hand, they set and maintain PWD to point to the current directory, using the path as specified by the user (possibly containing symbolic links). On the other hand, they check its value at startup. If $PWD points to the same physical directory as the actual working directory then they use this value. If $PWD points somewhere else then it's simply ignored (so it's a hint only as to which symlinks to use to get to the working directory, but never alters the actual cwd). Now mc also does the same at startup (with respect of "Cd follows links" option). Relative directories specified in the command line are applied after possibly replacing the canonical cwd with $PWD. This way for example user:/tmp/x/y$ mc . .. opens two panels in /tmp/x/y and /tmp/x instead of /tmp/a/b and /tmp/a (whereas /tmp/x is actually a different directory than /tmp/a). Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
2013-12-02 14:38:43 +04:00
char *current_dir;
vfs_path_t *tmp_vpath;
const vfs_path_element_t *path_element;
if (vfs_get_raw_current_dir () == NULL)
{
Ticket #3093: keep symlinks in cwd at startup. If you navigate in your shell to a directory containing symlinks and then start mc, mc will show the canonical path instead. It would be nice to make it show the directory with the symlinks. Example: in your shell execute these: user:~$ mkdir -p /tmp/a/b /tmp/x ; ln -s /tmp/a/b /tmp/x/y user:~$ cd /tmp/x/y user:/tmp/x/y$ mc In mc you'll find yourself in /tmp/a/b, though it'd be nicer to see /tmp/x/y at the top, and correspondingly navigating to the parent would take you to /tmp/x. If you start bash or zsh from /tmp/x/y, the new instance will start displaying the working directory as such. They do this via the PWD env variable. On one hand, they set and maintain PWD to point to the current directory, using the path as specified by the user (possibly containing symbolic links). On the other hand, they check its value at startup. If $PWD points to the same physical directory as the actual working directory then they use this value. If $PWD points somewhere else then it's simply ignored (so it's a hint only as to which symlinks to use to get to the working directory, but never alters the actual cwd). Now mc also does the same at startup (with respect of "Cd follows links" option). Relative directories specified in the command line are applied after possibly replacing the canonical cwd with $PWD. This way for example user:/tmp/x/y$ mc . .. opens two panels in /tmp/x/y and /tmp/x instead of /tmp/a/b and /tmp/a (whereas /tmp/x is actually a different directory than /tmp/a). Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
2013-12-02 14:38:43 +04:00
current_dir = g_get_current_dir ();
vfs_set_raw_current_dir (vfs_path_from_str (current_dir));
g_free (current_dir);
current_dir = getenv ("PWD");
tmp_vpath = vfs_path_from_str (current_dir);
if (tmp_vpath != NULL)
{
if (vfs_test_current_dir (tmp_vpath))
Ticket #3093: keep symlinks in cwd at startup. If you navigate in your shell to a directory containing symlinks and then start mc, mc will show the canonical path instead. It would be nice to make it show the directory with the symlinks. Example: in your shell execute these: user:~$ mkdir -p /tmp/a/b /tmp/x ; ln -s /tmp/a/b /tmp/x/y user:~$ cd /tmp/x/y user:/tmp/x/y$ mc In mc you'll find yourself in /tmp/a/b, though it'd be nicer to see /tmp/x/y at the top, and correspondingly navigating to the parent would take you to /tmp/x. If you start bash or zsh from /tmp/x/y, the new instance will start displaying the working directory as such. They do this via the PWD env variable. On one hand, they set and maintain PWD to point to the current directory, using the path as specified by the user (possibly containing symbolic links). On the other hand, they check its value at startup. If $PWD points to the same physical directory as the actual working directory then they use this value. If $PWD points somewhere else then it's simply ignored (so it's a hint only as to which symlinks to use to get to the working directory, but never alters the actual cwd). Now mc also does the same at startup (with respect of "Cd follows links" option). Relative directories specified in the command line are applied after possibly replacing the canonical cwd with $PWD. This way for example user:/tmp/x/y$ mc . .. opens two panels in /tmp/x/y and /tmp/x instead of /tmp/a/b and /tmp/a (whereas /tmp/x is actually a different directory than /tmp/a). Signed-off-by: Andrew Borodin <aborodin@vmail.ru>
2013-12-02 14:38:43 +04:00
vfs_set_raw_current_dir (tmp_vpath);
else
vfs_path_free (tmp_vpath);
}
}
path_element = vfs_path_get_by_index (vfs_get_raw_current_dir (), -1);
if ((path_element->class->flags & VFS_LOCAL) != 0)
{
current_dir = g_get_current_dir ();
tmp_vpath = vfs_path_from_str (current_dir);
g_free (current_dir);
if (tmp_vpath != NULL)
{
/* One of directories in the path is not readable */
/* Check if it is O.K. to use the current_dir */
if (!vfs_test_current_dir (tmp_vpath))
vfs_set_raw_current_dir (tmp_vpath);
else
vfs_path_free (tmp_vpath);
}
}
}
/* --------------------------------------------------------------------------------------------- */
/**
* Return current directory. If it's local, reread the current directory
* from the OS.
*/
char *
_vfs_get_cwd (void)
{
const vfs_path_t *current_dir_vpath;
vfs_setup_cwd ();
current_dir_vpath = vfs_get_raw_current_dir ();
return g_strdup (vfs_path_as_str (current_dir_vpath));
}
/* --------------------------------------------------------------------------------------------- */
/**
* Preallocate space for file in new place for ensure that file
* will be fully copied with less fragmentation.
*
* @param dest_vfs_fd mc VFS file handler
* @param src_fsize source file size
* @param dest_fsize destination file size (if destination exists, otherwise should be 0)
*
* @return 0 if success and non-zero otherwise.
* Note: function doesn't touch errno global variable.
*/
int
vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
{
#ifndef HAVE_POSIX_FALLOCATE
(void) dest_vfs_fd;
(void) src_fsize;
(void) dest_fsize;
return 0;
#else /* HAVE_POSIX_FALLOCATE */
void *dest_fd = NULL;
struct vfs_class *dest_class;
if (src_fsize == 0)
return 0;
dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
if ((dest_class->flags & VFS_LOCAL) == 0 || dest_fd == NULL)
return 0;
return posix_fallocate (*(int *) dest_fd, dest_fsize, src_fsize - dest_fsize);
#endif /* HAVE_POSIX_FALLOCATE */
}
/* --------------------------------------------------------------------------------------------- */
int
vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
{
#ifdef FICLONE
void *dest_fd = NULL;
void *src_fd = NULL;
struct vfs_class *dest_class;
struct vfs_class *src_class;
dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
if ((dest_class->flags & VFS_LOCAL) == 0)
{
errno = EOPNOTSUPP;
return (-1);
}
if (dest_fd == NULL)
{
errno = EBADF;
return (-1);
}
src_class = vfs_class_find_by_handle (src_vfs_fd, &src_fd);
if ((src_class->flags & VFS_LOCAL) == 0)
{
errno = EOPNOTSUPP;
return (-1);
}
if (src_fd == NULL)
{
errno = EBADF;
return (-1);
}
return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd);
#else
(void) dest_vfs_fd;
(void) src_vfs_fd;
errno = EOPNOTSUPP;
return (-1);
#endif
}
/* --------------------------------------------------------------------------------------------- */