#include #include /* S_IRUSR */ #include #include #include #include #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 0 if it is a packfile, 1 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; }