diff options
author | Alexander Larsson <alexl@redhat.com> | 2008-03-12 16:19:18 +0000 |
---|---|---|
committer | Alexander Larsson <alexl@src.gnome.org> | 2008-03-12 16:19:18 +0000 |
commit | 24672e3dd455c3ad8c603fb935408add1578a8d1 (patch) | |
tree | 846b3ccf831cf37a455290d6fa16489297a7000a /daemon/gvfsbackendarchive.c | |
parent | 14200388351c1bdbd45a70d814b76f108b033c8c (diff) | |
download | gvfs-24672e3dd455c3ad8c603fb935408add1578a8d1.tar.gz |
Added archive backend from Benjamin Otte Requires libarchive
2008-03-12 Alexander Larsson <alexl@redhat.com>
* configure.ac:
* daemon/Makefile.am:
* daemon/gvfsbackendarchive.[ch]:
Added archive backend from Benjamin Otte
Requires libarchive
svn path=/trunk/; revision=1648
Diffstat (limited to 'daemon/gvfsbackendarchive.c')
-rw-r--r-- | daemon/gvfsbackendarchive.c | 712 |
1 files changed, 712 insertions, 0 deletions
diff --git a/daemon/gvfsbackendarchive.c b/daemon/gvfsbackendarchive.c new file mode 100644 index 00000000..f89928ee --- /dev/null +++ b/daemon/gvfsbackendarchive.c @@ -0,0 +1,712 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2008 Benjamin Otte <otte@gnome.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Benjmain Otte <otte@gnome.org> + */ + + +#include <config.h> + +#include <glib/gi18n.h> +#include <archive.h> +#include <archive_entry.h> + +#include "gvfsbackendarchive.h" +#include "gvfsjobopenforread.h" +#include "gvfsjobread.h" +#include "gvfsjobseekread.h" +#include "gvfsjobopenforwrite.h" +#include "gvfsjobwrite.h" +#include "gvfsjobseekwrite.h" +#include "gvfsjobsetdisplayname.h" +#include "gvfsjobqueryinfo.h" +#include "gvfsjobqueryfsinfo.h" +#include "gvfsjobqueryattributes.h" +#include "gvfsjobenumerate.h" +#include "gvfsdaemonprotocol.h" +#include "gvfsdaemonutils.h" +#include "gvfskeyring.h" + +/* #define PRINT_DEBUG */ + +#ifdef PRINT_DEBUG +#define DEBUG g_print +#else +#define DEBUG(...) +#endif + +/*** TYPE DEFINITIONS ***/ + +typedef struct _ArchiveFile ArchiveFile; +struct _ArchiveFile { + char * name; /* name of the file inside the archive */ + GFileInfo * info; /* file info created from archive_entry */ + GSList * children; /* (unordered) list of child files */ +}; + +struct _GVfsBackendArchive +{ + GVfsBackend backend; + + GFile * file; + ArchiveFile * files; /* the tree of files */ +}; + +G_DEFINE_TYPE (GVfsBackendArchive, g_vfs_backend_archive, G_VFS_TYPE_BACKEND) + +/*** AN ARCHIVE WE CAN OPERATE ON ***/ + +typedef struct { + struct archive * archive; + GFile * file; + GFileInputStream *stream; + GVfsJob * job; + GError * error; + guchar data[4096]; +} GVfsArchive; + +#define gvfs_archive_return(d) ((d)->error ? ARCHIVE_FATAL : ARCHIVE_OK) + +static int +gvfs_archive_open (struct archive *archive, + void *data) +{ + GVfsArchive *d = data; + + DEBUG ("OPEN\n"); + g_assert (d->stream == NULL); + d->stream = g_file_read (d->file, + d->job->cancellable, + &d->error); + return gvfs_archive_return (d); +} + +static ssize_t +gvfs_archive_read (struct archive *archive, + void *data, + const void **buffer) +{ + GVfsArchive *d = data; + gssize read_bytes; + + *buffer = d->data; + read_bytes = g_input_stream_read (G_INPUT_STREAM (d->stream), + d->data, + sizeof (d->data), + d->job->cancellable, + &d->error); + + DEBUG ("READ %d\n", (int) read_bytes); + return read_bytes; +} + +static off_t +gvfs_archive_skip (struct archive *archive, + void *data, + off_t request) +{ + GVfsArchive *d = data; + + if (g_seekable_can_seek (G_SEEKABLE (d->stream))) + g_seekable_seek (G_SEEKABLE (d->stream), + request, + G_SEEK_CUR, + d->job->cancellable, + &d->error); + else + return 0; + + if (d->error) + { + g_clear_error (&d->error); + request = 0; + } + DEBUG ("SEEK %d (%d)\n", (int) request, + (int) g_seekable_tell (G_SEEKABLE (d->stream))); + + return request; +} + +static int +gvfs_archive_close (struct archive *archive, + void *data) +{ + GVfsArchive *d = data; + + DEBUG ("CLOSE\n"); + g_object_unref (d->stream); + d->stream = NULL; + return ARCHIVE_OK; +} + +#define gvfs_archive_in_error(archive) ((archive)->error != NULL) + +static void +gvfs_archive_push_job (GVfsArchive *archive, GVfsJob *job) +{ + archive->job = job; +} + +static void +gvfs_archive_pop_job (GVfsArchive *archive) +{ + if (archive->job == NULL) + return; + + DEBUG ("popping job %s\n", G_OBJECT_TYPE_NAME (archive->job)); + if (archive->error) + { + g_vfs_job_failed_from_error (archive->job, archive->error); + g_clear_error (&archive->error); + } + else if (archive_errno (archive->archive)) + { + g_vfs_job_failed (archive->job, + G_IO_ERROR, + archive_errno (archive->archive), + archive_error_string (archive->archive)); + } + else + g_vfs_job_succeeded (archive->job); + + + archive->job = NULL; +} + +static void +gvfs_archive_finish (GVfsArchive *archive) +{ + gvfs_archive_pop_job (archive); + + archive_read_finish (archive->archive); + g_slice_free (GVfsArchive, archive); +} + +/* NB: assumes an GVfsArchive initialized with ARCHIVE_DATA_INIT */ +static GVfsArchive * +gvfs_archive_new (GVfsBackendArchive *ba, GVfsJob *job) +{ + GVfsArchive *d; + + d = g_slice_new0 (GVfsArchive); + + d->file = ba->file; + gvfs_archive_push_job (d, job); + + d->archive = archive_read_new (); + archive_read_support_compression_all (d->archive); + archive_read_support_format_all (d->archive); + archive_read_open2 (d->archive, + d, + gvfs_archive_open, + gvfs_archive_read, + gvfs_archive_skip, + gvfs_archive_close); + + return d; +} + +/*** BACKEND ***/ + +static void +g_vfs_backend_archive_finalize (GObject *object) +{ + GVfsBackendArchive *archive = G_VFS_BACKEND_ARCHIVE (object); + + g_assert (archive->file == NULL); +} + +static void +g_vfs_backend_archive_init (GVfsBackendArchive *archive) +{ +} + +/*** FILE TREE HANDLING ***/ + +/* NB: filename must NOT start with a slash */ +static ArchiveFile * +archive_file_get_from_path (ArchiveFile *file, const char *filename, gboolean add) +{ + char **names = g_strsplit (filename, "/", -1); + ArchiveFile *cur; + GSList *walk; + guint i; + + DEBUG ("%s %s\n", add ? "add" : "find", filename); + for (i = 0; file && names[i] != NULL; i++) + { + cur = NULL; + for (walk = file->children; walk; walk = walk->next) + { + cur = walk->data; + if (g_str_equal (cur->name, names[i])) + break; + cur = NULL; + } + if (cur == NULL && add != FALSE) + { + DEBUG ("adding node %s to %s\n", names[i], file->name); + /* (hopefully) clever trick to avoid string copy */ + if (names[i][0] != 0 && + strcmp (names[i], ".") != 0) + { + cur = g_slice_new0 (ArchiveFile); + cur->name = names[i]; + names[i] = NULL; + file->children = g_slist_prepend (file->children, cur); + } + else + { + /* happens when adding directories, their path ends with a / */ + /* Can also happen with "." in e.g. iso files */ + g_assert (names[i + 1] == NULL); + g_free (names[i]); + names[i] = NULL; + cur = file; + } + } + file = cur; + } + g_strfreev (names); + return file; +} +#define archive_file_find(ba, filename) archive_file_get_from_path((ba)->files, (filename) + 1, FALSE) + +static void +create_root_file (GVfsBackendArchive *ba, GIcon *icon) +{ + ArchiveFile *root = g_slice_new0 (ArchiveFile); + GFileInfo *info; + char *s, *display_name; + + root = g_slice_new0 (ArchiveFile); + root->name = g_strdup ("/"); + ba->files = root; + + info = g_file_info_new (); + root->info = info; + + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + + g_file_info_set_name (info, "/"); + s = g_file_get_basename (ba->file); + /* FIXME: this should really be "/ in %s", but can't change + due to string freeze. */ + display_name = g_strdup_printf (_("/ on %s"), s); + g_free (s); + g_file_info_set_display_name (info, display_name); + g_free (display_name); + g_file_info_set_edit_name (info, "/"); + + g_file_info_set_content_type (info, "inode/directory"); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, "inode/directory"); + + g_file_info_set_icon (info, icon); +} + +static void +archive_file_set_info_from_entry (ArchiveFile * file, + struct archive_entry *entry) +{ + GFileInfo *info = g_file_info_new (); + GFileType type; + file->info = info; + + DEBUG ("setting up %s (%s)\n", archive_entry_pathname (entry), file->name); + + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_ACCESS, + archive_entry_atime (entry)); + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, + archive_entry_atime_nsec (entry) / 1000); + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_CHANGED, + archive_entry_ctime (entry)); + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_CHANGED_USEC, + archive_entry_ctime_nsec (entry) / 1000); + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_MODIFIED, + archive_entry_mtime (entry)); + g_file_info_set_attribute_uint64 (info, + G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, + archive_entry_mtime_nsec (entry) / 1000); + + switch (archive_entry_filetype (entry)) + { + case AE_IFREG: + type = G_FILE_TYPE_REGULAR; + break; + case AE_IFLNK: + g_file_info_set_symlink_target (info, + archive_entry_symlink (entry)); + type = G_FILE_TYPE_SYMBOLIC_LINK; + break; + case AE_IFDIR: + type = G_FILE_TYPE_DIRECTORY; + break; + case AE_IFCHR: + case AE_IFBLK: + case AE_IFIFO: + type = G_FILE_TYPE_SPECIAL; + break; + default: + g_warning ("unknown file type %u", archive_entry_filetype (entry)); + type = G_FILE_TYPE_SPECIAL; + break; + } + g_file_info_set_name (info, file->name); + gvfs_file_info_populate_default (info, + file->name, + type); + + g_file_info_set_size (info, + archive_entry_size (entry)); + + /* FIXME: add info for these +dev_t archive_entry_dev(struct archive_entry *); +dev_t archive_entry_devmajor(struct archive_entry *); +dev_t archive_entry_devminor(struct archive_entry *); +void archive_entry_fflags(struct archive_entry *, + unsigned long *set, unsigned long *clear); +const char *archive_entry_fflags_text(struct archive_entry *); +gid_t archive_entry_gid(struct archive_entry *); +const char *archive_entry_gname(struct archive_entry *); +const char *archive_entry_hardlink(struct archive_entry *); +ino_t archive_entry_ino(struct archive_entry *); +mode_t archive_entry_mode(struct archive_entry *); +unsigned int archive_entry_nlink(struct archive_entry *); +dev_t archive_entry_rdev(struct archive_entry *); +dev_t archive_entry_rdevmajor(struct archive_entry *); +dev_t archive_entry_rdevminor(struct archive_entry *); +uid_t archive_entry_uid(struct archive_entry *); +const char *archive_entry_uname(struct archive_entry *); + */ + + /* FIXME: do ACLs */ +} + +static void +fixup_dirs (ArchiveFile *file) +{ + GSList *l; + + if (file->info == NULL) + { + GFileInfo *info = g_file_info_new (); + + file->info = info; + g_file_info_set_name (info, file->name); + gvfs_file_info_populate_default (info, + file->name, + G_FILE_TYPE_DIRECTORY); + } + + for (l = file->children; l != NULL; l = l->next) + fixup_dirs (l->data); +} + +static void +create_file_tree (GVfsBackendArchive *ba, GVfsJob *job) +{ + GVfsArchive *archive; + struct archive_entry *entry; + int result; + + archive = gvfs_archive_new (ba, job); + + g_assert (ba->files != NULL); + + do + { + result = archive_read_next_header (archive->archive, &entry); + if (result == ARCHIVE_OK) + { + ArchiveFile *file = archive_file_get_from_path (ba->files, + archive_entry_pathname (entry), + TRUE); + archive_file_set_info_from_entry (file, entry); + archive_read_data_skip (archive->archive); + } + } + while (result != ARCHIVE_FATAL && result != ARCHIVE_EOF); + + fixup_dirs (ba->files); + + gvfs_archive_finish (archive); +} + +static void +archive_file_free (ArchiveFile *file) +{ + g_slist_foreach (file->children, (GFunc) archive_file_free, NULL); + g_slist_free (file->children); + if (file->info) + g_object_unref (file->info); + g_free (file->name); +} + +static void +do_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + GVfsBackendArchive *archive = G_VFS_BACKEND_ARCHIVE (backend); + const char *host; + GFileInfo *info; + GIcon *icon; + char *filename, *s; + GError *error = NULL; + + host = g_mount_spec_get (mount_spec, "host"); + if (host == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("No hostname specified")); + return; + } + + filename = g_uri_unescape_string (host, NULL); + if (filename == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid mount spec")); + return; + } + DEBUG ("Trying to mount %s\n", filename); + + archive->file = g_file_new_for_uri (filename); + g_free (filename); + + /* FIXME: check if this file is an archive */ + info = g_file_query_info (archive->file, + "*", + G_FILE_QUERY_INFO_NONE, + G_VFS_JOB (job)->cancellable, + &error); + if (info == NULL) + { + g_vfs_job_failed_from_error (G_VFS_JOB (job), + error); + g_error_free (error); + return; + } + + filename = g_file_get_uri (archive->file); + DEBUG ("mounted %s\n", filename); + s = g_uri_escape_string (filename, NULL, FALSE); + mount_spec = g_mount_spec_new ("archive"); + g_mount_spec_set (mount_spec, "host", s); + g_free (s); + g_vfs_backend_set_mount_spec (backend, mount_spec); + g_mount_spec_unref (mount_spec); + + g_vfs_backend_set_display_name (backend, g_file_info_get_display_name (info)); + + icon = g_file_info_get_icon (info); +#if 0 + if (G_IS_THEMED_ICON (icon)) + g_vfs_backend_set_icon_name (backend, + g_themed_icon_get_names (G_THEMED_ICON (icon))[0]); + else +#endif + g_vfs_backend_set_icon_name (backend, "package-x-generic"); + + create_root_file (archive, icon); + create_file_tree (archive, G_VFS_JOB (job)); + g_object_unref (info); +} + +static void +do_unmount (GVfsBackend *backend, + GVfsJobUnmount *job) +{ + GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend); + + g_object_unref (ba->file); + archive_file_free (ba->files); + ba->files = NULL; + + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +do_open_for_read (GVfsBackend * backend, + GVfsJobOpenForRead *job, + const char * filename) +{ + GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend); + GVfsArchive *archive; + struct archive_entry *entry; + int result; + + archive = gvfs_archive_new (ba, G_VFS_JOB (job)); + + do + { + result = archive_read_next_header (archive->archive, &entry); + if (result == ARCHIVE_OK) + { + if (g_str_equal (archive_entry_pathname (entry), filename + 1)) + { + /* SUCCESS */ + g_vfs_job_open_for_read_set_handle (job, archive); + g_vfs_job_open_for_read_set_can_seek (job, FALSE); + gvfs_archive_pop_job (archive); + return; + } + else + archive_read_data_skip (archive->archive); + } + } + while (result != ARCHIVE_FATAL && result != ARCHIVE_EOF); + + if (!gvfs_archive_in_error (archive)) + { + g_set_error (&archive->error, + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("File doesn't exist")); + } + gvfs_archive_finish (archive); +} + +static void +do_close_read (GVfsBackend *backend, + GVfsJobCloseRead *job, + GVfsBackendHandle handle) +{ + GVfsArchive *archive = handle; + + gvfs_archive_push_job (archive, G_VFS_JOB (job)); + gvfs_archive_finish (archive); +} + +static void +do_read (GVfsBackend *backend, + GVfsJobRead *job, + GVfsBackendHandle handle, + char *buffer, + gsize bytes_requested) +{ + GVfsArchive *archive = handle; + gssize bytes_read; + + gvfs_archive_push_job (archive, G_VFS_JOB (job)); + bytes_read = archive_read_data (archive->archive, buffer, bytes_requested); + if (bytes_read >= 0) + g_vfs_job_read_set_size (job, bytes_read); + gvfs_archive_pop_job (archive); +} + +static void +do_query_info (GVfsBackend *backend, + GVfsJobQueryInfo *job, + const char *filename, + GFileQueryInfoFlags flags, + GFileInfo *info, + GFileAttributeMatcher *attribute_matcher) +{ + GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend); + ArchiveFile *file; + + file = archive_file_find (ba, filename); + if (file == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("File doesn't exist")); + return; + } + + if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) + g_warning ("FIXME: follow symlinks"); + + g_file_info_copy_into (file->info, info); + + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +do_enumerate (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *filename, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags) +{ + GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend); + ArchiveFile *file; + GSList *walk; + + file = archive_file_find (ba, filename); + if (file == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("File doesn't exist")); + return; + } + + if (g_file_info_get_file_type (file->info) != G_FILE_TYPE_DIRECTORY) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, + G_IO_ERROR_NOT_DIRECTORY, + _("The file is not a directory")); + return; + } + + if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)) + g_warning ("FIXME: follow symlinks"); + + for (walk = file->children; walk; walk = walk->next) + { + GFileInfo *info = g_file_info_dup (((ArchiveFile *) walk->data)->info); + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + g_vfs_job_enumerate_done (job); + + g_vfs_job_succeeded (G_VFS_JOB (job)); +} + +static void +g_vfs_backend_archive_class_init (GVfsBackendArchiveClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); + + gobject_class->finalize = g_vfs_backend_archive_finalize; + + backend_class->mount = do_mount; + backend_class->unmount = do_unmount; + backend_class->open_for_read = do_open_for_read; + backend_class->close_read = do_close_read; + backend_class->read = do_read; + backend_class->enumerate = do_enumerate; + backend_class->query_info = do_query_info; +} |