/*
* 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
#include "defs.h"
#include "gzip.h"
#include "ustar.h"
#define OPKG_OPK_USTAR_NUM_BASE_ 8
struct _opkg_opk_ustar_header {
unsigned char name [100];
unsigned char mode [8];
unsigned char uid [8];
unsigned char gid [8];
unsigned char size [12];
unsigned char mtime [12];
unsigned char chksum [8];
unsigned char typeflag [1];
unsigned char linkname [100];
unsigned char magic [6];
unsigned char version [2];
unsigned char uname [32];
unsigned char gname [32];
unsigned char devmajor [8];
unsigned char devminor [8];
unsigned char prefix [155];
unsigned char padding [12];
} __attribute__((__packed__));
struct opkg_opk_ustar {
struct opkg_opk_gzip *gzip;
int64_t data_size_remaining;
unsigned char read_record[OPKG_OPK_USTAR_RECORD_SIZE];
};
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,
struct _opkg_opk_ustar_header *header)
{
static unsigned char record[OPKG_OPK_USTAR_RECORD_SIZE];
char *end;
uint32_t chksum_got;
uint32_t chksum_exp;
int i;
unsigned char *header_uc;
switch (opkg_opk_gzip_read(ustar->gzip, header)) {
case OPKG_OPK_OK:
break;
case OPKG_OPK_END:
case OPKG_OPK_ERROR:
return OPKG_OPK_ERROR;
}
memset(record, 0, OPKG_OPK_USTAR_RECORD_SIZE);
if (memcmp(header, record, OPKG_OPK_USTAR_RECORD_SIZE) == 0) {
return OPKG_OPK_END;
}
if (memcmp(header->magic, "ustar", strlen("ustar")) != 0) {
return OPKG_OPK_ERROR;
}
chksum_got = strtol((char *) header->chksum, &end,
OPKG_OPK_USTAR_NUM_BASE_);
chksum_exp = 0;
if (*end != '\0') {
return OPKG_OPK_ERROR;
}
for (i = 0; i < sizeof(header->chksum); ++i) {
header->chksum[i] = ' ';
}
header_uc = 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;
}
switch (*header->typeflag) {
case '0': /* Regular file */
case '7': /* High-performance or regular file */
ustar->data_size_remaining = strtol(
(char *) 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)
{
static struct _opkg_opk_ustar_header header;
int ret;
char *end;
if ((ret =_opkg_opk_ustar_next(ustar, &header)) != OPKG_OPK_OK) {
return ret; /* Error or end of archive */
}
if (header.prefix[0] != '\0') {
sprintf(member->name, "%s/%s", header.prefix, header.name);
} else {
memcpy(member->name, header.name, sizeof(header.name));
member->name[sizeof(header.name)] = '\0';
}
member->mode = strtol((char *) header.mode, &end,
OPKG_OPK_USTAR_NUM_BASE_);
if (*end != '\0') {
return OPKG_OPK_ERROR;
}
member->size = ustar->data_size_remaining;
member->mtime = strtol((char *) header.mtime, &end,
OPKG_OPK_USTAR_NUM_BASE_);
if (*end != '\0') {
return OPKG_OPK_ERROR;
}
switch (*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, header.linkname,
sizeof(member->linkname));
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 */
return OPKG_OPK_ERROR; /* Unsupported */
}
strncpy(member->uname, header.uname, sizeof(member->uname));
strncpy(member->gname, header.gname, sizeof(member->gname));
while (ustar->data_size_remaining > 0) {
if (opkg_opk_ustar_read(ustar, NULL, NULL) == OPKG_OPK_ERROR) {
return OPKG_OPK_ERROR;
}
}
return OPKG_OPK_OK; /* Possibly more members in archive */
}
int
opkg_opk_ustar_seek(struct opkg_opk_ustar *ustar, int num_keys, ...)
{
static struct _opkg_opk_ustar_header header;
static unsigned char name[OPKG_OPK_USTAR_NAME_MAX_LEN];
va_list ap;
int key;
const char *member;
for (;;) {
if (_opkg_opk_ustar_next(ustar, &header) != OPKG_OPK_OK) {
return OPKG_OPK_ERROR; /* Error or end (not found) */
}
if (header.prefix[0] != '\0') {
sprintf((char *) name, "%s/%s", header.prefix,
header.name);
} else {
memcpy(name, header.name, sizeof(header.name));
name[sizeof(header.name)] = '\0';
}
va_start(ap, num_keys);
for (key = 0; key < num_keys; ++key) {
member = va_arg(ap, const char *);
if (strcmp((char *) name, member) == 0) {
return OPKG_OPK_OK; /* Member found */
}
}
va_end(ap);
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_read(struct opkg_opk_ustar *ustar, char **buffer, size_t *size)
{
if (ustar->data_size_remaining == 0) {
return OPKG_OPK_END;
}
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;
}
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);
}