/* Virtual File System: External file system. Copyright (C) 1995-2022 Free Software Foundation, Inc. Written by: Jakub Jelinek, 1995 Pavel Machek, 1998 Andrew T. Veliath, 1999 Slava Zanko , 2013 This file is part of the Midnight Commander. The Midnight Commander is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The Midnight Commander is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** * \file * \brief Source: Virtual File System: External file system * \author Jakub Jelinek * \author Pavel Machek * \author Andrew T. Veliath * \date 1995, 1998, 1999 */ /* Namespace: init_extfs */ #include #include #include #include #include #include #include #include #include #include #include #include "lib/global.h" #include "lib/fileloc.h" #include "lib/mcconfig.h" #include "lib/util.h" #include "lib/widget.h" /* message() */ #include "src/execute.h" /* For shell_execute */ #include "lib/vfs/vfs.h" #include "lib/vfs/utilvfs.h" #include "lib/vfs/xdirentry.h" #include "lib/vfs/gc.h" /* vfs_rmstamp */ #include "extfs.h" /*** global variables ****************************************************************************/ /*** file scope macro definitions ****************************************************************/ #undef ERRNOR #define ERRNOR(x,y) do { my_errno = x; return y; } while(0) #define RECORDSIZE 512 #define EXTFS_SUPER(a) ((struct extfs_super_t *) (a)) /*** file scope type declarations ****************************************************************/ struct extfs_super_t { struct vfs_s_super base; /* base class */ int fstype; char *local_name; struct stat local_stat; dev_t rdev; }; typedef struct { char *path; char *prefix; gboolean need_archive; } extfs_plugin_info_t; /*** file scope variables ************************************************************************/ static GArray *extfs_plugins = NULL; static gboolean errloop; static gboolean notadir; static struct vfs_s_subclass extfs_subclass; static struct vfs_class *vfs_extfs_ops = VFS_CLASS (&extfs_subclass); static int my_errno = 0; /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry *extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list); /* --------------------------------------------------------------------------------------------- */ static struct extfs_super_t * extfs_super_new (struct vfs_class *me, const char *name, const vfs_path_t * local_name_vpath, int fstype) { struct extfs_super_t *super; struct vfs_s_super *vsuper; super = g_new0 (struct extfs_super_t, 1); vsuper = VFS_SUPER (super); vsuper->me = me; vsuper->name = g_strdup (name); super->fstype = fstype; if (local_name_vpath != NULL) { super->local_name = g_strdup (vfs_path_get_last_path_str (local_name_vpath)); mc_stat (local_name_vpath, &super->local_stat); } VFS_SUBCLASS (me)->supers = g_list_prepend (VFS_SUBCLASS (me)->supers, super); return super; } /* --------------------------------------------------------------------------------------------- */ /* unlike vfs_s_new_entry(), inode->ent is kept */ static struct vfs_s_entry * extfs_entry_new (struct vfs_class *me, const char *name, struct vfs_s_inode *inode) { struct vfs_s_entry *entry; (void) me; entry = g_new0 (struct vfs_s_entry, 1); entry->name = g_strdup (name); entry->ino = inode; return entry; } /* --------------------------------------------------------------------------------------------- */ static void extfs_fill_name (void *data, void *user_data) { struct vfs_s_super *a = VFS_SUPER (data); fill_names_f func = (fill_names_f) user_data; extfs_plugin_info_t *info; char *name; info = &g_array_index (extfs_plugins, extfs_plugin_info_t, EXTFS_SUPER (a)->fstype); name = g_strconcat (a->name != NULL ? a->name : "", PATH_SEP_STR, info->prefix, VFS_PATH_URL_DELIMITER, (char *) NULL); func (name); g_free (name); } /* --------------------------------------------------------------------------------------------- */ static gint extfs_cmp_archive (const void *a, const void *b) { const struct vfs_s_super *ar = (const struct vfs_s_super *) a; const char *archive_name = (const char *) b; return (ar->name != NULL && strcmp (ar->name, archive_name) == 0) ? 0 : 1; } /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry * extfs_generate_entry (struct extfs_super_t *archive, const char *name, struct vfs_s_inode *parent, mode_t mode) { struct vfs_class *me = VFS_SUPER (archive)->me; struct stat st; mode_t myumask; struct vfs_s_inode *inode; struct vfs_s_entry *entry; memset (&st, 0, sizeof (st)); st.st_ino = VFS_SUPER (archive)->ino_usage++; st.st_dev = archive->rdev; myumask = umask (022); umask (myumask); st.st_mode = mode & ~myumask; st.st_uid = getuid (); st.st_gid = getgid (); st.st_mtime = time (NULL); st.st_atime = st.st_mtime; st.st_ctime = st.st_mtime; st.st_nlink = 1; inode = vfs_s_new_inode (me, VFS_SUPER (archive), &st); entry = vfs_s_new_entry (me, name, inode); if (parent != NULL) vfs_s_insert_entry (me, parent, entry); return entry; } /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry * extfs_find_entry_int (struct vfs_s_inode *dir, const char *name, GSList * list, int flags) { struct vfs_s_entry *pent, *pdir; const char *p, *name_end; char *q; char c = PATH_SEP; struct extfs_super_t *super; if (g_path_is_absolute (name)) { /* Handle absolute paths */ name = g_path_skip_root (name); dir = dir->super->root; } super = EXTFS_SUPER (dir->super); pent = dir->ent; p = name; name_end = name + strlen (name); while ((pent != NULL) && (c != '\0') && (*p != '\0')) { q = strchr (p, PATH_SEP); if (q == NULL) q = (char *) name_end; c = *q; *q = '\0'; if (DIR_IS_DOTDOT (p)) pent = pent->dir != NULL ? pent->dir->ent : NULL; else { GList *pl; pent = extfs_resolve_symlinks_int (pent, list); if (pent == NULL) { *q = c; return NULL; } if (!S_ISDIR (pent->ino->st.st_mode)) { *q = c; notadir = TRUE; return NULL; } pdir = pent; pl = g_queue_find_custom (pent->ino->subdir, p, vfs_s_entry_compare); pent = pl != NULL ? VFS_ENTRY (pl->data) : NULL; if (pent != NULL && q + 1 > name_end) { /* Hack: I keep the original semanthic unless q+1 would break in the strchr */ *q = c; notadir = !S_ISDIR (pent->ino->st.st_mode); return pent; } /* When we load archive, we create automagically non-existent directories */ if (pent == NULL && (flags & FL_MKDIR) != 0) pent = extfs_generate_entry (super, p, pdir->ino, S_IFDIR | 0777); if (pent == NULL && (flags & FL_MKFILE) != 0) pent = extfs_generate_entry (super, p, pdir->ino, S_IFREG | 0666); } /* Next iteration */ *q = c; if (c != '\0') p = q + 1; } if (pent == NULL) my_errno = ENOENT; return pent; } /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry * extfs_find_entry (struct vfs_s_inode *dir, const char *name, int flags) { struct vfs_s_entry *res; errloop = FALSE; notadir = FALSE; res = extfs_find_entry_int (dir, name, NULL, flags); if (res == NULL) { if (errloop) my_errno = ELOOP; else if (notadir) my_errno = ENOTDIR; } return res; } /* --------------------------------------------------------------------------------------------- */ static void extfs_fill_names (struct vfs_class *me, fill_names_f func) { g_list_foreach (VFS_SUBCLASS (me)->supers, extfs_fill_name, func); } /* --------------------------------------------------------------------------------------------- */ /* Create this function because VFSF_USETMP flag is not used in extfs */ static void extfs_free_inode (struct vfs_class *me, struct vfs_s_inode *ino) { (void) me; if (ino->localname != NULL) { unlink (ino->localname); MC_PTR_FREE (ino->localname); } } /* --------------------------------------------------------------------------------------------- */ static void extfs_free_archive (struct vfs_class *me, struct vfs_s_super *psup) { struct extfs_super_t *archive = EXTFS_SUPER (psup); (void) me; if (archive->local_name != NULL) { struct stat my; vfs_path_t *local_name_vpath, *name_vpath; local_name_vpath = vfs_path_from_str (archive->local_name); name_vpath = vfs_path_from_str (psup->name); mc_stat (local_name_vpath, &my); mc_ungetlocalcopy (name_vpath, local_name_vpath, archive->local_stat.st_mtime != my.st_mtime); vfs_path_free (local_name_vpath, TRUE); vfs_path_free (name_vpath, TRUE); g_free (archive->local_name); } } /* --------------------------------------------------------------------------------------------- */ static inline char * extfs_skip_leading_dotslash (char *s) { /* Skip leading "./" (if present). * Some programs don't understand it: * * $ zip file.zip ./-file2.txt file1.txt * adding: -file2.txt (stored 0%) * adding: file1.txt (stored 0%) * $ /usr/lib/mc/extfs.d/uzip copyout file.zip ./-file2.txt ./tmp-file2.txt * caution: filename not matched: ./-file2.txt */ if (s[0] == '.' && s[1] == PATH_SEP) s += 2; return s; } /* --------------------------------------------------------------------------------------------- */ static int extfs_add_file (struct extfs_super_t *archive, const char *file_name) { struct vfs_s_super *super = VFS_SUPER (archive); struct stat hstat; char *current_file_name = NULL, *current_link_name = NULL; int ret = 0; if (vfs_parse_ls_lga (file_name, &hstat, ¤t_file_name, ¤t_link_name, NULL)) { char *cfn = current_file_name; if (*cfn != '\0') { struct vfs_s_entry *entry; struct vfs_s_entry *pent = NULL; struct vfs_s_inode *inode; char *p, *q; cfn = extfs_skip_leading_dotslash (cfn); if (IS_PATH_SEP (*cfn)) cfn++; p = strchr (cfn, '\0'); if (p != cfn && IS_PATH_SEP (p[-1])) p[-1] = '\0'; p = strrchr (cfn, PATH_SEP); if (p == NULL) { p = cfn; q = strchr (cfn, '\0'); } else { *(p++) = '\0'; q = cfn; } if (*q != '\0') { pent = extfs_find_entry (super->root, q, FL_MKDIR); if (pent == NULL) { ret = -1; goto done; } } if (pent != NULL) { entry = extfs_entry_new (super->me, p, pent->ino); entry->dir = pent->ino; g_queue_push_tail (pent->ino->subdir, entry); } else { entry = extfs_entry_new (super->me, p, super->root); entry->dir = super->root; g_queue_push_tail (super->root->subdir, entry); } if (!S_ISLNK (hstat.st_mode) && (current_link_name != NULL)) { pent = extfs_find_entry (super->root, current_link_name, FL_NONE); if (pent == NULL) { ret = -1; goto done; } pent->ino->st.st_nlink++; entry->ino = pent->ino; } else { struct stat st; memset (&st, 0, sizeof (st)); st.st_ino = super->ino_usage++; st.st_nlink = 1; st.st_dev = archive->rdev; st.st_mode = hstat.st_mode; #ifdef HAVE_STRUCT_STAT_ST_RDEV st.st_rdev = hstat.st_rdev; #endif st.st_uid = hstat.st_uid; st.st_gid = hstat.st_gid; st.st_size = hstat.st_size; st.st_mtime = hstat.st_mtime; st.st_atime = hstat.st_atime; st.st_ctime = hstat.st_ctime; if (current_link_name == NULL && S_ISLNK (hstat.st_mode)) st.st_mode &= ~S_IFLNK; /* You *DON'T* want to do this always */ inode = vfs_s_new_inode (super->me, super, &st); inode->ent = entry; entry->ino = inode; if (current_link_name != NULL && S_ISLNK (hstat.st_mode)) { inode->linkname = current_link_name; current_link_name = NULL; } } } done: g_free (current_file_name); g_free (current_link_name); } return ret; } /* --------------------------------------------------------------------------------------------- */ static mc_pipe_t * extfs_open_archive (int fstype, const char *name, struct extfs_super_t **pparc, GError ** error) { const extfs_plugin_info_t *info; static dev_t archive_counter = 0; mc_pipe_t *result = NULL; mode_t mode; char *cmd; struct stat mystat; struct extfs_super_t *current_archive; struct vfs_s_entry *root_entry; char *tmp = NULL; vfs_path_t *local_name_vpath = NULL; vfs_path_t *name_vpath; memset (&mystat, 0, sizeof (mystat)); name_vpath = vfs_path_from_str (name); info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype); if (info->need_archive) { if (mc_stat (name_vpath, &mystat) == -1) goto ret; if (!vfs_file_is_local (name_vpath)) { local_name_vpath = mc_getlocalcopy (name_vpath); if (local_name_vpath == NULL) goto ret; } tmp = name_quote (vfs_path_get_last_path_str (name_vpath), FALSE); } cmd = g_strconcat (info->path, info->prefix, " list ", vfs_path_get_last_path_str (local_name_vpath) != NULL ? vfs_path_get_last_path_str (local_name_vpath) : tmp, (char *) NULL); g_free (tmp); result = mc_popen (cmd, TRUE, TRUE, error); g_free (cmd); if (result == NULL) { if (local_name_vpath != NULL) { mc_ungetlocalcopy (name_vpath, local_name_vpath, FALSE); vfs_path_free (local_name_vpath, TRUE); } goto ret; } current_archive = extfs_super_new (vfs_extfs_ops, name, local_name_vpath, fstype); current_archive->rdev = archive_counter++; vfs_path_free (local_name_vpath, TRUE); mode = mystat.st_mode & 07777; if (mode & 0400) mode |= 0100; if (mode & 0040) mode |= 0010; if (mode & 0004) mode |= 0001; mode |= S_IFDIR; root_entry = extfs_generate_entry (current_archive, PATH_SEP_STR, NULL, mode); root_entry->ino->st.st_uid = mystat.st_uid; root_entry->ino->st.st_gid = mystat.st_gid; root_entry->ino->st.st_atime = mystat.st_atime; root_entry->ino->st.st_ctime = mystat.st_ctime; root_entry->ino->st.st_mtime = mystat.st_mtime; root_entry->ino->ent = root_entry; VFS_SUPER (current_archive)->root = root_entry->ino; *pparc = current_archive; ret: vfs_path_free (name_vpath, TRUE); return result; } /* --------------------------------------------------------------------------------------------- */ /** * Main loop for reading an archive. * Return 0 on success, -1 on error. */ static int extfs_read_archive (mc_pipe_t * pip, struct extfs_super_t *archive, GError ** error) { int ret = 0; GString *buffer; GString *err_msg = NULL; GString *remain_file_name = NULL; while (ret != -1) { /* init buffers before call of mc_pread() */ pip->out.len = MC_PIPE_BUFSIZE; pip->err.len = MC_PIPE_BUFSIZE; mc_pread (pip, error); if (*error != NULL) return (-1); if (pip->err.len > 0) { /* join errors/warnings */ if (err_msg == NULL) err_msg = g_string_new_len (pip->err.buf, pip->err.len); else { if (err_msg->str[err_msg->len - 1] != '\n') g_string_append_c (err_msg, '\n'); g_string_append_len (err_msg, pip->err.buf, pip->err.len); } } if (pip->out.len == MC_PIPE_STREAM_EOF) break; if (pip->out.len == 0) continue; if (pip->out.len == MC_PIPE_ERROR_READ) return (-1); while (ret != -1 && (buffer = mc_pstream_get_string (&pip->out)) != NULL) { /* handle a \n-separated file list */ if (buffer->str[buffer->len - 1] == '\n') { /* entire file name or last chunk */ g_string_truncate (buffer, buffer->len - 1); /* join filename chunks */ if (remain_file_name != NULL) { g_string_append_len (remain_file_name, buffer->str, buffer->len); g_string_free (buffer, TRUE); buffer = remain_file_name; remain_file_name = NULL; } } else { /* first or middle chunk of file name */ if (remain_file_name == NULL) remain_file_name = buffer; else { g_string_append_len (remain_file_name, buffer->str, buffer->len); g_string_free (buffer, TRUE); } continue; } ret = extfs_add_file (archive, buffer->str); g_string_free (buffer, TRUE); } } if (remain_file_name != NULL) g_string_free (remain_file_name, TRUE); if (err_msg != NULL) { if (*error == NULL) mc_propagate_error (error, 0, "%s", err_msg->str); g_string_free (err_msg, TRUE); } else if (ret == -1) mc_propagate_error (error, 0, "%s", _("Inconsistent archive")); return ret; } /* --------------------------------------------------------------------------------------------- */ static int extfs_which (struct vfs_class *me, const char *path) { size_t path_len; size_t i; (void) me; path_len = strlen (path); for (i = 0; i < extfs_plugins->len; i++) { extfs_plugin_info_t *info; info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i); if ((strncmp (path, info->prefix, path_len) == 0) && ((info->prefix[path_len] == '\0') || (info->prefix[path_len] == '+'))) return i; } return -1; } /* --------------------------------------------------------------------------------------------- */ static int extfs_open_and_read_archive (int fstype, const char *name, struct extfs_super_t **archive) { int result = -1; struct extfs_super_t *a; mc_pipe_t *pip; GError *error = NULL; pip = extfs_open_archive (fstype, name, archive, &error); a = *archive; if (pip == NULL) { const extfs_plugin_info_t *info; info = &g_array_index (extfs_plugins, extfs_plugin_info_t, fstype); message (D_ERROR, MSG_ERROR, _("Cannot open %s archive\n%s:\n%s"), info->prefix, name, error->message); g_error_free (error); } else { result = extfs_read_archive (pip, a, &error); if (result != 0) VFS_SUPER (a)->me->free (VFS_SUPER (a)); if (error != NULL) { message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); g_error_free (error); } mc_pclose (pip, NULL); } return result; } /* --------------------------------------------------------------------------------------------- */ /** * Dissect the path and create corresponding superblock. */ static const char * extfs_get_path (const vfs_path_t * vpath, struct extfs_super_t **archive, int flags) { char *archive_name; int result = -1; GList *parc; int fstype; const vfs_path_element_t *path_element; struct extfs_super_t *a = NULL; path_element = vfs_path_get_by_index (vpath, -1); fstype = extfs_which (path_element->class, path_element->vfs_prefix); if (fstype == -1) return NULL; archive_name = vfs_path_to_str_elements_count (vpath, -1); /* All filesystems should have some local archive, at least it can be PATH_SEP ('/'). */ parc = g_list_find_custom (extfs_subclass.supers, archive_name, extfs_cmp_archive); if (parc != NULL) { a = EXTFS_SUPER (parc->data); vfs_stamp (vfs_extfs_ops, (vfsid) a); g_free (archive_name); } else { if ((flags & FL_NO_OPEN) == 0) result = extfs_open_and_read_archive (fstype, archive_name, &a); g_free (archive_name); if (result == -1) { path_element->class->verrno = EIO; return NULL; } } *archive = a; return path_element->path; } /* --------------------------------------------------------------------------------------------- */ /* Return allocated path (without leading slash) inside the archive */ static char * extfs_get_path_from_entry (const struct vfs_s_entry *entry) { const struct vfs_s_entry *e; GString *localpath; localpath = g_string_new (""); for (e = entry; e->dir != NULL; e = e->dir->ent) { g_string_prepend (localpath, e->name); if (e->dir->ent->dir != NULL) g_string_prepend_c (localpath, PATH_SEP); } return g_string_free (localpath, FALSE); } /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry * extfs_resolve_symlinks_int (struct vfs_s_entry *entry, GSList * list) { struct vfs_s_entry *pent = NULL; if (!S_ISLNK (entry->ino->st.st_mode)) return entry; if (g_slist_find (list, entry) != NULL) { /* Here we protect us against symlink looping */ errloop = TRUE; } else { GSList *looping; looping = g_slist_prepend (list, entry); pent = extfs_find_entry_int (entry->dir, entry->ino->linkname, looping, FL_NONE); looping = g_slist_delete_link (looping, looping); if (pent == NULL) my_errno = ENOENT; } return pent; } /* --------------------------------------------------------------------------------------------- */ static struct vfs_s_entry * extfs_resolve_symlinks (struct vfs_s_entry *entry) { struct vfs_s_entry *res; errloop = FALSE; notadir = FALSE; res = extfs_resolve_symlinks_int (entry, NULL); if (res == NULL) { if (errloop) my_errno = ELOOP; else if (notadir) my_errno = ENOTDIR; } return res; } /* --------------------------------------------------------------------------------------------- */ static char * extfs_get_archive_name (const struct extfs_super_t *archive) { const char *archive_name; if (archive->local_name != NULL) archive_name = archive->local_name; else archive_name = CONST_VFS_SUPER (archive)->name; if (archive_name == NULL || *archive_name == '\0') return g_strdup ("no_archive_name"); else { char *ret_str; vfs_path_t *vpath; const vfs_path_element_t *path_element; vpath = vfs_path_from_str (archive_name); path_element = vfs_path_get_by_index (vpath, -1); ret_str = g_strdup (path_element->path); vfs_path_free (vpath, TRUE); return ret_str; } } /* --------------------------------------------------------------------------------------------- */ /** Don't pass localname as NULL */ static int extfs_cmd (const char *str_extfs_cmd, const struct extfs_super_t *archive, const struct vfs_s_entry *entry, const char *localname) { char *file; char *quoted_file; char *quoted_localname; char *archive_name, *quoted_archive_name; const extfs_plugin_info_t *info; char *cmd; int retval = 0; GError *error = NULL; mc_pipe_t *pip; file = extfs_get_path_from_entry (entry); quoted_file = name_quote (file, FALSE); g_free (file); /* Skip leading "./" (if present) added in name_quote() */ file = extfs_skip_leading_dotslash (quoted_file); archive_name = extfs_get_archive_name (archive); quoted_archive_name = name_quote (archive_name, FALSE); g_free (archive_name); quoted_localname = name_quote (localname, FALSE); info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype); cmd = g_strconcat (info->path, info->prefix, str_extfs_cmd, quoted_archive_name, " ", file, " ", quoted_localname, (char *) NULL); g_free (quoted_file); g_free (quoted_localname); g_free (quoted_archive_name); /* don't read stdout */ pip = mc_popen (cmd, FALSE, TRUE, &error); g_free (cmd); if (pip == NULL) { message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); g_error_free (error); return (-1); } pip->err.null_term = TRUE; mc_pread (pip, &error); if (error != NULL) { message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), error->message); g_error_free (error); retval = -1; } else if (pip->err.len > 0) message (D_ERROR, MSG_ERROR, _("EXTFS virtual file system:\n%s"), pip->err.buf); mc_pclose (pip, NULL); return retval; } /* --------------------------------------------------------------------------------------------- */ static void extfs_run (const vfs_path_t * vpath) { struct extfs_super_t *archive = NULL; const char *p; char *q, *archive_name, *quoted_archive_name; char *cmd; const extfs_plugin_info_t *info; p = extfs_get_path (vpath, &archive, FL_NONE); if (p == NULL) return; q = name_quote (p, FALSE); archive_name = extfs_get_archive_name (archive); quoted_archive_name = name_quote (archive_name, FALSE); g_free (archive_name); info = &g_array_index (extfs_plugins, extfs_plugin_info_t, archive->fstype); cmd = g_strconcat (info->path, info->prefix, " run ", quoted_archive_name, " ", q, (char *) NULL); g_free (quoted_archive_name); g_free (q); shell_execute (cmd, 0); g_free (cmd); } /* --------------------------------------------------------------------------------------------- */ static void * extfs_open (const vfs_path_t * vpath, int flags, mode_t mode) { vfs_file_handler_t *extfs_info; struct extfs_super_t *archive = NULL; const char *q; struct vfs_s_entry *entry; int local_handle; gboolean created = FALSE; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) return NULL; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if ((entry == NULL) && ((flags & O_CREAT) != 0)) { /* Create new entry */ entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKFILE); created = (entry != NULL); } if (entry == NULL) return NULL; entry = extfs_resolve_symlinks (entry); if (entry == NULL) return NULL; if (S_ISDIR (entry->ino->st.st_mode)) ERRNOR (EISDIR, NULL); if (entry->ino->localname == NULL) { vfs_path_t *local_filename_vpath; const char *local_filename; local_handle = vfs_mkstemps (&local_filename_vpath, "extfs", entry->name); if (local_handle == -1) return NULL; close (local_handle); local_filename = vfs_path_get_by_index (local_filename_vpath, -1)->path; if (!created && ((flags & O_TRUNC) == 0) && extfs_cmd (" copyout ", archive, entry, local_filename)) { unlink (local_filename); vfs_path_free (local_filename_vpath, TRUE); my_errno = EIO; return NULL; } entry->ino->localname = g_strdup (local_filename); vfs_path_free (local_filename_vpath, TRUE); } local_handle = open (entry->ino->localname, NO_LINEAR (flags), mode); if (local_handle == -1) { /* file exists(may be). Need to drop O_CREAT flag and truncate file content */ flags = ~O_CREAT & (NO_LINEAR (flags) | O_TRUNC); local_handle = open (entry->ino->localname, flags, mode); } if (local_handle == -1) ERRNOR (EIO, NULL); extfs_info = g_new (vfs_file_handler_t, 1); vfs_s_init_fh (extfs_info, entry->ino, created); extfs_info->handle = local_handle; /* i.e. we had no open files and now we have one */ vfs_rmstamp (vfs_extfs_ops, (vfsid) archive); VFS_SUPER (archive)->fd_usage++; return extfs_info; } /* --------------------------------------------------------------------------------------------- */ static ssize_t extfs_read (void *fh, char *buffer, size_t count) { vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); return read (file->handle, buffer, count); } /* --------------------------------------------------------------------------------------------- */ static int extfs_close (void *fh) { vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); int errno_code = 0; close (file->handle); file->handle = -1; /* Commit the file if it has changed */ if (file->changed) { struct stat file_status; if (extfs_cmd (" copyin ", EXTFS_SUPER (VFS_FILE_HANDLER_SUPER (fh)), file->ino->ent, file->ino->localname)) errno_code = EIO; if (stat (file->ino->localname, &file_status) != 0) errno_code = EIO; else file->ino->st.st_size = file_status.st_size; file->ino->st.st_mtime = time (NULL); } if (--VFS_FILE_HANDLER_SUPER (fh)->fd_usage == 0) vfs_stamp_create (vfs_extfs_ops, VFS_FILE_HANDLER_SUPER (fh)); g_free (fh); if (errno_code != 0) ERRNOR (EIO, -1); return 0; } /* --------------------------------------------------------------------------------------------- */ static int extfs_errno (struct vfs_class *me) { (void) me; return my_errno; } /* --------------------------------------------------------------------------------------------- */ static void * extfs_opendir (const vfs_path_t * vpath) { struct extfs_super_t *archive = NULL; const char *q; struct vfs_s_entry *entry; GList **info; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) return NULL; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry == NULL) return NULL; entry = extfs_resolve_symlinks (entry); if (entry == NULL) return NULL; if (!S_ISDIR (entry->ino->st.st_mode)) ERRNOR (ENOTDIR, NULL); info = g_new (GList *, 1); *info = g_queue_peek_head_link (entry->ino->subdir); return info; } /* --------------------------------------------------------------------------------------------- */ static struct vfs_dirent * extfs_readdir (void *data) { struct vfs_dirent *dir; GList **info = (GList **) data; if (*info == NULL) return NULL; dir = vfs_dirent_init (NULL, VFS_ENTRY ((*info)->data)->name, 0); /* FIXME: inode */ *info = g_list_next (*info); return dir; } /* --------------------------------------------------------------------------------------------- */ static int extfs_closedir (void *data) { g_free (data); return 0; } /* --------------------------------------------------------------------------------------------- */ static void extfs_stat_move (struct stat *buf, const struct vfs_s_inode *inode) { *buf = inode->st; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE buf->st_blksize = RECORDSIZE; #endif vfs_adjust_stat (buf); #ifdef HAVE_STRUCT_STAT_ST_MTIM buf->st_atim.tv_nsec = buf->st_mtim.tv_nsec = buf->st_ctim.tv_nsec = 0; #endif } /* --------------------------------------------------------------------------------------------- */ static int extfs_internal_stat (const vfs_path_t * vpath, struct stat *buf, gboolean resolve) { struct extfs_super_t *archive; const char *q; struct vfs_s_entry *entry; int result = -1; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) goto cleanup; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry == NULL) goto cleanup; if (resolve) { entry = extfs_resolve_symlinks (entry); if (entry == NULL) goto cleanup; } extfs_stat_move (buf, entry->ino); result = 0; cleanup: return result; } /* --------------------------------------------------------------------------------------------- */ static int extfs_stat (const vfs_path_t * vpath, struct stat *buf) { return extfs_internal_stat (vpath, buf, TRUE); } /* --------------------------------------------------------------------------------------------- */ static int extfs_lstat (const vfs_path_t * vpath, struct stat *buf) { return extfs_internal_stat (vpath, buf, FALSE); } /* --------------------------------------------------------------------------------------------- */ static int extfs_fstat (void *fh, struct stat *buf) { vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); extfs_stat_move (buf, file->ino); return 0; } /* --------------------------------------------------------------------------------------------- */ static int extfs_readlink (const vfs_path_t * vpath, char *buf, size_t size) { struct extfs_super_t *archive; const char *q; size_t len; struct vfs_s_entry *entry; int result = -1; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) goto cleanup; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry == NULL) goto cleanup; if (!S_ISLNK (entry->ino->st.st_mode)) { const vfs_path_element_t *path_element; path_element = vfs_path_get_by_index (vpath, -1); path_element->class->verrno = EINVAL; goto cleanup; } len = strlen (entry->ino->linkname); if (size < len) len = size; /* readlink() does not append a NUL character to buf */ result = len; memcpy (buf, entry->ino->linkname, result); cleanup: return result; } /* --------------------------------------------------------------------------------------------- */ static int extfs_chown (const vfs_path_t * vpath, uid_t owner, gid_t group) { (void) vpath; (void) owner; (void) group; return 0; } /* --------------------------------------------------------------------------------------------- */ static int extfs_chmod (const vfs_path_t * vpath, mode_t mode) { (void) vpath; (void) mode; return 0; } /* --------------------------------------------------------------------------------------------- */ static ssize_t extfs_write (void *fh, const char *buf, size_t nbyte) { vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); file->changed = TRUE; return write (file->handle, buf, nbyte); } /* --------------------------------------------------------------------------------------------- */ static int extfs_unlink (const vfs_path_t * vpath) { struct extfs_super_t *archive; const char *q; struct vfs_s_entry *entry; int result = -1; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) goto cleanup; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry == NULL) goto cleanup; entry = extfs_resolve_symlinks (entry); if (entry == NULL) goto cleanup; if (S_ISDIR (entry->ino->st.st_mode)) { const vfs_path_element_t *path_element; path_element = vfs_path_get_by_index (vpath, -1); path_element->class->verrno = EISDIR; goto cleanup; } if (extfs_cmd (" rm ", archive, entry, "")) { my_errno = EIO; goto cleanup; } vfs_s_free_entry (VFS_SUPER (archive)->me, entry); result = 0; cleanup: return result; } /* --------------------------------------------------------------------------------------------- */ static int extfs_mkdir (const vfs_path_t * vpath, mode_t mode) { struct extfs_super_t *archive; const char *q; struct vfs_s_entry *entry; int result = -1; const vfs_path_element_t *path_element; (void) mode; path_element = vfs_path_get_by_index (vpath, -1); q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) goto cleanup; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry != NULL) { path_element->class->verrno = EEXIST; goto cleanup; } entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_MKDIR); if (entry == NULL) goto cleanup; entry = extfs_resolve_symlinks (entry); if (entry == NULL) goto cleanup; if (!S_ISDIR (entry->ino->st.st_mode)) { path_element->class->verrno = ENOTDIR; goto cleanup; } if (extfs_cmd (" mkdir ", archive, entry, "")) { my_errno = EIO; vfs_s_free_entry (VFS_SUPER (archive)->me, entry); goto cleanup; } result = 0; cleanup: return result; } /* --------------------------------------------------------------------------------------------- */ static int extfs_rmdir (const vfs_path_t * vpath) { struct extfs_super_t *archive; const char *q; struct vfs_s_entry *entry; int result = -1; q = extfs_get_path (vpath, &archive, FL_NONE); if (q == NULL) goto cleanup; entry = extfs_find_entry (VFS_SUPER (archive)->root, q, FL_NONE); if (entry == NULL) goto cleanup; entry = extfs_resolve_symlinks (entry); if (entry == NULL) goto cleanup; if (!S_ISDIR (entry->ino->st.st_mode)) { const vfs_path_element_t *path_element; path_element = vfs_path_get_by_index (vpath, -1); path_element->class->verrno = ENOTDIR; goto cleanup; } if (extfs_cmd (" rmdir ", archive, entry, "")) { my_errno = EIO; goto cleanup; } vfs_s_free_entry (VFS_SUPER (archive)->me, entry); result = 0; cleanup: return result; } /* --------------------------------------------------------------------------------------------- */ static int extfs_chdir (const vfs_path_t * vpath) { void *data; my_errno = ENOTDIR; data = extfs_opendir (vpath); if (data == NULL) return (-1); extfs_closedir (data); my_errno = 0; return 0; } /* --------------------------------------------------------------------------------------------- */ static off_t extfs_lseek (void *fh, off_t offset, int whence) { vfs_file_handler_t *file = VFS_FILE_HANDLER (fh); return lseek (file->handle, offset, whence); } /* --------------------------------------------------------------------------------------------- */ static vfsid extfs_getid (const vfs_path_t * vpath) { struct extfs_super_t *archive = NULL; const char *p; p = extfs_get_path (vpath, &archive, FL_NO_OPEN); return (p == NULL ? NULL : (vfsid) archive); } /* --------------------------------------------------------------------------------------------- */ static vfs_path_t * extfs_getlocalcopy (const vfs_path_t * vpath) { vfs_file_handler_t *fh; vfs_path_t *p; fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0)); if (fh == NULL) return NULL; if (fh->ino->localname == NULL) { extfs_close ((void *) fh); return NULL; } p = vfs_path_from_str (fh->ino->localname); VFS_FILE_HANDLER_SUPER (fh)->fd_usage++; extfs_close ((void *) fh); return p; } /* --------------------------------------------------------------------------------------------- */ static int extfs_ungetlocalcopy (const vfs_path_t * vpath, const vfs_path_t * local, gboolean has_changed) { vfs_file_handler_t *fh; fh = VFS_FILE_HANDLER (extfs_open (vpath, O_RDONLY, 0)); if (fh == NULL) return 0; if (strcmp (fh->ino->localname, vfs_path_get_last_path_str (local)) == 0) { VFS_FILE_HANDLER_SUPER (fh)->fd_usage--; if (has_changed) fh->changed = TRUE; extfs_close ((void *) fh); return 0; } else { /* Should not happen */ extfs_close ((void *) fh); return 0; } } /* --------------------------------------------------------------------------------------------- */ static gboolean extfs_get_plugins (const char *where, gboolean silent) { char *dirname; GDir *dir; const char *filename; dirname = g_build_path (PATH_SEP_STR, where, MC_EXTFS_DIR, (char *) NULL); dir = g_dir_open (dirname, 0, NULL); /* We may not use vfs_die() message or message or similar, * UI is not initialized at this time and message would not * appear on screen. */ if (dir == NULL) { if (!silent) fprintf (stderr, _("Warning: cannot open %s directory\n"), dirname); g_free (dirname); return FALSE; } if (extfs_plugins == NULL) extfs_plugins = g_array_sized_new (FALSE, TRUE, sizeof (extfs_plugin_info_t), 32); while ((filename = g_dir_read_name (dir)) != NULL) { char fullname[MC_MAXPATHLEN]; struct stat s; g_snprintf (fullname, sizeof (fullname), "%s" PATH_SEP_STR "%s", dirname, filename); if ((stat (fullname, &s) == 0) && S_ISREG (s.st_mode) && !S_ISDIR (s.st_mode) && is_exe (s.st_mode)) { int f; f = open (fullname, O_RDONLY); if (f >= 0) { size_t len, i; extfs_plugin_info_t info; gboolean found = FALSE; close (f); /* Handle those with a trailing '+', those flag that the * file system does not require an archive to work */ len = strlen (filename); info.need_archive = (filename[len - 1] != '+'); info.path = g_strconcat (dirname, PATH_SEP_STR, (char *) NULL); info.prefix = g_strdup (filename); /* prepare to compare file names without trailing '+' */ if (!info.need_archive) info.prefix[len - 1] = '\0'; /* don't overload already found plugin */ for (i = 0; i < extfs_plugins->len; i++) { extfs_plugin_info_t *p; p = &g_array_index (extfs_plugins, extfs_plugin_info_t, i); /* 2 files with same names cannot be in a directory */ if ((strcmp (info.path, p->path) != 0) && (strcmp (info.prefix, p->prefix) == 0)) { found = TRUE; break; } } if (found) { g_free (info.path); g_free (info.prefix); } else { /* restore file name */ if (!info.need_archive) info.prefix[len - 1] = '+'; g_array_append_val (extfs_plugins, info); } } } } g_dir_close (dir); g_free (dirname); return TRUE; } /* --------------------------------------------------------------------------------------------- */ static int extfs_init (struct vfs_class *me) { gboolean d1, d2; (void) me; /* 1st: scan user directory */ d1 = extfs_get_plugins (mc_config_get_data_path (), TRUE); /* silent about user dir */ /* 2nd: scan system dir */ d2 = extfs_get_plugins (LIBEXECDIR, d1); return (d1 || d2 ? 1 : 0); } /* --------------------------------------------------------------------------------------------- */ static void extfs_done (struct vfs_class *me) { size_t i; while (VFS_SUBCLASS (me)->supers != NULL) me->free ((vfsid) VFS_SUBCLASS (me)->supers->data); if (extfs_plugins == NULL) return; for (i = 0; i < extfs_plugins->len; i++) { extfs_plugin_info_t *info; info = &g_array_index (extfs_plugins, extfs_plugin_info_t, i); g_free (info->path); g_free (info->prefix); } g_array_free (extfs_plugins, TRUE); } /* --------------------------------------------------------------------------------------------- */ static int extfs_setctl (const vfs_path_t * vpath, int ctlop, void *arg) { (void) arg; if (ctlop == VFS_SETCTL_RUN) { extfs_run (vpath); return 1; } return 0; } /* --------------------------------------------------------------------------------------------- */ /*** public functions ****************************************************************************/ /* --------------------------------------------------------------------------------------------- */ void vfs_init_extfs (void) { vfs_init_subclass (&extfs_subclass, "extfs", VFSF_UNKNOWN, NULL); vfs_extfs_ops->init = extfs_init; vfs_extfs_ops->done = extfs_done; vfs_extfs_ops->fill_names = extfs_fill_names; vfs_extfs_ops->which = extfs_which; vfs_extfs_ops->open = extfs_open; vfs_extfs_ops->close = extfs_close; vfs_extfs_ops->read = extfs_read; vfs_extfs_ops->write = extfs_write; vfs_extfs_ops->opendir = extfs_opendir; vfs_extfs_ops->readdir = extfs_readdir; vfs_extfs_ops->closedir = extfs_closedir; vfs_extfs_ops->stat = extfs_stat; vfs_extfs_ops->lstat = extfs_lstat; vfs_extfs_ops->fstat = extfs_fstat; vfs_extfs_ops->chmod = extfs_chmod; vfs_extfs_ops->chown = extfs_chown; vfs_extfs_ops->readlink = extfs_readlink; vfs_extfs_ops->unlink = extfs_unlink; vfs_extfs_ops->chdir = extfs_chdir; vfs_extfs_ops->ferrno = extfs_errno; vfs_extfs_ops->lseek = extfs_lseek; vfs_extfs_ops->getid = extfs_getid; vfs_extfs_ops->getlocalcopy = extfs_getlocalcopy; vfs_extfs_ops->ungetlocalcopy = extfs_ungetlocalcopy; vfs_extfs_ops->mkdir = extfs_mkdir; vfs_extfs_ops->rmdir = extfs_rmdir; vfs_extfs_ops->setctl = extfs_setctl; extfs_subclass.free_inode = extfs_free_inode; extfs_subclass.free_archive = extfs_free_archive; vfs_register_class (vfs_extfs_ops); } /* --------------------------------------------------------------------------------------------- */