1 /*
2    Virtual File System switch code
3 
4    Copyright (C) 1995-2021
5    Free Software Foundation, Inc.
6 
7    Written by: 1995 Miguel de Icaza
8    Jakub Jelinek, 1995
9    Pavel Machek, 1998
10    Slava Zanko <slavazanko@gmail.com>, 2013
11 
12    This file is part of the Midnight Commander.
13 
14    The Midnight Commander is free software: you can redistribute it
15    and/or modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation, either version 3 of the License,
17    or (at your option) any later version.
18 
19    The Midnight Commander is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22    GNU General Public License for more details.
23 
24    You should have received a copy of the GNU General Public License
25    along with this program.  If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 /**
29  * \file
30  * \brief Source: Virtual File System switch code
31  * \author Miguel de Icaza
32  * \author Jakub Jelinek
33  * \author Pavel Machek
34  * \date 1995, 1998
35  * \warning functions like extfs_lstat() have right to destroy any
36  * strings you pass to them. This is acutally ok as you g_strdup what
37  * you are passing to them, anyway; still, beware.
38  *
39  * Namespace: exports *many* functions with vfs_ prefix; exports
40  * parse_ls_lga and friends which do not have that prefix.
41  */
42 
43 #include <config.h>
44 
45 #include <errno.h>
46 #include <stdlib.h>
47 
48 #ifdef __linux__
49 #ifdef HAVE_LINUX_FS_H
50 #include <linux/fs.h>
51 #endif /* HAVE_LINUX_FS_H */
52 #ifdef HAVE_SYS_IOCTL_H
53 #include <sys/ioctl.h>
54 #endif /* HAVE_SYS_IOCTL_H */
55 #endif /* __linux__ */
56 
57 #include "lib/global.h"
58 #include "lib/strutil.h"
59 #include "lib/util.h"
60 #include "lib/widget.h"         /* message() */
61 #include "lib/event.h"
62 
63 #ifdef HAVE_CHARSET
64 #include "lib/charsets.h"
65 #endif
66 
67 #include "vfs.h"
68 #include "utilvfs.h"
69 #include "gc.h"
70 
71 /* TODO: move it to the separate .h */
72 extern struct vfs_dirent *mc_readdir_result;
73 extern GPtrArray *vfs__classes_list;
74 extern GString *vfs_str_buffer;
75 extern vfs_class *current_vfs;
76 
77 /*** global variables ****************************************************************************/
78 
79 struct vfs_dirent *mc_readdir_result = NULL;
80 GPtrArray *vfs__classes_list = NULL;
81 GString *vfs_str_buffer = NULL;
82 vfs_class *current_vfs = NULL;
83 
84 /*** file scope macro definitions ****************************************************************/
85 
86 #define VFS_FIRST_HANDLE 100
87 
88 /*** file scope type declarations ****************************************************************/
89 
90 struct vfs_openfile
91 {
92     int handle;
93     vfs_class *vclass;
94     void *fsinfo;
95 };
96 
97 /*** file scope variables ************************************************************************/
98 
99 /** They keep track of the current directory */
100 static vfs_path_t *current_path = NULL;
101 
102 static GPtrArray *vfs_openfiles = NULL;
103 static long vfs_free_handle_list = -1;
104 
105 /* --------------------------------------------------------------------------------------------- */
106 /*** file scope functions ************************************************************************/
107 /* --------------------------------------------------------------------------------------------- */
108 /* now used only by vfs_translate_path, but could be used in other vfs
109  * plugin to automatic detect encoding
110  * path - path to translate
111  * size - how many bytes from path translate
112  * defcnv - convertor, that is used as default, when path does not contain any
113  *          #enc: subtring
114  * buffer - used to store result of translation
115  */
116 
117 static estr_t
_vfs_translate_path(const char * path,int size,GIConv defcnv,GString * buffer)118 _vfs_translate_path (const char *path, int size, GIConv defcnv, GString * buffer)
119 {
120     estr_t state = ESTR_SUCCESS;
121 #ifdef HAVE_CHARSET
122     const char *semi;
123 
124     if (size == 0)
125         return ESTR_SUCCESS;
126 
127     size = (size > 0) ? size : (signed int) strlen (path);
128 
129     /* try found /#enc: */
130     semi = g_strrstr_len (path, size, VFS_ENCODING_PREFIX);
131     if (semi != NULL && (semi == path || IS_PATH_SEP (semi[-1])))
132     {
133         char encoding[16];
134         const char *slash;
135         GIConv coder = INVALID_CONV;
136         int ms;
137 
138         /* first must be translated part before #enc: */
139         ms = semi - path;
140 
141         state = _vfs_translate_path (path, ms, defcnv, buffer);
142 
143         if (state != ESTR_SUCCESS)
144             return state;
145 
146         /* now can be translated part after #enc: */
147         semi += strlen (VFS_ENCODING_PREFIX);   /* skip "#enc:" */
148         slash = strchr (semi, PATH_SEP);
149         /* ignore slashes after size; */
150         if (slash - path >= size)
151             slash = NULL;
152 
153         ms = (slash != NULL) ? slash - semi : (int) strlen (semi);
154         ms = MIN ((unsigned int) ms, sizeof (encoding) - 1);
155         /* limit encoding size (ms) to path size (size) */
156         if (semi + ms > path + size)
157             ms = path + size - semi;
158         memcpy (encoding, semi, ms);
159         encoding[ms] = '\0';
160 
161         if (is_supported_encoding (encoding))
162             coder = str_crt_conv_to (encoding);
163 
164         if (coder != INVALID_CONV)
165         {
166             if (slash != NULL)
167                 state = str_vfs_convert_to (coder, slash + 1, path + size - slash - 1, buffer);
168             str_close_conv (coder);
169             return state;
170         }
171 
172         errno = EINVAL;
173         state = ESTR_FAILURE;
174     }
175     else
176     {
177         /* path can be translated whole at once */
178         state = str_vfs_convert_to (defcnv, path, size, buffer);
179     }
180 #else
181     (void) size;
182     (void) defcnv;
183 
184     g_string_assign (buffer, path);
185 #endif /* HAVE_CHARSET */
186 
187     return state;
188 }
189 
190 /* --------------------------------------------------------------------------------------------- */
191 
192 static struct vfs_openfile *
vfs_get_openfile(int handle)193 vfs_get_openfile (int handle)
194 {
195     struct vfs_openfile *h;
196 
197     if (handle < VFS_FIRST_HANDLE || (guint) (handle - VFS_FIRST_HANDLE) >= vfs_openfiles->len)
198         return NULL;
199 
200     h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, handle - VFS_FIRST_HANDLE);
201     if (h == NULL)
202         return NULL;
203 
204     g_assert (h->handle == handle);
205 
206     return h;
207 }
208 
209 /* --------------------------------------------------------------------------------------------- */
210 
211 static gboolean
vfs_test_current_dir(const vfs_path_t * vpath)212 vfs_test_current_dir (const vfs_path_t * vpath)
213 {
214     struct stat my_stat, my_stat2;
215 
216     return (mc_global.vfs.cd_symlinks && mc_stat (vpath, &my_stat) == 0
217             && mc_stat (vfs_get_raw_current_dir (), &my_stat2) == 0
218             && my_stat.st_ino == my_stat2.st_ino && my_stat.st_dev == my_stat2.st_dev);
219 }
220 
221 
222 /* --------------------------------------------------------------------------------------------- */
223 /*** public functions ****************************************************************************/
224 /* --------------------------------------------------------------------------------------------- */
225 /** Free open file data for given file handle */
226 
227 void
vfs_free_handle(int handle)228 vfs_free_handle (int handle)
229 {
230     const int idx = handle - VFS_FIRST_HANDLE;
231 
232     if (handle >= VFS_FIRST_HANDLE && (guint) idx < vfs_openfiles->len)
233     {
234         struct vfs_openfile *h;
235 
236         h = (struct vfs_openfile *) g_ptr_array_index (vfs_openfiles, idx);
237         g_free (h);
238         g_ptr_array_index (vfs_openfiles, idx) = (void *) vfs_free_handle_list;
239         vfs_free_handle_list = idx;
240     }
241 }
242 
243 
244 /* --------------------------------------------------------------------------------------------- */
245 /** Find VFS class by file handle */
246 
247 struct vfs_class *
vfs_class_find_by_handle(int handle,void ** fsinfo)248 vfs_class_find_by_handle (int handle, void **fsinfo)
249 {
250     struct vfs_openfile *h;
251 
252     h = vfs_get_openfile (handle);
253 
254     if (h == NULL)
255         return NULL;
256 
257     if (fsinfo != NULL)
258         *fsinfo = h->fsinfo;
259 
260     return h->vclass;
261 }
262 
263 /* --------------------------------------------------------------------------------------------- */
264 
265 /**
266  * Create new VFS handle and put it to the list
267  */
268 
269 int
vfs_new_handle(struct vfs_class * vclass,void * fsinfo)270 vfs_new_handle (struct vfs_class *vclass, void *fsinfo)
271 {
272     struct vfs_openfile *h;
273 
274     h = g_new (struct vfs_openfile, 1);
275     h->fsinfo = fsinfo;
276     h->vclass = vclass;
277 
278     /* Allocate the first free handle */
279     h->handle = vfs_free_handle_list;
280     if (h->handle == -1)
281     {
282         /* No free allocated handles, allocate one */
283         h->handle = vfs_openfiles->len;
284         g_ptr_array_add (vfs_openfiles, h);
285     }
286     else
287     {
288         vfs_free_handle_list = (long) g_ptr_array_index (vfs_openfiles, vfs_free_handle_list);
289         g_ptr_array_index (vfs_openfiles, h->handle) = h;
290     }
291 
292     h->handle += VFS_FIRST_HANDLE;
293     return h->handle;
294 }
295 
296 /* --------------------------------------------------------------------------------------------- */
297 
298 int
vfs_ferrno(struct vfs_class * vfs)299 vfs_ferrno (struct vfs_class *vfs)
300 {
301     return vfs->ferrno ? (*vfs->ferrno) (vfs) : E_UNKNOWN;
302     /* Hope that error message is obscure enough ;-) */
303 }
304 
305 /* --------------------------------------------------------------------------------------------- */
306 
307 gboolean
vfs_register_class(struct vfs_class * vfs)308 vfs_register_class (struct vfs_class * vfs)
309 {
310     if (vfs->init != NULL)      /* vfs has own initialization function */
311         if (!vfs->init (vfs))   /* but it failed */
312             return FALSE;
313 
314     g_ptr_array_add (vfs__classes_list, vfs);
315 
316     return TRUE;
317 }
318 
319 /* --------------------------------------------------------------------------------------------- */
320 
321 void
vfs_unregister_class(struct vfs_class * vfs)322 vfs_unregister_class (struct vfs_class *vfs)
323 {
324     if (vfs->done != NULL)
325         vfs->done (vfs);
326 
327     g_ptr_array_remove (vfs__classes_list, vfs);
328 }
329 
330 /* --------------------------------------------------------------------------------------------- */
331 /** Strip known vfs suffixes from a filename (possible improvement: strip
332  *  suffix from last path component).
333  *  \return a malloced string which has to be freed.
334  */
335 
336 char *
vfs_strip_suffix_from_filename(const char * filename)337 vfs_strip_suffix_from_filename (const char *filename)
338 {
339     char *semi, *p;
340 
341     if (filename == NULL)
342         vfs_die ("vfs_strip_suffix_from_path got NULL: impossible");
343 
344     p = g_strdup (filename);
345     semi = g_strrstr (p, VFS_PATH_URL_DELIMITER);
346     if (semi != NULL)
347     {
348         char *vfs_prefix;
349 
350         *semi = '\0';
351         vfs_prefix = strrchr (p, PATH_SEP);
352         if (vfs_prefix == NULL)
353             *semi = *VFS_PATH_URL_DELIMITER;
354         else
355             *vfs_prefix = '\0';
356     }
357 
358     return p;
359 }
360 
361 /* --------------------------------------------------------------------------------------------- */
362 
363 const char *
vfs_translate_path(const char * path)364 vfs_translate_path (const char *path)
365 {
366     estr_t state;
367 
368     g_string_set_size (vfs_str_buffer, 0);
369     state = _vfs_translate_path (path, -1, str_cnv_from_term, vfs_str_buffer);
370     return (state != ESTR_FAILURE) ? vfs_str_buffer->str : NULL;
371 }
372 
373 /* --------------------------------------------------------------------------------------------- */
374 
375 char *
vfs_translate_path_n(const char * path)376 vfs_translate_path_n (const char *path)
377 {
378     const char *result;
379 
380     result = vfs_translate_path (path);
381     return g_strdup (result);
382 }
383 
384 /* --------------------------------------------------------------------------------------------- */
385 /**
386  * Get current directory without any OS calls.
387  *
388  * @return string contains current path
389  */
390 
391 const char *
vfs_get_current_dir(void)392 vfs_get_current_dir (void)
393 {
394     return current_path->str;
395 }
396 
397 /* --------------------------------------------------------------------------------------------- */
398 /**
399  * Get current directory without any OS calls.
400  *
401  * @return newly allocated string contains current path
402  */
403 
404 char *
vfs_get_current_dir_n(void)405 vfs_get_current_dir_n (void)
406 {
407     return g_strdup (current_path->str);
408 }
409 
410 /* --------------------------------------------------------------------------------------------- */
411 /**
412  * Get raw current directory object without any OS calls.
413  *
414  * @return object contain current path
415  */
416 
417 const vfs_path_t *
vfs_get_raw_current_dir(void)418 vfs_get_raw_current_dir (void)
419 {
420     return current_path;
421 }
422 
423 /* --------------------------------------------------------------------------------------------- */
424 /**
425  * Set current directory object.
426  *
427  * @param vpath new path
428  */
429 void
vfs_set_raw_current_dir(const vfs_path_t * vpath)430 vfs_set_raw_current_dir (const vfs_path_t * vpath)
431 {
432     vfs_path_free (current_path, TRUE);
433     current_path = (vfs_path_t *) vpath;
434 }
435 
436 /* --------------------------------------------------------------------------------------------- */
437 /* Return TRUE is the current VFS class is local */
438 
439 gboolean
vfs_current_is_local(void)440 vfs_current_is_local (void)
441 {
442     return (current_vfs->flags & VFSF_LOCAL) != 0;
443 }
444 
445 /* --------------------------------------------------------------------------------------------- */
446 /* Return flags of the VFS class of the given filename */
447 
448 vfs_flags_t
vfs_file_class_flags(const vfs_path_t * vpath)449 vfs_file_class_flags (const vfs_path_t * vpath)
450 {
451     const vfs_path_element_t *path_element;
452 
453     path_element = vfs_path_get_by_index (vpath, -1);
454     if (!vfs_path_element_valid (path_element))
455         return VFSF_UNKNOWN;
456 
457     return path_element->class->flags;
458 }
459 
460 /* --------------------------------------------------------------------------------------------- */
461 
462 void
vfs_init(void)463 vfs_init (void)
464 {
465     /* create the VFS handle arrays */
466     vfs__classes_list = g_ptr_array_new ();
467 
468     /* create the VFS handle array */
469     vfs_openfiles = g_ptr_array_new ();
470 
471     vfs_str_buffer = g_string_new ("");
472 
473     mc_readdir_result = vfs_dirent_init (NULL, "", -1);
474 }
475 
476 /* --------------------------------------------------------------------------------------------- */
477 
478 void
vfs_setup_work_dir(void)479 vfs_setup_work_dir (void)
480 {
481     const vfs_path_element_t *path_element;
482 
483     vfs_setup_cwd ();
484 
485     /* FIXME: is we really need for this check? */
486     /*
487        if (strlen (current_dir) > MC_MAXPATHLEN - 2)
488        vfs_die ("Current dir too long.\n");
489      */
490 
491     path_element = vfs_path_get_by_index (current_path, -1);
492     current_vfs = path_element->class;
493 }
494 
495 /* --------------------------------------------------------------------------------------------- */
496 
497 void
vfs_shut(void)498 vfs_shut (void)
499 {
500     guint i;
501 
502     vfs_gc_done ();
503 
504     vfs_set_raw_current_dir (NULL);
505 
506     for (i = 0; i < vfs__classes_list->len; i++)
507     {
508         struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
509 
510         if (vfs->done != NULL)
511             vfs->done (vfs);
512     }
513 
514     /* NULL-ize pointers to make unit tests happy */
515     g_ptr_array_free (vfs_openfiles, TRUE);
516     vfs_openfiles = NULL;
517     g_ptr_array_free (vfs__classes_list, TRUE);
518     vfs__classes_list = NULL;
519     g_string_free (vfs_str_buffer, TRUE);
520     vfs_str_buffer = NULL;
521     current_vfs = NULL;
522     vfs_free_handle_list = -1;
523     vfs_dirent_free (mc_readdir_result);
524     mc_readdir_result = NULL;
525 }
526 
527 /* --------------------------------------------------------------------------------------------- */
528 /**
529   * Init or create vfs_dirent structure
530   *
531   * @d vfs_dirent structure to init. If NULL, new structure is created.
532   * @fname file name
533   * @ino file inode number
534   *
535   * @return pointer to d if d isn't NULL, or pointer to newly created structure.
536   */
537 
538 struct vfs_dirent *
vfs_dirent_init(struct vfs_dirent * d,const char * fname,ino_t ino)539 vfs_dirent_init (struct vfs_dirent *d, const char *fname, ino_t ino)
540 {
541     struct vfs_dirent *ret = d;
542 
543     if (ret == NULL)
544         ret = g_new0 (struct vfs_dirent, 1);
545 
546     if (ret->d_name_str == NULL)
547         ret->d_name_str = g_string_sized_new (MC_MAXFILENAMELEN);
548 
549     vfs_dirent_assign (ret, fname, ino);
550 
551     return ret;
552 }
553 
554 /* --------------------------------------------------------------------------------------------- */
555 /**
556   * Assign members of vfs_dirent structure
557   *
558   * @d vfs_dirent structure for assignment
559   * @fname file name
560   * @ino file inode number
561   */
562 
563 void
vfs_dirent_assign(struct vfs_dirent * d,const char * fname,ino_t ino)564 vfs_dirent_assign (struct vfs_dirent *d, const char *fname, ino_t ino)
565 {
566     g_string_assign (d->d_name_str, fname);
567     d->d_name = d->d_name_str->str;
568     d->d_ino = ino;
569 }
570 
571 /* --------------------------------------------------------------------------------------------- */
572 /**
573   * Destroy vfs_dirent structure
574   *
575   * @d vfs_dirent structure to destroy.
576   */
577 
578 void
vfs_dirent_free(struct vfs_dirent * d)579 vfs_dirent_free (struct vfs_dirent *d)
580 {
581     g_string_free (d->d_name_str, TRUE);
582     g_free (d);
583 }
584 
585 /* --------------------------------------------------------------------------------------------- */
586 
587 /**
588  * These ones grab information from the VFS
589  *  and handles them to an upper layer
590  */
591 
592 void
vfs_fill_names(fill_names_f func)593 vfs_fill_names (fill_names_f func)
594 {
595     guint i;
596 
597     for (i = 0; i < vfs__classes_list->len; i++)
598     {
599         struct vfs_class *vfs = VFS_CLASS (g_ptr_array_index (vfs__classes_list, i));
600 
601         if (vfs->fill_names != NULL)
602             vfs->fill_names (vfs, func);
603     }
604 }
605 
606 /* --------------------------------------------------------------------------------------------- */
607 
608 gboolean
vfs_file_is_local(const vfs_path_t * vpath)609 vfs_file_is_local (const vfs_path_t * vpath)
610 {
611     return (vfs_file_class_flags (vpath) & VFSF_LOCAL) != 0;
612 }
613 
614 /* --------------------------------------------------------------------------------------------- */
615 
616 void
vfs_print_message(const char * msg,...)617 vfs_print_message (const char *msg, ...)
618 {
619     ev_vfs_print_message_t event_data;
620     va_list ap;
621 
622     va_start (ap, msg);
623     event_data.msg = g_strdup_vprintf (msg, ap);
624     va_end (ap);
625 
626     mc_event_raise (MCEVENT_GROUP_CORE, "vfs_print_message", (gpointer) & event_data);
627 }
628 
629 /* --------------------------------------------------------------------------------------------- */
630 /**
631  * If it's local, reread the current directory
632  * from the OS.
633  */
634 
635 void
vfs_setup_cwd(void)636 vfs_setup_cwd (void)
637 {
638     char *current_dir;
639     vfs_path_t *tmp_vpath;
640     const vfs_path_element_t *path_element;
641 
642     if (vfs_get_raw_current_dir () == NULL)
643     {
644         current_dir = g_get_current_dir ();
645         vfs_set_raw_current_dir (vfs_path_from_str (current_dir));
646         g_free (current_dir);
647 
648         current_dir = getenv ("PWD");
649         tmp_vpath = vfs_path_from_str (current_dir);
650 
651         if (tmp_vpath != NULL)
652         {
653             if (vfs_test_current_dir (tmp_vpath))
654                 vfs_set_raw_current_dir (tmp_vpath);
655             else
656                 vfs_path_free (tmp_vpath, TRUE);
657         }
658     }
659 
660     path_element = vfs_path_get_by_index (vfs_get_raw_current_dir (), -1);
661 
662     if ((path_element->class->flags & VFSF_LOCAL) != 0)
663     {
664         current_dir = g_get_current_dir ();
665         tmp_vpath = vfs_path_from_str (current_dir);
666         g_free (current_dir);
667 
668         if (tmp_vpath != NULL)
669         {
670             /* One of directories in the path is not readable */
671 
672             /* Check if it is O.K. to use the current_dir */
673             if (!vfs_test_current_dir (tmp_vpath))
674                 vfs_set_raw_current_dir (tmp_vpath);
675             else
676                 vfs_path_free (tmp_vpath, TRUE);
677         }
678     }
679 }
680 
681 /* --------------------------------------------------------------------------------------------- */
682 /**
683  * Return current directory.  If it's local, reread the current directory
684  * from the OS.
685  */
686 
687 char *
_vfs_get_cwd(void)688 _vfs_get_cwd (void)
689 {
690     const vfs_path_t *current_dir_vpath;
691 
692     vfs_setup_cwd ();
693     current_dir_vpath = vfs_get_raw_current_dir ();
694     return g_strdup (vfs_path_as_str (current_dir_vpath));
695 }
696 
697 /* --------------------------------------------------------------------------------------------- */
698 /**
699  * Preallocate space for file in new place for ensure that file
700  * will be fully copied with less fragmentation.
701  *
702  * @param dest_vfs_fd mc VFS file handler
703  * @param src_fsize source file size
704  * @param dest_fsize destination file size (if destination exists, otherwise should be 0)
705  *
706  * @return 0 if success and non-zero otherwise.
707  * Note: function doesn't touch errno global variable.
708  */
709 
710 int
vfs_preallocate(int dest_vfs_fd,off_t src_fsize,off_t dest_fsize)711 vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
712 {
713 #ifndef HAVE_POSIX_FALLOCATE
714     (void) dest_vfs_fd;
715     (void) src_fsize;
716     (void) dest_fsize;
717     return 0;
718 
719 #else /* HAVE_POSIX_FALLOCATE */
720     void *dest_fd = NULL;
721     struct vfs_class *dest_class;
722 
723     if (src_fsize == 0)
724         return 0;
725 
726     dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
727     if ((dest_class->flags & VFSF_LOCAL) == 0 || dest_fd == NULL)
728         return 0;
729 
730     return posix_fallocate (*(int *) dest_fd, dest_fsize, src_fsize - dest_fsize);
731 
732 #endif /* HAVE_POSIX_FALLOCATE */
733 }
734 
735  /* --------------------------------------------------------------------------------------------- */
736 
737 int
vfs_clone_file(int dest_vfs_fd,int src_vfs_fd)738 vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
739 {
740 #ifdef FICLONE
741     void *dest_fd = NULL;
742     void *src_fd = NULL;
743     struct vfs_class *dest_class;
744     struct vfs_class *src_class;
745 
746     dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
747     if ((dest_class->flags & VFSF_LOCAL) == 0)
748     {
749         errno = EOPNOTSUPP;
750         return (-1);
751     }
752     if (dest_fd == NULL)
753     {
754         errno = EBADF;
755         return (-1);
756     }
757 
758     src_class = vfs_class_find_by_handle (src_vfs_fd, &src_fd);
759     if ((src_class->flags & VFSF_LOCAL) == 0)
760     {
761         errno = EOPNOTSUPP;
762         return (-1);
763     }
764     if (src_fd == NULL)
765     {
766         errno = EBADF;
767         return (-1);
768     }
769 
770     return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd);
771 #else
772     (void) dest_vfs_fd;
773     (void) src_vfs_fd;
774     errno = EOPNOTSUPP;
775     return (-1);
776 #endif
777 }
778 
779 /* --------------------------------------------------------------------------------------------- */
780