/*
* 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 "dirent.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 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)
{
char *end;
uint32_t chksum_got;
uint32_t chksum_exp;
size_t i;
char *header_uc;
/* 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;
}
}
/* 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->record, 0, OPKG_OPK_USTAR_RECORD_SIZE);
if (memcmp(&ustar->header, ustar->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);
return OPKG_OPK_OK; /* Possibly more members in archive */
}
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->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->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;
}
int
opkg_opk_ustar_write_header(struct opkg_opk_ustar *ustar,
struct opkg_opk_dirent *dirent, uint16_t mode,
uid_t uid, const char *uname, gid_t gid, const char *gname,
uint64_t size, int64_t mtime, char type, const char *linkname)
{
size_t linkname_len;
uint32_t chksum;
size_t i;
char *header_uc;
if (opkg_opk_dirent_name_prefix(dirent,
ustar->header.name,
sizeof(ustar->header.name),
ustar->header.prefix,
sizeof(ustar->header.prefix)) != OPKG_OPK_OK) {
return OPKG_OPK_ERROR;
}
sprintf(ustar->header.mode, "%o", mode);
sprintf(ustar->header.uid, "%o", uid);
sprintf(ustar->header.gid, "%o", gid);
strncpy(ustar->header.uname, uname, sizeof(ustar->header.uname));
strncpy(ustar->header.gname, gname, sizeof(ustar->header.gname));
sprintf(ustar->header.size, "%o", size);
sprintf(ustar->header.mtime, "%o", mtime);
switch (type) {
case '-': /* Regular file */
*ustar->header.typeflag = '0';
break;
case 'l': /* Symbolic link */
*ustar->header.typeflag = '2';
break;
case 'c': /* Character special file */
*ustar->header.typeflag = '3';
break;
case 'b': /* Block special file */
*ustar->header.typeflag = '4';
break;
case 'd': /* Directory */
*ustar->header.typeflag = '5';
break;
case 'p': /* FIFO special file */
*ustar->header.typeflag = '6';
break;
default:
return OPKG_OPK_ERROR; /* Unsupported */
}
if (*ustar->header.typeflag == '2') {
linkname_len = strlen(linkname);
memcpy(ustar->header.linkname, linkname, linkname_len);
} else {
linkname_len = 0;
}
memset(ustar->header.linkname, 0,
sizeof(ustar->header.linkname) - linkname_len);
strcpy(ustar->header.magic, "ustar");
memcpy(ustar->header.version, "00", 2);
chksum = 0;
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 += header_uc[i];
}
sprintf(ustar->header.chksum, "%o", chksum);
if (opkg_opk_gzip_write(ustar->gzip, &ustar->header,
OPKG_OPK_USTAR_RECORD_SIZE, 0) != OPKG_OPK_OK) {
return OPKG_OPK_ERROR;
}
ustar->data_size_remaining = size;
return OPKG_OPK_OK;
}
int
opkg_opk_ustar_get_buffer(struct opkg_opk_ustar *ustar, char **buffer,
size_t *size)
{
*buffer = ustar->record;
*size = sizeof(ustar->record);
return OPKG_OPK_OK;
}
int
opkg_opk_ustar_write_data(struct opkg_opk_ustar *ustar, size_t size)
{
/* Sanity check. */
if (size > ustar->data_size_remaining ||
size > OPKG_OPK_USTAR_RECORD_SIZE) {
return OPKG_OPK_ERROR;
}
/* Zero out end of record. */
memset(ustar->record + size, 0, OPKG_OPK_USTAR_RECORD_SIZE - size);
/* Write to gzip stream. */
if (opkg_opk_gzip_write(ustar->gzip, ustar->record,
OPKG_OPK_USTAR_RECORD_SIZE, 0) != OPKG_OPK_OK) {
return OPKG_OPK_ERROR;
}
ustar->data_size_remaining -= size;
return OPKG_OPK_OK;
}
int
opkg_opk_ustar_write_trailer(struct opkg_opk_ustar *ustar)
{
memset(ustar->record, 0, OPKG_OPK_USTAR_RECORD_SIZE);
if (opkg_opk_gzip_write(ustar->gzip, ustar->record,
OPKG_OPK_USTAR_RECORD_SIZE, 0) != OPKG_OPK_OK ||
opkg_opk_gzip_write(ustar->gzip, ustar->record,
OPKG_OPK_USTAR_RECORD_SIZE, 1) != OPKG_OPK_END)
{
return OPKG_OPK_ERROR;
}
return OPKG_OPK_OK;
}
void
opkg_opk_ustar_free(struct opkg_opk_ustar *ustar)
{
free(ustar);
}