/* Last edited: Jun 18 10:29 1999 (fw) */ /* * Handles compression and decompression. * Two functions are available. One compresses files, and the other opens * (read only) a compressed file and returns a FILE pointer. * Neither of these two are likely to work under Windows or MacOS. */ #include #include #include #include #include #include #include #include "filecompress.h" #define BS 8192 /* * This contains the last used compression method. */ static int compression_used = 0; typedef struct { char magic[2]; long int offset; char *compress; char *uncompress; char *extension; } Magics; /* * The list of magic numbers. The attempted order for compression is the * order of entries in this file. * * NB: bzip gives very good (better than gzip) results, is sometimes faster for * compression, but unfortunately much slower (4x?) for decompression. Most * people won't have it anyway. */ static Magics magics[] = { {{'B', 'Z'}, 0, "bzip", "bzip -d <", ".bz"}, {{'\037','\213'}, 0, "gzip", "gzip -d <", ".gz"}, {{'\037','\235'}, 0, "compress", "uncompress < ",".Z"}, {{'\037','\036'}, 0, "pack", "pcat", ".z"}, }; void set_compression_method(int method) { compression_used = method; } int get_compression_method(void) { return compression_used; } /* * Converts compress mode strings (eg "gzip") to numbers. */ int compress_str2int(char *mode) { if (strcmp(mode, "bzip") == 0) return 1; else if (strcmp(mode, "gzip") == 0) return 2; else if (strcmp(mode, "compress") == 0) return 3; else if (strcmp(mode, "pack") == 0) return 4; else return 0; } /* * Compress a file using the method set in the compression_used value * (set by set_compression_method and fopen_compressed). * * If compression succeeds, we rename the file back its original name. * * When compression_used is 0 no compression is done. */ int compress_file(char *file) { int ret; char buf[2048]; struct stat statbuf; /* Do nothing unless requested */ if (compression_used == 0) return 0; /* Execute the compression program */ sprintf(buf, "%s %s 1>/dev/null 2>/dev/null", magics[compression_used-1].compress, file); if ((ret = system(buf)) != 0) { if (ret == -1) perror(buf); else fprintf(stderr, "%s: compression failed\n", file); return 1; } /* Rename the file back */ if (-1 == stat(file, &statbuf) && errno == ENOENT) { sprintf(buf, "%s%s", file, magics[compression_used-1].extension); rename(buf, file); } return 0; } /* * Compress a file using the method set in the compression_used value * (set by set_compression_method and fopen_compressed). * * If compression succeeds, we rename the file back its original name. * * When compression_used is 0 no compression is done. */ int fcompress_file(FILE *fp) { char buf[BS]; char fname[L_tmpnam]; FILE *newfp; int len; /* Do nothing unless requested */ if (compression_used == 0) return 0; /* It's also impossible if it's stdout as we can't rewind */ if (fp == stdout) return 0; /* Copy the file pointer to a temporary file */ if (NULL == tmpnam(fname)) return 0; if (NULL == (newfp = fopen(fname, "wb+"))) return 0; fflush(fp); rewind(fp); do { len = fread(buf, 1, BS, fp); if (len > 0) fwrite(buf, 1, len, newfp); } while (!feof(fp)); fflush(newfp); fclose(newfp); /* Compress it */ compress_file(fname); /* Copy it back */ if (NULL == (newfp = fopen(fname, "rb"))) return 0; rewind(fp); do { int len = fread(buf, 1, BS, newfp); if (len > 0) fwrite(buf, 1, len, fp); } while (!feof(newfp)); ftruncate(fileno(fp), ftell(fp)); remove(fname); return 0; } /* * Returns a file pointer of an uncompressed copy of 'file'. * 'file' need not exist if 'file'.ext (eg file.gz) * exists and can be uncompressed. * * If ofp is non NULL then the original file pointer will also be returned * (opened for update) to allow writing back to the original file. In cases * of uncompressed data this is the same as the returned file pointer. */ FILE *fopen_compressed(char *file, FILE **ofp) { int num_magics = sizeof(magics) / sizeof(*magics); int i, ret, fd, try, do_del = 1; char buf[2048], fext[1024], mg[3]; char *fname, *fptr; FILE *fp; if (NULL == (fname = tmpnam(NULL))) return NULL; /* * Try opening the file and reading the magic number. * If this doesn't work, then don't worry - the filename may be * the original name which has been renamed due to compression. * (eg file.gz). */ try = 1; fd = open(file, O_RDONLY); if (fd != -1) { if (2 != read(fd, mg, 2)) { close(fd); } else { try = 0; fptr = file; } } for (i = 0; i < num_magics; i++) { /* If necessary, try opening the file as 'file'.extension */ if (try) { sprintf(fext, "%s%s", file, magics[i].extension); fptr = fext; if (-1 == (fd = open(fext, O_RDONLY))) continue; if (2 != read(fd, mg, 2)) { close(fd); continue; } } /* Check the magic numbers */ if (mg[0] == magics[i].magic[0] && mg[1] == magics[i].magic[1]) { sprintf(buf, "%s %s 1>%s 2>/dev/null", magics[i].uncompress, fptr, fname); if ((ret = system(buf)) == 0) { compression_used = i+1; break; } } if (try && fd != -1) close(fd); } if (fd != -1) close(fd); if (i == num_magics) { /* * It's only an error if the file couldn't be found. If it * exists then we'll assume that it doesn't need uncompressing. */ if (try) { return NULL; } else { do_del = 0; compression_used = 0; fname = file; } } /* * We've now got the temporary file. Open it and unlink it. * We can also keep the original fp open for those who need to * write back to it. */ if (NULL == (fp = fopen(fname, "r+b"))) if (NULL == (fp = fopen(fname, "rb"))) return NULL; if (ofp) { if (file != fname) *ofp = fopen(try ? fext : file, "r+b"); else *ofp = fp; } if (do_del) remove(fname); return fp; } /* * Returns a file pointer of an uncompressed copy of 'fp'. * * If ofp is non NULL then the original file pointer will also be returned * (opened for update) to allow writing back to the original file. In cases * of uncompressed data this is the same as the returned file pointer. */ FILE *freopen_compressed(FILE *fp, FILE **ofp) { int num_magics = sizeof(magics) / sizeof(*magics); char fname[L_tmpnam]; char buf[BS], mg[3]; FILE *newfp; int i; /* Test that it's compressed with 1 byte of magic (can't ungetc more) */ mg[0] = fgetc(fp); ungetc(mg[0], fp); for (i = 0; i < num_magics; i++) { if (mg[0] == magics[i].magic[0]) { break; } } if (i == num_magics) { return fp; } tmpnam(fname); /* Copy the file to newfp */ newfp = fopen(fname, "wb+"); if (NULL == newfp) return fp; do { int len = fread(buf, 1, BS, fp); if (len > 0) fwrite(buf, 1, len, newfp); } while (!feof(fp)); fflush(newfp); rewind(newfp); rewind(fp); /* Test that it's compressed with full magic number */ fread(mg, 1, 2, newfp); rewind(newfp); for (i = 0; i < num_magics; i++) { if (mg[0] == magics[i].magic[0] && mg[1] == magics[i].magic[1]) { break; } } if (i == num_magics) { /* Uncompressed, just return fp */ remove(fname); return newfp; } /* It's compressed, so we use fopen_compressed on fname */ fp = fopen_compressed(fname, ofp); remove(fname); if (newfp != NULL) { return fp; } else { return newfp; } }