/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2008 Benjamin Otte * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Benjmain Otte */ #include #include #include #include #include #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 MOUNT_ICON_NAME "drive-removable-media" #define MOUNT_SYMBOLIC_ICON_NAME "drive-removable-media-symbolic" /*** 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 */ gsize size; }; G_DEFINE_TYPE (GVfsBackendArchive, g_vfs_backend_archive, G_VFS_TYPE_BACKEND) static void backend_unmount (GVfsBackendArchive *ba); /*** AN ARCHIVE WE CAN OPERATE ON ***/ typedef struct { struct archive * archive; GFile * file; GFileInputStream *stream; GVfsJob * job; GVfsBackendArchive *backend; 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; g_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); g_debug ("READ %d\n", (int) read_bytes); return read_bytes; } static int64_t gvfs_archive_skip (struct archive *archive, void *data, int64_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; } g_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; g_debug ("CLOSE\n"); if (!d->stream) g_vfs_backend_force_unmount (G_VFS_BACKEND (d->backend)); g_clear_object (&d->stream); return ARCHIVE_OK; } #define gvfs_archive_in_error(archive) ((archive)->error != NULL) static void gvfs_archive_set_error_from_errno (GVfsArchive *archive) { if (gvfs_archive_in_error (archive)) return; g_set_error_literal (&archive->error, G_IO_ERROR, g_io_error_from_errno (archive_errno (archive->archive)), archive_error_string (archive->archive)); } 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; g_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 g_vfs_job_succeeded (archive->job); archive->job = NULL; } static void gvfs_archive_finish (GVfsArchive *archive) { gvfs_archive_pop_job (archive); g_object_unref (archive->backend); archive_read_free (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->backend = g_object_ref (ba); d->file = ba->file; gvfs_archive_push_job (d, job); d->archive = archive_read_new (); archive_read_support_filter_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); backend_unmount (archive); if (G_OBJECT_CLASS (g_vfs_backend_archive_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_backend_archive_parent_class)->finalize) (object); } static void g_vfs_backend_archive_init (GVfsBackendArchive *archive) { } /*** FILE TREE HANDLING ***/ static char * fixup_path (const char *path) { char *str, *ptr; int len; /* skip leading garbage if present */ if (g_str_has_prefix (path, "./")) str = g_strdup (path + 2); else str = g_strdup (path); /* strip '/./' from the path */ ptr = str; while ((ptr = strstr (ptr, "/./"))) { char *dst = ptr + 2; while (*dst) *++ptr = *++dst; } /* strip '//' from the path */ ptr = str; while ((ptr = strstr (ptr, "//"))) { char *dst = ptr + 1; while (*dst) *++ptr = *++dst; } /* strip trailing slash from the path */ len = strlen (str); if (len > 0 && str[len - 1] == '/') str[len - 1] = '\0'; return str; } /* Filename must be a clean path containing no '.' entries, no empty entries * and must not start with a '/'. */ static ArchiveFile * archive_file_get_from_path (ArchiveFile *file, const char *filename, gboolean add) { char **names; ArchiveFile *cur; GSList *walk; guint i; names = g_strsplit (filename, "/", -1); g_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) { g_debug ("adding node %s to %s\n", names[i], file->name); cur = g_slice_new0 (ArchiveFile); cur->name = g_strdup (names[i]); file->children = g_slist_prepend (file->children, cur); } 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) { ArchiveFile *root; GFileInfo *info; const char *content_type = "inode/directory"; char *s, *display_name; GIcon *icon; 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); /* Translators: This is the name of the root in a mounted archive file, e.g. "/ in archive.tar.gz" for a file with the name "archive.tar.gz" */ display_name = g_strdup_printf (_("/ in %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, content_type); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, content_type); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); icon = g_content_type_get_icon (content_type); g_file_info_set_icon (info, icon); g_object_unref (icon); icon = g_content_type_get_symbolic_icon (content_type); g_file_info_set_symbolic_icon (info, icon); g_object_unref (icon); } /* Read archive entry data blocks to determine file size */ static int64_t archive_entry_determine_size (GVfsArchive *archive, struct archive_entry *entry) { size_t read; int result; const void *block; int64_t offset, size = 0; do { result = archive_read_data_block (archive->archive, &block, &read, &offset); if (result >= ARCHIVE_FAILED && result <= ARCHIVE_OK) { if (result < ARCHIVE_OK) { g_debug ("archive_read_data_block: result = %d, error = '%s'\n", result, archive_error_string (archive->archive)); archive_set_error (archive->archive, ARCHIVE_OK, "No error"); archive_clear_error (archive->archive); if (result == ARCHIVE_RETRY) continue; /* We don't want to fail the mount job, just because of unknown file * size (e.g. caused by unsupported archive encryption). */ if (result < ARCHIVE_WARN) { size = -1; break; } } size += read; } } while (result >= ARCHIVE_FAILED && result != ARCHIVE_EOF); if (result == ARCHIVE_FATAL) gvfs_archive_set_error_from_errno (archive); return size; } static void archive_file_set_info_from_entry (GVfsArchive * archive, ArchiveFile * file, struct archive_entry *entry, guint64 entry_index) { GFileInfo *info = g_file_info_new (); GFileType type; mode_t mode; int64_t size; file->info = info; g_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_uint32 (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_uint32 (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_uint32 (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); if (archive_entry_size_is_set (entry)) { size = archive_entry_size (entry); } else { size = archive_entry_determine_size (archive, entry); } if (size >= 0) g_file_info_set_size (info, size); if (file->name[0] == '.') g_file_info_set_is_hidden (info, TRUE); mode = archive_entry_perm (entry); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, type == G_FILE_TYPE_DIRECTORY || mode & S_IXUSR); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); /* Set inode number to reflect absolute position in the archive. */ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_UNIX_INODE, entry_index); /* 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 *); 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; guint64 entry_index = 0; archive = gvfs_archive_new (ba, job); g_assert (ba->files != NULL); do { result = archive_read_next_header (archive->archive, &entry); if (result >= ARCHIVE_WARN && result <= ARCHIVE_OK) { ArchiveFile *file; char *path; if (result < ARCHIVE_OK) { g_debug ("archive_read_next_header: result = %d, error = '%s'\n", result, archive_error_string (archive->archive)); archive_set_error (archive->archive, ARCHIVE_OK, "No error"); archive_clear_error (archive->archive); if (result == ARCHIVE_RETRY) continue; } path = fixup_path (archive_entry_pathname (entry)); file = archive_file_get_from_path (ba->files, path, TRUE); g_free (path); /* Don't set info for root */ if (file != ba->files) { archive_file_set_info_from_entry (archive, file, entry, entry_index); ba->size += g_file_info_get_size (file->info); } archive_read_data_skip (archive->archive); entry_index++; } } while (result >= ARCHIVE_WARN && result != ARCHIVE_EOF && !gvfs_archive_in_error (archive)); if (result < ARCHIVE_WARN) gvfs_archive_set_error_from_errno (archive); fixup_dirs (ba->files); gvfs_archive_finish (archive); } static void archive_file_free (ArchiveFile *file) { g_slist_free_full (file->children, (GDestroyNotify) archive_file_free); 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, *file; GFileInfo *info; char *filename, *s; GError *error = NULL; host = g_mount_spec_get (mount_spec, "host"); file = g_mount_spec_get (mount_spec, "file"); if (host == NULL && file == NULL) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("No hostname specified")); return; } if (host != NULL) { 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; } archive->file = g_file_new_for_commandline_arg (filename); g_free (filename); } else archive->file = g_file_new_for_commandline_arg (file); g_debug ("Trying to mount %s\n", g_file_get_uri (archive->file)); 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; } if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid mount spec")); return; } /* FIXME: check if this file is an archive */ filename = g_file_get_uri (archive->file); g_debug ("mounted %s\n", filename); s = g_uri_escape_string (filename, NULL, FALSE); g_free (filename); 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)); g_vfs_backend_set_icon_name (backend, MOUNT_ICON_NAME); g_vfs_backend_set_symbolic_icon_name (backend, MOUNT_SYMBOLIC_ICON_NAME); create_root_file (archive); create_file_tree (archive, G_VFS_JOB (job)); g_object_unref (info); } static void backend_unmount (GVfsBackendArchive *ba) { if (ba->file) { g_object_unref (ba->file); ba->file = NULL; } if (ba->files) { archive_file_free (ba->files); ba->files = NULL; } } static void do_unmount (GVfsBackend *backend, GVfsJobUnmount *job, GMountUnmountFlags flags, GMountSource *mount_source) { backend_unmount (G_VFS_BACKEND_ARCHIVE (backend)); 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; ArchiveFile *file; char *entry_pathname; 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_IS_DIRECTORY, _("Can’t open directory")); return; } archive = gvfs_archive_new (ba, G_VFS_JOB (job)); do { result = archive_read_next_header (archive->archive, &entry); if (result >= ARCHIVE_WARN && result <= ARCHIVE_OK) { if (result < ARCHIVE_OK) { g_debug ("do_open_for_read: result = %d, error = '%s'\n", result, archive_error_string (archive->archive)); archive_set_error (archive->archive, ARCHIVE_OK, "No error"); archive_clear_error (archive->archive); if (result == ARCHIVE_RETRY) continue; } entry_pathname = fixup_path (archive_entry_pathname (entry)); if (g_str_equal (entry_pathname, filename + 1)) { g_free (entry_pathname); /* 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); g_free (entry_pathname); } } while (result >= ARCHIVE_WARN && result != ARCHIVE_EOF); if (result < ARCHIVE_WARN) gvfs_archive_set_error_from_errno (archive); if (!gvfs_archive_in_error (archive)) { g_set_error_literal (&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); else gvfs_archive_set_error_from_errno (archive); 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 gboolean try_query_fs_info (GVfsBackend *backend, GVfsJobQueryFsInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *attribute_matcher) { GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "archive"); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, FALSE); g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, ba->size); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, ba->size); g_vfs_job_succeeded (G_VFS_JOB (job)); return TRUE; } 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; backend_class->try_query_fs_info = try_query_fs_info; }