summaryrefslogtreecommitdiff
path: root/exar/exar.c
diff options
context:
space:
mode:
Diffstat (limited to 'exar/exar.c')
-rw-r--r--exar/exar.c677
1 files changed, 677 insertions, 0 deletions
diff --git a/exar/exar.c b/exar/exar.c
new file mode 100644
index 00000000..96940d2b
--- /dev/null
+++ b/exar/exar.c
@@ -0,0 +1,677 @@
+/*
+ * Copyright (c) 2013 Stefan Bolte <portix@gmx.net>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <ftw.h>
+#include <errno.h>
+#include <assert.h>
+#include <inttypes.h>
+#include "exar.h"
+
+#define EXAR_VERSION_BASE "exar-"
+#define EXAR_VERSION EXAR_VERSION_BASE "1"
+#define EXTENSION "exar"
+
+#define SZ_VERSION 7
+#define SZ_DFLAG 1
+#define SZ_SIZE 14
+
+#define HDR_DFLAG (0)
+#define HDR_SIZE (HDR_DFLAG + SZ_DFLAG)
+#define HDR_NAME (HDR_SIZE + SZ_SIZE)
+
+#define DIR_FLAG (100)
+#define FILE_FLAG (102)
+
+#define MAX_FILE_HANDLES 64
+
+#define MIN(X, Y) ((X) > (Y) ? (Y) : (X))
+
+#define EXAR_NAME_MAX 4096
+
+#define EE_OK 0
+#define EE_ERROR -1
+#define EE_EOF -2
+
+struct exar_header_s {
+ unsigned char eh_flag;
+ off_t eh_size;
+ char eh_name[EXAR_NAME_MAX];
+};
+#define EXAR_HEADER_EMPTY { 0, 0, { 0 } }
+
+#define LOG(level, ...) do { if (s_verbose & EXAR_VERBOSE_L##level) { \
+ fprintf(stderr, "exar-log%d: ", level); \
+ fprintf(stderr, __VA_ARGS__); } } while(0)
+
+static size_t s_offset;
+static FILE *s_out;
+static unsigned char s_verbose = 0;
+
+static void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ret = calloc(nmemb, size);
+ if (ret == NULL)
+ {
+ fprintf(stderr, "Cannot malloc %zu bytes\n", size * nmemb);
+ exit(EXIT_FAILURE);
+ }
+ return ret;
+}
+
+static size_t
+get_offset(char *buffer, size_t n, const char *path, int *end)
+{
+ const char *tmp = path, *slash;
+ size_t len = strlen(path);
+ size_t offset = 0;
+ int i=0;
+
+ // strip trailing '/'
+ while (tmp[len-1] == '/')
+ len--;
+ strncpy(buffer, path, MIN(n, len));
+
+ // get base name offset
+ slash = strrchr(buffer, '/');
+ if (slash != NULL)
+ offset = slash - buffer + 1;
+ for (tmp = path + offset; *tmp && *tmp != '/'; i++, tmp++)
+ buffer[i] = *tmp;
+ if (end != NULL)
+ *end = i;
+ return offset;
+}
+static int
+check_version(FILE *f, int verbose)
+{
+ unsigned char version[SZ_VERSION] = {0};
+ unsigned char orig_version[SZ_VERSION] = {0};
+ LOG(2, "Reading version header\n");
+ if (fread(version, 1, SZ_VERSION, f) != SZ_VERSION)
+ {
+ if (feof(f))
+ return EE_EOF;
+ else
+ {
+ if (verbose)
+ fprintf(stderr, "Not an exar file?\n");
+ return EE_ERROR;
+ }
+ }
+ memcpy(orig_version, EXAR_VERSION, sizeof(orig_version));
+ LOG(2, "Checking filetype\n");
+ if (strncmp((char*)version, EXAR_VERSION_BASE, 5))
+ {
+ if (verbose)
+ fprintf(stderr, "Not an exar file?\n");
+ return EE_ERROR;
+ }
+
+ LOG(2, "Found version %s\n", version);
+ if (memcmp(version, orig_version, SZ_VERSION))
+ {
+ if (verbose)
+ fprintf(stderr, "Incompatible version number\n");
+ return EE_ERROR;
+ }
+ return EE_OK;
+}
+/*
+ * Opens archive and checks version, mode is either read or read-write
+ * */
+static FILE *
+open_archive(const char *path, const char *mode)
+{
+ FILE *f = NULL;
+ LOG(3, "Opening %s for %s\n", path, strcmp(mode, "r") == 0 ? "reading" : "reading and writing");
+ if ((f = fopen(path, mode)) == NULL)
+ {
+ perror(path);
+ return NULL;
+ }
+ return f;
+}
+static void
+close_file(FILE *f, const char *archive)
+{
+ if (f != NULL)
+ {
+ LOG(3, "Closing %s\n", archive);
+ fclose(f);
+ }
+}
+
+static int
+get_file_header(FILE *f, struct exar_header_s *head)
+{
+ char *endptr;
+ char header[HDR_NAME];
+ off_t fs;
+ char rb;
+ size_t i = 0;
+ int st_version = 0;
+
+ if ((st_version = check_version(f, 1)) != EE_OK)
+ return st_version;
+
+ LOG(2, "Reading file header\n");
+ if (fread(header, 1, HDR_NAME, f) != HDR_NAME)
+ {
+ fprintf(stderr, "Reading file header failed");
+ return EE_ERROR;
+ }
+
+ head->eh_flag = header[HDR_DFLAG];
+
+ if (head->eh_flag != DIR_FLAG && head->eh_flag != FILE_FLAG)
+ {
+ LOG(1, "No file flag found\n");
+ fprintf(stderr, "The archive seems to be corrupted\n");
+ return EE_ERROR;
+ }
+ if (head->eh_flag == FILE_FLAG)
+ {
+ fs = strtol(&header[HDR_SIZE], &endptr, 16);
+ if (*endptr)
+ {
+ LOG(1, "Cannot determine file size\n");
+ fprintf(stderr, "The archive seems to be corrupted\n");
+ return EE_ERROR;
+ }
+ head->eh_size = fs;
+ }
+ else
+ head->eh_size = 0;
+
+ while (fread(&rb, 1, 1, f) > 0)
+ {
+ head->eh_name[i] = rb;
+ i++;
+ if (rb == '\0')
+ break;
+ else if (i == EXAR_NAME_MAX)
+ {
+ fprintf(stderr, "Cannot get filename\n");
+ return EE_ERROR;
+ }
+ }
+
+ LOG(2, "Found file header (%s, %c, %jd)\n", head->eh_name, head->eh_flag, (intmax_t)head->eh_size);
+ return EE_OK;
+}
+static int
+next_file(FILE *f, struct exar_header_s *header)
+{
+ if (*(header->eh_name))
+ {
+ if (header->eh_flag == FILE_FLAG)
+ {
+ if (fseek(f, header->eh_size, SEEK_CUR) != 0)
+ return EE_ERROR;
+ else
+ LOG(3, "Skipping %s\n", header->eh_name);
+ }
+ }
+ return get_file_header(f, header);
+}
+static int
+find_cmp(const char *name, const char *search)
+{
+ char buffer[EXAR_NAME_MAX];
+ if (strcmp(name, search) != 0)
+ {
+ size_t offset = get_offset(buffer, EXAR_NAME_MAX, name, NULL);
+ return strcmp(&name[offset], search);
+ }
+ return 0;
+}
+static int
+contains(const char *archive, const char *name, int (*cmp)(const char *, const char *))
+{
+ FILE *f = NULL;
+ struct exar_header_s header = EXAR_HEADER_EMPTY;
+ int result = EE_ERROR;
+
+ if ((f = open_archive(archive, "r")) == NULL)
+ goto finish;
+ while (next_file(f, &header) == EE_OK)
+ {
+ if (cmp(header.eh_name, name) == 0)
+ {
+ result = EE_OK;
+ break;
+ }
+ }
+
+finish:
+ close_file(f, archive);
+ return result;
+}
+static unsigned char *
+extract(const char *archive, const char *file, off_t *s, int (*cmp)(const char *, const char *))
+{
+ struct exar_header_s header = EXAR_HEADER_EMPTY;
+ FILE *f = NULL;
+ unsigned char *ret = NULL;
+ if (s != NULL)
+ *s = 0;
+
+ if ((f = open_archive(archive, "r")) == NULL)
+ goto finish;
+ while (get_file_header(f, &header) == EE_OK)
+ {
+ if (cmp(header.eh_name, file) == 0)
+ {
+ if (header.eh_flag == FILE_FLAG)
+ {
+ ret = xcalloc(header.eh_size, sizeof(unsigned char));
+ LOG(3, "Reading %s\n", header.eh_name);
+ if (fread(ret, 1, header.eh_size, f) != (size_t)header.eh_size)
+ {
+ fprintf(stderr, "Failed to read %s\n", header.eh_name);
+ *s = -1;
+ free(ret);
+ ret = NULL;
+ }
+ else if (s != NULL)
+ *s = header.eh_size;
+ }
+ else
+ fprintf(stderr, "%s is a directory, only regular files can be extracted\n", file);
+ goto finish;
+ }
+ else if (header.eh_flag == FILE_FLAG)
+ {
+ LOG(3, "Skipping %s\n", header.eh_name);
+ fseek(f, header.eh_size, SEEK_CUR);
+ }
+ }
+ fprintf(stderr, "File %s was not found in %s\n", file, archive);
+finish:
+ close_file(f, archive);
+ return ret;
+}
+
+static int
+write_file_header(FILE *f, const char *name, char flag, off_t r)
+{
+ unsigned char version[SZ_VERSION] = {0};
+ char buffer[HDR_NAME] = {0};
+ size_t l_name;
+ char term = 0;
+
+ l_name = strlen(name);
+ if (l_name > EXAR_NAME_MAX)
+ {
+ fprintf(stderr, "Filename too long\n");
+ return EE_ERROR;
+ }
+
+ LOG(2, "Writing version header (%s)\n", EXAR_VERSION);
+
+ memcpy(version, EXAR_VERSION, sizeof(version));
+ if (fwrite(version, 1, sizeof(version), f) != sizeof(version))
+ {
+ fprintf(stderr, "Failed to write %zu bytes", sizeof(version));
+ return EE_ERROR;
+ }
+
+ LOG(2, "Writing file header for %s\n", name);
+
+ memset(buffer, 0, sizeof(buffer));
+ buffer[HDR_DFLAG] = flag;
+ snprintf(buffer + HDR_SIZE, SZ_SIZE, "%.13x", flag == FILE_FLAG ? (unsigned int)r : 0);
+ if (fwrite(buffer, 1, HDR_NAME, f) != HDR_NAME)
+ return EE_ERROR;
+ if (fwrite(name, 1, l_name, f) != l_name)
+ return EE_ERROR;
+ if (fwrite(&term, 1, 1, f) != 1)
+ return EE_ERROR;
+
+ return EE_OK;
+}
+
+static int
+ftw_pack(const char *fpath, const struct stat *st, int tf)
+{
+ (void)tf;
+
+ int result = -1;
+ unsigned char rbuf[512];
+ size_t r;
+ FILE *f = NULL;
+ char flag;
+ const char *stripped = &fpath[s_offset];
+
+ LOG(1, "Packing %s (archive path: %s)\n", fpath, stripped);
+ if (S_ISDIR(st->st_mode))
+ flag = DIR_FLAG;
+ else if (S_ISREG(st->st_mode))
+ {
+ flag = FILE_FLAG;
+ LOG(3, "Opening %s for reading\n", fpath);
+ f = fopen(fpath, "r");
+ if (f == NULL)
+ {
+ perror(fpath);
+ return 0;
+ }
+ }
+ else
+ {
+ LOG(1, "Only directories and regular files will be packed, ignoring %s\n", fpath);
+ return 0;
+ }
+
+ if (write_file_header(s_out, stripped, flag, st->st_size) != 0)
+ goto finish;
+
+ if (f != NULL)
+ {
+ LOG(2, "Writing %s (%jd bytes)\n", stripped, (intmax_t)(st->st_size));
+ while ((r = fread(rbuf, 1, sizeof(rbuf), f)) > 0)
+ {
+ if (fwrite(rbuf, 1, r, s_out) != r)
+ {
+ fprintf(stderr, "Failed to write %zu bytes", r);
+ goto finish;
+ }
+ }
+ }
+ result = 0;
+finish:
+ close_file(f, fpath);
+ return result;
+}
+static int
+pack (const char *archive, const char *path, const char *mode)
+{
+ int ret = EE_OK;
+
+ LOG(3, "Opening %s for writing\n", archive);
+ if ((s_out = fopen(archive, mode)) == NULL)
+ {
+ perror(archive);
+ return EE_ERROR;
+ }
+
+ ret = ftw(path, ftw_pack, MAX_FILE_HANDLES);
+
+ LOG(3, "Closing %s\n", archive);
+
+ fclose(s_out);
+ return ret;
+}
+
+int
+exar_pack(const char *path)
+{
+ assert(path != NULL);
+
+ char archive[EXAR_NAME_MAX];
+ int i = 0;
+
+ s_offset = get_offset(archive, sizeof(archive), path, &i);
+
+ strncpy(&archive[i], "." EXTENSION, sizeof(archive) - i);
+
+ return pack(archive, path, "w");
+}
+int
+exar_append(const char *archive, const char *path)
+{
+ assert(path != NULL);
+ char stripped[EXAR_NAME_MAX];
+
+ s_offset = get_offset(stripped, sizeof(stripped), path, 0);
+
+ return pack(archive, path, "a");
+}
+
+int
+exar_unpack(const char *archive, const char *dest)
+{
+ assert(archive != NULL);
+
+ struct exar_header_s header = EXAR_HEADER_EMPTY;
+ int ret = EE_ERROR;
+ FILE *of, *f = NULL;
+ unsigned char buf[512];
+ size_t r = 0;
+ int status;
+
+ if ((f = open_archive(archive, "r")) == NULL)
+ goto finish;
+
+ if (dest != NULL)
+ {
+ LOG(2, "Changing to directory %s\n", dest);
+ if (chdir(dest) != 0)
+ {
+ perror(dest);
+ goto finish;
+ }
+ }
+
+ while (1)
+ {
+ status = get_file_header(f, &header);
+ if (status == EE_EOF)
+ break;
+ else if (status == EE_ERROR)
+ goto finish;
+
+ if (header.eh_flag == DIR_FLAG)
+ {
+ LOG(1, "Creating directory %s\n", header.eh_name);
+ if (mkdir(header.eh_name, 0755) != 0 && errno != EEXIST)
+ {
+ perror(header.eh_name);
+ goto finish;
+ }
+ }
+ else
+ {
+ LOG(1, "Unpacking %s\n", header.eh_name);
+
+ LOG(3, "Opening %s for writing\n", header.eh_name);
+ of = fopen(header.eh_name, "w");
+ if (of == NULL)
+ {
+ perror(header.eh_name);
+ goto finish;
+ }
+
+ LOG(2, "Writing %s (%jd bytes)\n", header.eh_name, (intmax_t)header.eh_size);
+ for (off_t i=0; i<header.eh_size; i += sizeof(buf))
+ {
+ if ( (r = fread(buf, 1, MIN(sizeof(buf), (size_t)header.eh_size - i), f)) != 0)
+ {
+ if (fwrite(buf, 1, r, of) != r)
+ {
+ fprintf(stderr, "Failed to write %zu bytes\n", r);
+ goto finish;
+ }
+ }
+ }
+ LOG(3, "Closing %s\n", header.eh_name);
+ fclose(of);
+ }
+ }
+ ret = EE_OK;
+finish:
+ close_file(f, archive);
+ return ret;
+}
+
+unsigned char *
+exar_extract(const char *archive, const char *file, off_t *s)
+{
+ assert(archive != NULL && file != NULL);
+
+ return extract(archive, file, s, strcmp);
+}
+unsigned char *
+exar_search_extract(const char *archive, const char *file, off_t *s)
+{
+ assert(archive != NULL && file != NULL);
+
+ return extract(archive, file, s, find_cmp);
+}
+int
+exar_delete(const char *archive, const char *file)
+{
+ assert(archive != NULL && file != NULL);
+
+ int result = EE_ERROR;
+ struct exar_header_s header = EXAR_HEADER_EMPTY;
+ FILE *f = NULL, *ftmp = NULL;
+ char tmp_file[128];
+ char dir_name[EXAR_NAME_MAX-1] = {0};
+ unsigned char rbuf;
+ size_t dir_length = 0;
+ int status = EE_ERROR;
+
+ if ((f = open_archive(archive, "r")) == NULL)
+ goto finish;
+
+ snprintf(tmp_file, sizeof(tmp_file), "%s.XXXXXX", archive);
+ if (mktemp(tmp_file) == NULL)
+ {
+ fprintf(stderr, "Failed to create temporary file\n");
+ goto finish;
+ }
+
+ LOG(3, "Opening %s for writing\n", tmp_file);
+ if ((ftmp = fopen(tmp_file, "w")) == NULL)
+ goto finish;
+
+ while ((status = get_file_header(f, &header)) == EE_OK)
+ {
+ if (strcmp(header.eh_name, file) == 0)
+ {
+ if (header.eh_flag == FILE_FLAG)
+ {
+ LOG(1, "Skipping %s\n", header.eh_name);
+ fseek(f, header.eh_size, SEEK_CUR);
+ }
+ else if (header.eh_flag == DIR_FLAG)
+ dir_length = snprintf(dir_name, sizeof(dir_name), "%s/", header.eh_name);
+ }
+ else if (*dir_name && strncmp(dir_name, header.eh_name, dir_length) == 0)
+ {
+ if (header.eh_flag == FILE_FLAG)
+ {
+ LOG(1, "Skipping %s\n", header.eh_name);
+ fseek(f, header.eh_size, SEEK_CUR);
+ }
+ }
+ else
+ {
+ LOG(1, "Packing %s\n", header.eh_name);
+ write_file_header(ftmp, header.eh_name, header.eh_flag, header.eh_size);
+ if (header.eh_flag == FILE_FLAG)
+ {
+ LOG(2, "Copying %s (%jd bytes)\n", header.eh_name, (intmax_t)header.eh_size);
+ for (off_t s=0; s<header.eh_size; s++)
+ {
+ if (fread(&rbuf, 1, 1, f) != 1 || fwrite(&rbuf, 1, 1, ftmp) != 1)
+ {
+ fprintf(stderr, "Error copying %s\n", header.eh_name);
+ goto finish;
+ }
+ }
+ }
+ }
+ }
+finish:
+ if (status == EE_EOF)
+ {
+ LOG(2, "Copying %s to %s\n", tmp_file, archive);
+ if (rename(tmp_file, archive) == -1)
+ perror(archive);
+ else
+ result = EE_OK;
+ }
+ else if (status == EE_ERROR)
+ {
+ LOG(1, "An error occured, removing temporary file\n");
+ unlink(tmp_file);
+ }
+ close_file(f, archive);
+ close_file(ftmp, tmp_file);
+ return result;
+}
+void
+exar_info(const char *archive)
+{
+ assert(archive != NULL);
+
+ FILE *f = NULL;
+ struct exar_header_s header = EXAR_HEADER_EMPTY;
+
+ if ((f = open_archive(archive, "r")) == NULL)
+ goto finish;
+ while(next_file(f, &header) == EE_OK)
+ fprintf(stdout, "%c %-14jd %s\n", header.eh_flag, (intmax_t)header.eh_size, header.eh_name);
+finish:
+ close_file(f, archive);
+}
+int
+exar_contains(const char *archive, const char *name)
+{
+ assert(archive != NULL);
+
+ return contains(archive, name, strcmp);
+}
+int
+exar_search_contains(const char *archive, const char *name)
+{
+ assert(archive != NULL);
+
+ return contains(archive, name, find_cmp);
+}
+
+int
+exar_check_version(const char *archive)
+{
+ assert(archive != NULL);
+
+ int result = EE_ERROR;
+ FILE *f;
+ if ( (f = fopen(archive, "r")) != NULL && check_version(f, 0) == EE_OK)
+ result = EE_OK;
+ close_file(f, archive);
+ return result;
+}
+void
+exar_verbose(unsigned char v)
+{
+ s_verbose = v & EXAR_VERBOSE_MASK;
+}