aos/lib/cpio/cpiobin.c
Daniel Schwyn 6d444bf552 Main handout
Signed-off-by: Daniel Schwyn <daniel.schwyn@inf.ethz.ch>
2022-03-03 14:57:51 +01:00

654 lines
15 KiB
C

/**
* \file
* \brief CPIO archive reader routines for newc/crc/bin variants.
*/
/*
* Copyright (c) 2007, 2008, 2011, ETH Zurich.
* All rights reserved.
*
* Thi2s file is distributed under the terms in the attached LICENSE file.
* If you do not find this file, copies can be found by writing to:
* ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <aos/static_assert.h>
#include <sys/param.h> // for MIN
#include "cpiobin.h"
typedef struct
{
uint8_t magic[2];
uint8_t dev[2];
uint8_t inode[2];
uint8_t mode[2];
uint8_t uid[2];
uint8_t gid[2];
uint8_t nlink[2];
uint8_t rdev[2];
uint8_t mtime[4];
uint8_t namesize[2];
uint8_t filesize[4];
} __attribute__((packed)) cpio_bin_header_t;
STATIC_ASSERT_SIZEOF(cpio_bin_header_t, 26);
typedef struct
{
char magic[6];
char inode[8];
char mode[8];
char uid[8];
char gid[8];
char nlink[8];
char mtime[8];
char filesize[8];
char devmajor[8];
char devminor[8];
char rdevmajor[8];
char rdevminor[8];
char namesize[8];
char check[8];
} __attribute__((packed)) cpio_newc_header_t;
STATIC_ASSERT_SIZEOF(cpio_newc_header_t, 110);
static const char CPIO_LAST[] = "TRAILER!!!";
const size_t CPIO_BIN_LAST_SIZE = sizeof(cpio_bin_header_t) + sizeof(CPIO_LAST);
const size_t CPIO_NEWC_LAST_SIZE = sizeof(cpio_newc_header_t) + sizeof(CPIO_LAST);
// ----------------------------------------------------------------------------
// Identification
static inline int
cpio_is_bin_be(const uint8_t* buffer)
{
const cpio_bin_header_t* h = (const cpio_bin_header_t*)buffer;
return (h->magic[0] * 0x100 + h->magic[1]) == 070707;
}
static inline int
cpio_is_bin_le(const uint8_t* buffer)
{
const cpio_bin_header_t* h = (const cpio_bin_header_t*)buffer;
return (h->magic[0] + h->magic[1] * 0x100) == 070707;
}
static inline int
cpio_is_newc(const uint8_t* buffer)
{
const cpio_newc_header_t* h = (const cpio_newc_header_t*)buffer;
return strncmp(h->magic, "070701", 6) == 0;
}
static inline int
cpio_is_crc(const uint8_t* buffer)
{
const cpio_newc_header_t* h = (const cpio_newc_header_t*)buffer;
return strncmp(h->magic, "070702", 6) == 0;
}
static inline int
cpio_mode_is_file(cpio_mode_bits_t m)
{
return CPIO_MODE_FILE == (m & CPIO_MODE_FILE_TYPE_MASK);
}
// ----------------------------------------------------------------------------
// Decoding
static inline
int toupper(int c)
{
if (c >= 'a' && c <= 'z')
{
c = c - 'a' + 'A';
}
return c;
}
static inline
int isxdigit(int c)
{
return ((c >= '0' && c <= '9') |
(c >= 'A' && c <= 'F') |
(c >= 'a' && c <= 'f'));
}
static inline uint16_t
c2u16(const cpio_bin_header_t* h, const uint8_t* p)
{
if (cpio_is_bin_be((const uint8_t*)h))
{
return (p[0] * 0x100) + p[1];
}
else
{
return (p[1] * 0x100) + p[0];
}
}
static inline uint32_t
c4u32(const cpio_bin_header_t* h, const uint8_t* p)
{
if (cpio_is_bin_be((const uint8_t*)h))
{
return (p[0] * 0x1000000) + (p[1] * 0x10000) + (p[2] * 0x100) + p[3];
}
else
{
return (p[3] * 0x100 + p[2]) + (p[1] * 0x100 + p[0]) * 0x10000;
}
}
static inline uint32_t
a1u8(char c)
{
if (c == 0)
{
return 0;
}
else {
c = toupper(c);
if (c >= 'A' && c <= 'F')
{
return ((uint32_t)(c + 10 - 'A')) & 0xffu;
}
else if (c >= '0' || c <= '9')
{
return ((uint32_t)(c - '0')) & 0xffu;
}
else
{
return ~0;
}
}
}
static inline uint32_t
a6u32(const char* c)
{
return ((a1u8(c[0]) << 20) | (a1u8(c[1]) << 16) | (a1u8(c[2]) << 12) |
(a1u8(c[3]) << 8) | (a1u8(c[4]) << 4) | (a1u8(c[5])));
}
static inline uint32_t
a8u32(const char* c)
{
return ((a1u8(c[0]) << 28) | (a1u8(c[1]) << 24) | (a1u8(c[2]) << 20) |
(a1u8(c[3]) << 16) | (a1u8(c[4]) << 12) | (a1u8(c[5]) << 8) |
(a1u8(c[6]) << 4) | (a1u8(c[7])));
}
// ----------------------------------------------------------------------------
// Legacy Format (bin) accessors
static inline uintptr_t
cpio_bin_align(uintptr_t s)
{
return s + (s & 1);
}
static inline uint16_t
cpio_bin_name_bytes(const cpio_bin_header_t* h)
{
return c2u16(h, h->namesize);
}
static inline const uint8_t*
cpio_bin_data_start(const cpio_bin_header_t* h)
{
return cpio_bin_align(cpio_bin_name_bytes(h)) + ((const uint8_t*)(h + 1));
}
static inline uint32_t
cpio_bin_data_bytes(const cpio_bin_header_t* h)
{
return c4u32(h, h->filesize);
}
static inline cpio_mode_bits_t
cpio_bin_mode(const cpio_bin_header_t* h)
{
return (cpio_mode_bits_t)c2u16(h, h->mode);
}
static inline const char*
cpio_bin_name(const cpio_bin_header_t* h)
{
return (const char*)(h + 1);
}
static inline int
cpio_valid_bin_header(const uint8_t* h)
{
return cpio_is_bin_be(h) || cpio_is_bin_le(h);
}
// ----------------------------------------------------------------------------
// newc / crc accessors
static inline uintptr_t
cpio_newc_align(uintptr_t s)
{
return (s + 3) & ~3;
}
static inline uint32_t
cpio_newc_name_bytes(const cpio_newc_header_t* h)
{
return a8u32(h->namesize);
}
static inline const uint8_t*
cpio_newc_data_start(const cpio_newc_header_t* h)
{
uintptr_t h_end = (uintptr_t)(h + 1);
return (const uint8_t*)cpio_newc_align(cpio_newc_name_bytes(h) + h_end);
}
static inline uint32_t
cpio_newc_data_bytes(const cpio_newc_header_t* h)
{
return a8u32(h->filesize);
}
static inline cpio_mode_bits_t
cpio_newc_mode(const cpio_newc_header_t* h)
{
return (cpio_mode_bits_t)a8u32(h->mode);
}
static inline const char*
cpio_newc_name(const cpio_newc_header_t* h)
{
return (const char*)(h + 1);
}
static inline uint32_t
cpio_newc_checksum(const cpio_newc_header_t* h)
{
return a8u32(h->check);
}
static int
is_newc_string(const uint8_t* field, size_t bytes)
{
const char* s = (const char*)field;
size_t i = 0;
while (i < bytes)
{
if (!isxdigit(s[i]))
{
return 0;
}
i++;
}
return 1;
}
static inline int
cpio_valid_newc_header(const uint8_t* h)
{
return ((cpio_is_newc(h) || cpio_is_crc(h)) &&
is_newc_string(h+ 6, 8) && is_newc_string(h+14, 8) &&
is_newc_string(h+22, 8) && is_newc_string(h+30, 8) &&
is_newc_string(h+38, 8) && is_newc_string(h+46, 8) &&
is_newc_string(h+54, 8) && is_newc_string(h+62, 8) &&
is_newc_string(h+70, 8) && is_newc_string(h+78, 8) &&
is_newc_string(h+86, 8) && is_newc_string(h+94, 8) &&
(is_newc_string(h+102, 8)));
}
static int
cpio_bin_visit(
const uint8_t* cpio_base,
size_t cpio_bytes,
cpio_visitor_t cv,
cpio_generic_header_t* g,
void* arg
)
{
const uint8_t* cpio_limit = cpio_base + cpio_bytes;
int visited = 0;
while (cpio_limit - cpio_base >= CPIO_BIN_LAST_SIZE &&
cpio_valid_bin_header(cpio_base))
{
const cpio_bin_header_t* h = (const cpio_bin_header_t*)cpio_base;
g->mode = cpio_bin_mode(h);
g->name = cpio_bin_name(h);
g->data = cpio_bin_data_start(h);
g->datasize = cpio_bin_data_bytes(h);
g->checksum = 0;
if (((g->data + g->datasize) > cpio_limit) ||
(cv(visited, g, arg) && (g->mode != 0)))
{
break;
}
cpio_base = (cpio_bin_data_start(h) + cpio_bin_align(cpio_bin_data_bytes(h)));
visited++;
}
return visited;
}
static int
cpio_newc_visit(
const uint8_t* cpio_base,
size_t cpio_bytes,
cpio_visitor_t cv,
cpio_generic_header_t* g,
void* arg
)
{
const uint8_t* cpio_limit = cpio_base + cpio_bytes;
int visited = 0;
while (cpio_limit - cpio_base >= CPIO_NEWC_LAST_SIZE &&
cpio_valid_newc_header(cpio_base))
{
const cpio_newc_header_t* h = (const cpio_newc_header_t*)cpio_base;
g->mode = cpio_newc_mode(h);
g->name = cpio_newc_name(h);
g->data = cpio_newc_data_start(h);
g->datasize = cpio_newc_data_bytes(h);
g->checksum = cpio_newc_checksum(h);
if (((g->data + g->datasize) > cpio_limit) ||
(cv(visited, g, arg) && (g->mode != 0)))
{
break;
}
cpio_base = (cpio_newc_data_start(h) + cpio_newc_align(cpio_newc_data_bytes(h)));
visited++;
}
return visited;
}
int
cpio_visit(
const uint8_t* cpio_base,
size_t cpio_bytes,
cpio_visitor_t cpio_visit_fn,
cpio_generic_header_t* g,
void* arg
)
{
int visited = 0;
if (cpio_bytes >= CPIO_BIN_LAST_SIZE &&
cpio_valid_bin_header(cpio_base))
{
visited = cpio_bin_visit(cpio_base, cpio_bytes, cpio_visit_fn, g, arg);
}
else if (cpio_bytes >= CPIO_NEWC_LAST_SIZE &&
cpio_valid_newc_header(cpio_base))
{
visited = cpio_newc_visit(cpio_base, cpio_bytes, cpio_visit_fn, g, arg);
}
return visited;
}
struct cpio_find_info
{
const char* search_name;
int ordinal;
const cpio_generic_header_t* header; // result
};
static int
cpio_match_name(int ordinal, const cpio_generic_header_t* header, void* arg)
{
struct cpio_find_info* cfi = (struct cpio_find_info*)arg;
int match = !strcmp(cfi->search_name, header->name);
if (match)
{
cfi->ordinal = ordinal;
cfi->header = header;
}
return match;
}
int
cpio_get_file_by_name(
const uint8_t* cpio_base,
size_t cpio_bytes,
const char* name,
const uint8_t** file_base,
size_t* file_bytes
)
{
cpio_generic_header_t h;
struct cpio_find_info cfi = { name, 0, NULL };
cpio_visit(cpio_base, cpio_bytes, cpio_match_name, &h, &cfi);
int match = (cfi.header == &h) && cpio_mode_is_file(h.mode);
if (match)
{
if (file_base != NULL)
{
*file_base = h.data;
}
if (file_bytes != NULL)
{
*file_bytes = h.datasize;
}
}
return match;
}
static int
cpio_match_file_ordinal(
int ordinal,
const cpio_generic_header_t* header,
void* arg
)
{
int match = 0;
if (cpio_mode_is_file(header->mode))
{
struct cpio_find_info* cfi = (struct cpio_find_info*)arg;
if (cfi->ordinal == 0)
{
cfi->header = header;
match = 1;
}
else
{
cfi->ordinal--;
}
}
return match;
}
int
cpio_get_file_by_ordinal(
const uint8_t* cpio_base,
size_t cpio_bytes,
uint32_t ordinal,
const char** file_name,
const uint8_t** file_base,
size_t* file_bytes
)
{
cpio_generic_header_t h;
struct cpio_find_info cfi = { NULL, ordinal, NULL };
cpio_visit(cpio_base, cpio_bytes, cpio_match_file_ordinal, &h, &cfi);
int match = (cfi.header == &h);
if (match)
{
if (NULL != file_name)
{
*file_name = cfi.header->name;
}
if (NULL != file_base)
{
*file_base = cfi.header->data;
}
if (NULL != file_bytes)
{
*file_bytes = cfi.header->datasize;
}
}
return match;
}
size_t
cpio_archive_bytes(
const uint8_t* cpio_base,
size_t cpio_bytes
)
{
// Search for trailer
cpio_generic_header_t g;
struct cpio_find_info cfi = { CPIO_LAST, 0, NULL };
cpio_visit(cpio_base, cpio_bytes, cpio_match_name, &g, &cfi);
if (cfi.header != NULL) {
const uint8_t* cpio_limit = cfi.header->data - 1;
// Limit may be too far because of CPIO alignment rounding up
// of the data start region so...
return MIN((size_t)(cpio_limit - cpio_base), cpio_bytes);
}
else
{
return 0;
}
}
static int
cpio_crc_visitor(int ordinal, const cpio_generic_header_t* header, void *arg)
{
int* found_trailer = (int*)arg;
if (header->datasize > 0)
{
// "CRC" is the unsigned 32-bit sum of file data.
uint32_t computed = 0;
for (size_t i = 0; i < header->datasize; i++)
{
computed += header->data[i];
}
// Stop visiting if mismatch.
return computed != header->checksum;
}
else if (header->name)
{
// Success is reaching the trailer
*found_trailer = !strcmp(header->name, CPIO_LAST);
}
return 0;
}
int
cpio_archive_valid(const uint8_t* cpio_base, size_t cpio_bytes)
{
if ((cpio_bytes > sizeof(cpio_newc_header_t) &&
cpio_is_crc(cpio_base)))
{
int found_trailer = 0; // Success is reaching the trailer
cpio_generic_header_t g;
cpio_newc_visit(cpio_base, cpio_bytes, cpio_crc_visitor,
&g, &found_trailer);
return found_trailer;
}
return cpio_archive_bytes(cpio_base, cpio_bytes) > 0;
}
#ifdef TEST_CPIO
#include <malloc.h>
#include <sys/stat.h>
#include <fcntl.h>
static void cpio_newc_test(uint8_t* data, size_t data_bytes)
{
if (data_bytes >= sizeof(cpio_newc_header_t*))
{
const cpio_newc_header_t* ph = (const cpio_newc_header_t*)data;
printf("newc magic %08x inode %08x\n",
a6u32(ph->magic), a8u32(ph->inode));
}
}
static int
cpio_dump_info(int ordinal, const cpio_generic_header_t* header, void* arg)
{
const uint8_t* file_data = header->data;
uint32_t file_bytes = header->datasize;
printf("%3d %-16s mode %7o %p...%p [%d bytes]\n",
ordinal, header->name, header->mode,
file_data, file_data + file_bytes, file_bytes);
return 0;
}
int main(int argc, const char* argv[])
{
if (argc == 2)
{
size_t read_bytes;
struct stat st;
int fd = open(argv[1], O_RDONLY);
if (0 > fd)
{
fprintf(stderr, "Could not open %s\n", argv[1]);
return -1;
}
fstat(fd, &st);
size_t max_bytes = st.st_size;
uint8_t* read_data = (uint8_t*)malloc(max_bytes);
read_bytes = read(fd, read_data, max_bytes);
printf("Read %lu bytes\n", read_bytes);
printf("Image Valid %d Image bytes %lu\n",
cpio_archive_valid(read_data, read_bytes),
cpio_archive_bytes(read_data, read_bytes));
cpio_generic_header_t g;
cpio_visit(read_data, read_bytes, cpio_dump_info, &g, NULL);
{
const char* files[] = { "bfish/tip/build/hake/Path.o", "sbin/.marker", "fred" };
int i;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++)
{
const uint8_t *my_base;
size_t my_bytes;
if (cpio_get_file_by_name(read_data, read_bytes, files[i],
&my_base, &my_bytes))
{
printf("%s => %p...%p [%lu bytes]\n",
files[i], my_base, my_base + my_bytes, my_bytes);
}
else
{
printf("%s => FILE NOT FOUND\n", files[i]);
}
}
}
free(read_data);
close(fd);
}
return 0;
}
#endif // TEST_CPIO