/* * Copyright 2017, Data61 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) * ABN 41 687 119 230. * * This software may be distributed and modified according to the terms of * the BSD 2-Clause license. Note that NO WARRANTY is provided. * See "LICENSE_BSD2.txt" for details. * * @TAG(DATA61_BSD) */ #include #ifndef NULL #define NULL ((void *)0) #endif struct cpio_header_info { const char *filename; unsigned long filesize; void *data; 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(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, void *prev, 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(struct cpio_header *archive, unsigned long len, struct cpio_header_info *info) { const char *filename; unsigned long filesize; unsigned long filename_length; void *data; 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. */ void *cpio_get_entry(void *archive, unsigned long len, int n, const char **name, unsigned long *size) { 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. */ void *cpio_get_file(void *archive, unsigned long len, const char *name, unsigned long *size) { 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(void *archive, unsigned long len, struct cpio_info *info) { 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(void *archive, unsigned long len, char **buf, unsigned long buf_len) { 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; } }