ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / tar-method.c
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
3    System.
4
5    Copyright (C) 2002 Ximian, Inc
6
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.
11
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.
16
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.
21
22    Authors: Rachel Hestilow <hestilow@ximian.com>
23             Abigail Brady <morwen@evilmagic.org> (tarpet.h) 
24 */
25
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>
33 #include <string.h>
34 #include "tarpet.h"
35
36 typedef struct
37 {
38         union TARPET_block* blocks;
39         guint num_blocks;
40         GNode *info_tree;
41         int ref_count;
42         gchar *filename;
43 } TarFile;
44
45 typedef struct
46 {
47         TarFile *tar;
48         union TARPET_block* start;
49         union TARPET_block* current;
50         int current_offset;
51         int current_index;
52         gchar *filename;
53         gboolean is_directory;
54 } FileHandle;
55
56 #define TARPET_BLOCKSIZE (sizeof (union TARPET_block))
57
58 static GHashTable *tar_cache;
59 G_LOCK_DEFINE_STATIC (tar_cache);
60
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))
64
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')
68
69 static int parse_octal (const char *str, int len)
70 {
71         int i, ret = 0;
72         for (i = 0; i < len; i++)
73         {
74                 if (str[i] == '\0') break;
75                 else if (!IS_OCTAL_DIGIT (str[i])) return 0;
76                 ret = ret * 8 + OCTAL_DIGIT (str[i]);
77         }
78         return ret;
79 }
80
81 static void
82 split_name_with_level (const gchar *name, gchar **first, gchar **last, int level, gboolean backwards)
83 {
84         int i;
85         gchar *found = NULL;
86         int num_found = 0;
87         if (name[strlen (name) - 1] == '/' && backwards)
88                 level++;
89
90         for (iteration_initialize (backwards, i, strlen (name) - 1, 0);
91              iteration_check (backwards, i, >=, <, 0, strlen (name));
92              iteration_iterate (backwards, i, --, ++))
93         {
94                 if (name[i] == '/')
95                         num_found++;
96                 
97                 if (num_found >= level)
98                 {
99                         found = (gchar*) name + i;
100                         break;
101                 }
102         }
103
104         if (found)
105         {
106                 *first = g_strndup (name, found - name + 1);
107                 if (*(found + 1))
108                         *last = g_strdup (found + 1);
109                 else
110                         *last = NULL;
111         }
112         else
113         {
114                 *first = g_strdup (name);
115                 *last = NULL;
116         }
117 }
118
119 static void
120 split_name (const gchar *name, gchar **first, gchar **last)
121 {
122         split_name_with_level (name, first, last, 1, TRUE);
123 }
124
125 static GNode*
126 real_lookup_entry (const GNode *tree, const gchar *name, int level)
127 {
128         GNode *node, *ret = NULL;
129         gchar *first, *rest;
130
131         split_name_with_level (name, &first, &rest, level, FALSE);
132
133         for (node = tree->children; node; node = node->next)
134         {
135                 union TARPET_block *b = (union TARPET_block*) node->data;
136                 if (!strcmp (b->raw.data, first))
137                 {
138                         if (rest)
139                                 ret = real_lookup_entry (node, name, level + 1);
140                         else
141                                 ret = node;
142                         break;
143                 }
144                 else if (!strcmp (b->raw.data, name))
145                 {
146                         ret = node;
147                         break;
148                 }
149         }
150         g_free (first);
151         g_free (rest);
152         
153         
154         return ret;
155 }
156
157 static GNode*
158 tree_lookup_entry (const GNode *tree, const gchar *name)
159 {
160         GNode *ret;
161         char *root = g_strdup (name);
162         char *txt = root;
163         
164         if (txt[0] == '/')
165                 txt++;
166
167         ret = real_lookup_entry (tree, txt, 1);
168         if (!ret && txt[strlen (txt) - 1] != '/')
169         {
170                 txt = g_strconcat (txt, "/", NULL);
171                 g_free (root);
172                 root = txt;
173                 ret = real_lookup_entry (tree, txt, 1);
174         }
175         g_free (root);
176
177         if (ret && ret != tree->children)
178         {
179                 union TARPET_block *b = ret->data;
180                 b--;
181                 if (b->p.typeflag == TARPET_TYPE_LONGFILEN)
182                         ret = ret->next;
183         }
184
185         return ret;
186 }
187
188 static TarFile* read_tar_file (GnomeVFSHandle *handle)
189 {
190         GArray *arr = g_array_new (TRUE, TRUE, sizeof (union TARPET_block));
191         GnomeVFSResult res;
192         TarFile* ret;
193         GnomeVFSFileSize bytes_read;
194         int i;
195         
196         do
197         {
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);
204
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); 
209
210         for (i = 0; i < ret->num_blocks;)
211         {
212                 gchar *dir;
213                 gchar *rest;
214                 GNode *node;
215                 int size = 0, maxsize;
216                 int orig;
217
218                 if (!(*ret->blocks[i].p.name))
219                 {
220                         i++;
221                         continue;
222                 }
223
224                 if (ret->blocks[i].p.typeflag == TARPET_TYPE_LONGFILEN)
225                 {
226                         i++;
227                         continue;
228                 }
229                 
230                 split_name (ret->blocks[i].p.name, &dir, &rest);
231                 node = tree_lookup_entry (ret->info_tree, dir);
232                 
233                 if (!node)
234                 {
235                         node = ret->info_tree;
236                 }
237                 g_node_append (node, g_node_new (&(ret->blocks[i])));
238                 
239                 g_free (dir);
240                 g_free (rest);
241         
242                 maxsize = parse_octal_field (ret->blocks[i].p.size);
243                 if (maxsize)
244                 {
245                         for (orig = i; i < ret->num_blocks && size < maxsize; i++)
246                         {
247                                 int wsize = TARPET_BLOCKSIZE;
248                                 if ((maxsize - size) < TARPET_BLOCKSIZE)
249                                         wsize = maxsize - size;
250                                 size += wsize;
251                         }
252                         i++;
253                 }
254                 else
255                 {
256                         i++;
257                 }
258         }
259         
260         g_array_free (arr, FALSE);
261
262         return ret;
263 }
264
265 static TarFile* 
266 ensure_tarfile (GnomeVFSURI *uri)
267 {
268         TarFile *tar;
269         GnomeVFSHandle *handle;
270         gchar *parent_string;
271
272         parent_string = gnome_vfs_uri_to_string (uri->parent, GNOME_VFS_URI_HIDE_NONE);
273         G_LOCK (tar_cache);
274         tar = g_hash_table_lookup (tar_cache, parent_string);
275         if (!tar)
276         {
277                 if (gnome_vfs_open_uri (&handle, uri->parent, GNOME_VFS_OPEN_READ) != GNOME_VFS_OK)
278                         return NULL;
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);
283         }
284         G_UNLOCK (tar_cache);
285
286         tar->ref_count++;
287         return tar;
288 }
289
290 static void
291 tar_file_unref (TarFile *tar)
292 {
293         tar->ref_count--;
294         if (tar->ref_count < 0)
295         {
296                 G_LOCK (tar_cache);
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);
302                 g_free (tar);
303         }
304 }
305
306 static GnomeVFSResult
307 do_open (GnomeVFSMethod *method,
308          GnomeVFSMethodHandle **method_handle,
309          GnomeVFSURI *uri,
310          GnomeVFSOpenMode mode,
311          GnomeVFSContext *context)
312 {       
313         TarFile *tar;
314         FileHandle *new_handle;
315         GNode *node;
316         union TARPET_block *start;
317         int i;
318                 
319         if (!uri->parent)
320                 return GNOME_VFS_ERROR_INVALID_URI;
321         
322         tar = ensure_tarfile (uri);
323         if (!tar)
324                 return GNOME_VFS_ERROR_BAD_FILE;
325         node = tree_lookup_entry (tar->info_tree, uri->text);
326         if (!node)
327         {
328                 tar_file_unref (tar);
329                 return GNOME_VFS_ERROR_NOT_FOUND;
330         }
331         start = node->data;
332
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]))
343                         break;
344         new_handle->current_index = i;
345         new_handle->is_directory = FALSE;
346         
347         *method_handle = (GnomeVFSMethodHandle*) new_handle;
348         
349         return GNOME_VFS_OK;
350 }
351
352 static void
353 file_handle_unref (FileHandle *handle)
354 {
355         tar_file_unref (handle->tar);
356         g_free (handle->filename);
357         g_free (handle);
358 }
359
360 static GnomeVFSResult
361 do_close (GnomeVFSMethod *method,
362           GnomeVFSMethodHandle *method_handle,
363           GnomeVFSContext *context)
364 {
365         FileHandle *handle = (FileHandle*) method_handle;
366
367         file_handle_unref (handle);
368
369         return GNOME_VFS_OK;
370 }
371
372 static GnomeVFSResult
373 do_read (GnomeVFSMethod *method,
374          GnomeVFSMethodHandle *method_handle,
375          gpointer buffer,
376          GnomeVFSFileSize num_bytes,
377          GnomeVFSFileSize *bytes_read,
378          GnomeVFSContext *context)
379 {
380         FileHandle *handle = (FileHandle*) method_handle;
381         int i, size = 0, maxsize;
382         
383         if (handle->is_directory)
384                 return GNOME_VFS_ERROR_IS_DIRECTORY;
385
386         maxsize = parse_octal_field (handle->start->p.size);
387         if (handle->current == handle->start)
388         {
389                 handle->current_index++;
390                 handle->current_offset = TARPET_BLOCKSIZE;
391         }
392
393         for (i = handle->current_index; i < handle->tar->num_blocks && handle->current_offset < (maxsize + TARPET_BLOCKSIZE) && size < num_bytes; i++)
394         {
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;
402                 else
403                         handle->current_index = i + 1;
404                 
405                 memcpy (target_buf, handle->start->raw.data + handle->current_offset, wsize);
406                 
407                 size += wsize;
408                 handle->current_offset += wsize;
409         }
410         if (handle->current_index < handle->tar->num_blocks)
411                 handle->current = &handle->tar->blocks[handle->current_index]; 
412         else
413                 handle->current = NULL;
414         
415         *bytes_read = size;
416         return GNOME_VFS_OK;
417 }
418
419 static GnomeVFSResult
420 do_seek (GnomeVFSMethod *method,
421          GnomeVFSMethodHandle *method_handle,
422          GnomeVFSSeekPosition whence,
423          GnomeVFSFileOffset offset,
424          GnomeVFSContext *context)
425 {
426         FileHandle *handle = (FileHandle*) method_handle;
427         GnomeVFSFileOffset current_offset;
428
429         switch (whence)
430         {
431         case GNOME_VFS_SEEK_START:
432                 current_offset = 0;
433                 break;
434         case GNOME_VFS_SEEK_CURRENT:
435                 current_offset = handle->current_offset;
436                 break;
437         case GNOME_VFS_SEEK_END:
438                 current_offset = parse_octal_field (handle->start->p.size);
439                 break;
440         default:
441                 current_offset = handle->current_offset;
442                 break;
443         }
444
445         handle->current_offset = current_offset + offset;
446         return GNOME_VFS_OK;
447 }
448
449 static GnomeVFSResult
450 do_tell (GnomeVFSMethod *method,
451          GnomeVFSMethodHandle *method_handle,
452          GnomeVFSFileOffset *offset_return)
453 {
454         FileHandle *handle = (FileHandle*) method_handle;
455         *offset_return = handle->current_offset;
456         return GNOME_VFS_OK;
457 }
458
459 static GnomeVFSResult
460 do_open_directory (GnomeVFSMethod *method,
461                    GnomeVFSMethodHandle **method_handle,
462                    GnomeVFSURI *uri,
463                    GnomeVFSFileInfoOptions options,
464                    GnomeVFSContext *context)
465 {
466         TarFile *tar;
467         FileHandle *new_handle;
468         union TARPET_block *start, *current;
469         GNode *node;
470         int i;
471
472         if (!uri->parent)
473                 return GNOME_VFS_ERROR_INVALID_URI;
474         tar = ensure_tarfile (uri);
475         if (uri->text)
476         {
477                 node = tree_lookup_entry (tar->info_tree, uri->text);
478                 if (!node)
479                 {
480                         tar_file_unref (tar);
481                         return GNOME_VFS_ERROR_NOT_FOUND;
482                 }
483                 start = node->data;
484                 
485                 if (start->p.name[strlen (start->p.name) - 1] != '/')
486                         return GNOME_VFS_ERROR_NOT_A_DIRECTORY;
487
488                 if (node->children)
489                         current = node->children->data; 
490                 else
491                         current = NULL;
492         }
493         else
494         {
495                 node = tar->info_tree;
496                 if (!node)
497                 {
498                         tar_file_unref (tar);
499                         return GNOME_VFS_ERROR_NOT_FOUND;
500                 }
501
502                 if (node->children)
503                         start = node->children->data;
504                 else
505                         start = NULL;
506                 current = start;
507         }
508         
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]))
516                         break;
517         new_handle->current_index = i;
518         new_handle->is_directory = TRUE;
519
520         *method_handle = (GnomeVFSMethodHandle*) new_handle;
521         
522         return GNOME_VFS_OK;
523 }
524
525 static GnomeVFSResult
526 do_close_directory (GnomeVFSMethod *method,
527                     GnomeVFSMethodHandle *method_handle,
528                     GnomeVFSContext *context)
529 {
530         FileHandle *handle = (FileHandle*) method_handle;
531
532         file_handle_unref (handle);
533
534         return GNOME_VFS_OK;
535 }
536
537 static GnomeVFSResult 
538 do_get_file_info (GnomeVFSMethod *method,
539                   GnomeVFSURI *uri,
540                   GnomeVFSFileInfo *file_info,
541                   GnomeVFSFileInfoOptions options,
542                   GnomeVFSContext *context)
543 {
544         TarFile *tar = ensure_tarfile (uri);
545         GNode *node;
546         union TARPET_block *current;
547         gchar *name;
548         gchar *path;
549         char *mime_type;
550         int i;
551
552         if (uri->text)
553                 node = tree_lookup_entry (tar->info_tree, uri->text);
554         else
555                 node = tar->info_tree->children;
556         
557         if (!node)
558         {
559                 tar_file_unref (tar);
560                 return GNOME_VFS_ERROR_NOT_FOUND;
561         }
562
563         current = node->data;
564         for (i = 0; i < tar->num_blocks; i++)
565                 if (&(tar->blocks[i]) == current)
566                         break;
567         if (i && tar->blocks[i - 2].p.typeflag == TARPET_TYPE_LONGFILEN)
568                 name = g_strdup (tar->blocks[i - 1].raw.data);
569         else
570                 name = g_strdup (current->p.name);
571
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)
576         {
577                 file_info->type = GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK;
578                 file_info->symlink_name = g_strdup (current->p.linkname);
579         }
580         else
581                 file_info->type = GNOME_VFS_FILE_TYPE_REGULAR;
582
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);
590
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))
596         {
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);
599                 g_free (path);
600         }
601         else
602         {
603                 mime_type = (char*) gnome_vfs_get_mime_type_for_data ((current + 1)->raw.data, MIN (TARPET_BLOCKSIZE, file_info->size));
604                 if (!mime_type)
605                 {
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);
608                         g_free (path);
609                 }
610         }
611
612         file_info->mime_type = g_strdup (mime_type);
613
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;
621
622         g_free (name);
623         tar_file_unref (tar);
624
625         return GNOME_VFS_OK;
626 }
627
628 static GnomeVFSResult
629 do_read_directory (GnomeVFSMethod *method,
630                    GnomeVFSMethodHandle *method_handle,
631                    GnomeVFSFileInfo *file_info,
632                    GnomeVFSContext *context)
633 {
634         FileHandle *handle = (FileHandle*) method_handle;
635         GnomeVFSURI *uri;
636         gchar *str;
637         GNode *node;
638         
639         if (!handle->current)
640                 return GNOME_VFS_ERROR_EOF;
641
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);
646         if (!node)
647         {
648                 gnome_vfs_uri_unref (uri);
649                 return GNOME_VFS_ERROR_NOT_FOUND;
650         }
651         
652         if (node->next)
653                 handle->current = node->next->data;
654         else
655                 handle->current = NULL;
656         gnome_vfs_uri_unref (uri);
657
658         return GNOME_VFS_OK;
659 }
660
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)
667 {
668         FileHandle *handle = (FileHandle*) method_handle;
669         GnomeVFSURI *uri;
670         
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);
674
675         return GNOME_VFS_OK;
676 }
677
678 static gboolean
679 do_is_local (GnomeVFSMethod *method,
680              const GnomeVFSURI *uri)
681 {
682         return gnome_vfs_uri_is_local (uri->parent);
683 }
684
685 static GnomeVFSMethod method =
686 {
687         sizeof (GnomeVFSMethod),
688         do_open,
689         NULL,
690         do_close,
691         do_read,
692         NULL,
693         do_seek,
694         do_tell,
695         NULL,
696         do_open_directory,
697         do_close_directory,
698         do_read_directory,
699         do_get_file_info,
700         do_get_file_info_from_handle,
701         do_is_local,
702         NULL,
703         NULL,
704         NULL,
705         NULL,
706         NULL,
707         NULL,
708         NULL,
709         NULL,
710         NULL,
711         NULL,
712         NULL
713 };
714
715 GnomeVFSMethod *
716 vfs_module_init (const char *method_name, const char *args)
717 {
718         G_LOCK (tar_cache);
719         tar_cache = g_hash_table_new (g_str_hash, g_str_equal);
720         G_UNLOCK (tar_cache);
721         return &method;
722 }
723
724 void
725 vfs_module_shutdown (GnomeVFSMethod *method)
726 {
727         G_LOCK (tar_cache);
728         g_hash_table_destroy (tar_cache);
729         G_UNLOCK (tar_cache);
730 }