/* * 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 /* #define OPKG_OPK_USTAR_BB_EXACT_HEADER_ 1 */ 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; unsigned 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. */ /* Assumes chksum is NUL-terminated. Not required by POSIX, but done by * GNU and BB tar and opkg_opk_ustar_write_header(). */ 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 = (unsigned 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 */ /* Assumes size is NUL-terminated. Not required by * POSIX, but done by GNU and BB tar and * opkg_opk_ustar_write_header(). */ 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, gname, devmajor, * and devminor. */ 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'; } /* Assumes mode and mtime are NUL-terminated. Not required by POSIX, * but done by GNU and BB tar and opkg_opk_ustar_write_header(). */ (*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 */ } strncpy((*member)->uname, ustar->header.uname, sizeof((*member)->uname)); strncpy((*member)->gname, ustar->header.gname, sizeof((*member)->gname)); (*member)->devmajor = strtol(ustar->header.devmajor, &end, OPKG_OPK_USTAR_NUM_BASE_); if (*end != '\0') { free(*member); return OPKG_OPK_ERROR; } (*member)->devminor = strtol(ustar->header.devminor, &end, OPKG_OPK_USTAR_NUM_BASE_); if (*end != '\0') { free(*member); return OPKG_OPK_ERROR; } 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, uint32_t devmajor, uint32_t devminor, uint64_t size, uint64_t mtime, char type, const char *linkname) { uint32_t chksum; size_t i; unsigned char *header_uc; memset(&ustar->header, 0, sizeof(ustar->header)); if (opkg_opk_dirent_name_prefix(dirent, (type == 'd' ? 1 : 0), ustar->header.name, sizeof(ustar->header.name), ustar->header.prefix, sizeof(ustar->header.prefix)) != OPKG_OPK_OK) { return OPKG_OPK_ERROR; } /* POSIX doesn't say to NUL-terminate mode, uid, gid, size, or mtime. * GNU and BB tar accept values that aren't (but generate values that * ARE) NUL-terminated. But more importantly, opkg-lede (see file * libbb/unarchive.c) parses these with simple strtol() calls, without * even checking endptr, so we have to NUL-terminate these fields. Also * parsing these fields with strtol() are our very own * _opkg_opk_ustar_next() and opkg_opk_ustar_list() above. */ sprintf(ustar->header.mode, "%07o", mode); sprintf(ustar->header.uid, "%07o", uid); sprintf(ustar->header.gid, "%07o", gid); sprintf(ustar->header.size, "%011o", size); sprintf(ustar->header.mtime, "%011o", mtime); sprintf(ustar->header.devmajor, "%07o", devmajor); sprintf(ustar->header.devminor, "%07o", devminor); switch (type) { case '-': /* Regular file */ *ustar->header.typeflag = '0'; break; case 'l': /* Symbolic link */ *ustar->header.typeflag = '2'; strncpy(ustar->header.linkname, linkname, sizeof(ustar->header.linkname)); 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 */ } strncpy(ustar->header.uname, uname, sizeof(ustar->header.uname)); strncpy(ustar->header.gname, gname, sizeof(ustar->header.gname)); /* In these fields POSIX says to write: But GNU and BB tar write: */ strcpy(ustar->header.magic, "ustar"); /* "ustar " */ memcpy(ustar->header.version, "00", 2); /* " \0" i.e. 0x20 0x00 */ /* See files in BusyBox: * include/bb_archive.h * archival/chksum_and_xwrite_tar_header.c * archival/libarchive/get_header_tar.c * GNU and BB tar accept POSIX-conformant magic and version fields. * opkg-lede libbb/unarchive.c only checks first 5 bytes of magic and * ignores version. So conforming to POSIX seems safe. */ #if OPKG_OPK_USTAR_BB_EXACT_HEADER_ strcpy(ustar->header.magic, "ustar "); #endif chksum = 0; for (i = 0; i < sizeof(ustar->header.chksum); ++i) { ustar->header.chksum[i] = ' '; } header_uc = (unsigned char *) &ustar->header; for (i = 0; i < OPKG_OPK_USTAR_RECORD_SIZE; ++i) { chksum += header_uc[i]; } /* See above about NUL-terminating and strtol() in opkg-lede. */ /* Since commit 5661fe078eed752780b11f3f4fdd33bbd76a6c5e, BB tar has * filled chksum with "[6] digits, a null, then a space -- rather than * digits, followed by a null like the other fields...". The maxiumum * checksum is (255 x 512 = octal 377000), i.e. a maximum of 6 octal * digits, so it doesn't matter whether we use 6 or 7 digits. */ #if OPKG_OPK_USTAR_BB_EXACT_HEADER_ sprintf(ustar->header.chksum, "%06o", chksum); #else sprintf(ustar->header.chksum, "%07o", chksum); #endif if (opkg_opk_gzip_write(ustar->gzip, &ustar->header, OPKG_OPK_USTAR_RECORD_SIZE) != 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) != 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) != OPKG_OPK_OK || opkg_opk_gzip_write(ustar->gzip, ustar->record, OPKG_OPK_USTAR_RECORD_SIZE) != OPKG_OPK_OK) { return OPKG_OPK_ERROR; } return OPKG_OPK_OK; } void opkg_opk_ustar_free(struct opkg_opk_ustar *ustar) { free(ustar); }