Changed interface of mc_stat() and mc_lstat() functions
[midnight-commander.git] / src / vfs / extfs / extfs.c
blob18cddf3baedbc324712f5e19a0063b172645f65c
1 /*
2 Virtual File System: External file system.
4 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
5 2006, 2007, 2009, 2011
6 The Free Software Foundation, Inc.
8 Written by:
9 Jakub Jelinek, 1995
10 Pavel Machek, 1998
11 Andrew T. Veliath, 1999
13 This file is part of the Midnight Commander.
15 The Midnight Commander is free software: you can redistribute it
16 and/or modify it under the terms of the GNU General Public License as
17 published by the Free Software Foundation, either version 3 of the License,
18 or (at your option) any later version.
20 The Midnight Commander is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
29 /**
30 * \file
31 * \brief Source: Virtual File System: External file system
32 * \author Jakub Jelinek
33 * \author Pavel Machek
34 * \author Andrew T. Veliath
35 * \date 1995, 1998, 1999
38 /* Namespace: init_extfs */
40 #include <config.h>
42 #include <stdio.h>
43 #include <ctype.h>
44 #include <string.h>
45 #include <stdlib.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <unistd.h>
49 #include <signal.h>
50 #include <fcntl.h>
51 #include <errno.h>
52 #include <sys/wait.h>
53 #include <unistd.h>
55 #include "lib/global.h"
56 #include "lib/fileloc.h"
57 #include "lib/mcconfig.h"
58 #include "lib/util.h"
59 #include "lib/widget.h" /* message() */
61 #include "src/main.h" /* shell */
62 #include "src/execute.h" /* For shell_execute */
64 #include "lib/vfs/vfs.h"
65 #include "lib/vfs/utilvfs.h"
66 #include "lib/vfs/gc.h" /* vfs_rmstamp */
68 #include "extfs.h"
70 /*** global variables ****************************************************************************/
72 GArray *extfs_plugins = NULL;
74 /*** file scope macro definitions ****************************************************************/
76 #undef ERRNOR
77 #define ERRNOR(x,y) do { my_errno = x; return y; } while(0)
79 #define RECORDSIZE 512
81 /*** file scope type declarations ****************************************************************/
83 struct inode
85 nlink_t nlink;
86 struct entry *first_in_subdir; /* only used if this is a directory */
87 struct entry *last_in_subdir;
88 ino_t inode; /* This is inode # */
89 dev_t dev; /* This is an internal identification of the extfs archive */
90 struct archive *archive; /* And this is an archive structure */
91 dev_t rdev;
92 mode_t mode;
93 uid_t uid;
94 gid_t gid;
95 off_t size;
96 time_t mtime;
97 char *linkname;
98 time_t atime;
99 time_t ctime;
100 char *local_filename;
103 struct entry
105 struct entry *next_in_dir;
106 struct entry *dir;
107 char *name;
108 struct inode *inode;
111 struct pseudofile
113 struct archive *archive;
114 gboolean has_changed;
115 int local_handle;
116 struct entry *entry;
119 struct archive
121 int fstype;
122 char *name;
123 char *local_name;
124 struct stat local_stat;
125 dev_t rdev;
126 int fd_usage;
127 ino_t inode_counter;
128 struct entry *root_entry;
129 struct archive *next;
132 typedef struct
134 char *path;
135 char *prefix;
136 gboolean need_archive;
137 } extfs_plugin_info_t;
139 /*** file scope variables ************************************************************************/
141 static gboolean errloop;
142 static gboolean notadir;
144 static struct vfs_class vfs_extfs_ops;
145 static struct archive *first_archive = NULL;
146 static int my_errno = 0;
148 /*** file scope functions ************************************************************************/
149 /* --------------------------------------------------------------------------------------------- */
151 static void extfs_remove_entry (struct entry *e);
152 static void extfs_free (vfsid id);
153 static void extfs_free_entry (struct entry *e);
154 static struct entry *extfs_resolve_symlinks_int (struct entry *entry, GSList * list);
156 /* --------------------------------------------------------------------------------------------- */
158 static void
159 extfs_make_dots (struct entry *ent)
161 struct entry *entry = g_new (struct entry, 1);
162 struct entry *parentry = ent->dir;
163 struct inode *inode = ent->inode, *parent;
165 parent = (parentry != NULL) ? parentry->inode : NULL;
166 entry->name = g_strdup (".");
167 entry->inode = inode;
168 entry->dir = ent;
169 inode->local_filename = NULL;
170 inode->first_in_subdir = entry;
171 inode->nlink++;
173 entry->next_in_dir = g_new (struct entry, 1);
174 entry = entry->next_in_dir;
175 entry->name = g_strdup ("..");
176 inode->last_in_subdir = entry;
177 entry->next_in_dir = NULL;
178 if (parent != NULL)
180 entry->inode = parent;
181 entry->dir = parentry;
182 parent->nlink++;
184 else
186 entry->inode = inode;
187 entry->dir = ent;
188 inode->nlink++;
192 /* --------------------------------------------------------------------------------------------- */
194 static struct entry *
195 extfs_generate_entry (struct archive *archive,
196 const char *name, struct entry *parentry, mode_t mode)
198 mode_t myumask;
199 struct inode *inode, *parent;
200 struct entry *entry;
202 parent = (parentry != NULL) ? parentry->inode : NULL;
203 entry = g_new (struct entry, 1);
205 entry->name = g_strdup (name);
206 entry->next_in_dir = NULL;
207 entry->dir = parentry;
208 if (parent != NULL)
210 parent->last_in_subdir->next_in_dir = entry;
211 parent->last_in_subdir = entry;
213 inode = g_new (struct inode, 1);
214 entry->inode = inode;
215 inode->local_filename = NULL;
216 inode->linkname = NULL;
217 inode->last_in_subdir = NULL;
218 inode->inode = (archive->inode_counter)++;
219 inode->dev = archive->rdev;
220 inode->archive = archive;
221 myumask = umask (022);
222 umask (myumask);
223 inode->mode = mode & ~myumask;
224 mode = inode->mode;
225 inode->rdev = 0;
226 inode->uid = getuid ();
227 inode->gid = getgid ();
228 inode->size = 0;
229 inode->mtime = time (NULL);
230 inode->atime = inode->mtime;
231 inode->ctime = inode->mtime;
232 inode->nlink = 1;
233 if (S_ISDIR (mode))
234 extfs_make_dots (entry);
235 return entry;
238 /* --------------------------------------------------------------------------------------------- */
240 static struct entry *
241 extfs_find_entry_int (struct entry *dir, const char *name, GSList * list,
242 gboolean make_dirs, gboolean make_file)
244 struct entry *pent, *pdir;
245 const char *p, *name_end;
246 char *q;
247 char c = PATH_SEP;
249 if (g_path_is_absolute (name))
251 /* Handle absolute paths */
252 name = (char *) g_path_skip_root (name);
253 dir = dir->inode->archive->root_entry;
256 pent = dir;
257 p = name;
258 name_end = name + strlen (name);
260 q = strchr (p, PATH_SEP);
261 if (q == '\0')
262 q = strchr (p, '\0');
264 while ((pent != NULL) && (c != '\0') && (*p != '\0'))
266 c = *q;
267 *q = '\0';
269 if (strcmp (p, ".") != 0)
271 if (strcmp (p, "..") == 0)
272 pent = pent->dir;
273 else
275 pent = extfs_resolve_symlinks_int (pent, list);
276 if (pent == NULL)
278 *q = c;
279 return NULL;
281 if (!S_ISDIR (pent->inode->mode))
283 *q = c;
284 notadir = TRUE;
285 return NULL;
288 pdir = pent;
289 for (pent = pent->inode->first_in_subdir; pent != NULL; pent = pent->next_in_dir)
290 /* Hack: I keep the original semanthic unless
291 q+1 would break in the strchr */
292 if (strcmp (pent->name, p) == 0)
294 if (q + 1 > name_end)
296 *q = c;
297 notadir = !S_ISDIR (pent->inode->mode);
298 return pent;
300 break;
303 /* When we load archive, we create automagically
304 * non-existant directories
306 if (pent == NULL && make_dirs)
307 pent = extfs_generate_entry (dir->inode->archive, p, pdir, S_IFDIR | 0777);
308 if (pent == NULL && make_file)
309 pent = extfs_generate_entry (dir->inode->archive, p, pdir, S_IFREG | 0666);
312 /* Next iteration */
313 *q = c;
314 p = q + 1;
315 q = strchr (p, PATH_SEP);
316 if (q == '\0')
317 q = strchr (p, '\0');
319 if (pent == NULL)
320 my_errno = ENOENT;
321 return pent;
324 /* --------------------------------------------------------------------------------------------- */
326 static struct entry *
327 extfs_find_entry (struct entry *dir, const char *name, gboolean make_dirs, gboolean make_file)
329 struct entry *res;
331 errloop = FALSE;
332 notadir = FALSE;
334 res = extfs_find_entry_int (dir, name, NULL, make_dirs, make_file);
335 if (res == NULL)
337 if (errloop)
338 my_errno = ELOOP;
339 else if (notadir)
340 my_errno = ENOTDIR;
342 return res;
345 /* --------------------------------------------------------------------------------------------- */
347 static void
348 extfs_fill_names (struct vfs_class *me, fill_names_f func)
350 struct archive *a = first_archive;
352 (void) me;
354 while (a != NULL)
356 extfs_plugin_info_t *info;
357 char *name;
359 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, a->fstype);
360 name =
361 g_strconcat (a->name ? a->name : "", "/", info->prefix, VFS_PATH_URL_DELIMITER,
362 (char *) NULL);
363 func (name);
364 g_free (name);
365 a = a->next;
369 /* --------------------------------------------------------------------------------------------- */
371 static void
372 extfs_free_archive (struct archive *archive)
374 extfs_free_entry (archive->root_entry);
375 if (archive->local_name != NULL)
377 struct stat my;
378 vfs_path_t *tmp_vpath;
380 tmp_vpath = vfs_path_from_str (archive->local_name);
381 mc_stat (tmp_vpath, &my);
382 vfs_path_free (tmp_vpath);
383 mc_ungetlocalcopy (archive->name, archive->local_name,
384 archive->local_stat.st_mtime != my.st_mtime);
385 g_free (archive->local_name);
387 g_free (archive->name);
388 g_free (archive);
391 /* --------------------------------------------------------------------------------------------- */
393 static FILE *
394 extfs_open_archive (int fstype, const char *name, struct archive **pparc)
396 const extfs_plugin_info_t *info;
397 static dev_t archive_counter = 0;
398 FILE *result = NULL;
399 mode_t mode;
400 char *cmd;
401 struct stat mystat;
402 struct archive *current_archive;
403 struct entry *root_entry;
404 char *local_name = NULL, *tmp = NULL;
405 vfs_path_t *vpath;
406 vfs_path_element_t *path_element = NULL;
408 vpath = vfs_path_from_str (name);
409 if (vpath != NULL)
410 path_element = vfs_path_get_by_index (vpath, -1);
412 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
414 if (info->need_archive)
416 if (mc_stat (vpath, &mystat) == -1)
417 goto ret;
419 if (!vfs_file_is_local (vpath))
421 local_name = mc_getlocalcopy (name);
422 if (local_name == NULL)
423 goto ret;
425 tmp = name_quote ((vpath != NULL) ? path_element->path : name, 0);
428 cmd = g_strconcat (info->path, info->prefix, " list ",
429 local_name != NULL ? local_name : tmp, (char *) NULL);
430 g_free (tmp);
432 open_error_pipe ();
433 result = popen (cmd, "r");
434 g_free (cmd);
435 if (result == NULL)
437 close_error_pipe (D_ERROR, NULL);
438 if (local_name != NULL)
440 mc_ungetlocalcopy (name, local_name, 0);
441 g_free (local_name);
443 goto ret;
446 #ifdef ___QNXNTO__
447 setvbuf (result, NULL, _IONBF, 0);
448 #endif
450 current_archive = g_new (struct archive, 1);
451 current_archive->fstype = fstype;
452 current_archive->name = (name != NULL) ? g_strdup (name) : NULL;
453 current_archive->local_name = local_name;
455 if (local_name != NULL)
457 vfs_path_t *tmp_vpath = vfs_path_from_str (local_name);
458 mc_stat (tmp_vpath, &current_archive->local_stat);
459 vfs_path_free (tmp_vpath);
461 current_archive->inode_counter = 0;
462 current_archive->fd_usage = 0;
463 current_archive->rdev = archive_counter++;
464 current_archive->next = first_archive;
465 first_archive = current_archive;
466 mode = mystat.st_mode & 07777;
467 if (mode & 0400)
468 mode |= 0100;
469 if (mode & 0040)
470 mode |= 0010;
471 if (mode & 0004)
472 mode |= 0001;
473 mode |= S_IFDIR;
474 root_entry = extfs_generate_entry (current_archive, PATH_SEP_STR, NULL, mode);
475 root_entry->inode->uid = mystat.st_uid;
476 root_entry->inode->gid = mystat.st_gid;
477 root_entry->inode->atime = mystat.st_atime;
478 root_entry->inode->ctime = mystat.st_ctime;
479 root_entry->inode->mtime = mystat.st_mtime;
480 current_archive->root_entry = root_entry;
482 *pparc = current_archive;
484 ret:
485 vfs_path_free (vpath);
486 return result;
489 /* --------------------------------------------------------------------------------------------- */
491 * Main loop for reading an archive.
492 * Return 0 on success, -1 on error.
495 static int
496 extfs_read_archive (int fstype, const char *name, struct archive **pparc)
498 FILE *extfsd;
499 const extfs_plugin_info_t *info;
500 char *buffer;
501 struct archive *current_archive;
502 char *current_file_name, *current_link_name;
504 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype);
506 extfsd = extfs_open_archive (fstype, name, &current_archive);
508 if (extfsd == NULL)
510 message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s"), info->prefix, name);
511 return -1;
514 buffer = g_malloc (BUF_4K);
515 while (fgets (buffer, BUF_4K, extfsd) != NULL)
517 struct stat hstat;
519 current_link_name = NULL;
520 if (vfs_parse_ls_lga (buffer, &hstat, &current_file_name, &current_link_name, NULL))
522 struct entry *entry, *pent;
523 struct inode *inode;
524 char *p, *q, *cfn = current_file_name;
526 if (*cfn != '\0')
528 if (*cfn == PATH_SEP)
529 cfn++;
530 p = strchr (cfn, '\0');
531 if (p != cfn && *(p - 1) == PATH_SEP)
532 *(p - 1) = '\0';
533 p = strrchr (cfn, PATH_SEP);
534 if (p == NULL)
536 p = cfn;
537 q = strchr (cfn, '\0');
539 else
541 *(p++) = '\0';
542 q = cfn;
544 if (S_ISDIR (hstat.st_mode) && (strcmp (p, ".") == 0 || strcmp (p, "..") == 0))
545 goto read_extfs_continue;
546 pent = extfs_find_entry (current_archive->root_entry, q, TRUE, FALSE);
547 if (pent == NULL)
549 /* FIXME: Should clean everything one day */
550 g_free (buffer);
551 pclose (extfsd);
552 close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
553 return -1;
555 entry = g_new (struct entry, 1);
556 entry->name = g_strdup (p);
557 entry->next_in_dir = NULL;
558 entry->dir = pent;
559 if (pent->inode->last_in_subdir)
561 pent->inode->last_in_subdir->next_in_dir = entry;
562 pent->inode->last_in_subdir = entry;
564 if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL))
566 pent = extfs_find_entry (current_archive->root_entry,
567 current_link_name, FALSE, FALSE);
568 if (pent == NULL)
570 /* FIXME: Should clean everything one day */
571 g_free (buffer);
572 pclose (extfsd);
573 close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
574 return -1;
577 entry->inode = pent->inode;
578 pent->inode->nlink++;
580 else
582 inode = g_new (struct inode, 1);
583 entry->inode = inode;
584 inode->local_filename = NULL;
585 inode->inode = (current_archive->inode_counter)++;
586 inode->nlink = 1;
587 inode->dev = current_archive->rdev;
588 inode->archive = current_archive;
589 inode->mode = hstat.st_mode;
590 #ifdef HAVE_STRUCT_STAT_ST_RDEV
591 inode->rdev = hstat.st_rdev;
592 #else
593 inode->rdev = 0;
594 #endif
595 inode->uid = hstat.st_uid;
596 inode->gid = hstat.st_gid;
597 inode->size = hstat.st_size;
598 inode->mtime = hstat.st_mtime;
599 inode->atime = hstat.st_atime;
600 inode->ctime = hstat.st_ctime;
601 inode->first_in_subdir = NULL;
602 inode->last_in_subdir = NULL;
603 if (current_link_name != NULL && S_ISLNK (hstat.st_mode))
605 inode->linkname = current_link_name;
606 current_link_name = NULL;
608 else
610 if (S_ISLNK (hstat.st_mode))
611 inode->mode &= ~S_IFLNK; /* You *DON'T* want to do this always */
612 inode->linkname = NULL;
614 if (S_ISDIR (hstat.st_mode))
615 extfs_make_dots (entry);
618 read_extfs_continue:
619 g_free (current_file_name);
620 g_free (current_link_name);
623 g_free (buffer);
625 /* Check if extfs 'list' returned 0 */
626 if (pclose (extfsd) != 0)
628 extfs_free (current_archive);
629 close_error_pipe (D_ERROR, _("Inconsistent extfs archive"));
630 return -1;
633 close_error_pipe (D_ERROR, NULL);
634 *pparc = current_archive;
635 return 0;
638 /* --------------------------------------------------------------------------------------------- */
640 static int
641 extfs_which (struct vfs_class *me, const char *path)
643 size_t path_len;
644 size_t i;
646 (void) me;
648 path_len = strlen (path);
650 for (i = 0; i < extfs_plugins->len; i++)
652 extfs_plugin_info_t *info;
654 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
656 if ((strncmp (path, info->prefix, path_len) == 0)
657 && ((info->prefix[path_len] == '\0') || (info->prefix[path_len] == '+')))
658 return i;
660 return -1;
663 /* --------------------------------------------------------------------------------------------- */
665 * Dissect the path and create corresponding superblock.
667 static const char *
668 extfs_get_path_int (const vfs_path_t * vpath, struct archive **archive, gboolean do_not_open)
670 char *archive_name;
671 int result = -1;
672 struct archive *parc;
673 int fstype;
674 vfs_path_element_t *path_element;
676 path_element = vfs_path_get_by_index (vpath, -1);
678 archive_name = vfs_path_to_str_elements_count (vpath, -1);
680 fstype = extfs_which (path_element->class, path_element->vfs_prefix);
682 if (fstype == -1)
684 g_free (archive_name);
685 return NULL;
689 * All filesystems should have some local archive, at least
690 * it can be PATH_SEP ('/').
692 for (parc = first_archive; parc != NULL; parc = parc->next)
693 if (parc->name != NULL)
695 if (strcmp (parc->name, archive_name) == 0)
697 vfs_stamp (&vfs_extfs_ops, (vfsid) parc);
698 goto return_success;
702 result = do_not_open ? -1 : extfs_read_archive (fstype, archive_name, &parc);
703 if (result == -1)
705 path_element->class->verrno = EIO;
706 g_free (archive_name);
707 return NULL;
710 return_success:
711 *archive = parc;
712 g_free (archive_name);
713 return path_element->path;
716 /* --------------------------------------------------------------------------------------------- */
718 * Dissect the path and create corresponding superblock.
719 * The result should be freed.
722 static char *
723 extfs_get_path (const vfs_path_t * vpath, struct archive **archive, gboolean do_not_open)
725 return g_strdup (extfs_get_path_int (vpath, archive, do_not_open));
728 /* --------------------------------------------------------------------------------------------- */
729 /* Return allocated path (without leading slash) inside the archive */
731 static char *
732 extfs_get_path_from_entry (struct entry *entry)
734 GString *localpath;
736 localpath = g_string_new ("");
738 while (entry->dir != NULL)
740 g_string_prepend (localpath, entry->name);
741 if (entry->dir->dir != NULL)
742 g_string_prepend_c (localpath, PATH_SEP);
743 entry = entry->dir;
746 return g_string_free (localpath, FALSE);
749 /* --------------------------------------------------------------------------------------------- */
751 static struct entry *
752 extfs_resolve_symlinks_int (struct entry *entry, GSList * list)
754 struct entry *pent = NULL;
756 if (!S_ISLNK (entry->inode->mode))
757 return entry;
759 if (g_slist_find (list, entry) != NULL)
761 /* Here we protect us against symlink looping */
762 errloop = TRUE;
764 else
766 GSList *looping;
768 looping = g_slist_prepend (list, entry);
769 pent = extfs_find_entry_int (entry->dir, entry->inode->linkname, looping, FALSE, FALSE);
770 looping = g_slist_delete_link (looping, looping);
772 if (pent == NULL)
773 my_errno = ENOENT;
776 return pent;
779 /* --------------------------------------------------------------------------------------------- */
781 static struct entry *
782 extfs_resolve_symlinks (struct entry *entry)
784 struct entry *res;
786 errloop = FALSE;
787 notadir = FALSE;
788 res = extfs_resolve_symlinks_int (entry, NULL);
789 if (res == NULL)
791 if (errloop)
792 my_errno = ELOOP;
793 else if (notadir)
794 my_errno = ENOTDIR;
796 return res;
799 /* --------------------------------------------------------------------------------------------- */
801 static char *
802 extfs_get_archive_name (struct archive *archive)
804 const char *archive_name;
806 if (archive->local_name)
807 archive_name = archive->local_name;
808 else
809 archive_name = archive->name;
811 if (!archive_name || !*archive_name)
812 return g_strdup ("no_archive_name");
813 else
815 char *ret_str;
816 vfs_path_t *vpath = vfs_path_from_str (archive_name);
817 vfs_path_element_t *path_element = vfs_path_get_by_index (vpath, -1);
818 ret_str = g_strdup (path_element->path);
819 vfs_path_free (vpath);
820 return ret_str;
824 /* --------------------------------------------------------------------------------------------- */
825 /** Don't pass localname as NULL */
827 static int
828 extfs_cmd (const char *str_extfs_cmd, struct archive *archive,
829 struct entry *entry, const char *localname)
831 char *file;
832 char *quoted_file;
833 char *quoted_localname;
834 char *archive_name, *quoted_archive_name;
835 const extfs_plugin_info_t *info;
836 char *cmd;
837 int retval;
839 file = extfs_get_path_from_entry (entry);
840 quoted_file = name_quote (file, 0);
841 g_free (file);
843 archive_name = extfs_get_archive_name (archive);
844 quoted_archive_name = name_quote (archive_name, 0);
845 g_free (archive_name);
846 quoted_localname = name_quote (localname, 0);
847 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
848 cmd = g_strconcat (info->path, info->prefix, str_extfs_cmd,
849 quoted_archive_name, " ", quoted_file, " ", quoted_localname, (char *) NULL);
850 g_free (quoted_file);
851 g_free (quoted_localname);
852 g_free (quoted_archive_name);
854 open_error_pipe ();
855 retval = my_system (EXECUTE_AS_SHELL, shell, cmd);
856 g_free (cmd);
857 close_error_pipe (D_ERROR, NULL);
858 return retval;
861 /* --------------------------------------------------------------------------------------------- */
863 static void
864 extfs_run (const vfs_path_t * vpath)
866 struct archive *archive = NULL;
867 char *p, *q, *archive_name, *quoted_archive_name;
868 char *cmd;
869 const extfs_plugin_info_t *info;
871 p = extfs_get_path (vpath, &archive, FALSE);
872 if (p == NULL)
873 return;
874 q = name_quote (p, 0);
875 g_free (p);
877 archive_name = extfs_get_archive_name (archive);
878 quoted_archive_name = name_quote (archive_name, 0);
879 g_free (archive_name);
880 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype);
881 cmd =
882 g_strconcat (info->path, info->prefix, " run ", quoted_archive_name, " ", q, (char *) NULL);
883 g_free (quoted_archive_name);
884 g_free (q);
885 shell_execute (cmd, 0);
886 g_free (cmd);
889 /* --------------------------------------------------------------------------------------------- */
891 static void *
892 extfs_open (const vfs_path_t * vpath, int flags, mode_t mode)
894 struct pseudofile *extfs_info;
895 struct archive *archive = NULL;
896 char *q;
897 struct entry *entry;
898 int local_handle;
899 gboolean created = FALSE;
901 q = extfs_get_path (vpath, &archive, FALSE);
902 if (q == NULL)
903 return NULL;
904 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
905 if ((entry == NULL) && ((flags & O_CREAT) != 0))
907 /* Create new entry */
908 entry = extfs_find_entry (archive->root_entry, q, FALSE, TRUE);
909 created = (entry != NULL);
912 g_free (q);
913 if (entry == NULL)
914 return NULL;
915 entry = extfs_resolve_symlinks (entry);
916 if (entry == NULL)
917 return NULL;
919 if (S_ISDIR (entry->inode->mode))
920 ERRNOR (EISDIR, NULL);
922 if (entry->inode->local_filename == NULL)
924 char *local_filename;
926 local_handle = vfs_mkstemps (&local_filename, "extfs", entry->name);
928 if (local_handle == -1)
929 return NULL;
930 close (local_handle);
932 if (!created && ((flags & O_TRUNC) == 0)
933 && extfs_cmd (" copyout ", archive, entry, local_filename))
935 unlink (local_filename);
936 g_free (local_filename);
937 my_errno = EIO;
938 return NULL;
940 entry->inode->local_filename = local_filename;
943 local_handle = open (entry->inode->local_filename, NO_LINEAR (flags), mode);
945 if (local_handle == -1)
947 /* file exists(may be). Need to drop O_CREAT flag and truncate file content */
948 flags = ~O_CREAT & (NO_LINEAR (flags) | O_TRUNC);
949 local_handle = open (entry->inode->local_filename, flags, mode);
952 if (local_handle == -1)
953 ERRNOR (EIO, NULL);
955 extfs_info = g_new (struct pseudofile, 1);
956 extfs_info->archive = archive;
957 extfs_info->entry = entry;
958 extfs_info->has_changed = created;
959 extfs_info->local_handle = local_handle;
961 /* i.e. we had no open files and now we have one */
962 vfs_rmstamp (&vfs_extfs_ops, (vfsid) archive);
963 archive->fd_usage++;
964 return extfs_info;
967 /* --------------------------------------------------------------------------------------------- */
969 static ssize_t
970 extfs_read (void *data, char *buffer, size_t count)
972 struct pseudofile *file = (struct pseudofile *) data;
974 return read (file->local_handle, buffer, count);
977 /* --------------------------------------------------------------------------------------------- */
979 static int
980 extfs_close (void *data)
982 struct pseudofile *file;
983 int errno_code = 0;
984 file = (struct pseudofile *) data;
986 close (file->local_handle);
988 /* Commit the file if it has changed */
989 if (file->has_changed)
991 struct stat file_status;
993 if (extfs_cmd (" copyin ", file->archive, file->entry, file->entry->inode->local_filename))
994 errno_code = EIO;
996 if (stat (file->entry->inode->local_filename, &file_status) != 0)
997 errno_code = EIO;
998 else
999 file->entry->inode->size = file_status.st_size;
1001 file->entry->inode->mtime = time (NULL);
1004 if (--file->archive->fd_usage == 0)
1005 vfs_stamp_create (&vfs_extfs_ops, file->archive);
1007 g_free (data);
1008 if (errno_code != 0)
1009 ERRNOR (EIO, -1);
1010 return 0;
1013 /* --------------------------------------------------------------------------------------------- */
1015 static int
1016 extfs_errno (struct vfs_class *me)
1018 (void) me;
1019 return my_errno;
1022 /* --------------------------------------------------------------------------------------------- */
1024 static void *
1025 extfs_opendir (const vfs_path_t * vpath)
1027 struct archive *archive = NULL;
1028 char *q;
1029 struct entry *entry;
1030 struct entry **info;
1032 q = extfs_get_path (vpath, &archive, FALSE);
1033 if (q == NULL)
1034 return NULL;
1035 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1036 g_free (q);
1037 if (entry == NULL)
1038 return NULL;
1039 entry = extfs_resolve_symlinks (entry);
1040 if (entry == NULL)
1041 return NULL;
1042 if (!S_ISDIR (entry->inode->mode))
1043 ERRNOR (ENOTDIR, NULL);
1045 info = g_new (struct entry *, 2);
1046 info[0] = entry->inode->first_in_subdir;
1047 info[1] = entry->inode->first_in_subdir;
1049 return info;
1052 /* --------------------------------------------------------------------------------------------- */
1054 static void *
1055 extfs_readdir (void *data)
1057 static union vfs_dirent dir;
1058 struct entry **info = (struct entry **) data;
1060 if (*info == NULL)
1061 return NULL;
1063 g_strlcpy (dir.dent.d_name, (*info)->name, MC_MAXPATHLEN);
1065 compute_namelen (&dir.dent);
1066 *info = (*info)->next_in_dir;
1068 return (void *) &dir;
1071 /* --------------------------------------------------------------------------------------------- */
1073 static int
1074 extfs_closedir (void *data)
1076 g_free (data);
1077 return 0;
1080 /* --------------------------------------------------------------------------------------------- */
1082 static void
1083 extfs_stat_move (struct stat *buf, const struct inode *inode)
1085 buf->st_dev = inode->dev;
1086 buf->st_ino = inode->inode;
1087 buf->st_mode = inode->mode;
1088 buf->st_nlink = inode->nlink;
1089 buf->st_uid = inode->uid;
1090 buf->st_gid = inode->gid;
1091 #ifdef HAVE_STRUCT_STAT_ST_RDEV
1092 buf->st_rdev = inode->rdev;
1093 #endif
1094 buf->st_size = inode->size;
1095 #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
1096 buf->st_blksize = RECORDSIZE;
1097 #endif
1098 #ifdef HAVE_STRUCT_STAT_ST_BLOCKS
1099 buf->st_blocks = (inode->size + RECORDSIZE - 1) / RECORDSIZE;
1100 #endif
1101 buf->st_atime = inode->atime;
1102 buf->st_mtime = inode->mtime;
1103 buf->st_ctime = inode->ctime;
1106 /* --------------------------------------------------------------------------------------------- */
1108 static int
1109 extfs_internal_stat (const vfs_path_t * vpath, struct stat *buf, gboolean resolve)
1111 struct archive *archive;
1112 const char *q;
1113 struct entry *entry;
1114 int result = -1;
1116 q = extfs_get_path_int (vpath, &archive, FALSE);
1117 if (q == NULL)
1118 goto cleanup;
1119 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1120 if (entry == NULL)
1121 goto cleanup;
1122 if (resolve)
1124 entry = extfs_resolve_symlinks (entry);
1125 if (entry == NULL)
1126 goto cleanup;
1128 extfs_stat_move (buf, entry->inode);
1129 result = 0;
1130 cleanup:
1131 return result;
1134 /* --------------------------------------------------------------------------------------------- */
1136 static int
1137 extfs_stat (const vfs_path_t * vpath, struct stat *buf)
1139 return extfs_internal_stat (vpath, buf, TRUE);
1142 /* --------------------------------------------------------------------------------------------- */
1144 static int
1145 extfs_lstat (const vfs_path_t * vpath, struct stat *buf)
1147 return extfs_internal_stat (vpath, buf, FALSE);
1150 /* --------------------------------------------------------------------------------------------- */
1152 static int
1153 extfs_fstat (void *data, struct stat *buf)
1155 struct pseudofile *file = (struct pseudofile *) data;
1157 extfs_stat_move (buf, file->entry->inode);
1158 return 0;
1161 /* --------------------------------------------------------------------------------------------- */
1163 static int
1164 extfs_readlink (const vfs_path_t * vpath, char *buf, size_t size)
1166 struct archive *archive;
1167 const char *q;
1168 size_t len;
1169 struct entry *entry;
1170 int result = -1;
1172 q = extfs_get_path_int (vpath, &archive, FALSE);
1173 if (q == NULL)
1174 goto cleanup;
1175 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1176 if (entry == NULL)
1177 goto cleanup;
1178 if (!S_ISLNK (entry->inode->mode))
1180 vfs_path_element_t *path_element;
1181 path_element = vfs_path_get_by_index (vpath, -1);
1182 path_element->class->verrno = EINVAL;
1183 goto cleanup;
1185 len = strlen (entry->inode->linkname);
1186 if (size < len)
1187 len = size;
1188 /* readlink() does not append a NUL character to buf */
1189 result = len;
1190 memcpy (buf, entry->inode->linkname, result);
1191 cleanup:
1192 return result;
1195 /* --------------------------------------------------------------------------------------------- */
1197 static int
1198 extfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group)
1200 (void) vpath;
1201 (void) owner;
1202 (void) group;
1203 return 0;
1206 /* --------------------------------------------------------------------------------------------- */
1208 static int
1209 extfs_chmod (const vfs_path_t * vpath, mode_t mode)
1211 (void) vpath;
1212 (void) mode;
1213 return 0;
1216 /* --------------------------------------------------------------------------------------------- */
1218 static ssize_t
1219 extfs_write (void *data, const char *buf, size_t nbyte)
1221 struct pseudofile *file = (struct pseudofile *) data;
1223 file->has_changed = TRUE;
1224 return write (file->local_handle, buf, nbyte);
1227 /* --------------------------------------------------------------------------------------------- */
1229 static int
1230 extfs_unlink (const vfs_path_t * vpath)
1232 struct archive *archive;
1233 const char *q;
1234 struct entry *entry;
1235 int result = -1;
1237 q = extfs_get_path_int (vpath, &archive, FALSE);
1238 if (q == NULL)
1239 goto cleanup;
1240 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1241 if (entry == NULL)
1242 goto cleanup;
1243 entry = extfs_resolve_symlinks (entry);
1244 if (entry == NULL)
1245 goto cleanup;
1246 if (S_ISDIR (entry->inode->mode))
1248 vfs_path_element_t *path_element = vfs_path_get_by_index (vpath, -1);
1249 path_element->class->verrno = EISDIR;
1250 goto cleanup;
1252 if (extfs_cmd (" rm ", archive, entry, ""))
1254 my_errno = EIO;
1255 goto cleanup;
1257 extfs_remove_entry (entry);
1258 result = 0;
1259 cleanup:
1260 return result;
1263 /* --------------------------------------------------------------------------------------------- */
1265 static int
1266 extfs_mkdir (const vfs_path_t * vpath, mode_t mode)
1268 struct archive *archive;
1269 const char *q;
1270 struct entry *entry;
1271 int result = -1;
1272 vfs_path_element_t *path_element = vfs_path_get_by_index (vpath, -1);
1274 (void) mode;
1276 q = extfs_get_path_int (vpath, &archive, FALSE);
1277 if (q == NULL)
1278 goto cleanup;
1279 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1280 if (entry != NULL)
1282 path_element->class->verrno = EEXIST;
1283 goto cleanup;
1285 entry = extfs_find_entry (archive->root_entry, q, TRUE, FALSE);
1286 if (entry == NULL)
1287 goto cleanup;
1288 entry = extfs_resolve_symlinks (entry);
1289 if (entry == NULL)
1290 goto cleanup;
1291 if (!S_ISDIR (entry->inode->mode))
1293 path_element->class->verrno = ENOTDIR;
1294 goto cleanup;
1297 if (extfs_cmd (" mkdir ", archive, entry, ""))
1299 my_errno = EIO;
1300 extfs_remove_entry (entry);
1301 goto cleanup;
1303 result = 0;
1304 cleanup:
1305 return result;
1308 /* --------------------------------------------------------------------------------------------- */
1310 static int
1311 extfs_rmdir (const vfs_path_t * vpath)
1313 struct archive *archive;
1314 const char *q;
1315 struct entry *entry;
1316 int result = -1;
1318 q = extfs_get_path_int (vpath, &archive, FALSE);
1319 if (q == NULL)
1320 goto cleanup;
1321 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1322 if (entry == NULL)
1323 goto cleanup;
1324 entry = extfs_resolve_symlinks (entry);
1325 if (entry == NULL)
1326 goto cleanup;
1327 if (!S_ISDIR (entry->inode->mode))
1329 vfs_path_element_t *path_element = vfs_path_get_by_index (vpath, -1);
1330 path_element->class->verrno = ENOTDIR;
1331 goto cleanup;
1334 if (extfs_cmd (" rmdir ", archive, entry, ""))
1336 my_errno = EIO;
1337 goto cleanup;
1339 extfs_remove_entry (entry);
1340 result = 0;
1341 cleanup:
1342 return result;
1345 /* --------------------------------------------------------------------------------------------- */
1347 static int
1348 extfs_chdir (const vfs_path_t * vpath)
1350 struct archive *archive = NULL;
1351 char *q;
1352 struct entry *entry;
1354 my_errno = ENOTDIR;
1355 q = extfs_get_path (vpath, &archive, FALSE);
1356 if (q == NULL)
1357 return -1;
1358 entry = extfs_find_entry (archive->root_entry, q, FALSE, FALSE);
1359 g_free (q);
1360 if (entry == NULL)
1361 return -1;
1362 entry = extfs_resolve_symlinks (entry);
1363 if ((entry == NULL) || (!S_ISDIR (entry->inode->mode)))
1364 return -1;
1365 my_errno = 0;
1366 return 0;
1369 /* --------------------------------------------------------------------------------------------- */
1371 static off_t
1372 extfs_lseek (void *data, off_t offset, int whence)
1374 struct pseudofile *file = (struct pseudofile *) data;
1376 return lseek (file->local_handle, offset, whence);
1379 /* --------------------------------------------------------------------------------------------- */
1381 static vfsid
1382 extfs_getid (const vfs_path_t * vpath)
1384 struct archive *archive = NULL;
1385 char *p;
1387 p = extfs_get_path (vpath, &archive, TRUE);
1388 if (p == NULL)
1389 return NULL;
1390 g_free (p);
1391 return (vfsid) archive;
1394 /* --------------------------------------------------------------------------------------------- */
1396 static int
1397 extfs_nothingisopen (vfsid id)
1399 return (((struct archive *) id)->fd_usage <= 0);
1402 /* --------------------------------------------------------------------------------------------- */
1404 static void
1405 extfs_remove_entry (struct entry *e)
1407 int i = --e->inode->nlink;
1408 struct entry *pe, *ent, *prev;
1410 if (S_ISDIR (e->inode->mode) && e->inode->first_in_subdir != NULL)
1412 struct entry *f = e->inode->first_in_subdir;
1413 e->inode->first_in_subdir = NULL;
1414 extfs_remove_entry (f);
1416 pe = e->dir;
1417 if (e == pe->inode->first_in_subdir)
1418 pe->inode->first_in_subdir = e->next_in_dir;
1420 prev = NULL;
1421 for (ent = pe->inode->first_in_subdir; ent && ent->next_in_dir; ent = ent->next_in_dir)
1422 if (e == ent->next_in_dir)
1424 prev = ent;
1425 break;
1427 if (prev)
1428 prev->next_in_dir = e->next_in_dir;
1429 if (e == pe->inode->last_in_subdir)
1430 pe->inode->last_in_subdir = prev;
1432 if (i <= 0)
1434 if (e->inode->local_filename != NULL)
1436 unlink (e->inode->local_filename);
1437 g_free (e->inode->local_filename);
1439 g_free (e->inode->linkname);
1440 g_free (e->inode);
1443 g_free (e->name);
1444 g_free (e);
1447 /* --------------------------------------------------------------------------------------------- */
1449 static void
1450 extfs_free_entry (struct entry *e)
1452 int i = --e->inode->nlink;
1454 if (S_ISDIR (e->inode->mode) && e->inode->first_in_subdir != NULL)
1456 struct entry *f = e->inode->first_in_subdir;
1458 e->inode->first_in_subdir = NULL;
1459 extfs_free_entry (f);
1461 if (i <= 0)
1463 if (e->inode->local_filename != NULL)
1465 unlink (e->inode->local_filename);
1466 g_free (e->inode->local_filename);
1468 g_free (e->inode->linkname);
1469 g_free (e->inode);
1471 if (e->next_in_dir != NULL)
1472 extfs_free_entry (e->next_in_dir);
1473 g_free (e->name);
1474 g_free (e);
1477 /* --------------------------------------------------------------------------------------------- */
1479 static void
1480 extfs_free (vfsid id)
1482 struct archive *archive = (struct archive *) id;
1484 if (archive == first_archive)
1486 first_archive = archive->next;
1488 else
1490 struct archive *parc;
1491 for (parc = first_archive; parc != NULL; parc = parc->next)
1492 if (parc->next == archive)
1494 parc->next = archive->next;
1495 break;
1498 extfs_free_archive (archive);
1501 /* --------------------------------------------------------------------------------------------- */
1503 static char *
1504 extfs_getlocalcopy (const vfs_path_t * vpath)
1506 struct pseudofile *fp;
1507 char *p;
1509 fp = (struct pseudofile *) extfs_open (vpath, O_RDONLY, 0);
1510 if (fp == NULL)
1511 return NULL;
1512 if (fp->entry->inode->local_filename == NULL)
1514 extfs_close ((void *) fp);
1515 return NULL;
1517 p = g_strdup (fp->entry->inode->local_filename);
1518 fp->archive->fd_usage++;
1519 extfs_close ((void *) fp);
1520 return p;
1523 /* --------------------------------------------------------------------------------------------- */
1525 static int
1526 extfs_ungetlocalcopy (const vfs_path_t * vpath, const char *local, int has_changed)
1528 struct pseudofile *fp;
1530 fp = (struct pseudofile *) extfs_open (vpath, O_RDONLY, 0);
1531 if (fp == NULL)
1532 return 0;
1534 if (strcmp (fp->entry->inode->local_filename, local) == 0)
1536 fp->archive->fd_usage--;
1537 if (has_changed != 0)
1538 fp->has_changed = TRUE;
1539 extfs_close ((void *) fp);
1540 return 0;
1542 else
1544 /* Should not happen */
1545 extfs_close ((void *) fp);
1546 return 0;
1550 /* --------------------------------------------------------------------------------------------- */
1552 static gboolean
1553 extfs_get_plugins (const char *where, gboolean silent)
1555 char *dirname;
1556 GDir *dir;
1557 const char *filename;
1559 dirname = g_build_path (PATH_SEP_STR, where, MC_EXTFS_DIR, (char *) NULL);
1560 dir = g_dir_open (dirname, 0, NULL);
1562 /* We may not use vfs_die() message or message or similar,
1563 * UI is not initialized at this time and message would not
1564 * appear on screen. */
1565 if (dir == NULL)
1567 if (!silent)
1568 fprintf (stderr, _("Warning: cannot open %s directory\n"), dirname);
1569 g_free (dirname);
1570 return FALSE;
1573 if (extfs_plugins == NULL)
1574 extfs_plugins = g_array_sized_new (FALSE, TRUE, sizeof (extfs_plugin_info_t), 32);
1576 while ((filename = g_dir_read_name (dir)) != NULL)
1578 char fullname[MC_MAXPATHLEN];
1579 struct stat s;
1581 g_snprintf (fullname, sizeof (fullname), "%s" PATH_SEP_STR "%s", dirname, filename);
1583 if ((stat (fullname, &s) == 0)
1584 && S_ISREG (s.st_mode) && !S_ISDIR (s.st_mode)
1585 && (((s.st_mode & S_IXOTH) != 0) ||
1586 ((s.st_mode & S_IXUSR) != 0) || ((s.st_mode & S_IXGRP) != 0)))
1588 int f;
1590 f = open (fullname, O_RDONLY);
1592 if (f > 0)
1594 size_t len, i;
1595 extfs_plugin_info_t info;
1596 gboolean found = FALSE;
1598 close (f);
1600 /* Handle those with a trailing '+', those flag that the
1601 * file system does not require an archive to work
1603 len = strlen (filename);
1604 info.need_archive = (filename[len - 1] != '+');
1605 info.path = g_strconcat (dirname, PATH_SEP_STR, (char *) NULL);
1606 info.prefix = g_strdup (filename);
1608 /* prepare to compare file names without trailing '+' */
1609 if (!info.need_archive)
1610 info.prefix[len - 1] = '\0';
1612 /* don't overload already found plugin */
1613 for (i = 0; i < extfs_plugins->len; i++)
1615 extfs_plugin_info_t *p;
1617 p = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
1619 /* 2 files with same names cannot be in a directory */
1620 if ((strcmp (info.path, p->path) != 0)
1621 && (strcmp (info.prefix, p->prefix) == 0))
1623 found = TRUE;
1624 break;
1628 if (found)
1630 g_free (info.path);
1631 g_free (info.prefix);
1633 else
1635 /* restore file name */
1636 if (!info.need_archive)
1637 info.prefix[len - 1] = '+';
1638 g_array_append_val (extfs_plugins, info);
1644 g_dir_close (dir);
1645 g_free (dirname);
1647 return TRUE;
1650 /* --------------------------------------------------------------------------------------------- */
1652 static int
1653 extfs_init (struct vfs_class *me)
1655 gboolean d1, d2;
1657 (void) me;
1659 /* 1st: scan user directory */
1660 d1 = extfs_get_plugins (mc_config_get_data_path (), TRUE); /* silent about user dir */
1661 /* 2nd: scan system dir */
1662 d2 = extfs_get_plugins (LIBEXECDIR, d1);
1664 return (d1 || d2 ? 1 : 0);
1667 /* --------------------------------------------------------------------------------------------- */
1669 static void
1670 extfs_done (struct vfs_class *me)
1672 size_t i;
1673 struct archive *ar;
1675 (void) me;
1677 for (ar = first_archive; ar != NULL;)
1679 extfs_free ((vfsid) ar);
1680 ar = first_archive;
1683 for (i = 0; i < extfs_plugins->len; i++)
1685 extfs_plugin_info_t *info;
1687 info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i);
1688 g_free (info->path);
1689 g_free (info->prefix);
1692 if (extfs_plugins != NULL)
1693 g_array_free (extfs_plugins, TRUE);
1696 /* --------------------------------------------------------------------------------------------- */
1698 static int
1699 extfs_setctl (const vfs_path_t * vpath, int ctlop, void *arg)
1701 (void) arg;
1703 if (ctlop == VFS_SETCTL_RUN)
1705 extfs_run (vpath);
1706 return 1;
1708 return 0;
1711 /* --------------------------------------------------------------------------------------------- */
1712 /*** public functions ****************************************************************************/
1713 /* --------------------------------------------------------------------------------------------- */
1715 void
1716 init_extfs (void)
1718 vfs_extfs_ops.name = "extfs";
1719 vfs_extfs_ops.init = extfs_init;
1720 vfs_extfs_ops.done = extfs_done;
1721 vfs_extfs_ops.fill_names = extfs_fill_names;
1722 vfs_extfs_ops.which = extfs_which;
1723 vfs_extfs_ops.open = extfs_open;
1724 vfs_extfs_ops.close = extfs_close;
1725 vfs_extfs_ops.read = extfs_read;
1726 vfs_extfs_ops.write = extfs_write;
1727 vfs_extfs_ops.opendir = extfs_opendir;
1728 vfs_extfs_ops.readdir = extfs_readdir;
1729 vfs_extfs_ops.closedir = extfs_closedir;
1730 vfs_extfs_ops.stat = extfs_stat;
1731 vfs_extfs_ops.lstat = extfs_lstat;
1732 vfs_extfs_ops.fstat = extfs_fstat;
1733 vfs_extfs_ops.chmod = extfs_chmod;
1734 vfs_extfs_ops.chown = extfs_chown;
1735 vfs_extfs_ops.readlink = extfs_readlink;
1736 vfs_extfs_ops.unlink = extfs_unlink;
1737 vfs_extfs_ops.chdir = extfs_chdir;
1738 vfs_extfs_ops.ferrno = extfs_errno;
1739 vfs_extfs_ops.lseek = extfs_lseek;
1740 vfs_extfs_ops.getid = extfs_getid;
1741 vfs_extfs_ops.nothingisopen = extfs_nothingisopen;
1742 vfs_extfs_ops.free = extfs_free;
1743 vfs_extfs_ops.getlocalcopy = extfs_getlocalcopy;
1744 vfs_extfs_ops.ungetlocalcopy = extfs_ungetlocalcopy;
1745 vfs_extfs_ops.mkdir = extfs_mkdir;
1746 vfs_extfs_ops.rmdir = extfs_rmdir;
1747 vfs_extfs_ops.setctl = extfs_setctl;
1748 vfs_register_class (&vfs_extfs_ops);
1751 /* --------------------------------------------------------------------------------------------- */