1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* tar-method.c - The tar method implementation for the GNOME Virtual File
5 Copyright (C) 2002 Ximian, Inc
7 The Gnome Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 The Gnome Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with the Gnome Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.
22 Authors: Rachel Hestilow <hestilow@ximian.com>
23 Abigail Brady <morwen@evilmagic.org> (tarpet.h)
26 #include <libgnomevfs/gnome-vfs-method.h>
27 #include <libgnomevfs/gnome-vfs-mime.h>
28 #include <libgnomevfs/gnome-vfs-mime-utils.h>
29 #include <libgnomevfs/gnome-vfs-module.h>
30 #include <libgnomevfs/gnome-vfs-handle.h>
31 #include <libgnomevfs/gnome-vfs-file-info.h>
32 #include <libgnomevfs/gnome-vfs.h>
38 union TARPET_block* blocks;
48 union TARPET_block* start;
49 union TARPET_block* current;
53 gboolean is_directory;
56 #define TARPET_BLOCKSIZE (sizeof (union TARPET_block))
58 static GHashTable *tar_cache;
59 G_LOCK_DEFINE_STATIC (tar_cache);
61 #define iteration_initialize(condition, i, cond1, cond2) ((i) = (condition) ? (cond1) : (cond2))
62 #define iteration_check(condition, i, op1, op2, val1, val2) ((condition) ? (i op1 val1) : (i op2 val2))
63 #define iteration_iterate(condition, i, op1, op2) ((condition) ? (i op1) : (i op2))
65 #define parse_octal_field(v) (parse_octal ((v), sizeof (v)))
66 #define IS_OCTAL_DIGIT(c) ((c) >= '0' && (c) <= '8')
67 #define OCTAL_DIGIT(c) ((c) - '0')
69 static int parse_octal (const char *str, int len)
72 for (i = 0; i < len; i++)
74 if (str[i] == '\0') break;
75 else if (!IS_OCTAL_DIGIT (str[i])) return 0;
76 ret = ret * 8 + OCTAL_DIGIT (str[i]);
82 split_name_with_level (const gchar *name, gchar **first, gchar **last, int level, gboolean backwards)
87 if (name[strlen (name) - 1] == '/' && backwards)
90 for (iteration_initialize (backwards, i, strlen (name) - 1, 0);
91 iteration_check (backwards, i, >=, <, 0, strlen (name));
92 iteration_iterate (backwards, i, --, ++))
97 if (num_found >= level)
99 found = (gchar*) name + i;
106 *first = g_strndup (name, found - name + 1);
108 *last = g_strdup (found + 1);
114 *first = g_strdup (name);
120 split_name (const gchar *name, gchar **first, gchar **last)
122 split_name_with_level (name, first, last, 1, TRUE);
126 real_lookup_entry (const GNode *tree, const gchar *name, int level)
128 GNode *node, *ret = NULL;
131 split_name_with_level (name, &first, &rest, level, FALSE);
133 for (node = tree->children; node; node = node->next)
135 union TARPET_block *b = (union TARPET_block*) node->data;
136 if (!strcmp (b->raw.data, first))
139 ret = real_lookup_entry (node, name, level + 1);
144 else if (!strcmp (b->raw.data, name))
158 tree_lookup_entry (const GNode *tree, const gchar *name)
161 char *root = g_strdup (name);
167 ret = real_lookup_entry (tree, txt, 1);
168 if (!ret && txt[strlen (txt) - 1] != '/')
170 txt = g_strconcat (txt, "/", NULL);
173 ret = real_lookup_entry (tree, txt, 1);
177 if (ret && ret != tree->children)
179 union TARPET_block *b = ret->data;
181 if (b->p.typeflag == TARPET_TYPE_LONGFILEN)
188 static TarFile* read_tar_file (GnomeVFSHandle *handle)
190 GArray *arr = g_array_new (TRUE, TRUE, sizeof (union TARPET_block));
193 GnomeVFSFileSize bytes_read;
198 union TARPET_block b;
199 res = gnome_vfs_read (handle, b.raw.data,
200 TARPET_BLOCKSIZE, &bytes_read);
201 if (res == GNOME_VFS_OK)
202 g_array_append_val (arr, b);
203 } while (res == GNOME_VFS_OK && bytes_read > 0);
205 ret = g_new0 (TarFile, 1);
206 ret->blocks = (union TARPET_block*) arr->data;
207 ret->num_blocks = arr->len;
208 ret->info_tree = g_node_new (NULL);
210 for (i = 0; i < ret->num_blocks;)
215 int size = 0, maxsize;
218 if (!(*ret->blocks[i].p.name))
224 if (ret->blocks[i].p.typeflag == TARPET_TYPE_LONGFILEN)
230 split_name (ret->blocks[i].p.name, &dir, &rest);
231 node = tree_lookup_entry (ret->info_tree, dir);
235 node = ret->info_tree;
237 g_node_append (node, g_node_new (&(ret->blocks[i])));
242 maxsize = parse_octal_field (ret->blocks[i].p.size);
245 for (orig = i; i < ret->num_blocks && size < maxsize; i++)
247 int wsize = TARPET_BLOCKSIZE;
248 if ((maxsize - size) < TARPET_BLOCKSIZE)
249 wsize = maxsize - size;
260 g_array_free (arr, FALSE);
266 ensure_tarfile (GnomeVFSURI *uri)
269 GnomeVFSHandle *handle;
270 gchar *parent_string;
272 parent_string = gnome_vfs_uri_to_string (uri->parent, GNOME_VFS_URI_HIDE_NONE);
274 tar = g_hash_table_lookup (tar_cache, parent_string);
277 if (gnome_vfs_open_uri (&handle, uri->parent, GNOME_VFS_OPEN_READ) != GNOME_VFS_OK)
279 tar = read_tar_file (handle);
280 tar->filename = parent_string;
281 gnome_vfs_close (handle);
282 g_hash_table_insert (tar_cache, parent_string, tar);
284 G_UNLOCK (tar_cache);
291 tar_file_unref (TarFile *tar)
294 if (tar->ref_count < 0)
297 g_hash_table_remove (tar_cache, tar->filename);
298 G_UNLOCK (tar_cache);
299 g_free (tar->blocks);
300 g_node_destroy (tar->info_tree);
301 g_free (tar->filename);
306 static GnomeVFSResult
307 do_open (GnomeVFSMethod *method,
308 GnomeVFSMethodHandle **method_handle,
310 GnomeVFSOpenMode mode,
311 GnomeVFSContext *context)
314 FileHandle *new_handle;
316 union TARPET_block *start;
320 return GNOME_VFS_ERROR_INVALID_URI;
322 tar = ensure_tarfile (uri);
324 return GNOME_VFS_ERROR_BAD_FILE;
325 node = tree_lookup_entry (tar->info_tree, uri->text);
328 tar_file_unref (tar);
329 return GNOME_VFS_ERROR_NOT_FOUND;
333 if (start->p.name[strlen (start->p.name) - 1] == '/')
334 return GNOME_VFS_ERROR_IS_DIRECTORY;
335 new_handle = g_new0 (FileHandle, 1);
336 new_handle->tar = tar;
337 new_handle->filename = g_strdup (uri->text);
338 new_handle->start = start;
339 new_handle->current = new_handle->start;
340 new_handle->current_offset = 0;
341 for (i = 0; i < tar->num_blocks; i++)
342 if (start == &(tar->blocks[i]))
344 new_handle->current_index = i;
345 new_handle->is_directory = FALSE;
347 *method_handle = (GnomeVFSMethodHandle*) new_handle;
353 file_handle_unref (FileHandle *handle)
355 tar_file_unref (handle->tar);
356 g_free (handle->filename);
360 static GnomeVFSResult
361 do_close (GnomeVFSMethod *method,
362 GnomeVFSMethodHandle *method_handle,
363 GnomeVFSContext *context)
365 FileHandle *handle = (FileHandle*) method_handle;
367 file_handle_unref (handle);
372 static GnomeVFSResult
373 do_read (GnomeVFSMethod *method,
374 GnomeVFSMethodHandle *method_handle,
376 GnomeVFSFileSize num_bytes,
377 GnomeVFSFileSize *bytes_read,
378 GnomeVFSContext *context)
380 FileHandle *handle = (FileHandle*) method_handle;
381 int i, size = 0, maxsize;
383 if (handle->is_directory)
384 return GNOME_VFS_ERROR_IS_DIRECTORY;
386 maxsize = parse_octal_field (handle->start->p.size);
387 if (handle->current == handle->start)
389 handle->current_index++;
390 handle->current_offset = TARPET_BLOCKSIZE;
393 for (i = handle->current_index; i < handle->tar->num_blocks && handle->current_offset < (maxsize + TARPET_BLOCKSIZE) && size < num_bytes; i++)
395 int wsize = TARPET_BLOCKSIZE;
396 gpointer target_buf = (gchar*)buffer + size;
397 if ((maxsize - (handle->current_offset - TARPET_BLOCKSIZE)) < TARPET_BLOCKSIZE
398 && (maxsize - (handle->current_offset - TARPET_BLOCKSIZE)) > 0)
399 wsize = maxsize - handle->current_offset + TARPET_BLOCKSIZE;
400 else if (num_bytes < (size + wsize))
401 wsize = num_bytes - size;
403 handle->current_index = i + 1;
405 memcpy (target_buf, handle->start->raw.data + handle->current_offset, wsize);
408 handle->current_offset += wsize;
410 if (handle->current_index < handle->tar->num_blocks)
411 handle->current = &handle->tar->blocks[handle->current_index];
413 handle->current = NULL;
419 static GnomeVFSResult
420 do_seek (GnomeVFSMethod *method,
421 GnomeVFSMethodHandle *method_handle,
422 GnomeVFSSeekPosition whence,
423 GnomeVFSFileOffset offset,
424 GnomeVFSContext *context)
426 FileHandle *handle = (FileHandle*) method_handle;
427 GnomeVFSFileOffset current_offset;
431 case GNOME_VFS_SEEK_START:
434 case GNOME_VFS_SEEK_CURRENT:
435 current_offset = handle->current_offset;
437 case GNOME_VFS_SEEK_END:
438 current_offset = parse_octal_field (handle->start->p.size);
441 current_offset = handle->current_offset;
445 handle->current_offset = current_offset + offset;
449 static GnomeVFSResult
450 do_tell (GnomeVFSMethod *method,
451 GnomeVFSMethodHandle *method_handle,
452 GnomeVFSFileOffset *offset_return)
454 FileHandle *handle = (FileHandle*) method_handle;
455 *offset_return = handle->current_offset;
459 static GnomeVFSResult
460 do_open_directory (GnomeVFSMethod *method,
461 GnomeVFSMethodHandle **method_handle,
463 GnomeVFSFileInfoOptions options,
464 GnomeVFSContext *context)
467 FileHandle *new_handle;
468 union TARPET_block *start, *current;
473 return GNOME_VFS_ERROR_INVALID_URI;
474 tar = ensure_tarfile (uri);
477 node = tree_lookup_entry (tar->info_tree, uri->text);
480 tar_file_unref (tar);
481 return GNOME_VFS_ERROR_NOT_FOUND;
485 if (start->p.name[strlen (start->p.name) - 1] != '/')
486 return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
489 current = node->children->data;
495 node = tar->info_tree;
498 tar_file_unref (tar);
499 return GNOME_VFS_ERROR_NOT_FOUND;
503 start = node->children->data;
509 new_handle = g_new0 (FileHandle, 1);
510 new_handle->tar = tar;
511 new_handle->filename = g_strdup (tar->filename);
512 new_handle->start = start;
513 new_handle->current = current;
514 for (i = 0; i < tar->num_blocks; i++)
515 if (start == &(tar->blocks[i]))
517 new_handle->current_index = i;
518 new_handle->is_directory = TRUE;
520 *method_handle = (GnomeVFSMethodHandle*) new_handle;
525 static GnomeVFSResult
526 do_close_directory (GnomeVFSMethod *method,
527 GnomeVFSMethodHandle *method_handle,
528 GnomeVFSContext *context)
530 FileHandle *handle = (FileHandle*) method_handle;
532 file_handle_unref (handle);
537 static GnomeVFSResult
538 do_get_file_info (GnomeVFSMethod *method,
540 GnomeVFSFileInfo *file_info,
541 GnomeVFSFileInfoOptions options,
542 GnomeVFSContext *context)
544 TarFile *tar = ensure_tarfile (uri);
546 union TARPET_block *current;
553 node = tree_lookup_entry (tar->info_tree, uri->text);
555 node = tar->info_tree->children;
559 tar_file_unref (tar);
560 return GNOME_VFS_ERROR_NOT_FOUND;
563 current = node->data;
564 for (i = 0; i < tar->num_blocks; i++)
565 if (&(tar->blocks[i]) == current)
567 if (i && tar->blocks[i - 2].p.typeflag == TARPET_TYPE_LONGFILEN)
568 name = g_strdup (tar->blocks[i - 1].raw.data);
570 name = g_strdup (current->p.name);
572 file_info->name = g_path_get_basename (name);
573 if (name[strlen (name) - 1] == '/')
574 file_info->type = GNOME_VFS_FILE_TYPE_DIRECTORY;
575 else if (current->p.typeflag == TARPET_TYPE_SYMLINK)
577 file_info->type = GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK;
578 file_info->symlink_name = g_strdup (current->p.linkname);
581 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
583 file_info->permissions = parse_octal_field (current->p.mode);
584 file_info->uid = parse_octal_field (current->p.uid);
585 file_info->gid = parse_octal_field (current->p.gid);
586 file_info->size = parse_octal_field (current->p.size);
587 file_info->mtime = parse_octal_field (current->p.mtime);
588 file_info->atime = parse_octal_field (current->gnu.atime);
589 file_info->ctime = parse_octal_field (current->gnu.ctime);
591 if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY)
592 mime_type = "x-directory/normal";
593 else if (!(options & GNOME_VFS_FILE_INFO_FOLLOW_LINKS) && file_info->type == GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK)
594 mime_type = "x-special/symlink";
595 else if (!file_info->size || (options & GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE))
597 path = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
598 mime_type = (char*) gnome_vfs_get_file_mime_type (path, NULL, TRUE);
603 mime_type = (char*) gnome_vfs_get_mime_type_for_data ((current + 1)->raw.data, MIN (TARPET_BLOCKSIZE, file_info->size));
606 path = gnome_vfs_uri_to_string (uri, GNOME_VFS_URI_HIDE_NONE);
607 mime_type = (char*) gnome_vfs_get_file_mime_type (path, NULL, TRUE);
612 file_info->mime_type = g_strdup (mime_type);
614 file_info->valid_fields = GNOME_VFS_FILE_INFO_FIELDS_TYPE |
615 GNOME_VFS_FILE_INFO_FIELDS_PERMISSIONS |
616 GNOME_VFS_FILE_INFO_FIELDS_SIZE |
617 GNOME_VFS_FILE_INFO_FIELDS_ATIME |
618 GNOME_VFS_FILE_INFO_FIELDS_MTIME |
619 GNOME_VFS_FILE_INFO_FIELDS_CTIME |
620 GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
623 tar_file_unref (tar);
628 static GnomeVFSResult
629 do_read_directory (GnomeVFSMethod *method,
630 GnomeVFSMethodHandle *method_handle,
631 GnomeVFSFileInfo *file_info,
632 GnomeVFSContext *context)
634 FileHandle *handle = (FileHandle*) method_handle;
639 if (!handle->current)
640 return GNOME_VFS_ERROR_EOF;
642 str = g_strconcat (handle->filename, "#tar:", handle->current->p.name, NULL);
643 uri = gnome_vfs_uri_new (str);
644 do_get_file_info (method, uri, file_info, 0, context);
645 node = tree_lookup_entry (handle->tar->info_tree, uri->text);
648 gnome_vfs_uri_unref (uri);
649 return GNOME_VFS_ERROR_NOT_FOUND;
653 handle->current = node->next->data;
655 handle->current = NULL;
656 gnome_vfs_uri_unref (uri);
661 static GnomeVFSResult
662 do_get_file_info_from_handle (GnomeVFSMethod *method,
663 GnomeVFSMethodHandle *method_handle,
664 GnomeVFSFileInfo *file_info,
665 GnomeVFSFileInfoOptions options,
666 GnomeVFSContext *context)
668 FileHandle *handle = (FileHandle*) method_handle;
671 uri = gnome_vfs_uri_new (handle->start->p.name);
672 do_get_file_info (method, uri, file_info, options, context);
673 gnome_vfs_uri_unref (uri);
679 do_is_local (GnomeVFSMethod *method,
680 const GnomeVFSURI *uri)
682 return gnome_vfs_uri_is_local (uri->parent);
685 static GnomeVFSMethod method =
687 sizeof (GnomeVFSMethod),
700 do_get_file_info_from_handle,
716 vfs_module_init (const char *method_name, const char *args)
719 tar_cache = g_hash_table_new (g_str_hash, g_str_equal);
720 G_UNLOCK (tar_cache);
725 vfs_module_shutdown (GnomeVFSMethod *method)
728 g_hash_table_destroy (tar_cache);
729 G_UNLOCK (tar_cache);