I got tired of waiting when browsing kernel patch files
using the patchfs in mc, so wrote this patch. It adds
a new vfs called difffs (with prefix udiff), replacing
the patchfs bash script in extfs. The main (and only)
difference is that read a file in the diff takes O(1)
worst case, compared to O(n) worst case (n = size of
the whole diff file) in patchfs. (The kernel 2.5.5
5MB patch takes 3-4 seconds to load, and waiting that
long every time you want to view a single file inside
it is unacceptable.)

This patch is against the 2002-02-22-06 snapshot.

This patch does not replace patchfs with udiff
in the default configuration files.

Much of this patch was copied from tar.c, so I consider
the copyright owner to be same as for that file.

Let me know what you decide to do with this patch.

Oskar Liljeblad ([EMAIL PROTECTED])
diff -ruN mc-4.5.99a/vfs/Makefile.am mc-4.5.99a-oskar/vfs/Makefile.am
--- mc-4.5.99a/vfs/Makefile.am  Wed Feb 20 07:15:00 2002
+++ mc-4.5.99a-oskar/vfs/Makefile.am    Fri Feb 22 23:04:46 2002
@@ -13,6 +13,7 @@
        local.c                 \
        names.c                 \
        tar.c                   \
+       diff.c                  \
        sfs.c                   \
        vfs.c
 
diff -ruN mc-4.5.99a/vfs/diff.c mc-4.5.99a-oskar/vfs/diff.c
--- mc-4.5.99a/vfs/diff.c       Thu Jan  1 01:00:00 1970
+++ mc-4.5.99a-oskar/vfs/diff.c Fri Feb 22 23:06:27 2002
@@ -0,0 +1,438 @@
+/* Virtual File System: Diff file system.
+   Copyright (C) 1995 The Free Software Foundation
+   
+   Written by: 2002 Oskar Liljeblad
+
+   This program is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License
+   as published by the Free Software Foundation; either version 2 of
+   the License, or (at your option) any later version.
+
+   This program 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 Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this program; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
+
+/* Namespace: vfs_difffs_ops */
+
+#include <config.h>
+#include <errno.h>
+#include "utilvfs.h"           /* for message_2s, message_1s */
+#include "xdirentry.h"
+#include "../src/dialog.h"     /* for MSG_ERROR */
+#include <glib.h>
+
+#define MIN_CHUNK 64
+#define READBUFSIZE    32768
+
+struct readbuf {
+       int fd;
+       char *buf;
+       size_t pos;
+       size_t len;
+};
+
+static void *diff_super_check(vfs *me, char *archive_name, char *op)
+{
+       static struct stat stat_buf;
+       if (mc_stat(archive_name, &stat_buf))
+               return NULL;
+       return &stat_buf;
+}
+
+static int diff_super_same(vfs *me, struct vfs_s_super *parc, char *archive_name, 
+char *op, void *cookie)
+{      
+       struct stat *archive_stat = cookie;     /* stat of main archive */
+
+       if (strcmp(parc->name, archive_name) != 0)
+               return 0;
+
+       if (vfs_uid && (!(archive_stat->st_mode & 0004))
+                       && ((archive_stat->st_gid != vfs_gid) || 
+!(archive_stat->st_mode & 0040))
+                       && ((archive_stat->st_uid != vfs_uid) || 
+!(archive_stat->st_mode & 0400)))
+               return 0;
+
+       /* Has the cached archive been changed on the disk? */
+       if (parc->u.tar.tarstat.st_mtime < archive_stat->st_mtime) { /* Yes, reload! */
+               (*vfs_tarfs_ops.free)((vfsid) parc);
+               vfs_rmstamp (&vfs_tarfs_ops, (vfsid) parc, 0);
+               return 2;
+       }
+
+       /* Hasn't been modified, give it a new timeout */
+       vfs_stamp (&vfs_tarfs_ops, (vfsid) parc);
+       return 1;
+}
+
+static int diff_fh_open(vfs *me, vfs_s_fh *fh, int flags, int mode)
+{
+       if ((flags & O_ACCMODE) != O_RDONLY)
+               ERRNOR (EROFS, -1);
+       return 0;
+}
+
+static void diff_free_archive(vfs *me, vfs_s_super *archive)
+{
+       if (archive->u.tar.fd != -1)
+               mc_close(archive->u.tar.fd);
+       if (archive->u.diff.list_fd != -1)
+               close(archive->u.diff.list_fd);
+       if (archive->u.diff.list_name != NULL) {
+               unlink(archive->u.diff.list_name); /* ignore error? */
+               g_free(archive->u.diff.list_name);
+       }
+}
+
+static int diff_read(void *fh, char *buffer, int count)
+{
+       off_t begin = FH->ino->u.tar.data_offset;
+       int fd = FH_SUPER->u.tar.fd;
+       vfs *me = FH_SUPER->me;
+
+       if (begin == -2) {
+               if (lseek(FH_SUPER->u.diff.list_fd, FH->pos, SEEK_SET) < 0)
+                       ERRNOR(errno, -1);
+               if ((count = read(FH_SUPER->u.diff.list_fd, buffer, count)) < 0)
+                       ERRNOR(errno, -1);
+               FH->pos += count;
+               return count;
+       }
+
+       if (mc_lseek(fd, begin + FH->pos, SEEK_SET) != begin + FH->pos)
+               ERRNOR(EIO, -1);
+
+       count = MIN(count, FH->ino->st.st_size - FH->pos);
+
+       if ((count = mc_read(fd, buffer, count)) == -1)
+               ERRNOR(errno, -1);
+
+       FH->pos += count;
+       return count;
+}
+
+static int diff_ungetlocalcopy (vfs *me, char *path, char *local, int has_changed)
+{
+       /* We do just nothing. (We are read only and do not need to free local,
+        * since it will be freed when diff archive will be freed
+        * We have to report error if file has changed.
+        */
+       ERRNOR(EROFS, -has_changed);
+}
+
+static void rb_free(struct readbuf *rb)
+{
+       g_free(rb->buf);
+}
+
+static void rb_init(struct readbuf *rb, int fd)
+{
+       rb->fd = fd;
+       rb->len = 0;
+       rb->pos = 0;
+       rb->buf = g_malloc(READBUFSIZE);
+}
+
+static ssize_t rb_readchar(struct readbuf *rb, char *buf)
+{
+       if (rb->pos >= rb->len) {
+               rb->len = 0;
+               do {
+                       int rc = mc_read(rb->fd, rb->buf+rb->len, READBUFSIZE-rb->len);
+                       if (rc < 0)
+                               return rc;
+                       if (rc == 0) {
+                               if (rb->len == 0)
+                                       return 0;
+                               break;
+                       }
+                       rb->len += rc;
+               } while (rb->len < READBUFSIZE);
+               rb->pos = 0;
+       }
+       *buf = rb->buf[rb->pos++];
+       return 1;
+}
+
+static ssize_t rb_getdelim(struct readbuf *rb, char **lineptr, size_t *n, int delim)
+{
+       int nchars_avail;
+       char *read_pos;
+
+       if (!*lineptr) {
+               *n = MIN_CHUNK;
+               *lineptr = g_malloc(*n);
+       }
+       nchars_avail = *n;
+       read_pos = *lineptr;
+
+       for (;;) {
+               char c;
+               int rc;
+
+               rc = rb_readchar(rb, &c);
+
+               if (nchars_avail < 2) {
+                       if (*n > MIN_CHUNK)
+                               *n *= 2;
+                       else
+                               *n += MIN_CHUNK;
+                       nchars_avail = *n + *lineptr - read_pos;
+                       *lineptr = g_realloc (*lineptr, *n);
+                       read_pos = *n - nchars_avail + *lineptr;
+               }
+
+               if (rc < 0)
+                       return -1;
+               if (rc == 0) {
+                       if (read_pos == *lineptr)
+                               return 0;
+                       break;
+               }
+
+               *read_pos++ = c;
+               nchars_avail--;
+
+               if (c == delim)
+                       break;
+       }
+
+       *read_pos = 0;
+       return read_pos - (*lineptr);
+}
+
+static int diff_open_archive(vfs *me, vfs_s_super *archive, char *name, char *op)
+{
+       int diff_fd, list_fd, type, result;
+       long size, list_size = 0;
+       long offset;
+       mode_t mode;
+       struct vfs_s_inode *root;
+       struct stat std_st;
+       struct readbuf rb;
+       char *linebuf = NULL;
+       size_t linesize = 0;
+       ssize_t start_offset = -1;
+       struct vfs_s_inode *parent = NULL;
+       char *filename = NULL;
+       struct vfs_s_entry *entry;
+       struct vfs_s_inode *inode;
+
+       diff_fd = mc_open(name, O_RDONLY);
+       if (diff_fd == -1) {
+               message_3s(1, MSG_ERROR, _("Cannot open \"%s\"\n%s"), name, 
+g_strerror(mc_errno));
+               ERRNOR(ENOENT, -1);
+       }
+
+       archive->name = g_strdup(name);
+       mc_stat(name, &(archive->u.tar.tarstat));
+       archive->u.tar.fd = -1;
+
+       /* Find out the method to handle this diff file */
+       size = is_gunzipable(diff_fd, &type);
+       mc_lseek(diff_fd, 0, SEEK_SET);
+       if (size > 0) {
+               char *s;
+               mc_close(diff_fd);
+               s = g_strconcat(archive->name, decompress_extension(type), NULL );
+               diff_fd = mc_open(s, O_RDONLY);
+               if (diff_fd == -1)
+                       message_3s(1, MSG_ERROR, _("Cannot open \"%s\"\n%s"), s, 
+g_strerror(mc_errno));
+               g_free(s);
+               if (diff_fd == -1)
+                       ERRNOR(ENOENT, -1);
+       }
+
+       archive->u.tar.fd = diff_fd;
+       mode = archive->u.tar.tarstat.st_mode & 07777;
+       if (mode & 0400) mode |= 0100;
+       if (mode & 0040) mode |= 0010;
+       if (mode & 0004) mode |= 0001;
+       mode |= S_IFDIR;
+
+       root = vfs_s_new_inode (me, archive, &archive->u.tar.tarstat);
+       root->st.st_mode = mode;
+       root->u.tar.data_offset = -1;
+       root->st.st_nlink++;
+       root->st.st_dev = MEDATA->rdev++;
+       vfs_s_add_dots(me, root, NULL);
+       archive->root = root;
+
+       std_st.st_mode = 0444 | S_IFREG;
+       std_st.st_uid = 0;
+       std_st.st_gid = 0;
+       std_st.st_rdev = 0;
+       std_st.st_ctime = time(NULL); /* ignore errors */
+       std_st.st_atime = std_st.st_ctime;
+       std_st.st_mtime = std_st.st_ctime;
+
+       /* Create temporary file for FILELIST */
+       list_fd = mc_mkstemps(&archive->u.diff.list_name, "sfs", NULL);
+       if (list_fd == -1) {
+               message_1s(1, MSG_ERROR, _("Cannot create temporary file"));
+               return -1;      /* handle better? */
+       }
+       archive->u.diff.list_fd = list_fd;
+
+       rb_init(&rb, diff_fd);
+       offset = 0;
+       result = -1;
+
+       for (;;) {
+               ssize_t linelen;
+
+               linelen = rb_getdelim(&rb, &linebuf, &linesize, '\n');
+               if (linelen < 0) {
+                       me->verrno = errno;
+                       break;
+               }
+               offset += linelen;
+
+               if (linelen == 0 || strncmp(linebuf, "diff ", 5) == 0) {
+                       char *q;
+                       int c;
+
+                       if (start_offset != -1) {
+                               std_st.st_size = offset - start_offset - linelen;
+
+                               inode = vfs_s_new_inode(me, archive, &std_st);
+                               inode->u.tar.data_offset = start_offset;
+
+                               entry = vfs_s_new_entry(me, filename, inode);
+                               vfs_s_insert_entry(me, parent, entry);
+                               g_free(filename);
+                               filename = NULL;
+                       }
+                       if (linelen == 0) {
+                               result = 0;
+                               break;
+                       }
+
+                       start_offset = offset - linelen;
+
+                       for (c = linelen-1; isspace(linebuf[c]); c--);
+                       linebuf[c+1] = 0;
+
+                       q = strrchr(linebuf, ' ');
+                       if (q == NULL) {
+                               message_1s(1, MSG_ERROR, _("Inconsistent diff file"));
+                               break;
+                       }
+
+                       write(list_fd, q+1, linebuf + c - q);
+                       write(list_fd, "\n", 1);
+                       list_size += linebuf + c - q + 1;
+
+                       filename = strrchr(q+1, '/');
+                       if (filename == NULL) {
+                               filename = q+1;
+                               q = linebuf+linelen;    /* "" */
+                       } else {
+                               *(filename++) = 0;
+                               q = q+1;
+                       }
+
+                       filename = g_strdup(filename);
+
+                       parent = vfs_s_find_inode(me, archive->root, q, 
+LINK_NO_FOLLOW, FL_MKDIR);
+                       if (parent == NULL) {
+                               message_1s(1, MSG_ERROR, _("Inconsistent diff file"));
+                               break;
+                       }
+               }
+       }
+
+       std_st.st_size = list_size;
+       inode = vfs_s_new_inode(me, archive, &std_st);
+       /* -2 is the magic code that identifies this special file */
+       inode->u.tar.data_offset = -2;
+       entry = vfs_s_new_entry(me, "FILELIST", inode);
+       vfs_s_insert_entry(me, root, entry);
+
+       rb_free(&rb);
+       g_free(filename);
+       g_free(linebuf);
+       return result;
+}
+
+static struct vfs_s_data diff_data = {
+       NULL,                                   /* supers */
+       0,                                              /* inode_counter */
+       0,                                              /* rdev */
+       NULL,                                   /* logfile */
+       
+       NULL,                                   /* init_inode */
+       NULL,                                   /* free_inode */
+       NULL,                                   /* init_entry */
+
+       diff_super_check,               /* archive_check */
+       diff_super_same,                /* archive_same */
+       diff_open_archive,
+       diff_free_archive,
+       
+       diff_fh_open,
+       NULL,                                   /* fh_close */
+
+       vfs_s_find_entry_tree,
+       NULL,                                   /* dir_load */
+       NULL                                    /* file_store */
+};
+
+vfs vfs_difffs_ops = {
+       NULL,                                   /* next */
+       "difffs",
+       0,                                              /* flags */
+       "udiff",
+       &diff_data,
+       0,                                              /* errno */
+       NULL,                                   /* init */
+       NULL,                                   /* done */
+       vfs_s_fill_names,
+       NULL,                                   /* which */
+
+       vfs_s_open,
+       vfs_s_close,
+       diff_read,
+       NULL,                                   /* write */
+
+       vfs_s_opendir,
+       vfs_s_readdir,
+       vfs_s_closedir,
+       vfs_s_telldir,
+       vfs_s_seekdir,
+
+       vfs_s_stat,
+       vfs_s_lstat,
+       vfs_s_fstat,
+
+       NULL,                                   /* chmod */
+       NULL,                                   /* chown */
+       NULL,                                   /* utime */
+
+       vfs_s_readlink,
+       NULL,                                   /* symlink */
+       NULL,                                   /* link */
+       NULL,                                   /* unlink */
+       NULL,                                   /* rename */
+       vfs_s_chdir,
+       vfs_s_ferrno,
+       vfs_s_lseek,
+       NULL,                                   /* mknod */
+
+       vfs_s_getid,
+       vfs_s_nothingisopen,
+       vfs_s_free,
+
+       vfs_s_getlocalcopy,
+       diff_ungetlocalcopy,
+
+    NULL,                                      /* mkdir */
+    NULL,                                      /* rmdir */
+    NULL,                                      /* ctl */
+    NULL                                       /* setctl */
+
+MMAPNULL
+};
diff -ruN mc-4.5.99a/vfs/vfs.h mc-4.5.99a-oskar/vfs/vfs.h
--- mc-4.5.99a/vfs/vfs.h        Sun Feb 10 03:26:19 2002
+++ mc-4.5.99a-oskar/vfs/vfs.h  Fri Feb 22 23:12:07 2002
@@ -108,6 +108,7 @@
     extern vfs vfs_nil_ops;
     extern vfs vfs_tarfs_ops;
     extern vfs vfs_cpiofs_ops;
+    extern vfs vfs_difffs_ops;
 
     extern vfs vfs_ftpfs_ops;
     extern vfs vfs_smbfs_ops;
diff -ruN mc-4.5.99a/vfs/xdirentry.h mc-4.5.99a-oskar/vfs/xdirentry.h
--- mc-4.5.99a/vfs/xdirentry.h  Fri Aug  3 12:15:17 2001
+++ mc-4.5.99a-oskar/vfs/xdirentry.h    Fri Feb 22 23:12:47 2002
@@ -88,6 +88,12 @@
            struct stat tarstat;
        } tar;
        struct {
+           int fd;
+           struct stat tarstat;
+           int list_fd;
+           char *list_name;
+       } diff;
+       struct {
            int sockr, sockw;
            char *home, *cwdir;
            char *host, *user;
diff -ruN mc-4.5.99a/vfs/vfs.c mc-4.5.99a-oskar/vfs/vfs.c
--- mc-4.5.99a/vfs/vfs.c        Sun Feb 10 03:26:19 2002
+++ mc-4.5.99a-oskar/vfs/vfs.c  Fri Feb 22 23:19:20 2002
@@ -1275,6 +1275,7 @@
     vfs_register (&vfs_sfs_ops);
     vfs_register (&vfs_tarfs_ops);
     vfs_register (&vfs_cpiofs_ops);
+    vfs_register (&vfs_difffs_ops);
 
 #ifdef USE_EXT2FSLIB
     vfs_register (&vfs_undelfs_ops);

Reply via email to