1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2008 Benjamin Otte <otte@gnome.org>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * Author: Benjmain Otte <otte@gnome.org>
21  */
22 
23 
24 #include <config.h>
25 
26 #include <glib/gi18n.h>
27 #include <string.h>
28 #include <archive.h>
29 #include <archive_entry.h>
30 
31 #include "gvfsbackendarchive.h"
32 #include "gvfsjobopenforread.h"
33 #include "gvfsjobread.h"
34 #include "gvfsjobseekread.h"
35 #include "gvfsjobopenforwrite.h"
36 #include "gvfsjobwrite.h"
37 #include "gvfsjobseekwrite.h"
38 #include "gvfsjobsetdisplayname.h"
39 #include "gvfsjobqueryinfo.h"
40 #include "gvfsjobqueryfsinfo.h"
41 #include "gvfsjobqueryattributes.h"
42 #include "gvfsjobenumerate.h"
43 #include "gvfsdaemonprotocol.h"
44 #include "gvfsdaemonutils.h"
45 #include "gvfskeyring.h"
46 
47 #define MOUNT_ICON_NAME "drive-removable-media"
48 #define MOUNT_SYMBOLIC_ICON_NAME "drive-removable-media-symbolic"
49 
50 /*** TYPE DEFINITIONS ***/
51 
52 typedef struct _ArchiveFile ArchiveFile;
53 struct _ArchiveFile {
54   char *	name;			/* name of the file inside the archive */
55   GFileInfo *	info;			/* file info created from archive_entry */
56   GSList *	children;		/* (unordered) list of child files */
57 };
58 
59 struct _GVfsBackendArchive
60 {
61   GVfsBackend		backend;
62 
63   GFile *		file;
64   ArchiveFile *		files;		/* the tree of files */
65   gsize                 size;
66 };
67 
68 G_DEFINE_TYPE (GVfsBackendArchive, g_vfs_backend_archive, G_VFS_TYPE_BACKEND)
69 
70 static void backend_unmount (GVfsBackendArchive *ba);
71 
72 /*** AN ARCHIVE WE CAN OPERATE ON ***/
73 
74 typedef struct {
75   struct archive *  archive;
76   GFile *	    file;
77   GFileInputStream *stream;
78   GVfsJob *	    job;
79   GVfsBackendArchive *backend;
80   GError *	    error;
81   guchar	    data[4096];
82 } GVfsArchive;
83 
84 #define gvfs_archive_return(d) ((d)->error ? ARCHIVE_FATAL : ARCHIVE_OK)
85 
86 static int
gvfs_archive_open(struct archive * archive,void * data)87 gvfs_archive_open (struct archive *archive,
88                    void           *data)
89 {
90   GVfsArchive *d = data;
91 
92   g_debug ("OPEN\n");
93   g_assert (d->stream == NULL);
94   d->stream = g_file_read (d->file,
95 			   d->job->cancellable,
96 			   &d->error);
97   return gvfs_archive_return (d);
98 }
99 
100 static ssize_t
gvfs_archive_read(struct archive * archive,void * data,const void ** buffer)101 gvfs_archive_read (struct archive *archive,
102 		   void           *data,
103 		   const void    **buffer)
104 {
105   GVfsArchive *d = data;
106   gssize read_bytes;
107 
108   *buffer = d->data;
109   read_bytes = g_input_stream_read (G_INPUT_STREAM (d->stream),
110 				    d->data,
111 				    sizeof (d->data),
112 				    d->job->cancellable,
113 				    &d->error);
114 
115   g_debug ("READ %d\n", (int) read_bytes);
116   return read_bytes;
117 }
118 
119 static int64_t
gvfs_archive_skip(struct archive * archive,void * data,int64_t request)120 gvfs_archive_skip (struct archive *archive,
121 		   void           *data,
122 		   int64_t         request)
123 {
124   GVfsArchive *d = data;
125 
126   if (g_seekable_can_seek (G_SEEKABLE (d->stream)))
127     g_seekable_seek (G_SEEKABLE (d->stream),
128 		     request,
129 		     G_SEEK_CUR,
130 		     d->job->cancellable,
131 		     &d->error);
132   else
133     return 0;
134 
135   if (d->error)
136     {
137       g_clear_error (&d->error);
138       request = 0;
139     }
140   g_debug ("SEEK %d (%d)\n", (int) request,
141       (int) g_seekable_tell (G_SEEKABLE (d->stream)));
142 
143   return request;
144 }
145 
146 static int
gvfs_archive_close(struct archive * archive,void * data)147 gvfs_archive_close (struct archive *archive,
148 	      void *data)
149 {
150   GVfsArchive *d = data;
151 
152   g_debug ("CLOSE\n");
153   if (!d->stream)
154     g_vfs_backend_force_unmount (G_VFS_BACKEND (d->backend));
155   g_clear_object (&d->stream);
156   return ARCHIVE_OK;
157 }
158 
159 #define gvfs_archive_in_error(archive) ((archive)->error != NULL)
160 
161 static void
gvfs_archive_set_error_from_errno(GVfsArchive * archive)162 gvfs_archive_set_error_from_errno (GVfsArchive *archive)
163 {
164   if (gvfs_archive_in_error (archive))
165     return;
166 
167   g_set_error_literal (&archive->error,
168 		       G_IO_ERROR,
169 		       g_io_error_from_errno (archive_errno (archive->archive)),
170 		       archive_error_string (archive->archive));
171 }
172 
173 static void
gvfs_archive_push_job(GVfsArchive * archive,GVfsJob * job)174 gvfs_archive_push_job (GVfsArchive *archive, GVfsJob *job)
175 {
176   archive->job = job;
177 }
178 
179 static void
gvfs_archive_pop_job(GVfsArchive * archive)180 gvfs_archive_pop_job (GVfsArchive *archive)
181 {
182   if (archive->job == NULL)
183     return;
184 
185   g_debug ("popping job %s\n", G_OBJECT_TYPE_NAME (archive->job));
186   if (archive->error)
187     {
188       g_vfs_job_failed_from_error (archive->job, archive->error);
189       g_clear_error (&archive->error);
190     }
191   else
192     g_vfs_job_succeeded (archive->job);
193 
194 
195   archive->job = NULL;
196 }
197 
198 static void
gvfs_archive_finish(GVfsArchive * archive)199 gvfs_archive_finish (GVfsArchive *archive)
200 {
201   gvfs_archive_pop_job (archive);
202 
203   g_object_unref (archive->backend);
204   archive_read_free (archive->archive);
205   g_slice_free (GVfsArchive, archive);
206 }
207 
208 /* NB: assumes an GVfsArchive initialized with ARCHIVE_DATA_INIT */
209 static GVfsArchive *
gvfs_archive_new(GVfsBackendArchive * ba,GVfsJob * job)210 gvfs_archive_new (GVfsBackendArchive *ba, GVfsJob *job)
211 {
212   GVfsArchive *d;
213 
214   d = g_slice_new0 (GVfsArchive);
215 
216   d->backend = g_object_ref (ba);
217   d->file = ba->file;
218   gvfs_archive_push_job (d, job);
219 
220   d->archive = archive_read_new ();
221   archive_read_support_filter_all (d->archive);
222   archive_read_support_format_all (d->archive);
223   archive_read_open2 (d->archive,
224 		      d,
225 		      gvfs_archive_open,
226 		      gvfs_archive_read,
227 		      gvfs_archive_skip,
228 		      gvfs_archive_close);
229 
230   return d;
231 }
232 
233 /*** BACKEND ***/
234 
235 static void
g_vfs_backend_archive_finalize(GObject * object)236 g_vfs_backend_archive_finalize (GObject *object)
237 {
238   GVfsBackendArchive *archive = G_VFS_BACKEND_ARCHIVE (object);
239 
240   backend_unmount (archive);
241 
242   if (G_OBJECT_CLASS (g_vfs_backend_archive_parent_class)->finalize)
243     (*G_OBJECT_CLASS (g_vfs_backend_archive_parent_class)->finalize) (object);
244 }
245 
246 static void
g_vfs_backend_archive_init(GVfsBackendArchive * archive)247 g_vfs_backend_archive_init (GVfsBackendArchive *archive)
248 {
249 }
250 
251 /*** FILE TREE HANDLING ***/
252 static char *
fixup_path(const char * path)253 fixup_path (const char *path)
254 {
255   char *str, *ptr;
256   int len;
257 
258   /* skip leading garbage if present */
259   if (g_str_has_prefix (path, "./"))
260     str = g_strdup (path + 2);
261   else
262     str = g_strdup (path);
263 
264   /* strip '/./' from the path */
265   ptr = str;
266   while ((ptr = strstr (ptr, "/./")))
267     {
268       char *dst = ptr + 2;
269       while (*dst)
270         *++ptr = *++dst;
271     }
272 
273   /* strip '//' from the path */
274   ptr = str;
275   while ((ptr = strstr (ptr, "//")))
276     {
277       char *dst = ptr + 1;
278       while (*dst)
279         *++ptr = *++dst;
280     }
281 
282   /* strip trailing slash from the path */
283   len = strlen (str);
284   if (len > 0 && str[len - 1] == '/')
285     str[len - 1] = '\0';
286 
287   return str;
288 }
289 
290 /* Filename must be a clean path containing no '.' entries, no empty entries
291  * and must not start with a '/'. */
292 static ArchiveFile *
archive_file_get_from_path(ArchiveFile * file,const char * filename,gboolean add)293 archive_file_get_from_path (ArchiveFile *file, const char *filename, gboolean add)
294 {
295   char **names;
296   ArchiveFile *cur;
297   GSList *walk;
298   guint i;
299 
300   names = g_strsplit (filename, "/", -1);
301 
302   g_debug ("%s %s\n", add ? "add" : "find", filename);
303   for (i = 0; file && names[i] != NULL; i++)
304     {
305       cur = NULL;
306       for (walk = file->children; walk; walk = walk->next)
307 	{
308 	  cur = walk->data;
309 	  if (g_str_equal (cur->name, names[i]))
310 	    break;
311 	  cur = NULL;
312 	}
313       if (cur == NULL && add != FALSE)
314 	{
315           g_debug ("adding node %s to %s\n", names[i], file->name);
316           cur = g_slice_new0 (ArchiveFile);
317           cur->name = g_strdup (names[i]);
318           file->children = g_slist_prepend (file->children, cur);
319 	}
320       file = cur;
321     }
322   g_strfreev (names);
323   return file;
324 }
325 #define archive_file_find(ba, filename) archive_file_get_from_path((ba)->files, (filename) + 1, FALSE)
326 
327 static void
create_root_file(GVfsBackendArchive * ba)328 create_root_file (GVfsBackendArchive *ba)
329 {
330   ArchiveFile *root;
331   GFileInfo *info;
332   const char *content_type = "inode/directory";
333   char *s, *display_name;
334   GIcon *icon;
335 
336   root = g_slice_new0 (ArchiveFile);
337   root->name = g_strdup ("/");
338   ba->files = root;
339 
340   info = g_file_info_new ();
341   root->info = info;
342 
343   g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
344 
345   g_file_info_set_name (info, "/");
346   s = g_file_get_basename (ba->file);
347 
348   /* Translators: This is the name of the root in a mounted archive file,
349      e.g. "/ in archive.tar.gz" for a file with the name "archive.tar.gz" */
350   display_name = g_strdup_printf (_("/ in %s"), s);
351   g_free (s);
352   g_file_info_set_display_name (info, display_name);
353   g_free (display_name);
354   g_file_info_set_edit_name (info, "/");
355 
356   g_file_info_set_content_type (info, content_type);
357   g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, content_type);
358 
359   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
360   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
361   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
362   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE);
363   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
364   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE);
365 
366   icon = g_content_type_get_icon (content_type);
367   g_file_info_set_icon (info, icon);
368   g_object_unref (icon);
369   icon = g_content_type_get_symbolic_icon (content_type);
370   g_file_info_set_symbolic_icon (info, icon);
371   g_object_unref (icon);
372 }
373 
374 /* Read archive entry data blocks to determine file size */
375 static int64_t
archive_entry_determine_size(GVfsArchive * archive,struct archive_entry * entry)376 archive_entry_determine_size (GVfsArchive          *archive,
377 			      struct archive_entry *entry)
378 {
379   size_t read;
380   int result;
381   const void *block;
382   int64_t offset, size = 0;
383 
384   do
385     {
386       result = archive_read_data_block (archive->archive, &block, &read, &offset);
387       if (result >= ARCHIVE_FAILED && result <= ARCHIVE_OK)
388 	{
389 	  if (result < ARCHIVE_OK) {
390             g_debug ("archive_read_data_block: result = %d, error = '%s'\n", result, archive_error_string (archive->archive));
391 	    archive_set_error (archive->archive, ARCHIVE_OK, "No error");
392 	    archive_clear_error (archive->archive);
393             if (result == ARCHIVE_RETRY)
394               continue;
395 
396 	    /* We don't want to fail the mount job, just because of unknown file
397 	     * size (e.g. caused by unsupported archive encryption). */
398 	    if (result < ARCHIVE_WARN)
399 	      {
400 	        size = -1;
401 	        break;
402 	      }
403 	  }
404 
405 	  size += read;
406 	}
407     }
408   while (result >= ARCHIVE_FAILED && result != ARCHIVE_EOF);
409 
410   if (result == ARCHIVE_FATAL)
411     gvfs_archive_set_error_from_errno (archive);
412 
413   return size;
414 }
415 
416 static void
archive_file_set_info_from_entry(GVfsArchive * archive,ArchiveFile * file,struct archive_entry * entry,guint64 entry_index)417 archive_file_set_info_from_entry (GVfsArchive *         archive,
418 				  ArchiveFile *         file,
419 				  struct archive_entry *entry,
420 				  guint64               entry_index)
421 {
422   GFileInfo *info = g_file_info_new ();
423   GFileType type;
424   mode_t mode;
425   int64_t size;
426   file->info = info;
427 
428   g_debug ("setting up %s (%s)\n", archive_entry_pathname (entry), file->name);
429 
430   g_file_info_set_attribute_uint64 (info,
431 				    G_FILE_ATTRIBUTE_TIME_ACCESS,
432 				    archive_entry_atime (entry));
433   g_file_info_set_attribute_uint32 (info,
434 				    G_FILE_ATTRIBUTE_TIME_ACCESS_USEC,
435 				    archive_entry_atime_nsec (entry) / 1000);
436   g_file_info_set_attribute_uint64 (info,
437 				    G_FILE_ATTRIBUTE_TIME_CHANGED,
438 				    archive_entry_ctime (entry));
439   g_file_info_set_attribute_uint32 (info,
440 				    G_FILE_ATTRIBUTE_TIME_CHANGED_USEC,
441 				    archive_entry_ctime_nsec (entry) / 1000);
442   g_file_info_set_attribute_uint64 (info,
443 				    G_FILE_ATTRIBUTE_TIME_MODIFIED,
444 				    archive_entry_mtime (entry));
445   g_file_info_set_attribute_uint32 (info,
446 				    G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
447 				    archive_entry_mtime_nsec (entry) / 1000);
448 
449   switch (archive_entry_filetype (entry))
450     {
451       case AE_IFREG:
452 	type = G_FILE_TYPE_REGULAR;
453 	break;
454       case AE_IFLNK:
455 	g_file_info_set_symlink_target (info,
456 	                                archive_entry_symlink (entry));
457 	type = G_FILE_TYPE_SYMBOLIC_LINK;
458 	break;
459       case AE_IFDIR:
460 	type = G_FILE_TYPE_DIRECTORY;
461 	break;
462       case AE_IFCHR:
463       case AE_IFBLK:
464       case AE_IFIFO:
465 	type = G_FILE_TYPE_SPECIAL;
466 	break;
467       default:
468 	g_warning ("unknown file type %u", archive_entry_filetype (entry));
469 	type = G_FILE_TYPE_SPECIAL;
470 	break;
471     }
472   g_file_info_set_name (info, file->name);
473   gvfs_file_info_populate_default (info,
474 				   file->name,
475 				   type);
476 
477   if (archive_entry_size_is_set (entry))
478     {
479       size = archive_entry_size (entry);
480     }
481   else
482     {
483       size = archive_entry_determine_size (archive, entry);
484     }
485 
486   if (size >= 0)
487     g_file_info_set_size (info, size);
488 
489   if (file->name[0] == '.')
490     g_file_info_set_is_hidden (info, TRUE);
491 
492   mode = archive_entry_perm (entry);
493   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
494   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
495   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
496   g_file_info_set_attribute_boolean (info,
497                                      G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
498                                      type == G_FILE_TYPE_DIRECTORY || mode & S_IXUSR);
499   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
500   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE);
501 
502   /* Set inode number to reflect absolute position in the archive. */
503   g_file_info_set_attribute_uint64 (info,
504 				    G_FILE_ATTRIBUTE_UNIX_INODE,
505 				    entry_index);
506 
507 
508   /* FIXME: add info for these
509 dev_t			 archive_entry_dev(struct archive_entry *);
510 dev_t			 archive_entry_devmajor(struct archive_entry *);
511 dev_t			 archive_entry_devminor(struct archive_entry *);
512 void			 archive_entry_fflags(struct archive_entry *,
513 			     unsigned long *set, unsigned long *clear);
514 const char		*archive_entry_fflags_text(struct archive_entry *);
515 gid_t			 archive_entry_gid(struct archive_entry *);
516 const char		*archive_entry_gname(struct archive_entry *);
517 const char		*archive_entry_hardlink(struct archive_entry *);
518 unsigned int		 archive_entry_nlink(struct archive_entry *);
519 dev_t			 archive_entry_rdev(struct archive_entry *);
520 dev_t			 archive_entry_rdevmajor(struct archive_entry *);
521 dev_t			 archive_entry_rdevminor(struct archive_entry *);
522 uid_t			 archive_entry_uid(struct archive_entry *);
523 const char		*archive_entry_uname(struct archive_entry *);
524   */
525 
526   /* FIXME: do ACLs */
527 }
528 
529 static void
fixup_dirs(ArchiveFile * file)530 fixup_dirs (ArchiveFile *file)
531 {
532   GSList *l;
533 
534   if (file->info == NULL)
535     {
536       GFileInfo *info = g_file_info_new ();
537 
538       file->info = info;
539       g_file_info_set_name (info, file->name);
540       gvfs_file_info_populate_default (info,
541                                        file->name,
542                                        G_FILE_TYPE_DIRECTORY);
543     }
544 
545   for (l = file->children; l != NULL; l = l->next)
546     fixup_dirs (l->data);
547 }
548 
549 static void
create_file_tree(GVfsBackendArchive * ba,GVfsJob * job)550 create_file_tree (GVfsBackendArchive *ba, GVfsJob *job)
551 {
552   GVfsArchive *archive;
553   struct archive_entry *entry;
554   int result;
555   guint64 entry_index = 0;
556 
557   archive = gvfs_archive_new (ba, job);
558 
559   g_assert (ba->files != NULL);
560 
561   do
562     {
563       result = archive_read_next_header (archive->archive, &entry);
564       if (result >= ARCHIVE_WARN && result <= ARCHIVE_OK)
565 	{
566           ArchiveFile *file;
567           char *path;
568 
569   	  if (result < ARCHIVE_OK) {
570             g_debug ("archive_read_next_header: result = %d, error = '%s'\n", result, archive_error_string (archive->archive));
571   	    archive_set_error (archive->archive, ARCHIVE_OK, "No error");
572   	    archive_clear_error (archive->archive);
573             if (result == ARCHIVE_RETRY)
574               continue;
575 	  }
576 
577           path = fixup_path (archive_entry_pathname (entry));
578           file = archive_file_get_from_path (ba->files, path, TRUE);
579           g_free (path);
580           /* Don't set info for root */
581           if (file != ba->files)
582 	    {
583 	      archive_file_set_info_from_entry (archive, file, entry, entry_index);
584 	      ba->size += g_file_info_get_size (file->info);
585             }
586 	  archive_read_data_skip (archive->archive);
587 	  entry_index++;
588 	}
589     }
590   while (result >= ARCHIVE_WARN && result != ARCHIVE_EOF && !gvfs_archive_in_error (archive));
591 
592   if (result < ARCHIVE_WARN)
593     gvfs_archive_set_error_from_errno (archive);
594   fixup_dirs (ba->files);
595 
596   gvfs_archive_finish (archive);
597 }
598 
599 static void
archive_file_free(ArchiveFile * file)600 archive_file_free (ArchiveFile *file)
601 {
602   g_slist_free_full (file->children, (GDestroyNotify) archive_file_free);
603   if (file->info)
604     g_object_unref (file->info);
605   g_free (file->name);
606 }
607 
608 static void
do_mount(GVfsBackend * backend,GVfsJobMount * job,GMountSpec * mount_spec,GMountSource * mount_source,gboolean is_automount)609 do_mount (GVfsBackend *backend,
610 	  GVfsJobMount *job,
611 	  GMountSpec *mount_spec,
612 	  GMountSource *mount_source,
613 	  gboolean is_automount)
614 {
615   GVfsBackendArchive *archive = G_VFS_BACKEND_ARCHIVE (backend);
616   const char *host, *file;
617   GFileInfo *info;
618   char *filename, *s;
619   GError *error = NULL;
620 
621   host = g_mount_spec_get (mount_spec, "host");
622   file = g_mount_spec_get (mount_spec, "file");
623   if (host == NULL &&
624       file == NULL)
625     {
626       g_vfs_job_failed (G_VFS_JOB (job),
627                        G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
628                        _("No hostname specified"));
629       return;
630     }
631 
632   if (host != NULL)
633     {
634       filename = g_uri_unescape_string (host, NULL);
635       if (filename == NULL)
636         {
637           g_vfs_job_failed (G_VFS_JOB (job),
638                             G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
639                             _("Invalid mount spec"));
640           return;
641         }
642 
643       archive->file = g_file_new_for_commandline_arg (filename);
644       g_free (filename);
645     }
646   else
647     archive->file = g_file_new_for_commandline_arg (file);
648 
649   g_debug ("Trying to mount %s\n", g_file_get_uri (archive->file));
650 
651   info = g_file_query_info (archive->file,
652 			    "*",
653 			    G_FILE_QUERY_INFO_NONE,
654 			    G_VFS_JOB (job)->cancellable,
655 			    &error);
656   if (info == NULL)
657     {
658       g_vfs_job_failed_from_error (G_VFS_JOB (job),
659 				   error);
660       g_error_free (error);
661       return;
662     }
663 
664   if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
665     {
666       g_vfs_job_failed (G_VFS_JOB (job),
667                         G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
668                         _("Invalid mount spec"));
669       return;
670     }
671 
672   /* FIXME: check if this file is an archive */
673 
674   filename = g_file_get_uri (archive->file);
675   g_debug ("mounted %s\n", filename);
676   s = g_uri_escape_string (filename, NULL, FALSE);
677   g_free (filename);
678   mount_spec = g_mount_spec_new ("archive");
679   g_mount_spec_set (mount_spec, "host", s);
680   g_free (s);
681   g_vfs_backend_set_mount_spec (backend, mount_spec);
682   g_mount_spec_unref (mount_spec);
683 
684   g_vfs_backend_set_display_name (backend, g_file_info_get_display_name (info));
685 
686   g_vfs_backend_set_icon_name (backend, MOUNT_ICON_NAME);
687   g_vfs_backend_set_symbolic_icon_name (backend, MOUNT_SYMBOLIC_ICON_NAME);
688 
689   create_root_file (archive);
690   create_file_tree (archive, G_VFS_JOB (job));
691   g_object_unref (info);
692 }
693 
694 static void
backend_unmount(GVfsBackendArchive * ba)695 backend_unmount (GVfsBackendArchive *ba)
696 {
697   if (ba->file)
698     {
699       g_object_unref (ba->file);
700       ba->file = NULL;
701     }
702   if (ba->files)
703     {
704       archive_file_free (ba->files);
705       ba->files = NULL;
706     }
707 }
708 
709 static void
do_unmount(GVfsBackend * backend,GVfsJobUnmount * job,GMountUnmountFlags flags,GMountSource * mount_source)710 do_unmount (GVfsBackend *backend,
711 	    GVfsJobUnmount *job,
712             GMountUnmountFlags flags,
713             GMountSource *mount_source)
714 {
715   backend_unmount (G_VFS_BACKEND_ARCHIVE (backend));
716 
717   g_vfs_job_succeeded (G_VFS_JOB (job));
718 }
719 
720 static void
do_open_for_read(GVfsBackend * backend,GVfsJobOpenForRead * job,const char * filename)721 do_open_for_read (GVfsBackend *       backend,
722 		  GVfsJobOpenForRead *job,
723 		  const char *        filename)
724 {
725   GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend);
726   GVfsArchive *archive;
727   struct archive_entry *entry;
728   int result;
729   ArchiveFile *file;
730   char *entry_pathname;
731 
732   file = archive_file_find (ba, filename);
733   if (file == NULL)
734     {
735       g_vfs_job_failed (G_VFS_JOB (job),
736 		        G_IO_ERROR,
737 			G_IO_ERROR_NOT_FOUND,
738 			_("File doesn’t exist"));
739       return;
740     }
741 
742   if (g_file_info_get_file_type (file->info) == G_FILE_TYPE_DIRECTORY)
743     {
744       g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
745 			G_IO_ERROR_IS_DIRECTORY,
746 			_("Can’t open directory"));
747       return;
748     }
749 
750   archive = gvfs_archive_new (ba, G_VFS_JOB (job));
751 
752   do
753     {
754       result = archive_read_next_header (archive->archive, &entry);
755       if (result >= ARCHIVE_WARN && result <= ARCHIVE_OK)
756         {
757 	  if (result < ARCHIVE_OK) {
758             g_debug ("do_open_for_read: result = %d, error = '%s'\n", result, archive_error_string (archive->archive));
759 	    archive_set_error (archive->archive, ARCHIVE_OK, "No error");
760 	    archive_clear_error (archive->archive);
761             if (result == ARCHIVE_RETRY)
762               continue;
763 	  }
764 
765           entry_pathname = fixup_path (archive_entry_pathname (entry));
766 
767           if (g_str_equal (entry_pathname, filename + 1))
768             {
769               g_free (entry_pathname);
770 
771               /* SUCCESS */
772               g_vfs_job_open_for_read_set_handle (job, archive);
773               g_vfs_job_open_for_read_set_can_seek (job, FALSE);
774               gvfs_archive_pop_job (archive);
775               return;
776             }
777           else
778             archive_read_data_skip (archive->archive);
779 
780           g_free (entry_pathname);
781         }
782     }
783   while (result >= ARCHIVE_WARN && result != ARCHIVE_EOF);
784 
785   if (result < ARCHIVE_WARN)
786     gvfs_archive_set_error_from_errno (archive);
787 
788   if (!gvfs_archive_in_error (archive))
789     {
790       g_set_error_literal (&archive->error,
791 			   G_IO_ERROR,
792 			   G_IO_ERROR_NOT_FOUND,
793 			   _("File doesn’t exist"));
794     }
795   gvfs_archive_finish (archive);
796 }
797 
798 static void
do_close_read(GVfsBackend * backend,GVfsJobCloseRead * job,GVfsBackendHandle handle)799 do_close_read (GVfsBackend *backend,
800 	       GVfsJobCloseRead *job,
801 	       GVfsBackendHandle handle)
802 {
803   GVfsArchive *archive = handle;
804 
805   gvfs_archive_push_job (archive, G_VFS_JOB (job));
806   gvfs_archive_finish (archive);
807 }
808 
809 static void
do_read(GVfsBackend * backend,GVfsJobRead * job,GVfsBackendHandle handle,char * buffer,gsize bytes_requested)810 do_read (GVfsBackend *backend,
811 	 GVfsJobRead *job,
812 	 GVfsBackendHandle handle,
813 	 char *buffer,
814 	 gsize bytes_requested)
815 {
816   GVfsArchive *archive = handle;
817   gssize bytes_read;
818 
819   gvfs_archive_push_job (archive, G_VFS_JOB (job));
820   bytes_read = archive_read_data (archive->archive, buffer, bytes_requested);
821   if (bytes_read >= 0)
822     g_vfs_job_read_set_size (job, bytes_read);
823   else
824     gvfs_archive_set_error_from_errno (archive);
825   gvfs_archive_pop_job (archive);
826 }
827 
828 static void
do_query_info(GVfsBackend * backend,GVfsJobQueryInfo * job,const char * filename,GFileQueryInfoFlags flags,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)829 do_query_info (GVfsBackend *backend,
830 	       GVfsJobQueryInfo *job,
831 	       const char *filename,
832 	       GFileQueryInfoFlags flags,
833 	       GFileInfo *info,
834 	       GFileAttributeMatcher *attribute_matcher)
835 {
836   GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend);
837   ArchiveFile *file;
838 
839   file = archive_file_find (ba, filename);
840   if (file == NULL)
841     {
842       g_vfs_job_failed (G_VFS_JOB (job),
843 		        G_IO_ERROR,
844 			G_IO_ERROR_NOT_FOUND,
845 			_("File doesn’t exist"));
846       return;
847     }
848 
849   if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
850     g_warning ("FIXME: follow symlinks");
851 
852   g_file_info_copy_into (file->info, info);
853 
854   g_vfs_job_succeeded (G_VFS_JOB (job));
855 }
856 
857 static void
do_enumerate(GVfsBackend * backend,GVfsJobEnumerate * job,const char * filename,GFileAttributeMatcher * attribute_matcher,GFileQueryInfoFlags flags)858 do_enumerate (GVfsBackend *backend,
859 	      GVfsJobEnumerate *job,
860 	      const char *filename,
861 	      GFileAttributeMatcher *attribute_matcher,
862 	      GFileQueryInfoFlags flags)
863 {
864   GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend);
865   ArchiveFile *file;
866   GSList *walk;
867 
868   file = archive_file_find (ba, filename);
869   if (file == NULL)
870     {
871       g_vfs_job_failed (G_VFS_JOB (job),
872 		        G_IO_ERROR,
873 			G_IO_ERROR_NOT_FOUND,
874 			_("File doesn’t exist"));
875       return;
876     }
877 
878   if (g_file_info_get_file_type (file->info) != G_FILE_TYPE_DIRECTORY)
879     {
880       g_vfs_job_failed (G_VFS_JOB (job),
881 		        G_IO_ERROR,
882 			G_IO_ERROR_NOT_DIRECTORY,
883 			_("The file is not a directory"));
884       return;
885     }
886 
887   if (!(flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS))
888     g_warning ("FIXME: follow symlinks");
889 
890   for (walk = file->children; walk; walk = walk->next)
891     {
892       GFileInfo *info = g_file_info_dup (((ArchiveFile *) walk->data)->info);
893       g_vfs_job_enumerate_add_info (job, info);
894       g_object_unref (info);
895     }
896   g_vfs_job_enumerate_done (job);
897 
898   g_vfs_job_succeeded (G_VFS_JOB (job));
899 }
900 
901 static gboolean
try_query_fs_info(GVfsBackend * backend,GVfsJobQueryFsInfo * job,const char * filename,GFileInfo * info,GFileAttributeMatcher * attribute_matcher)902 try_query_fs_info (GVfsBackend *backend,
903                    GVfsJobQueryFsInfo *job,
904                    const char *filename,
905                    GFileInfo *info,
906                    GFileAttributeMatcher *attribute_matcher)
907 {
908   GVfsBackendArchive *ba = G_VFS_BACKEND_ARCHIVE (backend);
909 
910   g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "archive");
911   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, FALSE);
912   g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
913   g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
914   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, ba->size);
915   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 0);
916   g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, ba->size);
917   g_vfs_job_succeeded (G_VFS_JOB (job));
918   return TRUE;
919 }
920 
921 static void
g_vfs_backend_archive_class_init(GVfsBackendArchiveClass * klass)922 g_vfs_backend_archive_class_init (GVfsBackendArchiveClass *klass)
923 {
924   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
925   GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
926 
927   gobject_class->finalize = g_vfs_backend_archive_finalize;
928 
929   backend_class->mount = do_mount;
930   backend_class->unmount = do_unmount;
931   backend_class->open_for_read = do_open_for_read;
932   backend_class->close_read = do_close_read;
933   backend_class->read = do_read;
934   backend_class->enumerate = do_enumerate;
935   backend_class->query_info = do_query_info;
936   backend_class->try_query_fs_info = try_query_fs_info;
937 }
938