| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #include <cpio/cpio.h> |
| |
| #ifndef NULL |
| #define NULL ((void *)0) |
| #endif |
| |
| struct cpio_header_info { |
| char const *filename; |
| unsigned long filesize; |
| const void *data; |
| const struct cpio_header *next; |
| }; |
| |
| /* Align 'n' up to the value 'align', which must be a power of two. */ |
| static unsigned long align_up(unsigned long n, unsigned long align) |
| { |
| return (n + align - 1) & (~(align - 1)); |
| } |
| |
| /* Parse an ASCII hex string into an integer. */ |
| static unsigned long parse_hex_str(const char *s, unsigned int max_len) |
| { |
| unsigned long r = 0; |
| unsigned long i; |
| |
| for (i = 0; i < max_len; i++) { |
| r *= 16; |
| if (s[i] >= '0' && s[i] <= '9') { |
| r += s[i] - '0'; |
| } else if (s[i] >= 'a' && s[i] <= 'f') { |
| r += s[i] - 'a' + 10; |
| } else if (s[i] >= 'A' && s[i] <= 'F') { |
| r += s[i] - 'A' + 10; |
| } else { |
| return r; |
| } |
| continue; |
| } |
| return r; |
| } |
| |
| /* |
| * Compare up to 'n' characters in a string. |
| * |
| * We re-implement the wheel to avoid dependencies on 'libc', required for |
| * certain environments that are particularly impoverished. |
| */ |
| static int cpio_strncmp(const char *a, const char *b, unsigned long n) |
| { |
| unsigned long i; |
| for (i = 0; i < n; i++) { |
| if (a[i] != b[i]) { |
| return a[i] - b[i]; |
| } |
| if (a[i] == 0) { |
| return 0; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| * This is an implementation of string copy because, cpi doesn't want to |
| * use string.h. |
| */ |
| static char *cpio_strcpy(char *to, const char *from) |
| { |
| char *save = to; |
| while (*from != 0) { |
| *to = *from; |
| to++; |
| from++; |
| } |
| return save; |
| } |
| |
| static unsigned int cpio_strlen(const char *str) |
| { |
| const char *s; |
| for (s = str; *s; ++s) {} |
| return (s - str); |
| } |
| |
| /* Calculate the remaining length in a CPIO file after reading a header. */ |
| static unsigned long cpio_len_next(unsigned long len, const void *prev, const void *next) |
| { |
| unsigned long diff = (unsigned long)(next - prev); |
| if (len < diff) { |
| return 0; |
| } |
| return len; |
| } |
| |
| /* |
| * Parse the header of the given CPIO entry. |
| * |
| * Return -1 if the header is not valid, 1 if it is EOF. |
| */ |
| int cpio_parse_header(const struct cpio_header *archive, unsigned long len, |
| struct cpio_header_info *info) |
| { |
| const char *filename; |
| unsigned long filesize; |
| unsigned long filename_length; |
| const void *data; |
| const struct cpio_header *next; |
| |
| /* Ensure header is accessible */ |
| if (len < sizeof(struct cpio_header)) { |
| return -1; |
| } |
| |
| /* Ensure magic header exists. */ |
| if (cpio_strncmp(archive->c_magic, CPIO_HEADER_MAGIC, sizeof(archive->c_magic)) != 0) { |
| return -1; |
| } |
| |
| /* Get filename and file size. */ |
| filesize = parse_hex_str(archive->c_filesize, sizeof(archive->c_filesize)); |
| filename_length = parse_hex_str(archive->c_namesize, sizeof(archive->c_namesize)); |
| |
| /* Ensure header + filename + file contents are accessible */ |
| if (len < sizeof(struct cpio_header) + filename_length + filesize) { |
| return -1; |
| } |
| |
| filename = (char *) archive + sizeof(struct cpio_header); |
| /* Ensure filename is terminated */ |
| if (filename[filename_length - 1] != 0) { |
| return -1; |
| } |
| |
| /* Ensure filename is not the trailer indicating EOF. */ |
| if (filename_length >= sizeof(CPIO_FOOTER_MAGIC) && cpio_strncmp(filename, |
| CPIO_FOOTER_MAGIC, sizeof(CPIO_FOOTER_MAGIC)) == 0) { |
| return 1; |
| } |
| |
| /* Find offset to data. */ |
| data = (void *) align_up((unsigned long) archive + sizeof(struct cpio_header) + |
| filename_length, CPIO_ALIGNMENT); |
| next = (struct cpio_header *) align_up((unsigned long) data + filesize, CPIO_ALIGNMENT); |
| |
| if (info) { |
| info->filename = filename; |
| info->filesize = filesize; |
| info->data = data; |
| info->next = next; |
| } |
| return 0; |
| } |
| |
| /* |
| * Get the location of the data in the n'th entry in the given archive file. |
| * |
| * We also return a pointer to the name of the file (not NUL terminated). |
| * |
| * Return NULL if the n'th entry doesn't exist. |
| * |
| * Runs in O(n) time. |
| */ |
| const void *cpio_get_entry(const void *archive, unsigned long len, int n, const char **name, unsigned long *size) |
| { |
| const struct cpio_header *header = archive; |
| struct cpio_header_info header_info; |
| |
| /* Find n'th entry. */ |
| for (int i = 0; i <= n; i++) { |
| int error = cpio_parse_header(header, len, &header_info); |
| if (error) { |
| return NULL; |
| } |
| len = cpio_len_next(len, header, header_info.next); |
| header = header_info.next; |
| } |
| |
| if (name) { |
| *name = header_info.filename; |
| } |
| if (size) { |
| *size = header_info.filesize; |
| } |
| return header_info.data; |
| } |
| |
| /* |
| * Find the location and size of the file named "name" in the given 'cpio' |
| * archive. |
| * |
| * Return NULL if the entry doesn't exist. |
| * |
| * Runs in O(n) time. |
| */ |
| const void *cpio_get_file(const void *archive, unsigned long len, const char *name, unsigned long *size) |
| { |
| const struct cpio_header *header = archive; |
| struct cpio_header_info header_info; |
| |
| /* Find n'th entry. */ |
| while (1) { |
| int error = cpio_parse_header(header, len, &header_info); |
| if (error) { |
| return NULL; |
| } |
| if (cpio_strncmp(header_info.filename, name, -1) == 0) { |
| break; |
| } |
| len = cpio_len_next(len, header, header_info.next); |
| header = header_info.next; |
| } |
| |
| if (size) { |
| *size = header_info.filesize; |
| } |
| return header_info.data; |
| } |
| |
| int cpio_info(const void *archive, unsigned long len, struct cpio_info *info) |
| { |
| const struct cpio_header *header; |
| unsigned long current_path_sz; |
| struct cpio_header_info header_info; |
| |
| if (info == NULL) { |
| return 1; |
| } |
| info->file_count = 0; |
| info->max_path_sz = 0; |
| |
| header = archive; |
| while (1) { |
| int error = cpio_parse_header(header, len, &header_info); |
| if (error == -1) { |
| return error; |
| } else if (error == 1) { |
| /* EOF */ |
| return 0; |
| } |
| info->file_count++; |
| len = cpio_len_next(len, header, header_info.next); |
| header = header_info.next; |
| |
| // Check if this is the maximum file path size. |
| current_path_sz = cpio_strlen(header_info.filename); |
| if (current_path_sz > info->max_path_sz) { |
| info->max_path_sz = current_path_sz; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void cpio_ls(const void *archive, unsigned long len, char **buf, unsigned long buf_len) |
| { |
| const struct cpio_header *header; |
| struct cpio_header_info header_info; |
| |
| header = archive; |
| for (unsigned long i = 0; i < buf_len; i++) { |
| int error = cpio_parse_header(header, len, &header_info); |
| // Break on an error or nothing left to read. |
| if (error) { |
| break; |
| } |
| cpio_strcpy(buf[i], header_info.filename); |
| len = cpio_len_next(len, header, header_info.next); |
| header = header_info.next; |
| } |
| } |