#include #include // S_IRUSR #include #include #include #include #include "pack.h" #include "log.h" #include "md5.h" // == Store data in a funky format. ======================= // Format: // -- Index (in 512 byte chunks). // -- 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) // The read/write block size. #define BLOCKSIZE 128*1024 // Max filename length. #define MAX_FILENAME 100 #define PERMS S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH const uint64_t magic = 0x25524573; // sER% // Grab the filesize. static off_t getfilesize(const char* filename) { struct stat file; if(!stat(filename, &file)) return file.st_size; ERR("Unable to get filesize of %s", filename); return 0; } // Return true if filename is a Packfile. int pack_check(const char* filename) { int fd = open(filename, O_RDONLY); if(fd == -1) { ERR("Error opening %s: %s", filename, strerror(errno)); return -1; } char* buf = malloc(sizeof(magic)); if(read(fd, buf, sizeof(magic)) != sizeof(magic)) { ERR("Error reading magic number: %s", strerror(errno)); free(buf); return -1; } int ret = (memcmp(buf, &magic, sizeof(magic))==0) ? 0 : 1; free(buf); return ret; } // Pack nfiles, infiles into outfile. #define WRITE(f,b,n) if(write(f,b,n)==-1) { \ ERR("Error writing to file: %s", strerror(errno)); \ free(buf); return -1; } int pack_files(const char* outfile, const char** infiles, const uint32_t nfiles) { void* buf; struct stat file; uint32_t i; int namesize; int outfd, infd; 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. if(stat(infiles[i], &file)) { 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. outfd = creat(outfile, PERMS); if(outfd == -1) { ERR("Unable to open %s for writing", outfile); return -1; } // Index! buf = malloc(BLOCKSIZE); // Magic number. WRITE(outfd, &magic, sizeof(magic)); DEBUG("Wrote magic number"); // Number of files. WRITE(outfd, &nfiles, sizeof(nfiles)); DEBUG("Wrote number of files: %d", nfiles); // Create file dependent index part. pointer = indexsize; for(i = 0; i < nfiles; i++) { WRITE(outfd, infiles[i], strlen(infiles[i])); DEBUG("File: '%s' at %d", infiles[i], pointer); WRITE(outfd, &b, 1); WRITE(outfd, &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(outfd, &bytes, 4); // filesize. DEBUG("About to write '%s' of %d bytes", infiles[i], bytes); infd = open(infiles[i], O_RDONLY); md5_init(&md5); while((bytes = read(infd, buf, BLOCKSIZE))) { WRITE(outfd, buf, bytes); // Data. md5_append(&md5, buf, bytes); } md5_finish(&md5, md5val); WRITE(outfd, md5val, 16); close(infd); DEBUG("Wrote file '%s'", infiles[i]); } free(md5val); close(outfd); free(buf); DEBUG("Packfile success\n\t%d files\n\t%d bytes", nfiles, (int)getfilesize(outfile)); return 0; } #undef WRITE // Opens the filename in packfile for reading. #define READ(f,b,n) if(read(f,b,n) != n) { \ ERR("Too few bytes read. Expected more."); \ free(buf); return -1; } 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; file->fd = open(packfile, O_RDONLY); if(file->fd == -1) { ERR("Error opening %s: %s", filename, strerror(errno)); return -1; } READ(file->fd, 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(file->fd, &nfiles, 4); for(i = 0; i < nfiles; i++) { // Start to search files. j = 0; READ(file->fd, &buf[j], 1); // Get the name. while(buf[j++] != '\0') READ(file->fd, &buf[j], 1); if(strcmp(filename, buf) == 0) { // We found the file. READ(file->fd, &file->start, 4); DEBUG("'%s' found at %d", filename, file->start); break; } lseek(file->fd, 4, SEEK_CUR); // Ignore the file location. } free(buf); if(file->start) { // Go to the beginning of the file. if((uint32_t)lseek(file->fd, file->start, SEEK_SET) != file->start) { ERR("Failure to seek to file start: %s", strerror(errno)); return -1; } if(read(file->fd, &file->end, 4) != 4) { ERR("Too few bytes read. Expected more."); return -1; } 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 // Read count bytes from file and put them into buf. 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; if((bytes = read(file->fd, buf, count)) == -1) { ERR("Error while reading file: %s", strerror(errno)); return -1; } file->pos += bytes; return bytes; } // Loads an entire file into memory and returns a pointer to it. void* pack_readfile(const char* packfile, const char* filename, uint32_t* filesize) { Packfile* file = (Packfile*)malloc(sizeof(Packfile)); void* buf; 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 thing. size = file->end - file->start; buf = malloc(size); 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); // 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);; if((bytes = read(file->fd, md5fd, 16)) == -1) 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; } // Close the packfile. int pack_close(Packfile* file) { int i = close(file->fd); return (i) ? -1 : 0; } // Load the filenames int the packfile to filenames. // filenames should be freed after use // On error if filenames is (char**)-1. #define READ(f,b,n) if(read(f,b,n)!=n) { ERR("Too few bytes read. Expected more."); return; } void pack_listfiles(const char* packfile, char** filenames, uint32_t* nfiles) { int fd, j; uint32_t i; char* buf = malloc(sizeof(magic)); *nfiles = 0; filenames = malloc(sizeof(char*)); filenames = (char**)-1; fd = open(packfile, O_RDONLY); if(fd == -1) { ERR("opening %s: %s", packfile, strerror(errno)); return; } READ(fd, buf, sizeof(magic)); // Make sure it is a packfile. if(memcpy(buf, &magic, sizeof(magic))) { ERR("File %s is not a valid packfile", packfile); return; } free(buf); READ(fd, &nfiles, 4); filenames = realloc(filenames,(*nfiles+1)*sizeof(char*)); for(i = 0; i < *nfiles; i++) { // Start searching files. j = 0; filenames[i] = malloc(MAX_FILENAME * sizeof(char)); READ(fd, &filenames[i][j], 1); // Get the name. while(filenames[i][j++] != '\0') READ(fd, &filenames[i][j], 1); } filenames[i] == NULL; close(fd); } #undef READ