603 lines
15 KiB
C
603 lines
15 KiB
C
#include <stdio.h>
|
|
#include <sys/stat.h> /* S_IRUSR */
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <malloc.h>
|
|
|
|
#include "pack.h"
|
|
#include "log.h"
|
|
#include "md5.h"
|
|
|
|
/**
|
|
* @file pack.c
|
|
*
|
|
* @brief Stores data in funky format.
|
|
*
|
|
* Format:
|
|
* -- Index.
|
|
* -- Magic number (16 bytes).
|
|
* -- Number of files (uint32_t).
|
|
* -- Files in format name/location.
|
|
* -- File name (128 bytes max, ending in \0).
|
|
* -- File location (uint32_t).
|
|
* -- File data in format Size/Data.
|
|
* -- File size (uint32_t).
|
|
* -- File data (char*).
|
|
* -- File MD5 (16 byte char*).
|
|
* -- EOF.
|
|
*
|
|
* -- Write magic number and number of files.
|
|
* -- Write the index.
|
|
* -- Pack the files.
|
|
*/
|
|
|
|
#undef DEBUG /* This will be spammy. */
|
|
#define DEBUG(str, args...) do{;} while(0)
|
|
|
|
#define BLOCKSIZE 128*1024 /**< The read/write block size */
|
|
|
|
/* Max filename length. */
|
|
#define MAX_FILENAME 100 /**< Maximum file name length. */
|
|
|
|
#define PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH /**< Default permissions. */
|
|
|
|
const uint64_t magic = 0x25524573; /**< File magic number: sER% */
|
|
|
|
/**
|
|
* @fn static off_t getfilesize(const char* filename)
|
|
*
|
|
* @brief Get the file size.
|
|
* @param filename File to get the size of.
|
|
* @return The size of the file.
|
|
*/
|
|
static off_t getfilesize(const char* filename) {
|
|
#ifdef _POSIX_SOURCE
|
|
struct stat file;
|
|
|
|
if(!stat(filename, &file))
|
|
return file.st_size;
|
|
|
|
ERR("Unable to get filesize of %s", filename);
|
|
return 0;
|
|
#else
|
|
long size;
|
|
FILE* fp = fopen(filename, "rb");
|
|
if(fp == NULL) return 0;
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
size = ftell(fp);
|
|
|
|
fclose(fp);
|
|
|
|
return size;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @fn int pack_check(const char* filename)
|
|
*
|
|
* @brief Check to see if a file is a packfile
|
|
* @param filename Name of the file to check.
|
|
* @return 1 if it is a packfile, 0 if it isn't and -1 on error.
|
|
*/
|
|
int pack_check(const char* filename) {
|
|
int ret;
|
|
char* buf;
|
|
|
|
buf = malloc(sizeof(magic));
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
int fd = open(filename, O_RDONLY);
|
|
if(fd == -1) {
|
|
ERR("Error opening %s: %s", filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
if(read(fd, buf, sizeof(magic)) != sizeof(magic)) {
|
|
ERR("Error reading magic number: %s", strerror(errno));
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1;
|
|
close(fd);
|
|
#else
|
|
FILE* file = fopen(filename, "rb");
|
|
if(file == NULL) {
|
|
ERR("Error opening '%s': %s", filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
buf = malloc(sizeof(magic));
|
|
if(fread(buf, 1, sizeof(magic), file) != sizeof(magic)) {
|
|
ERR("Error reading magic number: %s", strerror(errno));
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1;
|
|
fclose(file);
|
|
#endif
|
|
|
|
free(buf);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @fn int pack_files(const char* outfile, const char** infiles, const uint32_t nfiles)
|
|
*
|
|
* @brief Packages files into a packfile.
|
|
* @param outfile Name of the file to output to.
|
|
* @param infiles Array of filenames to package.
|
|
* @param nfiles Number of filenames in infiles.
|
|
* @return 0 on success.
|
|
*/
|
|
#ifdef _POSIX_SOURCE
|
|
#define WRITE(b,n) if(write(outfd, b, n)==-1) { \
|
|
ERR("Error writing to file: %s", strerror(errno)); \
|
|
free(buf); return -1; }
|
|
#else
|
|
#define WRITE(b,n) if(fwrite(b, 1, n, outf)==0) { \
|
|
ERR("Error writing to file: %s", strerror(errno)); \
|
|
free(buf); return -1; }
|
|
#endif
|
|
int pack_files(const char* outfile, const char** infiles, const uint32_t nfiles) {
|
|
void* buf;
|
|
#ifdef _POSIX_SOURCE
|
|
struct stat file;
|
|
int outfd, infd;
|
|
#else
|
|
FILE* outf, *inf;
|
|
#endif
|
|
uint32_t i;
|
|
int namesize;
|
|
uint32_t indexsize, pointer;
|
|
int bytes;
|
|
const uint8_t b = '\0';
|
|
|
|
for(namesize = 0, i = 0; i < nfiles; i++) {
|
|
/* Make sure file exists before writing. */
|
|
#ifdef _POSIX_SOURCE
|
|
if(stat(infiles[i], &file)) {
|
|
#else
|
|
if(getfilesize(infiles[i]) == 0) {
|
|
#endif
|
|
ERR("File %s does not exist", infiles[i]);
|
|
return -1;
|
|
}
|
|
if(strlen(infiles[i]) > MAX_FILENAME) {
|
|
ERR("filename '%s' is too long, should be only %d characters", infiles[i],
|
|
MAX_FILENAME);
|
|
return -1;
|
|
}
|
|
namesize += strlen(infiles[i]);
|
|
}
|
|
|
|
indexsize = (sizeof(magic) + 4 + /* Magic number and number of files. */
|
|
namesize + /* Total length of file names. */
|
|
(1+4)*nfiles); /* File size and extra end of string char '\0'. */
|
|
DEBUG("Index size is %d", indexsize);
|
|
|
|
/* Create the output file. */
|
|
#ifdef _POSIX_SOURCE
|
|
outfd = creat(outfile, PERMS);
|
|
if(outfd == -1) {
|
|
#else
|
|
outf = fopen(outfile, "wb");
|
|
if(outf == NULL) {
|
|
#endif
|
|
ERR("Unable to open %s for writing", outfile);
|
|
return -1;
|
|
}
|
|
|
|
/* Index! */
|
|
buf = malloc(BLOCKSIZE);
|
|
|
|
/* Magic number. */
|
|
WRITE(&magic, sizeof(magic));
|
|
DEBUG("Wrote magic number");
|
|
/* Number of files. */
|
|
WRITE(&nfiles, sizeof(nfiles));
|
|
DEBUG("Wrote number of files: %d", nfiles);
|
|
/* Create file dependent index part. */
|
|
pointer = indexsize;
|
|
for(i = 0; i < nfiles; i++) {
|
|
WRITE(infiles[i], strlen(infiles[i]));
|
|
DEBUG("File: '%s' at %d", infiles[i], pointer);
|
|
WRITE(&b, 1);
|
|
WRITE(&pointer, 4);
|
|
pointer += 4 + getfilesize(infiles[i]) + 16; /* Set pointer to be next file pos. */
|
|
}
|
|
|
|
/* Data! */
|
|
md5_state_t md5;
|
|
md5_byte_t* md5val = malloc(16);
|
|
for(i = 0; i < nfiles; i++) {
|
|
bytes = (uint32_t)getfilesize(infiles[i]);
|
|
WRITE(&bytes, 4); /* filesize. */
|
|
DEBUG("About to write '%s' of %d bytes", infiles[i], bytes);
|
|
md5_init(&md5);
|
|
#ifdef _POSIX_SOURCE
|
|
infd = open(infiles[i], O_RDONLY);
|
|
while((bytes = read(infd, buf, BLOCKSIZE))) {
|
|
#else
|
|
inf = fopen(infiles[i], "rb");
|
|
while((bytes = fread(buf, 1, BLOCKSIZE, inf))) {
|
|
#endif
|
|
WRITE(buf, bytes); /* Data. */
|
|
md5_append(&md5, buf, bytes);
|
|
}
|
|
md5_finish(&md5, md5val);
|
|
WRITE(md5val, 16);
|
|
#ifdef _POSIX_SOURCE
|
|
close(infd);
|
|
#else
|
|
fclose(inf);
|
|
#endif
|
|
DEBUG("Wrote file '%s'", infiles[i]);
|
|
}
|
|
free(md5val);
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
close(outfd);
|
|
#else
|
|
fclose(outf);
|
|
#endif
|
|
free(buf);
|
|
|
|
DEBUG("Packfile success\n\t%d files\n\t%d bytes",
|
|
nfiles, (int)getfilesize(outfile));
|
|
return 0;
|
|
}
|
|
#undef WRITE
|
|
|
|
/**
|
|
* @fn int pack_open(Packfile* file, const char* packfile, const char* filename)
|
|
*
|
|
* @brief Open a file in the packfile for reading.
|
|
* @param file Packfile to store data into.
|
|
* @param packfile Path to real packfile.
|
|
* @param filename Name of the file within th. packfile.
|
|
* @return 0 on success.
|
|
*/
|
|
#ifdef _POSIX_SOURCE
|
|
#define READ(b,n) if(read(file->fd, (b), (n))!=(n)) { \
|
|
ERR("Too few bytes read. Expected more."); \
|
|
free(buf); return -1; }
|
|
#else
|
|
#define READ(b,n) if(fread((b), 1, (n), file->fp)!=(n)) { \
|
|
ERR("Fewer bytes read then expected"); \
|
|
free(buf); return -1; }
|
|
#endif
|
|
int pack_open(Packfile* file, const char* packfile, const char* filename) {
|
|
int j;
|
|
uint32_t nfiles, i;
|
|
char* buf = malloc(MAX_FILENAME);
|
|
|
|
file->start = file->end = 0;
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
file->fd = open(packfile, O_RDONLY);
|
|
if(file->fd == -1) {
|
|
#else
|
|
file->fp = fopen(packfile, "rb");
|
|
if(file->fp == NULL) {
|
|
#endif
|
|
ERR("Error opening %s: %s", filename, strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
READ(buf, sizeof(magic)); /* Make sure it's a packfile. */
|
|
if(memcmp(buf, &magic, sizeof(magic))) {
|
|
ERR("File %s is not a valid packfile", filename);
|
|
return -1;
|
|
}
|
|
|
|
READ(&nfiles, 4);
|
|
for(i = 0; i < nfiles; i++) {
|
|
/* Start to search files. */
|
|
j = 0;
|
|
READ(&buf[j], 1); /* Get the name. */
|
|
while(buf[j++] != '\0')
|
|
READ(&buf[j], 1);
|
|
|
|
if(strcmp(filename, buf) == 0) {
|
|
/* We found the file. */
|
|
READ(&file->start, 4);
|
|
DEBUG("'%s' found at %d", filename, file->start);
|
|
break;
|
|
}
|
|
#ifdef _POSIX_SOURCE
|
|
lseek(file->fd, 4, SEEK_CUR); /* Ignore the file location. */
|
|
#else
|
|
fseek(file->fp, 4, SEEK_CUR);
|
|
#endif
|
|
}
|
|
free(buf);
|
|
|
|
if(file->start) {
|
|
/* Go to the beginning of the file. */
|
|
#ifdef _POSIX_SOURCE
|
|
if((uint32_t)lseek(file->fd, file->start, SEEK_SET) != file->start) {
|
|
#else
|
|
fseek(file->fp, file->start, SEEK_SET);
|
|
if(errno) {
|
|
#endif
|
|
ERR("Failure to seek to file start: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
READ(&file->end, 4);
|
|
DEBUG("\t%d bytes", file->end);
|
|
file->pos = file->start;
|
|
file->end += file->start;
|
|
} else {
|
|
ERR("File '%s' not found in packfile '%s'", filename, packfile);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#undef READ
|
|
|
|
/**
|
|
* @fn ssize_t pack_read(Packfile* file, void* buf, size_t count)
|
|
*
|
|
* @brief Reads data from a packfile.
|
|
*
|
|
* Behaves like POSIX read.
|
|
* @param file Opened packfile to read data from.
|
|
* @param buf Allocated buffer to read into.
|
|
* @param count Bytes to read.
|
|
* @return Bytes to read or -1 on error.
|
|
*/
|
|
ssize_t pack_read(Packfile* file, void* buf, size_t count) {
|
|
if(file->pos + count > file->end)
|
|
count = file->end - file->pos; /* Can't go past the end! */
|
|
if(count == 0) return 0;
|
|
|
|
int bytes;
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
if((bytes = read(file->fd, buf, count)) == -1) {
|
|
#else
|
|
if((bytes = fread(buf, 1, count, file->fp)) == -1) {
|
|
#endif
|
|
ERR("Error while reading file: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
file->pos += bytes;
|
|
|
|
return bytes;
|
|
}
|
|
|
|
/**
|
|
* @fn off_t pack_seek(Packfile* file, off_t offset, int whence)
|
|
*
|
|
* @brief Seeks within a file inside a packfile.
|
|
*
|
|
* Behaves like lseek/fseek.
|
|
*
|
|
* @todo It's broken, needs fixing.
|
|
* @param file File to seek.
|
|
* @param offset Position to seek to.
|
|
* @param whence Either SEEK_SET, SEEK_CUR or SEEK_END.
|
|
* @return The position moved to.
|
|
*/
|
|
off_t pack_seek(Packfile* file, off_t offset, int whence) {
|
|
DEBUG("Attempting to seek offset: %d, whence: %d", offset, whence);
|
|
off_t ret;
|
|
switch(whence) {
|
|
#ifdef _POSIX_SOURCE
|
|
case SEEK_SET:
|
|
if((file->start + offset) > file->end) return -1;
|
|
ret = lseek(file->fd, file->start + offset, SEEK_SET);
|
|
if(ret != ((off_t)file->start + offset)) return -1;
|
|
break;
|
|
case SEEK_CUR:
|
|
if((file->start + offset) > file->end) return -1;
|
|
ret = lseek(file->fd, file->pos + offset, SEEK_SET);
|
|
if(ret != ((off_t)file->pos + offset)) return -1;
|
|
break;
|
|
case SEEK_END:
|
|
if((file->end - offset) < file->start) return -1;
|
|
ret = lseek(file->fd, file->end - offset - 1, SEEK_SET);
|
|
if(ret != ((off_t)file->end - offset)) return -1;
|
|
break;
|
|
#else
|
|
case SEEK_SET:
|
|
if((file->start + offset) > file->end) return -1;
|
|
ret = fseek(file->fd, file->start + offset, SEEK_SET);
|
|
if(ret != (file->start + offset)) return -1;
|
|
break;
|
|
case SEEK_CUR:
|
|
if((file->start + offset) > file->end) return -1;
|
|
ret = flseek(file->fd, file->pos + offset, SEEK_SET);
|
|
if(ret != (file->pos + offset)) return -1;
|
|
break;
|
|
case SEEK_END:
|
|
if((file->end - offset) < file->start) return -1;
|
|
ret = flseek(file->fd, file->end - offset - 1, SEEK_SET);
|
|
if(ret != (file->end - offset)) return -1;
|
|
break;
|
|
#endif
|
|
default:
|
|
ERR("Whence is not one of SEEK_SET, SEEK_CUR or SEEK_END");
|
|
return -1;
|
|
}
|
|
return ret - file->start;
|
|
}
|
|
|
|
/**
|
|
* @fn long pack_tell(Packfile* file)
|
|
*
|
|
* @brief Get the current position in the file.
|
|
* @param file Packfile to get the position from.
|
|
* @return The current position in the file.
|
|
*/
|
|
long pack_tell(Packfile* file) {
|
|
return file->pos - file->start;
|
|
}
|
|
|
|
/**
|
|
* @fn void* pack_readfile(const char* packfile, const char* filename, uint32_t* filesize)
|
|
*
|
|
* @brief Read an entire file into memory.
|
|
* @param packfile Name of the packfile to read from.
|
|
* @param filename Name of the packed file to read.
|
|
* @param filesize Is set to the size of the file.
|
|
* @return A pointer to the sata in the file or NULL if an error accured.
|
|
*/
|
|
void* pack_readfile(const char* packfile, const char* filename, uint32_t* filesize) {
|
|
Packfile* file = (Packfile*)malloc(sizeof(Packfile));
|
|
void* buf;
|
|
char* str;
|
|
int size, bytes;
|
|
|
|
if(filesize)
|
|
*filesize = 0;
|
|
|
|
if(pack_open(file, packfile, filename)) {
|
|
ERR("Opening packfile");
|
|
return NULL;
|
|
}
|
|
DEBUG("Opened file '%s' from '%s'", filename, packfile);
|
|
|
|
/* Read the entire file. */
|
|
size = file->end - file->start;
|
|
buf = malloc(size+1);
|
|
if(buf == NULL) {
|
|
ERR("Unable to allocate %d bytes of memory!", size+1);
|
|
free(file);
|
|
return NULL;
|
|
}
|
|
if((bytes = pack_read(file, buf, size)) != size) {
|
|
ERR("Reading '%s' from packfile '%s'. Expected %d bytes got %d bytes",
|
|
filename, packfile, size, bytes);
|
|
free(buf);
|
|
free(file);
|
|
return NULL;
|
|
}
|
|
DEBUG("Read %d bytes from '%s'", bytes, filename);
|
|
str = buf;
|
|
str[size] = '\0'; /* Append size '\0' for it to validate as a string. */
|
|
|
|
/* Check the md5. */
|
|
md5_state_t md5;
|
|
md5_byte_t* md5val = malloc(16);
|
|
md5_byte_t* md5fd = malloc(16);
|
|
md5_init(&md5);
|
|
md5_append(&md5, buf, bytes);
|
|
md5_finish(&md5, md5val);
|
|
#ifdef _POSIX_SOURCE
|
|
if((bytes = read(file->fd, md5fd, 16)) == -1)
|
|
#else
|
|
if((bytes = fread(md5fd, 1, 16, file->fp)) == -1)
|
|
#endif
|
|
WARN("Failure to read MD5, continuing anyway..");
|
|
else if(memcmp(md5val, md5fd, 16))
|
|
WARN("MD5 gives different value, possible memory corruption, continuing..");
|
|
free(md5val);
|
|
free(md5fd);
|
|
|
|
/* Cleanup. */
|
|
if(pack_close(file) == -1) {
|
|
ERR("Closing packfile");
|
|
free(file);
|
|
return NULL;
|
|
}
|
|
DEBUG("Closed '%s' in '%s'", filename, packfile);
|
|
free(file);
|
|
|
|
if(filesize)
|
|
*filesize = size;
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @fn char** pack_listfiles(const char* packfile, uint32_t* nfiles)
|
|
*
|
|
* @brief Get what files are in the packfile.
|
|
*
|
|
* Each name must be freed individually afterwords and the array of names too.
|
|
* @param packfile Packfile to query it's internal files.
|
|
* @param nfiles Stores the amount of files in packfile.
|
|
* @return An array of filenames in packfile.
|
|
*/
|
|
#ifdef _POSIX_SOURCE
|
|
#define READ(b,n) if(read(fd, (b), (n))!=(n)) { \
|
|
ERR("Too few bytes read"); \
|
|
return NULL; }
|
|
#else
|
|
#define READ(b,n) if(fread((b), 1, (n), fp) != (n)) { \
|
|
ERR("Too few bytes read"); \
|
|
return NULL; }
|
|
#endif
|
|
char** pack_listfiles(const char* packfile, uint32_t* nfiles) {
|
|
#ifdef _POSIX_SOURCE
|
|
int fd;
|
|
#else
|
|
FILE* fp;
|
|
#endif
|
|
int j;
|
|
uint32_t i;
|
|
char** filenames;
|
|
char* buf = malloc(sizeof(magic));
|
|
|
|
*nfiles = 0;
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
fd = open(packfile, O_RDONLY);
|
|
if(fd == -1) {
|
|
#else
|
|
fp = fopen(packfile, "rb");
|
|
if(fp == NULL) {
|
|
#endif
|
|
ERR("opening %s: %s", packfile, strerror(errno));
|
|
return NULL;
|
|
}
|
|
READ(buf, sizeof(magic)); /* Make sure it is a packfile. */
|
|
if(memcmp(buf, &magic, sizeof(magic))) {
|
|
ERR("File %s is not a valid packfile", packfile);
|
|
return NULL;
|
|
}
|
|
|
|
READ(nfiles, 4);
|
|
filenames = malloc(((*nfiles)+1)*sizeof(char*));
|
|
for(i = 0; i < *nfiles; i++) {
|
|
/* Start searching files. */
|
|
j = 0;
|
|
filenames[i] = malloc(MAX_FILENAME * sizeof(char));
|
|
READ(&filenames[i][j], 1); /* Get the name. */
|
|
while(filenames[i][j++] != '\0')
|
|
READ(&filenames[i][j], 1);
|
|
READ(buf, 4); /* skip the location. */
|
|
}
|
|
free(buf);
|
|
|
|
#ifdef _POSIX_SOURCE
|
|
close(fd);
|
|
#else
|
|
fclose(fp);
|
|
#endif
|
|
|
|
return filenames;
|
|
}
|
|
#undef READ
|
|
|
|
/**
|
|
* @fn int pack_close(Packfile* file)
|
|
*
|
|
* @brief Closes a packfile.
|
|
* @param file Packfile to close.
|
|
* @return 0 on success.
|
|
*/
|
|
int pack_close(Packfile* file) {
|
|
int i;
|
|
#ifdef _POSIX_SOURCE
|
|
i = close(file->fd);
|
|
#else
|
|
i = fclose(file->fp);
|
|
#endif
|
|
return (i) ? -1 : 0;
|
|
}
|
|
|