/*
* Copyright (C) 2023 Patrick McDermott
*
* This file is part of opkg-opk.
*
* opkg-opk 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 3 of the License, or
* (at your option) any later version.
*
* opkg-opk 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 opkg-opk. If not, see .
*/
#include
#include
#include
#include
#include "defs.h"
#include "gzip.h"
#include "ustar.h"
#define OPKG_OPK_USTAR_NUM_BASE_ 8
struct _opkg_opk_ustar_header {
char name [100];
char mode [8];
char uid [8];
char gid [8];
char size [12];
char mtime [12];
char chksum [8];
char typeflag [1];
char linkname [100];
char magic [6];
char version [2];
char uname [32];
char gname [32];
char devmajor [8];
char devminor [8];
char prefix [155];
char padding [12];
} __attribute__((__packed__));
struct opkg_opk_ustar {
struct opkg_opk_gzip *gzip;
uint64_t data_size_remaining;
struct _opkg_opk_ustar_header header;
char read_record[OPKG_OPK_USTAR_RECORD_SIZE];
};
struct opkg_opk_ustar_seek_name {
const char *name;
int found;
struct opkg_opk_ustar_seek_name *next;
};
struct opkg_opk_ustar *
opkg_opk_ustar_init(struct opkg_opk_gzip *gzip)
{
struct opkg_opk_ustar *ustar;
ustar = malloc(sizeof(*ustar));
if (ustar == NULL) {
return NULL;
}
ustar->gzip = gzip;
ustar->data_size_remaining = 0;
return ustar;
}
static int
_opkg_opk_ustar_next(struct opkg_opk_ustar *ustar)
{
char *end;
uint32_t chksum_got;
uint32_t chksum_exp;
size_t i;
char *header_uc;
/* Decompress next (hopefully valid header) record. */
switch (opkg_opk_gzip_read(ustar->gzip, &ustar->header)) {
case OPKG_OPK_OK:
break;
case OPKG_OPK_END:
/* End of gzip stream before end of ustar archive */
case OPKG_OPK_ERROR:
return OPKG_OPK_ERROR;
}
/* Check for end of archive. */
memset(ustar->read_record, 0, OPKG_OPK_USTAR_RECORD_SIZE);
if (memcmp(&ustar->header, ustar->read_record,
OPKG_OPK_USTAR_RECORD_SIZE) == 0) {
return OPKG_OPK_END;
}
/* Verify magic. */
if (memcmp(ustar->header.magic, "ustar", strlen("ustar")) != 0) {
return OPKG_OPK_ERROR;
}
/* Verify checksum. */
chksum_got = strtol(ustar->header.chksum, &end,
OPKG_OPK_USTAR_NUM_BASE_);
chksum_exp = 0;
if (*end != '\0') {
return OPKG_OPK_ERROR;
}
for (i = 0; i < sizeof(ustar->header.chksum); ++i) {
ustar->header.chksum[i] = ' ';
}
header_uc = (char *) &ustar->header;
for (i = 0; i < OPKG_OPK_USTAR_RECORD_SIZE; ++i) {
chksum_exp += header_uc[i];
}
if (chksum_got != chksum_exp) {
return OPKG_OPK_ERROR;
}
/* Depending on type, get size. */
switch (*ustar->header.typeflag) {
case '0': /* Regular file */
case '7': /* High-performance or regular file */
ustar->data_size_remaining = strtol(
ustar->header.size, &end,
OPKG_OPK_USTAR_NUM_BASE_);
if (*end != '\0') {
return OPKG_OPK_ERROR;
}
break;
case '2': /* Symbolic link */
case '3': /* Character special file */
case '4': /* Block special file */
case '5': /* Directory */
case '6': /* FIFO special file */
ustar->data_size_remaining = 0;
break;
case '1': /* Link */
default: /* Reserved */
return OPKG_OPK_ERROR; /* Unsupported */
}
return OPKG_OPK_OK;
}
int
opkg_opk_ustar_list(struct opkg_opk_ustar *ustar,
struct opkg_opk_ustar_member **member)
{
int ret;
char *end;
/* Get next header record, if any. */
if ((ret =_opkg_opk_ustar_next(ustar)) != OPKG_OPK_OK) {
return ret; /* Error or end of archive */
}
/* Allocate outward-facing member information structure. */
*member = malloc(sizeof(**member));
if (*member == NULL) {
return OPKG_OPK_ERROR;
}
/* Set name, mode, size, mtime, type, linkname, uname, and gname. */
if (ustar->header.prefix[0] != '\0') {
sprintf((*member)->name, "%s/%s",
ustar->header.prefix, ustar->header.name);
} else {
/* Use memcpy() because ustar->header.name may not be
* NUL-terminated. */
memcpy((*member)->name, ustar->header.name,
sizeof(ustar->header.name));
(*member)->name[sizeof(ustar->header.name)] = '\0';
}
(*member)->mode = strtol(ustar->header.mode, &end,
OPKG_OPK_USTAR_NUM_BASE_);
if (*end != '\0') {
free(*member);
return OPKG_OPK_ERROR;
}
(*member)->size = ustar->data_size_remaining;
(*member)->mtime = strtol(ustar->header.mtime, &end,
OPKG_OPK_USTAR_NUM_BASE_);
if (*end != '\0') {
free(*member);
return OPKG_OPK_ERROR;
}
switch (*ustar->header.typeflag) {
case '0': /* Regular file */
case '7': /* High-performance or regular file */
(*member)->type = '-';
break;
case '2': /* Symbolic link */
(*member)->type = 'l';
strncpy((*member)->linkname, ustar->header.linkname,
sizeof(ustar->header.linkname));
(*member)->linkname[sizeof((*member)->linkname) - 1] =
'\0';
break;
case '3': /* Character special file */
(*member)->type = 'c';
break;
case '4': /* Block special file */
(*member)->type = 'b';
break;
case '5': /* Directory */
(*member)->type = 'd';
break;
case '6': /* FIFO special file */
(*member)->type = 'p';
break;
case '1': /* Link */
default: /* Reserved */
free(*member);
return OPKG_OPK_ERROR; /* Unsupported */
}
strcpy((*member)->uname, ustar->header.uname);
strcpy((*member)->gname, ustar->header.gname);
/* Seek through data records until next header record. */
while (ustar->data_size_remaining > 0) {
if (opkg_opk_ustar_read(ustar, NULL, NULL) == OPKG_OPK_ERROR) {
free(*member);
return OPKG_OPK_ERROR;
}
}
return OPKG_OPK_OK; /* Possibly more members in archive */
}
int
opkg_opk_ustar_add_seek_name(struct opkg_opk_ustar_seek_name **names,
const char *name)
{
struct opkg_opk_ustar_seek_name *seek_name;
seek_name = malloc(sizeof(*seek_name));
if (seek_name == NULL) {
return OPKG_OPK_ERROR;
}
seek_name->name = name;
seek_name->found = 0;
seek_name->next = *names;
*names = seek_name;
return OPKG_OPK_OK;
}
void
opkg_opk_ustar_free_seek_names(struct opkg_opk_ustar_seek_name *names)
{
struct opkg_opk_ustar_seek_name *name;
while (names != NULL) {
name = names;
names = names->next;
free(name);
}
}
int
opkg_opk_ustar_seek(struct opkg_opk_ustar *ustar,
struct opkg_opk_ustar_seek_name *names)
{
char name[OPKG_OPK_USTAR_NAME_SIZE];
int found;
int found_all;
struct opkg_opk_ustar_seek_name *seek_name;
for (;;) {
/* Get next header record. */
if (_opkg_opk_ustar_next(ustar) != OPKG_OPK_OK) {
return OPKG_OPK_ERROR; /* Error or end (not found) */
}
/* Prepare name (with prefix if any) for check. */
if (ustar->header.prefix[0] != '\0') {
sprintf(name, "%s/%s", ustar->header.prefix,
ustar->header.name);
} else {
/* Use memcpy() because ustar->header.name may not be
* NUL-terminated. */
memcpy(name, ustar->header.name,
sizeof(ustar->header.name));
name[sizeof(ustar->header.name)] = '\0';
}
/* Check each requested name. */
found = 0;
found_all = 1;
for (seek_name = names; seek_name != NULL;
seek_name = seek_name->next) {
if (seek_name->found == 1) {
continue; /* Previously found this member */
}
if (strcmp(name, seek_name->name) == 0) {
if (found == 0) {
seek_name->found = 1;
found = 1;
continue;
}
}
if (name[0] == '.' && name[1] == '/' &&
strcmp(name + 2, seek_name->name) == 0)
{
if (found == 0) {
seek_name->found = 1;
found = 1;
continue;
}
}
found_all = 0;
}
if (found == 1) {
if (found_all == 1) {
/* All requested members found */
return OPKG_OPK_END;
}
return OPKG_OPK_OK; /* Member found, but more remain */
}
/* Seek through data records until next header record. */
while (ustar->data_size_remaining > 0) {
if (opkg_opk_ustar_read(ustar, NULL, NULL) ==
OPKG_OPK_ERROR) {
return OPKG_OPK_ERROR;
}
}
}
return OPKG_OPK_ERROR; /* Member not found */
}
int
opkg_opk_ustar_seek_one(struct opkg_opk_ustar *ustar, const char *name)
{
struct opkg_opk_ustar_seek_name seek_name;
seek_name.name = name;
seek_name.found = 0;
seek_name.next = NULL;
if (opkg_opk_ustar_seek(ustar, &seek_name) == OPKG_OPK_END) {
return OPKG_OPK_OK;
} else {
return OPKG_OPK_ERROR;
}
}
int
opkg_opk_ustar_read(struct opkg_opk_ustar *ustar, char **buffer, size_t *size)
{
if (ustar->data_size_remaining == 0) {
return OPKG_OPK_END;
}
/* Decompress next data record. */
switch (opkg_opk_gzip_read(ustar->gzip, ustar->read_record)) {
case OPKG_OPK_OK:
break;
case OPKG_OPK_END:
case OPKG_OPK_ERROR:
return OPKG_OPK_ERROR;
}
/* Store buffer and size in caller's memory and update remaining size.
*/
if (buffer != NULL) {
*buffer = ustar->read_record;
}
if (ustar->data_size_remaining >= OPKG_OPK_USTAR_RECORD_SIZE) {
if (size != NULL) {
*size = OPKG_OPK_USTAR_RECORD_SIZE;
}
ustar->data_size_remaining -= OPKG_OPK_USTAR_RECORD_SIZE;
} else {
if (size != NULL) {
*size = ustar->data_size_remaining;
}
ustar->data_size_remaining = 0;
}
return OPKG_OPK_OK;
}
void
opkg_opk_ustar_free(struct opkg_opk_ustar *ustar)
{
free(ustar);
}