Logo Search packages:      
Sourcecode: gnome-vfs2 version File versions  Download package

tar-method.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* tar-method.c - The tar method implementation for the GNOME Virtual File
   System.

   Copyright (C) 2002 Ximian, Inc

   The Gnome Library 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.

   The Gnome Library 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 the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors: Rachel Hestilow <hestilow@ximian.com>
          Abigail Brady <morwen@evilmagic.org> (tarpet.h) 
*/

#include <libgnomevfs/gnome-vfs-method.h>
#include <libgnomevfs/gnome-vfs-mime.h>
#include <libgnomevfs/gnome-vfs-mime-utils.h>
#include <libgnomevfs/gnome-vfs-module.h>
#include <libgnomevfs/gnome-vfs-handle.h>
#include <libgnomevfs/gnome-vfs-file-info.h>
#include <libgnomevfs/gnome-vfs.h>
#include <string.h>
#include "tarpet.h"

typedef struct
{
      union TARPET_block* blocks;
      guint num_blocks;
      GNode *info_tree;
      int ref_count;
      gchar *filename;
} TarFile;

typedef struct
{
      TarFile *tar;
      union TARPET_block* start;
      union TARPET_block* current;
      int current_offset;
      int current_index;
      gchar *filename;
      gboolean is_directory;
} FileHandle;

#define TARPET_BLOCKSIZE (sizeof (union TARPET_block))

static GHashTable *tar_cache;
G_LOCK_DEFINE_STATIC (tar_cache);

#define iteration_initialize(condition, i, cond1, cond2) ((i) = (condition) ? (cond1) : (cond2))
#define iteration_check(condition, i, op1, op2, val1, val2) ((condition) ? (i op1 val1) : (i op2 val2))
#define iteration_iterate(condition, i, op1, op2) ((condition) ? (i op1) : (i op2))

#define parse_octal_field(v) (parse_octal ((v), sizeof (v)))
#define IS_OCTAL_DIGIT(c) ((c) >= '0' && (c) <= '8')
#define OCTAL_DIGIT(c) ((c) - '0')

static int parse_octal (const char *str, int len)
{
      int i, ret = 0;
      for (i = 0; i < len; i++)
      {
            if (str[i] == '\0') break;
            else if (!IS_OCTAL_DIGIT (str[i])) return 0;
            ret = ret * 8 + OCTAL_DIGIT (str[i]);
      }
      return ret;
}

static void
split_name_with_level (const gchar *name, gchar **first, gchar **last, int level, gboolean backwards)
{
      int i;
      gchar *found = NULL;
      int num_found = 0;
      if (name[strlen (name) - 1] == '/' && backwards)
            level++;

      for (iteration_initialize (backwards, i, strlen (name) - 1, 0);
           iteration_check (backwards, i, >=, <, 0, strlen (name));
           iteration_iterate (backwards, i, --, ++))
      {
            if (name[i] == '/')
                  num_found++;
            
            if (num_found >= level)
            {
                  found = (gchar*) name + i;
                  break;
            }
      }

      if (found)
      {
            *first = g_strndup (name, found - name + 1);
            if (*(found + 1))
                  *last = g_strdup (found + 1);
            else
                  *last = NULL;
      }
      else
      {
            *first = g_strdup (name);
            *last = NULL;
      }
}

static void
split_name (const gchar *name, gchar **first, gchar **last)
{
      split_name_with_level (name, first, last, 1, TRUE);
}

static GNode*
real_lookup_entry (const GNode *tree, const gchar *name, int level)
{
      GNode *node, *ret = NULL;
      gchar *first, *rest;

      split_name_with_level (name, &first, &rest, level, FALSE);

      for (node = tree->children; node; node = node->next)
      {
            union TARPET_block *b = (union TARPET_block*) node->data;
            if (!strcmp (b->raw.data, first))
            {
                  if (rest)
                        ret = real_lookup_entry (node, name, level + 1);
                  else
                        ret = node;
                  break;
            }
            else if (!strcmp (b->raw.data, name))
            {
                  ret = node;
                  break;
            }
      }
      g_free (first);
      g_free (rest);
      
      
      return ret;
}

static GNode*
tree_lookup_entry (const GNode *tree, const gchar *name)
{
      GNode *ret;
      char *root = g_strdup (name);
      char *txt = root;
      
      if (txt[0] == '/')
            txt++;

      ret = real_lookup_entry (tree, txt, 1);
      if (!ret && txt[strlen (txt) - 1] != '/')
      {
            txt = g_strconcat (txt, "/", NULL);
            g_free (root);
            root = txt;
            ret = real_lookup_entry (tree, txt, 1);
      }
      g_free (root);

      if (ret && ret != tree->children)
      {
            union TARPET_block *b = ret->data;
            b--;
            if (b->p.typeflag == TARPET_TYPE_LONGFILEN)
                  ret = ret->next;
      }

      return ret;
}

static TarFile* read_tar_file (GnomeVFSHandle *handle)
{
      GArray *arr = g_array_new (TRUE, TRUE, sizeof (union TARPET_block));
      GnomeVFSResult res;
      TarFile* ret;
      GnomeVFSFileSize bytes_read;
      int i;
      
      do
      {
            union TARPET_block b;
            res = gnome_vfs_read (handle, b.raw.data,
                              TARPET_BLOCKSIZE, &bytes_read);
            if (res == GNOME_VFS_OK)
                  g_array_append_val (arr, b);
      } while (res == GNOME_VFS_OK && bytes_read > 0);

      ret = g_new0 (TarFile, 1);
      ret->blocks = (union TARPET_block*) arr->data;
      ret->num_blocks = arr->len;
      ret->info_tree = g_node_new (NULL); 

      for (i = 0; i < ret->num_blocks;)
      {
            gchar *dir;
            gchar *rest;
            GNode *node;
            int size = 0, maxsize;
            int orig;

            if (!(*ret->blocks[i].p.name))
            {
                  i++;
                  continue;
            }

            if (ret->blocks[i].p.typeflag == TARPET_TYPE_LONGFILEN)
            {
                  i++;
                  continue;
            }
            
            split_name (ret->blocks[i].p.name, &dir, &rest);
            node = tree_lookup_entry (ret->info_tree, dir);
            
            if (!node)
            {
                  node = ret->info_tree;
            }
            g_node_append (node, g_node_new (&(ret->blocks[i])));
            
            g_free (dir);
            g_free (rest);
      
            maxsize = parse_octal_field (ret->blocks[i].p.size);
            if (maxsize)
            {
                  for (orig = i; i < ret->num_blocks && size < maxsize; i++)
                  {
                        int wsize = TARPET_BLOCKSIZE;
                        if ((maxsize - size) < TARPET_BLOCKSIZE)
                              wsize = maxsize - size;
                        size += wsize;
                  }
                  i++;
            }
            else
            {
                  i++;
            }
      }
      
      g_array_free (arr, FALSE);

      return ret;
}

static TarFile* 
ensure_tarfile (GnomeVFSURI *uri)
{
      TarFile *tar;
      GnomeVFSHandle *handle;
      gchar *parent_string;

      parent_string = gnome_vfs_uri_to_string (uri->parent, GNOME_VFS_URI_HIDE_NONE);
      G_LOCK (tar_cache);
      tar = g_hash_table_lookup (tar_cache, parent_string);
      if (!tar)
      {
            if (gnome_vfs_open_uri (&handle, uri->parent, GNOME_VFS_OPEN_READ) != GNOME_VFS_OK)
                  return NULL;
            tar = read_tar_file (handle);
            tar->filename = parent_string;
            gnome_vfs_close (handle);
            g_hash_table_insert (tar_cache, parent_string, tar);
      }
      G_UNLOCK (tar_cache);

      tar->ref_count++;
      return tar;
}

static void
tar_file_unref (TarFile *tar)
{
      tar->ref_count--;
      if (tar->ref_count < 0)
      {
            G_LOCK (tar_cache);
            g_hash_table_remove (tar_cache, tar->filename);
            G_UNLOCK (tar_cache);
            g_free (tar->blocks);
            g_node_destroy (tar->info_tree);
            g_free (tar->filename);
            g_free (tar);
      }
}

static GnomeVFSResult
do_open (GnomeVFSMethod *method,
       GnomeVFSMethodHandle **method_handle,
       GnomeVFSURI *uri,
       GnomeVFSOpenMode mode,
       GnomeVFSContext *context)
{     
      TarFile *tar;
      FileHandle *new_handle;
      GNode *node;
      union TARPET_block *start;
      int i;
            
      if (!uri->parent)
            return GNOME_VFS_ERROR_INVALID_URI;
      
      tar = ensure_tarfile (uri);
      if (!tar)
            return GNOME_VFS_ERROR_BAD_FILE;
      node = tree_lookup_entry (tar->info_tree, uri->text);
      if (!node)
      {
            tar_file_unref (tar);
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      start = node->data;

      if (start->p.name[strlen (start->p.name) - 1] == '/')
            return GNOME_VFS_ERROR_IS_DIRECTORY;
      new_handle = g_new0 (FileHandle, 1);
      new_handle->tar = tar;
      new_handle->filename = g_strdup (uri->text);
      new_handle->start = start;
      new_handle->current = new_handle->start;
      new_handle->current_offset = 0;
      for (i = 0; i < tar->num_blocks; i++)
            if (start == &(tar->blocks[i]))
                  break;
      new_handle->current_index = i;
      new_handle->is_directory = FALSE;
      
      *method_handle = (GnomeVFSMethodHandle*) new_handle;
      
      return GNOME_VFS_OK;
}

static void
file_handle_unref (FileHandle *handle)
{
      tar_file_unref (handle->tar);
      g_free (handle->filename);
      g_free (handle);
}

static GnomeVFSResult
do_close (GnomeVFSMethod *method,
        GnomeVFSMethodHandle *method_handle,
        GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;

      file_handle_unref (handle);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_read (GnomeVFSMethod *method,
       GnomeVFSMethodHandle *method_handle,
       gpointer buffer,
       GnomeVFSFileSize num_bytes,
       GnomeVFSFileSize *bytes_read,
       GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;
      int i, size = 0, maxsize;
      
      if (handle->is_directory)
            return GNOME_VFS_ERROR_IS_DIRECTORY;

      maxsize = parse_octal_field (handle->start->p.size);
      if (handle->current == handle->start)
      {
            handle->current_index++;
            handle->current_offset = TARPET_BLOCKSIZE;
      }

      for (i = handle->current_index; i < handle->tar->num_blocks && handle->current_offset < (maxsize + TARPET_BLOCKSIZE) && size < num_bytes; i++)
      {
            int wsize = TARPET_BLOCKSIZE;
            gpointer target_buf = (gchar*)buffer + size;
            if ((maxsize - (handle->current_offset - TARPET_BLOCKSIZE)) < TARPET_BLOCKSIZE
               && (maxsize - (handle->current_offset - TARPET_BLOCKSIZE)) > 0)
                  wsize = maxsize - handle->current_offset + TARPET_BLOCKSIZE;
            else if (num_bytes < (size + wsize))
                  wsize = num_bytes - size;
            else
                  handle->current_index = i + 1;
            
            memcpy (target_buf, handle->start->raw.data + handle->current_offset, wsize);
            
            size += wsize;
            handle->current_offset += wsize;
      }
      if (handle->current_index < handle->tar->num_blocks)
            handle->current = &handle->tar->blocks[handle->current_index]; 
      else
            handle->current = NULL;
      
      *bytes_read = size;
      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_seek (GnomeVFSMethod *method,
       GnomeVFSMethodHandle *method_handle,
       GnomeVFSSeekPosition whence,
       GnomeVFSFileOffset offset,
       GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;
      GnomeVFSFileOffset current_offset;

      switch (whence)
      {
      case GNOME_VFS_SEEK_START:
            current_offset = 0;
            break;
      case GNOME_VFS_SEEK_CURRENT:
            current_offset = handle->current_offset;
            break;
      case GNOME_VFS_SEEK_END:
            current_offset = parse_octal_field (handle->start->p.size);
            break;
      default:
            current_offset = handle->current_offset;
            break;
      }

      handle->current_offset = current_offset + offset;
      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_tell (GnomeVFSMethod *method,
       GnomeVFSMethodHandle *method_handle,
       GnomeVFSFileSize *offset_return)
{
      FileHandle *handle = (FileHandle*) method_handle;
      *offset_return = handle->current_offset;
      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_open_directory (GnomeVFSMethod *method,
               GnomeVFSMethodHandle **method_handle,
               GnomeVFSURI *uri,
               GnomeVFSFileInfoOptions options,
               GnomeVFSContext *context)
{
      TarFile *tar;
      FileHandle *new_handle;
      union TARPET_block *start, *current;
      GNode *node;
      int i;

      if (!uri->parent)
            return GNOME_VFS_ERROR_INVALID_URI;
      tar = ensure_tarfile (uri);
      if (uri->text)
      {
            node = tree_lookup_entry (tar->info_tree, uri->text);
            if (!node)
            {
                  tar_file_unref (tar);
                  return GNOME_VFS_ERROR_NOT_FOUND;
            }
            start = node->data;
            
            if (start->p.name[strlen (start->p.name) - 1] != '/')
                  return GNOME_VFS_ERROR_NOT_A_DIRECTORY;

            if (node->children)
                  current = node->children->data; 
            else
                  current = NULL;
      }
      else
      {
            node = tar->info_tree;
            if (!node)
            {
                  tar_file_unref (tar);
                  return GNOME_VFS_ERROR_NOT_FOUND;
            }

            if (node->children)
                  start = node->children->data;
            else
                  start = NULL;
            current = start;
      }
      
      new_handle = g_new0 (FileHandle, 1);
      new_handle->tar = tar;
      new_handle->filename = g_strdup (tar->filename);
      new_handle->start = start;
      new_handle->current = current;
      for (i = 0; i < tar->num_blocks; i++)
            if (start == &(tar->blocks[i]))
                  break;
      new_handle->current_index = i;
      new_handle->is_directory = TRUE;

      *method_handle = (GnomeVFSMethodHandle*) new_handle;
      
      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_close_directory (GnomeVFSMethod *method,
                GnomeVFSMethodHandle *method_handle,
                GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;

      file_handle_unref (handle);

      return GNOME_VFS_OK;
}

static GnomeVFSResult 
do_get_file_info (GnomeVFSMethod *method,
              GnomeVFSURI *uri,
              GnomeVFSFileInfo *file_info,
              GnomeVFSFileInfoOptions options,
              GnomeVFSContext *context)
{
      TarFile *tar;
      GNode *node;
      union TARPET_block *current;
      gchar *name;
      gchar *path;
      char *mime_type;
      int i;

      if (!uri->parent)
            return GNOME_VFS_ERROR_INVALID_URI;

      tar = ensure_tarfile (uri);
      
      if (uri->text)
            node = tree_lookup_entry (tar->info_tree, uri->text);
      else
            node = tar->info_tree->children;
      
      if (!node)
      {
            tar_file_unref (tar);
            return GNOME_VFS_ERROR_NOT_FOUND;
      }

      current = node->data;
      for (i = 0; i < tar->num_blocks; i++)
            if (&(tar->blocks[i]) == current)
                  break;
      if (i && tar->blocks[i - 2].p.typeflag == TARPET_TYPE_LONGFILEN)
            name = g_strdup (tar->blocks[i - 1].raw.data);
      else
            name = g_strdup (current->p.name);

      file_info->name = g_path_get_basename (name);
      if (name[strlen (name) - 1] == '/')
            file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
      else if (current->p.typeflag == TARPET_TYPE_SYMLINK)
      {
            file_info->type = GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK;
            file_info->symlink_name = g_strdup (current->p.linkname);
      }
      else
            file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;

      file_info->permissions = parse_octal_field (current->p.mode);
      file_info->uid = parse_octal_field (current->p.uid);
      file_info->gid = parse_octal_field (current->p.gid);
      file_info->size = parse_octal_field (current->p.size);
      file_info->mtime = parse_octal_field (current->p.mtime);
      file_info->atime = parse_octal_field (current->gnu.atime);
      file_info->ctime = parse_octal_field (current->gnu.ctime);

      if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
            mime_type = "x-directory/normal";
      else if (!(options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && file_info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK)
            mime_type = "x-special/symlink";
      else if (!file_info->size || (options & GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE))
      {
            path = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
            mime_type = (char*) gnome_vfs_get_file_mime_type (path, NULL, TRUE);
            g_free (path);
      }
      else
      {
            mime_type = (char*) gnome_vfs_get_mime_type_for_data ((current + 1)->raw.data, MIN (TARPET_BLOCKSIZE, file_info->size));
            if (!mime_type)
            {
                  path = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
                  mime_type = (char*) gnome_vfs_get_file_mime_type (path, NULL, TRUE);
                  g_free (path);
            }
      }

      file_info->mime_type = g_strdup (mime_type);

      file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
                          GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS |
                          GNOME_VFS_FILE_INFO_FIELDS_SIZE |
                          GNOME_VFS_FILE_INFO_FIELDS_ATIME |
                          GNOME_VFS_FILE_INFO_FIELDS_MTIME |
                          GNOME_VFS_FILE_INFO_FIELDS_CTIME |
                          GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE |
                          GNOME_VFS_FILE_INFO_FIELDS_IDS;

      g_free (name);
      tar_file_unref (tar);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_read_directory (GnomeVFSMethod *method,
               GnomeVFSMethodHandle *method_handle,
               GnomeVFSFileInfo *file_info,
               GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;
      GnomeVFSURI *uri;
      gchar *str;
      GNode *node;
      
      if (!handle->current)
            return GNOME_VFS_ERROR_EOF;

      str = g_strconcat (handle->filename, "#tar:", handle->current->p.name, NULL);
      uri = gnome_vfs_uri_new (str);
      do_get_file_info (method, uri, file_info, 0, context);
      node = tree_lookup_entry (handle->tar->info_tree, uri->text);
      if (!node)
      {
            gnome_vfs_uri_unref (uri);
            return GNOME_VFS_ERROR_NOT_FOUND;
      }
      
      if (node->next)
            handle->current = node->next->data;
      else
            handle->current = NULL;
      gnome_vfs_uri_unref (uri);

      return GNOME_VFS_OK;
}

static GnomeVFSResult
do_get_file_info_from_handle (GnomeVFSMethod *method,
                        GnomeVFSMethodHandle *method_handle,
                        GnomeVFSFileInfo *file_info,
                        GnomeVFSFileInfoOptions options,
                        GnomeVFSContext *context)
{
      FileHandle *handle = (FileHandle*) method_handle;
      GnomeVFSURI *uri;
      
      uri = gnome_vfs_uri_new (handle->start->p.name);
      do_get_file_info (method, uri, file_info, options, context);
      gnome_vfs_uri_unref (uri);

      return GNOME_VFS_OK;
}

static gboolean
do_is_local (GnomeVFSMethod *method,
           const GnomeVFSURI *uri)
{
      return gnome_vfs_uri_is_local (uri->parent);
}

static GnomeVFSMethod method =
{
      sizeof (GnomeVFSMethod),
      do_open,
      NULL,
      do_close,
      do_read,
      NULL,
      do_seek,
      do_tell,
      NULL,
      do_open_directory,
      do_close_directory,
      do_read_directory,
      do_get_file_info,
      do_get_file_info_from_handle,
      do_is_local,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL,
      NULL
};

GnomeVFSMethod *
vfs_module_init (const char *method_name, const char *args)
{
      G_LOCK (tar_cache);
      tar_cache = g_hash_table_new (g_str_hash, g_str_equal);
      G_UNLOCK (tar_cache);
      return &method;
}

void
vfs_module_shutdown (GnomeVFSMethod *method)
{
      G_LOCK (tar_cache);
      g_hash_table_destroy (tar_cache);
      G_UNLOCK (tar_cache);
}

Generated by  Doxygen 1.6.0   Back to index