"Initial commit to Gerrit"
[profile/ivi/libgsf.git] / gsf / gsf-infile-tar.c
1 /*
2  * gsf-infile-tar.c :
3  *
4  * Copyright (C) 2008 Morten Welinder (terra@gnome.org)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  *
20  * TODO:
21  *   symlinks
22  *   hardlinks
23  *   weird headers
24  */
25
26 #include <gsf-config.h>
27 #include <gsf/gsf-infile-impl.h>
28 #include <gsf/gsf-infile-tar.h>
29 #include <gsf/gsf-impl-utils.h>
30 #include <gsf/gsf-utils.h>
31 #include <gsf/gsf-input-proxy.h>
32
33 #include <string.h>
34
35 #undef G_LOG_DOMAIN
36 #define G_LOG_DOMAIN "libgsf:tar"
37
38 static void gsf_infile_tar_set_source (GsfInfileTar *tar, GsfInput *src);
39
40 enum {
41         PROP_0,
42         PROP_SOURCE
43 };
44
45 static GObjectClass *parent_class;
46
47 typedef struct {
48         char *name;
49
50         /* The location of data.  */
51         gsf_off_t offset;
52         gsf_off_t length;
53
54         /* The directory object, or NULL for a data file.  */
55         GsfInfileTar *dir;
56 } TarChild;
57
58 /* tar header from POSIX 1003.1-1990.  */
59 typedef struct {
60         char name[100];               /*   0 */
61         char mode[8];                 /* 100 (octal) */
62         char uid[8];                  /* 108 (octal) */
63         char gid[8];                  /* 116 (octal) */
64         char size[12];                /* 124 (octal) */
65         char mtime[12];               /* 136 (octal) */
66         char chksum[8];               /* 148 (octal) */
67         char typeflag;                /* 156 */
68         char linkname[100];           /* 157 */
69         char magic[6];                /* 257 */
70         char version[2];              /* 263 */
71         char uname[32];               /* 265 */
72         char gname[32];               /* 297 */
73         char devmajor[8];             /* 329 (octal) */
74         char devminor[8];             /* 337 (octal) */
75         char prefix[155];             /* 345 */
76         char filler[12];              /* 500 */
77 } TarHeader;
78
79 #define HEADER_SIZE (sizeof (TarHeader))
80 #define BLOCK_SIZE 512
81 #define MAGIC_LONGNAME "././@LongLink"
82
83 struct _GsfInfileTar {
84         GsfInfile parent;
85
86         GsfInput *source;
87         GArray *children;  /* of TarChild */
88         GError *err;
89 };
90
91 typedef struct {
92         GsfInfileClass  parent_class;
93 } GsfInfileTarClass;
94
95 #define GSF_INFILE_TAR_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), GSF_INFILE_TAR_TYPE, GsfInfileTarClass))
96 #define GSF_IS_INFILE_TAR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSF_INFILE_TAR_TYPE))
97
98 static gsf_off_t
99 unpack_octal (GsfInfileTar *tar, const char *s, size_t len)
100 {
101         gsf_off_t res = 0;
102
103         while (len--) {
104                 unsigned char c = *s++;
105                 if (c == 0)
106                         break;
107                 if (c < '0' || c > '7') {
108                         tar->err = g_error_new (gsf_input_error_id (), 0,
109                                                 "Invalid tar header");
110                         return 0;
111                 }
112                 res = (res << 3) + (c - '0');
113         }
114
115         return res;
116 }
117
118
119 static GsfInfileTar *
120 tar_create_dir (GsfInfileTar *dir, const char *name)
121 {
122         TarChild c;
123
124         c.offset = 0;
125         c.length = 0;
126         c.name = g_strdup (name);
127         c.dir = g_object_new (GSF_INFILE_TAR_TYPE, NULL);
128
129         /*
130          * We set the source here, so gsf_infile_tar_constructor doesn't
131          * start reading the tarfile recursively.
132          */
133         gsf_infile_tar_set_source (c.dir, dir->source);
134
135         gsf_input_set_name (GSF_INPUT (c.dir), name);
136
137         g_array_append_val (dir->children, c);
138
139         return c.dir;
140 }
141
142 static GsfInfileTar *
143 tar_directory_for_file (GsfInfileTar *dir, const char *name, gboolean last)
144 {
145         const char *s = name;
146
147         while (1) {
148                 const char *s0 = s;
149                 char *dirname;
150
151                 /* Find a directory component, if any.  */
152                 while (1) {
153                         if (*s == 0) {
154                                 if (last && s != s0)
155                                         break;
156                                 else
157                                         return dir;
158                         }
159                         /* This is deliberately slash-only.  */
160                         if (*s == '/')
161                                 break;
162                         s++;
163                 }
164
165                 dirname = g_strndup (s0, s - s0);
166                 while (*s == '/')
167                         s++;
168
169                 if (strcmp (dirname, ".") != 0) {
170                         GsfInput *subdir =
171                                 gsf_infile_child_by_name (GSF_INFILE (dir),
172                                                           dirname);
173                         if (subdir) {
174                                 /* Undo the ref. */
175                                 g_object_unref (subdir);
176                                 dir = GSF_INFILE_TAR (subdir);
177                         } else
178                                 dir = tar_create_dir (dir, dirname);
179                 }
180
181                 g_free (dirname);
182         }
183 }
184
185
186 /*****************************************************************************/
187
188 /**
189  * tar_init_info :
190  * @tar : #GsfInfileTar
191  *
192  * Read tar headers and do some sanity checking
193  * along the way.
194  **/
195 static void
196 tar_init_info (GsfInfileTar *tar)
197 {
198         TarHeader end;
199         const TarHeader *header;
200         gsf_off_t pos0 = gsf_input_tell (tar->source);
201         char *pending_longname = NULL;
202
203         memset (&end, 0, sizeof (end));
204
205         while (tar->err == NULL &&
206                (header = (const TarHeader *)gsf_input_read (tar->source,
207                                                             HEADER_SIZE,
208                                                             NULL))) {
209                 char *name;
210                 gsf_off_t length;
211                 gsf_off_t offset;
212
213                 if (memcmp (header->filler, end.filler, sizeof (end.filler))) {
214                         tar->err = g_error_new (gsf_input_error_id (), 0,
215                                                 "Invalid tar header");
216                         break;
217                 }
218
219                 if (memcmp (header, &end, HEADER_SIZE) == 0)
220                         break;
221
222                 if (pending_longname) {
223                         name = pending_longname;
224                         pending_longname = NULL;
225                 } else
226                         name = g_strndup (header->name, sizeof (header->name));
227                 length = unpack_octal (tar, header->size, sizeof (header->size));
228                 offset = gsf_input_tell (tar->source);
229
230 #if 0
231                 g_printerr ("[%s]: %d\n", name, (int)length);
232 #endif
233
234                 switch (header->typeflag) {
235                 case '0': case 0: {
236                         /* Regular file. */
237                         GsfInfileTar *dir;
238                         const char *n = name, *s;
239                         TarChild c;
240
241                         /* This is deliberately slash-only.  */
242                         while ((s = strchr (n, '/')))
243                                 n = s + 1;
244                         c.name = g_strdup (n);
245                         c.offset = offset;
246                         c.length = length;
247                         c.dir = NULL;
248                         dir = tar_directory_for_file (tar, name, FALSE);
249                         g_array_append_val (dir->children, c);
250                         break;
251                 }
252                 case '5': {
253                         /* Directory */
254                         (void)tar_directory_for_file (tar, name, TRUE);
255                         break;
256                 }
257                 case 'L': {
258                         const char *n;
259
260                         if (pending_longname ||
261                             strcmp (name, MAGIC_LONGNAME) != 0) {
262                                 tar->err = g_error_new (gsf_input_error_id (), 0,
263                                                         "Invalid longname header");
264                                 break;
265                         }
266
267                         n = gsf_input_read (tar->source, length, NULL);
268                         if (!n) {
269                                 tar->err = g_error_new (gsf_input_error_id (), 0,
270                                                         "Failed to read longname");
271                                 break;
272                         }
273
274                         pending_longname = g_strndup (n, length);
275                         break;
276                 }
277                 default:
278                         /* Other -- ignore */
279                         break;
280                 }
281
282                 g_free (name);
283
284                 /* Round up to block size */
285                 length = (length + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE;
286                 
287                 if (!tar->err &&
288                     gsf_input_seek (tar->source, offset + length, G_SEEK_SET)) {
289                         tar->err = g_error_new (gsf_input_error_id (), 0,
290                                                 "Seek failed");
291                         break;
292                 }
293         }
294
295         if (pending_longname) {
296                 if (!tar->err)
297                         tar->err = g_error_new (gsf_input_error_id (), 0,
298                                                 "Truncated archive");
299                 g_free (pending_longname);
300         }
301
302         if (tar->err)
303                 gsf_input_seek (tar->source, pos0, G_SEEK_SET);
304 }
305
306
307 /* GsfInput class functions */
308
309 static GsfInput *
310 gsf_infile_tar_dup (GsfInput *src_input, GError **err)
311 {
312         GsfInfileTar *res, *src;
313         unsigned ui;
314
315         src = GSF_INFILE_TAR (src_input);
316         if (src->err) {
317                 if (err)
318                         *err = g_error_copy (src->err);
319                 return NULL;
320         }
321
322         res = (GsfInfileTar *)g_object_new (GSF_INFILE_TAR_TYPE, NULL);
323         gsf_infile_tar_set_source (res, src->source);
324
325         for (ui = 0; ui < src->children->len; ui++) {
326                 /* This copies the structure.  */
327                 TarChild c = g_array_index (src->children, TarChild, ui);
328                 c.name = g_strdup (c.name);
329                 if (c.dir) g_object_ref (c.dir);
330                 g_array_append_val (res->children, c);
331         }
332
333         return NULL;
334 }
335
336 static guint8 const *
337 gsf_infile_tar_read (GsfInput *input, size_t num_bytes, guint8 *buffer)
338 {
339         (void)input;
340         (void)num_bytes;
341         (void)buffer;
342         return NULL;
343 }
344
345 static gboolean
346 gsf_infile_tar_seek (GsfInput *input, gsf_off_t offset, GSeekType whence)
347 {
348         (void)input;
349         (void)offset;
350         (void)whence;
351         return FALSE;
352 }
353
354 /* GsfInfile class functions */
355
356 /*****************************************************************************/
357
358 static GsfInput *
359 gsf_infile_tar_child_by_index (GsfInfile *infile, int target, GError **err)
360 {
361         GsfInfileTar *tar = GSF_INFILE_TAR (infile);
362         const TarChild *c;
363
364         if (err)
365                 *err = NULL;
366
367         if (target < 0 || (unsigned)target >= tar->children->len)
368                 return NULL;
369
370         c = &g_array_index (tar->children, TarChild, target);
371         if (c->dir)
372                 return g_object_ref (c->dir);
373         else {
374                 GsfInput *input = gsf_input_proxy_new_section (tar->source,
375                                                                c->offset,
376                                                                c->length);
377                 gsf_input_set_name (input, c->name);
378                 return input;
379         }
380 }
381
382 static char const *
383 gsf_infile_tar_name_by_index (GsfInfile *infile, int target)
384 {
385         GsfInfileTar *tar = GSF_INFILE_TAR (infile);
386
387         if (target < 0 || (unsigned)target >= tar->children->len)
388                 return NULL;
389
390         return g_array_index (tar->children, TarChild, target).name;
391 }
392
393 static GsfInput *
394 gsf_infile_tar_child_by_name (GsfInfile *infile, char const *name, GError **err)
395 {
396         GsfInfileTar *tar = GSF_INFILE_TAR (infile);
397         unsigned ui;
398
399         for (ui = 0; ui < tar->children->len; ui++) {
400                 const TarChild *c = &g_array_index (tar->children,
401                                                     TarChild,
402                                                     ui);
403                 if (strcmp (name, c->name) == 0)
404                         return gsf_infile_tar_child_by_index (infile, ui, err);
405         }
406
407         return NULL;
408 }
409
410 static int
411 gsf_infile_tar_num_children (GsfInfile *infile)
412 {
413         GsfInfileTar *tar = GSF_INFILE_TAR (infile);
414
415         return tar->children->len;
416 }
417
418 static void
419 gsf_infile_tar_finalize (GObject *obj)
420 {
421         GsfInfileTar *tar = GSF_INFILE_TAR (obj);
422
423         if (tar->source != NULL) {
424                 g_object_unref (G_OBJECT (tar->source));
425                 tar->source = NULL;
426         }
427
428         if (tar->children) {
429                 unsigned ui;
430                 for (ui = 0; ui < tar->children->len; ui++) {
431                         TarChild *c = &g_array_index (tar->children,
432                                                       TarChild,
433                                                       ui);
434                         g_free (c->name);
435                         if (c->dir)
436                                 g_object_unref (c->dir);
437                 }
438                 g_array_free (tar->children, TRUE);
439         }
440
441         g_clear_error (&tar->err);
442
443         parent_class->finalize (obj);
444 }
445
446 static GObject*
447 gsf_infile_tar_constructor (GType                  type,
448                             guint                  n_construct_properties,
449                             GObjectConstructParam *construct_params)
450 {
451         GsfInfileTar *tar = (GsfInfileTar *)
452                 (parent_class->constructor (type,
453                                             n_construct_properties,
454                                             construct_params));
455         if (tar->source)
456                 tar_init_info (tar);
457
458         return (GObject *)tar;
459 }
460
461
462 static void
463 gsf_infile_tar_init (GObject *obj)
464 {
465         GsfInfileTar *tar = (GsfInfileTar *)obj;
466         tar->source = NULL;
467         tar->children = g_array_new (FALSE, FALSE, sizeof (TarChild));
468         tar->err = NULL;
469 }
470
471 static void
472 gsf_infile_tar_get_property (GObject     *object,
473                              guint        property_id,
474                              GValue      *value,
475                              GParamSpec  *pspec)
476 {
477         GsfInfileTar *tar = (GsfInfileTar *)object;
478
479         switch (property_id) {
480         case PROP_SOURCE:
481                 g_value_set_object (value, tar->source);
482                 break;
483         default:
484                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
485                 break;
486         }
487 }
488
489 static void
490 gsf_infile_tar_set_source (GsfInfileTar *tar, GsfInput *src)
491 {
492         if (src)
493                 src = gsf_input_proxy_new (src);
494         if (tar->source)
495                 g_object_unref (tar->source);
496         tar->source = src;
497 }
498
499 static void
500 gsf_infile_tar_set_property (GObject      *object,
501                              guint         property_id,
502                              GValue const *value,
503                              GParamSpec   *pspec)
504 {
505         GsfInfileTar *tar = (GsfInfileTar *)object;
506
507         switch (property_id) {
508         case PROP_SOURCE:
509                 gsf_infile_tar_set_source (tar, g_value_get_object (value));
510                 break;
511         default:
512                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
513                 break;
514         }
515 }
516
517
518 static void
519 gsf_infile_tar_class_init (GObjectClass *gobject_class)
520 {
521         GsfInputClass  *input_class  = GSF_INPUT_CLASS (gobject_class);
522         GsfInfileClass *infile_class = GSF_INFILE_CLASS (gobject_class);
523
524         gobject_class->constructor      = gsf_infile_tar_constructor;
525         gobject_class->finalize         = gsf_infile_tar_finalize;
526         gobject_class->get_property     = gsf_infile_tar_get_property;
527         gobject_class->set_property     = gsf_infile_tar_set_property;
528
529         input_class->Dup                = gsf_infile_tar_dup;
530         input_class->Read               = gsf_infile_tar_read;
531         input_class->Seek               = gsf_infile_tar_seek;
532         infile_class->num_children      = gsf_infile_tar_num_children;
533         infile_class->name_by_index     = gsf_infile_tar_name_by_index;
534         infile_class->child_by_index    = gsf_infile_tar_child_by_index;
535         infile_class->child_by_name     = gsf_infile_tar_child_by_name;
536
537         parent_class = g_type_class_peek_parent (gobject_class);
538
539         g_object_class_install_property
540                 (gobject_class,
541                  PROP_SOURCE,
542                  g_param_spec_object ("source",
543                                       "Source",
544                                       "The archive being interpreted.",
545                                       GSF_INPUT_TYPE,
546                                       GSF_PARAM_STATIC |
547                                       G_PARAM_READWRITE |
548                                       G_PARAM_CONSTRUCT_ONLY));
549 }
550
551 GSF_CLASS (GsfInfileTar, gsf_infile_tar,
552            gsf_infile_tar_class_init, gsf_infile_tar_init,
553            GSF_INFILE_TYPE)
554
555 /**
556  * gsf_infile_tar_new :
557  * @source: A base #GsfInput
558  * @err: A #GError, optionally %null
559  *
560  * Opens the root directory of a Tar file.
561  * <note>This adds a reference to @source.</note>
562  *
563  * Returns: the new tar file handler
564  **/
565 GsfInfile *
566 gsf_infile_tar_new (GsfInput *source, GError **err)
567 {
568         GsfInfileTar *tar;
569
570         g_return_val_if_fail (GSF_IS_INPUT (source), NULL);
571
572         tar = g_object_new (GSF_INFILE_TAR_TYPE,
573                             "source", source,
574                             NULL);
575
576         if (tar->err) {
577                 if (err)
578                         *err = g_error_copy (tar->err);
579                 g_object_unref (tar);
580                 return NULL;
581         }
582
583         return GSF_INFILE (tar);
584 }