1 /*----------------------------------------------------------------------*\
2 |* spkg - The Unofficial Slackware Linux Package Manager *|
3 |* designed by Ondøej Jirman, 2005 *|
4 |*----------------------------------------------------------------------*|
5 |* No copy/usage restrictions are imposed on anybody. *|
6 \*----------------------------------------------------------------------*/
20 extern int posix_utime (const char *path
, struct utimbuf
*buf
);
21 extern int link(const char *existing
, const char *newfile
);
24 /* Enable or disable parent directory modification and access times
26 #define UNTGZ_PRESERVE_DIR_TIMES 0
28 #if UNTGZ_PRESERVE_DIR_TIMES == 1
34 /* optimal (according to the benchmark), must be multiple of 512 */
35 #define BLOCKBUFSIZE (512*100)
36 #define WRITEBUFSIZE (1024*16)
45 struct untgz_state_internal
{
49 mode_t old_umask
; /* saved umask */
51 gboolean errblock
; /* if set all operation is bloked and error is returned */
52 gboolean data
;/* data from the current file were not read, yet */
53 gboolean written
;/* current file was written to disk (or buffer) */
54 gboolean eof
;/* end of archive reached */
57 gzFile
* gzf
; /* gzio tar stream */
58 FILE* fp
; /* file stream */
60 /* internal block buffer data */
61 gchar bbuf
[BLOCKBUFSIZE
]; /* block buffer */
62 gchar
* bend
; /* points to the end of the buffer */
63 gchar
* bpos
; /* points to the current block */
64 gint blockid
; /* block id (512*blockid == position of the block in the input file) */
66 gchar wbuf
[WRITEBUFSIZE
]; /* write buffer */
70 ************************************************************************/
72 #define AREGTYPE '\0' /* regular file */
73 #define REGTYPE '0' /* regular file */
74 #define LNKTYPE '1' /* link */
75 #define SYMTYPE '2' /* reserved */
76 #define CHRTYPE '3' /* character special */
77 #define BLKTYPE '4' /* block special */
78 #define DIRTYPE '5' /* directory */
79 #define FIFOTYPE '6' /* FIFO special */
80 #define CONTTYPE '7' /* reserved */
82 /* implemented GNU tar extensions */
83 #define GNUTYPE_LONGLINK 'K' /* long link name */
84 #define GNUTYPE_LONGNAME 'L' /* long file name */
86 /* unimplemented GNU tar extensions */
87 #define GNUTYPE_SPARSE 'S' /* sparse file */
88 #define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */
89 #define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */
90 #define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */
91 #define GNUTYPE_VOLHDR 'V' /* tape/volume header */
94 #define SHORTNAMESIZE 100
96 struct tar_header
{ /* byte offset */
97 char name
[100]; /* 0 */
98 char mode
[8]; /* 100 */
99 char uid
[8]; /* 108 */
100 char gid
[8]; /* 116 */
101 char size
[12]; /* 124 */
102 char mtime
[12]; /* 136 */
103 char chksum
[8]; /* 148 */
104 char typeflag
; /* 156 */
105 char linkname
[100]; /* 157 */
106 char magic
[6]; /* 257 */
107 char version
[2]; /* 263 */
108 char uname
[32]; /* 265 */
109 char gname
[32]; /* 297 */
110 char devmajor
[8]; /* 329 */
111 char devminor
[8]; /* 337 */
112 char prefix
[155]; /* 345 */
120 #define e_set(n, fmt, args...) e_add(s->i->err, "untgz", __func__, n, fmt, ##args)
121 #define e_jump() longjmp(s->i->errjmp, 1)
123 #define _e_set(e, n, fmt, args...) e_add(e, "untgz", __func__, n, fmt, ##args)
125 #define e_throw(n, fmt, args...) \
127 e_set(n, fmt, ##args); \
131 /* optimized conversion from octal ascii digits to uint */
132 static guint
getoct(struct untgz_state
* s
, gchar
*p
, guint w
)
135 static const guchar oct_tab
[256] = {
137 ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3,
138 ['4'] = 4, ['5'] = 5, ['6'] = 6, ['7'] = 7,
139 ['\0'] = 0x20, [' '] = 0x20
144 guchar c
= oct_tab
[(guchar
)p
[i
]];
145 if (G_UNLIKELY(c
== 0x20))
147 else if (G_UNLIKELY(c
== 0x10))
148 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (bad oct - possibly trying to checksum data block)", s
->i
->blockid
);
155 /* header checksum validation */
156 static void validate_header_csum(struct untgz_state
* s
)
158 struct untgz_state_internal
* i
= s
->i
;
159 guint n
, head_csum
, real_csum
=0;
160 union tar_block
* b
= (union tar_block
*)i
->bpos
;
162 head_csum
= getoct(s
, b
->h
.chksum
, 6);
163 memcpy(b
->h
.chksum
, " ", 8);
164 for (n
=0; G_LIKELY(n
<BLOCKSIZE
); n
+=2)
165 real_csum
+= (guchar
)b
->b
[n
] + (guchar
)b
->b
[n
+1];
166 if (G_UNLIKELY(real_csum
!= head_csum
))
167 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (invalid header checksum)", i
->blockid
);
170 /* this function returns pointer to the next tar block
171 * return value is ptr if pointer points to the next block
172 * 0 if eof block occured (header with first byte zero)
173 * longjmp on read error (or incomplete block)
175 static union tar_block
* read_next_block(struct untgz_state
* s
, gboolean is_header
)
177 struct untgz_state_internal
* i
= s
->i
;
178 i
->bpos
+= BLOCKSIZE
;
179 if (i
->bpos
>= i
->bend
)
184 if (i
->comptype
== COMPTYPE_GZIP
)
185 read
= gzread(i
->gzf
, i
->bbuf
, BLOCKBUFSIZE
);
187 read
= fread(i
->bbuf
, 1, BLOCKBUFSIZE
, i
->fp
);
190 if (read
< BLOCKSIZE
)
193 if (i
->comptype
== COMPTYPE_GZIP
)
194 err
= (read
== 0 && !gzeof(i
->gzf
));
196 err
= (read
== 0 && !feof(i
->fp
));
198 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (gzread failed)", i
->blockid
);
200 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (early EOF)", i
->blockid
);
203 i
->bend
= i
->bbuf
+ read
;
207 if (G_UNLIKELY(i
->bpos
[0] == 0))
210 validate_header_csum(s
);
214 return (union tar_block
*)i
->bpos
;
217 /* append two strings (reallocate memmory) */
218 static gchar
* strnappend(gchar
* dst
, gchar
* src
, gsize size
)
220 gsize newsize
= 1+size
;
221 if (G_UNLIKELY(src
== 0))
223 if (G_LIKELY(dst
!= 0))
225 newsize
+= strlen(dst
);
226 dst
= g_realloc(dst
, newsize
);
229 dst
= g_malloc0(newsize
);
230 dst
= strncat(dst
, src
, size
);
235 ************************************************************************/
237 struct untgz_state
* untgz_open(const gchar
* tgzfile
, struct error
* e
)
239 struct untgz_state
* s
=0;
248 g_assert(tgzfile
!= 0);
251 if (stat(tgzfile
, &st
) == -1)
253 _e_set(e
, E_ERROR
, "can't stat file: %s", tgzfile
);
257 if (g_str_has_suffix(tgzfile
, ".tgz"))
258 comptype
= COMPTYPE_GZIP
;
259 else if (g_str_has_suffix(tgzfile
, ".tlz"))
260 comptype
= COMPTYPE_LZMA
;
261 else if (g_str_has_suffix(tgzfile
, ".txz"))
262 comptype
= COMPTYPE_XZ
;
263 else if (g_str_has_suffix(tgzfile
, ".tar"))
264 comptype
= COMPTYPE_NONE
;
267 _e_set(e
, E_ERROR
, "unknown package type: %s", tgzfile
);
271 if (comptype
== COMPTYPE_GZIP
)
273 gzf
= gzopen(tgzfile
, "rb");
276 _e_set(e
, E_ERROR
, "can't gzopen file: %s", tgzfile
);
280 else if (comptype
== COMPTYPE_LZMA
)
282 gchar
* escaped
= g_shell_quote(tgzfile
);
283 gchar
* cmd
= g_strdup_printf("lzma -d -c %s", escaped
);
284 fp
= popen(cmd
, "r");
288 _e_set(e
, E_ERROR
, "can't popen command: %s", cmd
);
294 else if (comptype
== COMPTYPE_XZ
)
296 gchar
* escaped
= g_shell_quote(tgzfile
);
297 gchar
* cmd
= g_strdup_printf("xzdec %s", escaped
);
298 fp
= popen(cmd
, "r");
302 _e_set(e
, E_ERROR
, "can't popen command: %s", cmd
);
308 else if (comptype
== COMPTYPE_NONE
)
310 fp
= fopen(tgzfile
, "r");
313 _e_set(e
, E_ERROR
, "can't open file: %s", tgzfile
);
318 s
= g_new0(struct untgz_state
,1);
319 s
->i
= g_new0(struct untgz_state_internal
,1);
320 struct untgz_state_internal
* i
= s
->i
;
321 s
->tgzfile
= g_strdup(tgzfile
);
324 i
->comptype
= comptype
;
325 s
->csize
= st
.st_size
;
326 i
->old_umask
= umask(0);
334 void untgz_close(struct untgz_state
* s
)
339 struct untgz_state_internal
* i
= s
->i
;
341 if (i
->comptype
== COMPTYPE_GZIP
)
343 else if (i
->comptype
== COMPTYPE_LZMA
|| i
->comptype
== COMPTYPE_XZ
)
352 s
->f_name
= s
->f_link
= s
->tgzfile
= 0;
361 print_timer(0, "[untgz] extraction");
362 print_timer(1, "[untgz] untgz_open");
363 print_timer(2, "[untgz] untgz_close");
364 print_timer(3, "[untgz] untgz_get_header");
365 print_timer(4, "[untgz] untgz_write_file");
366 print_timer(5, "[untgz] untgz_write_data");
368 print_timer(6, "[untgz] gzread");
369 print_timer(7, "[untgz] fwrite");
370 print_timer(8, "[untgz] chksum");
373 gint
untgz_get_header(struct untgz_state
* s
)
380 struct untgz_state_internal
* i
= s
->i
;
382 if (G_UNLIKELY(i
->errblock
))
384 e_set(E_ERROR
|UNTGZ_BLOCKED
, "[block:%d] untgz is blocked", i
->blockid
);
387 else if (G_UNLIKELY(i
->eof
))
389 if (G_UNLIKELY(setjmp(i
->errjmp
) != 0))
391 e_set(E_PASS
, "error thrown");
395 /* skip data blocks if write_data or write_file was not called after previous get_header */
396 if (s
->f_type
== UNTGZ_REG
&& i
->data
)
398 remaining
= s
->f_size
;
399 while (G_LIKELY(remaining
> 0))
401 if (read_next_block(s
, 0) == 0)
402 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (missing data block)", i
->blockid
);
403 remaining
= remaining
<=BLOCKSIZE
?0:remaining
-BLOCKSIZE
;
410 s
->f_name
= s
->f_link
= 0;
411 s
->f_type
= UNTGZ_NONE
;
417 /* read next header */
418 b
= read_next_block(s
, 1);
421 /* empty header => end of archive */
426 remaining
= getoct(s
, b
->h
.size
, sizeof(b
->h
.size
));
427 /* read longname and/or longlink */
428 if (b
->h
.typeflag
== GNUTYPE_LONGLINK
)
430 while (G_LIKELY(remaining
> 0))
432 b
= read_next_block(s
, 0);
434 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (missing longlink data block)", i
->blockid
);
435 s
->f_link
= strnappend(s
->f_link
, (gchar
*)b
->b
, BLOCKSIZE
);
436 remaining
= remaining
<=BLOCKSIZE
?0:remaining
-BLOCKSIZE
;
440 else if (b
->h
.typeflag
== GNUTYPE_LONGNAME
)
442 while (G_LIKELY(remaining
> 0))
444 b
= read_next_block(s
, 0);
446 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (missing longname data block)", i
->blockid
);
447 s
->f_name
= strnappend(s
->f_name
, (gchar
*)b
->b
, BLOCKSIZE
);
448 remaining
= remaining
<=BLOCKSIZE
?0:remaining
-BLOCKSIZE
;
456 s
->f_size
= remaining
;
457 s
->f_mode
= getoct(s
, b
->h
.mode
, sizeof(b
->h
.mode
)) & 07777;
458 s
->f_uid
= getoct(s
, b
->h
.uid
, sizeof(b
->h
.uid
));
459 s
->f_gid
= getoct(s
, b
->h
.gid
, sizeof(b
->h
.gid
));
460 s
->f_mtime
= (time_t) getoct(s
, b
->h
.mtime
, sizeof(b
->h
.mtime
));
461 s
->f_devmaj
= getoct(s
, b
->h
.devmajor
, sizeof(b
->h
.devmajor
));
462 s
->f_devmin
= getoct(s
, b
->h
.devminor
, sizeof(b
->h
.devminor
));
463 strncpy(s
->f_uname
, b
->h
.uname
, sizeof(b
->h
.uname
));
464 strncpy(s
->f_gname
, b
->h
.gname
, sizeof(b
->h
.gname
));
466 switch (b
->h
.typeflag
)
469 case REGTYPE
: s
->f_type
= UNTGZ_REG
; i
->data
= 1; s
->usize
+= remaining
; break;
470 case DIRTYPE
: s
->f_type
= UNTGZ_DIR
; break;
471 case SYMTYPE
: s
->f_type
= UNTGZ_SYM
; break;
472 case LNKTYPE
: s
->f_type
= UNTGZ_LNK
; break;
473 case CHRTYPE
: s
->f_type
= UNTGZ_CHR
; break;
474 case BLKTYPE
: s
->f_type
= UNTGZ_BLK
; break;
475 case FIFOTYPE
: s
->f_type
= UNTGZ_FIFO
; break;
476 default: e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] \"corrupted\" tgz archive (unimplemented typeflag [%c])", i
->blockid
, b
->h
.typeflag
);
480 s
->f_name
= g_strndup(b
->h
.name
, SHORTNAMESIZE
);
481 /* just one more (unnecessary) check */
482 else if (strncmp(s
->f_name
, b
->h
.name
, SHORTNAMESIZE
-1)) /* -1 because it's zero terminated */
483 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (longname mismatch)", i
->blockid
);
485 s
->f_link
= g_strndup(b
->h
.linkname
, SHORTNAMESIZE
);
486 /* just one more (unnecessary) check */
487 else if (strncmp(s
->f_link
, b
->h
.linkname
, SHORTNAMESIZE
-1)) /* -1 because it's zero terminated */
488 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (longlink mismatch)", i
->blockid
);
500 gint
untgz_write_data(struct untgz_state
* s
, gchar
** buf
, gsize
* len
)
513 struct untgz_state_internal
* i
= s
->i
;
515 if (G_UNLIKELY(i
->errblock
))
517 e_set(E_ERROR
|UNTGZ_BLOCKED
, "[block:%d] untgz is blocked", i
->blockid
);
520 else if (i
->eof
|| s
->f_type
!= UNTGZ_REG
|| i
->data
== 0) /* no data for current file */
522 if (setjmp(i
->errjmp
) != 0)
525 e_set(E_PASS
, "error thrown");
530 buffer
= g_malloc(remaining
+1);
531 while (G_LIKELY(remaining
> 0))
533 b
= read_next_block(s
, 0);
535 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (missing data block)", i
->blockid
);
536 memcpy(buffer
+position
, b
, remaining
>BLOCKSIZE
?BLOCKSIZE
:remaining
);
537 remaining
= remaining
<=BLOCKSIZE
?0:remaining
-BLOCKSIZE
;
538 position
+= BLOCKSIZE
;
541 buffer
[s
->f_size
] = 0; /* zero terminate buffer */
555 gint
untgz_write_file(struct untgz_state
* s
, gchar
* altname
)
561 #if UNTGZ_PRESERVE_DIR_TIMES == 1
562 gchar
*dpath_tmp
= 0, *dpath
;
567 struct untgz_state_internal
* i
= s
->i
;
570 if (G_UNLIKELY(i
->errblock
))
572 e_set(E_ERROR
|UNTGZ_BLOCKED
, "[block:%d] untgz is blocked", i
->blockid
);
575 else if (G_UNLIKELY(i
->written
|| i
->eof
))
577 if (G_UNLIKELY(setjmp(i
->errjmp
) != 0))
579 #if UNTGZ_PRESERVE_DIR_TIMES == 1
582 e_set(E_PASS
, "error thrown");
591 #if UNTGZ_PRESERVE_DIR_TIMES == 1
594 dpath_tmp
= g_strdup(path
);
595 dpath
= dirname(dpath_tmp
);
596 if (stat(dpath
, &st
) == -1)
597 e_throw(E_ERROR
, "can't stat parent directory: %s", strerror(errno
));
598 dt
.actime
= st
.st_atime
;
599 dt
.modtime
= st
.st_mtime
;
609 remaining
= s
->f_size
;
610 FILE* f
= fopen(path
, "w");
612 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't open file for writing: %s", strerror(errno
));
613 if (setvbuf(f
, i
->wbuf
, _IOFBF
, WRITEBUFSIZE
))
614 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't setup write buffer");
615 while (G_LIKELY(remaining
> 0))
617 b
= read_next_block(s
, 0);
619 e_throw(E_ERROR
|UNTGZ_CORRUPT
, "[block:%d] corrupted tgz archive (missing data block)", i
->blockid
);
621 if (fwrite(b
->b
, remaining
>BLOCKSIZE
?BLOCKSIZE
:remaining
, 1, f
) != 1)
622 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't write to a file: %s", strerror(errno
));
624 remaining
= remaining
<=BLOCKSIZE
?0:remaining
-BLOCKSIZE
;
632 if (symlink(s
->f_link
, path
) == -1)
634 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create symlink: %s", strerror(errno
));
638 if (mkfifo(path
, s
->f_mode
) == -1)
640 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create fifo: %s", strerror(errno
));
643 printf("** ln %s %s", s
->f_link
, path
);
644 if (link(s
->f_link
, path
) == -1)
645 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create hardlink: %s", strerror(errno
));
649 if (mknod(path
, S_IFCHR
, (dev_t
)((((s
->f_devmaj
& 0xFF)
650 << 8) & 0xFF00) | (s
->f_devmin
& 0xFF))) == -1)
652 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create chrdev: %s", strerror(errno
));
656 if (mknod(path
, S_IFBLK
, (dev_t
)((((s
->f_devmaj
& 0xFF)
657 << 8) & 0xFF00) | (s
->f_devmin
& 0xFF))) == -1)
659 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create blkdev: %s", strerror(errno
));
662 /* because of the way tar stores directories, there
663 is no need to have mkdir_r here */
665 if (mkdir(path
) == -1 && errno
!= EEXIST
)
667 if (mkdir(path
, 0700) == -1 && errno
!= EEXIST
)
669 e_throw(E_ERROR
|UNTGZ_BADIO
, "can't create directory: %s", strerror(errno
));
672 e_throw(E_ERROR
, "unknown file type [%d]", s
->f_type
);
677 if (chown(path
, s
->f_uid
, s
->f_gid
) == -1)
678 e_throw(E_ERROR
|UNTGZ_BADMETA
, "can't chown file: %s", strerror(errno
));
680 if (chmod(path
, s
->f_mode
) == -1)
681 e_throw(E_ERROR
|UNTGZ_BADMETA
, "can't chmod file: %s", strerror(errno
));
682 t
.actime
= t
.modtime
= s
->f_mtime
;
684 if (posix_utime(path
, &t
) == -1)
686 if (utime(path
, &t
) == -1)
689 e_throw(E_ERROR
|UNTGZ_BADMETA
, "can't utime file: %s", strerror(errno
));
692 #if UNTGZ_PRESERVE_DIR_TIMES == 1
694 if (posix_utime(dpath
, &dt
) == -1)
696 if (utime(dpath
, &dt
) == -1)
698 e_throw(E_ERROR
|UNTGZ_BADMETA
, "can't utime parent directory: %s", strerror(errno
));