/* Clzip - LZMA lossless data compressor
Copyright (C) 2010-2017 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#define _FILE_OFFSET_BITS 64
#include
#include
#include
#include
#include
#include
#include
#include "lzip.h"
#include "file_index.h"
static int
seek_read(int fd, uint8_t *buf, int size, vlong pos)
{
if (lseek(fd, pos, SEEK_SET) == pos)
return readblock(fd, buf, size);
return 0;
}
static bool
add_error(File_index *fi, char *msg)
{
int len = strlen(msg);
void * tmp = resize_buffer(fi->error, fi->error_size + len + 1);
if (!tmp)
return false;
fi->error = (char *)tmp;
strncpy(fi->error + fi->error_size, msg, len + 1);
fi->error_size += len;
return true;
}
static bool
push_back_member(File_index *fi, vlong dp, vlong ds, vlong mp, vlong ms, unsigned dict_size)
{
Member *p;
void * tmp = resize_buffer(fi->member_vector,
(fi->members + 1) * sizeof fi->member_vector[0]);
if (!tmp) {
add_error( fi, "Not enough memory." );
fi->retval = 1;
return false;
}
fi->member_vector = (Member *)tmp;
p = &(fi->member_vector[fi->members]);
init_member(p, dp, ds, mp, ms, dict_size);
++fi->members;
return true;
}
static void
Fi_free_member_vector(File_index *fi)
{
if (fi->member_vector) {
free(fi->member_vector);
fi->member_vector = 0;
}
fi->members = 0;
}
static void
Fi_reverse_member_vector(File_index *fi)
{
Member tmp;
long i;
for (i = 0; i < fi->members / 2; ++i) {
tmp = fi->member_vector[i];
fi->member_vector[i] = fi->member_vector[fi->members-i-1];
fi->member_vector[fi->members-i-1] = tmp;
}
}
static void
Fi_set_errno_error(File_index *fi, char *msg)
{
add_error(fi, msg);
add_error(fi, strerror(errno));
fi->retval = 1;
}
static void
Fi_set_num_error(File_index *fi, char *msg, uvlong num)
{
char buf[80];
snprintf( buf, sizeof buf, "%s%llu", msg, num );
add_error(fi, buf);
fi->retval = 2;
}
/* If successful, push last member and set pos to member header. */
static bool
Fi_skip_trailing_data(File_index *fi, int fd, vlong *pos)
{
enum {
block_size = 16384,
buffer_size = block_size + Ft_size -1 + Fh_size
};
uint8_t buffer[buffer_size];
int bsize = *pos % block_size; /* total bytes in buffer */
int search_size, rd_size;
uvlong ipos;
int i;
if (bsize <= buffer_size - block_size)
bsize += block_size;
search_size = bsize; /* bytes to search for trailer */
rd_size = bsize; /* bytes to read from file */
ipos = *pos - rd_size; /* aligned to block_size */
if (*pos < min_member_size)
return false;
while (true) {
uint8_t max_msb = (ipos + search_size) >> 56;
if (seek_read(fd, buffer, rd_size, ipos) != rd_size) {
Fi_set_errno_error( fi, "Error seeking member trailer: " );
return false;
}
for (i = search_size; i >= Ft_size; --i)
if (buffer[i-1] <= max_msb) /* most significant byte of member_size */ {
File_header header;
File_trailer * trailer = (File_trailer *)(buffer + i - Ft_size);
uvlong member_size = Ft_get_member_size(*trailer);
unsigned dict_size;
if (member_size == 0) {
while (i > Ft_size && buffer[i-9] == 0)
--i;
continue;
}
if (member_size < min_member_size || member_size > ipos + i)
continue;
if (seek_read(fd, header, Fh_size,
ipos + i - member_size) != Fh_size) {
Fi_set_errno_error( fi, "Error reading member header: " );
return false;
}
dict_size = Fh_get_dict_size(header);
if (!Fh_verify_magic(header) || !Fh_verify_version(header) ||
!isvalid_ds(dict_size))
continue;
if (Fh_verify_prefix(buffer + i, bsize - i)) {
add_error( fi, "Last member in input file is truncated or corrupt." );
fi->retval = 2;
return false;
}
*pos = ipos + i - member_size;
return push_back_member(fi, 0, Ft_get_data_size(*trailer), *pos,
member_size, dict_size);
}
if (ipos <= 0) {
Fi_set_num_error( fi, "Member size in trailer is corrupt at pos ",
*pos - 8);
return false;
}
bsize = buffer_size;
search_size = bsize - Fh_size;
rd_size = block_size;
ipos -= rd_size;
memcpy(buffer + rd_size, buffer, buffer_size - rd_size);
}
}
bool
Fi_init(File_index *fi, int infd, bool ignore_trailing)
{
File_header header;
vlong pos;
long i;
fi->member_vector = 0;
fi->error = 0;
fi->isize = lseek(infd, 0, SEEK_END);
fi->members = 0;
fi->error_size = 0;
fi->retval = 0;
if (fi->isize < 0) {
Fi_set_errno_error( fi, "Input file is not seekable: " );
return false;
}
if (fi->isize < min_member_size) {
add_error( fi, "Input file is too short." );
fi->retval = 2;
return false;
}
if ((uvlong)fi->isize > INT64_MAX) {
add_error( fi, "Input file is too long (2^63 bytes or more)." );
fi->retval = 2;
return false;
}
if (seek_read(infd, header, Fh_size, 0) != Fh_size) {
Fi_set_errno_error( fi, "Error reading member header: " );
return false;
}
if (!Fh_verify_magic(header)) {
add_error(fi, bad_magic_msg);
fi->retval = 2;
return false;
}
if (!Fh_verify_version(header)) {
add_error(fi, bad_version(Fh_version(header)));
fi->retval = 2;
return false;
}
if (!isvalid_ds(Fh_get_dict_size(header))) {
add_error(fi, bad_dict_msg);
fi->retval = 2;
return false;
}
pos = fi->isize; /* always points to a header or to EOF */
while (pos >= min_member_size) {
File_trailer trailer;
uvlong member_size;
unsigned dict_size;
if (seek_read(infd, trailer, Ft_size, pos - Ft_size) != Ft_size) {
Fi_set_errno_error( fi, "Error reading member trailer: " );
break;
}
member_size = Ft_get_member_size(trailer);
if (member_size < min_member_size || member_size > (uvlong)pos) {
if (fi->members > 0)
Fi_set_num_error( fi, "Member size in trailer is corrupt at pos ",
pos - 8);
else if (Fi_skip_trailing_data(fi, infd, &pos)) {
if (ignore_trailing)
continue;
add_error(fi, trailing_msg);
fi->retval = 2;
return false;
}
break;
}
if (seek_read(infd, header, Fh_size, pos - member_size) != Fh_size) {
Fi_set_errno_error( fi, "Error reading member header: " );
break;
}
dict_size = Fh_get_dict_size(header);
if (!Fh_verify_magic(header) || !Fh_verify_version(header) ||
!isvalid_ds(dict_size)) {
if (fi->members > 0)
Fi_set_num_error( fi, "Bad header at pos ", pos - member_size );
else if (Fi_skip_trailing_data(fi, infd, &pos)) {
if (ignore_trailing)
continue;
add_error(fi, trailing_msg);
fi->retval = 2;
return false;
}
break;
}
pos -= member_size;
if (!push_back_member(fi, 0, Ft_get_data_size(trailer), pos,
member_size, dict_size))
return false;
}
if (pos != 0 || fi->members <= 0) {
Fi_free_member_vector(fi);
if (fi->retval == 0) {
add_error( fi, "Can't create file index." );
fi->retval = 2;
}
return false;
}
Fi_reverse_member_vector(fi);
for (i = 0; i < fi->members - 1; ++i) {
vlong end = block_end(fi->member_vector[i].dblock);
if (end < 0 || (uvlong)end > INT64_MAX) {
Fi_free_member_vector(fi);
add_error( fi, "Data in input file is too long (2^63 bytes or more)." );
fi->retval = 2;
return false;
}
fi->member_vector[i+1].dblock.pos = end;
}
return true;
}
void
Fi_free(File_index *fi)
{
Fi_free_member_vector(fi);
if (fi->error) {
free(fi->error);
fi->error = 0;
}
fi->error_size = 0;
}